@plasius/gpu-shared 0.1.4 → 0.1.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/CHANGELOG.md +34 -0
- package/dist/{chunk-S5NCFNKJ.js → chunk-OTCJ3VOK.js} +23 -2
- package/dist/{chunk-S5NCFNKJ.js.map → chunk-OTCJ3VOK.js.map} +1 -1
- package/dist/{chunk-UUJLYYQS.js → chunk-QBMXJ3V2.js} +42 -15
- package/dist/chunk-QBMXJ3V2.js.map +1 -0
- package/dist/{gltf-loader-4FNTT63R.js → gltf-loader-LKALCZAV.js} +2 -2
- package/dist/index.cjs +587 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{showcase-runtime-4BS7TWHS.js → showcase-runtime-AIPDRK7G.js} +535 -165
- package/dist/showcase-runtime-AIPDRK7G.js.map +1 -0
- package/package.json +3 -3
- package/src/asset-url.js +28 -1
- package/src/gltf-loader.js +36 -1
- package/src/index.d.ts +4 -0
- package/src/showcase-runtime.js +619 -163
- package/dist/chunk-UUJLYYQS.js.map +0 -1
- package/dist/showcase-runtime-4BS7TWHS.js.map +0 -1
- /package/dist/{gltf-loader-4FNTT63R.js.map → gltf-loader-LKALCZAV.js.map} +0 -0
package/src/showcase-runtime.js
CHANGED
|
@@ -35,9 +35,17 @@ import { resolveShowcaseAssetUrl } from "./asset-url.js";
|
|
|
35
35
|
import { loadGltfModel } from "./gltf-loader.js";
|
|
36
36
|
|
|
37
37
|
const STYLE_ID = "plasius-shared-3d-showcase-style";
|
|
38
|
+
const ROOT_CLASS = "plasius-showcase-root";
|
|
38
39
|
const DEFAULT_TITLE = "Flag by the Sea";
|
|
39
40
|
const DEFAULT_SUBTITLE =
|
|
40
41
|
"Shared 3D validation scene using GLTF ships, cloth, fluid continuity, adaptive performance, and telemetry.";
|
|
42
|
+
const SHIP_SCALE = 1.1;
|
|
43
|
+
const HARBOR_BOUNDS = Object.freeze({
|
|
44
|
+
minX: -11.2,
|
|
45
|
+
maxX: 11.2,
|
|
46
|
+
minZ: 1.8,
|
|
47
|
+
maxZ: 17.2,
|
|
48
|
+
});
|
|
41
49
|
const CAMERA_PRESETS = Object.freeze({
|
|
42
50
|
integrated: Object.freeze({ yaw: -0.55, pitch: 0.34, distance: 27, target: [0, 2.2, 0] }),
|
|
43
51
|
lighting: Object.freeze({ yaw: -0.28, pitch: 0.28, distance: 23, target: [0, 2.8, 0] }),
|
|
@@ -50,10 +58,10 @@ const CAMERA_PRESETS = Object.freeze({
|
|
|
50
58
|
export const showcaseFocusModes = Object.freeze(Object.keys(CAMERA_PRESETS));
|
|
51
59
|
|
|
52
60
|
const SCENE_NOTES = Object.freeze([
|
|
53
|
-
"Ships are loaded from a GLTF asset and carry
|
|
54
|
-
"
|
|
55
|
-
"Cloth and fluid continuity stay coherent across near, mid, far, and horizon bands.",
|
|
56
|
-
"Performance pressure reduces visual detail before authoritative collision motion is touched.",
|
|
61
|
+
"Ships are loaded from a GLTF asset and carry mass, damping, restitution, and hull extents from node extras.",
|
|
62
|
+
"Moonlight sets the cold ambient read while deck lanterns and harbor torches provide warm local contrast.",
|
|
63
|
+
"Cloth and fluid continuity stay coherent across near, mid, far, and horizon bands even in the darker night palette.",
|
|
64
|
+
"Performance pressure reduces visual detail before mass-weighted authoritative collision motion is touched.",
|
|
57
65
|
]);
|
|
58
66
|
|
|
59
67
|
const UNIT_BOX_MESH = Object.freeze({
|
|
@@ -77,6 +85,18 @@ const UNIT_BOX_MESH = Object.freeze({
|
|
|
77
85
|
]),
|
|
78
86
|
});
|
|
79
87
|
|
|
88
|
+
const SHIP_LANTERNS = Object.freeze([
|
|
89
|
+
Object.freeze({ x: 0.94, y: 1.54, z: 2.52, glow: 1 }),
|
|
90
|
+
Object.freeze({ x: -0.9, y: 1.58, z: 2.44, glow: 0.92 }),
|
|
91
|
+
Object.freeze({ x: 0.62, y: 1.42, z: -2.18, glow: 0.88 }),
|
|
92
|
+
Object.freeze({ x: -0.58, y: 1.46, z: -2.04, glow: 0.84 }),
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const HARBOR_TORCHES = Object.freeze([
|
|
96
|
+
Object.freeze({ x: -5.2, y: 1.25, z: 1.36, glow: 1.1 }),
|
|
97
|
+
Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
|
|
98
|
+
Object.freeze({ x: -10.4, y: 1.28, z: 0.82, glow: 0.92 }),
|
|
99
|
+
]);
|
|
80
100
|
function injectStyles() {
|
|
81
101
|
if (document.getElementById(STYLE_ID)) {
|
|
82
102
|
return;
|
|
@@ -85,27 +105,27 @@ function injectStyles() {
|
|
|
85
105
|
const style = document.createElement("style");
|
|
86
106
|
style.id = STYLE_ID;
|
|
87
107
|
style.textContent = `
|
|
88
|
-
|
|
89
|
-
color-scheme:
|
|
90
|
-
--plasius-paper: #
|
|
91
|
-
--plasius-ink: #
|
|
92
|
-
--plasius-muted: #
|
|
93
|
-
--plasius-accent: #
|
|
94
|
-
--plasius-panel: rgba(
|
|
95
|
-
--plasius-border: rgba(
|
|
96
|
-
--plasius-shadow: 0
|
|
97
|
-
}
|
|
98
|
-
* {
|
|
99
|
-
box-sizing: border-box;
|
|
100
|
-
}
|
|
101
|
-
body {
|
|
108
|
+
.${ROOT_CLASS} {
|
|
109
|
+
color-scheme: dark;
|
|
110
|
+
--plasius-paper: #081321;
|
|
111
|
+
--plasius-ink: #edf4ff;
|
|
112
|
+
--plasius-muted: #b6c5dd;
|
|
113
|
+
--plasius-accent: #f3b16a;
|
|
114
|
+
--plasius-panel: rgba(8, 19, 33, 0.72);
|
|
115
|
+
--plasius-border: rgba(159, 185, 223, 0.18);
|
|
116
|
+
--plasius-shadow: 0 24px 56px rgba(1, 6, 14, 0.44);
|
|
102
117
|
margin: 0;
|
|
103
|
-
min-height:
|
|
118
|
+
min-height: 100%;
|
|
104
119
|
font-family: "Fraunces", "Iowan Old Style", serif;
|
|
105
120
|
color: var(--plasius-ink);
|
|
106
121
|
background:
|
|
107
|
-
radial-gradient(circle at
|
|
108
|
-
|
|
122
|
+
radial-gradient(circle at 18% 12%, rgba(73, 101, 170, 0.28), transparent 30%),
|
|
123
|
+
radial-gradient(circle at 82% 18%, rgba(240, 188, 103, 0.08), transparent 18%),
|
|
124
|
+
linear-gradient(180deg, #04101d 0%, #0b1930 42%, #081321 100%);
|
|
125
|
+
}
|
|
126
|
+
.${ROOT_CLASS},
|
|
127
|
+
.${ROOT_CLASS} * {
|
|
128
|
+
box-sizing: border-box;
|
|
109
129
|
}
|
|
110
130
|
.plasius-demo {
|
|
111
131
|
width: min(1560px, calc(100vw - 32px));
|
|
@@ -139,7 +159,7 @@ function injectStyles() {
|
|
|
139
159
|
text-transform: uppercase;
|
|
140
160
|
letter-spacing: 0.18em;
|
|
141
161
|
font-size: 12px;
|
|
142
|
-
color: rgba(
|
|
162
|
+
color: rgba(226, 236, 255, 0.58);
|
|
143
163
|
}
|
|
144
164
|
.plasius-demo h1,
|
|
145
165
|
.plasius-demo h2,
|
|
@@ -157,7 +177,7 @@ function injectStyles() {
|
|
|
157
177
|
margin: 0;
|
|
158
178
|
padding: 8px 12px;
|
|
159
179
|
border-radius: 999px;
|
|
160
|
-
background: rgba(
|
|
180
|
+
background: rgba(243, 177, 106, 0.14);
|
|
161
181
|
color: var(--plasius-accent);
|
|
162
182
|
font-weight: 700;
|
|
163
183
|
}
|
|
@@ -179,8 +199,8 @@ function injectStyles() {
|
|
|
179
199
|
aspect-ratio: 16 / 9;
|
|
180
200
|
display: block;
|
|
181
201
|
border-radius: 20px;
|
|
182
|
-
border: 1px solid rgba(
|
|
183
|
-
background: linear-gradient(180deg, #
|
|
202
|
+
border: 1px solid rgba(159, 185, 223, 0.12);
|
|
203
|
+
background: linear-gradient(180deg, #071220 0%, #132440 42%, #10344b 42%, #05111d 100%);
|
|
184
204
|
}
|
|
185
205
|
.plasius-demo__toolbar {
|
|
186
206
|
position: absolute;
|
|
@@ -200,9 +220,9 @@ function injectStyles() {
|
|
|
200
220
|
.plasius-demo button,
|
|
201
221
|
.plasius-demo .plasius-toggle,
|
|
202
222
|
.plasius-demo select {
|
|
203
|
-
border: 1px solid rgba(
|
|
223
|
+
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
204
224
|
border-radius: 999px;
|
|
205
|
-
background: rgba(
|
|
225
|
+
background: rgba(9, 20, 34, 0.84);
|
|
206
226
|
color: var(--plasius-ink);
|
|
207
227
|
padding: 10px 14px;
|
|
208
228
|
}
|
|
@@ -241,8 +261,8 @@ function injectStyles() {
|
|
|
241
261
|
bottom: 24px;
|
|
242
262
|
padding: 10px 14px;
|
|
243
263
|
border-radius: 16px;
|
|
244
|
-
background: rgba(
|
|
245
|
-
border: 1px solid rgba(
|
|
264
|
+
background: rgba(9, 20, 34, 0.82);
|
|
265
|
+
border: 1px solid rgba(159, 185, 223, 0.16);
|
|
246
266
|
color: var(--plasius-muted);
|
|
247
267
|
font-size: 12px;
|
|
248
268
|
line-height: 1.45;
|
|
@@ -254,7 +274,7 @@ function injectStyles() {
|
|
|
254
274
|
}
|
|
255
275
|
.plasius-demo__footer {
|
|
256
276
|
margin-top: 4px;
|
|
257
|
-
color: rgba(
|
|
277
|
+
color: rgba(226, 236, 255, 0.68);
|
|
258
278
|
font-size: 13px;
|
|
259
279
|
line-height: 1.6;
|
|
260
280
|
}
|
|
@@ -276,6 +296,16 @@ function mix(a, b, t) {
|
|
|
276
296
|
return a + (b - a) * t;
|
|
277
297
|
}
|
|
278
298
|
|
|
299
|
+
function smoothstep(min, max, value) {
|
|
300
|
+
const t = clamp((value - min) / Math.max(0.0001, max - min), 0, 1);
|
|
301
|
+
return t * t * (3 - 2 * t);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function pseudoRandom(seed) {
|
|
305
|
+
const value = Math.sin(seed * 12.9898 + seed * seed * 0.0017) * 43758.5453;
|
|
306
|
+
return value - Math.floor(value);
|
|
307
|
+
}
|
|
308
|
+
|
|
279
309
|
function vec3(x = 0, y = 0, z = 0) {
|
|
280
310
|
return { x, y, z };
|
|
281
311
|
}
|
|
@@ -318,6 +348,14 @@ function reflectVec3(vector, normal) {
|
|
|
318
348
|
return subVec3(vector, scaleVec3(unitNormal, 2 * dotVec3(vector, unitNormal)));
|
|
319
349
|
}
|
|
320
350
|
|
|
351
|
+
function directionFromYaw(yaw) {
|
|
352
|
+
return normalizeVec3(vec3(Math.sin(yaw), 0, Math.cos(yaw)));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function perpendicularOnWater(direction) {
|
|
356
|
+
return vec3(-direction.z, 0, direction.x);
|
|
357
|
+
}
|
|
358
|
+
|
|
321
359
|
function rotateY(point, angle) {
|
|
322
360
|
const cosine = Math.cos(angle);
|
|
323
361
|
const sine = Math.sin(angle);
|
|
@@ -522,7 +560,7 @@ function buildDemoDom(root, options) {
|
|
|
522
560
|
<section class="plasius-panel plasius-demo__status">
|
|
523
561
|
<p id="demoStatus" class="plasius-demo__status-badge">Booting 3D scene…</p>
|
|
524
562
|
<p id="demoDetails" class="plasius-demo__status-text">
|
|
525
|
-
Preparing GLTF
|
|
563
|
+
Preparing a moonlit harbor scene, GLTF hull data, cloth and fluid continuity plans, and adaptive quality metadata.
|
|
526
564
|
</p>
|
|
527
565
|
</section>
|
|
528
566
|
</section>
|
|
@@ -550,9 +588,9 @@ function buildDemoDom(root, options) {
|
|
|
550
588
|
</div>
|
|
551
589
|
<div class="plasius-demo__legend">
|
|
552
590
|
<strong>Scene</strong>
|
|
553
|
-
GLTF ships carry
|
|
554
|
-
|
|
555
|
-
|
|
591
|
+
GLTF ships carry hull mass and damping metadata.<br />
|
|
592
|
+
Lanterns and torches warm the moonlit harbor.<br />
|
|
593
|
+
Mass-aware collisions stay authoritative near the camera.
|
|
556
594
|
</div>
|
|
557
595
|
</section>
|
|
558
596
|
<aside class="plasius-demo__sidebar">
|
|
@@ -635,6 +673,7 @@ function buildSceneSnapshot(state, shipModel) {
|
|
|
635
673
|
})
|
|
636
674
|
)
|
|
637
675
|
),
|
|
676
|
+
shipPhysics: shipModel?.physics ?? null,
|
|
638
677
|
physics: Object.freeze({
|
|
639
678
|
profile: state.physics.profile,
|
|
640
679
|
plan: state.physics.plan,
|
|
@@ -686,28 +725,39 @@ function readVisualNumber(value, fallback) {
|
|
|
686
725
|
function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {}) {
|
|
687
726
|
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
688
727
|
const defaults = {
|
|
689
|
-
skyTop: premiumShadows ? "#
|
|
690
|
-
skyMid: premiumShadows ? "#
|
|
691
|
-
skyBottom: premiumShadows ? "#
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
728
|
+
skyTop: premiumShadows ? "#040c1a" : "#06101f",
|
|
729
|
+
skyMid: premiumShadows ? "#11203b" : "#152643",
|
|
730
|
+
skyBottom: premiumShadows ? "#2f4468" : "#364d73",
|
|
731
|
+
duskGlow: premiumShadows ? "rgba(116, 142, 201, 0.26)" : "rgba(104, 128, 188, 0.22)",
|
|
732
|
+
seaTop: premiumShadows ? "#102946" : "#153050",
|
|
733
|
+
seaMid: premiumShadows ? "#0a1d33" : "#0d2138",
|
|
734
|
+
seaBottom: "#04101d",
|
|
735
|
+
moonCore: "rgba(241, 246, 255, 0.98)",
|
|
736
|
+
moonHalo: "rgba(167, 191, 255, 0.24)",
|
|
737
|
+
moonReflection: "rgba(192, 214, 255, 0.22)",
|
|
738
|
+
starColor: "rgba(232, 239, 255, 0.82)",
|
|
739
|
+
ambientMist: "rgba(41, 63, 97, 0.16)",
|
|
696
740
|
reflectionStrength: lightingSnapshot.currentLevel.config.reflectionStrength,
|
|
697
741
|
shadowAccent: lightingSnapshot.currentLevel.config.shadowStrength,
|
|
698
|
-
waveAmplitude:
|
|
699
|
-
waveDirection: { x: 0.
|
|
700
|
-
wavePhaseSpeed:
|
|
701
|
-
wakeStrength: 0.
|
|
702
|
-
wakeLength:
|
|
703
|
-
collisionRippleStrength: 0.
|
|
704
|
-
waterNear: { r: 0.
|
|
705
|
-
waterFar: { r: 0.
|
|
706
|
-
harborWall: { r: 0.
|
|
707
|
-
harborDeck: { r: 0.
|
|
708
|
-
harborTower: { r: 0.
|
|
709
|
-
flagColor: { r: 0.
|
|
710
|
-
flagMotion:
|
|
742
|
+
waveAmplitude: 0.94,
|
|
743
|
+
waveDirection: { x: 0.88, z: 0.28 },
|
|
744
|
+
wavePhaseSpeed: 0.88,
|
|
745
|
+
wakeStrength: 0.31,
|
|
746
|
+
wakeLength: 18,
|
|
747
|
+
collisionRippleStrength: 0.42,
|
|
748
|
+
waterNear: { r: 0.08, g: 0.23, b: 0.33 },
|
|
749
|
+
waterFar: { r: 0.18, g: 0.35, b: 0.49 },
|
|
750
|
+
harborWall: { r: 0.26, g: 0.24, b: 0.28 },
|
|
751
|
+
harborDeck: { r: 0.33, g: 0.22, b: 0.16 },
|
|
752
|
+
harborTower: { r: 0.23, g: 0.24, b: 0.29 },
|
|
753
|
+
flagColor: { r: 0.66, g: 0.16, b: 0.13 },
|
|
754
|
+
flagMotion: 0.92,
|
|
755
|
+
lanternCore: { r: 0.98, g: 0.8, b: 0.48 },
|
|
756
|
+
lanternGlow: { r: 1, g: 0.56, b: 0.2 },
|
|
757
|
+
lanternReflectionStrength: 0.42,
|
|
758
|
+
torchCore: { r: 0.99, g: 0.72, b: 0.36 },
|
|
759
|
+
torchGlow: { r: 0.98, g: 0.38, b: 0.15 },
|
|
760
|
+
collisionFlash: "rgba(255, 212, 168, 0.16)",
|
|
711
761
|
};
|
|
712
762
|
|
|
713
763
|
return {
|
|
@@ -719,8 +769,26 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
719
769
|
seaMid: typeof customVisuals.seaMid === "string" ? customVisuals.seaMid : defaults.seaMid,
|
|
720
770
|
seaBottom:
|
|
721
771
|
typeof customVisuals.seaBottom === "string" ? customVisuals.seaBottom : defaults.seaBottom,
|
|
722
|
-
|
|
723
|
-
typeof customVisuals.
|
|
772
|
+
duskGlow:
|
|
773
|
+
typeof customVisuals.duskGlow === "string" ? customVisuals.duskGlow : defaults.duskGlow,
|
|
774
|
+
moonCore:
|
|
775
|
+
typeof customVisuals.moonCore === "string"
|
|
776
|
+
? customVisuals.moonCore
|
|
777
|
+
: typeof customVisuals.sunCore === "string"
|
|
778
|
+
? customVisuals.sunCore
|
|
779
|
+
: defaults.moonCore,
|
|
780
|
+
moonHalo:
|
|
781
|
+
typeof customVisuals.moonHalo === "string" ? customVisuals.moonHalo : defaults.moonHalo,
|
|
782
|
+
moonReflection:
|
|
783
|
+
typeof customVisuals.moonReflection === "string"
|
|
784
|
+
? customVisuals.moonReflection
|
|
785
|
+
: defaults.moonReflection,
|
|
786
|
+
starColor:
|
|
787
|
+
typeof customVisuals.starColor === "string" ? customVisuals.starColor : defaults.starColor,
|
|
788
|
+
ambientMist:
|
|
789
|
+
typeof customVisuals.ambientMist === "string"
|
|
790
|
+
? customVisuals.ambientMist
|
|
791
|
+
: defaults.ambientMist,
|
|
724
792
|
reflectionStrength: readVisualNumber(
|
|
725
793
|
customVisuals.reflectionStrength,
|
|
726
794
|
defaults.reflectionStrength
|
|
@@ -747,6 +815,18 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
747
815
|
harborTower: normalizeColorOverride(customVisuals.harborTower, defaults.harborTower),
|
|
748
816
|
flagColor: normalizeColorOverride(customVisuals.flagColor, defaults.flagColor),
|
|
749
817
|
flagMotion: readVisualNumber(customVisuals.flagMotion, defaults.flagMotion),
|
|
818
|
+
lanternCore: normalizeColorOverride(customVisuals.lanternCore, defaults.lanternCore),
|
|
819
|
+
lanternGlow: normalizeColorOverride(customVisuals.lanternGlow, defaults.lanternGlow),
|
|
820
|
+
lanternReflectionStrength: readVisualNumber(
|
|
821
|
+
customVisuals.lanternReflectionStrength,
|
|
822
|
+
defaults.lanternReflectionStrength
|
|
823
|
+
),
|
|
824
|
+
torchCore: normalizeColorOverride(customVisuals.torchCore, defaults.torchCore),
|
|
825
|
+
torchGlow: normalizeColorOverride(customVisuals.torchGlow, defaults.torchGlow),
|
|
826
|
+
collisionFlash:
|
|
827
|
+
typeof customVisuals.collisionFlash === "string"
|
|
828
|
+
? customVisuals.collisionFlash
|
|
829
|
+
: defaults.collisionFlash,
|
|
750
830
|
};
|
|
751
831
|
}
|
|
752
832
|
|
|
@@ -1052,18 +1132,34 @@ function createSceneState(options) {
|
|
|
1052
1132
|
{
|
|
1053
1133
|
id: "northwind",
|
|
1054
1134
|
position: vec3(-5.2, 0, 7.2),
|
|
1055
|
-
velocity: vec3(2.
|
|
1056
|
-
rotationY: 0.
|
|
1057
|
-
angularVelocity: 0.
|
|
1135
|
+
velocity: vec3(2.35, 0, -1.08),
|
|
1136
|
+
rotationY: 0.58,
|
|
1137
|
+
angularVelocity: 0.09,
|
|
1058
1138
|
tint: { r: 0.62, g: 0.39, b: 0.23 },
|
|
1139
|
+
massScale: 1.42,
|
|
1140
|
+
cruiseSpeed: 2.25,
|
|
1141
|
+
throttleResponse: 0.46,
|
|
1142
|
+
rudderResponse: 0.54,
|
|
1143
|
+
wanderPhase: 0.35,
|
|
1144
|
+
lanterns: SHIP_LANTERNS,
|
|
1145
|
+
lanternStrength: 1.06,
|
|
1146
|
+
collisionRadiusScale: 1.04,
|
|
1059
1147
|
},
|
|
1060
1148
|
{
|
|
1061
1149
|
id: "tidecaller",
|
|
1062
1150
|
position: vec3(4.8, 0, 4.4),
|
|
1063
|
-
velocity: vec3(-
|
|
1064
|
-
rotationY: -2.
|
|
1065
|
-
angularVelocity: -0.
|
|
1151
|
+
velocity: vec3(-2.15, 0, 1.74),
|
|
1152
|
+
rotationY: -2.48,
|
|
1153
|
+
angularVelocity: -0.2,
|
|
1066
1154
|
tint: { r: 0.48, g: 0.28, b: 0.19 },
|
|
1155
|
+
massScale: 0.84,
|
|
1156
|
+
cruiseSpeed: 2.68,
|
|
1157
|
+
throttleResponse: 0.7,
|
|
1158
|
+
rudderResponse: 0.78,
|
|
1159
|
+
wanderPhase: 1.6,
|
|
1160
|
+
lanterns: SHIP_LANTERNS,
|
|
1161
|
+
lanternStrength: 1.18,
|
|
1162
|
+
collisionRadiusScale: 0.94,
|
|
1067
1163
|
},
|
|
1068
1164
|
],
|
|
1069
1165
|
sprays: [],
|
|
@@ -1087,14 +1183,30 @@ function setListContent(element, values) {
|
|
|
1087
1183
|
}
|
|
1088
1184
|
|
|
1089
1185
|
function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, shadowStrength, visuals) {
|
|
1090
|
-
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
1091
1186
|
const sky = ctx.createLinearGradient(0, 0, 0, canvas.height * 0.5);
|
|
1092
1187
|
sky.addColorStop(0, visuals.skyTop);
|
|
1093
|
-
sky.addColorStop(0.
|
|
1188
|
+
sky.addColorStop(0.54, visuals.skyMid);
|
|
1094
1189
|
sky.addColorStop(1, visuals.skyBottom);
|
|
1095
1190
|
ctx.fillStyle = sky;
|
|
1096
1191
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1097
1192
|
|
|
1193
|
+
for (let index = 0; index < 70; index += 1) {
|
|
1194
|
+
const x = pseudoRandom(index + 13) * canvas.width;
|
|
1195
|
+
const y = pseudoRandom(index * 7 + 5) * canvas.height * 0.42;
|
|
1196
|
+
const twinkle = 0.45 + Math.sin(state.time * 1.4 + index * 0.73) * 0.25;
|
|
1197
|
+
const radius = 0.6 + pseudoRandom(index * 11 + 2) * 1.9;
|
|
1198
|
+
ctx.fillStyle = visuals.starColor.replace(/[\d.]+\)$/u, `${clamp(twinkle, 0.16, 0.92)})`);
|
|
1199
|
+
ctx.beginPath();
|
|
1200
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
1201
|
+
ctx.fill();
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const horizonGlow = ctx.createLinearGradient(0, canvas.height * 0.22, 0, canvas.height * 0.62);
|
|
1205
|
+
horizonGlow.addColorStop(0, "rgba(0, 0, 0, 0)");
|
|
1206
|
+
horizonGlow.addColorStop(1, visuals.duskGlow);
|
|
1207
|
+
ctx.fillStyle = horizonGlow;
|
|
1208
|
+
ctx.fillRect(0, canvas.height * 0.2, canvas.width, canvas.height * 0.45);
|
|
1209
|
+
|
|
1098
1210
|
const shoreline = ctx.createLinearGradient(0, canvas.height * 0.45, 0, canvas.height);
|
|
1099
1211
|
shoreline.addColorStop(0, visuals.seaTop);
|
|
1100
1212
|
shoreline.addColorStop(0.52, visuals.seaMid);
|
|
@@ -1102,30 +1214,48 @@ function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, s
|
|
|
1102
1214
|
ctx.fillStyle = shoreline;
|
|
1103
1215
|
ctx.fillRect(0, canvas.height * 0.45, canvas.width, canvas.height * 0.55);
|
|
1104
1216
|
|
|
1105
|
-
const
|
|
1106
|
-
const
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1217
|
+
const moonX = canvas.width * 0.76 + Math.sin(state.time * 0.045) * 18;
|
|
1218
|
+
const moonY = canvas.height * 0.17 + Math.cos(state.time * 0.05) * 10;
|
|
1219
|
+
const moon = ctx.createRadialGradient(moonX, moonY, 14, moonX, moonY, 126);
|
|
1220
|
+
moon.addColorStop(0, visuals.moonCore);
|
|
1221
|
+
moon.addColorStop(0.46, visuals.moonHalo);
|
|
1222
|
+
moon.addColorStop(1, "rgba(167, 191, 255, 0)");
|
|
1223
|
+
ctx.fillStyle = moon;
|
|
1224
|
+
ctx.beginPath();
|
|
1225
|
+
ctx.arc(moonX, moonY, 94, 0, Math.PI * 2);
|
|
1226
|
+
ctx.fill();
|
|
1227
|
+
|
|
1228
|
+
const moonCore = ctx.createRadialGradient(moonX, moonY, 4, moonX, moonY, 28);
|
|
1229
|
+
moonCore.addColorStop(0, "rgba(255, 255, 255, 0.98)");
|
|
1230
|
+
moonCore.addColorStop(1, visuals.moonCore);
|
|
1231
|
+
ctx.fillStyle = moonCore;
|
|
1111
1232
|
ctx.beginPath();
|
|
1112
|
-
ctx.arc(
|
|
1233
|
+
ctx.arc(moonX, moonY, 24, 0, Math.PI * 2);
|
|
1113
1234
|
ctx.fill();
|
|
1114
1235
|
|
|
1115
|
-
const track = ctx.createLinearGradient(
|
|
1116
|
-
track.addColorStop(0,
|
|
1117
|
-
track.addColorStop(0.42,
|
|
1118
|
-
track.addColorStop(1, "rgba(
|
|
1236
|
+
const track = ctx.createLinearGradient(moonX, canvas.height * 0.44, moonX, canvas.height * 0.98);
|
|
1237
|
+
track.addColorStop(0, visuals.moonReflection.replace(/[\d.]+\)$/u, `${0.08 + reflectionStrength * 0.12})`));
|
|
1238
|
+
track.addColorStop(0.42, visuals.moonReflection.replace(/[\d.]+\)$/u, `${0.04 + reflectionStrength * 0.18})`));
|
|
1239
|
+
track.addColorStop(1, "rgba(192, 214, 255, 0)");
|
|
1119
1240
|
ctx.save();
|
|
1120
1241
|
ctx.globalCompositeOperation = "screen";
|
|
1121
1242
|
ctx.fillStyle = track;
|
|
1122
1243
|
ctx.beginPath();
|
|
1123
|
-
ctx.ellipse(
|
|
1244
|
+
ctx.ellipse(moonX, canvas.height * 0.75, 38 + shadowStrength * 42, canvas.height * 0.24, 0, 0, Math.PI * 2);
|
|
1124
1245
|
ctx.fill();
|
|
1125
1246
|
ctx.restore();
|
|
1126
1247
|
|
|
1248
|
+
const mist = ctx.createLinearGradient(0, canvas.height * 0.5, 0, canvas.height);
|
|
1249
|
+
mist.addColorStop(0, "rgba(0, 0, 0, 0)");
|
|
1250
|
+
mist.addColorStop(1, visuals.ambientMist);
|
|
1251
|
+
ctx.fillStyle = mist;
|
|
1252
|
+
ctx.fillRect(0, canvas.height * 0.45, canvas.width, canvas.height * 0.55);
|
|
1253
|
+
|
|
1127
1254
|
if (state.collisionFlash > 0.01) {
|
|
1128
|
-
ctx.fillStyle =
|
|
1255
|
+
ctx.fillStyle = visuals.collisionFlash.replace(
|
|
1256
|
+
/[\d.]+\)$/u,
|
|
1257
|
+
`${clamp(state.collisionFlash * 0.22, 0, 0.26)})`
|
|
1258
|
+
);
|
|
1129
1259
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1130
1260
|
}
|
|
1131
1261
|
}
|
|
@@ -1235,7 +1365,7 @@ function pushHarborGeometry(camera, viewport, triangles, visuals) {
|
|
|
1235
1365
|
}
|
|
1236
1366
|
|
|
1237
1367
|
function renderShipRigging(ctx, ship, camera, viewport) {
|
|
1238
|
-
const transform = { position: ship.position, rotationY: ship.rotationY, scale:
|
|
1368
|
+
const transform = { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE };
|
|
1239
1369
|
const mastBase = transformPoint(vec3(0, 0.38, -0.4), transform);
|
|
1240
1370
|
const mastTop = transformPoint(vec3(0, 3.8, -0.2), transform);
|
|
1241
1371
|
const aftBase = transformPoint(vec3(-0.25, 0.32, -1.9), transform);
|
|
@@ -1350,6 +1480,43 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
1350
1480
|
}
|
|
1351
1481
|
}
|
|
1352
1482
|
|
|
1483
|
+
function readPhysicsNumber(physics, key, fallback) {
|
|
1484
|
+
const value = physics?.[key];
|
|
1485
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function getShipMass(ship, shipModel) {
|
|
1489
|
+
const baseMass = readPhysicsNumber(shipModel.physics, "mass", 3200);
|
|
1490
|
+
return baseMass * readVisualNumber(ship.massScale, 1);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function getShipHalfExtents(ship, shipModel) {
|
|
1494
|
+
const physicsHalfExtents = Array.isArray(shipModel.physics.halfExtents)
|
|
1495
|
+
? shipModel.physics.halfExtents
|
|
1496
|
+
: [1.35, 0.95, 3.9];
|
|
1497
|
+
const scale = SHIP_SCALE * readVisualNumber(ship.collisionRadiusScale, 1);
|
|
1498
|
+
return {
|
|
1499
|
+
x: physicsHalfExtents[0] * scale,
|
|
1500
|
+
y: physicsHalfExtents[1] * scale,
|
|
1501
|
+
z: physicsHalfExtents[2] * scale,
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function getShipCollisionRadius(ship, shipModel) {
|
|
1506
|
+
const halfExtents = getShipHalfExtents(ship, shipModel);
|
|
1507
|
+
return Math.max(halfExtents.x * 1.08, halfExtents.z * 0.62);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
function getShipInverseMass(ship, shipModel) {
|
|
1511
|
+
return 1 / Math.max(1, getShipMass(ship, shipModel));
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function getShipInverseInertia(ship, shipModel) {
|
|
1515
|
+
const radius = getShipCollisionRadius(ship, shipModel);
|
|
1516
|
+
const inertia = getShipMass(ship, shipModel) * radius * radius * 0.72;
|
|
1517
|
+
return 1 / Math.max(1, inertia);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1353
1520
|
function spawnSpray(state, point, intensity) {
|
|
1354
1521
|
const count = state.fluidDetail.getSnapshot().currentLevel.config.splashCount;
|
|
1355
1522
|
for (let index = 0; index < count; index += 1) {
|
|
@@ -1363,58 +1530,226 @@ function spawnSpray(state, point, intensity) {
|
|
|
1363
1530
|
}
|
|
1364
1531
|
}
|
|
1365
1532
|
|
|
1366
|
-
function
|
|
1533
|
+
function resolveShipRoute(ship, state, radius) {
|
|
1534
|
+
if (typeof ship.routeDirection !== "number") {
|
|
1535
|
+
ship.routeDirection = ship.velocity.x >= 0 ? 1 : -1;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
if (ship.position.x > HARBOR_BOUNDS.maxX - radius * 1.1) {
|
|
1539
|
+
ship.routeDirection = -1;
|
|
1540
|
+
} else if (ship.position.x < HARBOR_BOUNDS.minX + radius * 1.1) {
|
|
1541
|
+
ship.routeDirection = 1;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
const wander = Math.sin(state.time * 0.22 + readVisualNumber(ship.wanderPhase, 0));
|
|
1545
|
+
const crossCurrent = Math.cos(state.time * 0.31 + readVisualNumber(ship.wanderPhase, 0));
|
|
1546
|
+
const laneCenter =
|
|
1547
|
+
ship.id === "northwind"
|
|
1548
|
+
? 10.2 + wander * 2.1 + crossCurrent * 0.6
|
|
1549
|
+
: 7 + wander * 3.3 - crossCurrent * 1.1;
|
|
1550
|
+
const targetX =
|
|
1551
|
+
ship.routeDirection > 0
|
|
1552
|
+
? HARBOR_BOUNDS.maxX - radius * 1.7
|
|
1553
|
+
: HARBOR_BOUNDS.minX + radius * 1.7;
|
|
1554
|
+
return vec3(targetX, 0, clamp(laneCenter, HARBOR_BOUNDS.minZ + 1.8, HARBOR_BOUNDS.maxZ - 1.8));
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function updateShipMotion(state, ship, dt, shipModel) {
|
|
1367
1558
|
const physics = shipModel.physics;
|
|
1368
|
-
const
|
|
1559
|
+
const massScale = Math.max(0.55, readVisualNumber(ship.massScale, 1));
|
|
1560
|
+
const radius = getShipCollisionRadius(ship, shipModel);
|
|
1561
|
+
const waterline = readPhysicsNumber(physics, "waterline", 0.42);
|
|
1562
|
+
const linearDamping = readPhysicsNumber(physics, "linearDamping", 0.04);
|
|
1563
|
+
const angularDamping = readPhysicsNumber(physics, "angularDamping", 0.08);
|
|
1564
|
+
const throttleResponse = readVisualNumber(ship.throttleResponse, 0.58);
|
|
1565
|
+
const rudderResponse = readVisualNumber(ship.rudderResponse, 0.62);
|
|
1566
|
+
const cruiseSpeed = readVisualNumber(ship.cruiseSpeed, 2.4);
|
|
1567
|
+
|
|
1568
|
+
ship.collisionCooldown = Math.max(0, readVisualNumber(ship.collisionCooldown, 0) - dt);
|
|
1569
|
+
|
|
1570
|
+
const forward = directionFromYaw(ship.rotationY);
|
|
1571
|
+
const lateral = perpendicularOnWater(forward);
|
|
1572
|
+
const routeTarget = resolveShipRoute(ship, state, radius);
|
|
1573
|
+
const desiredHeading = Math.atan2(routeTarget.x - ship.position.x, routeTarget.z - ship.position.z);
|
|
1574
|
+
const headingError = Math.atan2(
|
|
1575
|
+
Math.sin(desiredHeading - ship.rotationY),
|
|
1576
|
+
Math.cos(desiredHeading - ship.rotationY)
|
|
1577
|
+
);
|
|
1578
|
+
ship.angularVelocity +=
|
|
1579
|
+
headingError * rudderResponse * dt * (1.18 / Math.sqrt(massScale)) +
|
|
1580
|
+
Math.sin(state.time * 0.9 + readVisualNumber(ship.wanderPhase, 0)) * dt * 0.04;
|
|
1581
|
+
|
|
1582
|
+
const waveDirection = resolveWaveDirection(state);
|
|
1583
|
+
const forwardSpeed = dotVec3(ship.velocity, forward);
|
|
1584
|
+
const lateralSpeed = dotVec3(ship.velocity, lateral);
|
|
1585
|
+
const thrust = (cruiseSpeed - forwardSpeed) * throttleResponse;
|
|
1586
|
+
const currentDrift = sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.016;
|
|
1587
|
+
const acceleration = addVec3(
|
|
1588
|
+
scaleVec3(forward, thrust),
|
|
1589
|
+
addVec3(
|
|
1590
|
+
scaleVec3(lateral, -lateralSpeed * (1.28 + rudderResponse * 0.4)),
|
|
1591
|
+
scaleVec3(waveDirection, currentDrift / Math.sqrt(massScale))
|
|
1592
|
+
)
|
|
1593
|
+
);
|
|
1594
|
+
|
|
1595
|
+
ship.velocity = addVec3(ship.velocity, scaleVec3(acceleration, dt));
|
|
1596
|
+
ship.velocity = scaleVec3(
|
|
1597
|
+
ship.velocity,
|
|
1598
|
+
Math.max(0, 1 - (linearDamping / Math.pow(massScale, 0.22)) * dt)
|
|
1599
|
+
);
|
|
1600
|
+
ship.angularVelocity *= Math.max(
|
|
1601
|
+
0,
|
|
1602
|
+
1 - (angularDamping / Math.pow(massScale, 0.15)) * dt
|
|
1603
|
+
);
|
|
1604
|
+
ship.rotationY += ship.angularVelocity * dt;
|
|
1605
|
+
ship.position = addVec3(ship.position, scaleVec3(ship.velocity, dt));
|
|
1606
|
+
ship.position.y =
|
|
1607
|
+
sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.24 + waterline;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
function resolveBoundaryCollision(ship, state, shipModel) {
|
|
1611
|
+
const restitution = readPhysicsNumber(shipModel.physics, "restitution", 0.22) * 0.56;
|
|
1612
|
+
const radius = getShipCollisionRadius(ship, shipModel);
|
|
1613
|
+
const boundaries = [
|
|
1614
|
+
{ axis: "x", min: HARBOR_BOUNDS.minX + radius, max: HARBOR_BOUNDS.maxX - radius, normalMin: vec3(1, 0, 0), normalMax: vec3(-1, 0, 0) },
|
|
1615
|
+
{ axis: "z", min: HARBOR_BOUNDS.minZ + radius, max: HARBOR_BOUNDS.maxZ - radius, normalMin: vec3(0, 0, 1), normalMax: vec3(0, 0, -1) },
|
|
1616
|
+
];
|
|
1617
|
+
|
|
1618
|
+
for (const boundary of boundaries) {
|
|
1619
|
+
if (ship.position[boundary.axis] < boundary.min) {
|
|
1620
|
+
ship.position[boundary.axis] = boundary.min;
|
|
1621
|
+
const normal = boundary.normalMin;
|
|
1622
|
+
const speedIntoWall = dotVec3(ship.velocity, normal);
|
|
1623
|
+
if (speedIntoWall < 0) {
|
|
1624
|
+
ship.velocity = subVec3(
|
|
1625
|
+
ship.velocity,
|
|
1626
|
+
scaleVec3(normal, (1 + restitution) * speedIntoWall)
|
|
1627
|
+
);
|
|
1628
|
+
const tangent = vec3(-normal.z, 0, normal.x);
|
|
1629
|
+
const tangentSpeed = dotVec3(ship.velocity, tangent);
|
|
1630
|
+
ship.velocity = subVec3(ship.velocity, scaleVec3(tangent, tangentSpeed * 0.12));
|
|
1631
|
+
ship.angularVelocity += tangentSpeed * 0.004;
|
|
1632
|
+
}
|
|
1633
|
+
} else if (ship.position[boundary.axis] > boundary.max) {
|
|
1634
|
+
ship.position[boundary.axis] = boundary.max;
|
|
1635
|
+
const normal = boundary.normalMax;
|
|
1636
|
+
const speedIntoWall = dotVec3(ship.velocity, normal);
|
|
1637
|
+
if (speedIntoWall < 0) {
|
|
1638
|
+
ship.velocity = subVec3(
|
|
1639
|
+
ship.velocity,
|
|
1640
|
+
scaleVec3(normal, (1 + restitution) * speedIntoWall)
|
|
1641
|
+
);
|
|
1642
|
+
const tangent = vec3(-normal.z, 0, normal.x);
|
|
1643
|
+
const tangentSpeed = dotVec3(ship.velocity, tangent);
|
|
1644
|
+
ship.velocity = subVec3(ship.velocity, scaleVec3(tangent, tangentSpeed * 0.12));
|
|
1645
|
+
ship.angularVelocity += tangentSpeed * 0.004;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function resolveShipCollision(state, a, b, shipModel) {
|
|
1652
|
+
const delta = subVec3(b.position, a.position);
|
|
1653
|
+
const radiusA = getShipCollisionRadius(a, shipModel);
|
|
1654
|
+
const radiusB = getShipCollisionRadius(b, shipModel);
|
|
1655
|
+
const distance = Math.hypot(delta.x, delta.z);
|
|
1656
|
+
const minDistance = radiusA + radiusB;
|
|
1657
|
+
if (distance >= minDistance) {
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
const normal =
|
|
1662
|
+
distance > 0.0001
|
|
1663
|
+
? normalizeVec3(vec3(delta.x / distance, 0, delta.z / distance))
|
|
1664
|
+
: normalizeVec3(vec3(Math.cos(state.time * 5.2), 0, Math.sin(state.time * 4.8)));
|
|
1665
|
+
const tangent = vec3(-normal.z, 0, normal.x);
|
|
1666
|
+
const penetration = minDistance - distance;
|
|
1667
|
+
const invMassA = getShipInverseMass(a, shipModel);
|
|
1668
|
+
const invMassB = getShipInverseMass(b, shipModel);
|
|
1669
|
+
const invMassSum = invMassA + invMassB;
|
|
1670
|
+
const correction = scaleVec3(normal, (penetration / Math.max(0.0001, invMassSum)) * 0.72);
|
|
1671
|
+
a.position = subVec3(a.position, scaleVec3(correction, invMassA));
|
|
1672
|
+
b.position = addVec3(b.position, scaleVec3(correction, invMassB));
|
|
1673
|
+
|
|
1674
|
+
const relativeVelocity = subVec3(b.velocity, a.velocity);
|
|
1675
|
+
const velocityAlongNormal = dotVec3(relativeVelocity, normal);
|
|
1676
|
+
const restitution = readPhysicsNumber(shipModel.physics, "restitution", 0.22) * 0.88;
|
|
1677
|
+
if (velocityAlongNormal < 0) {
|
|
1678
|
+
const impulseMagnitude =
|
|
1679
|
+
(-(1 + restitution) * velocityAlongNormal) / Math.max(0.0001, invMassSum);
|
|
1680
|
+
const impulse = scaleVec3(normal, impulseMagnitude);
|
|
1681
|
+
a.velocity = subVec3(a.velocity, scaleVec3(impulse, invMassA));
|
|
1682
|
+
b.velocity = addVec3(b.velocity, scaleVec3(impulse, invMassB));
|
|
1683
|
+
|
|
1684
|
+
const tangentSpeed = dotVec3(relativeVelocity, tangent);
|
|
1685
|
+
const frictionMagnitude = clamp(
|
|
1686
|
+
(-tangentSpeed / Math.max(0.0001, invMassSum)),
|
|
1687
|
+
-impulseMagnitude * 0.16,
|
|
1688
|
+
impulseMagnitude * 0.16
|
|
1689
|
+
);
|
|
1690
|
+
const frictionImpulse = scaleVec3(tangent, frictionMagnitude);
|
|
1691
|
+
a.velocity = subVec3(a.velocity, scaleVec3(frictionImpulse, invMassA));
|
|
1692
|
+
b.velocity = addVec3(b.velocity, scaleVec3(frictionImpulse, invMassB));
|
|
1693
|
+
|
|
1694
|
+
a.angularVelocity -=
|
|
1695
|
+
tangentSpeed * radiusA * getShipInverseInertia(a, shipModel) * 0.2 +
|
|
1696
|
+
impulseMagnitude * 0.00024;
|
|
1697
|
+
b.angularVelocity +=
|
|
1698
|
+
tangentSpeed * radiusB * getShipInverseInertia(b, shipModel) * 0.2 +
|
|
1699
|
+
impulseMagnitude * 0.00024;
|
|
1700
|
+
|
|
1701
|
+
const impactSpeed = Math.abs(velocityAlongNormal);
|
|
1702
|
+
if (
|
|
1703
|
+
impactSpeed > 0.18 &&
|
|
1704
|
+
Math.max(readVisualNumber(a.collisionCooldown, 0), readVisualNumber(b.collisionCooldown, 0)) <= 0
|
|
1705
|
+
) {
|
|
1706
|
+
const contactPoint = vec3(
|
|
1707
|
+
(a.position.x + b.position.x) * 0.5,
|
|
1708
|
+
(a.position.y + b.position.y) * 0.5 + 0.14,
|
|
1709
|
+
(a.position.z + b.position.z) * 0.5
|
|
1710
|
+
);
|
|
1711
|
+
spawnSpray(state, contactPoint, impactSpeed * 2.4 + penetration * 8);
|
|
1712
|
+
state.waveImpulses.push({
|
|
1713
|
+
x: contactPoint.x,
|
|
1714
|
+
z: contactPoint.z,
|
|
1715
|
+
strength: clamp(0.24 + impactSpeed * 0.46 + penetration * 0.9, 0.2, 1.7),
|
|
1716
|
+
radius: 0.9 + penetration * 1.4,
|
|
1717
|
+
life: 1,
|
|
1718
|
+
});
|
|
1719
|
+
state.collisionCount += 1;
|
|
1720
|
+
state.collisionFlash = Math.max(
|
|
1721
|
+
state.collisionFlash,
|
|
1722
|
+
clamp(impactSpeed * 0.55 + penetration * 1.8, 0.16, 1)
|
|
1723
|
+
);
|
|
1724
|
+
a.collisionCooldown = 0.2;
|
|
1725
|
+
b.collisionCooldown = 0.2;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
state.contactCount += 1;
|
|
1730
|
+
return true;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function updateShips(state, dt, shipModel) {
|
|
1369
1734
|
let collided = false;
|
|
1370
1735
|
state.contactCount = 0;
|
|
1736
|
+
|
|
1371
1737
|
for (const ship of state.ships) {
|
|
1372
|
-
|
|
1373
|
-
ship
|
|
1374
|
-
ship.velocity = scaleVec3(ship.velocity, 1 - (physics.linearDamping ?? 0.04) * dt);
|
|
1375
|
-
ship.angularVelocity *= 1 - (physics.angularDamping ?? 0.08) * dt;
|
|
1376
|
-
ship.position.y =
|
|
1377
|
-
sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.22 +
|
|
1378
|
-
(physics.waterline ?? 0.42);
|
|
1379
|
-
if (Math.abs(ship.position.x) > 10) {
|
|
1380
|
-
ship.velocity.x *= -1;
|
|
1381
|
-
ship.angularVelocity *= -1;
|
|
1382
|
-
}
|
|
1383
|
-
if (ship.position.z < 2 || ship.position.z > 16) {
|
|
1384
|
-
ship.velocity.z *= -1;
|
|
1385
|
-
ship.angularVelocity *= -1;
|
|
1386
|
-
}
|
|
1738
|
+
updateShipMotion(state, ship, dt, shipModel);
|
|
1739
|
+
resolveBoundaryCollision(ship, state, shipModel);
|
|
1387
1740
|
}
|
|
1388
1741
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
const restitution = physics.restitution ?? 0.22;
|
|
1396
|
-
const swapX = a.velocity.x;
|
|
1397
|
-
const swapZ = a.velocity.z;
|
|
1398
|
-
a.velocity.x = -b.velocity.x * (0.86 + restitution);
|
|
1399
|
-
a.velocity.z = -b.velocity.z * (0.82 + restitution);
|
|
1400
|
-
b.velocity.x = -swapX * (0.86 + restitution);
|
|
1401
|
-
b.velocity.z = -swapZ * (0.82 + restitution);
|
|
1402
|
-
a.angularVelocity += 0.55;
|
|
1403
|
-
b.angularVelocity -= 0.55;
|
|
1404
|
-
const contactPoint = vec3((a.position.x + b.position.x) * 0.5, (a.position.y + b.position.y) * 0.5 + 0.1, (a.position.z + b.position.z) * 0.5);
|
|
1405
|
-
spawnSpray(state, contactPoint, Math.abs(dx) + Math.abs(dz));
|
|
1406
|
-
state.waveImpulses.push({
|
|
1407
|
-
x: contactPoint.x,
|
|
1408
|
-
z: contactPoint.z,
|
|
1409
|
-
strength: Math.min(1.4, 0.2 + (Math.abs(dx) + Math.abs(dz)) * 0.18),
|
|
1410
|
-
radius: 0.8,
|
|
1411
|
-
life: 1,
|
|
1412
|
-
});
|
|
1413
|
-
state.collisionCount += 1;
|
|
1414
|
-
state.contactCount = 1;
|
|
1415
|
-
collided = true;
|
|
1742
|
+
for (let index = 0; index < state.ships.length; index += 1) {
|
|
1743
|
+
for (let otherIndex = index + 1; otherIndex < state.ships.length; otherIndex += 1) {
|
|
1744
|
+
collided =
|
|
1745
|
+
resolveShipCollision(state, state.ships[index], state.ships[otherIndex], shipModel) ||
|
|
1746
|
+
collided;
|
|
1747
|
+
}
|
|
1416
1748
|
}
|
|
1417
|
-
|
|
1749
|
+
|
|
1750
|
+
state.collisionFlash = collided
|
|
1751
|
+
? Math.max(0.12, state.collisionFlash)
|
|
1752
|
+
: Math.max(0, state.collisionFlash - dt * 1.3);
|
|
1418
1753
|
}
|
|
1419
1754
|
|
|
1420
1755
|
function updateWaveImpulses(state, dt) {
|
|
@@ -1537,7 +1872,7 @@ function renderFlagPole(ctx, camera, viewport) {
|
|
|
1537
1872
|
function renderShipShadow(ctx, shipModel, ship, state, camera, viewport, lightDir, shadowStrength) {
|
|
1538
1873
|
const bounds = shipModel.bounds;
|
|
1539
1874
|
const keelY = (shipModel.physics.waterline ?? 0.42) - 0.28;
|
|
1540
|
-
const transform = { position: ship.position, rotationY: ship.rotationY, scale:
|
|
1875
|
+
const transform = { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE };
|
|
1541
1876
|
const hullCorners = [
|
|
1542
1877
|
vec3(bounds.min[0], keelY, bounds.min[2]),
|
|
1543
1878
|
vec3(bounds.max[0], keelY, bounds.min[2]),
|
|
@@ -1567,6 +1902,113 @@ function renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength
|
|
|
1567
1902
|
});
|
|
1568
1903
|
}
|
|
1569
1904
|
|
|
1905
|
+
function renderGlowLight(
|
|
1906
|
+
ctx,
|
|
1907
|
+
point,
|
|
1908
|
+
camera,
|
|
1909
|
+
viewport,
|
|
1910
|
+
coreColor,
|
|
1911
|
+
glowColor,
|
|
1912
|
+
glowScale,
|
|
1913
|
+
reflectionStrength = 0,
|
|
1914
|
+
state = null
|
|
1915
|
+
) {
|
|
1916
|
+
const projected = projectPoint(point, camera, viewport);
|
|
1917
|
+
if (!projected) {
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
const radius = clamp((1 / projected.depth) * 420 * glowScale, 4, 34);
|
|
1922
|
+
const halo = ctx.createRadialGradient(projected.x, projected.y, radius * 0.12, projected.x, projected.y, radius);
|
|
1923
|
+
halo.addColorStop(0, colorToRgba(coreColor, 0.98));
|
|
1924
|
+
halo.addColorStop(0.5, colorToRgba(glowColor, 0.42));
|
|
1925
|
+
halo.addColorStop(1, colorToRgba(glowColor, 0));
|
|
1926
|
+
ctx.save();
|
|
1927
|
+
ctx.globalCompositeOperation = "screen";
|
|
1928
|
+
ctx.fillStyle = halo;
|
|
1929
|
+
ctx.beginPath();
|
|
1930
|
+
ctx.arc(projected.x, projected.y, radius, 0, Math.PI * 2);
|
|
1931
|
+
ctx.fill();
|
|
1932
|
+
ctx.restore();
|
|
1933
|
+
|
|
1934
|
+
ctx.fillStyle = colorToRgba(coreColor, 0.98);
|
|
1935
|
+
ctx.beginPath();
|
|
1936
|
+
ctx.arc(projected.x, projected.y, Math.max(1.2, radius * 0.16), 0, Math.PI * 2);
|
|
1937
|
+
ctx.fill();
|
|
1938
|
+
|
|
1939
|
+
if (state && reflectionStrength > 0) {
|
|
1940
|
+
const waterline = sampleWave(state, point.x, point.z, state.time) * 0.22;
|
|
1941
|
+
const reflectedPoint = vec3(point.x, waterline - (point.y - waterline) * 0.58, point.z + 0.08);
|
|
1942
|
+
const reflected = projectPoint(reflectedPoint, camera, viewport);
|
|
1943
|
+
if (reflected) {
|
|
1944
|
+
const reflectionRadius = radius * 0.72;
|
|
1945
|
+
const glow = ctx.createRadialGradient(
|
|
1946
|
+
reflected.x,
|
|
1947
|
+
reflected.y,
|
|
1948
|
+
reflectionRadius * 0.1,
|
|
1949
|
+
reflected.x,
|
|
1950
|
+
reflected.y,
|
|
1951
|
+
reflectionRadius
|
|
1952
|
+
);
|
|
1953
|
+
glow.addColorStop(0, colorToRgba(coreColor, reflectionStrength * 0.34));
|
|
1954
|
+
glow.addColorStop(1, colorToRgba(glowColor, 0));
|
|
1955
|
+
ctx.save();
|
|
1956
|
+
ctx.globalCompositeOperation = "screen";
|
|
1957
|
+
ctx.fillStyle = glow;
|
|
1958
|
+
ctx.beginPath();
|
|
1959
|
+
ctx.ellipse(
|
|
1960
|
+
reflected.x,
|
|
1961
|
+
reflected.y,
|
|
1962
|
+
reflectionRadius * 0.34,
|
|
1963
|
+
reflectionRadius,
|
|
1964
|
+
0,
|
|
1965
|
+
0,
|
|
1966
|
+
Math.PI * 2
|
|
1967
|
+
);
|
|
1968
|
+
ctx.fill();
|
|
1969
|
+
ctx.restore();
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
function renderShipLanterns(ctx, ship, state, camera, viewport, visuals) {
|
|
1975
|
+
const lanterns = Array.isArray(ship.lanterns) ? ship.lanterns : SHIP_LANTERNS;
|
|
1976
|
+
const strength = readVisualNumber(ship.lanternStrength, 1);
|
|
1977
|
+
for (const lantern of lanterns) {
|
|
1978
|
+
const position = transformPoint(
|
|
1979
|
+
vec3(lantern.x, lantern.y, lantern.z),
|
|
1980
|
+
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE }
|
|
1981
|
+
);
|
|
1982
|
+
renderGlowLight(
|
|
1983
|
+
ctx,
|
|
1984
|
+
position,
|
|
1985
|
+
camera,
|
|
1986
|
+
viewport,
|
|
1987
|
+
visuals.lanternCore,
|
|
1988
|
+
visuals.lanternGlow,
|
|
1989
|
+
lantern.glow * strength,
|
|
1990
|
+
visuals.lanternReflectionStrength,
|
|
1991
|
+
state
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
function renderHarborTorches(ctx, state, camera, viewport, visuals) {
|
|
1997
|
+
for (const torch of HARBOR_TORCHES) {
|
|
1998
|
+
renderGlowLight(
|
|
1999
|
+
ctx,
|
|
2000
|
+
vec3(torch.x, torch.y, torch.z),
|
|
2001
|
+
camera,
|
|
2002
|
+
viewport,
|
|
2003
|
+
visuals.torchCore,
|
|
2004
|
+
visuals.torchGlow,
|
|
2005
|
+
torch.glow,
|
|
2006
|
+
visuals.lanternReflectionStrength * 0.55,
|
|
2007
|
+
state
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1570
2012
|
function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
1571
2013
|
const viewport = { width: canvas.width, height: canvas.height };
|
|
1572
2014
|
const camera = buildCamera(state, canvas);
|
|
@@ -1576,7 +2018,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
1576
2018
|
importance: state.focus === "lighting" ? "critical" : "high",
|
|
1577
2019
|
});
|
|
1578
2020
|
const nearLighting = lightingPlan.bands.find((entry) => entry.band === "near") ?? lightingPlan.bands[0];
|
|
1579
|
-
const lightDir = normalizeVec3(vec3(-0.
|
|
2021
|
+
const lightDir = normalizeVec3(vec3(-0.22, 0.94, -0.31));
|
|
1580
2022
|
const lightingSnapshot = state.lightingDetail.getSnapshot();
|
|
1581
2023
|
const visuals = resolveVisualConfig(
|
|
1582
2024
|
nearLighting,
|
|
@@ -1653,7 +2095,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
1653
2095
|
for (const ship of state.ships) {
|
|
1654
2096
|
buildTrianglesFromMesh(
|
|
1655
2097
|
shipModel,
|
|
1656
|
-
{ position: ship.position, rotationY: ship.rotationY, scale:
|
|
2098
|
+
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE },
|
|
1657
2099
|
ship.tint,
|
|
1658
2100
|
camera,
|
|
1659
2101
|
viewport,
|
|
@@ -1669,10 +2111,12 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
1669
2111
|
|
|
1670
2112
|
drawTriangles(ctx, triangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
1671
2113
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
2114
|
+
renderHarborTorches(ctx, state, camera, viewport, visuals);
|
|
1672
2115
|
renderFlagPole(ctx, camera, viewport);
|
|
1673
2116
|
renderClothAccent(ctx, cloth, camera, viewport);
|
|
1674
2117
|
for (const ship of state.ships) {
|
|
1675
2118
|
renderShipRigging(ctx, ship, camera, viewport);
|
|
2119
|
+
renderShipLanterns(ctx, ship, state, camera, viewport, visuals);
|
|
1676
2120
|
}
|
|
1677
2121
|
renderSprays(ctx, state.sprays, camera, viewport);
|
|
1678
2122
|
|
|
@@ -1686,8 +2130,10 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
1686
2130
|
const sceneMetrics = [
|
|
1687
2131
|
`focus: ${state.focus}`,
|
|
1688
2132
|
`ships: ${state.ships.length} active GLTF hulls`,
|
|
2133
|
+
`moonlight: cold overhead key + ${HARBOR_TORCHES.length + state.ships.length * SHIP_LANTERNS.length} warm deck and harbor lights`,
|
|
1689
2134
|
`physics snapshot: ${state.physics.snapshot.stage} (${state.physics.snapshot.stability})`,
|
|
1690
|
-
`physics contacts: ${state.
|
|
2135
|
+
`physics contacts: ${state.contactCount}`,
|
|
2136
|
+
`mass split: ${state.ships.map((ship) => `${ship.id} ${(getShipMass(ship, shipModel) / 1000).toFixed(1)}t`).join(" · ")}`,
|
|
1691
2137
|
`cloth band: ${cloth.band} -> ${cloth.representation.output}`,
|
|
1692
2138
|
`fluid near band: ${water.bandMeshes[0].representation.output}`,
|
|
1693
2139
|
`lighting profile: ${lightingPlan.profile} (${lightingDistanceBands.length} bands)`,
|
|
@@ -1712,8 +2158,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
1712
2158
|
state.focus === "physics"
|
|
1713
2159
|
? [
|
|
1714
2160
|
"Stable world snapshots are taken after the authoritative rigid-body commit and before visual follow-up work.",
|
|
1715
|
-
"The ships collide
|
|
1716
|
-
"
|
|
2161
|
+
"The ships collide with mass-weighted impulses and positional correction, so the heavier hull keeps more of its line.",
|
|
2162
|
+
"Moonlight keeps the overall read legible while lanterns and torches make collision moments easy to track against the water.",
|
|
1717
2163
|
]
|
|
1718
2164
|
: SCENE_NOTES;
|
|
1719
2165
|
const custom = state.demoDescription ?? null;
|
|
@@ -1740,8 +2186,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
1740
2186
|
typeof custom?.details === "string"
|
|
1741
2187
|
? custom.details
|
|
1742
2188
|
: state.focus === "physics"
|
|
1743
|
-
? `Stable world snapshots are emitted from ${state.physics.plan.snapshotStageId} after the authoritative solver;
|
|
1744
|
-
: `GLTF ships
|
|
2189
|
+
? `Stable world snapshots are emitted from ${state.physics.plan.snapshotStageId} after the authoritative solver; the heavier hull now carries momentum through mass-aware collision impulses while cloth and fluid remain downstream.`
|
|
2190
|
+
: `Moonlit GLTF ships collide on ${shipModel.physics.shape ?? "box"} physics volumes; lantern reflections, cloth, and fluid remain continuous while the governor pressure is ${state.lastDecision.pressureLevel}.`;
|
|
1745
2191
|
}
|
|
1746
2192
|
|
|
1747
2193
|
function updateSceneState(state, dt, shipModel) {
|
|
@@ -1763,6 +2209,8 @@ function syncTextState(state, shipModel) {
|
|
|
1763
2209
|
z: Number(ship.position.z.toFixed(2)),
|
|
1764
2210
|
vx: Number(ship.velocity.x.toFixed(2)),
|
|
1765
2211
|
vz: Number(ship.velocity.z.toFixed(2)),
|
|
2212
|
+
massKg: Math.round(getShipMass(ship, shipModel)),
|
|
2213
|
+
lanterns: Array.isArray(ship.lanterns) ? ship.lanterns.length : 0,
|
|
1766
2214
|
})),
|
|
1767
2215
|
shipPhysics: shipModel.physics,
|
|
1768
2216
|
sprays: state.sprays.length,
|
|
@@ -1791,6 +2239,7 @@ function syncTextState(state, shipModel) {
|
|
|
1791
2239
|
export async function mountGpuShowcase(options = {}) {
|
|
1792
2240
|
injectStyles();
|
|
1793
2241
|
const root = options.root ?? document.body;
|
|
2242
|
+
root.classList?.add?.(ROOT_CLASS);
|
|
1794
2243
|
const previousMarkup = root.innerHTML;
|
|
1795
2244
|
const previousRenderGameToText = window.render_game_to_text;
|
|
1796
2245
|
const previousAdvanceTime = window.advanceTime;
|
|
@@ -1813,14 +2262,15 @@ export async function mountGpuShowcase(options = {}) {
|
|
|
1813
2262
|
syncTextState(state, shipModel);
|
|
1814
2263
|
|
|
1815
2264
|
const ctx = dom.canvas.getContext("2d");
|
|
2265
|
+
if (!ctx) {
|
|
2266
|
+
throw new Error("2D canvas context is required for the shared showcase.");
|
|
2267
|
+
}
|
|
2268
|
+
let animationFrameId = 0;
|
|
1816
2269
|
let destroyed = false;
|
|
1817
|
-
let frameHandle = null;
|
|
1818
|
-
|
|
1819
2270
|
const renderFrame = (nowMs) => {
|
|
1820
2271
|
if (destroyed) {
|
|
1821
2272
|
return;
|
|
1822
2273
|
}
|
|
1823
|
-
|
|
1824
2274
|
if (!state.paused) {
|
|
1825
2275
|
if (state.lastTimeMs == null) {
|
|
1826
2276
|
state.lastTimeMs = nowMs;
|
|
@@ -1838,7 +2288,7 @@ export async function mountGpuShowcase(options = {}) {
|
|
|
1838
2288
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
1839
2289
|
renderScene(ctx, dom.canvas, state, shipModel, dom);
|
|
1840
2290
|
syncTextState(state, shipModel);
|
|
1841
|
-
|
|
2291
|
+
animationFrameId = requestAnimationFrame(renderFrame);
|
|
1842
2292
|
};
|
|
1843
2293
|
|
|
1844
2294
|
const handlePauseClick = () => {
|
|
@@ -1860,37 +2310,43 @@ export async function mountGpuShowcase(options = {}) {
|
|
|
1860
2310
|
dom.stressToggle.addEventListener("change", handleStressChange);
|
|
1861
2311
|
dom.focusMode.addEventListener("change", handleFocusChange);
|
|
1862
2312
|
|
|
1863
|
-
|
|
2313
|
+
animationFrameId = requestAnimationFrame(renderFrame);
|
|
2314
|
+
const destroy = () => {
|
|
2315
|
+
if (destroyed) {
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
destroyed = true;
|
|
2319
|
+
if (animationFrameId) {
|
|
2320
|
+
cancelAnimationFrame(animationFrameId);
|
|
2321
|
+
}
|
|
2322
|
+
dom.pauseButton.removeEventListener("click", handlePauseClick);
|
|
2323
|
+
dom.stressToggle.removeEventListener("change", handleStressChange);
|
|
2324
|
+
dom.focusMode.removeEventListener("change", handleFocusChange);
|
|
2325
|
+
try {
|
|
2326
|
+
if (typeof options.destroyState === "function") {
|
|
2327
|
+
options.destroyState(state.packageState);
|
|
2328
|
+
}
|
|
2329
|
+
} finally {
|
|
2330
|
+
state.packageState = undefined;
|
|
2331
|
+
}
|
|
2332
|
+
root.classList?.remove?.(ROOT_CLASS);
|
|
2333
|
+
root.innerHTML = previousMarkup;
|
|
2334
|
+
if (typeof previousRenderGameToText === "function") {
|
|
2335
|
+
window.render_game_to_text = previousRenderGameToText;
|
|
2336
|
+
} else {
|
|
2337
|
+
delete window.render_game_to_text;
|
|
2338
|
+
}
|
|
2339
|
+
if (typeof previousAdvanceTime === "function") {
|
|
2340
|
+
window.advanceTime = previousAdvanceTime;
|
|
2341
|
+
} else {
|
|
2342
|
+
delete window.advanceTime;
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
1864
2345
|
return {
|
|
1865
2346
|
state,
|
|
1866
2347
|
shipModel,
|
|
1867
2348
|
canvas: dom.canvas,
|
|
1868
|
-
destroy
|
|
1869
|
-
if (destroyed) {
|
|
1870
|
-
return;
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
destroyed = true;
|
|
1874
|
-
if (frameHandle != null) {
|
|
1875
|
-
cancelAnimationFrame(frameHandle);
|
|
1876
|
-
}
|
|
1877
|
-
dom.pauseButton.removeEventListener("click", handlePauseClick);
|
|
1878
|
-
dom.stressToggle.removeEventListener("change", handleStressChange);
|
|
1879
|
-
dom.focusMode.removeEventListener("change", handleFocusChange);
|
|
1880
|
-
root.innerHTML = previousMarkup;
|
|
1881
|
-
|
|
1882
|
-
if (typeof previousRenderGameToText === "function") {
|
|
1883
|
-
window.render_game_to_text = previousRenderGameToText;
|
|
1884
|
-
} else {
|
|
1885
|
-
delete window.render_game_to_text;
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
if (typeof previousAdvanceTime === "function") {
|
|
1889
|
-
window.advanceTime = previousAdvanceTime;
|
|
1890
|
-
} else {
|
|
1891
|
-
delete window.advanceTime;
|
|
1892
|
-
}
|
|
1893
|
-
},
|
|
2349
|
+
destroy,
|
|
1894
2350
|
};
|
|
1895
2351
|
}
|
|
1896
2352
|
|
|
@@ -1905,7 +2361,7 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
1905
2361
|
animationInputRevision: state.frame,
|
|
1906
2362
|
bodyCount: state.ships.length + 2,
|
|
1907
2363
|
dynamicBodyCount: state.ships.length,
|
|
1908
|
-
contactCount: state.
|
|
2364
|
+
contactCount: state.contactCount,
|
|
1909
2365
|
metadata: {
|
|
1910
2366
|
collisionCount: state.collisionCount,
|
|
1911
2367
|
contactCount: state.contactCount,
|