@plasius/gpu-shared 0.1.4 → 0.1.6
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 +17 -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 +583 -168
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{showcase-runtime-4BS7TWHS.js → showcase-runtime-JZIYGQAU.js} +531 -164
- package/dist/{showcase-runtime-4BS7TWHS.js.map → showcase-runtime-JZIYGQAU.js.map} +1 -1
- package/package.json +2 -2
- 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/{gltf-loader-4FNTT63R.js.map → gltf-loader-LKALCZAV.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -20,11 +20,32 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
20
20
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
21
|
|
|
22
22
|
// src/asset-url.js
|
|
23
|
+
function createInlineShowcaseAssetUrl() {
|
|
24
|
+
return new URL(INLINE_BRIGANTINE_GLTF_URL);
|
|
25
|
+
}
|
|
26
|
+
function getBrowserBaseUrl() {
|
|
27
|
+
if (typeof document !== "undefined" && typeof document.baseURI === "string" && document.baseURI.length > 0) {
|
|
28
|
+
return document.baseURI;
|
|
29
|
+
}
|
|
30
|
+
if (typeof window !== "undefined" && typeof window.location?.href === "string" && window.location.href.length > 0) {
|
|
31
|
+
return window.location.href;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
23
35
|
function resolveShowcaseAssetUrl(baseUrl2 = import_meta.url) {
|
|
24
36
|
try {
|
|
25
37
|
return new URL("../assets/brigantine.gltf", baseUrl2);
|
|
26
38
|
} catch {
|
|
27
|
-
|
|
39
|
+
const browserBaseUrl = getBrowserBaseUrl();
|
|
40
|
+
if (browserBaseUrl) {
|
|
41
|
+
try {
|
|
42
|
+
const normalizedBaseUrl = new URL(baseUrl2, browserBaseUrl);
|
|
43
|
+
return new URL("../assets/brigantine.gltf", normalizedBaseUrl);
|
|
44
|
+
} catch {
|
|
45
|
+
return createInlineShowcaseAssetUrl();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return createInlineShowcaseAssetUrl();
|
|
28
49
|
}
|
|
29
50
|
}
|
|
30
51
|
var import_meta, INLINE_BRIGANTINE_GLTF_URL;
|
|
@@ -110,12 +131,39 @@ function computeBounds(positions) {
|
|
|
110
131
|
}
|
|
111
132
|
return { min, max };
|
|
112
133
|
}
|
|
134
|
+
function resolveBrowserRequestBaseUrl() {
|
|
135
|
+
if (typeof document !== "undefined" && typeof document.baseURI === "string" && document.baseURI.length > 0) {
|
|
136
|
+
return document.baseURI;
|
|
137
|
+
}
|
|
138
|
+
if (typeof window !== "undefined" && typeof window.location?.href === "string" && window.location.href.length > 0) {
|
|
139
|
+
return window.location.href;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
function resolveFetchBaseUrl(requestUrl, responseUrl) {
|
|
144
|
+
if (typeof responseUrl === "string" && responseUrl.length > 0) {
|
|
145
|
+
try {
|
|
146
|
+
return new URL(responseUrl);
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
return new URL(requestUrl);
|
|
152
|
+
} catch {
|
|
153
|
+
const browserBaseUrl = resolveBrowserRequestBaseUrl();
|
|
154
|
+
if (browserBaseUrl) {
|
|
155
|
+
return new URL(requestUrl, browserBaseUrl);
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`Unable to resolve a stable base URL for glTF asset loading: ${String(requestUrl)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
113
160
|
async function loadGltfModel(url) {
|
|
114
161
|
const response = await fetch(url);
|
|
115
162
|
if (!response.ok) {
|
|
116
163
|
throw new Error(`Failed to load glTF asset: ${response.status} ${response.statusText}`);
|
|
117
164
|
}
|
|
118
165
|
const document2 = await response.json();
|
|
166
|
+
const baseUrl2 = resolveFetchBaseUrl(url, response.url);
|
|
119
167
|
const buffers = await Promise.all(
|
|
120
168
|
(document2.buffers ?? []).map(async (buffer) => {
|
|
121
169
|
if (typeof buffer.uri !== "string") {
|
|
@@ -124,7 +172,7 @@ async function loadGltfModel(url) {
|
|
|
124
172
|
if (buffer.uri.startsWith("data:")) {
|
|
125
173
|
return decodeDataUri(buffer.uri);
|
|
126
174
|
}
|
|
127
|
-
const nested = await fetch(new URL(buffer.uri,
|
|
175
|
+
const nested = await fetch(new URL(buffer.uri, baseUrl2));
|
|
128
176
|
if (!nested.ok) {
|
|
129
177
|
throw new Error(`Failed to load glTF buffer: ${nested.status} ${nested.statusText}`);
|
|
130
178
|
}
|
|
@@ -5107,7 +5155,7 @@ var init_dist5 = __esm({
|
|
|
5107
5155
|
}
|
|
5108
5156
|
});
|
|
5109
5157
|
|
|
5110
|
-
// node_modules/@plasius/gpu-physics/dist/chunk-
|
|
5158
|
+
// node_modules/@plasius/gpu-physics/dist/chunk-PFUNZLNF.js
|
|
5111
5159
|
function assertIdentifier5(name, value) {
|
|
5112
5160
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
5113
5161
|
throw new Error(`${name} must be a non-empty string.`);
|
|
@@ -5256,7 +5304,7 @@ function getPhysicsWorkerManifest(profile = defaultPhysicsWorkerProfile) {
|
|
|
5256
5304
|
return manifest;
|
|
5257
5305
|
}
|
|
5258
5306
|
function createPhysicsSimulationPlan(profile = defaultPhysicsWorkerProfile) {
|
|
5259
|
-
const plan =
|
|
5307
|
+
const plan = physicsSimulationPlans[profile];
|
|
5260
5308
|
if (!plan) {
|
|
5261
5309
|
const available = physicsWorkerProfileNames.join(", ");
|
|
5262
5310
|
throw new Error(
|
|
@@ -5320,9 +5368,9 @@ function createPhysicsWorldSnapshot(input) {
|
|
|
5320
5368
|
metadata: normalizeMetadata(input.metadata)
|
|
5321
5369
|
});
|
|
5322
5370
|
}
|
|
5323
|
-
var physicsDebugOwner, physicsWorkerQueueClasses, defaultPhysicsWorkerProfile, physicsSimulationStageOrder, physicsWorkerProfileSpecs, physicsWorkerDagSpecs,
|
|
5324
|
-
var
|
|
5325
|
-
"node_modules/@plasius/gpu-physics/dist/chunk-
|
|
5371
|
+
var physicsDebugOwner, physicsWorkerQueueClasses, defaultPhysicsWorkerProfile, physicsSimulationStageOrder, physicsWorkerProfileSpecs, physicsWorkerDagSpecs, physicsSimulationPlans, physicsWorkerManifests, physicsWorkerProfileNames;
|
|
5372
|
+
var init_chunk_PFUNZLNF = __esm({
|
|
5373
|
+
"node_modules/@plasius/gpu-physics/dist/chunk-PFUNZLNF.js"() {
|
|
5326
5374
|
physicsDebugOwner = "physics";
|
|
5327
5375
|
physicsWorkerQueueClasses = Object.freeze({
|
|
5328
5376
|
simulation: "simulation",
|
|
@@ -6068,7 +6116,7 @@ var init_chunk_CNTXT5QJ = __esm({
|
|
|
6068
6116
|
contactVisuals: { priority: 1, dependencies: ["worldSnapshot"] }
|
|
6069
6117
|
}
|
|
6070
6118
|
};
|
|
6071
|
-
|
|
6119
|
+
physicsSimulationPlans = Object.freeze({
|
|
6072
6120
|
gameplay: Object.freeze({
|
|
6073
6121
|
description: "Gameplay simulation plan that hands off a stable post-commit world snapshot to visual preparation.",
|
|
6074
6122
|
snapshotStageId: "worldSnapshot",
|
|
@@ -6284,7 +6332,7 @@ var init_chunk_CNTXT5QJ = __esm({
|
|
|
6284
6332
|
// node_modules/@plasius/gpu-physics/dist/browser.js
|
|
6285
6333
|
var init_browser = __esm({
|
|
6286
6334
|
"node_modules/@plasius/gpu-physics/dist/browser.js"() {
|
|
6287
|
-
|
|
6335
|
+
init_chunk_PFUNZLNF();
|
|
6288
6336
|
}
|
|
6289
6337
|
});
|
|
6290
6338
|
|
|
@@ -6301,27 +6349,27 @@ function injectStyles() {
|
|
|
6301
6349
|
const style = document.createElement("style");
|
|
6302
6350
|
style.id = STYLE_ID;
|
|
6303
6351
|
style.textContent = `
|
|
6304
|
-
|
|
6305
|
-
color-scheme:
|
|
6306
|
-
--plasius-paper: #
|
|
6307
|
-
--plasius-ink: #
|
|
6308
|
-
--plasius-muted: #
|
|
6309
|
-
--plasius-accent: #
|
|
6310
|
-
--plasius-panel: rgba(
|
|
6311
|
-
--plasius-border: rgba(
|
|
6312
|
-
--plasius-shadow: 0
|
|
6313
|
-
}
|
|
6314
|
-
* {
|
|
6315
|
-
box-sizing: border-box;
|
|
6316
|
-
}
|
|
6317
|
-
body {
|
|
6352
|
+
.${ROOT_CLASS} {
|
|
6353
|
+
color-scheme: dark;
|
|
6354
|
+
--plasius-paper: #081321;
|
|
6355
|
+
--plasius-ink: #edf4ff;
|
|
6356
|
+
--plasius-muted: #b6c5dd;
|
|
6357
|
+
--plasius-accent: #f3b16a;
|
|
6358
|
+
--plasius-panel: rgba(8, 19, 33, 0.72);
|
|
6359
|
+
--plasius-border: rgba(159, 185, 223, 0.18);
|
|
6360
|
+
--plasius-shadow: 0 24px 56px rgba(1, 6, 14, 0.44);
|
|
6318
6361
|
margin: 0;
|
|
6319
|
-
min-height:
|
|
6362
|
+
min-height: 100%;
|
|
6320
6363
|
font-family: "Fraunces", "Iowan Old Style", serif;
|
|
6321
6364
|
color: var(--plasius-ink);
|
|
6322
6365
|
background:
|
|
6323
|
-
radial-gradient(circle at
|
|
6324
|
-
|
|
6366
|
+
radial-gradient(circle at 18% 12%, rgba(73, 101, 170, 0.28), transparent 30%),
|
|
6367
|
+
radial-gradient(circle at 82% 18%, rgba(240, 188, 103, 0.08), transparent 18%),
|
|
6368
|
+
linear-gradient(180deg, #04101d 0%, #0b1930 42%, #081321 100%);
|
|
6369
|
+
}
|
|
6370
|
+
.${ROOT_CLASS},
|
|
6371
|
+
.${ROOT_CLASS} * {
|
|
6372
|
+
box-sizing: border-box;
|
|
6325
6373
|
}
|
|
6326
6374
|
.plasius-demo {
|
|
6327
6375
|
width: min(1560px, calc(100vw - 32px));
|
|
@@ -6355,7 +6403,7 @@ function injectStyles() {
|
|
|
6355
6403
|
text-transform: uppercase;
|
|
6356
6404
|
letter-spacing: 0.18em;
|
|
6357
6405
|
font-size: 12px;
|
|
6358
|
-
color: rgba(
|
|
6406
|
+
color: rgba(226, 236, 255, 0.58);
|
|
6359
6407
|
}
|
|
6360
6408
|
.plasius-demo h1,
|
|
6361
6409
|
.plasius-demo h2,
|
|
@@ -6373,7 +6421,7 @@ function injectStyles() {
|
|
|
6373
6421
|
margin: 0;
|
|
6374
6422
|
padding: 8px 12px;
|
|
6375
6423
|
border-radius: 999px;
|
|
6376
|
-
background: rgba(
|
|
6424
|
+
background: rgba(243, 177, 106, 0.14);
|
|
6377
6425
|
color: var(--plasius-accent);
|
|
6378
6426
|
font-weight: 700;
|
|
6379
6427
|
}
|
|
@@ -6395,8 +6443,8 @@ function injectStyles() {
|
|
|
6395
6443
|
aspect-ratio: 16 / 9;
|
|
6396
6444
|
display: block;
|
|
6397
6445
|
border-radius: 20px;
|
|
6398
|
-
border: 1px solid rgba(
|
|
6399
|
-
background: linear-gradient(180deg, #
|
|
6446
|
+
border: 1px solid rgba(159, 185, 223, 0.12);
|
|
6447
|
+
background: linear-gradient(180deg, #071220 0%, #132440 42%, #10344b 42%, #05111d 100%);
|
|
6400
6448
|
}
|
|
6401
6449
|
.plasius-demo__toolbar {
|
|
6402
6450
|
position: absolute;
|
|
@@ -6416,9 +6464,9 @@ function injectStyles() {
|
|
|
6416
6464
|
.plasius-demo button,
|
|
6417
6465
|
.plasius-demo .plasius-toggle,
|
|
6418
6466
|
.plasius-demo select {
|
|
6419
|
-
border: 1px solid rgba(
|
|
6467
|
+
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
6420
6468
|
border-radius: 999px;
|
|
6421
|
-
background: rgba(
|
|
6469
|
+
background: rgba(9, 20, 34, 0.84);
|
|
6422
6470
|
color: var(--plasius-ink);
|
|
6423
6471
|
padding: 10px 14px;
|
|
6424
6472
|
}
|
|
@@ -6457,8 +6505,8 @@ function injectStyles() {
|
|
|
6457
6505
|
bottom: 24px;
|
|
6458
6506
|
padding: 10px 14px;
|
|
6459
6507
|
border-radius: 16px;
|
|
6460
|
-
background: rgba(
|
|
6461
|
-
border: 1px solid rgba(
|
|
6508
|
+
background: rgba(9, 20, 34, 0.82);
|
|
6509
|
+
border: 1px solid rgba(159, 185, 223, 0.16);
|
|
6462
6510
|
color: var(--plasius-muted);
|
|
6463
6511
|
font-size: 12px;
|
|
6464
6512
|
line-height: 1.45;
|
|
@@ -6470,7 +6518,7 @@ function injectStyles() {
|
|
|
6470
6518
|
}
|
|
6471
6519
|
.plasius-demo__footer {
|
|
6472
6520
|
margin-top: 4px;
|
|
6473
|
-
color: rgba(
|
|
6521
|
+
color: rgba(226, 236, 255, 0.68);
|
|
6474
6522
|
font-size: 13px;
|
|
6475
6523
|
line-height: 1.6;
|
|
6476
6524
|
}
|
|
@@ -6489,6 +6537,10 @@ function clamp(value, min, max) {
|
|
|
6489
6537
|
function mix(a, b, t) {
|
|
6490
6538
|
return a + (b - a) * t;
|
|
6491
6539
|
}
|
|
6540
|
+
function pseudoRandom(seed) {
|
|
6541
|
+
const value = Math.sin(seed * 12.9898 + seed * seed * 17e-4) * 43758.5453;
|
|
6542
|
+
return value - Math.floor(value);
|
|
6543
|
+
}
|
|
6492
6544
|
function vec3(x = 0, y = 0, z = 0) {
|
|
6493
6545
|
return { x, y, z };
|
|
6494
6546
|
}
|
|
@@ -6522,6 +6574,12 @@ function reflectVec3(vector, normal) {
|
|
|
6522
6574
|
const unitNormal = normalizeVec3(normal);
|
|
6523
6575
|
return subVec3(vector, scaleVec3(unitNormal, 2 * dotVec3(vector, unitNormal)));
|
|
6524
6576
|
}
|
|
6577
|
+
function directionFromYaw(yaw) {
|
|
6578
|
+
return normalizeVec3(vec3(Math.sin(yaw), 0, Math.cos(yaw)));
|
|
6579
|
+
}
|
|
6580
|
+
function perpendicularOnWater(direction) {
|
|
6581
|
+
return vec3(-direction.z, 0, direction.x);
|
|
6582
|
+
}
|
|
6525
6583
|
function rotateY(point, angle) {
|
|
6526
6584
|
const cosine = Math.cos(angle);
|
|
6527
6585
|
const sine = Math.sin(angle);
|
|
@@ -6704,7 +6762,7 @@ function buildDemoDom(root, options) {
|
|
|
6704
6762
|
<section class="plasius-panel plasius-demo__status">
|
|
6705
6763
|
<p id="demoStatus" class="plasius-demo__status-badge">Booting 3D scene\u2026</p>
|
|
6706
6764
|
<p id="demoDetails" class="plasius-demo__status-text">
|
|
6707
|
-
Preparing GLTF
|
|
6765
|
+
Preparing a moonlit harbor scene, GLTF hull data, cloth and fluid continuity plans, and adaptive quality metadata.
|
|
6708
6766
|
</p>
|
|
6709
6767
|
</section>
|
|
6710
6768
|
</section>
|
|
@@ -6732,9 +6790,9 @@ function buildDemoDom(root, options) {
|
|
|
6732
6790
|
</div>
|
|
6733
6791
|
<div class="plasius-demo__legend">
|
|
6734
6792
|
<strong>Scene</strong>
|
|
6735
|
-
GLTF ships carry
|
|
6736
|
-
|
|
6737
|
-
|
|
6793
|
+
GLTF ships carry hull mass and damping metadata.<br />
|
|
6794
|
+
Lanterns and torches warm the moonlit harbor.<br />
|
|
6795
|
+
Mass-aware collisions stay authoritative near the camera.
|
|
6738
6796
|
</div>
|
|
6739
6797
|
</section>
|
|
6740
6798
|
<aside class="plasius-demo__sidebar">
|
|
@@ -6815,6 +6873,7 @@ function buildSceneSnapshot(state, shipModel) {
|
|
|
6815
6873
|
})
|
|
6816
6874
|
)
|
|
6817
6875
|
),
|
|
6876
|
+
shipPhysics: shipModel?.physics ?? null,
|
|
6818
6877
|
physics: Object.freeze({
|
|
6819
6878
|
profile: state.physics.profile,
|
|
6820
6879
|
plan: state.physics.plan,
|
|
@@ -6858,28 +6917,39 @@ function readVisualNumber(value, fallback) {
|
|
|
6858
6917
|
function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {}) {
|
|
6859
6918
|
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
6860
6919
|
const defaults = {
|
|
6861
|
-
skyTop: premiumShadows ? "#
|
|
6862
|
-
skyMid: premiumShadows ? "#
|
|
6863
|
-
skyBottom: premiumShadows ? "#
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6920
|
+
skyTop: premiumShadows ? "#040c1a" : "#06101f",
|
|
6921
|
+
skyMid: premiumShadows ? "#11203b" : "#152643",
|
|
6922
|
+
skyBottom: premiumShadows ? "#2f4468" : "#364d73",
|
|
6923
|
+
duskGlow: premiumShadows ? "rgba(116, 142, 201, 0.26)" : "rgba(104, 128, 188, 0.22)",
|
|
6924
|
+
seaTop: premiumShadows ? "#102946" : "#153050",
|
|
6925
|
+
seaMid: premiumShadows ? "#0a1d33" : "#0d2138",
|
|
6926
|
+
seaBottom: "#04101d",
|
|
6927
|
+
moonCore: "rgba(241, 246, 255, 0.98)",
|
|
6928
|
+
moonHalo: "rgba(167, 191, 255, 0.24)",
|
|
6929
|
+
moonReflection: "rgba(192, 214, 255, 0.22)",
|
|
6930
|
+
starColor: "rgba(232, 239, 255, 0.82)",
|
|
6931
|
+
ambientMist: "rgba(41, 63, 97, 0.16)",
|
|
6868
6932
|
reflectionStrength: lightingSnapshot.currentLevel.config.reflectionStrength,
|
|
6869
6933
|
shadowAccent: lightingSnapshot.currentLevel.config.shadowStrength,
|
|
6870
|
-
waveAmplitude:
|
|
6871
|
-
waveDirection: { x: 0.
|
|
6872
|
-
wavePhaseSpeed:
|
|
6873
|
-
wakeStrength: 0.
|
|
6874
|
-
wakeLength:
|
|
6875
|
-
collisionRippleStrength: 0.
|
|
6876
|
-
waterNear: { r: 0.
|
|
6877
|
-
waterFar: { r: 0.
|
|
6878
|
-
harborWall: { r: 0.
|
|
6879
|
-
harborDeck: { r: 0.
|
|
6880
|
-
harborTower: { r: 0.
|
|
6881
|
-
flagColor: { r: 0.
|
|
6882
|
-
flagMotion:
|
|
6934
|
+
waveAmplitude: 0.94,
|
|
6935
|
+
waveDirection: { x: 0.88, z: 0.28 },
|
|
6936
|
+
wavePhaseSpeed: 0.88,
|
|
6937
|
+
wakeStrength: 0.31,
|
|
6938
|
+
wakeLength: 18,
|
|
6939
|
+
collisionRippleStrength: 0.42,
|
|
6940
|
+
waterNear: { r: 0.08, g: 0.23, b: 0.33 },
|
|
6941
|
+
waterFar: { r: 0.18, g: 0.35, b: 0.49 },
|
|
6942
|
+
harborWall: { r: 0.26, g: 0.24, b: 0.28 },
|
|
6943
|
+
harborDeck: { r: 0.33, g: 0.22, b: 0.16 },
|
|
6944
|
+
harborTower: { r: 0.23, g: 0.24, b: 0.29 },
|
|
6945
|
+
flagColor: { r: 0.66, g: 0.16, b: 0.13 },
|
|
6946
|
+
flagMotion: 0.92,
|
|
6947
|
+
lanternCore: { r: 0.98, g: 0.8, b: 0.48 },
|
|
6948
|
+
lanternGlow: { r: 1, g: 0.56, b: 0.2 },
|
|
6949
|
+
lanternReflectionStrength: 0.42,
|
|
6950
|
+
torchCore: { r: 0.99, g: 0.72, b: 0.36 },
|
|
6951
|
+
torchGlow: { r: 0.98, g: 0.38, b: 0.15 },
|
|
6952
|
+
collisionFlash: "rgba(255, 212, 168, 0.16)"
|
|
6883
6953
|
};
|
|
6884
6954
|
return {
|
|
6885
6955
|
skyTop: typeof customVisuals.skyTop === "string" ? customVisuals.skyTop : defaults.skyTop,
|
|
@@ -6888,7 +6958,12 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
6888
6958
|
seaTop: typeof customVisuals.seaTop === "string" ? customVisuals.seaTop : defaults.seaTop,
|
|
6889
6959
|
seaMid: typeof customVisuals.seaMid === "string" ? customVisuals.seaMid : defaults.seaMid,
|
|
6890
6960
|
seaBottom: typeof customVisuals.seaBottom === "string" ? customVisuals.seaBottom : defaults.seaBottom,
|
|
6891
|
-
|
|
6961
|
+
duskGlow: typeof customVisuals.duskGlow === "string" ? customVisuals.duskGlow : defaults.duskGlow,
|
|
6962
|
+
moonCore: typeof customVisuals.moonCore === "string" ? customVisuals.moonCore : typeof customVisuals.sunCore === "string" ? customVisuals.sunCore : defaults.moonCore,
|
|
6963
|
+
moonHalo: typeof customVisuals.moonHalo === "string" ? customVisuals.moonHalo : defaults.moonHalo,
|
|
6964
|
+
moonReflection: typeof customVisuals.moonReflection === "string" ? customVisuals.moonReflection : defaults.moonReflection,
|
|
6965
|
+
starColor: typeof customVisuals.starColor === "string" ? customVisuals.starColor : defaults.starColor,
|
|
6966
|
+
ambientMist: typeof customVisuals.ambientMist === "string" ? customVisuals.ambientMist : defaults.ambientMist,
|
|
6892
6967
|
reflectionStrength: readVisualNumber(
|
|
6893
6968
|
customVisuals.reflectionStrength,
|
|
6894
6969
|
defaults.reflectionStrength
|
|
@@ -6909,7 +6984,16 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
6909
6984
|
harborDeck: normalizeColorOverride(customVisuals.harborDeck, defaults.harborDeck),
|
|
6910
6985
|
harborTower: normalizeColorOverride(customVisuals.harborTower, defaults.harborTower),
|
|
6911
6986
|
flagColor: normalizeColorOverride(customVisuals.flagColor, defaults.flagColor),
|
|
6912
|
-
flagMotion: readVisualNumber(customVisuals.flagMotion, defaults.flagMotion)
|
|
6987
|
+
flagMotion: readVisualNumber(customVisuals.flagMotion, defaults.flagMotion),
|
|
6988
|
+
lanternCore: normalizeColorOverride(customVisuals.lanternCore, defaults.lanternCore),
|
|
6989
|
+
lanternGlow: normalizeColorOverride(customVisuals.lanternGlow, defaults.lanternGlow),
|
|
6990
|
+
lanternReflectionStrength: readVisualNumber(
|
|
6991
|
+
customVisuals.lanternReflectionStrength,
|
|
6992
|
+
defaults.lanternReflectionStrength
|
|
6993
|
+
),
|
|
6994
|
+
torchCore: normalizeColorOverride(customVisuals.torchCore, defaults.torchCore),
|
|
6995
|
+
torchGlow: normalizeColorOverride(customVisuals.torchGlow, defaults.torchGlow),
|
|
6996
|
+
collisionFlash: typeof customVisuals.collisionFlash === "string" ? customVisuals.collisionFlash : defaults.collisionFlash
|
|
6913
6997
|
};
|
|
6914
6998
|
}
|
|
6915
6999
|
function buildClothSurface(model, state, meshDetail, visuals) {
|
|
@@ -7144,18 +7228,34 @@ function createSceneState(options) {
|
|
|
7144
7228
|
{
|
|
7145
7229
|
id: "northwind",
|
|
7146
7230
|
position: vec3(-5.2, 0, 7.2),
|
|
7147
|
-
velocity: vec3(2.
|
|
7148
|
-
rotationY: 0.
|
|
7149
|
-
angularVelocity: 0.
|
|
7150
|
-
tint: { r: 0.62, g: 0.39, b: 0.23 }
|
|
7231
|
+
velocity: vec3(2.35, 0, -1.08),
|
|
7232
|
+
rotationY: 0.58,
|
|
7233
|
+
angularVelocity: 0.09,
|
|
7234
|
+
tint: { r: 0.62, g: 0.39, b: 0.23 },
|
|
7235
|
+
massScale: 1.42,
|
|
7236
|
+
cruiseSpeed: 2.25,
|
|
7237
|
+
throttleResponse: 0.46,
|
|
7238
|
+
rudderResponse: 0.54,
|
|
7239
|
+
wanderPhase: 0.35,
|
|
7240
|
+
lanterns: SHIP_LANTERNS,
|
|
7241
|
+
lanternStrength: 1.06,
|
|
7242
|
+
collisionRadiusScale: 1.04
|
|
7151
7243
|
},
|
|
7152
7244
|
{
|
|
7153
7245
|
id: "tidecaller",
|
|
7154
7246
|
position: vec3(4.8, 0, 4.4),
|
|
7155
|
-
velocity: vec3(-
|
|
7156
|
-
rotationY: -2.
|
|
7157
|
-
angularVelocity: -0.
|
|
7158
|
-
tint: { r: 0.48, g: 0.28, b: 0.19 }
|
|
7247
|
+
velocity: vec3(-2.15, 0, 1.74),
|
|
7248
|
+
rotationY: -2.48,
|
|
7249
|
+
angularVelocity: -0.2,
|
|
7250
|
+
tint: { r: 0.48, g: 0.28, b: 0.19 },
|
|
7251
|
+
massScale: 0.84,
|
|
7252
|
+
cruiseSpeed: 2.68,
|
|
7253
|
+
throttleResponse: 0.7,
|
|
7254
|
+
rudderResponse: 0.78,
|
|
7255
|
+
wanderPhase: 1.6,
|
|
7256
|
+
lanterns: SHIP_LANTERNS,
|
|
7257
|
+
lanternStrength: 1.18,
|
|
7258
|
+
collisionRadiusScale: 0.94
|
|
7159
7259
|
}
|
|
7160
7260
|
],
|
|
7161
7261
|
sprays: [],
|
|
@@ -7177,41 +7277,71 @@ function setListContent(element, values) {
|
|
|
7177
7277
|
element.innerHTML = values.map((value) => `<li>${value}</li>`).join("");
|
|
7178
7278
|
}
|
|
7179
7279
|
function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, shadowStrength, visuals) {
|
|
7180
|
-
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
7181
7280
|
const sky = ctx.createLinearGradient(0, 0, 0, canvas.height * 0.5);
|
|
7182
7281
|
sky.addColorStop(0, visuals.skyTop);
|
|
7183
|
-
sky.addColorStop(0.
|
|
7282
|
+
sky.addColorStop(0.54, visuals.skyMid);
|
|
7184
7283
|
sky.addColorStop(1, visuals.skyBottom);
|
|
7185
7284
|
ctx.fillStyle = sky;
|
|
7186
7285
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
7286
|
+
for (let index = 0; index < 70; index += 1) {
|
|
7287
|
+
const x = pseudoRandom(index + 13) * canvas.width;
|
|
7288
|
+
const y = pseudoRandom(index * 7 + 5) * canvas.height * 0.42;
|
|
7289
|
+
const twinkle = 0.45 + Math.sin(state.time * 1.4 + index * 0.73) * 0.25;
|
|
7290
|
+
const radius = 0.6 + pseudoRandom(index * 11 + 2) * 1.9;
|
|
7291
|
+
ctx.fillStyle = visuals.starColor.replace(/[\d.]+\)$/u, `${clamp(twinkle, 0.16, 0.92)})`);
|
|
7292
|
+
ctx.beginPath();
|
|
7293
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
7294
|
+
ctx.fill();
|
|
7295
|
+
}
|
|
7296
|
+
const horizonGlow = ctx.createLinearGradient(0, canvas.height * 0.22, 0, canvas.height * 0.62);
|
|
7297
|
+
horizonGlow.addColorStop(0, "rgba(0, 0, 0, 0)");
|
|
7298
|
+
horizonGlow.addColorStop(1, visuals.duskGlow);
|
|
7299
|
+
ctx.fillStyle = horizonGlow;
|
|
7300
|
+
ctx.fillRect(0, canvas.height * 0.2, canvas.width, canvas.height * 0.45);
|
|
7187
7301
|
const shoreline = ctx.createLinearGradient(0, canvas.height * 0.45, 0, canvas.height);
|
|
7188
7302
|
shoreline.addColorStop(0, visuals.seaTop);
|
|
7189
7303
|
shoreline.addColorStop(0.52, visuals.seaMid);
|
|
7190
7304
|
shoreline.addColorStop(1, visuals.seaBottom);
|
|
7191
7305
|
ctx.fillStyle = shoreline;
|
|
7192
7306
|
ctx.fillRect(0, canvas.height * 0.45, canvas.width, canvas.height * 0.55);
|
|
7193
|
-
const
|
|
7194
|
-
const
|
|
7195
|
-
const
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7307
|
+
const moonX = canvas.width * 0.76 + Math.sin(state.time * 0.045) * 18;
|
|
7308
|
+
const moonY = canvas.height * 0.17 + Math.cos(state.time * 0.05) * 10;
|
|
7309
|
+
const moon = ctx.createRadialGradient(moonX, moonY, 14, moonX, moonY, 126);
|
|
7310
|
+
moon.addColorStop(0, visuals.moonCore);
|
|
7311
|
+
moon.addColorStop(0.46, visuals.moonHalo);
|
|
7312
|
+
moon.addColorStop(1, "rgba(167, 191, 255, 0)");
|
|
7313
|
+
ctx.fillStyle = moon;
|
|
7314
|
+
ctx.beginPath();
|
|
7315
|
+
ctx.arc(moonX, moonY, 94, 0, Math.PI * 2);
|
|
7316
|
+
ctx.fill();
|
|
7317
|
+
const moonCore = ctx.createRadialGradient(moonX, moonY, 4, moonX, moonY, 28);
|
|
7318
|
+
moonCore.addColorStop(0, "rgba(255, 255, 255, 0.98)");
|
|
7319
|
+
moonCore.addColorStop(1, visuals.moonCore);
|
|
7320
|
+
ctx.fillStyle = moonCore;
|
|
7199
7321
|
ctx.beginPath();
|
|
7200
|
-
ctx.arc(
|
|
7322
|
+
ctx.arc(moonX, moonY, 24, 0, Math.PI * 2);
|
|
7201
7323
|
ctx.fill();
|
|
7202
|
-
const track = ctx.createLinearGradient(
|
|
7203
|
-
track.addColorStop(0,
|
|
7204
|
-
track.addColorStop(0.42,
|
|
7205
|
-
track.addColorStop(1, "rgba(
|
|
7324
|
+
const track = ctx.createLinearGradient(moonX, canvas.height * 0.44, moonX, canvas.height * 0.98);
|
|
7325
|
+
track.addColorStop(0, visuals.moonReflection.replace(/[\d.]+\)$/u, `${0.08 + reflectionStrength * 0.12})`));
|
|
7326
|
+
track.addColorStop(0.42, visuals.moonReflection.replace(/[\d.]+\)$/u, `${0.04 + reflectionStrength * 0.18})`));
|
|
7327
|
+
track.addColorStop(1, "rgba(192, 214, 255, 0)");
|
|
7206
7328
|
ctx.save();
|
|
7207
7329
|
ctx.globalCompositeOperation = "screen";
|
|
7208
7330
|
ctx.fillStyle = track;
|
|
7209
7331
|
ctx.beginPath();
|
|
7210
|
-
ctx.ellipse(
|
|
7332
|
+
ctx.ellipse(moonX, canvas.height * 0.75, 38 + shadowStrength * 42, canvas.height * 0.24, 0, 0, Math.PI * 2);
|
|
7211
7333
|
ctx.fill();
|
|
7212
7334
|
ctx.restore();
|
|
7335
|
+
const mist = ctx.createLinearGradient(0, canvas.height * 0.5, 0, canvas.height);
|
|
7336
|
+
mist.addColorStop(0, "rgba(0, 0, 0, 0)");
|
|
7337
|
+
mist.addColorStop(1, visuals.ambientMist);
|
|
7338
|
+
ctx.fillStyle = mist;
|
|
7339
|
+
ctx.fillRect(0, canvas.height * 0.45, canvas.width, canvas.height * 0.55);
|
|
7213
7340
|
if (state.collisionFlash > 0.01) {
|
|
7214
|
-
ctx.fillStyle =
|
|
7341
|
+
ctx.fillStyle = visuals.collisionFlash.replace(
|
|
7342
|
+
/[\d.]+\)$/u,
|
|
7343
|
+
`${clamp(state.collisionFlash * 0.22, 0, 0.26)})`
|
|
7344
|
+
);
|
|
7215
7345
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
7216
7346
|
}
|
|
7217
7347
|
}
|
|
@@ -7310,7 +7440,7 @@ function pushHarborGeometry(camera, viewport, triangles, visuals) {
|
|
|
7310
7440
|
}
|
|
7311
7441
|
}
|
|
7312
7442
|
function renderShipRigging(ctx, ship, camera, viewport) {
|
|
7313
|
-
const transform = { position: ship.position, rotationY: ship.rotationY, scale:
|
|
7443
|
+
const transform = { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE };
|
|
7314
7444
|
const mastBase = transformPoint(vec3(0, 0.38, -0.4), transform);
|
|
7315
7445
|
const mastTop = transformPoint(vec3(0, 3.8, -0.2), transform);
|
|
7316
7446
|
const aftBase = transformPoint(vec3(-0.25, 0.32, -1.9), transform);
|
|
@@ -7414,6 +7544,35 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
7414
7544
|
}
|
|
7415
7545
|
}
|
|
7416
7546
|
}
|
|
7547
|
+
function readPhysicsNumber(physics, key, fallback) {
|
|
7548
|
+
const value = physics?.[key];
|
|
7549
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
7550
|
+
}
|
|
7551
|
+
function getShipMass(ship, shipModel) {
|
|
7552
|
+
const baseMass = readPhysicsNumber(shipModel.physics, "mass", 3200);
|
|
7553
|
+
return baseMass * readVisualNumber(ship.massScale, 1);
|
|
7554
|
+
}
|
|
7555
|
+
function getShipHalfExtents(ship, shipModel) {
|
|
7556
|
+
const physicsHalfExtents = Array.isArray(shipModel.physics.halfExtents) ? shipModel.physics.halfExtents : [1.35, 0.95, 3.9];
|
|
7557
|
+
const scale = SHIP_SCALE * readVisualNumber(ship.collisionRadiusScale, 1);
|
|
7558
|
+
return {
|
|
7559
|
+
x: physicsHalfExtents[0] * scale,
|
|
7560
|
+
y: physicsHalfExtents[1] * scale,
|
|
7561
|
+
z: physicsHalfExtents[2] * scale
|
|
7562
|
+
};
|
|
7563
|
+
}
|
|
7564
|
+
function getShipCollisionRadius(ship, shipModel) {
|
|
7565
|
+
const halfExtents = getShipHalfExtents(ship, shipModel);
|
|
7566
|
+
return Math.max(halfExtents.x * 1.08, halfExtents.z * 0.62);
|
|
7567
|
+
}
|
|
7568
|
+
function getShipInverseMass(ship, shipModel) {
|
|
7569
|
+
return 1 / Math.max(1, getShipMass(ship, shipModel));
|
|
7570
|
+
}
|
|
7571
|
+
function getShipInverseInertia(ship, shipModel) {
|
|
7572
|
+
const radius = getShipCollisionRadius(ship, shipModel);
|
|
7573
|
+
const inertia = getShipMass(ship, shipModel) * radius * radius * 0.72;
|
|
7574
|
+
return 1 / Math.max(1, inertia);
|
|
7575
|
+
}
|
|
7417
7576
|
function spawnSpray(state, point, intensity) {
|
|
7418
7577
|
const count = state.fluidDetail.getSnapshot().currentLevel.config.splashCount;
|
|
7419
7578
|
for (let index = 0; index < count; index += 1) {
|
|
@@ -7426,55 +7585,182 @@ function spawnSpray(state, point, intensity) {
|
|
|
7426
7585
|
});
|
|
7427
7586
|
}
|
|
7428
7587
|
}
|
|
7429
|
-
function
|
|
7588
|
+
function resolveShipRoute(ship, state, radius) {
|
|
7589
|
+
if (typeof ship.routeDirection !== "number") {
|
|
7590
|
+
ship.routeDirection = ship.velocity.x >= 0 ? 1 : -1;
|
|
7591
|
+
}
|
|
7592
|
+
if (ship.position.x > HARBOR_BOUNDS.maxX - radius * 1.1) {
|
|
7593
|
+
ship.routeDirection = -1;
|
|
7594
|
+
} else if (ship.position.x < HARBOR_BOUNDS.minX + radius * 1.1) {
|
|
7595
|
+
ship.routeDirection = 1;
|
|
7596
|
+
}
|
|
7597
|
+
const wander = Math.sin(state.time * 0.22 + readVisualNumber(ship.wanderPhase, 0));
|
|
7598
|
+
const crossCurrent = Math.cos(state.time * 0.31 + readVisualNumber(ship.wanderPhase, 0));
|
|
7599
|
+
const laneCenter = ship.id === "northwind" ? 10.2 + wander * 2.1 + crossCurrent * 0.6 : 7 + wander * 3.3 - crossCurrent * 1.1;
|
|
7600
|
+
const targetX = ship.routeDirection > 0 ? HARBOR_BOUNDS.maxX - radius * 1.7 : HARBOR_BOUNDS.minX + radius * 1.7;
|
|
7601
|
+
return vec3(targetX, 0, clamp(laneCenter, HARBOR_BOUNDS.minZ + 1.8, HARBOR_BOUNDS.maxZ - 1.8));
|
|
7602
|
+
}
|
|
7603
|
+
function updateShipMotion(state, ship, dt, shipModel) {
|
|
7430
7604
|
const physics = shipModel.physics;
|
|
7431
|
-
const
|
|
7605
|
+
const massScale = Math.max(0.55, readVisualNumber(ship.massScale, 1));
|
|
7606
|
+
const radius = getShipCollisionRadius(ship, shipModel);
|
|
7607
|
+
const waterline = readPhysicsNumber(physics, "waterline", 0.42);
|
|
7608
|
+
const linearDamping = readPhysicsNumber(physics, "linearDamping", 0.04);
|
|
7609
|
+
const angularDamping = readPhysicsNumber(physics, "angularDamping", 0.08);
|
|
7610
|
+
const throttleResponse = readVisualNumber(ship.throttleResponse, 0.58);
|
|
7611
|
+
const rudderResponse = readVisualNumber(ship.rudderResponse, 0.62);
|
|
7612
|
+
const cruiseSpeed = readVisualNumber(ship.cruiseSpeed, 2.4);
|
|
7613
|
+
ship.collisionCooldown = Math.max(0, readVisualNumber(ship.collisionCooldown, 0) - dt);
|
|
7614
|
+
const forward = directionFromYaw(ship.rotationY);
|
|
7615
|
+
const lateral = perpendicularOnWater(forward);
|
|
7616
|
+
const routeTarget = resolveShipRoute(ship, state, radius);
|
|
7617
|
+
const desiredHeading = Math.atan2(routeTarget.x - ship.position.x, routeTarget.z - ship.position.z);
|
|
7618
|
+
const headingError = Math.atan2(
|
|
7619
|
+
Math.sin(desiredHeading - ship.rotationY),
|
|
7620
|
+
Math.cos(desiredHeading - ship.rotationY)
|
|
7621
|
+
);
|
|
7622
|
+
ship.angularVelocity += headingError * rudderResponse * dt * (1.18 / Math.sqrt(massScale)) + Math.sin(state.time * 0.9 + readVisualNumber(ship.wanderPhase, 0)) * dt * 0.04;
|
|
7623
|
+
const waveDirection = resolveWaveDirection(state);
|
|
7624
|
+
const forwardSpeed = dotVec3(ship.velocity, forward);
|
|
7625
|
+
const lateralSpeed = dotVec3(ship.velocity, lateral);
|
|
7626
|
+
const thrust = (cruiseSpeed - forwardSpeed) * throttleResponse;
|
|
7627
|
+
const currentDrift = sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.016;
|
|
7628
|
+
const acceleration = addVec3(
|
|
7629
|
+
scaleVec3(forward, thrust),
|
|
7630
|
+
addVec3(
|
|
7631
|
+
scaleVec3(lateral, -lateralSpeed * (1.28 + rudderResponse * 0.4)),
|
|
7632
|
+
scaleVec3(waveDirection, currentDrift / Math.sqrt(massScale))
|
|
7633
|
+
)
|
|
7634
|
+
);
|
|
7635
|
+
ship.velocity = addVec3(ship.velocity, scaleVec3(acceleration, dt));
|
|
7636
|
+
ship.velocity = scaleVec3(
|
|
7637
|
+
ship.velocity,
|
|
7638
|
+
Math.max(0, 1 - linearDamping / Math.pow(massScale, 0.22) * dt)
|
|
7639
|
+
);
|
|
7640
|
+
ship.angularVelocity *= Math.max(
|
|
7641
|
+
0,
|
|
7642
|
+
1 - angularDamping / Math.pow(massScale, 0.15) * dt
|
|
7643
|
+
);
|
|
7644
|
+
ship.rotationY += ship.angularVelocity * dt;
|
|
7645
|
+
ship.position = addVec3(ship.position, scaleVec3(ship.velocity, dt));
|
|
7646
|
+
ship.position.y = sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.24 + waterline;
|
|
7647
|
+
}
|
|
7648
|
+
function resolveBoundaryCollision(ship, state, shipModel) {
|
|
7649
|
+
const restitution = readPhysicsNumber(shipModel.physics, "restitution", 0.22) * 0.56;
|
|
7650
|
+
const radius = getShipCollisionRadius(ship, shipModel);
|
|
7651
|
+
const boundaries = [
|
|
7652
|
+
{ axis: "x", min: HARBOR_BOUNDS.minX + radius, max: HARBOR_BOUNDS.maxX - radius, normalMin: vec3(1, 0, 0), normalMax: vec3(-1, 0, 0) },
|
|
7653
|
+
{ axis: "z", min: HARBOR_BOUNDS.minZ + radius, max: HARBOR_BOUNDS.maxZ - radius, normalMin: vec3(0, 0, 1), normalMax: vec3(0, 0, -1) }
|
|
7654
|
+
];
|
|
7655
|
+
for (const boundary of boundaries) {
|
|
7656
|
+
if (ship.position[boundary.axis] < boundary.min) {
|
|
7657
|
+
ship.position[boundary.axis] = boundary.min;
|
|
7658
|
+
const normal = boundary.normalMin;
|
|
7659
|
+
const speedIntoWall = dotVec3(ship.velocity, normal);
|
|
7660
|
+
if (speedIntoWall < 0) {
|
|
7661
|
+
ship.velocity = subVec3(
|
|
7662
|
+
ship.velocity,
|
|
7663
|
+
scaleVec3(normal, (1 + restitution) * speedIntoWall)
|
|
7664
|
+
);
|
|
7665
|
+
const tangent = vec3(-normal.z, 0, normal.x);
|
|
7666
|
+
const tangentSpeed = dotVec3(ship.velocity, tangent);
|
|
7667
|
+
ship.velocity = subVec3(ship.velocity, scaleVec3(tangent, tangentSpeed * 0.12));
|
|
7668
|
+
ship.angularVelocity += tangentSpeed * 4e-3;
|
|
7669
|
+
}
|
|
7670
|
+
} else if (ship.position[boundary.axis] > boundary.max) {
|
|
7671
|
+
ship.position[boundary.axis] = boundary.max;
|
|
7672
|
+
const normal = boundary.normalMax;
|
|
7673
|
+
const speedIntoWall = dotVec3(ship.velocity, normal);
|
|
7674
|
+
if (speedIntoWall < 0) {
|
|
7675
|
+
ship.velocity = subVec3(
|
|
7676
|
+
ship.velocity,
|
|
7677
|
+
scaleVec3(normal, (1 + restitution) * speedIntoWall)
|
|
7678
|
+
);
|
|
7679
|
+
const tangent = vec3(-normal.z, 0, normal.x);
|
|
7680
|
+
const tangentSpeed = dotVec3(ship.velocity, tangent);
|
|
7681
|
+
ship.velocity = subVec3(ship.velocity, scaleVec3(tangent, tangentSpeed * 0.12));
|
|
7682
|
+
ship.angularVelocity += tangentSpeed * 4e-3;
|
|
7683
|
+
}
|
|
7684
|
+
}
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
function resolveShipCollision(state, a, b, shipModel) {
|
|
7688
|
+
const delta = subVec3(b.position, a.position);
|
|
7689
|
+
const radiusA = getShipCollisionRadius(a, shipModel);
|
|
7690
|
+
const radiusB = getShipCollisionRadius(b, shipModel);
|
|
7691
|
+
const distance = Math.hypot(delta.x, delta.z);
|
|
7692
|
+
const minDistance = radiusA + radiusB;
|
|
7693
|
+
if (distance >= minDistance) {
|
|
7694
|
+
return false;
|
|
7695
|
+
}
|
|
7696
|
+
const normal = distance > 1e-4 ? normalizeVec3(vec3(delta.x / distance, 0, delta.z / distance)) : normalizeVec3(vec3(Math.cos(state.time * 5.2), 0, Math.sin(state.time * 4.8)));
|
|
7697
|
+
const tangent = vec3(-normal.z, 0, normal.x);
|
|
7698
|
+
const penetration = minDistance - distance;
|
|
7699
|
+
const invMassA = getShipInverseMass(a, shipModel);
|
|
7700
|
+
const invMassB = getShipInverseMass(b, shipModel);
|
|
7701
|
+
const invMassSum = invMassA + invMassB;
|
|
7702
|
+
const correction = scaleVec3(normal, penetration / Math.max(1e-4, invMassSum) * 0.72);
|
|
7703
|
+
a.position = subVec3(a.position, scaleVec3(correction, invMassA));
|
|
7704
|
+
b.position = addVec3(b.position, scaleVec3(correction, invMassB));
|
|
7705
|
+
const relativeVelocity = subVec3(b.velocity, a.velocity);
|
|
7706
|
+
const velocityAlongNormal = dotVec3(relativeVelocity, normal);
|
|
7707
|
+
const restitution = readPhysicsNumber(shipModel.physics, "restitution", 0.22) * 0.88;
|
|
7708
|
+
if (velocityAlongNormal < 0) {
|
|
7709
|
+
const impulseMagnitude = -(1 + restitution) * velocityAlongNormal / Math.max(1e-4, invMassSum);
|
|
7710
|
+
const impulse = scaleVec3(normal, impulseMagnitude);
|
|
7711
|
+
a.velocity = subVec3(a.velocity, scaleVec3(impulse, invMassA));
|
|
7712
|
+
b.velocity = addVec3(b.velocity, scaleVec3(impulse, invMassB));
|
|
7713
|
+
const tangentSpeed = dotVec3(relativeVelocity, tangent);
|
|
7714
|
+
const frictionMagnitude = clamp(
|
|
7715
|
+
-tangentSpeed / Math.max(1e-4, invMassSum),
|
|
7716
|
+
-impulseMagnitude * 0.16,
|
|
7717
|
+
impulseMagnitude * 0.16
|
|
7718
|
+
);
|
|
7719
|
+
const frictionImpulse = scaleVec3(tangent, frictionMagnitude);
|
|
7720
|
+
a.velocity = subVec3(a.velocity, scaleVec3(frictionImpulse, invMassA));
|
|
7721
|
+
b.velocity = addVec3(b.velocity, scaleVec3(frictionImpulse, invMassB));
|
|
7722
|
+
a.angularVelocity -= tangentSpeed * radiusA * getShipInverseInertia(a, shipModel) * 0.2 + impulseMagnitude * 24e-5;
|
|
7723
|
+
b.angularVelocity += tangentSpeed * radiusB * getShipInverseInertia(b, shipModel) * 0.2 + impulseMagnitude * 24e-5;
|
|
7724
|
+
const impactSpeed = Math.abs(velocityAlongNormal);
|
|
7725
|
+
if (impactSpeed > 0.18 && Math.max(readVisualNumber(a.collisionCooldown, 0), readVisualNumber(b.collisionCooldown, 0)) <= 0) {
|
|
7726
|
+
const contactPoint = vec3(
|
|
7727
|
+
(a.position.x + b.position.x) * 0.5,
|
|
7728
|
+
(a.position.y + b.position.y) * 0.5 + 0.14,
|
|
7729
|
+
(a.position.z + b.position.z) * 0.5
|
|
7730
|
+
);
|
|
7731
|
+
spawnSpray(state, contactPoint, impactSpeed * 2.4 + penetration * 8);
|
|
7732
|
+
state.waveImpulses.push({
|
|
7733
|
+
x: contactPoint.x,
|
|
7734
|
+
z: contactPoint.z,
|
|
7735
|
+
strength: clamp(0.24 + impactSpeed * 0.46 + penetration * 0.9, 0.2, 1.7),
|
|
7736
|
+
radius: 0.9 + penetration * 1.4,
|
|
7737
|
+
life: 1
|
|
7738
|
+
});
|
|
7739
|
+
state.collisionCount += 1;
|
|
7740
|
+
state.collisionFlash = Math.max(
|
|
7741
|
+
state.collisionFlash,
|
|
7742
|
+
clamp(impactSpeed * 0.55 + penetration * 1.8, 0.16, 1)
|
|
7743
|
+
);
|
|
7744
|
+
a.collisionCooldown = 0.2;
|
|
7745
|
+
b.collisionCooldown = 0.2;
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
state.contactCount += 1;
|
|
7749
|
+
return true;
|
|
7750
|
+
}
|
|
7751
|
+
function updateShips(state, dt, shipModel) {
|
|
7432
7752
|
let collided = false;
|
|
7433
7753
|
state.contactCount = 0;
|
|
7434
7754
|
for (const ship of state.ships) {
|
|
7435
|
-
|
|
7436
|
-
ship
|
|
7437
|
-
ship.velocity = scaleVec3(ship.velocity, 1 - (physics.linearDamping ?? 0.04) * dt);
|
|
7438
|
-
ship.angularVelocity *= 1 - (physics.angularDamping ?? 0.08) * dt;
|
|
7439
|
-
ship.position.y = sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.22 + (physics.waterline ?? 0.42);
|
|
7440
|
-
if (Math.abs(ship.position.x) > 10) {
|
|
7441
|
-
ship.velocity.x *= -1;
|
|
7442
|
-
ship.angularVelocity *= -1;
|
|
7443
|
-
}
|
|
7444
|
-
if (ship.position.z < 2 || ship.position.z > 16) {
|
|
7445
|
-
ship.velocity.z *= -1;
|
|
7446
|
-
ship.angularVelocity *= -1;
|
|
7447
|
-
}
|
|
7755
|
+
updateShipMotion(state, ship, dt, shipModel);
|
|
7756
|
+
resolveBoundaryCollision(ship, state, shipModel);
|
|
7448
7757
|
}
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
const overlapZ = Math.abs(dz) < halfExtents[2] * 0.8;
|
|
7454
|
-
if (overlapX && overlapZ) {
|
|
7455
|
-
const restitution = physics.restitution ?? 0.22;
|
|
7456
|
-
const swapX = a.velocity.x;
|
|
7457
|
-
const swapZ = a.velocity.z;
|
|
7458
|
-
a.velocity.x = -b.velocity.x * (0.86 + restitution);
|
|
7459
|
-
a.velocity.z = -b.velocity.z * (0.82 + restitution);
|
|
7460
|
-
b.velocity.x = -swapX * (0.86 + restitution);
|
|
7461
|
-
b.velocity.z = -swapZ * (0.82 + restitution);
|
|
7462
|
-
a.angularVelocity += 0.55;
|
|
7463
|
-
b.angularVelocity -= 0.55;
|
|
7464
|
-
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);
|
|
7465
|
-
spawnSpray(state, contactPoint, Math.abs(dx) + Math.abs(dz));
|
|
7466
|
-
state.waveImpulses.push({
|
|
7467
|
-
x: contactPoint.x,
|
|
7468
|
-
z: contactPoint.z,
|
|
7469
|
-
strength: Math.min(1.4, 0.2 + (Math.abs(dx) + Math.abs(dz)) * 0.18),
|
|
7470
|
-
radius: 0.8,
|
|
7471
|
-
life: 1
|
|
7472
|
-
});
|
|
7473
|
-
state.collisionCount += 1;
|
|
7474
|
-
state.contactCount = 1;
|
|
7475
|
-
collided = true;
|
|
7758
|
+
for (let index = 0; index < state.ships.length; index += 1) {
|
|
7759
|
+
for (let otherIndex = index + 1; otherIndex < state.ships.length; otherIndex += 1) {
|
|
7760
|
+
collided = resolveShipCollision(state, state.ships[index], state.ships[otherIndex], shipModel) || collided;
|
|
7761
|
+
}
|
|
7476
7762
|
}
|
|
7477
|
-
state.collisionFlash = collided ?
|
|
7763
|
+
state.collisionFlash = collided ? Math.max(0.12, state.collisionFlash) : Math.max(0, state.collisionFlash - dt * 1.3);
|
|
7478
7764
|
}
|
|
7479
7765
|
function updateWaveImpulses(state, dt) {
|
|
7480
7766
|
state.waveImpulses = state.waveImpulses.map((impulse) => ({
|
|
@@ -7587,7 +7873,7 @@ function renderFlagPole(ctx, camera, viewport) {
|
|
|
7587
7873
|
function renderShipShadow(ctx, shipModel, ship, state, camera, viewport, lightDir, shadowStrength) {
|
|
7588
7874
|
const bounds = shipModel.bounds;
|
|
7589
7875
|
const keelY = (shipModel.physics.waterline ?? 0.42) - 0.28;
|
|
7590
|
-
const transform = { position: ship.position, rotationY: ship.rotationY, scale:
|
|
7876
|
+
const transform = { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE };
|
|
7591
7877
|
const hullCorners = [
|
|
7592
7878
|
vec3(bounds.min[0], keelY, bounds.min[2]),
|
|
7593
7879
|
vec3(bounds.max[0], keelY, bounds.min[2]),
|
|
@@ -7613,6 +7899,97 @@ function renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength
|
|
|
7613
7899
|
blur: 12 + shadowStrength * 20
|
|
7614
7900
|
});
|
|
7615
7901
|
}
|
|
7902
|
+
function renderGlowLight(ctx, point, camera, viewport, coreColor, glowColor, glowScale, reflectionStrength = 0, state = null) {
|
|
7903
|
+
const projected = projectPoint(point, camera, viewport);
|
|
7904
|
+
if (!projected) {
|
|
7905
|
+
return;
|
|
7906
|
+
}
|
|
7907
|
+
const radius = clamp(1 / projected.depth * 420 * glowScale, 4, 34);
|
|
7908
|
+
const halo = ctx.createRadialGradient(projected.x, projected.y, radius * 0.12, projected.x, projected.y, radius);
|
|
7909
|
+
halo.addColorStop(0, colorToRgba(coreColor, 0.98));
|
|
7910
|
+
halo.addColorStop(0.5, colorToRgba(glowColor, 0.42));
|
|
7911
|
+
halo.addColorStop(1, colorToRgba(glowColor, 0));
|
|
7912
|
+
ctx.save();
|
|
7913
|
+
ctx.globalCompositeOperation = "screen";
|
|
7914
|
+
ctx.fillStyle = halo;
|
|
7915
|
+
ctx.beginPath();
|
|
7916
|
+
ctx.arc(projected.x, projected.y, radius, 0, Math.PI * 2);
|
|
7917
|
+
ctx.fill();
|
|
7918
|
+
ctx.restore();
|
|
7919
|
+
ctx.fillStyle = colorToRgba(coreColor, 0.98);
|
|
7920
|
+
ctx.beginPath();
|
|
7921
|
+
ctx.arc(projected.x, projected.y, Math.max(1.2, radius * 0.16), 0, Math.PI * 2);
|
|
7922
|
+
ctx.fill();
|
|
7923
|
+
if (state && reflectionStrength > 0) {
|
|
7924
|
+
const waterline = sampleWave(state, point.x, point.z, state.time) * 0.22;
|
|
7925
|
+
const reflectedPoint = vec3(point.x, waterline - (point.y - waterline) * 0.58, point.z + 0.08);
|
|
7926
|
+
const reflected = projectPoint(reflectedPoint, camera, viewport);
|
|
7927
|
+
if (reflected) {
|
|
7928
|
+
const reflectionRadius = radius * 0.72;
|
|
7929
|
+
const glow = ctx.createRadialGradient(
|
|
7930
|
+
reflected.x,
|
|
7931
|
+
reflected.y,
|
|
7932
|
+
reflectionRadius * 0.1,
|
|
7933
|
+
reflected.x,
|
|
7934
|
+
reflected.y,
|
|
7935
|
+
reflectionRadius
|
|
7936
|
+
);
|
|
7937
|
+
glow.addColorStop(0, colorToRgba(coreColor, reflectionStrength * 0.34));
|
|
7938
|
+
glow.addColorStop(1, colorToRgba(glowColor, 0));
|
|
7939
|
+
ctx.save();
|
|
7940
|
+
ctx.globalCompositeOperation = "screen";
|
|
7941
|
+
ctx.fillStyle = glow;
|
|
7942
|
+
ctx.beginPath();
|
|
7943
|
+
ctx.ellipse(
|
|
7944
|
+
reflected.x,
|
|
7945
|
+
reflected.y,
|
|
7946
|
+
reflectionRadius * 0.34,
|
|
7947
|
+
reflectionRadius,
|
|
7948
|
+
0,
|
|
7949
|
+
0,
|
|
7950
|
+
Math.PI * 2
|
|
7951
|
+
);
|
|
7952
|
+
ctx.fill();
|
|
7953
|
+
ctx.restore();
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
7956
|
+
}
|
|
7957
|
+
function renderShipLanterns(ctx, ship, state, camera, viewport, visuals) {
|
|
7958
|
+
const lanterns = Array.isArray(ship.lanterns) ? ship.lanterns : SHIP_LANTERNS;
|
|
7959
|
+
const strength = readVisualNumber(ship.lanternStrength, 1);
|
|
7960
|
+
for (const lantern of lanterns) {
|
|
7961
|
+
const position = transformPoint(
|
|
7962
|
+
vec3(lantern.x, lantern.y, lantern.z),
|
|
7963
|
+
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE }
|
|
7964
|
+
);
|
|
7965
|
+
renderGlowLight(
|
|
7966
|
+
ctx,
|
|
7967
|
+
position,
|
|
7968
|
+
camera,
|
|
7969
|
+
viewport,
|
|
7970
|
+
visuals.lanternCore,
|
|
7971
|
+
visuals.lanternGlow,
|
|
7972
|
+
lantern.glow * strength,
|
|
7973
|
+
visuals.lanternReflectionStrength,
|
|
7974
|
+
state
|
|
7975
|
+
);
|
|
7976
|
+
}
|
|
7977
|
+
}
|
|
7978
|
+
function renderHarborTorches(ctx, state, camera, viewport, visuals) {
|
|
7979
|
+
for (const torch of HARBOR_TORCHES) {
|
|
7980
|
+
renderGlowLight(
|
|
7981
|
+
ctx,
|
|
7982
|
+
vec3(torch.x, torch.y, torch.z),
|
|
7983
|
+
camera,
|
|
7984
|
+
viewport,
|
|
7985
|
+
visuals.torchCore,
|
|
7986
|
+
visuals.torchGlow,
|
|
7987
|
+
torch.glow,
|
|
7988
|
+
visuals.lanternReflectionStrength * 0.55,
|
|
7989
|
+
state
|
|
7990
|
+
);
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7616
7993
|
function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
7617
7994
|
const viewport = { width: canvas.width, height: canvas.height };
|
|
7618
7995
|
const camera = buildCamera(state, canvas);
|
|
@@ -7622,7 +7999,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7622
7999
|
importance: state.focus === "lighting" ? "critical" : "high"
|
|
7623
8000
|
});
|
|
7624
8001
|
const nearLighting = lightingPlan.bands.find((entry) => entry.band === "near") ?? lightingPlan.bands[0];
|
|
7625
|
-
const lightDir = normalizeVec3(vec3(-0.
|
|
8002
|
+
const lightDir = normalizeVec3(vec3(-0.22, 0.94, -0.31));
|
|
7626
8003
|
const lightingSnapshot = state.lightingDetail.getSnapshot();
|
|
7627
8004
|
const visuals = resolveVisualConfig(
|
|
7628
8005
|
nearLighting,
|
|
@@ -7696,7 +8073,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7696
8073
|
for (const ship of state.ships) {
|
|
7697
8074
|
buildTrianglesFromMesh(
|
|
7698
8075
|
shipModel,
|
|
7699
|
-
{ position: ship.position, rotationY: ship.rotationY, scale:
|
|
8076
|
+
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE },
|
|
7700
8077
|
ship.tint,
|
|
7701
8078
|
camera,
|
|
7702
8079
|
viewport,
|
|
@@ -7710,10 +8087,12 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7710
8087
|
renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength);
|
|
7711
8088
|
drawTriangles(ctx, triangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
7712
8089
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
8090
|
+
renderHarborTorches(ctx, state, camera, viewport, visuals);
|
|
7713
8091
|
renderFlagPole(ctx, camera, viewport);
|
|
7714
8092
|
renderClothAccent(ctx, cloth, camera, viewport);
|
|
7715
8093
|
for (const ship of state.ships) {
|
|
7716
8094
|
renderShipRigging(ctx, ship, camera, viewport);
|
|
8095
|
+
renderShipLanterns(ctx, ship, state, camera, viewport, visuals);
|
|
7717
8096
|
}
|
|
7718
8097
|
renderSprays(ctx, state.sprays, camera, viewport);
|
|
7719
8098
|
const debugSnapshot = state.debugSession.getSnapshot();
|
|
@@ -7725,8 +8104,10 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7725
8104
|
const sceneMetrics = [
|
|
7726
8105
|
`focus: ${state.focus}`,
|
|
7727
8106
|
`ships: ${state.ships.length} active GLTF hulls`,
|
|
8107
|
+
`moonlight: cold overhead key + ${HARBOR_TORCHES.length + state.ships.length * SHIP_LANTERNS.length} warm deck and harbor lights`,
|
|
7728
8108
|
`physics snapshot: ${state.physics.snapshot.stage} (${state.physics.snapshot.stability})`,
|
|
7729
|
-
`physics contacts: ${state.
|
|
8109
|
+
`physics contacts: ${state.contactCount}`,
|
|
8110
|
+
`mass split: ${state.ships.map((ship) => `${ship.id} ${(getShipMass(ship, shipModel) / 1e3).toFixed(1)}t`).join(" \xB7 ")}`,
|
|
7730
8111
|
`cloth band: ${cloth.band} -> ${cloth.representation.output}`,
|
|
7731
8112
|
`fluid near band: ${water.bandMeshes[0].representation.output}`,
|
|
7732
8113
|
`lighting profile: ${lightingPlan.profile} (${lightingDistanceBands.length} bands)`
|
|
@@ -7749,8 +8130,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7749
8130
|
];
|
|
7750
8131
|
const sceneNotes = state.focus === "physics" ? [
|
|
7751
8132
|
"Stable world snapshots are taken after the authoritative rigid-body commit and before visual follow-up work.",
|
|
7752
|
-
"The ships collide
|
|
7753
|
-
"
|
|
8133
|
+
"The ships collide with mass-weighted impulses and positional correction, so the heavier hull keeps more of its line.",
|
|
8134
|
+
"Moonlight keeps the overall read legible while lanterns and torches make collision moments easy to track against the water."
|
|
7754
8135
|
] : SCENE_NOTES;
|
|
7755
8136
|
const custom = state.demoDescription ?? null;
|
|
7756
8137
|
setListContent(
|
|
@@ -7767,7 +8148,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7767
8148
|
);
|
|
7768
8149
|
setListContent(dom.sceneNotes, Array.isArray(custom?.notes) ? custom.notes : sceneNotes);
|
|
7769
8150
|
dom.status.textContent = typeof custom?.status === "string" ? custom.status : `3D scene live \xB7 ${state.lastDecision.metrics.fps.toFixed(1)} FPS`;
|
|
7770
|
-
dom.details.textContent = typeof custom?.details === "string" ? custom.details : state.focus === "physics" ? `Stable world snapshots are emitted from ${state.physics.plan.snapshotStageId} after the authoritative solver;
|
|
8151
|
+
dom.details.textContent = typeof custom?.details === "string" ? custom.details : state.focus === "physics" ? `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.` : `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}.`;
|
|
7771
8152
|
}
|
|
7772
8153
|
function updateSceneState(state, dt, shipModel) {
|
|
7773
8154
|
updateShips(state, dt, shipModel);
|
|
@@ -7786,7 +8167,9 @@ function syncTextState(state, shipModel) {
|
|
|
7786
8167
|
y: Number(ship.position.y.toFixed(2)),
|
|
7787
8168
|
z: Number(ship.position.z.toFixed(2)),
|
|
7788
8169
|
vx: Number(ship.velocity.x.toFixed(2)),
|
|
7789
|
-
vz: Number(ship.velocity.z.toFixed(2))
|
|
8170
|
+
vz: Number(ship.velocity.z.toFixed(2)),
|
|
8171
|
+
massKg: Math.round(getShipMass(ship, shipModel)),
|
|
8172
|
+
lanterns: Array.isArray(ship.lanterns) ? ship.lanterns.length : 0
|
|
7790
8173
|
})),
|
|
7791
8174
|
shipPhysics: shipModel.physics,
|
|
7792
8175
|
sprays: state.sprays.length,
|
|
@@ -7814,6 +8197,7 @@ function syncTextState(state, shipModel) {
|
|
|
7814
8197
|
async function mountGpuShowcase(options = {}) {
|
|
7815
8198
|
injectStyles();
|
|
7816
8199
|
const root = options.root ?? document.body;
|
|
8200
|
+
root.classList?.add?.(ROOT_CLASS);
|
|
7817
8201
|
const previousMarkup = root.innerHTML;
|
|
7818
8202
|
const previousRenderGameToText = window.render_game_to_text;
|
|
7819
8203
|
const previousAdvanceTime = window.advanceTime;
|
|
@@ -7833,8 +8217,11 @@ async function mountGpuShowcase(options = {}) {
|
|
|
7833
8217
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
7834
8218
|
syncTextState(state, shipModel);
|
|
7835
8219
|
const ctx = dom.canvas.getContext("2d");
|
|
8220
|
+
if (!ctx) {
|
|
8221
|
+
throw new Error("2D canvas context is required for the shared showcase.");
|
|
8222
|
+
}
|
|
8223
|
+
let animationFrameId = 0;
|
|
7836
8224
|
let destroyed = false;
|
|
7837
|
-
let frameHandle = null;
|
|
7838
8225
|
const renderFrame = (nowMs) => {
|
|
7839
8226
|
if (destroyed) {
|
|
7840
8227
|
return;
|
|
@@ -7855,7 +8242,7 @@ async function mountGpuShowcase(options = {}) {
|
|
|
7855
8242
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
7856
8243
|
renderScene(ctx, dom.canvas, state, shipModel, dom);
|
|
7857
8244
|
syncTextState(state, shipModel);
|
|
7858
|
-
|
|
8245
|
+
animationFrameId = requestAnimationFrame(renderFrame);
|
|
7859
8246
|
};
|
|
7860
8247
|
const handlePauseClick = () => {
|
|
7861
8248
|
state.paused = !state.paused;
|
|
@@ -7874,34 +8261,43 @@ async function mountGpuShowcase(options = {}) {
|
|
|
7874
8261
|
dom.pauseButton.addEventListener("click", handlePauseClick);
|
|
7875
8262
|
dom.stressToggle.addEventListener("change", handleStressChange);
|
|
7876
8263
|
dom.focusMode.addEventListener("change", handleFocusChange);
|
|
7877
|
-
|
|
8264
|
+
animationFrameId = requestAnimationFrame(renderFrame);
|
|
8265
|
+
const destroy = () => {
|
|
8266
|
+
if (destroyed) {
|
|
8267
|
+
return;
|
|
8268
|
+
}
|
|
8269
|
+
destroyed = true;
|
|
8270
|
+
if (animationFrameId) {
|
|
8271
|
+
cancelAnimationFrame(animationFrameId);
|
|
8272
|
+
}
|
|
8273
|
+
dom.pauseButton.removeEventListener("click", handlePauseClick);
|
|
8274
|
+
dom.stressToggle.removeEventListener("change", handleStressChange);
|
|
8275
|
+
dom.focusMode.removeEventListener("change", handleFocusChange);
|
|
8276
|
+
try {
|
|
8277
|
+
if (typeof options.destroyState === "function") {
|
|
8278
|
+
options.destroyState(state.packageState);
|
|
8279
|
+
}
|
|
8280
|
+
} finally {
|
|
8281
|
+
state.packageState = void 0;
|
|
8282
|
+
}
|
|
8283
|
+
root.classList?.remove?.(ROOT_CLASS);
|
|
8284
|
+
root.innerHTML = previousMarkup;
|
|
8285
|
+
if (typeof previousRenderGameToText === "function") {
|
|
8286
|
+
window.render_game_to_text = previousRenderGameToText;
|
|
8287
|
+
} else {
|
|
8288
|
+
delete window.render_game_to_text;
|
|
8289
|
+
}
|
|
8290
|
+
if (typeof previousAdvanceTime === "function") {
|
|
8291
|
+
window.advanceTime = previousAdvanceTime;
|
|
8292
|
+
} else {
|
|
8293
|
+
delete window.advanceTime;
|
|
8294
|
+
}
|
|
8295
|
+
};
|
|
7878
8296
|
return {
|
|
7879
8297
|
state,
|
|
7880
8298
|
shipModel,
|
|
7881
8299
|
canvas: dom.canvas,
|
|
7882
|
-
destroy
|
|
7883
|
-
if (destroyed) {
|
|
7884
|
-
return;
|
|
7885
|
-
}
|
|
7886
|
-
destroyed = true;
|
|
7887
|
-
if (frameHandle != null) {
|
|
7888
|
-
cancelAnimationFrame(frameHandle);
|
|
7889
|
-
}
|
|
7890
|
-
dom.pauseButton.removeEventListener("click", handlePauseClick);
|
|
7891
|
-
dom.stressToggle.removeEventListener("change", handleStressChange);
|
|
7892
|
-
dom.focusMode.removeEventListener("change", handleFocusChange);
|
|
7893
|
-
root.innerHTML = previousMarkup;
|
|
7894
|
-
if (typeof previousRenderGameToText === "function") {
|
|
7895
|
-
window.render_game_to_text = previousRenderGameToText;
|
|
7896
|
-
} else {
|
|
7897
|
-
delete window.render_game_to_text;
|
|
7898
|
-
}
|
|
7899
|
-
if (typeof previousAdvanceTime === "function") {
|
|
7900
|
-
window.advanceTime = previousAdvanceTime;
|
|
7901
|
-
} else {
|
|
7902
|
-
delete window.advanceTime;
|
|
7903
|
-
}
|
|
7904
|
-
}
|
|
8300
|
+
destroy
|
|
7905
8301
|
};
|
|
7906
8302
|
}
|
|
7907
8303
|
function updatePhysicsSnapshot(state, shipModel) {
|
|
@@ -7915,7 +8311,7 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
7915
8311
|
animationInputRevision: state.frame,
|
|
7916
8312
|
bodyCount: state.ships.length + 2,
|
|
7917
8313
|
dynamicBodyCount: state.ships.length,
|
|
7918
|
-
contactCount: state.
|
|
8314
|
+
contactCount: state.contactCount,
|
|
7919
8315
|
metadata: {
|
|
7920
8316
|
collisionCount: state.collisionCount,
|
|
7921
8317
|
contactCount: state.contactCount,
|
|
@@ -7924,7 +8320,7 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
7924
8320
|
}
|
|
7925
8321
|
});
|
|
7926
8322
|
}
|
|
7927
|
-
var STYLE_ID, DEFAULT_TITLE, DEFAULT_SUBTITLE, CAMERA_PRESETS, showcaseFocusModes, SCENE_NOTES, UNIT_BOX_MESH;
|
|
8323
|
+
var STYLE_ID, ROOT_CLASS, DEFAULT_TITLE, DEFAULT_SUBTITLE, SHIP_SCALE, HARBOR_BOUNDS, CAMERA_PRESETS, showcaseFocusModes, SCENE_NOTES, UNIT_BOX_MESH, SHIP_LANTERNS, HARBOR_TORCHES;
|
|
7928
8324
|
var init_showcase_runtime = __esm({
|
|
7929
8325
|
"src/showcase-runtime.js"() {
|
|
7930
8326
|
init_dist();
|
|
@@ -7936,8 +8332,16 @@ var init_showcase_runtime = __esm({
|
|
|
7936
8332
|
init_asset_url();
|
|
7937
8333
|
init_gltf_loader();
|
|
7938
8334
|
STYLE_ID = "plasius-shared-3d-showcase-style";
|
|
8335
|
+
ROOT_CLASS = "plasius-showcase-root";
|
|
7939
8336
|
DEFAULT_TITLE = "Flag by the Sea";
|
|
7940
8337
|
DEFAULT_SUBTITLE = "Shared 3D validation scene using GLTF ships, cloth, fluid continuity, adaptive performance, and telemetry.";
|
|
8338
|
+
SHIP_SCALE = 1.1;
|
|
8339
|
+
HARBOR_BOUNDS = Object.freeze({
|
|
8340
|
+
minX: -11.2,
|
|
8341
|
+
maxX: 11.2,
|
|
8342
|
+
minZ: 1.8,
|
|
8343
|
+
maxZ: 17.2
|
|
8344
|
+
});
|
|
7941
8345
|
CAMERA_PRESETS = Object.freeze({
|
|
7942
8346
|
integrated: Object.freeze({ yaw: -0.55, pitch: 0.34, distance: 27, target: [0, 2.2, 0] }),
|
|
7943
8347
|
lighting: Object.freeze({ yaw: -0.28, pitch: 0.28, distance: 23, target: [0, 2.8, 0] }),
|
|
@@ -7949,10 +8353,10 @@ var init_showcase_runtime = __esm({
|
|
|
7949
8353
|
});
|
|
7950
8354
|
showcaseFocusModes = Object.freeze(Object.keys(CAMERA_PRESETS));
|
|
7951
8355
|
SCENE_NOTES = Object.freeze([
|
|
7952
|
-
"Ships are loaded from a GLTF asset and carry
|
|
7953
|
-
"
|
|
7954
|
-
"Cloth and fluid continuity stay coherent across near, mid, far, and horizon bands.",
|
|
7955
|
-
"Performance pressure reduces visual detail before authoritative collision motion is touched."
|
|
8356
|
+
"Ships are loaded from a GLTF asset and carry mass, damping, restitution, and hull extents from node extras.",
|
|
8357
|
+
"Moonlight sets the cold ambient read while deck lanterns and harbor torches provide warm local contrast.",
|
|
8358
|
+
"Cloth and fluid continuity stay coherent across near, mid, far, and horizon bands even in the darker night palette.",
|
|
8359
|
+
"Performance pressure reduces visual detail before mass-weighted authoritative collision motion is touched."
|
|
7956
8360
|
]);
|
|
7957
8361
|
UNIT_BOX_MESH = Object.freeze({
|
|
7958
8362
|
positions: Object.freeze([
|
|
@@ -8020,6 +8424,17 @@ var init_showcase_runtime = __esm({
|
|
|
8020
8424
|
0
|
|
8021
8425
|
])
|
|
8022
8426
|
});
|
|
8427
|
+
SHIP_LANTERNS = Object.freeze([
|
|
8428
|
+
Object.freeze({ x: 0.94, y: 1.54, z: 2.52, glow: 1 }),
|
|
8429
|
+
Object.freeze({ x: -0.9, y: 1.58, z: 2.44, glow: 0.92 }),
|
|
8430
|
+
Object.freeze({ x: 0.62, y: 1.42, z: -2.18, glow: 0.88 }),
|
|
8431
|
+
Object.freeze({ x: -0.58, y: 1.46, z: -2.04, glow: 0.84 })
|
|
8432
|
+
]);
|
|
8433
|
+
HARBOR_TORCHES = Object.freeze([
|
|
8434
|
+
Object.freeze({ x: -5.2, y: 1.25, z: 1.36, glow: 1.1 }),
|
|
8435
|
+
Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
|
|
8436
|
+
Object.freeze({ x: -10.4, y: 1.28, z: 0.82, glow: 0.92 })
|
|
8437
|
+
]);
|
|
8023
8438
|
}
|
|
8024
8439
|
});
|
|
8025
8440
|
|