Three.js rotate cube to show selected side in front view? - javascript

I have a rotating box made with three.js and I want to stop (and restart) animation to show the object face frontally.
I would like to show a smooth animation until the cube is in the correct position. I 've seen several examples here on StackOverflow but I wasn't able to replicate it in my example.
Example: JSFiddle example
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.z = 500;
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
Here the animation
function render() {
mesh.rotation.x += 0.01;
mesh.rotation.z += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
How is the best approach to keep it simple and smooth?

One approach to solve this problem is the usage of Quaternion.rotateTowards() (a method similar to Unity's Quaternion.RotateTowards()).
First, you have to define a target rotation for your box. This can be done like so:
var targetQuaternion = new THREE.Quaternion();
targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) );
In the next step, you use Quaternion.rotateTowards() in your animation loop like so:
if ( ! mesh.quaternion.equals( targetQuaternion ) ) {
var step = speed * delta;
mesh.quaternion.rotateTowards( targetQuaternion, step );
}
step represents the angular displacement in radians per seconds. In the following live example, the value is set to Math.PI. Meaning the box can rotate 180 degrees per second.
So essentially, you can define a target rotation for all sides of the cube and then use Quaternion.rotateTowards() to rotate towards it.
https://jsfiddle.net/0eutrjqs/
three.js R113

Related

The Three.js model does not rotate, error unknown

