/** * World SuperSet - contains the custom world instance * @param id * @param name * @param engine * @param gravity * @param broadphase * @param solver * @param rigidBodies * @constructor */ R3.D3.World = function( id, name, engine, gravity, broadphase, solver, rigidBodies ) { this.id = id; this.name = name; if (typeof gravity == 'undefined') { gravity = new R3.API.Vector3(0, -9.81, 0); } this.gravity = gravity; if (typeof broadphase == 'undefined') { broadphase = new R3.D3.Broadphase( null, 'broadPhaseNaive', R3.D3.Broadphase.BROADPHASE_TYPE_NAIVE, engine ); } this.broadphase = broadphase; if (typeof solver == 'undefined') { solver = new R3.D3.Solver( null, engine, R3.D3.Solver.GS_SOLVER ); } this.solver = solver; if (typeof rigidBodies == 'undefined') { rigidBodies = []; } this.rigidBodies = rigidBodies; this.engine = engine; this.engine.isNotCannonThrow(); this.instance = this.createInstance(); }; /** * private * @returns {R3.D3.World|R3.D3.Physics.World|*} */ R3.D3.World.prototype.createInstance = function() { var instance = new this.engine.instance.World(); instance.broadphase = this.broadphase.instance; instance.solver = this.solver.instance; instance.gravity.x = this.gravity.x; instance.gravity.y = this.gravity.y; instance.gravity.z = this.gravity.z; instance.name = this.name; return instance; }; R3.D3.World.prototype.toApiWorld = function() { //TODO: create API.World someday return { id: this.id, name: this.name, engine: this.engine.toApiEngine(), gravity: this.gravity, broadphase: this.broadphase.toApiBroadphase(), solver: this.solver.toApiSolver(), rigidBodies: this.rigidBodies.map(function(rigidBody){return rigidBody.toApiRigidBody()}) } }; R3.D3.World.FromObjectWorld = function(engine, objectWorld) { //todo implement this fully return new R3.D3.World( objectWorld.id, objectWorld.name, engine, objectWorld.gravity, null, null, null ); }; /** * * @param rigidBody R3.D3.RigidBody * @constructor */ R3.D3.World.prototype.addRigidBody = function( rigidBody ) { this.instance.addBody(rigidBody.instance); }; /** * * @param vehicle (R3.D3.RigidBodyVehicle | R3.D3.RaycastVehicle) * @constructor */ R3.D3.World.prototype.addVehicle = function( vehicle ) { vehicle.instance.addToWorld(this.instance); }; R3.D3.World.prototype.step = function( fixedStep, dtStep ) { this.instance.step(fixedStep, dtStep, 3); return; var now = Date.now() / 1000.0; //var now = null; if(!this.lastCallTime){ // last call time not saved, cant guess elapsed time. Take a simple step. this.instance.step(fixedStep); this.lastCallTime = now; return; } var timeSinceLastCall = (now - this.lastCallTime); this.instance.step(fixedStep, timeSinceLastCall, 4); this.lastCallTime = now; }; R3.D3.World.prototype.GetIndexedVertices = function( triangleMeshShape ) { if(this.engine.engineType == R3.D3.Physics.TYPE_CANNON) { return { vertices : triangleMeshShape.vertices, indices : triangleMeshShape.indices }; } else { // todo: implement this for other physics engines. return null; } }; /** * @param triangleMeshShape R3.D3.Shape * @param normalLength Number * @param scale R3.API.Vector3 * @param opacity Number * @param wireframeColor HexCode * @param graphics THREE * @returns {THREE.Mesh|this.meshes} * @constructor */ R3.D3.World.prototype.generateWireframeViewTriangleMesh = function( graphics, triangleMeshShape, normalLength, scale, opacity, wireframeColor ) { graphics.isNotThreeThrow(); this.engine.isNotCannonThrow(); if(typeof normalLength == 'undefined') { normalLength = 10; } if(typeof scale == 'undefined') { scale = new graphics.instance.Vector3(1, 1, 1); } if(typeof opacity == 'undefined') { opacity = 0.5; } if(typeof wireframeColor == 'undefined') { wireframeColor = 0xfefefe; } var graphicsGeometry = new graphics.instance.Geometry(); var wireframeMesh = new graphics.instance.Mesh( graphicsGeometry, new graphics.instance.MeshBasicMaterial({ color: wireframeColor, wireframe: true, opacity: opacity }) ); for(var v = 0, l = triangleMeshShape.instance.vertices.length / 3; v < l; ++v) { graphicsGeometry.vertices.push( new graphics.instance.Vector3( triangleMeshShape.instance.vertices[v * 3], triangleMeshShape.instance.vertices[v * 3 + 1], triangleMeshShape.instance.vertices[v * 3 + 2] ) ); } for(var i = 0, l = triangleMeshShape.instance.indices.length / 3; i < l; ++i) { var i0 = triangleMeshShape.instance.indices[i * 3]; var i1 = triangleMeshShape.instance.indices[i * 3 + 1]; var i2 = triangleMeshShape.instance.indices[i * 3 + 2]; graphicsGeometry.faces.push( new graphics.instance.Face3( i0, i1, i2 ) ); // Center point on the current triangle var centroid = new graphics.instance.Vector3() .add(graphicsGeometry.vertices[i0]) .add(graphicsGeometry.vertices[i1]) .add(graphicsGeometry.vertices[i2]) .divideScalar(3); // Get the normal from the mesh shape itself var normal = new this.engine.instance.Vec3(); triangleMeshShape.instance.getNormal(i , normal); var arrow = new graphics.instance.ArrowHelper( new graphics.instance.Vector3( normal.x, normal.y, normal.z ), centroid, normalLength, new graphics.instance.Color( normal.x, normal.y, normal.z ) ); wireframeMesh.add( arrow ); } wireframeMesh.scale.x = scale.x; wireframeMesh.scale.y = scale.y; wireframeMesh.scale.z = scale.z; return wireframeMesh; }; /** * @param convexPolyMeshShape R3.D3.Shape * @param normalLength Number * @param scale R3.API.Vector3 * @param opacity Number * @param wireframeColor HexCode * @param graphics THREE * @returns {THREE.Mesh|this.meshes} * @constructor */ R3.D3.World.prototype.generateWireframeViewConvexPolyMesh = function( graphics, convexPolyMeshShape, normalLength, scale, opacity, wireframeColor ) { graphics.isNotThreeThrow(); this.engine.isNotCannonThrow(); if(typeof normalLength == 'undefined') { normalLength = 10; } if(typeof scale == 'undefined') { scale = new graphics.instance.Vector3(1, 1, 1); } if(typeof opacity == 'undefined') { opacity = 0.5; } if(typeof wireframeColor == 'undefined') { wireframeColor = 0xfefefe; } var graphicsGeometry = new graphics.instance.Geometry(); var wireframeMesh = new graphics.instance.Mesh( graphicsGeometry, new graphics.instance.MeshBasicMaterial({ color: wireframeColor, wireframe: true, opacity: opacity }) ); for(var i = 0, l = convexPolyMeshShape.instance.vertices.length; i < l; i++) { var vertex = convexPolyMeshShape.instance.vertices[i]; graphicsGeometry.vertices.push(new graphics.instance.Vector3(vertex.x, vertex.y, vertex.z)); } for(var i = 0, l = convexPolyMeshShape.instance.faces.length; i < l; i++) { var face = convexPolyMeshShape.instance.faces[i]; var i0 = face[0]; var i1 = face[1]; var i2 = face[2]; graphicsGeometry.faces.push(new graphics.instance.Face3(i0, i1, i2)); // Center point on the current triangle var centroid = new graphics.instance.Vector3() .add(graphicsGeometry.vertices[i0]) .add(graphicsGeometry.vertices[i1]) .add(graphicsGeometry.vertices[i2]) .divideScalar(3); var normalVec3 = convexPolyMeshShape.instance.faceNormals[i]; var normal = new graphics.instance.Vector3( normalVec3.x, normalVec3.y, normalVec3.z ); var arrow = new graphics.instance.ArrowHelper( normal, centroid, normalLength, new graphics.instance.Color( normal.x, normal.y, normal.z ) ); wireframeMesh.add( arrow ); } wireframeMesh.scale.x = scale.x; wireframeMesh.scale.y = scale.y; wireframeMesh.scale.z = scale.z; return wireframeMesh; }; /** * @param graphics R3.D3.Graphics * @param graphicsMesh THREE.Mesh * @param mass Number * @param friction Number * @param createCollisionSubMeshes Boolean * @param facesPerSubsection Number * @param subsectionsToMerge Number * @returns {Object} * @constructor */ R3.D3.World.prototype.generateTriangleMeshShapeDivided = function( graphics, graphicsMesh, mass, friction, createCollisionSubMeshes, facesPerSubsection, subsectionsToMerge ) { graphics.isNotThreeThrow(); this.engine.isNotCannonThrow(); if(mass == null || typeof mass == 'undefined') { mass = 0; } if(friction == null || typeof friction == 'undefined') { friction = 10; } if(createCollisionSubMeshes == null || typeof createCollisionSubMeshes == 'undefined') { createCollisionSubMeshes = false; } var processedFaces = 0; var facesPerSubSection = facesPerSubsection || 0; var subMeshesToMerge = subsectionsToMerge || 0; var totalAmtFaces = graphicsMesh.geometry.faces.length; var facesToProcess = createCollisionSubMeshes ? (subMeshesToMerge * facesPerSubSection) : totalAmtFaces; var pairs = []; // output var vertices = []; var indicies = []; for(var i = 0; i <= totalAmtFaces; i++) { if(processedFaces == facesToProcess || i == totalAmtFaces) { var body = null; var meshShape = new this.engine.instance.Trimesh(vertices, indicies); meshShape.setScale(new this.engine.instance.Vec3( graphicsMesh.scale.x, graphicsMesh.scale.y, graphicsMesh.scale.z )); meshShape.updateAABB(); meshShape.updateNormals(); meshShape.updateEdges(); meshShape.updateBoundingSphereRadius(); meshShape.updateTree(); body = new this.engine.instance.Body({ mass: mass, friction: friction }); body.addShape(meshShape); pairs.push({ threeObject : createCollisionSubMeshes ? null : graphicsMesh, physicsObject : body }); vertices = []; indicies = []; processedFaces = 0; if(i == totalAmtFaces) { return pairs; } } var face = graphicsMesh.geometry.faces[i]; indicies.push(indicies.length); indicies.push(indicies.length); indicies.push(indicies.length); var v0 = graphicsMesh.geometry.vertices[face.a]; var v1 = graphicsMesh.geometry.vertices[face.b]; var v2 = graphicsMesh.geometry.vertices[face.c]; vertices.push(v0.x, v0.y, v0.z); vertices.push(v1.x, v1.y, v1.z); vertices.push(v2.x, v2.y, v2.z); processedFaces++; } }; R3.D3.World.prototype.generateConvexPolyShape = function( graphics, mesh ) { var processedFaces = 0; var facesPerSubSection = 2; // *2 -> SUBDIVISION MESH var subMeshesToMerge = 4; // *2 -> SUBDIVISION MESH var facesToProcess = subMeshesToMerge * facesPerSubSection; var vertices = []; var indicies = []; for(var i = 0; i <= mesh.geometry.faces.length; i++) { if(processedFaces == facesToProcess || i == mesh.geometry.faces.length) { // try and create convex poly........... var convexIndices = []; for(var index = 0; index < indicies.length / 3; index++) { convexIndices.push([ indicies[index * 3], indicies[index * 3 + 1], indicies[index * 3 + 2] ]); } var convexVertices = []; for(var vert = 0; vert < vertices.length / 3; vert++) { convexVertices[vert] = new CANNON.Vec3(vertices[vert * 3] * mesh.scale.x, vertices[vert * 3 + 1] * mesh.scale.y, vertices[vert * 3 + 2] * mesh.scale.z); } var meshShape = new R3.D3.Shape(this.engine, R3.D3.Shape.SHAPE_TYPE_CONVEX_HULL, {x:1,y:1,z:1},convexVertices, convexIndices); var body = new R3.D3.RigidBody(this.engine, 0, 1); body.addShape(meshShape); this.addRigidBody(body); vertices = []; indicies = []; processedFaces = 0; console.log("SPLIT MESH TO CONVEX POLY"); if(i == mesh.geometry.faces.length) { break; } } var face = mesh.geometry.faces[i]; indicies.push(indicies.length); indicies.push(indicies.length); indicies.push(indicies.length); var v0 = mesh.geometry.vertices[face.a]; var v1 = mesh.geometry.vertices[face.b]; var v2 = mesh.geometry.vertices[face.c]; vertices.push(v0.x, v0.y, v0.z); vertices.push(v1.x, v1.y, v1.z); vertices.push(v2.x, v2.y, v2.z); processedFaces++; } }; /** * @param graphics R3.D3.Graphics * @param graphicsMesh THREE.Mesh * @returns {R3.D3.Shape} * @constructor */ R3.D3.World.prototype.generateTriangleMeshShape = function( graphics, graphicsMesh ) { // - - - - - - - - - - - - - - - - - - - - - - - - - // Note: I did not test this yet with the API data. // - - - - - - - - - - - - - - - - - - - - - - - - - var scaledVertices = []; for(var i = 0, l = graphicsMesh.geometry.vertices.length; i < l; i++) { var vertex = graphicsMesh.geometry.vertices[i]; scaledVertices.push(new this.engine.instance.Vec3( vertex.x * graphicsMesh.scale.x, vertex.y * graphicsMesh.scale.y, vertex.z * graphicsMesh.scale.z )); } var triangleFaces = []; for(var f = 0, fl = graphicsMesh.geometry.faces.length; f < fl; f++) { var i0 = graphicsMesh.geometry.faces[f].a; var i1 = graphicsMesh.geometry.faces[f].b; var i2 = graphicsMesh.geometry.faces[f].c; triangleFaces.push([ i0, i1, i2 ]); } // - - - - - - - - - - - - - - - - - - - // Create collision mesh // - - - - - - - - - - - - - - - - - - - var reindexedFaces = {}; var vertices = []; var faces = []; var processedFaces = 0; var totalFacesToProcess = triangleFaces.length; var flLastIndex = 0; for(var f = 0; f < totalFacesToProcess; f++) { var i0 = triangleFaces[f][0]; var i1 = triangleFaces[f][1]; var i2 = triangleFaces[f][2]; if(typeof reindexedFaces[i0] === 'undefined') { vertices.push(scaledVertices[i0].x, scaledVertices[i0].y, scaledVertices[i0].z); reindexedFaces[i0] = flLastIndex; flLastIndex++; } if(typeof reindexedFaces[i1] === 'undefined') { vertices.push(scaledVertices[i1].x, scaledVertices[i1].y, scaledVertices[i1].z); reindexedFaces[i1] = flLastIndex; flLastIndex++; } if(typeof reindexedFaces[i2] === 'undefined') { vertices.push(scaledVertices[i2].x, scaledVertices[i2].y, scaledVertices[i2].z); reindexedFaces[i2] = flLastIndex; flLastIndex++; } faces.push(reindexedFaces[i0], reindexedFaces[i1], reindexedFaces[i2]); processedFaces++; } return new R3.D3.Shape(this.engine, R3.D3.Shape.SHAPE_TYPE_TRIMESH, {x : 1, y : 1, z : 1}, vertices, faces); }; /** * @param triangleMeshBody R3.D3.RigidBody * @param rayscale Number * @param maxTriangleDistance Number * @param createCompoundShape Boolean * @param graphics R3.D3.Graphics * @param triangleMeshShapes R3.D3.Shape[] * @param createDebugView Boolean * @returns {R3.D3.RigidBody} * @constructor */ R3.D3.World.prototype.fixupTriangleMeshShape = function( triangleMeshBody, triangleMeshShapes, rayscale, maxTriangleDistance, createCompoundShape, graphics, createDebugView ) { this.engine.isNotCannonThrow(); graphics.isNotThreeThrow(); if(rayscale == null || typeof rayscale == 'undefined' || rayscale == 0) { rayscale = 10; } if(maxTriangleDistance == null || typeof maxTriangleDistance == 'undefined') { maxTriangleDistance = 13; } var world = this.instance; var raycastResult = new this.engine.instance.RaycastResult(); var brokenFaceIndicators = []; var totalFaces = 0; var totalBrokenFaces = 0; var totalFixedFaces = 0; var fixedTriangleMeshObjects = []; for(var i in triangleMeshShapes) { var trimesh = triangleMeshShapes[i].instance; var brokenFaces = []; totalFaces += (trimesh.indices.length / 3); for(var face = 0; face < trimesh.indices.length / 3; face++) { var i0 = trimesh.indices[face * 3]; var i1 = trimesh.indices[face * 3 + 1]; var i2 = trimesh.indices[face * 3 + 2]; var triangleCenterPoint = new graphics.instance.Vector3() .add(new graphics.instance.Vector3( trimesh.vertices[i0 * 3], trimesh.vertices[i0 * 3 + 1], trimesh.vertices[i0 * 3 + 2]) ) .add(new graphics.instance.Vector3( trimesh.vertices[i1 * 3], trimesh.vertices[i1 * 3 + 1], trimesh.vertices[i1 * 3 + 2]) ) .add(new graphics.instance.Vector3( trimesh.vertices[i2 * 3], trimesh.vertices[i2 * 3 + 1], trimesh.vertices[i2 * 3 + 2]) ) .divideScalar(3); var triangleNormal = new this.engine.instance.Vec3(); trimesh.getNormal(face , triangleNormal); var from = new this.engine.instance.Vec3( triangleCenterPoint.x + triangleNormal.x, triangleCenterPoint.y + triangleNormal.y, triangleCenterPoint.z + triangleNormal.z ); var to = new this.engine.instance.Vec3( from.x - triangleNormal.x * rayscale, from.y - triangleNormal.y * rayscale, from.z - triangleNormal.z * rayscale ); world.raycastClosest(from, to, {}, raycastResult); // visualize results if(createDebugView){ var graphicsGeometry = new graphics.instance.Geometry(); var wireframeMesh = new graphics.instance.Mesh( graphicsGeometry, new graphics.instance.MeshBasicMaterial({ color: 0xff0000, wireframe: true, opacity: 1 }) ); var arrow = new graphics.instance.ArrowHelper( new graphics.instance.Vector3( triangleNormal.x, triangleNormal.y, triangleNormal.z ).normalize(), new graphics.instance.Vector3( from.x, from.y, from.z ), rayscale / 2, raycastResult.hasHit ? new graphics.instance.Color(0, 1, 0) : new graphics.instance.Color(1, 0, 0) ); wireframeMesh.add( arrow ); brokenFaceIndicators.push(wireframeMesh); } if(!raycastResult.hasHit) { brokenFaces.push({ faceIndex : face, vertices : [ new this.engine.instance.Vec3( trimesh.vertices[i0 * 3], trimesh.vertices[i0 * 3 + 1], trimesh.vertices[i0 * 3 + 2] ), new this.engine.instance.Vec3( trimesh.vertices[i1 * 3], trimesh.vertices[i1 * 3 + 1], trimesh.vertices[i1 * 3 + 2] ), new this.engine.instance.Vec3( trimesh.vertices[i2 * 3], trimesh.vertices[i2 * 3 + 1], trimesh.vertices[i2 * 3 + 2] ) ], center : triangleCenterPoint, parent : trimesh }); } } // fix up broken faces var bFaceIndexed = {}; for(var b = 0; b < brokenFaces.length; b++) { var brokenFace = brokenFaces[b]; if(brokenFace.marked) { continue; } bFaceIndexed[b] = { indices : [], vertices : [] }; var indicesAmount = bFaceIndexed[b].indices.length; // add the current broken face itself to the array bFaceIndexed[b].indices.push( indicesAmount, indicesAmount + 1, indicesAmount + 2 ); bFaceIndexed[b].vertices.push( brokenFace.vertices[0].x, brokenFace.vertices[0].y, brokenFace.vertices[0].z ); bFaceIndexed[b].vertices.push( brokenFace.vertices[1].x, brokenFace.vertices[1].y, brokenFace.vertices[1].z ); bFaceIndexed[b].vertices.push( brokenFace.vertices[2].x, brokenFace.vertices[2].y, brokenFace.vertices[2].z ); for(var bb = 0; bb < brokenFaces.length; bb++) { if(bb == b) { continue; } var otherBrokenFace = brokenFaces[bb]; if(otherBrokenFace.marked) { continue; } if(brokenFace.center.distanceTo(otherBrokenFace.center) <= maxTriangleDistance) { var indicesAmount = bFaceIndexed[b].indices.length; bFaceIndexed[b].indices.push( indicesAmount, indicesAmount + 1, indicesAmount + 2 ); bFaceIndexed[b].vertices.push( otherBrokenFace.vertices[0].x, otherBrokenFace.vertices[0].y, otherBrokenFace.vertices[0].z ); bFaceIndexed[b].vertices.push( otherBrokenFace.vertices[1].x, otherBrokenFace.vertices[1].y, otherBrokenFace.vertices[1].z ); bFaceIndexed[b].vertices.push( otherBrokenFace.vertices[2].x, otherBrokenFace.vertices[2].y, otherBrokenFace.vertices[2].z ); otherBrokenFace.marked = true; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Decide if we want to create new rigiwyd bodies, or create a compound mesh // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - for(var e in bFaceIndexed) { var element = bFaceIndexed[e]; var shape = new R3.D3.Shape(this.engine, R3.D3.Shape.SHAPE_TYPE_TRIMESH, { x : 1, y : 1, z : 1 }, element.vertices, element.indices); if(createCompoundShape) { triangleMeshBody.addShape(shape); } else { var body = new R3.D3.RigidBody(this.engine, 0, 12); //TODO: this is just a hack. body.instance.collisionFilterGroup = 1 | 2; // puts this body in two groups. body.addShape(shape); this.addRigidBody(body); } fixedTriangleMeshObjects.push(shape); totalFixedFaces += element.indices.length / 3; } // TODO: remove duplicate indices /*trimesh.updateNormals(); trimesh.updateEdges(); trimesh.updateTree(); trimesh.updateAABB(); trimesh.updateBoundingSphereRadius();*/ // map faceIndex to flat face index (faceIndex * 3) +0, 1, 2 -> triangle indices console.log("i = " + i, brokenFaces); totalBrokenFaces += brokenFaces.length; } console.log("total faces", totalFaces); console.log("total broken faces", totalBrokenFaces); console.log("broken faces in percent", (totalBrokenFaces / totalFaces) * 100); console.log("total fixed faces", totalFixedFaces); console.log("fixed triangle mesh shapes", fixedTriangleMeshObjects.length); return { brokenFaceIndicators : brokenFaceIndicators, fixedTriangleMeshShapes : fixedTriangleMeshObjects }; };