THREE.js: Looking for an alternative to MeshFaceMaterial - three.js

According to various posts at the three.js github, MeshFaceMaterial will be deprecated eventually.
I currently use this for my terrain. Granted it's not the best way to do it. Actually its pretty crappy. For one I cannot use BufferGeometry which is not good considering I generally have 2 layers of 128x128 (segmented) planes for terrain. Very high memory usage.
I've adapted all my code to allow for the terrain to be BufferGeometry except two things don't work. MeshFaceMaterial and BufferGeometry.merge(). The merge doesn't work on indexed geometry which to me is weird considering THREE creates this geometry, yet it can merge non-indexed geometry from blender. It cannot merge geometry it creates itself but can merge geometry from external sources... Oh well that's another post there, back to MeshFaceMaterial.
I currently use a 128x128 "MaterialMap". Each pixel represents a materialIndex for each face of the plane. This has two serious drawbacks. Squared up sections of terrain (no curves) and harsh distinctions on the borders of textures.
My question is: How can I generate this terrain with multiple textures without using MeshFaceMaterial. The highest res texture I have is 2048x2048 and zone size can easily be 10000x10000 making repeat necessary (right?).
Ultimately my goal is to use BufferGeometry and get rid of MeshFaceMaterial.
MaterialMap example:
Terrain Example (terribly cropped sorry {work pc}):

You helped me out a while back via email with advice on culling meshes so I would like to return the favor (with my humble strategy) :)
If you want to use THREE.PlaneBufferGeometry (which, as you know, is where all geometry in THREE.js is soon headed), then my advice would be to layer different PlaneBufferGeometries right on top of each other. For instance in the above example picture, you could have
var stoneFloorGeometry = new THREE.PlaneBufferGeometry(arenaWidth, arenaHeight, 1, 1);
var stoneFloorMaterial = new THREE.MeshBasicMaterial({
depthWrite: false, // This is always underneath every other object
map: stoneFloorTexture
});
var stoneFloor = new THREE.Mesh(stoneFloorGeometry, stoneFloorMaterial);
stoneFloor.rotation.x = Math.PI / -2; // rotate to be flat in the X-Z plane
stoneFloor.position.set(0, 0, 0);
scene.add(stoneFloor);
// now add the grass plane right on top of that with its own texture and shape
var grassGeometry = new THREE.PlaneBufferGeometry(lawnWidth, lawnHeight, 1, 1);
var grassMaterial = new THREE.MeshBasicMaterial({
depthWrite: false, // this is rendered right on top of the stone floor
map: grassTexture
});
var grass = new THREE.Mesh(grassGeometry, grassMaterial);
grass.rotation.x = Math.PI / -2;
grass.position.set(0, 0, 0);
scene.add(grass);
// finally add the stone path walkway on top of the grass, leading to the castle
var walkwayGeometry = new THREE.PlaneBufferGeometry(walkwayWidth, walkwayHeight, 1, 1);
var walkwayMaterial = new THREE.MeshBasicMaterial({
depthWrite: false, // this is rendered right on top of the grass
map: stoneFloorTexture // uses same texture as large stoneFloor before
});
var walkway = new THREE.Mesh(walkwayGeometry, walkwayMaterial);
walkway.rotation.x = Math.PI / -2;
walkway.position.set(0, 0, 0);
scene.add(walkway);
As long as you layer the level from bottom to top and disable depthWrite, all the various textures will correctly show up on top of each other, and none will Z-fight. So, stoneFloor is added to the scene first, followed by grass, followed by walkway.
And since depthTest is still active, your moving game characters will render on top of all these various textures. Initially, it also looked like it worked with just disabling 'depthTest', but the textures ended up rendering over ('above') the characters/models which is incorrect.
Eventually when THREE.js moves ShapeGeometry over to BufferGeometry, it would be nice to define an arbitrary polygonal shape (like an octagon or something) and then texture map that and lay down shapes on top of each other for the game level in a similar manner, thus avoiding the 'square' problem you mentioned.
As for this current solution, on the modern CPU/GPU I don't think you will see much performance cost in creating 3 PlaneBufferGeometries instead of 1 large one with multiple faces/indexes. This way you have the advantages of using THREE's BufferGeometry while still having everything 'looking' like it is all texture-mapped to one large plane.
Hope this helps!
-Erich (erichlof on GitHub)

Related

