Three.js - Detect collision by raycasting from a Group - javascript

I am creating an endless runner game in which a character on a skateboard is static at (0,0,0) and the scenery moves towards that point and is reset to the "back" when it is off screen.
The aim of the game is to dodge cars coming towards you and collect objects that move slower than the cars towards the character, both gaining you points. I am using THREE.Raycaster to detect collision between the skateboard and the cars/objects (different collision scenarios for the cars & objects) which I have got working well.
The Problem
I am detecting collision between the skateboard and the objects because the skateboard extends further than the character in the -z direction (where the objects are coming from) and so it is first to "hit" an object. The skateboard is a group of objects (Still a THREE.Object3D and not a THREE.Group(), but they function the same) containing for example 4 wheels, two trucks, etc... and so I cannot use the main SkateBoardGroup object to detect collision as it is not a Mesh and does not have vertices, which are what determines where the rays are cast from.
The closest Mesh to the objects is the SkateBoardBaseRight Mesh, which is a child of the SkateBoardBase object (containing 3 Meshes), which itself is a child of the main SkateBoardGroup object. I am currently accessing this Mesh with the following code and raycasting from that Mesh:
objectMesh = SkateBoardGroup.children[0].children[2];
I want to be able to raycast from the top level SkateBoardGroup as there are times in which the skateboard rotates and so the SkateBoardBaseRight mesh is not closest to the object.
What I want
I would appreciate an explanation and/or code example of how I can raycast from the THREE.Object3D SkateBoardGroup rather than having to dive into the children of an object.
Setting the raycaster.intersetObjects to recursive is not what I'm looking for as that is for the objects the skateboard collides with.
Here is my raycasting code:
I am calling this function twice, once for the cars and once for the other objects
originObject is the SkateBoardGroup, collidedObjects is the array containing either the cars or the other objects, and doesCollidedObjectsEndGame is a boolean flag to decide which type of object is it (car or other object)
let cooldown = false; // cooldown reset when try-again clicked
let cooldown2 = false;
function detectCollisionRaycaster(originObject, collidedObjects,
doesCollidedObjectsEndGame) {
let objectMesh = originObject.children[0].children[2];
let originPoint = originObject.position.clone();
if (cooldown === false) {
for (var vertexIndex = 0; vertexIndex < objectMesh.geometry.vertices.length; vertexIndex++) {
var localVertex = objectMesh.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( objectMesh.matrix );
var directionVector = globalVertex.sub( objectMesh.position );
var raycaster = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = raycaster.intersectObjects( collidedObjects, true );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) {
if (doesCollidedObjectsEndGame) {
// car objects collided
gameIsOver = true;
cooldown = true;
} else {
if (cooldown2 === false) {
cooldown2 = true;
// other objects collided
setTimeout(function(){
cooldown2 = false;
}, 100);
}
}
}
}
}
}

Related

THREE.js updating object matrix after setting position with Raycaster

