How to rotate a object on axis world three.js? - javascript

Is it possible to do rotations taking axis of the world and not of the object?
I need to do some rotations of an object, but after the first rotation, I can't do other rotations like i want.
If it's not possible to do rotation on the axis of the world, my second option is to reset the axis after the first rotation. Is there some function for this?
I can't use object.eulerOrder because it changes the orientation of my object when I set object.eulerOrder="YZX" after some rotations.

UPDATED: THREE - 0.125.2
DEMO: codesandbox.io
const THREE = require("three");
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1, 4, 4, 4);
const material = new THREE.MeshBasicMaterial({
color: 0x628297,
wireframe: true
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// select the Z world axis
const myAxis = new THREE.Vector3(0, 0, 1);
// rotate the mesh 45 on this axis
cube.rotateOnWorldAxis(myAxis, THREE.Math.degToRad(45));
function animate() {
// rotate our object on its Y axis,
// but notice the cube has been transformed on world axis, so it will be tilted 45deg.
cube.rotation.y += 0.008;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();

Here's a small variation. Tested with r56.
THREE.Object3D._matrixAux = new THREE.Matrix4(); // global auxiliar variable
// Warnings: 1) axis is assumed to be normalized.
// 2) matrix must be updated. If not, call object.updateMatrix() first
// 3) this assumes we are not using quaternions
THREE.Object3D.prototype.rotateAroundWorldAxis = function(axis, radians) {
THREE.Object3D._matrixAux.makeRotationAxis(axis, radians);
this.matrix.multiplyMatrices(THREE.Object3D._matrixAux,this.matrix); // r56
THREE.Object3D._matrixAux.extractRotation(this.matrix);
this.rotation.setEulerFromRotationMatrix(THREE.Object3D._matrixAux, this.eulerOrder );
this.position.getPositionFromMatrix( this.matrix );
}
THREE.Object3D.prototype.rotateAroundWorldAxisX = function(radians) {
this._vector.set(1,0,0);
this.rotateAroundWorldAxis(this._vector,radians);
}
THREE.Object3D.prototype.rotateAroundWorldAxisY = function(radians) {
this._vector.set(0,1,0);
this.rotateAroundWorldAxis(this._vector,radians);
}
THREE.Object3D.prototype. rotateAroundWorldAxisZ = function(degrees){
this._vector.set(0,0,1);
this.rotateAroundWorldAxis(this._vector,degrees);
}
The three last lines are just to resync the params (position,rotation) from the matrix... I wonder if there is a more efficient way to do that...

Somewhere around r59 this gets a lot easier (rotate around x):
bb.GraphicsEngine.prototype.calcRotation = function ( obj, rotationX)
{
var euler = new THREE.Euler( rotationX, 0, 0, 'XYZ' );
obj.position.applyEuler(euler);
}

Updated answer from #Neil (tested on r98)
function rotateAroundWorldAxis(obj, axis, radians) {
let rotWorldMatrix = new THREE.Matrix4();
rotWorldMatrix.makeRotationAxis(axis.normalize(), radians);
rotWorldMatrix.multiply(obj.matrix);
obj.matrix = rotWorldMatrix;
obj.setRotationFromMatrix(obj.matrix);
}

#acarlon Your answer might just have ended a week of frustration. I've refined your function a bit. Here are my variations. I hope this saves someone else the 20+ hours I spent trying to figure this out.
function calcRotationAroundAxis( obj3D, axis, angle ){
var euler;
if ( axis === "x" ){
euler = new THREE.Euler( angle, 0, 0, 'XYZ' );
}
if ( axis === "y" ){
euler = new THREE.Euler( 0, angle, 0, 'XYZ' );
}
if ( axis === "z" ){
euler = new THREE.Euler( 0, 0, angle, 'XYZ' );
}
obj3D.position.applyEuler( euler );
}
function calcRotationIn3D( obj3D, angles, order = 'XYZ' ){
var euler;
euler = new THREE.Euler( angles.x, angles.y, angles.z, order );
obj3D.position.applyEuler( euler );
}
This works beautifully in r91.
Hope it helps.

Related

How to render caps with clipping planes and stencil in Threejs with outer and inner mesh objects

I’m new to three.js and stackoverflow. I’m trying to clip and cap three.js objects that have been rendered so I can move the helperPlane back and forth through the object to see inside it. There's an object inside it. What I’m looking to do is similar to this description of advanced clipping techniques in OpenGL here: More OpenGL Game Programming - Bonus - Advanced Clip Planes. So, if this can be done in OpenGL, there must be some way to do it in WebGL too?
I adapted the clipping_stencil example from threejs ( webgl - clipping stencil ), and everything looks right as long as I don’t move the helperPlanes. When the helperPlanes are moved, some of the cap faces of the larger mesh disappear, there’s some rendering artifacts-I think this is z-fighting-and the caps might not be rendered in the desired position.
Setting the renderingOrder property for the meshes was the big trick to getting the inner mesh to be rendered in the scene, but I don't know what to do about the z-fighting? when I move the clipping planes on the sliders.
I also posted this on discourse.threejs. Everything is on a JSFiddle. Any help would be greatly appreciated.
import * as THREE from 'three';
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js';
import {GUI} from 'https://threejs.org/examples/jsm/libs/lil-gui.module.min.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
let camera, scene, renderer, object, object2, stats;
let planes, planeObjects, planeObjects2, planeHelpers;
let clock;
const params = {
animate: false,
planeX: {
constant: 1,
negated: false,
displayHelper: false
},
planeY: {
constant: 1,
negated: false,
displayHelper: false
},
planeZ: {
constant: 0,
negated: false,
displayHelper: false
}
};
init();
animate();
function createPlaneStencilGroup( geometry, plane, renderOrder ) {
const group = new THREE.Group();
const baseMat = new THREE.MeshBasicMaterial();
baseMat.depthWrite = false;
baseMat.depthTest = false;
baseMat.colorWrite = false;
baseMat.stencilWrite = true;
baseMat.stencilFunc = THREE.AlwaysStencilFunc;
/* Subtract the mask created from the front-facing image
from the mask created from the back-facing image, we get
a new mask that represents the area where the clip edge
would be. Set the stencil buffer operation to increment
when rederering back-facing polygons and decrement on
front-facing polygons. This results in the desired mask
stored in the stencil buffer : http://glbook.gamedev.net/GLBOOK/glbook.gamedev.net/moglgp/advclip.html */
// back faces
const mat0 = baseMat.clone();
mat0.side = THREE.BackSide;
mat0.clippingPlanes = [ plane ];
mat0.stencilFail = THREE.IncrementWrapStencilOp;
mat0.stencilZFail = THREE.IncrementWrapStencilOp;
mat0.stencilZPass = THREE.IncrementWrapStencilOp;
//mat0.depthFunc = THREE.LessDepth; // See reference above
const mesh0 = new THREE.Mesh( geometry, mat0 );
mesh0.renderOrder = renderOrder;
group.add( mesh0 );
// front faces
const mat1 = baseMat.clone();
mat1.side = THREE.FrontSide;
mat1.clippingPlanes = [ plane ];
mat1.stencilFail = THREE.DecrementWrapStencilOp;
mat1.stencilZFail = THREE.DecrementWrapStencilOp;
mat1.stencilZPass = THREE.DecrementWrapStencilOp;
//mat1.depthFunc = THREE.LessDepth;
const mesh1 = new THREE.Mesh( geometry, mat1 );
mesh1.renderOrder = renderOrder;
group.add( mesh1 );
return group;
}
function init(){
//clock
clock = new THREE.Clock();
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(36, window.innerWidth/window.innerHeight, 1,100);
camera.position.set(2,2,2);
// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff,1);
dirLight.position.set(5,10,7.5);
dirLight.castShadow = true;
dirLight.shadow.camera.right = 2;
dirLight.shadow.camera.left = -2;
dirLight.shadow.camera.top = 2;
dirLight.shadow.camera.bottom = -2;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
scene.add(dirLight);
//Clipping planes
planes = [
new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 1 ),
new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 1 ),
new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0 )
];
planeHelpers = planes.map( p => new THREE.PlaneHelper( p, 2, 0xffffff ) );
planeHelpers.forEach( ph => {
ph.visible = false;
scene.add( ph );
} );
//Inner Cube
const geometry = new THREE.BoxGeometry( 0.5,0.5,0.5 );
//Outer Cube
const geometry2 = new THREE.BoxGeometry( 1,1,1 );
object = new THREE.Group();
scene.add(object);
//Set up clip plane rendering
/*
See https://discourse.threejs.org/t/capping-two-clipped-geometries-using-two-planes-which-are-negated-to-each-other/32643
Object 1
Render order 1: Draw front face / back face clipped and front face
/ back face not clipped (4 meshes)
Render order 2: Draw planar clip cap
Object 2
Render order 3: Draw front face / back face clipped and front face
/ back face not clipped (4 meshes)
Render order 4: Draw planar clip cap
*/
planeObjects = [];
planeObjects2 = [];
const planeGeom = new THREE.PlaneGeometry( 4, 4 );
for ( let i = 0; i < 3; i ++ ) {
const poGroup = new THREE.Group();
const poGroup2 = new THREE.Group()
const plane = planes[ i ];
// Object 1
const stencilGroup = createPlaneStencilGroup( geometry,
plane, i + 4 ); // Render after first group
// Object 2
const stencilGroup2 = createPlaneStencilGroup( geometry2,
plane, i + 1 ); // Render this first
// PLANAR CLIP CAP
// plane is clipped by the other clipping planes
const planeMat =
new THREE.MeshStandardMaterial( {
color: 0xfff000, // inner torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes.filter( p => p !== plane ),
//depthFunc: THREE.LessDepth,
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
} );
const planeMat2 =
new THREE.MeshStandardMaterial( {
color: 0xff0000, // inner torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes.filter( p => p !== plane ),
//depthFunc: THREE.LessDepth,
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
} );
const po = new THREE.Mesh( planeGeom, planeMat );
const po2 = new THREE.Mesh( planeGeom, planeMat2 );
po.onAfterRender = function ( renderer ) {
renderer.clearStencil();
};
po2.onAfterRender = function ( renderer ) {
renderer.clearStencil();
};
// Draw Planar Clip Cap
po.renderOrder = i + 4.1; // Render last (slightly)
po2.renderOrder = i + 1.1; // Render slightly after first group
object.add( stencilGroup );
object.add( stencilGroup2 );
poGroup.add( po );
poGroup2.add( po2 );
planeObjects.push( po );
planeObjects2.push( po2 );
scene.add( poGroup );
scene.add( poGroup2 );
}
// Object 1
const material = new THREE.MeshStandardMaterial( {
color: 0xfff000, // outer torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes,
clipShadows: true,
shadowSide: THREE.DoubleSide,
} );
// add the color
const clippedColorFront = new THREE.Mesh( geometry, material );
clippedColorFront.castShadow = true;
clippedColorFront.renderOrder = 6;
object.add( clippedColorFront );
// Object 2
const material2 = new THREE.MeshStandardMaterial( {
color: 0xff0000, // outer colour
metalness: 0.1,
roughness: 0.75,
side: THREE.DoubleSide,
clippingPlanes: planes,
clipShadows: true,
shadowSide: THREE.DoubleSide,
} );
// add the color
const clippedColorFront2 = new THREE.Mesh( geometry2, material2 );
clippedColorFront2.castShadow = true;
clippedColorFront2.renderOrder = 3;
object.add( clippedColorFront2 );
//Ground
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(9,9,1,1),
new THREE.MeshPhongMaterial({color:0x999999, opacity:0.25, side:THREE.DoubleSide})
);
ground.rotation.x = - Math.PI/2; // rotates x/y to x/z
ground.position.y = -1;
ground.receiveShadow = true;
scene.add(ground);
//Stats
stats = new Stats();
document.body.appendChild(stats.dom);
//Renderer
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor( 0x263238 );
window.addEventListener('resize',onWindowResize);
document.body.appendChild(renderer.domElement);
renderer.localClippingEnabled = true;
const controls = new OrbitControls(camera, renderer.domElement);
controls.minDistance = 2;
controls.maxDistance = 20;
controls.update();
//GUI
const gui = new GUI();
gui.add(params, 'animate');
const planeX = gui.addFolder( 'planeX' );
planeX.add( params.planeX, 'displayHelper' ).onChange( v => planeHelpers[ 0 ].visible = v );
planeX.add( params.planeX, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 0 ].constant = d );
planeX.add( params.planeX, 'negated' ).onChange( () => {
planes[ 0 ].negate();
params.planeX.constant = planes[ 0 ].constant;
} );
planeX.open();
const planeY = gui.addFolder( 'planeY' );
planeY.add( params.planeY, 'displayHelper' ).onChange( v => planeHelpers[ 1 ].visible = v );
planeY.add( params.planeY, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 1 ].constant = d );
planeY.add( params.planeY, 'negated' ).onChange( () => {
planes[ 1 ].negate();
params.planeY.constant = planes[ 1 ].constant;
} );
planeY.open();
const planeZ = gui.addFolder( 'planeZ' );
planeZ.add( params.planeZ, 'displayHelper' ).onChange( v => planeHelpers[ 2 ].visible = v );
planeZ.add( params.planeZ, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 2 ].constant = d );
planeZ.add( params.planeZ, 'negated' ).onChange( () => {
planes[ 2 ].negate();
params.planeZ.constant = planes[ 2 ].constant;
} );
planeZ.open();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
const delta = clock.getDelta();
requestAnimationFrame( animate );
if ( params.animate ) {
object.rotation.x += delta * 0.5;
object.rotation.y += delta * 0.2;
}
for ( let i = 0; i < planeObjects.length; i ++ ) {
const plane = planes[ i ];
// Planar clip cap for object 1
const po = planeObjects[ i ];
plane.coplanarPoint( po.position );
// planar clip cap for object 2
const po2 = planeObjects[ i ];
plane.coplanarPoint( po2.position );
// planar clip cap for object 1
po.lookAt(
po.position.x - plane.normal.x,
po.position.y - plane.normal.y,
po.position.z - plane.normal.z,
);
// planar clip cap for object 2
po2.lookAt(
po2.position.x - plane.normal.x,
po2.position.y - plane.normal.y,
po2.position.z - plane.normal.z,
);
}
stats.begin();
renderer.render( scene, camera );
stats.end();
}
I had some success with what I set out to do. This is an updated JSFiddle. I was able to implement capping an object inside another object with clipping and stencils. I included drag and orbit controls and gui to select the plane (x,y,z) to section along. I noticed some strange behaviour in rendering the caps depending on the object position and the rotation of the camera.
I needed to move the object further away from the camera to see the caps rendered when sectioning in the x and y planes, but not z
The caps seemed to disappear like a sliding door if I rotated the camera from positive x to negative x
So I think the caps are rendering in the same place as the clipping plane and depth testing can’t discriminate between the two at some camera points. I think moving the caps away from the clipping plane by some tolerance along a vector normal to the plane will get the caps to render at more angles when I move the camera. I tried this in my animate function:
innerCap.translateOnAxis(clipPlane.normal, -1.5);
This gets the caps to render for a little more of an angle in the direction of negative x. I think this tolerance is some function of the distance from the object to the camera, but I’m not sure how to implement this. Thanks for your help.

