@plasius/gpu-shared 0.1.11 → 0.1.13
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 +36 -3
- package/README.md +55 -1
- package/assets/brigantine.gltf +549 -24
- package/assets/cutter.gltf +538 -0
- package/assets/harbor-dock.gltf +680 -0
- package/assets/lighthouse.gltf +604 -0
- package/dist/chunk-2FIFSBB4.js +74 -0
- package/dist/chunk-2FIFSBB4.js.map +1 -0
- package/dist/chunk-DABW627O.js +113 -0
- package/dist/chunk-DABW627O.js.map +1 -0
- package/dist/chunk-DQX4DXBR.js +369 -0
- package/dist/chunk-DQX4DXBR.js.map +1 -0
- package/dist/chunk-NCPJWLX3.js +17 -0
- package/dist/chunk-NCPJWLX3.js.map +1 -0
- package/dist/gltf-loader-WAM23F37.js +9 -0
- package/dist/index.cjs +1255 -279
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +19 -6
- package/dist/index.js.map +1 -1
- package/dist/showcase-inline-assets-B7U7VX5H.js +7 -0
- package/dist/{showcase-runtime-2ZNPKD7D.js → showcase-runtime-PN7N3FZY.js} +808 -237
- package/dist/showcase-runtime-PN7N3FZY.js.map +1 -0
- package/package.json +15 -1
- package/src/asset-url.js +62 -11
- package/src/feature-flags.js +1 -0
- package/src/gltf-loader.js +322 -32
- package/src/i18n.js +71 -0
- package/src/index.d.ts +115 -1
- package/src/index.js +9 -1
- package/src/showcase-inline-assets.js +3 -0
- package/src/showcase-runtime.js +912 -188
- package/src/translations/en-GB.js +55 -0
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-OTCJ3VOK.js +0 -35
- package/dist/chunk-OTCJ3VOK.js.map +0 -1
- package/dist/chunk-QBMXJ3V2.js +0 -142
- package/dist/chunk-QBMXJ3V2.js.map +0 -1
- package/dist/gltf-loader-LKALCZAV.js +0 -8
- package/dist/showcase-runtime-2ZNPKD7D.js.map +0 -1
- /package/dist/{chunk-DGUM43GV.js.map → gltf-loader-WAM23F37.js.map} +0 -0
- /package/dist/{gltf-loader-LKALCZAV.js.map → showcase-inline-assets-B7U7VX5H.js.map} +0 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
createGpuSharedTranslator,
|
|
3
|
+
gpuSharedTranslationKeys
|
|
4
|
+
} from "./chunk-DABW627O.js";
|
|
4
5
|
import {
|
|
5
6
|
loadGltfModel
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DQX4DXBR.js";
|
|
8
|
+
import {
|
|
9
|
+
resolveShowcaseAssetUrl
|
|
10
|
+
} from "./chunk-2FIFSBB4.js";
|
|
7
11
|
import {
|
|
8
12
|
__require
|
|
9
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-NCPJWLX3.js";
|
|
10
14
|
|
|
11
15
|
// node_modules/@plasius/gpu-cloth/dist/index.js
|
|
12
16
|
var clothProfileNames = ["interactive", "cinematic"];
|
|
@@ -2134,6 +2138,16 @@ var lightingProfiles = Object.freeze(
|
|
|
2134
2138
|
);
|
|
2135
2139
|
var lightingProfileNames = Object.freeze(Object.keys(lightingProfiles));
|
|
2136
2140
|
var defaultLightingProfile = "realtime";
|
|
2141
|
+
var lightingProfileModeOrder = Object.freeze([
|
|
2142
|
+
"realtime",
|
|
2143
|
+
"hybrid",
|
|
2144
|
+
"reference"
|
|
2145
|
+
]);
|
|
2146
|
+
var defaultAdaptiveLightingProfilePolicy = Object.freeze({
|
|
2147
|
+
preferredProfile: "reference",
|
|
2148
|
+
minimumFrameRate: 30,
|
|
2149
|
+
sampleWindowSize: 4
|
|
2150
|
+
});
|
|
2137
2151
|
var lightingDistanceBands = Object.freeze([
|
|
2138
2152
|
"near",
|
|
2139
2153
|
"mid",
|
|
@@ -2261,6 +2275,11 @@ function createLightingBandPlan(options = {}) {
|
|
|
2261
2275
|
bands
|
|
2262
2276
|
});
|
|
2263
2277
|
}
|
|
2278
|
+
var lightingProfileModeEstimatedCostMs = Object.freeze({
|
|
2279
|
+
realtime: 4.5,
|
|
2280
|
+
hybrid: 7.5,
|
|
2281
|
+
reference: 12.5
|
|
2282
|
+
});
|
|
2264
2283
|
function buildWorkerBudgetLevels(jobType, queueClass, presets) {
|
|
2265
2284
|
return Object.freeze([
|
|
2266
2285
|
Object.freeze({
|
|
@@ -6107,11 +6126,16 @@ function createPhysicsWorldSnapshot(input) {
|
|
|
6107
6126
|
});
|
|
6108
6127
|
}
|
|
6109
6128
|
|
|
6129
|
+
// src/feature-flags.js
|
|
6130
|
+
var GPU_SHOWCASE_REALISTIC_MODELS_FEATURE = "gpu_showcase_realistic_models_v1";
|
|
6131
|
+
|
|
6110
6132
|
// src/showcase-runtime.js
|
|
6111
6133
|
var STYLE_ID = "plasius-shared-3d-showcase-style";
|
|
6112
6134
|
var ROOT_CLASS = "plasius-showcase-root";
|
|
6113
|
-
var
|
|
6114
|
-
var
|
|
6135
|
+
var CAPTURE_CLASS = "plasius-showcase-root--capture";
|
|
6136
|
+
var DEFAULT_CANVAS_WIDTH = 1280;
|
|
6137
|
+
var DEFAULT_CANVAS_HEIGHT = 720;
|
|
6138
|
+
var CAPTURE_CANVAS_PIXEL_BUDGET = 1920 * 1080;
|
|
6115
6139
|
var SHIP_SCALE = 1.1;
|
|
6116
6140
|
var HARBOR_BOUNDS = Object.freeze({
|
|
6117
6141
|
minX: -11.2,
|
|
@@ -6129,84 +6153,75 @@ var CAMERA_PRESETS = Object.freeze({
|
|
|
6129
6153
|
debug: Object.freeze({ yaw: -0.7, pitch: 0.32, distance: 24, target: [0, 2.2, 0] })
|
|
6130
6154
|
});
|
|
6131
6155
|
var showcaseFocusModes = Object.freeze(Object.keys(CAMERA_PRESETS));
|
|
6132
|
-
var
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
-0.5,
|
|
6141
|
-
-0.5,
|
|
6142
|
-
-0.5,
|
|
6143
|
-
0.5,
|
|
6144
|
-
-0.5,
|
|
6145
|
-
-0.5,
|
|
6146
|
-
0.5,
|
|
6147
|
-
0.5,
|
|
6148
|
-
-0.5,
|
|
6149
|
-
-0.5,
|
|
6150
|
-
0.5,
|
|
6151
|
-
-0.5,
|
|
6152
|
-
-0.5,
|
|
6153
|
-
-0.5,
|
|
6154
|
-
0.5,
|
|
6155
|
-
0.5,
|
|
6156
|
-
-0.5,
|
|
6157
|
-
0.5,
|
|
6158
|
-
0.5,
|
|
6159
|
-
0.5,
|
|
6160
|
-
0.5,
|
|
6161
|
-
-0.5,
|
|
6162
|
-
0.5,
|
|
6163
|
-
0.5
|
|
6164
|
-
]),
|
|
6165
|
-
indices: Object.freeze([
|
|
6166
|
-
0,
|
|
6167
|
-
1,
|
|
6168
|
-
2,
|
|
6169
|
-
0,
|
|
6170
|
-
2,
|
|
6171
|
-
3,
|
|
6172
|
-
5,
|
|
6173
|
-
4,
|
|
6174
|
-
7,
|
|
6175
|
-
5,
|
|
6176
|
-
7,
|
|
6177
|
-
6,
|
|
6178
|
-
4,
|
|
6179
|
-
0,
|
|
6180
|
-
3,
|
|
6181
|
-
4,
|
|
6182
|
-
3,
|
|
6183
|
-
7,
|
|
6184
|
-
1,
|
|
6185
|
-
5,
|
|
6186
|
-
6,
|
|
6187
|
-
1,
|
|
6188
|
-
6,
|
|
6189
|
-
2,
|
|
6190
|
-
3,
|
|
6191
|
-
2,
|
|
6192
|
-
6,
|
|
6193
|
-
3,
|
|
6194
|
-
6,
|
|
6195
|
-
7,
|
|
6196
|
-
4,
|
|
6197
|
-
5,
|
|
6198
|
-
1,
|
|
6199
|
-
4,
|
|
6200
|
-
1,
|
|
6201
|
-
0
|
|
6202
|
-
])
|
|
6156
|
+
var FOCUS_MODE_TRANSLATION_KEYS = Object.freeze({
|
|
6157
|
+
integrated: gpuSharedTranslationKeys.focusIntegrated,
|
|
6158
|
+
lighting: gpuSharedTranslationKeys.focusLighting,
|
|
6159
|
+
cloth: gpuSharedTranslationKeys.focusCloth,
|
|
6160
|
+
fluid: gpuSharedTranslationKeys.focusFluid,
|
|
6161
|
+
physics: gpuSharedTranslationKeys.focusPhysics,
|
|
6162
|
+
performance: gpuSharedTranslationKeys.focusPerformance,
|
|
6163
|
+
debug: gpuSharedTranslationKeys.focusDebug
|
|
6203
6164
|
});
|
|
6165
|
+
var SCENE_NOTE_KEYS = Object.freeze([
|
|
6166
|
+
gpuSharedTranslationKeys.noteAssetLoading,
|
|
6167
|
+
gpuSharedTranslationKeys.noteMoonlight,
|
|
6168
|
+
gpuSharedTranslationKeys.noteContinuity,
|
|
6169
|
+
gpuSharedTranslationKeys.notePerformance
|
|
6170
|
+
]);
|
|
6171
|
+
var PHYSICS_SCENE_NOTE_KEYS = Object.freeze([
|
|
6172
|
+
gpuSharedTranslationKeys.notePhysicsSnapshots,
|
|
6173
|
+
gpuSharedTranslationKeys.notePhysicsCollisions,
|
|
6174
|
+
gpuSharedTranslationKeys.notePhysicsLighting
|
|
6175
|
+
]);
|
|
6176
|
+
var LEGACY_HARBOR_LAYOUT = Object.freeze([
|
|
6177
|
+
Object.freeze({
|
|
6178
|
+
position: Object.freeze({ x: -8.2, y: 1.1, z: -0.9 }),
|
|
6179
|
+
rotationY: -0.16,
|
|
6180
|
+
scale: 5.4,
|
|
6181
|
+
color: { r: 0.32, g: 0.27, b: 0.23, a: 1 },
|
|
6182
|
+
accent: 0.06
|
|
6183
|
+
}),
|
|
6184
|
+
Object.freeze({
|
|
6185
|
+
position: Object.freeze({ x: -5.7, y: 0.45, z: 1.4 }),
|
|
6186
|
+
rotationY: -0.08,
|
|
6187
|
+
scale: { x: 6.8, y: 0.3, z: 2.1 },
|
|
6188
|
+
color: { r: 0.31, g: 0.31, b: 0.34, a: 1 },
|
|
6189
|
+
accent: 0.04
|
|
6190
|
+
}),
|
|
6191
|
+
Object.freeze({
|
|
6192
|
+
position: Object.freeze({ x: -10.4, y: 0.28, z: 0.8 }),
|
|
6193
|
+
rotationY: 0.22,
|
|
6194
|
+
scale: { x: 1.2, y: 0.9, z: 1.2 },
|
|
6195
|
+
color: { r: 0.31, g: 0.35, b: 0.39, a: 1 },
|
|
6196
|
+
accent: 0.02
|
|
6197
|
+
})
|
|
6198
|
+
]);
|
|
6199
|
+
var SHOWCASE_ENVIRONMENT_LAYOUT = Object.freeze([
|
|
6200
|
+
Object.freeze({
|
|
6201
|
+
assetKey: "harbor-dock",
|
|
6202
|
+
position: Object.freeze({ x: -4.6, y: 0.16, z: 0.7 }),
|
|
6203
|
+
rotationY: -0.08,
|
|
6204
|
+
scale: 0.84,
|
|
6205
|
+
accent: 0.04
|
|
6206
|
+
}),
|
|
6207
|
+
Object.freeze({
|
|
6208
|
+
assetKey: "lighthouse",
|
|
6209
|
+
position: Object.freeze({ x: -9.8, y: 0, z: -0.58 }),
|
|
6210
|
+
rotationY: 0.12,
|
|
6211
|
+
scale: 0.56,
|
|
6212
|
+
accent: 0.08
|
|
6213
|
+
})
|
|
6214
|
+
]);
|
|
6204
6215
|
var SHIP_LANTERNS = Object.freeze([
|
|
6205
6216
|
Object.freeze({ x: 0.94, y: 1.54, z: 2.52, glow: 1 }),
|
|
6206
6217
|
Object.freeze({ x: -0.9, y: 1.58, z: 2.44, glow: 0.92 }),
|
|
6207
6218
|
Object.freeze({ x: 0.62, y: 1.42, z: -2.18, glow: 0.88 }),
|
|
6208
6219
|
Object.freeze({ x: -0.58, y: 1.46, z: -2.04, glow: 0.84 })
|
|
6209
6220
|
]);
|
|
6221
|
+
var CUTTER_LANTERNS = Object.freeze([
|
|
6222
|
+
Object.freeze({ x: 0.42, y: 1.04, z: 1.18, glow: 0.94 }),
|
|
6223
|
+
Object.freeze({ x: -0.42, y: 1.04, z: 1.12, glow: 0.88 })
|
|
6224
|
+
]);
|
|
6210
6225
|
var HARBOR_TORCHES = Object.freeze([
|
|
6211
6226
|
Object.freeze({ x: -5.2, y: 1.25, z: 1.36, glow: 1.1 }),
|
|
6212
6227
|
Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
|
|
@@ -6243,6 +6258,11 @@ function injectStyles() {
|
|
|
6243
6258
|
radial-gradient(circle at 82% 18%, rgba(240, 188, 103, 0.08), transparent 18%),
|
|
6244
6259
|
linear-gradient(180deg, #04101d 0%, #0b1930 42%, #081321 100%);
|
|
6245
6260
|
}
|
|
6261
|
+
.${ROOT_CLASS}.${CAPTURE_CLASS} {
|
|
6262
|
+
min-height: 100vh;
|
|
6263
|
+
overflow: hidden;
|
|
6264
|
+
background: #030710;
|
|
6265
|
+
}
|
|
6246
6266
|
.${ROOT_CLASS},
|
|
6247
6267
|
.${ROOT_CLASS} * {
|
|
6248
6268
|
box-sizing: border-box;
|
|
@@ -6322,6 +6342,40 @@ function injectStyles() {
|
|
|
6322
6342
|
border: 1px solid rgba(159, 185, 223, 0.12);
|
|
6323
6343
|
background: linear-gradient(180deg, #071220 0%, #132440 42%, #10344b 42%, #05111d 100%);
|
|
6324
6344
|
}
|
|
6345
|
+
.${CAPTURE_CLASS} .plasius-demo {
|
|
6346
|
+
width: 100vw;
|
|
6347
|
+
height: 100vh;
|
|
6348
|
+
padding: 0;
|
|
6349
|
+
display: block;
|
|
6350
|
+
}
|
|
6351
|
+
.${CAPTURE_CLASS} .plasius-demo__hero,
|
|
6352
|
+
.${CAPTURE_CLASS} .plasius-demo__toolbar,
|
|
6353
|
+
.${CAPTURE_CLASS} .plasius-demo__legend,
|
|
6354
|
+
.${CAPTURE_CLASS} .plasius-demo__sidebar,
|
|
6355
|
+
.${CAPTURE_CLASS} .plasius-demo__footer {
|
|
6356
|
+
display: none;
|
|
6357
|
+
}
|
|
6358
|
+
.${CAPTURE_CLASS} .plasius-demo__layout {
|
|
6359
|
+
display: block;
|
|
6360
|
+
height: 100%;
|
|
6361
|
+
}
|
|
6362
|
+
.${CAPTURE_CLASS} .plasius-demo__canvas-panel {
|
|
6363
|
+
height: 100%;
|
|
6364
|
+
padding: 0;
|
|
6365
|
+
border: 0;
|
|
6366
|
+
border-radius: 0;
|
|
6367
|
+
background: transparent;
|
|
6368
|
+
box-shadow: none;
|
|
6369
|
+
backdrop-filter: none;
|
|
6370
|
+
}
|
|
6371
|
+
.${CAPTURE_CLASS} .plasius-demo__canvas {
|
|
6372
|
+
width: 100%;
|
|
6373
|
+
height: 100%;
|
|
6374
|
+
aspect-ratio: auto;
|
|
6375
|
+
border: 0;
|
|
6376
|
+
border-radius: 0;
|
|
6377
|
+
background: #030710;
|
|
6378
|
+
}
|
|
6325
6379
|
.plasius-demo__toolbar {
|
|
6326
6380
|
position: absolute;
|
|
6327
6381
|
top: 26px;
|
|
@@ -6413,6 +6467,10 @@ function clamp(value, min, max) {
|
|
|
6413
6467
|
function mix(a, b, t) {
|
|
6414
6468
|
return a + (b - a) * t;
|
|
6415
6469
|
}
|
|
6470
|
+
function smoothstep(min, max, value) {
|
|
6471
|
+
const t = clamp((value - min) / Math.max(1e-4, max - min), 0, 1);
|
|
6472
|
+
return t * t * (3 - 2 * t);
|
|
6473
|
+
}
|
|
6416
6474
|
function pseudoRandom(seed) {
|
|
6417
6475
|
const value = Math.sin(seed * 12.9898 + seed * seed * 17e-4) * 43758.5453;
|
|
6418
6476
|
return value - Math.floor(value);
|
|
@@ -6471,6 +6529,11 @@ function transformPoint(point, transform) {
|
|
|
6471
6529
|
const rotated = rotateY(scaled, transform.rotationY);
|
|
6472
6530
|
return addVec3(rotated, transform.position);
|
|
6473
6531
|
}
|
|
6532
|
+
function transformDirection(direction, transform) {
|
|
6533
|
+
const scale = typeof transform.scale === "number" ? { x: transform.scale, y: transform.scale, z: transform.scale } : transform.scale;
|
|
6534
|
+
const scaled = vec3(direction.x * scale.x, direction.y * scale.y, direction.z * scale.z);
|
|
6535
|
+
return normalizeVec3(rotateY(scaled, transform.rotationY));
|
|
6536
|
+
}
|
|
6474
6537
|
function projectPoint(point, camera, viewport) {
|
|
6475
6538
|
const relative = subVec3(point, camera.eye);
|
|
6476
6539
|
const viewX = dotVec3(relative, camera.right);
|
|
@@ -6494,6 +6557,66 @@ function colorToRgba(color, alpha = 1) {
|
|
|
6494
6557
|
const b = Math.round(clamp(color.b, 0, 1) * 255);
|
|
6495
6558
|
return `rgba(${r}, ${g}, ${b}, ${clamp(alpha, 0, 1)})`;
|
|
6496
6559
|
}
|
|
6560
|
+
function mixColor(a, b, t) {
|
|
6561
|
+
return {
|
|
6562
|
+
r: mix(a.r, b.r, t),
|
|
6563
|
+
g: mix(a.g, b.g, t),
|
|
6564
|
+
b: mix(a.b, b.b, t),
|
|
6565
|
+
a: mix(a.a ?? 1, b.a ?? 1, t)
|
|
6566
|
+
};
|
|
6567
|
+
}
|
|
6568
|
+
function multiplyColor(a, b) {
|
|
6569
|
+
return {
|
|
6570
|
+
r: a.r * b.r,
|
|
6571
|
+
g: a.g * b.g,
|
|
6572
|
+
b: a.b * b.b,
|
|
6573
|
+
a: (a.a ?? 1) * (b.a ?? 1)
|
|
6574
|
+
};
|
|
6575
|
+
}
|
|
6576
|
+
function createLegacyMeshPrimitive(mesh) {
|
|
6577
|
+
return Object.freeze({
|
|
6578
|
+
name: mesh.name ?? "legacy-mesh",
|
|
6579
|
+
positions: mesh.positions,
|
|
6580
|
+
indices: mesh.indices,
|
|
6581
|
+
normals: null,
|
|
6582
|
+
colors: null,
|
|
6583
|
+
material: Object.freeze({
|
|
6584
|
+
name: "legacy-material",
|
|
6585
|
+
color: mesh.color ?? { r: 0.56, g: 0.33, b: 0.22, a: 1 },
|
|
6586
|
+
roughness: 0.88,
|
|
6587
|
+
metallic: 0.08,
|
|
6588
|
+
emissive: Object.freeze({ r: 0, g: 0, b: 0 })
|
|
6589
|
+
})
|
|
6590
|
+
});
|
|
6591
|
+
}
|
|
6592
|
+
function isFeatureEnabled(featureFlags, featureName, fallback = true) {
|
|
6593
|
+
const directValue = typeof featureFlags?.[featureName] === "boolean" ? featureFlags[featureName] : featureFlags?.flags?.[featureName];
|
|
6594
|
+
if (typeof directValue === "boolean") {
|
|
6595
|
+
return directValue;
|
|
6596
|
+
}
|
|
6597
|
+
const enabledValue = typeof featureFlags?.enabled?.[featureName] === "boolean" ? featureFlags.enabled[featureName] : void 0;
|
|
6598
|
+
if (typeof enabledValue === "boolean") {
|
|
6599
|
+
return enabledValue;
|
|
6600
|
+
}
|
|
6601
|
+
return fallback;
|
|
6602
|
+
}
|
|
6603
|
+
function getMeshPrimitives(mesh) {
|
|
6604
|
+
return Array.isArray(mesh?.primitives) && mesh.primitives.length > 0 ? mesh.primitives : [createLegacyMeshPrimitive(mesh)];
|
|
6605
|
+
}
|
|
6606
|
+
function tintPrimitiveColor(material, colorOverride) {
|
|
6607
|
+
if (!colorOverride) {
|
|
6608
|
+
return material.color;
|
|
6609
|
+
}
|
|
6610
|
+
const name = String(material.name ?? "").toLowerCase();
|
|
6611
|
+
if (name.includes("sail") || name.includes("glass") || name.includes("roof")) {
|
|
6612
|
+
return material.color;
|
|
6613
|
+
}
|
|
6614
|
+
const tintAmount = name.includes("hull") ? 0.54 : name.includes("trim") ? 0.22 : name.includes("deck") ? 0.12 : 0;
|
|
6615
|
+
if (tintAmount <= 0) {
|
|
6616
|
+
return material.color;
|
|
6617
|
+
}
|
|
6618
|
+
return mixColor(material.color, multiplyColor(material.color, colorOverride), tintAmount);
|
|
6619
|
+
}
|
|
6497
6620
|
function projectShadowPoint(point, lightDir, planeY) {
|
|
6498
6621
|
const shadowDir = scaleVec3(lightDir, -1);
|
|
6499
6622
|
if (Math.abs(shadowDir.y) < 1e-4) {
|
|
@@ -6514,6 +6637,50 @@ function shadeColor(base, normal, lightDir, heightBias = 0, accent = 0) {
|
|
|
6514
6637
|
b: clamp(base.b * (brightness + 0.03), 0, 1)
|
|
6515
6638
|
};
|
|
6516
6639
|
}
|
|
6640
|
+
function getMaterialSeed(materialName) {
|
|
6641
|
+
let seed = 0;
|
|
6642
|
+
for (let index = 0; index < materialName.length; index += 1) {
|
|
6643
|
+
seed += materialName.charCodeAt(index) * (index + 1);
|
|
6644
|
+
}
|
|
6645
|
+
return seed;
|
|
6646
|
+
}
|
|
6647
|
+
function getMaterialDetailStrength(material, surfaceType) {
|
|
6648
|
+
const name = String(material?.name ?? "").toLowerCase();
|
|
6649
|
+
if (surfaceType === "water" || name.includes("glass")) {
|
|
6650
|
+
return 0.018;
|
|
6651
|
+
}
|
|
6652
|
+
if (name.includes("wood") || name.includes("timber") || name.includes("plank")) {
|
|
6653
|
+
return 0.13;
|
|
6654
|
+
}
|
|
6655
|
+
if (name.includes("stone") || name.includes("concrete") || name.includes("plaster")) {
|
|
6656
|
+
return 0.1;
|
|
6657
|
+
}
|
|
6658
|
+
if (name.includes("roof") || name.includes("crate")) {
|
|
6659
|
+
return 0.09;
|
|
6660
|
+
}
|
|
6661
|
+
if (name.includes("paint")) {
|
|
6662
|
+
return 0.045;
|
|
6663
|
+
}
|
|
6664
|
+
if (name.includes("metal")) {
|
|
6665
|
+
return 0.035;
|
|
6666
|
+
}
|
|
6667
|
+
return 0.04;
|
|
6668
|
+
}
|
|
6669
|
+
function applyMaterialDetail(color, material, worldCenter, normal, surfaceType) {
|
|
6670
|
+
const materialName = String(material?.name ?? surfaceType ?? "material");
|
|
6671
|
+
const detailStrength = getMaterialDetailStrength(material, surfaceType);
|
|
6672
|
+
const sample = worldCenter.x * 3.17 + worldCenter.y * 5.29 + worldCenter.z * 7.83 + getMaterialSeed(materialName) * 0.013;
|
|
6673
|
+
const grain = (pseudoRandom(sample) - 0.5) * detailStrength;
|
|
6674
|
+
const lowerSurface = smoothstep(7.5, -0.8, worldCenter.y);
|
|
6675
|
+
const verticalSurface = 1 - clamp(Math.abs(normal.y), 0, 1);
|
|
6676
|
+
const materialLowerWear = /stone|concrete|plaster|paint|wood|timber|plank|crate/.test(materialName.toLowerCase()) ? lowerSurface * verticalSurface * 0.055 : 0;
|
|
6677
|
+
const wetlineWear = surfaceType === "ship" && worldCenter.y < 0.72 ? smoothstep(0.72, -0.1, worldCenter.y) * 0.05 : 0;
|
|
6678
|
+
return {
|
|
6679
|
+
r: clamp(color.r * (1 + grain) - materialLowerWear - wetlineWear, 0, 1),
|
|
6680
|
+
g: clamp(color.g * (1 + grain * 0.82) - materialLowerWear * 0.9 - wetlineWear, 0, 1),
|
|
6681
|
+
b: clamp(color.b * (1 + grain * 0.62) - materialLowerWear * 0.68 - wetlineWear * 0.75, 0, 1)
|
|
6682
|
+
};
|
|
6683
|
+
}
|
|
6517
6684
|
function buildCamera(state, canvas) {
|
|
6518
6685
|
const preset = CAMERA_PRESETS[state.focus] ?? CAMERA_PRESETS.integrated;
|
|
6519
6686
|
const yaw = state.camera.yaw ?? preset.yaw;
|
|
@@ -6538,44 +6705,131 @@ function buildCamera(state, canvas) {
|
|
|
6538
6705
|
aspect: canvas.width / canvas.height
|
|
6539
6706
|
};
|
|
6540
6707
|
}
|
|
6541
|
-
function buildTrianglesFromMesh(mesh, transform,
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
const
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6708
|
+
function buildTrianglesFromMesh(mesh, transform, colorOverride, camera, viewport, triangles, options = {}) {
|
|
6709
|
+
const primitives = getMeshPrimitives(mesh);
|
|
6710
|
+
for (const primitive of primitives) {
|
|
6711
|
+
const resolvedColor = tintPrimitiveColor(primitive.material, colorOverride);
|
|
6712
|
+
for (let index = 0; index < primitive.indices.length; index += 3) {
|
|
6713
|
+
const aIndex = primitive.indices[index] * 3;
|
|
6714
|
+
const bIndex = primitive.indices[index + 1] * 3;
|
|
6715
|
+
const cIndex = primitive.indices[index + 2] * 3;
|
|
6716
|
+
const a = transformPoint(
|
|
6717
|
+
vec3(
|
|
6718
|
+
primitive.positions[aIndex],
|
|
6719
|
+
primitive.positions[aIndex + 1],
|
|
6720
|
+
primitive.positions[aIndex + 2]
|
|
6721
|
+
),
|
|
6722
|
+
transform
|
|
6723
|
+
);
|
|
6724
|
+
const b = transformPoint(
|
|
6725
|
+
vec3(
|
|
6726
|
+
primitive.positions[bIndex],
|
|
6727
|
+
primitive.positions[bIndex + 1],
|
|
6728
|
+
primitive.positions[bIndex + 2]
|
|
6729
|
+
),
|
|
6730
|
+
transform
|
|
6731
|
+
);
|
|
6732
|
+
const c = transformPoint(
|
|
6733
|
+
vec3(
|
|
6734
|
+
primitive.positions[cIndex],
|
|
6735
|
+
primitive.positions[cIndex + 1],
|
|
6736
|
+
primitive.positions[cIndex + 2]
|
|
6737
|
+
),
|
|
6738
|
+
transform
|
|
6739
|
+
);
|
|
6740
|
+
const ab = subVec3(b, a);
|
|
6741
|
+
const ac = subVec3(c, a);
|
|
6742
|
+
const faceNormal = normalizeVec3(crossVec3(ab, ac));
|
|
6743
|
+
let normal = faceNormal;
|
|
6744
|
+
if (Array.isArray(primitive.normals)) {
|
|
6745
|
+
const aNormal = transformDirection(
|
|
6746
|
+
vec3(
|
|
6747
|
+
primitive.normals[aIndex],
|
|
6748
|
+
primitive.normals[aIndex + 1],
|
|
6749
|
+
primitive.normals[aIndex + 2]
|
|
6750
|
+
),
|
|
6751
|
+
transform
|
|
6752
|
+
);
|
|
6753
|
+
const bNormal = transformDirection(
|
|
6754
|
+
vec3(
|
|
6755
|
+
primitive.normals[bIndex],
|
|
6756
|
+
primitive.normals[bIndex + 1],
|
|
6757
|
+
primitive.normals[bIndex + 2]
|
|
6758
|
+
),
|
|
6759
|
+
transform
|
|
6760
|
+
);
|
|
6761
|
+
const cNormal = transformDirection(
|
|
6762
|
+
vec3(
|
|
6763
|
+
primitive.normals[cIndex],
|
|
6764
|
+
primitive.normals[cIndex + 1],
|
|
6765
|
+
primitive.normals[cIndex + 2]
|
|
6766
|
+
),
|
|
6767
|
+
transform
|
|
6768
|
+
);
|
|
6769
|
+
normal = normalizeVec3(
|
|
6770
|
+
scaleVec3(addVec3(addVec3(aNormal, bNormal), cNormal), 1 / 3)
|
|
6771
|
+
);
|
|
6772
|
+
}
|
|
6773
|
+
const viewDir = normalizeVec3(subVec3(camera.eye, a));
|
|
6774
|
+
if (dotVec3(faceNormal, viewDir) <= 0) {
|
|
6775
|
+
continue;
|
|
6776
|
+
}
|
|
6777
|
+
const projected = [
|
|
6778
|
+
projectPoint(a, camera, viewport),
|
|
6779
|
+
projectPoint(b, camera, viewport),
|
|
6780
|
+
projectPoint(c, camera, viewport)
|
|
6781
|
+
];
|
|
6782
|
+
if (projected.some((value) => value === null)) {
|
|
6783
|
+
continue;
|
|
6784
|
+
}
|
|
6785
|
+
triangles.push({
|
|
6786
|
+
points: projected,
|
|
6787
|
+
depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
|
|
6788
|
+
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
6789
|
+
normal,
|
|
6790
|
+
baseColor: resolvedColor,
|
|
6791
|
+
accent: options.accent ?? 0,
|
|
6792
|
+
material: primitive.material,
|
|
6793
|
+
reflection: options.reflection ?? 0,
|
|
6794
|
+
surfaceType: options.surfaceType ?? "solid"
|
|
6795
|
+
});
|
|
6568
6796
|
}
|
|
6569
|
-
triangles.push({
|
|
6570
|
-
points: projected,
|
|
6571
|
-
depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
|
|
6572
|
-
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
6573
|
-
normal,
|
|
6574
|
-
baseColor,
|
|
6575
|
-
accent
|
|
6576
|
-
});
|
|
6577
6797
|
}
|
|
6578
6798
|
}
|
|
6799
|
+
async function loadShowcaseAssetCatalog() {
|
|
6800
|
+
const [brigantine, cutter, lighthouse, harborDock] = await Promise.all([
|
|
6801
|
+
loadGltfModel(resolveShowcaseAssetUrl("brigantine")),
|
|
6802
|
+
loadGltfModel(resolveShowcaseAssetUrl("cutter")),
|
|
6803
|
+
loadGltfModel(resolveShowcaseAssetUrl("lighthouse")),
|
|
6804
|
+
loadGltfModel(resolveShowcaseAssetUrl("harbor-dock"))
|
|
6805
|
+
]);
|
|
6806
|
+
return Object.freeze({
|
|
6807
|
+
primaryShipKey: "brigantine",
|
|
6808
|
+
ships: Object.freeze({
|
|
6809
|
+
brigantine,
|
|
6810
|
+
cutter
|
|
6811
|
+
}),
|
|
6812
|
+
environment: Object.freeze({
|
|
6813
|
+
lighthouse,
|
|
6814
|
+
"harbor-dock": harborDock
|
|
6815
|
+
})
|
|
6816
|
+
});
|
|
6817
|
+
}
|
|
6818
|
+
function createLegacyShowcaseAssetCatalog() {
|
|
6819
|
+
const brigantine = loadGltfModel(resolveShowcaseAssetUrl("brigantine"));
|
|
6820
|
+
return Promise.resolve(brigantine).then(
|
|
6821
|
+
(primary) => Object.freeze({
|
|
6822
|
+
primaryShipKey: "brigantine",
|
|
6823
|
+
ships: Object.freeze({
|
|
6824
|
+
brigantine: primary
|
|
6825
|
+
}),
|
|
6826
|
+
environment: Object.freeze({})
|
|
6827
|
+
})
|
|
6828
|
+
);
|
|
6829
|
+
}
|
|
6830
|
+
function resolveShipModel(state, ship, fallbackModel = null) {
|
|
6831
|
+
return state.assetCatalog?.ships?.[ship.modelKey ?? state.assetCatalog?.primaryShipKey ?? "brigantine"] ?? fallbackModel ?? state.shipModel;
|
|
6832
|
+
}
|
|
6579
6833
|
function createPerformanceGovernor() {
|
|
6580
6834
|
const fluidDetail = createQualityLadderAdapter({
|
|
6581
6835
|
id: "fluid-detail",
|
|
@@ -6627,6 +6881,7 @@ function createPerformanceGovernor() {
|
|
|
6627
6881
|
return { governor, fluidDetail, clothDetail, lightingDetail };
|
|
6628
6882
|
}
|
|
6629
6883
|
function buildDemoDom(root, options) {
|
|
6884
|
+
const t = options.translate;
|
|
6630
6885
|
root.innerHTML = `
|
|
6631
6886
|
<main class="plasius-demo">
|
|
6632
6887
|
<section class="plasius-demo__hero">
|
|
@@ -6636,56 +6891,52 @@ function buildDemoDom(root, options) {
|
|
|
6636
6891
|
<p class="plasius-demo__lead">${options.subtitle}</p>
|
|
6637
6892
|
</section>
|
|
6638
6893
|
<section class="plasius-panel plasius-demo__status">
|
|
6639
|
-
<p id="demoStatus" class="plasius-demo__status-badge"
|
|
6894
|
+
<p id="demoStatus" class="plasius-demo__status-badge">${t(gpuSharedTranslationKeys.statusBooting)}</p>
|
|
6640
6895
|
<p id="demoDetails" class="plasius-demo__status-text">
|
|
6641
|
-
|
|
6896
|
+
${t(gpuSharedTranslationKeys.detailsBooting)}
|
|
6642
6897
|
</p>
|
|
6643
6898
|
</section>
|
|
6644
6899
|
</section>
|
|
6645
6900
|
<section class="plasius-demo__layout">
|
|
6646
6901
|
<section class="plasius-panel plasius-demo__canvas-panel">
|
|
6647
|
-
<canvas id="demoCanvas" class="plasius-demo__canvas" width="
|
|
6902
|
+
<canvas id="demoCanvas" class="plasius-demo__canvas" width="${DEFAULT_CANVAS_WIDTH}" height="${DEFAULT_CANVAS_HEIGHT}"></canvas>
|
|
6648
6903
|
<div class="plasius-demo__toolbar">
|
|
6649
|
-
<button id="pauseButton" type="button"
|
|
6904
|
+
<button id="pauseButton" type="button">${t(gpuSharedTranslationKeys.pause)}</button>
|
|
6650
6905
|
<label class="plasius-toggle">
|
|
6651
6906
|
<input id="stressToggle" type="checkbox" />
|
|
6652
|
-
|
|
6907
|
+
${t(gpuSharedTranslationKeys.stressMode)}
|
|
6653
6908
|
</label>
|
|
6654
6909
|
<label class="plasius-toggle">
|
|
6655
|
-
|
|
6910
|
+
${t(gpuSharedTranslationKeys.focus)}
|
|
6656
6911
|
<select id="focusMode">
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
<option value="fluid">fluid</option>
|
|
6661
|
-
<option value="physics">physics</option>
|
|
6662
|
-
<option value="performance">performance</option>
|
|
6663
|
-
<option value="debug">debug</option>
|
|
6912
|
+
${showcaseFocusModes.map(
|
|
6913
|
+
(mode) => `<option value="${mode}">${t(FOCUS_MODE_TRANSLATION_KEYS[mode])}</option>`
|
|
6914
|
+
).join("")}
|
|
6664
6915
|
</select>
|
|
6665
6916
|
</label>
|
|
6666
6917
|
</div>
|
|
6667
6918
|
<div class="plasius-demo__legend">
|
|
6668
|
-
<strong
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6919
|
+
<strong>${t(gpuSharedTranslationKeys.legendTitle)}</strong>
|
|
6920
|
+
${t(gpuSharedTranslationKeys.legendShipMetadata)}<br />
|
|
6921
|
+
${t(gpuSharedTranslationKeys.legendLighting)}<br />
|
|
6922
|
+
${t(gpuSharedTranslationKeys.legendCollisions)}
|
|
6672
6923
|
</div>
|
|
6673
6924
|
</section>
|
|
6674
6925
|
<aside class="plasius-demo__sidebar">
|
|
6675
6926
|
<section class="plasius-panel plasius-demo__card">
|
|
6676
|
-
<h2
|
|
6927
|
+
<h2>${t(gpuSharedTranslationKeys.sceneState)}</h2>
|
|
6677
6928
|
<ul id="sceneMetrics" class="plasius-demo__metrics"></ul>
|
|
6678
6929
|
</section>
|
|
6679
6930
|
<section class="plasius-panel plasius-demo__card">
|
|
6680
|
-
<h2
|
|
6931
|
+
<h2>${t(gpuSharedTranslationKeys.qualityBudgets)}</h2>
|
|
6681
6932
|
<ul id="qualityMetrics" class="plasius-demo__metrics"></ul>
|
|
6682
6933
|
</section>
|
|
6683
6934
|
<section class="plasius-panel plasius-demo__card">
|
|
6684
|
-
<h2
|
|
6935
|
+
<h2>${t(gpuSharedTranslationKeys.debugTelemetry)}</h2>
|
|
6685
6936
|
<ul id="debugMetrics" class="plasius-demo__metrics"></ul>
|
|
6686
6937
|
</section>
|
|
6687
6938
|
<section class="plasius-panel plasius-demo__card">
|
|
6688
|
-
<h2
|
|
6939
|
+
<h2>${t(gpuSharedTranslationKeys.notes)}</h2>
|
|
6689
6940
|
<ul id="sceneNotes" class="plasius-demo__metrics"></ul>
|
|
6690
6941
|
</section>
|
|
6691
6942
|
</aside>
|
|
@@ -6709,6 +6960,11 @@ function buildDemoDom(root, options) {
|
|
|
6709
6960
|
};
|
|
6710
6961
|
}
|
|
6711
6962
|
function buildSceneSnapshot(state, shipModel) {
|
|
6963
|
+
const shipPhysics = Object.freeze(
|
|
6964
|
+
Object.fromEntries(
|
|
6965
|
+
state.ships.map((ship) => [ship.id, resolveShipModel(state, ship, shipModel)?.physics ?? null])
|
|
6966
|
+
)
|
|
6967
|
+
);
|
|
6712
6968
|
return Object.freeze({
|
|
6713
6969
|
focus: state.focus,
|
|
6714
6970
|
frame: state.frame,
|
|
@@ -6730,6 +6986,7 @@ function buildSceneSnapshot(state, shipModel) {
|
|
|
6730
6986
|
state.ships.map(
|
|
6731
6987
|
(ship) => Object.freeze({
|
|
6732
6988
|
id: ship.id,
|
|
6989
|
+
modelKey: ship.modelKey ?? "brigantine",
|
|
6733
6990
|
position: Object.freeze({ ...ship.position }),
|
|
6734
6991
|
velocity: Object.freeze({ ...ship.velocity }),
|
|
6735
6992
|
rotationY: ship.rotationY,
|
|
@@ -6750,12 +7007,13 @@ function buildSceneSnapshot(state, shipModel) {
|
|
|
6750
7007
|
)
|
|
6751
7008
|
),
|
|
6752
7009
|
shipPhysics: shipModel?.physics ?? null,
|
|
7010
|
+
shipModels: shipPhysics,
|
|
6753
7011
|
physics: Object.freeze({
|
|
6754
7012
|
profile: state.physics.profile,
|
|
6755
7013
|
plan: state.physics.plan,
|
|
6756
7014
|
manifest: state.physics.manifest,
|
|
6757
7015
|
snapshot: state.physics.snapshot,
|
|
6758
|
-
shipPhysics
|
|
7016
|
+
shipPhysics
|
|
6759
7017
|
})
|
|
6760
7018
|
});
|
|
6761
7019
|
}
|
|
@@ -6790,6 +7048,61 @@ function normalizeColorOverride(color, fallback) {
|
|
|
6790
7048
|
function readVisualNumber(value, fallback) {
|
|
6791
7049
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
6792
7050
|
}
|
|
7051
|
+
function readPositiveNumber3(value, fallback) {
|
|
7052
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
7053
|
+
}
|
|
7054
|
+
function isTruthyCaptureValue(value) {
|
|
7055
|
+
return value === "1" || value === "true" || value === "scene" || value === "video";
|
|
7056
|
+
}
|
|
7057
|
+
function resolveCaptureSettings(options) {
|
|
7058
|
+
const explicitCaptureMode = typeof options.captureMode === "boolean" ? options.captureMode : void 0;
|
|
7059
|
+
let captureMode = explicitCaptureMode ?? false;
|
|
7060
|
+
let renderScale = readPositiveNumber3(options.renderScale, void 0);
|
|
7061
|
+
try {
|
|
7062
|
+
const params = new URLSearchParams(window.location.search);
|
|
7063
|
+
if (explicitCaptureMode === void 0) {
|
|
7064
|
+
captureMode = isTruthyCaptureValue(params.get("capture")) || params.get("presentation") === "capture";
|
|
7065
|
+
}
|
|
7066
|
+
renderScale = readPositiveNumber3(Number(params.get("renderScale")), renderScale);
|
|
7067
|
+
} catch {
|
|
7068
|
+
}
|
|
7069
|
+
return {
|
|
7070
|
+
captureMode,
|
|
7071
|
+
renderScale
|
|
7072
|
+
};
|
|
7073
|
+
}
|
|
7074
|
+
function getCanvasDisplaySize(canvas) {
|
|
7075
|
+
const rect = typeof canvas.getBoundingClientRect === "function" ? canvas.getBoundingClientRect() : null;
|
|
7076
|
+
const width = Math.round(
|
|
7077
|
+
readPositiveNumber3(rect?.width, readPositiveNumber3(canvas.clientWidth, canvas.width))
|
|
7078
|
+
);
|
|
7079
|
+
const height = Math.round(
|
|
7080
|
+
readPositiveNumber3(rect?.height, readPositiveNumber3(canvas.clientHeight, canvas.height))
|
|
7081
|
+
);
|
|
7082
|
+
return {
|
|
7083
|
+
width: Math.max(1, width || DEFAULT_CANVAS_WIDTH),
|
|
7084
|
+
height: Math.max(1, height || DEFAULT_CANVAS_HEIGHT)
|
|
7085
|
+
};
|
|
7086
|
+
}
|
|
7087
|
+
function resizeCanvasToDisplaySize(canvas, state) {
|
|
7088
|
+
const { width, height } = getCanvasDisplaySize(canvas);
|
|
7089
|
+
const deviceScale = readPositiveNumber3(globalThis.devicePixelRatio, 1);
|
|
7090
|
+
const requestedScale = readPositiveNumber3(state.renderScale, deviceScale);
|
|
7091
|
+
const maxScale = state.captureMode ? 2 : 1.5;
|
|
7092
|
+
let scale = clamp(requestedScale, 1, maxScale);
|
|
7093
|
+
const pixelBudget = state.captureMode ? CAPTURE_CANVAS_PIXEL_BUDGET : DEFAULT_CANVAS_WIDTH * DEFAULT_CANVAS_HEIGHT * 1.5;
|
|
7094
|
+
const projectedPixels = width * height * scale * scale;
|
|
7095
|
+
if (projectedPixels > pixelBudget) {
|
|
7096
|
+
scale = Math.sqrt(pixelBudget / Math.max(1, width * height));
|
|
7097
|
+
}
|
|
7098
|
+
const targetWidth = Math.max(1, Math.round(width * scale));
|
|
7099
|
+
const targetHeight = Math.max(1, Math.round(height * scale));
|
|
7100
|
+
if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
|
|
7101
|
+
canvas.width = targetWidth;
|
|
7102
|
+
canvas.height = targetHeight;
|
|
7103
|
+
}
|
|
7104
|
+
state.renderScale = scale;
|
|
7105
|
+
}
|
|
6793
7106
|
function resolveClothPresentation(state, meshDetail) {
|
|
6794
7107
|
const clothPlan = createClothRepresentationPlan({
|
|
6795
7108
|
garmentId: "shore-flag",
|
|
@@ -7319,6 +7632,7 @@ function buildWaterBands(state, fluidDetail, visuals) {
|
|
|
7319
7632
|
return { fluidPlan, bandMeshes };
|
|
7320
7633
|
}
|
|
7321
7634
|
function createSceneState(options) {
|
|
7635
|
+
const translate = options.translate;
|
|
7322
7636
|
const { governor, fluidDetail, clothDetail, lightingDetail } = createPerformanceGovernor();
|
|
7323
7637
|
const physicsProfile = defaultPhysicsWorkerProfile;
|
|
7324
7638
|
const physicsPlan = createPhysicsSimulationPlan(physicsProfile);
|
|
@@ -7326,7 +7640,7 @@ function createSceneState(options) {
|
|
|
7326
7640
|
const debugSession = createGpuDebugSession({
|
|
7327
7641
|
enabled: true,
|
|
7328
7642
|
adapter: {
|
|
7329
|
-
label:
|
|
7643
|
+
label: translate(gpuSharedTranslationKeys.debugAdapterShowcase),
|
|
7330
7644
|
memoryCapacityHintBytes: 6 * 1024 * 1024 * 1024,
|
|
7331
7645
|
coreCountHint: 12
|
|
7332
7646
|
}
|
|
@@ -7336,22 +7650,26 @@ function createSceneState(options) {
|
|
|
7336
7650
|
owner: "renderer",
|
|
7337
7651
|
category: "texture",
|
|
7338
7652
|
sizeBytes: 1280 * 720 * 4,
|
|
7339
|
-
label:
|
|
7653
|
+
label: translate(gpuSharedTranslationKeys.debugMainColorBuffer)
|
|
7340
7654
|
});
|
|
7341
7655
|
debugSession.trackAllocation({
|
|
7342
7656
|
id: "showcase.shadow-impression",
|
|
7343
7657
|
owner: "lighting",
|
|
7344
7658
|
category: "texture",
|
|
7345
7659
|
sizeBytes: 12 * 1024 * 1024,
|
|
7346
|
-
label:
|
|
7660
|
+
label: translate(gpuSharedTranslationKeys.debugShadowImpressionAtlas)
|
|
7347
7661
|
});
|
|
7348
7662
|
return {
|
|
7663
|
+
translate,
|
|
7349
7664
|
focus: options.focus,
|
|
7350
7665
|
governor,
|
|
7351
7666
|
fluidDetail,
|
|
7352
7667
|
clothDetail,
|
|
7353
7668
|
lightingDetail,
|
|
7354
7669
|
debugSession,
|
|
7670
|
+
showcaseRealisticModelsEnabled: options.realisticModelsEnabled !== false,
|
|
7671
|
+
captureMode: options.captureMode === true,
|
|
7672
|
+
renderScale: readPositiveNumber3(options.renderScale, void 0),
|
|
7355
7673
|
packageState: void 0,
|
|
7356
7674
|
demoDescription: null,
|
|
7357
7675
|
demoVisuals: null,
|
|
@@ -7366,6 +7684,7 @@ function createSceneState(options) {
|
|
|
7366
7684
|
ships: [
|
|
7367
7685
|
{
|
|
7368
7686
|
id: "northwind",
|
|
7687
|
+
modelKey: "brigantine",
|
|
7369
7688
|
position: vec3(-5.2, 0, 7.2),
|
|
7370
7689
|
velocity: vec3(2.35, 0, -1.08),
|
|
7371
7690
|
rotationY: 0.58,
|
|
@@ -7376,17 +7695,18 @@ function createSceneState(options) {
|
|
|
7376
7695
|
throttleResponse: 0.46,
|
|
7377
7696
|
rudderResponse: 0.54,
|
|
7378
7697
|
wanderPhase: 0.35,
|
|
7379
|
-
lanterns:
|
|
7698
|
+
lanterns: CUTTER_LANTERNS,
|
|
7380
7699
|
lanternStrength: 1.06,
|
|
7381
7700
|
collisionRadiusScale: 1.04
|
|
7382
7701
|
},
|
|
7383
7702
|
{
|
|
7384
7703
|
id: "tidecaller",
|
|
7704
|
+
modelKey: "cutter",
|
|
7385
7705
|
position: vec3(4.8, 0, 4.4),
|
|
7386
7706
|
velocity: vec3(-2.15, 0, 1.74),
|
|
7387
7707
|
rotationY: -2.48,
|
|
7388
7708
|
angularVelocity: -0.2,
|
|
7389
|
-
tint: { r: 0.
|
|
7709
|
+
tint: { r: 0.58, g: 0.24, b: 0.16 },
|
|
7390
7710
|
massScale: 0.84,
|
|
7391
7711
|
cruiseSpeed: 2.68,
|
|
7392
7712
|
throttleResponse: 0.7,
|
|
@@ -7410,6 +7730,7 @@ function createSceneState(options) {
|
|
|
7410
7730
|
manifest: physicsManifest,
|
|
7411
7731
|
snapshot: null
|
|
7412
7732
|
},
|
|
7733
|
+
assetCatalog: null,
|
|
7413
7734
|
shipModel: null
|
|
7414
7735
|
};
|
|
7415
7736
|
}
|
|
@@ -7485,10 +7806,38 @@ function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, s
|
|
|
7485
7806
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
7486
7807
|
}
|
|
7487
7808
|
}
|
|
7488
|
-
function
|
|
7809
|
+
function resolveLocalLightContribution(triangle, lightSources) {
|
|
7810
|
+
const contribution = { r: 0, g: 0, b: 0 };
|
|
7811
|
+
if (!Array.isArray(lightSources) || triangle.surfaceType === "water") {
|
|
7812
|
+
return contribution;
|
|
7813
|
+
}
|
|
7814
|
+
const normal = normalizeVec3(triangle.normal);
|
|
7815
|
+
for (const source of lightSources.slice(0, 8)) {
|
|
7816
|
+
const delta = subVec3(source.point, triangle.worldCenter);
|
|
7817
|
+
const distance = lengthVec3(delta);
|
|
7818
|
+
const attenuation = (source.glowScale ?? 1) / Math.max(1, 0.68 + distance * distance * 0.2);
|
|
7819
|
+
if (attenuation < 0.012) {
|
|
7820
|
+
continue;
|
|
7821
|
+
}
|
|
7822
|
+
const lightDir = normalizeVec3(delta);
|
|
7823
|
+
const facing = clamp(dotVec3(normal, lightDir), 0, 1);
|
|
7824
|
+
const response = attenuation * (0.18 + facing * 0.82);
|
|
7825
|
+
const glowColor = source.glowColor ?? source.coreColor ?? { r: 1, g: 0.72, b: 0.4 };
|
|
7826
|
+
contribution.r += glowColor.r * response * 0.32;
|
|
7827
|
+
contribution.g += glowColor.g * response * 0.26;
|
|
7828
|
+
contribution.b += glowColor.b * response * 0.18;
|
|
7829
|
+
}
|
|
7830
|
+
return contribution;
|
|
7831
|
+
}
|
|
7832
|
+
function drawTriangles(ctx, triangles, lightDir, reflectionStrength, camera, shadowStrength, localLights = []) {
|
|
7489
7833
|
triangles.sort((left, right) => right.depth - left.depth);
|
|
7490
7834
|
for (const triangle of triangles) {
|
|
7491
7835
|
const surfaceNormal = normalizeVec3(triangle.normal);
|
|
7836
|
+
const material = triangle.material ?? {
|
|
7837
|
+
roughness: 0.88,
|
|
7838
|
+
metallic: 0.08,
|
|
7839
|
+
emissive: { r: 0, g: 0, b: 0 }
|
|
7840
|
+
};
|
|
7492
7841
|
const shaded = shadeColor(
|
|
7493
7842
|
triangle.baseColor,
|
|
7494
7843
|
surfaceNormal,
|
|
@@ -7496,19 +7845,41 @@ function drawTriangles(ctx, triangles, lightDir, reflectionStrength, camera, sha
|
|
|
7496
7845
|
clamp((triangle.worldCenter.y + 3) / 10, 0, 1),
|
|
7497
7846
|
triangle.accent
|
|
7498
7847
|
);
|
|
7499
|
-
const reflection =
|
|
7848
|
+
const reflection = reflectionStrength * (triangle.reflection ?? 0);
|
|
7500
7849
|
const viewDir = normalizeVec3(subVec3(camera.eye, triangle.worldCenter));
|
|
7501
7850
|
const reflectedLight = reflectVec3(scaleVec3(lightDir, -1), surfaceNormal);
|
|
7502
|
-
const gloss =
|
|
7503
|
-
const
|
|
7504
|
-
const
|
|
7505
|
-
const
|
|
7851
|
+
const gloss = mix(0.78, 0.14, clamp(material.roughness ?? 0.88, 0, 1)) + (material.metallic ?? 0) * 0.18;
|
|
7852
|
+
const specularPower = mix(26, 7, clamp(material.roughness ?? 0.88, 0, 1));
|
|
7853
|
+
const specular = Math.pow(clamp(dotVec3(reflectedLight, viewDir), 0, 1), specularPower) * gloss;
|
|
7854
|
+
const emissive = material.emissive ?? { r: 0, g: 0, b: 0 };
|
|
7855
|
+
const localLight = resolveLocalLightContribution(triangle, localLights);
|
|
7856
|
+
const occlusion = triangle.surfaceType === "water" ? shadowStrength * 0.018 : shadowStrength * 0.04;
|
|
7857
|
+
const detailed = applyMaterialDetail(
|
|
7506
7858
|
{
|
|
7507
|
-
r: clamp(
|
|
7508
|
-
|
|
7509
|
-
|
|
7859
|
+
r: clamp(
|
|
7860
|
+
shaded.r + reflection * 0.08 + specular * 0.16 + emissive.r * 0.42 + localLight.r - occlusion,
|
|
7861
|
+
0,
|
|
7862
|
+
1
|
|
7863
|
+
),
|
|
7864
|
+
g: clamp(
|
|
7865
|
+
shaded.g + reflection * 0.08 + specular * 0.16 + emissive.g * 0.42 + localLight.g - occlusion,
|
|
7866
|
+
0,
|
|
7867
|
+
1
|
|
7868
|
+
),
|
|
7869
|
+
b: clamp(
|
|
7870
|
+
shaded.b + reflection * 0.16 + specular * 0.22 + emissive.b * 0.46 + localLight.b - occlusion * 0.5,
|
|
7871
|
+
0,
|
|
7872
|
+
1
|
|
7873
|
+
)
|
|
7510
7874
|
},
|
|
7511
|
-
|
|
7875
|
+
material,
|
|
7876
|
+
triangle.worldCenter,
|
|
7877
|
+
surfaceNormal,
|
|
7878
|
+
triangle.surfaceType
|
|
7879
|
+
);
|
|
7880
|
+
const fill = colorToRgba(
|
|
7881
|
+
detailed,
|
|
7882
|
+
triangle.baseColor.a ?? 0.98
|
|
7512
7883
|
);
|
|
7513
7884
|
ctx.fillStyle = fill;
|
|
7514
7885
|
ctx.beginPath();
|
|
@@ -7539,74 +7910,100 @@ function renderProjectedShadow(ctx, worldPoints, camera, viewport, lightDir, opt
|
|
|
7539
7910
|
ctx.fill();
|
|
7540
7911
|
ctx.restore();
|
|
7541
7912
|
}
|
|
7542
|
-
function pushHarborGeometry(camera, viewport, triangles,
|
|
7543
|
-
|
|
7544
|
-
{
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7913
|
+
function pushHarborGeometry(camera, viewport, triangles, state) {
|
|
7914
|
+
if (!state.showcaseRealisticModelsEnabled) {
|
|
7915
|
+
for (const object of LEGACY_HARBOR_LAYOUT) {
|
|
7916
|
+
buildTrianglesFromMesh(
|
|
7917
|
+
{ positions: [object], indices: [0], normals: null, colors: null, material: createLegacyMeshPrimitive({})?.material, bounds: null, name: "legacy-structure" },
|
|
7918
|
+
{
|
|
7919
|
+
position: object.position,
|
|
7920
|
+
rotationY: object.rotationY,
|
|
7921
|
+
scale: object.scale
|
|
7922
|
+
},
|
|
7923
|
+
object.color,
|
|
7924
|
+
camera,
|
|
7925
|
+
viewport,
|
|
7926
|
+
triangles,
|
|
7927
|
+
{
|
|
7928
|
+
accent: object.accent,
|
|
7929
|
+
reflection: 0,
|
|
7930
|
+
surfaceType: "structure"
|
|
7931
|
+
}
|
|
7932
|
+
);
|
|
7933
|
+
}
|
|
7934
|
+
return;
|
|
7935
|
+
}
|
|
7936
|
+
for (const placement of SHOWCASE_ENVIRONMENT_LAYOUT) {
|
|
7937
|
+
const mesh = state.assetCatalog?.environment?.[placement.assetKey] ?? null;
|
|
7938
|
+
if (!mesh) {
|
|
7939
|
+
continue;
|
|
7564
7940
|
}
|
|
7565
|
-
];
|
|
7566
|
-
for (const object of harborObjects) {
|
|
7567
7941
|
buildTrianglesFromMesh(
|
|
7568
|
-
|
|
7942
|
+
mesh,
|
|
7569
7943
|
{
|
|
7570
|
-
position:
|
|
7571
|
-
rotationY:
|
|
7572
|
-
scale:
|
|
7944
|
+
position: vec3(placement.position.x, placement.position.y, placement.position.z),
|
|
7945
|
+
rotationY: placement.rotationY,
|
|
7946
|
+
scale: placement.scale
|
|
7573
7947
|
},
|
|
7574
|
-
|
|
7948
|
+
null,
|
|
7575
7949
|
camera,
|
|
7576
7950
|
viewport,
|
|
7577
7951
|
triangles,
|
|
7578
|
-
|
|
7952
|
+
{
|
|
7953
|
+
accent: placement.accent,
|
|
7954
|
+
reflection: 0,
|
|
7955
|
+
surfaceType: "structure"
|
|
7956
|
+
}
|
|
7579
7957
|
);
|
|
7580
7958
|
}
|
|
7581
7959
|
}
|
|
7582
7960
|
function renderShipRigging(ctx, ship, camera, viewport) {
|
|
7583
7961
|
const transform = { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE };
|
|
7584
|
-
const
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7962
|
+
const layout = ship.modelKey === "cutter" ? {
|
|
7963
|
+
lineColor: "rgba(85, 89, 97, 0.92)",
|
|
7964
|
+
sailColor: "rgba(218, 232, 244, 0.28)",
|
|
7965
|
+
points: [
|
|
7966
|
+
vec3(0, 0.88, -0.32),
|
|
7967
|
+
vec3(0, 2.4, -0.28),
|
|
7968
|
+
vec3(0.1, 1.92, -0.3),
|
|
7969
|
+
vec3(1.18, 1.72, -0.18),
|
|
7970
|
+
vec3(1.04, 1.08, -0.12)
|
|
7971
|
+
],
|
|
7972
|
+
mastPairs: [[0, 1], [2, 3]],
|
|
7973
|
+
sailTriangle: [2, 3, 4]
|
|
7974
|
+
} : {
|
|
7975
|
+
lineColor: "rgba(73, 54, 45, 0.94)",
|
|
7976
|
+
sailColor: "rgba(238, 232, 214, 0.88)",
|
|
7977
|
+
points: [
|
|
7978
|
+
vec3(0, 0.38, -0.4),
|
|
7979
|
+
vec3(0, 3.8, -0.2),
|
|
7980
|
+
vec3(-0.25, 0.32, -1.9),
|
|
7981
|
+
vec3(-0.15, 2.7, -1.75),
|
|
7982
|
+
vec3(0.08, 3.2, -0.2),
|
|
7983
|
+
vec3(0.12, 1.2, -0.5),
|
|
7984
|
+
vec3(2.25, 2.25, 0.15)
|
|
7985
|
+
],
|
|
7986
|
+
mastPairs: [[0, 1], [2, 3]],
|
|
7987
|
+
sailTriangle: [4, 5, 6]
|
|
7988
|
+
};
|
|
7989
|
+
const projected = layout.points.map((point) => transformPoint(point, transform)).map((point) => projectPoint(point, camera, viewport));
|
|
7594
7990
|
if (projected.some((value) => value === null)) {
|
|
7595
7991
|
return;
|
|
7596
7992
|
}
|
|
7597
|
-
ctx.strokeStyle =
|
|
7598
|
-
ctx.lineWidth = 3.5;
|
|
7993
|
+
ctx.strokeStyle = layout.lineColor;
|
|
7994
|
+
ctx.lineWidth = ship.modelKey === "cutter" ? 2.2 : 3.5;
|
|
7599
7995
|
ctx.beginPath();
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7996
|
+
for (const [from, to] of layout.mastPairs) {
|
|
7997
|
+
ctx.moveTo(projected[from].x, projected[from].y);
|
|
7998
|
+
ctx.lineTo(projected[to].x, projected[to].y);
|
|
7999
|
+
}
|
|
7604
8000
|
ctx.stroke();
|
|
7605
|
-
|
|
8001
|
+
const [a, b, c] = layout.sailTriangle;
|
|
8002
|
+
ctx.fillStyle = layout.sailColor;
|
|
7606
8003
|
ctx.beginPath();
|
|
7607
|
-
ctx.moveTo(projected[
|
|
7608
|
-
ctx.lineTo(projected[
|
|
7609
|
-
ctx.lineTo(projected[
|
|
8004
|
+
ctx.moveTo(projected[a].x, projected[a].y);
|
|
8005
|
+
ctx.lineTo(projected[b].x, projected[b].y);
|
|
8006
|
+
ctx.lineTo(projected[c].x, projected[c].y);
|
|
7610
8007
|
ctx.closePath();
|
|
7611
8008
|
ctx.fill();
|
|
7612
8009
|
}
|
|
@@ -7824,10 +8221,10 @@ function resolveBoundaryCollision(ship, state, shipModel) {
|
|
|
7824
8221
|
}
|
|
7825
8222
|
}
|
|
7826
8223
|
}
|
|
7827
|
-
function resolveShipCollision(state, a, b,
|
|
8224
|
+
function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
7828
8225
|
const delta = subVec3(b.position, a.position);
|
|
7829
|
-
const radiusA = getShipCollisionRadius(a,
|
|
7830
|
-
const radiusB = getShipCollisionRadius(b,
|
|
8226
|
+
const radiusA = getShipCollisionRadius(a, shipModelA);
|
|
8227
|
+
const radiusB = getShipCollisionRadius(b, shipModelB);
|
|
7831
8228
|
const distance = Math.hypot(delta.x, delta.z);
|
|
7832
8229
|
const minDistance = radiusA + radiusB;
|
|
7833
8230
|
if (distance >= minDistance) {
|
|
@@ -7836,15 +8233,15 @@ function resolveShipCollision(state, a, b, shipModel) {
|
|
|
7836
8233
|
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)));
|
|
7837
8234
|
const tangent = vec3(-normal.z, 0, normal.x);
|
|
7838
8235
|
const penetration = minDistance - distance;
|
|
7839
|
-
const invMassA = getShipInverseMass(a,
|
|
7840
|
-
const invMassB = getShipInverseMass(b,
|
|
8236
|
+
const invMassA = getShipInverseMass(a, shipModelA);
|
|
8237
|
+
const invMassB = getShipInverseMass(b, shipModelB);
|
|
7841
8238
|
const invMassSum = invMassA + invMassB;
|
|
7842
8239
|
const correction = scaleVec3(normal, penetration / Math.max(1e-4, invMassSum) * 0.72);
|
|
7843
8240
|
a.position = subVec3(a.position, scaleVec3(correction, invMassA));
|
|
7844
8241
|
b.position = addVec3(b.position, scaleVec3(correction, invMassB));
|
|
7845
8242
|
const relativeVelocity = subVec3(b.velocity, a.velocity);
|
|
7846
8243
|
const velocityAlongNormal = dotVec3(relativeVelocity, normal);
|
|
7847
|
-
const restitution = readPhysicsNumber(
|
|
8244
|
+
const restitution = (readPhysicsNumber(shipModelA.physics, "restitution", 0.22) + readPhysicsNumber(shipModelB.physics, "restitution", 0.22)) / 2 * 0.88;
|
|
7848
8245
|
if (velocityAlongNormal < 0) {
|
|
7849
8246
|
const impulseMagnitude = -(1 + restitution) * velocityAlongNormal / Math.max(1e-4, invMassSum);
|
|
7850
8247
|
const impulse = scaleVec3(normal, impulseMagnitude);
|
|
@@ -7859,8 +8256,8 @@ function resolveShipCollision(state, a, b, shipModel) {
|
|
|
7859
8256
|
const frictionImpulse = scaleVec3(tangent, frictionMagnitude);
|
|
7860
8257
|
a.velocity = subVec3(a.velocity, scaleVec3(frictionImpulse, invMassA));
|
|
7861
8258
|
b.velocity = addVec3(b.velocity, scaleVec3(frictionImpulse, invMassB));
|
|
7862
|
-
a.angularVelocity -= tangentSpeed * radiusA * getShipInverseInertia(a,
|
|
7863
|
-
b.angularVelocity += tangentSpeed * radiusB * getShipInverseInertia(b,
|
|
8259
|
+
a.angularVelocity -= tangentSpeed * radiusA * getShipInverseInertia(a, shipModelA) * 0.2 + impulseMagnitude * 24e-5;
|
|
8260
|
+
b.angularVelocity += tangentSpeed * radiusB * getShipInverseInertia(b, shipModelB) * 0.2 + impulseMagnitude * 24e-5;
|
|
7864
8261
|
const impactSpeed = Math.abs(velocityAlongNormal);
|
|
7865
8262
|
if (impactSpeed > 0.18 && Math.max(readVisualNumber(a.collisionCooldown, 0), readVisualNumber(b.collisionCooldown, 0)) <= 0) {
|
|
7866
8263
|
const contactPoint = vec3(
|
|
@@ -7892,12 +8289,17 @@ function updateShips(state, dt, shipModel) {
|
|
|
7892
8289
|
let collided = false;
|
|
7893
8290
|
state.contactCount = 0;
|
|
7894
8291
|
for (const ship of state.ships) {
|
|
7895
|
-
|
|
7896
|
-
|
|
8292
|
+
const activeShipModel = resolveShipModel(state, ship, shipModel);
|
|
8293
|
+
updateShipMotion(state, ship, dt, activeShipModel);
|
|
8294
|
+
resolveBoundaryCollision(ship, state, activeShipModel);
|
|
7897
8295
|
}
|
|
7898
8296
|
for (let index = 0; index < state.ships.length; index += 1) {
|
|
7899
8297
|
for (let otherIndex = index + 1; otherIndex < state.ships.length; otherIndex += 1) {
|
|
7900
|
-
|
|
8298
|
+
const shipA = state.ships[index];
|
|
8299
|
+
const shipB = state.ships[otherIndex];
|
|
8300
|
+
const shipModelA = resolveShipModel(state, shipA, shipModel);
|
|
8301
|
+
const shipModelB = resolveShipModel(state, shipB, shipModel);
|
|
8302
|
+
collided = resolveShipCollision(state, shipA, shipB, shipModelA, shipModelB) || collided;
|
|
7901
8303
|
}
|
|
7902
8304
|
}
|
|
7903
8305
|
state.collisionFlash = collided ? Math.max(0.12, state.collisionFlash) : Math.max(0, state.collisionFlash - dt * 1.3);
|
|
@@ -8161,6 +8563,101 @@ function renderWaterLightReflection(ctx, source, state, camera, viewport) {
|
|
|
8161
8563
|
ctx.fill();
|
|
8162
8564
|
ctx.restore();
|
|
8163
8565
|
}
|
|
8566
|
+
function renderLighthouseBeam(ctx, state, camera, viewport, visuals) {
|
|
8567
|
+
const lighthousePlacement = SHOWCASE_ENVIRONMENT_LAYOUT.find(
|
|
8568
|
+
(placement) => placement.assetKey === "lighthouse"
|
|
8569
|
+
);
|
|
8570
|
+
if (!lighthousePlacement || !state.showcaseRealisticModelsEnabled) {
|
|
8571
|
+
return;
|
|
8572
|
+
}
|
|
8573
|
+
const source = transformPoint(
|
|
8574
|
+
vec3(0, 11.34, 0),
|
|
8575
|
+
{
|
|
8576
|
+
position: vec3(
|
|
8577
|
+
lighthousePlacement.position.x,
|
|
8578
|
+
lighthousePlacement.position.y,
|
|
8579
|
+
lighthousePlacement.position.z
|
|
8580
|
+
),
|
|
8581
|
+
rotationY: lighthousePlacement.rotationY,
|
|
8582
|
+
scale: lighthousePlacement.scale
|
|
8583
|
+
}
|
|
8584
|
+
);
|
|
8585
|
+
const sweep = state.time * 0.22 + 0.8;
|
|
8586
|
+
const direction = normalizeVec3(vec3(Math.sin(sweep), -0.07, Math.cos(sweep)));
|
|
8587
|
+
const spread = perpendicularOnWater(direction);
|
|
8588
|
+
const farCenter = addVec3(source, scaleVec3(direction, 34));
|
|
8589
|
+
const left = addVec3(farCenter, scaleVec3(spread, 7.4));
|
|
8590
|
+
const right = addVec3(farCenter, scaleVec3(spread, -7.4));
|
|
8591
|
+
const projectedSource = projectPoint(source, camera, viewport);
|
|
8592
|
+
const projectedLeft = projectPoint(left, camera, viewport);
|
|
8593
|
+
const projectedRight = projectPoint(right, camera, viewport);
|
|
8594
|
+
if (!projectedSource || !projectedLeft || !projectedRight) {
|
|
8595
|
+
return;
|
|
8596
|
+
}
|
|
8597
|
+
const pulse = 0.72 + Math.sin(state.time * 1.7) * 0.08;
|
|
8598
|
+
ctx.save();
|
|
8599
|
+
ctx.globalCompositeOperation = "screen";
|
|
8600
|
+
ctx.fillStyle = colorToRgba(visuals.torchCore, 0.055 * pulse);
|
|
8601
|
+
ctx.beginPath();
|
|
8602
|
+
ctx.moveTo(projectedSource.x, projectedSource.y);
|
|
8603
|
+
ctx.lineTo(projectedLeft.x, projectedLeft.y);
|
|
8604
|
+
ctx.lineTo(projectedRight.x, projectedRight.y);
|
|
8605
|
+
ctx.closePath();
|
|
8606
|
+
ctx.fill();
|
|
8607
|
+
const beamLength = Math.hypot(
|
|
8608
|
+
projectedLeft.x - projectedSource.x,
|
|
8609
|
+
projectedLeft.y - projectedSource.y
|
|
8610
|
+
);
|
|
8611
|
+
const core = ctx.createRadialGradient(
|
|
8612
|
+
projectedSource.x,
|
|
8613
|
+
projectedSource.y,
|
|
8614
|
+
2,
|
|
8615
|
+
projectedSource.x,
|
|
8616
|
+
projectedSource.y,
|
|
8617
|
+
clamp(beamLength * 0.22, 18, 80)
|
|
8618
|
+
);
|
|
8619
|
+
core.addColorStop(0, colorToRgba(visuals.torchCore, 0.58));
|
|
8620
|
+
core.addColorStop(0.5, colorToRgba(visuals.torchGlow, 0.18));
|
|
8621
|
+
core.addColorStop(1, colorToRgba(visuals.torchGlow, 0));
|
|
8622
|
+
ctx.fillStyle = core;
|
|
8623
|
+
ctx.beginPath();
|
|
8624
|
+
ctx.arc(projectedSource.x, projectedSource.y, clamp(beamLength * 0.18, 14, 64), 0, Math.PI * 2);
|
|
8625
|
+
ctx.fill();
|
|
8626
|
+
ctx.restore();
|
|
8627
|
+
}
|
|
8628
|
+
function renderAtmosphericGrade(ctx, canvas, state, visuals) {
|
|
8629
|
+
const vignette = ctx.createRadialGradient(
|
|
8630
|
+
canvas.width * 0.5,
|
|
8631
|
+
canvas.height * 0.48,
|
|
8632
|
+
canvas.width * 0.2,
|
|
8633
|
+
canvas.width * 0.5,
|
|
8634
|
+
canvas.height * 0.5,
|
|
8635
|
+
canvas.width * 0.72
|
|
8636
|
+
);
|
|
8637
|
+
vignette.addColorStop(0, "rgba(0, 0, 0, 0)");
|
|
8638
|
+
vignette.addColorStop(0.68, "rgba(0, 0, 0, 0.08)");
|
|
8639
|
+
vignette.addColorStop(1, "rgba(0, 0, 0, 0.32)");
|
|
8640
|
+
ctx.fillStyle = vignette;
|
|
8641
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
8642
|
+
const seaHaze = ctx.createLinearGradient(0, canvas.height * 0.34, 0, canvas.height);
|
|
8643
|
+
seaHaze.addColorStop(0, "rgba(0, 0, 0, 0)");
|
|
8644
|
+
seaHaze.addColorStop(0.5, visuals.ambientMist);
|
|
8645
|
+
seaHaze.addColorStop(1, "rgba(3, 8, 16, 0.18)");
|
|
8646
|
+
ctx.fillStyle = seaHaze;
|
|
8647
|
+
ctx.fillRect(0, canvas.height * 0.34, canvas.width, canvas.height * 0.66);
|
|
8648
|
+
if (state.captureMode) {
|
|
8649
|
+
ctx.save();
|
|
8650
|
+
ctx.globalCompositeOperation = "screen";
|
|
8651
|
+
for (let index = 0; index < 70; index += 1) {
|
|
8652
|
+
const x = pseudoRandom(index * 19 + 3) * canvas.width;
|
|
8653
|
+
const y = pseudoRandom(index * 23 + 7) * canvas.height;
|
|
8654
|
+
const alpha = 8e-3 + pseudoRandom(index * 31 + 11) * 0.012;
|
|
8655
|
+
ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
|
|
8656
|
+
ctx.fillRect(x, y, 1.1, 1.1);
|
|
8657
|
+
}
|
|
8658
|
+
ctx.restore();
|
|
8659
|
+
}
|
|
8660
|
+
}
|
|
8164
8661
|
function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
8165
8662
|
ctx.save();
|
|
8166
8663
|
ctx.globalCompositeOperation = "screen";
|
|
@@ -8279,13 +8776,22 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8279
8776
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
8280
8777
|
normal,
|
|
8281
8778
|
baseColor: bandMesh.color,
|
|
8282
|
-
accent: bandAccent
|
|
8779
|
+
accent: bandAccent,
|
|
8780
|
+
material: {
|
|
8781
|
+
name: "water-surface",
|
|
8782
|
+
color: bandMesh.color,
|
|
8783
|
+
roughness: 0.2,
|
|
8784
|
+
metallic: 0,
|
|
8785
|
+
emissive: { r: 0, g: 0, b: 0 }
|
|
8786
|
+
},
|
|
8787
|
+
reflection: 1,
|
|
8788
|
+
surfaceType: "water"
|
|
8283
8789
|
});
|
|
8284
8790
|
}
|
|
8285
8791
|
}
|
|
8286
8792
|
const waterMotionEffects = buildWaterMotionEffects(state);
|
|
8287
8793
|
const lightSources = collectSceneLightSources(state, visuals);
|
|
8288
|
-
pushHarborGeometry(camera, viewport, sceneTriangles,
|
|
8794
|
+
pushHarborGeometry(camera, viewport, sceneTriangles, state);
|
|
8289
8795
|
const cloth = buildClothSurface(
|
|
8290
8796
|
state,
|
|
8291
8797
|
state,
|
|
@@ -8307,23 +8813,46 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8307
8813
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
8308
8814
|
normal,
|
|
8309
8815
|
baseColor: cloth.color,
|
|
8310
|
-
accent: cloth.band === "near" ? 0.1 : 0.04
|
|
8816
|
+
accent: cloth.band === "near" ? 0.1 : 0.04,
|
|
8817
|
+
material: {
|
|
8818
|
+
name: "flag-cloth",
|
|
8819
|
+
color: cloth.color,
|
|
8820
|
+
roughness: 0.94,
|
|
8821
|
+
metallic: 0,
|
|
8822
|
+
emissive: { r: 0, g: 0, b: 0 }
|
|
8823
|
+
},
|
|
8824
|
+
reflection: 0,
|
|
8825
|
+
surfaceType: "cloth"
|
|
8311
8826
|
});
|
|
8312
8827
|
}
|
|
8313
8828
|
for (const ship of state.ships) {
|
|
8829
|
+
const activeShipModel = resolveShipModel(state, ship, shipModel);
|
|
8314
8830
|
buildTrianglesFromMesh(
|
|
8315
|
-
|
|
8831
|
+
activeShipModel,
|
|
8316
8832
|
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE },
|
|
8317
8833
|
ship.tint,
|
|
8318
8834
|
camera,
|
|
8319
8835
|
viewport,
|
|
8320
8836
|
sceneTriangles,
|
|
8321
|
-
|
|
8837
|
+
{
|
|
8838
|
+
accent: nearLighting.rtParticipation.directShadows === "premium" ? 0.08 : 0.02,
|
|
8839
|
+
reflection: 0,
|
|
8840
|
+
surfaceType: "ship"
|
|
8841
|
+
}
|
|
8322
8842
|
);
|
|
8323
8843
|
}
|
|
8324
8844
|
drawTriangles(ctx, waterTriangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
8325
8845
|
for (const ship of state.ships) {
|
|
8326
|
-
renderShipShadow(
|
|
8846
|
+
renderShipShadow(
|
|
8847
|
+
ctx,
|
|
8848
|
+
resolveShipModel(state, ship, shipModel),
|
|
8849
|
+
ship,
|
|
8850
|
+
state,
|
|
8851
|
+
camera,
|
|
8852
|
+
viewport,
|
|
8853
|
+
lightDir,
|
|
8854
|
+
shadowStrength
|
|
8855
|
+
);
|
|
8327
8856
|
}
|
|
8328
8857
|
renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength);
|
|
8329
8858
|
for (const source of lightSources.reflectionLights) {
|
|
@@ -8331,9 +8860,18 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8331
8860
|
}
|
|
8332
8861
|
renderWaterMotionEffects(ctx, waterMotionEffects, camera, viewport);
|
|
8333
8862
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
8334
|
-
drawTriangles(
|
|
8863
|
+
drawTriangles(
|
|
8864
|
+
ctx,
|
|
8865
|
+
sceneTriangles,
|
|
8866
|
+
lightDir,
|
|
8867
|
+
reflectionStrength,
|
|
8868
|
+
camera,
|
|
8869
|
+
shadowStrength,
|
|
8870
|
+
lightSources.directLights
|
|
8871
|
+
);
|
|
8335
8872
|
renderFlagPole(ctx, camera, viewport);
|
|
8336
8873
|
renderClothAccent(ctx, cloth, camera, viewport);
|
|
8874
|
+
renderLighthouseBeam(ctx, state, camera, viewport, visuals);
|
|
8337
8875
|
for (const source of lightSources.directLights) {
|
|
8338
8876
|
renderDirectLightGlow(ctx, source, camera, viewport);
|
|
8339
8877
|
}
|
|
@@ -8341,6 +8879,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8341
8879
|
renderShipRigging(ctx, ship, camera, viewport);
|
|
8342
8880
|
}
|
|
8343
8881
|
renderSprays(ctx, state.sprays, camera, viewport);
|
|
8882
|
+
renderAtmosphericGrade(ctx, canvas, state, visuals);
|
|
8344
8883
|
const debugSnapshot = state.debugSession.getSnapshot();
|
|
8345
8884
|
const quality = {
|
|
8346
8885
|
fluid: state.fluidDetail.getSnapshot(),
|
|
@@ -8349,11 +8888,11 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8349
8888
|
};
|
|
8350
8889
|
const sceneMetrics = [
|
|
8351
8890
|
`focus: ${state.focus}`,
|
|
8352
|
-
`ships: ${state.ships.length} active GLTF hulls`,
|
|
8353
|
-
`moonlight: cold overhead key + ${HARBOR_TORCHES.length + state.ships.
|
|
8891
|
+
`ships: ${state.ships.length} active GLTF hulls across ${new Set(state.ships.map((ship) => ship.modelKey)).size} model families`,
|
|
8892
|
+
`moonlight: cold overhead key + ${HARBOR_TORCHES.length + state.ships.reduce((total, ship) => total + (Array.isArray(ship.lanterns) ? ship.lanterns.length : 0), 0)} warm deck and harbor lights`,
|
|
8354
8893
|
`physics snapshot: ${state.physics.snapshot.stage} (${state.physics.snapshot.stability})`,
|
|
8355
8894
|
`physics contacts: ${state.contactCount}`,
|
|
8356
|
-
`mass split: ${state.ships.map((ship) => `${ship.id} ${(getShipMass(ship, shipModel) / 1e3).toFixed(1)}t`).join(" \xB7 ")}`,
|
|
8895
|
+
`mass split: ${state.ships.map((ship) => `${ship.id} ${(getShipMass(ship, resolveShipModel(state, ship, shipModel)) / 1e3).toFixed(1)}t`).join(" \xB7 ")}`,
|
|
8357
8896
|
`cloth band: ${cloth.band} -> ${cloth.representation.output}`,
|
|
8358
8897
|
`fluid near band: ${water.bandMeshes[0].representation.output}`,
|
|
8359
8898
|
`lighting profile: ${lightingPlan.profile} (${lightingDistanceBands.length} bands)`
|
|
@@ -8374,11 +8913,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8374
8913
|
`pipeline samples: ${debugSnapshot.pipeline.sampleCount}`,
|
|
8375
8914
|
`tracked memory: ${(debugSnapshot.memory.totalTrackedBytes / (1024 * 1024)).toFixed(1)} MB`
|
|
8376
8915
|
];
|
|
8377
|
-
const sceneNotes = state.focus === "physics" ?
|
|
8378
|
-
"Stable world snapshots are taken after the authoritative rigid-body commit and before visual follow-up work.",
|
|
8379
|
-
"The ships collide with mass-weighted impulses and positional correction, so the heavier hull keeps more of its line.",
|
|
8380
|
-
"Moonlight keeps the overall read legible while lanterns and torches make collision moments easy to track against the water."
|
|
8381
|
-
] : SCENE_NOTES;
|
|
8916
|
+
const sceneNotes = state.focus === "physics" ? PHYSICS_SCENE_NOTE_KEYS.map((key) => state.translate(key)) : SCENE_NOTE_KEYS.map((key) => state.translate(key));
|
|
8382
8917
|
const custom = state.demoDescription ?? null;
|
|
8383
8918
|
setListContent(
|
|
8384
8919
|
dom.sceneMetrics,
|
|
@@ -8393,8 +8928,16 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8393
8928
|
Array.isArray(custom?.debugMetrics) ? custom.debugMetrics : debugMetrics
|
|
8394
8929
|
);
|
|
8395
8930
|
setListContent(dom.sceneNotes, Array.isArray(custom?.notes) ? custom.notes : sceneNotes);
|
|
8396
|
-
dom.status.textContent = typeof custom?.status === "string" ? custom.status :
|
|
8397
|
-
|
|
8931
|
+
dom.status.textContent = typeof custom?.status === "string" ? custom.status : state.translate(gpuSharedTranslationKeys.statusLive, {
|
|
8932
|
+
fps: state.lastDecision.metrics.fps.toFixed(1)
|
|
8933
|
+
});
|
|
8934
|
+
dom.details.textContent = typeof custom?.details === "string" ? custom.details : state.focus === "physics" ? state.translate(gpuSharedTranslationKeys.detailsPhysics, {
|
|
8935
|
+
snapshotStageId: state.physics.plan.snapshotStageId
|
|
8936
|
+
}) : state.showcaseRealisticModelsEnabled ? state.translate(gpuSharedTranslationKeys.detailsRealistic, {
|
|
8937
|
+
pressureLevel: state.lastDecision.pressureLevel
|
|
8938
|
+
}) : state.translate(gpuSharedTranslationKeys.detailsLegacy, {
|
|
8939
|
+
pressureLevel: state.lastDecision.pressureLevel
|
|
8940
|
+
});
|
|
8398
8941
|
}
|
|
8399
8942
|
function updateSceneState(state, dt, shipModel) {
|
|
8400
8943
|
updateShips(state, dt, shipModel);
|
|
@@ -8424,15 +8967,18 @@ function syncTextState(state, shipModel) {
|
|
|
8424
8967
|
stress: state.stress,
|
|
8425
8968
|
ships: state.ships.map((ship) => ({
|
|
8426
8969
|
id: ship.id,
|
|
8970
|
+
modelKey: ship.modelKey ?? "brigantine",
|
|
8427
8971
|
x: Number(ship.position.x.toFixed(2)),
|
|
8428
8972
|
y: Number(ship.position.y.toFixed(2)),
|
|
8429
8973
|
z: Number(ship.position.z.toFixed(2)),
|
|
8430
8974
|
vx: Number(ship.velocity.x.toFixed(2)),
|
|
8431
8975
|
vz: Number(ship.velocity.z.toFixed(2)),
|
|
8432
|
-
massKg: Math.round(getShipMass(ship, shipModel)),
|
|
8976
|
+
massKg: Math.round(getShipMass(ship, resolveShipModel(state, ship, shipModel))),
|
|
8433
8977
|
lanterns: Array.isArray(ship.lanterns) ? ship.lanterns.length : 0
|
|
8434
8978
|
})),
|
|
8435
|
-
shipPhysics:
|
|
8979
|
+
shipPhysics: Object.fromEntries(
|
|
8980
|
+
state.ships.map((ship) => [ship.id, resolveShipModel(state, ship, shipModel)?.physics ?? null])
|
|
8981
|
+
),
|
|
8436
8982
|
sprays: state.sprays.length,
|
|
8437
8983
|
waveImpulses: state.waveImpulses.length,
|
|
8438
8984
|
pressure: state.lastDecision?.pressureLevel ?? "stable",
|
|
@@ -8455,22 +9001,36 @@ function syncTextState(state, shipModel) {
|
|
|
8455
9001
|
}
|
|
8456
9002
|
};
|
|
8457
9003
|
}
|
|
8458
|
-
async function mountGpuShowcase(options = {}) {
|
|
9004
|
+
async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
8459
9005
|
injectStyles();
|
|
8460
9006
|
const root = options.root ?? document.body;
|
|
8461
9007
|
root.classList?.add?.(ROOT_CLASS);
|
|
9008
|
+
const captureSettings = resolveCaptureSettings(options);
|
|
9009
|
+
if (captureSettings.captureMode) {
|
|
9010
|
+
root.classList?.add?.(CAPTURE_CLASS);
|
|
9011
|
+
}
|
|
8462
9012
|
const previousMarkup = root.innerHTML;
|
|
8463
9013
|
const previousRenderGameToText = window.render_game_to_text;
|
|
8464
9014
|
const previousAdvanceTime = window.advanceTime;
|
|
8465
9015
|
const focus = options.focus ?? new URLSearchParams(window.location.search).get("focus") ?? "integrated";
|
|
9016
|
+
const translate = createGpuSharedTranslator(options.translate);
|
|
8466
9017
|
const dom = buildDemoDom(root, {
|
|
8467
9018
|
packageName: options.packageName ?? "@plasius/gpu-demo-viewer",
|
|
8468
|
-
title: options.title ??
|
|
8469
|
-
subtitle: options.subtitle ??
|
|
9019
|
+
title: options.title ?? translate(gpuSharedTranslationKeys.showcaseTitle),
|
|
9020
|
+
subtitle: options.subtitle ?? translate(gpuSharedTranslationKeys.showcaseSubtitle),
|
|
9021
|
+
translate
|
|
8470
9022
|
});
|
|
8471
9023
|
dom.focusMode.value = focus;
|
|
8472
|
-
const state = createSceneState({
|
|
8473
|
-
|
|
9024
|
+
const state = createSceneState({
|
|
9025
|
+
focus,
|
|
9026
|
+
translate,
|
|
9027
|
+
realisticModelsEnabled: isFeatureEnabled(featureFlags, GPU_SHOWCASE_REALISTIC_MODELS_FEATURE, true),
|
|
9028
|
+
captureMode: captureSettings.captureMode,
|
|
9029
|
+
renderScale: captureSettings.renderScale
|
|
9030
|
+
});
|
|
9031
|
+
const assetCatalog = await (state.showcaseRealisticModelsEnabled ? loadShowcaseAssetCatalog() : createLegacyShowcaseAssetCatalog());
|
|
9032
|
+
const shipModel = assetCatalog.ships[assetCatalog.primaryShipKey];
|
|
9033
|
+
state.assetCatalog = assetCatalog;
|
|
8474
9034
|
state.shipModel = shipModel;
|
|
8475
9035
|
state.packageState = typeof options.createState === "function" ? options.createState() : void 0;
|
|
8476
9036
|
updatePhysicsSnapshot(state, shipModel);
|
|
@@ -8481,6 +9041,8 @@ async function mountGpuShowcase(options = {}) {
|
|
|
8481
9041
|
if (!ctx) {
|
|
8482
9042
|
throw new Error("2D canvas context is required for the shared showcase.");
|
|
8483
9043
|
}
|
|
9044
|
+
ctx.imageSmoothingEnabled = true;
|
|
9045
|
+
ctx.imageSmoothingQuality = "high";
|
|
8484
9046
|
let animationFrameId = 0;
|
|
8485
9047
|
let destroyed = false;
|
|
8486
9048
|
const renderFrame = (nowMs) => {
|
|
@@ -8501,13 +9063,14 @@ async function mountGpuShowcase(options = {}) {
|
|
|
8501
9063
|
state.lastDecision = recordTelemetry(state, syntheticFrame);
|
|
8502
9064
|
}
|
|
8503
9065
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
9066
|
+
resizeCanvasToDisplaySize(dom.canvas, state);
|
|
8504
9067
|
renderScene(ctx, dom.canvas, state, shipModel, dom);
|
|
8505
9068
|
syncTextState(state, shipModel);
|
|
8506
9069
|
animationFrameId = requestAnimationFrame(renderFrame);
|
|
8507
9070
|
};
|
|
8508
9071
|
const handlePauseClick = () => {
|
|
8509
9072
|
state.paused = !state.paused;
|
|
8510
|
-
dom.pauseButton.textContent = state.paused ?
|
|
9073
|
+
dom.pauseButton.textContent = state.paused ? state.translate(gpuSharedTranslationKeys.resume) : state.translate(gpuSharedTranslationKeys.pause);
|
|
8511
9074
|
};
|
|
8512
9075
|
const handleStressChange = () => {
|
|
8513
9076
|
state.stress = dom.stressToggle.checked;
|
|
@@ -8542,6 +9105,7 @@ async function mountGpuShowcase(options = {}) {
|
|
|
8542
9105
|
state.packageState = void 0;
|
|
8543
9106
|
}
|
|
8544
9107
|
root.classList?.remove?.(ROOT_CLASS);
|
|
9108
|
+
root.classList?.remove?.(CAPTURE_CLASS);
|
|
8545
9109
|
root.innerHTML = previousMarkup;
|
|
8546
9110
|
if (typeof previousRenderGameToText === "function") {
|
|
8547
9111
|
window.render_game_to_text = previousRenderGameToText;
|
|
@@ -8562,6 +9126,12 @@ async function mountGpuShowcase(options = {}) {
|
|
|
8562
9126
|
};
|
|
8563
9127
|
}
|
|
8564
9128
|
function updatePhysicsSnapshot(state, shipModel) {
|
|
9129
|
+
const rigidBodyShapes = Object.fromEntries(
|
|
9130
|
+
state.ships.map((ship) => [
|
|
9131
|
+
ship.id,
|
|
9132
|
+
resolveShipModel(state, ship, shipModel)?.physics?.shape ?? "box"
|
|
9133
|
+
])
|
|
9134
|
+
);
|
|
8565
9135
|
state.physics.snapshot = createPhysicsWorldSnapshot({
|
|
8566
9136
|
frameId: `showcase-${state.frame}`,
|
|
8567
9137
|
tick: state.frame,
|
|
@@ -8577,7 +9147,8 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
8577
9147
|
collisionCount: state.collisionCount,
|
|
8578
9148
|
contactCount: state.contactCount,
|
|
8579
9149
|
snapshotStageId: state.physics.plan.snapshotStageId,
|
|
8580
|
-
rigidBodyShape: shipModel.physics.shape ?? "box"
|
|
9150
|
+
rigidBodyShape: shipModel.physics.shape ?? "box",
|
|
9151
|
+
rigidBodyShapes
|
|
8581
9152
|
}
|
|
8582
9153
|
});
|
|
8583
9154
|
}
|
|
@@ -8590,4 +9161,4 @@ export {
|
|
|
8590
9161
|
mountGpuShowcase,
|
|
8591
9162
|
showcaseFocusModes
|
|
8592
9163
|
};
|
|
8593
|
-
//# sourceMappingURL=showcase-runtime-
|
|
9164
|
+
//# sourceMappingURL=showcase-runtime-PN7N3FZY.js.map
|