@plasius/gpu-shared 0.1.20 → 1.0.1
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 +44 -1
- package/README.md +30 -13
- package/assets/shoreline.gltf +532 -0
- package/dist/{chunk-EZHA3MH7.js → chunk-DBTWE2EF.js} +6 -2
- package/dist/chunk-DBTWE2EF.js.map +1 -0
- package/dist/{chunk-UKCJ2AWJ.js → chunk-KGKLNL4X.js} +3 -2
- package/dist/chunk-KGKLNL4X.js.map +1 -0
- package/dist/{chunk-CH3ZS5TQ.js → chunk-Z6SOXBHL.js} +2 -2
- package/dist/chunk-Z6SOXBHL.js.map +1 -0
- package/dist/{gltf-loader-FMRC3OEV.js → gltf-loader-44ILCO7V.js} +2 -2
- package/dist/index.cjs +503 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/{product-studio-runtime-3KANG3JE.js → product-studio-runtime-H7S5WN64.js} +5 -3
- package/dist/{showcase-runtime-OH3H6ZW2.js → showcase-runtime-B544T6AM.js} +496 -189
- package/dist/showcase-runtime-B544T6AM.js.map +1 -0
- package/package.json +2 -1
- package/src/asset-url.js +1 -0
- package/src/index.d.ts +10 -1
- package/src/index.js +1 -0
- package/src/product-studio-runtime.js +4 -0
- package/src/showcase-runtime.js +521 -189
- package/src/translations/en-GB.js +1 -2
- package/dist/chunk-CH3ZS5TQ.js.map +0 -1
- package/dist/chunk-EZHA3MH7.js.map +0 -1
- package/dist/chunk-UKCJ2AWJ.js.map +0 -1
- package/dist/showcase-runtime-OH3H6ZW2.js.map +0 -1
- /package/dist/{gltf-loader-FMRC3OEV.js.map → gltf-loader-44ILCO7V.js.map} +0 -0
- /package/dist/{product-studio-runtime-3KANG3JE.js.map → product-studio-runtime-H7S5WN64.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -119,7 +119,8 @@ var init_asset_url = __esm({
|
|
|
119
119
|
brigantine: "brigantine.gltf",
|
|
120
120
|
cutter: "cutter.gltf",
|
|
121
121
|
lighthouse: "lighthouse.gltf",
|
|
122
|
-
"harbor-dock": "harbor-dock.gltf"
|
|
122
|
+
"harbor-dock": "harbor-dock.gltf",
|
|
123
|
+
shoreline: "shoreline.gltf"
|
|
123
124
|
});
|
|
124
125
|
}
|
|
125
126
|
});
|
|
@@ -136,7 +137,7 @@ var init_en_GB = __esm({
|
|
|
136
137
|
"gpuShared.showcase.details.booting": "Preparing a moonlit harbor scene, GLTF hull data, cloth and fluid continuity plans, and adaptive quality metadata.",
|
|
137
138
|
"gpuShared.showcase.details.physics": "Stable world snapshots are emitted from {snapshotStageId} after the authoritative solver; the heavier hull now carries momentum through mass-aware collision impulses while cloth and fluid remain downstream.",
|
|
138
139
|
"gpuShared.showcase.details.realistic": "Moonlit GLTF ships now mix a brigantine and a cutter against modeled harbor assets; cloth, fluid, and ship-local lighting stay continuous while the governor pressure is {pressureLevel}.",
|
|
139
|
-
"gpuShared.showcase.details.legacy": "
|
|
140
|
+
"gpuShared.showcase.details.legacy": "Showcase fallback keeps the brigantine-only harbor path active while cloth, fluid, and ship-local lighting stay continuous under {pressureLevel} governor pressure.",
|
|
140
141
|
"gpuShared.showcase.action.pause": "Pause",
|
|
141
142
|
"gpuShared.showcase.action.resume": "Resume",
|
|
142
143
|
"gpuShared.showcase.control.stressMode": "Stress mode",
|
|
@@ -2407,6 +2408,7 @@ var init_dist = __esm({
|
|
|
2407
2408
|
// src/product-studio-runtime.js
|
|
2408
2409
|
var product_studio_runtime_exports = {};
|
|
2409
2410
|
__export(product_studio_runtime_exports, {
|
|
2411
|
+
buildProductStudioSceneObjects: () => buildProductStudioSceneObjects,
|
|
2410
2412
|
createProductStudioMeshes: () => createProductStudioMeshes,
|
|
2411
2413
|
mountGpuProductStudio: () => mountGpuProductStudio
|
|
2412
2414
|
});
|
|
@@ -2665,6 +2667,9 @@ function createProductStudioMeshes(model, options = {}) {
|
|
|
2665
2667
|
const modelMeshes = primitives.map((primitive, index) => createProductStudioMeshFromPrimitive(primitive, index, transform)).filter(Boolean);
|
|
2666
2668
|
return Object.freeze([...createProductStudioEnvironmentMeshes(), ...modelMeshes]);
|
|
2667
2669
|
}
|
|
2670
|
+
function buildProductStudioSceneObjects(model, options = {}) {
|
|
2671
|
+
return createProductStudioMeshes(model, options);
|
|
2672
|
+
}
|
|
2668
2673
|
function ensureStyles(documentRef) {
|
|
2669
2674
|
if (documentRef.getElementById?.(STYLE_ID)) {
|
|
2670
2675
|
return;
|
|
@@ -2845,6 +2850,8 @@ var init_product_studio_runtime = __esm({
|
|
|
2845
2850
|
var showcase_runtime_exports = {};
|
|
2846
2851
|
__export(showcase_runtime_exports, {
|
|
2847
2852
|
__testOnlyAdvanceShowcaseClothSimulationState: () => advanceShowcaseClothSimulationState,
|
|
2853
|
+
__testOnlyBuildClothSurface: () => buildClothSurface,
|
|
2854
|
+
__testOnlyBuildShorelineFoamSegments: () => buildShorelineFoamSegments,
|
|
2848
2855
|
__testOnlyBuildWaterBands: () => buildWaterBands,
|
|
2849
2856
|
__testOnlyBuildWaterMotionEffects: () => buildWaterMotionEffects,
|
|
2850
2857
|
__testOnlyCollectSceneLightSources: () => collectSceneLightSources,
|
|
@@ -3456,31 +3463,31 @@ function injectStyles() {
|
|
|
3456
3463
|
box-sizing: border-box;
|
|
3457
3464
|
}
|
|
3458
3465
|
.plasius-demo {
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
gap: 20px;
|
|
3464
|
-
}
|
|
3465
|
-
.plasius-demo__hero,
|
|
3466
|
-
.plasius-demo__layout {
|
|
3467
|
-
display: grid;
|
|
3468
|
-
gap: 20px;
|
|
3469
|
-
}
|
|
3470
|
-
.plasius-demo__hero {
|
|
3471
|
-
grid-template-columns: minmax(0, 1.5fr) minmax(320px, 0.85fr);
|
|
3472
|
-
align-items: start;
|
|
3466
|
+
position: relative;
|
|
3467
|
+
width: 100%;
|
|
3468
|
+
min-height: 100dvh;
|
|
3469
|
+
overflow: hidden;
|
|
3473
3470
|
}
|
|
3474
3471
|
.plasius-panel {
|
|
3475
3472
|
border: 1px solid var(--plasius-border);
|
|
3476
|
-
border-radius:
|
|
3473
|
+
border-radius: 8px;
|
|
3477
3474
|
background: var(--plasius-panel);
|
|
3478
3475
|
box-shadow: var(--plasius-shadow);
|
|
3479
3476
|
backdrop-filter: blur(12px);
|
|
3480
3477
|
}
|
|
3481
3478
|
.plasius-demo__hero-card,
|
|
3482
3479
|
.plasius-demo__status {
|
|
3483
|
-
|
|
3480
|
+
position: absolute;
|
|
3481
|
+
z-index: 3;
|
|
3482
|
+
padding: 10px 12px;
|
|
3483
|
+
}
|
|
3484
|
+
.plasius-demo__hero-card {
|
|
3485
|
+
display: none;
|
|
3486
|
+
}
|
|
3487
|
+
.plasius-demo__status {
|
|
3488
|
+
left: 16px;
|
|
3489
|
+
bottom: 84px;
|
|
3490
|
+
max-width: min(360px, calc(100vw - 32px));
|
|
3484
3491
|
}
|
|
3485
3492
|
.plasius-demo__eyebrow {
|
|
3486
3493
|
margin: 0 0 8px;
|
|
@@ -3503,31 +3510,36 @@ function injectStyles() {
|
|
|
3503
3510
|
.plasius-demo__status-badge {
|
|
3504
3511
|
width: fit-content;
|
|
3505
3512
|
margin: 0;
|
|
3506
|
-
padding:
|
|
3507
|
-
border-radius:
|
|
3513
|
+
padding: 6px 9px;
|
|
3514
|
+
border-radius: 6px;
|
|
3508
3515
|
background: rgba(243, 177, 106, 0.14);
|
|
3509
3516
|
color: var(--plasius-accent);
|
|
3510
3517
|
font-weight: 700;
|
|
3518
|
+
font-size: 12px;
|
|
3511
3519
|
}
|
|
3512
3520
|
.plasius-demo__status-text {
|
|
3513
3521
|
margin: 10px 0 0;
|
|
3514
3522
|
color: var(--plasius-muted);
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
.plasius-demo__layout {
|
|
3518
|
-
grid-template-columns: minmax(0, 1.45fr) minmax(320px, 0.68fr);
|
|
3519
|
-
align-items: start;
|
|
3523
|
+
font-size: 12px;
|
|
3524
|
+
line-height: 1.45;
|
|
3520
3525
|
}
|
|
3521
3526
|
.plasius-demo__canvas-panel {
|
|
3522
|
-
|
|
3523
|
-
|
|
3527
|
+
position: absolute;
|
|
3528
|
+
inset: 0;
|
|
3529
|
+
padding: 0;
|
|
3530
|
+
border: 0;
|
|
3531
|
+
border-radius: 0;
|
|
3532
|
+
background: transparent;
|
|
3533
|
+
box-shadow: none;
|
|
3534
|
+
backdrop-filter: none;
|
|
3524
3535
|
}
|
|
3525
3536
|
.plasius-demo__canvas {
|
|
3526
3537
|
width: 100%;
|
|
3527
|
-
|
|
3538
|
+
height: 100%;
|
|
3539
|
+
min-height: 100dvh;
|
|
3528
3540
|
display: block;
|
|
3529
|
-
border
|
|
3530
|
-
border:
|
|
3541
|
+
border: 0;
|
|
3542
|
+
border-radius: 0;
|
|
3531
3543
|
background: linear-gradient(180deg, #071220 0%, #132440 42%, #10344b 42%, #05111d 100%);
|
|
3532
3544
|
}
|
|
3533
3545
|
.${CAPTURE_CLASS} .plasius-demo {
|
|
@@ -3540,6 +3552,8 @@ function injectStyles() {
|
|
|
3540
3552
|
.${CAPTURE_CLASS} .plasius-demo__toolbar,
|
|
3541
3553
|
.${CAPTURE_CLASS} .plasius-demo__legend,
|
|
3542
3554
|
.${CAPTURE_CLASS} .plasius-demo__sidebar,
|
|
3555
|
+
.${CAPTURE_CLASS} .plasius-demo__diagnostics,
|
|
3556
|
+
.${CAPTURE_CLASS} .plasius-demo__status,
|
|
3543
3557
|
.${CAPTURE_CLASS} .plasius-demo__footer {
|
|
3544
3558
|
display: none;
|
|
3545
3559
|
}
|
|
@@ -3566,12 +3580,14 @@ function injectStyles() {
|
|
|
3566
3580
|
}
|
|
3567
3581
|
.plasius-demo__toolbar {
|
|
3568
3582
|
position: absolute;
|
|
3569
|
-
top:
|
|
3570
|
-
left:
|
|
3583
|
+
top: 84px;
|
|
3584
|
+
left: 16px;
|
|
3585
|
+
z-index: 4;
|
|
3571
3586
|
display: flex;
|
|
3572
|
-
gap:
|
|
3587
|
+
gap: 8px;
|
|
3573
3588
|
flex-wrap: wrap;
|
|
3574
3589
|
align-items: center;
|
|
3590
|
+
max-width: min(560px, calc(100vw - 32px));
|
|
3575
3591
|
}
|
|
3576
3592
|
.plasius-demo button,
|
|
3577
3593
|
.plasius-demo label,
|
|
@@ -3583,22 +3599,63 @@ function injectStyles() {
|
|
|
3583
3599
|
.plasius-demo .plasius-toggle,
|
|
3584
3600
|
.plasius-demo select {
|
|
3585
3601
|
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
3586
|
-
border-radius:
|
|
3602
|
+
border-radius: 6px;
|
|
3587
3603
|
background: rgba(9, 20, 34, 0.84);
|
|
3588
3604
|
color: var(--plasius-ink);
|
|
3589
|
-
padding: 10px
|
|
3605
|
+
padding: 8px 10px;
|
|
3590
3606
|
}
|
|
3591
3607
|
.plasius-toggle {
|
|
3592
3608
|
display: inline-flex;
|
|
3593
3609
|
align-items: center;
|
|
3594
3610
|
gap: 8px;
|
|
3595
3611
|
}
|
|
3612
|
+
.plasius-demo__diagnostics {
|
|
3613
|
+
position: absolute;
|
|
3614
|
+
right: 16px;
|
|
3615
|
+
bottom: 84px;
|
|
3616
|
+
z-index: 4;
|
|
3617
|
+
max-width: min(420px, calc(100vw - 32px));
|
|
3618
|
+
color: var(--plasius-ink);
|
|
3619
|
+
font-family: "JetBrains Mono", monospace;
|
|
3620
|
+
font-size: 12px;
|
|
3621
|
+
}
|
|
3622
|
+
.plasius-demo__diagnostics summary {
|
|
3623
|
+
width: fit-content;
|
|
3624
|
+
margin-left: auto;
|
|
3625
|
+
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
3626
|
+
border-radius: 6px;
|
|
3627
|
+
padding: 8px 10px;
|
|
3628
|
+
background: rgba(9, 20, 34, 0.84);
|
|
3629
|
+
cursor: pointer;
|
|
3630
|
+
list-style: none;
|
|
3631
|
+
}
|
|
3632
|
+
.plasius-demo__diagnostics summary::-webkit-details-marker {
|
|
3633
|
+
display: none;
|
|
3634
|
+
}
|
|
3635
|
+
.plasius-demo__diagnostics[open] {
|
|
3636
|
+
width: min(420px, calc(100vw - 32px));
|
|
3637
|
+
}
|
|
3638
|
+
.plasius-demo__diagnostics[open] summary {
|
|
3639
|
+
margin-bottom: 8px;
|
|
3640
|
+
background: rgba(243, 177, 106, 0.14);
|
|
3641
|
+
color: var(--plasius-accent);
|
|
3642
|
+
}
|
|
3596
3643
|
.plasius-demo__sidebar {
|
|
3597
3644
|
display: grid;
|
|
3598
|
-
gap:
|
|
3645
|
+
gap: 8px;
|
|
3646
|
+
max-height: min(58vh, 520px);
|
|
3647
|
+
overflow: auto;
|
|
3599
3648
|
}
|
|
3600
3649
|
.plasius-demo__card {
|
|
3601
|
-
padding:
|
|
3650
|
+
padding: 10px;
|
|
3651
|
+
}
|
|
3652
|
+
.plasius-demo__card h2 {
|
|
3653
|
+
margin: 0;
|
|
3654
|
+
color: rgba(226, 236, 255, 0.72);
|
|
3655
|
+
font-family: "JetBrains Mono", monospace;
|
|
3656
|
+
font-size: 11px;
|
|
3657
|
+
letter-spacing: 0.08em;
|
|
3658
|
+
text-transform: uppercase;
|
|
3602
3659
|
}
|
|
3603
3660
|
.plasius-demo__metrics,
|
|
3604
3661
|
.plasius-demo__metrics li {
|
|
@@ -3607,27 +3664,19 @@ function injectStyles() {
|
|
|
3607
3664
|
list-style: none;
|
|
3608
3665
|
}
|
|
3609
3666
|
.plasius-demo__metrics {
|
|
3610
|
-
margin-top:
|
|
3667
|
+
margin-top: 8px;
|
|
3611
3668
|
display: grid;
|
|
3612
|
-
gap:
|
|
3669
|
+
gap: 5px;
|
|
3613
3670
|
color: var(--plasius-muted);
|
|
3614
|
-
|
|
3671
|
+
font-size: 12px;
|
|
3672
|
+
line-height: 1.35;
|
|
3615
3673
|
}
|
|
3616
3674
|
.plasius-demo__metrics li {
|
|
3617
3675
|
border-top: 1px solid rgba(21, 32, 40, 0.08);
|
|
3618
|
-
padding-top:
|
|
3676
|
+
padding-top: 5px;
|
|
3619
3677
|
}
|
|
3620
3678
|
.plasius-demo__legend {
|
|
3621
|
-
|
|
3622
|
-
right: 24px;
|
|
3623
|
-
bottom: 24px;
|
|
3624
|
-
padding: 10px 14px;
|
|
3625
|
-
border-radius: 16px;
|
|
3626
|
-
background: rgba(9, 20, 34, 0.82);
|
|
3627
|
-
border: 1px solid rgba(159, 185, 223, 0.16);
|
|
3628
|
-
color: var(--plasius-muted);
|
|
3629
|
-
font-size: 12px;
|
|
3630
|
-
line-height: 1.45;
|
|
3679
|
+
display: none;
|
|
3631
3680
|
}
|
|
3632
3681
|
.plasius-demo__legend strong {
|
|
3633
3682
|
display: block;
|
|
@@ -3635,15 +3684,62 @@ function injectStyles() {
|
|
|
3635
3684
|
margin-bottom: 4px;
|
|
3636
3685
|
}
|
|
3637
3686
|
.plasius-demo__footer {
|
|
3638
|
-
|
|
3639
|
-
color: rgba(226, 236, 255, 0.68);
|
|
3640
|
-
font-size: 13px;
|
|
3641
|
-
line-height: 1.6;
|
|
3687
|
+
display: none;
|
|
3642
3688
|
}
|
|
3643
3689
|
@media (max-width: 1200px) {
|
|
3644
|
-
.plasius-
|
|
3645
|
-
|
|
3646
|
-
|
|
3690
|
+
.plasius-demo__toolbar {
|
|
3691
|
+
top: 92px;
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
@media (max-width: 640px) {
|
|
3695
|
+
.plasius-demo__status {
|
|
3696
|
+
left: 10px;
|
|
3697
|
+
bottom: 10px;
|
|
3698
|
+
max-width: calc(100vw - 126px);
|
|
3699
|
+
padding: 6px 8px;
|
|
3700
|
+
}
|
|
3701
|
+
.plasius-demo__status-text {
|
|
3702
|
+
display: none;
|
|
3703
|
+
}
|
|
3704
|
+
.plasius-demo__status-badge {
|
|
3705
|
+
max-width: calc(100vw - 142px);
|
|
3706
|
+
overflow: hidden;
|
|
3707
|
+
text-overflow: ellipsis;
|
|
3708
|
+
white-space: nowrap;
|
|
3709
|
+
}
|
|
3710
|
+
.plasius-demo__toolbar {
|
|
3711
|
+
top: 10px;
|
|
3712
|
+
left: 10px;
|
|
3713
|
+
right: 10px;
|
|
3714
|
+
max-width: calc(100vw - 20px);
|
|
3715
|
+
flex-wrap: nowrap;
|
|
3716
|
+
overflow-x: auto;
|
|
3717
|
+
padding-bottom: 4px;
|
|
3718
|
+
scrollbar-width: none;
|
|
3719
|
+
}
|
|
3720
|
+
.plasius-demo__toolbar::-webkit-scrollbar {
|
|
3721
|
+
display: none;
|
|
3722
|
+
}
|
|
3723
|
+
.plasius-demo button,
|
|
3724
|
+
.plasius-demo .plasius-toggle,
|
|
3725
|
+
.plasius-demo select {
|
|
3726
|
+
padding: 7px 8px;
|
|
3727
|
+
font-size: 12px;
|
|
3728
|
+
white-space: nowrap;
|
|
3729
|
+
}
|
|
3730
|
+
.plasius-demo__diagnostics {
|
|
3731
|
+
right: 10px;
|
|
3732
|
+
bottom: 10px;
|
|
3733
|
+
}
|
|
3734
|
+
.plasius-demo__diagnostics[open] {
|
|
3735
|
+
bottom: 56px;
|
|
3736
|
+
left: 10px;
|
|
3737
|
+
right: 10px;
|
|
3738
|
+
width: auto;
|
|
3739
|
+
max-width: none;
|
|
3740
|
+
}
|
|
3741
|
+
.plasius-demo__diagnostics[open] .plasius-demo__sidebar {
|
|
3742
|
+
max-height: min(42vh, 340px);
|
|
3647
3743
|
}
|
|
3648
3744
|
}
|
|
3649
3745
|
`;
|
|
@@ -3984,40 +4080,74 @@ function buildTrianglesFromMesh(mesh, transform, colorOverride, camera, viewport
|
|
|
3984
4080
|
}
|
|
3985
4081
|
}
|
|
3986
4082
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
4083
|
+
function createShowcaseAssetCatalog({
|
|
4084
|
+
mode,
|
|
4085
|
+
ships,
|
|
4086
|
+
environment,
|
|
4087
|
+
primaryShipKey = "brigantine",
|
|
4088
|
+
fallbackReason = null
|
|
4089
|
+
}) {
|
|
4090
|
+
return Object.freeze({
|
|
4091
|
+
mode,
|
|
4092
|
+
primaryShipKey,
|
|
4093
|
+
ships: Object.freeze(ships),
|
|
4094
|
+
environment: Object.freeze(environment),
|
|
4095
|
+
fallbackReason
|
|
4096
|
+
});
|
|
4097
|
+
}
|
|
4098
|
+
function normalizeAssetCatalogFailureReason(error) {
|
|
4099
|
+
if (typeof error?.message === "string" && error.message.trim().length > 0) {
|
|
4100
|
+
return error.message;
|
|
4101
|
+
}
|
|
4102
|
+
return "showcase asset loading failed";
|
|
4103
|
+
}
|
|
4104
|
+
async function loadShowcaseAssetCatalog({ includeSecondaryShip = true } = {}) {
|
|
4105
|
+
const [brigantine, lighthouse, harborDock, shoreline] = await Promise.all([
|
|
3989
4106
|
loadGltfModel(resolveShowcaseAssetUrl("brigantine")),
|
|
3990
|
-
loadGltfModel(resolveShowcaseAssetUrl("cutter")),
|
|
3991
4107
|
loadGltfModel(resolveShowcaseAssetUrl("lighthouse")),
|
|
3992
|
-
loadGltfModel(resolveShowcaseAssetUrl("harbor-dock"))
|
|
4108
|
+
loadGltfModel(resolveShowcaseAssetUrl("harbor-dock")),
|
|
4109
|
+
loadGltfModel(resolveShowcaseAssetUrl("shoreline"))
|
|
3993
4110
|
]);
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4111
|
+
const ships = {
|
|
4112
|
+
brigantine
|
|
4113
|
+
};
|
|
4114
|
+
if (includeSecondaryShip) {
|
|
4115
|
+
ships.cutter = await loadGltfModel(resolveShowcaseAssetUrl("cutter"));
|
|
4116
|
+
}
|
|
4117
|
+
return createShowcaseAssetCatalog({
|
|
4118
|
+
mode: includeSecondaryShip ? "modeled-rich" : "modeled-baseline",
|
|
4119
|
+
ships,
|
|
4120
|
+
environment: {
|
|
4001
4121
|
lighthouse,
|
|
4002
|
-
"harbor-dock": harborDock
|
|
4003
|
-
|
|
4122
|
+
"harbor-dock": harborDock,
|
|
4123
|
+
shoreline
|
|
4124
|
+
}
|
|
4004
4125
|
});
|
|
4005
4126
|
}
|
|
4006
|
-
function createLegacyShowcaseAssetCatalog() {
|
|
4007
|
-
const brigantine = loadGltfModel(resolveShowcaseAssetUrl("brigantine"));
|
|
4008
|
-
return
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4127
|
+
async function createLegacyShowcaseAssetCatalog(error = null) {
|
|
4128
|
+
const brigantine = await loadGltfModel(resolveShowcaseAssetUrl("brigantine"));
|
|
4129
|
+
return createShowcaseAssetCatalog({
|
|
4130
|
+
mode: "legacy-fallback",
|
|
4131
|
+
ships: {
|
|
4132
|
+
brigantine
|
|
4133
|
+
},
|
|
4134
|
+
environment: {},
|
|
4135
|
+
fallbackReason: normalizeAssetCatalogFailureReason(error)
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
async function loadShowcaseAssetCatalogWithFallback({ includeSecondaryShip = true } = {}) {
|
|
4139
|
+
try {
|
|
4140
|
+
return await loadShowcaseAssetCatalog({ includeSecondaryShip });
|
|
4141
|
+
} catch (error) {
|
|
4142
|
+
return createLegacyShowcaseAssetCatalog(error);
|
|
4143
|
+
}
|
|
4017
4144
|
}
|
|
4018
4145
|
function resolveShipModel(state, ship, fallbackModel = null) {
|
|
4019
4146
|
return state.assetCatalog?.ships?.[ship.modelKey ?? state.assetCatalog?.primaryShipKey ?? "brigantine"] ?? fallbackModel ?? state.shipModel;
|
|
4020
4147
|
}
|
|
4148
|
+
function hasModeledHarborEnvironment(state) {
|
|
4149
|
+
return Object.keys(state.assetCatalog?.environment ?? {}).length > 0;
|
|
4150
|
+
}
|
|
4021
4151
|
function createPerformanceGovernor(performanceFeatures) {
|
|
4022
4152
|
const createQualityLadderAdapter = assertRequiredFunction(
|
|
4023
4153
|
performanceFeatures,
|
|
@@ -4125,24 +4255,27 @@ function buildDemoDom(root, options) {
|
|
|
4125
4255
|
${t(gpuSharedTranslationKeys.legendCollisions)}
|
|
4126
4256
|
</div>
|
|
4127
4257
|
</section>
|
|
4128
|
-
<
|
|
4129
|
-
<
|
|
4130
|
-
|
|
4131
|
-
<
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
<
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
<
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
<
|
|
4144
|
-
|
|
4145
|
-
|
|
4258
|
+
<details class="plasius-demo__diagnostics">
|
|
4259
|
+
<summary>${t(gpuSharedTranslationKeys.debugTelemetry)}</summary>
|
|
4260
|
+
<aside class="plasius-demo__sidebar">
|
|
4261
|
+
<section class="plasius-panel plasius-demo__card">
|
|
4262
|
+
<h2>${t(gpuSharedTranslationKeys.sceneState)}</h2>
|
|
4263
|
+
<ul id="sceneMetrics" class="plasius-demo__metrics"></ul>
|
|
4264
|
+
</section>
|
|
4265
|
+
<section class="plasius-panel plasius-demo__card">
|
|
4266
|
+
<h2>${t(gpuSharedTranslationKeys.qualityBudgets)}</h2>
|
|
4267
|
+
<ul id="qualityMetrics" class="plasius-demo__metrics"></ul>
|
|
4268
|
+
</section>
|
|
4269
|
+
<section class="plasius-panel plasius-demo__card">
|
|
4270
|
+
<h2>${t(gpuSharedTranslationKeys.debugTelemetry)}</h2>
|
|
4271
|
+
<ul id="debugMetrics" class="plasius-demo__metrics"></ul>
|
|
4272
|
+
</section>
|
|
4273
|
+
<section class="plasius-panel plasius-demo__card">
|
|
4274
|
+
<h2>${t(gpuSharedTranslationKeys.notes)}</h2>
|
|
4275
|
+
<ul id="sceneNotes" class="plasius-demo__metrics"></ul>
|
|
4276
|
+
</section>
|
|
4277
|
+
</aside>
|
|
4278
|
+
</details>
|
|
4146
4279
|
</section>
|
|
4147
4280
|
<p class="plasius-demo__footer">
|
|
4148
4281
|
This visual example is shared across the GPU packages to keep manual validation fast and consistent.
|
|
@@ -4506,11 +4639,11 @@ function advanceShowcaseClothSimulationState(clothState, options = {}) {
|
|
|
4506
4639
|
1 + Math.sin(gustPhase * 0.74) * 0.18
|
|
4507
4640
|
)
|
|
4508
4641
|
);
|
|
4509
|
-
const windStrength = (
|
|
4642
|
+
const windStrength = (0.94 + broadMotion * 0.82 + wrinkleLayers * 0.08) * flagMotion * (0.36 + u * 0.92);
|
|
4510
4643
|
const wrinkleForce = vec3(
|
|
4511
|
-
Math.sin(wrinklePhase) * 0.
|
|
4512
|
-
Math.cos(wrinklePhase * 0.7) * 0.
|
|
4513
|
-
Math.cos(wrinklePhase) * 0.
|
|
4644
|
+
Math.sin(wrinklePhase) * 0.12 * wrinkleMotion * flagMotion,
|
|
4645
|
+
Math.cos(wrinklePhase * 0.7) * 0.045 * wrinkleMotion,
|
|
4646
|
+
Math.cos(wrinklePhase) * 0.08 * broadMotion * flagMotion
|
|
4514
4647
|
);
|
|
4515
4648
|
const acceleration = addVec3(
|
|
4516
4649
|
vec3(0, -0.48 - u * 0.08, 0),
|
|
@@ -4567,25 +4700,25 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
4567
4700
|
ambientMist: "rgba(41, 63, 97, 0.16)",
|
|
4568
4701
|
reflectionStrength: lightingSnapshot.currentLevel.config.reflectionStrength,
|
|
4569
4702
|
shadowAccent: lightingSnapshot.currentLevel.config.shadowStrength,
|
|
4570
|
-
waveAmplitude: 0.
|
|
4703
|
+
waveAmplitude: 0.82,
|
|
4571
4704
|
waveDirection: { x: 0.88, z: 0.28 },
|
|
4572
|
-
wavePhaseSpeed: 0.
|
|
4573
|
-
wakeStrength: 0.
|
|
4574
|
-
wakeLength:
|
|
4575
|
-
collisionRippleStrength: 0.
|
|
4576
|
-
waterNear: { r: 0.
|
|
4577
|
-
waterFar: { r: 0.
|
|
4705
|
+
wavePhaseSpeed: 0.74,
|
|
4706
|
+
wakeStrength: 0.24,
|
|
4707
|
+
wakeLength: 17,
|
|
4708
|
+
collisionRippleStrength: 0.22,
|
|
4709
|
+
waterNear: { r: 0.05, g: 0.2, b: 0.3 },
|
|
4710
|
+
waterFar: { r: 0.13, g: 0.31, b: 0.45 },
|
|
4578
4711
|
harborWall: { r: 0.26, g: 0.24, b: 0.28 },
|
|
4579
4712
|
harborDeck: { r: 0.33, g: 0.22, b: 0.16 },
|
|
4580
4713
|
harborTower: { r: 0.23, g: 0.24, b: 0.29 },
|
|
4581
|
-
flagColor: { r: 0.
|
|
4582
|
-
flagMotion: 0.
|
|
4714
|
+
flagColor: { r: 0.54, g: 0.13, b: 0.11 },
|
|
4715
|
+
flagMotion: 0.58,
|
|
4583
4716
|
lanternCore: { r: 0.98, g: 0.8, b: 0.48 },
|
|
4584
4717
|
lanternGlow: { r: 1, g: 0.56, b: 0.2 },
|
|
4585
4718
|
lanternReflectionStrength: 0.42,
|
|
4586
4719
|
torchCore: { r: 0.99, g: 0.72, b: 0.36 },
|
|
4587
4720
|
torchGlow: { r: 0.98, g: 0.38, b: 0.15 },
|
|
4588
|
-
collisionFlash: "rgba(255, 212, 168, 0.
|
|
4721
|
+
collisionFlash: "rgba(255, 212, 168, 0.08)"
|
|
4589
4722
|
};
|
|
4590
4723
|
return {
|
|
4591
4724
|
skyTop: typeof customVisuals.skyTop === "string" ? customVisuals.skyTop : defaults.skyTop,
|
|
@@ -4642,6 +4775,11 @@ function buildClothSurface(model, state, meshDetail, visuals, clothFeatures) {
|
|
|
4642
4775
|
representation: clothPresentation.representation,
|
|
4643
4776
|
continuity: clothPresentation.continuity,
|
|
4644
4777
|
color: visuals.flagColor,
|
|
4778
|
+
material: Object.freeze({
|
|
4779
|
+
weaveAlpha: clothPresentation.band === "near" ? 0.22 : 0.12,
|
|
4780
|
+
foldAlpha: clothPresentation.band === "near" ? 0.3 : 0.18,
|
|
4781
|
+
edgeHighlightAlpha: clothPresentation.band === "near" ? 0.42 : 0.28
|
|
4782
|
+
}),
|
|
4645
4783
|
positions: clothState.positions.map((point) => vec3(point.x, point.y, point.z)),
|
|
4646
4784
|
indices: clothState.indices,
|
|
4647
4785
|
grid: { rows: clothState.rows, cols: clothState.cols }
|
|
@@ -4725,7 +4863,7 @@ function buildWaterMotionEffects(state) {
|
|
|
4725
4863
|
impulse.z
|
|
4726
4864
|
),
|
|
4727
4865
|
radius,
|
|
4728
|
-
opacity: clamp(impulse.life * 0.
|
|
4866
|
+
opacity: clamp(impulse.life * 0.13, 0.035, 0.15)
|
|
4729
4867
|
});
|
|
4730
4868
|
});
|
|
4731
4869
|
for (const ship of state.ships) {
|
|
@@ -4738,7 +4876,7 @@ function buildWaterMotionEffects(state) {
|
|
|
4738
4876
|
const lateral = vec3(-direction.z, 0, direction.x);
|
|
4739
4877
|
const points = [];
|
|
4740
4878
|
for (let sampleIndex = 0; sampleIndex < 6; sampleIndex += 1) {
|
|
4741
|
-
const along = 1 + sampleIndex * 1.
|
|
4879
|
+
const along = 1 + sampleIndex * 1.55;
|
|
4742
4880
|
const lateralOffset = Math.sin(state.time * 1.2 + sampleIndex * 0.8 + readVisualNumber(ship.wanderPhase, 0)) * 0.12;
|
|
4743
4881
|
const worldPoint = addVec3(
|
|
4744
4882
|
ship.position,
|
|
@@ -4751,13 +4889,14 @@ function buildWaterMotionEffects(state) {
|
|
|
4751
4889
|
sampleWave(state, worldPoint.x, worldPoint.z, state.time) * 0.24 + 0.04,
|
|
4752
4890
|
worldPoint.z
|
|
4753
4891
|
),
|
|
4754
|
-
width: 0.
|
|
4892
|
+
width: 0.3 + sampleIndex * 0.11,
|
|
4893
|
+
foam: clamp(0.28 - sampleIndex * 0.028 + speed * 0.025, 0.1, 0.34)
|
|
4755
4894
|
})
|
|
4756
4895
|
);
|
|
4757
4896
|
}
|
|
4758
4897
|
wakeTrails.push(
|
|
4759
4898
|
Object.freeze({
|
|
4760
|
-
opacity: clamp(0.
|
|
4899
|
+
opacity: clamp(0.1 + speed * 0.048, 0.12, 0.24),
|
|
4761
4900
|
points: Object.freeze(points)
|
|
4762
4901
|
})
|
|
4763
4902
|
);
|
|
@@ -4767,6 +4906,27 @@ function buildWaterMotionEffects(state) {
|
|
|
4767
4906
|
rippleRings: Object.freeze(rippleRings)
|
|
4768
4907
|
});
|
|
4769
4908
|
}
|
|
4909
|
+
function buildShorelineFoamSegments(state) {
|
|
4910
|
+
return Object.freeze(
|
|
4911
|
+
SHORELINE_FOAM_ANCHORS.map((anchor, index) => {
|
|
4912
|
+
const pulse = 0.5 + Math.sin(state.time * 0.84 + index * 1.17) * 0.5;
|
|
4913
|
+
const drift = Math.sin(state.time * 0.38 + index * 0.61) * 0.1;
|
|
4914
|
+
const direction = normalizeVec3(vec3(Math.cos(anchor.angle), 0, Math.sin(anchor.angle)));
|
|
4915
|
+
const center = vec3(
|
|
4916
|
+
anchor.x + direction.x * drift,
|
|
4917
|
+
sampleWave(state, anchor.x, anchor.z, state.time) * 0.12 - 0.02,
|
|
4918
|
+
anchor.z + direction.z * drift
|
|
4919
|
+
);
|
|
4920
|
+
return Object.freeze({
|
|
4921
|
+
center,
|
|
4922
|
+
direction,
|
|
4923
|
+
length: anchor.length * (0.78 + pulse * 0.34),
|
|
4924
|
+
width: 0.16 + pulse * 0.12,
|
|
4925
|
+
opacity: 0.07 + pulse * 0.12
|
|
4926
|
+
});
|
|
4927
|
+
})
|
|
4928
|
+
);
|
|
4929
|
+
}
|
|
4770
4930
|
function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
4771
4931
|
const resolvedFluidFeatures = normalizeFluidFeatureAdapters(fluidFeatures);
|
|
4772
4932
|
const fluidPlan = resolvedFluidFeatures.createPlan({
|
|
@@ -4789,9 +4949,9 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
4789
4949
|
const representation = fluidPlan.representations.find((entry) => entry.band === bandSpec.band) ?? fluidPlan.representations[0];
|
|
4790
4950
|
const continuity = resolvedFluidFeatures.createContinuityEnvelope({ fluidBodyId: "harbor" });
|
|
4791
4951
|
const bandContinuity = resolveFluidBandContinuity(continuity, bandSpec.band);
|
|
4792
|
-
const bandResolution = bandSpec.band === "near" ? fluidDetail.nearResolution : bandSpec.band === "mid" ? fluidDetail.midResolution : bandSpec.band === "far" ? 5 : 3;
|
|
4793
|
-
const cols = Math.max(4, bandResolution * 2);
|
|
4794
|
-
const rows = Math.max(4, bandResolution + 2);
|
|
4952
|
+
const bandResolution = bandSpec.band === "near" ? Math.ceil(fluidDetail.nearResolution * 1.28) : bandSpec.band === "mid" ? Math.ceil(fluidDetail.midResolution * 1.2) : bandSpec.band === "far" ? 5 : 3;
|
|
4953
|
+
const cols = Math.max(4, bandResolution * (bandSpec.band === "near" ? 3 : 2));
|
|
4954
|
+
const rows = Math.max(4, bandResolution + (bandSpec.band === "near" ? 5 : 2));
|
|
4795
4955
|
const positions = [];
|
|
4796
4956
|
const indices = [];
|
|
4797
4957
|
const originX = -bandSpec.width * 0.5;
|
|
@@ -4802,7 +4962,9 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
4802
4962
|
const v = row / (rows - 1);
|
|
4803
4963
|
const x = originX + bandSpec.width * u;
|
|
4804
4964
|
const z = originZ + bandSpec.depth * v;
|
|
4805
|
-
const
|
|
4965
|
+
const baseHeight = bandSpec.y + sampleWave(state, x, z, state.time) * bandContinuity.amplitudeFloor * (bandSpec.band === "near" ? 0.9 : bandSpec.band === "mid" ? 0.55 : 0.3);
|
|
4966
|
+
const detailHeight = bandSpec.band === "near" ? Math.sin(x * 1.25 + z * 0.42 - state.time * 2.4) * 0.035 : 0;
|
|
4967
|
+
const y = baseHeight + detailHeight;
|
|
4806
4968
|
positions.push(vec3(x, y, z));
|
|
4807
4969
|
}
|
|
4808
4970
|
}
|
|
@@ -4831,7 +4993,12 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
4831
4993
|
r: mix(visuals.waterFar.r, 0.76, 0.2),
|
|
4832
4994
|
g: mix(visuals.waterFar.g, 0.78, 0.2),
|
|
4833
4995
|
b: mix(visuals.waterFar.b, 0.82, 0.2)
|
|
4834
|
-
}
|
|
4996
|
+
},
|
|
4997
|
+
material: Object.freeze({
|
|
4998
|
+
highlightAlpha: bandSpec.band === "near" ? 0.2 : bandSpec.band === "mid" ? 0.13 : 0.07,
|
|
4999
|
+
foamAlpha: bandSpec.band === "near" ? 0.28 : bandSpec.band === "mid" ? 0.14 : 0.05,
|
|
5000
|
+
microRippleScale: bandSpec.band === "near" ? 1 : bandSpec.band === "mid" ? 0.58 : 0.28
|
|
5001
|
+
})
|
|
4835
5002
|
});
|
|
4836
5003
|
}
|
|
4837
5004
|
return { fluidPlan, bandMeshes };
|
|
@@ -4907,15 +5074,15 @@ function createSceneState(options, featureAdapters) {
|
|
|
4907
5074
|
{
|
|
4908
5075
|
id: "northwind",
|
|
4909
5076
|
modelKey: "brigantine",
|
|
4910
|
-
position: vec3(-
|
|
4911
|
-
velocity: vec3(
|
|
4912
|
-
rotationY:
|
|
4913
|
-
angularVelocity: 0.
|
|
5077
|
+
position: vec3(-7.8, 0, 11.2),
|
|
5078
|
+
velocity: vec3(1.08, 0, -0.18),
|
|
5079
|
+
rotationY: 1.38,
|
|
5080
|
+
angularVelocity: 0.025,
|
|
4914
5081
|
tint: { r: 0.62, g: 0.39, b: 0.23 },
|
|
4915
5082
|
massScale: 1.42,
|
|
4916
|
-
cruiseSpeed:
|
|
4917
|
-
throttleResponse: 0.
|
|
4918
|
-
rudderResponse: 0.
|
|
5083
|
+
cruiseSpeed: 1.22,
|
|
5084
|
+
throttleResponse: 0.36,
|
|
5085
|
+
rudderResponse: 0.4,
|
|
4919
5086
|
wanderPhase: 0.35,
|
|
4920
5087
|
lanterns: CUTTER_LANTERNS,
|
|
4921
5088
|
lanternStrength: 1.06,
|
|
@@ -4924,15 +5091,15 @@ function createSceneState(options, featureAdapters) {
|
|
|
4924
5091
|
{
|
|
4925
5092
|
id: "tidecaller",
|
|
4926
5093
|
modelKey: "cutter",
|
|
4927
|
-
position: vec3(
|
|
4928
|
-
velocity: vec3(-
|
|
4929
|
-
rotationY: -
|
|
4930
|
-
angularVelocity: -0.
|
|
5094
|
+
position: vec3(6.8, 0, 5.4),
|
|
5095
|
+
velocity: vec3(-0.82, 0, 0.14),
|
|
5096
|
+
rotationY: -1.34,
|
|
5097
|
+
angularVelocity: -0.035,
|
|
4931
5098
|
tint: { r: 0.58, g: 0.24, b: 0.16 },
|
|
4932
5099
|
massScale: 0.84,
|
|
4933
|
-
cruiseSpeed:
|
|
4934
|
-
throttleResponse: 0.
|
|
4935
|
-
rudderResponse: 0.
|
|
5100
|
+
cruiseSpeed: 1.36,
|
|
5101
|
+
throttleResponse: 0.52,
|
|
5102
|
+
rudderResponse: 0.58,
|
|
4936
5103
|
wanderPhase: 1.6,
|
|
4937
5104
|
lanterns: SHIP_LANTERNS,
|
|
4938
5105
|
lanternStrength: 1.18,
|
|
@@ -5133,7 +5300,7 @@ function renderProjectedShadow(ctx, worldPoints, camera, viewport, lightDir, opt
|
|
|
5133
5300
|
ctx.restore();
|
|
5134
5301
|
}
|
|
5135
5302
|
function pushHarborGeometry(camera, viewport, triangles, state) {
|
|
5136
|
-
if (!state
|
|
5303
|
+
if (!hasModeledHarborEnvironment(state)) {
|
|
5137
5304
|
for (const object of LEGACY_HARBOR_LAYOUT) {
|
|
5138
5305
|
buildTrianglesFromMesh(
|
|
5139
5306
|
{ positions: [object], indices: [0], normals: null, colors: null, material: createLegacyMeshPrimitive({})?.material, bounds: null, name: "legacy-structure" },
|
|
@@ -5231,9 +5398,10 @@ function renderShipRigging(ctx, ship, camera, viewport) {
|
|
|
5231
5398
|
}
|
|
5232
5399
|
function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
5233
5400
|
const projected = cloth.positions.map((point) => projectPoint(point, camera, viewport));
|
|
5234
|
-
|
|
5235
|
-
ctx.
|
|
5236
|
-
|
|
5401
|
+
const material = cloth.material ?? {};
|
|
5402
|
+
ctx.strokeStyle = `rgba(255, 241, 226, ${material.foldAlpha ?? 0.32})`;
|
|
5403
|
+
ctx.lineWidth = 1.8;
|
|
5404
|
+
for (let row = 0; row < cloth.grid.rows; row += Math.max(1, Math.floor(cloth.grid.rows / 6))) {
|
|
5237
5405
|
ctx.beginPath();
|
|
5238
5406
|
let started = false;
|
|
5239
5407
|
for (let column = 0; column < cloth.grid.cols; column += 1) {
|
|
@@ -5252,6 +5420,27 @@ function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
|
5252
5420
|
ctx.stroke();
|
|
5253
5421
|
}
|
|
5254
5422
|
}
|
|
5423
|
+
ctx.strokeStyle = `rgba(255, 228, 204, ${material.weaveAlpha ?? 0.22})`;
|
|
5424
|
+
ctx.lineWidth = 0.85;
|
|
5425
|
+
for (let column = 1; column < cloth.grid.cols - 1; column += Math.max(1, Math.floor(cloth.grid.cols / 8))) {
|
|
5426
|
+
ctx.beginPath();
|
|
5427
|
+
let started = false;
|
|
5428
|
+
for (let row = 0; row < cloth.grid.rows; row += 1) {
|
|
5429
|
+
const point = projected[row * cloth.grid.cols + column];
|
|
5430
|
+
if (!point) {
|
|
5431
|
+
continue;
|
|
5432
|
+
}
|
|
5433
|
+
if (!started) {
|
|
5434
|
+
ctx.moveTo(point.x, point.y);
|
|
5435
|
+
started = true;
|
|
5436
|
+
} else {
|
|
5437
|
+
ctx.lineTo(point.x, point.y);
|
|
5438
|
+
}
|
|
5439
|
+
}
|
|
5440
|
+
if (started) {
|
|
5441
|
+
ctx.stroke();
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5255
5444
|
const borderIndices = [
|
|
5256
5445
|
0,
|
|
5257
5446
|
cloth.grid.cols - 1,
|
|
@@ -5259,6 +5448,25 @@ function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
|
5259
5448
|
(cloth.grid.rows - 1) * cloth.grid.cols
|
|
5260
5449
|
];
|
|
5261
5450
|
ctx.fillStyle = colorToRgba(cloth.color, 0.95);
|
|
5451
|
+
ctx.strokeStyle = `rgba(255, 246, 236, ${material.edgeHighlightAlpha ?? 0.5})`;
|
|
5452
|
+
ctx.lineWidth = 1.4;
|
|
5453
|
+
ctx.beginPath();
|
|
5454
|
+
let borderStarted = false;
|
|
5455
|
+
for (let column = 0; column < cloth.grid.cols; column += 1) {
|
|
5456
|
+
const point = projected[column];
|
|
5457
|
+
if (!point) {
|
|
5458
|
+
continue;
|
|
5459
|
+
}
|
|
5460
|
+
if (!borderStarted) {
|
|
5461
|
+
ctx.moveTo(point.x, point.y);
|
|
5462
|
+
borderStarted = true;
|
|
5463
|
+
} else {
|
|
5464
|
+
ctx.lineTo(point.x, point.y);
|
|
5465
|
+
}
|
|
5466
|
+
}
|
|
5467
|
+
if (borderStarted) {
|
|
5468
|
+
ctx.stroke();
|
|
5469
|
+
}
|
|
5262
5470
|
for (const index of borderIndices) {
|
|
5263
5471
|
const point = projected[index];
|
|
5264
5472
|
if (!point) {
|
|
@@ -5274,14 +5482,22 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
5274
5482
|
if (band.band === "horizon") {
|
|
5275
5483
|
continue;
|
|
5276
5484
|
}
|
|
5277
|
-
const interval = band.band === "near" ?
|
|
5278
|
-
const alpha = band.band === "near" ? 0.22 : 0.14;
|
|
5485
|
+
const interval = band.band === "near" ? 4 : 5;
|
|
5486
|
+
const alpha = band.material?.highlightAlpha ?? (band.band === "near" ? 0.22 : 0.14);
|
|
5279
5487
|
ctx.strokeStyle = `rgba(232, 247, 255, ${alpha})`;
|
|
5280
|
-
ctx.lineWidth = band.band === "near" ?
|
|
5488
|
+
ctx.lineWidth = band.band === "near" ? 0.9 : 0.65;
|
|
5281
5489
|
for (let row = interval; row < band.rows - 1; row += interval) {
|
|
5282
|
-
ctx.beginPath();
|
|
5283
5490
|
let started = false;
|
|
5284
|
-
|
|
5491
|
+
ctx.beginPath();
|
|
5492
|
+
for (let column = 0; column < band.cols; column += band.band === "near" ? 2 : 3) {
|
|
5493
|
+
if (pseudoRandom(row * 47 + column * 13) < 0.18) {
|
|
5494
|
+
if (started) {
|
|
5495
|
+
ctx.stroke();
|
|
5496
|
+
ctx.beginPath();
|
|
5497
|
+
started = false;
|
|
5498
|
+
}
|
|
5499
|
+
continue;
|
|
5500
|
+
}
|
|
5285
5501
|
const point = projectPoint(
|
|
5286
5502
|
band.positions[row * band.cols + column],
|
|
5287
5503
|
camera,
|
|
@@ -5301,8 +5517,56 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
5301
5517
|
ctx.stroke();
|
|
5302
5518
|
}
|
|
5303
5519
|
}
|
|
5520
|
+
if (band.band === "near") {
|
|
5521
|
+
ctx.fillStyle = `rgba(236, 249, 255, ${(band.material?.foamAlpha ?? 0.28) * 0.72})`;
|
|
5522
|
+
for (let column = 3; column < band.cols - 3; column += 10) {
|
|
5523
|
+
const point = projectPoint(
|
|
5524
|
+
band.positions[Math.floor(band.rows * 0.42) * band.cols + column],
|
|
5525
|
+
camera,
|
|
5526
|
+
viewport
|
|
5527
|
+
);
|
|
5528
|
+
if (!point) {
|
|
5529
|
+
continue;
|
|
5530
|
+
}
|
|
5531
|
+
ctx.beginPath();
|
|
5532
|
+
ctx.ellipse(point.x, point.y, 1.8, 0.75, -0.2, 0, Math.PI * 2);
|
|
5533
|
+
ctx.fill();
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5304
5536
|
}
|
|
5305
5537
|
}
|
|
5538
|
+
function renderShorelineFoamSegments(ctx, segments, camera, viewport) {
|
|
5539
|
+
ctx.save();
|
|
5540
|
+
ctx.globalCompositeOperation = "screen";
|
|
5541
|
+
ctx.lineCap = "round";
|
|
5542
|
+
ctx.lineJoin = "round";
|
|
5543
|
+
for (const segment of segments) {
|
|
5544
|
+
const half = scaleVec3(segment.direction, segment.length * 0.5);
|
|
5545
|
+
const start = projectPoint(subVec3(segment.center, half), camera, viewport);
|
|
5546
|
+
const end = projectPoint(addVec3(segment.center, half), camera, viewport);
|
|
5547
|
+
const center = projectPoint(segment.center, camera, viewport);
|
|
5548
|
+
if (!start || !end || !center) {
|
|
5549
|
+
continue;
|
|
5550
|
+
}
|
|
5551
|
+
const depthScale = clamp(140 / Math.max(12, center.depth), 3, 10);
|
|
5552
|
+
ctx.strokeStyle = `rgba(232, 242, 238, ${segment.opacity})`;
|
|
5553
|
+
ctx.lineWidth = clamp(segment.width * depthScale, 0.8, 2.8);
|
|
5554
|
+
ctx.beginPath();
|
|
5555
|
+
ctx.moveTo(start.x, start.y);
|
|
5556
|
+
ctx.quadraticCurveTo(
|
|
5557
|
+
center.x,
|
|
5558
|
+
center.y + Math.sin(segment.center.x * 1.7) * 2.4,
|
|
5559
|
+
end.x,
|
|
5560
|
+
end.y
|
|
5561
|
+
);
|
|
5562
|
+
ctx.stroke();
|
|
5563
|
+
ctx.fillStyle = `rgba(248, 251, 246, ${segment.opacity * 0.68})`;
|
|
5564
|
+
ctx.beginPath();
|
|
5565
|
+
ctx.ellipse(center.x, center.y, depthScale * 0.18, depthScale * 0.08, -0.2, 0, Math.PI * 2);
|
|
5566
|
+
ctx.fill();
|
|
5567
|
+
}
|
|
5568
|
+
ctx.restore();
|
|
5569
|
+
}
|
|
5306
5570
|
function readPhysicsNumber(physics, key, fallback) {
|
|
5307
5571
|
const value = physics?.[key];
|
|
5308
5572
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
@@ -5333,14 +5597,17 @@ function getShipInverseInertia(ship, shipModel) {
|
|
|
5333
5597
|
return 1 / Math.max(1, inertia);
|
|
5334
5598
|
}
|
|
5335
5599
|
function spawnSpray(state, point, intensity) {
|
|
5336
|
-
const count =
|
|
5600
|
+
const count = Math.max(
|
|
5601
|
+
3,
|
|
5602
|
+
Math.ceil(state.fluidDetail.getSnapshot().currentLevel.config.splashCount * 0.32)
|
|
5603
|
+
);
|
|
5337
5604
|
for (let index = 0; index < count; index += 1) {
|
|
5338
5605
|
const angle = index / count * Math.PI * 2;
|
|
5339
|
-
const speed = 0.
|
|
5606
|
+
const speed = 0.46 + Math.random() * intensity * 0.24;
|
|
5340
5607
|
state.sprays.push({
|
|
5341
5608
|
position: vec3(point.x, point.y, point.z),
|
|
5342
|
-
velocity: vec3(Math.cos(angle) * speed * 0.
|
|
5343
|
-
life:
|
|
5609
|
+
velocity: vec3(Math.cos(angle) * speed * 0.24, 0.46 + Math.random() * 0.34, Math.sin(angle) * speed * 0.18),
|
|
5610
|
+
life: 0.72 + Math.random() * 0.22
|
|
5344
5611
|
});
|
|
5345
5612
|
}
|
|
5346
5613
|
}
|
|
@@ -5355,7 +5622,7 @@ function resolveShipRoute(ship, state, radius) {
|
|
|
5355
5622
|
}
|
|
5356
5623
|
const wander = Math.sin(state.time * 0.22 + readVisualNumber(ship.wanderPhase, 0));
|
|
5357
5624
|
const crossCurrent = Math.cos(state.time * 0.31 + readVisualNumber(ship.wanderPhase, 0));
|
|
5358
|
-
const laneCenter = ship.id === "northwind" ?
|
|
5625
|
+
const laneCenter = ship.id === "northwind" ? 11.6 + wander * 0.82 + crossCurrent * 0.24 : 5.4 + wander * 0.94 - crossCurrent * 0.32;
|
|
5359
5626
|
const targetX = ship.routeDirection > 0 ? HARBOR_BOUNDS.maxX - radius * 1.7 : HARBOR_BOUNDS.minX + radius * 1.7;
|
|
5360
5627
|
return vec3(targetX, 0, clamp(laneCenter, HARBOR_BOUNDS.minZ + 1.8, HARBOR_BOUNDS.maxZ - 1.8));
|
|
5361
5628
|
}
|
|
@@ -5368,7 +5635,7 @@ function updateShipMotion(state, ship, dt, shipModel) {
|
|
|
5368
5635
|
const angularDamping = readPhysicsNumber(physics, "angularDamping", 0.08);
|
|
5369
5636
|
const throttleResponse = readVisualNumber(ship.throttleResponse, 0.58);
|
|
5370
5637
|
const rudderResponse = readVisualNumber(ship.rudderResponse, 0.62);
|
|
5371
|
-
const cruiseSpeed = readVisualNumber(ship.cruiseSpeed,
|
|
5638
|
+
const cruiseSpeed = readVisualNumber(ship.cruiseSpeed, 1.25);
|
|
5372
5639
|
ship.collisionCooldown = Math.max(0, readVisualNumber(ship.collisionCooldown, 0) - dt);
|
|
5373
5640
|
const forward = directionFromYaw(ship.rotationY);
|
|
5374
5641
|
const lateral = perpendicularOnWater(forward);
|
|
@@ -5463,7 +5730,7 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
|
5463
5730
|
b.position = addVec3(b.position, scaleVec3(correction, invMassB));
|
|
5464
5731
|
const relativeVelocity = subVec3(b.velocity, a.velocity);
|
|
5465
5732
|
const velocityAlongNormal = dotVec3(relativeVelocity, normal);
|
|
5466
|
-
const restitution = (readPhysicsNumber(shipModelA.physics, "restitution", 0.22) + readPhysicsNumber(shipModelB.physics, "restitution", 0.22)) / 2 * 0.
|
|
5733
|
+
const restitution = (readPhysicsNumber(shipModelA.physics, "restitution", 0.22) + readPhysicsNumber(shipModelB.physics, "restitution", 0.22)) / 2 * 0.42;
|
|
5467
5734
|
if (velocityAlongNormal < 0) {
|
|
5468
5735
|
const impulseMagnitude = -(1 + restitution) * velocityAlongNormal / Math.max(1e-4, invMassSum);
|
|
5469
5736
|
const impulse = scaleVec3(normal, impulseMagnitude);
|
|
@@ -5481,27 +5748,27 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
|
5481
5748
|
a.angularVelocity -= tangentSpeed * radiusA * getShipInverseInertia(a, shipModelA) * 0.2 + impulseMagnitude * 24e-5;
|
|
5482
5749
|
b.angularVelocity += tangentSpeed * radiusB * getShipInverseInertia(b, shipModelB) * 0.2 + impulseMagnitude * 24e-5;
|
|
5483
5750
|
const impactSpeed = Math.abs(velocityAlongNormal);
|
|
5484
|
-
if (impactSpeed > 0.
|
|
5751
|
+
if (impactSpeed > 0.36 && Math.max(readVisualNumber(a.collisionCooldown, 0), readVisualNumber(b.collisionCooldown, 0)) <= 0) {
|
|
5485
5752
|
const contactPoint = vec3(
|
|
5486
5753
|
(a.position.x + b.position.x) * 0.5,
|
|
5487
5754
|
(a.position.y + b.position.y) * 0.5 + 0.14,
|
|
5488
5755
|
(a.position.z + b.position.z) * 0.5
|
|
5489
5756
|
);
|
|
5490
|
-
spawnSpray(state, contactPoint, impactSpeed *
|
|
5757
|
+
spawnSpray(state, contactPoint, impactSpeed * 0.9 + penetration * 2.4);
|
|
5491
5758
|
state.waveImpulses.push({
|
|
5492
5759
|
x: contactPoint.x,
|
|
5493
5760
|
z: contactPoint.z,
|
|
5494
|
-
strength: clamp(0.
|
|
5495
|
-
radius: 0.
|
|
5761
|
+
strength: clamp(0.1 + impactSpeed * 0.18 + penetration * 0.28, 0.08, 0.52),
|
|
5762
|
+
radius: 0.72 + penetration * 0.72,
|
|
5496
5763
|
life: 1
|
|
5497
5764
|
});
|
|
5498
5765
|
state.collisionCount += 1;
|
|
5499
5766
|
state.collisionFlash = Math.max(
|
|
5500
5767
|
state.collisionFlash,
|
|
5501
|
-
clamp(impactSpeed * 0.
|
|
5768
|
+
clamp(impactSpeed * 0.14 + penetration * 0.32, 0.04, 0.24)
|
|
5502
5769
|
);
|
|
5503
|
-
a.collisionCooldown = 0.
|
|
5504
|
-
b.collisionCooldown = 0.
|
|
5770
|
+
a.collisionCooldown = 0.72;
|
|
5771
|
+
b.collisionCooldown = 0.72;
|
|
5505
5772
|
}
|
|
5506
5773
|
}
|
|
5507
5774
|
state.contactCount += 1;
|
|
@@ -5524,7 +5791,7 @@ function updateShips(state, dt, shipModel) {
|
|
|
5524
5791
|
collided = resolveShipCollision(state, shipA, shipB, shipModelA, shipModelB) || collided;
|
|
5525
5792
|
}
|
|
5526
5793
|
}
|
|
5527
|
-
state.collisionFlash = collided ? Math.max(0.
|
|
5794
|
+
state.collisionFlash = collided ? Math.max(0.04, state.collisionFlash) : Math.max(0, state.collisionFlash - dt * 1.7);
|
|
5528
5795
|
}
|
|
5529
5796
|
function updateWaveImpulses(state, dt) {
|
|
5530
5797
|
state.waveImpulses = state.waveImpulses.map((impulse) => ({
|
|
@@ -5789,7 +6056,7 @@ function renderLighthouseBeam(ctx, state, camera, viewport, visuals) {
|
|
|
5789
6056
|
const lighthousePlacement = SHOWCASE_ENVIRONMENT_LAYOUT.find(
|
|
5790
6057
|
(placement) => placement.assetKey === "lighthouse"
|
|
5791
6058
|
);
|
|
5792
|
-
if (!lighthousePlacement || !state.showcaseRealisticModelsEnabled) {
|
|
6059
|
+
if (!lighthousePlacement || !state.showcaseRealisticModelsEnabled || !hasModeledHarborEnvironment(state)) {
|
|
5793
6060
|
return;
|
|
5794
6061
|
}
|
|
5795
6062
|
const source = transformPoint2(
|
|
@@ -5886,7 +6153,8 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
5886
6153
|
for (const wake of effects.wakeTrails) {
|
|
5887
6154
|
const projected = wake.points.map((point) => ({
|
|
5888
6155
|
projected: projectPoint(point.center, camera, viewport),
|
|
5889
|
-
width: point.width
|
|
6156
|
+
width: point.width,
|
|
6157
|
+
foam: point.foam
|
|
5890
6158
|
})).filter((entry) => entry.projected);
|
|
5891
6159
|
if (projected.length < 2) {
|
|
5892
6160
|
continue;
|
|
@@ -5894,8 +6162,8 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
5894
6162
|
const averageDepth = projected.reduce((total, entry) => total + entry.projected.depth, 0) / projected.length;
|
|
5895
6163
|
const averageWidth = projected.reduce((total, entry) => total + entry.width, 0) / projected.length;
|
|
5896
6164
|
const baseWidth = clamp(averageWidth / Math.max(0.25, averageDepth) * 180, 1.6, 5.4);
|
|
5897
|
-
ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.
|
|
5898
|
-
ctx.lineWidth = baseWidth * 1.
|
|
6165
|
+
ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.34})`;
|
|
6166
|
+
ctx.lineWidth = baseWidth * 1.45;
|
|
5899
6167
|
ctx.lineCap = "round";
|
|
5900
6168
|
ctx.lineJoin = "round";
|
|
5901
6169
|
ctx.beginPath();
|
|
@@ -5904,8 +6172,8 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
5904
6172
|
ctx.lineTo(projected[index].projected.x, projected[index].projected.y);
|
|
5905
6173
|
}
|
|
5906
6174
|
ctx.stroke();
|
|
5907
|
-
ctx.strokeStyle = `rgba(234, 247, 255, ${wake.opacity})`;
|
|
5908
|
-
ctx.lineWidth = baseWidth;
|
|
6175
|
+
ctx.strokeStyle = `rgba(234, 247, 255, ${wake.opacity * 0.72})`;
|
|
6176
|
+
ctx.lineWidth = baseWidth * 0.72;
|
|
5909
6177
|
ctx.lineCap = "round";
|
|
5910
6178
|
ctx.lineJoin = "round";
|
|
5911
6179
|
ctx.beginPath();
|
|
@@ -5915,13 +6183,14 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
5915
6183
|
}
|
|
5916
6184
|
ctx.stroke();
|
|
5917
6185
|
for (const entry of projected.slice(1, 5)) {
|
|
5918
|
-
|
|
6186
|
+
const foam = entry.foam ?? 0.3;
|
|
6187
|
+
ctx.fillStyle = `rgba(239, 248, 255, ${wake.opacity * foam * 0.92})`;
|
|
5919
6188
|
ctx.beginPath();
|
|
5920
6189
|
ctx.ellipse(
|
|
5921
6190
|
entry.projected.x,
|
|
5922
6191
|
entry.projected.y,
|
|
5923
|
-
baseWidth * 0.
|
|
5924
|
-
baseWidth * 0.
|
|
6192
|
+
baseWidth * 0.54,
|
|
6193
|
+
baseWidth * 0.28,
|
|
5925
6194
|
0,
|
|
5926
6195
|
0,
|
|
5927
6196
|
Math.PI * 2
|
|
@@ -5939,13 +6208,22 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
5939
6208
|
const radiusX = Math.hypot(xAxis.x - center.x, xAxis.y - center.y);
|
|
5940
6209
|
const radiusY = Math.hypot(zAxis.x - center.x, zAxis.y - center.y);
|
|
5941
6210
|
ctx.strokeStyle = `rgba(216, 235, 255, ${ring.opacity})`;
|
|
5942
|
-
ctx.lineWidth = clamp((radiusX + radiusY) * 0.
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
6211
|
+
ctx.lineWidth = clamp((radiusX + radiusY) * 0.014, 0.65, 1.8);
|
|
6212
|
+
for (let segment = 0; segment < 5; segment += 1) {
|
|
6213
|
+
if (pseudoRandom(segment * 31 + radiusX * 0.7 + radiusY * 0.3) < 0.32) {
|
|
6214
|
+
continue;
|
|
6215
|
+
}
|
|
6216
|
+
const startAngle = segment * 1.22 + stateTimePhase(center.x, center.y) * 0.04;
|
|
6217
|
+
ctx.beginPath();
|
|
6218
|
+
ctx.ellipse(center.x, center.y, radiusX, radiusY, 0, startAngle, startAngle + 0.48);
|
|
6219
|
+
ctx.stroke();
|
|
6220
|
+
}
|
|
5946
6221
|
}
|
|
5947
6222
|
ctx.restore();
|
|
5948
6223
|
}
|
|
6224
|
+
function stateTimePhase(x, y) {
|
|
6225
|
+
return Math.sin(x * 0.013 + y * 0.017);
|
|
6226
|
+
}
|
|
5949
6227
|
function renderScene(ctx, canvas, state, shipModel, dom, lightingFeatures, fluidFeatures, clothFeatures) {
|
|
5950
6228
|
const viewport = { width: canvas.width, height: canvas.height };
|
|
5951
6229
|
const camera = buildCamera(state, canvas);
|
|
@@ -6013,6 +6291,7 @@ function renderScene(ctx, canvas, state, shipModel, dom, lightingFeatures, fluid
|
|
|
6013
6291
|
}
|
|
6014
6292
|
}
|
|
6015
6293
|
const waterMotionEffects = buildWaterMotionEffects(state);
|
|
6294
|
+
const shorelineFoamSegments = buildShorelineFoamSegments(state);
|
|
6016
6295
|
const lightSources = collectSceneLightSources(state, visuals);
|
|
6017
6296
|
pushHarborGeometry(camera, viewport, sceneTriangles, state);
|
|
6018
6297
|
const cloth = buildClothSurface(
|
|
@@ -6084,6 +6363,7 @@ function renderScene(ctx, canvas, state, shipModel, dom, lightingFeatures, fluid
|
|
|
6084
6363
|
}
|
|
6085
6364
|
renderWaterMotionEffects(ctx, waterMotionEffects, camera, viewport);
|
|
6086
6365
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
6366
|
+
renderShorelineFoamSegments(ctx, shorelineFoamSegments, camera, viewport);
|
|
6087
6367
|
drawTriangles(
|
|
6088
6368
|
ctx,
|
|
6089
6369
|
sceneTriangles,
|
|
@@ -6112,7 +6392,7 @@ function renderScene(ctx, canvas, state, shipModel, dom, lightingFeatures, fluid
|
|
|
6112
6392
|
};
|
|
6113
6393
|
const sceneMetrics = [
|
|
6114
6394
|
`focus: ${state.focus}`,
|
|
6115
|
-
`ships: ${state.ships.length} active GLTF hulls across ${new Set(state.ships.map((ship) => ship.modelKey)).size} model families`,
|
|
6395
|
+
`ships: ${state.ships.length} active GLTF hulls across ${new Set(state.ships.map((ship) => resolveShipModel(state, ship, shipModel)?.name ?? ship.modelKey)).size} model families`,
|
|
6116
6396
|
`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`,
|
|
6117
6397
|
`physics snapshot: ${state.physics.snapshot.stage} (${state.physics.snapshot.stability})`,
|
|
6118
6398
|
`physics contacts: ${state.contactCount}`,
|
|
@@ -6180,7 +6460,7 @@ function updateSceneState(state, dt, shipModel, featureAdapters) {
|
|
|
6180
6460
|
advanceShowcaseClothSimulationState(clothState, {
|
|
6181
6461
|
dt,
|
|
6182
6462
|
time: state.time,
|
|
6183
|
-
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.
|
|
6463
|
+
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.58),
|
|
6184
6464
|
waveInfluence: sampleWave(state, FLAG_LAYOUT.origin.x + FLAG_LAYOUT.width * 0.55, FLAG_LAYOUT.origin.z + FLAG_LAYOUT.width * 0.48, state.time)
|
|
6185
6465
|
});
|
|
6186
6466
|
updatePhysicsSnapshot(state, shipModel, featureAdapters.physics);
|
|
@@ -6190,17 +6470,28 @@ function syncTextState(state, shipModel, featureAdapters) {
|
|
|
6190
6470
|
coordinateSystem: "right-handed world; +x right, +y up, +z forward from the shore",
|
|
6191
6471
|
focus: state.focus,
|
|
6192
6472
|
stress: state.stress,
|
|
6193
|
-
ships: state.ships.map((ship) =>
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6473
|
+
ships: state.ships.map((ship) => {
|
|
6474
|
+
const resolvedShipModel = resolveShipModel(state, ship, shipModel);
|
|
6475
|
+
return {
|
|
6476
|
+
id: ship.id,
|
|
6477
|
+
modelKey: ship.modelKey ?? "brigantine",
|
|
6478
|
+
resolvedModelKey: resolvedShipModel?.name ?? ship.modelKey ?? "brigantine",
|
|
6479
|
+
x: Number(ship.position.x.toFixed(2)),
|
|
6480
|
+
y: Number(ship.position.y.toFixed(2)),
|
|
6481
|
+
z: Number(ship.position.z.toFixed(2)),
|
|
6482
|
+
vx: Number(ship.velocity.x.toFixed(2)),
|
|
6483
|
+
vz: Number(ship.velocity.z.toFixed(2)),
|
|
6484
|
+
massKg: Math.round(getShipMass(ship, resolvedShipModel)),
|
|
6485
|
+
lanterns: Array.isArray(ship.lanterns) ? ship.lanterns.length : 0
|
|
6486
|
+
};
|
|
6487
|
+
}),
|
|
6488
|
+
assetCatalog: {
|
|
6489
|
+
mode: state.assetCatalog?.mode ?? "unknown",
|
|
6490
|
+
shipKeys: Object.keys(state.assetCatalog?.ships ?? {}).sort(),
|
|
6491
|
+
environmentKeys: Object.keys(state.assetCatalog?.environment ?? {}).sort(),
|
|
6492
|
+
fallbackReason: state.assetCatalog?.fallbackReason ?? null,
|
|
6493
|
+
requestedRealisticModels: state.showcaseRealisticModelsEnabled
|
|
6494
|
+
},
|
|
6204
6495
|
shipPhysics: Object.fromEntries(
|
|
6205
6496
|
state.ships.map((ship) => [ship.id, resolveShipModel(state, ship, shipModel)?.physics ?? null])
|
|
6206
6497
|
),
|
|
@@ -6257,7 +6548,9 @@ async function mountGpuShowcase(options = {}, featureFlags = null) {
|
|
|
6257
6548
|
},
|
|
6258
6549
|
featureAdapters
|
|
6259
6550
|
);
|
|
6260
|
-
const assetCatalog = await (
|
|
6551
|
+
const assetCatalog = await loadShowcaseAssetCatalogWithFallback({
|
|
6552
|
+
includeSecondaryShip: state.showcaseRealisticModelsEnabled
|
|
6553
|
+
});
|
|
6261
6554
|
const shipModel = assetCatalog.ships[assetCatalog.primaryShipKey];
|
|
6262
6555
|
state.assetCatalog = assetCatalog;
|
|
6263
6556
|
state.shipModel = shipModel;
|
|
@@ -6395,7 +6688,7 @@ function updatePhysicsSnapshot(state, shipModel, physicsFeatures) {
|
|
|
6395
6688
|
}
|
|
6396
6689
|
});
|
|
6397
6690
|
}
|
|
6398
|
-
var STYLE_ID2, ROOT_CLASS, CAPTURE_CLASS, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT, CAPTURE_CANVAS_PIXEL_BUDGET, SHIP_SCALE, HARBOR_BOUNDS, CAMERA_PRESETS, FALLBACK_LIGHTING_DISTANCE_BANDS, FALLBACK_LIGHTING_PROFILE, FALLBACK_PHYSICS_PROFILE, FALLBACK_PERFORMANCE_LEVELS, SHOWCASE_FEATURE_LOADERS, DEFAULT_FLUID_BAND_THRESHOLDS, DEFAULT_CLOTH_BAND_THRESHOLDS, showcaseFocusModes, FOCUS_MODE_TRANSLATION_KEYS, SCENE_NOTE_KEYS, PHYSICS_SCENE_NOTE_KEYS, LEGACY_HARBOR_LAYOUT, SHOWCASE_ENVIRONMENT_LAYOUT, SHIP_LANTERNS, CUTTER_LANTERNS, HARBOR_TORCHES, FLAG_LAYOUT;
|
|
6691
|
+
var STYLE_ID2, ROOT_CLASS, CAPTURE_CLASS, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT, CAPTURE_CANVAS_PIXEL_BUDGET, SHIP_SCALE, HARBOR_BOUNDS, CAMERA_PRESETS, FALLBACK_LIGHTING_DISTANCE_BANDS, FALLBACK_LIGHTING_PROFILE, FALLBACK_PHYSICS_PROFILE, FALLBACK_PERFORMANCE_LEVELS, SHOWCASE_FEATURE_LOADERS, DEFAULT_FLUID_BAND_THRESHOLDS, DEFAULT_CLOTH_BAND_THRESHOLDS, showcaseFocusModes, FOCUS_MODE_TRANSLATION_KEYS, SCENE_NOTE_KEYS, PHYSICS_SCENE_NOTE_KEYS, LEGACY_HARBOR_LAYOUT, SHOWCASE_ENVIRONMENT_LAYOUT, SHIP_LANTERNS, CUTTER_LANTERNS, HARBOR_TORCHES, SHORELINE_FOAM_ANCHORS, FLAG_LAYOUT;
|
|
6399
6692
|
var init_showcase_runtime = __esm({
|
|
6400
6693
|
"src/showcase-runtime.js"() {
|
|
6401
6694
|
init_asset_url();
|
|
@@ -6586,6 +6879,13 @@ var init_showcase_runtime = __esm({
|
|
|
6586
6879
|
})
|
|
6587
6880
|
]);
|
|
6588
6881
|
SHOWCASE_ENVIRONMENT_LAYOUT = Object.freeze([
|
|
6882
|
+
Object.freeze({
|
|
6883
|
+
assetKey: "shoreline",
|
|
6884
|
+
position: Object.freeze({ x: 1.8, y: -0.04, z: 0.48 }),
|
|
6885
|
+
rotationY: -0.03,
|
|
6886
|
+
scale: 1.02,
|
|
6887
|
+
accent: 0.03
|
|
6888
|
+
}),
|
|
6589
6889
|
Object.freeze({
|
|
6590
6890
|
assetKey: "harbor-dock",
|
|
6591
6891
|
position: Object.freeze({ x: -4.6, y: 0.16, z: 0.7 }),
|
|
@@ -6616,6 +6916,18 @@ var init_showcase_runtime = __esm({
|
|
|
6616
6916
|
Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
|
|
6617
6917
|
Object.freeze({ x: -10.4, y: 1.28, z: 0.82, glow: 0.92 })
|
|
6618
6918
|
]);
|
|
6919
|
+
SHORELINE_FOAM_ANCHORS = Object.freeze([
|
|
6920
|
+
Object.freeze({ x: -7.8, z: 3, length: 1.25, angle: -0.12 }),
|
|
6921
|
+
Object.freeze({ x: -6.3, z: 2.72, length: 0.92, angle: 0.08 }),
|
|
6922
|
+
Object.freeze({ x: -4.9, z: 3.16, length: 1.08, angle: -0.2 }),
|
|
6923
|
+
Object.freeze({ x: -3.2, z: 2.42, length: 0.76, angle: 0.16 }),
|
|
6924
|
+
Object.freeze({ x: -1.4, z: 2.82, length: 1.18, angle: -0.04 }),
|
|
6925
|
+
Object.freeze({ x: 0.4, z: 3.08, length: 0.88, angle: 0.14 }),
|
|
6926
|
+
Object.freeze({ x: 2.1, z: 2.56, length: 1.34, angle: -0.18 }),
|
|
6927
|
+
Object.freeze({ x: 3.8, z: 3, length: 0.94, angle: 0.1 }),
|
|
6928
|
+
Object.freeze({ x: 5.5, z: 2.72, length: 1.12, angle: -0.08 }),
|
|
6929
|
+
Object.freeze({ x: 7, z: 3.22, length: 0.72, angle: 0.18 })
|
|
6930
|
+
]);
|
|
6619
6931
|
FLAG_LAYOUT = Object.freeze({
|
|
6620
6932
|
origin: Object.freeze({ x: -3.5, y: 5.9, z: 2.4 }),
|
|
6621
6933
|
width: 4.8,
|
|
@@ -6630,6 +6942,7 @@ var index_exports = {};
|
|
|
6630
6942
|
__export(index_exports, {
|
|
6631
6943
|
GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE: () => GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE,
|
|
6632
6944
|
GPU_SHOWCASE_REALISTIC_MODELS_FEATURE: () => GPU_SHOWCASE_REALISTIC_MODELS_FEATURE,
|
|
6945
|
+
buildProductStudioSceneObjects: () => buildProductStudioSceneObjects,
|
|
6633
6946
|
createGpuSharedTranslator: () => createGpuSharedTranslator,
|
|
6634
6947
|
createProductStudioMeshes: () => createProductStudioMeshes,
|
|
6635
6948
|
gpuSharedEnGbTranslations: () => gpuSharedEnGbTranslations,
|
|
@@ -6715,6 +7028,7 @@ async function mountGpuShowcase2(options = {}) {
|
|
|
6715
7028
|
0 && (module.exports = {
|
|
6716
7029
|
GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE,
|
|
6717
7030
|
GPU_SHOWCASE_REALISTIC_MODELS_FEATURE,
|
|
7031
|
+
buildProductStudioSceneObjects,
|
|
6718
7032
|
createGpuSharedTranslator,
|
|
6719
7033
|
createProductStudioMeshes,
|
|
6720
7034
|
gpuSharedEnGbTranslations,
|