@plasius/gpu-shared 0.1.3 → 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 +35 -1
- package/dist/chunk-OTCJ3VOK.js +35 -0
- package/dist/chunk-OTCJ3VOK.js.map +1 -0
- 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 +605 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/{showcase-runtime-V72XV55N.js → showcase-runtime-JZIYGQAU.js} +533 -167
- package/dist/{showcase-runtime-V72XV55N.js.map → showcase-runtime-JZIYGQAU.js.map} +1 -1
- package/package.json +2 -2
- package/src/asset-url.js +37 -0
- package/src/gltf-loader.js +36 -1
- package/src/index.d.ts +4 -0
- package/src/index.js +2 -4
- package/src/showcase-runtime.js +619 -166
- 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
|
@@ -19,6 +19,43 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
19
19
|
};
|
|
20
20
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
21
|
|
|
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
|
+
}
|
|
35
|
+
function resolveShowcaseAssetUrl(baseUrl2 = import_meta.url) {
|
|
36
|
+
try {
|
|
37
|
+
return new URL("../assets/brigantine.gltf", baseUrl2);
|
|
38
|
+
} catch {
|
|
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();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var import_meta, INLINE_BRIGANTINE_GLTF_URL;
|
|
52
|
+
var init_asset_url = __esm({
|
|
53
|
+
"src/asset-url.js"() {
|
|
54
|
+
import_meta = {};
|
|
55
|
+
INLINE_BRIGANTINE_GLTF_URL = "data:application/json;base64,ewogICJhc3NldCI6IHsKICAgICJ2ZXJzaW9uIjogIjIuMCIsCiAgICAiZ2VuZXJhdG9yIjogIlBsYXNpdXMgZGVtbyBhc3NldCBnZW5lcmF0b3IiCiAgfSwKICAic2NlbmUiOiAwLAogICJzY2VuZXMiOiBbCiAgICB7CiAgICAgICJub2RlcyI6IFswXQogICAgfQogIF0sCiAgIm5vZGVzIjogWwogICAgewogICAgICAibWVzaCI6IDAsCiAgICAgICJuYW1lIjogImJyaWdhbnRpbmUiLAogICAgICAiZXh0cmFzIjogewogICAgICAgICJwaHlzaWNzIjogewogICAgICAgICAgInNoYXBlIjogImJveCIsCiAgICAgICAgICAiaGFsZkV4dGVudHMiOiBbMS4zNSwgMC45NSwgMy45XSwKICAgICAgICAgICJtYXNzIjogMzIwMCwKICAgICAgICAgICJyZXN0aXR1dGlvbiI6IDAuMjIsCiAgICAgICAgICAibGluZWFyRGFtcGluZyI6IDAuMDQsCiAgICAgICAgICAiYW5ndWxhckRhbXBpbmciOiAwLjA4LAogICAgICAgICAgIndhdGVybGluZSI6IDAuNDIKICAgICAgICB9CiAgICAgIH0KICAgIH0KICBdLAogICJtZXNoZXMiOiBbCiAgICB7CiAgICAgICJuYW1lIjogImJyaWdhbnRpbmUtaHVsbCIsCiAgICAgICJwcmltaXRpdmVzIjogWwogICAgICAgIHsKICAgICAgICAgICJhdHRyaWJ1dGVzIjogewogICAgICAgICAgICAiUE9TSVRJT04iOiAwCiAgICAgICAgICB9LAogICAgICAgICAgImluZGljZXMiOiAxLAogICAgICAgICAgIm1hdGVyaWFsIjogMAogICAgICAgIH0KICAgICAgXQogICAgfQogIF0sCiAgIm1hdGVyaWFscyI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAicGFpbnRlZC1odWxsIiwKICAgICAgInBick1ldGFsbGljUm91Z2huZXNzIjogewogICAgICAgICJiYXNlQ29sb3JGYWN0b3IiOiBbMC41NiwgMC4zMywgMC4yMiwgMV0sCiAgICAgICAgIm1ldGFsbGljRmFjdG9yIjogMC4wOCwKICAgICAgICAicm91Z2huZXNzRmFjdG9yIjogMC45MgogICAgICB9CiAgICB9CiAgXSwKICAiYnVmZmVycyI6IFsKICAgIHsKICAgICAgInVyaSI6ICJkYXRhOmFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbTtiYXNlNjQsbXBtWnZ3QUFBTC9OekV6QW1wbVpQd0FBQUwvTnpFekF6Y3lzdnpNenM3NHpNN08vemN5c1B6TXpzNzR6TTdPL0FBQ2d2ODNNVEwzTnpNdy9BQUNnUDgzTVRMM056TXcvQUFBQUFPeFJPTDR6TTROQUFBQUFBR1ptNWo0QUFIQkFNek56dnpNenN6NmFtUm5BTXpOelB6TXpzejZhbVJuQXpjeE12ejBLMXo3TnpFdy96Y3hNUHowSzF6N056RXcvQUFBQUFETXpjejltWm1hL0FBQUNBQU1BQUFBREFBRUFBZ0FFQUFVQUFnQUZBQU1BQkFBSEFBVUFCQUFHQUFjQUJRQUhBQVlBQUFBQkFBa0FBQUFKQUFnQUNBQUpBQXdBQWdBSUFBd0FBd0FNQUFrQUFnQU1BQW9BQXdBTEFBd0FBZ0FLQUFRQUF3QUZBQXNBQ2dBTUFBc0FBQUFJQUFJQUFRQURBQWtBQkFBS0FBWUFCUUFHQUFzQUFnQUtBQXNBQWdBTEFBTUEiLAogICAgICAiYnl0ZUxlbmd0aCI6IDI5NAogICAgfQogIF0sCiAgImJ1ZmZlclZpZXdzIjogWwogICAgewogICAgICAiYnVmZmVyIjogMCwKICAgICAgImJ5dGVPZmZzZXQiOiAwLAogICAgICAiYnl0ZUxlbmd0aCI6IDE1NiwKICAgICAgInRhcmdldCI6IDM0OTYyCiAgICB9LAogICAgewogICAgICAiYnVmZmVyIjogMCwKICAgICAgImJ5dGVPZmZzZXQiOiAxNTYsCiAgICAgICJieXRlTGVuZ3RoIjogMTM4LAogICAgICAidGFyZ2V0IjogMzQ5NjMKICAgIH0KICBdLAogICJhY2Nlc3NvcnMiOiBbCiAgICB7CiAgICAgICJidWZmZXJWaWV3IjogMCwKICAgICAgImJ5dGVPZmZzZXQiOiAwLAogICAgICAiY29tcG9uZW50VHlwZSI6IDUxMjYsCiAgICAgICJjb3VudCI6IDEzLAogICAgICAidHlwZSI6ICJWRUMzIiwKICAgICAgIm1pbiI6IFstMS4zNSwgLTAuNSwgLTMuMl0sCiAgICAgICJtYXgiOiBbMS4zNSwgMC45NSwgNC4xXQogICAgfSwKICAgIHsKICAgICAgImJ1ZmZlclZpZXciOiAxLAogICAgICAiYnl0ZU9mZnNldCI6IDAsCiAgICAgICJjb21wb25lbnRUeXBlIjogNTEyMywKICAgICAgImNvdW50IjogNjksCiAgICAgICJ0eXBlIjogIlNDQUxBUiIsCiAgICAgICJtYXgiOiBbMTJdLAogICAgICAibWluIjogWzBdCiAgICB9CiAgXQp9Cg==";
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
22
59
|
// src/gltf-loader.js
|
|
23
60
|
var gltf_loader_exports = {};
|
|
24
61
|
__export(gltf_loader_exports, {
|
|
@@ -94,12 +131,39 @@ function computeBounds(positions) {
|
|
|
94
131
|
}
|
|
95
132
|
return { min, max };
|
|
96
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
|
+
}
|
|
97
160
|
async function loadGltfModel(url) {
|
|
98
161
|
const response = await fetch(url);
|
|
99
162
|
if (!response.ok) {
|
|
100
163
|
throw new Error(`Failed to load glTF asset: ${response.status} ${response.statusText}`);
|
|
101
164
|
}
|
|
102
165
|
const document2 = await response.json();
|
|
166
|
+
const baseUrl2 = resolveFetchBaseUrl(url, response.url);
|
|
103
167
|
const buffers = await Promise.all(
|
|
104
168
|
(document2.buffers ?? []).map(async (buffer) => {
|
|
105
169
|
if (typeof buffer.uri !== "string") {
|
|
@@ -108,7 +172,7 @@ async function loadGltfModel(url) {
|
|
|
108
172
|
if (buffer.uri.startsWith("data:")) {
|
|
109
173
|
return decodeDataUri(buffer.uri);
|
|
110
174
|
}
|
|
111
|
-
const nested = await fetch(new URL(buffer.uri,
|
|
175
|
+
const nested = await fetch(new URL(buffer.uri, baseUrl2));
|
|
112
176
|
if (!nested.ok) {
|
|
113
177
|
throw new Error(`Failed to load glTF buffer: ${nested.status} ${nested.statusText}`);
|
|
114
178
|
}
|
|
@@ -2452,10 +2516,10 @@ function getLightingProfile(name = defaultLightingProfile) {
|
|
|
2452
2516
|
}
|
|
2453
2517
|
return profile;
|
|
2454
2518
|
}
|
|
2455
|
-
var
|
|
2519
|
+
var import_meta2, __require, baseUrl, techniqueSpecs, lightingTechniques, lightingTechniqueNames, defaultLightingTechnique, profileSpecs, lightingProfiles, lightingProfileNames, defaultLightingProfile, lightingDistanceBands, lightingWorkerQueueClass, lightingDebugOwner, lightingImportanceLevels, lightingBandPolicySpecs, lightingWorkerSpecPresets, lightingWorkerDagSpecs, lightingWorkerManifests, defaultTechnique, lightingPreludeWgslUrl, lightingJobLabels, lightingJobs;
|
|
2456
2520
|
var init_dist3 = __esm({
|
|
2457
2521
|
"node_modules/@plasius/gpu-lighting/dist/index.js"() {
|
|
2458
|
-
|
|
2522
|
+
import_meta2 = {};
|
|
2459
2523
|
__require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2460
2524
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
2461
2525
|
}) : x)(function(x) {
|
|
@@ -2463,8 +2527,8 @@ var init_dist3 = __esm({
|
|
|
2463
2527
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
2464
2528
|
});
|
|
2465
2529
|
baseUrl = (() => {
|
|
2466
|
-
if (typeof
|
|
2467
|
-
return new URL("./index.js",
|
|
2530
|
+
if (typeof import_meta2.url !== "undefined") {
|
|
2531
|
+
return new URL("./index.js", import_meta2.url);
|
|
2468
2532
|
}
|
|
2469
2533
|
if (typeof __filename !== "undefined" && typeof __require !== "undefined") {
|
|
2470
2534
|
const { pathToFileURL } = __require("url");
|
|
@@ -5091,7 +5155,7 @@ var init_dist5 = __esm({
|
|
|
5091
5155
|
}
|
|
5092
5156
|
});
|
|
5093
5157
|
|
|
5094
|
-
// node_modules/@plasius/gpu-physics/dist/chunk-
|
|
5158
|
+
// node_modules/@plasius/gpu-physics/dist/chunk-PFUNZLNF.js
|
|
5095
5159
|
function assertIdentifier5(name, value) {
|
|
5096
5160
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
5097
5161
|
throw new Error(`${name} must be a non-empty string.`);
|
|
@@ -5240,7 +5304,7 @@ function getPhysicsWorkerManifest(profile = defaultPhysicsWorkerProfile) {
|
|
|
5240
5304
|
return manifest;
|
|
5241
5305
|
}
|
|
5242
5306
|
function createPhysicsSimulationPlan(profile = defaultPhysicsWorkerProfile) {
|
|
5243
|
-
const plan =
|
|
5307
|
+
const plan = physicsSimulationPlans[profile];
|
|
5244
5308
|
if (!plan) {
|
|
5245
5309
|
const available = physicsWorkerProfileNames.join(", ");
|
|
5246
5310
|
throw new Error(
|
|
@@ -5304,9 +5368,9 @@ function createPhysicsWorldSnapshot(input) {
|
|
|
5304
5368
|
metadata: normalizeMetadata(input.metadata)
|
|
5305
5369
|
});
|
|
5306
5370
|
}
|
|
5307
|
-
var physicsDebugOwner, physicsWorkerQueueClasses, defaultPhysicsWorkerProfile, physicsSimulationStageOrder, physicsWorkerProfileSpecs, physicsWorkerDagSpecs,
|
|
5308
|
-
var
|
|
5309
|
-
"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"() {
|
|
5310
5374
|
physicsDebugOwner = "physics";
|
|
5311
5375
|
physicsWorkerQueueClasses = Object.freeze({
|
|
5312
5376
|
simulation: "simulation",
|
|
@@ -6052,7 +6116,7 @@ var init_chunk_CNTXT5QJ = __esm({
|
|
|
6052
6116
|
contactVisuals: { priority: 1, dependencies: ["worldSnapshot"] }
|
|
6053
6117
|
}
|
|
6054
6118
|
};
|
|
6055
|
-
|
|
6119
|
+
physicsSimulationPlans = Object.freeze({
|
|
6056
6120
|
gameplay: Object.freeze({
|
|
6057
6121
|
description: "Gameplay simulation plan that hands off a stable post-commit world snapshot to visual preparation.",
|
|
6058
6122
|
snapshotStageId: "worldSnapshot",
|
|
@@ -6268,7 +6332,7 @@ var init_chunk_CNTXT5QJ = __esm({
|
|
|
6268
6332
|
// node_modules/@plasius/gpu-physics/dist/browser.js
|
|
6269
6333
|
var init_browser = __esm({
|
|
6270
6334
|
"node_modules/@plasius/gpu-physics/dist/browser.js"() {
|
|
6271
|
-
|
|
6335
|
+
init_chunk_PFUNZLNF();
|
|
6272
6336
|
}
|
|
6273
6337
|
});
|
|
6274
6338
|
|
|
@@ -6276,12 +6340,8 @@ var init_browser = __esm({
|
|
|
6276
6340
|
var showcase_runtime_exports = {};
|
|
6277
6341
|
__export(showcase_runtime_exports, {
|
|
6278
6342
|
mountGpuShowcase: () => mountGpuShowcase,
|
|
6279
|
-
resolveShowcaseAssetUrl: () => resolveShowcaseAssetUrl,
|
|
6280
6343
|
showcaseFocusModes: () => showcaseFocusModes
|
|
6281
6344
|
});
|
|
6282
|
-
function resolveShowcaseAssetUrl(baseUrl2 = import_meta2.url) {
|
|
6283
|
-
return new URL("../assets/brigantine.gltf", baseUrl2);
|
|
6284
|
-
}
|
|
6285
6345
|
function injectStyles() {
|
|
6286
6346
|
if (document.getElementById(STYLE_ID)) {
|
|
6287
6347
|
return;
|
|
@@ -6289,27 +6349,27 @@ function injectStyles() {
|
|
|
6289
6349
|
const style = document.createElement("style");
|
|
6290
6350
|
style.id = STYLE_ID;
|
|
6291
6351
|
style.textContent = `
|
|
6292
|
-
|
|
6293
|
-
color-scheme:
|
|
6294
|
-
--plasius-paper: #
|
|
6295
|
-
--plasius-ink: #
|
|
6296
|
-
--plasius-muted: #
|
|
6297
|
-
--plasius-accent: #
|
|
6298
|
-
--plasius-panel: rgba(
|
|
6299
|
-
--plasius-border: rgba(
|
|
6300
|
-
--plasius-shadow: 0
|
|
6301
|
-
}
|
|
6302
|
-
* {
|
|
6303
|
-
box-sizing: border-box;
|
|
6304
|
-
}
|
|
6305
|
-
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);
|
|
6306
6361
|
margin: 0;
|
|
6307
|
-
min-height:
|
|
6362
|
+
min-height: 100%;
|
|
6308
6363
|
font-family: "Fraunces", "Iowan Old Style", serif;
|
|
6309
6364
|
color: var(--plasius-ink);
|
|
6310
6365
|
background:
|
|
6311
|
-
radial-gradient(circle at
|
|
6312
|
-
|
|
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;
|
|
6313
6373
|
}
|
|
6314
6374
|
.plasius-demo {
|
|
6315
6375
|
width: min(1560px, calc(100vw - 32px));
|
|
@@ -6343,7 +6403,7 @@ function injectStyles() {
|
|
|
6343
6403
|
text-transform: uppercase;
|
|
6344
6404
|
letter-spacing: 0.18em;
|
|
6345
6405
|
font-size: 12px;
|
|
6346
|
-
color: rgba(
|
|
6406
|
+
color: rgba(226, 236, 255, 0.58);
|
|
6347
6407
|
}
|
|
6348
6408
|
.plasius-demo h1,
|
|
6349
6409
|
.plasius-demo h2,
|
|
@@ -6361,7 +6421,7 @@ function injectStyles() {
|
|
|
6361
6421
|
margin: 0;
|
|
6362
6422
|
padding: 8px 12px;
|
|
6363
6423
|
border-radius: 999px;
|
|
6364
|
-
background: rgba(
|
|
6424
|
+
background: rgba(243, 177, 106, 0.14);
|
|
6365
6425
|
color: var(--plasius-accent);
|
|
6366
6426
|
font-weight: 700;
|
|
6367
6427
|
}
|
|
@@ -6383,8 +6443,8 @@ function injectStyles() {
|
|
|
6383
6443
|
aspect-ratio: 16 / 9;
|
|
6384
6444
|
display: block;
|
|
6385
6445
|
border-radius: 20px;
|
|
6386
|
-
border: 1px solid rgba(
|
|
6387
|
-
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%);
|
|
6388
6448
|
}
|
|
6389
6449
|
.plasius-demo__toolbar {
|
|
6390
6450
|
position: absolute;
|
|
@@ -6404,9 +6464,9 @@ function injectStyles() {
|
|
|
6404
6464
|
.plasius-demo button,
|
|
6405
6465
|
.plasius-demo .plasius-toggle,
|
|
6406
6466
|
.plasius-demo select {
|
|
6407
|
-
border: 1px solid rgba(
|
|
6467
|
+
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
6408
6468
|
border-radius: 999px;
|
|
6409
|
-
background: rgba(
|
|
6469
|
+
background: rgba(9, 20, 34, 0.84);
|
|
6410
6470
|
color: var(--plasius-ink);
|
|
6411
6471
|
padding: 10px 14px;
|
|
6412
6472
|
}
|
|
@@ -6445,8 +6505,8 @@ function injectStyles() {
|
|
|
6445
6505
|
bottom: 24px;
|
|
6446
6506
|
padding: 10px 14px;
|
|
6447
6507
|
border-radius: 16px;
|
|
6448
|
-
background: rgba(
|
|
6449
|
-
border: 1px solid rgba(
|
|
6508
|
+
background: rgba(9, 20, 34, 0.82);
|
|
6509
|
+
border: 1px solid rgba(159, 185, 223, 0.16);
|
|
6450
6510
|
color: var(--plasius-muted);
|
|
6451
6511
|
font-size: 12px;
|
|
6452
6512
|
line-height: 1.45;
|
|
@@ -6458,7 +6518,7 @@ function injectStyles() {
|
|
|
6458
6518
|
}
|
|
6459
6519
|
.plasius-demo__footer {
|
|
6460
6520
|
margin-top: 4px;
|
|
6461
|
-
color: rgba(
|
|
6521
|
+
color: rgba(226, 236, 255, 0.68);
|
|
6462
6522
|
font-size: 13px;
|
|
6463
6523
|
line-height: 1.6;
|
|
6464
6524
|
}
|
|
@@ -6477,6 +6537,10 @@ function clamp(value, min, max) {
|
|
|
6477
6537
|
function mix(a, b, t) {
|
|
6478
6538
|
return a + (b - a) * t;
|
|
6479
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
|
+
}
|
|
6480
6544
|
function vec3(x = 0, y = 0, z = 0) {
|
|
6481
6545
|
return { x, y, z };
|
|
6482
6546
|
}
|
|
@@ -6510,6 +6574,12 @@ function reflectVec3(vector, normal) {
|
|
|
6510
6574
|
const unitNormal = normalizeVec3(normal);
|
|
6511
6575
|
return subVec3(vector, scaleVec3(unitNormal, 2 * dotVec3(vector, unitNormal)));
|
|
6512
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
|
+
}
|
|
6513
6583
|
function rotateY(point, angle) {
|
|
6514
6584
|
const cosine = Math.cos(angle);
|
|
6515
6585
|
const sine = Math.sin(angle);
|
|
@@ -6692,7 +6762,7 @@ function buildDemoDom(root, options) {
|
|
|
6692
6762
|
<section class="plasius-panel plasius-demo__status">
|
|
6693
6763
|
<p id="demoStatus" class="plasius-demo__status-badge">Booting 3D scene\u2026</p>
|
|
6694
6764
|
<p id="demoDetails" class="plasius-demo__status-text">
|
|
6695
|
-
Preparing GLTF
|
|
6765
|
+
Preparing a moonlit harbor scene, GLTF hull data, cloth and fluid continuity plans, and adaptive quality metadata.
|
|
6696
6766
|
</p>
|
|
6697
6767
|
</section>
|
|
6698
6768
|
</section>
|
|
@@ -6720,9 +6790,9 @@ function buildDemoDom(root, options) {
|
|
|
6720
6790
|
</div>
|
|
6721
6791
|
<div class="plasius-demo__legend">
|
|
6722
6792
|
<strong>Scene</strong>
|
|
6723
|
-
GLTF ships carry
|
|
6724
|
-
|
|
6725
|
-
|
|
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.
|
|
6726
6796
|
</div>
|
|
6727
6797
|
</section>
|
|
6728
6798
|
<aside class="plasius-demo__sidebar">
|
|
@@ -6803,6 +6873,7 @@ function buildSceneSnapshot(state, shipModel) {
|
|
|
6803
6873
|
})
|
|
6804
6874
|
)
|
|
6805
6875
|
),
|
|
6876
|
+
shipPhysics: shipModel?.physics ?? null,
|
|
6806
6877
|
physics: Object.freeze({
|
|
6807
6878
|
profile: state.physics.profile,
|
|
6808
6879
|
plan: state.physics.plan,
|
|
@@ -6846,28 +6917,39 @@ function readVisualNumber(value, fallback) {
|
|
|
6846
6917
|
function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {}) {
|
|
6847
6918
|
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
6848
6919
|
const defaults = {
|
|
6849
|
-
skyTop: premiumShadows ? "#
|
|
6850
|
-
skyMid: premiumShadows ? "#
|
|
6851
|
-
skyBottom: premiumShadows ? "#
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
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)",
|
|
6856
6932
|
reflectionStrength: lightingSnapshot.currentLevel.config.reflectionStrength,
|
|
6857
6933
|
shadowAccent: lightingSnapshot.currentLevel.config.shadowStrength,
|
|
6858
|
-
waveAmplitude:
|
|
6859
|
-
waveDirection: { x: 0.
|
|
6860
|
-
wavePhaseSpeed:
|
|
6861
|
-
wakeStrength: 0.
|
|
6862
|
-
wakeLength:
|
|
6863
|
-
collisionRippleStrength: 0.
|
|
6864
|
-
waterNear: { r: 0.
|
|
6865
|
-
waterFar: { r: 0.
|
|
6866
|
-
harborWall: { r: 0.
|
|
6867
|
-
harborDeck: { r: 0.
|
|
6868
|
-
harborTower: { r: 0.
|
|
6869
|
-
flagColor: { r: 0.
|
|
6870
|
-
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)"
|
|
6871
6953
|
};
|
|
6872
6954
|
return {
|
|
6873
6955
|
skyTop: typeof customVisuals.skyTop === "string" ? customVisuals.skyTop : defaults.skyTop,
|
|
@@ -6876,7 +6958,12 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
6876
6958
|
seaTop: typeof customVisuals.seaTop === "string" ? customVisuals.seaTop : defaults.seaTop,
|
|
6877
6959
|
seaMid: typeof customVisuals.seaMid === "string" ? customVisuals.seaMid : defaults.seaMid,
|
|
6878
6960
|
seaBottom: typeof customVisuals.seaBottom === "string" ? customVisuals.seaBottom : defaults.seaBottom,
|
|
6879
|
-
|
|
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,
|
|
6880
6967
|
reflectionStrength: readVisualNumber(
|
|
6881
6968
|
customVisuals.reflectionStrength,
|
|
6882
6969
|
defaults.reflectionStrength
|
|
@@ -6897,7 +6984,16 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
6897
6984
|
harborDeck: normalizeColorOverride(customVisuals.harborDeck, defaults.harborDeck),
|
|
6898
6985
|
harborTower: normalizeColorOverride(customVisuals.harborTower, defaults.harborTower),
|
|
6899
6986
|
flagColor: normalizeColorOverride(customVisuals.flagColor, defaults.flagColor),
|
|
6900
|
-
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
|
|
6901
6997
|
};
|
|
6902
6998
|
}
|
|
6903
6999
|
function buildClothSurface(model, state, meshDetail, visuals) {
|
|
@@ -7132,18 +7228,34 @@ function createSceneState(options) {
|
|
|
7132
7228
|
{
|
|
7133
7229
|
id: "northwind",
|
|
7134
7230
|
position: vec3(-5.2, 0, 7.2),
|
|
7135
|
-
velocity: vec3(2.
|
|
7136
|
-
rotationY: 0.
|
|
7137
|
-
angularVelocity: 0.
|
|
7138
|
-
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
|
|
7139
7243
|
},
|
|
7140
7244
|
{
|
|
7141
7245
|
id: "tidecaller",
|
|
7142
7246
|
position: vec3(4.8, 0, 4.4),
|
|
7143
|
-
velocity: vec3(-
|
|
7144
|
-
rotationY: -2.
|
|
7145
|
-
angularVelocity: -0.
|
|
7146
|
-
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
|
|
7147
7259
|
}
|
|
7148
7260
|
],
|
|
7149
7261
|
sprays: [],
|
|
@@ -7165,41 +7277,71 @@ function setListContent(element, values) {
|
|
|
7165
7277
|
element.innerHTML = values.map((value) => `<li>${value}</li>`).join("");
|
|
7166
7278
|
}
|
|
7167
7279
|
function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, shadowStrength, visuals) {
|
|
7168
|
-
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
7169
7280
|
const sky = ctx.createLinearGradient(0, 0, 0, canvas.height * 0.5);
|
|
7170
7281
|
sky.addColorStop(0, visuals.skyTop);
|
|
7171
|
-
sky.addColorStop(0.
|
|
7282
|
+
sky.addColorStop(0.54, visuals.skyMid);
|
|
7172
7283
|
sky.addColorStop(1, visuals.skyBottom);
|
|
7173
7284
|
ctx.fillStyle = sky;
|
|
7174
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);
|
|
7175
7301
|
const shoreline = ctx.createLinearGradient(0, canvas.height * 0.45, 0, canvas.height);
|
|
7176
7302
|
shoreline.addColorStop(0, visuals.seaTop);
|
|
7177
7303
|
shoreline.addColorStop(0.52, visuals.seaMid);
|
|
7178
7304
|
shoreline.addColorStop(1, visuals.seaBottom);
|
|
7179
7305
|
ctx.fillStyle = shoreline;
|
|
7180
7306
|
ctx.fillRect(0, canvas.height * 0.45, canvas.width, canvas.height * 0.55);
|
|
7181
|
-
const
|
|
7182
|
-
const
|
|
7183
|
-
const
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
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;
|
|
7187
7321
|
ctx.beginPath();
|
|
7188
|
-
ctx.arc(
|
|
7322
|
+
ctx.arc(moonX, moonY, 24, 0, Math.PI * 2);
|
|
7189
7323
|
ctx.fill();
|
|
7190
|
-
const track = ctx.createLinearGradient(
|
|
7191
|
-
track.addColorStop(0,
|
|
7192
|
-
track.addColorStop(0.42,
|
|
7193
|
-
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)");
|
|
7194
7328
|
ctx.save();
|
|
7195
7329
|
ctx.globalCompositeOperation = "screen";
|
|
7196
7330
|
ctx.fillStyle = track;
|
|
7197
7331
|
ctx.beginPath();
|
|
7198
|
-
ctx.ellipse(
|
|
7332
|
+
ctx.ellipse(moonX, canvas.height * 0.75, 38 + shadowStrength * 42, canvas.height * 0.24, 0, 0, Math.PI * 2);
|
|
7199
7333
|
ctx.fill();
|
|
7200
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);
|
|
7201
7340
|
if (state.collisionFlash > 0.01) {
|
|
7202
|
-
ctx.fillStyle =
|
|
7341
|
+
ctx.fillStyle = visuals.collisionFlash.replace(
|
|
7342
|
+
/[\d.]+\)$/u,
|
|
7343
|
+
`${clamp(state.collisionFlash * 0.22, 0, 0.26)})`
|
|
7344
|
+
);
|
|
7203
7345
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
7204
7346
|
}
|
|
7205
7347
|
}
|
|
@@ -7298,7 +7440,7 @@ function pushHarborGeometry(camera, viewport, triangles, visuals) {
|
|
|
7298
7440
|
}
|
|
7299
7441
|
}
|
|
7300
7442
|
function renderShipRigging(ctx, ship, camera, viewport) {
|
|
7301
|
-
const transform = { position: ship.position, rotationY: ship.rotationY, scale:
|
|
7443
|
+
const transform = { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE };
|
|
7302
7444
|
const mastBase = transformPoint(vec3(0, 0.38, -0.4), transform);
|
|
7303
7445
|
const mastTop = transformPoint(vec3(0, 3.8, -0.2), transform);
|
|
7304
7446
|
const aftBase = transformPoint(vec3(-0.25, 0.32, -1.9), transform);
|
|
@@ -7402,6 +7544,35 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
7402
7544
|
}
|
|
7403
7545
|
}
|
|
7404
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
|
+
}
|
|
7405
7576
|
function spawnSpray(state, point, intensity) {
|
|
7406
7577
|
const count = state.fluidDetail.getSnapshot().currentLevel.config.splashCount;
|
|
7407
7578
|
for (let index = 0; index < count; index += 1) {
|
|
@@ -7414,55 +7585,182 @@ function spawnSpray(state, point, intensity) {
|
|
|
7414
7585
|
});
|
|
7415
7586
|
}
|
|
7416
7587
|
}
|
|
7417
|
-
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) {
|
|
7418
7604
|
const physics = shipModel.physics;
|
|
7419
|
-
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) {
|
|
7420
7752
|
let collided = false;
|
|
7421
7753
|
state.contactCount = 0;
|
|
7422
7754
|
for (const ship of state.ships) {
|
|
7423
|
-
|
|
7424
|
-
ship
|
|
7425
|
-
ship.velocity = scaleVec3(ship.velocity, 1 - (physics.linearDamping ?? 0.04) * dt);
|
|
7426
|
-
ship.angularVelocity *= 1 - (physics.angularDamping ?? 0.08) * dt;
|
|
7427
|
-
ship.position.y = sampleWave(state, ship.position.x, ship.position.z, state.time) * 0.22 + (physics.waterline ?? 0.42);
|
|
7428
|
-
if (Math.abs(ship.position.x) > 10) {
|
|
7429
|
-
ship.velocity.x *= -1;
|
|
7430
|
-
ship.angularVelocity *= -1;
|
|
7431
|
-
}
|
|
7432
|
-
if (ship.position.z < 2 || ship.position.z > 16) {
|
|
7433
|
-
ship.velocity.z *= -1;
|
|
7434
|
-
ship.angularVelocity *= -1;
|
|
7435
|
-
}
|
|
7755
|
+
updateShipMotion(state, ship, dt, shipModel);
|
|
7756
|
+
resolveBoundaryCollision(ship, state, shipModel);
|
|
7436
7757
|
}
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
const overlapZ = Math.abs(dz) < halfExtents[2] * 0.8;
|
|
7442
|
-
if (overlapX && overlapZ) {
|
|
7443
|
-
const restitution = physics.restitution ?? 0.22;
|
|
7444
|
-
const swapX = a.velocity.x;
|
|
7445
|
-
const swapZ = a.velocity.z;
|
|
7446
|
-
a.velocity.x = -b.velocity.x * (0.86 + restitution);
|
|
7447
|
-
a.velocity.z = -b.velocity.z * (0.82 + restitution);
|
|
7448
|
-
b.velocity.x = -swapX * (0.86 + restitution);
|
|
7449
|
-
b.velocity.z = -swapZ * (0.82 + restitution);
|
|
7450
|
-
a.angularVelocity += 0.55;
|
|
7451
|
-
b.angularVelocity -= 0.55;
|
|
7452
|
-
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);
|
|
7453
|
-
spawnSpray(state, contactPoint, Math.abs(dx) + Math.abs(dz));
|
|
7454
|
-
state.waveImpulses.push({
|
|
7455
|
-
x: contactPoint.x,
|
|
7456
|
-
z: contactPoint.z,
|
|
7457
|
-
strength: Math.min(1.4, 0.2 + (Math.abs(dx) + Math.abs(dz)) * 0.18),
|
|
7458
|
-
radius: 0.8,
|
|
7459
|
-
life: 1
|
|
7460
|
-
});
|
|
7461
|
-
state.collisionCount += 1;
|
|
7462
|
-
state.contactCount = 1;
|
|
7463
|
-
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
|
+
}
|
|
7464
7762
|
}
|
|
7465
|
-
state.collisionFlash = collided ?
|
|
7763
|
+
state.collisionFlash = collided ? Math.max(0.12, state.collisionFlash) : Math.max(0, state.collisionFlash - dt * 1.3);
|
|
7466
7764
|
}
|
|
7467
7765
|
function updateWaveImpulses(state, dt) {
|
|
7468
7766
|
state.waveImpulses = state.waveImpulses.map((impulse) => ({
|
|
@@ -7575,7 +7873,7 @@ function renderFlagPole(ctx, camera, viewport) {
|
|
|
7575
7873
|
function renderShipShadow(ctx, shipModel, ship, state, camera, viewport, lightDir, shadowStrength) {
|
|
7576
7874
|
const bounds = shipModel.bounds;
|
|
7577
7875
|
const keelY = (shipModel.physics.waterline ?? 0.42) - 0.28;
|
|
7578
|
-
const transform = { position: ship.position, rotationY: ship.rotationY, scale:
|
|
7876
|
+
const transform = { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE };
|
|
7579
7877
|
const hullCorners = [
|
|
7580
7878
|
vec3(bounds.min[0], keelY, bounds.min[2]),
|
|
7581
7879
|
vec3(bounds.max[0], keelY, bounds.min[2]),
|
|
@@ -7601,6 +7899,97 @@ function renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength
|
|
|
7601
7899
|
blur: 12 + shadowStrength * 20
|
|
7602
7900
|
});
|
|
7603
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
|
+
}
|
|
7604
7993
|
function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
7605
7994
|
const viewport = { width: canvas.width, height: canvas.height };
|
|
7606
7995
|
const camera = buildCamera(state, canvas);
|
|
@@ -7610,7 +7999,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7610
7999
|
importance: state.focus === "lighting" ? "critical" : "high"
|
|
7611
8000
|
});
|
|
7612
8001
|
const nearLighting = lightingPlan.bands.find((entry) => entry.band === "near") ?? lightingPlan.bands[0];
|
|
7613
|
-
const lightDir = normalizeVec3(vec3(-0.
|
|
8002
|
+
const lightDir = normalizeVec3(vec3(-0.22, 0.94, -0.31));
|
|
7614
8003
|
const lightingSnapshot = state.lightingDetail.getSnapshot();
|
|
7615
8004
|
const visuals = resolveVisualConfig(
|
|
7616
8005
|
nearLighting,
|
|
@@ -7684,7 +8073,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7684
8073
|
for (const ship of state.ships) {
|
|
7685
8074
|
buildTrianglesFromMesh(
|
|
7686
8075
|
shipModel,
|
|
7687
|
-
{ position: ship.position, rotationY: ship.rotationY, scale:
|
|
8076
|
+
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE },
|
|
7688
8077
|
ship.tint,
|
|
7689
8078
|
camera,
|
|
7690
8079
|
viewport,
|
|
@@ -7698,10 +8087,12 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7698
8087
|
renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength);
|
|
7699
8088
|
drawTriangles(ctx, triangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
7700
8089
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
8090
|
+
renderHarborTorches(ctx, state, camera, viewport, visuals);
|
|
7701
8091
|
renderFlagPole(ctx, camera, viewport);
|
|
7702
8092
|
renderClothAccent(ctx, cloth, camera, viewport);
|
|
7703
8093
|
for (const ship of state.ships) {
|
|
7704
8094
|
renderShipRigging(ctx, ship, camera, viewport);
|
|
8095
|
+
renderShipLanterns(ctx, ship, state, camera, viewport, visuals);
|
|
7705
8096
|
}
|
|
7706
8097
|
renderSprays(ctx, state.sprays, camera, viewport);
|
|
7707
8098
|
const debugSnapshot = state.debugSession.getSnapshot();
|
|
@@ -7713,8 +8104,10 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7713
8104
|
const sceneMetrics = [
|
|
7714
8105
|
`focus: ${state.focus}`,
|
|
7715
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`,
|
|
7716
8108
|
`physics snapshot: ${state.physics.snapshot.stage} (${state.physics.snapshot.stability})`,
|
|
7717
|
-
`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 ")}`,
|
|
7718
8111
|
`cloth band: ${cloth.band} -> ${cloth.representation.output}`,
|
|
7719
8112
|
`fluid near band: ${water.bandMeshes[0].representation.output}`,
|
|
7720
8113
|
`lighting profile: ${lightingPlan.profile} (${lightingDistanceBands.length} bands)`
|
|
@@ -7737,8 +8130,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7737
8130
|
];
|
|
7738
8131
|
const sceneNotes = state.focus === "physics" ? [
|
|
7739
8132
|
"Stable world snapshots are taken after the authoritative rigid-body commit and before visual follow-up work.",
|
|
7740
|
-
"The ships collide
|
|
7741
|
-
"
|
|
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."
|
|
7742
8135
|
] : SCENE_NOTES;
|
|
7743
8136
|
const custom = state.demoDescription ?? null;
|
|
7744
8137
|
setListContent(
|
|
@@ -7755,7 +8148,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7755
8148
|
);
|
|
7756
8149
|
setListContent(dom.sceneNotes, Array.isArray(custom?.notes) ? custom.notes : sceneNotes);
|
|
7757
8150
|
dom.status.textContent = typeof custom?.status === "string" ? custom.status : `3D scene live \xB7 ${state.lastDecision.metrics.fps.toFixed(1)} FPS`;
|
|
7758
|
-
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}.`;
|
|
7759
8152
|
}
|
|
7760
8153
|
function updateSceneState(state, dt, shipModel) {
|
|
7761
8154
|
updateShips(state, dt, shipModel);
|
|
@@ -7774,7 +8167,9 @@ function syncTextState(state, shipModel) {
|
|
|
7774
8167
|
y: Number(ship.position.y.toFixed(2)),
|
|
7775
8168
|
z: Number(ship.position.z.toFixed(2)),
|
|
7776
8169
|
vx: Number(ship.velocity.x.toFixed(2)),
|
|
7777
|
-
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
|
|
7778
8173
|
})),
|
|
7779
8174
|
shipPhysics: shipModel.physics,
|
|
7780
8175
|
sprays: state.sprays.length,
|
|
@@ -7802,6 +8197,7 @@ function syncTextState(state, shipModel) {
|
|
|
7802
8197
|
async function mountGpuShowcase(options = {}) {
|
|
7803
8198
|
injectStyles();
|
|
7804
8199
|
const root = options.root ?? document.body;
|
|
8200
|
+
root.classList?.add?.(ROOT_CLASS);
|
|
7805
8201
|
const previousMarkup = root.innerHTML;
|
|
7806
8202
|
const previousRenderGameToText = window.render_game_to_text;
|
|
7807
8203
|
const previousAdvanceTime = window.advanceTime;
|
|
@@ -7821,8 +8217,11 @@ async function mountGpuShowcase(options = {}) {
|
|
|
7821
8217
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
7822
8218
|
syncTextState(state, shipModel);
|
|
7823
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;
|
|
7824
8224
|
let destroyed = false;
|
|
7825
|
-
let frameHandle = null;
|
|
7826
8225
|
const renderFrame = (nowMs) => {
|
|
7827
8226
|
if (destroyed) {
|
|
7828
8227
|
return;
|
|
@@ -7843,7 +8242,7 @@ async function mountGpuShowcase(options = {}) {
|
|
|
7843
8242
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
7844
8243
|
renderScene(ctx, dom.canvas, state, shipModel, dom);
|
|
7845
8244
|
syncTextState(state, shipModel);
|
|
7846
|
-
|
|
8245
|
+
animationFrameId = requestAnimationFrame(renderFrame);
|
|
7847
8246
|
};
|
|
7848
8247
|
const handlePauseClick = () => {
|
|
7849
8248
|
state.paused = !state.paused;
|
|
@@ -7862,34 +8261,43 @@ async function mountGpuShowcase(options = {}) {
|
|
|
7862
8261
|
dom.pauseButton.addEventListener("click", handlePauseClick);
|
|
7863
8262
|
dom.stressToggle.addEventListener("change", handleStressChange);
|
|
7864
8263
|
dom.focusMode.addEventListener("change", handleFocusChange);
|
|
7865
|
-
|
|
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
|
+
};
|
|
7866
8296
|
return {
|
|
7867
8297
|
state,
|
|
7868
8298
|
shipModel,
|
|
7869
8299
|
canvas: dom.canvas,
|
|
7870
|
-
destroy
|
|
7871
|
-
if (destroyed) {
|
|
7872
|
-
return;
|
|
7873
|
-
}
|
|
7874
|
-
destroyed = true;
|
|
7875
|
-
if (frameHandle != null) {
|
|
7876
|
-
cancelAnimationFrame(frameHandle);
|
|
7877
|
-
}
|
|
7878
|
-
dom.pauseButton.removeEventListener("click", handlePauseClick);
|
|
7879
|
-
dom.stressToggle.removeEventListener("change", handleStressChange);
|
|
7880
|
-
dom.focusMode.removeEventListener("change", handleFocusChange);
|
|
7881
|
-
root.innerHTML = previousMarkup;
|
|
7882
|
-
if (typeof previousRenderGameToText === "function") {
|
|
7883
|
-
window.render_game_to_text = previousRenderGameToText;
|
|
7884
|
-
} else {
|
|
7885
|
-
delete window.render_game_to_text;
|
|
7886
|
-
}
|
|
7887
|
-
if (typeof previousAdvanceTime === "function") {
|
|
7888
|
-
window.advanceTime = previousAdvanceTime;
|
|
7889
|
-
} else {
|
|
7890
|
-
delete window.advanceTime;
|
|
7891
|
-
}
|
|
7892
|
-
}
|
|
8300
|
+
destroy
|
|
7893
8301
|
};
|
|
7894
8302
|
}
|
|
7895
8303
|
function updatePhysicsSnapshot(state, shipModel) {
|
|
@@ -7903,7 +8311,7 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
7903
8311
|
animationInputRevision: state.frame,
|
|
7904
8312
|
bodyCount: state.ships.length + 2,
|
|
7905
8313
|
dynamicBodyCount: state.ships.length,
|
|
7906
|
-
contactCount: state.
|
|
8314
|
+
contactCount: state.contactCount,
|
|
7907
8315
|
metadata: {
|
|
7908
8316
|
collisionCount: state.collisionCount,
|
|
7909
8317
|
contactCount: state.contactCount,
|
|
@@ -7912,7 +8320,7 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
7912
8320
|
}
|
|
7913
8321
|
});
|
|
7914
8322
|
}
|
|
7915
|
-
var
|
|
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;
|
|
7916
8324
|
var init_showcase_runtime = __esm({
|
|
7917
8325
|
"src/showcase-runtime.js"() {
|
|
7918
8326
|
init_dist();
|
|
@@ -7921,11 +8329,19 @@ var init_showcase_runtime = __esm({
|
|
|
7921
8329
|
init_dist4();
|
|
7922
8330
|
init_dist5();
|
|
7923
8331
|
init_browser();
|
|
8332
|
+
init_asset_url();
|
|
7924
8333
|
init_gltf_loader();
|
|
7925
|
-
import_meta2 = {};
|
|
7926
8334
|
STYLE_ID = "plasius-shared-3d-showcase-style";
|
|
8335
|
+
ROOT_CLASS = "plasius-showcase-root";
|
|
7927
8336
|
DEFAULT_TITLE = "Flag by the Sea";
|
|
7928
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
|
+
});
|
|
7929
8345
|
CAMERA_PRESETS = Object.freeze({
|
|
7930
8346
|
integrated: Object.freeze({ yaw: -0.55, pitch: 0.34, distance: 27, target: [0, 2.2, 0] }),
|
|
7931
8347
|
lighting: Object.freeze({ yaw: -0.28, pitch: 0.28, distance: 23, target: [0, 2.8, 0] }),
|
|
@@ -7937,10 +8353,10 @@ var init_showcase_runtime = __esm({
|
|
|
7937
8353
|
});
|
|
7938
8354
|
showcaseFocusModes = Object.freeze(Object.keys(CAMERA_PRESETS));
|
|
7939
8355
|
SCENE_NOTES = Object.freeze([
|
|
7940
|
-
"Ships are loaded from a GLTF asset and carry
|
|
7941
|
-
"
|
|
7942
|
-
"Cloth and fluid continuity stay coherent across near, mid, far, and horizon bands.",
|
|
7943
|
-
"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."
|
|
7944
8360
|
]);
|
|
7945
8361
|
UNIT_BOX_MESH = Object.freeze({
|
|
7946
8362
|
positions: Object.freeze([
|
|
@@ -8008,6 +8424,17 @@ var init_showcase_runtime = __esm({
|
|
|
8008
8424
|
0
|
|
8009
8425
|
])
|
|
8010
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
|
+
]);
|
|
8011
8438
|
}
|
|
8012
8439
|
});
|
|
8013
8440
|
|
|
@@ -8016,11 +8443,11 @@ var index_exports = {};
|
|
|
8016
8443
|
__export(index_exports, {
|
|
8017
8444
|
loadGltfModel: () => loadGltfModel2,
|
|
8018
8445
|
mountGpuShowcase: () => mountGpuShowcase2,
|
|
8019
|
-
resolveShowcaseAssetUrl: () =>
|
|
8446
|
+
resolveShowcaseAssetUrl: () => resolveShowcaseAssetUrl,
|
|
8020
8447
|
showcaseFocusModes: () => showcaseFocusModes2
|
|
8021
8448
|
});
|
|
8022
8449
|
module.exports = __toCommonJS(index_exports);
|
|
8023
|
-
|
|
8450
|
+
init_asset_url();
|
|
8024
8451
|
var showcaseFocusModes2 = Object.freeze([
|
|
8025
8452
|
"integrated",
|
|
8026
8453
|
"lighting",
|
|
@@ -8030,9 +8457,6 @@ var showcaseFocusModes2 = Object.freeze([
|
|
|
8030
8457
|
"performance",
|
|
8031
8458
|
"debug"
|
|
8032
8459
|
]);
|
|
8033
|
-
function resolveShowcaseAssetUrl2(baseUrl2 = import_meta3.url) {
|
|
8034
|
-
return new URL("../assets/brigantine.gltf", baseUrl2);
|
|
8035
|
-
}
|
|
8036
8460
|
async function loadGltfModel2(url) {
|
|
8037
8461
|
const module2 = await Promise.resolve().then(() => (init_gltf_loader(), gltf_loader_exports));
|
|
8038
8462
|
return module2.loadGltfModel(url);
|