Finalized Algorithm
Added final descriptions and polished the system
This commit is contained in:
@@ -3,30 +3,42 @@
|
||||
<mat-card-title>{{ 'PENDULUM.TITLE' | translate }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<app-information [algorithmInformation]="algoInformation"/>
|
||||
<div class="controls-container">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<div class="slider-control-container">
|
||||
<p style="white-space: nowrap">{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}</p>
|
||||
<ngx-slider [(value)]="simParams.trailDecay" [options]="trailDecayOptions" ></ngx-slider>
|
||||
<p style="white-space: nowrap">{{ 'PENDULUM.ATTRACTION' | translate }}</p>
|
||||
<ngx-slider [(value)]="simParams.g" [options]="gravityOptions" ></ngx-slider>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<div class="slider-control-container">
|
||||
<p style="white-space: nowrap">{{ 'PENDULUM.L1_LENGTH' | translate }}</p>
|
||||
<ngx-slider [(value)]="simParams.l1" [options]="lengthOptions" ></ngx-slider>
|
||||
<p style="white-space: nowrap">{{ 'PENDULUM.L2_LENGTH' | translate }}</p>
|
||||
<ngx-slider [(value)]="simParams.l2" [options]="lengthOptions" ></ngx-slider>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<div class="slider-control-container">
|
||||
<p style="white-space: nowrap">{{ 'PENDULUM.M1_MASS' | translate }}</p>
|
||||
<ngx-slider [(value)]="simParams.m1" [options]="massOptions" ></ngx-slider>
|
||||
<p style="white-space: nowrap">{{ 'PENDULUM.M2_MASS' | translate }}</p>
|
||||
<ngx-slider [(value)]="simParams.m2" [options]="massOptions" ></ngx-slider>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<div class="slider-control-container">
|
||||
<p style="white-space: nowrap">{{ 'PENDULUM.DAMPING' | translate }}</p>
|
||||
<ngx-slider [(value)]="simParams.damping" [options]="dampingOptions" ></ngx-slider>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<div class="slider-control-container">
|
||||
<button mat-raised-button color="primary" (click)="pushPendulum(true)">
|
||||
{{ 'PENDULUM.POKE_M1' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="pushPendulum(false)">
|
||||
{{ 'PENDULUM.POKE_M2' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="resetPendulum()">
|
||||
{{ 'PENDULUM.RESET' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="legend" style="margin-top: 10px">
|
||||
<span><span class="legend-color L1"></span> L1</span>
|
||||
<span><span class="legend-color L2"></span> L2</span>
|
||||
<span><span class="legend-color M1"></span> M1</span>
|
||||
|
||||
@@ -5,8 +5,12 @@ import {ComputeShader, ShaderLanguage, StorageBuffer} from '@babylonjs/core';
|
||||
import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PENDULUM_RENDER_COMPUTE_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {NgxSliderModule, Options} from '@angular-slider/ngx-slider';
|
||||
import {DEFAULT_DAMPING, DEFAULT_G, DEFAULT_L1_LENGTH, DEFAULT_M1_MASS, DEFAULT_L2_LENGTH, DEFAULT_M2_MASS, DEFAULT_TRAIL_DECAY, MAX_DAMPING, MAX_G, MAX_LENGTH, MAX_MASS, MAX_TRAIL_DECAY, MIN_DAMPING, MIN_G, MIN_LENGTH, MIN_MASS, MIN_TRAIL_DECAY} from './pendulum.model';
|
||||
import {DEFAULT_DAMPING, DEFAULT_G, DEFAULT_L1_LENGTH, DEFAULT_M1_MASS, DEFAULT_L2_LENGTH, DEFAULT_M2_MASS, DEFAULT_TRAIL_DECAY, MAX_DAMPING, MAX_G, MAX_LENGTH, MAX_MASS, MAX_TRAIL_DECAY, MIN_DAMPING, MIN_G, MIN_LENGTH, MIN_MASS, MIN_TRAIL_DECAY, IMPULSE_M2, IMPULSE_M1} from './pendulum.model';
|
||||
import {TranslatePipe} from '@ngx-translate/core';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {Information} from '../information/information';
|
||||
import {AlgorithmInformation} from '../information/information.models';
|
||||
import {UrlConstants} from '../../../constants/UrlConstants';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pendulum',
|
||||
@@ -19,13 +23,30 @@ import {TranslatePipe} from '@ngx-translate/core';
|
||||
FormsModule,
|
||||
NgxSliderModule,
|
||||
TranslatePipe,
|
||||
MatButton,
|
||||
Information,
|
||||
],
|
||||
templateUrl: './pendulum.component.html',
|
||||
styleUrl: './pendulum.component.scss',
|
||||
})
|
||||
export class PendulumComponent {
|
||||
class PendulumComponent {
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
algoInformation: AlgorithmInformation = {
|
||||
title: 'PENDULUM.EXPLANATION.TITLE',
|
||||
entries: [
|
||||
{
|
||||
name: '',
|
||||
description: 'PENDULUM.EXPLANATION.EXPLANATION',
|
||||
link: UrlConstants.DOUBLE_PENDULUM_WIKI
|
||||
}
|
||||
],
|
||||
disclaimer: 'PENDULUM.EXPLANATION.DISCLAIMER',
|
||||
disclaimerBottom: 'PENDULUM.EXPLANATION.DISCLAIMER_BOTTOM',
|
||||
disclaimerListEntry: ['PENDULUM.EXPLANATION.DISCLAIMER_1', 'PENDULUM.EXPLANATION.DISCLAIMER_2', 'PENDULUM.EXPLANATION.DISCLAIMER_3', 'PENDULUM.EXPLANATION.DISCLAIMER_4']
|
||||
};
|
||||
|
||||
|
||||
renderConfig: RenderConfig = {
|
||||
mode: '2D',
|
||||
initialViewSize: 2,
|
||||
@@ -96,11 +117,23 @@ export class PendulumComponent {
|
||||
l1: DEFAULT_L1_LENGTH,
|
||||
l2: DEFAULT_L2_LENGTH,
|
||||
damping: DEFAULT_DAMPING,
|
||||
trailDecay: DEFAULT_TRAIL_DECAY
|
||||
trailDecay: DEFAULT_TRAIL_DECAY,
|
||||
impulseM1: 0.0,
|
||||
impulseM2: 0.0,
|
||||
};
|
||||
|
||||
private currentSceneData: SceneEventData | null = null;
|
||||
|
||||
onSceneReady(event: SceneEventData) {
|
||||
const { engine, scene } = event;
|
||||
this.currentSceneData = event;
|
||||
this.createSimulation();
|
||||
}
|
||||
|
||||
private createSimulation() {
|
||||
if (!this.currentSceneData){
|
||||
return;
|
||||
}
|
||||
const {engine, scene} = this.currentSceneData;
|
||||
engine.resize();
|
||||
|
||||
const width = engine.getRenderWidth();
|
||||
@@ -113,20 +146,20 @@ export class PendulumComponent {
|
||||
const stateBuffer = new StorageBuffer(engine, 4 * 4);
|
||||
stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); // Initial angles
|
||||
|
||||
const paramsBuffer = new StorageBuffer(engine, 12 * 4);
|
||||
const paramsData = new Float32Array(12);
|
||||
const paramsBuffer = new StorageBuffer(engine, 14 * 4);
|
||||
const paramsData = new Float32Array(14);
|
||||
|
||||
// --- 2. SHADERS ---
|
||||
const csPhysics = new ComputeShader("physics", engine,
|
||||
{ computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL },
|
||||
{ bindingsMapping: { "state": { group: 0, binding: 0 }, "p": { group: 0, binding: 1 } } }
|
||||
{computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL},
|
||||
{bindingsMapping: {"state": {group: 0, binding: 0}, "p": {group: 0, binding: 1}}}
|
||||
);
|
||||
csPhysics.setStorageBuffer("state", stateBuffer);
|
||||
csPhysics.setStorageBuffer("p", paramsBuffer);
|
||||
|
||||
const csRender = new ComputeShader("render", engine,
|
||||
{ computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL },
|
||||
{ bindingsMapping: { "pixelBuffer": { group: 0, binding: 0 }, "p": { group: 0, binding: 1 }, "state": { group: 0, binding: 2 } } }
|
||||
{computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL},
|
||||
{bindingsMapping: {"pixelBuffer": {group: 0, binding: 0}, "p": {group: 0, binding: 1}, "state": {group: 0, binding: 2}}}
|
||||
);
|
||||
csRender.setStorageBuffer("pixelBuffer", pixelBuffer);
|
||||
csRender.setStorageBuffer("p", paramsBuffer);
|
||||
@@ -140,6 +173,8 @@ export class PendulumComponent {
|
||||
mat.setStorageBuffer("p", paramsBuffer);
|
||||
}
|
||||
|
||||
//remove old observables if available
|
||||
scene.onBeforeRenderObservable.clear();
|
||||
// --- 4. RENDER LOOP ---
|
||||
scene.onBeforeRenderObservable.add(() => {
|
||||
this.simParams.time += this.simParams.dt;
|
||||
@@ -159,7 +194,11 @@ export class PendulumComponent {
|
||||
paramsData[8] = this.simParams.l2;
|
||||
paramsData[9] = this.simParams.damping;
|
||||
paramsData[10] = this.simParams.trailDecay;
|
||||
paramsData[11] = 0; // Pad
|
||||
paramsData[11] = this.simParams.impulseM1;
|
||||
paramsData[12] = this.simParams.impulseM2;
|
||||
paramsData[13] = 0; // Pad
|
||||
|
||||
this.resetImpulses();
|
||||
|
||||
paramsBuffer.update(paramsData);
|
||||
|
||||
@@ -171,4 +210,31 @@ export class PendulumComponent {
|
||||
});
|
||||
}
|
||||
|
||||
private resetImpulses() {
|
||||
if (this.simParams.impulseM1 !== 0.0) {
|
||||
this.simParams.impulseM1 = 0;
|
||||
}
|
||||
|
||||
if (this.simParams.impulseM2 !== 0.0) {
|
||||
this.simParams.impulseM2 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pushPendulum(m1: boolean) {
|
||||
if (m1)
|
||||
{
|
||||
this.simParams.impulseM1 = IMPULSE_M1;
|
||||
return;
|
||||
}
|
||||
|
||||
this.simParams.impulseM2 = IMPULSE_M2;
|
||||
}
|
||||
|
||||
resetPendulum() {
|
||||
this.createSimulation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default PendulumComponent
|
||||
|
||||
@@ -19,3 +19,6 @@ export const DEFAULT_M1_MASS = 2;
|
||||
export const DEFAULT_M2_MASS = 1;
|
||||
export const MIN_MASS = 0.1;
|
||||
export const MAX_MASS = 5;
|
||||
|
||||
export const IMPULSE_M1 = 7;
|
||||
export const IMPULSE_M2 = 15;
|
||||
|
||||
@@ -25,6 +25,8 @@ const SHARED_STRUCTS = `
|
||||
l2: f32,
|
||||
damping: f32,
|
||||
trailDecay: f32,
|
||||
impulseM1: f32,
|
||||
impulseM2: f32,
|
||||
pad: f32 // <-- Padding for safe 16-byte memory alignment
|
||||
};
|
||||
|
||||
@@ -134,8 +136,8 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + `
|
||||
let den2 = p.l2 * (2.0 * p.m1 + p.m2 - p.m2 * cos(2.0 * delta_t));
|
||||
let a2 = num2 / den2;
|
||||
|
||||
let new_v1 = (v1 + a1 * p.dt) * p.damping;
|
||||
let new_v2 = (v2 + a2 * p.dt) * p.damping;
|
||||
let new_v1 = (v1 + a1 * p.dt) * p.damping + p.impulseM1;
|
||||
let new_v2 = (v2 + a2 * p.dt) * p.damping + p.impulseM2;
|
||||
|
||||
state.v1 = new_v1;
|
||||
state.v2 = new_v2;
|
||||
|
||||
Reference in New Issue
Block a user