I'm working in 1.4.2 three.js version. I rotate mesh and want to get it updated vertexes. Here is my codepen: https://codepen.io/DYDOI-NSK/pen/mdxLpoB?editors=1012
Here is my code:
I have a function at 37 line, that allow me create planes.
/* generatePlanes - function that create planes.
* #param data - config for creating planes, must contain name,
* count of creating planes, width and height.
* #return planeHolder - array with created planes.
*/
let generatePlanes = (data) => {
let planeHolder = [];
for (let i = 0; i < data.count; i++) {
let geometry = new THREE.PlaneGeometry( data.width, data.height );
let material = new THREE.MeshBasicMaterial( {color: new THREE.Color('#cea6a6'), side: THREE.DoubleSide} );
let plane = new THREE.Mesh( geometry, material );
plane.name = data.name;
plane.isConnected = false;
planeHolder.push(plane);
}
return planeHolder;
}
I created two planes with generatePlanes function at 53 line
// Creating 2 planes
let planes = generatePlanes({
name: 'centerPlane',
count: 2,
width: 30,
height: 30,
});
At lines 63-67 I described problem. I logged plane.geometry.attributes.position.array(here contains vertexes position of mesh) before and after rotation.
// Check verticies before rotating
console.log(planes[0].geometry.attributes.position.array);
// Rotate first plane with rotateX method
planes[0].rotateX(Math.PI / 4);
// Check verticies after rotating
console.log(planes[0].geometry.attributes.position.array);
Result of logs:
Before rotation:
Float32Array(12)
0: -15
1: 15
2: 0
3: 15
4: 15
5: 0
6: -15
7: -15
8: 0
9: 15
10: -15
11: 0
After rotation:
Float32Array(12)
0: -15
1: 15
2: 0
3: 15
4: 15
5: 0
6: -15
7: -15
8: 0
9: 15
10: -15
11: 0
Vertexes having the same position, i need to update them, but i don't know how.
Since the goal is to calculate the angle between two planes, a better solution than looking at the position attribute is to create a vector representing the plane and apply the same rotation to the vector. There is already a solid answer for this here: https://stackoverflow.com/a/45309019/4536210
As to why the position attribute doesn't change when rotating, this is because the rotation is applied to the Mesh while the Geometry is always kept intact. The position attribute represents an intrinsic property of the geometry, representing the arrangement of its vertices in space, they're never modified by transforming the object.
Related
What I'm trying to achieve is a rotation of the geometry around pivot point and make that the new definition of the geometry. I do not want te keep editing the rotationZ but I want to have the current rotationZ to be the new rotationZ 0.
This way when I create a new rotation task, it will start from the new given pivot point and the newly given rad.
What I've tried, but then the rotation point moves:
// Add cube to do calculations
var box = new THREE.Box3().setFromObject( o );
var size = box.getSize();
var offsetZ = size.z / 2;
o.geometry.translate(0, -offsetZ, 0)
// Do ratation
o.rotateZ(CalcUtils.degreeToRad(degree));
o.geometry.translate(0, offsetZ, 0)
I also tried to add a Group and rotate that group and then remove the group. But I need to keep the rotation without all the extra objects. The code I created
var box = new THREE.Box3().setFromObject( o );
var size = box.size();
var geometry = new THREE.BoxGeometry( 20, 20, 20 );
var material = new THREE.MeshBasicMaterial( { color: 0xcc0000 } );
var cube = new THREE.Mesh( geometry, material );
cube.position.x = o.position.x;
cube.position.y = 0; // Height / 2
cube.position.z = -size.z / 2;
o.position.x = 0;
o.position.y = 0;
o.position.z = size.z / 2;
cube.add(o);
scene.add(cube);
// Do ratation
cube.rotateY(CalcUtils.degreeToRad(degree));
// Remove cube, and go back to single object
var position = o.getWorldPosition();
scene.add(o)
scene.remove(cube);
console.log(o);
o.position.x = position.x;
o.position.y = position.y;
o.position.z = position.z;
So my question, how do I save the current rotation as the new 0 rotation point. Make the rotation final
EDIT
I added an image of what I want to do. The object is green. I have a 0 point of the world (black). I have a 0 point of the object (red). And I have rotation point (blue).
How can I rotate the object around the blue point?
I wouldn't recommend updating the vertices, because you'll run into trouble with the normals (unless you keep them up-to-date, too). Basically, it's a lot of hassle to perform an action for which the transformation matrices were intended.
You came pretty close by translating, rotating, and un-translating, so you were on the right track. There are some built-in methods which can help make this super easy.
// obj - your object (THREE.Object3D or derived)
// point - the point of rotation (THREE.Vector3)
// axis - the axis of rotation (normalized THREE.Vector3)
// theta - radian value of rotation
// pointIsWorld - boolean indicating the point is in world coordinates (default = false)
function rotateAboutPoint(obj, point, axis, theta, pointIsWorld){
pointIsWorld = (pointIsWorld === undefined)? false : pointIsWorld;
if(pointIsWorld){
obj.parent.localToWorld(obj.position); // compensate for world coordinate
}
obj.position.sub(point); // remove the offset
obj.position.applyAxisAngle(axis, theta); // rotate the POSITION
obj.position.add(point); // re-add the offset
if(pointIsWorld){
obj.parent.worldToLocal(obj.position); // undo world coordinates compensation
}
obj.rotateOnAxis(axis, theta); // rotate the OBJECT
}
After this method completes, the rotation/position IS persisted. The next time you call the method, it will transform the object from its current state to wherever your inputs define next.
Also note the compensation for using world coordinates. This allows you to use a point in either world coordinates or local space by converting the object's position vector into the correct coordinate system. It's probably best to use it this way any time your point and object are in different coordinate systems, though your observations may differ.
As a simple solution for anyone trying to quickly change the pivot point of an object, I would recommend creating a group and adding the mesh to the group, and rotating around that.
Full example
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube)
Right now, this will just rotate around its center
cube.rotation.z = Math.PI / 4
Create a new group and add the cube
const group = new THREE.Group();
group.add(cube)
scene.add(group)
At this point we are back where we started. Now move the mesh:
cube.position.set(0.5,0.5,0)
Then move the group
group.position.set(-0.5, -0.5, 0)
Now use your group to rotate the object:
group.rotation.z = Math.PI / 4
I'm creating a three.js app which consists of floor (which is composed of different tiles) and shelving units (more than 5000...). I'm having some performance issues and low FPS (lower then 20), and I think it is because I'm creating a separate mesh for every tile and shelving unit. I know that I can leverage geometry/mesh merging in order to improve performance. This is the code for rendering the floor and shelving units (cells):
// add ground tiles
const tileGeometry = new THREE.PlaneBufferGeometry(
1,
1,
1
);
const edgeGeometry = new THREE.EdgesGeometry(tileGeometry);
const edges = new THREE.LineSegments(edgeGeometry, edgeMaterial);
let initialMesh = new THREE.Mesh(tileGeometry, floorMat);
Object.keys(groundTiles).forEach((key, index) => {
let tile = groundTiles[key];
let tileMesh = initialMesh.clone();
tileMesh.position.set(
tile.leftPoint[0] + tile.size[0] / 2,
tile.leftPoint[1] + tile.size[1] / 2,
0
);
tileMesh.scale.x = tile.size[0];
tileMesh.scale.y = tile.size[1];
tileMesh.name = `${tile.leftPoint[0]}-${tile.leftPoint[1]}`;
// Add tile edges (adds tile border lines)
tileMesh.add(edges.clone());
scene.add(tileMesh);
});
// add shelving units
const cellGeometry = new THREE.BoxBufferGeometry( 790, 790, 250 );
const wireframe = new THREE.WireframeGeometry( cellGeometry );
const cellLine = new THREE.LineSegments(wireframe, shelves_material);
Object.keys(cells).forEach((key, index) => {
let cell = cells[key];
const cellMesh = cellLine.clone();
cellMesh.position.set(
cell["x"] + 790 / 2,
// cell["x"],
cell["y"] + 490 / 2,
cell["z"] - 250
);
scene.add(cellMesh);
});
Also, here is a link to a screenshot from the final result.
I saw this article regarding merging of geometries, but I don't know how to implement it in my case because of the edges, line segments and wireframe objects I'm using..
Any help would be appriciated
Taking into account #Mugen87's comment, here's a possible approach :
Pretty straightforward merging of planes
Using a shader material to draw "borders"
Note : comment out the discard; line to fill the cards with red or whatever material you might want.
JsFiddle demo
I have a group of twenty or so lines that form a shape (a "birdy"). I am currently rotating it about the y-axis (the "up" axis) on top of a "mirror". Here is a screen shot:
[]
What I want to do is to project the rotating shape onto the xy plane of the "mirror" (the yellow plane) by just ignoring the z components, so you would just see the 2-d projection of the shape as it looks from its side.
Currently, my plan of attack is as follows:
Keep the original shape in 3-d form so the rotation works right. I think if I reduce it down to 2-d at this point, the rotation won't be right.
Rotate the 3-d archetype about the y-axis, but don't display it yet. Here is the code I'm currently using:
var m = new THREE.Matrix4();
var mat = m.makeRotationY(base.ONE_DEGREE * 0.2);
birdyGroupClone.applyMatrix(mat);
loop over the individual lines in the group, via the children array, and go to geometry.vertices and set the z component to zero.
Finally, render these 2-d lines (as a group) onto the xy-plane.
I am stuck on point 3, because when I multiply the lines in the group by the matrix, I can't find the intermediary rotation results anywhere in the heirarchy. The vertices themselves are not affected -- they still contain the canonical "root" values.
Here is the relevant portion of object heirarchy for the group taken from a Chrome browser debug session:
birdyGroupClone: THREE.Object3D
__webglActive: true
__webglInit: true
_listeners: Object
_modelViewMatrix: THREE.Matrix4
_normalMatrix: THREE.Matrix3
castShadow: false
children: Array[30]
0: THREE.Line
1: THREE.Line
2: THREE.Line
3: THREE.Line
4: THREE.Line
__webglActive: true
__webglInit: true
_listeners: Object
_modelViewMatrix: THREE.Matrix4
_normalMatrix: THREE.Matrix3
castShadow: false
children: Array[0]
eulerOrder: (...)
frustumCulled: true
geometry: THREE.Geometry
__colorArray: Float32Array[6]
__lineDistanceArray: Float32Array[2]
__vertexArray: Float32Array[6]
__webglColorBuffer: WebGLBuffer
__webglInit: true
__webglLineCount: 2
__webglLineDistanceBuffer: WebGLBuffer
__webglVertexBuffer: WebGLBuffer
_listeners: Object
boundingBox: null
boundingSphere: THREE.Sphere
colors: Array[0]
colorsNeedUpdate: false
dynamic: true
elementsNeedUpdate: false
faceVertexUvs: Array[1]
faces: Array[0]
groupsNeedUpdate: false
hasTangents: false
id: 160
lineDistances: Array[0]
lineDistancesNeedUpdate: false
morphColors: Array[0]
morphNormals: Array[0]
morphTargets: Array[0]
name: ""
normalsNeedUpdate: false
skinIndices: Array[0]
skinWeights: Array[0]
tangentsNeedUpdate: false
type: "Geometry"
uuid: "55EE9F6D-0DD1-4581-817A-9E324EC5225D"
uvsNeedUpdate: false
vertices: Array[2]
0: THREE.Vector3 <-- These are unaffected by the rotation
x: -0.616 <-- Where are the transformed coordinates?
y: 0
z: -0.68
__proto__: THREE.Vector3
1: THREE.Vector3
x: -0.5
y: 0
z: -1
__proto__: THREE.Vector3
length: 2
This is after doing a rotation. Where are the intermediate post-transform vertices, that I can use for my transformation "pipeline" (to take the 2-d projections). Are they only reflected in the browser canvas?
So my questions are:
Does my plan of attack sound right, or is there a better way to do it?
If yes, how do I access the results of intermediate transformations so I can apply a "pipeline" of processing?
Many thanks.
Based on the input from WestLangley, I was able to find a solution. Now that I've done it, I realize a better of way of describing my problem was to say I wanted to project the shadow of the 3-d object onto a plane.
I basically have to do a transform on each of the line geometries individually as opposed to doing it on the group. Doing it this way, the vertices of the lines are updated, and then I can proceed with the rest of my plan as described previously.
Here is what my code looks like:
var m = new THREE.Matrix4();
var mat = m.makeRotationY(base.ONE_DEGREE * 0.2);
// loop over each line and transform individually
// I had to do in a try catch block, but you shouldn't have to
try {
for (var i = 0; i < birdyGroupClone.children.length; i++) {
birdyGroupClone.children[i].geometry.applyMatrix(mat);
}
}
catch (e) {
console.log('caught error=' + e);
return false;
}
// Create a new Shadow group on each render
// make sure to remove the old one, otherwise your frame rate will bog down
// I had trouble with simply cloning birdyGroupClone because it doesn't
// deep copy all the way down (?), so I create anew each time.
factory.xyProjectionMesh.remove(birdyGroupShadow);
birdyGroupShadow = new THREE.Object3D();
//remove z-component from birdyGroupClone vertices
for (var i = 0; i < birdyGroupClone.children.length; i++) {
var line, lineGeometry, vertex;
lineGeometry = new THREE.Geometry();
vertex = birdyGroupClone.children[i].geometry.vertices[0];
lineGeometry.vertices.push(new THREE.Vector3(vertex.x, vertex.y, 0));
vertex = birdyGroupClone.children[i].geometry.vertices[1];
lineGeometry.vertices.push(new THREE.Vector3(vertex.x, vertex.y, 0));
line = new THREE.Line(lineGeometry, birdyGroupClone.children[i].material);
birdyGroupShadow.add(line);
};
birdyGroupShadow.position.set(0, 0, 0);
this.xyProjectionMesh.add(birdyGroupShadow);
Maybe this isn't the prettiest way to do it, but it works.
Here is a screen shot showing the result. Note, how it exists entirely on the plane and has no "depth":
I'm new to threejs
I need to draw a sphere connected with triangles. I use Icosahedron to construct the sphere in the following way
var material = new THREE.MeshPhongMaterial({
emissive : 0xffffff,
transparent: true,
opacity : 0.5,
wireframe : true
});
var icogeo = new THREE.IcosahedronGeometry(80,2);
var mesh = new THREE.Mesh(icogeo, material);
scean.add(mesh);
But i need the width of the line to be more but line width won't show up in windows so i taught of looping through the vertices and draw a cylinder/tube between the vertices. (I can't draw lines because the LineBasicMaterial was not responding to Light.)
for(i=0;i<icogeo.faces.length;i++){
var face = icogeo.faces[i];
//get vertices from face and draw cylinder/tube between the three vertices
}
Can some one please help on drawing the tube/cylinder between two vector3 vertices?
**the problem i'm facing with wireframe was it was not smooth and i can't increase width of it in windows.
If you really want to create a cylinder between two points one way to do is to create it in a unit space and then transform it to your line. But that is very mathy.
An intuitive way to create it is to think about how would you do it in a unit space? A circle around the z axis (in x,y) and another one a bit down z.
Creating a circle in 2d is easy: for ( angle(0,360,360/numsteps) ) (x,y)=(sin(angle),cos(angle))*radius. (see for example Calculating the position of points in a circle).
Now the two butt ends of your cylinder are not in x,y! But If you have two vectors dx,dy you can just multiply your x,y with them and get a 3d position!
So how to get dx, dy? One way is http://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
which reads way more scary than it is. You start with your forward direction, which is your line. forward = normalize(end-start). Then you just pick a direction "up". Usually (0,1,0). Unless forward is already close to up, then pick another one like (1,0,0). Take their cross product. This gives you "left". Then take the cross product between "left" and "forward" to get "right". Now "left" and "right" are you dx and dy!
That way you can make two circles at the two ends of your line. Add triangles in between and you have a cylinder!
Even though I do believe it is an overkill for what you are trying to achieve, here is code that draws a capsule (cylinder with spheres at the end) between two endpoints.
/**
* Returns a THREE.Object3D cylinder and spheres going from top to bottom positions
* #param radius - the radius of the capsule's cylinder
* #param top, bottom - THREE.Vector3, top and bottom positions of cone
* #param radiusSegments - tessellation around equator
* #param openTop, openBottom - whether the end is given a sphere; true means they are not
* #param material - THREE.Material
*/
function createCapsule (radius, top, bottom, radiusSegments, openTop, openBottom, material)
{
radiusSegments = (radiusSegments === undefined) ? 32 : radiusSegments;
openTop = (openTop === undefined) ? false : openTop;
openBottom = (openBottom === undefined) ? false : openBottom;
var capsule = new THREE.Object3D();
var cylinderAxis = new THREE.Vector3();
cylinderAxis.subVectors (top, bottom); // get cylinder height
var cylinderGeom = new THREE.CylinderGeometry (radius, radius, cylinderAxis.length(), radiusSegments, 1, true); // open-ended
var cylinderMesh = new THREE.Mesh (cylinderGeom, material);
// get cylinder center for translation
var center = new THREE.Vector3();
center.addVectors (top, bottom);
center.divideScalar (2.0);
// pass in the cylinder itself, its desired axis, and the place to move the center.
makeLengthAngleAxisTransform (cylinderMesh, cylinderAxis, center);
capsule.add (cylinderMesh);
if (! openTop || ! openBottom)
{
// instance geometry
var hemisphGeom = new THREE.SphereGeometry (radius, radiusSegments, radiusSegments/2, 0, 2*Math.PI, 0, Math.PI/2);
// make a cap instance of hemisphGeom around 'center', looking into some 'direction'
var makeHemiCapMesh = function (direction, center)
{
var cap = new THREE.Mesh (hemisphGeom, material);
makeLengthAngleAxisTransform (cap, direction, center);
return cap;
};
// ================================================================================
if (! openTop)
capsule.add (makeHemiCapMesh (cylinderAxis, top));
// reverse the axis so that the hemiCaps would look the other way
cylinderAxis.negate();
if (! openBottom)
capsule.add (makeHemiCapMesh (cylinderAxis, bottom));
}
return capsule;
}
// Transform object to align with given axis and then move to center
function makeLengthAngleAxisTransform (obj, align_axis, center)
{
obj.matrixAutoUpdate = false;
// From left to right using frames: translate, then rotate; TR.
// So translate is first.
obj.matrix.makeTranslation (center.x, center.y, center.z);
// take cross product of axis and up vector to get axis of rotation
var yAxis = new THREE.Vector3 (0, 1, 0);
// Needed later for dot product, just do it now;
var axis = new THREE.Vector3();
axis.copy (align_axis);
axis.normalize();
var rotationAxis = new THREE.Vector3();
rotationAxis.crossVectors (axis, yAxis);
if (rotationAxis.length() < 0.000001)
{
// Special case: if rotationAxis is just about zero, set to X axis,
// so that the angle can be given as 0 or PI. This works ONLY
// because we know one of the two axes is +Y.
rotationAxis.set (1, 0, 0);
}
rotationAxis.normalize();
// take dot product of axis and up vector to get cosine of angle of rotation
var theta = -Math.acos (axis.dot (yAxis));
// obj.matrix.makeRotationAxis (rotationAxis, theta);
var rotMatrix = new THREE.Matrix4();
rotMatrix.makeRotationAxis (rotationAxis, theta);
obj.matrix.multiply (rotMatrix);
}
I have a cube of size 1 x 1 x 2. On the larger (1 x 2) face, I would like to show one color on half of the face, and another color on the other half. What would the recommended way of implementing this? Should I use hierarchy to build this 1 x 1 x 2 cube using two 1 x 1 x 1 cubes of different face color?
Here is the pattern to follow. Adjust to your liking:
var geometry = new THREE.CubeGeometry( 10, 10, 20, 1, 1, 2 );
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[ i ].color.setHSL( Math.random(), 0.5, 0.5 ); // pick your colors
}
var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors } );
var mesh = new THREE.Mesh( geometry, material );
If you are using CanvasRenderer, you can set material.overdraw = 0.5 to try to eliminate the diagonal lines. This is not required for WebGLRenderer.
three.js r.60