threejs moving one object moves the second too - three.js

I'm new at three.js and i need to load a particle object
it seems to work if add particles on vertices
if i load my Json once and inside the load() i build 2 different THREE.Points when i mov the first the second also move if i use 2 different load calls and inside each i build a THREE.Points object each object can be move separately
this i the function that i call inside animate
function animateParticles( particleSystem, particleSystemOriginal, deltaTime ) {
var vertsOriginal = particleSystemOriginal.geometry.vertices;
var verts = particleSystem.geometry.vertices;
for(var i = 0; i < verts.length; i++) {
var vertOriginal = vertsOriginal[i]; // original position
var vert = verts[i]; // cloud position
var vertOriginalY = vertOriginal.y;
var vertY = vert.y;
if (i==1) console.log("vertOriginalY " + vertOriginalY + " vertY " + vertY);
vert.y = vertY - (10 * deltaTime); // move
}
particleSystemOriginal.geometry.verticesNeedUpdate = true;
particleSystem.geometry.verticesNeedUpdate = true;
}
it seems strange that i need to load twice the same object to move one of them
in my function i'just testing and moving dove the cloud but what i would like is to shake particles, so i need to know particles original position and set particles new position
EDIT 1
i load my model like this
var loader = new THREE.JSONLoader();
loader.load('../3d-models/creati/mymodel-001.json', function(geometry, materials) {
var material = new THREE.MeshNormalMaterial();
var particleModelOriginal = new THREE.Mesh( geometry, material );
var particleModel = particleModelOriginal.clone();
/* build particle THREE.Points */
particleSystemOriginal = new THREE.Points(particlesOriginal, particleMaterial);
particleSystem = new THREE.Points(particles, particleMaterial);
//particleSystem = particleSystemOriginal.clone();
});

If I understand the question correctly, you probably need to use the .clone() object on mesh/geometry that you are loading if you want to create multiple objects. Otherwise they are referencing the same object, and will both be modified if one of them are modified

Related

Applying user selected texture to cube dynamically?

I’m creating a cube using BoxGeometry and trying to apply the texture that the user has selected.
I created a raycaster and use that raycaster inside mouse down event.
If the object is present on cursor position then a user-selected texture gets applied.
So far everything works fine. Then I added if-else condition if the user selected single image for texture then that single image gets applied whole body of cube means all side of cube gets that image as material.
But if the user selected more than one image suppose three images then only three sides of cube get material. This also works very well.
Now a real problem when user select only three images remaining side of cube remain black and that is okay only material applied side shows up.
Now if user again select a single image and click on cube to apply single image texture to whole body again ray caster gives problem that
“–can’t access property “side”, the material is undefined–”
so this callback function which i added to select image files
function onUserMultiImageSelect(event) {
materialArray.length = 0;
length = multiFilesId.files.length;
let multiImage = [];
let userMultiImageUrl = [];
for (let i = 0; i < length; i++) {
multiImage[i] = multiFilesId.files.item(i);
userMultiImageUrl[i] = URL.createObjectURL(multiImage[i]);
materialArray.push(new THREE.MeshBasicMaterial({ map: new THREE.TextureLoader().load(userMultiImageUrl[i]),side:THREE.DoubleSide }));
}
isTextureSelect = true;
}
this is callback funtion which gets called when i click on object to apply texture
function onMouseButtonDown(event) {
var rect = event.target.getBoundingClientRect();
var mouseX = event.clientX - rect.left;
var mouseY = event.clientY - rect.top;
let raycaster = new THREE.Raycaster();
let pointer = new THREE.Vector2();
pointer.set((mouseX / window.innerWidth) * 2 - 1, -(mouseY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
var curentIntersectedObject = intersects[0];
if (isTextureSelect) {
if(length == 1){
var singleImage = multiFilesId.files.item(0);
var singleImageUrl = URL.createObjectURL(singleImage);
var texture = new THREE.TextureLoader().load(singleImageUrl);
curentIntersectedObject.object.material = new THREE.MeshBasicMaterial({ map: texture });
}else{
curentIntersectedObject.object.material = materialArray;
}
}
}
}

THREE.js planeGeometry clipped inside the walls

I want to make a 3D building using Three.js. For example, I made 6 walls and a floor by checkerboard texture. I used clippingPlanes for wall1 and wall4:
floor1.material.clippingPlanes = [plane1,plane4];
I made my planes(plane1 and plane4) by my walls(wall1 and wall4). For example, my wall4 planeGeometry and plane4 code is here:
var wallGeometry4 = new THREE.PlaneGeometry(40, Floor_Height, 1, 1);
var wall4 = createMesh(wallGeometry4, "brick_diffuse.jpg", THREE.DoubleSide, 1024, 1024);
unit1.add(wall4);
wall4.position.x = -10;
wall4.position.y = 0;
wall4.position.z = -20;
wall4.rotation.y = 1.5 * Math.PI;
wall4.add(new THREE.EdgesHelper(wall4, 0x000000));
var plane4 = new THREE.Plane();
var normal4 = new THREE.Vector3();
var point4 = new THREE.Vector3();
normal4.set(0, 0, -1).applyQuaternion(wall4.quaternion);
point4.copy(wall4.position);
plane4.setFromNormalAndCoplanarPoint(normal4, point4);
But I see an empty area between wall5 and wall6, because plane4(that used for clipping the floor) isn't the same size of wall4. I think Plane4 is whole of the scene. How to change size of my plane to clip correctly? Or Is there any way to make floor bounded in walls?
One way to achieve this as suggested is to use ShapeGeometry.
When you are creating your walls you can save the x and z co-ordinate of their starting and ending points in an array to form a loop of points of Vector2. Then you can create a new custom shape from these points using shapeGeometry.
points = [{x:0,y:0},{x:0,y:10},{x:10,y:10},{x:10,y:0},{x:0,y:0}]
function getShapeFromPoints(points){
const shape = new THREE.Shape();
shape.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
shape.lineTo(points[i].x, points[i].y);
}
return shape;
}
function createPlaneFromPoints(points) {
const planeMaterial = getPlaneMaterial();
const shape = getShapeFromPoints(points);
const geometry = new THREE.ShapeBufferGeometry(shape);
geometry.rotateX(degreeToRadians(-90));
const mesh = new THREE.Mesh(geometry, planeMaterial);
return mesh;
}
Hope that helps you!

