reze-engine 0.55.4 → 0.55.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.js CHANGED
@@ -593,6 +593,16 @@ function vertexMorphReach(model) {
593
593
  }
594
594
  /** Tried in order when a PMX names a texture without an extension. */
595
595
  const TEXTURE_EXTENSION_GUESSES = [".png", ".jpg", ".jpeg", ".bmp", ".tga", ".dds", ".spa", ".sph"];
596
+ /** One effect's GPU particle pool. Per effect: each declares its own count. */
597
+ /**
598
+ * The binding number an effect's params take in each pass.
599
+ *
600
+ * 7 in the composite, the particle stages and the ribbon pass, which all stop
601
+ * below it. The GRID reaches 8 on its own, so it takes the next one up rather
602
+ * than everything else moving to accommodate one mount.
603
+ */
604
+ const EFFECT_PARAMS_BINDING = 7;
605
+ const EFFECT_PARAMS_BINDING_GRID = 9;
596
606
  /**
597
607
  * How one field draw lands on the ones before it: OVER, into a premultiplied
598
608
  * target. Shared by both field attachments so they cannot drift apart, which
@@ -2214,6 +2224,9 @@ export class Engine {
2214
2224
  { binding: 6, resource: { buffer: this.compositeUniformBuffer } },
2215
2225
  { binding: 7, resource: { buffer: this.midiBuffer } },
2216
2226
  { binding: 8, resource: { buffer: this.lyricsBuffer } },
2227
+ // The grid is the one pass whose own bindings reach 8, so its params sit
2228
+ // above them rather than everything else shifting for one mount.
2229
+ ...(g.params ? [{ binding: EFFECT_PARAMS_BINDING_GRID, resource: { buffer: g.params } }] : []),
2217
2230
  ],
2218
2231
  });
2219
2232
  }
@@ -2251,7 +2264,7 @@ export class Engine {
2251
2264
  if (e.grid)
2252
2265
  e.grid.binds = [this.gridBindGroup(e.grid, 0), this.gridBindGroup(e.grid, 1)];
2253
2266
  if (e.lights)
2254
- e.lights.bind = this.lightEmitBindGroup(e.lights.layout, e.lights.uniform);
2267
+ e.lights.bind = this.lightEmitBindGroup(e.lights.layout, e.lights.uniform, e.lights.params);
2255
2268
  }
2256
2269
  }
2257
2270
  /**
@@ -2545,9 +2558,18 @@ export class Engine {
2545
2558
  paramsData[slot.offset + 2] = value.z;
2546
2559
  }
2547
2560
  }
2548
- const paramsDecl = entries.length
2549
- ? `struct EffectParams {\n${fields.join("\n")}\n}\n@group(0) @binding(7) var<uniform> params: EffectParams;\n`
2561
+ /**
2562
+ * The params block, at whatever binding the pass asking has free.
2563
+ *
2564
+ * A single hardcoded slot cannot serve every mount — the grid's layout
2565
+ * already reaches binding 8 — so the struct is generated per pass and each
2566
+ * one states the number it can spare. Empty when nothing is declared: WGSL
2567
+ * has no empty struct, and a binding nothing reads is a layout mismatch.
2568
+ */
2569
+ const paramsWgsl = (binding) => entries.length
2570
+ ? `struct EffectParams {\n${fields.join("\n")}\n}\n@group(0) @binding(${binding}) var<uniform> params: EffectParams;\n`
2550
2571
  : "";
2572
+ const paramsDecl = paramsWgsl(EFFECT_PARAMS_BINDING);
2551
2573
  // ── Compile with validation captured, not thrown at the console. Line
2552
2574
  // numbers in diagnostics are rebased to the USER's source.
2553
2575
  // The composite is STATIC: user field code compiles in its own half-res
