Added wind to cloth simulation
This commit is contained in:
@@ -3,6 +3,13 @@
|
||||
<mat-card-title>{{ 'CLOTH.TITLE' | translate }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div class="controls-container">
|
||||
<div class="controls-panel">
|
||||
<button mat-raised-button color="primary" (click)="toggleWind()">
|
||||
{{ isWindActive ? ('CLOTH.WIND_OFF' | translate) : ('CLOTH.WIND_ON' | translate) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-babylon-canvas
|
||||
[config]="renderConfig"
|
||||
(sceneReady)="onSceneReady($event)"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Component } from '@angular/core';
|
||||
import { MatCard, MatCardContent, MatCardHeader, MatCardTitle } from '@angular/material/card';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { BabylonCanvas, RenderConfig, SceneEventData } from '../../../shared/components/render-canvas/babylon-canvas.component';
|
||||
import { ComputeShader, StorageBuffer, MeshBuilder, ShaderMaterial, ShaderLanguage, ArcRotateCamera } from '@babylonjs/core';
|
||||
import {ComputeShader, StorageBuffer, MeshBuilder, ShaderMaterial, ShaderLanguage, ArcRotateCamera, GroundMesh} from '@babylonjs/core';
|
||||
import {
|
||||
CLOTH_FRAGMENT_SHADER_WGSL,
|
||||
CLOTH_INTEGRATE_COMPUTE_WGSL,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
CLOTH_VELOCITY_COMPUTE_WGSL,
|
||||
CLOTH_VERTEX_SHADER_WGSL
|
||||
} from './cloth.shader';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cloth',
|
||||
@@ -24,13 +25,18 @@ import {
|
||||
MatCardHeader,
|
||||
MatCardTitle,
|
||||
TranslatePipe,
|
||||
BabylonCanvas
|
||||
BabylonCanvas,
|
||||
MatButton
|
||||
],
|
||||
templateUrl: './cloth.component.html',
|
||||
styleUrl: './cloth.component.scss',
|
||||
})
|
||||
export class ClothComponent {
|
||||
private currentSceneData: SceneEventData | null = null;
|
||||
private simulationTime: number = 0;
|
||||
private clothMesh: GroundMesh | null = null;
|
||||
public isWindActive: boolean = false;
|
||||
|
||||
|
||||
public renderConfig: RenderConfig = {
|
||||
mode: '3D',
|
||||
@@ -47,6 +53,10 @@ export class ClothComponent {
|
||||
this.createSimulation();
|
||||
}
|
||||
|
||||
public toggleWind(): void {
|
||||
this.isWindActive = !this.isWindActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and starts the cloth simulation.
|
||||
*/
|
||||
@@ -58,10 +68,14 @@ export class ClothComponent {
|
||||
const { engine, scene } = this.currentSceneData;
|
||||
|
||||
// --- 1. CONFIGURE CLOTH GRID ---
|
||||
const gridWidth = 50;
|
||||
const gridHeight = 50;
|
||||
const gridWidth = 100;
|
||||
const gridHeight = 100;
|
||||
const spacing = 0.05;
|
||||
const numVertices = gridWidth * gridHeight;
|
||||
const spacing = 0.1;
|
||||
const density = 1.0;
|
||||
const particleArea = spacing * spacing;
|
||||
const particleMass = density * particleArea;
|
||||
const particleInvMass = 1.0 / particleMass;
|
||||
|
||||
const positionsData = new Float32Array(numVertices * 4);
|
||||
const prevPositionsData = new Float32Array(numVertices * 4);
|
||||
@@ -85,7 +99,7 @@ export class ClothComponent {
|
||||
positionsData[idx + 0] = (x - gridWidth / 2) * spacing;
|
||||
positionsData[idx + 1] = 5.0 - (y * spacing);
|
||||
positionsData[idx + 2] = 0.0;
|
||||
positionsData[idx + 3] = (y === 0) ? 0.0 : 1.0;
|
||||
positionsData[idx + 3] = (y === 0) ? 0.0 : particleInvMass;
|
||||
|
||||
prevPositionsData[idx + 0] = positionsData[idx + 0];
|
||||
prevPositionsData[idx + 1] = positionsData[idx + 1];
|
||||
@@ -192,7 +206,11 @@ export class ClothComponent {
|
||||
csVelocity.setStorageBuffer("velocities", velocitiesBuffer);
|
||||
|
||||
// --- 4. SETUP RENDER MESH ---
|
||||
const clothMesh = MeshBuilder.CreateGround("cloth", { width: 10, height: 10, subdivisions: gridWidth - 1 }, scene);
|
||||
if (this.clothMesh)
|
||||
{
|
||||
scene.removeMesh(this.clothMesh);
|
||||
}
|
||||
this.clothMesh = MeshBuilder.CreateGround("cloth", { width: 10, height: 10, subdivisions: gridWidth - 1 }, scene);
|
||||
|
||||
const clothMaterial = new ShaderMaterial("clothMat", scene, {
|
||||
vertexSource: CLOTH_VERTEX_SHADER_WGSL,
|
||||
@@ -206,7 +224,7 @@ export class ClothComponent {
|
||||
|
||||
clothMaterial.backFaceCulling = false;
|
||||
clothMaterial.setStorageBuffer("positions", positionsBuffer);
|
||||
clothMesh.material = clothMaterial;
|
||||
this.clothMesh.material = clothMaterial;
|
||||
|
||||
const camera = scene.activeCamera as ArcRotateCamera;
|
||||
if (camera) {
|
||||
@@ -218,10 +236,24 @@ export class ClothComponent {
|
||||
// --- 5. RENDER LOOP ---
|
||||
scene.onBeforeRenderObservable.clear();
|
||||
scene.onBeforeRenderObservable.add(() => {
|
||||
this.simulationTime += engine.getDeltaTime() / 1000.0;
|
||||
|
||||
const windX = this.isWindActive ? 5.0 : 0.0;
|
||||
const windY = 0.0;
|
||||
const windZ = this.isWindActive ? 15.0 : 0.0;
|
||||
|
||||
const baseCompliance = 0.00001;
|
||||
const scaledCompliance = baseCompliance * particleInvMass * spacing;
|
||||
|
||||
paramsData[0] = 0.016;
|
||||
paramsData[1] = -9.81;
|
||||
paramsData[2] = 0.0001; // Compliance (very small = stiff fabric)
|
||||
paramsData[2] = scaledCompliance; //scaled stiffness
|
||||
paramsData[3] = numVertices;
|
||||
paramsData[4] = windX;
|
||||
paramsData[5] = windY;
|
||||
paramsData[6] = windZ;
|
||||
paramsData[7] = this.simulationTime;
|
||||
|
||||
paramsBuffer.update(paramsData);
|
||||
|
||||
const dispatchXVertices = Math.ceil(numVertices / 64);
|
||||
@@ -230,7 +262,8 @@ export class ClothComponent {
|
||||
csIntegrate.dispatch(dispatchXVertices, 1, 1);
|
||||
|
||||
// 2. XPBD Solver (Substeps) - Solve each color individually
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const substeps = 15;
|
||||
for (let i = 0; i < substeps; i++) {
|
||||
csSolve0.dispatch(Math.ceil((constraintsP0.length / 4) / 64), 1, 1);
|
||||
csSolve1.dispatch(Math.ceil((constraintsP1.length / 4) / 64), 1, 1);
|
||||
csSolve2.dispatch(Math.ceil((constraintsP2.length / 4) / 64), 1, 1);
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
// --- SHARED DATA STRUCTURES ---
|
||||
export const CLOTH_SHARED_STRUCTS = `
|
||||
struct Params {
|
||||
dt: f32, // Time step per substep
|
||||
gravity_y: f32, // Gravity (e.g. -9.81)
|
||||
compliance: f32, // Inverse stiffness (0.0 = completely rigid)
|
||||
numVertices: f32, // Total number of vertices
|
||||
numConstraints: f32, // Total number of springs
|
||||
pad1: f32, // Padding
|
||||
pad2: f32, // Padding
|
||||
pad3: f32 // Padding (8 * f32 = 32 bytes)
|
||||
dt: f32,
|
||||
gravity_y: f32,
|
||||
compliance: f32,
|
||||
numVertices: f32,
|
||||
wind_x: f32,
|
||||
wind_y: f32,
|
||||
wind_z: f32,
|
||||
time: f32
|
||||
};
|
||||
`;
|
||||
|
||||
@@ -76,17 +76,25 @@ export const CLOTH_INTEGRATE_COMPUTE_WGSL = CLOTH_SHARED_STRUCTS + `
|
||||
|
||||
var pos = positions[idx];
|
||||
var vel = velocities[idx];
|
||||
let invMass = pos.w; // w stores inverse mass (0.0 = pinned/static)
|
||||
let invMass = pos.w;
|
||||
|
||||
// Only move if it is not pinned
|
||||
if (invMass > 0.0) {
|
||||
// 1. Apply Gravity: v = v + g * dt
|
||||
vel.y = vel.y + (p.gravity_y * p.dt);
|
||||
|
||||
// 2. Save current position for later velocity calculation
|
||||
let flutter = sin(pos.x * 2.0 + p.time * 5.0) * cos(pos.y * 2.0 + p.time * 3.0);
|
||||
|
||||
let windForce = vec3<f32>(
|
||||
p.wind_x + (flutter * p.wind_x * 0.8),
|
||||
p.wind_y + (flutter * 2.0), // Leichter Auftrieb durchs Flattern
|
||||
p.wind_z + (flutter * p.wind_z * 0.8)
|
||||
);
|
||||
|
||||
vel.x = vel.x + (windForce.x * p.dt);
|
||||
vel.y = vel.y + (windForce.y * p.dt);
|
||||
vel.z = vel.z + (windForce.z * p.dt);
|
||||
|
||||
prev_positions[idx] = pos;
|
||||
|
||||
// 3. Predict new position: p = p + v * dt
|
||||
pos.x = pos.x + vel.x * p.dt;
|
||||
pos.y = pos.y + vel.y * p.dt;
|
||||
pos.z = pos.z + vel.z * p.dt;
|
||||
|
||||
@@ -472,7 +472,9 @@
|
||||
}
|
||||
},
|
||||
"CLOTH": {
|
||||
"TITLE": "Stoff-Simulation"
|
||||
"TITLE": "Stoff-Simulation",
|
||||
"WIND_ON": "Wind Einschalten",
|
||||
"WIND_OFF": "Wind Ausschalten"
|
||||
},
|
||||
"ALGORITHM": {
|
||||
"TITLE": "Algorithmen",
|
||||
|
||||
@@ -471,7 +471,9 @@
|
||||
}
|
||||
},
|
||||
"CLOTH": {
|
||||
"TITLE": "Cloth simulation"
|
||||
"TITLE": "Cloth simulation",
|
||||
"WIND_ON": "Wind On",
|
||||
"WIND_OFF": "Wind Off"
|
||||
},
|
||||
"ALGORITHM": {
|
||||
"TITLE": "Algorithms",
|
||||
|
||||
Reference in New Issue
Block a user