Im trying to understand an issue i am facing with ThreeJS. I have a simple cube on a page and i have a PNG of a white question mark, with the rest of the image transparent. When i add the texture to the cube;
cubeGeometry = new THREE.BoxGeometry( 5, 5, 5 );
cubeMaterial = new THREE.MeshStandardMaterial( { color: this.cubeColor, flatShading: true, map:new THREE.TextureLoader().load( require("./question-mark.png") ) } );
cubeMesh = new THREE.Mesh( cubeGeometry, cubeMaterial );
The transparent pixels in the PNG appear black no matter what i do. Whats even stranger is the white pixels of the PNG appear the color of this.cubeColor - which is set to orange.
I would expect the cube color to be orange with the transparent pixels of the PNG taking that color and the white pixels in the PNG to remain white.
Anyone shed some light ?
what I see
the texture
You can apply 2 or more materials like this
const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);
// prepare geometry to use 2 materials
geometry.clearGroups();
geometry.addGroup( 0, Infinity, 0 );
geometry.addGroup( 0, Infinity, 1 );
const loader = new THREE.TextureLoader();
// create 2 materials
const material0 = new THREE.MeshBasicMaterial({
color: 'orange',
});
const material1 = new THREE.MeshBasicMaterial({
map: loader.load('https://i.imgur.com/iFom4eT.png'),
transparent: true,
});
// apply 2 materials to geometry
const materials = [material0, material1];
const cube = new THREE.Mesh(geometry, materials);
Example, note it uses BoxBufferGeometry not BoxGeometry
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
scene.background = new THREE.Color('purple');
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);
// prepare geometry to use 2 materials
geometry.clearGroups();
geometry.addGroup( 0, Infinity, 0 );
geometry.addGroup( 0, Infinity, 1 );
const loader = new THREE.TextureLoader();
// create 2 materials
const material0 = new THREE.MeshBasicMaterial({
color: 'orange',
});
const material1 = new THREE.MeshBasicMaterial({
map: loader.load('https://i.imgur.com/hrzoDMj.png'),
transparent: true,
});
// apply 2 materials to geometry
const materials = [material0, material1];
const cube = new THREE.Mesh(geometry, materials);
scene.add(cube);
function render(time) {
time *= 0.001; // convert time to seconds
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
cube.rotation.x = time;
cube.rotation.y = time;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
main();
body { margin: 0; }
#c { width: 100vw; height: 100vh; display: block; }
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.min.js"></script>
<canvas id="c"></canvas>
note that if you put a color and a texture (map) in a single material the 2 colors, (1) the color you specified and (2) the color from the texture, are multiplied with each other. So, wherever the texture has zeros, RGBA = 0,0,0,0 then the result is 0,0,0,0 as anything multiplied by zero is zero. This is why you got the results you got originally where outside the question mark is transparent and inside is orange.
Knowing this you can use your white question mark and change its color by setting the color on the material that is using the texture.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
scene.background = new THREE.Color('purple');
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxBufferGeometry(boxWidth, boxHeight, boxDepth);
// prepare geometry to use 2 materials
geometry.clearGroups();
geometry.addGroup( 0, Infinity, 0 );
geometry.addGroup( 0, Infinity, 1 );
const loader = new THREE.TextureLoader();
// create 2 materials
const material0 = new THREE.MeshBasicMaterial({
color: 'orange',
});
const material1 = new THREE.MeshBasicMaterial({
map: loader.load('https://i.imgur.com/hrzoDMj.png'),
transparent: true,
});
// apply 2 materials to geometry
const materials = [material0, material1];
const cube = new THREE.Mesh(geometry, materials);
scene.add(cube);
function render(time) {
time *= 0.001; // convert time to seconds
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
material1.color.setHSL(time, 1, 0.5);
cube.rotation.x = time;
cube.rotation.y = time;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
main();
body { margin: 0; }
#c { width: 100vw; height: 100vh; display: block; }
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.min.js"></script>
<canvas id="c"></canvas>
Related
I am using Three.js to develop a cube that translates and rotate in 3D space using data from accelerometer and gyroscope data.
So far I have one canvas that shows the accelerometer movement. Now I need to have another canvas that shows the gyroscope data on a separate canvas, I prefer to have two JS code for each canvas, so they are independent of each other. I wasn't able to make two separate canvases, and I don't know if it is even possible to use two different javascript codes in the html.
Below is how my HTML is structured:
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas id="canvas" width="500" height="800" style="border:1px solid #eb1c1c;"></canvas>
<div id="info">
<div>t = <span id="time">0</span> s</div>
<div>accX = <span id="accX">0</span></div>
<div>accY = <span id="accY">0</span></div>
<div>accZ = <span id="accZ">0</span></div>
</div>
</body>
</html>
and this is the javascript:
import * as THREE from "three";
import data from "../data/data.json"
import "./style.css"
var width = window.innerWidth;
var height = window.innerHeight;
const canvas = document.querySelector('#canvas');
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize (width, height);
var planeSize = 100000
const fov = 70;
const aspect = 2; // the canvas default
const near = 20;
const far = 500;
const camera = new THREE.PerspectiveCamera(70, width/height, 1, 10000);
camera.position.y = 3;
camera.position.z = 30;
camera.lookAt (new THREE.Vector3(0,0,0));
// camera.position.set(0, 40, 1.5);
// camera.up.set(0, 10, 1);
// camera.lookAt(0, 10, 0);
const scene = new THREE.Scene();
{
const color = 0x00afaf;
const intensity = 10;
const size = 10;
const divisions = 10;
const gridHelper = new THREE.GridHelper( planeSize, 5000 );
gridHelper.setColors( new THREE.Color(0xff0000), new THREE.Color(0xffffff) );
scene.add(gridHelper);
const light = new THREE.PointLight(color, intensity);
scene.add(light);
// scene.add( gridHelper );
}
// // label the axis
// var textGeo = new THREE.TextGeometry('Y', {
// size: 5,
// height: 2,
// curveSegments: 6,
// font: "helvetiker",
// style: "normal"
// });
// var color = new THREE.Color();
// color.setRGB(255, 250, 250);
// var textMaterial = new THREE.MeshBasicMaterial({ color: color });
// var text = new THREE.Mesh(textGeo , textMaterial);
// text.position.x = axis.geometry.vertices[1].x;
// text.position.y = axis.geometry.vertices[1].y;
// text.position.z = axis.geometry.vertices[1].z;
// text.rotation = camera.rotation;
// scene.add(text);
const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshBasicMaterial({ color: "green", wireframe: false });
const object = new THREE.Mesh(boxGeometry, boxMaterial);
var cubeAxis = new THREE.AxesHelper(3);
object.add(cubeAxis);
object.scale.set(5, 5, 5)
scene.add(object);
scene.background = new THREE.Color(0.22, 0.23, 0.22);
let currentIndex = 0
let time = data[currentIndex].time
let velocity = new THREE.Vector3()
requestAnimationFrame(render);
function render(dt) {
dt *= 0.0001 // in seconds
time += dt
document.querySelector("#time").textContent = time.toFixed(2)
// Find datapoint matching current time
while (data[currentIndex].time < time) {
currentIndex++
if (currentIndex >= data.length) return
}
const { rotX, rotY, rotZ, accX, accY, accZ } = data[currentIndex]
document.querySelector("#accX").textContent = accX* 10;
document.querySelector("#accY").textContent = accY* 10;
document.querySelector("#accZ").textContent = accZ* 10;
const acceleration = new THREE.Vector3()
// object.rotation.set( rotX, rotY, rotZ)
object.position.x = accX * 30;
// object.position.add(velocity.clone().multiplyScalar(dt)).add(acceleration.clone().multiplyScalar(50 * dt ** 2))
// object.position.add(accZ)
// velocity.add(acceleration.clone().multiplyScalar(dt))
var relativeCameraOffset = new THREE.Vector3 (0,10,10);
var cameraOffset = relativeCameraOffset.applyMatrix4( object.matrixWorld );
camera.position.x = cameraOffset.x;
camera.position.y = cameraOffset.y;
camera.position.z = cameraOffset.z;
camera.lookAt( object.position );
resizeToClient();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function resizeToClient() {
const needResize = resizeRendererToDisplaySize()
if (needResize) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
}
function resizeRendererToDisplaySize() {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
this is how I want the canvas to look like:
Good news is it is possible, you just have to put all your code(at least animations) in separate functions :
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const scene1 = new THREE.Scene();
const camera1 = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
scene.background = new THREE.Color( 0xf0000f);
scene1.background = new THREE.Color( 0x0000ff );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
const renderer1 = new THREE.WebGLRenderer();
renderer1.setSize( window.innerWidth, window.innerHeight );
renderer1.domElement.width=500;
renderer1.domElement.height=300;
renderer1.domElement.style.position="absolute";
renderer1.domElement.style.top=0;
renderer1.domElement.style.height=150+"px";
renderer1.domElement.style.width=200+"px";
renderer.domElement.width=500;
renderer.domElement.height=300;
renderer.domElement.style.position="absolute";
renderer.domElement.style.top=0;
document.body.appendChild( renderer.domElement );
document.body.appendChild( renderer1.domElement );
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
const geometry1 = new THREE.BoxGeometry(3,3,3);
const material1 = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube1 = new THREE.Mesh( geometry1, material1 );
scene1.add( cube1 );
camera.position.z = 5;
camera1.position.z = 5;
const animate = function () {
requestAnimationFrame( animate );
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render( scene, camera );
};
const animate1 = function () {
requestAnimationFrame( animate1 );
cube1.rotation.x += 0.01;
cube1.rotation.y += 0.01;
renderer1.render( scene1, camera1 );
};
animate();
animate1();
<script src="http://threejs.org/build/three.min.js"></script>
I've made this table mesh which i would like to cast shadows on the pane(floor) mesh. I've been trying playing around with the ShadowMap for quite some while now but i cant seem to get it to work.
This is the current look:
camera & scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(35, (window.innerWidth / window.innerHeight), 0.1, 1000);
camera.position.z = 8;
camera.position.y = 2;
camera.lookAt( scene.position );
The table mesh
const boxWidth = 1; const boxHeight = 0.1; const boxDepth = 1;
const tableBoardGeometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
const textureLoader = new THREE.TextureLoader();
const customPicture = textureLoader.load('https://threejsfundamentals.org/threejs/lessons/resources/images/compressed-but-large-wood-texture.jpg')
const tableBoardMaterial = new THREE.MeshLambertMaterial({map: customPicture, wireframe: false})
const tableBoard = new THREE.Mesh(tableBoardGeometry, tableBoardMaterial)
tableBoard.position.set(0, 0, 0)
tableBoard.castShadow = true;
The lightning & shadow
const lightAndShadow = () => {
const ambientLight = new THREE.AmbientLight(0xffffff, 0.75);
const lightIntensity = 1.3; const lightDistance = 18;
const light = new THREE.PointLight(0xffffff, lightIntensity, lightDistance);
light.position.set(1, 1, 1)
light.castShadow = true
light.target = tableBoard;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFShadowMap;
light.shadow = new THREE.LightShadow(new THREE.PerspectiveCamera(100, 1, 500, 1000))
light.shadow.bias = 0.0001
light.shadow.mapSize.width = 2048*2
light.shadow.mapSize.height = 2048*2
scene.add(light, ambientLight);
}
The floor mesh
const floorWidh = 20; const floorHeight = 20; const floorWidthSegments = 50; const floorHeightSegments = 50;
const floorGeometry = new THREE.PlaneGeometry( floorWidh, floorHeight, floorWidthSegments, floorHeightSegments );
floorGeometry.rotateX( - Math.PI / 2 );
const floorTexture = new THREE.TextureLoader().load('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQOOcWSD0K2mPwokAFfZIhq5Xl49bh8B17RlU6NqCGa4UOKydgX');
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(20, 20);
const floorMaterial = new THREE.MeshBasicMaterial({color: 0xFFFFFF, wireframe: false}),
floor = new THREE.Mesh( floorGeometry, floorMaterial );
floor.position.set(0, -tableLegHeightPosition, 0)
floor.receiveShadow = true;
Any suggestions on how i can get the shadows to work would be much appreciated.
light.shadow = new THREE.LightShadow(new THREE.PerspectiveCamera(100, 1, 500, 1000))
You are overriding the default camera parameters of LightShadow and defining the Near Clipping plane to 500 which is making the tableBoard shadows to disappear.
You can reduce this number to the default of 0.5 or something near to it. Having the near clipping plane as 500 is a very big number for your case.
From the Docs:
.shadow : LightShadow
A LightShadow used to calculate shadows for this light.
The lightShadow's camera is set to a PerspectiveCamera with fov of 90, aspect of 1, near clipping plane at 0.5 and far clipping plane at 500.
Refer Three.js complete doc for Point light shadows here
i m following this guide for the light Light in Threejs
and i already add some light in my scene.
Now i m try to add a light on the character of my game, but still dont work.
i use the same code of the guide changing just the position.set
const color = 0xFFFFFF;
const intensity = 1;
const light2 = new THREE.SpotLight(color, intensity,0,Math.PI/3);
light2.position.set(100,-5000,1000);
light2.target = lightTarget;
light2.castShadow = true;
const helper2 = new THREE.SpotLightHelper(light2);
and after i add in this way to my character
self.flame.add( helper2 );
self.flame.add(lightTarget);
self.flame.add(light2);
I added a helper too, but if I use just the helper in the scene , so if comment
self.flame.add(light2)
I see the position of the light in perfect way, when add the light the helper disappear (in other light don't happened) and the light go as her want.
Someone can help me?
The helpers have to be parented to the scene (or at least the SpotLightHelper does). You may or may not want to parent the target to the scene.
You also need to call helper.update for each helper
'use strict';
/* global THREE, dat */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 10, 20);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
{
const planeSize = 40;
const loader = new THREE.TextureLoader();
const texture = loader.load('https://threejsfundamentals.org/threejs/resources/images/checker.png');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const repeats = planeSize / 2;
texture.repeat.set(repeats, repeats);
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
}
const cubes = [];
let parent = scene;
{
const cubeSize = 1;
const cubeGeo = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
const cubeMat = new THREE.MeshPhongMaterial({
color: '#8AC',
emissive: '#333',
});
for (let i = 0; i < 6; ++i) {
const mesh = new THREE.Mesh(cubeGeo, cubeMat);
mesh.position.set(1, 0, 0);
parent.add(mesh);
cubes.push(mesh);
parent = mesh;
}
}
cubes[0].position.set(-3, 7, 0);
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.SpotLight(color, intensity);
light.position.set(0, 0, 0);
light.target.position.set(0, -1, 0);
parent.add(light);
parent.add(light.target);
//scene.add(light.target);
const helper = new THREE.SpotLightHelper(light);
scene.add(helper);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
for (const cube of cubes) {
cube.rotation.z = Math.sin(time) * .4;
}
light.angle = THREE.Math.lerp(
THREE.Math.degToRad(20),
THREE.Math.degToRad(80),
Math.sin(time * 0.77) * 0.5 + 0.5);
helper.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
html, body {
margin: 0;
height: 100%;
}
#c {
width: 100%;
height: 100%;
display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r105/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r105/js/controls/OrbitControls.js"></script>
I'm trying to raycast skinning mesh (of knowing issue) after some skeleton changes (without animation on it, so performance isn't a priority).
The tricky thing i imagine in this attempt is:
Load skinned mesh add to scene
Make some changes in positions of specific bones at loaded mesh
Copy geometries of transformed loaded mesh (maybe from buffer?)
Create new mesh (some kind of imitation ghost mesh) from copied geometries and apply to it
set raycast on ghost mesh with opacity material= 0.0
Above list should work, but I'm stuck third day on point 3 cause I can't get transformed vertices after skinning.
var scene, camera, renderer, mesh, ghostMesh;
var raycaster = new THREE.Raycaster();
var raycasterMeshHelper;
initScene();
render();
function initScene() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 200);
camera.position.set(20, 7, 3);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
var orbit = new THREE.OrbitControls(camera, renderer.domElement);
//lights stuff
var ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);
var lights = [];
lights[0] = new THREE.PointLight(0xffffff, 1, 0);
lights[1] = new THREE.PointLight(0xffffff, 1, 0);
lights[2] = new THREE.PointLight(0xffffff, 1, 0);
lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);
scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);
//raycaster mesh
var raycasterMaterial = new THREE.MeshBasicMaterial({
color: 0xdddddd,
opacity: 0.7,
transparent: true
});
var geometrySphere = new THREE.SphereGeometry(0.5, 16, 16);
raycasterMeshHelper = new THREE.Mesh(geometrySphere, raycasterMaterial);
raycasterMeshHelper.visible = false;
scene.add(raycasterMeshHelper);
renderer.domElement.addEventListener('mousemove', onMouseMove, false);
//model Loading
var loader = new THREE.JSONLoader();
loader.load("https://raw.githubusercontent.com/visus100/skinnedTests/master/js_fiddle/skinned_mesh.json", function(geometry) {
var meshMaterial = new THREE.MeshStandardMaterial({
color: 0x00df15,
skinning: true
});
mesh = new THREE.SkinnedMesh(geometry, meshMaterial);
scene.add(mesh);
var skeleton = new THREE.SkeletonHelper(mesh);
scene.add(skeleton);
//some experimental skeletonal changes
mesh.skeleton.bones[1].rotation.z += 0.10;
mesh.skeleton.bones[2].rotation.x += -0.65;
mesh.skeleton.bones[3].rotation.y += -0.45;
mesh.skeleton.bones[3].position.x += 0.11;
//updates matrix
mesh.updateMatrix();
mesh.geometry.applyMatrix(mesh.matrix);
mesh.updateMatrixWorld(true);
//crate ghost mesh geometry
createGhostMesh();
//crate point cloud helper from buffergeometry
var bufferGeometry = new THREE.BufferGeometry().fromGeometry(mesh.geometry);
var particesMaterial = new THREE.PointsMaterial({
color: 0xff00ea,
size: 0.07,
sizeAttenuation: false
});
particles = new THREE.Points(bufferGeometry, particesMaterial);
particles.sortParticles = true;
scene.add(particles);
});
}
function createGhostMesh() {
var geometryForGhostMesh = new THREE.Geometry();
//push vertices and other stuff to geometry
for (i = 0; i < mesh.geometry.vertices.length; i++) {
var temp = new THREE.Vector3(mesh.geometry.vertices[i].x, mesh.geometry.vertices[i].y, mesh.geometry.vertices[i].z);
geometryForGhostMesh.vertices.push(temp);
//////
//here should be the code for calc translation vertices of skinned mesh and added to geometryForGhostMesh
//////
geometryForGhostMesh.skinIndices.push(mesh.geometry.skinIndices[i]);
geometryForGhostMesh.skinWeights.push(mesh.geometry.skinWeights[i]);
}
for (i = 0; i < mesh.geometry.faces.length; i++) {
geometryForGhostMesh.faces.push(mesh.geometry.faces[i]);
}
//create material and add to scene
var ghostMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000,
opacity: 0.1,
transparent: true,
skinning: true
});
ghostMesh = new THREE.Mesh(geometryForGhostMesh, ghostMaterial);
scene.add(ghostMesh);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
function onMouseMove(event) {
//raycaster for ghostMesh
if (ghostMesh) {
var rect = renderer.domElement.getBoundingClientRect();
var mouseX = ((event.clientX - rect.left) / rect.width) * 2 - 1;
var mouseY = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(new THREE.Vector2(mouseX, mouseY), camera);
var intersects = raycaster.intersectObject(ghostMesh);
if (intersects.length > 0) {
raycasterMeshHelper.visible = true;
raycasterMeshHelper.position.set(0, 0, 0);
raycasterMeshHelper.lookAt(intersects[0].face.normal);
raycasterMeshHelper.position.copy(intersects[0].point);
} else {
raycasterMeshHelper.visible = false;
}
}
}
body {
margin: 0px;
background-color: #000000;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
Please note that I need this in thre.js build r98 or less, because the rest of my code (not included here) and without morph tangents only skinning bones.
I tried to write it clearly and please if anyone want help do it so because I'm not a pro.
I not including my approach of calculating transformed geometries because I failed too hard.
I dug a lot about this problem here e.g. issue6440 and for today it's still not fixed.
But there existing methods to work with it e.g https://jsfiddle.net/fnjkeg9x/1/ but after several of attempts I failed and my conclusion is that the stormtrooper works on morph tanges and this could be the reason I failed.
EDIT:
I created next codepen based on this topics get-the-global-position-of-a-vertex-of-a-skinned-mesh and Stormtrooper.
Decided to start with simple box to make bounding around skinned transformed mesh.
Result is fail because it giving 0 at line:
boneMatrix.fromArray(skeleton.boneMatrices, si * 16);
Here i comparing stormtrooper with my example output from console: Screen shot image
Codpen with new progress: https://codepen.io/donkeyLuck0/pen/XQbBMQ
My other idea is to apply this bones form loaded model and rig as a morph tangent programmatically (but i don't even know if it is possible and how to figure it out)
Founded example of animated model
Sketchfab animation with points tracking
This is super late to the game, but here's an example of GPU picking that works with skinned meshes and doesn't require a separate picking scene to keep in sync with your main scene, nor does it require the user to manage custom materials:
https://github.com/bzztbomb/three_js_gpu_picking
The trick that allows for easy material overriding and scene re-use is here:
https://github.com/bzztbomb/three_js_gpu_picking/blob/master/gpupicker.js#L58
A proper support for raycasting for skinned meshes was added in https://github.com/mrdoob/three.js/pull/19178 in revision 116.
You can use GPU picking to "pick" skinned object. It won't give you a position though
Note: GPU picking requires rendering every pickable object with a custom material. How you implement that is up to you. This article does it by making 2 scenes. That might not be as useful for skinned objects.
Unfortunately three.js provides no way to override materials AFAICT. Here's an example that replaces the materials on the pickable objects before rendering for picking and then restores them after. You would also need to hide any objects you don't want picked.
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: document.querySelector('canvas'),
});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 200);
camera.position.set(20, 7, 3);
const orbit = new THREE.OrbitControls(camera, renderer.domElement);
//lights stuff
const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);
const lights = [];
lights[0] = new THREE.PointLight(0xffffff, 1, 0);
lights[1] = new THREE.PointLight(0xffffff, 1, 0);
lights[2] = new THREE.PointLight(0xffffff, 1, 0);
lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);
scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);
//raycaster mesh
const raycasterMaterial = new THREE.MeshBasicMaterial({
color: 0xdddddd,
opacity: 0.7,
transparent: true
});
const geometrySphere = new THREE.SphereGeometry(0.5, 16, 16);
raycasterMeshHelper = new THREE.Mesh(geometrySphere, raycasterMaterial);
raycasterMeshHelper.visible = false;
scene.add(raycasterMeshHelper);
//model Loading
const pickableObjects = [];
const loader = new THREE.JSONLoader();
loader.load("https://raw.githubusercontent.com/visus100/skinnedTests/master/js_fiddle/skinned_mesh.json", (geometry) => {
const meshMaterial = new THREE.MeshStandardMaterial({
color: 0x00df15,
skinning: true
});
const mesh = new THREE.SkinnedMesh(geometry, meshMaterial);
scene.add(mesh);
const id = pickableObjects.length + 1;
pickableObjects.push({
mesh,
renderingMaterial: meshMaterial,
pickingMaterial: new THREE.MeshPhongMaterial({
skinning: true,
emissive: new THREE.Color(id),
color: new THREE.Color(0, 0, 0),
specular: new THREE.Color(0, 0, 0),
//map: texture,
//transparent: true,
//side: THREE.DoubleSide,
//alphaTest: 0.5,
blending: THREE.NoBlending,
}),
});
//some experimental skeletonal changes
mesh.skeleton.bones[1].rotation.z += 0.10;
mesh.skeleton.bones[2].rotation.x += -0.65;
mesh.skeleton.bones[3].rotation.y += -0.45;
mesh.skeleton.bones[3].position.x += 0.11;
//updates matrix
mesh.updateMatrix();
mesh.geometry.applyMatrix(mesh.matrix);
mesh.updateMatrixWorld(true);
});
class GPUPickHelper {
constructor() {
// create a 1x1 pixel render target
this.pickingTexture = new THREE.WebGLRenderTarget(1, 1);
this.pixelBuffer = new Uint8Array(4);
}
pick(cssPosition, scene, camera) {
const {
pickingTexture,
pixelBuffer
} = this;
// set the view offset to represent just a single pixel under the mouse
const pixelRatio = renderer.getPixelRatio();
camera.setViewOffset(
renderer.context.drawingBufferWidth, // full width
renderer.context.drawingBufferHeight, // full top
cssPosition.x * pixelRatio | 0, // rect x
cssPosition.y * pixelRatio | 0, // rect y
1, // rect width
1, // rect height
);
// render the scene
// r102
//renderer.setRenderTarget(pickingTexture);
//renderer.render(scene, camera);
//renderer.setRenderTarget(null);
// r98
renderer.render(scene, camera, pickingTexture);
// clear the view offset so rendering returns to normal
camera.clearViewOffset();
//read the pixel
renderer.readRenderTargetPixels(
pickingTexture,
0, // x
0, // y
1, // width
1, // height
pixelBuffer);
const id =
(pixelBuffer[0] << 16) |
(pixelBuffer[1] << 8) |
(pixelBuffer[2]);
return id;
}
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
const pickPosition = {
x: 0,
y: 0,
};
const pickHelper = new GPUPickHelper();
let lastPickedId = 0;
let lastPickedObjectSavedEmissive;
function render(time) {
time *= 0.001; // convert to seconds;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
if (lastPickedId) {
pickableObjects[lastPickedId - 1].renderingMaterial.emissive.setHex(lastPickedObjectSavedEmissive);
lastPickedId = 0;
}
for (pickableObject of pickableObjects) {
pickableObject.mesh.material = pickableObject.pickingMaterial;
}
const id = pickHelper.pick(pickPosition, scene, camera, time);
for (pickableObject of pickableObjects) {
pickableObject.mesh.material = pickableObject.renderingMaterial;
}
const pickedObject = pickableObjects[id - 1];
if (pickedObject) {
lastPickedId = id;
lastPickedObjectSavedEmissive = pickedObject.renderingMaterial.emissive.getHex();
pickedObject.renderingMaterial.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
}
renderer.render(scene, camera);
requestAnimationFrame(render);
};
requestAnimationFrame(render);
function setPickPosition(event) {
pickPosition.x = event.clientX;
pickPosition.y = event.clientY;
}
function clearPickPosition() {
// unlike the mouse which always has a position
// if the user stops touching the screen we want
// to stop picking. For now we just pick a value
// unlikely to pick something
pickPosition.x = -100000;
pickPosition.y = -100000;
}
window.addEventListener('mousemove', setPickPosition);
window.addEventListener('mouseout', clearPickPosition);
window.addEventListener('mouseleave', clearPickPosition);
window.addEventListener('touchstart', (event) => {
// prevent the window from scrolling
event.preventDefault();
setPickPosition(event.touches[0]);
}, {
passive: false
});
window.addEventListener('touchmove', (event) => {
setPickPosition(event.touches[0]);
});
window.addEventListener('touchend', clearPickPosition);
window.addEventListener('mousemove', setPickPosition);
window.addEventListener('mouseout', clearPickPosition);
window.addEventListener('mouseleave', clearPickPosition);
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/js/controls/OrbitControls.js"></script>
<canvas></canvas>
QUESTION:
So I came across this:
https://skin-tracker.com/pubg/outfit?no=000000000000000000000000000&set=1&char=1
I have the central ground area which is textured already in my code. But I have no idea how to make it extend up to the horizon like seen in the link.
It seems the person who coded what you see in the link hit some kind of limitation and had to use a single color for what extends beyond the central floor area.
How can I make my floor extend to the horizon/ create a skyline ?
CODE:
var floorTexture = new THREE.ImageUtils.loadTexture( '../../public/assets/grassTile.jpg' );
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set( 10, 10 );
var floorMaterial = new THREE.MeshBasicMaterial({ map: floorTexture, side: THREE.DoubleSide });
var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
var mesh = new THREE.Mesh(floorGeometry, floorMaterial);
mesh.rotation.x = - Math.PI / 2;
mesh.receiveShadow = true;
scene.add( mesh );
You can simply create an enormous textured plane. I found no limitations with the example below. If you implement something like this and encounter errors/problems, update your question with the errors you're seeing.
// prepare the renderer
let WIDTH
let HEIGHT
let aspectRatio = function() {
return WIDTH / HEIGHT
}
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
document.body.appendChild(renderer.domElement)
const camera = new THREE.PerspectiveCamera(32, aspectRatio(), 1, 1000)
camera.position.set(0, 10, 50)
function resize() {
WIDTH = window.innerWidth
HEIGHT = window.innerHeight
renderer.setSize(WIDTH, HEIGHT)
camera.aspect = aspectRatio()
camera.updateProjectionMatrix()
}
resize()
window.addEventListener("resize", resize)
const scene = new THREE.Scene()
const light = new THREE.DirectionalLight(0xffffff, 1, Infinity)
light.position.set(0, 0, 1)
camera.add(light)
scene.add(camera)
const sun = new THREE.DirectionalLight(0xffffcc)
sun.position.set(0, 1, 0)
scene.add(sun)
// populate the scene
let geo = new THREE.BoxBufferGeometry(10, 10, 10)
let mat = new THREE.MeshLambertMaterial({
color: "red"
})
let mesh = new THREE.Mesh(geo, mat)
scene.add(mesh)
let tex = new THREE.TextureLoader().load("https://upload.wikimedia.org/wikipedia/commons/4/4c/Grass_Texture.png")
tex.anisotropy = 32
tex.repeat.set(100, 100)
tex.wrapT = THREE.RepeatWrapping
tex.wrapS = THREE.RepeatWrapping
geo = new THREE.PlaneBufferGeometry(10000, 10000)
mat = new THREE.MeshLambertMaterial({
map: tex
})
mesh = new THREE.Mesh(geo, mat)
mesh.position.set(0, -5, 0)
mesh.rotation.set(Math.PI / -2, 0, 0)
scene.add(mesh)
let axis = new THREE.Vector3(0, 1, 0)
function updateCamera() {
camera.position.applyAxisAngle(axis, 0.01)
}
// rendering functions
function render() {
renderer.render(scene, camera)
camera.lookAt(scene.position)
}
let animationLoopId = null
function animationLoop() {
animationLoopId = requestAnimationFrame(animationLoop)
updateCamera()
render()
}
animationLoop()
html,
body {
padding: 0;
margin: 0;
overflow: hidden;
background: skyBLue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.js"></script>