@plasius/gpu-shared 0.1.10 → 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 +51 -1
- package/README.md +59 -2
- 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 +1265 -281
- 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-55OVDYWT.js → showcase-runtime-PN7N3FZY.js} +818 -239
- 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 +924 -190
- 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-55OVDYWT.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",
|
|
@@ -7191,6 +7504,12 @@ function sampleWave(state, x, z, time) {
|
|
|
7191
7504
|
const base = Math.sin(along * 0.22 - time * 1.12 * phaseSpeed) * 0.42 + Math.cos(along * 0.11 + cross * 0.07 - time * 0.78 * phaseSpeed) * 0.26 + Math.sin(cross * 0.19 - time * 1.34 * phaseSpeed) * 0.16;
|
|
7192
7505
|
return base * amplitude + sampleShipWake(state, x, z, time) + sampleWaveImpulses(state, x, z, time);
|
|
7193
7506
|
}
|
|
7507
|
+
function resolveFluidBandContinuity(continuity, band) {
|
|
7508
|
+
if (continuity?.bands && continuity.bands[band]) {
|
|
7509
|
+
return continuity.bands[band];
|
|
7510
|
+
}
|
|
7511
|
+
return continuity ?? { amplitudeFloor: 1, frequencyFloor: 1 };
|
|
7512
|
+
}
|
|
7194
7513
|
function buildWaterMotionEffects(state) {
|
|
7195
7514
|
const wakeTrails = [];
|
|
7196
7515
|
const rippleRings = state.waveImpulses.map((impulse) => {
|
|
@@ -7264,6 +7583,7 @@ function buildWaterBands(state, fluidDetail, visuals) {
|
|
|
7264
7583
|
for (const bandSpec of bandExtents) {
|
|
7265
7584
|
const representation = fluidPlan.representations.find((entry) => entry.band === bandSpec.band) ?? fluidPlan.representations[0];
|
|
7266
7585
|
const continuity = createFluidContinuityEnvelope({ fluidBodyId: "harbor" });
|
|
7586
|
+
const bandContinuity = resolveFluidBandContinuity(continuity, bandSpec.band);
|
|
7267
7587
|
const bandResolution = bandSpec.band === "near" ? fluidDetail.nearResolution : bandSpec.band === "mid" ? fluidDetail.midResolution : bandSpec.band === "far" ? 5 : 3;
|
|
7268
7588
|
const cols = Math.max(4, bandResolution * 2);
|
|
7269
7589
|
const rows = Math.max(4, bandResolution + 2);
|
|
@@ -7277,7 +7597,7 @@ function buildWaterBands(state, fluidDetail, visuals) {
|
|
|
7277
7597
|
const v = row / (rows - 1);
|
|
7278
7598
|
const x = originX + bandSpec.width * u;
|
|
7279
7599
|
const z = originZ + bandSpec.depth * v;
|
|
7280
|
-
const y = bandSpec.y + sampleWave(state, x, z, state.time) *
|
|
7600
|
+
const y = bandSpec.y + sampleWave(state, x, z, state.time) * bandContinuity.amplitudeFloor * (bandSpec.band === "near" ? 0.9 : bandSpec.band === "mid" ? 0.55 : 0.3);
|
|
7281
7601
|
positions.push(vec3(x, y, z));
|
|
7282
7602
|
}
|
|
7283
7603
|
}
|
|
@@ -7293,7 +7613,7 @@ function buildWaterBands(state, fluidDetail, visuals) {
|
|
|
7293
7613
|
bandMeshes.push({
|
|
7294
7614
|
band: bandSpec.band,
|
|
7295
7615
|
representation,
|
|
7296
|
-
continuity,
|
|
7616
|
+
continuity: bandContinuity,
|
|
7297
7617
|
rows,
|
|
7298
7618
|
cols,
|
|
7299
7619
|
positions,
|
|
@@ -7312,6 +7632,7 @@ function buildWaterBands(state, fluidDetail, visuals) {
|
|
|
7312
7632
|
return { fluidPlan, bandMeshes };
|
|
7313
7633
|
}
|
|
7314
7634
|
function createSceneState(options) {
|
|
7635
|
+
const translate = options.translate;
|
|
7315
7636
|
const { governor, fluidDetail, clothDetail, lightingDetail } = createPerformanceGovernor();
|
|
7316
7637
|
const physicsProfile = defaultPhysicsWorkerProfile;
|
|
7317
7638
|
const physicsPlan = createPhysicsSimulationPlan(physicsProfile);
|
|
@@ -7319,7 +7640,7 @@ function createSceneState(options) {
|
|
|
7319
7640
|
const debugSession = createGpuDebugSession({
|
|
7320
7641
|
enabled: true,
|
|
7321
7642
|
adapter: {
|
|
7322
|
-
label:
|
|
7643
|
+
label: translate(gpuSharedTranslationKeys.debugAdapterShowcase),
|
|
7323
7644
|
memoryCapacityHintBytes: 6 * 1024 * 1024 * 1024,
|
|
7324
7645
|
coreCountHint: 12
|
|
7325
7646
|
}
|
|
@@ -7329,22 +7650,26 @@ function createSceneState(options) {
|
|
|
7329
7650
|
owner: "renderer",
|
|
7330
7651
|
category: "texture",
|
|
7331
7652
|
sizeBytes: 1280 * 720 * 4,
|
|
7332
|
-
label:
|
|
7653
|
+
label: translate(gpuSharedTranslationKeys.debugMainColorBuffer)
|
|
7333
7654
|
});
|
|
7334
7655
|
debugSession.trackAllocation({
|
|
7335
7656
|
id: "showcase.shadow-impression",
|
|
7336
7657
|
owner: "lighting",
|
|
7337
7658
|
category: "texture",
|
|
7338
7659
|
sizeBytes: 12 * 1024 * 1024,
|
|
7339
|
-
label:
|
|
7660
|
+
label: translate(gpuSharedTranslationKeys.debugShadowImpressionAtlas)
|
|
7340
7661
|
});
|
|
7341
7662
|
return {
|
|
7663
|
+
translate,
|
|
7342
7664
|
focus: options.focus,
|
|
7343
7665
|
governor,
|
|
7344
7666
|
fluidDetail,
|
|
7345
7667
|
clothDetail,
|
|
7346
7668
|
lightingDetail,
|
|
7347
7669
|
debugSession,
|
|
7670
|
+
showcaseRealisticModelsEnabled: options.realisticModelsEnabled !== false,
|
|
7671
|
+
captureMode: options.captureMode === true,
|
|
7672
|
+
renderScale: readPositiveNumber3(options.renderScale, void 0),
|
|
7348
7673
|
packageState: void 0,
|
|
7349
7674
|
demoDescription: null,
|
|
7350
7675
|
demoVisuals: null,
|
|
@@ -7359,6 +7684,7 @@ function createSceneState(options) {
|
|
|
7359
7684
|
ships: [
|
|
7360
7685
|
{
|
|
7361
7686
|
id: "northwind",
|
|
7687
|
+
modelKey: "brigantine",
|
|
7362
7688
|
position: vec3(-5.2, 0, 7.2),
|
|
7363
7689
|
velocity: vec3(2.35, 0, -1.08),
|
|
7364
7690
|
rotationY: 0.58,
|
|
@@ -7369,17 +7695,18 @@ function createSceneState(options) {
|
|
|
7369
7695
|
throttleResponse: 0.46,
|
|
7370
7696
|
rudderResponse: 0.54,
|
|
7371
7697
|
wanderPhase: 0.35,
|
|
7372
|
-
lanterns:
|
|
7698
|
+
lanterns: CUTTER_LANTERNS,
|
|
7373
7699
|
lanternStrength: 1.06,
|
|
7374
7700
|
collisionRadiusScale: 1.04
|
|
7375
7701
|
},
|
|
7376
7702
|
{
|
|
7377
7703
|
id: "tidecaller",
|
|
7704
|
+
modelKey: "cutter",
|
|
7378
7705
|
position: vec3(4.8, 0, 4.4),
|
|
7379
7706
|
velocity: vec3(-2.15, 0, 1.74),
|
|
7380
7707
|
rotationY: -2.48,
|
|
7381
7708
|
angularVelocity: -0.2,
|
|
7382
|
-
tint: { r: 0.
|
|
7709
|
+
tint: { r: 0.58, g: 0.24, b: 0.16 },
|
|
7383
7710
|
massScale: 0.84,
|
|
7384
7711
|
cruiseSpeed: 2.68,
|
|
7385
7712
|
throttleResponse: 0.7,
|
|
@@ -7403,6 +7730,7 @@ function createSceneState(options) {
|
|
|
7403
7730
|
manifest: physicsManifest,
|
|
7404
7731
|
snapshot: null
|
|
7405
7732
|
},
|
|
7733
|
+
assetCatalog: null,
|
|
7406
7734
|
shipModel: null
|
|
7407
7735
|
};
|
|
7408
7736
|
}
|
|
@@ -7478,10 +7806,38 @@ function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, s
|
|
|
7478
7806
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
7479
7807
|
}
|
|
7480
7808
|
}
|
|
7481
|
-
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 = []) {
|
|
7482
7833
|
triangles.sort((left, right) => right.depth - left.depth);
|
|
7483
7834
|
for (const triangle of triangles) {
|
|
7484
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
|
+
};
|
|
7485
7841
|
const shaded = shadeColor(
|
|
7486
7842
|
triangle.baseColor,
|
|
7487
7843
|
surfaceNormal,
|
|
@@ -7489,19 +7845,41 @@ function drawTriangles(ctx, triangles, lightDir, reflectionStrength, camera, sha
|
|
|
7489
7845
|
clamp((triangle.worldCenter.y + 3) / 10, 0, 1),
|
|
7490
7846
|
triangle.accent
|
|
7491
7847
|
);
|
|
7492
|
-
const reflection =
|
|
7848
|
+
const reflection = reflectionStrength * (triangle.reflection ?? 0);
|
|
7493
7849
|
const viewDir = normalizeVec3(subVec3(camera.eye, triangle.worldCenter));
|
|
7494
7850
|
const reflectedLight = reflectVec3(scaleVec3(lightDir, -1), surfaceNormal);
|
|
7495
|
-
const gloss =
|
|
7496
|
-
const
|
|
7497
|
-
const
|
|
7498
|
-
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(
|
|
7499
7858
|
{
|
|
7500
|
-
r: clamp(
|
|
7501
|
-
|
|
7502
|
-
|
|
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
|
+
)
|
|
7503
7874
|
},
|
|
7504
|
-
|
|
7875
|
+
material,
|
|
7876
|
+
triangle.worldCenter,
|
|
7877
|
+
surfaceNormal,
|
|
7878
|
+
triangle.surfaceType
|
|
7879
|
+
);
|
|
7880
|
+
const fill = colorToRgba(
|
|
7881
|
+
detailed,
|
|
7882
|
+
triangle.baseColor.a ?? 0.98
|
|
7505
7883
|
);
|
|
7506
7884
|
ctx.fillStyle = fill;
|
|
7507
7885
|
ctx.beginPath();
|
|
@@ -7532,74 +7910,100 @@ function renderProjectedShadow(ctx, worldPoints, camera, viewport, lightDir, opt
|
|
|
7532
7910
|
ctx.fill();
|
|
7533
7911
|
ctx.restore();
|
|
7534
7912
|
}
|
|
7535
|
-
function pushHarborGeometry(camera, viewport, triangles,
|
|
7536
|
-
|
|
7537
|
-
{
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
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;
|
|
7557
7940
|
}
|
|
7558
|
-
];
|
|
7559
|
-
for (const object of harborObjects) {
|
|
7560
7941
|
buildTrianglesFromMesh(
|
|
7561
|
-
|
|
7942
|
+
mesh,
|
|
7562
7943
|
{
|
|
7563
|
-
position:
|
|
7564
|
-
rotationY:
|
|
7565
|
-
scale:
|
|
7944
|
+
position: vec3(placement.position.x, placement.position.y, placement.position.z),
|
|
7945
|
+
rotationY: placement.rotationY,
|
|
7946
|
+
scale: placement.scale
|
|
7566
7947
|
},
|
|
7567
|
-
|
|
7948
|
+
null,
|
|
7568
7949
|
camera,
|
|
7569
7950
|
viewport,
|
|
7570
7951
|
triangles,
|
|
7571
|
-
|
|
7952
|
+
{
|
|
7953
|
+
accent: placement.accent,
|
|
7954
|
+
reflection: 0,
|
|
7955
|
+
surfaceType: "structure"
|
|
7956
|
+
}
|
|
7572
7957
|
);
|
|
7573
7958
|
}
|
|
7574
7959
|
}
|
|
7575
7960
|
function renderShipRigging(ctx, ship, camera, viewport) {
|
|
7576
7961
|
const transform = { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE };
|
|
7577
|
-
const
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
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));
|
|
7587
7990
|
if (projected.some((value) => value === null)) {
|
|
7588
7991
|
return;
|
|
7589
7992
|
}
|
|
7590
|
-
ctx.strokeStyle =
|
|
7591
|
-
ctx.lineWidth = 3.5;
|
|
7993
|
+
ctx.strokeStyle = layout.lineColor;
|
|
7994
|
+
ctx.lineWidth = ship.modelKey === "cutter" ? 2.2 : 3.5;
|
|
7592
7995
|
ctx.beginPath();
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
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
|
+
}
|
|
7597
8000
|
ctx.stroke();
|
|
7598
|
-
|
|
8001
|
+
const [a, b, c] = layout.sailTriangle;
|
|
8002
|
+
ctx.fillStyle = layout.sailColor;
|
|
7599
8003
|
ctx.beginPath();
|
|
7600
|
-
ctx.moveTo(projected[
|
|
7601
|
-
ctx.lineTo(projected[
|
|
7602
|
-
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);
|
|
7603
8007
|
ctx.closePath();
|
|
7604
8008
|
ctx.fill();
|
|
7605
8009
|
}
|
|
@@ -7817,10 +8221,10 @@ function resolveBoundaryCollision(ship, state, shipModel) {
|
|
|
7817
8221
|
}
|
|
7818
8222
|
}
|
|
7819
8223
|
}
|
|
7820
|
-
function resolveShipCollision(state, a, b,
|
|
8224
|
+
function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
7821
8225
|
const delta = subVec3(b.position, a.position);
|
|
7822
|
-
const radiusA = getShipCollisionRadius(a,
|
|
7823
|
-
const radiusB = getShipCollisionRadius(b,
|
|
8226
|
+
const radiusA = getShipCollisionRadius(a, shipModelA);
|
|
8227
|
+
const radiusB = getShipCollisionRadius(b, shipModelB);
|
|
7824
8228
|
const distance = Math.hypot(delta.x, delta.z);
|
|
7825
8229
|
const minDistance = radiusA + radiusB;
|
|
7826
8230
|
if (distance >= minDistance) {
|
|
@@ -7829,15 +8233,15 @@ function resolveShipCollision(state, a, b, shipModel) {
|
|
|
7829
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)));
|
|
7830
8234
|
const tangent = vec3(-normal.z, 0, normal.x);
|
|
7831
8235
|
const penetration = minDistance - distance;
|
|
7832
|
-
const invMassA = getShipInverseMass(a,
|
|
7833
|
-
const invMassB = getShipInverseMass(b,
|
|
8236
|
+
const invMassA = getShipInverseMass(a, shipModelA);
|
|
8237
|
+
const invMassB = getShipInverseMass(b, shipModelB);
|
|
7834
8238
|
const invMassSum = invMassA + invMassB;
|
|
7835
8239
|
const correction = scaleVec3(normal, penetration / Math.max(1e-4, invMassSum) * 0.72);
|
|
7836
8240
|
a.position = subVec3(a.position, scaleVec3(correction, invMassA));
|
|
7837
8241
|
b.position = addVec3(b.position, scaleVec3(correction, invMassB));
|
|
7838
8242
|
const relativeVelocity = subVec3(b.velocity, a.velocity);
|
|
7839
8243
|
const velocityAlongNormal = dotVec3(relativeVelocity, normal);
|
|
7840
|
-
const restitution = readPhysicsNumber(
|
|
8244
|
+
const restitution = (readPhysicsNumber(shipModelA.physics, "restitution", 0.22) + readPhysicsNumber(shipModelB.physics, "restitution", 0.22)) / 2 * 0.88;
|
|
7841
8245
|
if (velocityAlongNormal < 0) {
|
|
7842
8246
|
const impulseMagnitude = -(1 + restitution) * velocityAlongNormal / Math.max(1e-4, invMassSum);
|
|
7843
8247
|
const impulse = scaleVec3(normal, impulseMagnitude);
|
|
@@ -7852,8 +8256,8 @@ function resolveShipCollision(state, a, b, shipModel) {
|
|
|
7852
8256
|
const frictionImpulse = scaleVec3(tangent, frictionMagnitude);
|
|
7853
8257
|
a.velocity = subVec3(a.velocity, scaleVec3(frictionImpulse, invMassA));
|
|
7854
8258
|
b.velocity = addVec3(b.velocity, scaleVec3(frictionImpulse, invMassB));
|
|
7855
|
-
a.angularVelocity -= tangentSpeed * radiusA * getShipInverseInertia(a,
|
|
7856
|
-
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;
|
|
7857
8261
|
const impactSpeed = Math.abs(velocityAlongNormal);
|
|
7858
8262
|
if (impactSpeed > 0.18 && Math.max(readVisualNumber(a.collisionCooldown, 0), readVisualNumber(b.collisionCooldown, 0)) <= 0) {
|
|
7859
8263
|
const contactPoint = vec3(
|
|
@@ -7885,12 +8289,17 @@ function updateShips(state, dt, shipModel) {
|
|
|
7885
8289
|
let collided = false;
|
|
7886
8290
|
state.contactCount = 0;
|
|
7887
8291
|
for (const ship of state.ships) {
|
|
7888
|
-
|
|
7889
|
-
|
|
8292
|
+
const activeShipModel = resolveShipModel(state, ship, shipModel);
|
|
8293
|
+
updateShipMotion(state, ship, dt, activeShipModel);
|
|
8294
|
+
resolveBoundaryCollision(ship, state, activeShipModel);
|
|
7890
8295
|
}
|
|
7891
8296
|
for (let index = 0; index < state.ships.length; index += 1) {
|
|
7892
8297
|
for (let otherIndex = index + 1; otherIndex < state.ships.length; otherIndex += 1) {
|
|
7893
|
-
|
|
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;
|
|
7894
8303
|
}
|
|
7895
8304
|
}
|
|
7896
8305
|
state.collisionFlash = collided ? Math.max(0.12, state.collisionFlash) : Math.max(0, state.collisionFlash - dt * 1.3);
|
|
@@ -8154,6 +8563,101 @@ function renderWaterLightReflection(ctx, source, state, camera, viewport) {
|
|
|
8154
8563
|
ctx.fill();
|
|
8155
8564
|
ctx.restore();
|
|
8156
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
|
+
}
|
|
8157
8661
|
function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
8158
8662
|
ctx.save();
|
|
8159
8663
|
ctx.globalCompositeOperation = "screen";
|
|
@@ -8272,13 +8776,22 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8272
8776
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
8273
8777
|
normal,
|
|
8274
8778
|
baseColor: bandMesh.color,
|
|
8275
|
-
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"
|
|
8276
8789
|
});
|
|
8277
8790
|
}
|
|
8278
8791
|
}
|
|
8279
8792
|
const waterMotionEffects = buildWaterMotionEffects(state);
|
|
8280
8793
|
const lightSources = collectSceneLightSources(state, visuals);
|
|
8281
|
-
pushHarborGeometry(camera, viewport, sceneTriangles,
|
|
8794
|
+
pushHarborGeometry(camera, viewport, sceneTriangles, state);
|
|
8282
8795
|
const cloth = buildClothSurface(
|
|
8283
8796
|
state,
|
|
8284
8797
|
state,
|
|
@@ -8300,23 +8813,46 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8300
8813
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
8301
8814
|
normal,
|
|
8302
8815
|
baseColor: cloth.color,
|
|
8303
|
-
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"
|
|
8304
8826
|
});
|
|
8305
8827
|
}
|
|
8306
8828
|
for (const ship of state.ships) {
|
|
8829
|
+
const activeShipModel = resolveShipModel(state, ship, shipModel);
|
|
8307
8830
|
buildTrianglesFromMesh(
|
|
8308
|
-
|
|
8831
|
+
activeShipModel,
|
|
8309
8832
|
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE },
|
|
8310
8833
|
ship.tint,
|
|
8311
8834
|
camera,
|
|
8312
8835
|
viewport,
|
|
8313
8836
|
sceneTriangles,
|
|
8314
|
-
|
|
8837
|
+
{
|
|
8838
|
+
accent: nearLighting.rtParticipation.directShadows === "premium" ? 0.08 : 0.02,
|
|
8839
|
+
reflection: 0,
|
|
8840
|
+
surfaceType: "ship"
|
|
8841
|
+
}
|
|
8315
8842
|
);
|
|
8316
8843
|
}
|
|
8317
8844
|
drawTriangles(ctx, waterTriangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
8318
8845
|
for (const ship of state.ships) {
|
|
8319
|
-
renderShipShadow(
|
|
8846
|
+
renderShipShadow(
|
|
8847
|
+
ctx,
|
|
8848
|
+
resolveShipModel(state, ship, shipModel),
|
|
8849
|
+
ship,
|
|
8850
|
+
state,
|
|
8851
|
+
camera,
|
|
8852
|
+
viewport,
|
|
8853
|
+
lightDir,
|
|
8854
|
+
shadowStrength
|
|
8855
|
+
);
|
|
8320
8856
|
}
|
|
8321
8857
|
renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength);
|
|
8322
8858
|
for (const source of lightSources.reflectionLights) {
|
|
@@ -8324,9 +8860,18 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8324
8860
|
}
|
|
8325
8861
|
renderWaterMotionEffects(ctx, waterMotionEffects, camera, viewport);
|
|
8326
8862
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
8327
|
-
drawTriangles(
|
|
8863
|
+
drawTriangles(
|
|
8864
|
+
ctx,
|
|
8865
|
+
sceneTriangles,
|
|
8866
|
+
lightDir,
|
|
8867
|
+
reflectionStrength,
|
|
8868
|
+
camera,
|
|
8869
|
+
shadowStrength,
|
|
8870
|
+
lightSources.directLights
|
|
8871
|
+
);
|
|
8328
8872
|
renderFlagPole(ctx, camera, viewport);
|
|
8329
8873
|
renderClothAccent(ctx, cloth, camera, viewport);
|
|
8874
|
+
renderLighthouseBeam(ctx, state, camera, viewport, visuals);
|
|
8330
8875
|
for (const source of lightSources.directLights) {
|
|
8331
8876
|
renderDirectLightGlow(ctx, source, camera, viewport);
|
|
8332
8877
|
}
|
|
@@ -8334,6 +8879,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8334
8879
|
renderShipRigging(ctx, ship, camera, viewport);
|
|
8335
8880
|
}
|
|
8336
8881
|
renderSprays(ctx, state.sprays, camera, viewport);
|
|
8882
|
+
renderAtmosphericGrade(ctx, canvas, state, visuals);
|
|
8337
8883
|
const debugSnapshot = state.debugSession.getSnapshot();
|
|
8338
8884
|
const quality = {
|
|
8339
8885
|
fluid: state.fluidDetail.getSnapshot(),
|
|
@@ -8342,11 +8888,11 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8342
8888
|
};
|
|
8343
8889
|
const sceneMetrics = [
|
|
8344
8890
|
`focus: ${state.focus}`,
|
|
8345
|
-
`ships: ${state.ships.length} active GLTF hulls`,
|
|
8346
|
-
`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`,
|
|
8347
8893
|
`physics snapshot: ${state.physics.snapshot.stage} (${state.physics.snapshot.stability})`,
|
|
8348
8894
|
`physics contacts: ${state.contactCount}`,
|
|
8349
|
-
`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 ")}`,
|
|
8350
8896
|
`cloth band: ${cloth.band} -> ${cloth.representation.output}`,
|
|
8351
8897
|
`fluid near band: ${water.bandMeshes[0].representation.output}`,
|
|
8352
8898
|
`lighting profile: ${lightingPlan.profile} (${lightingDistanceBands.length} bands)`
|
|
@@ -8367,11 +8913,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8367
8913
|
`pipeline samples: ${debugSnapshot.pipeline.sampleCount}`,
|
|
8368
8914
|
`tracked memory: ${(debugSnapshot.memory.totalTrackedBytes / (1024 * 1024)).toFixed(1)} MB`
|
|
8369
8915
|
];
|
|
8370
|
-
const sceneNotes = state.focus === "physics" ?
|
|
8371
|
-
"Stable world snapshots are taken after the authoritative rigid-body commit and before visual follow-up work.",
|
|
8372
|
-
"The ships collide with mass-weighted impulses and positional correction, so the heavier hull keeps more of its line.",
|
|
8373
|
-
"Moonlight keeps the overall read legible while lanterns and torches make collision moments easy to track against the water."
|
|
8374
|
-
] : 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));
|
|
8375
8917
|
const custom = state.demoDescription ?? null;
|
|
8376
8918
|
setListContent(
|
|
8377
8919
|
dom.sceneMetrics,
|
|
@@ -8386,8 +8928,16 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
8386
8928
|
Array.isArray(custom?.debugMetrics) ? custom.debugMetrics : debugMetrics
|
|
8387
8929
|
);
|
|
8388
8930
|
setListContent(dom.sceneNotes, Array.isArray(custom?.notes) ? custom.notes : sceneNotes);
|
|
8389
|
-
dom.status.textContent = typeof custom?.status === "string" ? custom.status :
|
|
8390
|
-
|
|
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
|
+
});
|
|
8391
8941
|
}
|
|
8392
8942
|
function updateSceneState(state, dt, shipModel) {
|
|
8393
8943
|
updateShips(state, dt, shipModel);
|
|
@@ -8417,15 +8967,18 @@ function syncTextState(state, shipModel) {
|
|
|
8417
8967
|
stress: state.stress,
|
|
8418
8968
|
ships: state.ships.map((ship) => ({
|
|
8419
8969
|
id: ship.id,
|
|
8970
|
+
modelKey: ship.modelKey ?? "brigantine",
|
|
8420
8971
|
x: Number(ship.position.x.toFixed(2)),
|
|
8421
8972
|
y: Number(ship.position.y.toFixed(2)),
|
|
8422
8973
|
z: Number(ship.position.z.toFixed(2)),
|
|
8423
8974
|
vx: Number(ship.velocity.x.toFixed(2)),
|
|
8424
8975
|
vz: Number(ship.velocity.z.toFixed(2)),
|
|
8425
|
-
massKg: Math.round(getShipMass(ship, shipModel)),
|
|
8976
|
+
massKg: Math.round(getShipMass(ship, resolveShipModel(state, ship, shipModel))),
|
|
8426
8977
|
lanterns: Array.isArray(ship.lanterns) ? ship.lanterns.length : 0
|
|
8427
8978
|
})),
|
|
8428
|
-
shipPhysics:
|
|
8979
|
+
shipPhysics: Object.fromEntries(
|
|
8980
|
+
state.ships.map((ship) => [ship.id, resolveShipModel(state, ship, shipModel)?.physics ?? null])
|
|
8981
|
+
),
|
|
8429
8982
|
sprays: state.sprays.length,
|
|
8430
8983
|
waveImpulses: state.waveImpulses.length,
|
|
8431
8984
|
pressure: state.lastDecision?.pressureLevel ?? "stable",
|
|
@@ -8448,22 +9001,36 @@ function syncTextState(state, shipModel) {
|
|
|
8448
9001
|
}
|
|
8449
9002
|
};
|
|
8450
9003
|
}
|
|
8451
|
-
async function mountGpuShowcase(options = {}) {
|
|
9004
|
+
async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
8452
9005
|
injectStyles();
|
|
8453
9006
|
const root = options.root ?? document.body;
|
|
8454
9007
|
root.classList?.add?.(ROOT_CLASS);
|
|
9008
|
+
const captureSettings = resolveCaptureSettings(options);
|
|
9009
|
+
if (captureSettings.captureMode) {
|
|
9010
|
+
root.classList?.add?.(CAPTURE_CLASS);
|
|
9011
|
+
}
|
|
8455
9012
|
const previousMarkup = root.innerHTML;
|
|
8456
9013
|
const previousRenderGameToText = window.render_game_to_text;
|
|
8457
9014
|
const previousAdvanceTime = window.advanceTime;
|
|
8458
9015
|
const focus = options.focus ?? new URLSearchParams(window.location.search).get("focus") ?? "integrated";
|
|
9016
|
+
const translate = createGpuSharedTranslator(options.translate);
|
|
8459
9017
|
const dom = buildDemoDom(root, {
|
|
8460
9018
|
packageName: options.packageName ?? "@plasius/gpu-demo-viewer",
|
|
8461
|
-
title: options.title ??
|
|
8462
|
-
subtitle: options.subtitle ??
|
|
9019
|
+
title: options.title ?? translate(gpuSharedTranslationKeys.showcaseTitle),
|
|
9020
|
+
subtitle: options.subtitle ?? translate(gpuSharedTranslationKeys.showcaseSubtitle),
|
|
9021
|
+
translate
|
|
8463
9022
|
});
|
|
8464
9023
|
dom.focusMode.value = focus;
|
|
8465
|
-
const state = createSceneState({
|
|
8466
|
-
|
|
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;
|
|
8467
9034
|
state.shipModel = shipModel;
|
|
8468
9035
|
state.packageState = typeof options.createState === "function" ? options.createState() : void 0;
|
|
8469
9036
|
updatePhysicsSnapshot(state, shipModel);
|
|
@@ -8474,6 +9041,8 @@ async function mountGpuShowcase(options = {}) {
|
|
|
8474
9041
|
if (!ctx) {
|
|
8475
9042
|
throw new Error("2D canvas context is required for the shared showcase.");
|
|
8476
9043
|
}
|
|
9044
|
+
ctx.imageSmoothingEnabled = true;
|
|
9045
|
+
ctx.imageSmoothingQuality = "high";
|
|
8477
9046
|
let animationFrameId = 0;
|
|
8478
9047
|
let destroyed = false;
|
|
8479
9048
|
const renderFrame = (nowMs) => {
|
|
@@ -8494,13 +9063,14 @@ async function mountGpuShowcase(options = {}) {
|
|
|
8494
9063
|
state.lastDecision = recordTelemetry(state, syntheticFrame);
|
|
8495
9064
|
}
|
|
8496
9065
|
state.demoDescription = resolveSceneDescription(state, options, shipModel).description;
|
|
9066
|
+
resizeCanvasToDisplaySize(dom.canvas, state);
|
|
8497
9067
|
renderScene(ctx, dom.canvas, state, shipModel, dom);
|
|
8498
9068
|
syncTextState(state, shipModel);
|
|
8499
9069
|
animationFrameId = requestAnimationFrame(renderFrame);
|
|
8500
9070
|
};
|
|
8501
9071
|
const handlePauseClick = () => {
|
|
8502
9072
|
state.paused = !state.paused;
|
|
8503
|
-
dom.pauseButton.textContent = state.paused ?
|
|
9073
|
+
dom.pauseButton.textContent = state.paused ? state.translate(gpuSharedTranslationKeys.resume) : state.translate(gpuSharedTranslationKeys.pause);
|
|
8504
9074
|
};
|
|
8505
9075
|
const handleStressChange = () => {
|
|
8506
9076
|
state.stress = dom.stressToggle.checked;
|
|
@@ -8535,6 +9105,7 @@ async function mountGpuShowcase(options = {}) {
|
|
|
8535
9105
|
state.packageState = void 0;
|
|
8536
9106
|
}
|
|
8537
9107
|
root.classList?.remove?.(ROOT_CLASS);
|
|
9108
|
+
root.classList?.remove?.(CAPTURE_CLASS);
|
|
8538
9109
|
root.innerHTML = previousMarkup;
|
|
8539
9110
|
if (typeof previousRenderGameToText === "function") {
|
|
8540
9111
|
window.render_game_to_text = previousRenderGameToText;
|
|
@@ -8555,6 +9126,12 @@ async function mountGpuShowcase(options = {}) {
|
|
|
8555
9126
|
};
|
|
8556
9127
|
}
|
|
8557
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
|
+
);
|
|
8558
9135
|
state.physics.snapshot = createPhysicsWorldSnapshot({
|
|
8559
9136
|
frameId: `showcase-${state.frame}`,
|
|
8560
9137
|
tick: state.frame,
|
|
@@ -8570,16 +9147,18 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
8570
9147
|
collisionCount: state.collisionCount,
|
|
8571
9148
|
contactCount: state.contactCount,
|
|
8572
9149
|
snapshotStageId: state.physics.plan.snapshotStageId,
|
|
8573
|
-
rigidBodyShape: shipModel.physics.shape ?? "box"
|
|
9150
|
+
rigidBodyShape: shipModel.physics.shape ?? "box",
|
|
9151
|
+
rigidBodyShapes
|
|
8574
9152
|
}
|
|
8575
9153
|
});
|
|
8576
9154
|
}
|
|
8577
9155
|
export {
|
|
8578
9156
|
advanceShowcaseClothSimulationState as __testOnlyAdvanceShowcaseClothSimulationState,
|
|
9157
|
+
buildWaterBands as __testOnlyBuildWaterBands,
|
|
8579
9158
|
buildWaterMotionEffects as __testOnlyBuildWaterMotionEffects,
|
|
8580
9159
|
collectSceneLightSources as __testOnlyCollectSceneLightSources,
|
|
8581
9160
|
createShowcaseClothSimulationState as __testOnlyCreateShowcaseClothSimulationState,
|
|
8582
9161
|
mountGpuShowcase,
|
|
8583
9162
|
showcaseFocusModes
|
|
8584
9163
|
};
|
|
8585
|
-
//# sourceMappingURL=showcase-runtime-
|
|
9164
|
+
//# sourceMappingURL=showcase-runtime-PN7N3FZY.js.map
|