I have a run into a problem with FPS camera controls in a three.js scene. I'm using a Raycaster to determine the camera group position based on it's intersection with the scene along the Y axis (a poor man's gravity if you will =) and then apply user input to move around. The camera group position keeps getting reset to the intersection location on every frame, essentially gluing you to the spot.
I'm assuming this is either an updateMatrix() problem or a that a Vector3 is getting passed by reference somewhere, but for the life of me I can't seem to put my finger on it. I need some help... I hope this code is clear enough to help understand the problem :
renderer.setAnimationLoop((event) => {
if (clock.running) {
update();
renderer.render(scene,character.view);
}
});
clock.start();
//
const update = () => {
// velocity
const velocity = new THREE.Vector3();
velocity.x = input.controller.direction.x;
velocity.z = input.controller.direction.y;
velocity.clampLength(0,1);
if (velocity.z < 0) {
velocity.z *= 1.4;
}
// gravity
if (scene.gravity.length() > 0) {
const origin = new THREE.Vector3().copy(character.view.position);
const direction = new THREE.Vector3().copy(scene.gravity).normalize();
const intersection = new THREE.Raycaster(origin,direction).intersectObjects(scene.collision).shift();
if (intersection) {
character.group.position.copy(intersection.point);
character.group.updateMatrix();
}
}
// rotation
const rotation = new THREE.Euler();
rotation.x = input.controller.rotation.y;
rotation.y = input.controller.rotation.x;
character.group.rotation.set(0,rotation.y,0);
character.view.rotation.set(rotation.x,0,0);
// velocity.applyEuler(rotation);
const quaternion = new THREE.Quaternion();
character.group.getWorldQuaternion(quaternion);
velocity.applyQuaternion(quaternion);
// collision
const origin = new THREE.Vector3().setFromMatrixPosition(character.view.matrixWorld);
const direction = new THREE.Vector3().copy(velocity).normalize();
const raycaster = new THREE.Raycaster(origin,direction);
for (const intersection of raycaster.intersectObjects(scene.collision)) {
if (intersection.distance < 0.5) {
// face normals ignore object quaternions
const normal = new THREE.Vector3().copy(intersection.face.normal);
const matrix = new THREE.Matrix4().extractRotation(intersection.object.matrixWorld);
normal.applyMatrix4(matrix);
// normal
normal.multiplyScalar(velocity.clone().dot(normal));
velocity.sub(normal);
}
}
// step
const delta = 0.001 / clock.getDelta();
velocity.multiplyScalar(delta);
// apply
character.group.position.add(velocity);
}
The camera setup is a lot like the PointerLockControls helper, the camera being a child of a Group Object for yaw and pitch. The controller input is defined elsewhere as it can come from the mouse or a gamepad, but it returns normalized values.
To be more precise, the part that is causing the problem is here :
// gravity
if (scene.gravity.length() > 0) {
const origin = new THREE.Vector3().copy(character.view.position);
const direction = new THREE.Vector3().copy(scene.gravity).normalize();
const intersection = new THREE.Raycaster(origin,direction).intersectObjects(scene.collision).shift();
if (intersection) {
character.group.position.copy(intersection.point);
character.group.updateMatrix();
}
}
if I comment out character.group.position.copy(intersection.point);, for example, the camera moves like it's supposed to (except of course it's flying), but otherwise it moves a frame's worth of distance and then gets reset back to the intersection point on the next frame.
I have tried all manner of updateMatrix(), updateMatrixWorld(), updateProjectionMatrix(), and Object.matrixWorldNeedsUpdate = true, but alas no joy.
I apologise for using a copy/paste of my code rather than a testable case scenario. Thank you for your time.
Holy cow, I feel dumb... const origin = new THREE.Vector3().copy(character.view.position); returns local space coordinates, of course it gets reset to the origin!
replacing it with const origin = new THREE.Vector3().setFromMatrixPosition(character.view.matrixWorld); gives me the proper result
There's a lesson in there somewhere about staring blankly et your code for too long. I hope at least that this question helps someone out there one day.

Javascript : optimize data structure for chess game

