bugfix/someImporvements #31

Merged
lobo merged 8 commits from bugfix/someImporvements into main 2026-03-07 17:20:38 +01:00
3 changed files with 50 additions and 16 deletions
Showing only changes of commit 150306333e - Show all commits

View File

@@ -12,6 +12,15 @@
<button mat-raised-button color="primary" (click)="toggleMesh()">
{{ isOutlineActive ? ('CLOTH.OUTLINE_OFF' | translate) : ('CLOTH.OUTLINE_ON' | translate) }}
</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>
<app-babylon-canvas

View File

@@ -4,7 +4,9 @@
*/
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatCard, MatCardContent, MatCardHeader, MatCardTitle } from '@angular/material/card';
import { MatSliderModule } from '@angular/material/slider';
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, GroundMesh, WebGPUEngine, Scene} from '@babylonjs/core';
@@ -31,6 +33,8 @@ import {UrlConstants} from '../../../constants/UrlConstants';
TranslatePipe,
BabylonCanvas,
MatButton,
MatSliderModule,
FormsModule,
Information
],
templateUrl: './cloth.component.html',
@@ -42,6 +46,9 @@ export class ClothComponent {
private clothMesh: GroundMesh | null = null;
public isWindActive: 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 = {
mode: '3D',
@@ -103,6 +110,11 @@ export class ClothComponent {
this.clothMesh.material.wireframe = this.isOutlineActive;
}
public restartSimulation(): void {
this.simulationTime = 0;
this.createSimulation();
}
/**
* Initializes and starts the cloth simulation.
*/
@@ -164,9 +176,13 @@ export class ClothComponent {
const constraintsP2: 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);
};
const addVerticalConstraint = (arr: number[], a: number, b: number): void => {
arr.push(a, b, config.spacing, 2.0);
};
// Fill positions (Pin top row)
for (let y = 0; y < config.gridHeight; y++) {
@@ -186,14 +202,14 @@ export class ClothComponent {
// Graph Coloring (4 Phases)
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 = 1; x < config.gridWidth - 1; x += 2) addConstraint(constraintsP1, 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) addHorizontalConstraint(constraintsP1, y * config.gridWidth + x, y * config.gridWidth + x + 1);
}
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 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[] = [];
@@ -228,7 +244,7 @@ export class ClothComponent {
constraintsP0, constraintsP1, constraintsP2, constraintsP3,
constraintsP4, constraintsP5, constraintsP6, constraintsP7
],
params: new Float32Array(8)
params: new Float32Array(9)
};
}
@@ -331,7 +347,7 @@ export class ClothComponent {
// 6. RENDER LOOP
// ========================================================================
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
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 windY = 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[1] = -9.81; // gravity
paramsData[2] = scaledCompliance;
paramsData[2] = compliance;
paramsData[3] = config.numVertices;
paramsData[4] = windX;
paramsData[5] = windY;
paramsData[6] = windZ;
paramsData[7] = this.simulationTime;
paramsData[8] = this.elongation;
buffers.params.update(paramsData);

View File

@@ -13,7 +13,8 @@ export const CLOTH_SHARED_STRUCTS = `
wind_x: f32,
wind_y: f32,
wind_z: f32,
time: f32
time: f32,
elongation: f32
};
`;
@@ -26,9 +27,8 @@ export const CLOTH_VERTEX_SHADER_WGSL = `
uniform viewProjection : mat4x4<f32>;
// Varyings, um Daten an den Fragment-Shader zu senden
varying vUV : vec2<f32>;
varying vWorldPos : vec3<f32>; // NEU: Wir brauchen die 3D-Position für das Licht!
varying vWorldPos : vec3<f32>;
@vertex
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.vUV = input.uv;
output.vWorldPos = worldPos; // Position weitergeben
output.vWorldPos = worldPos;
return output;
}
@@ -133,13 +133,15 @@ export const CLOTH_SOLVE_COMPUTE_WGSL = CLOTH_SHARED_STRUCTS + `
if (idx >= arrayLength(&constraints)) { return; }
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 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 pB = positions[idB];