BoxGeometry not aligning with SphereGeometry properly

I am trying to create spikes on earth(sphere geometry). Though everything works fines, but spikes dont align with globe. I want spike to align something like below image. But my spikes dont lookAt(new THREE.Vector3(0,0,0)) despite mentioned. Please help me out.
I purposefully mentioned code required for debugging. Let me know if you need more code for this. Below image is how i want my spikes to align with sphere.
But this is how it looks
My Main JS initialization file.
$(document).ready(function () {
// Initializing Camera
Influx.Camera = new Influx.Camera({
fov: 60,
aspectRatio: window.innerWidth / window.innerHeight,
near: 1,
far: 1000,
position: {
x: 0,
y: 0,
z: 750
}
});
//Initializing Scene
Influx.Scene = new Influx.Scene();
// Initializing renderer
Influx.Renderer = new Influx.Renderer({
clearColor: 0x000000,
size: {
width: window.innerWidth,
height: window.innerHeight
}
});
Influx.Globe = new Influx.Globe({
radius: 300,
width: 50,
height: 50
});
//
Influx.Stars = new Influx.Stars({
particleCount: 15000,
particle: {
color: 0xFFFFFF,
size: 1
}
});
Influx.moveTracker = new Influx.moveTracker();
Influx.EventListener = new Influx.EventListener();
(function animate() {
requestAnimationFrame( animate );
render();
controls.update();
})();
function render() {
camera.lookAt(scene.position);
group.rotation.y -= 0.001;
renderer.render( scene, camera );
};
});
Below is code responsible for generating spikes on Globe.
Influx.Spikes = function (lat, long) {
// convert the positions from a lat, lon to a position on a sphere.
var latLongToVector3 = function(lat, lon, RADIUS, heigth) {
var phi = (lat) * Math.PI/180,
theta = (lon-180) * Math.PI/180;
var x = -(RADIUS+heigth) * Math.cos(phi) * Math.cos(theta),
y = (RADIUS+heigth) * Math.sin(phi),
z = (RADIUS+heigth) * Math.cos(phi) * Math.sin(theta);
return new THREE.Vector3(x, y, z);
};
var geom = new THREE.Geometry();
var BoxGeometry = new THREE.BoxGeometry(1, 100, 1);
//iterates through the data points and makes boxes with the coordinates
var position = latLongToVector3(lat, long, 300, 2);
var box = new THREE.Mesh( BoxGeometry );
//each position axis needs to be set separately, otherwise the box
//will instantiate at (0,0,0)
box.position.x = position.x;
box.position.y = position.y;
box.position.z = position.z;
box.lookAt(new THREE.Vector3(0, 0, 0));
box.updateMatrix();
//merges the geometry to speed up rendering time, don't use THREE.GeometryUtils.merge because it's deprecated
geom.merge(box.geometry, box.matrix);
var total = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({
color: getRandomColor(),
morphTargets: true
}));
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
//add boxes to the group
group.add(total);
scene.add(group);
};
Influx.Camera = function(params = {}) {
if ( !$.isEmptyObject(params) ) {
window.camera = new THREE.PerspectiveCamera(params.fov, params.aspectRatio, params.near, params.far);
camera.position.set(params.position.x, params.position.y, params.position.z);
camera.lookAt(new THREE.Vector3(0,0,0));
} else {
console.log("Trouble with Initializing Camera");
return;
}
};
Remember that lookAt takes a direction vector, you give to this method the vector (0, 0, 0), this is actually not a normalized direction vector. So you must calculate the direction:
from your box position to the center of the sphere AND normalize it.
var dir = box.position.sub(world.position).normalize();
box.lookAt(dir);
And now just a set of code good conventions that may help you:
var BoxGeometry = new THREE.BoxGeometry(1, 100, 1);
Here I would rather use another var name for the box geometry, not to mix up with the "class" definition from THREE and to follow naming conventions:
var boxGeometry = new THREE.BoxGeometry(1, 100, 1);
And here:
box.position.x = position.x;
box.position.y = position.y;
box.position.z = position.z;
You can just set:
box.position.copy(position);
I also meet this problem, and I fixed it, the solution is: box.lookAt(new THREE.Vector3(0, 0, 0)) must after box.scale.z = xxxx