Trying to figure out how to store some useful data for chess games programming.
I decided to store rays emitted by on-board chessmen in a Raycaster; This question is about the implementation of this structure.
TL;DR (for chess gamers only...)
first of all, I have identified three kinds of rays :
Ray.NORMAL or Ray.FULL: they are emitted by all chessmen but not pawns, in an iterative way (rook, bishop, queen) or not (knight and king)
Ray.CAPTURE: emitted only by pawns, ahead left and /or ahead right captures
Ray.OFFSET: emitted by pawns when moving forward, and kings/rooks for castling
Thus a ray is defined like this :
class Ray {
constructor (owner, kind) {
this.owner = owner // chessman which emits ray
this.kind = kind
this.ref = null
this.sequence = []
}
// is computed afetr construction
expand (ref, sequence) {
this.ref = ref // starting ref (origin of the ray)
this.sequence = sequence // array of refs
}
// is called when ray is inserted into raycaster
interact (otherRay) {
// to be implemented
}
}
Rays also have two special compound properties :
shadowing { ray: null, idx: -1 }
crossing { ray: null, idx: -1 }
which denote where this ray instance may be shadowed by another chessman, and where another ray is crossing it, to detect passability and interference (for castling)
THE PROBLEM:
How to store efficiently rays in the RayCaster?
In a way that optimizes operations such as:
adding a newly computed ray, computing interactions with previously stored ones, at a mo,opmal cost?
determining from a given starting ref, all targeted tiles/ref?
determining easily which rays target a given ref, to compute pression balance on this tile?
PROPOSED SOLUTIONS / ALTERNATIVES
single array of rays : worst case 64 * 63 elements, costful for seeking a ray and compute interactions
Map of arrays : Map.set(startingRef, [list_of_emtted_rays_from_startingRef])
Map of arrays : Map.set(endingRef, [list_of_targetinhg_rays_to_endingRef])
and maybe a good candidate :
maintain 2 maps of arrays, one for emitted, and one for targeting
class RayCaster {
constructor() {
this.startings = new Map()
this.endings = new Map()
}
cast(board) { ...iterate through board and casts individual rays }
add(ray) { ... }
getRefsAttackedBy(ref) { ... }
getRefsAttacking(ref) { ... }
}
So what are your feelings about this data structure (the RayCaster)?
finally since Maps are time constant access, I have considered a double-maps implementation :
constructor() {
this.startings = new Map()
this.endings = new Map()
this.counters = { rays: 0, interactions: 0 }
}
Each map is keyed by board refs, from "a1" to "h8"
casts(board) {
this.counters = {
rays: this.raysFrom(board),
interactions: this.computeInteractions()
}
}
Adding rays is straighforward :
raysFrom(board) {
let counter = 0;
board.traverse((ref, chessman) => {
let rays = chessman.cast(ref)
for(let ray of rays) {
this.add(ray)
}
counter += rays.length
})
return counter
}
And a a simple ray :
add (ray) {
let skey = ray.ref
let sRays = this.startings.get(sKey)
if(sRays.indexOf(ray) === -1) {
sRays.push(ray)
}
ray.traverse((seqIdx, seqRef) => {
let seqKey = seqRef.key
let eRays = this.endings.get(seqKey)
if (eRays.indexOf(ray) === -1) {
eRays.push(ray)
}
})
}
Computing ray interactions (crossing and shading) is more complicated :
computeInteractions() {
let counter = 0
// consider all starting rays
for (let {sRef, sRays} of this.startings) {
for (let sRay of sRays) {
sRay.traverse((seqIdx, seqRef) => {
// consider all possible intersections
// into the endings map
let eRays = this.endings.get(seqRef.ref)
for(let eRay of eRays) {
// ensure that rays are different
if (sRay !== eRay) {
sRay.interact(eRay)
eRay.interact(sRay)
}
}
})
}
}
return counter
}
The rest of the work is just determining in the ray class how two rays could interact (crossing or shading)
Thanks for advice, best regards !

Three.js: Looking to spawn objects and animate the objects on a curve