@@ -2654,7 +2676,31 @@ export class Engine {
2654
2676
  let particles = null;
2655
2677
  let grid = null;
2656
2678
  let trails = null;
2679
+ // PARAMS ARE NOT A FIELD-MOUNT FEATURE, and used to be one by accident.
2680
+ //
2681
+ // `paramsDecl` was spliced into the composite alone, so `#param` worked for
2682
+ // background/foreground and produced "unresolved value 'params'" for every
2683
+ // particle, grid and ribbon effect — which is most of the ones anyone wants
2684
+ // a dial on. Rain's fall speed is the whole example.
2685
+ //
2686
+ // The buffer is created HERE rather than beside the instance it ends up on,
2687
+ // because the mount builders below need to bind it and they run first. That
2688
+ // makes `abandon` its owner: a mount that fails to compile must not leak it.
2689
+ let paramsBuffer = null;
2690
+ if (entries.length) {
2691
+ paramsBuffer = this.device.createBuffer({
2692
+ label: "effect params",
2693
+ size: paramsData.byteLength,
2694
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
2695
+ });
2696
+ this.device.queue.writeBuffer(paramsBuffer, 0, paramsData);
2697
+ }
2698
+ /** What every mount splices and binds: a generator for the struct decl at
2699
+ * the binding that mount has free, and the buffer behind it. The buffer is
2700
+ * null when nothing is declared, and then no binding is added at all. */
2701
+ const paramsFor = { wgsl: paramsWgsl, buffer: paramsBuffer };
2657
2702
  const abandon = (diagnostics) => {
2703
+ paramsBuffer?.destroy();
2658
2704
  particles?.buffer.destroy();
2659
2705
  particles?.uniform.destroy();
2660
2706
  grid?.textures[0].destroy();
@@ -2664,13 +2710,13 @@ export class Engine {
2664
2710
  return { ok: false, diagnostics, mounts, params: d.params, duration: d.duration };
2665
2711
  };
2666
2712
  if (wantsParticles) {
2667
- const built = await this.buildParticles(wgsl, d, anchors, alias);
2713
+ const built = await this.buildParticles(wgsl, d, anchors, alias, paramsFor);
2668
2714
  if (!built.ok)
2669
2715
  return abandon(built.diagnostics);
2670
2716
  particles = built.state;
2671
2717
  }
2672
2718
  if (gridEntryPoint(wgsl)) {
2673
- const built = await this.buildSim(wgsl, d, anchors, alias);
2719
+ const built = await this.buildSim(wgsl, d, anchors, alias, paramsFor);
2674
2720
  if (!built.ok)
2675
2721
  return abandon(built.diagnostics);
2676
2722
  grid = built.state;
@@ -2680,9 +2726,9 @@ export class Engine {
2680
2726
  // bone recorded without one would read zeroes and paint a line to the origin.
2681
2727
  const trailSlots = anchors.filter((a) => a.trail).length;
2682
2728
  if (trailSlots === 0) {
2683
- return { ok: false, diagnostics: ["a ribbon effect needs at least one #anchor <bone> trail"], mounts, params: d.params, duration: d.duration };
2729
+ return abandon(["a ribbon effect needs at least one #anchor <bone> trail"]);
2684
2730
  }
2685
- const built = await this.buildTrails(wgsl, d, anchors, alias);
2731
+ const built = await this.buildTrails(wgsl, d, anchors, alias, paramsFor);
2686
2732
  if (!built.ok)
2687
2733
  return abandon(built.diagnostics);
2688
2734
  trails = built.state;
@@ -2705,7 +2751,7 @@ export class Engine {
2705
2751
  ]);
2706
2752
  }
2707
2753
  if (declaredLights > 0) {
2708
- const built = await this.buildLightEmit(wgsl, declaredLights, alias, anchors);
2754
+ const built = await this.buildLightEmit(wgsl, declaredLights, alias, anchors, paramsFor);
2709
2755
  if (!built.ok)
2710
2756
  return abandon(built.diagnostics);
2711
2757
  lights = built.state;
@@ -2718,15 +2764,6 @@ export class Engine {
2718
2764
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
2719
2765
  })
2720
2766
  : null;
2721
- let paramsBuffer = null;
2722
- if (entries.length) {
2723
- paramsBuffer = this.device.createBuffer({
2724
- label: "effect params",
2725
- size: paramsData.byteLength,
2726
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
2727
- });
2728
- this.device.queue.writeBuffer(paramsBuffer, 0, paramsData);
2729
- }
2730
2767
  const instance = {
2731
2768
  wgsl,
2732
2769
  paramDecls: d.params,
@@ -2955,11 +2992,14 @@ export class Engine {
2955
2992
  /** This effect's local→scene slot map. Passed rather than read off the
2956
2993
  * engine: the builders run BEFORE the swap, so this.anchorTable still
2957
2994
  * describes the effect that is still on screen. */
2958
- alias) {
2995
+ alias,
2996
+ /** The effect's declared dials. Spliced into both stages and bound at 7,
2997
+ * the same binding the composite gives them. */
2998
+ params) {
2959
2999
  // No pragma means "some": an author who wrote the trio clearly wants
2960
3000
  // particles, and failing over a missing comment would be pedantry.
2961
3001
  const count = Math.min(d.particles || 1024, Engine.MAX_PARTICLES);
2962
- const src = { wgsl, count, blend: d.particleBlend, bloom: d.bloom };
3002
+ const src = { wgsl, count, blend: d.particleBlend, bloom: d.bloom, paramsDecl: params.wgsl(EFFECT_PARAMS_BINDING) };
2963
3003
  // Sparks want to spawn where a trail is, so the particle stages see the same
2964
3004
  // cast buffer the trail draw reads.
2965
3005
  const cast = {
@@ -3022,6 +3062,9 @@ export class Engine {
3022
3062
  // rule and a background read the same instant.
3023
3063
  { binding: 5, visibility, buffer: { type: "read-only-storage" } },
3024
3064
  { binding: 6, visibility, buffer: { type: "read-only-storage" } },
3065
+ // Only when the effect declared any: WGSL has no empty struct, so a
3066
+ // param-less effect must not carry the decl OR the binding.
3067
+ ...(params.buffer ? [{ binding: 7, visibility, buffer: { type: "uniform" } }] : []),
3025
3068
  ],
3026
3069
  });
3027
3070
  const bindFor = (layout, camera) => this.device.createBindGroup({
@@ -3034,6 +3077,7 @@ export class Engine {
3034
3077
  { binding: 4, resource: { buffer: this.audioBuffer } },
3035
3078
  { binding: 5, resource: { buffer: this.midiBuffer } },
3036
3079
  { binding: 6, resource: { buffer: this.lyricsBuffer } },
3080
+ ...(params.buffer ? [{ binding: 7, resource: { buffer: params.buffer } }] : []),
3037
3081
  ],
3038
3082
  });
3039
3083
  const computeLayout = layoutFor("storage", GPUShaderStage.COMPUTE);
@@ -3115,7 +3159,7 @@ export class Engine {
3115
3159
  * writable view here means a material pipeline cannot accidentally acquire
3116
3160
  * one.
3117
3161
  */
3118
- async buildLightEmit(wgsl, count, alias, anchors) {
3162
+ async buildLightEmit(wgsl, count, alias, anchors, params) {
3119
3163
  const layout = this.device.createBindGroupLayout({
3120
3164
  label: "light emit layout",
3121
3165
  entries: [
@@ -3126,13 +3170,16 @@ export class Engine {
3126
3170
  { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
3127
3171
  { binding: 5, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
3128
3172
  { binding: 6, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
3173
+ ...(params.buffer
3174
+ ? [{ binding: EFFECT_PARAMS_BINDING, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } }]
3175
+ : []),
3129
3176
  ],
3130
3177
  });
3131
3178
  const module = this.device.createShaderModule({
3132
3179
  label: "light emit",
3133
3180
  // The same scene API and the same per-effect anchor alias its drawing
3134
3181
  // half gets, so a lamp reads the cast exactly as the beam that paints it.
3135
- code: buildLightEmitShader(wgsl, EFFECT_SCENE_API + anchorAliasWgsl(alias), { trailCount: anchors.filter((a) => a.trail).length }),
3182
+ code: buildLightEmitShader(wgsl, EFFECT_SCENE_API + anchorAliasWgsl(alias), { trailCount: anchors.filter((a) => a.trail).length }, params.wgsl(EFFECT_PARAMS_BINDING)),
3136
3183
  });
3137
3184
  const info = await module.getCompilationInfo();
3138
3185
  const diagnostics = info.messages.filter((m) => m.type === "error").map((m) => `${m.lineNum}:${m.linePos} ${m.message}`);
@@ -3155,14 +3202,14 @@ export class Engine {
3155
3202
  uniform.destroy();
3156
3203
  return { ok: false, diagnostics: [scoped.message] };
3157
3204
  }
3158
- const bind = this.lightEmitBindGroup(layout, uniform);
3205
+ const bind = this.lightEmitBindGroup(layout, uniform, params.buffer);
3159
3206
  // The layout travels with the state so the emitter can be rebound when a
3160
3207
  // shared buffer under it is replaced — see rebindSharedBuffers.
3161
- return { ok: true, state: { pipeline, bind, layout, uniform, data, count } };
3208
+ return { ok: true, state: { pipeline, bind, layout, uniform, data, count, params: params.buffer } };
3162
3209
  }
3163
3210
  /** The light emitter's bind group. One author, for the reason gridBindGroup
3164
3211
  * gives: it is built once and rebuilt on every shared-buffer swap. */
3165
- lightEmitBindGroup(layout, uniform) {
3212
+ lightEmitBindGroup(layout, uniform, params) {
3166
3213
  return this.device.createBindGroup({
3167
3214
  label: "light emit bind",
3168
3215
  layout,
@@ -3174,6 +3221,7 @@ export class Engine {
3174
3221
  { binding: 4, resource: { buffer: this.audioBuffer } },
3175
3222
  { binding: 5, resource: { buffer: this.midiBuffer } },
3176
3223
  { binding: 6, resource: { buffer: this.lyricsBuffer } },
3224
+ ...(params ? [{ binding: EFFECT_PARAMS_BINDING, resource: { buffer: params } }] : []),
3177
3225
  ],
3178
3226
  });
3179
3227
  }
@@ -3281,7 +3329,9 @@ export class Engine {
3281
3329
  /** This effect's local→scene slot map. Passed rather than read off the
3282
3330
  * engine: the builders run BEFORE the swap, so this.anchorTable still
3283
3331
  * describes the effect that is still on screen. */
3284
- alias) {
3332
+ alias,
3333
+ /** The effect's declared dials, spliced and bound at 7 as everywhere else. */
3334
+ params) {
3285
3335
  // `slots` here is how many RIBBONS to draw — one per trailed anchor — which
3286
3336
  // is a different number from the anchor ADDRESS SPACE the accessors index
3287
3337
  // by. Conflating the two is what made a trail declared after a bare anchor
@@ -3291,7 +3341,7 @@ export class Engine {
3291
3341
  // nothing before: ribbon i was read as anchor slot i.
3292
3342
  const ribbonSlots = anchors.map((a, i) => (a.trail ? i : -1)).filter((i) => i >= 0);
3293
3343
  const slots = ribbonSlots.length;
3294
- const src = { wgsl, slots, ribbonSlots, blend: d.particleBlend, bloom: d.bloom };
3344
+ const src = { wgsl, slots, ribbonSlots, blend: d.particleBlend, bloom: d.bloom, paramsDecl: params.wgsl(EFFECT_PARAMS_BINDING) };
3295
3345
  const code = buildTrailShader(src, {
3296
3346
  subjects: MAX_EFFECT_SUBJECTS,
3297
3347
  samples: TRAIL_SAMPLES,
@@ -3336,6 +3386,9 @@ export class Engine {
3336
3386
  { binding: 5, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "read-only-storage" } },
3337
3387
  // The lyrics, for rzLyric*.
3338
3388
  { binding: 6, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "read-only-storage" } },
3389
+ ...(params.buffer
3390
+ ? [{ binding: 7, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }]
3391
+ : []),
3339
3392
  ],
3340
3393
  });
3341
3394
  // TWO targets, the scene pass's own: HDR colour and the aux (bloom mask,
@@ -3388,6 +3441,7 @@ export class Engine {
3388
3441
  { binding: 4, resource: { buffer: this.audioBuffer } },
3389
3442
  { binding: 5, resource: { buffer: this.midiBuffer } },
3390
3443
  { binding: 6, resource: { buffer: this.lyricsBuffer } },
3444
+ ...(params.buffer ? [{ binding: 7, resource: { buffer: params.buffer } }] : []),
3391
3445
  ],
3392
3446
  }),
3393
3447
  mirrorBind: this.device.createBindGroup({
@@ -3399,6 +3453,7 @@ export class Engine {
3399
3453
  { binding: 4, resource: { buffer: this.audioBuffer } },
3400
3454
  { binding: 5, resource: { buffer: this.midiBuffer } },
3401
3455
  { binding: 6, resource: { buffer: this.lyricsBuffer } },
3456
+ ...(params.buffer ? [{ binding: 7, resource: { buffer: params.buffer } }] : []),
3402
3457
  ],
3403
3458
  }),
3404
3459
  },
@@ -3595,7 +3650,9 @@ export class Engine {
3595
3650
  /** This effect's local→scene slot map. Passed rather than read off the
3596
3651
  * engine: the builders run BEFORE the swap, so this.anchorTable still
3597
3652
  * describes the effect that is still on screen. */
3598
- alias) {
3653
+ alias,
3654
+ /** The effect's declared dials, spliced and bound above the grid's own. */
3655
+ params) {
3599
3656
  const size = Math.min(d.grid || 256, GRID_MAX);
3600
3657
  const cast = {
3601
3658
  subjects: MAX_EFFECT_SUBJECTS,
@@ -3606,7 +3663,7 @@ export class Engine {
3606
3663
  trailCount: anchors.filter((x) => x.trail).length,
3607
3664
  alias,
3608
3665
  };
3609
- const code = buildSimShader(wgsl, size, cast);
3666
+ const code = buildSimShader(wgsl, size, cast, params.wgsl(EFFECT_PARAMS_BINDING_GRID));
3610
3667
  const offset = code.slice(0, code.indexOf(wgsl)).split("\n").length - 1;
3611
3668
  this.device.pushErrorScope("validation");
3612
3669
  const module = this.device.createShaderModule({ label: "grid step", code });
@@ -3635,6 +3692,9 @@ export class Engine {
3635
3692
  { binding: 6, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } },
3636
3693
  { binding: 7, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
3637
3694
  { binding: 8, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
3695
+ ...(params.buffer
3696
+ ? [{ binding: EFFECT_PARAMS_BINDING_GRID, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } }]
3697
+ : []),
3638
3698
  ],
3639
3699
  });
3640
3700
  const make = (n) => this.device.createTexture({
@@ -3665,7 +3725,7 @@ export class Engine {
3665
3725
  return { ok: false, diagnostics: [scoped.message] };
3666
3726
  }
3667
3727
  // One per parity: binds[i] READS textures[i] and WRITES the other.
3668
- const bindFor = (i) => this.gridBindGroup({ layout, uniform, read, textures }, i);
3728
+ const bindFor = (i) => this.gridBindGroup({ layout, uniform, read, textures, params: params.buffer }, i);
3669
3729
  return {
3670
3730
  ok: true,
3671
3731
  state: {
@@ -3679,6 +3739,7 @@ export class Engine {
3679
3739
  data: new Float32Array(4),
3680
3740
  parity: 0,
3681
3741
  frame: 0,
3742
+ params: params.buffer,
3682
3743
  },
3683
3744
  };
3684
3745
  }
@@ -6339,17 +6400,23 @@ export class Engine {
6339
6400
  this.camera.attachControl(this.canvas);
6340
6401
  }
6341
6402
  setCameraTarget(modelOrVec, boneName, offset) {
6403
+ // panLocked tracks the BINDING, on every path that changes it — a target
6404
+ // set to a fixed point is pannable again, and forgetting it here is how the
6405
+ // pan stays dead after the follow was turned off. See Camera.panLocked.
6342
6406
  if (modelOrVec === null) {
6407
+ this.camera.panLocked = false;
6343
6408
  this.cameraTargetModel = null;
6344
6409
  return;
6345
6410
  }
6346
6411
  if ("x" in modelOrVec && "y" in modelOrVec && "z" in modelOrVec) {
6412
+ this.camera.panLocked = false;
6347
6413
  this.cameraTargetModel = null;
6348
6414
  this.camera.target.x = modelOrVec.x;
6349
6415
  this.camera.target.y = modelOrVec.y;
6350
6416
  this.camera.target.z = modelOrVec.z;
6351
6417
  return;
6352
6418
  }
6419
+ this.camera.panLocked = true;
6353
6420
  this.cameraTargetModel = modelOrVec;
6354
6421
  this.cameraTargetBoneName = boneName ?? "";
6355
6422
  this.cameraTargetOffset.x = offset?.x ?? 0;
@@ -6358,6 +6425,9 @@ export class Engine {
6358
6425
  }
6359
6426
  /** Souls-style follow cam: orbit center tracks a model bone each frame. Shorthand for setCameraTarget(model, boneName, offset). */
6360
6427
  setCameraFollow(model, boneName, offset, smoothing) {
6428
+ // Panning sets the very thing the follow overwrites each frame, so it is
6429
+ // refused for as long as the shot is riding a bone. See Camera.panLocked.
6430
+ this.camera.panLocked = model !== null;
6361
6431
  if (model === null) {
6362
6432
  this.cameraTargetModel = null;
6363
6433
  return;
@@ -7958,6 +8028,52 @@ export class Engine {
7958
8028
  else
7959
8029
  inst.hiddenMaterials.add(materialName);
7960
8030
  }
8031
+ /**
8032
+ * Push a colour/shading edit straight into a material's own uniform buffer —
8033
+ * the same block createMaterialUniformBuffer wrote at load, offset for
8034
+ * offset. A single write per call, on the fields that actually moved, so
8035
+ * dragging one slider does not touch the other eleven.
8036
+ *
8037
+ * UNGROUPED materials only. A grouped material renders through its style
8038
+ * group's own compiled graph (setupPipelines' neutral/DEFAULT_GRAPH path is
8039
+ * what reads this buffer, and a grouped material never runs it) — the call
8040
+ * still writes the bytes, they are simply never sampled, which would look
8041
+ * like the edit silently failing. Callers check groupsByModel first and this
8042
+ * quietly no-ops rather than assume that check was made, since a document
8043
+ * edit landing to the WRONG channel (the group's own colour input) would be
8044
+ * a worse failure than one that does nothing.
8045
+ *
8046
+ * Structural fields — anything that changes which draw bucket a material is
8047
+ * in, or which texture it binds — are NOT here: alpha crossing the 1.0
8048
+ * opaque/transparent line, edge on/off, a texture swap, all need the draw
8049
+ * list or the bind group rebuilt, not a uniform write. Those get their own
8050
+ * call when something needs them.
8051
+ */
8052
+ setMaterialUniforms(modelName, materialName, patch) {
8053
+ const inst = this.modelInstances.get(modelName);
8054
+ if (!inst)
8055
+ return false;
8056
+ const materials = inst.model.getMaterials();
8057
+ const index = materials.findIndex((m) => m.name === materialName);
8058
+ if (index < 0)
8059
+ return false;
8060
+ const buffer = inst.materialUniformBuffers[index];
8061
+ if (!buffer)
8062
+ return false;
8063
+ if (patch.diffuse) {
8064
+ this.device.queue.writeBuffer(buffer, 0, new Float32Array(patch.diffuse));
8065
+ }
8066
+ if (patch.ambient) {
8067
+ this.device.queue.writeBuffer(buffer, 16, new Float32Array(patch.ambient));
8068
+ }
8069
+ if (patch.specularPower !== undefined) {
8070
+ this.device.queue.writeBuffer(buffer, 28, new Float32Array([patch.specularPower]));
8071
+ }
8072
+ if (patch.specular) {
8073
+ this.device.queue.writeBuffer(buffer, 32, new Float32Array(patch.specular));
8074
+ }
8075
+ return true;
8076
+ }
7961
8077
  isMaterialVisible(modelName, materialName) {
7962
8078
  const inst = this.modelInstances.get(modelName);
7963
8079
  return inst ? !inst.hiddenMaterials.has(materialName) : false;
package/dist/index.d.ts CHANGED
@@ -36,4 +36,17 @@ export { RezePhysics } from "./physics";
36
36
  export type { WindOptions } from "./physics/world";
37
37
  export { RigidbodyShape, RigidbodyType, type Rigidbody, type Joint } from "./physics/types";
38
38
  export { boneOverlay, boneMarkerPositions, rigidbodyOverlay, jointOverlay, buildOverlayShapes, lineBetween, writeOverlayInstance, OVERLAY_SHAPES, OVERLAY_INSTANCE_FLOATS, OVERLAY_VERTEX_FLOATS, DEFAULT_BONE_PALETTE, DEFAULT_RIGIDBODY_PALETTE, DEFAULT_JOINT_PALETTE, DEFAULT_VERTEX_COLOR, OVERLAY_STYLE, type OverlayPrimitive, type OverlayShape, type OverlayGeometry, type BoneOverlayOptions, type BoneClass, type RigidbodyOverlayOptions, type RigidbodyClass, type JointOverlayOptions, type RGBA, type Vec3Tuple, } from "./overlay";
39
+ /**
40
+ * The WGSL an effect's own source is compiled against — the hashes, the noise,
41
+ * the falloff, and the Particle struct the particle trio is written in terms of.
42
+ *
43
+ * Exported because reze-design renders LIBRARY PREVIEWS by hosting an effect's
44
+ * source itself, and a preview that reimplements this drifts from the renderer
45
+ * it is previewing. `rzHash13` meaning one thing on a card and another in the
46
+ * scene is worse than no card at all — and a preview missing them entirely just
47
+ * fails to compile, which is a blank card with nothing said anywhere.
48
+ *
49
+ * Pure strings, no GPU: importing them costs a host nothing.
50
+ */
51
+ export { EFFECT_MATH_API, PARTICLE_STRUCT_WGSL } from "./shaders/passes/hosted-api";
39
52
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -50,3 +50,16 @@ export { RigidbodyShape, RigidbodyType } from "./physics/types";
50
50
  // engine methods that call them, so a host can produce the same list itself —
51
51
  // to show it as data, diff two poses, or hit-test it on the CPU.
52
52
  export { boneOverlay, boneMarkerPositions, rigidbodyOverlay, jointOverlay, buildOverlayShapes, lineBetween, writeOverlayInstance, OVERLAY_SHAPES, OVERLAY_INSTANCE_FLOATS, OVERLAY_VERTEX_FLOATS, DEFAULT_BONE_PALETTE, DEFAULT_RIGIDBODY_PALETTE, DEFAULT_JOINT_PALETTE, DEFAULT_VERTEX_COLOR, OVERLAY_STYLE, } from "./overlay";
53
+ /**
54
+ * The WGSL an effect's own source is compiled against — the hashes, the noise,
55
+ * the falloff, and the Particle struct the particle trio is written in terms of.
56
+ *
57
+ * Exported because reze-design renders LIBRARY PREVIEWS by hosting an effect's
58
+ * source itself, and a preview that reimplements this drifts from the renderer
59
+ * it is previewing. `rzHash13` meaning one thing on a card and another in the
60
+ * scene is worse than no card at all — and a preview missing them entirely just
61
+ * fails to compile, which is a blank card with nothing said anywhere.
62
+ *
63
+ * Pure strings, no GPU: importing them costs a host nothing.
64
+ */
65
+ export { EFFECT_MATH_API, PARTICLE_STRUCT_WGSL } from "./shaders/passes/hosted-api";
@@ -63,7 +63,7 @@ export declare function hasLightEmit(wgsl: string): boolean;
63
63
  */
64
64
  export declare function buildLightEmitShader(wgsl: string, sceneApi: string, cast: {
65
65
  trailCount: number;
66
- }): string;
66
+ }, paramsDecl?: string): string;
67
67
  /** The rz*Light accessors, with the buffer declared at the given binding. */
68
68
  export declare function lightsApi(group: number, binding: number): string;
69
69
  //# sourceMappingURL=lights.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../src/shaders/lights.ts"],"names":[],"mappings":"AAmCA;yEACyE;AACzE,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,+CAA+C;AAC/C,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B;;;;;GAKG;AACH,eAAO,MAAM,UAAU,KAAK,CAAA;AAC5B,kCAAkC;AAClC,eAAO,MAAM,aAAa,QAA2C,CAAA;AAErE;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,qJAQhC,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,idAa9B,CAAA;AAED,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3B,MAAM,CAgFR;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAkEhE"}
1
+ {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../src/shaders/lights.ts"],"names":[],"mappings":"AAmCA;yEACyE;AACzE,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,+CAA+C;AAC/C,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B;;;;;GAKG;AACH,eAAO,MAAM,UAAU,KAAK,CAAA;AAC5B,kCAAkC;AAClC,eAAO,MAAM,aAAa,QAA2C,CAAA;AAErE;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,qJAQhC,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,idAa9B,CAAA;AAED,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAC5B,UAAU,SAAK,GACd,MAAM,CAoFR;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAkEhE"}
@@ -117,7 +117,7 @@ export function hasLightEmit(wgsl) {
117
117
  * did not know where she was could only sit where the fixture hangs. It also
118
118
  * brings RzLight, which is why this builder does not declare it again.
119
119
  */
120
- export function buildLightEmitShader(wgsl, sceneApi, cast) {
120
+ export function buildLightEmitShader(wgsl, sceneApi, cast, paramsDecl = "") {
121
121
  return /* wgsl */ `
122
122
  // read_write HERE and read-only in the material shaders. Different passes, so
123
123
  // the two never coexist: this compute runs before the scene pass that reads it.
@@ -154,6 +154,10 @@ ${trailSlotsApi(cast.trailCount)}
154
154
  // The id accessors, stubbed: this module cannot read an attachment the
155
155
  // scene pass writes. See id-api.ts — the author's whole file compiles here.
156
156
  ${idApi(false, 0, 0) + castDistanceStub()}
157
+ // The dials the author declared, if any. A lamp is exactly the thing someone
158
+ // retunes — its colour and its reach — so an emitter reads params like every
159
+ // other mount rather than being the one place a #param resolves to nothing.
160
+ ${paramsDecl}
157
161
  ${wgsl}
158
162
 
159
163
  @compute @workgroup_size(64)
@@ -25,5 +25,5 @@ export declare function gridReadApi(group: number, tex: number, samp: number, si
25
25
  * from", and that place is almost never a texel centre. Point-sampling it is
26
26
  * what turns smoke into a staircase.
27
27
  */
28
- export declare function buildSimShader(wgsl: string, size: number, cast: CastLayout): string;
28
+ export declare function buildSimShader(wgsl: string, size: number, cast: CastLayout, paramsDecl?: string): string;
29
29
  //# sourceMappingURL=grid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/grid.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAkC7C,eAAO,MAAM,QAAQ,OAAO,CAAA;AAC5B,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,gBAAgC,CAAA;AAEzD,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA6B1F;AAWD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA8EnF"}
1
+ {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/grid.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAkC7C,eAAO,MAAM,QAAQ,OAAO,CAAA;AAC5B,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,gBAAgC,CAAA;AAEzD,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA6B1F;AAWD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,SAAK,GAAG,MAAM,CAiFpG"}
@@ -100,8 +100,11 @@ struct SimU {
100
100
  * from", and that place is almost never a texel centre. Point-sampling it is
101
101
  * what turns smoke into a staircase.
102
102
  */
103
- export function buildSimShader(wgsl, size, cast) {
103
+ export function buildSimShader(wgsl, size, cast, paramsDecl = "") {
104
104
  return (SIM_UNIFORMS +
105
+ // The effect's declared dials, ahead of everything that reads them. Empty
106
+ // when it declared none.
107
+ paramsDecl +
105
108
  /* wgsl */ `
106
109
  @group(0) @binding(0) var<uniform> su: SimU;
107
110
  @group(0) @binding(1) var _rzGridPrevTex: texture_2d<f32>;
@@ -23,6 +23,10 @@ type ParticleSource = {
23
23
  blend: ParticleBlend;
24
24
  /** Feed the bloom pyramid. Off by default — rain should not glow. */
25
25
  bloom: boolean;
26
+ /** The effect's `#param` block, or empty. Spliced into BOTH stages: a spawn
27
+ * rule reads the same dial the shading does, and rain's fall speed is used
28
+ * in the compute half. */
29
+ paramsDecl: string;
26
30
  };
27
31
  /** Bytes per particle. Explicitly padded — see the struct below. */
28
32
  export declare const PARTICLE_STRIDE = 48;
@@ -1 +1 @@
1
- {"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/particles.ts"],"names":[],"mappings":"AA2BA,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb;;mFAE+E;IAC/E,UAAU,EAAE,MAAM,CAAA;IAClB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAiCD,qDAAqD;AACrD,KAAK,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEzC,KAAK,cAAc,GAAG;IACpB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,aAAa,CAAA;IACpB,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,oEAAoE;AACpE,eAAO,MAAM,eAAe,KAAK,CAAA;AAuDjC,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAMlG;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA6CxF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA4HvF"}
1
+ {"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/particles.ts"],"names":[],"mappings":"AA2BA,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb;;mFAE+E;IAC/E,UAAU,EAAE,MAAM,CAAA;IAClB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAiCD,qDAAqD;AACrD,KAAK,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEzC,KAAK,cAAc,GAAG;IACpB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,aAAa,CAAA;IACpB,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAA;IACd;;+BAE2B;IAC3B,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,oEAAoE;AACpE,eAAO,MAAM,eAAe,KAAK,CAAA;AAuDjC,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAMlG;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA8CxF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA6HvF"}
@@ -110,6 +110,7 @@ export function buildParticleComputeShader(src, cast) {
110
110
  CAMERA_STRUCT +
111
111
  PARTICLE_UNIFORMS +
112
112
  `
113
+ ${src.paramsDecl}
113
114
  @group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
114
115
  @group(0) @binding(1) var<uniform> pu: ParticleU;
115
116
  @group(0) @binding(2) var<uniform> cam: CameraU;
@@ -169,6 +170,7 @@ override ADDITIVE: bool = ${src.blend === "additive" ? "true" : "false"};\n` +
169
170
  CAMERA_STRUCT +
170
171
  PARTICLE_UNIFORMS +
171
172
  `
173
+ ${src.paramsDecl}
172
174
  @group(0) @binding(0) var<storage, read> particles: array<Particle>;
173
175
  @group(0) @binding(1) var<uniform> pu: ParticleU;
174
176
  @group(0) @binding(2) var<uniform> cam: CameraU;
@@ -6,6 +6,8 @@ type TrailSource = {
6
6
  /** For each ribbon, the LOCAL anchor slot it belongs to. Identity when every
7
7
  * anchor is trailed; otherwise it skips the untrailed ones. */
8
8
  ribbonSlots: number[];
9
+ /** The effect's `#param` block, or empty. */
10
+ paramsDecl: string;
9
11
  /** Additive, like most glowing ribbons, or straight alpha. */
10
12
  blend: "alpha" | "additive";
11
13
  bloom: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"AA2BA,KAAK,WAAW,GAAG;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb;oEACgE;IAChE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAKjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;kFAI8E;IAC9E,SAAS,EAAE,OAAO,CAAA;CACnB,GACA,MAAM,CAgVR"}
1
+ {"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"AA2BA,KAAK,WAAW,GAAG;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb;oEACgE;IAChE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAKjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;kFAI8E;IAC9E,SAAS,EAAE,OAAO,CAAA;CACnB,GACA,MAAM,CAiVR"}
@@ -89,6 +89,7 @@ struct TrailU {
89
89
  weight: f32,
90
90
  _pad2: f32,
91
91
  }
92
+ ${src.paramsDecl}
92
93
  @group(0) @binding(0) var<storage, read> _rzCast: array<vec4f>;
93
94
  @group(0) @binding(1) var<uniform> tu: TrailU;
94
95
  @group(0) @binding(2) var<uniform> cam: CameraU;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.55.4",
3
+ "version": "0.55.7",
4
4
  "description": "A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/camera.ts CHANGED
@@ -44,6 +44,16 @@ export class Camera {
44
44
  // Input state
45
45
  private canvas: HTMLCanvasElement | null = null
46
46
  private inputLocked: boolean = false
47
+ /**
48
+ * Panning is refused while the orbit rides a bone (Engine.setCameraFollow).
49
+ *
50
+ * A pan moves `target`, and the follow rewrites `target` from the bone every
51
+ * frame — so the drag was already going nowhere, it just spent a frame
52
+ * fighting for it and read as the camera stuttering under the pointer.
53
+ * Refusing it says the same thing the follow already means: while the shot is
54
+ * on somebody, where it points is theirs to decide.
55
+ */
56
+ panLocked: boolean = false
47
57
  private isDragging: boolean = false
48
58
  private mouseButton: number | null = null // Track which mouse button is pressed (0 = left, 2 = right)
49
59
  private lastMousePos = { x: 0, y: 0 }
@@ -430,7 +440,7 @@ export class Camera {
430
440
 
431
441
  if (this.mouseButton === 2) {
432
442
  // Right-click: pan the camera target
433
- this.panCamera(deltaX, deltaY)
443
+ if (!this.panLocked) this.panCamera(deltaX, deltaY)
434
444
  } else {
435
445
  // Left-click (or default): rotate the camera
436
446
  this.alpha += deltaX * this.angularSensitivity
@@ -549,7 +559,7 @@ export class Camera {
549
559
  if (isPanGesture) {
550
560
  // Primary gesture is pan (two-finger drag)
551
561
  // Use panning similar to right-click pan
552
- this.panCamera(midpointDeltaX, midpointDeltaY)
562
+ if (!this.panLocked) this.panCamera(midpointDeltaX, midpointDeltaY)
553
563
  }
554
564
 
555
565
  // Update tracking values