THREEjs create an intersection plane for a raycast with negative origin

I have a THREEJS scene with an object that 'looks at my mouse'. This works fine and I am using a raycast to get the mouse position like so:
this.intersectionPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 10);
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
this.pointOfIntersection = new THREE.Vector3();
On the mouse-move event I lookAt the pointOfIntersection vector and the object rotates. This works really well.
onDocumentMouseMove = (event) => {
event.preventDefault();
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.raycaster.setFromCamera(this.mouse, this.camera);
this.raycaster.ray.intersectPlane(this.intersectionPlane, this.pointOfIntersection);
let v3 = new THREE.Vector3(this.pointOfIntersection.x*0.05, this.pointOfIntersection.y*0.05, this.pointOfIntersection.z);
if(this.pebbleLogo){
this.pebbleLogo.lookAt(v3);
// console.log(v3);
}
if(this.videoWall){
this.videoWall.lookAt(v3);
}
}
BUT, I want to do the same thing with another object that lives at a z-depth of -20 and the camera flies through to this position. At this point, it also flies through the intersectionPlane and the raycast no longer works.
The intersectionPlane is not added to the scene so it doesn't have a position that I can move so how do I make sure that it stays with the camera?
I can see that the plane has two properties:
normal - (optional) a unit length Vector3 defining the normal of the plane. Default is (1, 0, 0).
constant - (optional) the signed distance from the origin to the plane. Default is 0.
I have been able to move the Plane using a translate but this is not ideal as I need the plane to be in a constant position in relation to the camera (just in front of it). I tried to make the plane a child of the camera but it didn't seem to make any difference to its position.
Any help appreciated.
When you perform renderer.render(scene, cam), the engine updates the transformation matrices of all objects that need to be rendered. However, since your camera and plane are not descendants of the scene, you'll have to manually update these matrices. The plane doesn't know that it's parent camera has moved, so you might need to perform plane.updateMatrix(). You can read about manually updating transformation matrices in the docs.
I think since only the parent moves, you might need to use updateMatrixWorld() or updateWorldMatrix() instead. But one of these 3 options should work.
Edit
Upon re-reading your code, it looks like you're using a purely Mathematical THREE.Plane object. This is not an Object3D, which means it cannot be added as a child of anything, so it doesn't behave as a regular object.
My answer assumed you were using a Mesh with PlaneGeometry, which is an Object3D, and it can be added as a child of the camera.

THREE.js raycaster intersectObject method returns no intersection when checking if point is inside mesh

I want to check whether a point is inside a mesh or not. To do so, I use a raycaster, set it to the point's origin and if the ray intersects the mesh only once, it must be inside. Unfortunately, the intersectObject always returns no intersection, even in cases I know that the point is located inside the mesh.
The point's origin is given in world coordinates and the mesh's matrixWorld is up to date too. Also, I set the mesh.material.side to THREE.DoubleSide, so that the intersection from inside should be detected. I tried setting the recursive attribute to true as well, but as expected, this didn't have any effect (since the mesh is a box geometry). The mesh is coming from the Autodesk Forge viewer interface.
Here is my code:
mesh.material.side = THREE.DoubleSide;
const raycaster = new THREE.Raycaster();
let vertex = new THREE.Vector3();
vertex.fromArray(positions, positionIndex);
vertex.applyMatrix4(matrixWorld);
const rayDirection = new THREE.Vector3(1, 1, 1).normalize();
raycaster.set(vertex, rayDirection);
const intersects = raycaster.intersectObject(mesh);
if (intersects.length % 2 === 1) {
isPointInside = true;
}
The vertex looks like this (and it obviosly lies inside of the bounding box):
The mesh is a box shaped room with the following bounding box:
The mesh looks like this:
The geometry of the mesh holds the vertices in the vb. After applying the world matrix, the mesh vertices are correct in world space. Here is a part of the vb list:
Why does the raycaster not return any intersection? Is the matrixWorld of the mesh taken into account when computing the intersections?
Thanks for any kind of help!
Note that Forge Viewer is based on three.js version R71, and it had to modify/reimplement some parts of the library to handle large and complex models (especially architecture and infrastructure designs), so THREE.Mesh objects might have a slightly different structure. In that case I'd suggest to raycast using Forge Viewer's own mechanisms, e.g., using viewer.impl.rayIntersect(ray, ignoreTransparent, dbIds, modelIds, intersections);.

Three.js - get terrain height (position.y) of the mesh at specific position.x,z - without mouse and raycaster?

Let's say I have a sort of rather simple terrain from Blender exported as GLB object, which is a Group and contains a Mesh with BufferGeometry. I have another GLB object which is a model of vehicle. How can I read proper position.y at specific x,z locations (idealy 4 locations for setting car position and rotation) without moving mouse and using raycaster? I need to know what is elevation and height at specific region. Any simple clue without game-physics engine on top of ThreeJS?
Just use a Raycaster. I don't know why you don't want to use it, it's the easiest way to find an intersection without a physics engine and without tons of math.
Simply use Raycaster.set() to point straight down from your XZ coords and see where it intersects the terrain:
var ray = new THREE.Raycaster();
var rayPos = new THREE.Vector3();
// Use y = 100 to ensure ray starts above terran
rayPos.set(x, 100, z);
var rayDir = new THREE.Vector3(0, -1, 0); // Ray points down
// Set ray from pos, pointing down
ray.set(rayPos, rayDir);
// Check where it intersects terrain Mesh
let intersect = ray.intersectObject(terrainMesh);
console.log(intersect);
See here for the intersect object. It includes the point in space where the intersection takes place.

Make text always appear orthogonal to the plane when rotating a cube

I'm creating text labels that appear on a 3D cube using the following pattern:
canvas = createTextCanvas(text, color, font, size);
texture = new THREE.Texture(canvas);
geom = new THREE.PlaneBufferGeometry(canvas.width, canvas.height, segW, segH);
material = new THREE.MeshBasicMaterial({map: texture, transparent: true});
mesh = new THREE.Mesh(geom, material);
mesh.position.x = x;
mesh.position.y = y;
mesh.position.z = z;
texture.needsUpdate = true;
The labels and their positions get set within a for loop for each edge of the cube. This results in labels appearing similar to this:
But then when I rotate the cube (using OrbitControls), you'll see that the label no longer appears vertically like above:
So using the Cost label as an example, I would want the text to remain vertically oriented whenever the cube is rotated. Basically, I'm trying to mimic the behavior of axis labeling in VTK.
So I believe the solution here is to set the up vector of the label to a vector that's always orthogonal to the plane. But I'm not sure how to implement this. Any suggestions or examples would be greatly appreciated.
If it helps, I'm constructing the cube using a BoxGeometry and MeshNormalMaterial.
Do you mean the label keeps moving with the cube or not?
If not, there is a example: http://stemkoski.github.io/Three.js/Sprite-Text-Labels.html. The label keeps facing to you but may not vertical.
Else ,you may need a canvas texture,the label is a object just like the cube and you can set its position to keep it vertical.But it doesn't look good sometime.the example:http://stemkoski.github.io/Three.js/Texture-From-Canvas.html.
I think you just want the label always facing to you when you change your sight.

Automatically keep texture's ratio and texture's size when repeating

I'm using three.js to build a house, i have walls and textures for walls.
Walls are basically CubeGeometry.
I took a unique scale : 1cm = 1px
textures could be bricks or any other construction materials, but textures files are always 512x512 with details in it. for example : One brick in the texture file will be 10px per 4px.
I need to keep this ratio to keep the reality of the scene.
var texture = THREE.ImageUtils.loadTexture("images/wall.jpg");
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat = new THREE.Vector2(0.2 , 0.2);
var wall = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 10), new THREE.MeshBasicMaterial({ map: texture }));
that's works quite well because the case is simple ;), it is also very simple to made a ratio for every walls (which can have any sort of sizes)
But in fact i have plenty of other geometries (procedurally generated and so on), is there by any chance a property like : "THREE.KeepMyTextureSizeAndRepeat" wrapping ? Or will i need to make ratios for each of my custom geometries ?
If you have any advice on this use case, i will be greatfull :)
Many thanks
EDIT : the final goal is to do something like "patterns" in 2D canvas
var pattern = context.createPattern(imageObj, repeatOption);
context.fillStyle = pattern;
context.fill();
when you do something like that, on a rect for example the pattern will be repeated keeping the aspect ratio (e.g http://www.html5canvastutorials.com/tutorials/html5-canvas-patterns-tutorial/)
you'll have to calculate every ratio by yourself because a texture doesn't know anything about the pixels. It only knows about the uvs.

Resources