Created
April 29, 2024 23:12
-
-
Save PhoenixIllusion/c6b0507b64c3ca4f3634e2bb0693ca61 to your computer and use it in GitHub Desktop.
Jolt Physics - using PhysX tetra generation - Using generated Tetras to map position for detailed model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>JoltPhysics.js demo</title> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | |
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/jrouwe/[email protected]/Examples/style.css"> | |
<script type="importmap"> | |
{ | |
"imports": { | |
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js", | |
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/" | |
} | |
} | |
</script> | |
</head> | |
<body> | |
<div id="container">Loading...</div> | |
<div id="info">JoltPhysics.js Tool: Generate Tetragon Soft Mesh<br /> | |
<button id="open-file">Open .glb File</button><br /><input type="file" id="file-input" style="display: none" /><br /> | |
Remesh Resolution: <select id="resolution"><option value="40">40</option><option value="20">20</option><option value="10">10</option><option value="5">5</option></button> | |
</div> | |
<script src="https://cdn.jsdelivr.net/gh/jrouwe/[email protected]/Examples/js/three/three.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/gh/jrouwe/[email protected]/Examples/js/three/OrbitControls.js"></script> | |
<script src="https://cdn.jsdelivr.net/gh/jrouwe/[email protected]/Examples/js/three/WebGL.js"></script> | |
<script src="https://cdn.jsdelivr.net/gh/jrouwe/[email protected]/Examples/js/three/stats.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/gh/jrouwe/[email protected]/Examples/js/example.js"></script> | |
<script src="https://cdn.jsdelivr.net/gh/jrouwe/[email protected]/Examples/js/three/CSS3DRenderer.js"></script> | |
<script src="https://cdn.jsdelivr.net/gh/jrouwe/[email protected]/Examples/js/debug-renderer.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/physx-js-webidl.min.js"></script> | |
<script type="module"> | |
// In case you haven't built the library yourself, replace URL with: https://www.unpkg.com/jolt-physics/dist/jolt-physics.wasm-compat.js | |
import initJolt from 'https://www.unpkg.com/jolt-physics/dist/jolt-physics.wasm-compat.js'; | |
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; | |
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'; | |
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; | |
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js'; | |
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js'; | |
import { ConvexHull } from 'three/addons/math/ConvexHull.js'; | |
import { Mesh, Object3D, LoadingManager, REVISION } from "three"; | |
import AABBTree from 'https://cdn.skypack.dev/[email protected]'; | |
import AABB from "https://cdn.skypack.dev/[email protected]"; | |
const THREE_PATH = `https://cdn.jsdelivr.net/npm/three@0.${REVISION}.0`; | |
const manager = new LoadingManager(); | |
const dracoLoader = new DRACOLoader( manager ) | |
.setDecoderPath( `${THREE_PATH}/examples/jsm/libs/draco/gltf/` ); | |
const ktx2Loader = new KTX2Loader( manager ) | |
.setTranscoderPath( `${THREE_PATH}/examples/jsm/libs/basis/` ) | |
const loader = new GLTFLoader( manager ) | |
.setCrossOrigin( 'anonymous' ) | |
.setDRACOLoader( dracoLoader ) | |
.setKTX2Loader( ktx2Loader ) | |
.setMeshoptDecoder( MeshoptDecoder ); | |
const openFile = document.getElementById('open-file'); | |
const filePrompt = document.getElementById('file-input'); | |
const remeshRes = document.getElementById('resolution'); | |
let px; | |
let mesh; | |
async function loadPx() { | |
px = await PhysX(); | |
let version = px.PHYSICS_VERSION; | |
var allocator = new px.PxDefaultAllocator(); | |
var errorCb = new px.PxDefaultErrorCallback(); | |
var _foundation = px.CreateFoundation(version, allocator, errorCb); | |
console.log('PhysX loaded! Version: ' + ((version >> 24) & 0xff) + '.' + ((version >> 16) & 0xff) + '.' + ((version >> 8) & 0xff)); | |
} | |
class PhysXTool { | |
static remesh(mesh, remesherGridResolution = 20) { | |
const vertices = mesh.position; | |
const indices = mesh.index; | |
let inputVertices = new px.PxArray_PxVec3(vertices.length / 3); | |
let inputIndices = new px.PxArray_PxU32(indices.length); | |
for (let i = 0; i < vertices.length; i += 3) { | |
inputVertices.set(i / 3, new px.PxVec3(vertices[i], vertices[i + 1], vertices[i + 2])); | |
} | |
for (let i = 0; i < indices.length; i++) { | |
inputIndices.set(i, indices[i]); | |
} | |
let outputVertices = new px.PxArray_PxVec3(); | |
let outputIndices = new px.PxArray_PxU32(); | |
let vertexMap = new px.PxArray_PxU32(); | |
px.PxTetMaker.prototype.remeshTriangleMesh(inputVertices, inputIndices, remesherGridResolution, outputVertices, outputIndices, vertexMap); | |
// Transform From PxVec3 to THREE.Vector3 | |
let triIndices = new Uint32Array(outputIndices.size()); | |
for (let i = 0; i < triIndices.length; i++) { | |
triIndices[i] = outputIndices.get(i); | |
} | |
let vertPositions = new Float32Array(outputVertices.size() * 3); | |
for (let i = 0; i < outputVertices.size(); i++) { | |
let vec3 = outputVertices.get(i); | |
vertPositions[i * 3 + 0] = vec3.get_x(); | |
vertPositions[i * 3 + 1] = vec3.get_y(); | |
vertPositions[i * 3 + 2] = vec3.get_z(); | |
} | |
inputVertices.__destroy__(); | |
inputIndices.__destroy__(); | |
outputVertices.__destroy__(); | |
outputIndices.__destroy__(); | |
vertexMap.__destroy__(); | |
return { position: vertPositions, index: triIndices }; | |
} | |
static simplifyMesh(mesh, targetTriangleCount = 5000, maximalTriangleEdgeLength = 110.0) { | |
const vertices = mesh.position; | |
const indices = mesh.index; | |
let inputVertices = new px.PxArray_PxVec3(vertices.length / 3); | |
let inputIndices = new px.PxArray_PxU32(indices.length); | |
for (let i = 0; i < vertices.length; i += 3) { | |
inputVertices.set(i / 3, new px.PxVec3(vertices[i], vertices[i + 1], vertices[i + 2])); | |
} | |
for (let i = 0; i < indices.length; i++) { | |
inputIndices.set(i, indices[i]); | |
} | |
let outputVertices = new px.PxArray_PxVec3(); | |
let outputIndices = new px.PxArray_PxU32(); | |
px.PxTetMaker.prototype.simplifyTriangleMesh(inputVertices, inputIndices, targetTriangleCount, maximalTriangleEdgeLength, outputVertices, outputIndices); | |
console.log(inputVertices.size(), inputIndices.size(), outputVertices.size(), outputIndices.size()); | |
// Transform From PxVec3 to THREE.Vector3 | |
let triIndices = new Uint32Array(outputIndices.size()); | |
for (let i = 0; i < triIndices.length; i++) { | |
triIndices[i] = outputIndices.get(i); | |
} | |
let vertPositions = new Float32Array(outputVertices.size() * 3); | |
for (let i = 0; i < outputVertices.size(); i++) { | |
let vec3 = outputVertices.get(i); | |
vertPositions[i * 3 + 0] = vec3.get_x(); | |
vertPositions[i * 3 + 1] = vec3.get_y(); | |
vertPositions[i * 3 + 2] = vec3.get_z(); | |
} | |
inputVertices.__destroy__(); | |
inputIndices.__destroy__(); | |
outputVertices.__destroy__(); | |
outputIndices.__destroy__(); | |
return { position: vertPositions, index: triIndices }; | |
} | |
static createConformingTetrahedronMesh(mesh, minTetVolume = 0.01) { | |
const vertices = mesh.position; | |
const indices = mesh.index; | |
// First need to get the data into PhysX | |
let inputVertices = new px.PxArray_PxVec3(vertices.length / 3); | |
let inputIndices = new px.PxArray_PxU32(indices.length); | |
for (let i = 0; i < vertices.length; i += 3) { | |
inputVertices.set(i / 3, new px.PxVec3(vertices[i], vertices[i + 1], vertices[i + 2])); | |
} | |
for (let i = 0; i < indices.length; i++) { | |
inputIndices.set(i, indices[i]); | |
if (indices[i] < 0 || indices[i] >= inputVertices.size()) { | |
console.log("Index out of range!", i, indices[i], inputVertices.size()); | |
} | |
} | |
// Next need to make the PxBoundedData for both the vertices and indices to make the 'Simple'TriangleMesh | |
let vertexData = new px.PxBoundedData(); | |
let indexData = new px.PxBoundedData(); | |
vertexData.set_count(inputVertices.size()); | |
vertexData.set_data(inputVertices.begin()); | |
indexData.set_count(inputIndices.size() / 3); | |
indexData.set_data(inputIndices.begin()); | |
let simpleMesh = new px.PxSimpleTriangleMesh(); | |
simpleMesh.set_points(vertexData); | |
simpleMesh.set_triangles(indexData); | |
let analysis = px.PxTetMaker.prototype.validateTriangleMesh(simpleMesh); | |
if (!analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eVALID) || analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eMESH_IS_INVALID)) { | |
console.log("eVALID", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eVALID), | |
"\neZERO_VOLUME", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eZERO_VOLUME), | |
"\neOPEN_BOUNDARIES", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eOPEN_BOUNDARIES), | |
"\neSELF_INTERSECTIONS", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eSELF_INTERSECTIONS), | |
"\neINCONSISTENT_TRIANGLE_ORIENTATION", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eINCONSISTENT_TRIANGLE_ORIENTATION), | |
"\neCONTAINS_ACUTE_ANGLED_TRIANGLES", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eCONTAINS_ACUTE_ANGLED_TRIANGLES), | |
"\neEDGE_SHARED_BY_MORE_THAN_TWO_TRIANGLES", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eEDGE_SHARED_BY_MORE_THAN_TWO_TRIANGLES), | |
"\neCONTAINS_DUPLICATE_POINTS", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eCONTAINS_DUPLICATE_POINTS), | |
"\neCONTAINS_INVALID_POINTS", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eCONTAINS_INVALID_POINTS), | |
"\neREQUIRES_32BIT_INDEX_BUFFER", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eREQUIRES_32BIT_INDEX_BUFFER), | |
"\neTRIANGLE_INDEX_OUT_OF_RANGE", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eTRIANGLE_INDEX_OUT_OF_RANGE), | |
"\neMESH_IS_PROBLEMATIC", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eMESH_IS_PROBLEMATIC), | |
"\neMESH_IS_INVALID", analysis.isSet(px.PxTriangleMeshAnalysisResultEnum.eMESH_IS_INVALID)); | |
} | |
// Now we should be able to make the Conforming Tetrahedron Mesh | |
let outputVertices = new px.PxArray_PxVec3(); | |
let outputIndices = new px.PxArray_PxU32(); | |
px.PxTetMaker.prototype.createConformingTetrahedronMesh(simpleMesh, outputVertices, outputIndices, true, minTetVolume); | |
// Transform From PxVec3 to THREE.Vector3 | |
let tetIndices = new Uint32Array(outputIndices.size()); | |
for (let i = 0; i < tetIndices.length; i++) { | |
tetIndices[i] = outputIndices.get(i); | |
} | |
// Transform from Tet Indices to Edge Indices | |
let segIndices = new Uint32Array((outputIndices.size() / 4) * 12); | |
for (let i = 0; i < outputIndices.size() / 4; i++) { | |
let a = outputIndices.get(i * 4 + 0); | |
let b = outputIndices.get(i * 4 + 1); | |
let c = outputIndices.get(i * 4 + 2); | |
let d = outputIndices.get(i * 4 + 3); | |
segIndices[i * 12 + 0] = a; | |
segIndices[i * 12 + 1] = b; | |
segIndices[i * 12 + 2] = a; | |
segIndices[i * 12 + 3] = c; | |
segIndices[i * 12 + 4] = a; | |
segIndices[i * 12 + 5] = d; | |
segIndices[i * 12 + 6] = b; | |
segIndices[i * 12 + 7] = c; | |
segIndices[i * 12 + 8] = b; | |
segIndices[i * 12 + 9] = d; | |
segIndices[i * 12 + 10] = c; | |
segIndices[i * 12 + 11] = d; | |
} | |
let vertPositions = new Float32Array(outputVertices.size() * 3); | |
for (let i = 0; i < outputVertices.size(); i++) { | |
let vec3 = outputVertices.get(i); | |
vertPositions[i * 3 + 0] = vec3.get_x(); | |
vertPositions[i * 3 + 1] = vec3.get_y(); | |
vertPositions[i * 3 + 2] = vec3.get_z(); | |
} inputVertices.__destroy__(); | |
inputIndices.__destroy__(); | |
vertexData.__destroy__(); | |
indexData.__destroy__(); | |
simpleMesh.__destroy__(); | |
outputVertices.__destroy__(); | |
outputIndices.__destroy__(); | |
return { | |
faceIDs: indices, | |
numTets: tetIndices.length / 4, | |
numTetEdges: segIndices.length / 2, | |
vertices: vertPositions, | |
tetIDs: tetIndices, | |
tetEdgeIDs: segIndices | |
}; | |
} | |
static generateTetMesh(geo, options) { | |
const meshingParams = { | |
RemeshResolution: 20, | |
TargetTriangles: 2000, | |
MaxTriangleEdgeLength: 50.0, | |
MinTetVolume: 0.00001 | |
} | |
Object.assign(meshingParams, options); | |
const opt = { | |
remesh: false, | |
simplify: false | |
} | |
Object.assign(opt, options); | |
let index = geo.getIndex()?.array; | |
if (!index) { | |
index = new Uint32Array(geo.getAttribute("position").array.length / 3); | |
for (let i = 0; i < index.length; i++) { index[i] = i; } | |
} | |
let meshData = { position: new Float32Array(geo.getAttribute("position").array), index } | |
if (opt.remesh) | |
meshData = this.remesh(meshData, meshingParams.RemeshResolution); | |
if (opt.simplify) | |
meshData = this.simplifyMesh(meshData, meshingParams.TargetTriangles, meshingParams.MaxTriangleEdgeLength); | |
const tetrahedronGeo = this.createConformingTetrahedronMesh(meshData, meshingParams.MinTetVolume); | |
return tetrahedronGeo; | |
} | |
} | |
function CreateSoftMesh(tetrahedronGeo, edgeCompliance, volumeCompliance) { | |
// Create settings | |
const sharedSettings = new Jolt.SoftBodySharedSettings; | |
const v = new Jolt.SoftBodySharedSettingsVertex; | |
for (let i = 0; i < tetrahedronGeo.vertices.length; i+= 3) { | |
v.mPosition.x = tetrahedronGeo.vertices[i + 0]; | |
v.mPosition.y = tetrahedronGeo.vertices[i + 1]; | |
v.mPosition.z = tetrahedronGeo.vertices[i + 2]; | |
sharedSettings.mVertices.push_back(v); | |
} | |
Jolt.destroy(v); | |
// Function to get the vertex index of a point on the cloth | |
const vertex_index = (inX, inY, inZ) => { | |
return inX + inY * inGridSize + inZ * inGridSize * inGridSize; | |
}; | |
const sEdge = new Jolt.SoftBodySharedSettingsEdge(0, 0, 0); | |
sEdge.mCompliance = edgeCompliance; | |
// Create edges | |
for (let i = 0; i < tetrahedronGeo.tetEdgeIDs.length; i += 2) { | |
sEdge.set_mVertex(0, tetrahedronGeo.tetEdgeIDs[i + 0]); | |
sEdge.set_mVertex(1, tetrahedronGeo.tetEdgeIDs[i + 1]); | |
sharedSettings.mEdgeConstraints.push_back(sEdge); | |
} | |
Jolt.destroy(sEdge); | |
sharedSettings.CalculateEdgeLengths(); | |
// Create volume constraints | |
const sVol = new Jolt.SoftBodySharedSettingsVolume(0, 0, 0, 0, 0); | |
sVol.mCompliance = volumeCompliance; | |
for (let i = 0; i < tetrahedronGeo.tetIDs.length; i += 4) { | |
sVol.set_mVertex(0, tetrahedronGeo.tetIDs[i + 0]); | |
sVol.set_mVertex(1, tetrahedronGeo.tetIDs[i + 1]); | |
sVol.set_mVertex(2, tetrahedronGeo.tetIDs[i + 2]); | |
sVol.set_mVertex(3, tetrahedronGeo.tetIDs[i + 3]); | |
sharedSettings.mVolumeConstraints.push_back(sVol); | |
} | |
Jolt.destroy(sVol); | |
sharedSettings.CalculateVolumeConstraintVolumes(); | |
// Create faces | |
const f = new Jolt.SoftBodySharedSettingsFace(0, 0, 0, 0); | |
for (let i = 0; i < tetrahedronGeo.faceIDs.length; i += 3) { | |
// Face 1 | |
f.set_mVertex(0, tetrahedronGeo.faceIDs[i + 0]); | |
f.set_mVertex(1, tetrahedronGeo.faceIDs[i + 1]); | |
f.set_mVertex(2, tetrahedronGeo.faceIDs[i + 2]); | |
sharedSettings.AddFace(f); | |
} | |
Jolt.destroy(f); | |
// Optimize the settings | |
sharedSettings.Optimize(); | |
return sharedSettings; | |
} | |
const tmpV3 = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()]; | |
function tetraVolume(a,b,c,d) { | |
const AB = tmpV3[0].subVectors(b,a); | |
const AC = tmpV3[1].subVectors(c,a); | |
const AD = tmpV3[2].subVectors(d,a); | |
const cross_product = tmpV3[3].crossVectors(AC,AD) | |
const volume = Math.abs(AB.dot(cross_product))/6; | |
return volume; | |
} | |
function barycentricTetragon(a,b,c,d,p) { | |
const V = tetraVolume(a,b,c,d); | |
const dA = tetraVolume(p,b,c,d); | |
const dB = tetraVolume(p,a,c,d); | |
const dC = tetraVolume(p,a,b,d); | |
const dD = tetraVolume(p,a,b,c); | |
return [ dA/V, dB/V, dC/V, dD/V ] | |
} | |
function calculateMapping(obj, softGeo) { | |
const tree = new AABBTree(); | |
const hull = new ConvexHull(); | |
const tri = new THREE.Triangle(); | |
const point = new THREE.Vector3(); | |
{ | |
const { vertices, tetIDs } = softGeo; | |
const box = new THREE.Box3(); | |
const position = []; | |
for (let i = 0; i < vertices.length; i+= 3) { | |
position.push(new THREE.Vector3().fromArray(vertices, i)); | |
} | |
for (let i = 0; i < tetIDs.length; i += 4) { | |
box.makeEmpty(); | |
const idx = new Array(... tetIDs.subarray(i, i+4)); | |
const v = idx.map(i => position[i]); | |
v.forEach( x => box.expandByPoint(x)); | |
const [x0,y0,z0] = box.min.toArray(); | |
const [x1,y1,z1] = box.max.toArray(); | |
const aabb = new AABB([x0,x1,y0,y1,z0,z1]); | |
aabb.tetraIdx = i/4, | |
aabb.vertex = v; | |
aabb.idx = idx; | |
aabb.tetra = new ConvexHull().setFromPoints(v); | |
tree.add(aabb); | |
} | |
tree.globalOptimisation(); | |
} | |
let requiredVertexCount = 1; | |
const requiredVertex = {}; // stores tetrahedron vertex needed to display end mesh, as not all vertex are needed | |
mesh.traverse((x) => { | |
if(x.isMesh) { | |
const geo = x.geometry; | |
const positions = new Float32Array(geo.getAttribute("position").array); | |
const map = []; | |
for(let i=0;i<positions.length;i+=3) { | |
const [x,y,z] = positions.subarray(i, i+3); | |
const pointIdx = i/3; | |
point.set(x,y,z); | |
const DELTA = 1 | |
let min = {entry: null, dist: 1e30, point: new THREE.Vector3()}; | |
tree.forEachCollidingWithAABB(new AABB([x-DELTA,x+DELTA,y-DELTA,y+DELTA,z-DELTA,z+DELTA]), (_, entry) => { | |
const { tetra } = entry; | |
if(tetra.containsPoint(point)) { | |
map[pointIdx] = entry; | |
} | |
if(!map[pointIdx]) { | |
tetra.faces.forEach(face => { | |
tri.set(face.edge.vertex.point, face.edge.prev.vertex.point, face.edge.next.vertex.point); | |
tri.closestPointToPoint(point, tmpV3[0]); | |
const dist = tmpV3[0].distanceTo(point); | |
if(dist < min.dist) { | |
min.point.copy(tmpV3[0]); | |
min.dist = dist; | |
min.entry = entry; | |
} | |
}); | |
} | |
}) | |
if(map[pointIdx] == undefined && min.entry != null) { | |
map[pointIdx] = min.entry; | |
point.copy(min.point); | |
} | |
if(map[pointIdx] == undefined) { | |
throw new Error("Error processing detail-to-soft mesh mapping. Detail model vertex found not within "+DELTA+" from soft mesh tetrahedron.") | |
} | |
const entry = map[pointIdx]; | |
const weights = barycentricTetragon(... entry.vertex, point); | |
const index = entry.idx.map(idx => { | |
const x = requiredVertex[idx] = requiredVertex[idx] || requiredVertexCount++; | |
return x-1; | |
}); | |
map[pointIdx] = { | |
weights, | |
index | |
} | |
} | |
x.userData.mapping = map; | |
} | |
}); | |
return Object.entries(requiredVertex).sort(([aK,aV],[bK,bV]) => aV-bV).map(x => parseInt(x[0], 10)); | |
} | |
let position, rotAxis, rotation; | |
let body, sharedSettings; | |
function wireMapping(threeObject, mapping, softBody) { | |
const motionProperties = Jolt.castObject(body.GetMotionProperties(), Jolt.SoftBodyMotionProperties); | |
const vertexSettings = motionProperties.GetVertices(); | |
const settings = motionProperties.GetSettings(); | |
const positionOffset = Jolt.SoftBodyVertexTraits.prototype.mPositionOffset; | |
const softVertex = []; | |
// WARNING: The code uses direct memory mapping of properties in Jolt and makes assumptions about the memory layout. | |
function memoryMapVertex(i) { | |
const offset = Jolt.getPointer(vertexSettings.at(i)); | |
return new Float32Array(Jolt.HEAPF32.buffer, offset + positionOffset, 3); | |
} | |
mapping.forEach(idx => { | |
softVertex.push(memoryMapVertex(idx)); | |
}) | |
const meshes = []; | |
mesh.traverse((x) => { | |
if(x.isMesh) { | |
meshes.push(x); | |
} | |
}); | |
threeObject.userData.updateVertex = () => { | |
meshes.forEach(mesh => { | |
const geometry = mesh.geometry; | |
const vertices = geometry.getAttribute("position").array; | |
const meshMap = mesh.userData.mapping; | |
for (let i = 0; i < meshMap.length; i++) { | |
const { weights, index } = meshMap[i]; | |
const value = tmpV3[0].set(0,0,0); | |
for(let j=0;j<4;j++) { | |
const v = tmpV3[1].fromArray(softVertex[index[j]]).multiplyScalar(weights[j]); | |
value.add(v); | |
} | |
value.x -= 10; | |
value.toArray(vertices, i*3); | |
} | |
geometry.computeVertexNormals(); | |
geometry.getAttribute('position').needsUpdate = true; | |
geometry.getAttribute('normal').needsUpdate = true; | |
}) | |
} | |
} | |
async function processMesh() { | |
if (!px) { | |
await loadPx(); | |
position = new Jolt.RVec3(); | |
rotAxis = new Jolt.Vec3(1, 0, 0); | |
rotation = Jolt.Quat.prototype.sRotation(rotAxis, -Math.PI / 4); | |
} | |
const geometry= []; | |
mesh.traverse((x) => { if(x.isMesh) geometry.push(x.geometry)}); | |
const geo = BufferGeometryUtils.mergeGeometries ( geometry, false ); | |
const softGeo = PhysXTool.generateTetMesh(geo, {remesh: true, simplify: true, RemeshResolution: parseInt(remeshRes.value)}); | |
const mapping = calculateMapping(mesh, softGeo); | |
sharedSettings = CreateSoftMesh(softGeo, 5e-5, 1e-6); | |
position.Set(0, 10, 0) | |
const bodyCreationSettings = new Jolt.SoftBodyCreationSettings(sharedSettings, position, rotation, LAYER_MOVING); | |
bodyCreationSettings.mPressure = 2; | |
bodyCreationSettings.mObjectLayer = LAYER_MOVING; | |
body = bodyInterface.CreateSoftBody(bodyCreationSettings); | |
addToScene(body, 0xff00ff); | |
mesh.userData.body = body; | |
wireMapping(mesh, mapping, body); | |
dynamicObjects.push(mesh); | |
Jolt.destroy(bodyCreationSettings); | |
} | |
let material = new THREE.MeshPhongMaterial({ color: 0xff00ff }); | |
async function loadGltf(arrayBuffer) { | |
if(mesh) { | |
scene.remove(mesh); | |
dynamicObjects.splice(dynamicObjects.length - 1, 1) | |
removeFromScene(dynamicObjects[dynamicObjects.length - 1]); | |
} | |
mesh = (await loader.parseAsync(arrayBuffer, 'OBJ/')).scenes[0]; | |
mesh.position.x -= 10; | |
mesh.position.y += 10; | |
mesh.traverse((x) => { if(x.isMesh) x.geometry.scale(3,3,3)}); | |
scene.add(mesh); | |
processMesh(); | |
} | |
const reader = new FileReader(); | |
filePrompt.onchange = e => { | |
const file = e.target.files[0]; | |
if (file) { | |
reader.readAsArrayBuffer(file, 'UTF-8'); | |
reader.onload = (readerEvent) => { | |
const content = readerEvent.target.result; | |
loadGltf(content) | |
} | |
} | |
} | |
document.getElementById('open-file').onclick = () => { | |
filePrompt.click(); | |
} | |
initJolt().then(function (Jolt) { | |
if(Jolt.DebugRendererJS) { | |
// Initialize this example | |
const debugRendererWidget = new RenderWidget(Jolt); | |
initExample(Jolt, () => { | |
debugRendererWidget.render(); | |
}); | |
debugRendererWidget.init(); | |
camera.layers.mask = 1 | |
document.body.appendChild(debugRendererWidget.domElement); | |
} else { | |
initExample(Jolt); | |
} | |
// Create a basic floor | |
let floor = createFloor(); | |
floor.SetFriction(1.0); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment