UNPKG

@micosmo/aframe

Version:
290 lines (267 loc) 8.7 kB
/* global THREE */ import aframe from "aframe"; import { bindEvent } from "aframe-event-decorators"; import { isVisibleInScene } from "./lib/utils"; const v1 = new THREE.Vector3(); const v2 = new THREE.Vector3(); const debugMaterial = new THREE.MeshBasicMaterial({ color: "blue", wireframe: true, depthTest: false, transparent: true }); aframe.registerSystem("collider", { init() { this.collisions = new Map(); this.prevCollisions = new Map(); this.newCollisions = new Map(); this.colliders = new Set(); this.layers = new Map(); }, tick(tm, dtm) { this.prevCollisions.clear(); const temp = this.prevCollisions; this.prevCollisions = this.collisions; this.collisions = temp; for (const c1 of this.colliders) { const layers = c1.getAttribute("collider").collidesWith; for (const layer of layers) { if (!this.layers.has(layer)) continue; for (const c2 of this.layers.get(layer)) { if (c1 !== c2) { this.addAnyCollisions(c1, c2); } } } } // Get newly intersected entities. this.newCollisions.clear(); for (const [c1, cols] of this.collisions) { for (const c2 of cols) { if (!this.hasCollided(c1, c2, this.prevCollisions)) { this.addCollision(c1, c2, this.newCollisions); c1.emit("collisionstart", c2); } } } // Find collision which have cleared for (const [c1, cols] of this.prevCollisions) { for (const c2 of cols) { if (!this.hasCollided(c1, c2)) { c1.emit("collisionend", c2); } } } }, addAnyCollisions(c1, c2) { if ( c1.components === undefined || c1.components.collider === undefined || c2.components === undefined || c2.components.collider === undefined ) { return; } else if (!c1.isPlaying || !c2.isPlaying) { return; } else if ( !c1.getAttribute("collider").collideNonVisible && (!isVisibleInScene(c2) || !isVisibleInScene(c1)) ) { return; } const shape1 = c1.getAttribute("collider").shape; const shape2 = c2.getAttribute("collider").shape; let isCollided = false; if (shape1 === "sphere" && shape2 === "sphere") { isCollided = this.collisionSphereSphere( c1.components.collider, c2.components.collider ); } else if (shape1 === "sphere" && shape2 === "box") { isCollided = this.collisionSphereBox( c1.components.collider, c2.components.collider ); } else if (shape1 === "box" && shape2 === "sphere") { isCollided = this.collisionSphereBox( c2.components.collider, c1.components.collider ); } else if ((shape1 === shape2) === "box") { isCollided = this.collisionBoxBox( c2.components.collider, c1.components.collider ); } if (isCollided) { this.addCollision(c1, c2); } }, addCollision(c1, c2, list = this.collisions) { if (!this.collisions.has(c1)) { this.collisions.set(c1, new Set()); } this.collisions.get(c1).add(c2); }, hasCollided(collider, other, list = this.collisions) { if (list.has(collider) && list.get(collider).has(other)) { return true; } return false; }, collisionSphereSphere: (() => { const v1 = new THREE.Vector3(); const v2 = new THREE.Vector3(); return (sphere1, sphere2) => { const s1Pos = sphere1.el.object3D.getWorldPosition(v1); const s2Pos = sphere2.el.object3D.getWorldPosition(v2); const distance = s1Pos.distanceTo(s2Pos); const combinedRadius = sphere1.getScaledRadius() + sphere2.getScaledRadius(); return distance <= combinedRadius; }; })(), collisionSphereBox: (() => { const v1 = new THREE.Vector3(); const v2 = new THREE.Vector3(); const v3 = new THREE.Vector3(); const s = new THREE.Sphere(); const b = new THREE.Box3(); return (sphere, box) => { const spherePos = sphere.el.object3D.getWorldPosition(v1); const sphereRadius = sphere.getScaledRadius(); s.set(spherePos, sphereRadius); const boxPos = box.el.object3D.getWorldPosition(v2); const boxSize = box.getScaledDimensions(v3); b.setFromCenterAndSize(boxPos, boxSize); return b.intersectsSphere(s); }; })(), collisionBoxBox: (() => { const v1 = new THREE.Vector3(); const v2 = new THREE.Vector3(); const v3 = new THREE.Vector3(); const v4 = new THREE.Vector3(); const b1 = new THREE.Box3(); const b2 = new THREE.Box3(); return (box1, box2) => { const box1Pos = box1.el.object3D.getWorldPosition(v1); const box1Size = box1.getScaledDimensions(v2); b1.setFromCenterAndSize(box1Pos, box1Size); const box2Pos = box2.el.object3D.getWorldPosition(v3); const box2Size = box2.getScaledDimensions(v4); b2.setFromCenterAndSize(box2Pos, box2Size); return b1.intersectsBox(b2); }; })(), addCollider(c, layer) { if (!this.layers.has(layer)) { this.layers.set(layer, new Set()); } this.layers.get(layer).add(c); this.colliders.add(c); }, removeCollider(c, layer) { this.layers.get(layer).delete(c); this.colliders.delete(c); } }); const shapeNames = ["sphere", "box"]; const shapeSchemas = { sphere: { radius: { type: "number", default: 1, min: 0 } }, box: { width: { type: "number", default: 1, min: 0 }, height: { type: "number", default: 1, min: 0 }, depth: { type: "number", default: 1, min: 0 } } }; /** * @property {string} objects - Selector of entities to test for collision. */ aframe.registerComponent("collider", { schema: { collideNonVisible: { default: false }, enabled: { default: true }, shape: { default: "sphere", oneOf: shapeNames }, layer: { default: "default" }, collidesWith: { type: "array" } }, init: function () { this._debugMesh = new THREE.Mesh( new THREE.SphereGeometry(this.data.radius, 6, 6), debugMaterial ); this._debugMesh.visible = false; this.el.object3D.add(this._debugMesh); if (aframe.INSPECTOR && aframe.INSPECTOR.inspectorActive) { this.inspectorEnabled(); } }, update: function (oldData) { if (this.data.layer !== oldData.layer) { if (oldData.layer !== undefined) { this.system.removeCollider(this.el, oldData.layer); } this.system.addCollider(this.el, this.data.layer); } }, remove: function () { this.system.removeCollider(this.el, this.data.layer); }, inspectorenabled: bindEvent( { listenIn: "init", removeIn: "remove", target: "a-scene" }, function () { this._debugMesh.visible = true; this.rebuildDebugMesh(); } ), inspectordisabled: bindEvent( { listenIn: "init", removeIn: "remove", target: "a-scene" }, function () { this._debugMesh.visible = false; } ), inspectorcomponentchanged: bindEvent(function () { this.rebuildDebugMesh(); }), getScaledRadius: function () { const scale = this.el.object3D.getWorldScale(v1); return Math.max(scale.x, Math.max(scale.y, scale.z)) * this.data.radius; }, getScaledDimensions: function (target) { const scale = this.el.object3D.getWorldScale(v1); target .set(this.data.width, this.data.height, this.data.depth) .multiply(scale); return target; }, rebuildDebugMesh: function () { if (this.data.shape === "sphere") { const scaledRadius = this.getScaledRadius(); this._debugMesh.geometry = new THREE.SphereGeometry(scaledRadius, 6, 6); } else if (this.data.shape === "box") { const scaledDimensions = this.getScaledDimensions(v2); this._debugMesh.geometry = new THREE.BoxGeometry( scaledDimensions.x, scaledDimensions.y, scaledDimensions.z ); } const s = this._debugMesh.scale; this.el.object3D.getWorldScale(s); s.set(1 / s.x, 1 / s.y, 1 / s.z); }, updateSchema: function (data) { const newShape = data.shape; const currentShape = this.data && this.data.shape; const shape = newShape || currentShape; const schema = shapeSchemas[shape]; if (!schema) { console.error("unknown shape: " + shape); } if (currentShape && newShape === currentShape) return; this.extendSchema(schema); } });