Wie erkennt man eine Kollision in three.js?

Lesezeit: 11 Minuten

Benutzeravatar von eqiproo
eqiproo

Ich verwende three.js.

Ich habe zwei Mesh-Geometrien in meiner Szene.

Wenn diese Geometrien geschnitten werden (bzw würde sich schneiden falls übersetzt) ​​möchte ich dies als Kollision erkennen.

Wie führe ich eine Kollisionserkennung mit three.js durch? Wenn three.js keine Kollisionserkennungsfunktionen hat, gibt es andere Bibliotheken, die ich in Verbindung mit three.js verwenden könnte?

  • Ich habe es auf Google gesucht, aber ich habe nur Strahlenkollision gefunden.

    – eqiproo

    13. Juli 2012 um 15:56 Uhr

  • Ich denke, Ray Collision ist der richtige Weg … die CollisionUtils.js- und Collisions.js-Dateien, auf die Adnan (vermutlich) verweist, sind veraltet und nicht Teil der neuesten (v49 zum Zeitpunkt des Schreibens) three.js Ausführung.

    – Stemkoski

    13. Juli 2012 um 19:14 Uhr

  • Scheint mir eine gute Frage zu sein. SO kann so dumm sein.

    – Lee Goddard

    22. Oktober 2014 um 8:56 Uhr

  • @Adi Ich habe es gegoogelt. Und dies IST das erste Ergebnis.

    – Benutzer3024235

    26. Juni 2016 um 3:18 Uhr

  • Ich muss eine Wand erstellen und ein Fenster hinzufügen, das der Benutzer und der Benutzer über die Wand ziehen, bevor sie entscheiden, wo sie an der Wand platziert werden sollen. Ich muss das Ziehen des Fensters in den Grenzen der Wand begrenzen. Ich denke, ich muss Kollisionen erkennen und Scheitelpunkte oder so etwas bekommen. Ich bin mir nicht sicher, bitte schlagen Sie etwas vor. Ich bin neu bei three.js oder jeder Art von 3D-App.

    – Tiefen

    27. September 2017 um 9:49 Uhr

Stemkoskis Benutzeravatar
Stammkoski

In Three.js scheinen die Hilfsprogramme CollisionUtils.js und Collisions.js nicht mehr unterstützt zu werden, und mrdoob (Ersteller von three.js) selbst empfiehlt, auf die neueste Version von three.js zu aktualisieren und zu diesem Zweck die Ray-Klasse zu verwenden stattdessen. Was folgt, ist eine Möglichkeit, dies zu tun.

Die Idee ist folgende: Nehmen wir an, wir wollen prüfen, ob ein bestimmtes Mesh namens “Player” irgendwelche Meshes schneidet, die in einem Array namens “collidableMeshList” enthalten sind. Was wir tun können, ist eine Reihe von Strahlen zu erstellen, die an den Koordinaten des Player-Netzes (Player.position) beginnen und sich zu jedem Scheitelpunkt in der Geometrie des Player-Netzes erstrecken. Jeder Strahl hat eine Methode namens “intersectObjects”, die ein Array von Objekten zurückgibt, die der Strahl geschnitten hat, sowie die Entfernung zu jedem dieser Objekte (gemessen vom Ursprung des Strahls). Wenn der Abstand zu einer Kreuzung geringer ist als der Abstand zwischen der Position des Spielers und dem Scheitelpunkt der Geometrie, dann ist die Kollision im Inneren des Netzes des Spielers aufgetreten – was wir wahrscheinlich als „tatsächliche“ Kollision bezeichnen würden.

Ich habe ein funktionierendes Beispiel gepostet unter:

http://stemkoski.github.io/Three.js/Collision-Detection.html

Du kannst den roten Drahtgitterwürfel mit den Pfeiltasten bewegen und mit W/A/S/D drehen. Wenn es einen der blauen Würfel schneidet, erscheint das Wort „Hit“ oben auf dem Bildschirm einmal für jede Kreuzung, wie oben beschrieben. Der wichtige Teil des Codes ist unten.