I am trying to rotate the model. However, I have no idea why my model is not rotating because there is no error shown on the developer tools. Therefore I can not identify what causes the error. I might be missing something in my code but I'm not sure where about. Please, if anyone has any solution on this, thank you for your advice.
let scene, camera, renderer, controls, car;
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 5000);
camera.rotation.y = 45 / 180 * Math.PI;
camera.position.x = 40;
camera.position.y = 0;
camera.position.z = 40;
hlight = new THREE.AmbientLight(0x404040, 0.001);
scene.add(hlight);
directionalLight = new THREE.DirectionalLight(0x333333, 10);
directionalLight.position.set(1, 1, 0);
directionalLight.castShadow = true;
scene.add(directionalLight);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.addEventListener('change');
let loader = new THREE.GLTFLoader();
loader.load('./5/3D_model.gltf', function (gltf) {
car = gltf.scene.children[0];
car.scale.set(10, 10, 10);
scene.add(gltf.scene);
renderer.render(scene, camera);
animate();
});
}
function animate() {
if (car) car.rotation.y += 0.3;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
init();
animate();
There are a couple of issues in your code:
You are setting an undefined event listener to the change event of OrbitControls. Please remove this line.
You start the animation loop twice. Remove one call of animate();.
Rotating the asset 0.3 per animation step is quite a lot. The model might rotate so fast that you don't see it. Use 0.01 for now.
The intensity of the directional light is too high. Just use the default value for now. Besides, setting castShadow to true has no effect with your current scene setup.

Three.js Draw where mouse clicks, but entirely parallel to camera orientation

I"m not exactly sure how I can do this. I read a lot about raycasting and that seems to be good for finding points that intersect with something, but in this case I just want it to interpolate the 2d mouse coordinates to the 3d point exactly where the mouse clicks, regardless of scale, rotation, whether or not there's an object there, etc.
One method I've thought of but not approached would be making an invisible plane that is parallel to the camera, always oriented upright and always intersecting the y axis. Then use a raycaster to hit the plane, draw as needed, then delete the plane. Seems like a silly way to do this though.
At the moment I have a method that works pretty well but it has some issues when the line gets further away from the origin, or the camera gets zoomed
In this photo I drew two lines from two different perspectives. The vertical line what it looks like when the camera is level with the x and z axis, and I draw a straight line down the y axis, while the horizontal line is what happens when i scribble with the camera facing down.
https://i.imgur.com/f8qw5xV.png
As you can see, it seems to use the distance to the camera to make this calculation, so the further the distance from the camera, the more distortion is in the calculation. How can get rid of this distortion?
source: https://github.com/AskAlice/mandala-3d-threejs
live demo: https://askalice.me/mandala/
Here is the relevant code:
js/content.js#112
function get3dPointZAxis(event)
{
camPos = camera.position;
var mv = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY/window.innerHeight) * 2 + 1, 1).unproject(camera);
var m2 = new THREE.Vector3(0,0,0);
var pos = camPos.clone();
pos.add(mv.sub(camPos).normalize().multiplyScalar(m2.distanceTo(camPos)));
return pos;
}
I used information from two stackoverflow posts to come up with this, and it has the issues I've described.
firstly, this post shows how to draw and convert it to the z axis. It is flat. But I had a lot of trouble trying to make that work in three dimensions.
How to draw a line segment at run time using three.js
and then i used information in the below post to at least get it parallel to the camera on the x-z axis like such: https://i.imgur.com/E9AQNpH.png
Moving objects parallel to projection plane in three.js
That option with THREE.Plane() and THREE.Raycaster().ray.intersectPlane():
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();
function getPoint(event){
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
planeNormal.copy(camera.position).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(plane, point);
}
Run the code snippet, click the "draw" checkbox to set it as checked, move your mouse randomly (without mouse down), click the checkbox again, rotate the scene with mousedown. All points are on the same plane.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();
document.addEventListener("mousedown", onMouseDown, false);
document.addEventListener("mousemove", onMouseMove, false);
function getPoint(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
planeNormal.copy(camera.position).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(plane, point);
}
function setPoint() {
var sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(.125, 4, 2), new THREE.MeshBasicMaterial({
color: "yellow",
wireframe: true
}));
sphere.position.copy(point);
scene.add(sphere);
}
function onMouseDown(event) {
getPoint(event);
if (draw.checked) setPoint();
}
function onMouseMove(event) {
getPoint(event);
if (draw.checked) setPoint();
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div style="position:absolute;">
<input id="draw" type="checkbox">
<label for="draw" style="color: white;">draw</label>
</div>

Rotate around local axis

I'm trying to implement an object, flying around a position (POI - Point Of Interest) and facing it. when you press WASD, you can change the POI's rotation. A and D -> change y-axis, W and S for the x-axis.
As you can see in the demo (http://jsbin.com/yodusufupi) the y-axis rotation is done based on local rotation of the helper object, but the x-axis is calculated in global space. Setting the rotation is done via: helper.rotation.set(rotX, rotY, 0);.
What I'm doing wrong? I want to have both rotations beeing done in local space.
Thx!
PS: minimal working example (the rotation around Y seems correct while x-axis is calculated globally)
var scene = new THREE.Scene();
var DEG_2_RAD = Math.PI / 180;
var rotX = -45 * DEG_2_RAD;
var rotY = 45 * DEG_2_RAD;
var helper = new THREE.AxisHelper(2);
var cam = new THREE.AxisHelper(1);
helper.add(cam);
scene.add(helper);
cam.translateZ(4);
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 10;
camera.position.y = 10;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild( renderer.domElement );
helper.rotation.set(rotX, rotY, 0);
renderer.render( scene, camera );
You need to understand how Euler angles work in three.js. For that, see this answer.
For your use case you can use these methods:
object.rotateX( angle ); // angle is in radians
object.rotateY( angle );
three.js r.77

Is it possible to style/position the canvas that is generated automatically by three.js?

I have been following an online tutorial for three.js that results in a spinning 3D cube.
I have used the following JavaScript inside a script file:
function spin() {
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth/2, window.innerHeight/2);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial( { color: 0xffff00});
var cube = new THREE.Mesh(geometry, material);
camera.position.z = 5;
scene.add(cube);
var render = function(){
requestAnimationFrame(render);
cube.rotation.x += 0.1;
cube.rotation.y += 0.1;
renderer.render(scene, camera);
};
render();
}
Currently, this generates a spinning yellow cube on a black background. I can see that it is possible to change the size of the canvas (using renderer.setSize()), but is it possible to reposition it on the page (or add other styling such as transparent backgroud)? e.g. fit it around other elements.
I thought that it might be possible to create my own canvas and create the cube on this canvas, instead of using the one generated by three.js, would this be possible?
Both ways are possible, both using CSS (for static style) or JS (for dynamic style changes). In the end, it's just a canvas element and you can access it like any other element.
You can pass in your own canvas to the renderer like this:
new THREE.WebGLRenderer({ canvas: myCanvasElement });
where myCanvasElement is the element reference, of course.

Understanding Sprite distance using Raycast in three js

I'm adding sprites in a 3d scene using three.js and I want to know distance between the camera and sprite when I click on screen. So I use a Raycater.
But if I click on the sprite, the distance property of intersection object is always "wrong" (someting like 0.3), or maybe I don't know how to read and understand the result. I thought "distance" value of intersection is the distance from camera to sprite (so, in my case something like 5).
Here is a shortened version of my code :
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sprite = new THREE.Sprite(new THREE.SpriteMaterial({color: 0x00ff00}));
scene.add(sprite);
camera.position.z = 5;
var render = function () {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
window.addEventListener('mousedown', function (e) {
if (e.target == renderer.domElement) {
var vector = new THREE.Vector3((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1, 0.5);
var projector = new THREE.Projector();
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sprite]);
console.log(intersects[0]);
}
}, false);
You can see it in action here : http://jsfiddle.net/pWr57/
So how can I have the distance form camera to a sprite ?
three.js r66
Do this, instead
console.log( raycaster.ray.origin.distanceTo( intersects[0].point ) );
Tip: Read the source code, Raycaster.js, so you know what it is doing. It is currently returning the perpendicular distance from the sprite center to the ray.
In this case, I agree that it would be better to return the distance from the camera.
three.js r.66

Categories