Incrementally display three.js TubeGeometry

I am able to display a THREE.TubeGeometry figure as follows
Code below, link to jsbin
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<script>
// global variables
var renderer;
var scene;
var camera;
var geometry;
var control;
var count = 0;
var animationTracker;
init();
drawSpline();
function init()
{
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
renderer.setClearColor('lightgray', 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
}
function drawSpline(numPoints)
{
var numPoints = 100;
// var start = new THREE.Vector3(-5, 0, 20);
var start = new THREE.Vector3(-5, 0, 20);
var middle = new THREE.Vector3(0, 35, 0);
var end = new THREE.Vector3(5, 0, -20);
var curveQuad = new THREE.QuadraticBezierCurve3(start, middle, end);
var tube = new THREE.TubeGeometry(curveQuad, numPoints, 0.5, 20, false);
var mesh = new THREE.Mesh(tube, new THREE.MeshNormalMaterial({
opacity: 0.9,
transparent: true
}));
scene.add(mesh);
renderer.render(scene, camera);
}
</script>
</body>
</html>
However, I would like to display incrementally, as in, like an arc that is loading, such that it starts as the start point, draws incrementally and finally looks the below arc upon completion.
I have been putting in some effort, and was able to do this by storing all the points/coordinates covered by the arc, and drawing lines between the consecutive coordinates, such that I get the 'arc loading incrementally' feel. However, is there a better way to achieve this? This is the link to jsbin
Adding the code here as well
<!DOCTYPE html>
<html>
<head>
<title>Incremental Spline Curve</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<script>
// global variables
var renderer;
var scene;
var camera;
var splineGeometry;
var control;
var count = 0;
var animationTracker;
// var sphereCamera;
var sphere;
var light;
function init() {
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
// renderer.setClearColor(0x000000, 1.0);
renderer.setClearColor( 0xffffff, 1 );
renderer.setSize(window.innerWidth, window.innerHeight);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// //init for sphere
// sphereCamera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
// sphereCamera.position.y = -400;
// sphereCamera.position.z = 400;
// sphereCamera.rotation.x = .70;
sphere = new THREE.Mesh(new THREE.SphereGeometry(0.8,31,31), new THREE.MeshLambertMaterial({
color: 'yellow',
}));
light = new THREE.DirectionalLight('white', 1);
// light.position.set(0,-400,400).normalize();
light.position.set(0,10,10).normalize();
//get points covered by Spline
getSplineData();
}
//save points in geometry.vertices
function getSplineData() {
var curve = new THREE.CubicBezierCurve3(
new THREE.Vector3( -5, 0, 10 ),
new THREE.Vector3(0, 20, 0 ),
new THREE.Vector3(0, 20, 0 ),
new THREE.Vector3( 2, 0, -25 )
);
splineGeometry = new THREE.Geometry();
splineGeometry.vertices = curve.getPoints( 50 );
animate();
}
//scheduler loop
function animate() {
if(count == 50)
{
cancelAnimationFrame(animationTracker);
return;
}
//add line to the scene
drawLine();
renderer.render(scene, camera);
// renderer.render(scene, sphereCamera);
count += 1;
// camera.position.z -= 0.25;
// camera.position.y -= 0.25;
animationTracker = requestAnimationFrame(animate);
}
function drawLine() {
var lineGeometry = new THREE.Geometry();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0x0000ff
});
console.log(splineGeometry.vertices[count]);
console.log(splineGeometry.vertices[count+1]);
lineGeometry.vertices.push(
splineGeometry.vertices[count],
splineGeometry.vertices[count+1]
);
var line = new THREE.Line( lineGeometry, lineMaterial );
scene.add( line );
}
// calls the init function when the window is done loading.
window.onload = init;
</script>
<body>
</body>
</html>
Drawback : The drawback of doing it the above way is that, end of the day, I'm drawing a line between consecutive points, and so I lose out on a lot of the effects possible in TubeGeometry such as, thickness, transparency etc.
Please suggest me an alternative way to get a smooth incremental load for the TubeGeometry.
THREE.TubeGeometry returns a THREE.BufferGeometry.
With THREE.BufferGeometry, you have access to a property drawRange that you can set to animate the drawing of the mesh:
let nEnd = 0, nMax, nStep = 90; // 30 faces * 3 vertices/face
...
const geometry = new THREE.TubeGeometry( path, pathSegments, tubeRadius, radiusSegments, closed );
nMax = geometry.attributes.position.count;
...
function animate() {
requestAnimationFrame( animate );
nEnd = ( nEnd + nStep ) % nMax;
mesh.geometry.setDrawRange( 0, nEnd );
renderer.render( scene, camera );
}
EDIT: For another approach, see this SO answer.
three.js r.144
Normally you would be able to use the method .getPointAt() to "get a vector for point at relative position in curve according to arc length" to get a point at a certain percentage of the length of the curve.
So normally if you want to draw 70% of the curve and a full curve is drawn in 100 segments. Then you could do:
var percentage = 70;
var curvePath = new THREE.CurvePath();
var end, start = curveQuad.getPointAt( 0 );
for(var i = 1; i < percentage; i++){
end = curveQuad.getPointAt( percentage / 100 );
lineCurve = new THREE.LineCurve( start, end );
curvePath.add( lineCurve );
start = end;
}
But I think this is not working for your curveQuad since the getPointAt method is not implemented for this type. A work around is to get a 100 points for your curve in an array like this:
points = curve.getPoints(100);
And then you can do almost the same:
var percentage = 70;
var curvePath = new THREE.CurvePath();
var end, start = points[ 0 ];
for(var i = 1; i < percentage; i++){
end = points[ percentage ]
lineCurve = new THREE.LineCurve( start, end );
curvePath.add( lineCurve );
start = end;
}
now your curvePath holds the line segments you want to use for drawing the tube:
// draw the geometry
var radius = 5, radiusSegments = 8, closed = false;
var geometry = new THREE.TubeGeometry(curvePath, percentage, radius, radiusSegments, closed);
Here a fiddle with a demonstration on how to use this dynamically
I'm not really that familiar with three.js. But I think I can be of assistance. I have two solutions for you. Both based on the same principle: build a new TubeGeometry or rebuild the current one, around a new curve.
Solution 1 (Simple):
var CurveSection = THREE.Curve.create(function(base, from, to) {
this.base = base;
this.from = from;
this.to = to;
}, function(t) {
return this.base.getPoint((1 - t) * this.from + t * this.to);
});
You define a new type of curve which just selects a segment out of a given curve. Usage:
var curve = new CurveSection(yourCurve, 0, .76); // Where .76 is your percentage
Now you can build a new tube.
Solution 2 (Mathematics!):
You are using for your arc a quadratic bezier curve, that's awesome! This curve is a parabola. You want just a segment of that parabola and that is again a parabola, just with other bounds.
What we need is a section of the bezier curve. Let's say the curve is defined by A (start), B (direction), C (end). If we want to change the start to a point D and the end to a point F we need the point E that is the direction of the curve in D and F. So the tangents to our parabola in D and F have to intersect in E. So the following code will give us the desired result:
// Calculates the instersection point of Line3 l1 and Line3 l2.
function intersection(l1, l2) {
var A = l1.start;
var P = l2.closestPointToPoint(A);
var Q = l1.closestPointToPoint(P);
var l = P.distanceToSquared(A) / Q.distanceTo(A);
var d = (new THREE.Vector3()).subVectors(Q, A);
return d.multiplyScalar(l / d.length()).add(A);
}
// Calculate the tangentVector of the bezier-curve
function tangentQuadraticBezier(bezier, t) {
var s = bezier.v0,
m = bezier.v1,
e = bezier.v2;
return new THREE.Vector3(
THREE.CurveUtils.tangentQuadraticBezier(t, s.x, m.x, e.x),
THREE.CurveUtils.tangentQuadraticBezier(t, s.y, m.y, e.y),
THREE.CurveUtils.tangentQuadraticBezier(t, s.z, m.z, e.z)
);
}
// Returns a new QuadraticBezierCurve3 with the new bounds.
function sectionInQuadraticBezier(bezier, from, to) {
var s = bezier.v0,
m = bezier.v1,
e = bezier.v2;
var ns = bezier.getPoint(from),
ne = bezier.getPoint(to);
var nm = intersection(
new THREE.Line3(ns, tangentQuadraticBezier(bezier, from).add(ns)),
new THREE.Line3(ne, tangentQuadraticBezier(bezier, to).add(ne))
);
return new THREE.QuadraticBezierCurve3(ns, nm, ne);
}
This is a very mathematical way, but if you should need the special properties of a Bezier curve, this is the way to go.
Note: The first solution is the simplest. I am not familiar with Three.js so I wouldn't know what the most efficient way to implement the animation is. Three.js doesn't seem to use the special properties of a bezier curve so maybe solution 2 isn't that useful.
I hope you have gotten something useful out of this.