for (var vertexIndex = 0; vertexIndex < Player.geometry.vertices.length; vertexIndex++)
{       
    var localVertex = Player.geometry.vertices[vertexIndex].clone();
    var globalVertex = Player.matrix.multiplyVector3(localVertex);
    var directionVector = globalVertex.subSelf( Player.position );

    var ray = new THREE.Ray( Player.position, directionVector.clone().normalize() );
    var collisionResults = ray.intersectObjects( collidableMeshList );
    if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) 
    {
        // a collision occurred... do something...
    }
}

Es gibt zwei potentielle Probleme bei diesem speziellen Ansatz.

(1) Wenn der Ursprung des Strahls innerhalb eines Netzes M liegt, werden keine Kollisionsergebnisse zwischen dem Strahl und M zurückgegeben.

(2) Es ist möglich, dass ein (im Verhältnis zum Player-Mesh) kleines Objekt zwischen den verschiedenen Strahlen „durchrutscht“ und somit keine Kollision registriert wird. Zwei mögliche Ansätze, um die Wahrscheinlichkeit dieses Problems zu verringern, bestehen darin, Code zu schreiben, sodass die kleinen Objekte die Strahlen erzeugen und die Kollisionserkennung aus ihrer Perspektive durchführen, oder mehr Scheitelpunkte in das Netz aufzunehmen (z. B. mit CubeGeometry(100, 100, 100, 20, 20, 20) statt CubeGeometry(100, 100, 100, 1, 1, 1).) Der letztere Ansatz wird wahrscheinlich zu Leistungseinbußen führen, daher empfehle ich, ihn sparsam zu verwenden.

Ich hoffe, dass andere mit ihren Lösungen zu dieser Frage zu dieser Frage beitragen werden. Ich habe selbst eine ganze Weile damit gekämpft, bevor ich die hier beschriebene Lösung entwickelt habe.

  • Vielen Dank für diese ausführliche Erklärung! Ich kämpfe auch immer noch damit, eine anständige Lösung für mein Spiel mit Gelände und 3D-Objekten zu finden, und Ihre Antwort hat mir einige neue Einblicke gegeben!

    – Nik

    25. August 2012 um 13:13 Uhr

  • Während diese Methode zu testen scheint, ob sich ein Scheitelpunkt von der Mitte des Objekts schneidet, wäre es doppelt so langsam, aber 100% (?) genau, alle Kanten (verbundene Scheitelpunkte) zu testen. Um dies näher auszuführen, müssten Sie jede Fläche durchlaufen und einen Scheitelpunkt nehmen[n] und Scheitel[(n + 1)%len] um alle Kanten zu bekommen. Wenn ich jemanden umarme, schneiden sie die Mitte meiner Position und meine Hand, aber sie schneiden nicht meine Haut, wie es eine Kantenprüfung testen würde.

    – dansch

    10. August 2013 um 3:59 Uhr


  • Das ist eine nette Idee! Für 100% (?) Genauigkeit müssten Sie die Kanten an jedem der beiden Netze testen, und Sie müssten sie in beide Richtungen testen, da Kollisionen nur in einer Richtung erkannt werden, wenn der Strahl ausgeht von der Außenseite zur Innenseite des Netzes. Sicher, es könnte etwas langsamer sein, aber Sie könnten es mit einer vorläufigen Überprüfung des Begrenzungskugelradius beschleunigen. Aber vor allem denke ich, dass Sie mit 100% Genauigkeit Recht haben könnten …

    – Stemkoski

    10. August 2013 um 20:30 Uhr


  • Ich würde vorschlagen, dass Sie vermeiden, eine neue zu instanziieren Raycaster innerhalb der Renderschleife. Instanziieren Sie eine und verwenden Sie sie wieder.

    – WestLangley

    18. November 2013 um 15:11 Uhr

  • Was ist, wenn es keine gibt geometry.vertices Daten in Netzgeometrie. In obj-Modellen gibt es geometry.attributes.position.array aber nein geometry.vertices

    – XIMRX

    28. Januar 2020 um 13:34 Uhr

