I am using the OrbitControls class and I am trying to replace the panUp function by panFront. My objective is to move the camera along the z axis.
I did the following change but seems that it doesn't work:
var panFront = function() {
var v = new THREE.Vector3();
return function panFront( distance, objectMatrix ) {
v.setFromMatrixColumn( objectMatrix, 2 ); // get Z column of objectMatrix
v.multiplyScalar( distance );
panOffset.add( v );
};
}();
Solution:
Edited
Set v.y = 0; to secure the pan only in xz axis.
var panFront = function() {
let v = new THREE.Vector3();
return function panFront( distance, objectMatrix ) {
v.setFromMatrixColumn( objectMatrix, 2 ); // get Z column of objectMatrix
v.y = 0;
v.multiplyScalar( -distance );
panOffset.add( v );
};
}();
I set the x and y components to 0.
Related
I am developing a shape drawing tool in threejs. I have drawn line, square, circle, rectangle, triangle, ellipse etc. on mouse down + drag successfully. Now i want editing of these shapes, such as scale, move and rotate. How do I get the corners and put points at corners where user can mouse+drag to scale , move points etc.
function onMouseMove(event) {
if(edit) {
event.preventDefault();
if ( selectedObject ) {
selectedObject.material.color.set( '#49BFFE' );
selectedObject = null;
}
var intersects = getIntersects( event );
if ( intersects.length > 0 ) {
var res = intersects.filter( function ( res ) {
return res && res.object;
} )[ 0 ];
if ( res && res.object ) {
selectedObject = res.object;
selectedObject.material.color.set( '#f00' );
console.log(selectedObject)
}
}
}
}
Code to create shapes is as follows:
this.onMouseDown = function( relative ) {
var geometry = new THREE.PlaneBufferGeometry( 1, 1);
var material = new THREE.MeshBasicMaterial({ color: 0x49BFFE, wireframe: false });
square = new THREE.Mesh(geometry, material);
scope.group.add(square);
}
this.onDrag = function( clientX, clientY ) {
var scale = Math.abs(clientX) + Math.abs(clientY);
if(scale == 0) scale = scale + 0.1;
square.scale.set(scale, scale, 0.1);
}
In three.js how can I know how much the camera has panned?
I need to keep an SVG layer on top of the three.js canvas aligned with the 3D objects as I pan the camera with orbitControls.
I use svg-pan-zoom for the SVG layer, and it works great for zooming.
For panning, I currently use the 'change' event and follow the camera accordingly like this (SVG and three.js setup omitted):
var unproject = function(vector3d, transform) {
var app = this.app;
var v = vector3d.clone();
v.project( app.three.camera );
v.x = Math.round( ( v.x + 1 ) * three_element.offsetWidth / 2 );
v.y = Math.round( ( - v.y + 1 ) * three_element.offsetHeight / 2 );
v.z = 0;
if (transform) {
var matrix = svg.node.getCTM().inverse()
var p = svg.node.createSVGPoint()
p.x = v.x; p.y = v.y
p = p.matrixTransform(matrix)
v.x = p.x
v.y = p.y
}
return v;
}
var panBy = function(d){ panZoom.panBy(d) };
var controls = new THREE.OrbitControls( camera, document.getElementById('canvas') );
var ref = unproject(new THREE.Vector3(0, 0, 0));
controls.addEventListener( 'change', function(e) {
var curr = unproject(new THREE.Vector3(0, 0, 0))
var dist = curr.clone().sub(ref)
panBy(dist)
ref = curr;
} );
But obviously the SVG panning lags a bit behind the 3D window panning. Is there a better way?
How to visualize the skeleton correctly? I feel like I did it right, but somehow the skeleton is not shown.
How do I specify the bone position/rotation in x,y,z axis in separate guiControls for each bone in GUI, such as "Bone_0.x" instead of "Bone_0"? Right now each 'Bone_0' is controlling both rotation and position... Or is there a way to control the bones by clicking on the bone, and use key input to translate/rotate, such as 'g'+'x' to translate the bone in x axis?
Thanks in advance!
//load file
let mixer;
const loader = new THREE.GLTFLoader();
loader.load('js/simple.gltf', function (gltf) {
scene.add(gltf.scene);
const model = gltf.scene;
mixer = new THREE.AnimationMixer(model);
gltf.animations.forEach((clip) => {
mixer.clipAction(clip).play();
});
helper = new THREE.SkeletonHelper(model);
helper.material.linewidth = 3;
helper.visible = true;
scene.add(helper);
////GUI
guiControls = new function () {
this.Bone_0 = 0.0;
this.Bone_1 = 0.0;
}
datGUI = new dat.GUI();
//datGUI.add(guiControls, "scene");
var folder = datGUI.addFolder('Controls');
folder.add(guiControls, 'Bone_0', -3.14, 3.14);
folder.add(guiControls, 'Bone_0', -3.14, 3.14);
folder.add(guiControls, 'Bone_1', -3.14, 3.14);
folder.add(guiControls, 'Bone_1', -3.14, 3.14);
});
//RENDER LOOP
render();
function render() {
controls.update();
var delta = 0.75 * clock.getDelta();
if (mixer) {
mixer.update(delta);
}
scene.traverse(function (child) {
if (child instanceof THREE.SkinnedMesh) {
child.position.y += .01;
child.skeleton.bones[0].position.y = guiControls.Bone_0;
child.skeleton.bones[0].rotation.y = guiControls.Bone_0;
child.skeleton.bones[1].position.y = guiControls.Bone_1;
child.skeleton.bones[1].rotation.y = guiControls.Bone_1;
}
});
renderer.render(scene, camera);
requestAnimationFrame(render);
};
Something wrong in the model, the code is correct.
Use Bone_0_x to specify axis, such as folder.add(guiControls, 'Bone_0_x', -3.14, 3.14);
Or:
var datGUI = new dat.GUI();
var folder = datGUI.addFolder('Controls');
var bones = skeleton.bones;
for ( var i = 0; i < bones.length; i++ ) {
folder.add( bones[i].position, 'x', 0, Math.PI * 2 ).name( 'bones[' + i + '].position.x' );
folder.add( bones[i].position, 'y', 0, Math.PI * 2 ).name( 'bones[' + i + '].position.y' );
// ...
}
https://discourse.threejs.org/t/bone-properties/4076
I for now work on a 3D engine for an amateur video game, and i want to add to him tons of features to get the best performances & gameplay. But i've got serious problems to get an "on click event" with lod meshes.
For the "lod" part, no problem for now, he's integrated, but i found no solution to apply that exemple with him :
https://stackoverflow.com/a/12808987/3379444
Did i must push the "lod" object, or every mesh individually ?
a part of my code :
var i, j, mesh, lod;
for ( j = 0; j <= 42; j++) {
lod = new THREE.LOD();
//Here, it's just var for place my mesh into a circle
rayonSysteme = Math.floor(Math.random() * 10000) + 2500;
angleSysteme = Math.random() * 360;
for ( i = 0; i < geometry.length; i ++ ) {
mesh = new THREE.Mesh( geometry[ i ][ 0 ], material );
mesh.scale.set( 1.5, 1.5, 1.5 );
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
//Callback here ?
mesh.callback = function() { console.log( this.name ); }
lod.addLevel( mesh, geometry[ i ][ 1 ] );
}
lod.position.x = rayonSysteme * Math.cos(angleSysteme);
lod.position.y = Math.floor(Math.random() * 1000)-500;
lod.position.z = rayonSysteme * Math.sin(angleSysteme);
//Or here ?
lod.updateMatrix();
lod.matrixAutoUpdate = false;
gravity = drawCircle("XZ", 64, 500, 360);
gravity.position.x = lod.position.x;
gravity.position.y = lod.position.y;
gravity.position.z = lod.position.z;
scene.add( lod );
//objects.push( lod );
scene.add( gravity );
};
Raycaster will test the mesh that should be used based on the distance to the ray origin.
This is the relevant code inside Raycaster:
} else if ( object instanceof THREE.LOD ) {
matrixPosition.setFromMatrixPosition( object.matrixWorld );
var distance = raycaster.ray.origin.distanceTo( matrixPosition );
intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );
}
I'm using Three.js and I wonder how to get all objects in a given area?
For example, get all objects that found in the green-square:
Solution:
getEntitiesInSelection: function(x, z, width, height, inGroup) {
var self = this,
entitiesMap = [],
color = 0,
colors = [],
ids = [],
pickingGeometry = new THREE.Geometry(),
pickingMaterial = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } ),
pickingScene = new THREE.Scene(),
pickingTexture = new THREE.WebGLRenderTarget( this._renderer.domElement.width, this._renderer.domElement.height),
cloneMesh,
entities = inGroup ?
engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities();
pickingTexture.generateMipmaps = false;
//Go over each entity, change its color into its ID
_.forEach(entities, function(entity) {
if(undefined == entity.threeRenderable) {
return ;
}
//Clone entity
cloneMesh = entity.threeRenderable.mesh().clone();
cloneMesh.material = entity.threeRenderable.mesh().material.clone();
cloneMesh.material.map = null;
cloneMesh.material.vertexColors = THREE.VertexColors;
cloneMesh.geometry = entity.threeRenderable.mesh().geometry.clone();
cloneMesh.position.copy( entity.threeRenderable.mesh().position );
cloneMesh.rotation.copy( entity.threeRenderable.mesh().rotation );
cloneMesh.scale.copy( entity.threeRenderable.mesh().scale );
//Cancel shadow
cloneMesh.castShadow = false;
cloneMesh.receiveShadow = false;
//Set color as entity ID
entitiesMap[color] = entity.id();
self._applyVertexColors(cloneMesh.geometry, new THREE.Color( color ) );
color++;
THREE.GeometryUtils.merge( pickingGeometry, cloneMesh);
});
pickingScene.add( new THREE.Mesh( pickingGeometry, pickingMaterial ) );
//render the picking scene off-screen
this._renderer.render(pickingScene, this._objs[this._mainCamera], pickingTexture );
var gl = this._renderer.getContext();
//read the pixel under the mouse from the texture
var pixelBuffer = new Uint8Array( 4 * width * height );
gl.readPixels( x, this._renderer.domElement.height - z, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixelBuffer );
//Convert RGB in the selected area back to color
for(var i=0; i<pixelBuffer.length; i+=4) {
if( 0 == pixelBuffer[i] && 0 == pixelBuffer[i+1] && 0 == pixelBuffer[i+2] && 0 == pixelBuffer[i+3] ) {
continue;
}
color = ( pixelBuffer[i] << 16 ) | ( pixelBuffer[i+1] << 8 ) | ( pixelBuffer[i+2] );
colors.push(color);
}
colors = _.unique(colors);
//Convert colors to ids
_.forEach(colors, function(color) {
ids.push(entitiesMap[color]);
});
return ids;
}
The line engine.getObjectsByGroup(inGroup) : engine.getRegisteredEntities();
just return an array of entities, which in turn, I iterate over the entities:
_.forEach(entities, function(entity) { ...
Only entities that have the 'threeRenderable' property (object) are visible, therefore, I ignore those that doesn't have it:
if(undefined == entity.threeRenderable) {
return ;
}
then I merge the entity's cloned mesh with with the pickingGeometry:
THREE.GeometryUtils.merge( pickingGeometry, cloneMesh);
eventually, I add the pickingGeometry to the pickingScene:
pickingScene.add( new THREE.Mesh( pickingGeometry, pickingMaterial ) );
Then I read the colors of the selected area, and return an array of IDs.
You can checkout the Node.js game engine I wrote back then.
I've wanted to implement something like this and I choose a very different method - maybe much worse, I don't really know - but much easier to do IMO, so I put it here in case someone wants it.
Basically, I used only 2 raycasts to know the first and last points of the selection rectangle, projected on my ground plane, and iterate over my objects to know which ones are in.
Some very basic code:
function onDocumentMouseDown(event) {
// usual Raycaster stuff ...
// get the ground intersection
var intersects = raycaster.intersectObject(ground);
GlobalGroundSelection = {
screen: { x: event.clientX, y: event.clientY },
ground: intersects[0].point
};
}
function onDocumentMouseUp(event) {
// ends a ground selection
if (GlobalGroundSelection) {
// usual Raycaster stuff ...
// get the ground intersection
var intersects = raycaster.intersectObjects(ground);
var selection = {
begins: GlobalGroundSelection.ground,
ends: intersects[0].point
};
GlobalGroundSelection = null;
selectCharactersInZone(selection.begins, selection.ends);
}
}
function onDocumentMouseMove(event) {
if (GlobalGroundSelection) {
// in a selection, draw a rectangle
var p1 = GlobalGroundSelection.screen,
p2 = { x: event.clientX, y: event.clientY };
/* with these coordinates
left: p1.x > p2.x ? p2.x : p1.x,
top: p1.y > p2.y ? p2.y : p1.y,
width: Math.abs(p1.x - p2.x),
height: Math.abs(p1.y - p2.y)
*/
}
}
Here is my select function:
function selectCharactersInZone (start, end) {
var selected = _.filter( SELECTABLE_OBJECTS , function(object) {
// warning: this ignore the Y elevation value
var itsin = object.position.x > start.x
&& object.position.z > start.z
&& object.position.x < end.x
&& object.position.z < end.z;
return itsin;
});
return selected;
}
Some warnings: as far as I know, this technique is only usable when you don't care about Y positions AND your selection is a basic rectangle.
My 2c