UNPKG

ggabcd-meshwalk

Version:

MeshWalk.js is a JS library which helps your TPS game development with three.js.

1,704 lines (1,447 loc) 120 kB
var THREE$1; // bind on install var onInstallHandlers = []; function install(_THREE) { if (THREE$1 && _THREE === THREE$1) { console.error("[THREE] already installed. `install` should be called only once."); return; } THREE$1 = _THREE; onInstallHandlers.forEach(function (handler) { return handler(); }); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _get() { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(arguments.length < 3 ? target : receiver); } return desc.value; }; } return _get.apply(this, arguments); } var vec3; var vec3_0; var vec3_1; var center; var extents; onInstallHandlers.push(function () { vec3 = new THREE$1.Vector3(); vec3_0 = new THREE$1.Vector3(); vec3_1 = new THREE$1.Vector3(); center = new THREE$1.Vector3(); extents = new THREE$1.Vector3(); }); // aabb: <THREE.Box3> // Plane: <THREE.Plane> function isIntersectionAABBPlane(aabb, Plane) { center.addVectors(aabb.max, aabb.min).multiplyScalar(0.5); extents.subVectors(aabb.max, center); var r = extents.x * Math.abs(Plane.normal.x) + extents.y * Math.abs(Plane.normal.y) + extents.z * Math.abs(Plane.normal.z); var s = Plane.normal.dot(center) - Plane.constant; return Math.abs(s) <= r; } var v0; var v1; var v2; var f0; var f1; var f2; var a00; var a01; var a02; var a10; var a11; var a12; var a20; var a21; var a22; var plane; onInstallHandlers.push(function () { v0 = new THREE$1.Vector3(); v1 = new THREE$1.Vector3(); v2 = new THREE$1.Vector3(); f0 = new THREE$1.Vector3(); f1 = new THREE$1.Vector3(); f2 = new THREE$1.Vector3(); a00 = new THREE$1.Vector3(); a01 = new THREE$1.Vector3(); a02 = new THREE$1.Vector3(); a10 = new THREE$1.Vector3(); a11 = new THREE$1.Vector3(); a12 = new THREE$1.Vector3(); a20 = new THREE$1.Vector3(); a21 = new THREE$1.Vector3(); a22 = new THREE$1.Vector3(); plane = new THREE$1.Plane(); }); // based on http://www.gamedev.net/topic/534655-aabb-triangleplane-intersection--distance-to-plane-is-incorrect-i-have-solved-it/ // // a: <THREE.Vector3>, // vertex of a triangle // b: <THREE.Vector3>, // vertex of a triangle // c: <THREE.Vector3>, // vertex of a triangle // aabb: <THREE.Box3> function isIntersectionTriangleAABB(a, b, c, aabb) { var p0, p1, p2, r; // Compute box center and extents of AABoundingBox (if not already given in that format) center.addVectors(aabb.max, aabb.min).multiplyScalar(0.5); extents.subVectors(aabb.max, center); // Translate triangle as conceptually moving AABB to origin v0.subVectors(a, center); v1.subVectors(b, center); v2.subVectors(c, center); // Compute edge vectors for triangle f0.subVectors(v1, v0); f1.subVectors(v2, v1); f2.subVectors(v0, v2); // Test axes a00..a22 (category 3) a00.set(0, -f0.z, f0.y); a01.set(0, -f1.z, f1.y); a02.set(0, -f2.z, f2.y); a10.set(f0.z, 0, -f0.x); a11.set(f1.z, 0, -f1.x); a12.set(f2.z, 0, -f2.x); a20.set(-f0.y, f0.x, 0); a21.set(-f1.y, f1.x, 0); a22.set(-f2.y, f2.x, 0); // Test axis a00 p0 = v0.dot(a00); p1 = v1.dot(a00); p2 = v2.dot(a00); r = extents.y * Math.abs(f0.z) + extents.z * Math.abs(f0.y); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test axis a01 p0 = v0.dot(a01); p1 = v1.dot(a01); p2 = v2.dot(a01); r = extents.y * Math.abs(f1.z) + extents.z * Math.abs(f1.y); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test axis a02 p0 = v0.dot(a02); p1 = v1.dot(a02); p2 = v2.dot(a02); r = extents.y * Math.abs(f2.z) + extents.z * Math.abs(f2.y); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test axis a10 p0 = v0.dot(a10); p1 = v1.dot(a10); p2 = v2.dot(a10); r = extents.x * Math.abs(f0.z) + extents.z * Math.abs(f0.x); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test axis a11 p0 = v0.dot(a11); p1 = v1.dot(a11); p2 = v2.dot(a11); r = extents.x * Math.abs(f1.z) + extents.z * Math.abs(f1.x); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test axis a12 p0 = v0.dot(a12); p1 = v1.dot(a12); p2 = v2.dot(a12); r = extents.x * Math.abs(f2.z) + extents.z * Math.abs(f2.x); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test axis a20 p0 = v0.dot(a20); p1 = v1.dot(a20); p2 = v2.dot(a20); r = extents.x * Math.abs(f0.y) + extents.y * Math.abs(f0.x); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test axis a21 p0 = v0.dot(a21); p1 = v1.dot(a21); p2 = v2.dot(a21); r = extents.x * Math.abs(f1.y) + extents.y * Math.abs(f1.x); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test axis a22 p0 = v0.dot(a22); p1 = v1.dot(a22); p2 = v2.dot(a22); r = extents.x * Math.abs(f2.y) + extents.y * Math.abs(f2.x); if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) { return false; // Axis is a separating axis } // Test the three axes corresponding to the face normals of AABB b (category 1). // Exit if... // ... [-extents.x, extents.x] and [min(v0.x,v1.x,v2.x), max(v0.x,v1.x,v2.x)] do not overlap if (Math.max(v0.x, v1.x, v2.x) < -extents.x || Math.min(v0.x, v1.x, v2.x) > extents.x) { return false; } // ... [-extents.y, extents.y] and [min(v0.y,v1.y,v2.y), max(v0.y,v1.y,v2.y)] do not overlap if (Math.max(v0.y, v1.y, v2.y) < -extents.y || Math.min(v0.y, v1.y, v2.y) > extents.y) { return false; } // ... [-extents.z, extents.z] and [min(v0.z,v1.z,v2.z), max(v0.z,v1.z,v2.z)] do not overlap if (Math.max(v0.z, v1.z, v2.z) < -extents.z || Math.min(v0.z, v1.z, v2.z) > extents.z) { return false; } // Test separating axis corresponding to triangle face normal (category 2) // Face Normal is -ve as Triangle is clockwise winding (and XNA uses -z for into screen) plane.normal.copy(f1).cross(f0).normalize(); plane.constant = plane.normal.dot(a); return isIntersectionAABBPlane(aabb, plane); } // sphere1: <THREE.Sphere> // sphere: <THREE.Sphere> // aabb: <THREE.Box3> function isIntersectionSphereAABB(sphere, aabb) { var sqDist = 0; if (sphere.center.x < aabb.min.x) sqDist += (aabb.min.x - sphere.center.x) * (aabb.min.x - sphere.center.x); if (sphere.center.x > aabb.max.x) sqDist += (sphere.center.x - aabb.max.x) * (sphere.center.x - aabb.max.x); if (sphere.center.y < aabb.min.y) sqDist += (aabb.min.y - sphere.center.y) * (aabb.min.y - sphere.center.y); if (sphere.center.y > aabb.max.y) sqDist += (sphere.center.y - aabb.max.y) * (sphere.center.y - aabb.max.y); if (sphere.center.z < aabb.min.z) sqDist += (aabb.min.z - sphere.center.z) * (aabb.min.z - sphere.center.z); if (sphere.center.z > aabb.max.z) sqDist += (sphere.center.z - aabb.max.z) * (sphere.center.z - aabb.max.z); return sqDist <= sphere.radius * sphere.radius; } var A; var B; var C; var V; var AB; var BC; var CA; var Q1; var Q2; var Q3; var QC; var QA; var QB; var negatedNormal; onInstallHandlers.push(function () { A = new THREE$1.Vector3(); B = new THREE$1.Vector3(); C = new THREE$1.Vector3(); V = new THREE$1.Vector3(); AB = new THREE$1.Vector3(); BC = new THREE$1.Vector3(); CA = new THREE$1.Vector3(); Q1 = new THREE$1.Vector3(); Q2 = new THREE$1.Vector3(); Q3 = new THREE$1.Vector3(); QC = new THREE$1.Vector3(); QA = new THREE$1.Vector3(); QB = new THREE$1.Vector3(); negatedNormal = new THREE$1.Vector3(); }); //http://clb.demon.fi/MathGeoLib/docs/Triangle.cpp_code.html#459 // sphere: <THREE.Sphere> // a: <THREE.Vector3>, // vertex of a triangle // b: <THREE.Vector3>, // vertex of a triangle // c: <THREE.Vector3>, // vertex of a triangle // normal: <THREE.Vector3>, // normal of a triangle function isIntersectionSphereTriangle(sphere, a, b, c, normal) { // http://realtimecollisiondetection.net/blog/?p=103 // vs plain of triangle face A.subVectors(a, sphere.center); B.subVectors(b, sphere.center); C.subVectors(c, sphere.center); var rr = sphere.radius * sphere.radius; V.crossVectors(vec3_0.subVectors(B, A), vec3_1.subVectors(C, A)); var d = A.dot(V); var e = V.dot(V); if (d * d > rr * e) { return false; } // vs triangle vertex var aa = A.dot(A); var ab = A.dot(B); var ac = A.dot(C); var bb = B.dot(B); var bc = B.dot(C); var cc = C.dot(C); if (aa > rr & ab > aa & ac > aa || bb > rr & ab > bb & bc > bb || cc > rr & ac > cc & bc > cc) { return false; } // vs edge AB.subVectors(B, A); BC.subVectors(C, B); CA.subVectors(A, C); var d1 = ab - aa; var d2 = bc - bb; var d3 = ac - cc; var e1 = AB.dot(AB); var e2 = BC.dot(BC); var e3 = CA.dot(CA); Q1.subVectors(A.multiplyScalar(e1), AB.multiplyScalar(d1)); Q2.subVectors(B.multiplyScalar(e2), BC.multiplyScalar(d2)); Q3.subVectors(C.multiplyScalar(e3), CA.multiplyScalar(d3)); QC.subVectors(C.multiplyScalar(e1), Q1); QA.subVectors(A.multiplyScalar(e2), Q2); QB.subVectors(B.multiplyScalar(e3), Q3); if (Q1.dot(Q1) > rr * e1 * e1 && Q1.dot(QC) >= 0 || Q2.dot(Q2) > rr * e2 * e2 && Q2.dot(QA) >= 0 || Q3.dot(Q3) > rr * e3 * e3 && Q3.dot(QB) >= 0) { return false; } var distance = Math.sqrt(d * d / e) - sphere.radius - 1; negatedNormal.set(-normal.x, -normal.y, -normal.z); var contactPoint = sphere.center.clone().add(negatedNormal.multiplyScalar(distance)); return { distance: distance, contactPoint: contactPoint }; } // based on Real-Time Collision Detection Section 5.3.4 // p: <THREE.Vector3>, // line3.start // q: <THREE.Vector3>, // line3.end // a: <THREE.Vector3>, // triangle.a // b: <THREE.Vector3>, // triangle.b // c: <THREE.Vector3>, // triangle.c // normal: <THREE.Vector3>, // triangle.normal, optional // var scalarTriple = function ( a, b, c ) { // var m = b.clone().cross( c ); // return a.dot( m ); // } // var vectorTriple = function ( a, b, c ) { // var m = b.clone().cross( c ); // return a.clone().cross( m ); // } // export function isIntersectionLineTrianglefunction ( p, q, a, b, c, precisio{ // var pq = q.clone().sub( p ), // pa = a.clone().sub( p ), // pb = b.clone().sub( p ), // pc = c.clone().sub( p ), // u, v, w; // u = scalarTriple( pq, pc, pb ); // if ( u < 0 ) { return false; } // v = scalarTriple( pq, pa, pc ); // if ( v < 0 ) { return false; } // w = scalarTriple( pq, pb, pa ); // if ( w < 0 ) { return false; } // var denom = 1 / ( u + v + w ); // u *= denom; // v *= denom; // w *= denom; // var au = a.clone().multiplyScalar( u ), // bv = b.clone().multiplyScalar( v ), // cw = c.clone().multiplyScalar( w ), // contactPoint = au.clone().add( bv ).add( cw ); // return { // contactPoint: contactPoint // } // } var ab; var ac; var qp; var n; var ap; var e; var au; var bv; var cw; onInstallHandlers.push(function () { ab = new THREE$1.Vector3(); ac = new THREE$1.Vector3(); qp = new THREE$1.Vector3(); n = new THREE$1.Vector3(); ap = new THREE$1.Vector3(); e = new THREE$1.Vector3(); au = new THREE$1.Vector3(); bv = new THREE$1.Vector3(); cw = new THREE$1.Vector3(); }); function testSegmentTriangle(p, q, a, b, c) { ab.subVectors(b, a); ac.subVectors(c, a); qp.subVectors(p, q); n.copy(ab).cross(ac); var d = qp.dot(n); if (d <= 0) return false; ap.subVectors(p, a); var t = ap.dot(n); if (t < 0) return 0; if (t > d) return 0; e.copy(qp).cross(ap); var v = ac.dot(e); if (v < 0 || v > d) return 0; var w = vec3.copy(ab).dot(e) * -1; if (w < 0 || v + w > d) return 0; var ood = 1 / d; t *= ood; v *= ood; w *= ood; var u = 1 - v - w; au.copy(a).multiplyScalar(u); bv.copy(b).multiplyScalar(v); cw.copy(c).multiplyScalar(w); var contactPoint = au.clone().add(bv).add(cw); return { contactPoint: contactPoint }; } // based on http://marupeke296.com/COL_3D_No15_Octree.html // // +------+------+ // |\ 2 \ 3 \ // | +------+------+ // + |\ \ \ // |\| +------+------+ // | + | | | // +0|\| 6 | 7 | // \| +------+------+ // + | | | // y \| 4 | 5 | // | +------+------+ // +--x // \ // z // // // +------+------+ // |\ 6 \ 7 \ // | +------+------+ // + |\ \ \ // |\| +------+------+ // | + | | | // +4|\| 2 | 3 | // \| +------+------+ // + | | | // z y \| 0 | 1 | // \| +------+------+ // +--x // // min: <THREE.Vector3> // max: <THREE.Vector3> // maxDepth: <Number> var Octree = /*#__PURE__*/function () { function Octree(min, max, maxDepth) { _classCallCheck(this, Octree); this.min = min; this.max = max; this.maxDepth = maxDepth; this.nodes = []; this.isOctree = true; var nodeBoxSize = new THREE$1.Vector3(); var nodeBoxMin = new THREE$1.Vector3(); var nodeBoxMax = new THREE$1.Vector3(); for (var depth = 0; depth < this.maxDepth; depth++) { this.nodes.push([]); var pow2 = Math.pow(2, depth); var pow4 = Math.pow(4, depth); nodeBoxSize.subVectors(this.max, this.min).divideScalar(pow2); for (var i = 0, length = Math.pow(8, depth); i < length; i++) { var indexX = i % pow2; var indexY = i / pow4 | 0; var indexZ = (i / pow2 | 0) % pow2; nodeBoxMin.set(this.min.x + indexX * nodeBoxSize.x, this.min.y + indexY * nodeBoxSize.y, this.min.z + indexZ * nodeBoxSize.z); nodeBoxMax.copy(nodeBoxMin).add(nodeBoxSize); var mortonNumber = Octree.getMortonNumber(indexX, indexY, indexZ); this.nodes[depth][mortonNumber] = new OctreeNode(this, depth, mortonNumber, nodeBoxMin, nodeBoxMax); } } } _createClass(Octree, [{ key: "importThreeMesh", value: function importThreeMesh(threeMesh) { threeMesh.updateMatrix(); var geometryId = threeMesh.geometry.uuid; var geometry = threeMesh.geometry.clone(); geometry.applyMatrix4(threeMesh.matrix); geometry.computeVertexNormals(); if (geometry instanceof THREE$1.BufferGeometry) { if (geometry.index !== undefined) { var indices = geometry.index.array; var positions = geometry.attributes.position.array; // const normals = geometry.attributes.normal.array; var offsets = geometry.groups.length !== 0 ? geometry.groups : [{ start: 0, count: indices.length, materialIndex: 0 }]; for (var i = 0, l = offsets.length; i < l; ++i) { var start = offsets[i].start; var count = offsets[i].count; var index = offsets[i].materialIndex; for (var ii = start, ll = start + count; ii < ll; ii += 3) { var a = index + indices[ii]; var b = index + indices[ii + 1]; var c = index + indices[ii + 2]; var vA = new THREE$1.Vector3().fromArray(positions, a * 3); var vB = new THREE$1.Vector3().fromArray(positions, b * 3); var vC = new THREE$1.Vector3().fromArray(positions, c * 3); // https://github.com/mrdoob/three.js/issues/4691 // make face normal var cb = new THREE$1.Vector3().subVectors(vC, vB); var ab = new THREE$1.Vector3().subVectors(vA, vB); var faceNormal = cb.cross(ab).normalize().clone(); var face = new Face(vA, vB, vC, faceNormal, geometryId); this.addFace(face); } } } return; } geometry.computeFaceNormals(); for (var _i = 0, _l = geometry.faces.length; _i < _l; _i++) { var _face = new Face(geometry.vertices[geometry.faces[_i].a], geometry.vertices[geometry.faces[_i].b], geometry.vertices[geometry.faces[_i].c], geometry.faces[_i].normal, geometryId); this.addFace(_face); } } }, { key: "addFace", value: function addFace(face) { var tmp = []; var targetNodes = this.nodes[0].slice(0); for (var i = 0, l = this.maxDepth; i < l; i++) { for (var ii = 0, ll = targetNodes.length; ii < ll; ii++) { var node = targetNodes[ii]; var isIntersected = isIntersectionTriangleAABB(face.a, face.b, face.c, node); if (isIntersected) { node.trianglePool.push(face); if (i + 1 !== this.maxDepth) { tmp = tmp.concat(node.getChildNodes()); } } } if (tmp.length === 0) { break; } targetNodes = tmp.slice(0); tmp.length = 0; } } }, { key: "removeThreeMesh", value: function removeThreeMesh(meshID) { this.nodes.forEach(function (nodeDepth) { nodeDepth.forEach(function (node) { var newTrianglePool = []; node.trianglePool.forEach(function (face) { if (face.meshID !== meshID) { newTrianglePool.push(face); } }); node.trianglePool = newTrianglePool; }); }); } }, { key: "getIntersectedNodes", value: function getIntersectedNodes(sphere, depth) { var tmp = []; var intersectedNodes = []; var isIntersected = isIntersectionSphereAABB(sphere, this); if (!isIntersected) return []; var targetNodes = this.nodes[0].slice(0); for (var i = 0, l = depth; i < l; i++) { for (var ii = 0, ll = targetNodes.length; ii < ll; ii++) { var node = targetNodes[ii]; var _isIntersected = isIntersectionSphereAABB(sphere, node); if (_isIntersected) { var isAtMaxDepth = i + 1 === depth; if (isAtMaxDepth) { if (node.trianglePool.length !== 0) { intersectedNodes.push(node); } } else { tmp = tmp.concat(node.getChildNodes()); } } } targetNodes = tmp.slice(0); tmp.length = 0; } return intersectedNodes; } }]); return Octree; }(); Octree.separate3Bit = function (n) { n = (n | n << 8) & 0x0000f00f; n = (n | n << 4) & 0x000c30c3; n = (n | n << 2) & 0x00249249; return n; }; Octree.getMortonNumber = function (x, y, z) { return Octree.separate3Bit(x) | Octree.separate3Bit(y) << 1 | Octree.separate3Bit(z) << 2; }; Octree.uniqTrianglesFromNodes = function (nodes) { var uniq = []; var isContained = false; if (nodes.length === 0) return []; if (nodes.length === 1) return nodes[0].trianglePool.slice(0); for (var i = 0, l = nodes.length; i < l; i++) { for (var ii = 0, ll = nodes[i].trianglePool.length; ii < ll; ii++) { for (var iii = 0, lll = uniq.length; iii < lll; iii++) { if (nodes[i].trianglePool[ii] === uniq[iii]) { isContained = true; } } if (!isContained) { uniq.push(nodes[i].trianglePool[ii]); } isContained = false; } } return uniq; }; // var OctreeNode = /*#__PURE__*/function () { function OctreeNode(tree, depth, mortonNumber, min, max) { _classCallCheck(this, OctreeNode); this.tree = tree; this.depth = depth; this.mortonNumber = mortonNumber; this.min = new THREE$1.Vector3(min.x, min.y, min.z); this.max = new THREE$1.Vector3(max.x, max.y, max.z); this.trianglePool = []; } _createClass(OctreeNode, [{ key: "getParentNode", value: function getParentNode() { if (this.depth === 0) return null; this.tree.nodes[this.depth][this.mortonNumber >> 3]; } }, { key: "getChildNodes", value: function getChildNodes() { if (this.tree.maxDepth === this.depth) { return null; } var firstChild = this.mortonNumber << 3; return [this.tree.nodes[this.depth + 1][firstChild], this.tree.nodes[this.depth + 1][firstChild + 1], this.tree.nodes[this.depth + 1][firstChild + 2], this.tree.nodes[this.depth + 1][firstChild + 3], this.tree.nodes[this.depth + 1][firstChild + 4], this.tree.nodes[this.depth + 1][firstChild + 5], this.tree.nodes[this.depth + 1][firstChild + 6], this.tree.nodes[this.depth + 1][firstChild + 7]]; } }]); return OctreeNode; }(); // // a: <THREE.Vector3> // b: <THREE.Vector3> // c: <THREE.Vector3> // normal: <THREE.Vector3> // meshID: <String> var Face = /*#__PURE__*/_createClass(function Face(a, b, c, normal, meshID) { _classCallCheck(this, Face); this.a = a.clone(); this.b = b.clone(); this.c = c.clone(); this.normal = normal.clone(); this.meshID = meshID; }); // origin : <THREE.Vector3> // direction: <THREE.Vector3> // distance : <Float> // class Ray{ // constructor( origin, direction, distance ) { // this.origin = origin; // this.direction = direction; // this.distance = distance; // } // } var sphere; onInstallHandlers.push(function () { sphere = new THREE$1.Sphere(); }); var World = /*#__PURE__*/function () { function World() { _classCallCheck(this, World); this.colliderPool = []; this.characterPool = []; } _createClass(World, [{ key: "add", value: function add(object) { if (object.isOctree) { this.colliderPool.push(object); } else if (object.isCharacterController) { this.characterPool.push(object); object.world = this; } } }, { key: "step", value: function step(dt) { for (var i = 0, l = this.characterPool.length; i < l; i++) { var character = this.characterPool[i]; var faces = void 0; // octree で絞られた node に含まれる face だけを // character に渡して判定する for (var ii = 0, ll = this.colliderPool.length; ii < ll; ii++) { var octree = this.colliderPool[ii]; sphere.set(character.center, character.radius + character.groundPadding); var intersectedNodes = octree.getIntersectedNodes(sphere, octree.maxDepth); faces = Octree.uniqTrianglesFromNodes(intersectedNodes); } character.collisionCandidate = faces; character.update(dt); } } }]); return World; }(); // based on https://github.com/mrdoob/eventdispatcher.js/ var EventDispatcher$1 = /*#__PURE__*/function () { function EventDispatcher() { _classCallCheck(this, EventDispatcher); this._listeners = {}; } _createClass(EventDispatcher, [{ key: "addEventListener", value: function addEventListener(type, listener) { var listeners = this._listeners; if (listeners[type] === undefined) { listeners[type] = []; } if (listeners[type].indexOf(listener) === -1) { listeners[type].push(listener); } } }, { key: "hasEventListener", value: function hasEventListener(type, listener) { var listeners = this._listeners; return listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1; } }, { key: "removeEventListener", value: function removeEventListener(type, listener) { var listeners = this._listeners; var listenerArray = listeners[type]; if (listenerArray !== undefined) { var index = listenerArray.indexOf(listener); if (index !== -1) { listenerArray.splice(index, 1); } } } }, { key: "dispatchEvent", value: function dispatchEvent(event) { var listeners = this._listeners; var listenerArray = listeners[event.type]; if (listenerArray !== undefined) { event.target = this; var array = listenerArray.slice(0); for (var i = 0, l = array.length; i < l; i++) { array[i].call(this, event); } } } }]); return EventDispatcher; }(); var PI_HALF$1 = Math.PI * 0.5; var PI_ONE_HALF = Math.PI * 1.5; var direction2D; var wallNormal2D; var groundingHead; var groundingTo; var point1; var point2; var direction; var translateScoped; var translate; onInstallHandlers.push(function () { direction2D = new THREE$1.Vector2(); wallNormal2D = new THREE$1.Vector2(); groundingHead = new THREE$1.Vector3(); groundingTo = new THREE$1.Vector3(); point1 = new THREE$1.Vector3(); point2 = new THREE$1.Vector3(); direction = new THREE$1.Vector3(); translateScoped = new THREE$1.Vector3(); translate = new THREE$1.Vector3(); }); var CharacterController = /*#__PURE__*/function (_EventDispatcher) { _inherits(CharacterController, _EventDispatcher); var _super = _createSuper(CharacterController); function CharacterController(object3d, radius, head, height) { var _this; _classCallCheck(this, CharacterController); _this = _super.call(this); _this.isCharacterController = true; _this.object = object3d; _this.headObject = head; _this.height = height; _this.fallVelocity = -20; // 下落速度 _this.jumpDuration = 1000; // 跳跃过程时间 _this.headHeight = 5; _this.center = _this.object.position.clone(); _this.radius = radius; _this.groundPadding = 0.5; _this.maxSlopeGradient = Math.cos(50 * THREE$1.Math.DEG2RAD); _this.isGrounded = false; _this.isOnSlope = false; _this.isIdling = false; _this.isRunning = false; _this.isWalking = false; _this.isJumping = false; _this.direction = 0; // 0 to 2PI(=360deg) in rad _this.movementSpeed = 10; // Meters Per Second _this.walkSpeed = 10; // Meters Per Second _this.runSpeed = 30; // Meters Per Second _this.moveAction = "run"; _this.velocity = new THREE$1.Vector3(0, -10, 0); _this.currentJumpPower = 0; _this.jumpStartTime = 0; _this.groundHeight = 0; _this.groundNormal = new THREE$1.Vector3(); _this.collisionCandidate; _this.contactInfo = []; var isFirstUpdate = true; var wasGrounded; var wasOnSlope; // let wasIdling; var wasRunning; var wasJumping; var lastMoveAction; _this._events = function () { // 首次执行内容 if (isFirstUpdate) { isFirstUpdate = false; wasGrounded = _this.isGrounded; wasOnSlope = _this.isOnSlope; // wasIdling = this.isIdling; wasRunning = _this.isRunning; wasJumping = _this.isJumping; return; } var moveName = _this.isWalking ? "walk" : "run"; if (!wasRunning && !_this.isRunning && _this.isGrounded && !_this.isIdling) { _this.isIdling = true; _this.dispatchEvent({ type: "startIdling" }); } else if (!wasRunning && _this.isRunning && !_this.isJumping && _this.isGrounded || !wasGrounded && _this.isGrounded && _this.isRunning || wasOnSlope && !_this.isOnSlope && _this.isRunning && _this.isGrounded) { _this.isIdling = false; if (_this.isWalking) { _this.dispatchEvent({ type: "startWalking" }); } else { _this.dispatchEvent({ type: "startRunning" }); } } else if (!wasJumping && _this.isJumping) { _this.isIdling = false; _this.dispatchEvent({ type: "startJumping" }); } else if (!wasOnSlope && _this.isOnSlope) { _this.dispatchEvent({ type: "startSliding" }); } else if (wasGrounded && !_this.isGrounded && !_this.isJumping) { _this.dispatchEvent({ type: "startFalling" }); } else if (wasRunning && _this.isRunning && lastMoveAction !== moveName) { lastMoveAction = moveName; if (_this.isWalking) { _this.dispatchEvent({ type: "startWalking" }); } else { _this.dispatchEvent({ type: "startRunning" }); } } if (!wasGrounded && _this.isGrounded) ; wasGrounded = _this.isGrounded; wasOnSlope = _this.isOnSlope; // wasIdling = this.isIdling; wasRunning = _this.isRunning; wasJumping = _this.isJumping; }; return _this; } _createClass(CharacterController, [{ key: "update", value: function update(dt) { // 重置状态 this.isGrounded = false; this.isOnSlope = false; this.groundHeight = -Infinity; this.groundNormal.set(0, 1, 0); this._updateGrounding(); this._updateJumping(); this._updatePosition(dt); this._collisionDetection(); this._solvePosition(); this._updateVelocity(); this._events(); } }, { key: "_updateVelocity", value: function _updateVelocity() { var frontDirection = -Math.cos(this.direction); var rightDirection = -Math.sin(this.direction); var isHittingCeiling = false; var moveSpeed = this.isWalking ? this.walkSpeed : this.runSpeed; this.velocity.set(rightDirection * moveSpeed * this.isRunning, this.fallVelocity, frontDirection * moveSpeed * this.isRunning); // 处理自动应用的速度,例如陡坡和自由落体 if (this.contactInfo.length === 0 && !this.isJumping) { // 没有碰撞,所以自由落体 return; } else if (this.isGrounded && !this.isOnSlope && !this.isJumping) { // 如果你在正常的地面上,除了在跳跃开始时 this.velocity.y = 0; } else if (this.isOnSlope) { // TODO 0.2 是一个神奇的数字,所以想想如何从几何上找到它。 var slidingDownVelocity = this.fallVelocity; var horizontalSpeed = -slidingDownVelocity / (1 - this.groundNormal.y) * 0.2; this.velocity.x = this.groundNormal.x * horizontalSpeed; this.velocity.y = this.fallVelocity; this.velocity.z = this.groundNormal.z * horizontalSpeed; } else if (!this.isGrounded && !this.isOnSlope && this.isJumping) { // 跳跃处理 this.velocity.y = this.currentJumpPower * -this.fallVelocity; } // 面向墙壁时将向墙壁的速度设置为0的处理 // vs 墙壁和在墙上滑动 direction2D.set(rightDirection, frontDirection); // const frontAngle = Math.atan2( direction2D.y, direction2D.x ); var negativeFrontAngle = Math.atan2(-direction2D.y, -direction2D.x); for (var i = 0, l = this.contactInfo.length; i < l; i++) { var normal = this.contactInfo[i].face.normal; // var distance = this.contactInfo[ i ].distance; if (this.maxSlopeGradient < normal.y || this.isOnSlope) { // 由于人脸是在地面上,所以没有像墙一样碰撞的可能性。 // 不衰减速度 continue; } if (!isHittingCeiling && normal.y < 0) { isHittingCeiling = true; } wallNormal2D.set(normal.x, normal.z).normalize(); var wallAngle = Math.atan2(wallNormal2D.y, wallNormal2D.x); if (Math.abs(negativeFrontAngle - wallAngle) >= PI_HALF$1 && // 90deg Math.abs(negativeFrontAngle - wallAngle) <= PI_ONE_HALF // 270deg ) { // 面与行进方向相反,点为背面的墙 // 不衰减速度 continue; } // 如果不满足以上条件,人脸就是墙 // 找到墙壁法线并将指向相反方向的速度向量设置为0 wallNormal2D.set(direction2D.dot(wallNormal2D) * wallNormal2D.x, direction2D.dot(wallNormal2D) * wallNormal2D.y); direction2D.sub(wallNormal2D); var _moveSpeed = this.isWalking ? this.walkSpeed : this.runSpeed; this.velocity.x = direction2D.x * _moveSpeed * this.isRunning; this.velocity.z = direction2D.y * _moveSpeed * this.isRunning; } // 如果在跳跃时撞到天花板,中断跳跃 if (isHittingCeiling) { this.velocity.y = Math.min(0, this.velocity.y); this.isJumping = false; } } }, { key: "_updateGrounding", value: function _updateGrounding() { // “从头顶到几乎无限向下的分段 (segment)”与“三角形(triangle)” // 进行交叉判断 // 如果与面部的交点在“头顶”和“地下填充(groundPadding)”之间 // isGrounded认定在地面上 // // ___ // / | \ // | | | player sphere 玩家 // \_|_/ // | //---[+]---- ground 地面 // | // | // | segment (玩家头部到无限) var groundContactInfo; var groundContactInfoTmp; var faces = this.collisionCandidate; groundingHead.set(this.center.x, this.center.y + this.radius, this.center.z); groundingTo.set(this.center.x, this.center.y - 1e10, this.center.z); for (var i = 0, l = faces.length; i < l; i++) { groundContactInfoTmp = testSegmentTriangle(groundingHead, groundingTo, faces[i].a, faces[i].b, faces[i].c); if (groundContactInfoTmp && !groundContactInfo) { groundContactInfo = groundContactInfoTmp; groundContactInfo.face = faces[i]; } else if (groundContactInfoTmp && groundContactInfoTmp.contactPoint.y > groundContactInfo.contactPoint.y) { groundContactInfo = groundContactInfoTmp; groundContactInfo.face = faces[i]; } } if (!groundContactInfo) { return; } this.groundHeight = groundContactInfo.contactPoint.y; // this.groundNormal.copy(groundContactInfo.face.normal); var top = groundingHead.y; var bottom = this.center.y - this.radius - this.groundPadding; // 跳跃和向上移动时不要强迫地面 if (this.isJumping && 0 < this.currentJumpPower) { this.isOnSlope = false; this.isGrounded = false; return; } this.isGrounded = bottom <= this.groundHeight && this.groundHeight <= top; this.isOnSlope = this.groundNormal.y <= this.maxSlopeGradient; if (this.isGrounded) { this.isJumping = false; } } }, { key: "_updatePosition", value: function _updatePosition(dt) { // 暂时忽略墙壁等(速度*时间) // 推进中心坐标 // 与墙壁的碰撞检测会在下一步进行,这里就不做了 // 如果是isGrounded,强制将y的值调整到地面 var groundedY = this.groundHeight + this.radius; var x = this.center.x + this.velocity.x * dt; var y = this.center.y + this.velocity.y * dt; var z = this.center.z + this.velocity.z * dt; this.center.set(x, this.isGrounded ? groundedY : y, z); } }, { key: "_collisionDetection", value: function _collisionDetection() { // 从可能相交的人脸列表中(collisionCandidate) // 提取实际相交的墙面 // 添加到 this.contactInfo var faces = this.collisionCandidate; this.contactInfo.length = 0; for (var i = 0, l = faces.length; i < l; i++) { var contactInfo = isIntersectionSphereTriangle(this, faces[i].a, faces[i].b, faces[i].c, faces[i].normal); if (!contactInfo) continue; contactInfo.face = faces[i]; this.contactInfo.push(contactInfo); } } }, { key: "_calHeadPosition", value: function _calHeadPosition(position) { var center = { x: position.x, y: position.y + 1 + this.height, z: position.z }; return center; } }, { key: "_solvePosition", value: function _solvePosition() { // 用 updatePosition() 运行中心后 // 如果它与墙壁碰撞并咬入它 // 这里从墙中挤出 var face; var normal; // let distance; if (this.contactInfo.length === 0) { // 没有冲突 // 使用 center 的值退出 var _newPosition = { x: this.center.x, y: this.center.y - this.radius, z: this.center.z }; this.object.position.copy(_newPosition); this.headObject.position.copy(this._calHeadPosition(_newPosition)); return; } // // vs 墙壁和在墙上滑动 translate.set(0, 0, 0); for (var i = 0, l = this.contactInfo.length; i < l; i++) { face = this.contactInfo[i].face; normal = this.contactInfo[i].face.normal; // distance = this.contactInfo[ i ].distance; // if ( 0 <= distance ) { // // 交差点までの距離が 0 以上ならこのフェイスとは衝突していない // // 無視する // continue; // } if (this.maxSlopeGradient < normal.y) { // 这个三角形是地面或斜坡,而不是墙壁或天花板 // 面是非陡坡,即地面。 // 忽略接地过程,因为它在 updatePosition () 中解决 continue; } // 面是否为陡坡 var isSlopeFace = this.maxSlopeGradient <= face.normal.y && face.normal.y < 1; // 如果您在跳跃下降过程中遇到陡坡,则跳跃结束 if (this.isJumping && 0 >= this.currentJumpPower && isSlopeFace) { this.isJumping = false; this.isGrounded = true; // console.log( 'jump end' ); } if (this.isGrounded || this.isOnSlope) { // 如果您在地面上,y(垂直)方向保持不变 // 通过仅更改 x、z(水平)方向进行拉伸 // http://gamedev.stackexchange.com/questions/80293/how-do-i-resolve-a-sphere-triangle-collision-in-a-given-direction point1.copy(normal).multiplyScalar(-this.radius).add(this.center); direction.set(normal.x, 0, normal.z).normalize(); var plainD = face.a.dot(normal); var t = (plainD - (normal.x * point1.x + normal.y * point1.y + normal.z * point1.z)) / (normal.x * direction.x + normal.y * direction.y + normal.z * direction.z); point2.copy(direction).multiplyScalar(t).add(point1); translateScoped.subVectors(point2, point1); if (Math.abs(translate.x) > Math.abs(translateScoped.x)) { translate.x += translateScoped.x; } if (Math.abs(translate.z) > Math.abs(translateScoped.z)) { translate.z += translateScoped.z; } // break; continue; } } var newPosition = { x: this.center.x, y: this.center.y - this.radius, z: this.center.z }; this.object.position.copy(newPosition); this.headObject.position.copy(this._calHeadPosition(newPosition)); } }, { key: "setDirection", value: function setDirection() {} }, { key: "jump", value: function jump() { if (this.isJumping || !this.isGrounded || this.isOnSlope) return; this.jumpStartTime = performance.now(); this.currentJumpPower = 1; this.isJumping = true; } }, { key: "_updateJumping", value: function _updateJumping() { if (!this.isJumping) return; var elapsed = performance.now() - this.jumpStartTime; var progress = elapsed / this.jumpDuration; this.currentJumpPower = Math.cos(Math.min(progress, 1) * Math.PI); } }]); return CharacterController; }(EventDispatcher$1); var TURN_DURATION = 200; var TAU = 2 * Math.PI; var modulo = function modulo(n, d) { return (n % d + d) % d; }; var getDeltaTurnAngle = function getDeltaTurnAngle(current, target) { var a = modulo(current - target, TAU); var b = modulo(target - current, TAU); return a < b ? -a : b; }; var AnimationController = /*#__PURE__*/function () { function AnimationController(mesh) { _classCallCheck(this, AnimationController); this.mesh = mesh; this.motion = {}; this.mixer = new THREE$1.AnimationMixer(mesh.scene); this.currentMotionName = ""; for (var i = 0, l = this.mesh.animations.length; i < l; i++) { var anim = this.mesh.animations[i]; this.motion[anim.name] = this.mixer.clipAction(anim); this.motion[anim.name].setEffectiveWeight(1); } } _createClass(AnimationController, [{ key: "play", value: function play(name) { if (this.currentMotionName === name) return; if (this.motion[this.currentMotionName]) { var from = this.motion[this.currentMotionName].play(); var to = this.motion[name].play(); from.enabled = true; to.enabled = true; from.crossFadeTo(to, 0.3); } else { this.motion[name].enabled = true; this.motion[name].play(); } this.currentMotionName = name; } }, { key: "turn", value: function turn(rad, immediate) { var that = this; var prevRotY = this.mesh.rotation.y; var targetRotY = rad; var deltaY = getDeltaTurnAngle(prevRotY, targetRotY); // const duration = Math.abs( deltaY ) * 100; var start = Date.now(); var end = start + TURN_DURATION; var progress = 0; if (immediate) { this.mesh.rotation.y = targetRotY; return; } if (this._targetRotY === targetRotY) return; this._targetRotY = targetRotY; { var _targetRotY = targetRotY; (function interval() { var now = Date.now(); var isAborted = _targetRotY !== that._targetRotY; if (isAborted) return; if (now >= end) { that.mesh.rotation.y = _targetRotY; delete that._targetRotY; return; } requestAnimationFrame(interval); progress = (now - start) / TURN_DURATION; that.mesh.rotation.y = prevRotY + deltaY * progress; })(); } } }, { key: "update", value: function update(delta) { this.mixer.update(delta); } }]); return AnimationController; }(); var KEY_W = 87; var KEY_UP = 38; var KEY_S = 83; var KEY_DOWN = 40; var KEY_A = 65; var KEY_LEFT = 37; var KEY_D = 68; var KEY_RIGHT = 39; var KEY_SPACE = 32; var DEG2RAD = Math.PI / 180; var DEG_0 = 0 * DEG2RAD; var DEG_45 = 45 * DEG2RAD; var DEG_90 = 90 * DEG2RAD; var DEG_135 = 135 * DEG2RAD; var DEG_180 = 180 * DEG2RAD; var DEG_225 = 225 * DEG2RAD; var DEG_270 = 270 * DEG2RAD; var DEG_315 = 315 * DEG2RAD; var KeyInputControl = /*#__PURE__*/function (_EventDispatcher) { _inherits(KeyInputControl, _EventDispatcher); var _super = _createSuper(KeyInputControl); function KeyInputControl() { var _this; _classCallCheck(this, KeyInputControl); _this = _super.call(this); _this.isDisabled = false; _this.isUp = false; _this.isDown = false; _this.isLeft = false; _this.isRight = false; _this.isMoveKeyHolding = false; _this.frontAngle = 0; _this._keydownListener = onKeyDown.bind(_assertThisInitialized(_this)); _this._keyupListener = onKeyUp.bind(_assertThisInitialized(_this)); _this._blurListener = onBlur.bind(_assertThisInitialized(_this)); window.addEventListener('keydown', _this._keydownListener); window.addEventListener('keyup', _this._keyupListener); window.addEventListener('blur', _this._blurListener); return _this; } _createClass(KeyInputControl, [{ key: "jump", value: function jump() { this.dispatchEvent({ type: 'jumpkeypress' }); } }, { key: "updateAngle", value: function updateAngle() { var up = this.isUp; var down = this.isDown; var left = this.isLeft; var right = this.isRight; if (up && !left && !down && !right) this.frontAngle = DEG_0;else if (up && left && !down && !right) this.frontAngle = DEG_45;else if (!up && left && !down && !right) this.frontAngle = DEG_90;else if (!up && left && down && !right) this.frontAngle = DEG_135;else if (!up && !left && down && !right) this.frontAngle = DEG_180;else if (!up && !left && down && right) this.frontAngle = DEG_225;else if (!up && !left && !down && right) this.frontAngle = DEG_270;else if (up && !left && !down && right) this.frontAngle = DEG_315; } }]); return KeyInputControl; }(EventDispatcher$1); function onKeyDown(event) { if (this.isDisabled) return; if (isInputEvent(event)) return; switch (event.keyCode) { case KEY_W: case KEY_UP: this.isUp = true; break; case KEY_S: case KEY_DOWN: this.isDown = true; break; case KEY_A: case KEY_LEFT: this.isLeft = true; break; case KEY_D: case KEY_RIGHT: this.isRight = true; break; case KEY_SPACE: this.jump(); break; default: return; } var prevAngle = this.frontAngle; this.updateAngle(); if (prevAngle !== this.frontAngle) { this.dispatchEvent({ type: 'movekeychange' }); } if ((this.isUp || this.isDown || this.isLeft || this.isRight) && !this.isMoveKeyHolding) { this.isMoveKeyHolding = true; this.dispatchEvent({ type: 'movekeyon' }); } } function onKeyUp(event) { if (this.isDisabled) return; switch (event.keyCode) { case KEY_W: case KEY_UP: this.isUp = false; break; case KEY_S: case KEY_DOWN: this.isDown = false; break; case KEY_A: case KEY_LEFT: this.isLeft = false; break; case KEY_D: case KEY_RIGHT: this.isRight = false; break; case KEY_SPACE: break; default: return; } var prevAngle = this.frontAngle; this.updateAngle(); if (prevAngle !== this.frontAngle) { this.dispatchEvent({ type: 'movekeychange' }); } if (!this.isUp && !this.isDown && !this.isLeft && !this.isRight && (event.keyCode === KEY_W || event.keyCode === KEY_UP || event.keyCode === KEY_S || event.keyCode === KEY_DOWN || event.keyCode === KEY_A || event.keyCode === KEY_LEFT || event.keyCode === KEY_D || event.keyCode === KEY_RIGHT)) { this.isMoveKeyHolding = false; this.dispatchEvent({ type: 'movekeyoff' }); } } function onBlur() { this.isUp = false; this.isDown = false; this.isLeft = false; this.isRight = false; if (this.isMoveKeyHolding) { this.isMoveKeyHolding = false; this.dispatchEvent({ type: 'movekeyoff' }); } } function isInputEvent(event) { var target = event.target; return target.tagName === 'INPUT' || target.tagName === 'SELECT' || target.tagName === 'TEXTAREA' || target.isContentEditable; } /*! * ggabcd-camera-controls * https://github.com/yomotsu/camera-controls * (c) 2017 @yomotsu * Released under the MIT License. */ var ACTION; (function (ACTION) { ACTION[ACTION["NONE"] = 0] = "NONE"; ACTION[ACTION["ROTATE"] = 1] = "ROTATE"; ACTION[ACTION["TRUCK"] = 2] = "TRUCK"; ACTION[ACTION["OFFSET"] = 3] = "OFFSET"; ACTION[ACTION["DOLLY"] = 4] = "DOLLY"; ACTION[ACTION["ZOOM"] = 5] = "ZOOM"; ACTION[ACTION["TOUCH_ROTATE"] = 6] = "TOUCH_ROTATE"; ACTION[ACTION["TOUCH_TRUCK"] = 7] = "TOUCH_TRUCK"; ACTION[ACTION["TOUCH_OFFSET"] = 8] = "TOUCH_OFFSET"; ACTION[ACTION["TOUCH_DOLLY"] = 9] = "TOUCH_DOLLY"; ACTION[ACTION["TOUCH_ZOOM"] = 10] = "TOUCH_ZOOM"; ACTION[ACTION["TOUCH_DOLLY_TRUCK"] = 11] = "TOUCH_DOLLY_TRUCK"; ACTION[ACTION["TOUCH_DOLLY_OFFSET"] = 12] = "TOUCH_DOLLY_OFFSET"; ACTION[ACTION["TOUCH_ZOOM_TRUCK"] = 13] = "TOUCH_ZOOM_TRUCK"; ACTION[ACTION["TOUCH_ZOOM_OFFSET"] = 14] = "TOUCH_ZOOM_OFFSET"; })(ACTION || (ACTION = {})); function isPerspectiveCamera(camera) { return camera.isPerspectiveCamera; } function isOrthographicCamera(camera) { return camera.isOrt