Added one cloth parameter elongation
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
Reference in New Issue
Block a user