I am trying to spawn a set of objects on a setInterval and give each of these objects their own animation on a path (currently using requestAnimationFrame to do so). I managed to add one object and animate this on a path. With this code:
var psGeometry = new THREE.PlaneGeometry(3,2,10,1);
var psPlane = new THREE.Mesh(psGeometry, new THREE.MeshBasicMaterial({color:0x0000ff}));
scene.add(psPlane);
function animatePaper(obj = psPlane, offset= 0.007)
{
if(counter <=( 1-obj.geometry.vertices.length/2 *offset))
{
for (var i=0; i < obj.geometry.vertices.length/2; i++)
{
obj.geometry.vertices[i].y = curvePath.getPoint(counter + i * offset).y;
obj.geometry.vertices[i].z = -0.5;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].y = curvePath.getPoint(counter + i * offset).y;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].z = -2.5;
obj.geometry.vertices[i].x = curvePath.getPoint(counter + i * offset).x;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].x = curvePath.getPoint(counter + i * offset).x;
}
obj.geometry.verticesNeedUpdate = true;
counter += 0.005;
}
else{
console.log("Removing...");
scene.remove(obj);
}
}
function animate() {
requestAnimationFrame(animate);
animatePaper(psPlane, 0.007);
render();
}
Example can be found here: jsfiddle.net.
Since this animates the object along the curvePath (see jsfiddle example), I figured that spawning these objects on an interval and applying the above code should work. Wrong!.
I tried: creating a function spawning objects and applying the above code:
setInterval(drawSheets, 1000);
function drawSheets()
{
var psGeometry = new THREE.PlaneGeometry(3,2,10,1);
var psPlane = new THREE.Mesh(psGeometry, new THREE.MeshBasicMaterial({color:0x0000ff}));
scene.add(psPlane);
setInterval(function(){animatePaper(psPlane, 0.007);}, 30);
}
I also tried on the basis of this answer:
setInterval(objArray.forEach(function(obj){setInterval(function(){animatePaper(obj);},300);}), 3000);
Expected:
Spawning multiple objects on an interval and animate each of these objects seperately over a curve.
Hopefully anyone could help me out! Cheers.
Version: Three.js r82
** EDIT: ** Small refinement. After another small test (jsfiddle). I found out that when I use setInterval on a function, it shares the same variable (thus speeding up the animation). Since this is part of the problem I would like to ask if someone knows how to make these variables local to an object.
Consider creating an array containing each of your Path and Plane objects (or perhaps one array for Paths and one array for Planes) along with their distinctive offsets or other values, then loop though these in an update function in your animation loop, running each through your animatePaper function.
In pseudo code:
var planesAndMeshesArray = [
{ path1 : (your plane here), plane1 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
{ path2 : (your plane here), plane2 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
{ path3 : (your plane here), plane3 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
...]
- create a loop to write the above array with random values in an appropriate range to suit the effects you're looking for
- loop through the above array to add each of the meshes and planes to the scene
function update() {
- update each object by looping through the above array through your `animatePaper` function. It works as a handy set of pointers to each of the objects in your scene - if you change them in the array, they will change in your scene.
- also update your controls
}
function animate() {
requestAnimationFrame(animate);
update();
render();
}
Going one step further, you can write object-oriented Javascript to create each of your curve-and-paper objects. I'd recommend starting with the array first and adding further complexity as needed.

Physijs simple collision between meshes without gravity

i am using Physijs to determine static collision between my meshes. As i need to know what surfaces are intersecting.
i hacked a simple demo that seems to work.
currently i have to configure my scene to use gravity, which prevents me from position my meshes in any y position, as they start to fall or float.
is there is simple way to remove the gravity from the simulation, and just use the mesh collision detection?
--update---
i had to explicitly set the mass to each mesh to 0 rather than blank. With mass=0 gravity has no affect. great!
however meshes are not reporting a collision.
any ideas where i am going wrong?
thanks
-lp
You cannot use Physijs for collision detection alone. It just comes fully equipped with real-time physics simulation, based on the ammo.js library. When you set the mass of the meshes to 0, it made them static. They were then unresponsive to external forces, such as collision responses (i.e. the change of velocity applied on the mesh after the collision was detected) or gravity. Also, two static meshes that overlap each other do not fire a collision event.
Solution A: Use ammo.js directly
Ported from Bullet Physics, the library provides the necessary tools for generating physics simulations, or just detect collisions between defined shapes (which Physijs doesn't want us to see). Here's a snippet for detecting collision between 2 rigid spheres:
var bt_collision_configuration;
var bt_dispatcher;
var bt_broadphase;
var bt_collision_world;
var scene_size = 500;
var max_objects = 10; // Tweak this as needed
bt_collision_configuration = new Ammo.btDefaultCollisionConfiguration();
bt_dispatcher = new Ammo.btCollisionDispatcher(bt_collision_configuration);
var wmin = new Ammo.btVector3(-scene_size, -scene_size, -scene_size);
var wmax = new Ammo.btVector3(scene_size, scene_size, scene_size);
// This is one type of broadphase, Ammo.js has others that might be faster
bt_broadphase = new Ammo.bt32BitAxisSweep3(
wmin, wmax, max_objects, 0, true /* disable raycast accelerator */);
bt_collision_world = new Ammo.btCollisionWorld(bt_dispatcher, bt_broadphase, bt_collision_configuration);
// Create two collision objects
var sphere_A = new Ammo.btCollisionObject();
var sphere_B = new Ammo.btCollisionObject();
// Move each to a specific location
sphere_A.getWorldTransform().setOrigin(new Ammo.btVector3(2, 1.5, 0));
sphere_B.getWorldTransform().setOrigin(new Ammo.btVector3(2, 0, 0));
// Create the sphere shape with a radius of 1
var sphere_shape = new Ammo.btSphereShape(1);
// Set the shape of each collision object
sphere_A.setCollisionShape(sphere_shape);
sphere_B.setCollisionShape(sphere_shape);
// Add the collision objects to our collision world
bt_collision_world.addCollisionObject(sphere_A);
bt_collision_world.addCollisionObject(sphere_B);
// Perform collision detection
bt_collision_world.performDiscreteCollisionDetection();
var numManifolds = bt_collision_world.getDispatcher().getNumManifolds();
// For each contact manifold
for(var i = 0; i < numManifolds; i++){
var contactManifold = bt_collision_world.getDispatcher().getManifoldByIndexInternal(i);
var obA = contactManifold.getBody0();
var obB = contactManifold.getBody1();
contactManifold.refreshContactPoints(obA.getWorldTransform(), obB.getWorldTransform());
var numContacts = contactManifold.getNumContacts();
// For each contact point in that manifold
for(var j = 0; j < numContacts; j++){
// Get the contact information
var pt = contactManifold.getContactPoint(j);
var ptA = pt.getPositionWorldOnA();
var ptB = pt.getPositionWorldOnB();
var ptdist = pt.getDistance();
// Do whatever else you need with the information...
}
}
// Oh yeah! Ammo.js wants us to deallocate
// the objects with 'Ammo.destroy(obj)'
I transformed this C++ code into its JS equivalent. There might have been some missing syntax, so you can check the Ammo.js API binding changes for anything that doesn't work.
Solution B: Use THREE's ray caster
The ray caster is less accurate, but can be more precise with the addition of extra vertex count in your shapes. Here's some code to detect collision between 2 boxes:
// General box mesh data
var boxGeometry = new THREE.CubeGeometry(100, 100, 20, 1, 1, 1);
var boxMaterial = new THREE.MeshBasicMaterial({color: 0x8888ff, wireframe: true});
// Create box that detects collision
var dcube = new THREE.Mesh(boxGeometry, boxMaterial);
// Create box to check collision with
var ocube = new THREE.Mesh(boxGeometry, boxMaterial);
// Create ray caster
var rcaster = new THREE.Raycaster(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0));
// Cast a ray through every vertex or extremity
for(var vi = 0, l = dcube.geometry.vertices.length; vi < l; vi++){
var glovert = dcube.geometry.vertices[vi].clone().applyMatrix4(dcube.matrix);
var dirv = glovert.sub(dcube.position);
// Setup ray caster
rcaster.set(dcubeOrigin, dirv.clone().normalize());
// Get collision result
var hitResult = rcaster.intersectObject(ocube);
// Check if collision is within range of other cube
if(hitResult.length && hitResult[0].distance < dirv.length()){
// There was a hit detected between dcube and ocube
}
}
Check out these links for more information (and maybe their source code):
Three.js-Collision-Detection
Basic Collision Detection, Raycasting with Three.js
THREE's ray caster docs

Three.js collision detection with Raycaster: IntersectObjects method is returning intersection distances that don't make sense

I am making a simple three.js game where the user controls a spaceship called playerModel (a Mesh obj with a simple BoxGeometry), and must avoid asteroids (SphereGeometry) which are generated far off in the -Z direction and move in the +Z direction towards the ship.
This is my detectCollisions function:
// Returns true if playerModel is intersecting with
// any of obstacles, false otherwise.
function detectCollisions(obstacles) {
var origin = playerModel.position.clone();
for (var v = 0; v < playerModel.geometry.vertices.length; v++) {
var localVertex = playerModel.geometry.vertices[v].clone();
var globalVertex = localVertex.applyMatrix4(playerModel.matrix);
var directionVector = globalVertex.sub(playerModel.position);
var ray = new THREE.Raycaster(origin, directionVector.clone().normalize());
var intersections = ray.intersectObjects(obstacles);
if (intersections.length > 0 &&
intersections[0].distance < directionVector.length()) {
console.log("Fatal collision!"); // definitely a collision
return true;
}
}
return false;
}
I call this method in the game loop as follows, where "spheres" is the array of asteroids:
function draw() {
// loop draw function call
requestAnimationFrame(draw);
// draw THREE.JS scene
renderer.render(scene, camera);
if (!isGameOver) {
generateSpheres();
handleKey(); // updates player direction
movePlayer(); // moves player in direction
moveSpheres();
if (detectCollisions(spheres))
gameOver();
}
}
This is 'working' in the sense that it detects intersections and calls the gameOver function when it does so, but it is detecting collisions that, visually, are not even close to being collisions.
The ray.intersectObjects method seems to return intersections whose distance is calculated as quite close (something like 10), even though the position of the asteroid object it is supposedly intersecting with is very far (position is something like: x:-872 y:-331 z:-5940) from the playerModel which is at (0, 0, 50).
It also seems like the intersecting asteroid object causing the fatal collision always has faceIndex 37, not sure why. The asteroids are created with rings and segments == 7 and varying radii between 20 and 150. The playerModel is fairly small (w:15, h:15, d:30). Thanks so much in advance for any help!
Maybe it's the BoundingSphere but this can only be a problem if the Object change the size (without scale).
Object.Model.geometry.computeBoundingSphere();
But I don't think this is the solution for your Problem. Can you upload an jsfiddle please?

Categories

Resources