i am working on an the application to optimize packs in a truck.
i use three.js to show the 3D results.
below is the code i use.
i want to make it a moving scene , i want the boxes to show one by one move towards there position inside the truck
curent 3D result
i don't know how to do that, any idea please ?
var camera, controls, scene, renderer;
init();
//render(); // remove when using next line for animation loop (requestAnimationFrame)
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xcccccc );
scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 400, 200, 0 );
// controls
controls = new THREE.MapControls( camera, renderer.domElement );
//controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.minDistance = 1;
controls.maxDistance = 50;
controls.maxPolarAngle = Math.PI / 2;
// world
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
geometry.translate( 0, 0.5, 0 );
console.log("this is the list we get " , result ) ;
for (Element in result)
{
if( result[Element].Object === "Box" || result[Element].Object === "BigBox" )
{
var material = new THREE.MeshPhongMaterial( { opacity: 0.25 , color: getRandomColor() , transparent: false } );
var material2 = new THREE.MeshPhongMaterial( { opacity: 0.25 , color: getRandomColor() , transparent: true } );
var mesh = new THREE.Mesh( geometry, material );
if(result[Element].Object === "BigBox")
{
mesh.opacity = 0.01
mesh = new THREE.Mesh( geometry, material2 );
}
mesh.opacity = 0.75
mesh.position.x = result[Element].CenterPoint.X ;
mesh.position.y = result[Element].CenterPoint.Y;
mesh.position.z = result[Element].CenterPoint.Z;
mesh.scale.x = result[Element].Dimentions.X;
mesh.scale.y = result[Element].Dimentions.Y;
mesh.scale.z = result[Element].Dimentions.Z;
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
scene.add( mesh );
}
}
// lights
var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
var light = new THREE.DirectionalLight( 0x002288 );
light.position.set( - 1, - 1, - 1 );
scene.add( light );
var light = new THREE.AmbientLight( 0x222222 );
scene.add( light );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function animate() {
requestAnimationFrame( animate );
controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
render();
}
function render() {
renderer.render( scene, camera );
}
var link = document.createElement( 'a' );
link.style.display = 'none';
document.body.appendChild( link ); // Firefox workaround, see #6594
function save( blob, filename ) {
link.href = URL.createObjectURL( blob );
link.download = filename;
link.click();
// URL.revokeObjectURL( url ); breaks Firefox...
}
function saveString( text, filename ) {
save( new Blob( [ text ], { type: 'text/plain' } ), filename );
}
function saveArrayBuffer( buffer, filename ) {
save( new Blob( [ buffer ], { type: 'application/octet-stream' } ), filename );
}
So, if I understand you well, you have two tasks to do:
Move the camera
Show the boxes one by one
MOVE THE CAMERA
Create a global variable:
let previousTime = performance.now();
Then create an update() function and call it in your animate() function:
function update() {
const time = performance.now();
const deltaTime = time - previousTime; // this variable tells us, how much time has passed since the last frame (the last call of update() function)
previousTime = time;
moveCamera(deltaTime);
}
And the moveCamera() function:
function moveCamera(deltaTime) {
// you have to set this variable by your preferences
const SPEED = 10;
// This is the vector that determines the direction in which your camera moves.
// In this case the camera will move only along the X axis.
// Set it by your preferences depending on which way you want the camera to move.
const MoveCameraDirection = new THREE.Vector3(1,0,0);
// You have to multiply it by SPEED and by deltaTime
// Multiplying by deltaTime is to make the movement speed independent of the number of FPS
MoveCameraDirection.multiplyScalar( SPEED * deltaTime );
camera.position.add(MoveCameraDirection);
}
Camera movement is done.
SHOW THE BOXES ONE BY ONE
You need to have a handler to your boxes. So create a variable boxes and place your objects there:
var camera, controls, scene, renderer, boxes = [];
...
// before scene.add( mesh );
mesh.visible = false ; // set all boxes invisible at the begin
boxes.push(mesh);
Now, in your update() function, under moveCamera(deltaTime) you can call showBoxes().
The question is: when to show the boxes? Let's say we want to show the boxes, when the camera passes them.
Let's create this function:
function showBoxes() {
// I don't know which direction is your camera moving, so let's assume it's moving along the X axis (like in previos function). You can use any other axis as well.
// We want to show only the boxes that have **box.position.x < camera.position.x** and are not currenly displayed
const boxesToShow = boxes.filter( box => !box.visible && box.position.x < camera.position.x);
for( let i=0 ; i < boxesToShow.length ; i++ ) {
// we can do it just like this:
// boxesToShow[i].visible = true;
// but we don't want them to show all at once, so let's use a timer and display them one by one in 1 second intervals
setTimeout( () => boxesToShow[i].visible=true, i * 1000 );
}
}
Related
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.
I'm new to THREE.js.
For the purpose of learning JavaScript and THREE.js I made this (kind of) Mini Game where player is able to explore space and different space objects (work in process).
Anyways, my problem is when the scene and objects are rendered, and you start moving your cuboid, camera doesn't move with it (actually it doesn't move away from the world origin) only rotates in place looking at the player's object.
This is the code:
import './style.css'
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// Scene object
const scene = new THREE.Scene();
const clock = new THREE.Clock();
var cameraObject, keys;
var temp = new THREE.Vector3;
var dir = new THREE.Vector3;
var a = new THREE.Vector3;
var b = new THREE.Vector3;
var distance = 0.3;
var velocity = 0.0;
var speed = 0.0;
// Camera object
const perspectiveCamera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 50, 1e7 )
perspectiveCamera.position.setZ(80);
cameraObject = new THREE.Object3D;
cameraObject.add(perspectiveCamera);
// perspectiveCamera.lookAt(scene.position)
// Renderer object
const renderer = new THREE.WebGLRenderer({
});
document.body.appendChild( renderer.domElement );
// Renderer
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.render( scene, perspectiveCamera )
// Light
// PointLight
const pointLight = new THREE.PointLight(0xffffff)
pointLight.position.set(20,20,20)
// AmbientLight
const ambientLight = new THREE.AmbientLight(0xffffff)
scene.add(pointLight, ambientLight)
// Helper classes
const lightHelper = new THREE.PointLightHelper(pointLight);
const axesHelper = new THREE.AxesHelper(5);
scene.add(lightHelper, axesHelper)
// Controls
const controls = new OrbitControls(perspectiveCamera, renderer.domElement);
controls.movementSpeed = 1000;
controls.domElement = renderer.domElement;
controls.rollSpeed = Math.PI / 24;
controls.autoForward = false;
controls.dragToLook = false;
let cube = new THREE.Group();
const cubeGeometry = new THREE.BoxGeometry(3,10,3)
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0x238423 })
const cube1 = new THREE.Mesh( cubeGeometry, cubeMaterial );
cube.add(cube1)
scene.add(cube)
// Objects
// Stars
function addStar() {
const geometry = new THREE.SphereGeometry(0.25, 24, 24);
const material = new THREE.MeshStandardMaterial( { color: 0xffffff })
const star = new THREE.Mesh( geometry, material );
const [x, y, z] = Array(3).fill().map(() => THREE.MathUtils.randFloatSpread( 500 ));
star.position.set(x,y,z);
scene.add(star)
}
Array(1500).fill().forEach(addStar)
let keyState = {};
keys = {
a: false,
s: false,
d: false,
w: false,
q: false,
e: false,
space: false,
shiftleft: false,
};
document.addEventListener("keydown", function(e) {
console.log(e.code);
const key = e.code.replace('Key', '').toLowerCase();
if ( keys[ key ] !== undefined )
keys[ key ] = true;
});
document.body.addEventListener( 'keyup', function(e) {
const key = e.code.replace('Key', '').toLowerCase();
if ( keys[ key ] !== undefined )
keys[ key ] = false;
});
// Animate objects
function animate() {
const delta = clock.getDelta();
requestAnimationFrame( animate );
speed = 0.0;
if ( keys.w )
cube.rotateX(-0.03);
if ( keys.s )
cube.rotateX(0.03);
if ( keys.a )
cube.rotateZ(0.03);
if ( keys.d )
cube.rotateZ(-0.03);
if ( keys.q )
cube.rotateY(-0.06);
if ( keys.e )
cube.rotateY(0.06);
if ( keys.space )
speed = 0.9;
velocity += ( speed - velocity ) * .3;
cube.translateY( velocity );
if ( keys.shiftleft )
speed = 0.9*5;
velocity += ( speed - velocity ) * .3;
cube.translateY( velocity );
perspectiveCamera.lookAt( cube.position );
controls.update(delta);
renderer.render( scene, perspectiveCamera );
}
animate();
I'm actually trying to make some sort of Third Person Camera. In order to make the camera follow this little cuboid and rotate as the object rotates on the screen.
First attempt:
const idealOffset = new THREE.Vector3(0, 0, -80);
function animate(){
...
perspectiveCamera.position.set(0, 0, -80);
perspectiveCamera.applyQuaternion(cube.rotation);
perspectiveCamera.position.add(cube.position);
perspectiveCamera.lookAt(idealOffset);
...
}
Camera moves with the cube but after a few seconds turns back to look at the world origin.
Second attempt:
// Animate objects
requestAnimationFrame( function animate(milliseconds) {
const delta = clock.getDelta();
let moveDistance = 10*delta;
let rotateAngle = Math.PI/2*delta;
requestAnimationFrame( animate );
speed = 0.0;
if ( keys.w ){
cube.rotateX(-0.03);
}
if ( keys.s ){
cube.rotateX(0.03);
}
let rotation_matrix = new THREE.Matrix4().identity();
if ( keys.a ){
cube.rotateZ(rotateAngle);
}
if ( keys.d ){
cube.rotateZ(-rotateAngle);
}
if ( keys.q ){
cube.rotateY(-rotateAngle);
}
if ( keys.e ){
cube.rotateY(rotateAngle);
}
if ( keys.space ){
speed = 0.9;
velocity += ( speed - velocity ) * .3;
cube.translateY( velocity );
}
if ( keys.shiftleft ){
speed = 0.9*5;
velocity += ( speed - velocity ) * .3;
cube.translateY( velocity );
}
var relativeCameraOffset = new THREE.Vector3(0,-80,0);
var cameraOffset = relativeCameraOffset.applyMatrix4(cube.matrixWorld);
perspectiveCamera.position.x = cameraOffset.x;
perspectiveCamera.position.y = cameraOffset.y;
perspectiveCamera.position.z = cameraOffset.z;
perspectiveCamera.lookAt(cube.position);
renderer.render( scene, perspectiveCamera );
});
Camera does follow my cube but when I rotate it 180 DEG on x-axis it turns upside-down,it's fixed to the player and doesn't move smoothly and it looks directly at the player (back side of the cube).
This inspired me to read more about rotating/translating in 3d space, about Euler angles, quaternions, gimbal lock. I'm just not quite sure how to apply these scary quaternions to my project...
Any help, reference, whatever would be greatly appreciated.
Thanks in advance.
If you're using OrbitControls, just update the .target property to the position of the object you want it to focus on. When the cube moves, the camera will move with it. See here for more on the docs:
orbitControls.target = cube.position;
I'm not sure why you're doing this: cameraObject.add(perspectiveCamera);. This doesn't do anything if you don't make any changes to cameraObject.
I have just discovered the world of three.js and it's amazing.
I downloaded the examples, and started checking some of them.
I have never been coding in JavaScript, so I was wondering if somebody could help me with editing one of the example files (misc_controls_trackball.html). Instead of generated geometry (mesh.position.x = ( Math.random() - 0.5 ) ...) I was wondering if I could just include an already made mesh (from 3 studio max for example)?
I think this is the part of the code which generates the mesh:
// world
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );
var geometry = new THREE.CylinderGeometry( 0, 10, 30, 4, 1 );
var material = new THREE.MeshLambertMaterial( { color:0xffffff, shading: THREE.FlatShading } );
for ( var i = 0; i < 500; i ++ ) {
var mesh = new THREE.Mesh( geometry, material );
mesh.position.x = ( Math.random() - 0.5 ) * 1000;
mesh.position.y = ( Math.random() - 0.5 ) * 1000;
mesh.position.z = ( Math.random() - 0.5 ) * 1000;
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
scene.add( mesh );
}
In what way should this be changed, so that I could import my external mesh (in form of .3ds, .obj, .dae, does not matter)?
Thank you.
Here is the misc_controls_trackball.html example file along with "js" folder.
Tried this?
http://threejs.org/examples/webgl_loader_collada
It`s an example for Collada, but for the other formats the concept is the same, just using a different loader.
var loader = new THREE.ColladaLoader();
// Depending on how you created your model, you may need to
loader.options.convertUpAxis = true;
// Then load it:
loader.load( './models/collada/monster/monster.dae', function ( collada ) {
// All this will happen asynchronously
dae = collada.scene;
// Before displaying it, you can tweak it as necessary
dae.scale.x = dae.scale.y = dae.scale.z = 0.002;
dae.updateMatrix();
scene.add(dae);
// At the next frame, you`ll have your model loaded.
} );
EDIT, additions
First you need the links to the proper libraries, including the ColladaLoader
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.js"></script>
<script src="js/loaders/ColladaLoader.js"></script>
Then a number of things needed fixing in the code.
- scene object was missing
- Model loaded, but to be scaled up a bit
- No call to render() in the animate function, so you had no animation.
- The fog statement was broken... Best spending some time on the basics, first...
function init() {
// Create your scene first
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 500;
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.keys = [ 65, 83, 68 ];
controls.addEventListener( 'change', render );
// world
var loader = new THREE.ColladaLoader();
// Depending on how you created your model, you may need to
loader.options.convertUpAxis = true;
// Then load it:
//loader.load( './models/collada/monster/monster.dae', function ( collada ) {
loader.load( 'models/monster.dae', function ( collada ) {
// All this will happen asynchronously
dae = collada.scene;
// Give it a decent scale
dae.scale.x = dae.scale.y = dae.scale.z = 1;
dae.updateMatrix();
scene.add(dae);
// At the next frame, you`ll have your model loaded.
} );
// lights
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
light = new THREE.DirectionalLight( 0x002288 );
light.position.set( -1, -1, -1 );
scene.add( light );
light = new THREE.AmbientLight( 0x222222 );
scene.add( light );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: false } );
//renderer.setClearColor( scene.fog.color, 1 );
renderer.setSize( window.innerWidth, window.innerHeight );
container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
stats.domElement.style.zIndex = 100;
container.appendChild( stats.domElement );
//
window.addEventListener( 'resize', onWindowResize, false );
// The following is not necessary at this stage, as you`ll call it
// from animate later down (if you want to do an animation, of course,
// which I guess you do)
render();
}
And the animate function should look like this
function animate() {
requestAnimationFrame( animate );
controls.update();
render();
}
Hope that helps! :)
I recently got three.js example from the official site working with my collada objects (.dae) using the ColladaLoader.js.
Now my question is, how do i change the loaded collada object color attribute and add a custom texture?? I tried adding the texture with no luck yet.
Here is my code (slightly changed from the original example):
function load_model(el) {
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var container, stats;
var camera, scene, renderer, objects;
var particleLight, pointLight;
var dae, skin;
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load( '/site_media/models/model.dae', function ( collada ) {
dae = collada.scene;
skin = collada.skins[ 0 ];
dae.scale.x = dae.scale.y = dae.scale.z = 0.90;
dae.updateMatrix();
init(el);
animate();
} );
function init(el) {
container = document.createElement( 'div' );
el.append( container );
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.set( 2, 2, 3 );
scene = new THREE.Scene();
scene.add( dae );
particleLight = new THREE.Mesh( new THREE.SphereGeometry( 4, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0xffffff } ) );
scene.add( particleLight );
// Lights
scene.add( new THREE.AmbientLight( 0xcccccc ) );
var directionalLight = new THREE.DirectionalLight(/*Math.random() * 0xffffff*/0xeeeeee );
directionalLight.position.x = Math.random() - 0.5;
directionalLight.position.y = Math.random() - 0.5;
directionalLight.position.z = Math.random() - 0.5;
directionalLight.position.normalize();
scene.add( directionalLight );
// pointLight = new THREE.PointLight( 0xffffff, 4 );
// pointLight.position = particleLight.position;
// scene.add( pointLight );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth/2, window.innerHeight/2 );
container.appendChild( renderer.domElement );
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth/2, window.innerHeight/2 );
}
//
var t = 0;
var clock = new THREE.Clock();
function animate() {
var delta = clock.getDelta();
requestAnimationFrame( animate );
if ( t > 1 ) t = 0;
if ( skin ) {
// guess this can be done smarter...
// (Indeed, there are way more frames than needed and interpolation is not used at all
// could be something like - one morph per each skinning pose keyframe, or even less,
// animation could be resampled, morphing interpolation handles sparse keyframes quite well.
// Simple animation cycles like this look ok with 10-15 frames instead of 100 ;)
for ( var i = 0; i < skin.morphTargetInfluences.length; i++ ) {
skin.morphTargetInfluences[ i ] = 0;
}
skin.morphTargetInfluences[ Math.floor( t * 30 ) ] = 1;
t += delta;
}
render();
stats.update();
}
function render() {
var timer = Date.now() * 0.0005;
camera.position.x = Math.cos( timer ) * 10;
camera.position.y = 2;
camera.position.z = Math.sin( timer ) * 10;
camera.lookAt( scene.position );
particleLight.position.x = Math.sin( timer * 4 ) * 3009;
particleLight.position.y = Math.cos( timer * 5 ) * 4000;
particleLight.position.z = Math.cos( timer * 4 ) * 3009;
renderer.render( scene, camera );
}
}
You can override your collada scene materials recursively with this kind of function. It goes through the whole hierarchy and assigns a material.
var setMaterial = function(node, material) {
node.material = material;
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
setMaterial(node.children[i], material);
}
}
}
Use it like setMaterial(dae, new THREE.MeshBasicMaterial({color: 0xff0000}));
You could probably adapt that to modify the existing material properties instead of assigning a new one, if needed.
After many problems, we wrote a small hack in ColladaLoader.js taking the idea from #gaitat
witch basically replaces the old path to the textures from the images, passing some new ones in an array, and using regular expressions to parse the xml for the .png or .jpg under images tag. Not sure if there is an easier way but since support was limited we had to come up with a fix somehow
function parse( doc, imageReplace, callBack, url ) {
COLLADA = doc;
callBack = callBack || readyCallbackFunc;
if ( url !== undefined ) {
var parts = url.split( '/' );
parts.pop();
baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/';
}
parseAsset();
setUpConversion();
images = parseLib( "//dae:library_images/dae:image", _Image, "image" );
for(var i in imageReplace) {
var iR = imageReplace[i];
for(var i in images) {
var image = images[i];
var patt=new RegExp('[a-zA-Z0-9\-\_]*\/'+iR.name,'g');
//if(image.id==iR.id)
if(patt.test(image.init_from))
image.init_from = iR.new_image;
}//for
}
materials = parseLib( "//dae:library_materials/dae:material", Material, "material" );
effects = parseLib( "//dae:library_effects/dae:effect", Effect, "effect" );
geometries = parseLib( "//dae:library_geometries/dae:geometry", Geometry, "geometry" );
cameras = parseLib( ".//dae:library_cameras/dae:camera", Camera, "camera" );
controllers = parseLib( "//dae:library_controllers/dae:controller", Controller, "controller" );
animations = parseLib( "//dae:library_animations/dae:animation", Animation, "animation" );
visualScenes = parseLib( ".//dae:library_visual_scenes/dae:visual_scene", VisualScene, "visual_scene" );
morphs = [];
skins = [];
daeScene = parseScene();
scene = new THREE.Object3D();
for ( var i = 0; i < daeScene.nodes.length; i ++ ) {
scene.add( createSceneGraph( daeScene.nodes[ i ] ) );
}
// unit conversion
scene.position.multiplyScalar(colladaUnit);
scene.scale.multiplyScalar(colladaUnit);
createAnimations();
var result = {
scene: scene,
morphs: morphs,
skins: skins,
animations: animData,
dae: {
images: images,
materials: materials,
cameras: cameras,
effects: effects,
geometries: geometries,
controllers: controllers,
animations: animations,
visualScenes: visualScenes,
scene: daeScene
}
};
if ( callBack ) {
callBack( result );
}
return result;
};
One thing you can do is modify your collada model (dae file) locate the texture reference there and change it to your liking.
if ( url !== undefined ) {
var parts = url.split( '/' );
parts.pop();
baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/';
}
parseAsset();
setUpConversion();
images = parseLib( "//dae:library_images/dae:image", _Image, "image" );
for(var i in imageReplace) {
var iR = imageReplace[i];
for(var i in images) {
var image = images[i];
var patt=new RegExp('[a-zA-Z0-9\-\_]*\/'+iR.name,'g');
//if(image.id==iR.id)
if(patt.test(image.init_from))
image.init_from = iR.new_image;
}//for
}
Less words, more code =)
var objects = [];
var camera, scene, renderer;
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
init();
render();
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectObjects( objects );
if ( intersects.length > 0 ) {
console.log(intersects[ 0 ].object);
}
}
function init() {
container = document.getElementById( 'container' );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 90, window.innerWidth / window.innerHeight, 1, 1100 );
camera.position.z = 50;
scene.add( camera );
var particle = new THREE.Particle( new THREE.ParticleBasicMaterial( { map: THREE.ImageUtils.loadTexture( "img/satellite.png" ) } ) );
objects.push( particle );
//particle.scale.x = particle.scale.y = 0.25
scene.add( particle );
projector = new THREE.Projector();
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
}
function render() {
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
As a result, we get clickable particle with a texture. But I don't understand several things:
Why the "clickable" area of particle is so small? It works only if I click in the middle of a particle.
Why is that particle so huge? The texture is this .png file and the particle is way more bigger than 16×16. How can I fix that? Yes, I know about particle.scale, that will make particle look smaller. But, the "clickable" area of particle woukd also become smaller.
I know this is an old question but I came across the same issue today and i found this question unanswered, after some workaround i came across a solution for this.
The solution is to create 2 particles, one as a simple particle that draws a geometry (rect or arc) that is a ParticleCanvasMaterial and then the particle that displays the image on top of it.
So you can use the ParticleCanvasMaterial to track the intersections and display the other particle as a dummy object where it's only purpose is displaying an image on the 3D scene.
A little bit of code:
var programFill = function (context) {
context.beginPath();
context.rect(-0.5, -0.38, 1, 1);
//context.fill();
}
//creating particle to intersect with.
var p = new THREE.ParticleCanvasMaterial({ program: programFill, transparent: true });
var particle = new THREE.Particle(p);
particle.scale.set(23, 23);
//use same position for both particle and imgParticle
particle.position.set(200, 300, 200);
//creating particle that displays image.
var imgTexture = THREE.ImageUtils.loadTexture('images/image.png');
var p2 = new THREE.ParticleBasicMaterial({
map: imgTexture
, size: 1
});
var imgParticle = new THREE.Particle(p2);
imgParticle.scale.x = 0.5;
imgParticle.scale.y = 0.5;
imgParticle.position.set(200, 300, 200);