|
@@ -19,7 +19,7 @@ module.exports = {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-},{"./src/controls":84,"./src/loaders":93,"./src/misc":101,"./src/pathfinding":107,"./src/primitives":115,"aframe-physics-system":11}],3:[function(require,module,exports){
|
|
|
|
|
|
+},{"./src/controls":89,"./src/loaders":97,"./src/misc":104,"./src/pathfinding":110,"./src/primitives":118,"aframe-physics-system":11}],3:[function(require,module,exports){
|
|
/**
|
|
/**
|
|
* @author Kyle-Larson https://github.com/Kyle-Larson
|
|
* @author Kyle-Larson https://github.com/Kyle-Larson
|
|
* @author Takahiro https://github.com/takahirox
|
|
* @author Takahiro https://github.com/takahirox
|
|
@@ -6986,7 +6986,7 @@ module.exports = {
|
|
}())
|
|
}())
|
|
};
|
|
};
|
|
|
|
|
|
-},{"../../../lib/CANNON-shape2mesh":12,"cannon":23,"three-to-cannon":79}],14:[function(require,module,exports){
|
|
|
|
|
|
+},{"../../../lib/CANNON-shape2mesh":12,"cannon":23,"three-to-cannon":84}],14:[function(require,module,exports){
|
|
var Body = require('./body');
|
|
var Body = require('./body');
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -21920,4733 +21920,4483 @@ World.prototype.clearForces = function(){
|
|
};
|
|
};
|
|
|
|
|
|
},{"../collision/AABB":24,"../collision/ArrayCollisionMatrix":25,"../collision/NaiveBroadphase":28,"../collision/OverlapKeeper":30,"../collision/Ray":31,"../collision/RaycastResult":32,"../equations/ContactEquation":41,"../equations/FrictionEquation":43,"../material/ContactMaterial":46,"../material/Material":47,"../math/Quaternion":50,"../math/Vec3":52,"../objects/Body":53,"../shapes/Shape":65,"../solver/GSSolver":68,"../utils/EventTarget":71,"../utils/TupleDictionary":74,"./Narrowphase":77}],79:[function(require,module,exports){
|
|
},{"../collision/AABB":24,"../collision/ArrayCollisionMatrix":25,"../collision/NaiveBroadphase":28,"../collision/OverlapKeeper":30,"../collision/Ray":31,"../collision/RaycastResult":32,"../equations/ContactEquation":41,"../equations/FrictionEquation":43,"../material/ContactMaterial":46,"../material/Material":47,"../math/Quaternion":50,"../math/Vec3":52,"../objects/Body":53,"../shapes/Shape":65,"../solver/GSSolver":68,"../utils/EventTarget":71,"../utils/TupleDictionary":74,"./Narrowphase":77}],79:[function(require,module,exports){
|
|
-var CANNON = require('cannon'),
|
|
|
|
- quickhull = require('./lib/THREE.quickhull');
|
|
|
|
|
|
+const BinaryHeap = require('./BinaryHeap');
|
|
|
|
+const utils = require('./utils.js');
|
|
|
|
|
|
-var PI_2 = Math.PI / 2;
|
|
|
|
|
|
+class AStar {
|
|
|
|
+ static init (graph) {
|
|
|
|
+ for (let x = 0; x < graph.length; x++) {
|
|
|
|
+ //for(var x in graph) {
|
|
|
|
+ const node = graph[x];
|
|
|
|
+ node.f = 0;
|
|
|
|
+ node.g = 0;
|
|
|
|
+ node.h = 0;
|
|
|
|
+ node.cost = 1.0;
|
|
|
|
+ node.visited = false;
|
|
|
|
+ node.closed = false;
|
|
|
|
+ node.parent = null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
-var Type = {
|
|
|
|
- BOX: 'Box',
|
|
|
|
- CYLINDER: 'Cylinder',
|
|
|
|
- SPHERE: 'Sphere',
|
|
|
|
- HULL: 'ConvexPolyhedron',
|
|
|
|
- MESH: 'Trimesh'
|
|
|
|
-};
|
|
|
|
|
|
+ static cleanUp (graph) {
|
|
|
|
+ for (let x = 0; x < graph.length; x++) {
|
|
|
|
+ const node = graph[x];
|
|
|
|
+ delete node.f;
|
|
|
|
+ delete node.g;
|
|
|
|
+ delete node.h;
|
|
|
|
+ delete node.cost;
|
|
|
|
+ delete node.visited;
|
|
|
|
+ delete node.closed;
|
|
|
|
+ delete node.parent;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
-/**
|
|
|
|
- * Given a THREE.Object3D instance, creates a corresponding CANNON shape.
|
|
|
|
- * @param {THREE.Object3D} object
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-module.exports = CANNON.mesh2shape = function (object, options) {
|
|
|
|
- options = options || {};
|
|
|
|
|
|
+ static heap () {
|
|
|
|
+ return new BinaryHeap(function (node) {
|
|
|
|
+ return node.f;
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
|
|
- var geometry;
|
|
|
|
|
|
+ static search (graph, start, end) {
|
|
|
|
+ this.init(graph);
|
|
|
|
+ //heuristic = heuristic || astar.manhattan;
|
|
|
|
|
|
- if (options.type === Type.BOX) {
|
|
|
|
- return createBoundingBoxShape(object);
|
|
|
|
- } else if (options.type === Type.CYLINDER) {
|
|
|
|
- return createBoundingCylinderShape(object, options);
|
|
|
|
- } else if (options.type === Type.SPHERE) {
|
|
|
|
- return createBoundingSphereShape(object, options);
|
|
|
|
- } else if (options.type === Type.HULL) {
|
|
|
|
- return createConvexPolyhedron(object);
|
|
|
|
- } else if (options.type === Type.MESH) {
|
|
|
|
- geometry = getGeometry(object);
|
|
|
|
- return geometry ? createTrimeshShape(geometry) : null;
|
|
|
|
- } else if (options.type) {
|
|
|
|
- throw new Error('[CANNON.mesh2shape] Invalid type "%s".', options.type);
|
|
|
|
- }
|
|
|
|
|
|
|
|
- geometry = getGeometry(object);
|
|
|
|
- if (!geometry) return null;
|
|
|
|
|
|
+ const openHeap = this.heap();
|
|
|
|
|
|
- var type = geometry.metadata
|
|
|
|
- ? geometry.metadata.type
|
|
|
|
- : geometry.type;
|
|
|
|
|
|
+ openHeap.push(start);
|
|
|
|
|
|
- switch (type) {
|
|
|
|
- case 'BoxGeometry':
|
|
|
|
- case 'BoxBufferGeometry':
|
|
|
|
- return createBoxShape(geometry);
|
|
|
|
- case 'CylinderGeometry':
|
|
|
|
- case 'CylinderBufferGeometry':
|
|
|
|
- return createCylinderShape(geometry);
|
|
|
|
- case 'PlaneGeometry':
|
|
|
|
- case 'PlaneBufferGeometry':
|
|
|
|
- return createPlaneShape(geometry);
|
|
|
|
- case 'SphereGeometry':
|
|
|
|
- case 'SphereBufferGeometry':
|
|
|
|
- return createSphereShape(geometry);
|
|
|
|
- case 'TubeGeometry':
|
|
|
|
- case 'Geometry':
|
|
|
|
- case 'BufferGeometry':
|
|
|
|
- return createBoundingBoxShape(object);
|
|
|
|
- default:
|
|
|
|
- console.warn('Unrecognized geometry: "%s". Using bounding box as shape.', geometry.type);
|
|
|
|
- return createBoxShape(geometry);
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ while (openHeap.size() > 0) {
|
|
|
|
|
|
-CANNON.mesh2shape.Type = Type;
|
|
|
|
|
|
+ // Grab the lowest f(x) to process next. Heap keeps this sorted for us.
|
|
|
|
+ const currentNode = openHeap.pop();
|
|
|
|
|
|
-/******************************************************************************
|
|
|
|
- * Shape construction
|
|
|
|
- */
|
|
|
|
|
|
+ // End case -- result has been found, return the traced path.
|
|
|
|
+ if (currentNode === end) {
|
|
|
|
+ let curr = currentNode;
|
|
|
|
+ const ret = [];
|
|
|
|
+ while (curr.parent) {
|
|
|
|
+ ret.push(curr);
|
|
|
|
+ curr = curr.parent;
|
|
|
|
+ }
|
|
|
|
+ this.cleanUp(ret);
|
|
|
|
+ return ret.reverse();
|
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
|
- * @param {THREE.Geometry} geometry
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
- function createBoxShape (geometry) {
|
|
|
|
- var vertices = getVertices(geometry);
|
|
|
|
|
|
+ // Normal case -- move currentNode from open to closed, process each of its neighbours.
|
|
|
|
+ currentNode.closed = true;
|
|
|
|
|
|
- if (!vertices.length) return null;
|
|
|
|
|
|
+ // Find all neighbours for the current node. Optionally find diagonal neighbours as well (false by default).
|
|
|
|
+ const neighbours = this.neighbours(graph, currentNode);
|
|
|
|
|
|
- geometry.computeBoundingBox();
|
|
|
|
- var box = geometry.boundingBox;
|
|
|
|
- return new CANNON.Box(new CANNON.Vec3(
|
|
|
|
- (box.max.x - box.min.x) / 2,
|
|
|
|
- (box.max.y - box.min.y) / 2,
|
|
|
|
- (box.max.z - box.min.z) / 2
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
|
|
+ for (let i = 0, il = neighbours.length; i < il; i++) {
|
|
|
|
+ const neighbour = neighbours[i];
|
|
|
|
|
|
-/**
|
|
|
|
- * Bounding box needs to be computed with the entire mesh, not just geometry.
|
|
|
|
- * @param {THREE.Object3D} mesh
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-function createBoundingBoxShape (object) {
|
|
|
|
- var shape, localPosition, worldPosition,
|
|
|
|
- box = new THREE.Box3();
|
|
|
|
|
|
+ if (neighbour.closed) {
|
|
|
|
+ // Not a valid node to process, skip to next neighbour.
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- box.setFromObject(object);
|
|
|
|
|
|
+ // The g score is the shortest distance from start to current node.
|
|
|
|
+ // We need to check if the path we have arrived at this neighbour is the shortest one we have seen yet.
|
|
|
|
+ const gScore = currentNode.g + neighbour.cost;
|
|
|
|
+ const beenVisited = neighbour.visited;
|
|
|
|
|
|
- if (!isFinite(box.min.lengthSq())) return null;
|
|
|
|
|
|
+ if (!beenVisited || gScore < neighbour.g) {
|
|
|
|
|
|
- shape = new CANNON.Box(new CANNON.Vec3(
|
|
|
|
- (box.max.x - box.min.x) / 2,
|
|
|
|
- (box.max.y - box.min.y) / 2,
|
|
|
|
- (box.max.z - box.min.z) / 2
|
|
|
|
- ));
|
|
|
|
|
|
+ // Found an optimal (so far) path to this node. Take score for node to see how good it is.
|
|
|
|
+ neighbour.visited = true;
|
|
|
|
+ neighbour.parent = currentNode;
|
|
|
|
+ if (!neighbour.centroid || !end.centroid) throw new Error('Unexpected state');
|
|
|
|
+ neighbour.h = neighbour.h || this.heuristic(neighbour.centroid, end.centroid);
|
|
|
|
+ neighbour.g = gScore;
|
|
|
|
+ neighbour.f = neighbour.g + neighbour.h;
|
|
|
|
|
|
- object.updateMatrixWorld();
|
|
|
|
- worldPosition = new THREE.Vector3();
|
|
|
|
- worldPosition.setFromMatrixPosition(object.matrixWorld);
|
|
|
|
- localPosition = box.translate(worldPosition.negate()).getCenter();
|
|
|
|
- if (localPosition.lengthSq()) {
|
|
|
|
- shape.offset = localPosition;
|
|
|
|
|
|
+ if (!beenVisited) {
|
|
|
|
+ // Pushing to heap will put it in proper place based on the 'f' value.
|
|
|
|
+ openHeap.push(neighbour);
|
|
|
|
+ } else {
|
|
|
|
+ // Already seen the node, but since it has been rescored we need to reorder it in the heap
|
|
|
|
+ openHeap.rescoreElement(neighbour);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // No result was found - empty array signifies failure to find path.
|
|
|
|
+ return [];
|
|
}
|
|
}
|
|
|
|
|
|
- return shape;
|
|
|
|
|
|
+ static heuristic (pos1, pos2) {
|
|
|
|
+ return utils.distanceToSquared(pos1, pos2);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ static neighbours (graph, node) {
|
|
|
|
+ const ret = [];
|
|
|
|
+
|
|
|
|
+ for (let e = 0; e < node.neighbours.length; e++) {
|
|
|
|
+ ret.push(graph[node.neighbours[e]]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
-/**
|
|
|
|
- * Computes 3D convex hull as a CANNON.ConvexPolyhedron.
|
|
|
|
- * @param {THREE.Object3D} mesh
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-function createConvexPolyhedron (object) {
|
|
|
|
- var i, vertices, faces, hull,
|
|
|
|
- eps = 1e-4,
|
|
|
|
- geometry = getGeometry(object);
|
|
|
|
|
|
+module.exports = AStar;
|
|
|
|
|
|
- if (!geometry || !geometry.vertices.length) return null;
|
|
|
|
|
|
+},{"./BinaryHeap":80,"./utils.js":83}],80:[function(require,module,exports){
|
|
|
|
+// javascript-astar
|
|
|
|
+// http://github.com/bgrins/javascript-astar
|
|
|
|
+// Freely distributable under the MIT License.
|
|
|
|
+// Implements the astar search algorithm in javascript using a binary heap.
|
|
|
|
|
|
- // Perturb.
|
|
|
|
- for (i = 0; i < geometry.vertices.length; i++) {
|
|
|
|
- geometry.vertices[i].x += (Math.random() - 0.5) * eps;
|
|
|
|
- geometry.vertices[i].y += (Math.random() - 0.5) * eps;
|
|
|
|
- geometry.vertices[i].z += (Math.random() - 0.5) * eps;
|
|
|
|
|
|
+class BinaryHeap {
|
|
|
|
+ constructor (scoreFunction) {
|
|
|
|
+ this.content = [];
|
|
|
|
+ this.scoreFunction = scoreFunction;
|
|
}
|
|
}
|
|
|
|
|
|
- // Compute the 3D convex hull.
|
|
|
|
- hull = quickhull(geometry);
|
|
|
|
|
|
+ push (element) {
|
|
|
|
+ // Add the new element to the end of the array.
|
|
|
|
+ this.content.push(element);
|
|
|
|
|
|
- // Convert from THREE.Vector3 to CANNON.Vec3.
|
|
|
|
- vertices = new Array(hull.vertices.length);
|
|
|
|
- for (i = 0; i < hull.vertices.length; i++) {
|
|
|
|
- vertices[i] = new CANNON.Vec3(hull.vertices[i].x, hull.vertices[i].y, hull.vertices[i].z);
|
|
|
|
|
|
+ // Allow it to sink down.
|
|
|
|
+ this.sinkDown(this.content.length - 1);
|
|
}
|
|
}
|
|
|
|
|
|
- // Convert from THREE.Face to Array<number>.
|
|
|
|
- faces = new Array(hull.faces.length);
|
|
|
|
- for (i = 0; i < hull.faces.length; i++) {
|
|
|
|
- faces[i] = [hull.faces[i].a, hull.faces[i].b, hull.faces[i].c];
|
|
|
|
|
|
+ pop () {
|
|
|
|
+ // Store the first element so we can return it later.
|
|
|
|
+ const result = this.content[0];
|
|
|
|
+ // Get the element at the end of the array.
|
|
|
|
+ const end = this.content.pop();
|
|
|
|
+ // If there are any elements left, put the end element at the
|
|
|
|
+ // start, and let it bubble up.
|
|
|
|
+ if (this.content.length > 0) {
|
|
|
|
+ this.content[0] = end;
|
|
|
|
+ this.bubbleUp(0);
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
}
|
|
}
|
|
|
|
|
|
- return new CANNON.ConvexPolyhedron(vertices, faces);
|
|
|
|
-}
|
|
|
|
|
|
+ remove (node) {
|
|
|
|
+ const i = this.content.indexOf(node);
|
|
|
|
|
|
-/**
|
|
|
|
- * @param {THREE.Geometry} geometry
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-function createCylinderShape (geometry) {
|
|
|
|
- var shape,
|
|
|
|
- params = geometry.metadata
|
|
|
|
- ? geometry.metadata.parameters
|
|
|
|
- : geometry.parameters;
|
|
|
|
- shape = new CANNON.Cylinder(
|
|
|
|
- params.radiusTop,
|
|
|
|
- params.radiusBottom,
|
|
|
|
- params.height,
|
|
|
|
- params.radialSegments
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- // Include metadata for serialization.
|
|
|
|
- shape._type = CANNON.Shape.types.CYLINDER; // Patch schteppe/cannon.js#329.
|
|
|
|
- shape.radiusTop = params.radiusTop;
|
|
|
|
- shape.radiusBottom = params.radiusBottom;
|
|
|
|
- shape.height = params.height;
|
|
|
|
- shape.numSegments = params.radialSegments;
|
|
|
|
-
|
|
|
|
- shape.orientation = new CANNON.Quaternion();
|
|
|
|
- shape.orientation.setFromEuler(THREE.Math.degToRad(-90), 0, 0, 'XYZ').normalize();
|
|
|
|
- return shape;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * @param {THREE.Object3D} object
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-function createBoundingCylinderShape (object, options) {
|
|
|
|
- var shape, height, radius,
|
|
|
|
- box = new THREE.Box3(),
|
|
|
|
- axes = ['x', 'y', 'z'],
|
|
|
|
- majorAxis = options.cylinderAxis || 'y',
|
|
|
|
- minorAxes = axes.splice(axes.indexOf(majorAxis), 1) && axes;
|
|
|
|
-
|
|
|
|
- box.setFromObject(object);
|
|
|
|
-
|
|
|
|
- if (!isFinite(box.min.lengthSq())) return null;
|
|
|
|
-
|
|
|
|
- // Compute cylinder dimensions.
|
|
|
|
- height = box.max[majorAxis] - box.min[majorAxis];
|
|
|
|
- radius = 0.5 * Math.max(
|
|
|
|
- box.max[minorAxes[0]] - box.min[minorAxes[0]],
|
|
|
|
- box.max[minorAxes[1]] - box.min[minorAxes[1]]
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- // Create shape.
|
|
|
|
- shape = new CANNON.Cylinder(radius, radius, height, 12);
|
|
|
|
-
|
|
|
|
- // Include metadata for serialization.
|
|
|
|
- shape._type = CANNON.Shape.types.CYLINDER; // Patch schteppe/cannon.js#329.
|
|
|
|
- shape.radiusTop = radius;
|
|
|
|
- shape.radiusBottom = radius;
|
|
|
|
- shape.height = height;
|
|
|
|
- shape.numSegments = 12;
|
|
|
|
-
|
|
|
|
- shape.orientation = new CANNON.Quaternion();
|
|
|
|
- shape.orientation.setFromEuler(
|
|
|
|
- majorAxis === 'y' ? PI_2 : 0,
|
|
|
|
- majorAxis === 'z' ? PI_2 : 0,
|
|
|
|
- 0,
|
|
|
|
- 'XYZ'
|
|
|
|
- ).normalize();
|
|
|
|
- return shape;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * @param {THREE.Geometry} geometry
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-function createPlaneShape (geometry) {
|
|
|
|
- geometry.computeBoundingBox();
|
|
|
|
- var box = geometry.boundingBox;
|
|
|
|
- return new CANNON.Box(new CANNON.Vec3(
|
|
|
|
- (box.max.x - box.min.x) / 2 || 0.1,
|
|
|
|
- (box.max.y - box.min.y) / 2 || 0.1,
|
|
|
|
- (box.max.z - box.min.z) / 2 || 0.1
|
|
|
|
- ));
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * @param {THREE.Geometry} geometry
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-function createSphereShape (geometry) {
|
|
|
|
- var params = geometry.metadata
|
|
|
|
- ? geometry.metadata.parameters
|
|
|
|
- : geometry.parameters;
|
|
|
|
- return new CANNON.Sphere(params.radius);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * @param {THREE.Object3D} object
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-function createBoundingSphereShape (object, options) {
|
|
|
|
- if (options.sphereRadius) {
|
|
|
|
- return new CANNON.Sphere(options.sphereRadius);
|
|
|
|
- }
|
|
|
|
- var geometry = getGeometry(object);
|
|
|
|
- if (!geometry) return null;
|
|
|
|
- geometry.computeBoundingSphere();
|
|
|
|
- return new CANNON.Sphere(geometry.boundingSphere.radius);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * @param {THREE.Geometry} geometry
|
|
|
|
- * @return {CANNON.Shape}
|
|
|
|
- */
|
|
|
|
-function createTrimeshShape (geometry) {
|
|
|
|
- var indices,
|
|
|
|
- vertices = getVertices(geometry);
|
|
|
|
-
|
|
|
|
- if (!vertices.length) return null;
|
|
|
|
-
|
|
|
|
- indices = Object.keys(vertices).map(Number);
|
|
|
|
- return new CANNON.Trimesh(vertices, indices);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/******************************************************************************
|
|
|
|
- * Utils
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * Returns a single geometry for the given object. If the object is compound,
|
|
|
|
- * its geometries are automatically merged.
|
|
|
|
- * @param {THREE.Object3D} object
|
|
|
|
- * @return {THREE.Geometry}
|
|
|
|
- */
|
|
|
|
-function getGeometry (object) {
|
|
|
|
- var matrix, mesh,
|
|
|
|
- meshes = getMeshes(object),
|
|
|
|
- tmp = new THREE.Geometry(),
|
|
|
|
- combined = new THREE.Geometry();
|
|
|
|
-
|
|
|
|
- if (meshes.length === 0) return null;
|
|
|
|
-
|
|
|
|
- // Apply scale – it can't easily be applied to a CANNON.Shape later.
|
|
|
|
- if (meshes.length === 1) {
|
|
|
|
- var position = new THREE.Vector3(),
|
|
|
|
- quaternion = new THREE.Quaternion(),
|
|
|
|
- scale = new THREE.Vector3();
|
|
|
|
- if (meshes[0].geometry instanceof THREE.BufferGeometry) {
|
|
|
|
- if (meshes[0].geometry.attributes.position) {
|
|
|
|
- tmp.fromBufferGeometry(meshes[0].geometry);
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- tmp = meshes[0].geometry.clone();
|
|
|
|
- }
|
|
|
|
- tmp.metadata = meshes[0].geometry.metadata;
|
|
|
|
- meshes[0].updateMatrixWorld();
|
|
|
|
- meshes[0].matrixWorld.decompose(position, quaternion, scale);
|
|
|
|
- return tmp.scale(scale.x, scale.y, scale.z);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Recursively merge geometry, preserving local transforms.
|
|
|
|
- while ((mesh = meshes.pop())) {
|
|
|
|
- mesh.updateMatrixWorld();
|
|
|
|
- if (mesh.geometry instanceof THREE.BufferGeometry) {
|
|
|
|
- tmp.fromBufferGeometry(mesh.geometry);
|
|
|
|
- combined.merge(tmp, mesh.matrixWorld);
|
|
|
|
- } else {
|
|
|
|
- combined.merge(mesh.geometry, mesh.matrixWorld);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- matrix = new THREE.Matrix4();
|
|
|
|
- matrix.scale(object.scale);
|
|
|
|
- combined.applyMatrix(matrix);
|
|
|
|
- return combined;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * @param {THREE.Geometry} geometry
|
|
|
|
- * @return {Array<number>}
|
|
|
|
- */
|
|
|
|
-function getVertices (geometry) {
|
|
|
|
- if (!geometry.attributes) {
|
|
|
|
- geometry = new THREE.BufferGeometry().fromGeometry(geometry);
|
|
|
|
- }
|
|
|
|
- return (geometry.attributes.position || {}).array || [];
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * Returns a flat array of THREE.Mesh instances from the given object. If
|
|
|
|
- * nested transformations are found, they are applied to child meshes
|
|
|
|
- * as mesh.userData.matrix, so that each mesh has its position/rotation/scale
|
|
|
|
- * independently of all of its parents except the top-level object.
|
|
|
|
- * @param {THREE.Object3D} object
|
|
|
|
- * @return {Array<THREE.Mesh>}
|
|
|
|
- */
|
|
|
|
-function getMeshes (object) {
|
|
|
|
- var meshes = [];
|
|
|
|
- object.traverse(function (o) {
|
|
|
|
- if (o.type === 'Mesh') {
|
|
|
|
- meshes.push(o);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- return meshes;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-},{"./lib/THREE.quickhull":80,"cannon":23}],80:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
-
|
|
|
|
- QuickHull
|
|
|
|
- ---------
|
|
|
|
-
|
|
|
|
- The MIT License
|
|
|
|
-
|
|
|
|
- Copyright © 2010-2014 three.js authors
|
|
|
|
-
|
|
|
|
- Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
- of this software and associated documentation files (the "Software"), to deal
|
|
|
|
- in the Software without restriction, including without limitation the rights
|
|
|
|
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
- copies of the Software, and to permit persons to whom the Software is
|
|
|
|
- furnished to do so, subject to the following conditions:
|
|
|
|
-
|
|
|
|
- The above copyright notice and this permission notice shall be included in
|
|
|
|
- all copies or substantial portions of the Software.
|
|
|
|
-
|
|
|
|
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
-
|
|
|
|
- THE SOFTWARE.
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- @author mark lundin / http://mark-lundin.com
|
|
|
|
-
|
|
|
|
- This is a 3D implementation of the Quick Hull algorithm.
|
|
|
|
- It is a fast way of computing a convex hull with average complexity
|
|
|
|
- of O(n log(n)).
|
|
|
|
- It uses depends on three.js and is supposed to create THREE.Geometry.
|
|
|
|
-
|
|
|
|
- It's also very messy
|
|
|
|
-
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
-module.exports = (function(){
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- var faces = [],
|
|
|
|
- faceStack = [],
|
|
|
|
- i, NUM_POINTS, extremes,
|
|
|
|
- max = 0,
|
|
|
|
- dcur, current, j, v0, v1, v2, v3,
|
|
|
|
- N, D;
|
|
|
|
-
|
|
|
|
- var ab, ac, ax,
|
|
|
|
- suba, subb, normal,
|
|
|
|
- diff, subaA, subaB, subC;
|
|
|
|
-
|
|
|
|
- function reset(){
|
|
|
|
-
|
|
|
|
- ab = new THREE.Vector3(),
|
|
|
|
- ac = new THREE.Vector3(),
|
|
|
|
- ax = new THREE.Vector3(),
|
|
|
|
- suba = new THREE.Vector3(),
|
|
|
|
- subb = new THREE.Vector3(),
|
|
|
|
- normal = new THREE.Vector3(),
|
|
|
|
- diff = new THREE.Vector3(),
|
|
|
|
- subaA = new THREE.Vector3(),
|
|
|
|
- subaB = new THREE.Vector3(),
|
|
|
|
- subC = new THREE.Vector3();
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- //temporary vectors
|
|
|
|
-
|
|
|
|
- function process( points ){
|
|
|
|
-
|
|
|
|
- // Iterate through all the faces and remove
|
|
|
|
- while( faceStack.length > 0 ){
|
|
|
|
- cull( faceStack.shift(), points );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- var norm = function(){
|
|
|
|
-
|
|
|
|
- var ca = new THREE.Vector3(),
|
|
|
|
- ba = new THREE.Vector3(),
|
|
|
|
- N = new THREE.Vector3();
|
|
|
|
-
|
|
|
|
- return function( a, b, c ){
|
|
|
|
-
|
|
|
|
- ca.subVectors( c, a );
|
|
|
|
- ba.subVectors( b, a );
|
|
|
|
-
|
|
|
|
- N.crossVectors( ca, ba );
|
|
|
|
-
|
|
|
|
- return N.normalize();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }();
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- function getNormal( face, points ){
|
|
|
|
-
|
|
|
|
- if( face.normal !== undefined ) return face.normal;
|
|
|
|
-
|
|
|
|
- var p0 = points[face[0]],
|
|
|
|
- p1 = points[face[1]],
|
|
|
|
- p2 = points[face[2]];
|
|
|
|
-
|
|
|
|
- ab.subVectors( p1, p0 );
|
|
|
|
- ac.subVectors( p2, p0 );
|
|
|
|
- normal.crossVectors( ac, ab );
|
|
|
|
- normal.normalize();
|
|
|
|
-
|
|
|
|
- return face.normal = normal.clone();
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- function assignPoints( face, pointset, points ){
|
|
|
|
-
|
|
|
|
- // ASSIGNING POINTS TO FACE
|
|
|
|
- var p0 = points[face[0]],
|
|
|
|
- dots = [], apex,
|
|
|
|
- norm = getNormal( face, points );
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- // Sory all the points by there distance from the plane
|
|
|
|
- pointset.sort( function( aItem, bItem ){
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- dots[aItem.x/3] = dots[aItem.x/3] !== undefined ? dots[aItem.x/3] : norm.dot( suba.subVectors( aItem, p0 ));
|
|
|
|
- dots[bItem.x/3] = dots[bItem.x/3] !== undefined ? dots[bItem.x/3] : norm.dot( subb.subVectors( bItem, p0 ));
|
|
|
|
-
|
|
|
|
- return dots[aItem.x/3] - dots[bItem.x/3] ;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- //TODO :: Must be a faster way of finding and index in this array
|
|
|
|
- var index = pointset.length;
|
|
|
|
-
|
|
|
|
- if( index === 1 ) dots[pointset[0].x/3] = norm.dot( suba.subVectors( pointset[0], p0 ));
|
|
|
|
- while( index-- > 0 && dots[pointset[index].x/3] > 0 )
|
|
|
|
|
|
+ // When it is found, the process seen in 'pop' is repeated
|
|
|
|
+ // to fill up the hole.
|
|
|
|
+ const end = this.content.pop();
|
|
|
|
|
|
- var point;
|
|
|
|
- if( index + 1 < pointset.length && dots[pointset[index+1].x/3] > 0 ){
|
|
|
|
|
|
+ if (i !== this.content.length - 1) {
|
|
|
|
+ this.content[i] = end;
|
|
|
|
|
|
- face.visiblePoints = pointset.splice( index + 1 );
|
|
|
|
|
|
+ if (this.scoreFunction(end) < this.scoreFunction(node)) {
|
|
|
|
+ this.sinkDown(i);
|
|
|
|
+ } else {
|
|
|
|
+ this.bubbleUp(i);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ size () {
|
|
|
|
+ return this.content.length;
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ rescoreElement (node) {
|
|
|
|
+ this.sinkDown(this.content.indexOf(node));
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ sinkDown (n) {
|
|
|
|
+ // Fetch the element that has to be sunk.
|
|
|
|
+ const element = this.content[n];
|
|
|
|
|
|
- function cull( face, points ){
|
|
|
|
-
|
|
|
|
- var i = faces.length,
|
|
|
|
- dot, visibleFace, currentFace,
|
|
|
|
- visibleFaces = [face];
|
|
|
|
-
|
|
|
|
- var apex = points.indexOf( face.visiblePoints.pop() );
|
|
|
|
|
|
+ // When at 0, an element can not sink any further.
|
|
|
|
+ while (n > 0) {
|
|
|
|
+ // Compute the parent element's index, and fetch it.
|
|
|
|
+ const parentN = ((n + 1) >> 1) - 1;
|
|
|
|
+ const parent = this.content[parentN];
|
|
|
|
|
|
- // Iterate through all other faces...
|
|
|
|
- while( i-- > 0 ){
|
|
|
|
- currentFace = faces[i];
|
|
|
|
- if( currentFace !== face ){
|
|
|
|
- // ...and check if they're pointing in the same direction
|
|
|
|
- dot = getNormal( currentFace, points ).dot( diff.subVectors( points[apex], points[currentFace[0]] ));
|
|
|
|
- if( dot > 0 ){
|
|
|
|
- visibleFaces.push( currentFace );
|
|
|
|
- }
|
|
|
|
|
|
+ if (this.scoreFunction(element) < this.scoreFunction(parent)) {
|
|
|
|
+ // Swap the elements if the parent is greater.
|
|
|
|
+ this.content[parentN] = element;
|
|
|
|
+ this.content[n] = parent;
|
|
|
|
+ // Update 'n' to continue at the new position.
|
|
|
|
+ n = parentN;
|
|
|
|
+ } else {
|
|
|
|
+ // Found a parent that is less, no need to sink any further.
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- var index, neighbouringIndex, vertex;
|
|
|
|
-
|
|
|
|
- // Determine Perimeter - Creates a bounded horizon
|
|
|
|
-
|
|
|
|
- // 1. Pick an edge A out of all possible edges
|
|
|
|
- // 2. Check if A is shared by any other face. a->b === b->a
|
|
|
|
- // 2.1 for each edge in each triangle, isShared = ( f1.a == f2.a && f1.b == f2.b ) || ( f1.a == f2.b && f1.b == f2.a )
|
|
|
|
- // 3. If not shared, then add to convex horizon set,
|
|
|
|
- //pick an end point (N) of the current edge A and choose a new edge NA connected to A.
|
|
|
|
- //Restart from 1.
|
|
|
|
- // 4. If A is shared, it is not an horizon edge, therefore flag both faces that share this edge as candidates for culling
|
|
|
|
- // 5. If candidate geometry is a degenrate triangle (ie. the tangent space normal cannot be computed) then remove that triangle from all further processing
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- var j = i = visibleFaces.length;
|
|
|
|
- var isDistinct = false,
|
|
|
|
- hasOneVisibleFace = i === 1,
|
|
|
|
- cull = [],
|
|
|
|
- perimeter = [],
|
|
|
|
- edgeIndex = 0, compareFace, nextIndex,
|
|
|
|
- a, b;
|
|
|
|
-
|
|
|
|
- var allPoints = [];
|
|
|
|
- var originFace = [visibleFaces[0][0], visibleFaces[0][1], visibleFaces[0][1], visibleFaces[0][2], visibleFaces[0][2], visibleFaces[0][0]];
|
|
|
|
-
|
|
|
|
|
|
+ bubbleUp (n) {
|
|
|
|
+ // Look up the target element and its score.
|
|
|
|
+ const length = this.content.length,
|
|
|
|
+ element = this.content[n],
|
|
|
|
+ elemScore = this.scoreFunction(element);
|
|
|
|
|
|
- if( visibleFaces.length === 1 ){
|
|
|
|
- currentFace = visibleFaces[0];
|
|
|
|
|
|
+ while (true) {
|
|
|
|
+ // Compute the indices of the child elements.
|
|
|
|
+ const child2N = (n + 1) << 1,
|
|
|
|
+ child1N = child2N - 1;
|
|
|
|
+ // This is used to store the new position of the element,
|
|
|
|
+ // if any.
|
|
|
|
+ let swap = null;
|
|
|
|
+ let child1Score;
|
|
|
|
+ // If the first child exists (is inside the array)...
|
|
|
|
+ if (child1N < length) {
|
|
|
|
+ // Look it up and compute its score.
|
|
|
|
+ const child1 = this.content[child1N];
|
|
|
|
+ child1Score = this.scoreFunction(child1);
|
|
|
|
|
|
- perimeter = [currentFace[0], currentFace[1], currentFace[1], currentFace[2], currentFace[2], currentFace[0]];
|
|
|
|
- // remove visible face from list of faces
|
|
|
|
- if( faceStack.indexOf( currentFace ) > -1 ){
|
|
|
|
- faceStack.splice( faceStack.indexOf( currentFace ), 1 );
|
|
|
|
|
|
+ // If the score is less than our element's, we need to swap.
|
|
|
|
+ if (child1Score < elemScore) {
|
|
|
|
+ swap = child1N;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
- if( currentFace.visiblePoints ) allPoints = allPoints.concat( currentFace.visiblePoints );
|
|
|
|
- faces.splice( faces.indexOf( currentFace ), 1 );
|
|
|
|
-
|
|
|
|
- }else{
|
|
|
|
-
|
|
|
|
- while( i-- > 0 ){ // for each visible face
|
|
|
|
-
|
|
|
|
- currentFace = visibleFaces[i];
|
|
|
|
-
|
|
|
|
- // remove visible face from list of faces
|
|
|
|
- if( faceStack.indexOf( currentFace ) > -1 ){
|
|
|
|
- faceStack.splice( faceStack.indexOf( currentFace ), 1 );
|
|
|
|
|
|
+ // Do the same checks for the other child.
|
|
|
|
+ if (child2N < length) {
|
|
|
|
+ const child2 = this.content[child2N],
|
|
|
|
+ child2Score = this.scoreFunction(child2);
|
|
|
|
+ if (child2Score < (swap === null ? elemScore : child1Score)) {
|
|
|
|
+ swap = child2N;
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- if( currentFace.visiblePoints ) allPoints = allPoints.concat( currentFace.visiblePoints );
|
|
|
|
- faces.splice( faces.indexOf( currentFace ), 1 );
|
|
|
|
-
|
|
|
|
|
|
+ // If the element needs to be moved, swap it, and continue.
|
|
|
|
+ if (swap !== null) {
|
|
|
|
+ this.content[n] = this.content[swap];
|
|
|
|
+ this.content[swap] = element;
|
|
|
|
+ n = swap;
|
|
|
|
+ }
|
|
|
|
|
|
- var isSharedEdge;
|
|
|
|
- cEdgeIndex = 0;
|
|
|
|
|
|
+ // Otherwise, we are done.
|
|
|
|
+ else {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- while( cEdgeIndex < 3 ){ // Iterate through it's edges
|
|
|
|
|
|
+}
|
|
|
|
|
|
- isSharedEdge = false;
|
|
|
|
- j = visibleFaces.length;
|
|
|
|
- a = currentFace[cEdgeIndex]
|
|
|
|
- b = currentFace[(cEdgeIndex+1)%3];
|
|
|
|
|
|
+module.exports = BinaryHeap;
|
|
|
|
|
|
|
|
+},{}],81:[function(require,module,exports){
|
|
|
|
+const utils = require('./utils');
|
|
|
|
|
|
- while( j-- > 0 && !isSharedEdge ){ // find another visible faces
|
|
|
|
|
|
+class Channel {
|
|
|
|
+ constructor () {
|
|
|
|
+ this.portals = [];
|
|
|
|
+ }
|
|
|
|
|
|
- compareFace = visibleFaces[j];
|
|
|
|
- edgeIndex = 0;
|
|
|
|
|
|
+ push (p1, p2) {
|
|
|
|
+ if (p2 === undefined) p2 = p1;
|
|
|
|
+ this.portals.push({
|
|
|
|
+ left: p1,
|
|
|
|
+ right: p2
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
|
|
- // isSharedEdge = compareFace == currentFace;
|
|
|
|
- if( compareFace !== currentFace ){
|
|
|
|
|
|
+ stringPull () {
|
|
|
|
+ const portals = this.portals;
|
|
|
|
+ const pts = [];
|
|
|
|
+ // Init scan state
|
|
|
|
+ let portalApex, portalLeft, portalRight;
|
|
|
|
+ let apexIndex = 0,
|
|
|
|
+ leftIndex = 0,
|
|
|
|
+ rightIndex = 0;
|
|
|
|
|
|
- while( edgeIndex < 3 && !isSharedEdge ){ //Check all it's indices
|
|
|
|
|
|
+ portalApex = portals[0].left;
|
|
|
|
+ portalLeft = portals[0].left;
|
|
|
|
+ portalRight = portals[0].right;
|
|
|
|
|
|
- nextIndex = ( edgeIndex + 1 );
|
|
|
|
- isSharedEdge = ( compareFace[edgeIndex] === a && compareFace[nextIndex%3] === b ) ||
|
|
|
|
- ( compareFace[edgeIndex] === b && compareFace[nextIndex%3] === a );
|
|
|
|
|
|
+ // Add start point.
|
|
|
|
+ pts.push(portalApex);
|
|
|
|
|
|
- edgeIndex++;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ for (let i = 1; i < portals.length; i++) {
|
|
|
|
+ const left = portals[i].left;
|
|
|
|
+ const right = portals[i].right;
|
|
|
|
|
|
- if( !isSharedEdge || hasOneVisibleFace ){
|
|
|
|
- perimeter.push( a );
|
|
|
|
- perimeter.push( b );
|
|
|
|
- }
|
|
|
|
|
|
+ // Update right vertex.
|
|
|
|
+ if (utils.triarea2(portalApex, portalRight, right) <= 0.0) {
|
|
|
|
+ if (utils.vequal(portalApex, portalRight) || utils.triarea2(portalApex, portalLeft, right) > 0.0) {
|
|
|
|
+ // Tighten the funnel.
|
|
|
|
+ portalRight = right;
|
|
|
|
+ rightIndex = i;
|
|
|
|
+ } else {
|
|
|
|
+ // Right over left, insert left to path and restart scan from portal left point.
|
|
|
|
+ pts.push(portalLeft);
|
|
|
|
+ // Make current left the new apex.
|
|
|
|
+ portalApex = portalLeft;
|
|
|
|
+ apexIndex = leftIndex;
|
|
|
|
+ // Reset portal
|
|
|
|
+ portalLeft = portalApex;
|
|
|
|
+ portalRight = portalApex;
|
|
|
|
+ leftIndex = apexIndex;
|
|
|
|
+ rightIndex = apexIndex;
|
|
|
|
+ // Restart scan
|
|
|
|
+ i = apexIndex;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- cEdgeIndex++;
|
|
|
|
|
|
+ // Update left vertex.
|
|
|
|
+ if (utils.triarea2(portalApex, portalLeft, left) >= 0.0) {
|
|
|
|
+ if (utils.vequal(portalApex, portalLeft) || utils.triarea2(portalApex, portalRight, left) < 0.0) {
|
|
|
|
+ // Tighten the funnel.
|
|
|
|
+ portalLeft = left;
|
|
|
|
+ leftIndex = i;
|
|
|
|
+ } else {
|
|
|
|
+ // Left over right, insert right to path and restart scan from portal right point.
|
|
|
|
+ pts.push(portalRight);
|
|
|
|
+ // Make current right the new apex.
|
|
|
|
+ portalApex = portalRight;
|
|
|
|
+ apexIndex = rightIndex;
|
|
|
|
+ // Reset portal
|
|
|
|
+ portalLeft = portalApex;
|
|
|
|
+ portalRight = portalApex;
|
|
|
|
+ leftIndex = apexIndex;
|
|
|
|
+ rightIndex = apexIndex;
|
|
|
|
+ // Restart scan
|
|
|
|
+ i = apexIndex;
|
|
|
|
+ continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // create new face for all pairs around edge
|
|
|
|
- i = 0;
|
|
|
|
- var l = perimeter.length/2;
|
|
|
|
- var f;
|
|
|
|
-
|
|
|
|
- while( i < l ){
|
|
|
|
- f = [ perimeter[i*2+1], apex, perimeter[i*2] ];
|
|
|
|
- assignPoints( f, allPoints, points );
|
|
|
|
- faces.push( f )
|
|
|
|
- if( f.visiblePoints !== undefined )faceStack.push( f );
|
|
|
|
- i++;
|
|
|
|
|
|
+ if ((pts.length === 0) || (!utils.vequal(pts[pts.length - 1], portals[portals.length - 1].left))) {
|
|
|
|
+ // Append last point to path.
|
|
|
|
+ pts.push(portals[portals.length - 1].left);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ this.path = pts;
|
|
|
|
+ return pts;
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- var distSqPointSegment = function(){
|
|
|
|
-
|
|
|
|
- var ab = new THREE.Vector3(),
|
|
|
|
- ac = new THREE.Vector3(),
|
|
|
|
- bc = new THREE.Vector3();
|
|
|
|
-
|
|
|
|
- return function( a, b, c ){
|
|
|
|
-
|
|
|
|
- ab.subVectors( b, a );
|
|
|
|
- ac.subVectors( c, a );
|
|
|
|
- bc.subVectors( c, b );
|
|
|
|
-
|
|
|
|
- var e = ac.dot(ab);
|
|
|
|
- if (e < 0.0) return ac.dot( ac );
|
|
|
|
- var f = ab.dot( ab );
|
|
|
|
- if (e >= f) return bc.dot( bc );
|
|
|
|
- return ac.dot( ac ) - e * e / f;
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }();
|
|
|
|
|
|
+module.exports = Channel;
|
|
|
|
|
|
|
|
+},{"./utils":83}],82:[function(require,module,exports){
|
|
|
|
+const utils = require('./utils');
|
|
|
|
+const AStar = require('./AStar');
|
|
|
|
+const Channel = require('./Channel');
|
|
|
|
|
|
|
|
+var polygonId = 1;
|
|
|
|
|
|
|
|
+var buildPolygonGroups = function (navigationMesh) {
|
|
|
|
|
|
|
|
+ var polygons = navigationMesh.polygons;
|
|
|
|
|
|
- return function( geometry ){
|
|
|
|
|
|
+ var polygonGroups = [];
|
|
|
|
+ var groupCount = 0;
|
|
|
|
|
|
- reset();
|
|
|
|
|
|
+ var spreadGroupId = function (polygon) {
|
|
|
|
+ polygon.neighbours.forEach((neighbour) => {
|
|
|
|
+ if (neighbour.group === undefined) {
|
|
|
|
+ neighbour.group = polygon.group;
|
|
|
|
+ spreadGroupId(neighbour);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ };
|
|
|
|
|
|
|
|
+ polygons.forEach((polygon) => {
|
|
|
|
|
|
- points = geometry.vertices;
|
|
|
|
- faces = [],
|
|
|
|
- faceStack = [],
|
|
|
|
- i = NUM_POINTS = points.length,
|
|
|
|
- extremes = points.slice( 0, 6 ),
|
|
|
|
- max = 0;
|
|
|
|
|
|
+ if (polygon.group === undefined) {
|
|
|
|
+ polygon.group = groupCount++;
|
|
|
|
+ // Spread it
|
|
|
|
+ spreadGroupId(polygon);
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ if (!polygonGroups[polygon.group]) polygonGroups[polygon.group] = [];
|
|
|
|
|
|
|
|
+ polygonGroups[polygon.group].push(polygon);
|
|
|
|
+ });
|
|
|
|
|
|
- /*
|
|
|
|
- * FIND EXTREMETIES
|
|
|
|
- */
|
|
|
|
- while( i-- > 0 ){
|
|
|
|
- if( points[i].x < extremes[0].x ) extremes[0] = points[i];
|
|
|
|
- if( points[i].x > extremes[1].x ) extremes[1] = points[i];
|
|
|
|
|
|
+ console.log('Groups built: ', polygonGroups.length);
|
|
|
|
|
|
- if( points[i].y < extremes[2].y ) extremes[2] = points[i];
|
|
|
|
- if( points[i].y < extremes[3].y ) extremes[3] = points[i];
|
|
|
|
|
|
+ return polygonGroups;
|
|
|
|
+};
|
|
|
|
|
|
- if( points[i].z < extremes[4].z ) extremes[4] = points[i];
|
|
|
|
- if( points[i].z < extremes[5].z ) extremes[5] = points[i];
|
|
|
|
- }
|
|
|
|
|
|
+var buildPolygonNeighbours = function (polygon, navigationMesh) {
|
|
|
|
+ polygon.neighbours = [];
|
|
|
|
|
|
|
|
+ // All other nodes that contain at least two of our vertices are our neighbours
|
|
|
|
+ for (var i = 0, len = navigationMesh.polygons.length; i < len; i++) {
|
|
|
|
+ if (polygon === navigationMesh.polygons[i]) continue;
|
|
|
|
|
|
- /*
|
|
|
|
- * Find the longest line between the extremeties
|
|
|
|
- */
|
|
|
|
|
|
+ // Don't check polygons that are too far, since the intersection tests take a long time
|
|
|
|
+ if (polygon.centroid.distanceToSquared(navigationMesh.polygons[i].centroid) > 100 * 100) continue;
|
|
|
|
|
|
- j = i = 6;
|
|
|
|
- while( i-- > 0 ){
|
|
|
|
- j = i - 1;
|
|
|
|
- while( j-- > 0 ){
|
|
|
|
- if( max < (dcur = extremes[i].distanceToSquared( extremes[j] )) ){
|
|
|
|
- max = dcur;
|
|
|
|
- v0 = extremes[ i ];
|
|
|
|
- v1 = extremes[ j ];
|
|
|
|
|
|
+ var matches = utils.array_intersect(polygon.vertexIds, navigationMesh.polygons[i].vertexIds);
|
|
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ if (matches.length >= 2) {
|
|
|
|
+ polygon.neighbours.push(navigationMesh.polygons[i]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
|
|
+var buildPolygonsFromGeometry = function (geometry) {
|
|
|
|
|
|
- // 3. Find the most distant point to the line segment, this creates a plane
|
|
|
|
- i = 6;
|
|
|
|
- max = 0;
|
|
|
|
- while( i-- > 0 ){
|
|
|
|
- dcur = distSqPointSegment( v0, v1, extremes[i]);
|
|
|
|
- if( max < dcur ){
|
|
|
|
- max = dcur;
|
|
|
|
- v2 = extremes[ i ];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ console.log('Vertices:', geometry.vertices.length, 'polygons:', geometry.faces.length);
|
|
|
|
|
|
|
|
+ var polygons = [];
|
|
|
|
+ var vertices = geometry.vertices;
|
|
|
|
+ var faceVertexUvs = geometry.faceVertexUvs;
|
|
|
|
|
|
- // 4. Find the most distant point to the plane.
|
|
|
|
|
|
+ // Convert the faces into a custom format that supports more than 3 vertices
|
|
|
|
+ geometry.faces.forEach((face) => {
|
|
|
|
+ polygons.push({
|
|
|
|
+ id: polygonId++,
|
|
|
|
+ vertexIds: [face.a, face.b, face.c],
|
|
|
|
+ centroid: face.centroid,
|
|
|
|
+ normal: face.normal,
|
|
|
|
+ neighbours: []
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
|
|
- N = norm(v0, v1, v2);
|
|
|
|
- D = N.dot( v0 );
|
|
|
|
|
|
+ var navigationMesh = {
|
|
|
|
+ polygons: polygons,
|
|
|
|
+ vertices: vertices,
|
|
|
|
+ faceVertexUvs: faceVertexUvs
|
|
|
|
+ };
|
|
|
|
|
|
|
|
+ // Build a list of adjacent polygons
|
|
|
|
+ polygons.forEach((polygon) => {
|
|
|
|
+ buildPolygonNeighbours(polygon, navigationMesh);
|
|
|
|
+ });
|
|
|
|
|
|
- max = 0;
|
|
|
|
- i = NUM_POINTS;
|
|
|
|
- while( i-- > 0 ){
|
|
|
|
- dcur = Math.abs( points[i].dot( N ) - D );
|
|
|
|
- if( max < dcur ){
|
|
|
|
- max = dcur;
|
|
|
|
- v3 = points[i];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ return navigationMesh;
|
|
|
|
+};
|
|
|
|
|
|
|
|
+var buildNavigationMesh = function (geometry) {
|
|
|
|
+ // Prepare geometry
|
|
|
|
+ utils.computeCentroids(geometry);
|
|
|
|
+ geometry.mergeVertices();
|
|
|
|
+ return buildPolygonsFromGeometry(geometry);
|
|
|
|
+};
|
|
|
|
|
|
|
|
+var getSharedVerticesInOrder = function (a, b) {
|
|
|
|
|
|
- var v0Index = points.indexOf( v0 ),
|
|
|
|
- v1Index = points.indexOf( v1 ),
|
|
|
|
- v2Index = points.indexOf( v2 ),
|
|
|
|
- v3Index = points.indexOf( v3 );
|
|
|
|
|
|
+ var aList = a.vertexIds;
|
|
|
|
+ var bList = b.vertexIds;
|
|
|
|
|
|
|
|
+ var sharedVertices = [];
|
|
|
|
|
|
- // We now have a tetrahedron as the base geometry.
|
|
|
|
- // Now we must subdivide the
|
|
|
|
|
|
+ aList.forEach((vId) => {
|
|
|
|
+ if (bList.includes(vId)) {
|
|
|
|
+ sharedVertices.push(vId);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- var tetrahedron =[
|
|
|
|
- [ v2Index, v1Index, v0Index ],
|
|
|
|
- [ v1Index, v3Index, v0Index ],
|
|
|
|
- [ v2Index, v3Index, v1Index ],
|
|
|
|
- [ v0Index, v3Index, v2Index ],
|
|
|
|
- ];
|
|
|
|
|
|
+ if (sharedVertices.length < 2) return [];
|
|
|
|
|
|
|
|
+ // console.log("TRYING aList:", aList, ", bList:", bList, ", sharedVertices:", sharedVertices);
|
|
|
|
|
|
|
|
+ if (sharedVertices.includes(aList[0]) && sharedVertices.includes(aList[aList.length - 1])) {
|
|
|
|
+ // Vertices on both edges are bad, so shift them once to the left
|
|
|
|
+ aList.push(aList.shift());
|
|
|
|
+ }
|
|
|
|
|
|
- subaA.subVectors( v1, v0 ).normalize();
|
|
|
|
- subaB.subVectors( v2, v0 ).normalize();
|
|
|
|
- subC.subVectors ( v3, v0 ).normalize();
|
|
|
|
- var sign = subC.dot( new THREE.Vector3().crossVectors( subaB, subaA ));
|
|
|
|
|
|
+ if (sharedVertices.includes(bList[0]) && sharedVertices.includes(bList[bList.length - 1])) {
|
|
|
|
+ // Vertices on both edges are bad, so shift them once to the left
|
|
|
|
+ bList.push(bList.shift());
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ // Again!
|
|
|
|
+ sharedVertices = [];
|
|
|
|
|
|
- // Reverse the winding if negative sign
|
|
|
|
- if( sign < 0 ){
|
|
|
|
- tetrahedron[0].reverse();
|
|
|
|
- tetrahedron[1].reverse();
|
|
|
|
- tetrahedron[2].reverse();
|
|
|
|
- tetrahedron[3].reverse();
|
|
|
|
- }
|
|
|
|
|
|
+ aList.forEach((vId) => {
|
|
|
|
+ if (bList.includes(vId)) {
|
|
|
|
+ sharedVertices.push(vId);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
|
|
+ return sharedVertices;
|
|
|
|
+};
|
|
|
|
|
|
- //One for each face of the pyramid
|
|
|
|
- var pointsCloned = points.slice();
|
|
|
|
- pointsCloned.splice( pointsCloned.indexOf( v0 ), 1 );
|
|
|
|
- pointsCloned.splice( pointsCloned.indexOf( v1 ), 1 );
|
|
|
|
- pointsCloned.splice( pointsCloned.indexOf( v2 ), 1 );
|
|
|
|
- pointsCloned.splice( pointsCloned.indexOf( v3 ), 1 );
|
|
|
|
|
|
+var groupNavMesh = function (navigationMesh) {
|
|
|
|
|
|
|
|
+ var saveObj = {};
|
|
|
|
|
|
- var i = tetrahedron.length;
|
|
|
|
- while( i-- > 0 ){
|
|
|
|
- assignPoints( tetrahedron[i], pointsCloned, points );
|
|
|
|
- if( tetrahedron[i].visiblePoints !== undefined ){
|
|
|
|
- faceStack.push( tetrahedron[i] );
|
|
|
|
- }
|
|
|
|
- faces.push( tetrahedron[i] );
|
|
|
|
- }
|
|
|
|
|
|
+ navigationMesh.vertices.forEach((v) => {
|
|
|
|
+ v.x = utils.roundNumber(v.x, 2);
|
|
|
|
+ v.y = utils.roundNumber(v.y, 2);
|
|
|
|
+ v.z = utils.roundNumber(v.z, 2);
|
|
|
|
+ });
|
|
|
|
|
|
- process( points );
|
|
|
|
|
|
+ saveObj.vertices = navigationMesh.vertices;
|
|
|
|
|
|
|
|
+ var groups = buildPolygonGroups(navigationMesh);
|
|
|
|
|
|
- // Assign to our geometry object
|
|
|
|
|
|
+ saveObj.groups = [];
|
|
|
|
|
|
- var ll = faces.length;
|
|
|
|
- while( ll-- > 0 ){
|
|
|
|
- geometry.faces[ll] = new THREE.Face3( faces[ll][2], faces[ll][1], faces[ll][0], faces[ll].normal )
|
|
|
|
- }
|
|
|
|
|
|
+ var findPolygonIndex = function (group, p) {
|
|
|
|
+ for (var i = 0; i < group.length; i++) {
|
|
|
|
+ if (p === group[i]) return i;
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
|
|
- geometry.normalsNeedUpdate = true;
|
|
|
|
|
|
+ groups.forEach((group) => {
|
|
|
|
|
|
- return geometry;
|
|
|
|
|
|
+ var newGroup = [];
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ group.forEach((p) => {
|
|
|
|
|
|
-}())
|
|
|
|
|
|
+ var neighbours = [];
|
|
|
|
|
|
-},{}],81:[function(require,module,exports){
|
|
|
|
-var EPS = 0.1;
|
|
|
|
|
|
+ p.neighbours.forEach((n) => {
|
|
|
|
+ neighbours.push(findPolygonIndex(group, n));
|
|
|
|
+ });
|
|
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- enabled: {default: true},
|
|
|
|
- mode: {default: 'teleport', oneOf: ['teleport', 'animate']},
|
|
|
|
- animateSpeed: {default: 3.0}
|
|
|
|
- },
|
|
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.active = true;
|
|
|
|
- this.checkpoint = null;
|
|
|
|
|
|
+ // Build a portal list to each neighbour
|
|
|
|
+ var portals = [];
|
|
|
|
+ p.neighbours.forEach((n) => {
|
|
|
|
+ portals.push(getSharedVerticesInOrder(p, n));
|
|
|
|
+ });
|
|
|
|
|
|
- this.offset = new THREE.Vector3();
|
|
|
|
- this.position = new THREE.Vector3();
|
|
|
|
- this.targetPosition = new THREE.Vector3();
|
|
|
|
- },
|
|
|
|
|
|
|
|
- play: function () { this.active = true; },
|
|
|
|
- pause: function () { this.active = false; },
|
|
|
|
|
|
+ p.centroid.x = utils.roundNumber(p.centroid.x, 2);
|
|
|
|
+ p.centroid.y = utils.roundNumber(p.centroid.y, 2);
|
|
|
|
+ p.centroid.z = utils.roundNumber(p.centroid.z, 2);
|
|
|
|
|
|
- setCheckpoint: function (checkpoint) {
|
|
|
|
- var el = this.el;
|
|
|
|
|
|
+ newGroup.push({
|
|
|
|
+ id: findPolygonIndex(group, p),
|
|
|
|
+ neighbours: neighbours,
|
|
|
|
+ vertexIds: p.vertexIds,
|
|
|
|
+ centroid: p.centroid,
|
|
|
|
+ portals: portals
|
|
|
|
+ });
|
|
|
|
|
|
- if (!this.active) return;
|
|
|
|
- if (this.checkpoint === checkpoint) return;
|
|
|
|
|
|
+ });
|
|
|
|
|
|
- if (this.checkpoint) {
|
|
|
|
- el.emit('navigation-end', {checkpoint: checkpoint});
|
|
|
|
- }
|
|
|
|
|
|
+ saveObj.groups.push(newGroup);
|
|
|
|
+ });
|
|
|
|
|
|
- this.checkpoint = checkpoint;
|
|
|
|
- el.emit('navigation-start', {checkpoint: checkpoint});
|
|
|
|
|
|
+ return saveObj;
|
|
|
|
+};
|
|
|
|
|
|
- if (this.data.mode === 'teleport') {
|
|
|
|
- this.sync();
|
|
|
|
- this.el.setAttribute('position', this.targetPosition);
|
|
|
|
- this.checkpoint = null;
|
|
|
|
- el.emit('navigation-end', {checkpoint: checkpoint});
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+var zoneNodes = {};
|
|
|
|
|
|
- isVelocityActive: function () {
|
|
|
|
- return !!(this.active && this.checkpoint);
|
|
|
|
- },
|
|
|
|
|
|
+module.exports = {
|
|
|
|
+ buildNodes: function (geometry) {
|
|
|
|
+ var navigationMesh = buildNavigationMesh(geometry);
|
|
|
|
|
|
- getVelocity: function () {
|
|
|
|
- if (!this.active) return;
|
|
|
|
|
|
+ var zoneNodes = groupNavMesh(navigationMesh);
|
|
|
|
|
|
- var data = this.data,
|
|
|
|
- offset = this.offset,
|
|
|
|
- position = this.position,
|
|
|
|
- targetPosition = this.targetPosition,
|
|
|
|
- checkpoint = this.checkpoint;
|
|
|
|
|
|
+ return zoneNodes;
|
|
|
|
+ },
|
|
|
|
+ setZoneData: function (zone, data) {
|
|
|
|
+ zoneNodes[zone] = data;
|
|
|
|
+ },
|
|
|
|
+ getGroup: function (zone, position) {
|
|
|
|
|
|
- this.sync();
|
|
|
|
- if (position.distanceTo(targetPosition) < EPS) {
|
|
|
|
- this.checkpoint = null;
|
|
|
|
- this.el.emit('navigation-end', {checkpoint: checkpoint});
|
|
|
|
- return offset.set(0, 0, 0);
|
|
|
|
- }
|
|
|
|
- offset.setLength(data.animateSpeed);
|
|
|
|
- return offset;
|
|
|
|
- },
|
|
|
|
|
|
+ if (!zoneNodes[zone]) return null;
|
|
|
|
|
|
- sync: function () {
|
|
|
|
- var offset = this.offset,
|
|
|
|
- position = this.position,
|
|
|
|
- targetPosition = this.targetPosition;
|
|
|
|
|
|
+ var closestNodeGroup = null;
|
|
|
|
|
|
- position.copy(this.el.getAttribute('position'));
|
|
|
|
- targetPosition.copy(this.checkpoint.object3D.getWorldPosition());
|
|
|
|
- targetPosition.add(this.checkpoint.components.checkpoint.getOffset());
|
|
|
|
- offset.copy(targetPosition).sub(position);
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ var distance = Math.pow(50, 2);
|
|
|
|
|
|
-},{}],82:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * Gamepad controls for A-Frame.
|
|
|
|
- *
|
|
|
|
- * Stripped-down version of: https://github.com/donmccurdy/aframe-gamepad-controls
|
|
|
|
- *
|
|
|
|
- * For more information about the Gamepad API, see:
|
|
|
|
- * https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
|
|
|
|
- */
|
|
|
|
|
|
+ zoneNodes[zone].groups.forEach((group, index) => {
|
|
|
|
+ group.forEach((node) => {
|
|
|
|
+ var measuredDistance = utils.distanceToSquared(node.centroid, position);
|
|
|
|
+ if (measuredDistance < distance) {
|
|
|
|
+ closestNodeGroup = index;
|
|
|
|
+ distance = measuredDistance;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
|
|
-var GamepadButton = require('../../lib/GamepadButton'),
|
|
|
|
- GamepadButtonEvent = require('../../lib/GamepadButtonEvent');
|
|
|
|
|
|
+ return closestNodeGroup;
|
|
|
|
+ },
|
|
|
|
+ getRandomNode: function (zone, group, nearPosition, nearRange) {
|
|
|
|
|
|
-var JOYSTICK_EPS = 0.2;
|
|
|
|
|
|
+ if (!zoneNodes[zone]) return new THREE.Vector3();
|
|
|
|
|
|
-module.exports = {
|
|
|
|
|
|
+ nearPosition = nearPosition || null;
|
|
|
|
+ nearRange = nearRange || 0;
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Statics
|
|
|
|
- */
|
|
|
|
|
|
+ var candidates = [];
|
|
|
|
|
|
- GamepadButton: GamepadButton,
|
|
|
|
|
|
+ var polygons = zoneNodes[zone].groups[group];
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Schema
|
|
|
|
- */
|
|
|
|
|
|
+ polygons.forEach((p) => {
|
|
|
|
+ if (nearPosition && nearRange) {
|
|
|
|
+ if (utils.distanceToSquared(nearPosition, p.centroid) < nearRange * nearRange) {
|
|
|
|
+ candidates.push(p.centroid);
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ candidates.push(p.centroid);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- schema: {
|
|
|
|
- // Controller 0-3
|
|
|
|
- controller: { default: 0, oneOf: [0, 1, 2, 3] },
|
|
|
|
|
|
+ return utils.sample(candidates) || new THREE.Vector3();
|
|
|
|
+ },
|
|
|
|
+ getClosestNode: function (position, zone, group, checkPolygon = false) {
|
|
|
|
+ const nodes = zoneNodes[zone].groups[group];
|
|
|
|
+ const vertices = zoneNodes[zone].vertices;
|
|
|
|
+ let closestNode = null;
|
|
|
|
+ let closestDistance = Infinity;
|
|
|
|
|
|
- // Enable/disable features
|
|
|
|
- enabled: { default: true },
|
|
|
|
|
|
+ nodes.forEach((node) => {
|
|
|
|
+ const distance = utils.distanceToSquared(node.centroid, position);
|
|
|
|
+ if (distance < closestDistance
|
|
|
|
+ && (!checkPolygon || utils.isVectorInPolygon(position, node, vertices))) {
|
|
|
|
+ closestNode = node;
|
|
|
|
+ closestDistance = distance;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- // Debugging
|
|
|
|
- debug: { default: false }
|
|
|
|
- },
|
|
|
|
|
|
+ return closestNode;
|
|
|
|
+ },
|
|
|
|
+ findPath: function (startPosition, targetPosition, zone, group) {
|
|
|
|
+ const nodes = zoneNodes[zone].groups[group];
|
|
|
|
+ const vertices = zoneNodes[zone].vertices;
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Core
|
|
|
|
- */
|
|
|
|
|
|
+ const closestNode = this.getClosestNode(startPosition, zone, group);
|
|
|
|
+ const farthestNode = this.getClosestNode(targetPosition, zone, group, true);
|
|
|
|
|
|
- /**
|
|
|
|
- * Called once when component is attached. Generally for initial setup.
|
|
|
|
- */
|
|
|
|
- init: function () {
|
|
|
|
- var scene = this.el.sceneEl;
|
|
|
|
- this.prevTime = window.performance.now();
|
|
|
|
|
|
+ // If we can't find any node, just go straight to the target
|
|
|
|
+ if (!closestNode || !farthestNode) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
|
|
- // Button state
|
|
|
|
- this.buttons = {};
|
|
|
|
|
|
+ const paths = AStar.search(nodes, closestNode, farthestNode);
|
|
|
|
|
|
- scene.addBehavior(this);
|
|
|
|
- },
|
|
|
|
|
|
+ const getPortalFromTo = function (a, b) {
|
|
|
|
+ for (var i = 0; i < a.neighbours.length; i++) {
|
|
|
|
+ if (a.neighbours[i] === b.id) {
|
|
|
|
+ return a.portals[i];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
|
|
- /**
|
|
|
|
- * Called when component is attached and when component data changes.
|
|
|
|
- * Generally modifies the entity based on the data.
|
|
|
|
- */
|
|
|
|
- update: function () { this.tick(); },
|
|
|
|
|
|
+ // We have the corridor, now pull the rope.
|
|
|
|
+ const channel = new Channel();
|
|
|
|
+ channel.push(startPosition);
|
|
|
|
+ for (let i = 0; i < paths.length; i++) {
|
|
|
|
+ const polygon = paths[i];
|
|
|
|
+ const nextPolygon = paths[i + 1];
|
|
|
|
|
|
- /**
|
|
|
|
- * Called on each iteration of main render loop.
|
|
|
|
- */
|
|
|
|
- tick: function () {
|
|
|
|
- this.updateButtonState();
|
|
|
|
- },
|
|
|
|
|
|
+ if (nextPolygon) {
|
|
|
|
+ const portals = getPortalFromTo(polygon, nextPolygon);
|
|
|
|
+ channel.push(
|
|
|
|
+ vertices[portals[0]],
|
|
|
|
+ vertices[portals[1]]
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ channel.push(targetPosition);
|
|
|
|
+ channel.stringPull();
|
|
|
|
|
|
- /**
|
|
|
|
- * Called when a component is removed (e.g., via removeAttribute).
|
|
|
|
- * Generally undoes all modifications to the entity.
|
|
|
|
- */
|
|
|
|
- remove: function () { },
|
|
|
|
|
|
+ // Return the path, omitting first position (which is already known).
|
|
|
|
+ const path = channel.path.map((c) => new THREE.Vector3(c.x, c.y, c.z));
|
|
|
|
+ path.shift();
|
|
|
|
+ return path;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Universal controls - movement
|
|
|
|
- */
|
|
|
|
|
|
+},{"./AStar":79,"./Channel":81,"./utils":83}],83:[function(require,module,exports){
|
|
|
|
+class Utils {
|
|
|
|
|
|
- isVelocityActive: function () {
|
|
|
|
- if (!this.data.enabled || !this.isConnected()) return false;
|
|
|
|
|
|
+ static computeCentroids (geometry) {
|
|
|
|
+ var f, fl, face;
|
|
|
|
|
|
- var dpad = this.getDpad(),
|
|
|
|
- joystick0 = this.getJoystick(0),
|
|
|
|
- inputX = dpad.x || joystick0.x,
|
|
|
|
- inputY = dpad.y || joystick0.y;
|
|
|
|
|
|
+ for ( f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
|
|
|
|
|
|
- return Math.abs(inputX) > JOYSTICK_EPS || Math.abs(inputY) > JOYSTICK_EPS;
|
|
|
|
- },
|
|
|
|
|
|
+ face = geometry.faces[ f ];
|
|
|
|
+ face.centroid = new THREE.Vector3( 0, 0, 0 );
|
|
|
|
|
|
- getVelocityDelta: function () {
|
|
|
|
- var dpad = this.getDpad(),
|
|
|
|
- joystick0 = this.getJoystick(0),
|
|
|
|
- inputX = dpad.x || joystick0.x,
|
|
|
|
- inputY = dpad.y || joystick0.y,
|
|
|
|
- dVelocity = new THREE.Vector3();
|
|
|
|
|
|
+ face.centroid.add( geometry.vertices[ face.a ] );
|
|
|
|
+ face.centroid.add( geometry.vertices[ face.b ] );
|
|
|
|
+ face.centroid.add( geometry.vertices[ face.c ] );
|
|
|
|
+ face.centroid.divideScalar( 3 );
|
|
|
|
|
|
- if (Math.abs(inputX) > JOYSTICK_EPS) {
|
|
|
|
- dVelocity.x += inputX;
|
|
|
|
- }
|
|
|
|
- if (Math.abs(inputY) > JOYSTICK_EPS) {
|
|
|
|
- dVelocity.z += inputY;
|
|
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- return dVelocity;
|
|
|
|
- },
|
|
|
|
|
|
+ static roundNumber (number, decimals) {
|
|
|
|
+ var newnumber = Number(number + '').toFixed(parseInt(decimals));
|
|
|
|
+ return parseFloat(newnumber);
|
|
|
|
+ }
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Universal controls - rotation
|
|
|
|
- */
|
|
|
|
|
|
+ static sample (list) {
|
|
|
|
+ return list[Math.floor(Math.random() * list.length)];
|
|
|
|
+ }
|
|
|
|
|
|
- isRotationActive: function () {
|
|
|
|
- if (!this.data.enabled || !this.isConnected()) return false;
|
|
|
|
|
|
+ static mergeVertexIds (aList, bList) {
|
|
|
|
|
|
- var joystick1 = this.getJoystick(1);
|
|
|
|
|
|
+ var sharedVertices = [];
|
|
|
|
|
|
- return Math.abs(joystick1.x) > JOYSTICK_EPS || Math.abs(joystick1.y) > JOYSTICK_EPS;
|
|
|
|
- },
|
|
|
|
|
|
+ aList.forEach((vID) => {
|
|
|
|
+ if (bList.indexOf(vID) >= 0) {
|
|
|
|
+ sharedVertices.push(vID);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- getRotationDelta: function () {
|
|
|
|
- var lookVector = this.getJoystick(1);
|
|
|
|
- if (Math.abs(lookVector.x) <= JOYSTICK_EPS) lookVector.x = 0;
|
|
|
|
- if (Math.abs(lookVector.y) <= JOYSTICK_EPS) lookVector.y = 0;
|
|
|
|
- return lookVector;
|
|
|
|
- },
|
|
|
|
|
|
+ if (sharedVertices.length < 2) return [];
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Button events
|
|
|
|
- */
|
|
|
|
|
|
+ if (sharedVertices.includes(aList[0]) && sharedVertices.includes(aList[aList.length - 1])) {
|
|
|
|
+ // Vertices on both edges are bad, so shift them once to the left
|
|
|
|
+ aList.push(aList.shift());
|
|
|
|
+ }
|
|
|
|
|
|
- updateButtonState: function () {
|
|
|
|
- var gamepad = this.getGamepad();
|
|
|
|
- if (this.data.enabled && gamepad) {
|
|
|
|
|
|
+ if (sharedVertices.includes(bList[0]) && sharedVertices.includes(bList[bList.length - 1])) {
|
|
|
|
+ // Vertices on both edges are bad, so shift them once to the left
|
|
|
|
+ bList.push(bList.shift());
|
|
|
|
+ }
|
|
|
|
|
|
- // Fire DOM events for button state changes.
|
|
|
|
- for (var i = 0; i < gamepad.buttons.length; i++) {
|
|
|
|
- if (gamepad.buttons[i].pressed && !this.buttons[i]) {
|
|
|
|
- this.emit(new GamepadButtonEvent('gamepadbuttondown', i, gamepad.buttons[i]));
|
|
|
|
- } else if (!gamepad.buttons[i].pressed && this.buttons[i]) {
|
|
|
|
- this.emit(new GamepadButtonEvent('gamepadbuttonup', i, gamepad.buttons[i]));
|
|
|
|
- }
|
|
|
|
- this.buttons[i] = gamepad.buttons[i].pressed;
|
|
|
|
|
|
+ // Again!
|
|
|
|
+ sharedVertices = [];
|
|
|
|
+
|
|
|
|
+ aList.forEach((vId) => {
|
|
|
|
+ if (bList.includes(vId)) {
|
|
|
|
+ sharedVertices.push(vId);
|
|
}
|
|
}
|
|
|
|
+ });
|
|
|
|
|
|
- } else if (Object.keys(this.buttons)) {
|
|
|
|
- // Reset state if controls are disabled or controller is lost.
|
|
|
|
- this.buttons = {};
|
|
|
|
|
|
+ var clockwiseMostSharedVertex = sharedVertices[1];
|
|
|
|
+ var counterClockwiseMostSharedVertex = sharedVertices[0];
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ var cList = aList.slice();
|
|
|
|
+ while (cList[0] !== clockwiseMostSharedVertex) {
|
|
|
|
+ cList.push(cList.shift());
|
|
}
|
|
}
|
|
- },
|
|
|
|
|
|
|
|
- emit: function (event) {
|
|
|
|
- // Emit original event.
|
|
|
|
- this.el.emit(event.type, event);
|
|
|
|
|
|
+ var c = 0;
|
|
|
|
|
|
- // Emit convenience event, identifying button index.
|
|
|
|
- this.el.emit(
|
|
|
|
- event.type + ':' + event.index,
|
|
|
|
- new GamepadButtonEvent(event.type, event.index, event)
|
|
|
|
- );
|
|
|
|
- },
|
|
|
|
|
|
+ var temp = bList.slice();
|
|
|
|
+ while (temp[0] !== counterClockwiseMostSharedVertex) {
|
|
|
|
+ temp.push(temp.shift());
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Gamepad state
|
|
|
|
- */
|
|
|
|
|
|
+ if (c++ > 10) throw new Error('Unexpected state');
|
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
|
- * Returns the Gamepad instance attached to the component. If connected,
|
|
|
|
- * a proxy-controls component may provide access to Gamepad input from a
|
|
|
|
- * remote device.
|
|
|
|
- *
|
|
|
|
- * @return {Gamepad}
|
|
|
|
- */
|
|
|
|
- getGamepad: function () {
|
|
|
|
- var localGamepad = navigator.getGamepads
|
|
|
|
- && navigator.getGamepads()[this.data.controller],
|
|
|
|
- proxyControls = this.el.sceneEl.components['proxy-controls'],
|
|
|
|
- proxyGamepad = proxyControls && proxyControls.isConnected()
|
|
|
|
- && proxyControls.getGamepad(this.data.controller);
|
|
|
|
- return proxyGamepad || localGamepad;
|
|
|
|
- },
|
|
|
|
|
|
+ // Shave
|
|
|
|
+ temp.shift();
|
|
|
|
+ temp.pop();
|
|
|
|
|
|
- /**
|
|
|
|
- * Returns the state of the given button.
|
|
|
|
- * @param {number} index The button (0-N) for which to find state.
|
|
|
|
- * @return {GamepadButton}
|
|
|
|
- */
|
|
|
|
- getButton: function (index) {
|
|
|
|
- return this.getGamepad().buttons[index];
|
|
|
|
- },
|
|
|
|
|
|
+ cList = cList.concat(temp);
|
|
|
|
|
|
- /**
|
|
|
|
- * Returns state of the given axis. Axes are labelled 0-N, where 0-1 will
|
|
|
|
- * represent X/Y on the first joystick, and 2-3 X/Y on the second.
|
|
|
|
- * @param {number} index The axis (0-N) for which to find state.
|
|
|
|
- * @return {number} On the interval [-1,1].
|
|
|
|
- */
|
|
|
|
- getAxis: function (index) {
|
|
|
|
- return this.getGamepad().axes[index];
|
|
|
|
- },
|
|
|
|
|
|
+ return cList;
|
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
|
- * Returns the state of the given joystick (0 or 1) as a THREE.Vector2.
|
|
|
|
- * @param {number} id The joystick (0, 1) for which to find state.
|
|
|
|
- * @return {THREE.Vector2}
|
|
|
|
- */
|
|
|
|
- getJoystick: function (index) {
|
|
|
|
- var gamepad = this.getGamepad();
|
|
|
|
- switch (index) {
|
|
|
|
- case 0: return new THREE.Vector2(gamepad.axes[0], gamepad.axes[1]);
|
|
|
|
- case 1: return new THREE.Vector2(gamepad.axes[2], gamepad.axes[3]);
|
|
|
|
- default: throw new Error('Unexpected joystick index "%d".', index);
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ static setPolygonCentroid (polygon, navigationMesh) {
|
|
|
|
+ var sum = new THREE.Vector3();
|
|
|
|
|
|
- /**
|
|
|
|
- * Returns the state of the dpad as a THREE.Vector2.
|
|
|
|
- * @return {THREE.Vector2}
|
|
|
|
- */
|
|
|
|
- getDpad: function () {
|
|
|
|
- var gamepad = this.getGamepad();
|
|
|
|
- if (!gamepad.buttons[GamepadButton.DPAD_RIGHT]) {
|
|
|
|
- return new THREE.Vector2();
|
|
|
|
- }
|
|
|
|
- return new THREE.Vector2(
|
|
|
|
- (gamepad.buttons[GamepadButton.DPAD_RIGHT].pressed ? 1 : 0)
|
|
|
|
- + (gamepad.buttons[GamepadButton.DPAD_LEFT].pressed ? -1 : 0),
|
|
|
|
- (gamepad.buttons[GamepadButton.DPAD_UP].pressed ? -1 : 0)
|
|
|
|
- + (gamepad.buttons[GamepadButton.DPAD_DOWN].pressed ? 1 : 0)
|
|
|
|
- );
|
|
|
|
- },
|
|
|
|
|
|
+ var vertices = navigationMesh.vertices;
|
|
|
|
|
|
- /**
|
|
|
|
- * Returns true if the gamepad is currently connected to the system.
|
|
|
|
- * @return {boolean}
|
|
|
|
- */
|
|
|
|
- isConnected: function () {
|
|
|
|
- var gamepad = this.getGamepad();
|
|
|
|
- return !!(gamepad && gamepad.connected);
|
|
|
|
- },
|
|
|
|
|
|
+ polygon.vertexIds.forEach((vId) => {
|
|
|
|
+ sum.add(vertices[vId]);
|
|
|
|
+ });
|
|
|
|
|
|
- /**
|
|
|
|
- * Returns a string containing some information about the controller. Result
|
|
|
|
- * may vary across browsers, for a given controller.
|
|
|
|
- * @return {string}
|
|
|
|
- */
|
|
|
|
- getID: function () {
|
|
|
|
- return this.getGamepad().id;
|
|
|
|
|
|
+ sum.divideScalar(polygon.vertexIds.length);
|
|
|
|
+
|
|
|
|
+ polygon.centroid.copy(sum);
|
|
}
|
|
}
|
|
-};
|
|
|
|
|
|
|
|
-},{"../../lib/GamepadButton":4,"../../lib/GamepadButtonEvent":5}],83:[function(require,module,exports){
|
|
|
|
-var radToDeg = THREE.Math.radToDeg,
|
|
|
|
- isMobile = AFRAME.utils.device.isMobile();
|
|
|
|
|
|
+ static cleanPolygon (polygon, navigationMesh) {
|
|
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- enabled: {default: true},
|
|
|
|
- standing: {default: true}
|
|
|
|
- },
|
|
|
|
|
|
+ var newVertexIds = [];
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.isPositionCalibrated = false;
|
|
|
|
- this.dolly = new THREE.Object3D();
|
|
|
|
- this.hmdEuler = new THREE.Euler();
|
|
|
|
- this.previousHMDPosition = new THREE.Vector3();
|
|
|
|
- this.deltaHMDPosition = new THREE.Vector3();
|
|
|
|
- this.vrControls = new THREE.VRControls(this.dolly);
|
|
|
|
- this.rotation = new THREE.Vector3();
|
|
|
|
- },
|
|
|
|
|
|
+ var vertices = navigationMesh.vertices;
|
|
|
|
|
|
- update: function () {
|
|
|
|
- var data = this.data;
|
|
|
|
- var vrControls = this.vrControls;
|
|
|
|
- vrControls.standing = data.standing;
|
|
|
|
- vrControls.update();
|
|
|
|
- },
|
|
|
|
|
|
+ for (var i = 0; i < polygon.vertexIds.length; i++) {
|
|
|
|
+
|
|
|
|
+ var vertex = vertices[polygon.vertexIds[i]];
|
|
|
|
+
|
|
|
|
+ var nextVertexId, previousVertexId;
|
|
|
|
+ var nextVertex, previousVertex;
|
|
|
|
+
|
|
|
|
+ // console.log("nextVertex: ", nextVertex);
|
|
|
|
+
|
|
|
|
+ if (i === 0) {
|
|
|
|
+ nextVertexId = polygon.vertexIds[1];
|
|
|
|
+ previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 1];
|
|
|
|
+ } else if (i === polygon.vertexIds.length - 1) {
|
|
|
|
+ nextVertexId = polygon.vertexIds[0];
|
|
|
|
+ previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 2];
|
|
|
|
+ } else {
|
|
|
|
+ nextVertexId = polygon.vertexIds[i + 1];
|
|
|
|
+ previousVertexId = polygon.vertexIds[i - 1];
|
|
|
|
+ }
|
|
|
|
|
|
- tick: function () {
|
|
|
|
- this.vrControls.update();
|
|
|
|
- },
|
|
|
|
|
|
+ nextVertex = vertices[nextVertexId];
|
|
|
|
+ previousVertex = vertices[previousVertexId];
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- this.vrControls.dispose();
|
|
|
|
- },
|
|
|
|
|
|
+ var a = nextVertex.clone().sub(vertex);
|
|
|
|
+ var b = previousVertex.clone().sub(vertex);
|
|
|
|
|
|
- isRotationActive: function () {
|
|
|
|
- var hmdEuler = this.hmdEuler;
|
|
|
|
- if (!this.data.enabled || !(this.el.sceneEl.is('vr-mode') || isMobile)) {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- hmdEuler.setFromQuaternion(this.dolly.quaternion, 'YXZ');
|
|
|
|
- return !isNullVector(hmdEuler);
|
|
|
|
- },
|
|
|
|
|
|
+ var angle = a.angleTo(b);
|
|
|
|
|
|
- getRotation: function () {
|
|
|
|
- var hmdEuler = this.hmdEuler;
|
|
|
|
- return this.rotation.set(
|
|
|
|
- radToDeg(hmdEuler.x),
|
|
|
|
- radToDeg(hmdEuler.y),
|
|
|
|
- radToDeg(hmdEuler.z)
|
|
|
|
- );
|
|
|
|
- },
|
|
|
|
|
|
+ // console.log(angle);
|
|
|
|
|
|
- isVelocityActive: function () {
|
|
|
|
- var deltaHMDPosition = this.deltaHMDPosition;
|
|
|
|
- var previousHMDPosition = this.previousHMDPosition;
|
|
|
|
- var currentHMDPosition = this.calculateHMDPosition();
|
|
|
|
- this.isPositionCalibrated = this.isPositionCalibrated || !isNullVector(previousHMDPosition);
|
|
|
|
- if (!this.data.enabled || !this.el.sceneEl.is('vr-mode') || isMobile) {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- deltaHMDPosition.copy(currentHMDPosition).sub(previousHMDPosition);
|
|
|
|
- previousHMDPosition.copy(currentHMDPosition);
|
|
|
|
- return this.isPositionCalibrated && !isNullVector(deltaHMDPosition);
|
|
|
|
- },
|
|
|
|
|
|
+ if (angle > Math.PI - 0.01 && angle < Math.PI + 0.01) {
|
|
|
|
+ // Unneccesary vertex
|
|
|
|
+ // console.log("Unneccesary vertex: ", polygon.vertexIds[i]);
|
|
|
|
+ // console.log("Angle between "+previousVertexId+", "+polygon.vertexIds[i]+" "+nextVertexId+" was: ", angle);
|
|
|
|
|
|
- getPositionDelta: function () {
|
|
|
|
- return this.deltaHMDPosition;
|
|
|
|
- },
|
|
|
|
|
|
|
|
- calculateHMDPosition: function () {
|
|
|
|
- var dolly = this.dolly;
|
|
|
|
- var position = new THREE.Vector3();
|
|
|
|
- dolly.updateMatrix();
|
|
|
|
- position.setFromMatrixPosition(dolly.matrix);
|
|
|
|
- return position;
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ // Remove the neighbours who had this vertex
|
|
|
|
+ var goodNeighbours = [];
|
|
|
|
+ polygon.neighbours.forEach((neighbour) => {
|
|
|
|
+ if (!neighbour.vertexIds.includes(polygon.vertexIds[i])) {
|
|
|
|
+ goodNeighbours.push(neighbour);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ polygon.neighbours = goodNeighbours;
|
|
|
|
|
|
-function isNullVector (vector) {
|
|
|
|
- return vector.x === 0 && vector.y === 0 && vector.z === 0;
|
|
|
|
-}
|
|
|
|
|
|
|
|
-},{}],84:[function(require,module,exports){
|
|
|
|
-var physics = require('aframe-physics-system');
|
|
|
|
|
|
+ // TODO cleanup the list of vertices and rebuild vertexIds for all polygons
|
|
|
|
+ } else {
|
|
|
|
+ newVertexIds.push(polygon.vertexIds[i]);
|
|
|
|
+ }
|
|
|
|
|
|
-module.exports = {
|
|
|
|
- 'checkpoint-controls': require('./checkpoint-controls'),
|
|
|
|
- 'gamepad-controls': require('./gamepad-controls'),
|
|
|
|
- 'hmd-controls': require('./hmd-controls'),
|
|
|
|
- 'keyboard-controls': require('./keyboard-controls'),
|
|
|
|
- 'mouse-controls': require('./mouse-controls'),
|
|
|
|
- 'touch-controls': require('./touch-controls'),
|
|
|
|
- 'universal-controls': require('./universal-controls'),
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- registerAll: function (AFRAME) {
|
|
|
|
- if (this._registered) return;
|
|
|
|
|
|
+ // console.log("New vertexIds: ", newVertexIds);
|
|
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
|
|
+ polygon.vertexIds = newVertexIds;
|
|
|
|
|
|
- physics.registerAll();
|
|
|
|
- if (!AFRAME.components['checkpoint-controls']) AFRAME.registerComponent('checkpoint-controls', this['checkpoint-controls']);
|
|
|
|
- if (!AFRAME.components['gamepad-controls']) AFRAME.registerComponent('gamepad-controls', this['gamepad-controls']);
|
|
|
|
- if (!AFRAME.components['hmd-controls']) AFRAME.registerComponent('hmd-controls', this['hmd-controls']);
|
|
|
|
- if (!AFRAME.components['keyboard-controls']) AFRAME.registerComponent('keyboard-controls', this['keyboard-controls']);
|
|
|
|
- if (!AFRAME.components['mouse-controls']) AFRAME.registerComponent('mouse-controls', this['mouse-controls']);
|
|
|
|
- if (!AFRAME.components['touch-controls']) AFRAME.registerComponent('touch-controls', this['touch-controls']);
|
|
|
|
- if (!AFRAME.components['universal-controls']) AFRAME.registerComponent('universal-controls', this['universal-controls']);
|
|
|
|
|
|
+ setPolygonCentroid(polygon, navigationMesh);
|
|
|
|
|
|
- this._registered = true;
|
|
|
|
}
|
|
}
|
|
-};
|
|
|
|
|
|
|
|
-},{"./checkpoint-controls":81,"./gamepad-controls":82,"./hmd-controls":83,"./keyboard-controls":85,"./mouse-controls":86,"./touch-controls":87,"./universal-controls":88,"aframe-physics-system":11}],85:[function(require,module,exports){
|
|
|
|
-require('../../lib/keyboard.polyfill');
|
|
|
|
|
|
+ static isConvex (polygon, navigationMesh) {
|
|
|
|
|
|
-var MAX_DELTA = 0.2,
|
|
|
|
- PROXY_FLAG = '__keyboard-controls-proxy';
|
|
|
|
|
|
+ var vertices = navigationMesh.vertices;
|
|
|
|
|
|
-var KeyboardEvent = window.KeyboardEvent;
|
|
|
|
|
|
+ if (polygon.vertexIds.length < 3) return false;
|
|
|
|
|
|
-/**
|
|
|
|
- * Keyboard Controls component.
|
|
|
|
- *
|
|
|
|
- * Stripped-down version of: https://github.com/donmccurdy/aframe-keyboard-controls
|
|
|
|
- *
|
|
|
|
- * Bind keyboard events to components, or control your entities with the WASD keys.
|
|
|
|
- *
|
|
|
|
- * Why use KeyboardEvent.code? "This is set to a string representing the key that was pressed to
|
|
|
|
- * generate the KeyboardEvent, without taking the current keyboard layout (e.g., QWERTY vs.
|
|
|
|
- * Dvorak), locale (e.g., English vs. French), or any modifier keys into account. This is useful
|
|
|
|
- * when you care about which physical key was pressed, rather thanwhich character it corresponds
|
|
|
|
- * to. For example, if you’re a writing a game, you might want a certain set of keys to move the
|
|
|
|
- * player in different directions, and that mapping should ideally be independent of keyboard
|
|
|
|
- * layout. See: https://developers.google.com/web/updates/2016/04/keyboardevent-keys-codes
|
|
|
|
- *
|
|
|
|
- * @namespace wasd-controls
|
|
|
|
- * keys the entity moves and if you release it will stop. Easing simulates friction.
|
|
|
|
- * to the entity when pressing the keys.
|
|
|
|
- * @param {bool} [enabled=true] - To completely enable or disable the controls
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- enabled: { default: true },
|
|
|
|
- debug: { default: false }
|
|
|
|
- },
|
|
|
|
|
|
+ var convex = true;
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.dVelocity = new THREE.Vector3();
|
|
|
|
- this.localKeys = {};
|
|
|
|
- this.listeners = {
|
|
|
|
- keydown: this.onKeyDown.bind(this),
|
|
|
|
- keyup: this.onKeyUp.bind(this),
|
|
|
|
- blur: this.onBlur.bind(this)
|
|
|
|
- };
|
|
|
|
- this.attachEventListeners();
|
|
|
|
- },
|
|
|
|
|
|
+ var total = 0;
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Movement
|
|
|
|
- */
|
|
|
|
|
|
+ var results = [];
|
|
|
|
|
|
- isVelocityActive: function () {
|
|
|
|
- return this.data.enabled && !!Object.keys(this.getKeys()).length;
|
|
|
|
- },
|
|
|
|
|
|
+ for (var i = 0; i < polygon.vertexIds.length; i++) {
|
|
|
|
|
|
- getVelocityDelta: function () {
|
|
|
|
- var data = this.data,
|
|
|
|
- keys = this.getKeys();
|
|
|
|
|
|
+ var vertex = vertices[polygon.vertexIds[i]];
|
|
|
|
|
|
- this.dVelocity.set(0, 0, 0);
|
|
|
|
- if (data.enabled) {
|
|
|
|
- if (keys.KeyW || keys.ArrowUp) { this.dVelocity.z -= 1; }
|
|
|
|
- if (keys.KeyA || keys.ArrowLeft) { this.dVelocity.x -= 1; }
|
|
|
|
- if (keys.KeyS || keys.ArrowDown) { this.dVelocity.z += 1; }
|
|
|
|
- if (keys.KeyD || keys.ArrowRight) { this.dVelocity.x += 1; }
|
|
|
|
- }
|
|
|
|
|
|
+ var nextVertex, previousVertex;
|
|
|
|
|
|
- return this.dVelocity.clone();
|
|
|
|
- },
|
|
|
|
|
|
+ if (i === 0) {
|
|
|
|
+ nextVertex = vertices[polygon.vertexIds[1]];
|
|
|
|
+ previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 1]];
|
|
|
|
+ } else if (i === polygon.vertexIds.length - 1) {
|
|
|
|
+ nextVertex = vertices[polygon.vertexIds[0]];
|
|
|
|
+ previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 2]];
|
|
|
|
+ } else {
|
|
|
|
+ nextVertex = vertices[polygon.vertexIds[i + 1]];
|
|
|
|
+ previousVertex = vertices[polygon.vertexIds[i - 1]];
|
|
|
|
+ }
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Events
|
|
|
|
- */
|
|
|
|
|
|
+ var a = nextVertex.clone().sub(vertex);
|
|
|
|
+ var b = previousVertex.clone().sub(vertex);
|
|
|
|
|
|
- play: function () {
|
|
|
|
- this.attachEventListeners();
|
|
|
|
- },
|
|
|
|
|
|
+ var angle = a.angleTo(b);
|
|
|
|
+ total += angle;
|
|
|
|
|
|
- pause: function () {
|
|
|
|
- this.removeEventListeners();
|
|
|
|
- },
|
|
|
|
|
|
+ if (angle === Math.PI || angle === 0) return false;
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- this.pause();
|
|
|
|
- },
|
|
|
|
|
|
+ var r = a.cross(b).y;
|
|
|
|
+ results.push(r);
|
|
|
|
+ }
|
|
|
|
|
|
- attachEventListeners: function () {
|
|
|
|
- window.addEventListener('keydown', this.listeners.keydown, false);
|
|
|
|
- window.addEventListener('keyup', this.listeners.keyup, false);
|
|
|
|
- window.addEventListener('blur', this.listeners.blur, false);
|
|
|
|
- },
|
|
|
|
|
|
+ // if ( total > (polygon.vertexIds.length-2)*Math.PI ) return false;
|
|
|
|
|
|
- removeEventListeners: function () {
|
|
|
|
- window.removeEventListener('keydown', this.listeners.keydown);
|
|
|
|
- window.removeEventListener('keyup', this.listeners.keyup);
|
|
|
|
- window.removeEventListener('blur', this.listeners.blur);
|
|
|
|
- },
|
|
|
|
|
|
+ results.forEach((r) => {
|
|
|
|
+ if (r === 0) convex = false;
|
|
|
|
+ });
|
|
|
|
|
|
- onKeyDown: function (event) {
|
|
|
|
- if (AFRAME.utils.shouldCaptureKeyEvent(event)) {
|
|
|
|
- this.localKeys[event.code] = true;
|
|
|
|
- this.emit(event);
|
|
|
|
|
|
+ if (results[0] > 0) {
|
|
|
|
+ results.forEach((r) => {
|
|
|
|
+ if (r < 0) convex = false;
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ results.forEach((r) => {
|
|
|
|
+ if (r > 0) convex = false;
|
|
|
|
+ });
|
|
}
|
|
}
|
|
- },
|
|
|
|
|
|
|
|
- onKeyUp: function (event) {
|
|
|
|
- if (AFRAME.utils.shouldCaptureKeyEvent(event)) {
|
|
|
|
- delete this.localKeys[event.code];
|
|
|
|
- this.emit(event);
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ return convex;
|
|
|
|
+ }
|
|
|
|
|
|
- onBlur: function () {
|
|
|
|
- for (var code in this.localKeys) {
|
|
|
|
- if (this.localKeys.hasOwnProperty(code)) {
|
|
|
|
- delete this.localKeys[code];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ static distanceToSquared (a, b) {
|
|
|
|
|
|
- emit: function (event) {
|
|
|
|
- // TODO - keydown only initially?
|
|
|
|
- // TODO - where the f is the spacebar
|
|
|
|
|
|
+ var dx = a.x - b.x;
|
|
|
|
+ var dy = a.y - b.y;
|
|
|
|
+ var dz = a.z - b.z;
|
|
|
|
+
|
|
|
|
+ return dx * dx + dy * dy + dz * dz;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //+ Jonas Raoni Soares Silva
|
|
|
|
+ //@ http://jsfromhell.com/math/is-point-in-poly [rev. #0]
|
|
|
|
+ static isPointInPoly (poly, pt) {
|
|
|
|
+ for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
|
|
|
|
+ ((poly[i].z <= pt.z && pt.z < poly[j].z) || (poly[j].z <= pt.z && pt.z < poly[i].z)) && (pt.x < (poly[j].x - poly[i].x) * (pt.z - poly[i].z) / (poly[j].z - poly[i].z) + poly[i].x) && (c = !c);
|
|
|
|
+ return c;
|
|
|
|
+ }
|
|
|
|
|
|
- // Emit original event.
|
|
|
|
- if (PROXY_FLAG in event) {
|
|
|
|
- // TODO - Method never triggered.
|
|
|
|
- this.el.emit(event.type, event);
|
|
|
|
- }
|
|
|
|
|
|
+ static isVectorInPolygon (vector, polygon, vertices) {
|
|
|
|
|
|
- // Emit convenience event, identifying key.
|
|
|
|
- this.el.emit(event.type + ':' + event.code, new KeyboardEvent(event.type, event));
|
|
|
|
- if (this.data.debug) console.log(event.type + ':' + event.code);
|
|
|
|
- },
|
|
|
|
|
|
+ // reference point will be the centroid of the polygon
|
|
|
|
+ // We need to rotate the vector as well as all the points which the polygon uses
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Accessors
|
|
|
|
- */
|
|
|
|
|
|
+ var lowestPoint = 100000;
|
|
|
|
+ var highestPoint = -100000;
|
|
|
|
|
|
- isPressed: function (code) {
|
|
|
|
- return code in this.getKeys();
|
|
|
|
- },
|
|
|
|
|
|
+ var polygonVertices = [];
|
|
|
|
|
|
- getKeys: function () {
|
|
|
|
- if (this.isProxied()) {
|
|
|
|
- return this.el.sceneEl.components['proxy-controls'].getKeyboard();
|
|
|
|
|
|
+ polygon.vertexIds.forEach((vId) => {
|
|
|
|
+ lowestPoint = Math.min(vertices[vId].y, lowestPoint);
|
|
|
|
+ highestPoint = Math.max(vertices[vId].y, highestPoint);
|
|
|
|
+ polygonVertices.push(vertices[vId]);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (vector.y < highestPoint + 0.5 && vector.y > lowestPoint - 0.5 &&
|
|
|
|
+ this.isPointInPoly(polygonVertices, vector)) {
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
- return this.localKeys;
|
|
|
|
- },
|
|
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
|
|
- isProxied: function () {
|
|
|
|
- var proxyControls = this.el.sceneEl.components['proxy-controls'];
|
|
|
|
- return proxyControls && proxyControls.isConnected();
|
|
|
|
|
|
+ static triarea2 (a, b, c) {
|
|
|
|
+ var ax = b.x - a.x;
|
|
|
|
+ var az = b.z - a.z;
|
|
|
|
+ var bx = c.x - a.x;
|
|
|
|
+ var bz = c.z - a.z;
|
|
|
|
+ return bx * az - ax * bz;
|
|
}
|
|
}
|
|
|
|
|
|
-};
|
|
|
|
|
|
+ static vequal (a, b) {
|
|
|
|
+ return this.distanceToSquared(a, b) < 0.00001;
|
|
|
|
+ }
|
|
|
|
|
|
-},{"../../lib/keyboard.polyfill":10}],86:[function(require,module,exports){
|
|
|
|
-document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock;
|
|
|
|
|
|
+ static array_intersect () {
|
|
|
|
+ let i, shortest, nShortest, n, len, ret = [],
|
|
|
|
+ obj = {},
|
|
|
|
+ nOthers;
|
|
|
|
+ nOthers = arguments.length - 1;
|
|
|
|
+ nShortest = arguments[0].length;
|
|
|
|
+ shortest = 0;
|
|
|
|
+ for (i = 0; i <= nOthers; i++) {
|
|
|
|
+ n = arguments[i].length;
|
|
|
|
+ if (n < nShortest) {
|
|
|
|
+ shortest = i;
|
|
|
|
+ nShortest = n;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
-/**
|
|
|
|
- * Mouse + Pointerlock controls.
|
|
|
|
- *
|
|
|
|
- * Based on: https://github.com/aframevr/aframe/pull/1056
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- enabled: { default: true },
|
|
|
|
- pointerlockEnabled: { default: true },
|
|
|
|
- sensitivity: { default: 1 / 25 }
|
|
|
|
- },
|
|
|
|
|
|
+ for (i = 0; i <= nOthers; i++) {
|
|
|
|
+ n = (i === shortest) ? 0 : (i || shortest); //Read the shortest array first. Read the first array instead of the shortest
|
|
|
|
+ len = arguments[n].length;
|
|
|
|
+ for (var j = 0; j < len; j++) {
|
|
|
|
+ var elem = arguments[n][j];
|
|
|
|
+ if (obj[elem] === i - 1) {
|
|
|
|
+ if (i === nOthers) {
|
|
|
|
+ ret.push(elem);
|
|
|
|
+ obj[elem] = 0;
|
|
|
|
+ } else {
|
|
|
|
+ obj[elem] = i;
|
|
|
|
+ }
|
|
|
|
+ } else if (i === 0) {
|
|
|
|
+ obj[elem] = 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.mouseDown = false;
|
|
|
|
- this.pointerLocked = false;
|
|
|
|
- this.lookVector = new THREE.Vector2();
|
|
|
|
- this.bindMethods();
|
|
|
|
- },
|
|
|
|
|
|
|
|
- update: function (previousData) {
|
|
|
|
- var data = this.data;
|
|
|
|
- if (previousData.pointerlockEnabled && !data.pointerlockEnabled && this.pointerLocked) {
|
|
|
|
- document.exitPointerLock();
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
|
|
- play: function () {
|
|
|
|
- this.addEventListeners();
|
|
|
|
- },
|
|
|
|
|
|
+module.exports = Utils;
|
|
|
|
|
|
- pause: function () {
|
|
|
|
- this.removeEventListeners();
|
|
|
|
- this.lookVector.set(0, 0);
|
|
|
|
- },
|
|
|
|
|
|
+},{}],84:[function(require,module,exports){
|
|
|
|
+var CANNON = require('cannon'),
|
|
|
|
+ quickhull = require('./lib/THREE.quickhull');
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- this.pause();
|
|
|
|
- },
|
|
|
|
|
|
+var PI_2 = Math.PI / 2;
|
|
|
|
|
|
- bindMethods: function () {
|
|
|
|
- this.onMouseDown = this.onMouseDown.bind(this);
|
|
|
|
- this.onMouseMove = this.onMouseMove.bind(this);
|
|
|
|
- this.onMouseUp = this.onMouseUp.bind(this);
|
|
|
|
- this.onMouseUp = this.onMouseUp.bind(this);
|
|
|
|
- this.onPointerLockChange = this.onPointerLockChange.bind(this);
|
|
|
|
- this.onPointerLockChange = this.onPointerLockChange.bind(this);
|
|
|
|
- this.onPointerLockChange = this.onPointerLockChange.bind(this);
|
|
|
|
- },
|
|
|
|
|
|
+var Type = {
|
|
|
|
+ BOX: 'Box',
|
|
|
|
+ CYLINDER: 'Cylinder',
|
|
|
|
+ SPHERE: 'Sphere',
|
|
|
|
+ HULL: 'ConvexPolyhedron',
|
|
|
|
+ MESH: 'Trimesh'
|
|
|
|
+};
|
|
|
|
|
|
- addEventListeners: function () {
|
|
|
|
- var sceneEl = this.el.sceneEl;
|
|
|
|
- var canvasEl = sceneEl.canvas;
|
|
|
|
- var data = this.data;
|
|
|
|
|
|
+/**
|
|
|
|
+ * Given a THREE.Object3D instance, creates a corresponding CANNON shape.
|
|
|
|
+ * @param {THREE.Object3D} object
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+module.exports = CANNON.mesh2shape = function (object, options) {
|
|
|
|
+ options = options || {};
|
|
|
|
|
|
- if (!canvasEl) {
|
|
|
|
- sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ var geometry;
|
|
|
|
|
|
- canvasEl.addEventListener('mousedown', this.onMouseDown, false);
|
|
|
|
- canvasEl.addEventListener('mousemove', this.onMouseMove, false);
|
|
|
|
- canvasEl.addEventListener('mouseup', this.onMouseUp, false);
|
|
|
|
- canvasEl.addEventListener('mouseout', this.onMouseUp, false);
|
|
|
|
|
|
+ if (options.type === Type.BOX) {
|
|
|
|
+ return createBoundingBoxShape(object);
|
|
|
|
+ } else if (options.type === Type.CYLINDER) {
|
|
|
|
+ return createBoundingCylinderShape(object, options);
|
|
|
|
+ } else if (options.type === Type.SPHERE) {
|
|
|
|
+ return createBoundingSphereShape(object, options);
|
|
|
|
+ } else if (options.type === Type.HULL) {
|
|
|
|
+ return createConvexPolyhedron(object);
|
|
|
|
+ } else if (options.type === Type.MESH) {
|
|
|
|
+ geometry = getGeometry(object);
|
|
|
|
+ return geometry ? createTrimeshShape(geometry) : null;
|
|
|
|
+ } else if (options.type) {
|
|
|
|
+ throw new Error('[CANNON.mesh2shape] Invalid type "%s".', options.type);
|
|
|
|
+ }
|
|
|
|
|
|
- if (data.pointerlockEnabled) {
|
|
|
|
- document.addEventListener('pointerlockchange', this.onPointerLockChange, false);
|
|
|
|
- document.addEventListener('mozpointerlockchange', this.onPointerLockChange, false);
|
|
|
|
- document.addEventListener('pointerlockerror', this.onPointerLockError, false);
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ geometry = getGeometry(object);
|
|
|
|
+ if (!geometry) return null;
|
|
|
|
|
|
- removeEventListeners: function () {
|
|
|
|
- var canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
|
|
|
|
- if (canvasEl) {
|
|
|
|
- canvasEl.removeEventListener('mousedown', this.onMouseDown, false);
|
|
|
|
- canvasEl.removeEventListener('mousemove', this.onMouseMove, false);
|
|
|
|
- canvasEl.removeEventListener('mouseup', this.onMouseUp, false);
|
|
|
|
- canvasEl.removeEventListener('mouseout', this.onMouseUp, false);
|
|
|
|
- }
|
|
|
|
- document.removeEventListener('pointerlockchange', this.onPointerLockChange, false);
|
|
|
|
- document.removeEventListener('mozpointerlockchange', this.onPointerLockChange, false);
|
|
|
|
- document.removeEventListener('pointerlockerror', this.onPointerLockError, false);
|
|
|
|
- },
|
|
|
|
|
|
+ var type = geometry.metadata
|
|
|
|
+ ? geometry.metadata.type
|
|
|
|
+ : geometry.type;
|
|
|
|
|
|
- isRotationActive: function () {
|
|
|
|
- return this.data.enabled && (this.mouseDown || this.pointerLocked);
|
|
|
|
- },
|
|
|
|
|
|
+ switch (type) {
|
|
|
|
+ case 'BoxGeometry':
|
|
|
|
+ case 'BoxBufferGeometry':
|
|
|
|
+ return createBoxShape(geometry);
|
|
|
|
+ case 'CylinderGeometry':
|
|
|
|
+ case 'CylinderBufferGeometry':
|
|
|
|
+ return createCylinderShape(geometry);
|
|
|
|
+ case 'PlaneGeometry':
|
|
|
|
+ case 'PlaneBufferGeometry':
|
|
|
|
+ return createPlaneShape(geometry);
|
|
|
|
+ case 'SphereGeometry':
|
|
|
|
+ case 'SphereBufferGeometry':
|
|
|
|
+ return createSphereShape(geometry);
|
|
|
|
+ case 'TubeGeometry':
|
|
|
|
+ case 'Geometry':
|
|
|
|
+ case 'BufferGeometry':
|
|
|
|
+ return createBoundingBoxShape(object);
|
|
|
|
+ default:
|
|
|
|
+ console.warn('Unrecognized geometry: "%s". Using bounding box as shape.', geometry.type);
|
|
|
|
+ return createBoxShape(geometry);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- /**
|
|
|
|
- * Returns the sum of all mouse movement since last call.
|
|
|
|
- */
|
|
|
|
- getRotationDelta: function () {
|
|
|
|
- var dRotation = this.lookVector.clone().multiplyScalar(this.data.sensitivity);
|
|
|
|
- this.lookVector.set(0, 0);
|
|
|
|
- return dRotation;
|
|
|
|
- },
|
|
|
|
|
|
+CANNON.mesh2shape.Type = Type;
|
|
|
|
|
|
- onMouseMove: function (event) {
|
|
|
|
- var previousMouseEvent = this.previousMouseEvent;
|
|
|
|
|
|
+/******************************************************************************
|
|
|
|
+ * Shape construction
|
|
|
|
+ */
|
|
|
|
|
|
- if (!this.data.enabled || !(this.mouseDown || this.pointerLocked)) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @param {THREE.Geometry} geometry
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+ function createBoxShape (geometry) {
|
|
|
|
+ var vertices = getVertices(geometry);
|
|
|
|
|
|
- var movementX = event.movementX || event.mozMovementX || 0;
|
|
|
|
- var movementY = event.movementY || event.mozMovementY || 0;
|
|
|
|
|
|
+ if (!vertices.length) return null;
|
|
|
|
+
|
|
|
|
+ geometry.computeBoundingBox();
|
|
|
|
+ var box = geometry.boundingBox;
|
|
|
|
+ return new CANNON.Box(new CANNON.Vec3(
|
|
|
|
+ (box.max.x - box.min.x) / 2,
|
|
|
|
+ (box.max.y - box.min.y) / 2,
|
|
|
|
+ (box.max.z - box.min.z) / 2
|
|
|
|
+ ));
|
|
|
|
+ }
|
|
|
|
|
|
- if (!this.pointerLocked) {
|
|
|
|
- movementX = event.screenX - previousMouseEvent.screenX;
|
|
|
|
- movementY = event.screenY - previousMouseEvent.screenY;
|
|
|
|
- }
|
|
|
|
|
|
+/**
|
|
|
|
+ * Bounding box needs to be computed with the entire mesh, not just geometry.
|
|
|
|
+ * @param {THREE.Object3D} mesh
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+function createBoundingBoxShape (object) {
|
|
|
|
+ var shape, localPosition, worldPosition,
|
|
|
|
+ box = new THREE.Box3();
|
|
|
|
|
|
- this.lookVector.x += movementX;
|
|
|
|
- this.lookVector.y += movementY;
|
|
|
|
|
|
+ box.setFromObject(object);
|
|
|
|
|
|
- this.previousMouseEvent = event;
|
|
|
|
- },
|
|
|
|
|
|
+ if (!isFinite(box.min.lengthSq())) return null;
|
|
|
|
|
|
- onMouseDown: function (event) {
|
|
|
|
- var canvasEl = this.el.sceneEl.canvas,
|
|
|
|
- isEditing = (AFRAME.INSPECTOR || {}).opened;
|
|
|
|
|
|
+ shape = new CANNON.Box(new CANNON.Vec3(
|
|
|
|
+ (box.max.x - box.min.x) / 2,
|
|
|
|
+ (box.max.y - box.min.y) / 2,
|
|
|
|
+ (box.max.z - box.min.z) / 2
|
|
|
|
+ ));
|
|
|
|
|
|
- this.mouseDown = true;
|
|
|
|
- this.previousMouseEvent = event;
|
|
|
|
|
|
+ object.updateMatrixWorld();
|
|
|
|
+ worldPosition = new THREE.Vector3();
|
|
|
|
+ worldPosition.setFromMatrixPosition(object.matrixWorld);
|
|
|
|
+ localPosition = box.translate(worldPosition.negate()).getCenter();
|
|
|
|
+ if (localPosition.lengthSq()) {
|
|
|
|
+ shape.offset = localPosition;
|
|
|
|
+ }
|
|
|
|
|
|
- if (this.data.pointerlockEnabled && !this.pointerLocked && !isEditing) {
|
|
|
|
- if (canvasEl.requestPointerLock) {
|
|
|
|
- canvasEl.requestPointerLock();
|
|
|
|
- } else if (canvasEl.mozRequestPointerLock) {
|
|
|
|
- canvasEl.mozRequestPointerLock();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ return shape;
|
|
|
|
+}
|
|
|
|
|
|
- onMouseUp: function () {
|
|
|
|
- this.mouseDown = false;
|
|
|
|
- },
|
|
|
|
|
|
+/**
|
|
|
|
+ * Computes 3D convex hull as a CANNON.ConvexPolyhedron.
|
|
|
|
+ * @param {THREE.Object3D} mesh
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+function createConvexPolyhedron (object) {
|
|
|
|
+ var i, vertices, faces, hull,
|
|
|
|
+ eps = 1e-4,
|
|
|
|
+ geometry = getGeometry(object);
|
|
|
|
|
|
- onPointerLockChange: function () {
|
|
|
|
- this.pointerLocked = !!(document.pointerLockElement || document.mozPointerLockElement);
|
|
|
|
- },
|
|
|
|
|
|
+ if (!geometry || !geometry.vertices.length) return null;
|
|
|
|
|
|
- onPointerLockError: function () {
|
|
|
|
- this.pointerLocked = false;
|
|
|
|
|
|
+ // Perturb.
|
|
|
|
+ for (i = 0; i < geometry.vertices.length; i++) {
|
|
|
|
+ geometry.vertices[i].x += (Math.random() - 0.5) * eps;
|
|
|
|
+ geometry.vertices[i].y += (Math.random() - 0.5) * eps;
|
|
|
|
+ geometry.vertices[i].z += (Math.random() - 0.5) * eps;
|
|
}
|
|
}
|
|
-};
|
|
|
|
-
|
|
|
|
-},{}],87:[function(require,module,exports){
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- enabled: { default: true }
|
|
|
|
- },
|
|
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.dVelocity = new THREE.Vector3();
|
|
|
|
- this.bindMethods();
|
|
|
|
- },
|
|
|
|
|
|
+ // Compute the 3D convex hull.
|
|
|
|
+ hull = quickhull(geometry);
|
|
|
|
|
|
- play: function () {
|
|
|
|
- this.addEventListeners();
|
|
|
|
- },
|
|
|
|
|
|
+ // Convert from THREE.Vector3 to CANNON.Vec3.
|
|
|
|
+ vertices = new Array(hull.vertices.length);
|
|
|
|
+ for (i = 0; i < hull.vertices.length; i++) {
|
|
|
|
+ vertices[i] = new CANNON.Vec3(hull.vertices[i].x, hull.vertices[i].y, hull.vertices[i].z);
|
|
|
|
+ }
|
|
|
|
|
|
- pause: function () {
|
|
|
|
- this.removeEventListeners();
|
|
|
|
- this.dVelocity.set(0, 0, 0);
|
|
|
|
- },
|
|
|
|
|
|
+ // Convert from THREE.Face to Array<number>.
|
|
|
|
+ faces = new Array(hull.faces.length);
|
|
|
|
+ for (i = 0; i < hull.faces.length; i++) {
|
|
|
|
+ faces[i] = [hull.faces[i].a, hull.faces[i].b, hull.faces[i].c];
|
|
|
|
+ }
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- this.pause();
|
|
|
|
- },
|
|
|
|
|
|
+ return new CANNON.ConvexPolyhedron(vertices, faces);
|
|
|
|
+}
|
|
|
|
|
|
- addEventListeners: function () {
|
|
|
|
- var sceneEl = this.el.sceneEl;
|
|
|
|
- var canvasEl = sceneEl.canvas;
|
|
|
|
|
|
+/**
|
|
|
|
+ * @param {THREE.Geometry} geometry
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+function createCylinderShape (geometry) {
|
|
|
|
+ var shape,
|
|
|
|
+ params = geometry.metadata
|
|
|
|
+ ? geometry.metadata.parameters
|
|
|
|
+ : geometry.parameters;
|
|
|
|
+ shape = new CANNON.Cylinder(
|
|
|
|
+ params.radiusTop,
|
|
|
|
+ params.radiusBottom,
|
|
|
|
+ params.height,
|
|
|
|
+ params.radialSegments
|
|
|
|
+ );
|
|
|
|
|
|
- if (!canvasEl) {
|
|
|
|
- sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ // Include metadata for serialization.
|
|
|
|
+ shape._type = CANNON.Shape.types.CYLINDER; // Patch schteppe/cannon.js#329.
|
|
|
|
+ shape.radiusTop = params.radiusTop;
|
|
|
|
+ shape.radiusBottom = params.radiusBottom;
|
|
|
|
+ shape.height = params.height;
|
|
|
|
+ shape.numSegments = params.radialSegments;
|
|
|
|
|
|
- canvasEl.addEventListener('touchstart', this.onTouchStart);
|
|
|
|
- canvasEl.addEventListener('touchend', this.onTouchEnd);
|
|
|
|
- },
|
|
|
|
|
|
+ shape.orientation = new CANNON.Quaternion();
|
|
|
|
+ shape.orientation.setFromEuler(THREE.Math.degToRad(-90), 0, 0, 'XYZ').normalize();
|
|
|
|
+ return shape;
|
|
|
|
+}
|
|
|
|
|
|
- removeEventListeners: function () {
|
|
|
|
- var canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
|
|
|
|
- if (!canvasEl) { return; }
|
|
|
|
|
|
+/**
|
|
|
|
+ * @param {THREE.Object3D} object
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+function createBoundingCylinderShape (object, options) {
|
|
|
|
+ var shape, height, radius,
|
|
|
|
+ box = new THREE.Box3(),
|
|
|
|
+ axes = ['x', 'y', 'z'],
|
|
|
|
+ majorAxis = options.cylinderAxis || 'y',
|
|
|
|
+ minorAxes = axes.splice(axes.indexOf(majorAxis), 1) && axes;
|
|
|
|
|
|
- canvasEl.removeEventListener('touchstart', this.onTouchStart);
|
|
|
|
- canvasEl.removeEventListener('touchend', this.onTouchEnd);
|
|
|
|
- },
|
|
|
|
|
|
+ box.setFromObject(object);
|
|
|
|
|
|
- isVelocityActive: function () {
|
|
|
|
- return this.data.enabled && this.isMoving;
|
|
|
|
- },
|
|
|
|
|
|
+ if (!isFinite(box.min.lengthSq())) return null;
|
|
|
|
|
|
- getVelocityDelta: function () {
|
|
|
|
- this.dVelocity.z = this.isMoving ? -1 : 0;
|
|
|
|
- return this.dVelocity.clone();
|
|
|
|
- },
|
|
|
|
|
|
+ // Compute cylinder dimensions.
|
|
|
|
+ height = box.max[majorAxis] - box.min[majorAxis];
|
|
|
|
+ radius = 0.5 * Math.max(
|
|
|
|
+ box.max[minorAxes[0]] - box.min[minorAxes[0]],
|
|
|
|
+ box.max[minorAxes[1]] - box.min[minorAxes[1]]
|
|
|
|
+ );
|
|
|
|
|
|
- bindMethods: function () {
|
|
|
|
- this.onTouchStart = this.onTouchStart.bind(this);
|
|
|
|
- this.onTouchEnd = this.onTouchEnd.bind(this);
|
|
|
|
- },
|
|
|
|
|
|
+ // Create shape.
|
|
|
|
+ shape = new CANNON.Cylinder(radius, radius, height, 12);
|
|
|
|
|
|
- onTouchStart: function (e) {
|
|
|
|
- this.isMoving = true;
|
|
|
|
- e.preventDefault();
|
|
|
|
- },
|
|
|
|
|
|
+ // Include metadata for serialization.
|
|
|
|
+ shape._type = CANNON.Shape.types.CYLINDER; // Patch schteppe/cannon.js#329.
|
|
|
|
+ shape.radiusTop = radius;
|
|
|
|
+ shape.radiusBottom = radius;
|
|
|
|
+ shape.height = height;
|
|
|
|
+ shape.numSegments = 12;
|
|
|
|
|
|
- onTouchEnd: function (e) {
|
|
|
|
- this.isMoving = false;
|
|
|
|
- e.preventDefault();
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ shape.orientation = new CANNON.Quaternion();
|
|
|
|
+ shape.orientation.setFromEuler(
|
|
|
|
+ majorAxis === 'y' ? PI_2 : 0,
|
|
|
|
+ majorAxis === 'z' ? PI_2 : 0,
|
|
|
|
+ 0,
|
|
|
|
+ 'XYZ'
|
|
|
|
+ ).normalize();
|
|
|
|
+ return shape;
|
|
|
|
+}
|
|
|
|
|
|
-},{}],88:[function(require,module,exports){
|
|
|
|
/**
|
|
/**
|
|
- * Universal Controls
|
|
|
|
- *
|
|
|
|
- * @author Don McCurdy <dm@donmccurdy.com>
|
|
|
|
|
|
+ * @param {THREE.Geometry} geometry
|
|
|
|
+ * @return {CANNON.Shape}
|
|
*/
|
|
*/
|
|
|
|
+function createPlaneShape (geometry) {
|
|
|
|
+ geometry.computeBoundingBox();
|
|
|
|
+ var box = geometry.boundingBox;
|
|
|
|
+ return new CANNON.Box(new CANNON.Vec3(
|
|
|
|
+ (box.max.x - box.min.x) / 2 || 0.1,
|
|
|
|
+ (box.max.y - box.min.y) / 2 || 0.1,
|
|
|
|
+ (box.max.z - box.min.z) / 2 || 0.1
|
|
|
|
+ ));
|
|
|
|
+}
|
|
|
|
|
|
-var COMPONENT_SUFFIX = '-controls',
|
|
|
|
- MAX_DELTA = 0.2, // ms
|
|
|
|
- PI_2 = Math.PI / 2;
|
|
|
|
-
|
|
|
|
-module.exports = {
|
|
|
|
-
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Schema
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
- dependencies: ['velocity', 'rotation'],
|
|
|
|
|
|
+/**
|
|
|
|
+ * @param {THREE.Geometry} geometry
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+function createSphereShape (geometry) {
|
|
|
|
+ var params = geometry.metadata
|
|
|
|
+ ? geometry.metadata.parameters
|
|
|
|
+ : geometry.parameters;
|
|
|
|
+ return new CANNON.Sphere(params.radius);
|
|
|
|
+}
|
|
|
|
|
|
- schema: {
|
|
|
|
- enabled: { default: true },
|
|
|
|
- movementEnabled: { default: true },
|
|
|
|
- movementControls: { default: ['gamepad', 'keyboard', 'touch', 'hmd'] },
|
|
|
|
- rotationEnabled: { default: true },
|
|
|
|
- rotationControls: { default: ['hmd', 'gamepad', 'mouse'] },
|
|
|
|
- movementSpeed: { default: 5 }, // m/s
|
|
|
|
- movementEasing: { default: 15 }, // m/s2
|
|
|
|
- movementEasingY: { default: 0 }, // m/s2
|
|
|
|
- movementAcceleration: { default: 80 }, // m/s2
|
|
|
|
- rotationSensitivity: { default: 0.05 }, // radians/frame, ish
|
|
|
|
- fly: { default: false },
|
|
|
|
- },
|
|
|
|
|
|
+/**
|
|
|
|
+ * @param {THREE.Object3D} object
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+function createBoundingSphereShape (object, options) {
|
|
|
|
+ if (options.sphereRadius) {
|
|
|
|
+ return new CANNON.Sphere(options.sphereRadius);
|
|
|
|
+ }
|
|
|
|
+ var geometry = getGeometry(object);
|
|
|
|
+ if (!geometry) return null;
|
|
|
|
+ geometry.computeBoundingSphere();
|
|
|
|
+ return new CANNON.Sphere(geometry.boundingSphere.radius);
|
|
|
|
+}
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Lifecycle
|
|
|
|
- */
|
|
|
|
|
|
+/**
|
|
|
|
+ * @param {THREE.Geometry} geometry
|
|
|
|
+ * @return {CANNON.Shape}
|
|
|
|
+ */
|
|
|
|
+function createTrimeshShape (geometry) {
|
|
|
|
+ var indices,
|
|
|
|
+ vertices = getVertices(geometry);
|
|
|
|
|
|
- init: function () {
|
|
|
|
- var rotation = this.el.getAttribute('rotation');
|
|
|
|
|
|
+ if (!vertices.length) return null;
|
|
|
|
|
|
- if (this.el.hasAttribute('look-controls') && this.data.rotationEnabled) {
|
|
|
|
- console.error('[universal-controls] The `universal-controls` component is a replacement '
|
|
|
|
- + 'for `look-controls`, and cannot be used in combination with it.');
|
|
|
|
- }
|
|
|
|
|
|
+ indices = Object.keys(vertices).map(Number);
|
|
|
|
+ return new CANNON.Trimesh(vertices, indices);
|
|
|
|
+}
|
|
|
|
|
|
- // Movement
|
|
|
|
- this.velocity = new THREE.Vector3();
|
|
|
|
|
|
+/******************************************************************************
|
|
|
|
+ * Utils
|
|
|
|
+ */
|
|
|
|
|
|
- // Rotation
|
|
|
|
- this.pitch = new THREE.Object3D();
|
|
|
|
- this.pitch.rotation.x = THREE.Math.degToRad(rotation.x);
|
|
|
|
- this.yaw = new THREE.Object3D();
|
|
|
|
- this.yaw.position.y = 10;
|
|
|
|
- this.yaw.rotation.y = THREE.Math.degToRad(rotation.y);
|
|
|
|
- this.yaw.add(this.pitch);
|
|
|
|
- this.heading = new THREE.Euler(0, 0, 0, 'YXZ');
|
|
|
|
|
|
+/**
|
|
|
|
+ * Returns a single geometry for the given object. If the object is compound,
|
|
|
|
+ * its geometries are automatically merged.
|
|
|
|
+ * @param {THREE.Object3D} object
|
|
|
|
+ * @return {THREE.Geometry}
|
|
|
|
+ */
|
|
|
|
+function getGeometry (object) {
|
|
|
|
+ var matrix, mesh,
|
|
|
|
+ meshes = getMeshes(object),
|
|
|
|
+ tmp = new THREE.Geometry(),
|
|
|
|
+ combined = new THREE.Geometry();
|
|
|
|
|
|
- if (this.el.sceneEl.hasLoaded) {
|
|
|
|
- this.injectControls();
|
|
|
|
|
|
+ if (meshes.length === 0) return null;
|
|
|
|
+
|
|
|
|
+ // Apply scale – it can't easily be applied to a CANNON.Shape later.
|
|
|
|
+ if (meshes.length === 1) {
|
|
|
|
+ var position = new THREE.Vector3(),
|
|
|
|
+ quaternion = new THREE.Quaternion(),
|
|
|
|
+ scale = new THREE.Vector3();
|
|
|
|
+ if (meshes[0].geometry.isBufferGeometry) {
|
|
|
|
+ if (meshes[0].geometry.attributes.position) {
|
|
|
|
+ tmp.fromBufferGeometry(meshes[0].geometry);
|
|
|
|
+ }
|
|
} else {
|
|
} else {
|
|
- this.el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
|
|
|
|
|
|
+ tmp = meshes[0].geometry.clone();
|
|
}
|
|
}
|
|
- },
|
|
|
|
|
|
+ tmp.metadata = meshes[0].geometry.metadata;
|
|
|
|
+ meshes[0].updateMatrixWorld();
|
|
|
|
+ meshes[0].matrixWorld.decompose(position, quaternion, scale);
|
|
|
|
+ return tmp.scale(scale.x, scale.y, scale.z);
|
|
|
|
+ }
|
|
|
|
|
|
- update: function () {
|
|
|
|
- if (this.el.sceneEl.hasLoaded) {
|
|
|
|
- this.injectControls();
|
|
|
|
|
|
+ // Recursively merge geometry, preserving local transforms.
|
|
|
|
+ while ((mesh = meshes.pop())) {
|
|
|
|
+ mesh.updateMatrixWorld();
|
|
|
|
+ if (mesh.geometry.isBufferGeometry) {
|
|
|
|
+ tmp.fromBufferGeometry(mesh.geometry);
|
|
|
|
+ combined.merge(tmp, mesh.matrixWorld);
|
|
|
|
+ } else {
|
|
|
|
+ combined.merge(mesh.geometry, mesh.matrixWorld);
|
|
}
|
|
}
|
|
- },
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- injectControls: function () {
|
|
|
|
- var i, name,
|
|
|
|
- data = this.data;
|
|
|
|
|
|
+ matrix = new THREE.Matrix4();
|
|
|
|
+ matrix.scale(object.scale);
|
|
|
|
+ combined.applyMatrix(matrix);
|
|
|
|
+ return combined;
|
|
|
|
+}
|
|
|
|
|
|
- for (i = 0; i < data.movementControls.length; i++) {
|
|
|
|
- name = data.movementControls[i] + COMPONENT_SUFFIX;
|
|
|
|
- if (!this.el.components[name]) {
|
|
|
|
- this.el.setAttribute(name, '');
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+/**
|
|
|
|
+ * @param {THREE.Geometry} geometry
|
|
|
|
+ * @return {Array<number>}
|
|
|
|
+ */
|
|
|
|
+function getVertices (geometry) {
|
|
|
|
+ if (!geometry.attributes) {
|
|
|
|
+ geometry = new THREE.BufferGeometry().fromGeometry(geometry);
|
|
|
|
+ }
|
|
|
|
+ return (geometry.attributes.position || {}).array || [];
|
|
|
|
+}
|
|
|
|
|
|
- for (i = 0; i < data.rotationControls.length; i++) {
|
|
|
|
- name = data.rotationControls[i] + COMPONENT_SUFFIX;
|
|
|
|
- if (!this.el.components[name]) {
|
|
|
|
- this.el.setAttribute(name, '');
|
|
|
|
- }
|
|
|
|
|
|
+/**
|
|
|
|
+ * Returns a flat array of THREE.Mesh instances from the given object. If
|
|
|
|
+ * nested transformations are found, they are applied to child meshes
|
|
|
|
+ * as mesh.userData.matrix, so that each mesh has its position/rotation/scale
|
|
|
|
+ * independently of all of its parents except the top-level object.
|
|
|
|
+ * @param {THREE.Object3D} object
|
|
|
|
+ * @return {Array<THREE.Mesh>}
|
|
|
|
+ */
|
|
|
|
+function getMeshes (object) {
|
|
|
|
+ var meshes = [];
|
|
|
|
+ object.traverse(function (o) {
|
|
|
|
+ if (o.type === 'Mesh') {
|
|
|
|
+ meshes.push(o);
|
|
}
|
|
}
|
|
- },
|
|
|
|
|
|
+ });
|
|
|
|
+ return meshes;
|
|
|
|
+}
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Tick
|
|
|
|
- */
|
|
|
|
|
|
+},{"./lib/THREE.quickhull":85,"cannon":23}],85:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
|
|
- tick: function (t, dt) {
|
|
|
|
- if (!dt) { return; }
|
|
|
|
|
|
+ QuickHull
|
|
|
|
+ ---------
|
|
|
|
|
|
- // Update rotation.
|
|
|
|
- if (this.data.rotationEnabled) this.updateRotation(dt);
|
|
|
|
|
|
+ The MIT License
|
|
|
|
|
|
- // Update velocity. If FPS is too low, reset.
|
|
|
|
- if (this.data.movementEnabled && dt / 1000 > MAX_DELTA) {
|
|
|
|
- this.velocity.set(0, 0, 0);
|
|
|
|
- this.el.setAttribute('velocity', this.velocity);
|
|
|
|
- } else {
|
|
|
|
- this.updateVelocity(dt);
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ Copyright © 2010-2014 three.js authors
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Rotation
|
|
|
|
- */
|
|
|
|
|
|
+ Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
+ of this software and associated documentation files (the "Software"), to deal
|
|
|
|
+ in the Software without restriction, including without limitation the rights
|
|
|
|
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
+ copies of the Software, and to permit persons to whom the Software is
|
|
|
|
+ furnished to do so, subject to the following conditions:
|
|
|
|
|
|
- updateRotation: function (dt) {
|
|
|
|
- var control, dRotation,
|
|
|
|
- data = this.data;
|
|
|
|
|
|
+ The above copyright notice and this permission notice shall be included in
|
|
|
|
+ all copies or substantial portions of the Software.
|
|
|
|
|
|
- for (var i = 0, l = data.rotationControls.length; i < l; i++) {
|
|
|
|
- control = this.el.components[data.rotationControls[i] + COMPONENT_SUFFIX];
|
|
|
|
- if (control && control.isRotationActive()) {
|
|
|
|
- if (control.getRotationDelta) {
|
|
|
|
- dRotation = control.getRotationDelta(dt);
|
|
|
|
- dRotation.multiplyScalar(data.rotationSensitivity);
|
|
|
|
- this.yaw.rotation.y -= dRotation.x;
|
|
|
|
- this.pitch.rotation.x -= dRotation.y;
|
|
|
|
- this.pitch.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitch.rotation.x));
|
|
|
|
- this.el.setAttribute('rotation', {
|
|
|
|
- x: THREE.Math.radToDeg(this.pitch.rotation.x),
|
|
|
|
- y: THREE.Math.radToDeg(this.yaw.rotation.y),
|
|
|
|
- z: 0
|
|
|
|
- });
|
|
|
|
- } else if (control.getRotation) {
|
|
|
|
- this.el.setAttribute('rotation', control.getRotation());
|
|
|
|
- } else {
|
|
|
|
- throw new Error('Incompatible rotation controls: %s', data.rotationControls[i]);
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Movement
|
|
|
|
- */
|
|
|
|
|
|
+ THE SOFTWARE.
|
|
|
|
|
|
- updateVelocity: function (dt) {
|
|
|
|
- var control, dVelocity,
|
|
|
|
- velocity = this.velocity,
|
|
|
|
- data = this.data;
|
|
|
|
|
|
|
|
- if (data.movementEnabled) {
|
|
|
|
- for (var i = 0, l = data.movementControls.length; i < l; i++) {
|
|
|
|
- control = this.el.components[data.movementControls[i] + COMPONENT_SUFFIX];
|
|
|
|
- if (control && control.isVelocityActive()) {
|
|
|
|
- if (control.getVelocityDelta) {
|
|
|
|
- dVelocity = control.getVelocityDelta(dt);
|
|
|
|
- } else if (control.getVelocity) {
|
|
|
|
- this.el.setAttribute('velocity', control.getVelocity());
|
|
|
|
- return;
|
|
|
|
- } else if (control.getPositionDelta) {
|
|
|
|
- velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
|
|
|
|
- this.el.setAttribute('velocity', velocity);
|
|
|
|
- return;
|
|
|
|
- } else {
|
|
|
|
- throw new Error('Incompatible movement controls: ', data.movementControls[i]);
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ @author mark lundin / http://mark-lundin.com
|
|
|
|
|
|
- velocity.copy(this.el.getAttribute('velocity'));
|
|
|
|
- velocity.x -= velocity.x * data.movementEasing * dt / 1000;
|
|
|
|
- velocity.y -= velocity.y * data.movementEasingY * dt / 1000;
|
|
|
|
- velocity.z -= velocity.z * data.movementEasing * dt / 1000;
|
|
|
|
|
|
+ This is a 3D implementation of the Quick Hull algorithm.
|
|
|
|
+ It is a fast way of computing a convex hull with average complexity
|
|
|
|
+ of O(n log(n)).
|
|
|
|
+ It uses depends on three.js and is supposed to create THREE.Geometry.
|
|
|
|
|
|
- if (dVelocity && data.movementEnabled) {
|
|
|
|
- // Set acceleration
|
|
|
|
- if (dVelocity.length() > 1) {
|
|
|
|
- dVelocity.setLength(this.data.movementAcceleration * dt / 1000);
|
|
|
|
- } else {
|
|
|
|
- dVelocity.multiplyScalar(this.data.movementAcceleration * dt / 1000);
|
|
|
|
- }
|
|
|
|
|
|
+ It's also very messy
|
|
|
|
|
|
- // Rotate to heading
|
|
|
|
- var rotation = this.el.getAttribute('rotation');
|
|
|
|
- if (rotation) {
|
|
|
|
- this.heading.set(
|
|
|
|
- data.fly ? THREE.Math.degToRad(rotation.x) : 0,
|
|
|
|
- THREE.Math.degToRad(rotation.y),
|
|
|
|
- 0
|
|
|
|
- );
|
|
|
|
- dVelocity.applyEuler(this.heading);
|
|
|
|
- }
|
|
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+module.exports = (function(){
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ var faces = [],
|
|
|
|
+ faceStack = [],
|
|
|
|
+ i, NUM_POINTS, extremes,
|
|
|
|
+ max = 0,
|
|
|
|
+ dcur, current, j, v0, v1, v2, v3,
|
|
|
|
+ N, D;
|
|
|
|
|
|
- velocity.add(dVelocity);
|
|
|
|
|
|
+ var ab, ac, ax,
|
|
|
|
+ suba, subb, normal,
|
|
|
|
+ diff, subaA, subaB, subC;
|
|
|
|
|
|
- // TODO - Several issues here:
|
|
|
|
- // (1) Interferes w/ gravity.
|
|
|
|
- // (2) Interferes w/ jumping.
|
|
|
|
- // (3) Likely to interfere w/ relative position to moving platform.
|
|
|
|
- // if (velocity.length() > data.movementSpeed) {
|
|
|
|
- // velocity.setLength(data.movementSpeed);
|
|
|
|
- // }
|
|
|
|
- }
|
|
|
|
|
|
+ function reset(){
|
|
|
|
+
|
|
|
|
+ ab = new THREE.Vector3(),
|
|
|
|
+ ac = new THREE.Vector3(),
|
|
|
|
+ ax = new THREE.Vector3(),
|
|
|
|
+ suba = new THREE.Vector3(),
|
|
|
|
+ subb = new THREE.Vector3(),
|
|
|
|
+ normal = new THREE.Vector3(),
|
|
|
|
+ diff = new THREE.Vector3(),
|
|
|
|
+ subaA = new THREE.Vector3(),
|
|
|
|
+ subaB = new THREE.Vector3(),
|
|
|
|
+ subC = new THREE.Vector3();
|
|
|
|
|
|
- this.el.setAttribute('velocity', velocity);
|
|
|
|
}
|
|
}
|
|
-};
|
|
|
|
|
|
|
|
-},{}],89:[function(require,module,exports){
|
|
|
|
-var LoopMode = {
|
|
|
|
- once: THREE.LoopOnce,
|
|
|
|
- repeat: THREE.LoopRepeat,
|
|
|
|
- pingpong: THREE.LoopPingPong
|
|
|
|
-};
|
|
|
|
|
|
+ //temporary vectors
|
|
|
|
|
|
-/**
|
|
|
|
- * animation-mixer
|
|
|
|
- *
|
|
|
|
- * Player for animation clips. Intended to be compatible with any model format that supports
|
|
|
|
- * skeletal or morph animations through THREE.AnimationMixer.
|
|
|
|
- * See: https://threejs.org/docs/?q=animation#Reference/Animation/AnimationMixer
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- clip: {default: '*'},
|
|
|
|
- duration: {default: 0},
|
|
|
|
- crossFadeDuration: {default: 0},
|
|
|
|
- loop: {default: 'repeat', oneOf: Object.keys(LoopMode)},
|
|
|
|
- repetitions: {default: Infinity, min: 0}
|
|
|
|
- },
|
|
|
|
|
|
+ function process( points ){
|
|
|
|
|
|
- init: function () {
|
|
|
|
- /** @type {THREE.Mesh} */
|
|
|
|
- this.model = null;
|
|
|
|
- /** @type {THREE.AnimationMixer} */
|
|
|
|
- this.mixer = null;
|
|
|
|
- /** @type {Array<THREE.AnimationAction>} */
|
|
|
|
- this.activeActions = [];
|
|
|
|
|
|
+ // Iterate through all the faces and remove
|
|
|
|
+ while( faceStack.length > 0 ){
|
|
|
|
+ cull( faceStack.shift(), points );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- var model = this.el.getObject3D('mesh');
|
|
|
|
|
|
|
|
- if (model) {
|
|
|
|
- this.load(model);
|
|
|
|
- } else {
|
|
|
|
- this.el.addEventListener('model-loaded', function(e) {
|
|
|
|
- this.load(e.detail.model);
|
|
|
|
- }.bind(this));
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ var norm = function(){
|
|
|
|
|
|
- load: function (model) {
|
|
|
|
- var el = this.el;
|
|
|
|
- this.model = model;
|
|
|
|
- this.mixer = new THREE.AnimationMixer(model);
|
|
|
|
- this.mixer.addEventListener('loop', function (e) {
|
|
|
|
- el.emit('animation-loop', {action: e.action, loopDelta: e.loopDelta});
|
|
|
|
- }.bind(this));
|
|
|
|
- this.mixer.addEventListener('finished', function (e) {
|
|
|
|
- el.emit('animation-finished', {action: e.action, direction: e.direction});
|
|
|
|
- }.bind(this));
|
|
|
|
- if (this.data.clip) this.update({});
|
|
|
|
- },
|
|
|
|
|
|
+ var ca = new THREE.Vector3(),
|
|
|
|
+ ba = new THREE.Vector3(),
|
|
|
|
+ N = new THREE.Vector3();
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- if (this.mixer) this.mixer.stopAllAction();
|
|
|
|
- },
|
|
|
|
|
|
+ return function( a, b, c ){
|
|
|
|
|
|
- update: function (previousData) {
|
|
|
|
- if (!previousData) return;
|
|
|
|
|
|
+ ca.subVectors( c, a );
|
|
|
|
+ ba.subVectors( b, a );
|
|
|
|
|
|
- this.stopAction();
|
|
|
|
|
|
+ N.crossVectors( ca, ba );
|
|
|
|
|
|
- if (this.data.clip) {
|
|
|
|
- this.playAction();
|
|
|
|
|
|
+ return N.normalize();
|
|
}
|
|
}
|
|
- },
|
|
|
|
|
|
|
|
- stopAction: function () {
|
|
|
|
- var data = this.data;
|
|
|
|
- for (var i = 0; i < this.activeActions.length; i++) {
|
|
|
|
- data.crossFadeDuration
|
|
|
|
- ? this.activeActions[i].fadeOut(data.crossFadeDuration)
|
|
|
|
- : this.activeActions[i].stop();
|
|
|
|
- }
|
|
|
|
- this.activeActions.length = 0;
|
|
|
|
- },
|
|
|
|
|
|
+ }();
|
|
|
|
|
|
- playAction: function () {
|
|
|
|
- if (!this.mixer) return;
|
|
|
|
|
|
|
|
- var model = this.model,
|
|
|
|
- data = this.data,
|
|
|
|
- clips = model.animations || (model.geometry || {}).animations || [];
|
|
|
|
|
|
+ function getNormal( face, points ){
|
|
|
|
|
|
- if (!clips.length) return;
|
|
|
|
|
|
+ if( face.normal !== undefined ) return face.normal;
|
|
|
|
|
|
- var re = wildcardToRegExp(data.clip);
|
|
|
|
|
|
+ var p0 = points[face[0]],
|
|
|
|
+ p1 = points[face[1]],
|
|
|
|
+ p2 = points[face[2]];
|
|
|
|
|
|
- for (var clip, i = 0; (clip = clips[i]); i++) {
|
|
|
|
- if (clip.name.match(re)) {
|
|
|
|
- var action = this.mixer.clipAction(clip, model);
|
|
|
|
- action.enabled = true;
|
|
|
|
- if (data.duration) action.setDuration(data.duration);
|
|
|
|
- action
|
|
|
|
- .setLoop(LoopMode[data.loop], data.repetitions)
|
|
|
|
- .fadeIn(data.crossFadeDuration)
|
|
|
|
- .play();
|
|
|
|
- this.activeActions.push(action);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
+ ab.subVectors( p1, p0 );
|
|
|
|
+ ac.subVectors( p2, p0 );
|
|
|
|
+ normal.crossVectors( ac, ab );
|
|
|
|
+ normal.normalize();
|
|
|
|
+
|
|
|
|
+ return face.normal = normal.clone();
|
|
|
|
|
|
- tick: function (t, dt) {
|
|
|
|
- if (this.mixer && !isNaN(dt)) this.mixer.update(dt / 1000);
|
|
|
|
}
|
|
}
|
|
-};
|
|
|
|
|
|
|
|
-/**
|
|
|
|
- * Creates a RegExp from the given string, converting asterisks to .* expressions,
|
|
|
|
- * and escaping all other characters.
|
|
|
|
- */
|
|
|
|
-function wildcardToRegExp (s) {
|
|
|
|
- return new RegExp('^' + s.split(/\*+/).map(regExpEscape).join('.*') + '$');
|
|
|
|
-}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
- * RegExp-escapes all characters in the given string.
|
|
|
|
- */
|
|
|
|
-function regExpEscape (s) {
|
|
|
|
- return s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
|
|
|
|
-}
|
|
|
|
|
|
+ function assignPoints( face, pointset, points ){
|
|
|
|
|
|
-},{}],90:[function(require,module,exports){
|
|
|
|
-THREE.FBXLoader = require('../../lib/FBXLoader');
|
|
|
|
|
|
+ // ASSIGNING POINTS TO FACE
|
|
|
|
+ var p0 = points[face[0]],
|
|
|
|
+ dots = [], apex,
|
|
|
|
+ norm = getNormal( face, points );
|
|
|
|
|
|
-/**
|
|
|
|
- * fbx-model
|
|
|
|
- *
|
|
|
|
- * Loader for FBX format. Supports ASCII, but *not* binary, models.
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- src: { type: 'asset' },
|
|
|
|
- crossorigin: { default: '' }
|
|
|
|
- },
|
|
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.model = null;
|
|
|
|
- },
|
|
|
|
|
|
+ // Sory all the points by there distance from the plane
|
|
|
|
+ pointset.sort( function( aItem, bItem ){
|
|
|
|
|
|
- update: function () {
|
|
|
|
- var loader,
|
|
|
|
- data = this.data;
|
|
|
|
- if (!data.src) return;
|
|
|
|
|
|
|
|
- this.remove();
|
|
|
|
- loader = new THREE.FBXLoader();
|
|
|
|
- if (data.crossorigin) loader.setCrossOrigin(data.crossorigin);
|
|
|
|
- loader.load(data.src, this.load.bind(this));
|
|
|
|
- },
|
|
|
|
|
|
+ dots[aItem.x/3] = dots[aItem.x/3] !== undefined ? dots[aItem.x/3] : norm.dot( suba.subVectors( aItem, p0 ));
|
|
|
|
+ dots[bItem.x/3] = dots[bItem.x/3] !== undefined ? dots[bItem.x/3] : norm.dot( subb.subVectors( bItem, p0 ));
|
|
|
|
|
|
- load: function (model) {
|
|
|
|
- this.model = model;
|
|
|
|
- this.el.setObject3D('mesh', model);
|
|
|
|
- this.el.emit('model-loaded', {format: 'fbx', model: model});
|
|
|
|
- },
|
|
|
|
|
|
+ return dots[aItem.x/3] - dots[bItem.x/3] ;
|
|
|
|
+ });
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- if (this.model) this.el.removeObject3D('mesh');
|
|
|
|
|
|
+ //TODO :: Must be a faster way of finding and index in this array
|
|
|
|
+ var index = pointset.length;
|
|
|
|
+
|
|
|
|
+ if( index === 1 ) dots[pointset[0].x/3] = norm.dot( suba.subVectors( pointset[0], p0 ));
|
|
|
|
+ while( index-- > 0 && dots[pointset[index].x/3] > 0 )
|
|
|
|
+
|
|
|
|
+ var point;
|
|
|
|
+ if( index + 1 < pointset.length && dots[pointset[index+1].x/3] > 0 ){
|
|
|
|
+
|
|
|
|
+ face.visiblePoints = pointset.splice( index + 1 );
|
|
|
|
+ }
|
|
}
|
|
}
|
|
-};
|
|
|
|
|
|
|
|
-},{"../../lib/FBXLoader":3}],91:[function(require,module,exports){
|
|
|
|
-var fetchScript = require('../../lib/fetch-script')();
|
|
|
|
|
|
|
|
-var LOADER_SRC = 'https://rawgit.com/mrdoob/three.js/r86/examples/js/loaders/GLTFLoader.js';
|
|
|
|
|
|
|
|
-/**
|
|
|
|
- * Legacy loader for glTF 1.0 models.
|
|
|
|
- * Asynchronously loads THREE.GLTFLoader from rawgit.
|
|
|
|
- */
|
|
|
|
-module.exports.Component = {
|
|
|
|
- schema: {type: 'model'},
|
|
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.model = null;
|
|
|
|
- this.loader = null;
|
|
|
|
- this.loaderPromise = loadLoader().then(function () {
|
|
|
|
- this.loader = new THREE.GLTFLoader();
|
|
|
|
- this.loader.setCrossOrigin('Anonymous');
|
|
|
|
- }.bind(this));
|
|
|
|
- },
|
|
|
|
|
|
+ function cull( face, points ){
|
|
|
|
+
|
|
|
|
+ var i = faces.length,
|
|
|
|
+ dot, visibleFace, currentFace,
|
|
|
|
+ visibleFaces = [face];
|
|
|
|
+
|
|
|
|
+ var apex = points.indexOf( face.visiblePoints.pop() );
|
|
|
|
+
|
|
|
|
+ // Iterate through all other faces...
|
|
|
|
+ while( i-- > 0 ){
|
|
|
|
+ currentFace = faces[i];
|
|
|
|
+ if( currentFace !== face ){
|
|
|
|
+ // ...and check if they're pointing in the same direction
|
|
|
|
+ dot = getNormal( currentFace, points ).dot( diff.subVectors( points[apex], points[currentFace[0]] ));
|
|
|
|
+ if( dot > 0 ){
|
|
|
|
+ visibleFaces.push( currentFace );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var index, neighbouringIndex, vertex;
|
|
|
|
+
|
|
|
|
+ // Determine Perimeter - Creates a bounded horizon
|
|
|
|
+
|
|
|
|
+ // 1. Pick an edge A out of all possible edges
|
|
|
|
+ // 2. Check if A is shared by any other face. a->b === b->a
|
|
|
|
+ // 2.1 for each edge in each triangle, isShared = ( f1.a == f2.a && f1.b == f2.b ) || ( f1.a == f2.b && f1.b == f2.a )
|
|
|
|
+ // 3. If not shared, then add to convex horizon set,
|
|
|
|
+ //pick an end point (N) of the current edge A and choose a new edge NA connected to A.
|
|
|
|
+ //Restart from 1.
|
|
|
|
+ // 4. If A is shared, it is not an horizon edge, therefore flag both faces that share this edge as candidates for culling
|
|
|
|
+ // 5. If candidate geometry is a degenrate triangle (ie. the tangent space normal cannot be computed) then remove that triangle from all further processing
|
|
|
|
|
|
- update: function () {
|
|
|
|
- var self = this;
|
|
|
|
- var el = this.el;
|
|
|
|
- var src = this.data;
|
|
|
|
|
|
|
|
- if (!src) { return; }
|
|
|
|
|
|
+ var j = i = visibleFaces.length;
|
|
|
|
+ var isDistinct = false,
|
|
|
|
+ hasOneVisibleFace = i === 1,
|
|
|
|
+ cull = [],
|
|
|
|
+ perimeter = [],
|
|
|
|
+ edgeIndex = 0, compareFace, nextIndex,
|
|
|
|
+ a, b;
|
|
|
|
|
|
- this.remove();
|
|
|
|
|
|
+ var allPoints = [];
|
|
|
|
+ var originFace = [visibleFaces[0][0], visibleFaces[0][1], visibleFaces[0][1], visibleFaces[0][2], visibleFaces[0][2], visibleFaces[0][0]];
|
|
|
|
|
|
- this.loaderPromise.then(function () {
|
|
|
|
- this.loader.load(src, function gltfLoaded (gltfModel) {
|
|
|
|
- self.model = gltfModel.scene;
|
|
|
|
- self.model.animations = gltfModel.animations;
|
|
|
|
- self.system.registerModel(self.model);
|
|
|
|
- el.setObject3D('mesh', self.model);
|
|
|
|
- el.emit('model-loaded', {format: 'gltf', model: self.model});
|
|
|
|
- });
|
|
|
|
- }.bind(this));
|
|
|
|
- },
|
|
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- if (!this.model) { return; }
|
|
|
|
- this.el.removeObject3D('mesh');
|
|
|
|
- this.system.unregisterModel(this.model);
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ if( visibleFaces.length === 1 ){
|
|
|
|
+ currentFace = visibleFaces[0];
|
|
|
|
|
|
-/**
|
|
|
|
- * glTF model system.
|
|
|
|
- */
|
|
|
|
-module.exports.System = {
|
|
|
|
- init: function () {
|
|
|
|
- this.models = [];
|
|
|
|
- },
|
|
|
|
|
|
+ perimeter = [currentFace[0], currentFace[1], currentFace[1], currentFace[2], currentFace[2], currentFace[0]];
|
|
|
|
+ // remove visible face from list of faces
|
|
|
|
+ if( faceStack.indexOf( currentFace ) > -1 ){
|
|
|
|
+ faceStack.splice( faceStack.indexOf( currentFace ), 1 );
|
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
|
- * Updates shaders for all glTF models in the system.
|
|
|
|
- */
|
|
|
|
- tick: function () {
|
|
|
|
- var sceneEl = this.sceneEl;
|
|
|
|
- if (sceneEl.hasLoaded && this.models.length) {
|
|
|
|
- THREE.GLTFLoader.Shaders.update(sceneEl.object3D, sceneEl.camera);
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
|
|
|
|
- /**
|
|
|
|
- * Registers a glTF asset.
|
|
|
|
- * @param {object} gltf Asset containing a scene and (optional) animations and cameras.
|
|
|
|
- */
|
|
|
|
- registerModel: function (gltf) {
|
|
|
|
- this.models.push(gltf);
|
|
|
|
- },
|
|
|
|
|
|
+ if( currentFace.visiblePoints ) allPoints = allPoints.concat( currentFace.visiblePoints );
|
|
|
|
+ faces.splice( faces.indexOf( currentFace ), 1 );
|
|
|
|
|
|
- /**
|
|
|
|
- * Unregisters a glTF asset.
|
|
|
|
- * @param {object} gltf Asset containing a scene and (optional) animations and cameras.
|
|
|
|
- */
|
|
|
|
- unregisterModel: function (gltf) {
|
|
|
|
- var models = this.models;
|
|
|
|
- var index = models.indexOf(gltf);
|
|
|
|
- if (index >= 0) {
|
|
|
|
- models.splice(index, 1);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ }else{
|
|
|
|
|
|
-var loadLoader = (function () {
|
|
|
|
- var promise;
|
|
|
|
- return function () {
|
|
|
|
- promise = promise || fetchScript(LOADER_SRC);
|
|
|
|
- return promise;
|
|
|
|
- };
|
|
|
|
-}());
|
|
|
|
|
|
+ while( i-- > 0 ){ // for each visible face
|
|
|
|
|
|
-},{"../../lib/fetch-script":8}],92:[function(require,module,exports){
|
|
|
|
-var fetchScript = require('../../lib/fetch-script')();
|
|
|
|
|
|
+ currentFace = visibleFaces[i];
|
|
|
|
|
|
-var LOADER_SRC = 'https://rawgit.com/mrdoob/three.js/r87/examples/js/loaders/GLTFLoader.js';
|
|
|
|
-// Monkeypatch while waiting for three.js r86.
|
|
|
|
-if (THREE.PropertyBinding.sanitizeNodeName === undefined) {
|
|
|
|
|
|
+ // remove visible face from list of faces
|
|
|
|
+ if( faceStack.indexOf( currentFace ) > -1 ){
|
|
|
|
+ faceStack.splice( faceStack.indexOf( currentFace ), 1 );
|
|
|
|
+ }
|
|
|
|
|
|
- THREE.PropertyBinding.sanitizeNodeName = function (s) {
|
|
|
|
- return s.replace( /\s/g, '_' ).replace( /[^\w-]/g, '' );
|
|
|
|
- };
|
|
|
|
|
|
+ if( currentFace.visiblePoints ) allPoints = allPoints.concat( currentFace.visiblePoints );
|
|
|
|
+ faces.splice( faces.indexOf( currentFace ), 1 );
|
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
- * Upcoming loader for glTF 2.0 models.
|
|
|
|
- * Asynchronously loads THREE.GLTF2Loader from rawgit.
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {type: 'model'},
|
|
|
|
|
|
+ var isSharedEdge;
|
|
|
|
+ cEdgeIndex = 0;
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.model = null;
|
|
|
|
- this.loader = null;
|
|
|
|
- this.loaderPromise = loadLoader().then(function () {
|
|
|
|
- this.loader = new THREE.GLTFLoader();
|
|
|
|
- this.loader.setCrossOrigin('Anonymous');
|
|
|
|
- }.bind(this));
|
|
|
|
- },
|
|
|
|
|
|
+ while( cEdgeIndex < 3 ){ // Iterate through it's edges
|
|
|
|
|
|
- update: function () {
|
|
|
|
- var self = this;
|
|
|
|
- var el = this.el;
|
|
|
|
- var src = this.data;
|
|
|
|
|
|
+ isSharedEdge = false;
|
|
|
|
+ j = visibleFaces.length;
|
|
|
|
+ a = currentFace[cEdgeIndex]
|
|
|
|
+ b = currentFace[(cEdgeIndex+1)%3];
|
|
|
|
|
|
- if (!src) { return; }
|
|
|
|
|
|
|
|
- this.remove();
|
|
|
|
|
|
+ while( j-- > 0 && !isSharedEdge ){ // find another visible faces
|
|
|
|
|
|
- this.loaderPromise.then(function () {
|
|
|
|
- this.loader.load(src, function gltfLoaded (gltfModel) {
|
|
|
|
- self.model = gltfModel.scene;
|
|
|
|
- self.model.animations = gltfModel.animations;
|
|
|
|
- el.setObject3D('mesh', self.model);
|
|
|
|
- el.emit('model-loaded', {format: 'gltf', model: self.model});
|
|
|
|
- });
|
|
|
|
- }.bind(this));
|
|
|
|
- },
|
|
|
|
|
|
+ compareFace = visibleFaces[j];
|
|
|
|
+ edgeIndex = 0;
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- if (!this.model) { return; }
|
|
|
|
- this.el.removeObject3D('mesh');
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ // isSharedEdge = compareFace == currentFace;
|
|
|
|
+ if( compareFace !== currentFace ){
|
|
|
|
|
|
-var loadLoader = (function () {
|
|
|
|
- var promise;
|
|
|
|
- return function () {
|
|
|
|
- promise = promise || fetchScript(LOADER_SRC);
|
|
|
|
- return promise;
|
|
|
|
- };
|
|
|
|
-}());
|
|
|
|
|
|
+ while( edgeIndex < 3 && !isSharedEdge ){ //Check all it's indices
|
|
|
|
|
|
-},{"../../lib/fetch-script":8}],93:[function(require,module,exports){
|
|
|
|
-module.exports = {
|
|
|
|
- 'animation-mixer': require('./animation-mixer'),
|
|
|
|
- 'fbx-model': require('./fbx-model'),
|
|
|
|
- 'gltf-model-next': require('./gltf-model-next'),
|
|
|
|
- 'gltf-model-legacy': require('./gltf-model-legacy'),
|
|
|
|
- 'json-model': require('./json-model'),
|
|
|
|
- 'object-model': require('./object-model'),
|
|
|
|
- 'ply-model': require('./ply-model'),
|
|
|
|
- 'three-model': require('./three-model'),
|
|
|
|
|
|
+ nextIndex = ( edgeIndex + 1 );
|
|
|
|
+ isSharedEdge = ( compareFace[edgeIndex] === a && compareFace[nextIndex%3] === b ) ||
|
|
|
|
+ ( compareFace[edgeIndex] === b && compareFace[nextIndex%3] === a );
|
|
|
|
|
|
- registerAll: function (AFRAME) {
|
|
|
|
- if (this._registered) return;
|
|
|
|
|
|
+ edgeIndex++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
|
|
+ if( !isSharedEdge || hasOneVisibleFace ){
|
|
|
|
+ perimeter.push( a );
|
|
|
|
+ perimeter.push( b );
|
|
|
|
+ }
|
|
|
|
|
|
- // THREE.AnimationMixer
|
|
|
|
- if (!AFRAME.components['animation-mixer']) {
|
|
|
|
- AFRAME.registerComponent('animation-mixer', this['animation-mixer']);
|
|
|
|
|
|
+ cEdgeIndex++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- // THREE.PlyLoader
|
|
|
|
- if (!AFRAME.systems['ply-model']) {
|
|
|
|
- AFRAME.registerSystem('ply-model', this['ply-model'].System);
|
|
|
|
- }
|
|
|
|
- if (!AFRAME.components['ply-model']) {
|
|
|
|
- AFRAME.registerComponent('ply-model', this['ply-model'].Component);
|
|
|
|
- }
|
|
|
|
|
|
+ // create new face for all pairs around edge
|
|
|
|
+ i = 0;
|
|
|
|
+ var l = perimeter.length/2;
|
|
|
|
+ var f;
|
|
|
|
|
|
- // THREE.FBXLoader
|
|
|
|
- if (!AFRAME.components['fbx-model']) {
|
|
|
|
- AFRAME.registerComponent('fbx-model', this['fbx-model']);
|
|
|
|
|
|
+ while( i < l ){
|
|
|
|
+ f = [ perimeter[i*2+1], apex, perimeter[i*2] ];
|
|
|
|
+ assignPoints( f, allPoints, points );
|
|
|
|
+ faces.push( f )
|
|
|
|
+ if( f.visiblePoints !== undefined )faceStack.push( f );
|
|
|
|
+ i++;
|
|
}
|
|
}
|
|
|
|
|
|
- // THREE.GLTF2Loader
|
|
|
|
- if (!AFRAME.components['gltf-model-next']) {
|
|
|
|
- AFRAME.registerComponent('gltf-model-next', this['gltf-model-next']);
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- // THREE.GLTFLoader
|
|
|
|
- if (!AFRAME.components['gltf-model-legacy']) {
|
|
|
|
- AFRAME.registerComponent('gltf-model-legacy', this['gltf-model-legacy'].Component);
|
|
|
|
- AFRAME.registerSystem('gltf-model-legacy', this['gltf-model-legacy'].System);
|
|
|
|
- }
|
|
|
|
|
|
+ var distSqPointSegment = function(){
|
|
|
|
|
|
- // THREE.JsonLoader
|
|
|
|
- if (!AFRAME.components['json-model']) {
|
|
|
|
- AFRAME.registerComponent('json-model', this['json-model']);
|
|
|
|
- }
|
|
|
|
|
|
+ var ab = new THREE.Vector3(),
|
|
|
|
+ ac = new THREE.Vector3(),
|
|
|
|
+ bc = new THREE.Vector3();
|
|
|
|
|
|
- // THREE.ObjectLoader
|
|
|
|
- if (!AFRAME.components['object-model']) {
|
|
|
|
- AFRAME.registerComponent('object-model', this['object-model']);
|
|
|
|
- }
|
|
|
|
|
|
+ return function( a, b, c ){
|
|
|
|
|
|
- // (deprecated) THREE.JsonLoader and THREE.ObjectLoader
|
|
|
|
- if (!AFRAME.components['three-model']) {
|
|
|
|
- AFRAME.registerComponent('three-model', this['three-model']);
|
|
|
|
- }
|
|
|
|
|
|
+ ab.subVectors( b, a );
|
|
|
|
+ ac.subVectors( c, a );
|
|
|
|
+ bc.subVectors( c, b );
|
|
|
|
|
|
- this._registered = true;
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ var e = ac.dot(ab);
|
|
|
|
+ if (e < 0.0) return ac.dot( ac );
|
|
|
|
+ var f = ab.dot( ab );
|
|
|
|
+ if (e >= f) return bc.dot( bc );
|
|
|
|
+ return ac.dot( ac ) - e * e / f;
|
|
|
|
|
|
-},{"./animation-mixer":89,"./fbx-model":90,"./gltf-model-legacy":91,"./gltf-model-next":92,"./json-model":94,"./object-model":95,"./ply-model":96,"./three-model":97}],94:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * json-model
|
|
|
|
- *
|
|
|
|
- * Loader for THREE.js JSON format. Somewhat confusingly, there are two different THREE.js formats,
|
|
|
|
- * both having the .json extension. This loader supports only THREE.JsonLoader, which typically
|
|
|
|
- * includes only a single mesh.
|
|
|
|
- *
|
|
|
|
- * Check the console for errors, if in doubt. You may need to use `object-model` or
|
|
|
|
- * `blend-character-model` for some .js and .json files.
|
|
|
|
- *
|
|
|
|
- * See: https://clara.io/learn/user-guide/data_exchange/threejs_export
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- src: { type: 'asset' },
|
|
|
|
- crossorigin: { default: '' }
|
|
|
|
- },
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.model = null;
|
|
|
|
- },
|
|
|
|
|
|
+ }();
|
|
|
|
|
|
- update: function () {
|
|
|
|
- var loader,
|
|
|
|
- data = this.data;
|
|
|
|
- if (!data.src) return;
|
|
|
|
|
|
|
|
- this.remove();
|
|
|
|
- loader = new THREE.JSONLoader();
|
|
|
|
- if (data.crossorigin) loader.crossOrigin = data.crossorigin;
|
|
|
|
- loader.load(data.src, function (geometry, materials) {
|
|
|
|
|
|
|
|
- // Attempt to automatically detect common material options.
|
|
|
|
- materials.forEach(function (mat) {
|
|
|
|
- mat.vertexColors = (geometry.faces[0] || {}).color ? THREE.FaceColors : THREE.NoColors;
|
|
|
|
- mat.skinning = !!(geometry.bones || []).length;
|
|
|
|
- mat.morphTargets = !!(geometry.morphTargets || []).length;
|
|
|
|
- mat.morphNormals = !!(geometry.morphNormals || []).length;
|
|
|
|
- });
|
|
|
|
|
|
|
|
- var model = (geometry.bones || []).length
|
|
|
|
- ? new THREE.SkinnedMesh(geometry, new THREE.MultiMaterial(materials))
|
|
|
|
- : new THREE.Mesh(geometry, new THREE.MultiMaterial(materials));
|
|
|
|
|
|
|
|
- this.load(model);
|
|
|
|
- }.bind(this));
|
|
|
|
- },
|
|
|
|
|
|
+ return function( geometry ){
|
|
|
|
+
|
|
|
|
+ reset();
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ points = geometry.vertices;
|
|
|
|
+ faces = [],
|
|
|
|
+ faceStack = [],
|
|
|
|
+ i = NUM_POINTS = points.length,
|
|
|
|
+ extremes = points.slice( 0, 6 ),
|
|
|
|
+ max = 0;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
|
|
- load: function (model) {
|
|
|
|
- this.model = model;
|
|
|
|
- this.el.setObject3D('mesh', model);
|
|
|
|
- this.el.emit('model-loaded', {format: 'json', model: model});
|
|
|
|
- },
|
|
|
|
|
|
+ /*
|
|
|
|
+ * FIND EXTREMETIES
|
|
|
|
+ */
|
|
|
|
+ while( i-- > 0 ){
|
|
|
|
+ if( points[i].x < extremes[0].x ) extremes[0] = points[i];
|
|
|
|
+ if( points[i].x > extremes[1].x ) extremes[1] = points[i];
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- if (this.model) this.el.removeObject3D('mesh');
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ if( points[i].y < extremes[2].y ) extremes[2] = points[i];
|
|
|
|
+ if( points[i].y < extremes[3].y ) extremes[3] = points[i];
|
|
|
|
|
|
-},{}],95:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * object-model
|
|
|
|
- *
|
|
|
|
- * Loader for THREE.js JSON format. Somewhat confusingly, there are two different THREE.js formats,
|
|
|
|
- * both having the .json extension. This loader supports only THREE.ObjectLoader, which typically
|
|
|
|
- * includes multiple meshes or an entire scene.
|
|
|
|
- *
|
|
|
|
- * Check the console for errors, if in doubt. You may need to use `json-model` or
|
|
|
|
- * `blend-character-model` for some .js and .json files.
|
|
|
|
- *
|
|
|
|
- * See: https://clara.io/learn/user-guide/data_exchange/threejs_export
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- src: { type: 'asset' },
|
|
|
|
- crossorigin: { default: '' }
|
|
|
|
- },
|
|
|
|
|
|
+ if( points[i].z < extremes[4].z ) extremes[4] = points[i];
|
|
|
|
+ if( points[i].z < extremes[5].z ) extremes[5] = points[i];
|
|
|
|
+ }
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.model = null;
|
|
|
|
- },
|
|
|
|
|
|
|
|
- update: function () {
|
|
|
|
- var loader,
|
|
|
|
- data = this.data;
|
|
|
|
- if (!data.src) return;
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Find the longest line between the extremeties
|
|
|
|
+ */
|
|
|
|
|
|
- this.remove();
|
|
|
|
- loader = new THREE.ObjectLoader();
|
|
|
|
- if (data.crossorigin) loader.setCrossOrigin(data.crossorigin);
|
|
|
|
- loader.load(data.src, function(object) {
|
|
|
|
|
|
+ j = i = 6;
|
|
|
|
+ while( i-- > 0 ){
|
|
|
|
+ j = i - 1;
|
|
|
|
+ while( j-- > 0 ){
|
|
|
|
+ if( max < (dcur = extremes[i].distanceToSquared( extremes[j] )) ){
|
|
|
|
+ max = dcur;
|
|
|
|
+ v0 = extremes[ i ];
|
|
|
|
+ v1 = extremes[ j ];
|
|
|
|
|
|
- // Enable skinning, if applicable.
|
|
|
|
- object.traverse(function(o) {
|
|
|
|
- if (o instanceof THREE.SkinnedMesh && o.material) {
|
|
|
|
- o.material.skinning = !!((o.geometry && o.geometry.bones) || []).length;
|
|
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- });
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- this.load(object);
|
|
|
|
- }.bind(this));
|
|
|
|
- },
|
|
|
|
|
|
|
|
- load: function (model) {
|
|
|
|
- this.model = model;
|
|
|
|
- this.el.setObject3D('mesh', model);
|
|
|
|
- this.el.emit('model-loaded', {format: 'json', model: model});
|
|
|
|
- },
|
|
|
|
|
|
+ // 3. Find the most distant point to the line segment, this creates a plane
|
|
|
|
+ i = 6;
|
|
|
|
+ max = 0;
|
|
|
|
+ while( i-- > 0 ){
|
|
|
|
+ dcur = distSqPointSegment( v0, v1, extremes[i]);
|
|
|
|
+ if( max < dcur ){
|
|
|
|
+ max = dcur;
|
|
|
|
+ v2 = extremes[ i ];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- if (this.model) this.el.removeObject3D('mesh');
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
|
|
-},{}],96:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * ply-model
|
|
|
|
- *
|
|
|
|
- * Wraps THREE.PLYLoader.
|
|
|
|
- */
|
|
|
|
-THREE.PLYLoader = require('../../lib/PLYLoader');
|
|
|
|
|
|
+ // 4. Find the most distant point to the plane.
|
|
|
|
|
|
-/**
|
|
|
|
- * Loads, caches, resolves geometries.
|
|
|
|
- *
|
|
|
|
- * @member cache - Promises that resolve geometries keyed by `src`.
|
|
|
|
- */
|
|
|
|
-module.exports.System = {
|
|
|
|
- init: function () {
|
|
|
|
- this.cache = {};
|
|
|
|
- },
|
|
|
|
|
|
+ N = norm(v0, v1, v2);
|
|
|
|
+ D = N.dot( v0 );
|
|
|
|
|
|
- /**
|
|
|
|
- * @returns {Promise}
|
|
|
|
- */
|
|
|
|
- getOrLoadGeometry: function (src, skipCache) {
|
|
|
|
- var cache = this.cache;
|
|
|
|
- var cacheItem = cache[src];
|
|
|
|
|
|
|
|
- if (!skipCache && cacheItem) {
|
|
|
|
- return cacheItem;
|
|
|
|
- }
|
|
|
|
|
|
+ max = 0;
|
|
|
|
+ i = NUM_POINTS;
|
|
|
|
+ while( i-- > 0 ){
|
|
|
|
+ dcur = Math.abs( points[i].dot( N ) - D );
|
|
|
|
+ if( max < dcur ){
|
|
|
|
+ max = dcur;
|
|
|
|
+ v3 = points[i];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- cache[src] = new Promise(function (resolve) {
|
|
|
|
- var loader = new THREE.PLYLoader();
|
|
|
|
- loader.load(src, function (geometry) {
|
|
|
|
- resolve(geometry);
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
- return cache[src];
|
|
|
|
- },
|
|
|
|
-};
|
|
|
|
|
|
|
|
-module.exports.Component = {
|
|
|
|
- schema: {
|
|
|
|
- skipCache: {type: 'boolean', default: false},
|
|
|
|
- src: {type: 'asset'}
|
|
|
|
- },
|
|
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.model = null;
|
|
|
|
- },
|
|
|
|
|
|
+ var v0Index = points.indexOf( v0 ),
|
|
|
|
+ v1Index = points.indexOf( v1 ),
|
|
|
|
+ v2Index = points.indexOf( v2 ),
|
|
|
|
+ v3Index = points.indexOf( v3 );
|
|
|
|
|
|
- update: function () {
|
|
|
|
- var data = this.data;
|
|
|
|
- var el = this.el;
|
|
|
|
- var loader;
|
|
|
|
|
|
|
|
- if (!data.src) {
|
|
|
|
- console.warn('[%s] `src` property is required.', this.name);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ // We now have a tetrahedron as the base geometry.
|
|
|
|
+ // Now we must subdivide the
|
|
|
|
|
|
- // Get geometry from system, create and set mesh.
|
|
|
|
- this.system.getOrLoadGeometry(data.src, data.skipCache).then(function (geometry) {
|
|
|
|
- var model = createModel(geometry);
|
|
|
|
- el.setObject3D('mesh', model);
|
|
|
|
- el.emit('model-loaded', {format: 'ply', model: model});
|
|
|
|
- });
|
|
|
|
- },
|
|
|
|
|
|
+ var tetrahedron =[
|
|
|
|
+ [ v2Index, v1Index, v0Index ],
|
|
|
|
+ [ v1Index, v3Index, v0Index ],
|
|
|
|
+ [ v2Index, v3Index, v1Index ],
|
|
|
|
+ [ v0Index, v3Index, v2Index ],
|
|
|
|
+ ];
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- if (this.model) { this.el.removeObject3D('mesh'); }
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
|
|
-function createModel (geometry) {
|
|
|
|
- return new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({
|
|
|
|
- color: 0xFFFFFF,
|
|
|
|
- shading: THREE.FlatShading,
|
|
|
|
- vertexColors: THREE.VertexColors,
|
|
|
|
- shininess: 0
|
|
|
|
- }));
|
|
|
|
-}
|
|
|
|
|
|
|
|
-},{"../../lib/PLYLoader":6}],97:[function(require,module,exports){
|
|
|
|
-var DEFAULT_ANIMATION = '__auto__';
|
|
|
|
|
|
+ subaA.subVectors( v1, v0 ).normalize();
|
|
|
|
+ subaB.subVectors( v2, v0 ).normalize();
|
|
|
|
+ subC.subVectors ( v3, v0 ).normalize();
|
|
|
|
+ var sign = subC.dot( new THREE.Vector3().crossVectors( subaB, subaA ));
|
|
|
|
|
|
-/**
|
|
|
|
- * three-model
|
|
|
|
- *
|
|
|
|
- * Loader for THREE.js JSON format. Somewhat confusingly, there are two
|
|
|
|
- * different THREE.js formats, both having the .json extension. This loader
|
|
|
|
- * supports both, but requires you to specify the mode as "object" or "json".
|
|
|
|
- *
|
|
|
|
- * Typically, you will use "json" for a single mesh, and "object" for a scene
|
|
|
|
- * or multiple meshes. Check the console for errors, if in doubt.
|
|
|
|
- *
|
|
|
|
- * See: https://clara.io/learn/user-guide/data_exchange/threejs_export
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- deprecated: true,
|
|
|
|
|
|
|
|
- schema: {
|
|
|
|
- src: { type: 'asset' },
|
|
|
|
- loader: { default: 'object', oneOf: ['object', 'json'] },
|
|
|
|
- enableAnimation: { default: true },
|
|
|
|
- animation: { default: DEFAULT_ANIMATION },
|
|
|
|
- animationDuration: { default: 0 },
|
|
|
|
- crossorigin: { default: '' }
|
|
|
|
- },
|
|
|
|
|
|
+ // Reverse the winding if negative sign
|
|
|
|
+ if( sign < 0 ){
|
|
|
|
+ tetrahedron[0].reverse();
|
|
|
|
+ tetrahedron[1].reverse();
|
|
|
|
+ tetrahedron[2].reverse();
|
|
|
|
+ tetrahedron[3].reverse();
|
|
|
|
+ }
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.model = null;
|
|
|
|
- this.mixer = null;
|
|
|
|
- console.warn('[three-model] Component is deprecated. Use json-model or object-model instead.');
|
|
|
|
- },
|
|
|
|
|
|
|
|
- update: function (previousData) {
|
|
|
|
- previousData = previousData || {};
|
|
|
|
|
|
+ //One for each face of the pyramid
|
|
|
|
+ var pointsCloned = points.slice();
|
|
|
|
+ pointsCloned.splice( pointsCloned.indexOf( v0 ), 1 );
|
|
|
|
+ pointsCloned.splice( pointsCloned.indexOf( v1 ), 1 );
|
|
|
|
+ pointsCloned.splice( pointsCloned.indexOf( v2 ), 1 );
|
|
|
|
+ pointsCloned.splice( pointsCloned.indexOf( v3 ), 1 );
|
|
|
|
|
|
- var loader,
|
|
|
|
- data = this.data;
|
|
|
|
|
|
|
|
- if (!data.src) {
|
|
|
|
- this.remove();
|
|
|
|
- return;
|
|
|
|
|
|
+ var i = tetrahedron.length;
|
|
|
|
+ while( i-- > 0 ){
|
|
|
|
+ assignPoints( tetrahedron[i], pointsCloned, points );
|
|
|
|
+ if( tetrahedron[i].visiblePoints !== undefined ){
|
|
|
|
+ faceStack.push( tetrahedron[i] );
|
|
|
|
+ }
|
|
|
|
+ faces.push( tetrahedron[i] );
|
|
}
|
|
}
|
|
|
|
|
|
- // First load.
|
|
|
|
- if (!Object.keys(previousData).length) {
|
|
|
|
- this.remove();
|
|
|
|
- if (data.loader === 'object') {
|
|
|
|
- loader = new THREE.ObjectLoader();
|
|
|
|
- if (data.crossorigin) loader.setCrossOrigin(data.crossorigin);
|
|
|
|
- loader.load(data.src, function(loaded) {
|
|
|
|
- loaded.traverse( function(object) {
|
|
|
|
- if (object instanceof THREE.SkinnedMesh)
|
|
|
|
- loaded = object;
|
|
|
|
- });
|
|
|
|
- if(loaded.material)
|
|
|
|
- loaded.material.skinning = !!((loaded.geometry && loaded.geometry.bones) || []).length;
|
|
|
|
- this.load(loaded);
|
|
|
|
- }.bind(this));
|
|
|
|
- } else if (data.loader === 'json') {
|
|
|
|
- loader = new THREE.JSONLoader();
|
|
|
|
- if (data.crossorigin) loader.crossOrigin = data.crossorigin;
|
|
|
|
- loader.load(data.src, function (geometry, materials) {
|
|
|
|
-
|
|
|
|
- // Attempt to automatically detect common material options.
|
|
|
|
- materials.forEach(function (mat) {
|
|
|
|
- mat.vertexColors = (geometry.faces[0] || {}).color ? THREE.FaceColors : THREE.NoColors;
|
|
|
|
- mat.skinning = !!(geometry.bones || []).length;
|
|
|
|
- mat.morphTargets = !!(geometry.morphTargets || []).length;
|
|
|
|
- mat.morphNormals = !!(geometry.morphNormals || []).length;
|
|
|
|
- });
|
|
|
|
|
|
+ process( points );
|
|
|
|
|
|
- var mesh = (geometry.bones || []).length
|
|
|
|
- ? new THREE.SkinnedMesh(geometry, new THREE.MultiMaterial(materials))
|
|
|
|
- : new THREE.Mesh(geometry, new THREE.MultiMaterial(materials));
|
|
|
|
|
|
|
|
- this.load(mesh);
|
|
|
|
- }.bind(this));
|
|
|
|
- } else {
|
|
|
|
- throw new Error('[three-model] Invalid mode "%s".', data.mode);
|
|
|
|
- }
|
|
|
|
- return;
|
|
|
|
|
|
+ // Assign to our geometry object
|
|
|
|
+
|
|
|
|
+ var ll = faces.length;
|
|
|
|
+ while( ll-- > 0 ){
|
|
|
|
+ geometry.faces[ll] = new THREE.Face3( faces[ll][2], faces[ll][1], faces[ll][0], faces[ll].normal )
|
|
}
|
|
}
|
|
|
|
|
|
- var activeAction = this.model && this.model.activeAction;
|
|
|
|
|
|
+ geometry.normalsNeedUpdate = true;
|
|
|
|
|
|
- if (data.animation !== previousData.animation) {
|
|
|
|
- if (activeAction) activeAction.stop();
|
|
|
|
- this.playAnimation();
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ return geometry;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}())
|
|
|
|
+
|
|
|
|
+},{}],86:[function(require,module,exports){
|
|
|
|
+var EPS = 0.1;
|
|
|
|
+
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ enabled: {default: true},
|
|
|
|
+ mode: {default: 'teleport', oneOf: ['teleport', 'animate']},
|
|
|
|
+ animateSpeed: {default: 3.0}
|
|
|
|
+ },
|
|
|
|
|
|
- if (activeAction && data.enableAnimation !== activeAction.isRunning()) {
|
|
|
|
- data.enableAnimation ? this.playAnimation() : activeAction.stop();
|
|
|
|
- }
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.active = true;
|
|
|
|
+ this.checkpoint = null;
|
|
|
|
|
|
- if (activeAction && data.animationDuration) {
|
|
|
|
- activeAction.setDuration(data.animationDuration);
|
|
|
|
- }
|
|
|
|
|
|
+ this.offset = new THREE.Vector3();
|
|
|
|
+ this.position = new THREE.Vector3();
|
|
|
|
+ this.targetPosition = new THREE.Vector3();
|
|
},
|
|
},
|
|
|
|
|
|
- load: function (model) {
|
|
|
|
- this.model = model;
|
|
|
|
- this.mixer = new THREE.AnimationMixer(this.model);
|
|
|
|
- this.el.setObject3D('mesh', model);
|
|
|
|
- this.el.emit('model-loaded', {format: 'three', model: model});
|
|
|
|
|
|
+ play: function () { this.active = true; },
|
|
|
|
+ pause: function () { this.active = false; },
|
|
|
|
|
|
- if (this.data.enableAnimation) this.playAnimation();
|
|
|
|
- },
|
|
|
|
|
|
+ setCheckpoint: function (checkpoint) {
|
|
|
|
+ var el = this.el;
|
|
|
|
|
|
- playAnimation: function () {
|
|
|
|
- var clip,
|
|
|
|
- data = this.data,
|
|
|
|
- animations = this.model.animations || this.model.geometry.animations || [];
|
|
|
|
|
|
+ if (!this.active) return;
|
|
|
|
+ if (this.checkpoint === checkpoint) return;
|
|
|
|
|
|
- if (!data.enableAnimation || !data.animation || !animations.length) {
|
|
|
|
- return;
|
|
|
|
|
|
+ if (this.checkpoint) {
|
|
|
|
+ el.emit('navigation-end', {checkpoint: this.checkpoint});
|
|
}
|
|
}
|
|
|
|
|
|
- clip = data.animation === DEFAULT_ANIMATION
|
|
|
|
- ? animations[0]
|
|
|
|
- : THREE.AnimationClip.findByName(animations, data.animation);
|
|
|
|
|
|
+ this.checkpoint = checkpoint;
|
|
|
|
+ this.sync();
|
|
|
|
|
|
- if (!clip) {
|
|
|
|
- console.error('[three-model] Animation "%s" not found.', data.animation);
|
|
|
|
|
|
+ // Ignore new checkpoint if we're already there.
|
|
|
|
+ if (this.position.distanceTo(this.targetPosition) < EPS) {
|
|
|
|
+ this.checkpoint = null;
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- this.model.activeAction = this.mixer.clipAction(clip, this.model);
|
|
|
|
- if (data.animationDuration) {
|
|
|
|
- this.model.activeAction.setDuration(data.animationDuration);
|
|
|
|
- }
|
|
|
|
- this.model.activeAction.play();
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- remove: function () {
|
|
|
|
- if (this.mixer) this.mixer.stopAllAction();
|
|
|
|
- if (this.model) this.el.removeObject3D('mesh');
|
|
|
|
- },
|
|
|
|
|
|
+ el.emit('navigation-start', {checkpoint: checkpoint});
|
|
|
|
|
|
- tick: function (t, dt) {
|
|
|
|
- if (this.mixer && !isNaN(dt)) {
|
|
|
|
- this.mixer.update(dt / 1000);
|
|
|
|
|
|
+ if (this.data.mode === 'teleport') {
|
|
|
|
+ this.el.setAttribute('position', this.targetPosition);
|
|
|
|
+ this.checkpoint = null;
|
|
|
|
+ el.emit('navigation-end', {checkpoint: checkpoint});
|
|
}
|
|
}
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-},{}],98:[function(require,module,exports){
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- offset: {default: {x: 0, y: 0, z: 0}, type: 'vec3'}
|
|
|
|
},
|
|
},
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.active = false;
|
|
|
|
- this.targetEl = null;
|
|
|
|
- this.fire = this.fire.bind(this);
|
|
|
|
- this.offset = new THREE.Vector3();
|
|
|
|
|
|
+ isVelocityActive: function () {
|
|
|
|
+ return !!(this.active && this.checkpoint);
|
|
},
|
|
},
|
|
|
|
|
|
- update: function () {
|
|
|
|
- this.offset.copy(this.data.offset);
|
|
|
|
- },
|
|
|
|
|
|
+ getVelocity: function () {
|
|
|
|
+ if (!this.active) return;
|
|
|
|
|
|
- play: function () { this.el.addEventListener('click', this.fire); },
|
|
|
|
- pause: function () { this.el.removeEventListener('click', this.fire); },
|
|
|
|
- remove: function () { this.pause(); },
|
|
|
|
|
|
+ var data = this.data,
|
|
|
|
+ offset = this.offset,
|
|
|
|
+ position = this.position,
|
|
|
|
+ targetPosition = this.targetPosition,
|
|
|
|
+ checkpoint = this.checkpoint;
|
|
|
|
|
|
- fire: function () {
|
|
|
|
- var targetEl = this.el.sceneEl.querySelector('[checkpoint-controls]');
|
|
|
|
- if (!targetEl) {
|
|
|
|
- throw new Error('No `checkpoint-controls` component found.');
|
|
|
|
|
|
+ this.sync();
|
|
|
|
+ if (position.distanceTo(targetPosition) < EPS) {
|
|
|
|
+ this.checkpoint = null;
|
|
|
|
+ this.el.emit('navigation-end', {checkpoint: checkpoint});
|
|
|
|
+ return offset.set(0, 0, 0);
|
|
}
|
|
}
|
|
- targetEl.components['checkpoint-controls'].setCheckpoint(this.el);
|
|
|
|
|
|
+ offset.setLength(data.animateSpeed);
|
|
|
|
+ return offset;
|
|
},
|
|
},
|
|
|
|
|
|
- getOffset: function () {
|
|
|
|
- return this.offset.copy(this.data.offset);
|
|
|
|
|
|
+ sync: function () {
|
|
|
|
+ var offset = this.offset,
|
|
|
|
+ position = this.position,
|
|
|
|
+ targetPosition = this.targetPosition;
|
|
|
|
+
|
|
|
|
+ position.copy(this.el.getAttribute('position'));
|
|
|
|
+ targetPosition.copy(this.checkpoint.object3D.getWorldPosition());
|
|
|
|
+ targetPosition.add(this.checkpoint.components.checkpoint.getOffset());
|
|
|
|
+ offset.copy(targetPosition).sub(position);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-},{}],99:[function(require,module,exports){
|
|
|
|
|
|
+},{}],87:[function(require,module,exports){
|
|
/**
|
|
/**
|
|
- * Specifies an envMap on an entity, without replacing any existing material
|
|
|
|
- * properties.
|
|
|
|
|
|
+ * Gamepad controls for A-Frame.
|
|
|
|
+ *
|
|
|
|
+ * Stripped-down version of: https://github.com/donmccurdy/aframe-gamepad-controls
|
|
|
|
+ *
|
|
|
|
+ * For more information about the Gamepad API, see:
|
|
|
|
+ * https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
|
|
*/
|
|
*/
|
|
|
|
+
|
|
|
|
+var GamepadButton = require('../../lib/GamepadButton'),
|
|
|
|
+ GamepadButtonEvent = require('../../lib/GamepadButtonEvent');
|
|
|
|
+
|
|
|
|
+var JOYSTICK_EPS = 0.2;
|
|
|
|
+
|
|
module.exports = {
|
|
module.exports = {
|
|
- schema: {
|
|
|
|
- path: {default: ''},
|
|
|
|
- extension: {default: 'jpg'},
|
|
|
|
- format: {default: 'RGBFormat'},
|
|
|
|
- enableBackground: {default: false}
|
|
|
|
- },
|
|
|
|
|
|
|
|
- init: function () {
|
|
|
|
- var data = this.data;
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Statics
|
|
|
|
+ */
|
|
|
|
|
|
- this.texture = new THREE.CubeTextureLoader().load([
|
|
|
|
- data.path + 'posx.' + data.extension, data.path + 'negx.' + data.extension,
|
|
|
|
- data.path + 'posy.' + data.extension, data.path + 'negy.' + data.extension,
|
|
|
|
- data.path + 'posz.' + data.extension, data.path + 'negz.' + data.extension
|
|
|
|
- ]);
|
|
|
|
- this.texture.format = THREE[data.format];
|
|
|
|
|
|
+ GamepadButton: GamepadButton,
|
|
|
|
|
|
- if (data.enableBackground) {
|
|
|
|
- this.el.sceneEl.object3D.background = this.texture;
|
|
|
|
- }
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Schema
|
|
|
|
+ */
|
|
|
|
|
|
- this.applyEnvMap();
|
|
|
|
- this.el.addEventListener('object3dset', this.applyEnvMap.bind(this));
|
|
|
|
- },
|
|
|
|
|
|
+ schema: {
|
|
|
|
+ // Controller 0-3
|
|
|
|
+ controller: { default: 0, oneOf: [0, 1, 2, 3] },
|
|
|
|
|
|
- applyEnvMap: function () {
|
|
|
|
- var mesh = this.el.getObject3D('mesh');
|
|
|
|
- var envMap = this.texture;
|
|
|
|
|
|
+ // Enable/disable features
|
|
|
|
+ enabled: { default: true },
|
|
|
|
|
|
- if (!mesh) return;
|
|
|
|
|
|
+ // Debugging
|
|
|
|
+ debug: { default: false }
|
|
|
|
+ },
|
|
|
|
|
|
- mesh.traverse(function (node) {
|
|
|
|
- if (node.material && 'envMap' in node.material) {
|
|
|
|
- node.material.envMap = envMap;
|
|
|
|
- node.material.needsUpdate = true;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Core
|
|
|
|
+ */
|
|
|
|
|
|
-},{}],100:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * Based on aframe/examples/showcase/tracked-controls.
|
|
|
|
- *
|
|
|
|
- * Handles events coming from the hand-controls.
|
|
|
|
- * Determines if the entity is grabbed or released.
|
|
|
|
- * Updates its position to move along the controller.
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Called once when component is attached. Generally for initial setup.
|
|
|
|
+ */
|
|
init: function () {
|
|
init: function () {
|
|
- this.GRABBED_STATE = 'grabbed';
|
|
|
|
-
|
|
|
|
- this.grabbing = false;
|
|
|
|
- this.hitEl = /** @type {AFRAME.Element} */ null;
|
|
|
|
- this.physics = /** @type {AFRAME.System} */ this.el.sceneEl.systems.physics;
|
|
|
|
- this.constraint = /** @type {CANNON.Constraint} */ null;
|
|
|
|
|
|
+ var scene = this.el.sceneEl;
|
|
|
|
+ this.prevTime = window.performance.now();
|
|
|
|
|
|
- // Bind event handlers
|
|
|
|
- this.onHit = this.onHit.bind(this);
|
|
|
|
- this.onGripOpen = this.onGripOpen.bind(this);
|
|
|
|
- this.onGripClose = this.onGripClose.bind(this);
|
|
|
|
- },
|
|
|
|
|
|
+ // Button state
|
|
|
|
+ this.buttons = {};
|
|
|
|
|
|
- play: function () {
|
|
|
|
- var el = this.el;
|
|
|
|
- el.addEventListener('hit', this.onHit);
|
|
|
|
- el.addEventListener('gripdown', this.onGripClose);
|
|
|
|
- el.addEventListener('gripup', this.onGripOpen);
|
|
|
|
- el.addEventListener('trackpaddown', this.onGripClose);
|
|
|
|
- el.addEventListener('trackpadup', this.onGripOpen);
|
|
|
|
- el.addEventListener('triggerdown', this.onGripClose);
|
|
|
|
- el.addEventListener('triggerup', this.onGripOpen);
|
|
|
|
|
|
+ scene.addBehavior(this);
|
|
},
|
|
},
|
|
|
|
|
|
- pause: function () {
|
|
|
|
- var el = this.el;
|
|
|
|
- el.removeEventListener('hit', this.onHit);
|
|
|
|
- el.removeEventListener('gripdown', this.onGripClose);
|
|
|
|
- el.removeEventListener('gripup', this.onGripOpen);
|
|
|
|
- el.removeEventListener('trackpaddown', this.onGripClose);
|
|
|
|
- el.removeEventListener('trackpadup', this.onGripOpen);
|
|
|
|
- el.removeEventListener('triggerdown', this.onGripClose);
|
|
|
|
- el.removeEventListener('triggerup', this.onGripOpen);
|
|
|
|
- },
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Called when component is attached and when component data changes.
|
|
|
|
+ * Generally modifies the entity based on the data.
|
|
|
|
+ */
|
|
|
|
+ update: function () { this.tick(); },
|
|
|
|
|
|
- onGripClose: function (evt) {
|
|
|
|
- this.grabbing = true;
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Called on each iteration of main render loop.
|
|
|
|
+ */
|
|
|
|
+ tick: function () {
|
|
|
|
+ this.updateButtonState();
|
|
},
|
|
},
|
|
|
|
|
|
- onGripOpen: function (evt) {
|
|
|
|
- var hitEl = this.hitEl;
|
|
|
|
- this.grabbing = false;
|
|
|
|
- if (!hitEl) { return; }
|
|
|
|
- hitEl.removeState(this.GRABBED_STATE);
|
|
|
|
- this.hitEl = undefined;
|
|
|
|
- this.physics.world.removeConstraint(this.constraint);
|
|
|
|
- this.constraint = null;
|
|
|
|
- },
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Called when a component is removed (e.g., via removeAttribute).
|
|
|
|
+ * Generally undoes all modifications to the entity.
|
|
|
|
+ */
|
|
|
|
+ remove: function () { },
|
|
|
|
|
|
- onHit: function (evt) {
|
|
|
|
- var hitEl = evt.detail.el;
|
|
|
|
- // If the element is already grabbed (it could be grabbed by another controller).
|
|
|
|
- // If the hand is not grabbing the element does not stick.
|
|
|
|
- // If we're already grabbing something you can't grab again.
|
|
|
|
- if (!hitEl || hitEl.is(this.GRABBED_STATE) || !this.grabbing || this.hitEl) { return; }
|
|
|
|
- hitEl.addState(this.GRABBED_STATE);
|
|
|
|
- this.hitEl = hitEl;
|
|
|
|
- this.constraint = new CANNON.LockConstraint(this.el.body, hitEl.body);
|
|
|
|
- this.physics.world.addConstraint(this.constraint);
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Universal controls - movement
|
|
|
|
+ */
|
|
|
|
|
|
-},{}],101:[function(require,module,exports){
|
|
|
|
-var physics = require('aframe-physics-system');
|
|
|
|
|
|
+ isVelocityActive: function () {
|
|
|
|
+ if (!this.data.enabled || !this.isConnected()) return false;
|
|
|
|
|
|
-module.exports = {
|
|
|
|
- 'checkpoint': require('./checkpoint'),
|
|
|
|
- 'cube-env-map': require('./cube-env-map'),
|
|
|
|
- 'grab': require('./grab'),
|
|
|
|
- 'jump-ability': require('./jump-ability'),
|
|
|
|
- 'kinematic-body': require('./kinematic-body'),
|
|
|
|
- 'mesh-smooth': require('./mesh-smooth'),
|
|
|
|
- 'sphere-collider': require('./sphere-collider'),
|
|
|
|
- 'toggle-velocity': require('./toggle-velocity'),
|
|
|
|
|
|
+ var dpad = this.getDpad(),
|
|
|
|
+ joystick0 = this.getJoystick(0),
|
|
|
|
+ inputX = dpad.x || joystick0.x,
|
|
|
|
+ inputY = dpad.y || joystick0.y;
|
|
|
|
|
|
- registerAll: function (AFRAME) {
|
|
|
|
- if (this._registered) return;
|
|
|
|
|
|
+ return Math.abs(inputX) > JOYSTICK_EPS || Math.abs(inputY) > JOYSTICK_EPS;
|
|
|
|
+ },
|
|
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
|
|
+ getVelocityDelta: function () {
|
|
|
|
+ var dpad = this.getDpad(),
|
|
|
|
+ joystick0 = this.getJoystick(0),
|
|
|
|
+ inputX = dpad.x || joystick0.x,
|
|
|
|
+ inputY = dpad.y || joystick0.y,
|
|
|
|
+ dVelocity = new THREE.Vector3();
|
|
|
|
|
|
- physics.registerAll();
|
|
|
|
- if (!AFRAME.components['checkpoint']) AFRAME.registerComponent('checkpoint', this['checkpoint']);
|
|
|
|
- if (!AFRAME.components['cube-env-map']) AFRAME.registerComponent('cube-env-map', this['cube-env-map']);
|
|
|
|
- if (!AFRAME.components['grab']) AFRAME.registerComponent('grab', this['grab']);
|
|
|
|
- if (!AFRAME.components['jump-ability']) AFRAME.registerComponent('jump-ability', this['jump-ability']);
|
|
|
|
- if (!AFRAME.components['kinematic-body']) AFRAME.registerComponent('kinematic-body', this['kinematic-body']);
|
|
|
|
- if (!AFRAME.components['mesh-smooth']) AFRAME.registerComponent('mesh-smooth', this['mesh-smooth']);
|
|
|
|
- if (!AFRAME.components['sphere-collider']) AFRAME.registerComponent('sphere-collider', this['sphere-collider']);
|
|
|
|
- if (!AFRAME.components['toggle-velocity']) AFRAME.registerComponent('toggle-velocity', this['toggle-velocity']);
|
|
|
|
|
|
+ if (Math.abs(inputX) > JOYSTICK_EPS) {
|
|
|
|
+ dVelocity.x += inputX;
|
|
|
|
+ }
|
|
|
|
+ if (Math.abs(inputY) > JOYSTICK_EPS) {
|
|
|
|
+ dVelocity.z += inputY;
|
|
|
|
+ }
|
|
|
|
|
|
- this._registered = true;
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ return dVelocity;
|
|
|
|
+ },
|
|
|
|
|
|
-},{"./checkpoint":98,"./cube-env-map":99,"./grab":100,"./jump-ability":102,"./kinematic-body":103,"./mesh-smooth":104,"./sphere-collider":105,"./toggle-velocity":106,"aframe-physics-system":11}],102:[function(require,module,exports){
|
|
|
|
-var ACCEL_G = -9.8, // m/s^2
|
|
|
|
- EASING = -15; // m/s^2
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Universal controls - rotation
|
|
|
|
+ */
|
|
|
|
|
|
-/**
|
|
|
|
- * Jump ability.
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- dependencies: ['velocity'],
|
|
|
|
|
|
+ isRotationActive: function () {
|
|
|
|
+ if (!this.data.enabled || !this.isConnected()) return false;
|
|
|
|
|
|
- /* Schema
|
|
|
|
- ——————————————————————————————————————————————*/
|
|
|
|
|
|
+ var joystick1 = this.getJoystick(1);
|
|
|
|
|
|
- schema: {
|
|
|
|
- on: { default: 'keydown:Space gamepadbuttondown:0' },
|
|
|
|
- playerHeight: { default: 1.764 },
|
|
|
|
- maxJumps: { default: 1 },
|
|
|
|
- distance: { default: 5 },
|
|
|
|
- soundJump: { default: '' },
|
|
|
|
- soundLand: { default: '' },
|
|
|
|
- debug: { default: false }
|
|
|
|
|
|
+ return Math.abs(joystick1.x) > JOYSTICK_EPS || Math.abs(joystick1.y) > JOYSTICK_EPS;
|
|
},
|
|
},
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.velocity = 0;
|
|
|
|
- this.numJumps = 0;
|
|
|
|
-
|
|
|
|
- var beginJump = this.beginJump.bind(this),
|
|
|
|
- events = this.data.on.split(' ');
|
|
|
|
- this.bindings = {};
|
|
|
|
- for (var i = 0; i < events.length; i++) {
|
|
|
|
- this.bindings[events[i]] = beginJump;
|
|
|
|
- this.el.addEventListener(events[i], beginJump);
|
|
|
|
- }
|
|
|
|
- this.bindings.collide = this.onCollide.bind(this);
|
|
|
|
- this.el.addEventListener('collide', this.bindings.collide);
|
|
|
|
|
|
+ getRotationDelta: function () {
|
|
|
|
+ var lookVector = this.getJoystick(1);
|
|
|
|
+ if (Math.abs(lookVector.x) <= JOYSTICK_EPS) lookVector.x = 0;
|
|
|
|
+ if (Math.abs(lookVector.y) <= JOYSTICK_EPS) lookVector.y = 0;
|
|
|
|
+ return lookVector;
|
|
},
|
|
},
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- for (var event in this.bindings) {
|
|
|
|
- if (this.bindings.hasOwnProperty(event)) {
|
|
|
|
- this.el.removeEventListener(event, this.bindings[event]);
|
|
|
|
- delete this.bindings[event];
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Button events
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ updateButtonState: function () {
|
|
|
|
+ var gamepad = this.getGamepad();
|
|
|
|
+ if (this.data.enabled && gamepad) {
|
|
|
|
+
|
|
|
|
+ // Fire DOM events for button state changes.
|
|
|
|
+ for (var i = 0; i < gamepad.buttons.length; i++) {
|
|
|
|
+ if (gamepad.buttons[i].pressed && !this.buttons[i]) {
|
|
|
|
+ this.emit(new GamepadButtonEvent('gamepadbuttondown', i, gamepad.buttons[i]));
|
|
|
|
+ } else if (!gamepad.buttons[i].pressed && this.buttons[i]) {
|
|
|
|
+ this.emit(new GamepadButtonEvent('gamepadbuttonup', i, gamepad.buttons[i]));
|
|
|
|
+ }
|
|
|
|
+ this.buttons[i] = gamepad.buttons[i].pressed;
|
|
}
|
|
}
|
|
- }
|
|
|
|
- this.el.removeEventListener('collide', this.bindings.collide);
|
|
|
|
- delete this.bindings.collide;
|
|
|
|
- },
|
|
|
|
|
|
|
|
- beginJump: function () {
|
|
|
|
- if (this.numJumps < this.data.maxJumps) {
|
|
|
|
- var data = this.data,
|
|
|
|
- initialVelocity = Math.sqrt(-2 * data.distance * (ACCEL_G + EASING)),
|
|
|
|
- v = this.el.getAttribute('velocity');
|
|
|
|
- this.el.setAttribute('velocity', {x: v.x, y: initialVelocity, z: v.z});
|
|
|
|
- this.numJumps++;
|
|
|
|
|
|
+ } else if (Object.keys(this.buttons)) {
|
|
|
|
+ // Reset state if controls are disabled or controller is lost.
|
|
|
|
+ this.buttons = {};
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
|
|
- onCollide: function () {
|
|
|
|
- this.numJumps = 0;
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-},{}],103:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * Kinematic body.
|
|
|
|
- *
|
|
|
|
- * Managed dynamic body, which moves but is not affected (directly) by the
|
|
|
|
- * physics engine. This is not a true kinematic body, in the sense that we are
|
|
|
|
- * letting the physics engine _compute_ collisions against it and selectively
|
|
|
|
- * applying those collisions to the object. The physics engine does not decide
|
|
|
|
- * the position/velocity/rotation of the element.
|
|
|
|
- *
|
|
|
|
- * Used for the camera object, because full physics simulation would create
|
|
|
|
- * movement that feels unnatural to the player. Bipedal movement does not
|
|
|
|
- * translate nicely to rigid body physics.
|
|
|
|
- *
|
|
|
|
- * See: http://www.learn-cocos2d.com/2013/08/physics-engine-platformer-terrible-idea/
|
|
|
|
- * And: http://oxleygamedev.blogspot.com/2011/04/player-physics-part-2.html
|
|
|
|
- */
|
|
|
|
-var CANNON = window.CANNON;
|
|
|
|
-var EPS = 0.000001;
|
|
|
|
|
|
+ emit: function (event) {
|
|
|
|
+ // Emit original event.
|
|
|
|
+ this.el.emit(event.type, event);
|
|
|
|
|
|
-module.exports = {
|
|
|
|
- dependencies: ['velocity'],
|
|
|
|
|
|
+ // Emit convenience event, identifying button index.
|
|
|
|
+ this.el.emit(
|
|
|
|
+ event.type + ':' + event.index,
|
|
|
|
+ new GamepadButtonEvent(event.type, event.index, event)
|
|
|
|
+ );
|
|
|
|
+ },
|
|
|
|
|
|
/*******************************************************************
|
|
/*******************************************************************
|
|
- * Schema
|
|
|
|
|
|
+ * Gamepad state
|
|
*/
|
|
*/
|
|
|
|
|
|
- schema: {
|
|
|
|
- mass: { default: 5 },
|
|
|
|
- radius: { default: 1.3 },
|
|
|
|
- height: { default: 1.764 },
|
|
|
|
- linearDamping: { default: 0.05 },
|
|
|
|
- enableSlopes: { default: true }
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns the Gamepad instance attached to the component. If connected,
|
|
|
|
+ * a proxy-controls component may provide access to Gamepad input from a
|
|
|
|
+ * remote device.
|
|
|
|
+ *
|
|
|
|
+ * @return {Gamepad}
|
|
|
|
+ */
|
|
|
|
+ getGamepad: function () {
|
|
|
|
+ var localGamepad = navigator.getGamepads
|
|
|
|
+ && navigator.getGamepads()[this.data.controller],
|
|
|
|
+ proxyControls = this.el.sceneEl.components['proxy-controls'],
|
|
|
|
+ proxyGamepad = proxyControls && proxyControls.isConnected()
|
|
|
|
+ && proxyControls.getGamepad(this.data.controller);
|
|
|
|
+ return proxyGamepad || localGamepad;
|
|
},
|
|
},
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Lifecycle
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns the state of the given button.
|
|
|
|
+ * @param {number} index The button (0-N) for which to find state.
|
|
|
|
+ * @return {GamepadButton}
|
|
*/
|
|
*/
|
|
|
|
+ getButton: function (index) {
|
|
|
|
+ return this.getGamepad().buttons[index];
|
|
|
|
+ },
|
|
|
|
|
|
- init: function () {
|
|
|
|
- this.system = this.el.sceneEl.systems.physics;
|
|
|
|
- this.system.addBehavior(this, this.system.Phase.SIMULATE);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns state of the given axis. Axes are labelled 0-N, where 0-1 will
|
|
|
|
+ * represent X/Y on the first joystick, and 2-3 X/Y on the second.
|
|
|
|
+ * @param {number} index The axis (0-N) for which to find state.
|
|
|
|
+ * @return {number} On the interval [-1,1].
|
|
|
|
+ */
|
|
|
|
+ getAxis: function (index) {
|
|
|
|
+ return this.getGamepad().axes[index];
|
|
|
|
+ },
|
|
|
|
|
|
- var el = this.el,
|
|
|
|
- data = this.data,
|
|
|
|
- position = (new CANNON.Vec3()).copy(el.getAttribute('position'));
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns the state of the given joystick (0 or 1) as a THREE.Vector2.
|
|
|
|
+ * @param {number} id The joystick (0, 1) for which to find state.
|
|
|
|
+ * @return {THREE.Vector2}
|
|
|
|
+ */
|
|
|
|
+ getJoystick: function (index) {
|
|
|
|
+ var gamepad = this.getGamepad();
|
|
|
|
+ switch (index) {
|
|
|
|
+ case 0: return new THREE.Vector2(gamepad.axes[0], gamepad.axes[1]);
|
|
|
|
+ case 1: return new THREE.Vector2(gamepad.axes[2], gamepad.axes[3]);
|
|
|
|
+ default: throw new Error('Unexpected joystick index "%d".', index);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- this.body = new CANNON.Body({
|
|
|
|
- material: this.system.material,
|
|
|
|
- position: position,
|
|
|
|
- mass: data.mass,
|
|
|
|
- linearDamping: data.linearDamping,
|
|
|
|
- fixedRotation: true
|
|
|
|
- });
|
|
|
|
- this.body.addShape(
|
|
|
|
- new CANNON.Sphere(data.radius),
|
|
|
|
- new CANNON.Vec3(0, data.radius - data.height, 0)
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns the state of the dpad as a THREE.Vector2.
|
|
|
|
+ * @return {THREE.Vector2}
|
|
|
|
+ */
|
|
|
|
+ getDpad: function () {
|
|
|
|
+ var gamepad = this.getGamepad();
|
|
|
|
+ if (!gamepad.buttons[GamepadButton.DPAD_RIGHT]) {
|
|
|
|
+ return new THREE.Vector2();
|
|
|
|
+ }
|
|
|
|
+ return new THREE.Vector2(
|
|
|
|
+ (gamepad.buttons[GamepadButton.DPAD_RIGHT].pressed ? 1 : 0)
|
|
|
|
+ + (gamepad.buttons[GamepadButton.DPAD_LEFT].pressed ? -1 : 0),
|
|
|
|
+ (gamepad.buttons[GamepadButton.DPAD_UP].pressed ? -1 : 0)
|
|
|
|
+ + (gamepad.buttons[GamepadButton.DPAD_DOWN].pressed ? 1 : 0)
|
|
);
|
|
);
|
|
|
|
+ },
|
|
|
|
|
|
- this.body.el = this.el;
|
|
|
|
- this.el.body = this.body;
|
|
|
|
- this.system.addBody(this.body);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns true if the gamepad is currently connected to the system.
|
|
|
|
+ * @return {boolean}
|
|
|
|
+ */
|
|
|
|
+ isConnected: function () {
|
|
|
|
+ var gamepad = this.getGamepad();
|
|
|
|
+ return !!(gamepad && gamepad.connected);
|
|
},
|
|
},
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- this.system.removeBody(this.body);
|
|
|
|
- this.system.removeBehavior(this, this.system.Phase.SIMULATE);
|
|
|
|
- delete this.el.body;
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns a string containing some information about the controller. Result
|
|
|
|
+ * may vary across browsers, for a given controller.
|
|
|
|
+ * @return {string}
|
|
|
|
+ */
|
|
|
|
+ getID: function () {
|
|
|
|
+ return this.getGamepad().id;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+},{"../../lib/GamepadButton":4,"../../lib/GamepadButtonEvent":5}],88:[function(require,module,exports){
|
|
|
|
+var radToDeg = THREE.Math.radToDeg,
|
|
|
|
+ isMobile = AFRAME.utils.device.isMobile();
|
|
|
|
+
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ enabled: {default: true},
|
|
|
|
+ standing: {default: true}
|
|
},
|
|
},
|
|
|
|
|
|
- /*******************************************************************
|
|
|
|
- * Tick
|
|
|
|
- */
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.isPositionCalibrated = false;
|
|
|
|
+ this.dolly = new THREE.Object3D();
|
|
|
|
+ this.hmdEuler = new THREE.Euler();
|
|
|
|
+ this.previousHMDPosition = new THREE.Vector3();
|
|
|
|
+ this.deltaHMDPosition = new THREE.Vector3();
|
|
|
|
+ this.vrControls = new THREE.VRControls(this.dolly);
|
|
|
|
+ this.rotation = new THREE.Vector3();
|
|
|
|
+ },
|
|
|
|
|
|
- /**
|
|
|
|
- * Checks CANNON.World for collisions and attempts to apply them to the
|
|
|
|
- * element automatically, in a player-friendly way.
|
|
|
|
- *
|
|
|
|
- * There's extra logic for horizontal surfaces here. The basic requirements:
|
|
|
|
- * (1) Only apply gravity when not in contact with _any_ horizontal surface.
|
|
|
|
- * (2) When moving, project the velocity against exactly one ground surface.
|
|
|
|
- * If in contact with two ground surfaces (e.g. ground + ramp), choose
|
|
|
|
- * the one that collides with current velocity, if any.
|
|
|
|
- */
|
|
|
|
- step: (function () {
|
|
|
|
- var velocity = new THREE.Vector3(),
|
|
|
|
- normalizedVelocity = new THREE.Vector3(),
|
|
|
|
- currentSurfaceNormal = new THREE.Vector3(),
|
|
|
|
- groundNormal = new THREE.Vector3();
|
|
|
|
|
|
+ update: function () {
|
|
|
|
+ var data = this.data;
|
|
|
|
+ var vrControls = this.vrControls;
|
|
|
|
+ vrControls.standing = data.standing;
|
|
|
|
+ vrControls.update();
|
|
|
|
+ },
|
|
|
|
|
|
- return function (t, dt) {
|
|
|
|
- if (!dt) return;
|
|
|
|
|
|
+ tick: function () {
|
|
|
|
+ this.vrControls.update();
|
|
|
|
+ },
|
|
|
|
|
|
- var body = this.body,
|
|
|
|
- data = this.data,
|
|
|
|
- didCollide = false,
|
|
|
|
- height, groundHeight = -Infinity,
|
|
|
|
- groundBody;
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.vrControls.dispose();
|
|
|
|
+ },
|
|
|
|
|
|
- dt = Math.min(dt, this.system.data.maxInterval * 1000);
|
|
|
|
|
|
+ isRotationActive: function () {
|
|
|
|
+ var hmdEuler = this.hmdEuler;
|
|
|
|
+ if (!this.data.enabled || !(this.el.sceneEl.is('vr-mode') || isMobile)) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ hmdEuler.setFromQuaternion(this.dolly.quaternion, 'YXZ');
|
|
|
|
+ return !isNullVector(hmdEuler);
|
|
|
|
+ },
|
|
|
|
|
|
- groundNormal.set(0, 0, 0);
|
|
|
|
- velocity.copy(this.el.getAttribute('velocity'));
|
|
|
|
- body.velocity.copy(velocity);
|
|
|
|
- body.position.copy(this.el.getAttribute('position'));
|
|
|
|
|
|
+ getRotation: function () {
|
|
|
|
+ var hmdEuler = this.hmdEuler;
|
|
|
|
+ return this.rotation.set(
|
|
|
|
+ radToDeg(hmdEuler.x),
|
|
|
|
+ radToDeg(hmdEuler.y),
|
|
|
|
+ radToDeg(hmdEuler.z)
|
|
|
|
+ );
|
|
|
|
+ },
|
|
|
|
|
|
- for (var i = 0, contact; (contact = this.system.world.contacts[i]); i++) {
|
|
|
|
- // 1. Find any collisions involving this element. Get the contact
|
|
|
|
- // normal, and make sure it's oriented _out_ of the other object.
|
|
|
|
- if (body.id === contact.bi.id) {
|
|
|
|
- contact.ni.negate(currentSurfaceNormal);
|
|
|
|
- } else if (body.id === contact.bj.id) {
|
|
|
|
- currentSurfaceNormal.copy(contact.ni);
|
|
|
|
- } else {
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ isVelocityActive: function () {
|
|
|
|
+ var deltaHMDPosition = this.deltaHMDPosition;
|
|
|
|
+ var previousHMDPosition = this.previousHMDPosition;
|
|
|
|
+ var currentHMDPosition = this.calculateHMDPosition();
|
|
|
|
+ this.isPositionCalibrated = this.isPositionCalibrated || !isNullVector(previousHMDPosition);
|
|
|
|
+ if (!this.data.enabled || !this.el.sceneEl.is('vr-mode') || isMobile) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ deltaHMDPosition.copy(currentHMDPosition).sub(previousHMDPosition);
|
|
|
|
+ previousHMDPosition.copy(currentHMDPosition);
|
|
|
|
+ return this.isPositionCalibrated && !isNullVector(deltaHMDPosition);
|
|
|
|
+ },
|
|
|
|
|
|
- didCollide = body.velocity.dot(currentSurfaceNormal) < -EPS;
|
|
|
|
- if (didCollide && currentSurfaceNormal.y <= 0.5) {
|
|
|
|
- // 2. If current trajectory attempts to move _through_ another
|
|
|
|
- // object, project the velocity against the collision plane to
|
|
|
|
- // prevent passing through.
|
|
|
|
- velocity = velocity.projectOnPlane(currentSurfaceNormal);
|
|
|
|
- } else if (currentSurfaceNormal.y > 0.5) {
|
|
|
|
- // 3. If in contact with something roughly horizontal (+/- 45º) then
|
|
|
|
- // consider that the current ground. Only the highest qualifying
|
|
|
|
- // ground is retained.
|
|
|
|
- height = body.id === contact.bi.id
|
|
|
|
- ? Math.abs(contact.rj.y + contact.bj.position.y)
|
|
|
|
- : Math.abs(contact.ri.y + contact.bi.position.y);
|
|
|
|
- if (height > groundHeight) {
|
|
|
|
- groundHeight = height;
|
|
|
|
- groundNormal.copy(currentSurfaceNormal);
|
|
|
|
- groundBody = body.id === contact.bi.id ? contact.bj : contact.bi;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ getPositionDelta: function () {
|
|
|
|
+ return this.deltaHMDPosition;
|
|
|
|
+ },
|
|
|
|
|
|
- normalizedVelocity.copy(velocity).normalize();
|
|
|
|
- if (groundBody && normalizedVelocity.y < 0.5) {
|
|
|
|
- if (!data.enableSlopes) {
|
|
|
|
- groundNormal.set(0, 1, 0);
|
|
|
|
- } else if (groundNormal.y < 1 - EPS) {
|
|
|
|
- groundNormal.copy(this.raycastToGround(groundBody, groundNormal));
|
|
|
|
- }
|
|
|
|
|
|
+ calculateHMDPosition: function () {
|
|
|
|
+ var dolly = this.dolly;
|
|
|
|
+ var position = new THREE.Vector3();
|
|
|
|
+ dolly.updateMatrix();
|
|
|
|
+ position.setFromMatrixPosition(dolly.matrix);
|
|
|
|
+ return position;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- // 4. Project trajectory onto the top-most ground object, unless
|
|
|
|
- // trajectory is > 45º.
|
|
|
|
- velocity = velocity.projectOnPlane(groundNormal);
|
|
|
|
- } else {
|
|
|
|
- // 5. If not in contact with anything horizontal, apply world gravity.
|
|
|
|
- // TODO - Why is the 4x scalar necessary.
|
|
|
|
- velocity.add(this.system.world.gravity.scale(dt * 4.0 / 1000));
|
|
|
|
- }
|
|
|
|
|
|
+function isNullVector (vector) {
|
|
|
|
+ return vector.x === 0 && vector.y === 0 && vector.z === 0;
|
|
|
|
+}
|
|
|
|
|
|
- // 6. If the ground surface has a velocity, apply it directly to current
|
|
|
|
- // position, not velocity, to preserve relative velocity.
|
|
|
|
- if (groundBody && groundBody.el && groundBody.el.components.velocity) {
|
|
|
|
- var groundVelocity = groundBody.el.getAttribute('velocity');
|
|
|
|
- body.position.copy({
|
|
|
|
- x: body.position.x + groundVelocity.x * dt / 1000,
|
|
|
|
- y: body.position.y + groundVelocity.y * dt / 1000,
|
|
|
|
- z: body.position.z + groundVelocity.z * dt / 1000
|
|
|
|
- });
|
|
|
|
- this.el.setAttribute('position', body.position);
|
|
|
|
- }
|
|
|
|
|
|
+},{}],89:[function(require,module,exports){
|
|
|
|
+var physics = require('aframe-physics-system');
|
|
|
|
|
|
- body.velocity.copy(velocity);
|
|
|
|
- this.el.setAttribute('velocity', velocity);
|
|
|
|
- };
|
|
|
|
- }()),
|
|
|
|
|
|
+module.exports = {
|
|
|
|
+ 'checkpoint-controls': require('./checkpoint-controls'),
|
|
|
|
+ 'gamepad-controls': require('./gamepad-controls'),
|
|
|
|
+ 'hmd-controls': require('./hmd-controls'),
|
|
|
|
+ 'keyboard-controls': require('./keyboard-controls'),
|
|
|
|
+ 'mouse-controls': require('./mouse-controls'),
|
|
|
|
+ 'touch-controls': require('./touch-controls'),
|
|
|
|
+ 'universal-controls': require('./universal-controls'),
|
|
|
|
|
|
- /**
|
|
|
|
- * When walking on complex surfaces (trimeshes, borders between two shapes),
|
|
|
|
- * the collision normals returned for the player sphere can be very
|
|
|
|
- * inconsistent. To address this, raycast straight down, find the collision
|
|
|
|
- * normal, and return whichever normal is more vertical.
|
|
|
|
- * @param {CANNON.Body} groundBody
|
|
|
|
- * @param {CANNON.Vec3} groundNormal
|
|
|
|
- * @return {CANNON.Vec3}
|
|
|
|
- */
|
|
|
|
- raycastToGround: function (groundBody, groundNormal) {
|
|
|
|
- var ray,
|
|
|
|
- hitNormal,
|
|
|
|
- vFrom = this.body.position,
|
|
|
|
- vTo = this.body.position.clone();
|
|
|
|
|
|
+ registerAll: function (AFRAME) {
|
|
|
|
+ if (this._registered) return;
|
|
|
|
|
|
- vTo.y -= this.data.height;
|
|
|
|
- ray = new CANNON.Ray(vFrom, vTo);
|
|
|
|
- ray._updateDirection(); // TODO - Report bug.
|
|
|
|
- ray.intersectBody(groundBody);
|
|
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
|
|
- if (!ray.hasHit) return groundNormal;
|
|
|
|
|
|
+ physics.registerAll();
|
|
|
|
+ if (!AFRAME.components['checkpoint-controls']) AFRAME.registerComponent('checkpoint-controls', this['checkpoint-controls']);
|
|
|
|
+ if (!AFRAME.components['gamepad-controls']) AFRAME.registerComponent('gamepad-controls', this['gamepad-controls']);
|
|
|
|
+ if (!AFRAME.components['hmd-controls']) AFRAME.registerComponent('hmd-controls', this['hmd-controls']);
|
|
|
|
+ if (!AFRAME.components['keyboard-controls']) AFRAME.registerComponent('keyboard-controls', this['keyboard-controls']);
|
|
|
|
+ if (!AFRAME.components['mouse-controls']) AFRAME.registerComponent('mouse-controls', this['mouse-controls']);
|
|
|
|
+ if (!AFRAME.components['touch-controls']) AFRAME.registerComponent('touch-controls', this['touch-controls']);
|
|
|
|
+ if (!AFRAME.components['universal-controls']) AFRAME.registerComponent('universal-controls', this['universal-controls']);
|
|
|
|
|
|
- // Compare ABS, in case we're projecting against the inside of the face.
|
|
|
|
- hitNormal = ray.result.hitNormalWorld;
|
|
|
|
- return Math.abs(hitNormal.y) > Math.abs(groundNormal.y) ? hitNormal : groundNormal;
|
|
|
|
|
|
+ this._registered = true;
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-},{}],104:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * Apply this component to models that looks "blocky", to have Three.js compute
|
|
|
|
- * vertex normals on the fly for a "smoother" look.
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- init: function () {
|
|
|
|
- this.el.addEventListener('model-loaded', function (e) {
|
|
|
|
- e.detail.model.traverse(function (node) {
|
|
|
|
- if (node.isMesh) node.geometry.computeVertexNormals();
|
|
|
|
- });
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+},{"./checkpoint-controls":86,"./gamepad-controls":87,"./hmd-controls":88,"./keyboard-controls":90,"./mouse-controls":91,"./touch-controls":92,"./universal-controls":93,"aframe-physics-system":11}],90:[function(require,module,exports){
|
|
|
|
+require('../../lib/keyboard.polyfill');
|
|
|
|
+
|
|
|
|
+var MAX_DELTA = 0.2,
|
|
|
|
+ PROXY_FLAG = '__keyboard-controls-proxy';
|
|
|
|
+
|
|
|
|
+var KeyboardEvent = window.KeyboardEvent;
|
|
|
|
|
|
-},{}],105:[function(require,module,exports){
|
|
|
|
/**
|
|
/**
|
|
- * Based on aframe/examples/showcase/tracked-controls.
|
|
|
|
|
|
+ * Keyboard Controls component.
|
|
*
|
|
*
|
|
- * Implement bounding sphere collision detection for entities with a mesh.
|
|
|
|
- * Sets the specified state on the intersected entities.
|
|
|
|
|
|
+ * Stripped-down version of: https://github.com/donmccurdy/aframe-keyboard-controls
|
|
*
|
|
*
|
|
- * @property {string} objects - Selector of the entities to test for collision.
|
|
|
|
- * @property {string} state - State to set on collided entities.
|
|
|
|
|
|
+ * Bind keyboard events to components, or control your entities with the WASD keys.
|
|
|
|
+ *
|
|
|
|
+ * Why use KeyboardEvent.code? "This is set to a string representing the key that was pressed to
|
|
|
|
+ * generate the KeyboardEvent, without taking the current keyboard layout (e.g., QWERTY vs.
|
|
|
|
+ * Dvorak), locale (e.g., English vs. French), or any modifier keys into account. This is useful
|
|
|
|
+ * when you care about which physical key was pressed, rather thanwhich character it corresponds
|
|
|
|
+ * to. For example, if you’re a writing a game, you might want a certain set of keys to move the
|
|
|
|
+ * player in different directions, and that mapping should ideally be independent of keyboard
|
|
|
|
+ * layout. See: https://developers.google.com/web/updates/2016/04/keyboardevent-keys-codes
|
|
*
|
|
*
|
|
|
|
+ * @namespace wasd-controls
|
|
|
|
+ * keys the entity moves and if you release it will stop. Easing simulates friction.
|
|
|
|
+ * to the entity when pressing the keys.
|
|
|
|
+ * @param {bool} [enabled=true] - To completely enable or disable the controls
|
|
*/
|
|
*/
|
|
module.exports = {
|
|
module.exports = {
|
|
schema: {
|
|
schema: {
|
|
- objects: {default: ''},
|
|
|
|
- state: {default: 'collided'},
|
|
|
|
- radius: {default: 0.05},
|
|
|
|
- watch: {default: true}
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- init: function () {
|
|
|
|
- /** @type {MutationObserver} */
|
|
|
|
- this.observer = null;
|
|
|
|
- /** @type {Array<Element>} Elements to watch for collisions. */
|
|
|
|
- this.els = [];
|
|
|
|
- /** @type {Array<Element>} Elements currently in collision state. */
|
|
|
|
- this.collisions = [];
|
|
|
|
-
|
|
|
|
- this.handleHit = this.handleHit.bind(this);
|
|
|
|
|
|
+ enabled: { default: true },
|
|
|
|
+ debug: { default: false }
|
|
},
|
|
},
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- this.pause();
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.dVelocity = new THREE.Vector3();
|
|
|
|
+ this.localKeys = {};
|
|
|
|
+ this.listeners = {
|
|
|
|
+ keydown: this.onKeyDown.bind(this),
|
|
|
|
+ keyup: this.onKeyUp.bind(this),
|
|
|
|
+ blur: this.onBlur.bind(this)
|
|
|
|
+ };
|
|
|
|
+ this.attachEventListeners();
|
|
},
|
|
},
|
|
|
|
|
|
- play: function () {
|
|
|
|
- var sceneEl = this.el.sceneEl;
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Movement
|
|
|
|
+ */
|
|
|
|
|
|
- if (this.data.watch) {
|
|
|
|
- this.observer = new MutationObserver(this.update.bind(this, null));
|
|
|
|
- this.observer.observe(sceneEl, {childList: true, subtree: true});
|
|
|
|
- }
|
|
|
|
|
|
+ isVelocityActive: function () {
|
|
|
|
+ return this.data.enabled && !!Object.keys(this.getKeys()).length;
|
|
},
|
|
},
|
|
|
|
|
|
- pause: function () {
|
|
|
|
- if (this.observer) {
|
|
|
|
- this.observer.disconnect();
|
|
|
|
- this.observer = null;
|
|
|
|
|
|
+ getVelocityDelta: function () {
|
|
|
|
+ var data = this.data,
|
|
|
|
+ keys = this.getKeys();
|
|
|
|
+
|
|
|
|
+ this.dVelocity.set(0, 0, 0);
|
|
|
|
+ if (data.enabled) {
|
|
|
|
+ if (keys.KeyW || keys.ArrowUp) { this.dVelocity.z -= 1; }
|
|
|
|
+ if (keys.KeyA || keys.ArrowLeft) { this.dVelocity.x -= 1; }
|
|
|
|
+ if (keys.KeyS || keys.ArrowDown) { this.dVelocity.z += 1; }
|
|
|
|
+ if (keys.KeyD || keys.ArrowRight) { this.dVelocity.x += 1; }
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return this.dVelocity.clone();
|
|
},
|
|
},
|
|
|
|
|
|
- /**
|
|
|
|
- * Update list of entities to test for collision.
|
|
|
|
- */
|
|
|
|
- update: function () {
|
|
|
|
- var data = this.data;
|
|
|
|
- var objectEls;
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Events
|
|
|
|
+ */
|
|
|
|
|
|
- // Push entities into list of els to intersect.
|
|
|
|
- if (data.objects) {
|
|
|
|
- objectEls = this.el.sceneEl.querySelectorAll(data.objects);
|
|
|
|
- } else {
|
|
|
|
- // If objects not defined, intersect with everything.
|
|
|
|
- objectEls = this.el.sceneEl.children;
|
|
|
|
- }
|
|
|
|
- // Convert from NodeList to Array
|
|
|
|
- this.els = Array.prototype.slice.call(objectEls);
|
|
|
|
|
|
+ play: function () {
|
|
|
|
+ this.attachEventListeners();
|
|
},
|
|
},
|
|
|
|
|
|
- tick: (function () {
|
|
|
|
- var position = new THREE.Vector3(),
|
|
|
|
- meshPosition = new THREE.Vector3(),
|
|
|
|
- meshScale = new THREE.Vector3(),
|
|
|
|
- colliderScale = new THREE.Vector3(),
|
|
|
|
- distanceMap = new Map();
|
|
|
|
- return function () {
|
|
|
|
- var el = this.el,
|
|
|
|
- data = this.data,
|
|
|
|
- mesh = el.getObject3D('mesh'),
|
|
|
|
- colliderRadius,
|
|
|
|
- collisions = [];
|
|
|
|
-
|
|
|
|
- if (!mesh) { return; }
|
|
|
|
|
|
+ pause: function () {
|
|
|
|
+ this.removeEventListeners();
|
|
|
|
+ },
|
|
|
|
|
|
- distanceMap.clear();
|
|
|
|
- position.copy(el.object3D.getWorldPosition());
|
|
|
|
- el.object3D.getWorldScale(colliderScale);
|
|
|
|
- colliderRadius = data.radius * scaleFactor(colliderScale);
|
|
|
|
- // Update collision list.
|
|
|
|
- this.els.forEach(intersect);
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.pause();
|
|
|
|
+ },
|
|
|
|
|
|
- // Emit events and add collision states, in order of distance.
|
|
|
|
- collisions
|
|
|
|
- .sort(function (a, b) {
|
|
|
|
- return distanceMap.get(a) > distanceMap.get(b) ? 1 : -1;
|
|
|
|
- })
|
|
|
|
- .forEach(this.handleHit);
|
|
|
|
|
|
+ attachEventListeners: function () {
|
|
|
|
+ window.addEventListener('keydown', this.listeners.keydown, false);
|
|
|
|
+ window.addEventListener('keyup', this.listeners.keyup, false);
|
|
|
|
+ window.addEventListener('blur', this.listeners.blur, false);
|
|
|
|
+ },
|
|
|
|
|
|
- // Remove collision state from current element.
|
|
|
|
- if (collisions.length === 0) { el.emit('hit', {el: null}); }
|
|
|
|
|
|
+ removeEventListeners: function () {
|
|
|
|
+ window.removeEventListener('keydown', this.listeners.keydown);
|
|
|
|
+ window.removeEventListener('keyup', this.listeners.keyup);
|
|
|
|
+ window.removeEventListener('blur', this.listeners.blur);
|
|
|
|
+ },
|
|
|
|
|
|
- // Remove collision state from other elements.
|
|
|
|
- this.collisions.filter(function (el) {
|
|
|
|
- return !distanceMap.has(el);
|
|
|
|
- }).forEach(function removeState (el) {
|
|
|
|
- el.removeState(data.state);
|
|
|
|
- });
|
|
|
|
|
|
+ onKeyDown: function (event) {
|
|
|
|
+ if (AFRAME.utils.shouldCaptureKeyEvent(event)) {
|
|
|
|
+ this.localKeys[event.code] = true;
|
|
|
|
+ this.emit(event);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- // Store new collisions
|
|
|
|
- this.collisions = collisions;
|
|
|
|
|
|
+ onKeyUp: function (event) {
|
|
|
|
+ if (AFRAME.utils.shouldCaptureKeyEvent(event)) {
|
|
|
|
+ delete this.localKeys[event.code];
|
|
|
|
+ this.emit(event);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- // Bounding sphere collision detection
|
|
|
|
- function intersect (el) {
|
|
|
|
- var radius, mesh, distance, box, extent, size;
|
|
|
|
|
|
+ onBlur: function () {
|
|
|
|
+ for (var code in this.localKeys) {
|
|
|
|
+ if (this.localKeys.hasOwnProperty(code)) {
|
|
|
|
+ delete this.localKeys[code];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- if (!el.isEntity) { return; }
|
|
|
|
|
|
+ emit: function (event) {
|
|
|
|
+ // TODO - keydown only initially?
|
|
|
|
+ // TODO - where the f is the spacebar
|
|
|
|
|
|
- mesh = el.getObject3D('mesh');
|
|
|
|
|
|
+ // Emit original event.
|
|
|
|
+ if (PROXY_FLAG in event) {
|
|
|
|
+ // TODO - Method never triggered.
|
|
|
|
+ this.el.emit(event.type, event);
|
|
|
|
+ }
|
|
|
|
|
|
- if (!mesh) { return; }
|
|
|
|
|
|
+ // Emit convenience event, identifying key.
|
|
|
|
+ this.el.emit(event.type + ':' + event.code, new KeyboardEvent(event.type, event));
|
|
|
|
+ if (this.data.debug) console.log(event.type + ':' + event.code);
|
|
|
|
+ },
|
|
|
|
|
|
- box = new THREE.Box3().setFromObject(mesh);
|
|
|
|
- size = box.getSize();
|
|
|
|
- extent = Math.max(size.x, size.y, size.z) / 2;
|
|
|
|
- radius = Math.sqrt(2 * extent * extent);
|
|
|
|
- box.getCenter(meshPosition);
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Accessors
|
|
|
|
+ */
|
|
|
|
|
|
- if (!radius) { return; }
|
|
|
|
|
|
+ isPressed: function (code) {
|
|
|
|
+ return code in this.getKeys();
|
|
|
|
+ },
|
|
|
|
|
|
- distance = position.distanceTo(meshPosition);
|
|
|
|
- if (distance < radius + colliderRadius) {
|
|
|
|
- collisions.push(el);
|
|
|
|
- distanceMap.set(el, distance);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- // use max of scale factors to maintain bounding sphere collision
|
|
|
|
- function scaleFactor (scaleVec) {
|
|
|
|
- return Math.max.apply(null, scaleVec.toArray());
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
- })(),
|
|
|
|
|
|
+ getKeys: function () {
|
|
|
|
+ if (this.isProxied()) {
|
|
|
|
+ return this.el.sceneEl.components['proxy-controls'].getKeyboard();
|
|
|
|
+ }
|
|
|
|
+ return this.localKeys;
|
|
|
|
+ },
|
|
|
|
|
|
- handleHit: function (targetEl) {
|
|
|
|
- targetEl.emit('hit');
|
|
|
|
- targetEl.addState(this.data.state);
|
|
|
|
- this.el.emit('hit', {el: targetEl});
|
|
|
|
|
|
+ isProxied: function () {
|
|
|
|
+ var proxyControls = this.el.sceneEl.components['proxy-controls'];
|
|
|
|
+ return proxyControls && proxyControls.isConnected();
|
|
}
|
|
}
|
|
|
|
+
|
|
};
|
|
};
|
|
|
|
|
|
-},{}],106:[function(require,module,exports){
|
|
|
|
|
|
+},{"../../lib/keyboard.polyfill":10}],91:[function(require,module,exports){
|
|
|
|
+document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
- * Toggle velocity.
|
|
|
|
|
|
+ * Mouse + Pointerlock controls.
|
|
*
|
|
*
|
|
- * Moves an object back and forth along an axis, within a min/max extent.
|
|
|
|
|
|
+ * Based on: https://github.com/aframevr/aframe/pull/1056
|
|
*/
|
|
*/
|
|
module.exports = {
|
|
module.exports = {
|
|
- dependencies: ['velocity'],
|
|
|
|
schema: {
|
|
schema: {
|
|
- axis: { default: 'x', oneOf: ['x', 'y', 'z'] },
|
|
|
|
- min: { default: 0 },
|
|
|
|
- max: { default: 0 },
|
|
|
|
- speed: { default: 1 }
|
|
|
|
|
|
+ enabled: { default: true },
|
|
|
|
+ pointerlockEnabled: { default: true },
|
|
|
|
+ sensitivity: { default: 1 / 25 }
|
|
},
|
|
},
|
|
- init: function () {
|
|
|
|
- var velocity = {x: 0, y: 0, z: 0};
|
|
|
|
- velocity[this.data.axis] = this.data.speed;
|
|
|
|
- this.el.setAttribute('velocity', velocity);
|
|
|
|
|
|
|
|
- if (this.el.sceneEl.addBehavior) this.el.sceneEl.addBehavior(this);
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.mouseDown = false;
|
|
|
|
+ this.pointerLocked = false;
|
|
|
|
+ this.lookVector = new THREE.Vector2();
|
|
|
|
+ this.bindMethods();
|
|
},
|
|
},
|
|
- remove: function () {},
|
|
|
|
- update: function () { this.tick(); },
|
|
|
|
- tick: function () {
|
|
|
|
- var data = this.data,
|
|
|
|
- velocity = this.el.getAttribute('velocity'),
|
|
|
|
- position = this.el.getAttribute('position');
|
|
|
|
- if (velocity[data.axis] > 0 && position[data.axis] > data.max) {
|
|
|
|
- velocity[data.axis] = -data.speed;
|
|
|
|
- this.el.setAttribute('velocity', velocity);
|
|
|
|
- } else if (velocity[data.axis] < 0 && position[data.axis] < data.min) {
|
|
|
|
- velocity[data.axis] = data.speed;
|
|
|
|
- this.el.setAttribute('velocity', velocity);
|
|
|
|
|
|
+
|
|
|
|
+ update: function (previousData) {
|
|
|
|
+ var data = this.data;
|
|
|
|
+ if (previousData.pointerlockEnabled && !data.pointerlockEnabled && this.pointerLocked) {
|
|
|
|
+ document.exitPointerLock();
|
|
}
|
|
}
|
|
},
|
|
},
|
|
-};
|
|
|
|
|
|
|
|
-},{}],107:[function(require,module,exports){
|
|
|
|
-module.exports = {
|
|
|
|
- 'nav-mesh': require('./nav-mesh'),
|
|
|
|
- 'nav-controller': require('./nav-controller'),
|
|
|
|
- 'system': require('./system'),
|
|
|
|
|
|
+ play: function () {
|
|
|
|
+ this.addEventListeners();
|
|
|
|
+ },
|
|
|
|
|
|
- registerAll: function (AFRAME) {
|
|
|
|
- if (this._registered) return;
|
|
|
|
|
|
+ pause: function () {
|
|
|
|
+ this.removeEventListeners();
|
|
|
|
+ this.lookVector.set(0, 0);
|
|
|
|
+ },
|
|
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.pause();
|
|
|
|
+ },
|
|
|
|
|
|
- if (!AFRAME.components['nav-mesh']) {
|
|
|
|
- AFRAME.registerComponent('nav-mesh', this['nav-mesh']);
|
|
|
|
- }
|
|
|
|
|
|
+ bindMethods: function () {
|
|
|
|
+ this.onMouseDown = this.onMouseDown.bind(this);
|
|
|
|
+ this.onMouseMove = this.onMouseMove.bind(this);
|
|
|
|
+ this.onMouseUp = this.onMouseUp.bind(this);
|
|
|
|
+ this.onMouseUp = this.onMouseUp.bind(this);
|
|
|
|
+ this.onPointerLockChange = this.onPointerLockChange.bind(this);
|
|
|
|
+ this.onPointerLockChange = this.onPointerLockChange.bind(this);
|
|
|
|
+ this.onPointerLockChange = this.onPointerLockChange.bind(this);
|
|
|
|
+ },
|
|
|
|
|
|
- if (!AFRAME.components['nav-controller']) {
|
|
|
|
- AFRAME.registerComponent('nav-controller', this['nav-controller']);
|
|
|
|
- }
|
|
|
|
|
|
+ addEventListeners: function () {
|
|
|
|
+ var sceneEl = this.el.sceneEl;
|
|
|
|
+ var canvasEl = sceneEl.canvas;
|
|
|
|
+ var data = this.data;
|
|
|
|
|
|
- if (!AFRAME.systems.nav) {
|
|
|
|
- AFRAME.registerSystem('nav', this.system);
|
|
|
|
|
|
+ if (!canvasEl) {
|
|
|
|
+ sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
|
|
|
|
- this._registered = true;
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ canvasEl.addEventListener('mousedown', this.onMouseDown, false);
|
|
|
|
+ canvasEl.addEventListener('mousemove', this.onMouseMove, false);
|
|
|
|
+ canvasEl.addEventListener('mouseup', this.onMouseUp, false);
|
|
|
|
+ canvasEl.addEventListener('mouseout', this.onMouseUp, false);
|
|
|
|
|
|
-},{"./nav-controller":108,"./nav-mesh":109,"./system":110}],108:[function(require,module,exports){
|
|
|
|
-module.exports = {
|
|
|
|
- schema: {
|
|
|
|
- destination: {type: 'vec3'},
|
|
|
|
- active: {default: false},
|
|
|
|
- speed: {default: 2}
|
|
|
|
|
|
+ if (data.pointerlockEnabled) {
|
|
|
|
+ document.addEventListener('pointerlockchange', this.onPointerLockChange, false);
|
|
|
|
+ document.addEventListener('mozpointerlockchange', this.onPointerLockChange, false);
|
|
|
|
+ document.addEventListener('pointerlockerror', this.onPointerLockError, false);
|
|
|
|
+ }
|
|
},
|
|
},
|
|
- init: function () {
|
|
|
|
- this.system = this.el.sceneEl.systems.nav;
|
|
|
|
- this.system.addController(this);
|
|
|
|
- this.path = [];
|
|
|
|
- this.raycaster = new THREE.Raycaster();
|
|
|
|
|
|
+
|
|
|
|
+ removeEventListeners: function () {
|
|
|
|
+ var canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
|
|
|
|
+ if (canvasEl) {
|
|
|
|
+ canvasEl.removeEventListener('mousedown', this.onMouseDown, false);
|
|
|
|
+ canvasEl.removeEventListener('mousemove', this.onMouseMove, false);
|
|
|
|
+ canvasEl.removeEventListener('mouseup', this.onMouseUp, false);
|
|
|
|
+ canvasEl.removeEventListener('mouseout', this.onMouseUp, false);
|
|
|
|
+ }
|
|
|
|
+ document.removeEventListener('pointerlockchange', this.onPointerLockChange, false);
|
|
|
|
+ document.removeEventListener('mozpointerlockchange', this.onPointerLockChange, false);
|
|
|
|
+ document.removeEventListener('pointerlockerror', this.onPointerLockError, false);
|
|
},
|
|
},
|
|
- remove: function () {
|
|
|
|
- this.system.removeController(this);
|
|
|
|
|
|
+
|
|
|
|
+ isRotationActive: function () {
|
|
|
|
+ return this.data.enabled && (this.mouseDown || this.pointerLocked);
|
|
},
|
|
},
|
|
- update: function () {
|
|
|
|
- this.path.length = 0;
|
|
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Returns the sum of all mouse movement since last call.
|
|
|
|
+ */
|
|
|
|
+ getRotationDelta: function () {
|
|
|
|
+ var dRotation = this.lookVector.clone().multiplyScalar(this.data.sensitivity);
|
|
|
|
+ this.lookVector.set(0, 0);
|
|
|
|
+ return dRotation;
|
|
},
|
|
},
|
|
- tick: (function () {
|
|
|
|
- var vDest = new THREE.Vector3();
|
|
|
|
- var vDelta = new THREE.Vector3();
|
|
|
|
- var vNext = new THREE.Vector3();
|
|
|
|
|
|
|
|
- return function (t, dt) {
|
|
|
|
- var el = this.el;
|
|
|
|
- var data = this.data;
|
|
|
|
- var raycaster = this.raycaster;
|
|
|
|
- var speed = data.speed * dt / 1000;
|
|
|
|
|
|
+ onMouseMove: function (event) {
|
|
|
|
+ var previousMouseEvent = this.previousMouseEvent;
|
|
|
|
|
|
- if (!data.active) return;
|
|
|
|
|
|
+ if (!this.data.enabled || !(this.mouseDown || this.pointerLocked)) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- // Use PatrolJS pathfinding system to get shortest path to target.
|
|
|
|
- if (!this.path.length) {
|
|
|
|
- this.path = this.system.getPath(this.el.object3D, vDest.copy(data.destination));
|
|
|
|
- this.path = this.path || [];
|
|
|
|
- el.emit('nav-start');
|
|
|
|
- }
|
|
|
|
|
|
+ var movementX = event.movementX || event.mozMovementX || 0;
|
|
|
|
+ var movementY = event.movementY || event.mozMovementY || 0;
|
|
|
|
|
|
- // If no path is found, exit.
|
|
|
|
- if (!this.path.length) {
|
|
|
|
- console.warn('[nav] Unable to find path to %o.', data.destination);
|
|
|
|
- this.el.setAttribute('nav-controller', {active: false});
|
|
|
|
- el.emit('nav-end');
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ if (!this.pointerLocked) {
|
|
|
|
+ movementX = event.screenX - previousMouseEvent.screenX;
|
|
|
|
+ movementY = event.screenY - previousMouseEvent.screenY;
|
|
|
|
+ }
|
|
|
|
|
|
- // Current segment is a vector from current position to next waypoint.
|
|
|
|
- var vCurrent = el.object3D.position;
|
|
|
|
- var vWaypoint = this.path[0];
|
|
|
|
- vDelta.subVectors(vWaypoint, vCurrent);
|
|
|
|
|
|
+ this.lookVector.x += movementX;
|
|
|
|
+ this.lookVector.y += movementY;
|
|
|
|
|
|
- var distance = vDelta.length();
|
|
|
|
- var gazeTarget;
|
|
|
|
|
|
+ this.previousMouseEvent = event;
|
|
|
|
+ },
|
|
|
|
|
|
- if (distance < speed) {
|
|
|
|
- // If <1 step from current waypoint, discard it and move toward next.
|
|
|
|
- this.path.shift();
|
|
|
|
|
|
+ onMouseDown: function (event) {
|
|
|
|
+ var canvasEl = this.el.sceneEl.canvas,
|
|
|
|
+ isEditing = (AFRAME.INSPECTOR || {}).opened;
|
|
|
|
|
|
- // After discarding the last waypoint, exit pathfinding.
|
|
|
|
- if (!this.path.length) {
|
|
|
|
- this.el.setAttribute('nav-controller', {active: false});
|
|
|
|
- el.emit('nav-end');
|
|
|
|
- return;
|
|
|
|
- } else {
|
|
|
|
- gazeTarget = this.path[0];
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- // If still far away from next waypoint, find next position for
|
|
|
|
- // the current frame.
|
|
|
|
- vNext.copy(vDelta.setLength(speed)).add(vCurrent);
|
|
|
|
- gazeTarget = vWaypoint;
|
|
|
|
- }
|
|
|
|
|
|
+ this.mouseDown = true;
|
|
|
|
+ this.previousMouseEvent = event;
|
|
|
|
|
|
- // Look at the next waypoint.
|
|
|
|
- gazeTarget.y = vCurrent.y;
|
|
|
|
- el.object3D.lookAt(gazeTarget);
|
|
|
|
|
|
+ if (this.data.pointerlockEnabled && !this.pointerLocked && !isEditing) {
|
|
|
|
+ if (canvasEl.requestPointerLock) {
|
|
|
|
+ canvasEl.requestPointerLock();
|
|
|
|
+ } else if (canvasEl.mozRequestPointerLock) {
|
|
|
|
+ canvasEl.mozRequestPointerLock();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- // Raycast against the nav mesh, to keep the controller moving along the
|
|
|
|
- // ground, not traveling in a straight line from higher to lower waypoints.
|
|
|
|
- raycaster.ray.origin.copy(vNext);
|
|
|
|
- raycaster.ray.origin.y += 1.5;
|
|
|
|
- raycaster.ray.direction.y = -1;
|
|
|
|
- var intersections = raycaster.intersectObject(this.system.getNavMesh());
|
|
|
|
|
|
+ onMouseUp: function () {
|
|
|
|
+ this.mouseDown = false;
|
|
|
|
+ },
|
|
|
|
|
|
- if (!intersections.length) {
|
|
|
|
- // Raycasting failed. Step toward the waypoint and hope for the best.
|
|
|
|
- vCurrent.copy(vNext);
|
|
|
|
- } else {
|
|
|
|
- // Re-project next position onto nav mesh.
|
|
|
|
- vDelta.subVectors(intersections[0].point, vCurrent);
|
|
|
|
- vCurrent.add(vDelta.setLength(speed));
|
|
|
|
- }
|
|
|
|
|
|
+ onPointerLockChange: function () {
|
|
|
|
+ this.pointerLocked = !!(document.pointerLockElement || document.mozPointerLockElement);
|
|
|
|
+ },
|
|
|
|
|
|
- };
|
|
|
|
- }())
|
|
|
|
|
|
+ onPointerLockError: function () {
|
|
|
|
+ this.pointerLocked = false;
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
-},{}],109:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * nav-mesh
|
|
|
|
- *
|
|
|
|
- * Waits for a mesh to be loaded on the current entity, then sets it as the
|
|
|
|
- * nav mesh in the pathfinding system.
|
|
|
|
- */
|
|
|
|
|
|
+},{}],92:[function(require,module,exports){
|
|
module.exports = {
|
|
module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ enabled: { default: true }
|
|
|
|
+ },
|
|
|
|
+
|
|
init: function () {
|
|
init: function () {
|
|
- this.system = this.el.sceneEl.systems.nav;
|
|
|
|
- this.loadNavMesh();
|
|
|
|
- this.el.addEventListener('model-loaded', this.loadNavMesh.bind(this));
|
|
|
|
|
|
+ this.dVelocity = new THREE.Vector3();
|
|
|
|
+ this.bindMethods();
|
|
},
|
|
},
|
|
|
|
|
|
- loadNavMesh: function () {
|
|
|
|
- var object = this.el.getObject3D('mesh');
|
|
|
|
|
|
+ play: function () {
|
|
|
|
+ this.addEventListeners();
|
|
|
|
+ },
|
|
|
|
|
|
- if (!object) return;
|
|
|
|
|
|
+ pause: function () {
|
|
|
|
+ this.removeEventListeners();
|
|
|
|
+ this.dVelocity.set(0, 0, 0);
|
|
|
|
+ },
|
|
|
|
|
|
- var navMesh;
|
|
|
|
- object.traverse(function (node) {
|
|
|
|
- if (node.isMesh) navMesh = node;
|
|
|
|
- });
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.pause();
|
|
|
|
+ },
|
|
|
|
|
|
- if (!navMesh) return;
|
|
|
|
|
|
+ addEventListeners: function () {
|
|
|
|
+ var sceneEl = this.el.sceneEl;
|
|
|
|
+ var canvasEl = sceneEl.canvas;
|
|
|
|
|
|
- this.system.setNavMesh(navMesh);
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ if (!canvasEl) {
|
|
|
|
+ sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
-},{}],110:[function(require,module,exports){
|
|
|
|
-var Path = require('three-pathfinding');
|
|
|
|
|
|
+ canvasEl.addEventListener('touchstart', this.onTouchStart);
|
|
|
|
+ canvasEl.addEventListener('touchend', this.onTouchEnd);
|
|
|
|
+ },
|
|
|
|
|
|
-/**
|
|
|
|
- * nav
|
|
|
|
- *
|
|
|
|
- * Pathfinding system, using PatrolJS.
|
|
|
|
- */
|
|
|
|
-module.exports = {
|
|
|
|
- init: function () {
|
|
|
|
- this.navMesh = null;
|
|
|
|
- this.nodes = null;
|
|
|
|
- this.controllers = new Set();
|
|
|
|
|
|
+ removeEventListeners: function () {
|
|
|
|
+ var canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
|
|
|
|
+ if (!canvasEl) { return; }
|
|
|
|
+
|
|
|
|
+ canvasEl.removeEventListener('touchstart', this.onTouchStart);
|
|
|
|
+ canvasEl.removeEventListener('touchend', this.onTouchEnd);
|
|
},
|
|
},
|
|
|
|
|
|
- /**
|
|
|
|
- * @param {THREE.Mesh} mesh
|
|
|
|
- */
|
|
|
|
- setNavMesh: function (mesh) {
|
|
|
|
- var geometry = mesh.geometry.isBufferGeometry
|
|
|
|
- ? new THREE.Geometry().fromBufferGeometry(mesh.geometry)
|
|
|
|
- : mesh.geometry;
|
|
|
|
- this.navMesh = new THREE.Mesh(geometry);
|
|
|
|
- this.nodes = Path.buildNodes(this.navMesh.geometry);
|
|
|
|
- Path.setZoneData('level', this.nodes);
|
|
|
|
|
|
+ isVelocityActive: function () {
|
|
|
|
+ return this.data.enabled && this.isMoving;
|
|
},
|
|
},
|
|
|
|
|
|
- /**
|
|
|
|
- * @return {THREE.Mesh}
|
|
|
|
- */
|
|
|
|
- getNavMesh: function () {
|
|
|
|
- return this.navMesh;
|
|
|
|
|
|
+ getVelocityDelta: function () {
|
|
|
|
+ this.dVelocity.z = this.isMoving ? -1 : 0;
|
|
|
|
+ return this.dVelocity.clone();
|
|
},
|
|
},
|
|
|
|
|
|
- /**
|
|
|
|
- * @param {NavController} ctrl
|
|
|
|
- */
|
|
|
|
- addController: function (ctrl) {
|
|
|
|
- this.controllers.add(ctrl);
|
|
|
|
|
|
+ bindMethods: function () {
|
|
|
|
+ this.onTouchStart = this.onTouchStart.bind(this);
|
|
|
|
+ this.onTouchEnd = this.onTouchEnd.bind(this);
|
|
},
|
|
},
|
|
|
|
|
|
- /**
|
|
|
|
- * @param {NavController} ctrl
|
|
|
|
- */
|
|
|
|
- removeController: function (ctrl) {
|
|
|
|
- this.controllers.remove(ctrl);
|
|
|
|
|
|
+ onTouchStart: function (e) {
|
|
|
|
+ this.isMoving = true;
|
|
|
|
+ e.preventDefault();
|
|
},
|
|
},
|
|
|
|
|
|
- /**
|
|
|
|
- * @param {NavController} ctrl
|
|
|
|
- * @param {THREE.Vector3} target
|
|
|
|
- * @return {Array<THREE.Vector3>}
|
|
|
|
- */
|
|
|
|
- getPath: function (ctrl, target) {
|
|
|
|
- var start = ctrl.el.object3D.position;
|
|
|
|
- // TODO(donmccurdy): Current group should be cached.
|
|
|
|
- var group = Path.getGroup('level', start);
|
|
|
|
- return Path.findPath(start, target, 'level', group);
|
|
|
|
|
|
+ onTouchEnd: function (e) {
|
|
|
|
+ this.isMoving = false;
|
|
|
|
+ e.preventDefault();
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-},{"three-pathfinding":119}],111:[function(require,module,exports){
|
|
|
|
|
|
+},{}],93:[function(require,module,exports){
|
|
/**
|
|
/**
|
|
- * Flat grid.
|
|
|
|
|
|
+ * Universal Controls
|
|
*
|
|
*
|
|
- * Defaults to 75x75.
|
|
|
|
|
|
+ * @author Don McCurdy <dm@donmccurdy.com>
|
|
*/
|
|
*/
|
|
-var Primitive = module.exports = {
|
|
|
|
- defaultComponents: {
|
|
|
|
- geometry: {
|
|
|
|
- primitive: 'plane',
|
|
|
|
- width: 75,
|
|
|
|
- height: 75
|
|
|
|
- },
|
|
|
|
- rotation: {x: -90, y: 0, z: 0},
|
|
|
|
- material: {
|
|
|
|
- src: 'url(https://cdn.rawgit.com/donmccurdy/aframe-extras/v1.16.3/assets/grid.png)',
|
|
|
|
- repeat: '75 75'
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- mappings: {
|
|
|
|
- width: 'geometry.width',
|
|
|
|
- height: 'geometry.height',
|
|
|
|
- src: 'material.src'
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
|
|
-module.exports.registerAll = (function () {
|
|
|
|
- var registered = false;
|
|
|
|
- return function (AFRAME) {
|
|
|
|
- if (registered) return;
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
- AFRAME.registerPrimitive('a-grid', Primitive);
|
|
|
|
- registered = true;
|
|
|
|
- };
|
|
|
|
-}());
|
|
|
|
|
|
+var COMPONENT_SUFFIX = '-controls',
|
|
|
|
+ MAX_DELTA = 0.2, // ms
|
|
|
|
+ PI_2 = Math.PI / 2;
|
|
|
|
|
|
-},{}],112:[function(require,module,exports){
|
|
|
|
-var vg = require('../../lib/hex-grid.min.js');
|
|
|
|
-var defaultHexGrid = require('../../lib/default-hex-grid.json');
|
|
|
|
|
|
+module.exports = {
|
|
|
|
|
|
-/**
|
|
|
|
- * Hex grid.
|
|
|
|
- */
|
|
|
|
-var Primitive = module.exports.Primitive = {
|
|
|
|
- defaultComponents: {
|
|
|
|
- 'hexgrid': {}
|
|
|
|
- },
|
|
|
|
- mappings: {
|
|
|
|
- src: 'hexgrid.src'
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Schema
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ dependencies: ['velocity', 'rotation'],
|
|
|
|
|
|
-var Component = module.exports.Component = {
|
|
|
|
- dependencies: ['material'],
|
|
|
|
schema: {
|
|
schema: {
|
|
- src: {type: 'asset'}
|
|
|
|
|
|
+ enabled: { default: true },
|
|
|
|
+ movementEnabled: { default: true },
|
|
|
|
+ movementControls: { default: ['gamepad', 'keyboard', 'touch', 'hmd'] },
|
|
|
|
+ rotationEnabled: { default: true },
|
|
|
|
+ rotationControls: { default: ['hmd', 'gamepad', 'mouse'] },
|
|
|
|
+ movementSpeed: { default: 5 }, // m/s
|
|
|
|
+ movementEasing: { default: 15 }, // m/s2
|
|
|
|
+ movementEasingY: { default: 0 }, // m/s2
|
|
|
|
+ movementAcceleration: { default: 80 }, // m/s2
|
|
|
|
+ rotationSensitivity: { default: 0.05 }, // radians/frame, ish
|
|
|
|
+ fly: { default: false },
|
|
},
|
|
},
|
|
|
|
+
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Lifecycle
|
|
|
|
+ */
|
|
|
|
+
|
|
init: function () {
|
|
init: function () {
|
|
- var data = this.data;
|
|
|
|
- if (data.src) {
|
|
|
|
- fetch(data.src)
|
|
|
|
- .then(function (response) { response.json(); })
|
|
|
|
- .then(function (json) { this.addMesh(json); });
|
|
|
|
|
|
+ var rotation = this.el.getAttribute('rotation');
|
|
|
|
+
|
|
|
|
+ if (this.el.hasAttribute('look-controls') && this.data.rotationEnabled) {
|
|
|
|
+ console.error('[universal-controls] The `universal-controls` component is a replacement '
|
|
|
|
+ + 'for `look-controls`, and cannot be used in combination with it.');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Movement
|
|
|
|
+ this.velocity = new THREE.Vector3();
|
|
|
|
+
|
|
|
|
+ // Rotation
|
|
|
|
+ this.pitch = new THREE.Object3D();
|
|
|
|
+ this.pitch.rotation.x = THREE.Math.degToRad(rotation.x);
|
|
|
|
+ this.yaw = new THREE.Object3D();
|
|
|
|
+ this.yaw.position.y = 10;
|
|
|
|
+ this.yaw.rotation.y = THREE.Math.degToRad(rotation.y);
|
|
|
|
+ this.yaw.add(this.pitch);
|
|
|
|
+ this.heading = new THREE.Euler(0, 0, 0, 'YXZ');
|
|
|
|
+
|
|
|
|
+ if (this.el.sceneEl.hasLoaded) {
|
|
|
|
+ this.injectControls();
|
|
} else {
|
|
} else {
|
|
- this.addMesh(defaultHexGrid);
|
|
|
|
|
|
+ this.el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
|
|
}
|
|
}
|
|
},
|
|
},
|
|
- addMesh: function (json) {
|
|
|
|
- var grid = new vg.HexGrid();
|
|
|
|
- grid.fromJSON(json);
|
|
|
|
- var board = new vg.Board(grid);
|
|
|
|
- board.generateTilemap();
|
|
|
|
- this.el.setObject3D('mesh', board.group);
|
|
|
|
- this.addMaterial();
|
|
|
|
|
|
+
|
|
|
|
+ update: function () {
|
|
|
|
+ if (this.el.sceneEl.hasLoaded) {
|
|
|
|
+ this.injectControls();
|
|
|
|
+ }
|
|
},
|
|
},
|
|
- addMaterial: function () {
|
|
|
|
- var materialComponent = this.el.components.material;
|
|
|
|
- var material = (materialComponent || {}).material;
|
|
|
|
- if (!material) return;
|
|
|
|
- this.el.object3D.traverse(function (node) {
|
|
|
|
- if (node.isMesh) {
|
|
|
|
- node.material = material;
|
|
|
|
|
|
+
|
|
|
|
+ injectControls: function () {
|
|
|
|
+ var i, name,
|
|
|
|
+ data = this.data;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < data.movementControls.length; i++) {
|
|
|
|
+ name = data.movementControls[i] + COMPONENT_SUFFIX;
|
|
|
|
+ if (!this.el.components[name]) {
|
|
|
|
+ this.el.setAttribute(name, '');
|
|
}
|
|
}
|
|
- });
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < data.rotationControls.length; i++) {
|
|
|
|
+ name = data.rotationControls[i] + COMPONENT_SUFFIX;
|
|
|
|
+ if (!this.el.components[name]) {
|
|
|
|
+ this.el.setAttribute(name, '');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
},
|
|
},
|
|
- remove: function () {
|
|
|
|
- this.el.removeObject3D('mesh');
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
|
|
-module.exports.registerAll = (function () {
|
|
|
|
- var registered = false;
|
|
|
|
- return function (AFRAME) {
|
|
|
|
- if (registered) return;
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
- AFRAME.registerComponent('hexgrid', Component);
|
|
|
|
- AFRAME.registerPrimitive('a-hexgrid', Primitive);
|
|
|
|
- registered = true;
|
|
|
|
- };
|
|
|
|
-}());
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Tick
|
|
|
|
+ */
|
|
|
|
|
|
-},{"../../lib/default-hex-grid.json":7,"../../lib/hex-grid.min.js":9}],113:[function(require,module,exports){
|
|
|
|
-/**
|
|
|
|
- * Flat-shaded ocean primitive.
|
|
|
|
- *
|
|
|
|
- * Based on a Codrops tutorial:
|
|
|
|
- * http://tympanus.net/codrops/2016/04/26/the-aviator-animating-basic-3d-scene-threejs/
|
|
|
|
- */
|
|
|
|
-var Primitive = module.exports.Primitive = {
|
|
|
|
- defaultComponents: {
|
|
|
|
- ocean: {},
|
|
|
|
- rotation: {x: -90, y: 0, z: 0}
|
|
|
|
- },
|
|
|
|
- mappings: {
|
|
|
|
- width: 'ocean.width',
|
|
|
|
- depth: 'ocean.depth',
|
|
|
|
- density: 'ocean.density',
|
|
|
|
- color: 'ocean.color',
|
|
|
|
- opacity: 'ocean.opacity'
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ tick: function (t, dt) {
|
|
|
|
+ if (!dt) { return; }
|
|
|
|
|
|
-var Component = module.exports.Component = {
|
|
|
|
- schema: {
|
|
|
|
- // Dimensions of the ocean area.
|
|
|
|
- width: {default: 10, min: 0},
|
|
|
|
- depth: {default: 10, min: 0},
|
|
|
|
|
|
+ // Update rotation.
|
|
|
|
+ if (this.data.rotationEnabled) this.updateRotation(dt);
|
|
|
|
|
|
- // Density of waves.
|
|
|
|
- density: {default: 10},
|
|
|
|
|
|
+ // Update velocity. If FPS is too low, reset.
|
|
|
|
+ if (this.data.movementEnabled && dt / 1000 > MAX_DELTA) {
|
|
|
|
+ this.velocity.set(0, 0, 0);
|
|
|
|
+ this.el.setAttribute('velocity', this.velocity);
|
|
|
|
+ } else {
|
|
|
|
+ this.updateVelocity(dt);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- // Wave amplitude and variance.
|
|
|
|
- amplitude: {default: 0.1},
|
|
|
|
- amplitudeVariance: {default: 0.3},
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Rotation
|
|
|
|
+ */
|
|
|
|
|
|
- // Wave speed and variance.
|
|
|
|
- speed: {default: 1},
|
|
|
|
- speedVariance: {default: 2},
|
|
|
|
|
|
+ updateRotation: function (dt) {
|
|
|
|
+ var control, dRotation,
|
|
|
|
+ data = this.data;
|
|
|
|
|
|
- // Material.
|
|
|
|
- color: {default: '#7AD2F7', type: 'color'},
|
|
|
|
- opacity: {default: 0.8}
|
|
|
|
|
|
+ for (var i = 0, l = data.rotationControls.length; i < l; i++) {
|
|
|
|
+ control = this.el.components[data.rotationControls[i] + COMPONENT_SUFFIX];
|
|
|
|
+ if (control && control.isRotationActive()) {
|
|
|
|
+ if (control.getRotationDelta) {
|
|
|
|
+ dRotation = control.getRotationDelta(dt);
|
|
|
|
+ dRotation.multiplyScalar(data.rotationSensitivity);
|
|
|
|
+ this.yaw.rotation.y -= dRotation.x;
|
|
|
|
+ this.pitch.rotation.x -= dRotation.y;
|
|
|
|
+ this.pitch.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitch.rotation.x));
|
|
|
|
+ this.el.setAttribute('rotation', {
|
|
|
|
+ x: THREE.Math.radToDeg(this.pitch.rotation.x),
|
|
|
|
+ y: THREE.Math.radToDeg(this.yaw.rotation.y),
|
|
|
|
+ z: 0
|
|
|
|
+ });
|
|
|
|
+ } else if (control.getRotation) {
|
|
|
|
+ this.el.setAttribute('rotation', control.getRotation());
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error('Incompatible rotation controls: %s', data.rotationControls[i]);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
},
|
|
},
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Use play() instead of init(), because component mappings – unavailable as dependencies – are
|
|
|
|
- * not guaranteed to have parsed when this component is initialized.
|
|
|
|
|
|
+
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Movement
|
|
*/
|
|
*/
|
|
- play: function () {
|
|
|
|
- var el = this.el,
|
|
|
|
- data = this.data,
|
|
|
|
- material = el.components.material;
|
|
|
|
|
|
|
|
- var geometry = new THREE.PlaneGeometry(data.width, data.depth, data.density, data.density);
|
|
|
|
- geometry.mergeVertices();
|
|
|
|
- this.waves = [];
|
|
|
|
- for (var v, i = 0, l = geometry.vertices.length; i < l; i++) {
|
|
|
|
- v = geometry.vertices[i];
|
|
|
|
- this.waves.push({
|
|
|
|
- z: v.z,
|
|
|
|
- ang: Math.random() * Math.PI * 2,
|
|
|
|
- amp: data.amplitude + Math.random() * data.amplitudeVariance,
|
|
|
|
- speed: (data.speed + Math.random() * data.speedVariance) / 1000 // radians / frame
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
|
|
+ updateVelocity: function (dt) {
|
|
|
|
+ var control, dVelocity,
|
|
|
|
+ velocity = this.velocity,
|
|
|
|
+ data = this.data;
|
|
|
|
|
|
- if (!material) {
|
|
|
|
- material = {};
|
|
|
|
- material.material = new THREE.MeshPhongMaterial({
|
|
|
|
- color: data.color,
|
|
|
|
- transparent: data.opacity < 1,
|
|
|
|
- opacity: data.opacity,
|
|
|
|
- shading: THREE.FlatShading,
|
|
|
|
- });
|
|
|
|
|
|
+ if (data.movementEnabled) {
|
|
|
|
+ for (var i = 0, l = data.movementControls.length; i < l; i++) {
|
|
|
|
+ control = this.el.components[data.movementControls[i] + COMPONENT_SUFFIX];
|
|
|
|
+ if (control && control.isVelocityActive()) {
|
|
|
|
+ if (control.getVelocityDelta) {
|
|
|
|
+ dVelocity = control.getVelocityDelta(dt);
|
|
|
|
+ } else if (control.getVelocity) {
|
|
|
|
+ this.el.setAttribute('velocity', control.getVelocity());
|
|
|
|
+ return;
|
|
|
|
+ } else if (control.getPositionDelta) {
|
|
|
|
+ velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
|
|
|
|
+ this.el.setAttribute('velocity', velocity);
|
|
|
|
+ return;
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error('Incompatible movement controls: ', data.movementControls[i]);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- this.mesh = new THREE.Mesh(geometry, material.material);
|
|
|
|
- el.setObject3D('mesh', this.mesh);
|
|
|
|
- },
|
|
|
|
|
|
+ velocity.copy(this.el.getAttribute('velocity'));
|
|
|
|
+ velocity.x -= velocity.x * data.movementEasing * dt / 1000;
|
|
|
|
+ velocity.y -= velocity.y * data.movementEasingY * dt / 1000;
|
|
|
|
+ velocity.z -= velocity.z * data.movementEasing * dt / 1000;
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- this.el.removeObject3D('mesh');
|
|
|
|
- },
|
|
|
|
|
|
+ if (dVelocity && data.movementEnabled) {
|
|
|
|
+ // Set acceleration
|
|
|
|
+ if (dVelocity.length() > 1) {
|
|
|
|
+ dVelocity.setLength(this.data.movementAcceleration * dt / 1000);
|
|
|
|
+ } else {
|
|
|
|
+ dVelocity.multiplyScalar(this.data.movementAcceleration * dt / 1000);
|
|
|
|
+ }
|
|
|
|
|
|
- tick: function (t, dt) {
|
|
|
|
- if (!dt) return;
|
|
|
|
|
|
+ // Rotate to heading
|
|
|
|
+ var rotation = this.el.getAttribute('rotation');
|
|
|
|
+ if (rotation) {
|
|
|
|
+ this.heading.set(
|
|
|
|
+ data.fly ? THREE.Math.degToRad(rotation.x) : 0,
|
|
|
|
+ THREE.Math.degToRad(rotation.y),
|
|
|
|
+ 0
|
|
|
|
+ );
|
|
|
|
+ dVelocity.applyEuler(this.heading);
|
|
|
|
+ }
|
|
|
|
|
|
- var verts = this.mesh.geometry.vertices;
|
|
|
|
- for (var v, vprops, i = 0; (v = verts[i]); i++){
|
|
|
|
- vprops = this.waves[i];
|
|
|
|
- v.z = vprops.z + Math.sin(vprops.ang) * vprops.amp;
|
|
|
|
- vprops.ang += vprops.speed * dt;
|
|
|
|
|
|
+ velocity.add(dVelocity);
|
|
|
|
+
|
|
|
|
+ // TODO - Several issues here:
|
|
|
|
+ // (1) Interferes w/ gravity.
|
|
|
|
+ // (2) Interferes w/ jumping.
|
|
|
|
+ // (3) Likely to interfere w/ relative position to moving platform.
|
|
|
|
+ // if (velocity.length() > data.movementSpeed) {
|
|
|
|
+ // velocity.setLength(data.movementSpeed);
|
|
|
|
+ // }
|
|
}
|
|
}
|
|
- this.mesh.geometry.verticesNeedUpdate = true;
|
|
|
|
|
|
+
|
|
|
|
+ this.el.setAttribute('velocity', velocity);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-module.exports.registerAll = (function () {
|
|
|
|
- var registered = false;
|
|
|
|
- return function (AFRAME) {
|
|
|
|
- if (registered) return;
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
- AFRAME.registerComponent('ocean', Component);
|
|
|
|
- AFRAME.registerPrimitive('a-ocean', Primitive);
|
|
|
|
- registered = true;
|
|
|
|
- };
|
|
|
|
-}());
|
|
|
|
|
|
+},{}],94:[function(require,module,exports){
|
|
|
|
+var LoopMode = {
|
|
|
|
+ once: THREE.LoopOnce,
|
|
|
|
+ repeat: THREE.LoopRepeat,
|
|
|
|
+ pingpong: THREE.LoopPingPong
|
|
|
|
+};
|
|
|
|
|
|
-},{}],114:[function(require,module,exports){
|
|
|
|
/**
|
|
/**
|
|
- * Tube following a custom path.
|
|
|
|
- *
|
|
|
|
- * Usage:
|
|
|
|
|
|
+ * animation-mixer
|
|
*
|
|
*
|
|
- * ```html
|
|
|
|
- * <a-tube path="5 0 5, 5 0 -5, -5 0 -5" radius="0.5"></a-tube>
|
|
|
|
- * ```
|
|
|
|
|
|
+ * Player for animation clips. Intended to be compatible with any model format that supports
|
|
|
|
+ * skeletal or morph animations through THREE.AnimationMixer.
|
|
|
|
+ * See: https://threejs.org/docs/?q=animation#Reference/Animation/AnimationMixer
|
|
*/
|
|
*/
|
|
-var Primitive = module.exports.Primitive = {
|
|
|
|
- defaultComponents: {
|
|
|
|
- tube: {},
|
|
|
|
- },
|
|
|
|
- mappings: {
|
|
|
|
- path: 'tube.path',
|
|
|
|
- segments: 'tube.segments',
|
|
|
|
- radius: 'tube.radius',
|
|
|
|
- radialSegments: 'tube.radialSegments',
|
|
|
|
- closed: 'tube.closed'
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-var Component = module.exports.Component = {
|
|
|
|
|
|
+module.exports = {
|
|
schema: {
|
|
schema: {
|
|
- path: {default: []},
|
|
|
|
- segments: {default: 64},
|
|
|
|
- radius: {default: 1},
|
|
|
|
- radialSegments: {default: 8},
|
|
|
|
- closed: {default: false}
|
|
|
|
|
|
+ clip: {default: '*'},
|
|
|
|
+ duration: {default: 0},
|
|
|
|
+ crossFadeDuration: {default: 0},
|
|
|
|
+ loop: {default: 'repeat', oneOf: Object.keys(LoopMode)},
|
|
|
|
+ repetitions: {default: Infinity, min: 0}
|
|
},
|
|
},
|
|
|
|
|
|
init: function () {
|
|
init: function () {
|
|
- var el = this.el,
|
|
|
|
- data = this.data,
|
|
|
|
- material = el.components.material;
|
|
|
|
-
|
|
|
|
- if (!data.path.length) {
|
|
|
|
- console.error('[a-tube] `path` property expected but not found.');
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ /** @type {THREE.Mesh} */
|
|
|
|
+ this.model = null;
|
|
|
|
+ /** @type {THREE.AnimationMixer} */
|
|
|
|
+ this.mixer = null;
|
|
|
|
+ /** @type {Array<THREE.AnimationAction>} */
|
|
|
|
+ this.activeActions = [];
|
|
|
|
|
|
- var curve = new THREE.CatmullRomCurve3(data.path.map(function (point) {
|
|
|
|
- point = point.split(' ');
|
|
|
|
- return new THREE.Vector3(Number(point[0]), Number(point[1]), Number(point[2]));
|
|
|
|
- }));
|
|
|
|
- var geometry = new THREE.TubeGeometry(
|
|
|
|
- curve, data.segments, data.radius, data.radialSegments, data.closed
|
|
|
|
- );
|
|
|
|
|
|
+ var model = this.el.getObject3D('mesh');
|
|
|
|
|
|
- if (!material) {
|
|
|
|
- material = {};
|
|
|
|
- material.material = new THREE.MeshPhongMaterial();
|
|
|
|
|
|
+ if (model) {
|
|
|
|
+ this.load(model);
|
|
|
|
+ } else {
|
|
|
|
+ this.el.addEventListener('model-loaded', function(e) {
|
|
|
|
+ this.load(e.detail.model);
|
|
|
|
+ }.bind(this));
|
|
}
|
|
}
|
|
-
|
|
|
|
- this.mesh = new THREE.Mesh(geometry, material.material);
|
|
|
|
- this.el.setObject3D('mesh', this.mesh);
|
|
|
|
},
|
|
},
|
|
|
|
|
|
- remove: function () {
|
|
|
|
- if (this.mesh) this.el.removeObject3D('mesh');
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-module.exports.registerAll = (function () {
|
|
|
|
- var registered = false;
|
|
|
|
- return function (AFRAME) {
|
|
|
|
- if (registered) return;
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
- AFRAME.registerComponent('tube', Component);
|
|
|
|
- AFRAME.registerPrimitive('a-tube', Primitive);
|
|
|
|
- registered = true;
|
|
|
|
- };
|
|
|
|
-}());
|
|
|
|
-
|
|
|
|
-},{}],115:[function(require,module,exports){
|
|
|
|
-module.exports = {
|
|
|
|
- 'a-grid': require('./a-grid'),
|
|
|
|
- 'a-hexgrid': require('./a-hexgrid'),
|
|
|
|
- 'a-ocean': require('./a-ocean'),
|
|
|
|
- 'a-tube': require('./a-tube'),
|
|
|
|
-
|
|
|
|
- registerAll: function (AFRAME) {
|
|
|
|
- if (this._registered) return;
|
|
|
|
- AFRAME = AFRAME || window.AFRAME;
|
|
|
|
- this['a-grid'].registerAll(AFRAME);
|
|
|
|
- this['a-hexgrid'].registerAll(AFRAME);
|
|
|
|
- this['a-ocean'].registerAll(AFRAME);
|
|
|
|
- this['a-tube'].registerAll(AFRAME);
|
|
|
|
- this._registered = true;
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-},{"./a-grid":111,"./a-hexgrid":112,"./a-ocean":113,"./a-tube":114}],116:[function(require,module,exports){
|
|
|
|
-const BinaryHeap = require('./BinaryHeap');
|
|
|
|
-const utils = require('./utils.js');
|
|
|
|
-
|
|
|
|
-class AStar {
|
|
|
|
- static init (graph) {
|
|
|
|
- for (let x = 0; x < graph.length; x++) {
|
|
|
|
- //for(var x in graph) {
|
|
|
|
- const node = graph[x];
|
|
|
|
- node.f = 0;
|
|
|
|
- node.g = 0;
|
|
|
|
- node.h = 0;
|
|
|
|
- node.cost = 1.0;
|
|
|
|
- node.visited = false;
|
|
|
|
- node.closed = false;
|
|
|
|
- node.parent = null;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- static cleanUp (graph) {
|
|
|
|
- for (let x = 0; x < graph.length; x++) {
|
|
|
|
- const node = graph[x];
|
|
|
|
- delete node.f;
|
|
|
|
- delete node.g;
|
|
|
|
- delete node.h;
|
|
|
|
- delete node.cost;
|
|
|
|
- delete node.visited;
|
|
|
|
- delete node.closed;
|
|
|
|
- delete node.parent;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ load: function (model) {
|
|
|
|
+ var el = this.el;
|
|
|
|
+ this.model = model;
|
|
|
|
+ this.mixer = new THREE.AnimationMixer(model);
|
|
|
|
+ this.mixer.addEventListener('loop', function (e) {
|
|
|
|
+ el.emit('animation-loop', {action: e.action, loopDelta: e.loopDelta});
|
|
|
|
+ }.bind(this));
|
|
|
|
+ this.mixer.addEventListener('finished', function (e) {
|
|
|
|
+ el.emit('animation-finished', {action: e.action, direction: e.direction});
|
|
|
|
+ }.bind(this));
|
|
|
|
+ if (this.data.clip) this.update({});
|
|
|
|
+ },
|
|
|
|
|
|
- static heap () {
|
|
|
|
- return new BinaryHeap(function (node) {
|
|
|
|
- return node.f;
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ if (this.mixer) this.mixer.stopAllAction();
|
|
|
|
+ },
|
|
|
|
|
|
- static search (graph, start, end) {
|
|
|
|
- this.init(graph);
|
|
|
|
- //heuristic = heuristic || astar.manhattan;
|
|
|
|
|
|
+ update: function (previousData) {
|
|
|
|
+ if (!previousData) return;
|
|
|
|
|
|
|
|
+ this.stopAction();
|
|
|
|
|
|
- const openHeap = this.heap();
|
|
|
|
|
|
+ if (this.data.clip) {
|
|
|
|
+ this.playAction();
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- openHeap.push(start);
|
|
|
|
|
|
+ stopAction: function () {
|
|
|
|
+ var data = this.data;
|
|
|
|
+ for (var i = 0; i < this.activeActions.length; i++) {
|
|
|
|
+ data.crossFadeDuration
|
|
|
|
+ ? this.activeActions[i].fadeOut(data.crossFadeDuration)
|
|
|
|
+ : this.activeActions[i].stop();
|
|
|
|
+ }
|
|
|
|
+ this.activeActions.length = 0;
|
|
|
|
+ },
|
|
|
|
|
|
- while (openHeap.size() > 0) {
|
|
|
|
|
|
+ playAction: function () {
|
|
|
|
+ if (!this.mixer) return;
|
|
|
|
|
|
- // Grab the lowest f(x) to process next. Heap keeps this sorted for us.
|
|
|
|
- const currentNode = openHeap.pop();
|
|
|
|
|
|
+ var model = this.model,
|
|
|
|
+ data = this.data,
|
|
|
|
+ clips = model.animations || (model.geometry || {}).animations || [];
|
|
|
|
|
|
- // End case -- result has been found, return the traced path.
|
|
|
|
- if (currentNode === end) {
|
|
|
|
- let curr = currentNode;
|
|
|
|
- const ret = [];
|
|
|
|
- while (curr.parent) {
|
|
|
|
- ret.push(curr);
|
|
|
|
- curr = curr.parent;
|
|
|
|
- }
|
|
|
|
- this.cleanUp(ret);
|
|
|
|
- return ret.reverse();
|
|
|
|
|
|
+ if (!clips.length) return;
|
|
|
|
+
|
|
|
|
+ var re = wildcardToRegExp(data.clip);
|
|
|
|
+
|
|
|
|
+ for (var clip, i = 0; (clip = clips[i]); i++) {
|
|
|
|
+ if (clip.name.match(re)) {
|
|
|
|
+ var action = this.mixer.clipAction(clip, model);
|
|
|
|
+ action.enabled = true;
|
|
|
|
+ if (data.duration) action.setDuration(data.duration);
|
|
|
|
+ action
|
|
|
|
+ .setLoop(LoopMode[data.loop], data.repetitions)
|
|
|
|
+ .fadeIn(data.crossFadeDuration)
|
|
|
|
+ .play();
|
|
|
|
+ this.activeActions.push(action);
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- // Normal case -- move currentNode from open to closed, process each of its neighbours.
|
|
|
|
- currentNode.closed = true;
|
|
|
|
|
|
+ tick: function (t, dt) {
|
|
|
|
+ if (this.mixer && !isNaN(dt)) this.mixer.update(dt / 1000);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- // Find all neighbours for the current node. Optionally find diagonal neighbours as well (false by default).
|
|
|
|
- const neighbours = this.neighbours(graph, currentNode);
|
|
|
|
|
|
+/**
|
|
|
|
+ * Creates a RegExp from the given string, converting asterisks to .* expressions,
|
|
|
|
+ * and escaping all other characters.
|
|
|
|
+ */
|
|
|
|
+function wildcardToRegExp (s) {
|
|
|
|
+ return new RegExp('^' + s.split(/\*+/).map(regExpEscape).join('.*') + '$');
|
|
|
|
+}
|
|
|
|
|
|
- for (let i = 0, il = neighbours.length; i < il; i++) {
|
|
|
|
- const neighbour = neighbours[i];
|
|
|
|
|
|
+/**
|
|
|
|
+ * RegExp-escapes all characters in the given string.
|
|
|
|
+ */
|
|
|
|
+function regExpEscape (s) {
|
|
|
|
+ return s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
|
|
|
|
+}
|
|
|
|
|
|
- if (neighbour.closed) {
|
|
|
|
- // Not a valid node to process, skip to next neighbour.
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+},{}],95:[function(require,module,exports){
|
|
|
|
+THREE.FBXLoader = require('../../lib/FBXLoader');
|
|
|
|
|
|
- // The g score is the shortest distance from start to current node.
|
|
|
|
- // We need to check if the path we have arrived at this neighbour is the shortest one we have seen yet.
|
|
|
|
- const gScore = currentNode.g + neighbour.cost;
|
|
|
|
- const beenVisited = neighbour.visited;
|
|
|
|
|
|
+/**
|
|
|
|
+ * fbx-model
|
|
|
|
+ *
|
|
|
|
+ * Loader for FBX format. Supports ASCII, but *not* binary, models.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ src: { type: 'asset' },
|
|
|
|
+ crossorigin: { default: '' }
|
|
|
|
+ },
|
|
|
|
|
|
- if (!beenVisited || gScore < neighbour.g) {
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.model = null;
|
|
|
|
+ },
|
|
|
|
|
|
- // Found an optimal (so far) path to this node. Take score for node to see how good it is.
|
|
|
|
- neighbour.visited = true;
|
|
|
|
- neighbour.parent = currentNode;
|
|
|
|
- if (!neighbour.centroid || !end.centroid) throw new Error('Unexpected state');
|
|
|
|
- neighbour.h = neighbour.h || this.heuristic(neighbour.centroid, end.centroid);
|
|
|
|
- neighbour.g = gScore;
|
|
|
|
- neighbour.f = neighbour.g + neighbour.h;
|
|
|
|
|
|
+ update: function () {
|
|
|
|
+ var loader,
|
|
|
|
+ data = this.data;
|
|
|
|
+ if (!data.src) return;
|
|
|
|
|
|
- if (!beenVisited) {
|
|
|
|
- // Pushing to heap will put it in proper place based on the 'f' value.
|
|
|
|
- openHeap.push(neighbour);
|
|
|
|
- } else {
|
|
|
|
- // Already seen the node, but since it has been rescored we need to reorder it in the heap
|
|
|
|
- openHeap.rescoreElement(neighbour);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ this.remove();
|
|
|
|
+ loader = new THREE.FBXLoader();
|
|
|
|
+ if (data.crossorigin) loader.setCrossOrigin(data.crossorigin);
|
|
|
|
+ loader.load(data.src, this.load.bind(this));
|
|
|
|
+ },
|
|
|
|
|
|
- // No result was found - empty array signifies failure to find path.
|
|
|
|
- return [];
|
|
|
|
- }
|
|
|
|
|
|
+ load: function (model) {
|
|
|
|
+ this.model = model;
|
|
|
|
+ this.el.setObject3D('mesh', model);
|
|
|
|
+ this.el.emit('model-loaded', {format: 'fbx', model: model});
|
|
|
|
+ },
|
|
|
|
|
|
- static heuristic (pos1, pos2) {
|
|
|
|
- return utils.distanceToSquared(pos1, pos2);
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ if (this.model) this.el.removeObject3D('mesh');
|
|
}
|
|
}
|
|
|
|
+};
|
|
|
|
|
|
- static neighbours (graph, node) {
|
|
|
|
- const ret = [];
|
|
|
|
|
|
+},{"../../lib/FBXLoader":3}],96:[function(require,module,exports){
|
|
|
|
+var fetchScript = require('../../lib/fetch-script')();
|
|
|
|
|
|
- for (let e = 0; e < node.neighbours.length; e++) {
|
|
|
|
- ret.push(graph[node.neighbours[e]]);
|
|
|
|
- }
|
|
|
|
|
|
+var LOADER_SRC = 'https://rawgit.com/mrdoob/three.js/r86/examples/js/loaders/GLTFLoader.js';
|
|
|
|
|
|
- return ret;
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+/**
|
|
|
|
+ * Legacy loader for glTF 1.0 models.
|
|
|
|
+ * Asynchronously loads THREE.GLTFLoader from rawgit.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {type: 'model'},
|
|
|
|
|
|
-module.exports = AStar;
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.model = null;
|
|
|
|
+ this.loader = null;
|
|
|
|
+ this.loaderPromise = loadLoader().then(function () {
|
|
|
|
+ this.loader = new THREE.GLTFLoader();
|
|
|
|
+ this.loader.setCrossOrigin('Anonymous');
|
|
|
|
+ }.bind(this));
|
|
|
|
+ },
|
|
|
|
|
|
-},{"./BinaryHeap":117,"./utils.js":120}],117:[function(require,module,exports){
|
|
|
|
-// javascript-astar
|
|
|
|
-// http://github.com/bgrins/javascript-astar
|
|
|
|
-// Freely distributable under the MIT License.
|
|
|
|
-// Implements the astar search algorithm in javascript using a binary heap.
|
|
|
|
|
|
+ update: function () {
|
|
|
|
+ var self = this;
|
|
|
|
+ var el = this.el;
|
|
|
|
+ var src = this.data;
|
|
|
|
|
|
-class BinaryHeap {
|
|
|
|
- constructor (scoreFunction) {
|
|
|
|
- this.content = [];
|
|
|
|
- this.scoreFunction = scoreFunction;
|
|
|
|
- }
|
|
|
|
|
|
+ if (!src) { return; }
|
|
|
|
|
|
- push (element) {
|
|
|
|
- // Add the new element to the end of the array.
|
|
|
|
- this.content.push(element);
|
|
|
|
|
|
+ this.remove();
|
|
|
|
|
|
- // Allow it to sink down.
|
|
|
|
- this.sinkDown(this.content.length - 1);
|
|
|
|
- }
|
|
|
|
|
|
+ this.loaderPromise.then(function () {
|
|
|
|
+ this.loader.load(src, function gltfLoaded (gltfModel) {
|
|
|
|
+ self.model = gltfModel.scene;
|
|
|
|
+ self.model.animations = gltfModel.animations;
|
|
|
|
+ el.setObject3D('mesh', self.model);
|
|
|
|
+ el.emit('model-loaded', {format: 'gltf', model: self.model});
|
|
|
|
+ });
|
|
|
|
+ }.bind(this));
|
|
|
|
+ },
|
|
|
|
|
|
- pop () {
|
|
|
|
- // Store the first element so we can return it later.
|
|
|
|
- const result = this.content[0];
|
|
|
|
- // Get the element at the end of the array.
|
|
|
|
- const end = this.content.pop();
|
|
|
|
- // If there are any elements left, put the end element at the
|
|
|
|
- // start, and let it bubble up.
|
|
|
|
- if (this.content.length > 0) {
|
|
|
|
- this.content[0] = end;
|
|
|
|
- this.bubbleUp(0);
|
|
|
|
- }
|
|
|
|
- return result;
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ if (!this.model) { return; }
|
|
|
|
+ this.el.removeObject3D('mesh');
|
|
}
|
|
}
|
|
|
|
+};
|
|
|
|
|
|
- remove (node) {
|
|
|
|
- const i = this.content.indexOf(node);
|
|
|
|
|
|
+var loadLoader = (function () {
|
|
|
|
+ var promise;
|
|
|
|
+ return function () {
|
|
|
|
+ promise = promise || fetchScript(LOADER_SRC);
|
|
|
|
+ return promise;
|
|
|
|
+ };
|
|
|
|
+}());
|
|
|
|
|
|
- // When it is found, the process seen in 'pop' is repeated
|
|
|
|
- // to fill up the hole.
|
|
|
|
- const end = this.content.pop();
|
|
|
|
|
|
+},{"../../lib/fetch-script":8}],97:[function(require,module,exports){
|
|
|
|
+module.exports = {
|
|
|
|
+ 'animation-mixer': require('./animation-mixer'),
|
|
|
|
+ 'fbx-model': require('./fbx-model'),
|
|
|
|
+ 'gltf-model-legacy': require('./gltf-model-legacy'),
|
|
|
|
+ 'json-model': require('./json-model'),
|
|
|
|
+ 'object-model': require('./object-model'),
|
|
|
|
+ 'ply-model': require('./ply-model'),
|
|
|
|
+
|
|
|
|
+ registerAll: function (AFRAME) {
|
|
|
|
+ if (this._registered) return;
|
|
|
|
+
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
+
|
|
|
|
+ // THREE.AnimationMixer
|
|
|
|
+ if (!AFRAME.components['animation-mixer']) {
|
|
|
|
+ AFRAME.registerComponent('animation-mixer', this['animation-mixer']);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // THREE.PlyLoader
|
|
|
|
+ if (!AFRAME.systems['ply-model']) {
|
|
|
|
+ AFRAME.registerSystem('ply-model', this['ply-model'].System);
|
|
|
|
+ }
|
|
|
|
+ if (!AFRAME.components['ply-model']) {
|
|
|
|
+ AFRAME.registerComponent('ply-model', this['ply-model'].Component);
|
|
|
|
+ }
|
|
|
|
|
|
- if (i !== this.content.length - 1) {
|
|
|
|
- this.content[i] = end;
|
|
|
|
|
|
+ // THREE.FBXLoader
|
|
|
|
+ if (!AFRAME.components['fbx-model']) {
|
|
|
|
+ AFRAME.registerComponent('fbx-model', this['fbx-model']);
|
|
|
|
+ }
|
|
|
|
|
|
- if (this.scoreFunction(end) < this.scoreFunction(node)) {
|
|
|
|
- this.sinkDown(i);
|
|
|
|
- } else {
|
|
|
|
- this.bubbleUp(i);
|
|
|
|
- }
|
|
|
|
|
|
+ // THREE.GLTFLoader
|
|
|
|
+ if (!AFRAME.components['gltf-model-legacy']) {
|
|
|
|
+ AFRAME.registerComponent('gltf-model-legacy', this['gltf-model-legacy']);
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- size () {
|
|
|
|
- return this.content.length;
|
|
|
|
- }
|
|
|
|
|
|
+ // THREE.JsonLoader
|
|
|
|
+ if (!AFRAME.components['json-model']) {
|
|
|
|
+ AFRAME.registerComponent('json-model', this['json-model']);
|
|
|
|
+ }
|
|
|
|
|
|
- rescoreElement (node) {
|
|
|
|
- this.sinkDown(this.content.indexOf(node));
|
|
|
|
|
|
+ // THREE.ObjectLoader
|
|
|
|
+ if (!AFRAME.components['object-model']) {
|
|
|
|
+ AFRAME.registerComponent('object-model', this['object-model']);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this._registered = true;
|
|
}
|
|
}
|
|
|
|
+};
|
|
|
|
|
|
- sinkDown (n) {
|
|
|
|
- // Fetch the element that has to be sunk.
|
|
|
|
- const element = this.content[n];
|
|
|
|
|
|
+},{"./animation-mixer":94,"./fbx-model":95,"./gltf-model-legacy":96,"./json-model":98,"./object-model":99,"./ply-model":100}],98:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * json-model
|
|
|
|
+ *
|
|
|
|
+ * Loader for THREE.js JSON format. Somewhat confusingly, there are two different THREE.js formats,
|
|
|
|
+ * both having the .json extension. This loader supports only THREE.JsonLoader, which typically
|
|
|
|
+ * includes only a single mesh.
|
|
|
|
+ *
|
|
|
|
+ * Check the console for errors, if in doubt. You may need to use `object-model` or
|
|
|
|
+ * `blend-character-model` for some .js and .json files.
|
|
|
|
+ *
|
|
|
|
+ * See: https://clara.io/learn/user-guide/data_exchange/threejs_export
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ src: { type: 'asset' },
|
|
|
|
+ crossorigin: { default: '' }
|
|
|
|
+ },
|
|
|
|
|
|
- // When at 0, an element can not sink any further.
|
|
|
|
- while (n > 0) {
|
|
|
|
- // Compute the parent element's index, and fetch it.
|
|
|
|
- const parentN = ((n + 1) >> 1) - 1;
|
|
|
|
- const parent = this.content[parentN];
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.model = null;
|
|
|
|
+ },
|
|
|
|
|
|
- if (this.scoreFunction(element) < this.scoreFunction(parent)) {
|
|
|
|
- // Swap the elements if the parent is greater.
|
|
|
|
- this.content[parentN] = element;
|
|
|
|
- this.content[n] = parent;
|
|
|
|
- // Update 'n' to continue at the new position.
|
|
|
|
- n = parentN;
|
|
|
|
- } else {
|
|
|
|
- // Found a parent that is less, no need to sink any further.
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ update: function () {
|
|
|
|
+ var loader,
|
|
|
|
+ data = this.data;
|
|
|
|
+ if (!data.src) return;
|
|
|
|
|
|
- bubbleUp (n) {
|
|
|
|
- // Look up the target element and its score.
|
|
|
|
- const length = this.content.length,
|
|
|
|
- element = this.content[n],
|
|
|
|
- elemScore = this.scoreFunction(element);
|
|
|
|
|
|
+ this.remove();
|
|
|
|
+ loader = new THREE.JSONLoader();
|
|
|
|
+ if (data.crossorigin) loader.crossOrigin = data.crossorigin;
|
|
|
|
+ loader.load(data.src, function (geometry, materials) {
|
|
|
|
|
|
- while (true) {
|
|
|
|
- // Compute the indices of the child elements.
|
|
|
|
- const child2N = (n + 1) << 1,
|
|
|
|
- child1N = child2N - 1;
|
|
|
|
- // This is used to store the new position of the element,
|
|
|
|
- // if any.
|
|
|
|
- let swap = null;
|
|
|
|
- let child1Score;
|
|
|
|
- // If the first child exists (is inside the array)...
|
|
|
|
- if (child1N < length) {
|
|
|
|
- // Look it up and compute its score.
|
|
|
|
- const child1 = this.content[child1N];
|
|
|
|
- child1Score = this.scoreFunction(child1);
|
|
|
|
|
|
+ // Attempt to automatically detect common material options.
|
|
|
|
+ materials.forEach(function (mat) {
|
|
|
|
+ mat.vertexColors = (geometry.faces[0] || {}).color ? THREE.FaceColors : THREE.NoColors;
|
|
|
|
+ mat.skinning = !!(geometry.bones || []).length;
|
|
|
|
+ mat.morphTargets = !!(geometry.morphTargets || []).length;
|
|
|
|
+ mat.morphNormals = !!(geometry.morphNormals || []).length;
|
|
|
|
+ });
|
|
|
|
|
|
- // If the score is less than our element's, we need to swap.
|
|
|
|
- if (child1Score < elemScore) {
|
|
|
|
- swap = child1N;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ var model = (geometry.bones || []).length
|
|
|
|
+ ? new THREE.SkinnedMesh(geometry, new THREE.MultiMaterial(materials))
|
|
|
|
+ : new THREE.Mesh(geometry, new THREE.MultiMaterial(materials));
|
|
|
|
|
|
- // Do the same checks for the other child.
|
|
|
|
- if (child2N < length) {
|
|
|
|
- const child2 = this.content[child2N],
|
|
|
|
- child2Score = this.scoreFunction(child2);
|
|
|
|
- if (child2Score < (swap === null ? elemScore : child1Score)) {
|
|
|
|
- swap = child2N;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ this.load(model);
|
|
|
|
+ }.bind(this));
|
|
|
|
+ },
|
|
|
|
|
|
- // If the element needs to be moved, swap it, and continue.
|
|
|
|
- if (swap !== null) {
|
|
|
|
- this.content[n] = this.content[swap];
|
|
|
|
- this.content[swap] = element;
|
|
|
|
- n = swap;
|
|
|
|
- }
|
|
|
|
|
|
+ load: function (model) {
|
|
|
|
+ this.model = model;
|
|
|
|
+ this.el.setObject3D('mesh', model);
|
|
|
|
+ this.el.emit('model-loaded', {format: 'json', model: model});
|
|
|
|
+ },
|
|
|
|
|
|
- // Otherwise, we are done.
|
|
|
|
- else {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ if (this.model) this.el.removeObject3D('mesh');
|
|
}
|
|
}
|
|
|
|
+};
|
|
|
|
|
|
-}
|
|
|
|
|
|
+},{}],99:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * object-model
|
|
|
|
+ *
|
|
|
|
+ * Loader for THREE.js JSON format. Somewhat confusingly, there are two different THREE.js formats,
|
|
|
|
+ * both having the .json extension. This loader supports only THREE.ObjectLoader, which typically
|
|
|
|
+ * includes multiple meshes or an entire scene.
|
|
|
|
+ *
|
|
|
|
+ * Check the console for errors, if in doubt. You may need to use `json-model` or
|
|
|
|
+ * `blend-character-model` for some .js and .json files.
|
|
|
|
+ *
|
|
|
|
+ * See: https://clara.io/learn/user-guide/data_exchange/threejs_export
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ src: { type: 'asset' },
|
|
|
|
+ crossorigin: { default: '' }
|
|
|
|
+ },
|
|
|
|
|
|
-module.exports = BinaryHeap;
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.model = null;
|
|
|
|
+ },
|
|
|
|
|
|
-},{}],118:[function(require,module,exports){
|
|
|
|
-const utils = require('./utils');
|
|
|
|
|
|
+ update: function () {
|
|
|
|
+ var loader,
|
|
|
|
+ data = this.data;
|
|
|
|
+ if (!data.src) return;
|
|
|
|
|
|
-class Channel {
|
|
|
|
- constructor () {
|
|
|
|
- this.portals = [];
|
|
|
|
- }
|
|
|
|
|
|
+ this.remove();
|
|
|
|
+ loader = new THREE.ObjectLoader();
|
|
|
|
+ if (data.crossorigin) loader.setCrossOrigin(data.crossorigin);
|
|
|
|
+ loader.load(data.src, function(object) {
|
|
|
|
|
|
- push (p1, p2) {
|
|
|
|
- if (p2 === undefined) p2 = p1;
|
|
|
|
- this.portals.push({
|
|
|
|
- left: p1,
|
|
|
|
- right: p2
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
|
|
+ // Enable skinning, if applicable.
|
|
|
|
+ object.traverse(function(o) {
|
|
|
|
+ if (o instanceof THREE.SkinnedMesh && o.material) {
|
|
|
|
+ o.material.skinning = !!((o.geometry && o.geometry.bones) || []).length;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- stringPull () {
|
|
|
|
- const portals = this.portals;
|
|
|
|
- const pts = [];
|
|
|
|
- // Init scan state
|
|
|
|
- let portalApex, portalLeft, portalRight;
|
|
|
|
- let apexIndex = 0,
|
|
|
|
- leftIndex = 0,
|
|
|
|
- rightIndex = 0;
|
|
|
|
|
|
+ this.load(object);
|
|
|
|
+ }.bind(this));
|
|
|
|
+ },
|
|
|
|
|
|
- portalApex = portals[0].left;
|
|
|
|
- portalLeft = portals[0].left;
|
|
|
|
- portalRight = portals[0].right;
|
|
|
|
|
|
+ load: function (model) {
|
|
|
|
+ this.model = model;
|
|
|
|
+ this.el.setObject3D('mesh', model);
|
|
|
|
+ this.el.emit('model-loaded', {format: 'json', model: model});
|
|
|
|
+ },
|
|
|
|
|
|
- // Add start point.
|
|
|
|
- pts.push(portalApex);
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ if (this.model) this.el.removeObject3D('mesh');
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- for (let i = 1; i < portals.length; i++) {
|
|
|
|
- const left = portals[i].left;
|
|
|
|
- const right = portals[i].right;
|
|
|
|
|
|
+},{}],100:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * ply-model
|
|
|
|
+ *
|
|
|
|
+ * Wraps THREE.PLYLoader.
|
|
|
|
+ */
|
|
|
|
+THREE.PLYLoader = require('../../lib/PLYLoader');
|
|
|
|
|
|
- // Update right vertex.
|
|
|
|
- if (utils.triarea2(portalApex, portalRight, right) <= 0.0) {
|
|
|
|
- if (utils.vequal(portalApex, portalRight) || utils.triarea2(portalApex, portalLeft, right) > 0.0) {
|
|
|
|
- // Tighten the funnel.
|
|
|
|
- portalRight = right;
|
|
|
|
- rightIndex = i;
|
|
|
|
- } else {
|
|
|
|
- // Right over left, insert left to path and restart scan from portal left point.
|
|
|
|
- pts.push(portalLeft);
|
|
|
|
- // Make current left the new apex.
|
|
|
|
- portalApex = portalLeft;
|
|
|
|
- apexIndex = leftIndex;
|
|
|
|
- // Reset portal
|
|
|
|
- portalLeft = portalApex;
|
|
|
|
- portalRight = portalApex;
|
|
|
|
- leftIndex = apexIndex;
|
|
|
|
- rightIndex = apexIndex;
|
|
|
|
- // Restart scan
|
|
|
|
- i = apexIndex;
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+/**
|
|
|
|
+ * Loads, caches, resolves geometries.
|
|
|
|
+ *
|
|
|
|
+ * @member cache - Promises that resolve geometries keyed by `src`.
|
|
|
|
+ */
|
|
|
|
+module.exports.System = {
|
|
|
|
+ init: function () {
|
|
|
|
+ this.cache = {};
|
|
|
|
+ },
|
|
|
|
|
|
- // Update left vertex.
|
|
|
|
- if (utils.triarea2(portalApex, portalLeft, left) >= 0.0) {
|
|
|
|
- if (utils.vequal(portalApex, portalLeft) || utils.triarea2(portalApex, portalRight, left) < 0.0) {
|
|
|
|
- // Tighten the funnel.
|
|
|
|
- portalLeft = left;
|
|
|
|
- leftIndex = i;
|
|
|
|
- } else {
|
|
|
|
- // Left over right, insert right to path and restart scan from portal right point.
|
|
|
|
- pts.push(portalRight);
|
|
|
|
- // Make current right the new apex.
|
|
|
|
- portalApex = portalRight;
|
|
|
|
- apexIndex = rightIndex;
|
|
|
|
- // Reset portal
|
|
|
|
- portalLeft = portalApex;
|
|
|
|
- portalRight = portalApex;
|
|
|
|
- leftIndex = apexIndex;
|
|
|
|
- rightIndex = apexIndex;
|
|
|
|
- // Restart scan
|
|
|
|
- i = apexIndex;
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @returns {Promise}
|
|
|
|
+ */
|
|
|
|
+ getOrLoadGeometry: function (src, skipCache) {
|
|
|
|
+ var cache = this.cache;
|
|
|
|
+ var cacheItem = cache[src];
|
|
|
|
|
|
- if ((pts.length === 0) || (!utils.vequal(pts[pts.length - 1], portals[portals.length - 1].left))) {
|
|
|
|
- // Append last point to path.
|
|
|
|
- pts.push(portals[portals.length - 1].left);
|
|
|
|
|
|
+ if (!skipCache && cacheItem) {
|
|
|
|
+ return cacheItem;
|
|
}
|
|
}
|
|
|
|
|
|
- this.path = pts;
|
|
|
|
- return pts;
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+ cache[src] = new Promise(function (resolve) {
|
|
|
|
+ var loader = new THREE.PLYLoader();
|
|
|
|
+ loader.load(src, function (geometry) {
|
|
|
|
+ resolve(geometry);
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ return cache[src];
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
|
|
-module.exports = Channel;
|
|
|
|
|
|
+module.exports.Component = {
|
|
|
|
+ schema: {
|
|
|
|
+ skipCache: {type: 'boolean', default: false},
|
|
|
|
+ src: {type: 'asset'}
|
|
|
|
+ },
|
|
|
|
|
|
-},{"./utils":120}],119:[function(require,module,exports){
|
|
|
|
-const utils = require('./utils');
|
|
|
|
-const AStar = require('./AStar');
|
|
|
|
-const Channel = require('./Channel');
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.model = null;
|
|
|
|
+ },
|
|
|
|
|
|
-var polygonId = 1;
|
|
|
|
|
|
+ update: function () {
|
|
|
|
+ var data = this.data;
|
|
|
|
+ var el = this.el;
|
|
|
|
+ var loader;
|
|
|
|
|
|
-var buildPolygonGroups = function (navigationMesh) {
|
|
|
|
|
|
+ if (!data.src) {
|
|
|
|
+ console.warn('[%s] `src` property is required.', this.name);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- var polygons = navigationMesh.polygons;
|
|
|
|
|
|
+ // Get geometry from system, create and set mesh.
|
|
|
|
+ this.system.getOrLoadGeometry(data.src, data.skipCache).then(function (geometry) {
|
|
|
|
+ var model = createModel(geometry);
|
|
|
|
+ el.setObject3D('mesh', model);
|
|
|
|
+ el.emit('model-loaded', {format: 'ply', model: model});
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
|
|
- var polygonGroups = [];
|
|
|
|
- var groupCount = 0;
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ if (this.model) { this.el.removeObject3D('mesh'); }
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- var spreadGroupId = function (polygon) {
|
|
|
|
- polygon.neighbours.forEach((neighbour) => {
|
|
|
|
- if (neighbour.group === undefined) {
|
|
|
|
- neighbour.group = polygon.group;
|
|
|
|
- spreadGroupId(neighbour);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- };
|
|
|
|
|
|
+function createModel (geometry) {
|
|
|
|
+ return new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({
|
|
|
|
+ color: 0xFFFFFF,
|
|
|
|
+ shading: THREE.FlatShading,
|
|
|
|
+ vertexColors: THREE.VertexColors,
|
|
|
|
+ shininess: 0
|
|
|
|
+ }));
|
|
|
|
+}
|
|
|
|
|
|
- polygons.forEach((polygon) => {
|
|
|
|
|
|
+},{"../../lib/PLYLoader":6}],101:[function(require,module,exports){
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ offset: {default: {x: 0, y: 0, z: 0}, type: 'vec3'}
|
|
|
|
+ },
|
|
|
|
|
|
- if (polygon.group === undefined) {
|
|
|
|
- polygon.group = groupCount++;
|
|
|
|
- // Spread it
|
|
|
|
- spreadGroupId(polygon);
|
|
|
|
- }
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.active = false;
|
|
|
|
+ this.targetEl = null;
|
|
|
|
+ this.fire = this.fire.bind(this);
|
|
|
|
+ this.offset = new THREE.Vector3();
|
|
|
|
+ },
|
|
|
|
|
|
- if (!polygonGroups[polygon.group]) polygonGroups[polygon.group] = [];
|
|
|
|
|
|
+ update: function () {
|
|
|
|
+ this.offset.copy(this.data.offset);
|
|
|
|
+ },
|
|
|
|
|
|
- polygonGroups[polygon.group].push(polygon);
|
|
|
|
- });
|
|
|
|
|
|
+ play: function () { this.el.addEventListener('click', this.fire); },
|
|
|
|
+ pause: function () { this.el.removeEventListener('click', this.fire); },
|
|
|
|
+ remove: function () { this.pause(); },
|
|
|
|
|
|
- console.log('Groups built: ', polygonGroups.length);
|
|
|
|
|
|
+ fire: function () {
|
|
|
|
+ var targetEl = this.el.sceneEl.querySelector('[checkpoint-controls]');
|
|
|
|
+ if (!targetEl) {
|
|
|
|
+ throw new Error('No `checkpoint-controls` component found.');
|
|
|
|
+ }
|
|
|
|
+ targetEl.components['checkpoint-controls'].setCheckpoint(this.el);
|
|
|
|
+ },
|
|
|
|
|
|
- return polygonGroups;
|
|
|
|
|
|
+ getOffset: function () {
|
|
|
|
+ return this.offset.copy(this.data.offset);
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
-var buildPolygonNeighbours = function (polygon, navigationMesh) {
|
|
|
|
- polygon.neighbours = [];
|
|
|
|
|
|
+},{}],102:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Specifies an envMap on an entity, without replacing any existing material
|
|
|
|
+ * properties.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ path: {default: ''},
|
|
|
|
+ extension: {default: 'jpg'},
|
|
|
|
+ format: {default: 'RGBFormat'},
|
|
|
|
+ enableBackground: {default: false}
|
|
|
|
+ },
|
|
|
|
|
|
- // All other nodes that contain at least two of our vertices are our neighbours
|
|
|
|
- for (var i = 0, len = navigationMesh.polygons.length; i < len; i++) {
|
|
|
|
- if (polygon === navigationMesh.polygons[i]) continue;
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ var data = this.data;
|
|
|
|
|
|
- // Don't check polygons that are too far, since the intersection tests take a long time
|
|
|
|
- if (polygon.centroid.distanceToSquared(navigationMesh.polygons[i].centroid) > 100 * 100) continue;
|
|
|
|
|
|
+ this.texture = new THREE.CubeTextureLoader().load([
|
|
|
|
+ data.path + 'posx.' + data.extension, data.path + 'negx.' + data.extension,
|
|
|
|
+ data.path + 'posy.' + data.extension, data.path + 'negy.' + data.extension,
|
|
|
|
+ data.path + 'posz.' + data.extension, data.path + 'negz.' + data.extension
|
|
|
|
+ ]);
|
|
|
|
+ this.texture.format = THREE[data.format];
|
|
|
|
|
|
- var matches = utils.array_intersect(polygon.vertexIds, navigationMesh.polygons[i].vertexIds);
|
|
|
|
|
|
+ if (data.enableBackground) {
|
|
|
|
+ this.el.sceneEl.object3D.background = this.texture;
|
|
|
|
+ }
|
|
|
|
|
|
- if (matches.length >= 2) {
|
|
|
|
- polygon.neighbours.push(navigationMesh.polygons[i]);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ this.applyEnvMap();
|
|
|
|
+ this.el.addEventListener('object3dset', this.applyEnvMap.bind(this));
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ applyEnvMap: function () {
|
|
|
|
+ var mesh = this.el.getObject3D('mesh');
|
|
|
|
+ var envMap = this.texture;
|
|
|
|
+
|
|
|
|
+ if (!mesh) return;
|
|
|
|
+
|
|
|
|
+ mesh.traverse(function (node) {
|
|
|
|
+ if (node.material && 'envMap' in node.material) {
|
|
|
|
+ node.material.envMap = envMap;
|
|
|
|
+ node.material.needsUpdate = true;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
-var buildPolygonsFromGeometry = function (geometry) {
|
|
|
|
|
|
+},{}],103:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Based on aframe/examples/showcase/tracked-controls.
|
|
|
|
+ *
|
|
|
|
+ * Handles events coming from the hand-controls.
|
|
|
|
+ * Determines if the entity is grabbed or released.
|
|
|
|
+ * Updates its position to move along the controller.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ init: function () {
|
|
|
|
+ this.GRABBED_STATE = 'grabbed';
|
|
|
|
|
|
- console.log('Vertices:', geometry.vertices.length, 'polygons:', geometry.faces.length);
|
|
|
|
|
|
+ this.grabbing = false;
|
|
|
|
+ this.hitEl = /** @type {AFRAME.Element} */ null;
|
|
|
|
+ this.physics = /** @type {AFRAME.System} */ this.el.sceneEl.systems.physics;
|
|
|
|
+ this.constraint = /** @type {CANNON.Constraint} */ null;
|
|
|
|
|
|
- var polygons = [];
|
|
|
|
- var vertices = geometry.vertices;
|
|
|
|
- var faceVertexUvs = geometry.faceVertexUvs;
|
|
|
|
|
|
+ // Bind event handlers
|
|
|
|
+ this.onHit = this.onHit.bind(this);
|
|
|
|
+ this.onGripOpen = this.onGripOpen.bind(this);
|
|
|
|
+ this.onGripClose = this.onGripClose.bind(this);
|
|
|
|
+ },
|
|
|
|
|
|
- // Convert the faces into a custom format that supports more than 3 vertices
|
|
|
|
- geometry.faces.forEach((face) => {
|
|
|
|
- polygons.push({
|
|
|
|
- id: polygonId++,
|
|
|
|
- vertexIds: [face.a, face.b, face.c],
|
|
|
|
- centroid: face.centroid,
|
|
|
|
- normal: face.normal,
|
|
|
|
- neighbours: []
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
|
|
+ play: function () {
|
|
|
|
+ var el = this.el;
|
|
|
|
+ el.addEventListener('hit', this.onHit);
|
|
|
|
+ el.addEventListener('gripdown', this.onGripClose);
|
|
|
|
+ el.addEventListener('gripup', this.onGripOpen);
|
|
|
|
+ el.addEventListener('trackpaddown', this.onGripClose);
|
|
|
|
+ el.addEventListener('trackpadup', this.onGripOpen);
|
|
|
|
+ el.addEventListener('triggerdown', this.onGripClose);
|
|
|
|
+ el.addEventListener('triggerup', this.onGripOpen);
|
|
|
|
+ },
|
|
|
|
|
|
- var navigationMesh = {
|
|
|
|
- polygons: polygons,
|
|
|
|
- vertices: vertices,
|
|
|
|
- faceVertexUvs: faceVertexUvs
|
|
|
|
- };
|
|
|
|
|
|
+ pause: function () {
|
|
|
|
+ var el = this.el;
|
|
|
|
+ el.removeEventListener('hit', this.onHit);
|
|
|
|
+ el.removeEventListener('gripdown', this.onGripClose);
|
|
|
|
+ el.removeEventListener('gripup', this.onGripOpen);
|
|
|
|
+ el.removeEventListener('trackpaddown', this.onGripClose);
|
|
|
|
+ el.removeEventListener('trackpadup', this.onGripOpen);
|
|
|
|
+ el.removeEventListener('triggerdown', this.onGripClose);
|
|
|
|
+ el.removeEventListener('triggerup', this.onGripOpen);
|
|
|
|
+ },
|
|
|
|
|
|
- // Build a list of adjacent polygons
|
|
|
|
- polygons.forEach((polygon) => {
|
|
|
|
- buildPolygonNeighbours(polygon, navigationMesh);
|
|
|
|
- });
|
|
|
|
|
|
+ onGripClose: function (evt) {
|
|
|
|
+ this.grabbing = true;
|
|
|
|
+ },
|
|
|
|
|
|
- return navigationMesh;
|
|
|
|
-};
|
|
|
|
|
|
+ onGripOpen: function (evt) {
|
|
|
|
+ var hitEl = this.hitEl;
|
|
|
|
+ this.grabbing = false;
|
|
|
|
+ if (!hitEl) { return; }
|
|
|
|
+ hitEl.removeState(this.GRABBED_STATE);
|
|
|
|
+ this.hitEl = undefined;
|
|
|
|
+ this.physics.world.removeConstraint(this.constraint);
|
|
|
|
+ this.constraint = null;
|
|
|
|
+ },
|
|
|
|
|
|
-var buildNavigationMesh = function (geometry) {
|
|
|
|
- // Prepare geometry
|
|
|
|
- utils.computeCentroids(geometry);
|
|
|
|
- geometry.mergeVertices();
|
|
|
|
- return buildPolygonsFromGeometry(geometry);
|
|
|
|
|
|
+ onHit: function (evt) {
|
|
|
|
+ var hitEl = evt.detail.el;
|
|
|
|
+ // If the element is already grabbed (it could be grabbed by another controller).
|
|
|
|
+ // If the hand is not grabbing the element does not stick.
|
|
|
|
+ // If we're already grabbing something you can't grab again.
|
|
|
|
+ if (!hitEl || hitEl.is(this.GRABBED_STATE) || !this.grabbing || this.hitEl) { return; }
|
|
|
|
+ hitEl.addState(this.GRABBED_STATE);
|
|
|
|
+ this.hitEl = hitEl;
|
|
|
|
+ this.constraint = new CANNON.LockConstraint(this.el.body, hitEl.body);
|
|
|
|
+ this.physics.world.addConstraint(this.constraint);
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
-var getSharedVerticesInOrder = function (a, b) {
|
|
|
|
-
|
|
|
|
- var aList = a.vertexIds;
|
|
|
|
- var bList = b.vertexIds;
|
|
|
|
|
|
+},{}],104:[function(require,module,exports){
|
|
|
|
+var physics = require('aframe-physics-system');
|
|
|
|
|
|
- var sharedVertices = [];
|
|
|
|
|
|
+module.exports = {
|
|
|
|
+ 'checkpoint': require('./checkpoint'),
|
|
|
|
+ 'cube-env-map': require('./cube-env-map'),
|
|
|
|
+ 'grab': require('./grab'),
|
|
|
|
+ 'jump-ability': require('./jump-ability'),
|
|
|
|
+ 'kinematic-body': require('./kinematic-body'),
|
|
|
|
+ 'mesh-smooth': require('./mesh-smooth'),
|
|
|
|
+ 'sphere-collider': require('./sphere-collider'),
|
|
|
|
+ 'toggle-velocity': require('./toggle-velocity'),
|
|
|
|
|
|
- aList.forEach((vId) => {
|
|
|
|
- if (bList.includes(vId)) {
|
|
|
|
- sharedVertices.push(vId);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
+ registerAll: function (AFRAME) {
|
|
|
|
+ if (this._registered) return;
|
|
|
|
|
|
- if (sharedVertices.length < 2) return [];
|
|
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
|
|
- // console.log("TRYING aList:", aList, ", bList:", bList, ", sharedVertices:", sharedVertices);
|
|
|
|
|
|
+ physics.registerAll();
|
|
|
|
+ if (!AFRAME.components['checkpoint']) AFRAME.registerComponent('checkpoint', this['checkpoint']);
|
|
|
|
+ if (!AFRAME.components['cube-env-map']) AFRAME.registerComponent('cube-env-map', this['cube-env-map']);
|
|
|
|
+ if (!AFRAME.components['grab']) AFRAME.registerComponent('grab', this['grab']);
|
|
|
|
+ if (!AFRAME.components['jump-ability']) AFRAME.registerComponent('jump-ability', this['jump-ability']);
|
|
|
|
+ if (!AFRAME.components['kinematic-body']) AFRAME.registerComponent('kinematic-body', this['kinematic-body']);
|
|
|
|
+ if (!AFRAME.components['mesh-smooth']) AFRAME.registerComponent('mesh-smooth', this['mesh-smooth']);
|
|
|
|
+ if (!AFRAME.components['sphere-collider']) AFRAME.registerComponent('sphere-collider', this['sphere-collider']);
|
|
|
|
+ if (!AFRAME.components['toggle-velocity']) AFRAME.registerComponent('toggle-velocity', this['toggle-velocity']);
|
|
|
|
|
|
- if (sharedVertices.includes(aList[0]) && sharedVertices.includes(aList[aList.length - 1])) {
|
|
|
|
- // Vertices on both edges are bad, so shift them once to the left
|
|
|
|
- aList.push(aList.shift());
|
|
|
|
- }
|
|
|
|
|
|
+ this._registered = true;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- if (sharedVertices.includes(bList[0]) && sharedVertices.includes(bList[bList.length - 1])) {
|
|
|
|
- // Vertices on both edges are bad, so shift them once to the left
|
|
|
|
- bList.push(bList.shift());
|
|
|
|
- }
|
|
|
|
|
|
+},{"./checkpoint":101,"./cube-env-map":102,"./grab":103,"./jump-ability":105,"./kinematic-body":106,"./mesh-smooth":107,"./sphere-collider":108,"./toggle-velocity":109,"aframe-physics-system":11}],105:[function(require,module,exports){
|
|
|
|
+var ACCEL_G = -9.8, // m/s^2
|
|
|
|
+ EASING = -15; // m/s^2
|
|
|
|
|
|
- // Again!
|
|
|
|
- sharedVertices = [];
|
|
|
|
|
|
+/**
|
|
|
|
+ * Jump ability.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ dependencies: ['velocity'],
|
|
|
|
|
|
- aList.forEach((vId) => {
|
|
|
|
- if (bList.includes(vId)) {
|
|
|
|
- sharedVertices.push(vId);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
+ /* Schema
|
|
|
|
+ ——————————————————————————————————————————————*/
|
|
|
|
|
|
- return sharedVertices;
|
|
|
|
-};
|
|
|
|
|
|
+ schema: {
|
|
|
|
+ on: { default: 'keydown:Space gamepadbuttondown:0' },
|
|
|
|
+ playerHeight: { default: 1.764 },
|
|
|
|
+ maxJumps: { default: 1 },
|
|
|
|
+ distance: { default: 5 },
|
|
|
|
+ soundJump: { default: '' },
|
|
|
|
+ soundLand: { default: '' },
|
|
|
|
+ debug: { default: false }
|
|
|
|
+ },
|
|
|
|
|
|
-var groupNavMesh = function (navigationMesh) {
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.velocity = 0;
|
|
|
|
+ this.numJumps = 0;
|
|
|
|
|
|
- var saveObj = {};
|
|
|
|
|
|
+ var beginJump = this.beginJump.bind(this),
|
|
|
|
+ events = this.data.on.split(' ');
|
|
|
|
+ this.bindings = {};
|
|
|
|
+ for (var i = 0; i < events.length; i++) {
|
|
|
|
+ this.bindings[events[i]] = beginJump;
|
|
|
|
+ this.el.addEventListener(events[i], beginJump);
|
|
|
|
+ }
|
|
|
|
+ this.bindings.collide = this.onCollide.bind(this);
|
|
|
|
+ this.el.addEventListener('collide', this.bindings.collide);
|
|
|
|
+ },
|
|
|
|
|
|
- navigationMesh.vertices.forEach((v) => {
|
|
|
|
- v.x = utils.roundNumber(v.x, 2);
|
|
|
|
- v.y = utils.roundNumber(v.y, 2);
|
|
|
|
- v.z = utils.roundNumber(v.z, 2);
|
|
|
|
- });
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ for (var event in this.bindings) {
|
|
|
|
+ if (this.bindings.hasOwnProperty(event)) {
|
|
|
|
+ this.el.removeEventListener(event, this.bindings[event]);
|
|
|
|
+ delete this.bindings[event];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ this.el.removeEventListener('collide', this.bindings.collide);
|
|
|
|
+ delete this.bindings.collide;
|
|
|
|
+ },
|
|
|
|
|
|
- saveObj.vertices = navigationMesh.vertices;
|
|
|
|
|
|
+ beginJump: function () {
|
|
|
|
+ if (this.numJumps < this.data.maxJumps) {
|
|
|
|
+ var data = this.data,
|
|
|
|
+ initialVelocity = Math.sqrt(-2 * data.distance * (ACCEL_G + EASING)),
|
|
|
|
+ v = this.el.getAttribute('velocity');
|
|
|
|
+ this.el.setAttribute('velocity', {x: v.x, y: initialVelocity, z: v.z});
|
|
|
|
+ this.numJumps++;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- var groups = buildPolygonGroups(navigationMesh);
|
|
|
|
|
|
+ onCollide: function () {
|
|
|
|
+ this.numJumps = 0;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- saveObj.groups = [];
|
|
|
|
|
|
+},{}],106:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Kinematic body.
|
|
|
|
+ *
|
|
|
|
+ * Managed dynamic body, which moves but is not affected (directly) by the
|
|
|
|
+ * physics engine. This is not a true kinematic body, in the sense that we are
|
|
|
|
+ * letting the physics engine _compute_ collisions against it and selectively
|
|
|
|
+ * applying those collisions to the object. The physics engine does not decide
|
|
|
|
+ * the position/velocity/rotation of the element.
|
|
|
|
+ *
|
|
|
|
+ * Used for the camera object, because full physics simulation would create
|
|
|
|
+ * movement that feels unnatural to the player. Bipedal movement does not
|
|
|
|
+ * translate nicely to rigid body physics.
|
|
|
|
+ *
|
|
|
|
+ * See: http://www.learn-cocos2d.com/2013/08/physics-engine-platformer-terrible-idea/
|
|
|
|
+ * And: http://oxleygamedev.blogspot.com/2011/04/player-physics-part-2.html
|
|
|
|
+ */
|
|
|
|
+var CANNON = window.CANNON;
|
|
|
|
+var EPS = 0.000001;
|
|
|
|
|
|
- var findPolygonIndex = function (group, p) {
|
|
|
|
- for (var i = 0; i < group.length; i++) {
|
|
|
|
- if (p === group[i]) return i;
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
|
|
+module.exports = {
|
|
|
|
+ dependencies: ['velocity'],
|
|
|
|
|
|
- groups.forEach((group) => {
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Schema
|
|
|
|
+ */
|
|
|
|
|
|
- var newGroup = [];
|
|
|
|
|
|
+ schema: {
|
|
|
|
+ mass: { default: 5 },
|
|
|
|
+ radius: { default: 1.3 },
|
|
|
|
+ height: { default: 1.764 },
|
|
|
|
+ linearDamping: { default: 0.05 },
|
|
|
|
+ enableSlopes: { default: true }
|
|
|
|
+ },
|
|
|
|
|
|
- group.forEach((p) => {
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Lifecycle
|
|
|
|
+ */
|
|
|
|
|
|
- var neighbours = [];
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ this.system = this.el.sceneEl.systems.physics;
|
|
|
|
+ this.system.addBehavior(this, this.system.Phase.SIMULATE);
|
|
|
|
|
|
- p.neighbours.forEach((n) => {
|
|
|
|
- neighbours.push(findPolygonIndex(group, n));
|
|
|
|
- });
|
|
|
|
|
|
+ var el = this.el,
|
|
|
|
+ data = this.data,
|
|
|
|
+ position = (new CANNON.Vec3()).copy(el.getAttribute('position'));
|
|
|
|
|
|
|
|
+ this.body = new CANNON.Body({
|
|
|
|
+ material: this.system.material,
|
|
|
|
+ position: position,
|
|
|
|
+ mass: data.mass,
|
|
|
|
+ linearDamping: data.linearDamping,
|
|
|
|
+ fixedRotation: true
|
|
|
|
+ });
|
|
|
|
+ this.body.addShape(
|
|
|
|
+ new CANNON.Sphere(data.radius),
|
|
|
|
+ new CANNON.Vec3(0, data.radius - data.height, 0)
|
|
|
|
+ );
|
|
|
|
|
|
- // Build a portal list to each neighbour
|
|
|
|
- var portals = [];
|
|
|
|
- p.neighbours.forEach((n) => {
|
|
|
|
- portals.push(getSharedVerticesInOrder(p, n));
|
|
|
|
- });
|
|
|
|
|
|
+ this.body.el = this.el;
|
|
|
|
+ this.el.body = this.body;
|
|
|
|
+ this.system.addBody(this.body);
|
|
|
|
+ },
|
|
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.system.removeBody(this.body);
|
|
|
|
+ this.system.removeBehavior(this, this.system.Phase.SIMULATE);
|
|
|
|
+ delete this.el.body;
|
|
|
|
+ },
|
|
|
|
|
|
- p.centroid.x = utils.roundNumber(p.centroid.x, 2);
|
|
|
|
- p.centroid.y = utils.roundNumber(p.centroid.y, 2);
|
|
|
|
- p.centroid.z = utils.roundNumber(p.centroid.z, 2);
|
|
|
|
|
|
+ /*******************************************************************
|
|
|
|
+ * Tick
|
|
|
|
+ */
|
|
|
|
|
|
- newGroup.push({
|
|
|
|
- id: findPolygonIndex(group, p),
|
|
|
|
- neighbours: neighbours,
|
|
|
|
- vertexIds: p.vertexIds,
|
|
|
|
- centroid: p.centroid,
|
|
|
|
- portals: portals
|
|
|
|
- });
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Checks CANNON.World for collisions and attempts to apply them to the
|
|
|
|
+ * element automatically, in a player-friendly way.
|
|
|
|
+ *
|
|
|
|
+ * There's extra logic for horizontal surfaces here. The basic requirements:
|
|
|
|
+ * (1) Only apply gravity when not in contact with _any_ horizontal surface.
|
|
|
|
+ * (2) When moving, project the velocity against exactly one ground surface.
|
|
|
|
+ * If in contact with two ground surfaces (e.g. ground + ramp), choose
|
|
|
|
+ * the one that collides with current velocity, if any.
|
|
|
|
+ */
|
|
|
|
+ step: (function () {
|
|
|
|
+ var velocity = new THREE.Vector3(),
|
|
|
|
+ normalizedVelocity = new THREE.Vector3(),
|
|
|
|
+ currentSurfaceNormal = new THREE.Vector3(),
|
|
|
|
+ groundNormal = new THREE.Vector3();
|
|
|
|
|
|
- });
|
|
|
|
|
|
+ return function (t, dt) {
|
|
|
|
+ if (!dt) return;
|
|
|
|
|
|
- saveObj.groups.push(newGroup);
|
|
|
|
- });
|
|
|
|
|
|
+ var body = this.body,
|
|
|
|
+ data = this.data,
|
|
|
|
+ didCollide = false,
|
|
|
|
+ height, groundHeight = -Infinity,
|
|
|
|
+ groundBody;
|
|
|
|
|
|
- return saveObj;
|
|
|
|
-};
|
|
|
|
|
|
+ dt = Math.min(dt, this.system.data.maxInterval * 1000);
|
|
|
|
|
|
-var zoneNodes = {};
|
|
|
|
|
|
+ groundNormal.set(0, 0, 0);
|
|
|
|
+ velocity.copy(this.el.getAttribute('velocity'));
|
|
|
|
+ body.velocity.copy(velocity);
|
|
|
|
+ body.position.copy(this.el.getAttribute('position'));
|
|
|
|
|
|
-module.exports = {
|
|
|
|
- buildNodes: function (geometry) {
|
|
|
|
- var navigationMesh = buildNavigationMesh(geometry);
|
|
|
|
|
|
+ for (var i = 0, contact; (contact = this.system.world.contacts[i]); i++) {
|
|
|
|
+ // 1. Find any collisions involving this element. Get the contact
|
|
|
|
+ // normal, and make sure it's oriented _out_ of the other object and
|
|
|
|
+ // enabled (body.collisionReponse is true for both bodies)
|
|
|
|
+ if (!contact.enabled) { continue; }
|
|
|
|
+ if (body.id === contact.bi.id) {
|
|
|
|
+ contact.ni.negate(currentSurfaceNormal);
|
|
|
|
+ } else if (body.id === contact.bj.id) {
|
|
|
|
+ currentSurfaceNormal.copy(contact.ni);
|
|
|
|
+ } else {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- var zoneNodes = groupNavMesh(navigationMesh);
|
|
|
|
|
|
+ didCollide = body.velocity.dot(currentSurfaceNormal) < -EPS;
|
|
|
|
+ if (didCollide && currentSurfaceNormal.y <= 0.5) {
|
|
|
|
+ // 2. If current trajectory attempts to move _through_ another
|
|
|
|
+ // object, project the velocity against the collision plane to
|
|
|
|
+ // prevent passing through.
|
|
|
|
+ velocity = velocity.projectOnPlane(currentSurfaceNormal);
|
|
|
|
+ } else if (currentSurfaceNormal.y > 0.5) {
|
|
|
|
+ // 3. If in contact with something roughly horizontal (+/- 45º) then
|
|
|
|
+ // consider that the current ground. Only the highest qualifying
|
|
|
|
+ // ground is retained.
|
|
|
|
+ height = body.id === contact.bi.id
|
|
|
|
+ ? Math.abs(contact.rj.y + contact.bj.position.y)
|
|
|
|
+ : Math.abs(contact.ri.y + contact.bi.position.y);
|
|
|
|
+ if (height > groundHeight) {
|
|
|
|
+ groundHeight = height;
|
|
|
|
+ groundNormal.copy(currentSurfaceNormal);
|
|
|
|
+ groundBody = body.id === contact.bi.id ? contact.bj : contact.bi;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- return zoneNodes;
|
|
|
|
- },
|
|
|
|
- setZoneData: function (zone, data) {
|
|
|
|
- zoneNodes[zone] = data;
|
|
|
|
- },
|
|
|
|
- getGroup: function (zone, position) {
|
|
|
|
|
|
+ normalizedVelocity.copy(velocity).normalize();
|
|
|
|
+ if (groundBody && normalizedVelocity.y < 0.5) {
|
|
|
|
+ if (!data.enableSlopes) {
|
|
|
|
+ groundNormal.set(0, 1, 0);
|
|
|
|
+ } else if (groundNormal.y < 1 - EPS) {
|
|
|
|
+ groundNormal.copy(this.raycastToGround(groundBody, groundNormal));
|
|
|
|
+ }
|
|
|
|
|
|
- if (!zoneNodes[zone]) return null;
|
|
|
|
|
|
+ // 4. Project trajectory onto the top-most ground object, unless
|
|
|
|
+ // trajectory is > 45º.
|
|
|
|
+ velocity = velocity.projectOnPlane(groundNormal);
|
|
|
|
+ } else {
|
|
|
|
+ // 5. If not in contact with anything horizontal, apply world gravity.
|
|
|
|
+ // TODO - Why is the 4x scalar necessary.
|
|
|
|
+ velocity.add(this.system.world.gravity.scale(dt * 4.0 / 1000));
|
|
|
|
+ }
|
|
|
|
|
|
- var closestNodeGroup = null;
|
|
|
|
|
|
+ // 6. If the ground surface has a velocity, apply it directly to current
|
|
|
|
+ // position, not velocity, to preserve relative velocity.
|
|
|
|
+ if (groundBody && groundBody.el && groundBody.el.components.velocity) {
|
|
|
|
+ var groundVelocity = groundBody.el.getAttribute('velocity');
|
|
|
|
+ body.position.copy({
|
|
|
|
+ x: body.position.x + groundVelocity.x * dt / 1000,
|
|
|
|
+ y: body.position.y + groundVelocity.y * dt / 1000,
|
|
|
|
+ z: body.position.z + groundVelocity.z * dt / 1000
|
|
|
|
+ });
|
|
|
|
+ this.el.setAttribute('position', body.position);
|
|
|
|
+ }
|
|
|
|
|
|
- var distance = Math.pow(50, 2);
|
|
|
|
|
|
+ body.velocity.copy(velocity);
|
|
|
|
+ this.el.setAttribute('velocity', velocity);
|
|
|
|
+ };
|
|
|
|
+ }()),
|
|
|
|
|
|
- zoneNodes[zone].groups.forEach((group, index) => {
|
|
|
|
- group.forEach((node) => {
|
|
|
|
- var measuredDistance = utils.distanceToSquared(node.centroid, position);
|
|
|
|
- if (measuredDistance < distance) {
|
|
|
|
- closestNodeGroup = index;
|
|
|
|
- distance = measuredDistance;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
|
|
+ /**
|
|
|
|
+ * When walking on complex surfaces (trimeshes, borders between two shapes),
|
|
|
|
+ * the collision normals returned for the player sphere can be very
|
|
|
|
+ * inconsistent. To address this, raycast straight down, find the collision
|
|
|
|
+ * normal, and return whichever normal is more vertical.
|
|
|
|
+ * @param {CANNON.Body} groundBody
|
|
|
|
+ * @param {CANNON.Vec3} groundNormal
|
|
|
|
+ * @return {CANNON.Vec3}
|
|
|
|
+ */
|
|
|
|
+ raycastToGround: function (groundBody, groundNormal) {
|
|
|
|
+ var ray,
|
|
|
|
+ hitNormal,
|
|
|
|
+ vFrom = this.body.position,
|
|
|
|
+ vTo = this.body.position.clone();
|
|
|
|
|
|
- return closestNodeGroup;
|
|
|
|
- },
|
|
|
|
- getRandomNode: function (zone, group, nearPosition, nearRange) {
|
|
|
|
|
|
+ vTo.y -= this.data.height;
|
|
|
|
+ ray = new CANNON.Ray(vFrom, vTo);
|
|
|
|
+ ray._updateDirection(); // TODO - Report bug.
|
|
|
|
+ ray.intersectBody(groundBody);
|
|
|
|
|
|
- if (!zoneNodes[zone]) return new THREE.Vector3();
|
|
|
|
|
|
+ if (!ray.hasHit) return groundNormal;
|
|
|
|
|
|
- nearPosition = nearPosition || null;
|
|
|
|
- nearRange = nearRange || 0;
|
|
|
|
|
|
+ // Compare ABS, in case we're projecting against the inside of the face.
|
|
|
|
+ hitNormal = ray.result.hitNormalWorld;
|
|
|
|
+ return Math.abs(hitNormal.y) > Math.abs(groundNormal.y) ? hitNormal : groundNormal;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- var candidates = [];
|
|
|
|
|
|
+},{}],107:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Apply this component to models that looks "blocky", to have Three.js compute
|
|
|
|
+ * vertex normals on the fly for a "smoother" look.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ init: function () {
|
|
|
|
+ this.el.addEventListener('model-loaded', function (e) {
|
|
|
|
+ e.detail.model.traverse(function (node) {
|
|
|
|
+ if (node.isMesh) node.geometry.computeVertexNormals();
|
|
|
|
+ });
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
|
|
- var polygons = zoneNodes[zone].groups[group];
|
|
|
|
|
|
+},{}],108:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Based on aframe/examples/showcase/tracked-controls.
|
|
|
|
+ *
|
|
|
|
+ * Implement bounding sphere collision detection for entities with a mesh.
|
|
|
|
+ * Sets the specified state on the intersected entities.
|
|
|
|
+ *
|
|
|
|
+ * @property {string} objects - Selector of the entities to test for collision.
|
|
|
|
+ * @property {string} state - State to set on collided entities.
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ objects: {default: ''},
|
|
|
|
+ state: {default: 'collided'},
|
|
|
|
+ radius: {default: 0.05},
|
|
|
|
+ watch: {default: true}
|
|
|
|
+ },
|
|
|
|
|
|
- polygons.forEach((p) => {
|
|
|
|
- if (nearPosition && nearRange) {
|
|
|
|
- if (utils.distanceToSquared(nearPosition, p.centroid) < nearRange * nearRange) {
|
|
|
|
- candidates.push(p.centroid);
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- candidates.push(p.centroid);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ /** @type {MutationObserver} */
|
|
|
|
+ this.observer = null;
|
|
|
|
+ /** @type {Array<Element>} Elements to watch for collisions. */
|
|
|
|
+ this.els = [];
|
|
|
|
+ /** @type {Array<Element>} Elements currently in collision state. */
|
|
|
|
+ this.collisions = [];
|
|
|
|
|
|
- return utils.sample(candidates) || new THREE.Vector3();
|
|
|
|
- },
|
|
|
|
- getClosestNode: function (position, zone, group, checkPolygon = false) {
|
|
|
|
- const nodes = zoneNodes[zone].groups[group];
|
|
|
|
- const vertices = zoneNodes[zone].vertices;
|
|
|
|
- let closestNode = null;
|
|
|
|
- let closestDistance = Infinity;
|
|
|
|
|
|
+ this.handleHit = this.handleHit.bind(this);
|
|
|
|
+ this.handleHitEnd = this.handleHitEnd.bind(this);
|
|
|
|
+ },
|
|
|
|
|
|
- nodes.forEach((node) => {
|
|
|
|
- const distance = utils.distanceToSquared(node.centroid, position);
|
|
|
|
- if (distance < closestDistance
|
|
|
|
- && (!checkPolygon || utils.isVectorInPolygon(position, node, vertices))) {
|
|
|
|
- closestNode = node;
|
|
|
|
- closestDistance = distance;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.pause();
|
|
|
|
+ },
|
|
|
|
|
|
- return closestNode;
|
|
|
|
- },
|
|
|
|
- findPath: function (startPosition, targetPosition, zone, group) {
|
|
|
|
- const nodes = zoneNodes[zone].groups[group];
|
|
|
|
- const vertices = zoneNodes[zone].vertices;
|
|
|
|
|
|
+ play: function () {
|
|
|
|
+ var sceneEl = this.el.sceneEl;
|
|
|
|
|
|
- const closestNode = this.getClosestNode(startPosition, zone, group);
|
|
|
|
- const farthestNode = this.getClosestNode(targetPosition, zone, group, true);
|
|
|
|
|
|
+ if (this.data.watch) {
|
|
|
|
+ this.observer = new MutationObserver(this.update.bind(this, null));
|
|
|
|
+ this.observer.observe(sceneEl, {childList: true, subtree: true});
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- // If we can't find any node, just go straight to the target
|
|
|
|
- if (!closestNode || !farthestNode) {
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
|
|
+ pause: function () {
|
|
|
|
+ if (this.observer) {
|
|
|
|
+ this.observer.disconnect();
|
|
|
|
+ this.observer = null;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- const paths = AStar.search(nodes, closestNode, farthestNode);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Update list of entities to test for collision.
|
|
|
|
+ */
|
|
|
|
+ update: function () {
|
|
|
|
+ var data = this.data;
|
|
|
|
+ var objectEls;
|
|
|
|
|
|
- const getPortalFromTo = function (a, b) {
|
|
|
|
- for (var i = 0; i < a.neighbours.length; i++) {
|
|
|
|
- if (a.neighbours[i] === b.id) {
|
|
|
|
- return a.portals[i];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
|
|
+ // Push entities into list of els to intersect.
|
|
|
|
+ if (data.objects) {
|
|
|
|
+ objectEls = this.el.sceneEl.querySelectorAll(data.objects);
|
|
|
|
+ } else {
|
|
|
|
+ // If objects not defined, intersect with everything.
|
|
|
|
+ objectEls = this.el.sceneEl.children;
|
|
|
|
+ }
|
|
|
|
+ // Convert from NodeList to Array
|
|
|
|
+ this.els = Array.prototype.slice.call(objectEls);
|
|
|
|
+ },
|
|
|
|
|
|
- // We have the corridor, now pull the rope.
|
|
|
|
- const channel = new Channel();
|
|
|
|
- channel.push(startPosition);
|
|
|
|
- for (let i = 0; i < paths.length; i++) {
|
|
|
|
- const polygon = paths[i];
|
|
|
|
- const nextPolygon = paths[i + 1];
|
|
|
|
|
|
+ tick: (function () {
|
|
|
|
+ var position = new THREE.Vector3(),
|
|
|
|
+ meshPosition = new THREE.Vector3(),
|
|
|
|
+ meshScale = new THREE.Vector3(),
|
|
|
|
+ colliderScale = new THREE.Vector3(),
|
|
|
|
+ distanceMap = new Map();
|
|
|
|
+ return function () {
|
|
|
|
+ var el = this.el,
|
|
|
|
+ data = this.data,
|
|
|
|
+ mesh = el.getObject3D('mesh'),
|
|
|
|
+ colliderRadius,
|
|
|
|
+ collisions = [];
|
|
|
|
|
|
- if (nextPolygon) {
|
|
|
|
- const portals = getPortalFromTo(polygon, nextPolygon);
|
|
|
|
- channel.push(
|
|
|
|
- vertices[portals[0]],
|
|
|
|
- vertices[portals[1]]
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- channel.push(targetPosition);
|
|
|
|
- channel.stringPull();
|
|
|
|
|
|
+ if (!mesh) { return; }
|
|
|
|
|
|
- // Return the path, omitting first position (which is already known).
|
|
|
|
- const path = channel.path.map((c) => new THREE.Vector3(c.x, c.y, c.z));
|
|
|
|
- path.shift();
|
|
|
|
- return path;
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ distanceMap.clear();
|
|
|
|
+ position.copy(el.object3D.getWorldPosition());
|
|
|
|
+ el.object3D.getWorldScale(colliderScale);
|
|
|
|
+ colliderRadius = data.radius * scaleFactor(colliderScale);
|
|
|
|
+ // Update collision list.
|
|
|
|
+ this.els.forEach(intersect);
|
|
|
|
|
|
-},{"./AStar":116,"./Channel":118,"./utils":120}],120:[function(require,module,exports){
|
|
|
|
-class Utils {
|
|
|
|
|
|
+ // Emit events and add collision states, in order of distance.
|
|
|
|
+ collisions
|
|
|
|
+ .sort(function (a, b) {
|
|
|
|
+ return distanceMap.get(a) > distanceMap.get(b) ? 1 : -1;
|
|
|
|
+ })
|
|
|
|
+ .forEach(this.handleHit);
|
|
|
|
|
|
- static computeCentroids (geometry) {
|
|
|
|
- var f, fl, face;
|
|
|
|
|
|
+ // Remove collision state from current element.
|
|
|
|
+ if (collisions.length === 0) { el.emit('hit', {el: null}); }
|
|
|
|
|
|
- for ( f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
|
|
|
|
|
|
+ // Remove collision state from other elements.
|
|
|
|
+ this.collisions.filter(function (el) {
|
|
|
|
+ return !distanceMap.has(el);
|
|
|
|
+ }).forEach(this.handleHitEnd);
|
|
|
|
|
|
- face = geometry.faces[ f ];
|
|
|
|
- face.centroid = new THREE.Vector3( 0, 0, 0 );
|
|
|
|
|
|
+ // Store new collisions
|
|
|
|
+ this.collisions = collisions;
|
|
|
|
|
|
- face.centroid.add( geometry.vertices[ face.a ] );
|
|
|
|
- face.centroid.add( geometry.vertices[ face.b ] );
|
|
|
|
- face.centroid.add( geometry.vertices[ face.c ] );
|
|
|
|
- face.centroid.divideScalar( 3 );
|
|
|
|
|
|
+ // Bounding sphere collision detection
|
|
|
|
+ function intersect (el) {
|
|
|
|
+ var radius, mesh, distance, box, extent, size;
|
|
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ if (!el.isEntity) { return; }
|
|
|
|
|
|
- static roundNumber (number, decimals) {
|
|
|
|
- var newnumber = Number(number + '').toFixed(parseInt(decimals));
|
|
|
|
- return parseFloat(newnumber);
|
|
|
|
- }
|
|
|
|
|
|
+ mesh = el.getObject3D('mesh');
|
|
|
|
|
|
- static sample (list) {
|
|
|
|
- return list[Math.floor(Math.random() * list.length)];
|
|
|
|
- }
|
|
|
|
|
|
+ if (!mesh) { return; }
|
|
|
|
|
|
- static mergeVertexIds (aList, bList) {
|
|
|
|
|
|
+ box = new THREE.Box3().setFromObject(mesh);
|
|
|
|
+ size = box.getSize();
|
|
|
|
+ extent = Math.max(size.x, size.y, size.z) / 2;
|
|
|
|
+ radius = Math.sqrt(2 * extent * extent);
|
|
|
|
+ box.getCenter(meshPosition);
|
|
|
|
|
|
- var sharedVertices = [];
|
|
|
|
|
|
+ if (!radius) { return; }
|
|
|
|
|
|
- aList.forEach((vID) => {
|
|
|
|
- if (bList.indexOf(vID) >= 0) {
|
|
|
|
- sharedVertices.push(vID);
|
|
|
|
|
|
+ distance = position.distanceTo(meshPosition);
|
|
|
|
+ if (distance < radius + colliderRadius) {
|
|
|
|
+ collisions.push(el);
|
|
|
|
+ distanceMap.set(el, distance);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- });
|
|
|
|
|
|
+ // use max of scale factors to maintain bounding sphere collision
|
|
|
|
+ function scaleFactor (scaleVec) {
|
|
|
|
+ return Math.max.apply(null, scaleVec.toArray());
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ })(),
|
|
|
|
|
|
- if (sharedVertices.length < 2) return [];
|
|
|
|
|
|
+ handleHit: function (targetEl) {
|
|
|
|
+ targetEl.emit('hit');
|
|
|
|
+ targetEl.addState(this.data.state);
|
|
|
|
+ this.el.emit('hit', {el: targetEl});
|
|
|
|
+ },
|
|
|
|
+ handleHitEnd: function (targetEl) {
|
|
|
|
+ targetEl.emit('hitend');
|
|
|
|
+ targetEl.removeState(this.data.state);
|
|
|
|
+ this.el.emit('hitend', {el: targetEl});
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- if (sharedVertices.includes(aList[0]) && sharedVertices.includes(aList[aList.length - 1])) {
|
|
|
|
- // Vertices on both edges are bad, so shift them once to the left
|
|
|
|
- aList.push(aList.shift());
|
|
|
|
- }
|
|
|
|
|
|
+},{}],109:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Toggle velocity.
|
|
|
|
+ *
|
|
|
|
+ * Moves an object back and forth along an axis, within a min/max extent.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ dependencies: ['velocity'],
|
|
|
|
+ schema: {
|
|
|
|
+ axis: { default: 'x', oneOf: ['x', 'y', 'z'] },
|
|
|
|
+ min: { default: 0 },
|
|
|
|
+ max: { default: 0 },
|
|
|
|
+ speed: { default: 1 }
|
|
|
|
+ },
|
|
|
|
+ init: function () {
|
|
|
|
+ var velocity = {x: 0, y: 0, z: 0};
|
|
|
|
+ velocity[this.data.axis] = this.data.speed;
|
|
|
|
+ this.el.setAttribute('velocity', velocity);
|
|
|
|
|
|
- if (sharedVertices.includes(bList[0]) && sharedVertices.includes(bList[bList.length - 1])) {
|
|
|
|
- // Vertices on both edges are bad, so shift them once to the left
|
|
|
|
- bList.push(bList.shift());
|
|
|
|
|
|
+ if (this.el.sceneEl.addBehavior) this.el.sceneEl.addBehavior(this);
|
|
|
|
+ },
|
|
|
|
+ remove: function () {},
|
|
|
|
+ update: function () { this.tick(); },
|
|
|
|
+ tick: function () {
|
|
|
|
+ var data = this.data,
|
|
|
|
+ velocity = this.el.getAttribute('velocity'),
|
|
|
|
+ position = this.el.getAttribute('position');
|
|
|
|
+ if (velocity[data.axis] > 0 && position[data.axis] > data.max) {
|
|
|
|
+ velocity[data.axis] = -data.speed;
|
|
|
|
+ this.el.setAttribute('velocity', velocity);
|
|
|
|
+ } else if (velocity[data.axis] < 0 && position[data.axis] < data.min) {
|
|
|
|
+ velocity[data.axis] = data.speed;
|
|
|
|
+ this.el.setAttribute('velocity', velocity);
|
|
}
|
|
}
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
|
|
- // Again!
|
|
|
|
- sharedVertices = [];
|
|
|
|
-
|
|
|
|
- aList.forEach((vId) => {
|
|
|
|
- if (bList.includes(vId)) {
|
|
|
|
- sharedVertices.push(vId);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
+},{}],110:[function(require,module,exports){
|
|
|
|
+module.exports = {
|
|
|
|
+ 'nav-mesh': require('./nav-mesh'),
|
|
|
|
+ 'nav-controller': require('./nav-controller'),
|
|
|
|
+ 'system': require('./system'),
|
|
|
|
|
|
- var clockwiseMostSharedVertex = sharedVertices[1];
|
|
|
|
- var counterClockwiseMostSharedVertex = sharedVertices[0];
|
|
|
|
|
|
+ registerAll: function (AFRAME) {
|
|
|
|
+ if (this._registered) return;
|
|
|
|
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
|
|
- var cList = aList.slice();
|
|
|
|
- while (cList[0] !== clockwiseMostSharedVertex) {
|
|
|
|
- cList.push(cList.shift());
|
|
|
|
|
|
+ if (!AFRAME.components['nav-mesh']) {
|
|
|
|
+ AFRAME.registerComponent('nav-mesh', this['nav-mesh']);
|
|
}
|
|
}
|
|
|
|
|
|
- var c = 0;
|
|
|
|
-
|
|
|
|
- var temp = bList.slice();
|
|
|
|
- while (temp[0] !== counterClockwiseMostSharedVertex) {
|
|
|
|
- temp.push(temp.shift());
|
|
|
|
-
|
|
|
|
- if (c++ > 10) throw new Error('Unexpected state');
|
|
|
|
|
|
+ if (!AFRAME.components['nav-controller']) {
|
|
|
|
+ AFRAME.registerComponent('nav-controller', this['nav-controller']);
|
|
}
|
|
}
|
|
|
|
|
|
- // Shave
|
|
|
|
- temp.shift();
|
|
|
|
- temp.pop();
|
|
|
|
-
|
|
|
|
- cList = cList.concat(temp);
|
|
|
|
|
|
+ if (!AFRAME.systems.nav) {
|
|
|
|
+ AFRAME.registerSystem('nav', this.system);
|
|
|
|
+ }
|
|
|
|
|
|
- return cList;
|
|
|
|
|
|
+ this._registered = true;
|
|
}
|
|
}
|
|
|
|
+};
|
|
|
|
|
|
- static setPolygonCentroid (polygon, navigationMesh) {
|
|
|
|
- var sum = new THREE.Vector3();
|
|
|
|
-
|
|
|
|
- var vertices = navigationMesh.vertices;
|
|
|
|
|
|
+},{"./nav-controller":111,"./nav-mesh":112,"./system":113}],111:[function(require,module,exports){
|
|
|
|
+module.exports = {
|
|
|
|
+ schema: {
|
|
|
|
+ destination: {type: 'vec3'},
|
|
|
|
+ active: {default: false},
|
|
|
|
+ speed: {default: 2}
|
|
|
|
+ },
|
|
|
|
+ init: function () {
|
|
|
|
+ this.system = this.el.sceneEl.systems.nav;
|
|
|
|
+ this.system.addController(this);
|
|
|
|
+ this.path = [];
|
|
|
|
+ this.raycaster = new THREE.Raycaster();
|
|
|
|
+ },
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.system.removeController(this);
|
|
|
|
+ },
|
|
|
|
+ update: function () {
|
|
|
|
+ this.path.length = 0;
|
|
|
|
+ },
|
|
|
|
+ tick: (function () {
|
|
|
|
+ var vDest = new THREE.Vector3();
|
|
|
|
+ var vDelta = new THREE.Vector3();
|
|
|
|
+ var vNext = new THREE.Vector3();
|
|
|
|
|
|
- polygon.vertexIds.forEach((vId) => {
|
|
|
|
- sum.add(vertices[vId]);
|
|
|
|
- });
|
|
|
|
|
|
+ return function (t, dt) {
|
|
|
|
+ var el = this.el;
|
|
|
|
+ var data = this.data;
|
|
|
|
+ var raycaster = this.raycaster;
|
|
|
|
+ var speed = data.speed * dt / 1000;
|
|
|
|
|
|
- sum.divideScalar(polygon.vertexIds.length);
|
|
|
|
|
|
+ if (!data.active) return;
|
|
|
|
|
|
- polygon.centroid.copy(sum);
|
|
|
|
- }
|
|
|
|
|
|
+ // Use PatrolJS pathfinding system to get shortest path to target.
|
|
|
|
+ if (!this.path.length) {
|
|
|
|
+ this.path = this.system.getPath(this.el.object3D, vDest.copy(data.destination));
|
|
|
|
+ this.path = this.path || [];
|
|
|
|
+ el.emit('nav-start');
|
|
|
|
+ }
|
|
|
|
|
|
- static cleanPolygon (polygon, navigationMesh) {
|
|
|
|
|
|
+ // If no path is found, exit.
|
|
|
|
+ if (!this.path.length) {
|
|
|
|
+ console.warn('[nav] Unable to find path to %o.', data.destination);
|
|
|
|
+ this.el.setAttribute('nav-controller', {active: false});
|
|
|
|
+ el.emit('nav-end');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- var newVertexIds = [];
|
|
|
|
|
|
+ // Current segment is a vector from current position to next waypoint.
|
|
|
|
+ var vCurrent = el.object3D.position;
|
|
|
|
+ var vWaypoint = this.path[0];
|
|
|
|
+ vDelta.subVectors(vWaypoint, vCurrent);
|
|
|
|
|
|
- var vertices = navigationMesh.vertices;
|
|
|
|
|
|
+ var distance = vDelta.length();
|
|
|
|
+ var gazeTarget;
|
|
|
|
|
|
- for (var i = 0; i < polygon.vertexIds.length; i++) {
|
|
|
|
|
|
+ if (distance < speed) {
|
|
|
|
+ // If <1 step from current waypoint, discard it and move toward next.
|
|
|
|
+ this.path.shift();
|
|
|
|
|
|
- var vertex = vertices[polygon.vertexIds[i]];
|
|
|
|
|
|
+ // After discarding the last waypoint, exit pathfinding.
|
|
|
|
+ if (!this.path.length) {
|
|
|
|
+ this.el.setAttribute('nav-controller', {active: false});
|
|
|
|
+ el.emit('nav-end');
|
|
|
|
+ return;
|
|
|
|
+ } else {
|
|
|
|
+ gazeTarget = this.path[0];
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // If still far away from next waypoint, find next position for
|
|
|
|
+ // the current frame.
|
|
|
|
+ vNext.copy(vDelta.setLength(speed)).add(vCurrent);
|
|
|
|
+ gazeTarget = vWaypoint;
|
|
|
|
+ }
|
|
|
|
|
|
- var nextVertexId, previousVertexId;
|
|
|
|
- var nextVertex, previousVertex;
|
|
|
|
|
|
+ // Look at the next waypoint.
|
|
|
|
+ gazeTarget.y = vCurrent.y;
|
|
|
|
+ el.object3D.lookAt(gazeTarget);
|
|
|
|
|
|
- // console.log("nextVertex: ", nextVertex);
|
|
|
|
|
|
+ // Raycast against the nav mesh, to keep the controller moving along the
|
|
|
|
+ // ground, not traveling in a straight line from higher to lower waypoints.
|
|
|
|
+ raycaster.ray.origin.copy(vNext);
|
|
|
|
+ raycaster.ray.origin.y += 1.5;
|
|
|
|
+ raycaster.ray.direction.y = -1;
|
|
|
|
+ var intersections = raycaster.intersectObject(this.system.getNavMesh());
|
|
|
|
|
|
- if (i === 0) {
|
|
|
|
- nextVertexId = polygon.vertexIds[1];
|
|
|
|
- previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 1];
|
|
|
|
- } else if (i === polygon.vertexIds.length - 1) {
|
|
|
|
- nextVertexId = polygon.vertexIds[0];
|
|
|
|
- previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 2];
|
|
|
|
|
|
+ if (!intersections.length) {
|
|
|
|
+ // Raycasting failed. Step toward the waypoint and hope for the best.
|
|
|
|
+ vCurrent.copy(vNext);
|
|
} else {
|
|
} else {
|
|
- nextVertexId = polygon.vertexIds[i + 1];
|
|
|
|
- previousVertexId = polygon.vertexIds[i - 1];
|
|
|
|
|
|
+ // Re-project next position onto nav mesh.
|
|
|
|
+ vDelta.subVectors(intersections[0].point, vCurrent);
|
|
|
|
+ vCurrent.add(vDelta.setLength(speed));
|
|
}
|
|
}
|
|
|
|
|
|
- nextVertex = vertices[nextVertexId];
|
|
|
|
- previousVertex = vertices[previousVertexId];
|
|
|
|
|
|
+ };
|
|
|
|
+ }())
|
|
|
|
+};
|
|
|
|
|
|
- var a = nextVertex.clone().sub(vertex);
|
|
|
|
- var b = previousVertex.clone().sub(vertex);
|
|
|
|
|
|
+},{}],112:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * nav-mesh
|
|
|
|
+ *
|
|
|
|
+ * Waits for a mesh to be loaded on the current entity, then sets it as the
|
|
|
|
+ * nav mesh in the pathfinding system.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ init: function () {
|
|
|
|
+ this.system = this.el.sceneEl.systems.nav;
|
|
|
|
+ this.loadNavMesh();
|
|
|
|
+ this.el.addEventListener('model-loaded', this.loadNavMesh.bind(this));
|
|
|
|
+ },
|
|
|
|
|
|
- var angle = a.angleTo(b);
|
|
|
|
|
|
+ loadNavMesh: function () {
|
|
|
|
+ var object = this.el.getObject3D('mesh');
|
|
|
|
|
|
- // console.log(angle);
|
|
|
|
|
|
+ if (!object) return;
|
|
|
|
|
|
- if (angle > Math.PI - 0.01 && angle < Math.PI + 0.01) {
|
|
|
|
- // Unneccesary vertex
|
|
|
|
- // console.log("Unneccesary vertex: ", polygon.vertexIds[i]);
|
|
|
|
- // console.log("Angle between "+previousVertexId+", "+polygon.vertexIds[i]+" "+nextVertexId+" was: ", angle);
|
|
|
|
|
|
+ var navMesh;
|
|
|
|
+ object.traverse(function (node) {
|
|
|
|
+ if (node.isMesh) navMesh = node;
|
|
|
|
+ });
|
|
|
|
|
|
|
|
+ if (!navMesh) return;
|
|
|
|
|
|
- // Remove the neighbours who had this vertex
|
|
|
|
- var goodNeighbours = [];
|
|
|
|
- polygon.neighbours.forEach((neighbour) => {
|
|
|
|
- if (!neighbour.vertexIds.includes(polygon.vertexIds[i])) {
|
|
|
|
- goodNeighbours.push(neighbour);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- polygon.neighbours = goodNeighbours;
|
|
|
|
|
|
+ this.system.setNavMesh(navMesh);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
|
|
+},{}],113:[function(require,module,exports){
|
|
|
|
+var Path = require('three-pathfinding');
|
|
|
|
|
|
- // TODO cleanup the list of vertices and rebuild vertexIds for all polygons
|
|
|
|
- } else {
|
|
|
|
- newVertexIds.push(polygon.vertexIds[i]);
|
|
|
|
- }
|
|
|
|
|
|
+/**
|
|
|
|
+ * nav
|
|
|
|
+ *
|
|
|
|
+ * Pathfinding system, using PatrolJS.
|
|
|
|
+ */
|
|
|
|
+module.exports = {
|
|
|
|
+ init: function () {
|
|
|
|
+ this.navMesh = null;
|
|
|
|
+ this.nodes = null;
|
|
|
|
+ this.controllers = new Set();
|
|
|
|
+ },
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @param {THREE.Mesh} mesh
|
|
|
|
+ */
|
|
|
|
+ setNavMesh: function (mesh) {
|
|
|
|
+ var geometry = mesh.geometry.isBufferGeometry
|
|
|
|
+ ? new THREE.Geometry().fromBufferGeometry(mesh.geometry)
|
|
|
|
+ : mesh.geometry;
|
|
|
|
+ this.navMesh = new THREE.Mesh(geometry);
|
|
|
|
+ this.nodes = Path.buildNodes(this.navMesh.geometry);
|
|
|
|
+ Path.setZoneData('level', this.nodes);
|
|
|
|
+ },
|
|
|
|
|
|
- // console.log("New vertexIds: ", newVertexIds);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @return {THREE.Mesh}
|
|
|
|
+ */
|
|
|
|
+ getNavMesh: function () {
|
|
|
|
+ return this.navMesh;
|
|
|
|
+ },
|
|
|
|
|
|
- polygon.vertexIds = newVertexIds;
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @param {NavController} ctrl
|
|
|
|
+ */
|
|
|
|
+ addController: function (ctrl) {
|
|
|
|
+ this.controllers.add(ctrl);
|
|
|
|
+ },
|
|
|
|
|
|
- setPolygonCentroid(polygon, navigationMesh);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @param {NavController} ctrl
|
|
|
|
+ */
|
|
|
|
+ removeController: function (ctrl) {
|
|
|
|
+ this.controllers.remove(ctrl);
|
|
|
|
+ },
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @param {NavController} ctrl
|
|
|
|
+ * @param {THREE.Vector3} target
|
|
|
|
+ * @return {Array<THREE.Vector3>}
|
|
|
|
+ */
|
|
|
|
+ getPath: function (ctrl, target) {
|
|
|
|
+ var start = ctrl.el.object3D.position;
|
|
|
|
+ // TODO(donmccurdy): Current group should be cached.
|
|
|
|
+ var group = Path.getGroup('level', start);
|
|
|
|
+ return Path.findPath(start, target, 'level', group);
|
|
}
|
|
}
|
|
|
|
+};
|
|
|
|
|
|
- static isConvex (polygon, navigationMesh) {
|
|
|
|
-
|
|
|
|
- var vertices = navigationMesh.vertices;
|
|
|
|
-
|
|
|
|
- if (polygon.vertexIds.length < 3) return false;
|
|
|
|
-
|
|
|
|
- var convex = true;
|
|
|
|
|
|
+},{"three-pathfinding":82}],114:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Flat grid.
|
|
|
|
+ *
|
|
|
|
+ * Defaults to 75x75.
|
|
|
|
+ */
|
|
|
|
+var Primitive = module.exports = {
|
|
|
|
+ defaultComponents: {
|
|
|
|
+ geometry: {
|
|
|
|
+ primitive: 'plane',
|
|
|
|
+ width: 75,
|
|
|
|
+ height: 75
|
|
|
|
+ },
|
|
|
|
+ rotation: {x: -90, y: 0, z: 0},
|
|
|
|
+ material: {
|
|
|
|
+ src: 'url(https://cdn.rawgit.com/donmccurdy/aframe-extras/v1.16.3/assets/grid.png)',
|
|
|
|
+ repeat: '75 75'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ mappings: {
|
|
|
|
+ width: 'geometry.width',
|
|
|
|
+ height: 'geometry.height',
|
|
|
|
+ src: 'material.src'
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- var total = 0;
|
|
|
|
|
|
+module.exports.registerAll = (function () {
|
|
|
|
+ var registered = false;
|
|
|
|
+ return function (AFRAME) {
|
|
|
|
+ if (registered) return;
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
+ AFRAME.registerPrimitive('a-grid', Primitive);
|
|
|
|
+ registered = true;
|
|
|
|
+ };
|
|
|
|
+}());
|
|
|
|
|
|
- var results = [];
|
|
|
|
|
|
+},{}],115:[function(require,module,exports){
|
|
|
|
+var vg = require('../../lib/hex-grid.min.js');
|
|
|
|
+var defaultHexGrid = require('../../lib/default-hex-grid.json');
|
|
|
|
|
|
- for (var i = 0; i < polygon.vertexIds.length; i++) {
|
|
|
|
|
|
+/**
|
|
|
|
+ * Hex grid.
|
|
|
|
+ */
|
|
|
|
+var Primitive = module.exports.Primitive = {
|
|
|
|
+ defaultComponents: {
|
|
|
|
+ 'hexgrid': {}
|
|
|
|
+ },
|
|
|
|
+ mappings: {
|
|
|
|
+ src: 'hexgrid.src'
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- var vertex = vertices[polygon.vertexIds[i]];
|
|
|
|
|
|
+var Component = module.exports.Component = {
|
|
|
|
+ dependencies: ['material'],
|
|
|
|
+ schema: {
|
|
|
|
+ src: {type: 'asset'}
|
|
|
|
+ },
|
|
|
|
+ init: function () {
|
|
|
|
+ var data = this.data;
|
|
|
|
+ if (data.src) {
|
|
|
|
+ fetch(data.src)
|
|
|
|
+ .then(function (response) { response.json(); })
|
|
|
|
+ .then(function (json) { this.addMesh(json); });
|
|
|
|
+ } else {
|
|
|
|
+ this.addMesh(defaultHexGrid);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ addMesh: function (json) {
|
|
|
|
+ var grid = new vg.HexGrid();
|
|
|
|
+ grid.fromJSON(json);
|
|
|
|
+ var board = new vg.Board(grid);
|
|
|
|
+ board.generateTilemap();
|
|
|
|
+ this.el.setObject3D('mesh', board.group);
|
|
|
|
+ this.addMaterial();
|
|
|
|
+ },
|
|
|
|
+ addMaterial: function () {
|
|
|
|
+ var materialComponent = this.el.components.material;
|
|
|
|
+ var material = (materialComponent || {}).material;
|
|
|
|
+ if (!material) return;
|
|
|
|
+ this.el.object3D.traverse(function (node) {
|
|
|
|
+ if (node.isMesh) {
|
|
|
|
+ node.material = material;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.el.removeObject3D('mesh');
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- var nextVertex, previousVertex;
|
|
|
|
|
|
+module.exports.registerAll = (function () {
|
|
|
|
+ var registered = false;
|
|
|
|
+ return function (AFRAME) {
|
|
|
|
+ if (registered) return;
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
+ AFRAME.registerComponent('hexgrid', Component);
|
|
|
|
+ AFRAME.registerPrimitive('a-hexgrid', Primitive);
|
|
|
|
+ registered = true;
|
|
|
|
+ };
|
|
|
|
+}());
|
|
|
|
|
|
- if (i === 0) {
|
|
|
|
- nextVertex = vertices[polygon.vertexIds[1]];
|
|
|
|
- previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 1]];
|
|
|
|
- } else if (i === polygon.vertexIds.length - 1) {
|
|
|
|
- nextVertex = vertices[polygon.vertexIds[0]];
|
|
|
|
- previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 2]];
|
|
|
|
- } else {
|
|
|
|
- nextVertex = vertices[polygon.vertexIds[i + 1]];
|
|
|
|
- previousVertex = vertices[polygon.vertexIds[i - 1]];
|
|
|
|
- }
|
|
|
|
|
|
+},{"../../lib/default-hex-grid.json":7,"../../lib/hex-grid.min.js":9}],116:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Flat-shaded ocean primitive.
|
|
|
|
+ *
|
|
|
|
+ * Based on a Codrops tutorial:
|
|
|
|
+ * http://tympanus.net/codrops/2016/04/26/the-aviator-animating-basic-3d-scene-threejs/
|
|
|
|
+ */
|
|
|
|
+var Primitive = module.exports.Primitive = {
|
|
|
|
+ defaultComponents: {
|
|
|
|
+ ocean: {},
|
|
|
|
+ rotation: {x: -90, y: 0, z: 0}
|
|
|
|
+ },
|
|
|
|
+ mappings: {
|
|
|
|
+ width: 'ocean.width',
|
|
|
|
+ depth: 'ocean.depth',
|
|
|
|
+ density: 'ocean.density',
|
|
|
|
+ color: 'ocean.color',
|
|
|
|
+ opacity: 'ocean.opacity'
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- var a = nextVertex.clone().sub(vertex);
|
|
|
|
- var b = previousVertex.clone().sub(vertex);
|
|
|
|
|
|
+var Component = module.exports.Component = {
|
|
|
|
+ schema: {
|
|
|
|
+ // Dimensions of the ocean area.
|
|
|
|
+ width: {default: 10, min: 0},
|
|
|
|
+ depth: {default: 10, min: 0},
|
|
|
|
|
|
- var angle = a.angleTo(b);
|
|
|
|
- total += angle;
|
|
|
|
|
|
+ // Density of waves.
|
|
|
|
+ density: {default: 10},
|
|
|
|
|
|
- if (angle === Math.PI || angle === 0) return false;
|
|
|
|
|
|
+ // Wave amplitude and variance.
|
|
|
|
+ amplitude: {default: 0.1},
|
|
|
|
+ amplitudeVariance: {default: 0.3},
|
|
|
|
|
|
- var r = a.cross(b).y;
|
|
|
|
- results.push(r);
|
|
|
|
- }
|
|
|
|
|
|
+ // Wave speed and variance.
|
|
|
|
+ speed: {default: 1},
|
|
|
|
+ speedVariance: {default: 2},
|
|
|
|
|
|
- // if ( total > (polygon.vertexIds.length-2)*Math.PI ) return false;
|
|
|
|
|
|
+ // Material.
|
|
|
|
+ color: {default: '#7AD2F7', type: 'color'},
|
|
|
|
+ opacity: {default: 0.8}
|
|
|
|
+ },
|
|
|
|
|
|
- results.forEach((r) => {
|
|
|
|
- if (r === 0) convex = false;
|
|
|
|
- });
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Use play() instead of init(), because component mappings – unavailable as dependencies – are
|
|
|
|
+ * not guaranteed to have parsed when this component is initialized.
|
|
|
|
+ */
|
|
|
|
+ play: function () {
|
|
|
|
+ var el = this.el,
|
|
|
|
+ data = this.data,
|
|
|
|
+ material = el.components.material;
|
|
|
|
|
|
- if (results[0] > 0) {
|
|
|
|
- results.forEach((r) => {
|
|
|
|
- if (r < 0) convex = false;
|
|
|
|
- });
|
|
|
|
- } else {
|
|
|
|
- results.forEach((r) => {
|
|
|
|
- if (r > 0) convex = false;
|
|
|
|
|
|
+ var geometry = new THREE.PlaneGeometry(data.width, data.depth, data.density, data.density);
|
|
|
|
+ geometry.mergeVertices();
|
|
|
|
+ this.waves = [];
|
|
|
|
+ for (var v, i = 0, l = geometry.vertices.length; i < l; i++) {
|
|
|
|
+ v = geometry.vertices[i];
|
|
|
|
+ this.waves.push({
|
|
|
|
+ z: v.z,
|
|
|
|
+ ang: Math.random() * Math.PI * 2,
|
|
|
|
+ amp: data.amplitude + Math.random() * data.amplitudeVariance,
|
|
|
|
+ speed: (data.speed + Math.random() * data.speedVariance) / 1000 // radians / frame
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
- return convex;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- static distanceToSquared (a, b) {
|
|
|
|
|
|
+ if (!material) {
|
|
|
|
+ material = {};
|
|
|
|
+ material.material = new THREE.MeshPhongMaterial({
|
|
|
|
+ color: data.color,
|
|
|
|
+ transparent: data.opacity < 1,
|
|
|
|
+ opacity: data.opacity,
|
|
|
|
+ shading: THREE.FlatShading,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
|
|
- var dx = a.x - b.x;
|
|
|
|
- var dy = a.y - b.y;
|
|
|
|
- var dz = a.z - b.z;
|
|
|
|
|
|
+ this.mesh = new THREE.Mesh(geometry, material.material);
|
|
|
|
+ el.setObject3D('mesh', this.mesh);
|
|
|
|
+ },
|
|
|
|
|
|
- return dx * dx + dy * dy + dz * dz;
|
|
|
|
|
|
+ remove: function () {
|
|
|
|
+ this.el.removeObject3D('mesh');
|
|
|
|
+ },
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ tick: function (t, dt) {
|
|
|
|
+ if (!dt) return;
|
|
|
|
|
|
- //+ Jonas Raoni Soares Silva
|
|
|
|
- //@ http://jsfromhell.com/math/is-point-in-poly [rev. #0]
|
|
|
|
- static isPointInPoly (poly, pt) {
|
|
|
|
- for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
|
|
|
|
- ((poly[i].z <= pt.z && pt.z < poly[j].z) || (poly[j].z <= pt.z && pt.z < poly[i].z)) && (pt.x < (poly[j].x - poly[i].x) * (pt.z - poly[i].z) / (poly[j].z - poly[i].z) + poly[i].x) && (c = !c);
|
|
|
|
- return c;
|
|
|
|
|
|
+ var verts = this.mesh.geometry.vertices;
|
|
|
|
+ for (var v, vprops, i = 0; (v = verts[i]); i++){
|
|
|
|
+ vprops = this.waves[i];
|
|
|
|
+ v.z = vprops.z + Math.sin(vprops.ang) * vprops.amp;
|
|
|
|
+ vprops.ang += vprops.speed * dt;
|
|
|
|
+ }
|
|
|
|
+ this.mesh.geometry.verticesNeedUpdate = true;
|
|
}
|
|
}
|
|
|
|
+};
|
|
|
|
|
|
- static isVectorInPolygon (vector, polygon, vertices) {
|
|
|
|
-
|
|
|
|
- // reference point will be the centroid of the polygon
|
|
|
|
- // We need to rotate the vector as well as all the points which the polygon uses
|
|
|
|
|
|
+module.exports.registerAll = (function () {
|
|
|
|
+ var registered = false;
|
|
|
|
+ return function (AFRAME) {
|
|
|
|
+ if (registered) return;
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
+ AFRAME.registerComponent('ocean', Component);
|
|
|
|
+ AFRAME.registerPrimitive('a-ocean', Primitive);
|
|
|
|
+ registered = true;
|
|
|
|
+ };
|
|
|
|
+}());
|
|
|
|
|
|
- var lowestPoint = 100000;
|
|
|
|
- var highestPoint = -100000;
|
|
|
|
|
|
+},{}],117:[function(require,module,exports){
|
|
|
|
+/**
|
|
|
|
+ * Tube following a custom path.
|
|
|
|
+ *
|
|
|
|
+ * Usage:
|
|
|
|
+ *
|
|
|
|
+ * ```html
|
|
|
|
+ * <a-tube path="5 0 5, 5 0 -5, -5 0 -5" radius="0.5"></a-tube>
|
|
|
|
+ * ```
|
|
|
|
+ */
|
|
|
|
+var Primitive = module.exports.Primitive = {
|
|
|
|
+ defaultComponents: {
|
|
|
|
+ tube: {},
|
|
|
|
+ },
|
|
|
|
+ mappings: {
|
|
|
|
+ path: 'tube.path',
|
|
|
|
+ segments: 'tube.segments',
|
|
|
|
+ radius: 'tube.radius',
|
|
|
|
+ radialSegments: 'tube.radialSegments',
|
|
|
|
+ closed: 'tube.closed'
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- var polygonVertices = [];
|
|
|
|
|
|
+var Component = module.exports.Component = {
|
|
|
|
+ schema: {
|
|
|
|
+ path: {default: []},
|
|
|
|
+ segments: {default: 64},
|
|
|
|
+ radius: {default: 1},
|
|
|
|
+ radialSegments: {default: 8},
|
|
|
|
+ closed: {default: false}
|
|
|
|
+ },
|
|
|
|
|
|
- polygon.vertexIds.forEach((vId) => {
|
|
|
|
- lowestPoint = Math.min(vertices[vId].y, lowestPoint);
|
|
|
|
- highestPoint = Math.max(vertices[vId].y, highestPoint);
|
|
|
|
- polygonVertices.push(vertices[vId]);
|
|
|
|
- });
|
|
|
|
|
|
+ init: function () {
|
|
|
|
+ var el = this.el,
|
|
|
|
+ data = this.data,
|
|
|
|
+ material = el.components.material;
|
|
|
|
|
|
- if (vector.y < highestPoint + 0.5 && vector.y > lowestPoint - 0.5 &&
|
|
|
|
- this.isPointInPoly(polygonVertices, vector)) {
|
|
|
|
- return true;
|
|
|
|
|
|
+ if (!data.path.length) {
|
|
|
|
+ console.error('[a-tube] `path` property expected but not found.');
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- static triarea2 (a, b, c) {
|
|
|
|
- var ax = b.x - a.x;
|
|
|
|
- var az = b.z - a.z;
|
|
|
|
- var bx = c.x - a.x;
|
|
|
|
- var bz = c.z - a.z;
|
|
|
|
- return bx * az - ax * bz;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- static vequal (a, b) {
|
|
|
|
- return this.distanceToSquared(a, b) < 0.00001;
|
|
|
|
- }
|
|
|
|
|
|
+ var curve = new THREE.CatmullRomCurve3(data.path.map(function (point) {
|
|
|
|
+ point = point.split(' ');
|
|
|
|
+ return new THREE.Vector3(Number(point[0]), Number(point[1]), Number(point[2]));
|
|
|
|
+ }));
|
|
|
|
+ var geometry = new THREE.TubeGeometry(
|
|
|
|
+ curve, data.segments, data.radius, data.radialSegments, data.closed
|
|
|
|
+ );
|
|
|
|
|
|
- static array_intersect () {
|
|
|
|
- let i, shortest, nShortest, n, len, ret = [],
|
|
|
|
- obj = {},
|
|
|
|
- nOthers;
|
|
|
|
- nOthers = arguments.length - 1;
|
|
|
|
- nShortest = arguments[0].length;
|
|
|
|
- shortest = 0;
|
|
|
|
- for (i = 0; i <= nOthers; i++) {
|
|
|
|
- n = arguments[i].length;
|
|
|
|
- if (n < nShortest) {
|
|
|
|
- shortest = i;
|
|
|
|
- nShortest = n;
|
|
|
|
- }
|
|
|
|
|
|
+ if (!material) {
|
|
|
|
+ material = {};
|
|
|
|
+ material.material = new THREE.MeshPhongMaterial();
|
|
}
|
|
}
|
|
|
|
|
|
- for (i = 0; i <= nOthers; i++) {
|
|
|
|
- n = (i === shortest) ? 0 : (i || shortest); //Read the shortest array first. Read the first array instead of the shortest
|
|
|
|
- len = arguments[n].length;
|
|
|
|
- for (var j = 0; j < len; j++) {
|
|
|
|
- var elem = arguments[n][j];
|
|
|
|
- if (obj[elem] === i - 1) {
|
|
|
|
- if (i === nOthers) {
|
|
|
|
- ret.push(elem);
|
|
|
|
- obj[elem] = 0;
|
|
|
|
- } else {
|
|
|
|
- obj[elem] = i;
|
|
|
|
- }
|
|
|
|
- } else if (i === 0) {
|
|
|
|
- obj[elem] = 0;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return ret;
|
|
|
|
|
|
+ this.mesh = new THREE.Mesh(geometry, material.material);
|
|
|
|
+ this.el.setObject3D('mesh', this.mesh);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ remove: function () {
|
|
|
|
+ if (this.mesh) this.el.removeObject3D('mesh');
|
|
}
|
|
}
|
|
-}
|
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
+module.exports.registerAll = (function () {
|
|
|
|
+ var registered = false;
|
|
|
|
+ return function (AFRAME) {
|
|
|
|
+ if (registered) return;
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
+ AFRAME.registerComponent('tube', Component);
|
|
|
|
+ AFRAME.registerPrimitive('a-tube', Primitive);
|
|
|
|
+ registered = true;
|
|
|
|
+ };
|
|
|
|
+}());
|
|
|
|
|
|
|
|
+},{}],118:[function(require,module,exports){
|
|
|
|
+module.exports = {
|
|
|
|
+ 'a-grid': require('./a-grid'),
|
|
|
|
+ 'a-hexgrid': require('./a-hexgrid'),
|
|
|
|
+ 'a-ocean': require('./a-ocean'),
|
|
|
|
+ 'a-tube': require('./a-tube'),
|
|
|
|
|
|
-module.exports = Utils;
|
|
|
|
|
|
+ registerAll: function (AFRAME) {
|
|
|
|
+ if (this._registered) return;
|
|
|
|
+ AFRAME = AFRAME || window.AFRAME;
|
|
|
|
+ this['a-grid'].registerAll(AFRAME);
|
|
|
|
+ this['a-hexgrid'].registerAll(AFRAME);
|
|
|
|
+ this['a-ocean'].registerAll(AFRAME);
|
|
|
|
+ this['a-tube'].registerAll(AFRAME);
|
|
|
|
+ this._registered = true;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
-},{}]},{},[1]);
|
|
|
|
|
|
+},{"./a-grid":114,"./a-hexgrid":115,"./a-ocean":116,"./a-tube":117}]},{},[1]);
|