Goal
I am trying to create an EllipseCurve (https://threejs.org/docs/#api/en/extras/curves/EllipseCurve) on which a camera should move.
What I did to achive the goal?
This is the code for the ellipse so far.
var curve = new THREE.EllipseCurve(
0,0,
1, 1,
0, 2 * Math.PI,
false,
1.57
)
const points = curve.getPoints( 50 );
const geometry = new THREE.BufferGeometry().setFromPoints( points );
var material = new THREE.LineBasicMaterial( { color : 0xffffff } );
// Create the final object to add to the scene
var curveObject = new THREE.Line( geometry, material );
scene.add(curveObject);
I can see it in the scene like this:
The Problem
I tried to rotate the Ellipse Curve 90 degree arround the x axis clockwise. As I understood from the documentation, the last parameter of the defining function should rotate it.
const curve = new THREE.EllipseCurve(
0, 0, // ax, aY
10, 10, // xRadius, yRadius
0, 2 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0 // aRotation
);
Thank you in advance for you answer. I am quite new to Three.js so sorry if this question might be stupid :D
Get a point on the curve and apply a matrix4 to it.
Here is a concept of how you can do it (see the lines with cam in the animation loop, better to watch with "Full page"):
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.134.0";
import {
OrbitControls
} from "https://cdn.skypack.dev/three#0.134.0/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(-10, 10, 10);
let renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.autoClear = false;
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
})
let controls = new OrbitControls(camera, renderer.domElement);
let light = new THREE.DirectionalLight(0xffffff, 1);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));
let grid = new THREE.GridHelper();
grid.position.y = -5;
scene.add(grid);
let obj = new THREE.Mesh(new THREE.IcosahedronGeometry(1, 0), new THREE.MeshLambertMaterial({
color: "aqua"
}));
scene.add(obj);
let curve = new THREE.EllipseCurve(0, 0, 10, 5);
let line = new THREE.Line(new THREE.BufferGeometry().setFromPoints(curve.getSpacedPoints(100)), new THREE.LineBasicMaterial({
color: "yellow"
}));
line.rotation.x = -Math.PI * 0.25;
line.rotation.z = Math.PI * 0.125;
line.position.x = 5;
scene.add(line);
let cam = new THREE.PerspectiveCamera(25, 1, 1.5, 25);
let camHelper = new THREE.CameraHelper(cam);
scene.add(camHelper);
let clock = new THREE.Clock();
let v = new THREE.Vector3();
renderer.setAnimationLoop(() => {
let t = (clock.getElapsedTime() * 0.05) % 1;
// magic is in these lines //////////////////
cam.position.copy(curve.getPointAt(t, v));
cam.position.applyMatrix4(line.matrixWorld);
cam.lookAt(obj.position);
/////////////////////////////////////////////
renderer.clear();
renderer.setViewport(0, 0, innerWidth, innerHeight);
renderer.render(scene, camera);
renderer.setViewport(0, innerHeight - 256, 256, 256);
renderer.render(scene, cam);
})
</script>
the aRotation, angle-rotation, will affect the local origin of other angle settings for this curve. It is not the overall ellipse rotation, but the orientation of any offset relative to default. A different starting point. It would turn a Pac-Man mouth into a backwards Pac-Man mouth at +/- 180-degrees. To rotate the overall curve in world-space, use one of the various methods available, such as curve.rotation.set(0,1,0) or rotation.y += 1. Please refer to documentation for specific variants of rotation.
Related
I've created a Ring and would like to have only half of it. And after that animate it, that it builds itself up from 0 to half.
var geometry = new THREE.RingGeometry(10, 9, 32);
var material = new THREE.MeshBasicMaterial({
color: 0xffff00,
side: THREE.DoubleSide,
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
How can I archive it? I'm new to three.js.
Use thetaStart and thetaLength to animate the half-ring.
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three#0.118.3/build/three.module.js";
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var grid = new THREE.GridHelper(10, 10);
grid.rotation.x = Math.PI * 0.5;
scene.add(grid);
var innerRadius = 1;
var outerRadius = 2;
// re-building geometry
var usualRingGeom = new THREE.RingBufferGeometry(innerRadius, outerRadius, 32, 1, 0, 1);
var usualRingMat = new THREE.MeshBasicMaterial({color: 0xffff00});
var usualRing = new THREE.Mesh(usualRingGeom, usualRingMat);
scene.add(usualRing);
var clock = new THREE.Clock();
renderer.setAnimationLoop(()=>{
let t = clock.getElapsedTime();
// re-building geometry
usualRingGeom = new THREE.RingBufferGeometry(innerRadius, outerRadius, 32, 1, 0, (Math.sin(t) * 0.5 + 0.5) * Math.PI);
usualRing.geometry.dispose();
usualRing.geometry = usualRingGeom;
renderer.render(scene, camera);
});
</script>
PS You also can achieve the same result without re-building a geometry. For that, you can bend a plane in js (changing vertices) or in shaders :)
I would like to set the global position of an object, so I wrote a function moveToPoint which takes in a world point x y z, converts it into the local coordinate system, and updates the object.position to go to that new local coordinate.
But, when I call this function in the animation loop, I get an unusual jittering artifact. I'm not quite sure why this could happen since the code is deceptively simple.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(0, 0, 0);
scene.add(sphere)
camera.position.z = 5;
function moveToPosition(object, x, y, z) {
const pos = new THREE.Vector3(x, y, z);
object.worldToLocal(pos);
object.position.copy(pos);
}
function animate() {
requestAnimationFrame(animate);
moveToPosition(sphere, 0, 1, 0);
renderer.render(scene, camera);
};
animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
Thanks for any assistance.
You're basically toggling the Y-position from 0 to 1 to 0 to 1 to 0, etc...
Let's take it frame-by-frame
Frame 1:
starting sphere position: 0, 0, 0
worldToLocal() sets pos to 0, 1, 0
ending sphere position: 0, 1, 0
Frame 2:
starting sphere position: 0, 1, 0
worldToLocal() sets pos to 0, 0, 0
ending sphere position: 0, 0, 0
... and so on
Another way to look at it:
Frame1: [0, 0, 0].worldToLocal([0, 1, 0]) = [0, 1, 0]
Frame2: [0, 1, 0].worldToLocal([0, 1, 0]) = [0, 0, 0]
Solution:
Since the sphere isn't nested within any other objects, you can skip the worldToLocal() call. You typically only need this when you're dealing with nested objects, not when they're directly added to the scene.
I created a simple sin-wave animation from -1 to +1 to demonstrate:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(0, 0, 0);
scene.add(sphere)
var axesHelper = new THREE.AxesHelper( 2 );
scene.add( axesHelper );
camera.position.z = 5;
function moveToPosition(object, x, y, z) {
const pos = new THREE.Vector3(x, y, z);
//object.worldToLocal(pos);
object.position.copy(pos);
}
function animate(t) {
requestAnimationFrame(animate);
// Sin movement from -1 to +1
const yPos = Math.sin(t / 1000);
moveToPosition(sphere, 0, yPos, 0);
renderer.render(scene, camera);
};
animate(0);
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
I have a simple 3d cube (BoxGeometry of 100, 100, 100) and I am trying to rotate it. If we call all 100x100x100 a tile - when I rotate it I can see it's overlapping the below tile.
(by changing color, now I totally understand the behaviour).
tl.to(this.cube4.rotation, 0.5, {z: -45* Math.PI/180});
[
What if I want to rotate it based on an anchor point of right bottom? So instead of overflowing inside the below tile, it will overflow that portion to above tile.
So it will look like the green example and not the red example:
The red example here is achieved by
tl.to(this.cube4.rotation, 0.5, {z: -45* Math.PI/180});
tl.to(this.cube4.position, 0.5, {x: 50 }, 0.5);
I am very new to three.js so if any terminology is wrong, please warn me
Add the ("red") cube to a THREE.Group, in that way that the rotation axis (the edge) is in the origin of the group. This means the cube has to be shifted by the half side length.
If you rotate the group object, then the cube (which is inside the group) will rotate around the edge and not around its center.
e.g.
var bbox = new THREE.Box3().setFromObject(cube);
cube.position.set(bbox.min.x, bbox.max.y, 0);
var pivot = new THREE.Group();
pivot.add(cube);
scene.add(pivot);
See also the answer to How to center a group of objects?, which uses this solution to rotate a group of objects.
(function onLoad() {
var camera, scene, renderer, orbitControls, pivot;
var rot = 0.02;
init();
animate();
function init() {
container = document.getElementById('container');
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 100);
camera.position.set(4, 1, 2);
//camera.lookAt( -1, 0, 0 );
loader = new THREE.TextureLoader();
loader.setCrossOrigin("");
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
scene.add(camera);
window.onresize = function() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
orbitControls = new THREE.OrbitControls(camera, container);
var ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set(1,2,-1.5);
scene.add( directionalLight );
addGridHelper();
createModel();
}
function createModel() {
var material = new THREE.MeshPhongMaterial({color:'#80f080'});
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var cube1 = new THREE.Mesh(geometry, material);
cube1.position.set(0,-0.5,-0.5);
var cube2 = new THREE.Mesh(geometry, material);
cube2.position.set(0,0.5,-0.5);
var cube3 = new THREE.Mesh(geometry, material);
cube3.position.set(0,-0.5,0.5);
var material2 = new THREE.MeshPhongMaterial({color:'#f08080'});
var cube4 = new THREE.Mesh(geometry, material2);
var bbox = new THREE.Box3().setFromObject(cube4);
cube4.position.set(bbox.min.x, bbox.max.y, 0);
pivot = new THREE.Group();
pivot.add(cube4);
pivot.position.set(-bbox.min.x, 0.5-bbox.max.y, 0.5);
scene.add(cube1);
scene.add(cube2);
scene.add(cube3);
scene.add(pivot);
}
function addGridHelper() {
var helper = new THREE.GridHelper(100, 100);
helper.material.opacity = 0.25;
helper.material.transparent = true;
scene.add(helper);
var axis = new THREE.AxesHelper(1000);
scene.add(axis);
}
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
pivot.rotation.z += rot;
if (pivot.rotation.z > 0.0 || pivot.rotation.z < -Math.PI/2) rot *= -1;
render();
}
function render() {
renderer.render(scene, camera);
}
})();
<!--script src="https://threejs.org/build/three.js"></!--script-->
<script src="https://rawcdn.githack.com/mrdoob/three.js/r124/build/three.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/r124/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.js"></script>
<div id="container"></div>
From the first image, it appears that the pivot of your red tile is at its center.
For the rotation you want, you would ideally change the pivot to the lower right of the cube. This is impossible without modifying the geometry of the cube.
BUT a simple trick is to create an empty node at that pivot point, parent your cube to that empty, and apply your rotation to the empty. (Don't forget to remove your translation, you don't need it anymore)
Here is some pseudo code, assuming your red box is centered at (0,0,0) and has a width and height of 100:
// create an empty node at desired rotation pivot
var empty = new Object3D or group
empty.position = (50, -50, 0)
// parent your cube to the empty
var cube = your box
empty.add(cube)
// you may need to change the local position of your cube to bring it back to its global position of (0,0,0)
cube.position = (-50, 50, 0)
rotate empty by 45°
I think you can get the bounds of the rotated object like this:
bounds = new THREE.Box3().setFromObject( theRedObject )
Then reposition the object.y based on its bounds.min.y
let scene, camera, controls, ambient, point, loader, renderer, container, stats;
const targetRotation = 0;
const targetRotationOnMouseDown = 0;
const mouseX = 0;
const mouseXOnMouseDown = 0;
const windowHalfX = window.innerWidth / 2;
const windowHalfY = window.innerHeight / 2;
init();
animate();
var box, b1, b2, b3;
function init() {
// Create a scene which will hold all our meshes to be rendered
scene = new THREE.Scene();
// Create and position a camera
camera = new THREE.PerspectiveCamera(
60, // Field of view
window.innerWidth / window.innerHeight, // Aspect ratio
/*window.innerWidth / -8,
window.innerWidth / 8,
window.innerHeight / 8,
window.innerHeight / -8,
*/
0.1, // Near clipping pane
1000 // Far clipping pane
);
scene.add(camera)
// Reposition the camera
camera.position.set(0, 5, 10);
// Point the camera at a given coordinate
camera.lookAt(new THREE.Vector3(0, 0, 0));
// Add orbit control
controls = new THREE.OrbitControls(camera);
controls.target.set(0, -0.5, 0);
controls.update();
// Add an ambient lights
ambient = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambient);
// Add a point light that will cast shadows
point = new THREE.PointLight(0xffffff, 1);
point.position.set(25, 50, 25);
point.castShadow = true;
point.shadow.mapSize.width = 1024;
point.shadow.mapSize.height = 1024;
scene.add(point);
group = new THREE.Group();
group.position.y = 0;
scene.add(group);
rotationAnchor = new THREE.Object3D()
group.add(rotationAnchor);
box = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshStandardMaterial({
color: 'grey'
}))
b1 = box.clone();
b2 = box.clone();
b3 = box.clone();
b3.material = b3.material.clone()
b3.material.color.set('red')
group.add(box);
group.add(b1);
b1.position.y += 1
group.add(b2);
b2.position.z += 1
rotationAnchor.add(b3);
rotationAnchor.position.set(0.5, 0.5, 1.5)
b3.position.set(-.5, -.5, -.5)
// Create a renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
// Set size
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// Set color
renderer.setClearColor(0xf8a5c2);
renderer.gammaOutput = true;
// Enable shadow mapping
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Append to the document
container = document.createElement("div");
document.body.appendChild(container);
document.body.appendChild(renderer.domElement);
// Add resize listener
window.addEventListener("resize", onWindowResize, false);
// Enable FPS stats
stats = new Stats();
container.appendChild(stats.dom);
var gui = new dat.GUI({
height: 5 * 32 - 1
});
let params = {
'test': 4,
'bevelThickness': 1,
'bevelSize': 1.5,
'bevelSegments': 3
}
gui.add(params, 'test', 0, 10).onChange(val => {
test = val
})
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
rotationAnchor.rotation.z = (Math.cos(performance.now() * 0.001) * Math.PI * 0.25) + (Math.PI * 1.25)
requestAnimationFrame(animate);
// Re-render scene
renderer.render(scene, camera);
// Update stats
stats.update();
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/libs/stats.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.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>
I already asked a Question here: Add Thickness to faces
The core question is solved but i ran into another problem.
Before my walls were set on side:THREE.BackSide so that they didnt show when they faced the camera but now when they have a thickness that doesnt work anymore and i dont realy understand why.
Before:
Before
After: After
How can i make the thick walls behave like the Plane walls ?
A very rough concept of controlling the visibility of a wall (I've slightly changed translating and positioning of a geometry):
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 5, 5);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var points = [
new THREE.Vector3(-2, 0, 2),
new THREE.Vector3(2, 0, 2),
new THREE.Vector3(2, 0, -2),
new THREE.Vector3(-2, 0, -2)
]
var walls = [];
points.forEach((p, idx, points) => {
let nextIdx = idx === points.length - 1 ? 0 : idx + 1;
buildWall(p, points[nextIdx], 2, 0.1);
});
function buildWall(pointStart, pointEnd, height, thickness) {
var boxW = pointEnd.clone().sub(pointStart).length();
var boxH = height;
var boxD = thickness;
var boxGeometry = new THREE.BoxGeometry(boxW, boxH, boxD);
boxGeometry.translate(0, boxH * 0.5, 0);
boxGeometry.rotateY(-Math.PI * 0.5);
var wall = new THREE.Mesh(boxGeometry, new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
}));
wall.position.copy(pointStart).add(pointEnd).multiplyScalar(0.5);
wall.lookAt(pointEnd);
scene.add(wall);
walls.push(wall);
}
var currentPosition = new THREE.Vector3();
render();
function render() {
requestAnimationFrame(render);
walls.forEach(w => {
w.visible = currentPosition.copy(w.position).sub(camera.position).lengthSq() > camera.position.lengthSq();
})
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>