Raycaster inside box with BackSide material doesn't work - three.js

I am doing a project on Tree.js (NICE lib!).
I have a problem. The rayCaster does not work when the ray is made inside the box.
I have an example here: https://jsfiddle.net/manumid/7sqd6xzs/7/
(extracted from https://github.com/mrdoob/three.js/issues/3969).
The box materials are THREE.BackSide, and I am using MultiMaterial because I need to represent a room with different textures. The material is made like this:
var materials = [
new THREE.MeshBasicMaterial({color:0xff0000,
side:THREE.BackSide}), // Right
new THREE.MeshBasicMaterial({color:0x00ff00,
side:THREE.BackSide}), // Left
new THREE.MeshBasicMaterial({color:0x0000ff,
side:THREE.BackSide}), // Top
new THREE.MeshBasicMaterial({color:0x222222,
side:THREE.BackSide}), // Bottom
new THREE.MeshBasicMaterial({color:0xffff00,
side:THREE.BackSide}), // Back
new THREE.MeshBasicMaterial({color:0x00ffff,
side:THREE.BackSide}), // Front
];
var cubeSidesMaterial = new THREE.MultiMaterial(materials);
var roomGeometry = new THREE.BoxGeometry(4, 4, 4);
var newRoom = new THREE.Mesh(roomGeometry, cubeSidesMaterial);
When the ray is made from outside, there is intersection, but against the external faces of the box (can it be a bug? The external faces have no textures!). This can be seen with the distance of the intersection.
Anyway, as a temporal solution, I can make the room with individual Planes joined on a Group.
Thank you!

Related

A-frame: How to give a primitive like Ring or Cylinder depth?

Is it possible to give a 2D primitive like Ring thickness/depth? I'm trying to make a 3D door with primitives and I want to make either a ring that has some depth or a cylinder with a thicker mesh. Even if it's at the three.js layer I would like to learn how to do this so I don't have to rely on imported 3D objects for the whole design.
To extrude a shape, you will need to use THREE.js. Below is an example for how to do this.
https://threejs.org/examples/?q=extrude#webgl_geometry_extrude_shapes2
How to use THREE.js geometery inside AFrame?
You make a custom component that creates a new THREE.Geometry, and inside that component, you build your shape and extrude it (see the THREE.js example, click (<>) button in lower right to see the code).
Below is an example that makes a custom quad in a component.
You can see the glitch here.
https://glitch.com/edit/#!/custom-quad
Look in the quad.js for details.
You should be able to copy code from the three.js extrude demo, and place it into your custom component init function to build the extruded shape.
Then you can create parameters, like 'thickness', and put them in the schema, and then they can be accessed from the component name on the entity.
If you don't yet know how to write a custom component, you will need to practice this to understand how it works.
https://aframe.io/docs/0.9.0/introduction/writing-a-component.html
Here is a snippet from my glitch that makes a custom quad. It shows how to make THREE.Geometry in an AFrame custom component.
init: function (data) {
var geometry = new THREE.Geometry();
geometry.vertices = data.vertices.map(function (vertex) {
var points = vertex.split(' ').map(function(x){return parseFloat(x);});
return new THREE.Vector3(points[0], points[1], points[2], points[3]);
});
// Build the UVs on the faces.
geometry.faceVertexUvs[0].push(
[ new THREE.Vector2(1,0), new THREE.Vector2(0,1), new THREE.Vector2(1,1)
],
[ new THREE.Vector2(1,0), new THREE.Vector2(0,0), new THREE.Vector2(0,1)
]);
geometry.computeBoundingBox();
geometry.faces.push(new THREE.Face3(0, 2, 1), new THREE.Face3(0, 3, 2));
geometry.mergeVertices();
geometry.computeFaceNormals();
geometry.computeVertexNormals();
this.geometry = geometry;
//console.log(data);
}

How to declare a "mask" material using A-Frame.js

I'm trying to make a scene where there's a "hole in the wall".
This requires a plane with square removed, and then a material applied to the plane with the following properties:
Invisible to the camera
Hides any other objects from being rendered that are behind it
There's an example of this with three.js here, but how can I do it with the a-frame material syntax?
The "Mask".
Looking at the box-hole example, to create the illusion, Lee creates two boxes.
1) The box which is "in the hole"
2) A slightly bigger invisible box without a top - to cloak the first one. The top is removed to work as a "hole" through which you can see the first box
How it can be done in THREE.js
The cloaking is done by preventing the second box from rendering any color. From Lee's example:
let material = new THREE.MeshBasicMaterial({
colorWrite: false;
})
The docs state, that the flag can be used to create invisible objects hiding others.
How it can be done in a-frame
I'm afraid you can't simply make the "cloak" material in a-frame. The colorWrite property is not exposed in the material component.
What i think the simplest way would be - is creating a cloak component, which will create the second box in THREE.js:
AFRAME.registerComponent('cloak', {
init: function() {
let geometry = new THREE.BoxGeometry(1, 1, 1)
geometry.faces.splice(4, 2) // cut out the top faces
let material = new THREE.MeshBasicMaterial({
colorWrite: false
})
let mesh = new THREE.Mesh(geometry, material)
mesh.scale.set(1.1, 1.1, 1.1)
this.el.object3D.add(mesh)
}
})
and use it like this:
<a-box material="src: myPic.png; side: back;" cloak>
Check it out in this codepen. With a HIRO marker, you should get a hole like this:
Using models, or other objects as "cloaks"
Here we need to apply the colorWrite=false magic to each node/child of the model.
init: function() {
// make sure the model is loaded first
this.el.addEventListener('model-loaded', e=>{
let mesh = this.el.getObject3D('mesh') // grab the mesh
if (mesh === undefined) return; // return if no mesh :(
mesh.traverse(function(node) { // traverse through and apply settings
if (node.isMesh && node.material) { // make sure the element can be a cloak
node.material.colorWrite = false
node.material.needsUpdate = true;
}
});
})
}
Also make sure the cloak is rendered before the elements that needs cloaking:
<a-marker>
<a-entity gltf-model="#wall-with-a-hole" cloak-component></a-entity>
<!-- the other stuff that needs to be cloaked-->
</a-marker

