|
|
|
|
@@ -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);
|
|
|
|
|
|
|
|
|
|
|