/ proof-of-concept / cymsimweb
Sign in
proof-of-concept / cymsimweb
cymsimweb / src / model / geometry.ts
78 lines · 4.0 KBCodeBlameHistory
1e29264cymsimweb: WebGPU companion of cymsim with YAML/form editor, live spectrogram and playbackJeremy Magland 1// Cymbal mid-surface height z0(x, y) and thickness h(x, y): bow, bell, rim warp and seeded
2// hammer dents, as in cymsim/geometry.py. The slope that enters the strains is the CST
3// gradient of the nodal heights (see shell.ts), so no analytic gradient is needed here.
4import type { Imperfections, Profile } from "../config/schema.ts";
5import { Rng } from "./rng.ts";
7export class CymbalProfile {
8 readonly dentX: Float64Array; readonly dentY: Float64Array; readonly dentAmp: Float64Array; readonly dentSigma: Float64Array;
9 readonly warpM: Float64Array; readonly warpA: Float64Array; readonly warpPhase: Float64Array;
10 readonly ovalPhase: number; readonly bellCx: number; readonly bellCy: number;
12 readonly profile: Profile; readonly imperfections: Imperfections;
14 constructor(profile: Profile, imperfections: Imperfections) {
15 this.profile = profile; this.imperfections = imperfections;
16 const p = profile, im = imperfections, rng = new Rng(im.seed);
17 const r0 = p.bell_shoulder_radius + im.dent_margin, r1 = p.radius - im.dent_margin;
18 const n = Math.max(0, Math.floor(im.n_dents));
19 this.dentX = new Float64Array(n); this.dentY = new Float64Array(n);
20 this.dentAmp = new Float64Array(n); this.dentSigma = new Float64Array(n);
21 for (let i = 0; i < n; i++) {
22 const rr = Math.sqrt(rng.uniform(r0 * r0, r1 * r1)), tt = rng.uniform(0, 2 * Math.PI);
23 this.dentX[i] = rr * Math.cos(tt); this.dentY[i] = rr * Math.sin(tt);
24 }
25 for (let i = 0; i < n; i++) this.dentAmp[i] = im.dent_depth * rng.sign() * rng.uniform(0.5, 1.5);
26 for (let i = 0; i < n; i++) this.dentSigma[i] = im.dent_sigma * rng.uniform(0.7, 1.3);
27 const modes = im.rim_warp_modes.map((m) => Math.floor(m));
28 this.warpM = Float64Array.from(modes);
29 this.warpA = Float64Array.from(modes.map((m) => (im.rim_warp * 2) / m));
30 this.warpPhase = Float64Array.from(modes.map(() => rng.uniform(0, 2 * Math.PI)));
31 this.ovalPhase = rng.uniform(0, 2 * Math.PI);
32 const bo = rng.uniform(0, 2 * Math.PI);
33 this.bellCx = im.bell_offset * Math.cos(bo); this.bellCy = im.bell_offset * Math.sin(bo);
34 }
36 private shoulderRadius(phi: number): number {
37 return this.profile.bell_shoulder_radius * (1 + this.imperfections.shoulder_ovality * Math.cos(2 * phi + this.ovalPhase));
38 }
40 /** 1 on the bell plateau, 0 beyond the shoulder, C1 cosine ramp in between. */
41 private bellBlend(rBell: number, rSh: number): number {
42 const top = this.profile.bell_top_radius;
43 const t = Math.min(Math.max((rBell - top) / (rSh - top), 0), 1);
44 return 0.5 * (1 + Math.cos(Math.PI * t));
45 }
47 private bowCoordinate(x: number, y: number) {
48 const r = Math.sqrt(x * x + y * y + 1e-30);
49 const phi = Math.atan2(y, r < 1e-6 ? 1e-6 : x);
50 const rSh = this.shoulderRadius(phi);
51 const rBell = Math.sqrt((x - this.bellCx) ** 2 + (y - this.bellCy) ** 2 + 1e-30);
52 const s = Math.min(Math.max((this.profile.radius - r) / (this.profile.radius - rSh), 0), 1);
53 return { s, phi, rBell, rSh };
54 }
56 /** Mid-surface height [m]. */
57 z0(x: number, y: number): number {
58 const p = this.profile;
59 const { s, phi, rBell, rSh } = this.bowCoordinate(x, y);
60 const u = Math.max(1 - s, 1e-12);
61 let z = p.bow_height * (1 - Math.pow(u, p.bow_power)) + p.bell_height * this.bellBlend(rBell, rSh);
62 let warp = 0;
63 for (let k = 0; k < this.warpM.length; k++) warp += this.warpA[k] * Math.cos(this.warpM[k] * phi + this.warpPhase[k]);
64 z += u * warp;
65 for (let i = 0; i < this.dentX.length; i++) {
66 const dx = x - this.dentX[i], dy = y - this.dentY[i], s2 = this.dentSigma[i] * this.dentSigma[i];
67 z += this.dentAmp[i] * Math.exp(-(dx * dx + dy * dy) / (2 * s2));
68 }
69 return z;
70 }
72 /** Thickness [m]: linear taper along the bow, smooth step up into the bell. */
73 thickness(x: number, y: number): number {
74 const p = this.profile;
75 const { s, rBell, rSh } = this.bowCoordinate(x, y);
76 return p.h_rim + (p.h_shoulder - p.h_rim) * s + (p.h_bell - p.h_shoulder) * this.bellBlend(rBell, rSh);
77 }
↑↓ move↵openescclose