Rotate a face along one of its edges - three.js

Given two faces f and f' with a common edge e, i'm looking for a way to rotate f around e.
See: illustration of f/f' and e
My goal is to unfold f and f' so they can be mapped on the same plan. More specifically, I want the coordinate of the vertex r of f that is not part of e after such unfolding (r').
See: after unfolding with r/r'
Currently i've tried to apply the method described here: https://sites.google.com/site/glennmurray/Home/rotation-matrices-and-formulas/rotation-about-an-arbitrary-axis-in-3-dimensions
In the case from the screenshot, i've simplified it as the rotation axis is already on the Z-axis. So my code looks like this:
// Object contains only two faces
var geometry = object.children[0].geometry;
var f = geometry.faces[0];
var fprime = geometry.faces[1];
// Find two vertices in common
var edge = [f.a, f.b];
if (f.a != fprime.a && f.a != fprime.b && f.a != fprime.c) {
edge = [f.b, f.c];
} else if (f.b != fprime.a && f.b != fprime.b && f.b != fprime.c) {
edge = [f.a, f.c];
}
var v1 = geometry.vertices[edge[0]];
var v2 = geometry.vertices[edge[1]];
polyhedron.translateOnAxis(v1, -1);
polyhedron.rotateOnAxis(v2, THREE.Math.degToRad(90));
polyhedron.translateOnAxis(v1, 1);
But this only send my object into space:
Before
After
Without the rotation, the object does not move (as expected). Any hints on how to fix the rotation ?

If I got you correctly, here's a rough concept of you can rotate a vertex around an axis:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(1, 5, 10);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.add(new THREE.GridHelper(10, 10));
var planeGeom = new THREE.PlaneGeometry(5, 5);
planeGeom.rotateZ(Math.PI * 0.25);
planeGeom.vertices[0].basePosition = new THREE.Vector3().copy(planeGeom.vertices[0]);
planeGeom.vertices[2].set(0, 0, 0); // let's make a triangle from the plane
var plane = new THREE.Mesh(planeGeom, new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
}));
scene.add(plane);
var axis = new THREE.Vector3(0, 1, 0); // in three.js, up is positive Y
render();
function render() {
requestAnimationFrame(render);
planeGeom.vertices[0].copy(planeGeom.vertices[0].basePosition).applyAxisAngle(axis, (Math.sin(Date.now() * 0.001) * 0.5 + 0.5) * Math.PI * 0.5); // we'll use .applyAxisAngle() method
planeGeom.verticesNeedUpdate = true;
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>

I was able to unfold a triangle to another triangle using the following snippet: Rotate object on specific axis anywhere in Three.js - including outside of mesh
// Object contains only two faces
var geometry = object.children[0].geometry;
geometry.computeFaceNormals();
geometry.computeVertexNormals();
var f = geometry.faces[0];
var fprime = geometry.faces[1];
// Find two vertices in common
var edge = [f.a, f.b];
if (f.a != fprime.a && f.a != fprime.b && f.a != fprime.c) {
edge = [f.b, f.c];
} else if (f.b != fprime.a && f.b != fprime.b && f.b != fprime.c) {
edge = [f.a, f.c];
}
var point = geometry.vertices[edge[0]].clone();
var axis = geometry.vertices[edge[1]].clone();
axis = axis.sub(point);
axis.normalize();
// Adds a triangle to show rotation
var newGeometry = new THREE.Geometry();
newGeometry.vertices.push(
geometry.vertices[f.a].clone(),
geometry.vertices[f.b].clone(),
geometry.vertices[f.c].clone()
);
newGeometry.faces.push(new THREE.Face3(0, 1, 2));
var material = new THREE.MeshBasicMaterial({color: 0xffff00, side: THREE.DoubleSide});
var mesh = new THREE.Mesh(newGeometry, material);
scene.add(mesh);
var dot_product = f.normal.dot(fprime.normal); // Give cosinus of the angle
var angle = Math.acos(dot_product);
mesh.rotateAroundWorldAxis(point, axis, angle);
THREE.Object3D.prototype.rotateAroundWorldAxis = function() {
// rotate object around axis in world space (the axis passes through point)
// axis is assumed to be normalized
// assumes object does not have a rotated parent
var q = new THREE.Quaternion();
return function rotateAroundWorldAxis(point, axis, angle) {
q.setFromAxisAngle(axis, angle);
this.applyQuaternion(q);
this.position.sub(point);
this.position.applyQuaternion(q);
this.position.add(point);
return this;
}
}();
Result

Related

Three.js: Rotate object with lookAt() while located at the current lookAt() position

I'm trying to implement a simple turn-around-and-move feature with Three.js. On mouse click, the object is supposed to first turn around and then move to the clicked location.
Codepen
The rotation is achieved with raycasting and lookAt(). It works by itself and it always works on the first click. If you remove the translation, it works continuously. The issue occurs when rotation and translation are implemented together. If you click a second time, after the object has moved to the previous clicked location, it doesn't rotate as expected. Depending on the mouse location it can flip to the other side without rotating at all.
Clarification: When you click the first time, notice how the object slowly and steadily turns around to face that direction? But the second time, after the object has moved, the rotation is quicker and/or flimsier or it simply flips over and there is no rotation at all. It depends on where you click in relation to the object.
I believe the issue stems from trying to implement lookAt while being located at the current lookAt location? If I stop the translation half way, the next rotation will work better. But of course I need it to go all the way.
I'm somewhat lost on how to proceed with this issue. Any help would be appreciated.
/*** Setup scene ***/
let width = 800
let height = 600
let scene
let renderer
let worldAxis
let box
let angle
let boxAxes
scene = new THREE.Scene()
worldAxis = new THREE.AxesHelper(200);
scene.add(worldAxis);
// Setup renderer
renderer = new THREE.WebGLRenderer({alpha: true, antialias: true})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
// Setup camera
const camera = new THREE.OrthographicCamera(
width / - 2, // left
width / 2, // right
height / 2, // top
height / - 2, // bottom
0, // near
1000 ); // far
camera.position.set(0, 0, 500)
camera.updateProjectionMatrix()
// Setup box
let geometry = new THREE.BoxGeometry( 15, 15, 15 );
let material = new THREE.MeshBasicMaterial( { color: "grey" } );
box = new THREE.Mesh( geometry, material );
box.position.set(100, 150, 0)
box.lookAt(getPointOfIntersection(new THREE.Vector2(0, 0)))
addAngle()
boxAxes = new THREE.AxesHelper(50);
box.add(boxAxes)
scene.add(box)
renderer.render(scene, camera);
/*** Setup animation ***/
let animate = false
let currentlyObservedPoint = new THREE.Vector2();
let rotationIncrement = {}
let translationIncrement = {}
let frameCount = 0
document.addEventListener('click', (event) => {
let mousePosForRotate = getMousePos(event.clientX, event.clientY)
rotationIncrement.x = (mousePosForRotate.x - currentlyObservedPoint.x)/100
rotationIncrement.y = (mousePosForRotate.y - currentlyObservedPoint.y)/100
let mousePosForTranslate = getMousePosForTranslate(event)
translationIncrement.x = (mousePosForTranslate.x - box.position.x)/100
translationIncrement.y = (mousePosForTranslate.y - box.position.y)/100
animate = true
})
function animationLoop() {
if (animate === true) {
if (frameCount < 100) {
rotate()
} else if (frameCount < 200) {
translate()
} else {
animate = false
frameCount = 0
}
frameCount++
renderer.render(scene, camera)
}
requestAnimationFrame(animationLoop)
}
function rotate() {
currentlyObservedPoint.x += rotationIncrement.x
currentlyObservedPoint.y += rotationIncrement.y
let pointOfIntersection = getPointOfIntersection(currentlyObservedPoint)
box.lookAt(pointOfIntersection)
addAngle()
}
function translate() {
box.position.x += translationIncrement.x
box.position.y += translationIncrement.y
}
function getMousePos(x, y) {
let mousePos = new THREE.Vector3(
(x / width) * 2 - 1,
- (y / height) * 2 + 1,
0)
return mousePos
}
function getMousePosForTranslate(event) {
let rect = event.target.getBoundingClientRect();
let mousePos = { x: event.clientX - rect.top, y: event.clientY - rect.left }
let vec = getMousePos(mousePos.x, mousePos.y)
vec.unproject(camera);
vec.sub(camera.position).normalize();
let distance = - camera.position.z / vec.z;
let pos = new THREE.Vector3(0, 0, 0);
pos.copy(camera.position).add(vec.multiplyScalar(distance));
return pos
}
function getPointOfIntersection(mousePos) {
let plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
let pointOfIntersection = new THREE.Vector3()
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mousePos, camera)
raycaster.ray.intersectPlane(plane, pointOfIntersection)
return pointOfIntersection
}
function addAngle() {
let angle = box.rotation.x - 32
box.rotation.x = angle
}
animationLoop()
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js'></script>

In three.js how to position image texture similar to 'contain' in css?

My image texture is positioned relative to the center of 3d space instead of mesh and I don't quite understand what determines its size.
Here is example showing how the same image is positioned on different meshes:
https://imgur.com/glHE97L
I'd like the image be in the center of the mesh and it's size set similar as 'contain' in css.
The mesh is flat plane created using ShapeBufferGeometry:
const shape = new THREE.Shape( edgePoints );
const geometry = new THREE.ShapeBufferGeometry( shape );
To see any image I have to set:
texture.repeat.set(0.001, 0.001);
Not sure if that matters but after creating the mesh I than set its position and rotation:
mesh.position.copy( position[0] );
mesh.rotation.set( rotation[0], rotation[1], rotation[2] );
I've tried setting those:
mesh.updateMatrixWorld( true );
mesh.geometry.computeBoundingSphere();
mesh.geometry.verticesNeedUpdate = true;
mesh.geometry.elementsNeedUpdate = true;
mesh.geometry.morphTargetsNeedUpdate = true;
mesh.geometry.uvsNeedUpdate = true;
mesh.geometry.normalsNeedUpdate = true;
mesh.geometry.colorsNeedUpdate = true;
mesh.geometry.tangentsNeedUpdate = true;
texture.needsUpdate = true;
I've played with wrapS / wrapT and offset.
I've checked UV's - I don't yet fully understand this concept but it seems fine. Example of UV for one mesh (I understand those are XY coordinates and they seem to reflect the actual corners of my mesh):
uv: Float32BufferAttribute
array: Float32Array(8)
0: -208
1: 188
2: 338
3: 188
4: 338
5: 12
6: -208
7: 12
I've tried setting:
texture.repeat.set(imgHeight/geometryHeight/1000, imgWidth/geometryWidth/1000);
This is how THREE.ShapeGeometry() computes UV coordinate:
https://github.com/mrdoob/three.js/blob/e622cc7890e86663011d12ec405847baa4068515/src/geometries/ShapeGeometry.js#L157
But you can re-compute them, to put in range [0..1].
Here is an example, click the button to re-compute uvs of the shape geometry:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var grid = new THREE.GridHelper(10, 10);
grid.rotation.x = Math.PI * 0.5;
scene.add(grid);
var points = [
new THREE.Vector2(0, 5),
new THREE.Vector2(-5, 4),
new THREE.Vector2(-3, -3),
new THREE.Vector2(2, -5),
new THREE.Vector2(5, 0)
];
var shape = new THREE.Shape(points);
var shapeGeom = new THREE.ShapeBufferGeometry(shape);
var shapeMat = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/uv_grid_opengl.jpg")
});
var mesh = new THREE.Mesh(shapeGeom, shapeMat);
scene.add(mesh);
btnRecalc.addEventListener("click", onClick);
var box3 = new THREE.Box3();
var size = new THREE.Vector3();
var v3 = new THREE.Vector3(); // for re-use
function onClick(event) {
box3.setFromObject(mesh); // get AABB of the shape mesh
box3.getSize(size); // get size of that box
var pos = shapeGeom.attributes.position;
var uv = shapeGeom.attributes.uv;
for (let i = 0; i < pos.count; i++) {
v3.fromBufferAttribute(pos, i);
v3.subVectors(v3, box3.min).divide(size); // cast world uvs to range 0..1
uv.setXY(i, v3.x, v3.y);
}
uv.needsUpdate = true; // set it to true to make changes visible
}
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<button id="btnRecalc" style="position: absolute;">Re-calculate UVs</button>

How to make a wave pattern circularly around a cylinder?

