I’m trying to make a scene, with rain drops and a 3D model of a roof. For now, i’m trying to detect the collision of the rain drops to the roof, then make a splash effect when there’s a collision. I read that you can use a bound box, but my bound box for the 3D model looks like this
Here is my code for loading the 3D model and creating bound box:
const loader = new GLTFLoader();
loader.load('./simple_roof_model/scene.gltf', function (gltf) {
scene.add(gltf.scene);
gltf.scene.scale.set(0.01, 0.01, 0.01);
gltf.scene.position.set(0, -3, 0);
gltf.scene.rotation.set.y = 1.5;
const model = gltf.scene;
// Update the model's matrix to apply transformations
model.updateMatrixWorld();
// Get the bounding box of the model
const modelBoundingBox = new THREE.Box3().setFromObject(model);
// Create a wireframe geometry based on the bounding box
const wireframeGeometry = new THREE.BoxGeometry().setFromPoints([
modelBoundingBox.min,
modelBoundingBox.max
]);
// Create a wireframe material
const wireframeMaterial = new THREE.MeshBasicMaterial({
color: 0x00ff00, // Green color
wireframe: true // Render as wireframe
});
// Create the wireframe mesh
const wireframeMesh = new THREE.Mesh(wireframeGeometry, wireframeMaterial);
// Add the wireframe mesh to the scene
scene.add(wireframeMesh);
});
How do i fix the bound box to accurately cover the model. And do you have any suggestion on how to make the splash effect?
Technically, if you don’t spawn too many drops per second, it shouldn’t be crazy expensive to just do a raycast from rain drop spawn point towards its falling direction - and just measure the distance to the collision (then despawn the raindrop as soon as it crosses that distance.)
But if you’re creating 1000s of these raindrops and want something quicker:
Create an InstancedMesh of the rain drop particles
Create a spawn surface (plane that limits the area at which the rain drops can spawn and start falling down from.)
Place a camera at the center of that plane, point it downwards and render DepthTexture from that camera perspective to a RenderTarget texture. That way you’ve just did all the raycasting in a single RT.
Whenever you spawn a new raindrop, just sample the RT at the position at which you create the drop (or at the position it will fall at, if it’s falling at an angle.), and calculate the maximum distance drop can travel before splashing.
Ain’t no need to base your rain / particle systems on manually set bounding boxes.
Does make the plane to limit the area where the rain can spawn and fall down make a square where there’s no rain at all? I want to make a 3D scene where a can move the camera around.
Or do you mean like this, i’m trying to make a plane that when the drop get in contact with the plane, the drop will reset/ make spalsh effect (but i’m struggling to write the logic for that)
Also, used layers for cameras and objects. For example, when you render with the orthographic camera to the render target, you don’t need it to “see” the rain, so the camera “sees” objects at the layer 1.
I’m trying to follow your first way, implementing this function to update the rain and detect collision. My logic is that if the y coordinate of the rain drop is close to the y coordinate of the collision point, it\s should be the collision, then it will print out “met”. However, even though the particle met the plane in the scene, no message was printed out. Could you take a look and see what is the problem?
function updateRainPositions() {
intersectionPoints.length = 0; // Clear intersection points array
raindrops.forEach((raindrop, index) => {
// Update position
raindrop.y -= raindrop.velocity*0.1; // Move the raindrop upward
// Check if raindrop is out of screen
if (raindrop.y < -3.5) {
raindrop.y = 10; // Reset position above the screen
raindrop.velocity = THREE.MathUtils.randFloat(0.1, 0.5); // Reset velocity
console.log("reset");
}
// Raycast to check for collision with the plane
const raycaster = new THREE.Raycaster(new THREE.Vector3(raindrop.x, raindrop.y, raindrop.z), new THREE.Vector3(0, -1, 0));
const intersects = raycaster.intersectObject(planeMesh);
// Raycast to check for collision with the second plane
const intersects2 = raycaster.intersectObject(roofMesh);
let collided = false; // Flag to track collision
let intersectionPoint = null; // Store intersection point
// Check for intersection with the plane
if (intersects.length > 0) {
intersectionPoint = intersects[0].point; // Get the intersection point
collided = intersectionPoint.y >= raindrop.y; // Check if collision happened at or below the raindrop
// console.log(intersectionPoint.y);
}
// Check for intersection with the second plane
if (!collided && intersects2.length > 0) {
intersectionPoint = intersects2[0].point;
collided = intersectionPoint.y >= raindrop.y;
// console.log("Intersection point:")
// console.log(intersectionPoint.y);
// console.log("rain drop:")
// console.log(raindrop.y);
}
// Reset raindrop if collision detected
if (collided) {
// Store the intersection point for visualization
intersectionPoints.push(intersectionPoint);
// console.log(raindrop.x);
// console.log(raindrop.y);
// console.log(raindrop.z);
const tolerance = 0.1 * raindrop.velocity;
if (intersectionPoint.y - raindrop.y <= tolerance) {
console.log("met");
// Reset raindrop position and velocity
}
// raindrop.y -= raindrop.velocity; // Move the raindrop upward
// // Reset raindrop position and velocity
// raindrop.y = 10; // Reset position above the screen
// raindrop.velocity = THREE.MathUtils.randFloat(0.1, 0.5); // Reset velocity
}
});
// Update rainMesh positions
for (let i = 0; i < gCount; i++) {
const raindrop = raindrops[i];
const matrix = new THREE.Matrix4().makeTranslation(raindrop.x, raindrop.y, raindrop.z);
rainMesh.setMatrixAt(i, matrix);
}
rainMesh.instanceMatrix.needsUpdate = true; // Update instance matrix
// Visualize intersection points
visualizeIntersectionPoints();
}```
You shouldn’t do the raycasting on every frame - it’s expensive and the result will always be the same (for collisions with static world.)
Only when you spawn a raindrop do the raycast, one time per drop for the entire lifetime of the raindrop - read the length of the vector rainDropStart.sub(intersections[0].point). Then on every frame move the raindrop along it’s path and calculate current distance of the raindrop from it’s starting position. Is the distance between raindrop and the starting position longer than between starting position and the raycaster intersection? Then splash the raindrop.
Genius! This makes me think, water, earth, wind, fire are all in the same category for a reason… Surfaced based fire could essentially be the inverted time scale here with some noise growth following suit with both wind and earth / ‘dust’