Benutzeravatar von Kartheyan
Karthejan

Eine aktualisierte Version von Lees Antwort, die mit der neuesten Version von three.js funktioniert

for (var vertexIndex = 0; vertexIndex < Player.geometry.attributes.position.array.length; vertexIndex++)
{       
    var localVertex = new THREE.Vector3().fromBufferAttribute(Player.geometry.attributes.position, vertexIndex).clone();
    var globalVertex = localVertex.applyMatrix4(Player.matrix);
    var directionVector = globalVertex.sub( Player.position );

    var ray = new THREE.Raycaster( Player.position, directionVector.clone().normalize() );
    var collisionResults = ray.intersectObjects( collidableMeshList );
    if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) 
    {
        // a collision occurred... do something...
    }
}

  • ist es Index in der dritten Zeile oder VertexIndex?

    – Hesamoy

    27. Januar um 10:50 Uhr

  • Ja, so ist es. Behoben.

    – Karthejan

    31. Januar um 9:38 Uhr

Benutzeravatar von Toji
Toji

Dies ist wirklich ein viel zu weit gefasstes Thema, um es in einer SO-Frage zu behandeln, aber um die SEO der Website ein wenig zu verbessern, hier ein paar einfache Ausgangspunkte:

Wenn Sie eine wirklich einfache Kollisionserkennung und keine vollwertige Physik-Engine wünschen, dann schauen Sie sich das an (Link entfernt, da keine Website mehr existiert).

Wenn Sie andererseits eine Kollisionsreaktion wünschen, nicht nur “haben A und B gestoßen?”, Schauen Sie sich an Physikein superleicht zu verwendender Ammo.js-Wrapper, der um Three.js herum gebaut wurde

  • Die Demo, die Sie verlinkt haben, ist Strahlenkollision

    – eqiproo

    13. Juli 2012 um 18:40 Uhr

Benutzeravatar von JUDE DAILY
JUDAS TÄGLICH

funktioniert nur bei BoxGeometry und BoxBufferGeometry

folgende Funktion erstellen:

function checkTouching(a, d) {
  let b1 = a.position.y - a.geometry.parameters.height / 2;
  let t1 = a.position.y + a.geometry.parameters.height / 2;
  let r1 = a.position.x + a.geometry.parameters.width / 2;
  let l1 = a.position.x - a.geometry.parameters.width / 2;
  let f1 = a.position.z - a.geometry.parameters.depth / 2;
  let B1 = a.position.z + a.geometry.parameters.depth / 2;
  let b2 = d.position.y - d.geometry.parameters.height / 2;
  let t2 = d.position.y + d.geometry.parameters.height / 2;
  let r2 = d.position.x + d.geometry.parameters.width / 2;
  let l2 = d.position.x - d.geometry.parameters.width / 2;
  let f2 = d.position.z - d.geometry.parameters.depth / 2;
  let B2 = d.position.z + d.geometry.parameters.depth / 2;
  if (t1 < b2 || r1 < l2 || b1 > t2 || l1 > r2 || f1 > B2 || B1 < f2) {
    return false;
  }
  return true;
}

Verwenden Sie es in bedingten Anweisungen wie diesen:

if (checkTouching(cube1,cube2)) {
alert("collision!")
}

Ich habe ein Beispiel mit diesem at https://3d-collion-test.glitch.me/

Notiz: Wenn Sie einen (oder beide) der Würfel / Prismen drehen (oder skalieren), wird erkannt, als ob sie nicht gedreht (oder skaliert) wurden.

