There is an example to show how to raycast using BufferGeometry, but how to do raycast using InstancedBufferGeometry?
For example, add raycasting capability to this demo?
https://threejs.org/examples/?q=instancing#webgl_buffergeometry_instancing
Raycasting support for meshes with InstancedBufferGeometry was never implemented. However, you can use the new InstancedMesh class which was added with R109 and which does support raycasting. Check out the following example to see this feature in action:
https://threejs.org/examples/webgl_instancing_raycast
The important raycasting code section looks like so:
var intersection = raycaster.intersectObject( mesh ); // mesh is type of InstancedMesh
if ( intersection.length > 0 ) {
var instanceId = intersection[ 0 ].instanceId;
mesh.getMatrixAt( instanceId, instanceMatrix ); // get local transformation matrix of the instance
matrix.multiplyMatrices( instanceMatrix, rotationMatrix ); // modify it
mesh.setMatrixAt( instanceId, matrix ); // write it back
mesh.instanceMatrix.needsUpdate = true;
}
three.js R113
Related
I am working on a project that displays buildings. The requirement is to let the building gradually fade out (transparent) based on the distance between the camera and the buildings. Also, this effect has to follow the camera's movement.
I consider using THREE.Fog(), but the Fog seems can only change the material's color.
Above is a picture of the building with white fog.
The buildings are in tiles, each tile is one single geometry (I merged all the buildings into one) using
var bigGeometry = new THREE.Geometry();
bigGeometry.merge(smallGeometry);
The purple/blue color thing is the ground, and ground.material.fog = false;. So the ground won't interact with the fog.
My question is:
Is it possible to let the fog interact with the building's material's opacity instead of color? (more white translate to more transparent)
Or should I use Shader to control the material's opacity based on distance to the camera? But I have no idea of how to do this.
I also considered adding alphaMap. If so, each building tile have to map an alphaMap and all these alphaMap have to interact with the camera's movement. It's going to be a tons of work.
So any suggestions?
Best Regards,
Arthur
NOTE: I suspect there are probably easier/prettier ways to solve this than opacity. In particular, note that partially-opaque buildings will show other buildings behind them. To address that, consider using a gradient or some other scene background, and choosing a fog color to match that, rather than using opacity. But for the sake of trying it...
Here's how to alter an object's opacity based on its distance. This doesn't actually require THREE.Fog, I'm not sure how you would use the fog data directly. Instead I'll use THREE.NodeMaterial, which (as of three.js r96) is fairly experimental. The alternative would be to write a custom shader with THREE.ShaderMaterial, which is also fine.
const material = new THREE.StandardNodeMaterial();
material.transparent = true;
material.color = new THREE.ColorNode( 0xeeeeee );
// Calculate alpha of each fragment roughly as:
// alpha = 1.0 - saturate( distance / cutoff )
//
// Technically this is distance from the origin, for the demo, but
// distance from a custom THREE.Vector3Node would work just as well.
const distance = new THREE.Math2Node(
new THREE.PositionNode( THREE.PositionNode.WORLD ),
new THREE.PositionNode( THREE.PositionNode.WORLD ),
THREE.Math2Node.DOT
);
const normalizedDistance = new THREE.Math1Node(
new THREE.OperatorNode(
distance,
new THREE.FloatNode( 50 * 50 ),
THREE.OperatorNode.DIV
),
THREE.Math1Node.SAT
);
material.alpha = new THREE.OperatorNode(
new THREE.FloatNode( 1.0 ),
normalizedDistance,
THREE.OperatorNode.SUB
);
Demo: https://jsfiddle.net/donmccurdy/1L4s9e0c/
Screenshot:
I am the OP. After spending some time reading how to use Three.js's Shader material. I got some code that is working as desired.
Here's the code: https://jsfiddle.net/yingcai/4dxnysvq/
The basic idea is:
Create an Uniform that contains controls.target (Vector3 position).
Pass vertex position attributes to varying in the Vertex Shader. So
that the Fragment Shader can access it.
Get the distance between each vertex position and controls.target. Calculate alpha value based on the distance.
Assign alpha value to the vertex color.
Another important thing is: Because the fade out mask should follow the camera move, so don't forget to update the control in the uniforms every frame.
// Create uniforms that contains control position value.
uniforms = {
texture: {
value: new THREE.TextureLoader().load("https://threejs.org/examples/textures/water.jpg")
},
control: {
value: controls.target
}
};
// In the render() method.
// Update the uniforms value every frame.
uniforms.control.value = controls.target;
I had the same issue - a few years later - and solved it with the .onBeforeCompile function which is maybe more convenient to use.
There is a great tutorial here
The code itself is simple and could be easily changed for other materials. It just uses the fogFactor as alpha value in the material.
Here the material function:
alphaFog() {
const material = new THREE.MeshPhysicalMaterial();
material.onBeforeCompile = function (shader) {
const alphaFog =
`
#ifdef USE_FOG
#ifdef FOG_EXP2
float fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );
#else
float fogFactor = smoothstep( fogNear, fogFar, vFogDepth );
#endif
gl_FragColor.a = saturate(1.0 - fogFactor);
#endif
`
shader.fragmentShader = shader.fragmentShader.replace(
'#include <fog_fragment>', alphaFog
);
material.userData.shader = shader;
};
material.transparent = true
return material;
}
and afterwards you can use it like
const cube = new THREE.Mesh(geometry, this.alphaFog());
I want to use a texture on the surface of my extruded geometry. I have been researching custom UVgenerators for a while now, and have found these related questions:
1.) How to apply a texture to THREE.ExtrudeGeometry?
2.) Loaded texture appears blurred, or like a single color. How to make the texture crisp and sharp
However, the method proposed to divide my geometry points by 1000 and to mesh.scale.set(1000,1000,1) doesn't work because my geometry is no longer in the correct place. I would prefer to specify the UV Mapping. One answer says to implement a custom uvgenerator based on the source code, but I am stuck & can't figure out what to do.
This is my geometry creation, the material is 512x512px, how can I map a texture onto the top?:
pointList=[[0,0,0],
[0,1000,0],
[750,1000,0],
[750,750,0],
[1000,750,0],
[1000,0,0]]
for (i=0;i < pointList.length; i++) {
point = pointList[i];
x = point[0];
y = point[1];
myPoints.push( new THREE.Vector2 (x,y) );
}
myShape = new THREE.Shape( myPoints );
extrusionSettings = {
amount:height
};
myGeometry = new THREE.ExtrudeGeometry( myShape, extrusionSettings );
resultshape = new THREE.Mesh( myGeometry, material );
You can specify custom UVs for your ExtrudeGeometry by specifying your own UVGenerator, one of the properties of extrusionSettings.
To specify your custom UV generator, you can use as a template THREE.ExtrudeGeometry.WorldUVGenerator, which can be found in src/extras/geometries/ExtrudeGeometry.js.
There is a simpler solution that may work for you, however.
Instead of a custom UV generator, you can take advantage of the offset and repeat properties of your texture. Use the following pattern:
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 1 / 500, 1 / 500 );
texture.offset.set( 0.1, 0.5 );
three.js r.68
I am trying to build a virtual tour inside a building (the whole building is an obj model) using three.js. Everything loads fine and the library is pretty straightforward. My most critical issue is that I can't implement collision detection with the camera, I tried using rays but I couldn't find a suitable example for my case.
My model load:
var loader = new THREE.OBJMTLLoader();
loader.addEventListener( 'load', function ( event ) {
var newModel = event.content;
newModel.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.castShadow = true;
child.receiveShadow = true;
}
} );
scene.add( newModel );
objects.push( newModel );
});
loader.load( 'model/model.obj', 'model/model.mtl' );
The camera creation (I don't know if it is relevant to the issue)
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
10000
);
camera.position.set( 0, 25, 0 );
camera.lookAt( 0, 0, 0 );
NOTE: The camera moves inside the model, I don't want to detect collision between two separate obj models, I want to detect collision (and stop the camera from passing through walls) inside one single model.
Any help will be greatly appreciated
Looking at the documentation for Raycaster in Three.js at http://threejs.org/docs/58/#Reference/Core/Raycaster, you can create a ray like Raycaster( origin, direction, near, far ). Perhaps for you this would look something like
var ray = new THREE.Raycaster(camera.position, cameraForwardDirection, camera.position, collisionDistance);
Where cameraForwardDirection is the direction in front of you camera. I think you can get this by doing something like:
var cameraForwardDirection = new THREE.Vector3(0,0,-1).applyMatrix4(camera.matrixWorld);
This should work because the camera points in the negative Z direction (hence the 0,0,-1) and we want to apply the orientation of the camera to this vector. This assumes you are only moving forward. If you wanted to check for collisions in other directions, you could cast rays in other directions.
collisionDistance would be the minimum distance for a collision. You can experiment with this to find what works with respect to the scale of things in your scene.
Once you have cast this ray, you will need to check for intersections. You can use the ray.intersectObject( object, recursive ) method. Since it seems like you just have that one model, it might look something like:
var intersects = ray.intersectObject(newModel, true);
if(intersects.length>0){
// stop the camera from moving farther into the wall
}
I'm attempting to modify TrackballControls.js so that its rotation is like that of OrbitControls.js, where the horizon stays flat, but maintain the ability to rotate over and around a scene (specifically, a collada building model). I've been trying to figure this out for the better part of a day now, but I'm a designer, not a programmer. :-) I'm not even sure if I should be focusing on this.rotateCamera and/or this.update.
(BTW, I would just use OrbitControls.js, but it doesn't support panning, which is necessary when looking at large collada building models.)
Any help would be much appreciated.
It's been awhile since this question was asked, but I ran into the same problem and didn't find much discussion online, so I thought I'd post my solution.
If you must use TrackballControls and you want to a flat horizon, you can simply edit the TrackballControls.js library by adding the following line to the end of the 'this.rotateCamera' method
this.object.up = new THREE.Vector3(0,1,0);
This locks the camera up direction in the (0,1,0) direction (i.e in the y direction). The entire modified method function would then read:
this.rotateCamera = function () {
var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
if ( angle ) {
var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize();
quaternion = new THREE.Quaternion();
angle *= _this.rotateSpeed;
quaternion.setFromAxisAngle( axis, -angle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
_rotateEnd.applyQuaternion( quaternion );
if ( _this.staticMoving ) {
_rotateStart.copy( _rotateEnd );
} else {
quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
_rotateStart.applyQuaternion( quaternion );
}
}
// Lock the camera up direction
this.object.up = new THREE.Vector3(0,1,0);
};
I have some custom geometries obtained from a STEP file conversion and I use the mouse to rotate them. They rotate around the origin of the scene, but since they are far from it, they seem rotating on a virtual sphere. How can I move them to the origin so that they don't seem "floating" around (I mean that I'd like to reduce to zero the radius of the virtual sphere). This is the example I'd like to move. I've tried setting their position to (0, 0, 0) doing:
object.position.x = 0;
object.position.y = 0;
object.position.z = 0;
but it didin't work.
The typical solution to this problem is to translate the geometry right after it is created. You do that by applying a translation matrix to the geometry like so:
geometry.applyMatrix( new THREE.Matrix4().makeTranslation( distX, distY, distZ ) );
EDIT: You can simply do this, instead:
geometry.translate( distX, distY, distZ ); // three.js r.72
The function geometry.computeBoundingBox() may be of help to you in determining an amount to translate.
However, I see in your case, you have multiple geometries, so it it a bit more complicated, but doable. You will need to translate each geometry by the same amount.
EDIT
Tip: Instead of adding each object to the scene, create a parent object, add it to the scene, and then add the objects to the parent.
var parent;
parent = new THREE.Object3D();
scene.add( parent );
parent.add( object1 );
parent.add( object2 );
// and so on...
Then in your render function, just rotate the parent, not the individual objects.