Three.js FBXLoader2, change color of single Mesh (in Group or detached)

I have a .fbx model which load via FBXLoader2.
When I add the model (which include 3 meshes) to the scene, it is added as a Group. Now I try to change the color of a single Mesh in the Group, but all 3 meshes are getting the color. Then i thought they might be "linked" because of the Group. So i detached them (SceneUtils.detach).
Now I have all the meshes in the scene. Still, when I change the color of a single mesh, all three of them get the color. When I console.log the mesh (in the group or detached) it shows me the correct mesh.
var loader = new THREE.FBXLoader( manager );
loader.load( 'somemodel.fbx', function( object ) {
model = object;
var modelLength = model.children.length;
for (i=0;i<modelLength;i++) {
THREE.SceneUtils.detach(model.children[0], model, scene);
}
scene.children[0].material.emissive.setHex( 0xff0000 );
}
When i try to change position or scale the mesh, it works fine.
Anybody had the problem before?
Just clone the material and replace it with original once.
scene.children[0].material = scene.children[0].material.clone();
scene.children[0].material.emissive.setHex( 0xff0000 );

THREE.js repeating UV texture using JSONLoader

I need help for getting UV Textures to be rendered correctly in three.js. I've created a model with repeating tile texture in Blender. The tile texture is applied using UV mapping, and it looks like this if it is being rendered correctly:
Render image using Blender
.However, when it is loaded using JSONLoader by three.js, the tiles are just stretched to fill each polygon, giving weird result like this:Screenshot of render using three.js
. I've tried setting THREE.RepeatWrapping in my code but nothing changed:
bodyLoader = new THREE.JSONLoader();
bodyLoader.load('./starofthesea_threejs.json', function(geometry, materials) {
mainBodyMaterials = new THREE.MeshFaceMaterial(materials);
console.log(materials);
mainBodyMaterials.wrapS = THREE.RepeatWrapping;
mainBodyMaterials.wrapT = THREE.RepeatWrapping;
mainBodyMaterials.needsUpdate = true;
mainBody = new THREE.Mesh(geometry, mainBodyMaterials);
mainBody.traverse ( function (child) {
if (child instanceof THREE.Mesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
mainBody.scale.x = mainBody.scale.y = mainBody.scale.z = 1;
geometry.computeBoundingBox();
geometry.computeFaceNormals();
geometry.computeFlatVertexNormals();
scene.add(mainBody);
});
Is there anything wrong in my code, or workaround to get it rendered correctly? All help is deeply appreciated.
Finally I've figured out the problem by myself, where both the Blender model and JS are misconfigured. RepeatWrapping should be applied to texture but not material. I need to study the structure of THREE.MeshFaceMaterial to find the handle for the underlying textures. I need to traverse through the materials to find out all materials with image textures:
mainBodyMaterials = new THREE.MeshFaceMaterial(materials);
for(prop in mainBodyMaterials.materials) {
if(mainBodyMaterials.materials[prop].map != null) {
mainBodyMaterials.materials[prop].map.wrapS = THREE.RepeatWrapping;
mainBodyMaterials.materials[prop].map.wrapT = THREE.RepeatWrapping;
tex.push(mainBodyMaterials.materials[prop].map.clone());
tex[tex_sequence].needsUpdate = true;
tex_sequence++;
}
}
After applying wrapS and wrapT to textures correctly, one of the tile materials get rendered correctly, but 'Texture marked for update but image is undefined' error keep throwing out. I need to clone the texture to get rid of the error, according to another question: Three.js r72 - Texture marked for update but image is undefined?
As there are several materials with repeating tiles, I need to create a global array at the beginning of the JS routine, and push the modified textures one by one when looping through the materials:
var tex = new Array();
var tex_sequence = 0;
After fixing the JS calls, one of the textures are still not working. I forgot that only ONE UV slot is allowed for three.js. I need to unwrap the UVs again under the same UV slot in Blender. Everything works like a charm, and I hope my painful experience can help those who are getting mad by similar problems.

Applying a material to an imported model [three.js]

Part of a project that I'm working on requires that I pull in an array of base64 images, then pull in imported models via THREE.JSONLoader(). If I keep these two types of assets apart, they both load without issue. However, if I try to apply the image as a material to the model, I get an error.
WARNING: Output of vertex shader 'vWorldPosition' not read by fragment shader
I realize that much of the time when you load in geometry, you need to run geometry.computeTangets(), but that isn't working. I thought perhaps there's another computation that might need to run prior to creating a mesh with the geometry and the material. Here's what I have:
var material = new THREE.MeshPhongMaterial({
map: THREE.ImageUtils.loadTexture(data.images[0]),
shininess: 4.0,
});
var loader = new THREE.JSONLoader();
loader.load("app/museum/geometry.json", function (geometry) {
geometry.computeTangents();
mesh = new THREE.Mesh(geometry, material);
mesh.scale.set(100, 100, 100);
mesh.position.y = 150;
mesh.position.x = 0;
scene.add(mesh);
});
I assume there's an additional setting or method that needs to be called, or perhaps there are attributes in the geometry.json that I need to set manually?

Resources