Da meine andere Antwort begrenzt ist, habe ich etwas anderes gemacht, das genauer ist und nur zurückkehrt true Wenn es eine Kollision gibt und falsch, wenn es keine gibt (aber manchmal, wenn es immer noch gibt), machen Sie zuerst die folgende Funktion:

function rt(a,b) {
  let d = [b]; 
  let e = a.position.clone();
  let f = a.geometry.vertices.length;
  let g = a.position;
  let h = a.matrix;
  let i = a.geometry.vertices;
    for (var vertexIndex = f-1; vertexIndex >= 0; vertexIndex--) {      
        let localVertex = i[vertexIndex].clone();
        let globalVertex = localVertex.applyMatrix4(h);
        let directionVector = globalVertex.sub(g);
        
        let ray = new THREE.Raycaster(e,directionVector.clone().normalize());
        let collisionResults = ray.intersectObjects(d);
        if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) { 
            return true;
    }
    }
 return false;
}

Die obige Funktion ist die gleiche wie eine Antwort auf diese Frage von Lee Stemkoski (dem ich Anerkennung gebe, indem ich das eintippe), aber ich habe Änderungen vorgenommen, damit es schneller läuft und Sie keine Reihe von Netzen erstellen müssen. Ok Schritt 2: Erstellen Sie diese Funktion:

function ft(a,b) {
  return rt(a,b)||rt(b,a)||(a.position.z==b.position.z&&a.position.x==b.position.x&&a.position.y==b.position.y)
}

es gibt true zurück, wenn sich die Mitte von Mesh A nicht in Mesh B befindet UND die Mitte von Mesh B nicht in A liegt ODER die Positionen gleich sind UND sie sich tatsächlich berühren. Dies funktioniert immer noch, wenn Sie eines (oder beide) der Maschen skalieren. Ich habe ein Beispiel unter: https://3d-collsion-test-r.glitch.me/

Benutzeravatar von investInSoup
investInSoup

Es scheint, als ob dies bereits gelöst wurde, aber ich habe eine einfachere Lösung, wenn Sie sich nicht mit Raycasting und dem Erstellen Ihrer eigenen Physikumgebung auskennen.

CANNON.js und AMMO.js sind beides Physikbibliotheken, die auf THREE.js aufbauen. Sie erstellen eine sekundäre physikalische Umgebung und Sie binden Ihre Objektpositionen an diese Szene, um eine physikalische Umgebung zu emulieren. Die Dokumentation ist einfach genug, um für CANNON zu folgen, und sie ist das, was ich verwende, aber sie wurde nicht aktualisiert, seit sie vor 4 Jahren veröffentlicht wurde. Das Repo wurde seitdem gegabelt und eine Community hält es als Kanonen auf dem neuesten Stand. Ich werde hier ein Code-Snippet hinterlassen, damit Sie sehen können, wie es funktioniert

/**
* Floor
*/
const floorShape = new CANNON.Plane()
const floorBody = new CANNON.Body()
floorBody.mass = 0
floorBody.addShape(floorShape)
floorBody.quaternion.setFromAxisAngle(
    new CANNON.Vec3(-1,0,0),
    Math.PI / 2
)
world.addBody(floorBody)

const floor = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.MeshStandardMaterial({
    color: '#777777',
    metalness: 0.3,
    roughness: 0.4,
    envMap: environmentMapTexture
})
)
floor.receiveShadow = true
floor.rotation.x = - Math.PI * 0.5
scene.add(floor)

// THREE mesh
const mesh = new THREE.Mesh(
    sphereGeometry,
    sphereMaterial
)
mesh.scale.set(1,1,1)
mesh.castShadow = true
mesh.position.copy({x: 0, y: 3, z: 0})
scene.add(mesh)

// Cannon
const shape = new CANNON.Sphere(1)
const body = new CANNON.Body({
    mass: 1,
    shape,
    material: concretePlasticMaterial
})
body.position.copy({x: 0, y: 3, z: 0})
world.addBody(body)

Dies macht einen Boden und einen Ball, erzeugt aber auch dasselbe in der CANNON.js-Umgebung.

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime() 
    const deltaTime = elapsedTime - oldElapsedTime
    oldElapsedTime = elapsedTime

    // Update Physics World
    mesh.position.copy(body.position)

    world.step(1/60,deltaTime,3)


    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

Danach aktualisieren Sie einfach die Position Ihrer THREE.js-Szene in der Animationsfunktion basierend auf der Position Ihrer Physikszene.

Bitte schauen Sie sich die Dokumentation an, da es komplizierter erscheinen mag, als es wirklich ist. Die Verwendung einer Physikbibliothek wird der einfachste Weg sein, Kollisionen zu simulieren. Schauen Sie sich auch Physi.js an, ich habe es nie benutzt, aber es soll eine freundlichere Bibliothek sein, für die Sie keine sekundäre Umgebung erstellen müssen

Benutzeravatar von Robert Townley
RobertTownley

In meiner threejs-Version habe ich nur geometry.attributes.position.array und nicht geometry.vertices. Um es in Scheitelpunkte umzuwandeln, verwende ich die folgende TS-Funktion:

export const getVerticesForObject = (obj: THREE.Mesh): THREE.Vector3[] => {
  const bufferVertices = obj.geometry.attributes.position.array;
  const vertices: THREE.Vector3[] = [];

  for (let i = 0; i < bufferVertices.length; i += 3) {
    vertices.push(
      new THREE.Vector3(
        bufferVertices[i] + obj.position.x,
        bufferVertices[i + 1] + obj.position.y,
        bufferVertices[i + 2] + obj.position.z
      )
    );
  }
  return vertices;
};

Ich übergebe die Position des Objekts für jede Dimension, da die bufferVertices standardmäßig relativ zum Mittelpunkt des Objekts sind und ich sie für meine Zwecke global haben wollte.

Ich habe auch eine kleine Funktion geschrieben, um Kollisionen basierend auf Scheitelpunkten zu erkennen. Es tastet optional Scheitelpunkte für sehr involvierte Objekte ab oder prüft die Nähe aller Scheitelpunkte zu den Scheitelpunkten des anderen Objekts:

const COLLISION_DISTANCE = 0.025;
const SAMPLE_SIZE = 50;
export const detectCollision = ({
  collider,
  collidables,
  method,
}: DetectCollisionParams): GameObject | undefined => {
  const { geometry, position } = collider.obj;
  if (!geometry.boundingSphere) return;

  const colliderCenter = new THREE.Vector3(position.x, position.y, position.z);
  const colliderSampleVertices =
    method === "sample"
      ? _.sampleSize(getVerticesForObject(collider.obj), SAMPLE_SIZE)
      : getVerticesForObject(collider.obj);

  for (const collidable of collidables) {
    // First, detect if it's within the bounding box
    const { geometry: colGeometry, position: colPosition } = collidable.obj;
    if (!colGeometry.boundingSphere) continue;
    const colCenter = new THREE.Vector3(
      colPosition.x,
      colPosition.y,
      colPosition.z
    );
    const bothRadiuses =
      geometry.boundingSphere.radius + colGeometry.boundingSphere.radius;
    const distance = colliderCenter.distanceTo(colCenter);
    if (distance > bothRadiuses) continue;

    // Then, detect if there are overlapping vectors
    const colSampleVertices =
      method === "sample"
        ? _.sampleSize(getVerticesForObject(collidable.obj), SAMPLE_SIZE)
        : getVerticesForObject(collidable.obj);
    for (const v1 of colliderSampleVertices) {
      for (const v2 of colSampleVertices) {
        if (v1.distanceTo(v2) < COLLISION_DISTANCE) {
          return collidable;
        }
      }
    }
  }
};

1404800cookie-checkWie erkennt man eine Kollision in three.js?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy