@adatechnology/game-vfx-three 0.2.1 → 0.3.1

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/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/renderer.ts
2
+ import { markKitOrigin } from "@adatechnology/game-contracts";
2
3
  import { colorOfChunk, createVfx, hexToLinearRgb } from "@adatechnology/game-vfx";
3
4
 
4
5
  // src/shaders/glow.ts
@@ -531,6 +532,81 @@ function createMarkLayer(THREE, options) {
531
532
  };
532
533
  }
533
534
 
535
+ // src/mesh-layer.ts
536
+ var SPHERE_DEFAULT_WIDTH = 8;
537
+ var SPHERE_DEFAULT_HEIGHT = 6;
538
+ function geometryOf(THREE, particle) {
539
+ switch (particle.shape) {
540
+ case "cone":
541
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
542
+ case "cone_open":
543
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
544
+ case "sphere":
545
+ return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
546
+ case "icosahedron":
547
+ return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
548
+ }
549
+ }
550
+ function materialOf(THREE, particle) {
551
+ if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
552
+ return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
553
+ }
554
+ function createMeshLayerProbes(THREE) {
555
+ const geometry = new THREE.BoxGeometry(1e-3, 1e-3, 1e-3);
556
+ return [{ lit: true, additive: false }, { lit: false, additive: false }, { lit: false, additive: true }].map(
557
+ (kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
558
+ );
559
+ }
560
+ function createMeshLayer(THREE, pool) {
561
+ const group = new THREE.Group();
562
+ group.name = "workshop-meshes";
563
+ const drawn = /* @__PURE__ */ new Map();
564
+ const frame = new THREE.Quaternion();
565
+ const euler = new THREE.Euler();
566
+ const turn = new THREE.Quaternion();
567
+ const offset = new THREE.Vector3();
568
+ function release(mesh) {
569
+ mesh.removeFromParent();
570
+ mesh.geometry.dispose();
571
+ mesh.material.dispose();
572
+ }
573
+ return {
574
+ group,
575
+ sync() {
576
+ const live = /* @__PURE__ */ new Set();
577
+ for (const particle of pool.particles) {
578
+ live.add(particle.id);
579
+ let mesh = drawn.get(particle.id);
580
+ if (!mesh) {
581
+ mesh = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
582
+ mesh.frustumCulled = false;
583
+ mesh.renderOrder = particle.additive ? 5 : 3;
584
+ drawn.set(particle.id, mesh);
585
+ group.add(mesh);
586
+ }
587
+ frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
588
+ offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
589
+ mesh.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
590
+ turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
591
+ mesh.quaternion.copy(frame).multiply(turn);
592
+ mesh.scale.setScalar(particle.scale);
593
+ mesh.material.opacity = particle.opacity;
594
+ }
595
+ for (const [id, mesh] of drawn) {
596
+ if (live.has(id)) continue;
597
+ release(mesh);
598
+ drawn.delete(id);
599
+ }
600
+ return pool.count;
601
+ },
602
+ dispose() {
603
+ for (const mesh of drawn.values()) release(mesh);
604
+ drawn.clear();
605
+ group.removeFromParent();
606
+ }
607
+ };
608
+ }
609
+
534
610
  // src/screen-overlay.ts
535
611
  var clamp = (value, min, max) => value < min ? min : value > max ? max : value;
536
612
  function layer(container, name, blend) {
@@ -710,75 +786,6 @@ function createSpriteLayer(THREE, system, pointScale) {
710
786
  };
711
787
  }
712
788
 
713
- // src/mesh-layer.ts
714
- var SPHERE_DEFAULT_WIDTH = 8;
715
- var SPHERE_DEFAULT_HEIGHT = 6;
716
- function geometryOf(THREE, particle) {
717
- switch (particle.shape) {
718
- case "cone":
719
- return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
720
- case "cone_open":
721
- return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
722
- case "sphere":
723
- return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
724
- case "icosahedron":
725
- return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
726
- }
727
- }
728
- function materialOf(THREE, particle) {
729
- if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
730
- return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
731
- }
732
- function createMeshLayer(THREE, pool) {
733
- const group = new THREE.Group();
734
- group.name = "workshop-meshes";
735
- const drawn = /* @__PURE__ */ new Map();
736
- const frame = new THREE.Quaternion();
737
- const euler = new THREE.Euler();
738
- const turn = new THREE.Quaternion();
739
- const offset = new THREE.Vector3();
740
- function release(mesh) {
741
- mesh.removeFromParent();
742
- mesh.geometry.dispose();
743
- mesh.material.dispose();
744
- }
745
- return {
746
- group,
747
- sync() {
748
- const live = /* @__PURE__ */ new Set();
749
- for (const particle of pool.particles) {
750
- live.add(particle.id);
751
- let mesh = drawn.get(particle.id);
752
- if (!mesh) {
753
- mesh = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
754
- mesh.frustumCulled = false;
755
- mesh.renderOrder = particle.additive ? 5 : 3;
756
- drawn.set(particle.id, mesh);
757
- group.add(mesh);
758
- }
759
- frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
760
- offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
761
- mesh.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
762
- turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
763
- mesh.quaternion.copy(frame).multiply(turn);
764
- mesh.scale.setScalar(particle.scale);
765
- mesh.material.opacity = particle.opacity;
766
- }
767
- for (const [id, mesh] of drawn) {
768
- if (live.has(id)) continue;
769
- release(mesh);
770
- drawn.delete(id);
771
- }
772
- return pool.count;
773
- },
774
- dispose() {
775
- for (const mesh of drawn.values()) release(mesh);
776
- drawn.clear();
777
- group.removeFromParent();
778
- }
779
- };
780
- }
781
-
782
789
  // src/shaders/workshop-sprite.ts
783
790
  var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
784
791
  void main(){vC=pcol;vA=alpha;vec4 mv=modelViewMatrix*vec4(position,1.);gl_PointSize=clamp(size*uScale/-mv.z,1.,512.);gl_Position=projectionMatrix*mv;}`;
@@ -1149,7 +1156,14 @@ function createVfxRenderer(THREE, options) {
1149
1156
  const requested = options.vfx;
1150
1157
  const world = isWorld(requested) ? requested : createVfx({ profile: options.quality ?? "medium", reducedMotion, ...requested });
1151
1158
  const pools = world.pools();
1152
- const root = new THREE.Group();
1159
+ let anchors;
1160
+ const releaseAnchors = () => {
1161
+ if (!anchors) return;
1162
+ anchors.meshes[0]?.geometry.dispose();
1163
+ for (const mesh of anchors.meshes) mesh.material.dispose();
1164
+ anchors = void 0;
1165
+ };
1166
+ const root = markKitOrigin(new THREE.Group(), { owner: "game-vfx-three", kind: "effect" });
1153
1167
  options.scene.add(root);
1154
1168
  const direction = new THREE.Vector3(...options.lightDirection ?? LAB_LIGHT_DIRECTION).normalize();
1155
1169
  const sky = hexToLinearRgb(SKY_COLOR);
@@ -1265,6 +1279,16 @@ function createVfxRenderer(THREE, options) {
1265
1279
  syncAll();
1266
1280
  overlay?.update(screenNow());
1267
1281
  },
1282
+ warmUpProbes() {
1283
+ releaseAnchors();
1284
+ const meshes = createMeshLayerProbes(THREE);
1285
+ for (const mesh of meshes) mesh.frustumCulled = false;
1286
+ const group = new THREE.Group();
1287
+ group.add(...meshes);
1288
+ anchors = { meshes, group };
1289
+ return { group, dispose: () => {
1290
+ } };
1291
+ },
1268
1292
  applyCamera() {
1269
1293
  const shake = screenNow().shake;
1270
1294
  if (reducedMotion || !(shake > SHAKE_FLOOR)) return () => {
@@ -1283,6 +1307,7 @@ function createVfxRenderer(THREE, options) {
1283
1307
  },
1284
1308
  screen: screenNow,
1285
1309
  dispose() {
1310
+ releaseAnchors();
1286
1311
  stopMarks();
1287
1312
  overlay?.dispose();
1288
1313
  workshopLayers?.dispose();
@@ -1556,6 +1581,11 @@ function createVfxTestModule(options = {}) {
1556
1581
  controls,
1557
1582
  layers,
1558
1583
  metrics,
1584
+ // opening the effects area puts the camera at the bench view (the initial distance does not move it: that would take the player's view on load)
1585
+ area: () => ({
1586
+ placements: [],
1587
+ view: { eye: eyeFor(DEFAULT_DISTANCE), target: [0, 1.2, 0] }
1588
+ }),
1559
1589
  attach(host) {
1560
1590
  const bench = host.bench;
1561
1591
  let own;
@@ -1575,6 +1605,7 @@ function createVfxTestModule(options = {}) {
1575
1605
  let phase = 0;
1576
1606
  let handle = null;
1577
1607
  let sinceRepeat = 0;
1608
+ let live = false;
1578
1609
  read = () => {
1579
1610
  const def = explosions[effect];
1580
1611
  const recipe = recipes[effect];
@@ -1613,7 +1644,7 @@ function createVfxTestModule(options = {}) {
1613
1644
  }
1614
1645
  );
1615
1646
  bench?.sheet?.(sheetFor(id));
1616
- replay();
1647
+ if (live) replay();
1617
1648
  }
1618
1649
  function look() {
1619
1650
  const eye = eyeFor(distance);
@@ -1630,7 +1661,7 @@ function createVfxTestModule(options = {}) {
1630
1661
  choose(change.value);
1631
1662
  else if (change.id === "variant" && typeof change.value === "string") {
1632
1663
  variant = change.value;
1633
- if (recipes[effect]?.params) replay();
1664
+ if (live && recipes[effect]?.params) replay();
1634
1665
  } else if (change.id === "auto_repeat") auto = change.value === true;
1635
1666
  else if (change.id === "distance" && typeof change.value === "number") {
1636
1667
  distance = change.value;
@@ -1640,6 +1671,7 @@ function createVfxTestModule(options = {}) {
1640
1671
  };
1641
1672
  const offChange = bench?.onChange(apply);
1642
1673
  const offFrame = host.onFrame((dt) => {
1674
+ live = true;
1643
1675
  phase += dt;
1644
1676
  sinceRepeat += dt;
1645
1677
  own?.update(dt);