///
interface CollisionResults {
intersectedElements: AFrame.Entity[],
collidingElement: AFrame.Entity
}
interface CollidingCheck {
isColliding: boolean,
collisionResults: CollisionResults
}
/**
* Creates a class that will check for the colliders.
* Since this is a class, it will share the same reference
* of the properties to other components. In this case,
* this is what we want.
*
* To better understand what's going on I had to read:
* http://www.tomdalling.com/blog/modern-opengl/explaining-homogenous-coordinates-and-projective-geometry/
*
* And
* http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/
*
* It's not that long to read
* (Spare about an hour or so, it's going to be worh it.)
*
* If you don't know about homogeneous coordinates:
* http://deltaorange.com/2012/03/08/the-truth-behind-homogenous-coordinates/
*
* Vector Normalization:
* Creates a vector whose magnitud is 1.
* https://threejs.org/docs/#api/math/Vector3
*
* Matrix 4:
* https://threejs.org/docs/#api/math/Matrix4
*
* Matrix 3:
* https://threejs.org/docs/#api/math/Matrix3
*
* Note the collideStart, collideEnd events are limited
* and are really simple. If an element keeps colliding
* with multiple objects without stopping the collide,
*
* The collideEnd will never fire. This will be solved
* in future releases.
*/
class ColliderComponent {
//Holds the reference of all the colliding elements.
collidingElements: Map
// Used for checking with other collidable elements.
collidableRef: string
meshMap: Map;
constructor() {
this.collidingElements = new Map();
this.meshMap = new Map();
this.collidableRef = ".collidable";
this.aframe();
}
//Registers the AFRAME component.
aframe() {
const __this = this;
AFRAME.registerComponent('collider', {
schema: {
/*
* TODO:
* true => Checks for collision with every other object
* false => (Default) Checks for collision with other collidables.
*/
// %%%%%%%collidesAll: { type: 'boolean', default: false }
},
init: function () {
// TO DO:
// debugger;
// console.log("Element's attribute are")
// for (var i = 0; i < this.el.attributes; i++) {
// if (this.el.attributes[i].key in AFRAME.components) {
// // Wait for all to load (e.g., count them up and increment a counter or Promise.all).
// }
// }
window.setTimeout(this.initialize.bind(this), 1000);
},
initialize: function () {
const id: string = this.el.object3D.uuid;
this.collideCheck = {
isColliding: false,
};
// Since the object can have multiple meshes, besides "mesh"
// we'll have a list which we'll iterate through
__this.meshMap.set(id, Object.keys(this.el.object3DMap));
__this.addElementToCollidingList(this.el, id);
__this.generateBoundingBox(this.el, id);
// Checks if the element has its position changed.
this.el.addEventListener('componentchanged', evt => {
if (evt.detail.name !== 'position') return;
let intersected = __this.intersectedElements(this.el, id);
if (!intersected || intersected == null) return;
if (!this.collideCheck.isColliding && intersected.intersectedElements.length > 0) {
this.collideCheck.isColliding = true;
this.collideCheck.collisionResults = intersected;
__this.startColliding(intersected);
}
else if (this.collideCheck.isColliding && intersected.intersectedElements.length === 0) {
this.collideCheck.isColliding = false;
__this.endColliding(this.collideCheck.collisionResults);
// https://stackoverflow.com/questions/208105/how-do-i-remove-a-property-from-a-javascript-object
this.collideCheck.collisionResults = undefined;
}
});
}
});
}
addElementToCollidingList(el: AFrame.Entity, id: string) {
// Iterates over each mesh and adds the vertices.
let meshMap = this.meshMap.get(id);
let collidedElements = new Array();
meshMap.forEach(mesh => {
let obj3d = el.getObject3D(mesh) as any;
obj3d.vertices = this.calculateVertices(el, mesh);
collidedElements.push(
{
element: el,
mesh: mesh,
vertices: obj3d.vertices
}
);
})
this.collidingElements.set(id, collidedElements);
}
generateBoundingBox(el: AFrame.Entity, id: string) {
let meshes = this.meshMap.get(id);
meshes.forEach(mesh => {
let obj = el.getObject3D(mesh);
//let bb = new THREE.Box3().setFromObject(obj);
let bb = new THREE.Box3().setFromPoints((obj as any).vertices);
(obj as any).geometry.boundingBox = bb;
});
}
/*
* A-Frames the elements in BufferGeometry.
* According to https://stackoverflow.com/a/35372430/1057052
* BufferGeoemtry generates the vertices in groups of three.
* We need to generate a Vector3 for each of this group of
* vertices. %
*
* @param el The AFrame element using this.el
*/
calculateVertices(el: AFrame.Entity, mesh: string): THREE.Vector3[] {
const elOrigin = el.parentElement.getAttribute('position');
const obj = el.getObject3D(mesh) as any;
/**
* This is where the BufferGeometry from A-Frame has the
* list of the vertices
**/
const rawVertices = obj.geometry.attributes.position.array;
let vertices: THREE.Vector3[] = new Array();
// Iterates in pair of 3 and appends it to the vertices list.
for (let i = 0; i < rawVertices.length; i++) {
let x = rawVertices[i * 3 + 0];
let y = rawVertices[i * 3 + 1];
let z = rawVertices[i * 3 + 2];
let vector3 = new THREE.Vector3(x, y, z);
vertices.push(vector3);
}
return vertices;
}
intersectedElements(el, id: string): CollisionResults {
let hasIntersected = false;
let intersectedEntities: AFrame.Entity[] = new Array();
let getOtherElements = this.getOtherElements(el, id);
this.meshMap.get(id).forEach(mesh => {
let elBoundingBox = el.getObject3D(mesh).geometry.boundingBox;
if (!elBoundingBox) { console.log("No Bounding Box found"); return };
let globalPosition = el.getObject3D(mesh).matrixWorld;
let globalElBoundingBox = elBoundingBox.clone().applyMatrix4(globalPosition);
getOtherElements.forEach((elem) => {
let obj = elem.element.getObject3D(elem.mesh) as any;
let elemBoundingBox = obj.geometry.boundingBox;
let globalElemPosition = obj.matrixWorld;
let globalElemBoundingBox = elemBoundingBox.clone().applyMatrix4(globalElemPosition);
hasIntersected = globalElBoundingBox.intersectsBox(globalElemBoundingBox) ? true : hasIntersected;
if (hasIntersected) intersectedEntities.push(elem.element);
})
})
return {
"intersectedElements": intersectedEntities,
"collidingElement": hasIntersected ? el : ''
}
}
/**
* Gets the list of collided elements except
* for the current element
* @param el The current AFrame element.
*/
getOtherElements(el, id: string): CollidedElement[] {
// Each element has a mesh id. We use it to compare elements.
let otherElements = new Array();
// We return a new array, containing all the elements except
// the current one.
this.collidingElements.forEach((value, key, map) => {
if (key === id) return;
// Flattens the array:
otherElements.push(...value);
});
return otherElements;
}
startColliding(collisionResults: CollisionResults) {
this.emitCollide('collideStart', collisionResults);
console.log("started colliding");
}
endColliding(collisionResults: CollisionResults) {
this.emitCollide('collideEnd', collisionResults)
}
emitCollide(evtName: string, details: CollisionResults) {
details.intersectedElements.forEach(intersect => {
intersect.emit(evtName, details);
});
details.collidingElement.emit(evtName, details);
}
}