Hello i'm trying to make a wave pattern on the surface of a cylinder. The waves should rotate with the rotation of the surface. and in a way the sine period is moving in circles, and the amplitudes are long mounds on the surface. Here's some pictures to better explain what i mean.
This is what i'm trying to get the top down view of the cylinder to look similar to:
this is the top view of my cylinder. I'd like the wave to change direction with the rotation of the circle, so it looks the same from all directions.
I feel like i'm very close, i'm just not sure what quaternion or angle to multiply against the vector:
var geometry = this.threeDHandler.threeD_meshes[0].geometry;
var vec3 = new THREE.Vector3(); // temp vector
for (let i = 0; i < geometry.vertices.length; i++) {
vec3.copy(geometry.vertices[i]); // copy current vertex to the temp vector
vec3.setX(0);
vec3.normalize(); // normalize
//part i'm confsude about
const quaternion = new THREE.Quaternion();
const xPos = geometry.vertices[i].x;
//trying to twist the sine around the circle
const twistAmount = 100;
const upVec = new THREE.Vector3(0, 0, 1);
quaternion.setFromAxisAngle(
upVec,
(Math.PI / 180) * (xPos / twistAmount)
);
vec3.multiplyScalar(Math.sin((geometry.vertices[i].x* Math.PI) * period) * amplitude) // multiply with sin function
geometry.vertices[i].add(vec3); // add the temp vector to the current vertex
geometry.vertices[i].applyQuaternion(quaternion);
}
geometry.verticesNeedUpdate = true;
geometry.computeVertexNormals();
You can use absolute value of the sin function of the angle, that a vertex belongs to.
In this case you can use THREE.Spherical() object that allows to get spherical coordinates of a vector:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 6);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var cylinderGeom = new THREE.CylinderGeometry(1, 1, 4, 128, 40, true);
var vec3 = new THREE.Vector3(); // temp vector
var vec3_2 = new THREE.Vector3(); // temp vector 2
var spherical = new THREE.Spherical();
cylinderGeom.vertices.forEach(v => {
vec3.copy(v); // copy current vertex to the temp vector
vec3.setY(0); // leave x and z (thus the vector is parallel to XZ plane)
vec3.normalize(); // normalize
vec3.multiplyScalar(Math.sin(v.y * Math.PI) * 0.25) // multiply with sin function
// radial wave
vec3_2.copy(v).setY(0).normalize();
spherical.setFromVector3(vec3_2);
vec3_2.setLength(Math.abs(Math.sin((spherical.theta * 4) + v.y * 2) * 0.25));
v.add(vec3).add(vec3_2); // add the temp vectors to the current vertex
})
cylinderGeom.computeVertexNormals();
var cylinder = new THREE.Mesh(cylinderGeom, new THREE.MeshNormalMaterial({
side: THREE.DoubleSide,
wireframe: false
}));
scene.add(cylinder);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
})
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.124.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.124.0/examples/js/controls/OrbitControls.js"></script>

Three.js - Create new mesh from certain faces/vertices of another mesh

I´ve been several days struggling with a particular Three.js issue, and I cannot find any way to do it. This is my case:
1) I have a floating mesh, formed by several triangled faces. This mesh is created from the geometry returned by a loader, after obtaining its vertices and faces using getAttribute('position'): How to smooth mesh triangles in STL loaded BufferGeometry
2) What I want to do now is to "project" the bottom face agains the floor.
3) Later, with this new face added, create the resulting mesh of filling the space between the 3 vertices of both faces.
I already have troubles in step 2... To create a new face I´m supossed to have its 3 vertices already added to geometry.vertices. I did it, cloning the original face vertices. I use geometry.vertices.push() results to know their new indexes, and later I use that indexes (-1) to finally create the new face. But its shape is weird, also the positions and the size. I think I´m not getting the world/scene/vector position equivalence theory right :P
I tried applying this, with no luck:
How to get the absolute position of a vertex in three.js?
Converting World coordinates to Screen coordinates in Three.js using Projection
http://barkofthebyte.azurewebsites.net/post/2014/05/05/three-js-projecting-mouse-clicks-to-a-3d-scene-how-to-do-it-and-how-it-works
I discovered that if I directly clone the full original face and simply add it to the mesh, the face is added but in the same position, so I cannot then change its vertices to place it on the floor (or at least without modifying the original face vertices!). I mean, I can change their x, y, z properties, but they are in a very small measure that doesn´t match the original mesh dimensions.
Could someone help me get this concept right?
EDIT: source code
// Create geometry
var geo = new THREE.Geometry();
var geofaces = [];
var geovertices = [];
original_geometry.updateMatrixWorld();
for(var index in original_geometry.faces){
// Get original face vertexNormals to know its 3 vertices
var face = original_geometry[index];
var vertexNormals = face.vertexNormals;
// Create 3 new vertices, add it to the array and then create a new face using the vertices indexes
var vertexIndexes = [null, null, null];
for (var i = 0, l = vertexNormals.length; i < l; i++) {
var vectorClone = vertexNormals[i].clone();
vectorClone.applyMatrix4( original_geometry.matrixWorld );
//vectorClone.unproject(camera); // JUST TESTING
//vectorClone.normalize(); // JUST TESTING
var vector = new THREE.Vector3(vectorClone.x, vectorClone.z, vectorClone.y)
//vector.normalize(); // JUST TESTING
//vector.project(camera); // JUST TESTING
//vector.unproject(camera); // JUST TESTING
vertexIndexes[i] = geovertices.push( vector ) - 1;
}
var newFace = new THREE.Face3( vertexIndexes[0], vertexIndexes[1], vertexIndexes[2] );
geofaces.push(newFace);
}
// Assign filled arrays to the geometry
geo.faces = geofaces;
geo.vertices = geovertices;
geo.mergeVertices();
geo.computeVertexNormals();
geo.computeFaceNormals();
// Create a new mesh with resulting geometry and add it to scene (in this case, to the original mesh to keep the positions)
new_mesh = new THREE.Mesh( geo, new THREE.MeshFaceMaterial(material) ); // material is defined elsewhere
new_mesh.position.set(0, -100, 0);
original_mesh.add( new_mesh );
I created a fully operational JSFiddle with the case to try things and see the problem more clear. With this STL (smaller than my local example) I cannot even see the badly cloned faces added to the scene.. Maybe they are too small or out of focus.
Take a look to the calculateProjectedMesh() function, here is where I tried to clone and place the bottom faces (already detected because they have a different materialIndex):
JSFiddle: https://jsfiddle.net/tc39sgo1/
var container;
var stlPath = 'https://dl.dropboxusercontent.com/s/p1xp4lhy4wxmf19/Handle_Tab_floating.STL';
var camera, controls, scene, renderer, model;
var mouseX = 0,
mouseY = 0;
var test = true;
var meshPlane = null, meshStl = null, meshCube = null, meshHang = null;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
/*THREE.FrontSide = 0;
THREE.BackSide = 1;
THREE.DoubleSide = 2;*/
var materials = [];
materials.push( new THREE.MeshPhongMaterial({color : 0x00FF00, side:0, shading: THREE.FlatShading, transparent: true, opacity: 0.9, overdraw : true, wireframe: false}) );
materials.push( new THREE.MeshPhongMaterial({color : 0xFF0000, transparent: true, opacity: 0.8, side:0, shading: THREE.FlatShading, overdraw : true, metal: false, wireframe: false}) );
materials.push( new THREE.MeshPhongMaterial({color : 0x0000FF, side:2, shading: THREE.FlatShading, overdraw : true, metal: false, wireframe: false}) );
var lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.05 });
init();
animate();
function webglAvailable() {
try {
var canvas = document.createElement('canvas');
return !!(window.WebGLRenderingContext && (
canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
} catch (e) {
return false;
}
}
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100000000);
camera.position.x = 1500;
camera.position.z = -2000;
camera.position.y = 1000;
controls = new THREE.OrbitControls(camera);
// scene
scene = new THREE.Scene();
var ambient = new THREE.AmbientLight(0x101030); //0x101030
scene.add(ambient);
var directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(0, 3, 0).normalize();
scene.add(directionalLight);
var directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(0, 1, -2).normalize();
scene.add(directionalLight);
if (webglAvailable()) {
renderer = new THREE.WebGLRenderer();
} else {
renderer = new THREE.CanvasRenderer();
}
renderer.setClearColor( 0xCDCDCD, 1 );
// renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
document.addEventListener('mousemove', onDocumentMouseMove, false);
window.addEventListener('resize', onWindowResize, false);
createPlane(500, 500);
createCube(500);
loadStl();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove(event) {
mouseX = (event.clientX - windowHalfX) / 2;
mouseY = (event.clientY - windowHalfY) / 2;
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
function createPlane(width, height) {
var planegeometry = new THREE.PlaneBufferGeometry(width, height, 0, 0);
var material = new THREE.MeshLambertMaterial({
color: 0xFFFFFF,
side: THREE.DoubleSide
});
planegeometry.computeBoundingBox();
planegeometry.center();
meshPlane = new THREE.Mesh(planegeometry, material);
meshPlane.rotation.x = 90 * (Math.PI/180);
//meshPlane.position.y = -height/2;
scene.add(meshPlane);
}
function createCube(size) {
var geometry = new THREE.BoxGeometry( size, size, size );
geometry.computeFaceNormals();
geometry.mergeVertices();
geometry.computeVertexNormals();
geometry.center();
var material = new THREE.MeshPhongMaterial({
color: 0xFF0000,
opacity: 0.04,
transparent: true,
wireframe: true,
side: THREE.DoubleSide
});
meshCube = new THREE.Mesh(geometry, material);
meshCube.position.y = size/2;
scene.add(meshCube);
}
function loadStl() {
var loader = new THREE.STLLoader();
loader.load( stlPath, function ( geometry ) {
// Convert BufferGeometry to Geometry
var geometry = new THREE.Geometry().fromBufferGeometry( geometry );
geometry.computeBoundingBox();
geometry.computeVertexNormals();
geometry.center();
var faces = geometry.faces;
for(var index in faces){
var face = faces[index];
var faceNormal = face.normal;
var axis = new THREE.Vector3(0,-1,0);
var angle = Math.acos(axis.dot(faceNormal));
var angleReal = (angle / (Math.PI/180));
if(angleReal <= 70){
face.materialIndex = 1;
}
else{
face.materialIndex = 0;
}
}
geometry.computeFaceNormals();
geometry.computeVertexNormals();
meshStl = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
meshStl.position.x = 0;
meshStl.position.y = 400;
scene.add( meshStl );
// Once loaded, calculate projections mesh
calculateProjectedMesh();
});
}
function calculateProjectedMesh(){
var geometry = meshStl.geometry;
var faces = geometry.faces;
var vertices = geometry.vertices;
var geometry_projected = new THREE.Geometry();
var faces_projected = [];
var vertices_projected = [];
meshStl.updateMatrixWorld();
for(var index in faces){
var face = faces[index];
// This are the faces
if(face.materialIndex == 1){
var vertexIndexes = [face.a, face.b, face.c];
for (var i = 0, l = vertexIndexes.length; i < l; i++) {
var relatedVertice = vertices[ vertexIndexes[i] ];
var vectorClone = relatedVertice.clone();
console.warn(vectorClone);
vectorClone.applyMatrix4( meshStl.matrixWorld );
////////////////////////////////////////////////////////////////
// TEST: draw line
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z));
//geometry.vertices.push(new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z));
geometry.vertices.push(new THREE.Vector3(vectorClone.x, meshPlane.position.y, vectorClone.z));
var line = new THREE.Line(geometry, lineMaterial);
scene.add(line);
console.log("line added");
////////////////////////////////////////////////////////////////
vectorClone.y = 0;
var vector = new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z);
vertexIndexes[i] = vertices_projected.push( vector ) - 1;
}
var newFace = new THREE.Face3( vertexIndexes[0], vertexIndexes[1], vertexIndexes[2] );
newFace.materialIndex = 2;
faces_projected.push(newFace);
}
}
geometry_projected.faces = faces_projected;
geometry_projected.vertices = vertices_projected;
geometry_projected.mergeVertices();
console.info(geometry_projected);
meshHang = new THREE.Mesh(geometry_projected, new THREE.MeshFaceMaterial(materials));
var newY = -(2 * meshStl.position.y) + 0;
var newY = -meshStl.position.y;
meshHang.position.set(0, newY, 0);
meshStl.add( meshHang );
}
EDIT: Finally!! I got it! To clone the original faces I must access their 3 original vertices using "a", "b" and "c" properties, which are indexes referencing Vector3 instances in the "vertices" array of the original geometry.
I cloned the 3 vertices flatting the Z position to zero, use their new indexes to create the new face and add it to the projection mesh (in blue).
I´m also adding lines as a visual union between both faces. Now I´m ready for step 3, but I think this is complex enough to close this question.
Thanks for the updateMatrixWorld clue! It was vital to achieve my goal ;)
try this
original_geometry.updateMatrixWorld();
var vertexIndexes = [null, null, null];
for (var i = 0, l = vertexNormals.length; i < l; i++) {
var position = original_geometry.geometry.vertices[i].clone();
position.applyMatrix4( original_geometry.matrixWorld );
var vector = new THREE.Vector3(position.x, position.y, position.z)
vertexIndexes[i] = geovertices.push( vector ) - 1;
}

ThreeJS - how to pick just one type of objects?

I'm new to ThreeJS and I have an issue with picking objects by raycasting. I have created some spheres and some lines but only want to change the spheres on mouseover. I think I need to add some condition in the raycast code but I have no idea what...
Here's my code, hope anyone can help:
This creates the objects:
var numSpheres = 10;
var angRand = [numSpheres];
var spread = 10;
var radius = windowY/5;
var radiusControl = 20;
//sphere
var sphereGeometry = new THREE.SphereGeometry(0.35, 100, 100);
//line
var lineGeometry = new THREE.Geometry();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0xCCCCCC
});
//create dynamically
for (var i = 0; i < numSpheres; i++) {
var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x334455});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
var line = new THREE.Line(lineGeometry, lineMaterial);
angRand[i] = Math.floor((Math.random() * 360) + 1);//random angle for each sphere/line
var radiusIncr = spread * (angRand[i]+200)/180;
var xPos = Math.cos((360/numSpheres * (i) + angRand[i]/2 )) * (radius - radiusIncr);
var yPos = Math.sin((360/numSpheres * (i) + angRand[i]/2 )) * (radius - radiusIncr);
var offsetY = Math.floor((Math.random()*5)+1);
sphere.position.x = xPos/radiusControl;
sphere.position.y = yPos/radiusControl + offsetY;
lineGeometry.vertices.push(
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(sphere.position.x, sphere.position.y, 0)
);
scene.add(sphere);
scene.add(line);
}
And this is my raycast:
var mouse = {
x: 0,
y: 0
},
INTERSECTED;
window.addEventListener('mousemove', onMouseMove, false);
window.requestAnimationFrame(render);
function onMouseMove(event) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
//event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
//console.log(mouse.x + " | " + mouse.y);
}
function mousePos() {
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject(camera);
var ray = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
ray.linePrecision = 1;
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects(scene.children, true);
//console.log(intersects.length);
// INTERSECTED = the object in the scene currently closest to the camera
// and intersected by the Ray projected from the mouse position
// if there is one (or more) intersections
if (intersects.length > 0) {
// if the closest object intersected is not the currently stored intersection object
if (intersects[0].object != INTERSECTED) {
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// store reference to closest object as current intersection object
INTERSECTED = intersects[0].object;
// store color of closest object (for later restoration)
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// set a new color for closest object
INTERSECTED.material.color.setHex(0xEE7F00);
//INTERSECTED.radius.set( 1, 2, 2 );
}
} else // there are no intersections
{
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
//INTERSECTED.scale.set( 1, 1, 1 );
// remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
}
}
The raycast returns an intersect array of objects which itself contains information about what the ray hit.
Since you only have spheres and lines you can branch on the geometry type intersects[0].object.geometry.type which would be either 'LineGeometry' or 'SphereGeometry'.
Edit: Obligatory jsfiddle, see console for hit output.
http://jsfiddle.net/z43hjqm9/1/
To simplify working with the mouse, you can use the class EventsControls. Try to make through this example.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.attachEvent('mouseOver', function() {
this.container.style.cursor = 'pointer';
this.mouseOvered.material = selMaterial;
...
});
EventsControls.attachEvent('mouseOut', function() {
this.container.style.cursor = 'auto';
this.mouseOvered.material = autoMaterial;
...
});
//
function render() {
EventsControls.update();
controls.update();
renderer.render(scene, camera);
}
In your code,
var intersects = ray.intersectObjects(scene.children, true);
the first parameter to the call is an object that will be evaluated to see if it, or any of its descendants (recursive is true) intersect the ray.
So, simply create an object target and add the spheres to it (but not the lines).
This will make your call also more effective
1.use different arrays to place different objects
a.for all objectType1,after scene.add(objectType1)-> do array1.push(objectType1)
b.for all objectType 2,after scene.add(objectType2)-> do array2.push(objectType2)
now whichever type of objects you want to interact, pass that array in intersect as-
var intersects = raycaster.intersectObjects( arrayType1,true);
now only the arrayType1 objects will interact.

Resources