three.js: rotate object around world axis combined with tween.js

I'm currently trying to tween-rotate a cube in 3D and thanks to this post (How to rotate a object on axis world three.js?) the rotation without tweening works without any problems. So currently I'm trying to transfer the rotation done by setFromRotationMatrix to something I can use as end rotation for my tween.
EDIT:
Here is what I have at the moment:
// function for rotation dice
function moveCube() {
// reset parent object rotation
pivot.rotation.set( 0, 0, 0 );
pivot.updateMatrixWorld();
// attach dice to pivot object
THREE.SceneUtils.attach( dice, scene, pivot );
// set variables for rotation direction
var rotateZ = -1;
var rotateX = -1;
if (targetRotationX < 0) {
rotateZ = 1;
} else if (targetRotationY < 0) {
rotateX = 1;
}
// check what drag direction was higher
if (Math.abs(targetRotationX) > Math.abs(targetRotationY)) {
// rotation
var newPosRotate = {z: rotateZ * (Math.PI / 2)};
new TWEEN.Tween(pivot.rotation)
.to(newPosRotate, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
//rotateAroundWorldAxis(dice, new THREE.Vector3(0, 0, rotateZ), Math.PI / 2);
} else {
// rotation
var newPosRotate = {x: -rotateX * (Math.PI / 2)};
new TWEEN.Tween(pivot.rotation)
.to(newPosRotate, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
//rotateAroundWorldAxis(dice, new THREE.Vector3(-rotateX, 0, 0), Math.PI / 2);
}
// detach dice from parent object
THREE.SceneUtils.detach( dice, pivot, scene );
}
Thanks to WestLangley I think I'm finally close to a solution that is easy to do and will serve my purpose. When initializing the pivot object I set it to the exact same position as the dice, so the rotation will still be around the center of the dice.
var loader = new THREE.JSONLoader();
loader.load(
'models/dice.json',
function ( geometry, materials ) {
material = new THREE.MeshFaceMaterial( materials );
dice = new THREE.Mesh( geometry, material );
dice.scale.set(1.95, 1.95, 1.95);
dice.position.set(2.88, 0.98, 0.96);
scene.add( dice );
pivot = new THREE.Object3D();
pivot.rotation.set( 0, 0, 0 );
pivot.position.set(dice.position.x, dice.position.y, dice.position.z);
scene.add( pivot );
}
);
The solution I have atm (upper snippet) does not attach the dice to the pivot object as parent. I'm probably overlooking something very basic ...
EDIT END
As I thought it was a really simple thing I had to do, to get it working:
I only needed to move the detachment of the child object (the dice) to the beginning of the function, instead of having it at the end of it and it works the charm.
Here's the working code:
// function for rotating dice
function moveCube() {
// detach dice from parent object first or attaching child object won't work as expected
THREE.SceneUtils.detach( dice, pivot, scene );
// reset parent object rotation
pivot.rotation.set( 0, 0, 0 );
pivot.updateMatrixWorld();
// attach dice to pivot object
THREE.SceneUtils.attach( dice, scene, pivot );
// set variables for rotation direction
var rotateZ = -1;
var rotateX = -1;
if (targetRotationX < 0) {
rotateZ = 1;
} else if (targetRotationY < 0) {
rotateX = 1;
}
// check what drag direction was higher
if (Math.abs(targetRotationX) > Math.abs(targetRotationY)) {
// rotation
var newPosRotate = {z: rotateZ * (Math.PI / 2)};
new TWEEN.Tween(pivot.rotation)
.to(newPosRotate, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
} else {
// rotation
var newPosRotate = {x: -rotateX * (Math.PI / 2)};
new TWEEN.Tween(pivot.rotation)
.to(newPosRotate, 2000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
}
}
Thanks a lot for helping!

SpotLight Rotation in three.js

I'm trying to rotate a spotlight. My code is:
var light = new THREE.SpotLight( color, intensity, distance );
light.position.set( 0, 100, 0 );
light.rotation.set( 0, Math.PI, 0 );
light.shadowCameraFar = 50;
light.shadowCameraNear = 0.01;
light.castShadow = true;
light.shadowDarkness = 0.5;
light.shadowCameraVisible = true;
light.shadowCameraFar = 800;
light.shadowCameraFov = 15;
scene.add( light );
I want to know what I'm doing wrong. The spotlight doesn't change its rotation independent the value I put.
light.target determines the spotlight's shadow camera orientation. If, for example, you have an object in your scene called myObject, you could do something like this:
light.target = myObject;
Remember, light.target is an Object3D, not a position vector.
Three.js r.49
So you would position the light target either by direct assignment:
myLight.target.position = new THREE.Object3D( 10, 20, 30 );
Or by defining the light target object properties:
myLight.target.position.x = 10;
myLight.target.position.y = 20;
myLight.target.position.z = 30;

Categories

Resources