I'm trying to add a line(s) between animated sprites, like in this example. I tryed different solutions. But I couldn't make a dynamic line and not even a static line between sprites that are animated. Is it possible to create a dymanic line between those sprites from that example. If yes, how can I do it?
This is the code I used:
for ( var i = 0; i < objects.length; i ++ ) {
var geometry = new THREE.Geometry();
geometry.vertices.push(objects.position);
var material = new THREE.LineBasicMaterial( {
color: 0x0000FF,
transparent: true,
opacity: 1
} );
var line = new THREE.Line( geometry, material, THREE.LinePieces );
scene.add( line );
}
On ...jsfiddle.net/LxpmN/40/ u can see what I'm try to achieve, but he used two meshes instead of sprites. I understand that I need to put line.geometry.verticesNeedUpdate = true;, but I can't even make a static line from objects, like in that previous example.
If you want to add a collection of line segments to your scene, create one THREE.Line instead of many. Use a pattern like this one:
var geometry = new THREE.Geometry();
for ( var i = 0; i < objects.length - 1; i ++ ) { // stop one short of end
geometry.vertices.push( objects[ i ].position );
geometry.vertices.push( objects[ i + 1 ].position );
}
var material = new THREE.LineBasicMaterial( { color: 0x0000FF } );
var line = new THREE.Line( geometry, material, THREE.LinePieces );
scene.add( line );
If you modify any of the object's positions, you will have to add the following line in the render loop:
line.geometry.verticesNeedUpdate = true;
three.js r.71
Related
I want to remove some cylinders that are in scene so i can put new ones in another position.
This is how I place the cylinders (this works just showing so you understand what I am trying to do).
for (i = 0; i < aantalLangs; i++) {
var geometry = new THREE.CylinderGeometry( (langsDiameter * scale), (langsDiameter * scale) , langsLengte * scale , 20 );
var material = new THREE.MeshBasicMaterial( {color: 0xffe26f} );
var cylinder = new THREE.Mesh( geometry, material );
scene.add( cylinder );
cylinder.position.set( 0 , 0 ,onderRandRooster);
onderRandRooster -= (langsMaas * scale);
cylinder.rotation.z = Math.PI / 2;
}
And I use this function to remove them.
function ClearMesh(){
scene.remove(scene.getObjectByName(cylinder));
scene.remove(scene.getObjectByName(cylinder2));
}
I want to use this button to remove the cylinders.
<button onclick="ClearMesh();">Clear mesh</button>
If you want to use Object3D.getObjectByName(), it's necessary to apply a string as a parameter. To be more precise, Object3D.name which you currently don't set in your application. Something like this should work:
// in your for loop
var cylinder = new THREE.Mesh( geometry, material );
cylinder.name = 'cylinder' + i;
// in your ClearMesh() function
scene.remove( scene.getObjectByName( 'cylinder1' ) );
Besides, consider to reuse your material and geometry when creating your cylinder meshes if they have the same properties. Just declare them outside of the for loop. Otherwise you should use the respective .dispose() methods in order to free internal resources of the engine when you remove a cylinder. Have a look at https://stackoverflow.com/a/40730686/5250847 for more details.
three.js R103
I make a game where you can add objects to a world without using a grid. Now I want to make a footpath. When you click on "Create footpath", then you can add a point to on the world at the raycaster position. After you add a first point you can add a second point to the world. When these 2 objects where placed. A line/footpath is visible from the first point to the second one.
I can do this really simple with THREE.Line. See the code:
var lineGeometry = new THREE.Geometry();
lineGeometry.vertices.push( new THREE.Vector3(x1,0,z1), new THREE.Vector3(x2,0,z2) );
lineGeometry.computeLineDistances();
var lineMaterial = new THREE.LineBasicMaterial( { color: 0xFF0000 } );
var line = new THREE.Line( lineGeometry, lineMaterial );
scene.add(line);
But I can't add a texture on a simple line. Now I want to do something the same with a Mesh. I have the position of the first point and the raycaster position of the second point. I also have the lenght between the two objects for the lenght of the footpath. But I don't know how I can get the rotation what is needed.
Note. I saw something about LookAt, is this maybe a good idea, how can I use this with a mesh?
Can anyone help me to get the correct rotation for the footpath object?
I use this code for the foodpath mesh:
var loader = new THREE.TextureLoader();
loader.load('images/floor.jpg', function ( texture ) {
var geometry = new THREE.BoxGeometry(10, 0, 2);
var material = new THREE.MeshBasicMaterial({ map: texture, overdraw: 0.5 });
var footpath = new THREE.Mesh(geometry, material);
footpath.position.copy(point2);
var direction = // What can I do here?
footpath.rotation.y = direction;
scene.add(footpath);
});
I want to get the correct rotation for direction.
[UPDATE]
The code of WestLangley helps a lot. But it works not in all directions. I used this code for the lenght:
var lenght = footpaths[i].position.z - point2.position.z;
What can I do that the lenght works in all directions?
You want to align a box between two 3D points. You can do that like so:
var geometry = new THREE.BoxGeometry( width, height, length ); // align length with z-axis
geometry.translate( 0, 0, length / 2 ); // so one end is at the origin
...
var footpath = new THREE.Mesh( geometry, material );
footpath.position.copy( point1 );
footpath.lookAt( point2 );
three.js r.84
Using Three.js r75
I am trying to display cubes that change color depending on an integer value from green to red. I have tried multiple ways as I am stuck on this. I was unable to make cubeMat.material.color.setRGB work and creating a new Three.Color doesn't seem to work either. Please note I merge all the geometries at the end for one draw call. I am hoping this isn't the issue.
[UPDATE]
I am confirming the rgb values are set correctly with getStyle however they do not render correctly. All cube stacks should be different colors.
function colorData(percentage){
var rgbString = "",
r = parseInt(percentage * 25.5),
g = parseInt(((percentage * 25.5) - 255) * -1),
b = 0;
rgbString = "rgb("+r+","+g+",0)";
return rgbString;
}
...
var position = latLongToSphere(objectCoord[1], objectCoord[0], 300),
rgb = colorData(objectMag),
cubeColor = new THREE.Color(rgb),
cubeMat = new THREE.MeshBasicMaterial({color: cubeColor}),
cubeHeight = objectMag * 175,
cubeGeom = new THREE.BoxGeometry(3,3,cubeHeight,1,1,1),
cube = new THREE.Mesh(cubeGeom, cubeMat);
// set position of cube on globe, point to center, merge together for one draw call
cube.geometry.colorsNeedUpdate = true;
cube.position.set(position.x, position.y, position.z);
cube.lookAt(lookCenter);
cube.updateMatrix();
console.log(cube.material.color.getStyle());
geom.merge(cube.geometry, cube.matrix);
You are merging geometries so you can render with a single draw call and a single material, but you want each of the merged geometries to have a different color.
You can achieve that by defining vertexColors (or faceColor) in your geometry. Here is a pattern to follow:
// geometry
var geometry = new THREE.Geometry();
for ( var count = 0; count < 10; count ++ ) {
var geo = new THREE.BoxGeometry( 5, 5, 5 );
geo.translate( THREE.Math.randFloat( - 5, 5 ), THREE.Math.randFloat( - 5, 5 ), THREE.Math.randFloat( - 5, 5 ) );
var color = new THREE.Color().setHSL( Math.random(), 0.5, 0.5 );
for ( var i = 0; i < geo.faces.length; i ++ ) {
var face = geo.faces[ i ];
face.vertexColors.push( color, color, color ); // all the same in this case
//face.color.set( color ); // this works, too; use one or the other
}
geometry.merge( geo );
}
Then, when you specify the material for the merged geometry, set vertexColors like so:
// material
var material = new THREE.MeshPhongMaterial( {
color: 0xffffff,
vertexColors: THREE.VertexColors // or THREE.FaceColors, if defined
} );
Your geometry will be rendered with a single draw call. You can verify that by typing renderer.info into the console. renderer.info.render.calls should be 1.
three.js r.75
cubeMat.material.color.setRGB won't work because it's like you're calling the material twice (cubeMat and material), try this instead:
cube.material.color.setRGB( value, value, value );
Turns out if you merge the geometry the materials cant have different colors.
I had to set the face color of each cube before merging.
See
Changing material color on a merged mesh with three js
Three js materials of different colors are showing up as one color
It seems like this should be easy, but I have spent nearly a week on this trying every possible combination of how to drag multiple items as a group in Three.js. It started out simple, I used this example https://jsfiddle.net/mz7Lv9dt/1/ to get the ball working. I thought I could just add some TextGeometry, which of course had some major API changes in the last couple releases rendering most examples obsolete.
After finally getting it to work as a single line, I wanted to add in wordwrap and move it as a group, but I can't seem to do so.
Here you can see it working just fine with the ball, but you can't drag the text https://jsfiddle.net/ajhalls/h05v48wd/
By swapping around three lines of code (location line 93-99), I can get it to where you can drag the individual lines around, which you can see here: https://jsfiddle.net/ajhalls/t0e2se3x/
function addText(text, fontSize, boundingWidth) {
var wrapArray;
wrapArray = text.wordwrap(10,2);
var loader = new THREE.FontLoader();
loader.load( 'https://cdn.coursesaver.com/three.js-74/examples/fonts/helvetiker_bold.typeface.js',
function ( font ) {
group = new THREE.Group();
group.name = "infoTag";
for (var i = 0; i < wrapArray.length; i++) {
var objectID=i;
var line = wrapArray[objectID];
var textGeo = new THREE.TextGeometry( line, {font: font,size: fontSize,height: 10,curveSegments: 12,bevelThickness: 0.02,bevelSize: 0.05,bevelEnabled: true});
textGeo.computeBoundingBox();
var centerOffset = -0.5 * ( textGeo.boundingBox.max.x - textGeo.boundingBox.min.x );
var textMaterial = new THREE.MeshPhongMaterial( { color: 0xff0000, specular: 0xffffff } );
var mesh = new THREE.Mesh( textGeo, textMaterial );
mesh.position.x = centerOffset +200;
mesh.position.y = i*fontSize*-1+11;
mesh.position.z = 280;
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.geometry.center();
mesh.lookAt(camera.position);
mesh.name = i;
group.add( mesh ); //this should work - comment out and swap for other two lines to see
scene.add(mesh); // this should work- comment out and swap for other two lines to see
//objects.push(mesh);//works on individual lines if you uncomment this
//scene.add(mesh); //works on individual lines if you uncomment this
}
objects.push( group ); // this should work- comment out and swap for other two lines to see
});
}
That "should" be working according to everything I had researched over the last week. I had one moment where it was "working" but because of the size of the group object, the pivot points were wrong, the setLength function didn't work, and it flipped the object away from the camera. All in all it was a mess.
I did try using 2d objects such as canvases and sprites, but for reasons detailed here Three.js TextGeometry Wordwrap - drag as group couldn't get it working.
Please, someone help me!
The issue ended up being with the group. Previously I was creating it, adding objects to it with a position.z which increased the size of the box around the group, then after doing that I moved the box to in front of the camera and did a group.lookAt which meant that when I was dragging it everything including pivot point and looking at it from the back was wrong. The right way was to create the group, position it, face the camera, then add the text.
function addText(text, fontSize, wrapWidth, tagColor, positionX, positionY, positionZ) {
var wrapArray;
wrapArray = text.wordwrap(wrapWidth,2);
var loader = new THREE.FontLoader();
loader.load( '/js/fonts/helvetiker_bold.typeface.js', function ( font ) {
group = new THREE.Group();
group.position.x=positionX;
group.position.y=positionY;
group.position.z=positionZ;
group.lookAt(camera.position);
group.tourType = "infoTag";
group.name = "infoTag-" + objects.length;
group.dataID=objects.length;
group.textData=text;
for (var i = 0; i < wrapArray.length; i++) {
var objectID=i;
var line = wrapArray[objectID];
var textGeo = new THREE.TextGeometry( line, {
font: font,
size: fontSize,
height: 1,
curveSegments: 12,
bevelThickness: 0.02,
bevelSize: 0.05,
bevelEnabled: true
});
textGeo.computeBoundingBox();
var centerOffset = -0.5 * ( textGeo.boundingBox.max.x - textGeo.boundingBox.min.x );
var textMaterial = new THREE.MeshPhongMaterial( { color: tagColor, specular: 0xffffff } );
var mesh = new THREE.Mesh( textGeo, textMaterial );
mesh.dataID=objects.length;
mesh.position.x = 0;
mesh.position.y = (i*mesh.geometry.boundingBox.max.y*-1)*1.15;
mesh.position.z = 0;
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.geometry.center();
//mesh.lookAt(camera.position);
mesh.name = "infoTag-mesh-" + objects.length;
group.add( mesh );
}
scene.add(group);
objects.push( group );
});
}
Of course there were some changes to be made in the mouse events to take into account that you want to move the parent of the object, which looks something like this:
function onDocumentMouseDown(event) {
event.preventDefault();
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(objects, true);
if (intersects.length > 0) {
if (intersects[0].object.parent.tourType == 'infoTag') {
var manipulatingInfoTag = true;
SELECTED = intersects[0].object.parent;
}else{
SELECTED = intersects[0].object;
}
var intersects = raycaster.intersectObject(plane);
if (intersects.length > 0) {
offset.copy(intersects[0].point).sub(plane.position);
}
container.style.cursor = 'move';
}
isUserInteracting = true;
onPointerDownPointerX = event.clientX; onPointerDownPointerY = event.clientY; onPointerDownLon = lon; onPointerDownLat = lat;
}
I don't know what I'm doing wrong. I have multiple meshes that I am trying to merge into one mesh so that I can save on draw calls.
Each of my meshes has a unique materials. In this example it just has a different color, but really they will have unique textures mapped.
This is my code:
materials = [];
blocks = [];
var tempMat;
var tempCube;
var tempGeo;
var tempvec;
// block 1
tempMat = new THREE.MeshLambertMaterial({ color: '0x0000ff' });
materials.push( tempMat );
tempGeo = new THREE.CubeGeometry(1, 1, 1);
for (var ix=0; ix<tempGeo.faces.length; ix++) {
tempGeo.faces[ix].materialIndex = 0;
}
tempCube = new THREE.Mesh( tempGeo, tempMat );
tempCube.position.set(0, 3, -6);
blocks.push( tempCube );
// block 2
tempMat = new THREE.MeshLambertMaterial({ color: '0x00ff00' });
materials.push( tempMat );
tempGeo = new THREE.CubeGeometry(1, 1, 1);
for (var ix=0; ix<tempGeo.faces.length; ix++) {
tempGeo.faces[ix].materialIndex = 1;
}
tempCube = new THREE.Mesh( tempGeo, tempMat );
tempCube.position.set(1, 3, -6);
blocks.push( tempCube );
// Merging them all into one
var geo = new THREE.Geometry();
for (var i=0; i<blocks.length; i++) {
blocks[i].updateMatrix();
geo.merge(blocks[i].geometry, blocks[i].matrix, i);
}
var newmesh = new THREE.Mesh( geo, new THREE.MeshFaceMaterial( materials ) );
scene.add(newmesh);
Basically, that gives me an error that says:
Uncaught TypeError: Cannot read property 'visible' of undefined
every time my render function is called.
Where did I go wrong?
You are merging geometries into one, and using MeshFaceMaterial (renamed MultiMaterial in r.72).
It does not make any sense to merge geometries having different material indices.
WebGLRenderer needs to segment the geometry by material to render it.
As a rule-of-thumb, only merge geometries if they will be rendered with a single material.
three.js r.72