@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.d.ts +3 -1
- package/dist/index.global.js +201 -126
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +104 -72
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
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);
|