Added one cloth parameter elongation

This commit is contained in:
2026-03-07 16:53:36 +01:00
parent 7ff59bf734
commit 150306333e
3 changed files with 50 additions and 16 deletions

View File

@@ -12,6 +12,15 @@
<button mat-raised-button color="primary" (click)="toggleMesh()"> <button mat-raised-button color="primary" (click)="toggleMesh()">
{{ isOutlineActive ? ('CLOTH.OUTLINE_OFF' | translate) : ('CLOTH.OUTLINE_ON' | translate) }} {{ isOutlineActive ? ('CLOTH.OUTLINE_OFF' | translate) : ('CLOTH.OUTLINE_ON' | translate) }}
</button> </button>
<button mat-raised-button color="accent" (click)="restartSimulation()">
{{ 'CLOTH.RESTART_SIMULATION' | translate }}
</button>
</div>
<div class="sliders-panel">
<label>{{ 'CLOTH.ELONGATION' | translate }}: {{ elongation }}</label>
<mat-slider min="0.5" max="2.0" step="0.1">
<input matSliderThumb [(ngModel)]="elongation">
</mat-slider>
</div> </div>
</div> </div>
<app-babylon-canvas <app-babylon-canvas

View File

@@ -4,7 +4,9 @@
*/ */
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatCard, MatCardContent, MatCardHeader, MatCardTitle } from '@angular/material/card'; import { MatCard, MatCardContent, MatCardHeader, MatCardTitle } from '@angular/material/card';
import { MatSliderModule } from '@angular/material/slider';
import { TranslatePipe } from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core';
import { BabylonCanvas, RenderConfig, SceneEventData } from '../../../shared/components/render-canvas/babylon-canvas.component'; import { BabylonCanvas, RenderConfig, SceneEventData } from '../../../shared/components/render-canvas/babylon-canvas.component';
import {ComputeShader, StorageBuffer, MeshBuilder, ShaderMaterial, ShaderLanguage, ArcRotateCamera, GroundMesh, WebGPUEngine, Scene} from '@babylonjs/core'; import {ComputeShader, StorageBuffer, MeshBuilder, ShaderMaterial, ShaderLanguage, ArcRotateCamera, GroundMesh, WebGPUEngine, Scene} from '@babylonjs/core';
@@ -31,6 +33,8 @@ import {UrlConstants} from '../../../constants/UrlConstants';
TranslatePipe, TranslatePipe,
BabylonCanvas, BabylonCanvas,
MatButton, MatButton,
MatSliderModule,
FormsModule,
Information Information
], ],
templateUrl: './cloth.component.html', templateUrl: './cloth.component.html',
@@ -42,6 +46,9 @@ export class ClothComponent {
private clothMesh: GroundMesh | null = null; private clothMesh: GroundMesh | null = null;
public isWindActive: boolean = false; public isWindActive: boolean = false;
public isOutlineActive: boolean = false; public isOutlineActive: boolean = false;
public stiffness: number = 80;
// Elongation along the vertical (Y) axis, 0.5 = compressed, 2.0 = stretched
public elongation: number = 1.0;
public renderConfig: RenderConfig = { public renderConfig: RenderConfig = {
mode: '3D', mode: '3D',
@@ -103,6 +110,11 @@ export class ClothComponent {
this.clothMesh.material.wireframe = this.isOutlineActive; this.clothMesh.material.wireframe = this.isOutlineActive;
} }
public restartSimulation(): void {
this.simulationTime = 0;
this.createSimulation();
}
/** /**
* Initializes and starts the cloth simulation. * Initializes and starts the cloth simulation.
*/ */
@@ -164,9 +176,13 @@ export class ClothComponent {
const constraintsP2: number[] = []; const constraintsP2: number[] = [];
const constraintsP3: number[] = []; const constraintsP3: number[] = [];
const addConstraint = (arr: number[], a: number, b: number): void => { // Type 1.0 = horizontal/diagonal (no elongation), Type 2.0 = vertical (elongation applies)
const addHorizontalConstraint = (arr: number[], a: number, b: number): void => {
arr.push(a, b, config.spacing, 1.0); arr.push(a, b, config.spacing, 1.0);
}; };
const addVerticalConstraint = (arr: number[], a: number, b: number): void => {
arr.push(a, b, config.spacing, 2.0);
};
// Fill positions (Pin top row) // Fill positions (Pin top row)
for (let y = 0; y < config.gridHeight; y++) { for (let y = 0; y < config.gridHeight; y++) {
@@ -186,14 +202,14 @@ export class ClothComponent {
// Graph Coloring (4 Phases) // Graph Coloring (4 Phases)
for (let y = 0; y < config.gridHeight; y++) { for (let y = 0; y < config.gridHeight; y++) {
for (let x = 0; x < config.gridWidth - 1; x += 2) addConstraint(constraintsP0, y * config.gridWidth + x, y * config.gridWidth + x + 1); for (let x = 0; x < config.gridWidth - 1; x += 2) addHorizontalConstraint(constraintsP0, y * config.gridWidth + x, y * config.gridWidth + x + 1);
for (let x = 1; x < config.gridWidth - 1; x += 2) addConstraint(constraintsP1, y * config.gridWidth + x, y * config.gridWidth + x + 1); for (let x = 1; x < config.gridWidth - 1; x += 2) addHorizontalConstraint(constraintsP1, y * config.gridWidth + x, y * config.gridWidth + x + 1);
} }
for (let y = 0; y < config.gridHeight - 1; y += 2) { for (let y = 0; y < config.gridHeight - 1; y += 2) {
for (let x = 0; x < config.gridWidth; x++) addConstraint(constraintsP2, y * config.gridWidth + x, (y + 1) * config.gridWidth + x); for (let x = 0; x < config.gridWidth; x++) addVerticalConstraint(constraintsP2, y * config.gridWidth + x, (y + 1) * config.gridWidth + x);
} }
for (let y = 1; y < config.gridHeight - 1; y += 2) { for (let y = 1; y < config.gridHeight - 1; y += 2) {
for (let x = 0; x < config.gridWidth; x++) addConstraint(constraintsP3, y * config.gridWidth + x, (y + 1) * config.gridWidth + x); for (let x = 0; x < config.gridWidth; x++) addVerticalConstraint(constraintsP3, y * config.gridWidth + x, (y + 1) * config.gridWidth + x);
} }
const constraintsP4: number[] = []; const constraintsP4: number[] = [];
@@ -228,7 +244,7 @@ export class ClothComponent {
constraintsP0, constraintsP1, constraintsP2, constraintsP3, constraintsP0, constraintsP1, constraintsP2, constraintsP3,
constraintsP4, constraintsP5, constraintsP6, constraintsP7 constraintsP4, constraintsP5, constraintsP6, constraintsP7
], ],
params: new Float32Array(8) params: new Float32Array(9)
}; };
} }
@@ -331,7 +347,7 @@ export class ClothComponent {
// 6. RENDER LOOP // 6. RENDER LOOP
// ======================================================================== // ========================================================================
private startRenderLoop(engine: WebGPUEngine, scene: Scene, config: ClothConfig, buffers: ClothBuffers, pipelines: ClothPipelines): void { private startRenderLoop(engine: WebGPUEngine, scene: Scene, config: ClothConfig, buffers: ClothBuffers, pipelines: ClothPipelines): void {
const paramsData = new Float32Array(8); const paramsData = new Float32Array(9);
// Pre-calculate constraint dispatch sizes for the 4 phases // Pre-calculate constraint dispatch sizes for the 4 phases
const constraintsLength = buffers.constraints.map(b => (b as any)._buffer.capacity / 4 / 4); // Elements / vec4 length const constraintsLength = buffers.constraints.map(b => (b as any)._buffer.capacity / 4 / 4); // Elements / vec4 length
@@ -347,16 +363,23 @@ export class ClothComponent {
const windX = this.isWindActive ? 5.0 : 0.0; const windX = this.isWindActive ? 5.0 : 0.0;
const windY = 0.0; const windY = 0.0;
const windZ = this.isWindActive ? 15.0 : 0.0; const windZ = this.isWindActive ? 15.0 : 0.0;
const scaledCompliance = 0.00001 * config.particleInvMass * config.spacing;
// Logarithmic compliance: stiffness=1 → very soft fabric, stiffness=100 → rigid metal sheet.
// alpha = compliance / dt² must be >> wSum (≈800) to be soft, << wSum to be rigid.
const softCompliance = 10.0;
const rigidCompliance = 0.00001;
const t = (this.stiffness - 1) / 99.0;
const compliance = softCompliance * Math.pow(rigidCompliance / softCompliance, t);
paramsData[0] = 0.016; // dt paramsData[0] = 0.016; // dt
paramsData[1] = -9.81; // gravity paramsData[1] = -9.81; // gravity
paramsData[2] = scaledCompliance; paramsData[2] = compliance;
paramsData[3] = config.numVertices; paramsData[3] = config.numVertices;
paramsData[4] = windX; paramsData[4] = windX;
paramsData[5] = windY; paramsData[5] = windY;
paramsData[6] = windZ; paramsData[6] = windZ;
paramsData[7] = this.simulationTime; paramsData[7] = this.simulationTime;
paramsData[8] = this.elongation;
buffers.params.update(paramsData); buffers.params.update(paramsData);

View File

@@ -13,7 +13,8 @@ export const CLOTH_SHARED_STRUCTS = `
wind_x: f32, wind_x: f32,
wind_y: f32, wind_y: f32,
wind_z: f32, wind_z: f32,
time: f32 time: f32,
elongation: f32
}; };
`; `;
@@ -26,9 +27,8 @@ export const CLOTH_VERTEX_SHADER_WGSL = `
uniform viewProjection : mat4x4<f32>; uniform viewProjection : mat4x4<f32>;
// Varyings, um Daten an den Fragment-Shader zu senden
varying vUV : vec2<f32>; varying vUV : vec2<f32>;
varying vWorldPos : vec3<f32>; // NEU: Wir brauchen die 3D-Position für das Licht! varying vWorldPos : vec3<f32>;
@vertex @vertex
fn main(input : VertexInputs) -> FragmentInputs { fn main(input : VertexInputs) -> FragmentInputs {
@@ -38,7 +38,7 @@ export const CLOTH_VERTEX_SHADER_WGSL = `
output.position = uniforms.viewProjection * vec4<f32>(worldPos, 1.0); output.position = uniforms.viewProjection * vec4<f32>(worldPos, 1.0);
output.vUV = input.uv; output.vUV = input.uv;
output.vWorldPos = worldPos; // Position weitergeben output.vWorldPos = worldPos;
return output; return output;
} }
@@ -133,13 +133,15 @@ export const CLOTH_SOLVE_COMPUTE_WGSL = CLOTH_SHARED_STRUCTS + `
if (idx >= arrayLength(&constraints)) { return; } if (idx >= arrayLength(&constraints)) { return; }
let constraint = constraints[idx]; let constraint = constraints[idx];
let isActive = constraint.w;
if (isActive < 0.5) { return; } // constraint.w: 0.0 = inactive, 1.0 = horizontal/diagonal, 2.0 = vertical
if (constraint.w < 0.5) { return; }
let idA = u32(constraint.x); let idA = u32(constraint.x);
let idB = u32(constraint.y); let idB = u32(constraint.y);
let restLength = constraint.z;
// constraint.w encodes type: 1.0 = horizontal/diagonal, 2.0 = vertical (elongation applies)
let restLength =constraint.z * p.elongation;
var pA = positions[idA]; var pA = positions[idA];
var pB = positions[idB]; var pB = positions[idB];