Cloth: add info, outline, diagonals, shader
Add an informational panel and mesh-outline toggle to the cloth demo, plus richer physics and shading. The cloth component now provides AlgorithmInformation to an <app-information> view and a toggleMesh() that flips the mesh wireframe. Constraint generation was extended with four diagonal phases (constraintsP4..P7) and the solver loop was generalized to iterate solver pipelines, improving parallel XPBD constraint handling. The WGSL vertex/fragment shaders were updated to pass world positions, compute normals, add simple lighting and a grid-based base color. Also update information template/model to support optional translated entry names and expand i18n (DE/EN) with cloth texts and a Docker key.
This commit is contained in:
@@ -3,11 +3,15 @@
|
||||
<mat-card-title>{{ 'CLOTH.TITLE' | translate }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<app-information [algorithmInformation]="algoInformation"/>
|
||||
<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>
|
||||
<button mat-raised-button color="primary" (click)="toggleMesh()">
|
||||
{{ isOutlineActive ? ('CLOTH.OUTLINE_OFF' | translate) : ('CLOTH.OUTLINE_ON' | translate) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-babylon-canvas
|
||||
|
||||
@@ -17,6 +17,9 @@ import {
|
||||
} from './cloth.shader';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {ClothBuffers, ClothConfig, ClothData, ClothPipelines} from './cloth.model';
|
||||
import {Information} from '../information/information';
|
||||
import {AlgorithmInformation} from '../information/information.models';
|
||||
import {UrlConstants} from '../../../constants/UrlConstants';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cloth',
|
||||
@@ -27,7 +30,8 @@ import {ClothBuffers, ClothConfig, ClothData, ClothPipelines} from './cloth.mode
|
||||
MatCardTitle,
|
||||
TranslatePipe,
|
||||
BabylonCanvas,
|
||||
MatButton
|
||||
MatButton,
|
||||
Information
|
||||
],
|
||||
templateUrl: './cloth.component.html',
|
||||
styleUrl: './cloth.component.scss',
|
||||
@@ -37,7 +41,7 @@ export class ClothComponent {
|
||||
private simulationTime: number = 0;
|
||||
private clothMesh: GroundMesh | null = null;
|
||||
public isWindActive: boolean = false;
|
||||
|
||||
public isOutlineActive: boolean = false;
|
||||
|
||||
public renderConfig: RenderConfig = {
|
||||
mode: '3D',
|
||||
@@ -45,6 +49,39 @@ export class ClothComponent {
|
||||
shaderLanguage: ShaderLanguage.WGSL
|
||||
};
|
||||
|
||||
algoInformation: AlgorithmInformation = {
|
||||
title: 'CLOTH.EXPLANATION.TITLE',
|
||||
entries: [
|
||||
{
|
||||
name: 'CLOTH.EXPLANATION.CLOTH_SIMULATION_EXPLANATION_TITLE',
|
||||
description: 'CLOTH.EXPLANATION.CLOTH_SIMULATION_EXPLANATION',
|
||||
link: UrlConstants.MANDELBULB_WIKI,
|
||||
translateName: true
|
||||
},
|
||||
{
|
||||
name: 'CLOTH.EXPLANATION.XPBD_EXPLANATION_TITLE',
|
||||
description: 'CLOTH.EXPLANATION.XPBD_EXPLANATION',
|
||||
link: UrlConstants.MANDELBOX_WIKI,
|
||||
translateName: true
|
||||
},
|
||||
{
|
||||
name: 'CLOTH.EXPLANATION.GPU_PARALLELIZATION_EXPLANATION_TITLE',
|
||||
description: 'CLOTH.EXPLANATION.GPU_PARALLELIZATION_EXPLANATION',
|
||||
link: UrlConstants.JULIA3D_WIKI,
|
||||
translateName: true
|
||||
},
|
||||
{
|
||||
name: 'CLOTH.EXPLANATION.DATA_STRUCTURES_EXPLANATION_TITLE',
|
||||
description: 'CLOTH.EXPLANATION.DATA_STRUCTURES_EXPLANATION',
|
||||
link: UrlConstants.JULIA3D_WIKI,
|
||||
translateName: true
|
||||
}
|
||||
],
|
||||
disclaimer: 'CLOTH.EXPLANATION.DISCLAIMER',
|
||||
disclaimerBottom: '',
|
||||
disclaimerListEntry: ['CLOTH.EXPLANATION.DISCLAIMER_1', 'CLOTH.EXPLANATION.DISCLAIMER_2', 'CLOTH.EXPLANATION.DISCLAIMER_3', 'CLOTH.EXPLANATION.DISCLAIMER_4']
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the Babylon scene is ready.
|
||||
* @param event The scene event data.
|
||||
@@ -58,6 +95,14 @@ export class ClothComponent {
|
||||
this.isWindActive = !this.isWindActive;
|
||||
}
|
||||
|
||||
public toggleMesh(): void {
|
||||
this.isOutlineActive = !this.isOutlineActive;
|
||||
if (!this.clothMesh?.material) {
|
||||
return;
|
||||
}
|
||||
this.clothMesh.material.wireframe = this.isOutlineActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and starts the cloth simulation.
|
||||
*/
|
||||
@@ -151,11 +196,38 @@ export class ClothComponent {
|
||||
for (let x = 0; x < config.gridWidth; x++) addConstraint(constraintsP3, y * config.gridWidth + x, (y + 1) * config.gridWidth + x);
|
||||
}
|
||||
|
||||
const constraintsP4: number[] = [];
|
||||
const constraintsP5: number[] = [];
|
||||
const constraintsP6: number[] = [];
|
||||
const constraintsP7: number[] = [];
|
||||
|
||||
const diagSpacing = config.spacing * Math.SQRT2;
|
||||
const addDiagConstraint = (arr: number[], a: number, b: number): void => {
|
||||
arr.push(a, b, diagSpacing, 1.0);
|
||||
};
|
||||
|
||||
for (let y = 0; y < config.gridHeight - 1; y++) {
|
||||
const arr = (y % 2 === 0) ? constraintsP4 : constraintsP5;
|
||||
for (let x = 0; x < config.gridWidth - 1; x++) {
|
||||
addDiagConstraint(arr, y * config.gridWidth + x, (y + 1) * config.gridWidth + (x + 1));
|
||||
}
|
||||
}
|
||||
|
||||
for (let y = 0; y < config.gridHeight - 1; y++) {
|
||||
const arr = (y % 2 === 0) ? constraintsP6 : constraintsP7;
|
||||
for (let x = 0; x < config.gridWidth - 1; x++) {
|
||||
addDiagConstraint(arr, y * config.gridWidth + (x + 1), (y + 1) * config.gridWidth + x);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
positions: positionsData,
|
||||
prevPositions: prevPositionsData,
|
||||
velocities: velocitiesData,
|
||||
constraints: [constraintsP0, constraintsP1, constraintsP2, constraintsP3],
|
||||
constraints: [
|
||||
constraintsP0, constraintsP1, constraintsP2, constraintsP3,
|
||||
constraintsP4, constraintsP5, constraintsP6, constraintsP7
|
||||
],
|
||||
params: new Float32Array(8)
|
||||
};
|
||||
}
|
||||
@@ -293,10 +365,9 @@ export class ClothComponent {
|
||||
|
||||
// 2. XPBD Solver (Substeps) - Graph Coloring Phase
|
||||
for (let i = 0; i < substeps; i++) {
|
||||
pipelines.solvers[0].dispatch(dispatchXConstraints[0], 1, 1);
|
||||
pipelines.solvers[1].dispatch(dispatchXConstraints[1], 1, 1);
|
||||
pipelines.solvers[2].dispatch(dispatchXConstraints[2], 1, 1);
|
||||
pipelines.solvers[3].dispatch(dispatchXConstraints[3], 1, 1);
|
||||
for (let phase = 0; phase < pipelines.solvers.length; phase++) {
|
||||
pipelines.solvers[phase].dispatch(dispatchXConstraints[phase], 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update velocities
|
||||
|
||||
@@ -22,22 +22,23 @@ export const CLOTH_SHARED_STRUCTS = `
|
||||
// ==========================================
|
||||
export const CLOTH_VERTEX_SHADER_WGSL = `
|
||||
attribute uv : vec2<f32>;
|
||||
|
||||
// Storage Buffer
|
||||
var<storage, read> positions : array<vec4<f32>>;
|
||||
|
||||
// Babylon Preprocessor Magic
|
||||
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!
|
||||
|
||||
@vertex
|
||||
fn main(input : VertexInputs) -> FragmentInputs {
|
||||
var output : FragmentInputs;
|
||||
|
||||
let worldPos = positions[input.vertexIndex].xyz;
|
||||
|
||||
output.position = uniforms.viewProjection * vec4<f32>(worldPos, 1.0);
|
||||
|
||||
output.vUV = input.uv;
|
||||
output.vWorldPos = worldPos; // Position weitergeben
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -48,13 +49,24 @@ export const CLOTH_VERTEX_SHADER_WGSL = `
|
||||
// ==========================================
|
||||
export const CLOTH_FRAGMENT_SHADER_WGSL = `
|
||||
varying vUV : vec2<f32>;
|
||||
varying vWorldPos : vec3<f32>;
|
||||
|
||||
@fragment
|
||||
fn main(input: FragmentInputs) -> FragmentOutputs {
|
||||
var output: FragmentOutputs;
|
||||
|
||||
let color = vec3<f32>(input.vUV.x * 0.8, input.vUV.y * 0.8, 0.9);
|
||||
output.color = vec4<f32>(color, 1.0);
|
||||
let dx = dpdx(input.vWorldPos);
|
||||
let dy = dpdy(input.vWorldPos);
|
||||
let normal = normalize(cross(dx, dy));
|
||||
let lightDir = normalize(vec3<f32>(1.0, 1.0, 0.5));
|
||||
let diffuse = max(0.0, abs(dot(normal, lightDir)));
|
||||
let ambient = 0.3;
|
||||
let lightIntensity = ambient + (diffuse * 0.7);
|
||||
let grid = (floor(input.vUV.x * 20.0) + floor(input.vUV.y * 20.0)) % 2.0;
|
||||
let baseColor = mix(vec3<f32>(0.8, 0.4, 0.15), vec3<f32>(0.9, 0.5, 0.2), grid);
|
||||
let finalColor = baseColor * lightIntensity;
|
||||
|
||||
output.color = vec4<f32>(finalColor, 1.0);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,14 @@
|
||||
@for (algo of algorithmInformation.entries; track algo)
|
||||
{
|
||||
<p>
|
||||
<strong>{{ algo.name }}</strong> {{ algo.description | translate }}
|
||||
<strong>
|
||||
@if(algo.translateName){
|
||||
{{ algo.name | translate}}
|
||||
} @else {
|
||||
{{ algo.name }}
|
||||
}
|
||||
</strong>
|
||||
{{ algo.description | translate }}
|
||||
<a href="{{algo.link}}" target="_blank" rel="noopener noreferrer">Wikipedia</a>
|
||||
</p>
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ export interface AlgorithmEntry {
|
||||
name: string;
|
||||
description: string;
|
||||
link: string;
|
||||
|
||||
translateName?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user