-
Notifications
You must be signed in to change notification settings - Fork 505
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sample computer soft body simulation
add sample computer soft body simulation
- Loading branch information
Showing
15 changed files
with
1,880 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { GUIHelp } from '@orillusion/debug/GUIHelp'; | ||
import { AtmosphericComponent, BoxGeometry, CameraUtil, DirectLight, Engine3D, ForwardRenderJob, HoverCameraController, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, webGPUContext } from '@orillusion/core'; | ||
import { BunnySimulator } from "./softbody/BunnySimulator"; | ||
|
||
export class Demo_Softbody { | ||
constructor() {} | ||
|
||
async run() { | ||
|
||
Engine3D.setting.shadow.autoUpdate = true; | ||
Engine3D.setting.shadow.updateFrameRate = 1; | ||
Engine3D.setting.shadow.shadowBound = 8; | ||
//Engine3D.setting.shadow.shadowBias = 0.000001; | ||
|
||
await Engine3D.init({}); | ||
|
||
GUIHelp.init(); | ||
|
||
let scene = new Scene3D(); | ||
let sky = scene.addComponent(AtmosphericComponent); | ||
await this.initScene(scene); | ||
|
||
let camera = CameraUtil.createCamera3DObject(scene); | ||
|
||
camera.perspective(60, webGPUContext.aspect, 1 , 5000.0); | ||
let ctl = camera.object3D.addComponent(HoverCameraController); | ||
ctl.setCamera(30, -28, 15); | ||
|
||
let view = new View3D(); | ||
view.scene = scene; | ||
view.camera = camera; | ||
|
||
Engine3D.startRenderView(view); | ||
} | ||
|
||
async initScene(scene: Scene3D) { | ||
let mat = new LitMaterial(); | ||
mat.baseMap = Engine3D.res.grayTexture; | ||
mat.roughness = 0.8; | ||
mat.metallic = 0.1; | ||
|
||
let box = new Object3D(); | ||
box.transform.y = 0.0; | ||
box.transform.x = 0.0; | ||
box.transform.z = 0.0; | ||
let mr = box.addComponent(MeshRenderer); | ||
mr.geometry = new BoxGeometry(3.0, 3.0, 3.0); | ||
let boxMat = new LitMaterial(); | ||
boxMat.roughness = 0.8; | ||
boxMat.metallic = 0.1 | ||
boxMat.cullMode = `front` | ||
//boxMat.depthCompare = `greater` | ||
|
||
mr.material = boxMat; | ||
// mr.material.doubleSide = true; | ||
mr.castShadow = true; | ||
scene.addChild(box); | ||
|
||
let bunny = new Object3D(); | ||
let simulator = bunny.addComponent(BunnySimulator); | ||
simulator.castShadow = true; | ||
simulator.SetInteractionBox(box); | ||
scene.addChild(bunny); | ||
|
||
// { | ||
// let mat = new HDRLitMaterial(); | ||
// mat.baseMap = defaultTexture.createTexture(32, 32, 72, 126, 2, 255); | ||
// mat.roughness = 0.8; | ||
// let plane = new Object3D(); | ||
// plane.transform.y = -1; | ||
// let planeMesh = plane.addComponent(MeshRenderer); | ||
// planeMesh.geometry = new PlaneGeometry(100, 100); | ||
// planeMesh.material = mat; | ||
// planeMesh.receiveShadow = true; | ||
// scene.addChild(plane); | ||
// } | ||
|
||
{ | ||
var lightObj = new Object3D(); | ||
lightObj.x = 0; | ||
lightObj.y = 0; | ||
lightObj.z = 0; | ||
lightObj.rotationX = 45; | ||
lightObj.rotationY = 0; | ||
lightObj.rotationZ = 0; | ||
let lc = lightObj.addComponent(DirectLight); | ||
lc.castShadow = true; | ||
lc.intensity = 20; | ||
scene.addChild(lightObj); | ||
} | ||
|
||
} | ||
|
||
// async initComputeBuffer() {} | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { Engine3D, LitMaterial, KeyCode, KeyEvent, MeshRenderer, Object3D, Time, Vector3, VertexAttributeName, View3D } from '@orillusion/core'; | ||
import { BunnySimulatorConfig } from "./BunnySimulatorConfig"; | ||
import { BunnySimulatorPipeline } from "./BunnySimulatorPipeline"; | ||
import bunnyMesh from "./bunnyMesh" | ||
import { BunnyGeometry } from "./BunnyGeometry"; | ||
|
||
export class BunnySimulator extends MeshRenderer { | ||
protected mConfig: BunnySimulatorConfig; | ||
protected mBunnyGeometry: BunnyGeometry; | ||
protected mInteractionBox: Object3D; | ||
protected mBunnyComputePipeline: BunnySimulatorPipeline; | ||
protected mKeyState: boolean[] = [false, false, false, false]; | ||
|
||
constructor() { | ||
super(); | ||
this.mConfig = { | ||
NUMPARTICLES: 0, | ||
NUMTETS: 0, | ||
NUMTEDGES: 0, | ||
NUMTSURFACES: 0, | ||
GRAVITY: -10, | ||
DELTATIME: 1 / 60, | ||
NUMSUBSTEPS: 10, | ||
EDGECOMPLIANCE: 0.0, | ||
VOLCOMPLIANCE: 0.0, | ||
CUBECENTREX: 0.0, | ||
CUBECENTREY: 0.0, | ||
CUBECENTREZ: 0.0, | ||
CUBEWIDTH: 3.0, | ||
CUBEHEIGHT: 3.0, | ||
CUBEDEPTH: 3.0, | ||
bunnyVertex: null, | ||
bunnyTetIds: null, | ||
bunnyEdgeIds: null, | ||
bunnySurfaceTriIds: null, | ||
bunnyVertexBuffer: null, | ||
}; | ||
this.mConfig.bunnyVertex = new Float32Array(bunnyMesh.verts); | ||
this.mConfig.bunnyTetIds = new Uint16Array(bunnyMesh.tetIds); | ||
this.mConfig.bunnyEdgeIds = new Uint16Array(bunnyMesh.tetEdgeIds); | ||
this.mConfig.bunnySurfaceTriIds = new Uint16Array(bunnyMesh.tetSurfaceTriIds); | ||
this.mConfig.NUMPARTICLES = this.mConfig.bunnyVertex.length / 3; | ||
this.mConfig.NUMTETS = this.mConfig.bunnyTetIds.length / 4; | ||
this.mConfig.NUMTEDGES = this.mConfig.bunnyEdgeIds.length / 2; | ||
this.mConfig.NUMTSURFACES = this.mConfig.bunnySurfaceTriIds.length / 3; | ||
this.mBunnyGeometry = new BunnyGeometry(0.0, 0.0, 0.0, this.mConfig.NUMPARTICLES); | ||
// this.mConfig.bunnyVertex = this.mBunnyGeometry.getAttribute(VertexAttributeName.position).data; | ||
// console.log(this.mConfig.NUMPARTICLES,this.mConfig.NUMTETS,this.mConfig.NUMTEDGES,this.mConfig.NUMTSURFACES) | ||
} | ||
|
||
protected updateKeyState(keyCode: number, state: boolean) { | ||
switch (keyCode) { | ||
case KeyCode.Key_W: | ||
this.mKeyState[0] = state; | ||
break; | ||
case KeyCode.Key_S: | ||
this.mKeyState[1] = state; | ||
break; | ||
case KeyCode.Key_A: | ||
this.mKeyState[2] = state; | ||
break; | ||
case KeyCode.Key_D: | ||
this.mKeyState[3] = state; | ||
break; | ||
} | ||
} | ||
|
||
public init(){ | ||
super.init(); | ||
this.alwaysRender = true; | ||
this.geometry = this.mBunnyGeometry; | ||
var mat = new LitMaterial(); | ||
mat.roughness = 0.8; | ||
mat.baseMap = Engine3D.res.redTexture; | ||
this.material = mat; | ||
this.material.doubleSide = true; | ||
} | ||
|
||
public start() { | ||
Engine3D.inputSystem.addEventListener(KeyEvent.KEY_DOWN, (e: KeyEvent) => this.updateKeyState(e.keyCode, true), this); | ||
Engine3D.inputSystem.addEventListener(KeyEvent.KEY_UP, (e: KeyEvent) => this.updateKeyState(e.keyCode, false), this); | ||
} | ||
|
||
public SetInteractionBox(box: Object3D) { | ||
this.mInteractionBox = box; | ||
} | ||
|
||
private _tickTime = 0; | ||
|
||
public onCompute(view: View3D, command?: GPUCommandEncoder) { | ||
if (!this.mBunnyComputePipeline) { | ||
this.mConfig.bunnyVertexBuffer = this.mBunnyGeometry.vertexBuffer.vertexGPUBuffer; | ||
this.mBunnyComputePipeline = new BunnySimulatorPipeline(this.mConfig); | ||
} | ||
|
||
var pos = new Vector3(); | ||
if (this.mInteractionBox) { | ||
var transform = this.mInteractionBox.transform; | ||
let dt = Time.delta / 1000.0; | ||
let speed = 5 * dt; | ||
// W S | ||
if (this.mKeyState[0]) { | ||
transform.y += speed | ||
} else if (this.mKeyState[1]) { | ||
transform.y -= speed | ||
} | ||
// A D | ||
if (this.mKeyState[2]) { | ||
transform.x -= speed | ||
} else if (this.mKeyState[3]) { | ||
transform.x += speed | ||
} | ||
pos.copyFrom(this.mInteractionBox.transform.worldPosition); | ||
} | ||
|
||
this._tickTime += Time.delta / 1000.0; | ||
if (this._tickTime >= this.mConfig.DELTATIME) { | ||
this._tickTime -= this.mConfig.DELTATIME; | ||
this.mBunnyComputePipeline.compute(command, pos); | ||
} | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { ComputeGPUBuffer, Vector3, webGPUContext } from '@orillusion/core'; | ||
import { BunnySimulatorConfig } from "./BunnySimulatorConfig"; | ||
|
||
export class BunnySimulatorBuffer { | ||
protected mPositionBuffer: ComputeGPUBuffer; | ||
protected mNormalBuffer: ComputeGPUBuffer; | ||
protected mVertexPositionData: Float32Array; | ||
protected mVertexPositionBuffer: ComputeGPUBuffer; | ||
protected mNewPositionBuffer: ComputeGPUBuffer; | ||
protected mAtomicPositionBuffer: ComputeGPUBuffer; | ||
protected mAtomicNormalBuffer: ComputeGPUBuffer; | ||
protected mVelocityBuffer: ComputeGPUBuffer; | ||
protected mEdgeInfosBuffer: ComputeGPUBuffer; | ||
protected mTetIdsBuffer: ComputeGPUBuffer; | ||
protected mRestVolBuffer: ComputeGPUBuffer; | ||
protected mSurfaceInfosBuffer: ComputeGPUBuffer; | ||
// protected mInputData: Float32Array; | ||
protected mInputBuffer: ComputeGPUBuffer; | ||
protected mOutput0Buffer: ComputeGPUBuffer; | ||
|
||
constructor(config: BunnySimulatorConfig) { | ||
this.initGPUBuffer(config); | ||
} | ||
|
||
protected initGPUBuffer(config: BunnySimulatorConfig) { | ||
const { NUMPARTICLES, bunnyVertex } = config; | ||
|
||
let vertex = new Float32Array(NUMPARTICLES * 4) | ||
for (let i = 0; i < NUMPARTICLES; i++) { | ||
vertex[i * 4] = bunnyVertex[i * 3] | ||
vertex[i * 4 + 1] = bunnyVertex[i * 3 + 1] | ||
vertex[i * 4 + 2] = bunnyVertex[i * 3 + 2] | ||
vertex[i * 4 + 3] = 1 | ||
} | ||
this.mVertexPositionData = vertex; | ||
this.mVertexPositionBuffer = new ComputeGPUBuffer(this.mVertexPositionData.length); | ||
this.mVertexPositionBuffer.setFloat32Array("", this.mVertexPositionData); | ||
this.mVertexPositionBuffer.apply(); | ||
|
||
this.mNewPositionBuffer = new ComputeGPUBuffer(this.mVertexPositionData.length); | ||
this.mNewPositionBuffer.setFloat32Array("", this.mVertexPositionData); | ||
this.mNewPositionBuffer.apply(); | ||
|
||
this.mAtomicPositionBuffer = new ComputeGPUBuffer(this.mVertexPositionData.length); | ||
|
||
this.mAtomicNormalBuffer = new ComputeGPUBuffer(NUMPARTICLES * 4); | ||
this.mNormalBuffer = new ComputeGPUBuffer(NUMPARTICLES * 4); | ||
|
||
const { NUMTETS, bunnyTetIds } = config; | ||
|
||
const invMass = new Float32Array(NUMPARTICLES) | ||
const restVol = new Float32Array(NUMTETS) | ||
const tetIds = new Int32Array(NUMTETS * 4) | ||
for (let i = 0; i < NUMTETS; i++) { | ||
var vol = this.getTetVolume(bunnyVertex, bunnyTetIds, i); | ||
restVol[i] = vol; | ||
var pInvMass = vol > 0.0 ? 1.0 / (vol / 4.0) : 0.0; | ||
invMass[bunnyTetIds[4 * i]] += pInvMass; | ||
invMass[bunnyTetIds[4 * i + 1]] += pInvMass; | ||
invMass[bunnyTetIds[4 * i + 2]] += pInvMass; | ||
invMass[bunnyTetIds[4 * i + 3]] += pInvMass; | ||
tetIds[4 * i] = bunnyTetIds[4 * i]; | ||
tetIds[4 * i + 1] = bunnyTetIds[4 * i + 1]; | ||
tetIds[4 * i + 2] = bunnyTetIds[4 * i + 2]; | ||
tetIds[4 * i + 3] = bunnyTetIds[4 * i + 3]; | ||
} | ||
this.mTetIdsBuffer = new ComputeGPUBuffer(tetIds.length); | ||
webGPUContext.device.queue.writeBuffer(this.mTetIdsBuffer.buffer, 0, tetIds); | ||
// this.mTetIdsBuffer.setInt32Array("", tetIds); | ||
// this.mTetIdsBuffer.apply(); | ||
|
||
this.mRestVolBuffer = new ComputeGPUBuffer(restVol.length); | ||
this.mRestVolBuffer.setFloat32Array("", restVol); | ||
this.mRestVolBuffer.apply(); | ||
|
||
const velocity = new Float32Array(4 * NUMPARTICLES) | ||
for (let i = 0; i < NUMPARTICLES; ++i) { | ||
velocity[i * 4 + 3] = invMass[i] | ||
} | ||
this.mVelocityBuffer = new ComputeGPUBuffer(velocity.length); | ||
this.mVelocityBuffer.setFloat32Array("", velocity); | ||
this.mVelocityBuffer.apply(); | ||
|
||
const { NUMTEDGES, bunnyEdgeIds } = config; | ||
|
||
const edgeInfos = new Float32Array(NUMTEDGES * 4) | ||
for (var i = 0; i < NUMTEDGES; i++) { | ||
edgeInfos[i * 4 + 0] = bunnyEdgeIds[2 * i]; | ||
edgeInfos[i * 4 + 1] = bunnyEdgeIds[2 * i + 1]; | ||
edgeInfos[i * 4 + 2] = Math.sqrt(this.distance(bunnyVertex, edgeInfos[i * 4], bunnyVertex, edgeInfos[i * 4 + 1])); | ||
} | ||
this.mEdgeInfosBuffer = new ComputeGPUBuffer(edgeInfos.length); | ||
this.mEdgeInfosBuffer.setFloat32Array("", edgeInfos); | ||
this.mEdgeInfosBuffer.apply(); | ||
|
||
const {NUMTSURFACES, bunnySurfaceTriIds} = config; | ||
const surfaceInfos = new Float32Array(NUMTSURFACES * 4) | ||
for (let i = 0; i < NUMTSURFACES; i++) { | ||
surfaceInfos[i * 4] = bunnySurfaceTriIds[i * 3] | ||
surfaceInfos[i * 4 + 1] = bunnySurfaceTriIds[i * 3 + 1] | ||
surfaceInfos[i * 4 + 2] = bunnySurfaceTriIds[i * 3 + 2] | ||
surfaceInfos[i * 4 + 3] = 1 | ||
} | ||
this.mSurfaceInfosBuffer = new ComputeGPUBuffer(surfaceInfos.length); | ||
this.mSurfaceInfosBuffer.setFloat32Array("", surfaceInfos); | ||
this.mSurfaceInfosBuffer.apply(); | ||
|
||
const { GRAVITY, DELTATIME, NUMSUBSTEPS, EDGECOMPLIANCE, VOLCOMPLIANCE, CUBECENTREX, CUBECENTREY, CUBECENTREZ, CUBEWIDTH, CUBEHEIGHT, CUBEDEPTH } = config; | ||
this.mInputBuffer = new ComputeGPUBuffer(16); | ||
this.mInputBuffer.setFloat("NUMPARTICLES", NUMPARTICLES); | ||
this.mInputBuffer.setFloat("NUMTETS", NUMTETS); | ||
this.mInputBuffer.setFloat("NUMTEDGES", NUMTEDGES); | ||
this.mInputBuffer.setFloat("NUMTSURFACES", NUMTSURFACES); | ||
this.mInputBuffer.setFloat("GRAVITY", GRAVITY); | ||
this.mInputBuffer.setFloat("DELTATIME", DELTATIME / NUMSUBSTEPS); | ||
this.mInputBuffer.setFloat("EDGECOMPLIANCE", EDGECOMPLIANCE); | ||
this.mInputBuffer.setFloat("VOLCOMPLIANCE", VOLCOMPLIANCE); | ||
this.mInputBuffer.setFloat("CUBECENTREX", CUBECENTREX); | ||
this.mInputBuffer.setFloat("CUBECENTREY", CUBECENTREY); | ||
this.mInputBuffer.setFloat("CUBECENTREZ", CUBECENTREZ); | ||
this.mInputBuffer.setFloat("CUBEWIDTH", CUBEWIDTH); | ||
this.mInputBuffer.setFloat("CUBEHEIGHT", CUBEHEIGHT); | ||
this.mInputBuffer.setFloat("CUBEDEPTH", CUBEDEPTH); | ||
this.mInputBuffer.apply(); | ||
|
||
this.mOutput0Buffer = new ComputeGPUBuffer(NUMTETS * 4); | ||
// this.mOutput0Buffer.debug(); | ||
} | ||
|
||
public updateInputData(pos: Vector3) { | ||
this.mInputBuffer.setFloat("CUBECENTREX", pos.x); | ||
this.mInputBuffer.setFloat("CUBECENTREY", pos.y); | ||
this.mInputBuffer.setFloat("CUBECENTREZ", pos.z); | ||
this.mInputBuffer.apply(); | ||
} | ||
|
||
protected cross(a: number[], indexa: number, b: number[], indexb: number, c: number[], indexc: number) { | ||
a[indexa * 3] = b[indexb * 3 + 1] * c[indexc * 3 + 2] - b[indexb * 3 + 2] * c[indexc * 3 + 1]; | ||
a[indexa * 3 + 1] = b[indexb * 3 + 2] * c[indexc * 3 + 0] - b[indexb * 3 + 0] * c[indexc * 3 + 2]; | ||
a[indexa * 3 + 2] = b[indexb * 3 + 0] * c[indexc * 3 + 1] - b[indexb * 3 + 1] * c[indexc * 3 + 0]; | ||
} | ||
|
||
protected dot(a: number[], indexa: number, b: number[], indexb: number) { | ||
return a[indexa * 3] * b[indexb * 3] + a[indexa * 3 + 1] * b[indexb * 3 + 1] + a[indexa * 3 + 2] * b[indexb * 3 + 2]; | ||
} | ||
|
||
protected getTetVolume(position: Float32Array | Uint16Array | Uint32Array, tetIds: Float32Array | Uint16Array | Uint32Array, i: number) { | ||
let id0 = tetIds[4 * i]; | ||
let id1 = tetIds[4 * i + 1]; | ||
let id2 = tetIds[4 * i + 2]; | ||
let id3 = tetIds[4 * i + 3]; | ||
|
||
let temp = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; | ||
temp[0] = position[id1 * 3] - position[id0 * 3]; | ||
temp[1] = position[id1 * 3 + 1] - position[id0 * 3 + 1]; | ||
temp[2] = position[id1 * 3 + 2] - position[id0 * 3 + 2]; | ||
temp[3] = position[id2 * 3] - position[id0 * 3]; | ||
temp[4] = position[id2 * 3 + 1] - position[id0 * 3 + 1]; | ||
temp[5] = position[id2 * 3 + 2] - position[id0 * 3 + 2]; | ||
temp[6] = position[id3 * 3] - position[id0 * 3]; | ||
temp[7] = position[id3 * 3 + 1] - position[id0 * 3 + 1]; | ||
temp[8] = position[id3 * 3 + 2] - position[id0 * 3 + 2]; | ||
|
||
this.cross(temp, 3, temp, 0, temp, 1); | ||
return this.dot(temp, 3, temp, 2) / 6.0; | ||
} | ||
|
||
protected distance(a: Float32Array | Uint16Array | Uint32Array, indexa: number, b: Float32Array | Uint16Array | Uint32Array, indexb: number) { | ||
let a0 = a[indexa * 3] - b[indexb * 3], a1 = a[indexa * 3 + 1] - b[indexb * 3 + 1], a2 = a[indexa * 3 + 2] - b[indexb * 3 + 2]; | ||
return a0 * a0 + a1 * a1 + a2 * a2; | ||
} | ||
} |
Oops, something went wrong.