Three.js r73 Merged Sphere Geometry Disappearing When Camera Moves - three.js

I'm trying to merge some geometries and am seeing some strange things with certain types of geometry. I've tried boxes, cylinders, spheres, and tetrahedrons. Boxes and cylinders seem fine. Spheres and tetrahedrons disappear when the camera view target gets far enough away from the world center (0,0,0).
I've created a fiddle here: http://jsfiddle.net/rdhntLns/
Use WASD in the fiddle to rotate the camera around.
Here is my merge code. It's probable that I'm missing a step.
// Merged Trees
var trees = new THREE.Object3D();
var treeTrunkGeom = new THREE.BoxGeometry(1, 20, 1);
if (sphereTops) {
var treeTopGeom = new THREE.SphereGeometry(10, 5, 5);
} else {
var treeTopGeom = new THREE.CylinderGeometry(.1, 30, 16, 16, 4);
}
treeTopGeom.translate(0, 16, 0);
for (var i = 0; i < 500; i++) {
var treePos = [getRandomInt(-512, 512), getRandomInt(-512, 512)];
var trunkSize = Math.random() * 3 + 1;
var treeTrunkGeom2 = new THREE.BoxGeometry(trunkSize, 20, trunkSize);
var topWidth = getRandomInt(10, 18);
var topHeight = getRandomInt(17, 32);
if (sphereTops) {
var treeTopGeom2 = new THREE.SphereGeometry(getRandomInt(10, 20), 5, 5);
} else {
var treeTopGeom2 = new THREE.CylinderGeometry(.1, topWidth, topHeight, 16, 4);
}
treeTrunkGeom2.translate(treePos[0], 0, treePos[1]);
treeTopGeom2.translate(treePos[0], 16, treePos[1]);
treeTrunkGeom.merge(treeTrunkGeom2);
treeTopGeom.merge(treeTopGeom2);
}
var treeTrunks = new THREE.Mesh(treeTrunkGeom, new THREE.MeshLambertMaterial({color: 0xA6895A}));
treeTrunks.castShadow = true;
treeTrunks.receiveShadow = true;
var treeTops = new THREE.Mesh(treeTopGeom, new THREE.MeshLambertMaterial({color: 0xC1E825}));
treeTops.castShadow = true;
treeTops.receiveShadow = true;
treeTrunks.add(treeTops);
trees.add(treeTrunks);
trees.position.y += 8;
scene.add(trees);
My questions:
Why are the merged sphere geometries disappearing when the camera is looking too far off from world center?
Why doesn't the same happen with cylinder geometry?
Am I missing a step (or steps) in the merge process?
Thank you in advance for your time.

Once the the bounding box of your model falls outside the view frustum it will be culled. So you need to merge carefully. Since you have trees merge those that are close together.

Related

Three.js place one box upon another

To display rack structure, placing one box upon another. But y Position calculation fails.Currently creates gap between boxes. Please inform how could it be fixed, whether camera or light effect creates a problem. As per rack size, altering y position. Data contain size and starting place.
```
var data = [{"id": 10075,"size": 3,"slotNumber": 1},{"id": 10174,"size": 7,"slotNumber": 4}];
var rackListGroup;
init();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x999999 );
var light = new THREE.AmbientLight( 0xffffff );
light.position.set( 0.5, 1.0, 0.5 ).normalize();
scene.add( light );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.fromArray([0, 0, 140]);
scene.add( camera );
rackListGroup = new THREE.Mesh();
rackListGroup.name = "Rack List"
var i;
for (i = 0; i < 1; i++) {
rackListGroup.add(drawRack(10, i))
}
scene.add(rackListGroup);
render();
}
function drawRack(size, rackNo){
var rackGroup = new THREE.Group();
rackGroup.name = "rack "+rackNo;
var yPosition = -42;
var xPosition = -20 + parseInt(rackNo)*40;
var slot = 1, counter = 0;
var slotWidth = 5;
while(slot <= parseInt(size)){
var slotSize = data[counter].size;
slot = slot + slotSize;
yPosition = yPosition + slotSize* slotWidth;
var geometry = new THREE.BoxGeometry( 30, slotWidth*slotSize, 5 );
var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
var shape = new THREE.Mesh( geometry, material );
shape.name = data[counter].name;
shape.position.set(xPosition, yPosition, 0);
rackGroup.add(shape);
var boxGeometry = new THREE.BoxBufferGeometry( 30, slotWidth*slotSize, 5, 1, 1, 1 );
var boxMaterial = new THREE.MeshBasicMaterial( { wireframe:true } );
var box = new THREE.Mesh( boxGeometry, boxMaterial );
box.name = data[counter].name;
box.position.set(xPosition, yPosition, 0);
rackGroup.add(box);
if(counter+1 < data.length){
counter++;
}
}
return rackGroup;
}
```
I've tried your code and I see a misunderstanding between the objects position and the objects height to be able to stack them on top of each other.
You use one variable for yPosition and you need 2 variables, the reason is that geometries are positioned based on its axes center, so it means a 15 units height mesh positioned at y=0 it will place indeed at -7.5 units below the y=0 position and the upper side of the geometry will be at 7.5. So next slot to stack will be needed to place (conceptually) at y = 7.5 + (topSlotHeight / 2).
That's why your calculation of the next slot to stack y position is wrong. I have created this fiddle with the solution, and I have added a gridHelper at y=0 for your reference and the OrbitControls to be able to check it better. Now it works perfectly doing like this, storing the accumulated base position of the previous slot in yBaseHeight and the yPosition for the slot on top:
var slotHeight = (slotSize * slotWidth);
yPosition = yBaseHeight + (slotHeight / 2);
yBaseHeight = yBaseHeight + slotHeight;
PD.- I saw you start placing objects at y=-42, I started from y=0 to show better the effect.

THREE.js Many instances of the same simple mesh killing framerate

I have a basic scene in which I'm using each loops to add multiple meshes ( hundreds of simple cylinders ) to a group (for each line), and grouping the lines to cover the surfaces. The result is this:
This is the code to add these cylinders:
var base_material = new THREE.MeshPhongMaterial( {
color: 0x666666,
side: THREE.FrontSide,
});
var cylinderGeometry = new THREE.CylinderGeometry( 1, 1, 1, 4 );
var floor_geometry = new THREE.PlaneGeometry( 68, 10000, 10 );
var floor = new THREE.Mesh( floor_geometry, base_material );
floor.receiveShadow = true;
scene.add( floor );
floor.position.set(0,-15,-530);
floor.rotation.x = -Math.PI / 2;
// Add Floor Studs
for ( var i = 0; i < 15; i++ ) {
var lineGroup = new THREE.Group();
for ( var n = 0; n < 1000; n++ ) {
var cylinder = new THREE.Mesh( cylinderGeometry, base_material );
// cylinder.castShadow = true;
// cylinder.receiveShadow = true;
lineGroup.add( cylinder );
posZ = 0 - n*6;
cylinder.position.set(0,0, posZ);
}
scene.add( lineGroup );
posX = -28.4 + i*4.1;
lineGroup.position.set(posX,-14.7,0);
}
When I animate the camera to traverse through the scene the framerate is dire. Potential approaches I've come across include merging the geometry, possibly rendering out and loading in a single GLTF model with all of these cylinders, or duplicating them somehow. As you can see the geometry and material is created once and reused, however the mesh is recreated each time which I suspect is the culprit..
My question is, what is the most optimum of these approaches to do this, is there a standard best practice method?
Thanks in advance!

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!

Cannon.js - How to prevent jittery/shaky blocks?

I'm using Cannon.js with Three.js. I have set a scene which has 5 columns of 4 blocks stacked on top of each other.
I want these to be interactable with other objects I'm planning on adding to the scene. However, the blocks in the columns seem to be causing lots of micro-collisions and over time, jitter out of position. I want them to stay exactly in line until they're interacted with.
If you view the codepen and wait for about 20/30 seconds you'll see the blocks start to move. Is there something specific I need to set on these blocks to prevent this from happening?
Here is an example I've put together - https://codepen.io/danlong/pen/XxZROj
As an aside, there's also quite a big performance drop when there are these blocks in the scene which I wasn't expecting. I plan to add more objects to the scene and not sure why the performance drops?
Is it something to do with the below in my animate() loop?
this.world.step(1 / 30);
Code specifically to set up my 'Cannon world' and 'columns' is below:
Cannon World:
this.world = new CANNON.World();
this.world.defaultContactMaterial.contactEquationStiffness = 1e6;
this.world.defaultContactMaterial.contactEquationRegularizationTime = 3;
this.world.solver.iterations = 20;
this.world.gravity.set(0,-25,0);
this.world.allowSleep = true;
this.world.broadphase = new CANNON.SAPBroadphase(this.world);
Columns:
var geometry = new THREE.BoxBufferGeometry(5,5,5);
var material = new THREE.MeshNormalMaterial();
var shape = new CANNON.Box(new CANNON.Vec3(5/2, 5/2, 5/2));
for (var rows = 0, yPos = 2.5; rows < 4; rows++, yPos+=5) {
for (var i = -20; i <= 20; i+=10) {
// physics
var body = new CANNON.Body({
mass: 0.5,
position: new CANNON.Vec3(i, yPos, 0),
friction: 0.1,
restitution: 0.3
});
body.allowSleep = true;
body.sleepSpeedLimit = 0.01;
body.sleepTimeLimit = 1.0;
body.addShape(shape);
this.world.addBody(body);
this.bodies.push(body);
// material
var mesh = new THREE.Mesh(geometry, material);
this.scene.add(mesh);
this.meshes.push(mesh);
}
}
Try this?
body.sleepSpeedLimit = 1.0;

Merge geometries, but use a single material

Somewhat new to Three.js and 3d libraries in general.
I merged two geometries (a quarter cylinder and a plane) using this code:
var planeGeo = new THREE.PlaneGeometry(planeW, planeD / 2, 199, 399);
var planeMesh = new THREE.Mesh(planeGeo);
planeMesh.updateMatrix();
var cylinderGeo = new THREE.CylinderGeometry(100, 100, planeW, 199, 399, true, 0, Math.PI / 2);
cylinderGeo.rotateZ(Math.PI / 2).translate(0, 200, -100);
var cylinderMesh = new THREE.Mesh(cylinderGeo);
cylinderMesh.updateMatrix();
var singleGeometry = new THREE.Geometry();
singleGeometry.merge(planeMesh.geometry, planeMesh.matrix);
singleGeometry.merge(cylinderMesh.geometry, cylinderMesh.matrix);
var testmaterial = new THREE.MeshPhongMaterial({ color: 0x666666 });
mesh = new THREE.Mesh(singleGeometry, testmaterial);
scene.add(mesh);
I then would like to use a single material (png) over the entire thing. This code doesn't work:
textureLoader.load('data/test.png', function (texture) {
material = new THREE.MeshLambertMaterial({
map: texture
});
});
Later in the block with the merging...
mesh = new THREE.Mesh(singleGeometry, material);
scene.add(mesh);
This results in:
I would like the end result to be a single draped png over the entire merged geometry, but I can't find anything that suggests this is a normal thing to do. Is there a better way to achieve that result than merging geometries? Or am I just looking in the wrong places?
A poor-mans solution to achieve this, using the shape supplied in your post, is the following:
https://jsfiddle.net/87wg5z27/44/
Using code from this answer: https://stackoverflow.com/a/20774922/4977165
It sets the UVs based on the bounding box of the geometry, leaving out the z-coordinate (=0). Thats why the texture is a little bit stretched at the top, you can correct that manually or maybe its sufficent for you.
geometry.computeBoundingBox();
var max = geometry.boundingBox.max,
min = geometry.boundingBox.min;
var offset = new THREE.Vector2(0 - min.x, 0 - min.y);
var range = new THREE.Vector2(max.x - min.x, max.y - min.y);
var faces = geometry.faces;
geometry.faceVertexUvs[0] = [];
for (var i = 0; i < faces.length ; i++) {
var v1 = geometry.vertices[faces[i].a],
v2 = geometry.vertices[faces[i].b],
v3 = geometry.vertices[faces[i].c];
geometry.faceVertexUvs[0].push([
new THREE.Vector2((v1.x + offset.x)/range.x ,(v1.y + offset.y)/range.y),
new THREE.Vector2((v2.x + offset.x)/range.x ,(v2.y + offset.y)/range.y),
new THREE.Vector2((v3.x + offset.x)/range.x ,(v3.y + offset.y)/range.y)
]);
}
geometry.uvsNeedUpdate = true;

Resources