How to center a THREE.Group based on the width of its children?

I am using Three.js (r86) to render a group of spheres. I'm doing this by calling a createSphereGroup function from an external library:
// External library code.
function createSphereGroup(sphereCount) [
var group = new THREE.Group();
for (var i = 0; i < sphereCount; i++) {
var geometry = new THREE.SphereGeometry(50);
var material = new THREE.MeshPhongMaterial({ color: 0xFF0000 });
var sphere = new THREE.Mesh(geometry, material);
sphere.position.x = i * 150;
group.add(sphere);
}
return group;
}
(Assume that the external library is opaque and I can't modify any code in it.)
// My code.
var group = createSphereGroup(5);
scene.add(group);
...which looks like this:
As you can see, the group of spheres is off-center. I would like the group to be centered, with the 3rd sphere to be at the point where the two lines intersect (x=0).
So is there some way that I can center the group? Maybe by computing the width of the group and then positioning it at x=-width/2? I know you can call computeBoundingBox on a geometry to determine its width, but a THREE.Group doesn't have a geometry, so I'm not sure how to do this...
If you want to center an object (or group) and its children, you can do so by setting the object's position like so:
new THREE.Box3().setFromObject( object ).getCenter( object.position ).multiplyScalar( - 1 );
scene.add( object );
three.js r.92
If all the objects in the group are rather the same size, as in your example, you can just take the average of the children's position :
function computeGroupCenter(count) {
var center = new THREE.Vector3();
var children = group.children;
var count = children.length;
for (var i = 0; i < count; i++) {
center.add(children[i].position);
}
center.divideScalar(count);
return center;
}
Another (more robust) way to do this is to create a bounding box containing the bounding boxes of every child of the group.
There are some important things to consider :
A THREE.Group can contain others THREE.Group, therefore, the algorithm should be recursive
Bounding boxes are computed in local space, however, the object may be translated with respect to its parent so we need to work in world space.
The group itself might be translated to! So we have to jump back from world space to the group's local space to have the center defined in the group's space.
You can achieve this easily with THREE.Object3D.traverse, THREE.BufferGeometry.computeBoundingBox and THREE.Box3.ApplyMatrix4 :
/**
* Compute the center of a THREE.Group by creating a bounding box
* containing every children's bounding box.
* #param {THREE.Group} group - the input group
* #param {THREE.Vector3=} optionalTarget - an optional output point
* #return {THREE.Vector3} the center of the group
*/
var computeGroupCenter = (function () {
var childBox = new THREE.Box3();
var groupBox = new THREE.Box3();
var invMatrixWorld = new THREE.Matrix4();
return function (group, optionalTarget) {
if (!optionalTarget) optionalTarget = new THREE.Vector3();
group.traverse(function (child) {
if (child instanceof THREE.Mesh) {
if (!child.geometry.boundingBox) {
child.geometry.computeBoundingBox();
childBox.copy(child.geometry.boundingBox);
child.updateMatrixWorld(true);
childBox.applyMatrix4(child.matrixWorld);
groupBox.min.min(childBox.min);
groupBox.max.max(childBox.max);
}
}
});
// All computations are in world space
// But the group might not be in world space
group.matrixWorld.getInverse(invMatrixWorld);
groupBox.applyMatrix4(invMatrixWorld);
groupBox.getCenter(optionalTarget);
return optionalTarget;
}
})();
Here's a fiddle.

Attach text above 3D Object

I'm wondering if it is possible to attach a text above a 3D Object?
If so, how would I do it?
So far I'm doing the following below to load a mesh with its material and lastly adding it to a THREE.Object3D(); and adding it to the scene. Works great without any problems.
Next step is I want to show a nice text above its this object that is always fixed and can be seen from every angle.
loader.load('assets/' + enemyUrl, function (geometry, materials) {
material = new THREE.MeshFaceMaterial( materials );
model = new THREE.SkinnedMesh( geometry, material );
var mats = model.material.materials;
for (var i = 0,length = mats.length; i < length; i++) {
var m = mats[i];
m.skinning = true;
}
ensureLoop(geometry.animations[0]);
function ensureLoop( tmp ) {
for ( var i = 0; i < tmp.hierarchy.length; i ++ ) {
var bone = tmp.hierarchy[ i ];
var first = bone.keys[ 0 ];
var last = bone.keys[ bone.keys.length - 1 ];
last.pos = first.pos;
last.rot = first.rot;
last.scl = first.scl;
}
}
model.scale.set(2.5,2.5,2.5);
// TODO: Randomize where to put it in the world
yawObject.position.y = spawnPosition.y;
yawObject.position.x = spawnPosition.x;
yawObject.position.z = spawnPosition.z;
yawObject.add(model);
scene.add(yawObject);
});
Something like this:
This is what my game looks like now:
sure its possible. you can create a canvas with text on it, which you use as a texture on a plane that always looks at the camera, you could also do it with a single particle, but im not quite sure how it would work since particle materials have a size parameter. something like:
var name = 'Rovdjuret';
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
ctx.font="20px Georgia";
ctx.fillText(name,10,50);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true; //just to make sure it's all up to date.
var label = new THREE.Mesh(new THREE.PlaneGeometry, new THREE.MeshBasicMaterial({map:texture}));
and inside the render/animation loop you make all the labels look at the camera:
label.lookAt(camera.position);

Three.js Child Mesh position always return (0,0,0)

I loaded some objects via OBJLoader , loaded object contain one parent and multiple childs; then I apply Raycaster and find clicked child.
Then I want to update position of child object, but initial position comes zero for all childs.
var intersect = intersects[0].object;
intersect.position.y = intersect.position.y + 5; // 0 + 5
But in my scene all looks fine. Also, If i remove clicked object, actually it is removed from scene. I think I missed some point their positions cant be (0,0,0). How can I reach their relative position ?
Here is code for set Transform Control into center of object:
let objLoader = new THREE.OBJLoader();
let mtlLoader = new THREE.MTLLoader();
mtlLoader.load("path/to/mtlFile.mtl", (materials) => {
materials.preload();
objLoader.setMaterials(materials).load("path/to/objFile.obj", (object3d) => {
object3d.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.geometry.computeBoundingBox();
let matrix = new THREE.Vector3();
let offset = child.geometry.boundingBox.getCenter(matrix);
child.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-offset.x, -offset.y, -offset.z));
child.position.copy(offset);
objects.push(child);
}
});
scene.add(object3d);
});
});
Try this, then read position(). I had the same issue, got an answer here. (geom is your Meshes geometry.)
objMesh.centroid = new THREE.Vector3();
for (var i = 0, l = geom.vertices.length; i < l; i++) {
objMesh.centroid.add(geom.vertices[i].clone());
}
objMesh.centroid.divideScalar(geom.vertices.length);
var offset = objMesh.centroid.clone();
objMesh.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-offset.x, -offset.y, -offset.z));
objMesh.position.copy(objMesh.centroid);
The position is relative to the parent
Multiply the position by the transform of the parent to get the world-space coordinates, if that's what you're seeking
That is because all objects in a obj file have their pivot at World 0,0,0
no matter where their local pivot was before exporting.

Resources