@plasius/gpu-shared 0.1.20 → 1.0.0
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 +26 -1
- package/README.md +26 -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/{gltf-loader-FMRC3OEV.js → gltf-loader-44ILCO7V.js} +2 -2
- package/dist/index.cjs +421 -152
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -5
- 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-INRAPCXW.js} +414 -152
- package/dist/showcase-runtime-INRAPCXW.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 +430 -151
- 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
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
loadGltfModel,
|
|
8
8
|
resolveShowcaseAssetUrl
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KGKLNL4X.js";
|
|
10
10
|
import "./chunk-2GM64LB6.js";
|
|
11
11
|
import "./chunk-DGUM43GV.js";
|
|
12
12
|
|
|
@@ -764,6 +764,13 @@ var LEGACY_HARBOR_LAYOUT = Object.freeze([
|
|
|
764
764
|
})
|
|
765
765
|
]);
|
|
766
766
|
var SHOWCASE_ENVIRONMENT_LAYOUT = Object.freeze([
|
|
767
|
+
Object.freeze({
|
|
768
|
+
assetKey: "shoreline",
|
|
769
|
+
position: Object.freeze({ x: 1.8, y: -0.04, z: 0.48 }),
|
|
770
|
+
rotationY: -0.03,
|
|
771
|
+
scale: 1.02,
|
|
772
|
+
accent: 0.03
|
|
773
|
+
}),
|
|
767
774
|
Object.freeze({
|
|
768
775
|
assetKey: "harbor-dock",
|
|
769
776
|
position: Object.freeze({ x: -4.6, y: 0.16, z: 0.7 }),
|
|
@@ -794,6 +801,18 @@ var HARBOR_TORCHES = Object.freeze([
|
|
|
794
801
|
Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
|
|
795
802
|
Object.freeze({ x: -10.4, y: 1.28, z: 0.82, glow: 0.92 })
|
|
796
803
|
]);
|
|
804
|
+
var SHORELINE_FOAM_ANCHORS = Object.freeze([
|
|
805
|
+
Object.freeze({ x: -7.8, z: 3, length: 1.25, angle: -0.12 }),
|
|
806
|
+
Object.freeze({ x: -6.3, z: 2.72, length: 0.92, angle: 0.08 }),
|
|
807
|
+
Object.freeze({ x: -4.9, z: 3.16, length: 1.08, angle: -0.2 }),
|
|
808
|
+
Object.freeze({ x: -3.2, z: 2.42, length: 0.76, angle: 0.16 }),
|
|
809
|
+
Object.freeze({ x: -1.4, z: 2.82, length: 1.18, angle: -0.04 }),
|
|
810
|
+
Object.freeze({ x: 0.4, z: 3.08, length: 0.88, angle: 0.14 }),
|
|
811
|
+
Object.freeze({ x: 2.1, z: 2.56, length: 1.34, angle: -0.18 }),
|
|
812
|
+
Object.freeze({ x: 3.8, z: 3, length: 0.94, angle: 0.1 }),
|
|
813
|
+
Object.freeze({ x: 5.5, z: 2.72, length: 1.12, angle: -0.08 }),
|
|
814
|
+
Object.freeze({ x: 7, z: 3.22, length: 0.72, angle: 0.18 })
|
|
815
|
+
]);
|
|
797
816
|
var FLAG_LAYOUT = Object.freeze({
|
|
798
817
|
origin: Object.freeze({ x: -3.5, y: 5.9, z: 2.4 }),
|
|
799
818
|
width: 4.8,
|
|
@@ -835,31 +854,31 @@ function injectStyles() {
|
|
|
835
854
|
box-sizing: border-box;
|
|
836
855
|
}
|
|
837
856
|
.plasius-demo {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
gap: 20px;
|
|
843
|
-
}
|
|
844
|
-
.plasius-demo__hero,
|
|
845
|
-
.plasius-demo__layout {
|
|
846
|
-
display: grid;
|
|
847
|
-
gap: 20px;
|
|
848
|
-
}
|
|
849
|
-
.plasius-demo__hero {
|
|
850
|
-
grid-template-columns: minmax(0, 1.5fr) minmax(320px, 0.85fr);
|
|
851
|
-
align-items: start;
|
|
857
|
+
position: relative;
|
|
858
|
+
width: 100%;
|
|
859
|
+
min-height: 100dvh;
|
|
860
|
+
overflow: hidden;
|
|
852
861
|
}
|
|
853
862
|
.plasius-panel {
|
|
854
863
|
border: 1px solid var(--plasius-border);
|
|
855
|
-
border-radius:
|
|
864
|
+
border-radius: 8px;
|
|
856
865
|
background: var(--plasius-panel);
|
|
857
866
|
box-shadow: var(--plasius-shadow);
|
|
858
867
|
backdrop-filter: blur(12px);
|
|
859
868
|
}
|
|
860
869
|
.plasius-demo__hero-card,
|
|
861
870
|
.plasius-demo__status {
|
|
862
|
-
|
|
871
|
+
position: absolute;
|
|
872
|
+
z-index: 3;
|
|
873
|
+
padding: 10px 12px;
|
|
874
|
+
}
|
|
875
|
+
.plasius-demo__hero-card {
|
|
876
|
+
display: none;
|
|
877
|
+
}
|
|
878
|
+
.plasius-demo__status {
|
|
879
|
+
left: 16px;
|
|
880
|
+
bottom: 84px;
|
|
881
|
+
max-width: min(360px, calc(100vw - 32px));
|
|
863
882
|
}
|
|
864
883
|
.plasius-demo__eyebrow {
|
|
865
884
|
margin: 0 0 8px;
|
|
@@ -882,31 +901,36 @@ function injectStyles() {
|
|
|
882
901
|
.plasius-demo__status-badge {
|
|
883
902
|
width: fit-content;
|
|
884
903
|
margin: 0;
|
|
885
|
-
padding:
|
|
886
|
-
border-radius:
|
|
904
|
+
padding: 6px 9px;
|
|
905
|
+
border-radius: 6px;
|
|
887
906
|
background: rgba(243, 177, 106, 0.14);
|
|
888
907
|
color: var(--plasius-accent);
|
|
889
908
|
font-weight: 700;
|
|
909
|
+
font-size: 12px;
|
|
890
910
|
}
|
|
891
911
|
.plasius-demo__status-text {
|
|
892
912
|
margin: 10px 0 0;
|
|
893
913
|
color: var(--plasius-muted);
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
.plasius-demo__layout {
|
|
897
|
-
grid-template-columns: minmax(0, 1.45fr) minmax(320px, 0.68fr);
|
|
898
|
-
align-items: start;
|
|
914
|
+
font-size: 12px;
|
|
915
|
+
line-height: 1.45;
|
|
899
916
|
}
|
|
900
917
|
.plasius-demo__canvas-panel {
|
|
901
|
-
|
|
902
|
-
|
|
918
|
+
position: absolute;
|
|
919
|
+
inset: 0;
|
|
920
|
+
padding: 0;
|
|
921
|
+
border: 0;
|
|
922
|
+
border-radius: 0;
|
|
923
|
+
background: transparent;
|
|
924
|
+
box-shadow: none;
|
|
925
|
+
backdrop-filter: none;
|
|
903
926
|
}
|
|
904
927
|
.plasius-demo__canvas {
|
|
905
928
|
width: 100%;
|
|
906
|
-
|
|
929
|
+
height: 100%;
|
|
930
|
+
min-height: 100dvh;
|
|
907
931
|
display: block;
|
|
908
|
-
border
|
|
909
|
-
border:
|
|
932
|
+
border: 0;
|
|
933
|
+
border-radius: 0;
|
|
910
934
|
background: linear-gradient(180deg, #071220 0%, #132440 42%, #10344b 42%, #05111d 100%);
|
|
911
935
|
}
|
|
912
936
|
.${CAPTURE_CLASS} .plasius-demo {
|
|
@@ -919,6 +943,8 @@ function injectStyles() {
|
|
|
919
943
|
.${CAPTURE_CLASS} .plasius-demo__toolbar,
|
|
920
944
|
.${CAPTURE_CLASS} .plasius-demo__legend,
|
|
921
945
|
.${CAPTURE_CLASS} .plasius-demo__sidebar,
|
|
946
|
+
.${CAPTURE_CLASS} .plasius-demo__diagnostics,
|
|
947
|
+
.${CAPTURE_CLASS} .plasius-demo__status,
|
|
922
948
|
.${CAPTURE_CLASS} .plasius-demo__footer {
|
|
923
949
|
display: none;
|
|
924
950
|
}
|
|
@@ -945,12 +971,14 @@ function injectStyles() {
|
|
|
945
971
|
}
|
|
946
972
|
.plasius-demo__toolbar {
|
|
947
973
|
position: absolute;
|
|
948
|
-
top:
|
|
949
|
-
left:
|
|
974
|
+
top: 84px;
|
|
975
|
+
left: 16px;
|
|
976
|
+
z-index: 4;
|
|
950
977
|
display: flex;
|
|
951
|
-
gap:
|
|
978
|
+
gap: 8px;
|
|
952
979
|
flex-wrap: wrap;
|
|
953
980
|
align-items: center;
|
|
981
|
+
max-width: min(560px, calc(100vw - 32px));
|
|
954
982
|
}
|
|
955
983
|
.plasius-demo button,
|
|
956
984
|
.plasius-demo label,
|
|
@@ -962,22 +990,63 @@ function injectStyles() {
|
|
|
962
990
|
.plasius-demo .plasius-toggle,
|
|
963
991
|
.plasius-demo select {
|
|
964
992
|
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
965
|
-
border-radius:
|
|
993
|
+
border-radius: 6px;
|
|
966
994
|
background: rgba(9, 20, 34, 0.84);
|
|
967
995
|
color: var(--plasius-ink);
|
|
968
|
-
padding: 10px
|
|
996
|
+
padding: 8px 10px;
|
|
969
997
|
}
|
|
970
998
|
.plasius-toggle {
|
|
971
999
|
display: inline-flex;
|
|
972
1000
|
align-items: center;
|
|
973
1001
|
gap: 8px;
|
|
974
1002
|
}
|
|
1003
|
+
.plasius-demo__diagnostics {
|
|
1004
|
+
position: absolute;
|
|
1005
|
+
right: 16px;
|
|
1006
|
+
bottom: 84px;
|
|
1007
|
+
z-index: 4;
|
|
1008
|
+
max-width: min(420px, calc(100vw - 32px));
|
|
1009
|
+
color: var(--plasius-ink);
|
|
1010
|
+
font-family: "JetBrains Mono", monospace;
|
|
1011
|
+
font-size: 12px;
|
|
1012
|
+
}
|
|
1013
|
+
.plasius-demo__diagnostics summary {
|
|
1014
|
+
width: fit-content;
|
|
1015
|
+
margin-left: auto;
|
|
1016
|
+
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
1017
|
+
border-radius: 6px;
|
|
1018
|
+
padding: 8px 10px;
|
|
1019
|
+
background: rgba(9, 20, 34, 0.84);
|
|
1020
|
+
cursor: pointer;
|
|
1021
|
+
list-style: none;
|
|
1022
|
+
}
|
|
1023
|
+
.plasius-demo__diagnostics summary::-webkit-details-marker {
|
|
1024
|
+
display: none;
|
|
1025
|
+
}
|
|
1026
|
+
.plasius-demo__diagnostics[open] {
|
|
1027
|
+
width: min(420px, calc(100vw - 32px));
|
|
1028
|
+
}
|
|
1029
|
+
.plasius-demo__diagnostics[open] summary {
|
|
1030
|
+
margin-bottom: 8px;
|
|
1031
|
+
background: rgba(243, 177, 106, 0.14);
|
|
1032
|
+
color: var(--plasius-accent);
|
|
1033
|
+
}
|
|
975
1034
|
.plasius-demo__sidebar {
|
|
976
1035
|
display: grid;
|
|
977
|
-
gap:
|
|
1036
|
+
gap: 8px;
|
|
1037
|
+
max-height: min(58vh, 520px);
|
|
1038
|
+
overflow: auto;
|
|
978
1039
|
}
|
|
979
1040
|
.plasius-demo__card {
|
|
980
|
-
padding:
|
|
1041
|
+
padding: 10px;
|
|
1042
|
+
}
|
|
1043
|
+
.plasius-demo__card h2 {
|
|
1044
|
+
margin: 0;
|
|
1045
|
+
color: rgba(226, 236, 255, 0.72);
|
|
1046
|
+
font-family: "JetBrains Mono", monospace;
|
|
1047
|
+
font-size: 11px;
|
|
1048
|
+
letter-spacing: 0.08em;
|
|
1049
|
+
text-transform: uppercase;
|
|
981
1050
|
}
|
|
982
1051
|
.plasius-demo__metrics,
|
|
983
1052
|
.plasius-demo__metrics li {
|
|
@@ -986,27 +1055,19 @@ function injectStyles() {
|
|
|
986
1055
|
list-style: none;
|
|
987
1056
|
}
|
|
988
1057
|
.plasius-demo__metrics {
|
|
989
|
-
margin-top:
|
|
1058
|
+
margin-top: 8px;
|
|
990
1059
|
display: grid;
|
|
991
|
-
gap:
|
|
1060
|
+
gap: 5px;
|
|
992
1061
|
color: var(--plasius-muted);
|
|
993
|
-
|
|
1062
|
+
font-size: 12px;
|
|
1063
|
+
line-height: 1.35;
|
|
994
1064
|
}
|
|
995
1065
|
.plasius-demo__metrics li {
|
|
996
1066
|
border-top: 1px solid rgba(21, 32, 40, 0.08);
|
|
997
|
-
padding-top:
|
|
1067
|
+
padding-top: 5px;
|
|
998
1068
|
}
|
|
999
1069
|
.plasius-demo__legend {
|
|
1000
|
-
|
|
1001
|
-
right: 24px;
|
|
1002
|
-
bottom: 24px;
|
|
1003
|
-
padding: 10px 14px;
|
|
1004
|
-
border-radius: 16px;
|
|
1005
|
-
background: rgba(9, 20, 34, 0.82);
|
|
1006
|
-
border: 1px solid rgba(159, 185, 223, 0.16);
|
|
1007
|
-
color: var(--plasius-muted);
|
|
1008
|
-
font-size: 12px;
|
|
1009
|
-
line-height: 1.45;
|
|
1070
|
+
display: none;
|
|
1010
1071
|
}
|
|
1011
1072
|
.plasius-demo__legend strong {
|
|
1012
1073
|
display: block;
|
|
@@ -1014,15 +1075,62 @@ function injectStyles() {
|
|
|
1014
1075
|
margin-bottom: 4px;
|
|
1015
1076
|
}
|
|
1016
1077
|
.plasius-demo__footer {
|
|
1017
|
-
|
|
1018
|
-
color: rgba(226, 236, 255, 0.68);
|
|
1019
|
-
font-size: 13px;
|
|
1020
|
-
line-height: 1.6;
|
|
1078
|
+
display: none;
|
|
1021
1079
|
}
|
|
1022
1080
|
@media (max-width: 1200px) {
|
|
1023
|
-
.plasius-
|
|
1024
|
-
|
|
1025
|
-
|
|
1081
|
+
.plasius-demo__toolbar {
|
|
1082
|
+
top: 92px;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
@media (max-width: 640px) {
|
|
1086
|
+
.plasius-demo__status {
|
|
1087
|
+
left: 10px;
|
|
1088
|
+
bottom: 10px;
|
|
1089
|
+
max-width: calc(100vw - 126px);
|
|
1090
|
+
padding: 6px 8px;
|
|
1091
|
+
}
|
|
1092
|
+
.plasius-demo__status-text {
|
|
1093
|
+
display: none;
|
|
1094
|
+
}
|
|
1095
|
+
.plasius-demo__status-badge {
|
|
1096
|
+
max-width: calc(100vw - 142px);
|
|
1097
|
+
overflow: hidden;
|
|
1098
|
+
text-overflow: ellipsis;
|
|
1099
|
+
white-space: nowrap;
|
|
1100
|
+
}
|
|
1101
|
+
.plasius-demo__toolbar {
|
|
1102
|
+
top: 10px;
|
|
1103
|
+
left: 10px;
|
|
1104
|
+
right: 10px;
|
|
1105
|
+
max-width: calc(100vw - 20px);
|
|
1106
|
+
flex-wrap: nowrap;
|
|
1107
|
+
overflow-x: auto;
|
|
1108
|
+
padding-bottom: 4px;
|
|
1109
|
+
scrollbar-width: none;
|
|
1110
|
+
}
|
|
1111
|
+
.plasius-demo__toolbar::-webkit-scrollbar {
|
|
1112
|
+
display: none;
|
|
1113
|
+
}
|
|
1114
|
+
.plasius-demo button,
|
|
1115
|
+
.plasius-demo .plasius-toggle,
|
|
1116
|
+
.plasius-demo select {
|
|
1117
|
+
padding: 7px 8px;
|
|
1118
|
+
font-size: 12px;
|
|
1119
|
+
white-space: nowrap;
|
|
1120
|
+
}
|
|
1121
|
+
.plasius-demo__diagnostics {
|
|
1122
|
+
right: 10px;
|
|
1123
|
+
bottom: 10px;
|
|
1124
|
+
}
|
|
1125
|
+
.plasius-demo__diagnostics[open] {
|
|
1126
|
+
bottom: 56px;
|
|
1127
|
+
left: 10px;
|
|
1128
|
+
right: 10px;
|
|
1129
|
+
width: auto;
|
|
1130
|
+
max-width: none;
|
|
1131
|
+
}
|
|
1132
|
+
.plasius-demo__diagnostics[open] .plasius-demo__sidebar {
|
|
1133
|
+
max-height: min(42vh, 340px);
|
|
1026
1134
|
}
|
|
1027
1135
|
}
|
|
1028
1136
|
`;
|
|
@@ -1364,11 +1472,12 @@ function buildTrianglesFromMesh(mesh, transform, colorOverride, camera, viewport
|
|
|
1364
1472
|
}
|
|
1365
1473
|
}
|
|
1366
1474
|
async function loadShowcaseAssetCatalog() {
|
|
1367
|
-
const [brigantine, cutter, lighthouse, harborDock] = await Promise.all([
|
|
1475
|
+
const [brigantine, cutter, lighthouse, harborDock, shoreline] = await Promise.all([
|
|
1368
1476
|
loadGltfModel(resolveShowcaseAssetUrl("brigantine")),
|
|
1369
1477
|
loadGltfModel(resolveShowcaseAssetUrl("cutter")),
|
|
1370
1478
|
loadGltfModel(resolveShowcaseAssetUrl("lighthouse")),
|
|
1371
|
-
loadGltfModel(resolveShowcaseAssetUrl("harbor-dock"))
|
|
1479
|
+
loadGltfModel(resolveShowcaseAssetUrl("harbor-dock")),
|
|
1480
|
+
loadGltfModel(resolveShowcaseAssetUrl("shoreline"))
|
|
1372
1481
|
]);
|
|
1373
1482
|
return Object.freeze({
|
|
1374
1483
|
primaryShipKey: "brigantine",
|
|
@@ -1378,7 +1487,8 @@ async function loadShowcaseAssetCatalog() {
|
|
|
1378
1487
|
}),
|
|
1379
1488
|
environment: Object.freeze({
|
|
1380
1489
|
lighthouse,
|
|
1381
|
-
"harbor-dock": harborDock
|
|
1490
|
+
"harbor-dock": harborDock,
|
|
1491
|
+
shoreline
|
|
1382
1492
|
})
|
|
1383
1493
|
});
|
|
1384
1494
|
}
|
|
@@ -1504,24 +1614,27 @@ function buildDemoDom(root, options) {
|
|
|
1504
1614
|
${t(gpuSharedTranslationKeys.legendCollisions)}
|
|
1505
1615
|
</div>
|
|
1506
1616
|
</section>
|
|
1507
|
-
<
|
|
1508
|
-
<
|
|
1509
|
-
|
|
1510
|
-
<
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
<
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
<
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
<
|
|
1523
|
-
|
|
1524
|
-
|
|
1617
|
+
<details class="plasius-demo__diagnostics">
|
|
1618
|
+
<summary>${t(gpuSharedTranslationKeys.debugTelemetry)}</summary>
|
|
1619
|
+
<aside class="plasius-demo__sidebar">
|
|
1620
|
+
<section class="plasius-panel plasius-demo__card">
|
|
1621
|
+
<h2>${t(gpuSharedTranslationKeys.sceneState)}</h2>
|
|
1622
|
+
<ul id="sceneMetrics" class="plasius-demo__metrics"></ul>
|
|
1623
|
+
</section>
|
|
1624
|
+
<section class="plasius-panel plasius-demo__card">
|
|
1625
|
+
<h2>${t(gpuSharedTranslationKeys.qualityBudgets)}</h2>
|
|
1626
|
+
<ul id="qualityMetrics" class="plasius-demo__metrics"></ul>
|
|
1627
|
+
</section>
|
|
1628
|
+
<section class="plasius-panel plasius-demo__card">
|
|
1629
|
+
<h2>${t(gpuSharedTranslationKeys.debugTelemetry)}</h2>
|
|
1630
|
+
<ul id="debugMetrics" class="plasius-demo__metrics"></ul>
|
|
1631
|
+
</section>
|
|
1632
|
+
<section class="plasius-panel plasius-demo__card">
|
|
1633
|
+
<h2>${t(gpuSharedTranslationKeys.notes)}</h2>
|
|
1634
|
+
<ul id="sceneNotes" class="plasius-demo__metrics"></ul>
|
|
1635
|
+
</section>
|
|
1636
|
+
</aside>
|
|
1637
|
+
</details>
|
|
1525
1638
|
</section>
|
|
1526
1639
|
<p class="plasius-demo__footer">
|
|
1527
1640
|
This visual example is shared across the GPU packages to keep manual validation fast and consistent.
|
|
@@ -1885,11 +1998,11 @@ function advanceShowcaseClothSimulationState(clothState, options = {}) {
|
|
|
1885
1998
|
1 + Math.sin(gustPhase * 0.74) * 0.18
|
|
1886
1999
|
)
|
|
1887
2000
|
);
|
|
1888
|
-
const windStrength = (
|
|
2001
|
+
const windStrength = (0.94 + broadMotion * 0.82 + wrinkleLayers * 0.08) * flagMotion * (0.36 + u * 0.92);
|
|
1889
2002
|
const wrinkleForce = vec3(
|
|
1890
|
-
Math.sin(wrinklePhase) * 0.
|
|
1891
|
-
Math.cos(wrinklePhase * 0.7) * 0.
|
|
1892
|
-
Math.cos(wrinklePhase) * 0.
|
|
2003
|
+
Math.sin(wrinklePhase) * 0.12 * wrinkleMotion * flagMotion,
|
|
2004
|
+
Math.cos(wrinklePhase * 0.7) * 0.045 * wrinkleMotion,
|
|
2005
|
+
Math.cos(wrinklePhase) * 0.08 * broadMotion * flagMotion
|
|
1893
2006
|
);
|
|
1894
2007
|
const acceleration = addVec3(
|
|
1895
2008
|
vec3(0, -0.48 - u * 0.08, 0),
|
|
@@ -1946,25 +2059,25 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
1946
2059
|
ambientMist: "rgba(41, 63, 97, 0.16)",
|
|
1947
2060
|
reflectionStrength: lightingSnapshot.currentLevel.config.reflectionStrength,
|
|
1948
2061
|
shadowAccent: lightingSnapshot.currentLevel.config.shadowStrength,
|
|
1949
|
-
waveAmplitude: 0.
|
|
2062
|
+
waveAmplitude: 0.82,
|
|
1950
2063
|
waveDirection: { x: 0.88, z: 0.28 },
|
|
1951
|
-
wavePhaseSpeed: 0.
|
|
1952
|
-
wakeStrength: 0.
|
|
1953
|
-
wakeLength:
|
|
1954
|
-
collisionRippleStrength: 0.
|
|
1955
|
-
waterNear: { r: 0.
|
|
1956
|
-
waterFar: { r: 0.
|
|
2064
|
+
wavePhaseSpeed: 0.74,
|
|
2065
|
+
wakeStrength: 0.24,
|
|
2066
|
+
wakeLength: 17,
|
|
2067
|
+
collisionRippleStrength: 0.22,
|
|
2068
|
+
waterNear: { r: 0.05, g: 0.2, b: 0.3 },
|
|
2069
|
+
waterFar: { r: 0.13, g: 0.31, b: 0.45 },
|
|
1957
2070
|
harborWall: { r: 0.26, g: 0.24, b: 0.28 },
|
|
1958
2071
|
harborDeck: { r: 0.33, g: 0.22, b: 0.16 },
|
|
1959
2072
|
harborTower: { r: 0.23, g: 0.24, b: 0.29 },
|
|
1960
|
-
flagColor: { r: 0.
|
|
1961
|
-
flagMotion: 0.
|
|
2073
|
+
flagColor: { r: 0.54, g: 0.13, b: 0.11 },
|
|
2074
|
+
flagMotion: 0.58,
|
|
1962
2075
|
lanternCore: { r: 0.98, g: 0.8, b: 0.48 },
|
|
1963
2076
|
lanternGlow: { r: 1, g: 0.56, b: 0.2 },
|
|
1964
2077
|
lanternReflectionStrength: 0.42,
|
|
1965
2078
|
torchCore: { r: 0.99, g: 0.72, b: 0.36 },
|
|
1966
2079
|
torchGlow: { r: 0.98, g: 0.38, b: 0.15 },
|
|
1967
|
-
collisionFlash: "rgba(255, 212, 168, 0.
|
|
2080
|
+
collisionFlash: "rgba(255, 212, 168, 0.08)"
|
|
1968
2081
|
};
|
|
1969
2082
|
return {
|
|
1970
2083
|
skyTop: typeof customVisuals.skyTop === "string" ? customVisuals.skyTop : defaults.skyTop,
|
|
@@ -2021,6 +2134,11 @@ function buildClothSurface(model, state, meshDetail, visuals, clothFeatures) {
|
|
|
2021
2134
|
representation: clothPresentation.representation,
|
|
2022
2135
|
continuity: clothPresentation.continuity,
|
|
2023
2136
|
color: visuals.flagColor,
|
|
2137
|
+
material: Object.freeze({
|
|
2138
|
+
weaveAlpha: clothPresentation.band === "near" ? 0.22 : 0.12,
|
|
2139
|
+
foldAlpha: clothPresentation.band === "near" ? 0.3 : 0.18,
|
|
2140
|
+
edgeHighlightAlpha: clothPresentation.band === "near" ? 0.42 : 0.28
|
|
2141
|
+
}),
|
|
2024
2142
|
positions: clothState.positions.map((point) => vec3(point.x, point.y, point.z)),
|
|
2025
2143
|
indices: clothState.indices,
|
|
2026
2144
|
grid: { rows: clothState.rows, cols: clothState.cols }
|
|
@@ -2104,7 +2222,7 @@ function buildWaterMotionEffects(state) {
|
|
|
2104
2222
|
impulse.z
|
|
2105
2223
|
),
|
|
2106
2224
|
radius,
|
|
2107
|
-
opacity: clamp(impulse.life * 0.
|
|
2225
|
+
opacity: clamp(impulse.life * 0.13, 0.035, 0.15)
|
|
2108
2226
|
});
|
|
2109
2227
|
});
|
|
2110
2228
|
for (const ship of state.ships) {
|
|
@@ -2117,7 +2235,7 @@ function buildWaterMotionEffects(state) {
|
|
|
2117
2235
|
const lateral = vec3(-direction.z, 0, direction.x);
|
|
2118
2236
|
const points = [];
|
|
2119
2237
|
for (let sampleIndex = 0; sampleIndex < 6; sampleIndex += 1) {
|
|
2120
|
-
const along = 1 + sampleIndex * 1.
|
|
2238
|
+
const along = 1 + sampleIndex * 1.55;
|
|
2121
2239
|
const lateralOffset = Math.sin(state.time * 1.2 + sampleIndex * 0.8 + readVisualNumber(ship.wanderPhase, 0)) * 0.12;
|
|
2122
2240
|
const worldPoint = addVec3(
|
|
2123
2241
|
ship.position,
|
|
@@ -2130,13 +2248,14 @@ function buildWaterMotionEffects(state) {
|
|
|
2130
2248
|
sampleWave(state, worldPoint.x, worldPoint.z, state.time) * 0.24 + 0.04,
|
|
2131
2249
|
worldPoint.z
|
|
2132
2250
|
),
|
|
2133
|
-
width: 0.
|
|
2251
|
+
width: 0.3 + sampleIndex * 0.11,
|
|
2252
|
+
foam: clamp(0.28 - sampleIndex * 0.028 + speed * 0.025, 0.1, 0.34)
|
|
2134
2253
|
})
|
|
2135
2254
|
);
|
|
2136
2255
|
}
|
|
2137
2256
|
wakeTrails.push(
|
|
2138
2257
|
Object.freeze({
|
|
2139
|
-
opacity: clamp(0.
|
|
2258
|
+
opacity: clamp(0.1 + speed * 0.048, 0.12, 0.24),
|
|
2140
2259
|
points: Object.freeze(points)
|
|
2141
2260
|
})
|
|
2142
2261
|
);
|
|
@@ -2146,6 +2265,27 @@ function buildWaterMotionEffects(state) {
|
|
|
2146
2265
|
rippleRings: Object.freeze(rippleRings)
|
|
2147
2266
|
});
|
|
2148
2267
|
}
|
|
2268
|
+
function buildShorelineFoamSegments(state) {
|
|
2269
|
+
return Object.freeze(
|
|
2270
|
+
SHORELINE_FOAM_ANCHORS.map((anchor, index) => {
|
|
2271
|
+
const pulse = 0.5 + Math.sin(state.time * 0.84 + index * 1.17) * 0.5;
|
|
2272
|
+
const drift = Math.sin(state.time * 0.38 + index * 0.61) * 0.1;
|
|
2273
|
+
const direction = normalizeVec3(vec3(Math.cos(anchor.angle), 0, Math.sin(anchor.angle)));
|
|
2274
|
+
const center = vec3(
|
|
2275
|
+
anchor.x + direction.x * drift,
|
|
2276
|
+
sampleWave(state, anchor.x, anchor.z, state.time) * 0.12 - 0.02,
|
|
2277
|
+
anchor.z + direction.z * drift
|
|
2278
|
+
);
|
|
2279
|
+
return Object.freeze({
|
|
2280
|
+
center,
|
|
2281
|
+
direction,
|
|
2282
|
+
length: anchor.length * (0.78 + pulse * 0.34),
|
|
2283
|
+
width: 0.16 + pulse * 0.12,
|
|
2284
|
+
opacity: 0.07 + pulse * 0.12
|
|
2285
|
+
});
|
|
2286
|
+
})
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2149
2289
|
function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
2150
2290
|
const resolvedFluidFeatures = normalizeFluidFeatureAdapters(fluidFeatures);
|
|
2151
2291
|
const fluidPlan = resolvedFluidFeatures.createPlan({
|
|
@@ -2168,9 +2308,9 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
2168
2308
|
const representation = fluidPlan.representations.find((entry) => entry.band === bandSpec.band) ?? fluidPlan.representations[0];
|
|
2169
2309
|
const continuity = resolvedFluidFeatures.createContinuityEnvelope({ fluidBodyId: "harbor" });
|
|
2170
2310
|
const bandContinuity = resolveFluidBandContinuity(continuity, bandSpec.band);
|
|
2171
|
-
const bandResolution = bandSpec.band === "near" ? fluidDetail.nearResolution : bandSpec.band === "mid" ? fluidDetail.midResolution : bandSpec.band === "far" ? 5 : 3;
|
|
2172
|
-
const cols = Math.max(4, bandResolution * 2);
|
|
2173
|
-
const rows = Math.max(4, bandResolution + 2);
|
|
2311
|
+
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;
|
|
2312
|
+
const cols = Math.max(4, bandResolution * (bandSpec.band === "near" ? 3 : 2));
|
|
2313
|
+
const rows = Math.max(4, bandResolution + (bandSpec.band === "near" ? 5 : 2));
|
|
2174
2314
|
const positions = [];
|
|
2175
2315
|
const indices = [];
|
|
2176
2316
|
const originX = -bandSpec.width * 0.5;
|
|
@@ -2181,7 +2321,9 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
2181
2321
|
const v = row / (rows - 1);
|
|
2182
2322
|
const x = originX + bandSpec.width * u;
|
|
2183
2323
|
const z = originZ + bandSpec.depth * v;
|
|
2184
|
-
const
|
|
2324
|
+
const baseHeight = bandSpec.y + sampleWave(state, x, z, state.time) * bandContinuity.amplitudeFloor * (bandSpec.band === "near" ? 0.9 : bandSpec.band === "mid" ? 0.55 : 0.3);
|
|
2325
|
+
const detailHeight = bandSpec.band === "near" ? Math.sin(x * 1.25 + z * 0.42 - state.time * 2.4) * 0.035 : 0;
|
|
2326
|
+
const y = baseHeight + detailHeight;
|
|
2185
2327
|
positions.push(vec3(x, y, z));
|
|
2186
2328
|
}
|
|
2187
2329
|
}
|
|
@@ -2210,7 +2352,12 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
2210
2352
|
r: mix(visuals.waterFar.r, 0.76, 0.2),
|
|
2211
2353
|
g: mix(visuals.waterFar.g, 0.78, 0.2),
|
|
2212
2354
|
b: mix(visuals.waterFar.b, 0.82, 0.2)
|
|
2213
|
-
}
|
|
2355
|
+
},
|
|
2356
|
+
material: Object.freeze({
|
|
2357
|
+
highlightAlpha: bandSpec.band === "near" ? 0.2 : bandSpec.band === "mid" ? 0.13 : 0.07,
|
|
2358
|
+
foamAlpha: bandSpec.band === "near" ? 0.28 : bandSpec.band === "mid" ? 0.14 : 0.05,
|
|
2359
|
+
microRippleScale: bandSpec.band === "near" ? 1 : bandSpec.band === "mid" ? 0.58 : 0.28
|
|
2360
|
+
})
|
|
2214
2361
|
});
|
|
2215
2362
|
}
|
|
2216
2363
|
return { fluidPlan, bandMeshes };
|
|
@@ -2286,15 +2433,15 @@ function createSceneState(options, featureAdapters) {
|
|
|
2286
2433
|
{
|
|
2287
2434
|
id: "northwind",
|
|
2288
2435
|
modelKey: "brigantine",
|
|
2289
|
-
position: vec3(-
|
|
2290
|
-
velocity: vec3(
|
|
2291
|
-
rotationY:
|
|
2292
|
-
angularVelocity: 0.
|
|
2436
|
+
position: vec3(-7.8, 0, 11.2),
|
|
2437
|
+
velocity: vec3(1.08, 0, -0.18),
|
|
2438
|
+
rotationY: 1.38,
|
|
2439
|
+
angularVelocity: 0.025,
|
|
2293
2440
|
tint: { r: 0.62, g: 0.39, b: 0.23 },
|
|
2294
2441
|
massScale: 1.42,
|
|
2295
|
-
cruiseSpeed:
|
|
2296
|
-
throttleResponse: 0.
|
|
2297
|
-
rudderResponse: 0.
|
|
2442
|
+
cruiseSpeed: 1.22,
|
|
2443
|
+
throttleResponse: 0.36,
|
|
2444
|
+
rudderResponse: 0.4,
|
|
2298
2445
|
wanderPhase: 0.35,
|
|
2299
2446
|
lanterns: CUTTER_LANTERNS,
|
|
2300
2447
|
lanternStrength: 1.06,
|
|
@@ -2303,15 +2450,15 @@ function createSceneState(options, featureAdapters) {
|
|
|
2303
2450
|
{
|
|
2304
2451
|
id: "tidecaller",
|
|
2305
2452
|
modelKey: "cutter",
|
|
2306
|
-
position: vec3(
|
|
2307
|
-
velocity: vec3(-
|
|
2308
|
-
rotationY: -
|
|
2309
|
-
angularVelocity: -0.
|
|
2453
|
+
position: vec3(6.8, 0, 5.4),
|
|
2454
|
+
velocity: vec3(-0.82, 0, 0.14),
|
|
2455
|
+
rotationY: -1.34,
|
|
2456
|
+
angularVelocity: -0.035,
|
|
2310
2457
|
tint: { r: 0.58, g: 0.24, b: 0.16 },
|
|
2311
2458
|
massScale: 0.84,
|
|
2312
|
-
cruiseSpeed:
|
|
2313
|
-
throttleResponse: 0.
|
|
2314
|
-
rudderResponse: 0.
|
|
2459
|
+
cruiseSpeed: 1.36,
|
|
2460
|
+
throttleResponse: 0.52,
|
|
2461
|
+
rudderResponse: 0.58,
|
|
2315
2462
|
wanderPhase: 1.6,
|
|
2316
2463
|
lanterns: SHIP_LANTERNS,
|
|
2317
2464
|
lanternStrength: 1.18,
|
|
@@ -2610,9 +2757,10 @@ function renderShipRigging(ctx, ship, camera, viewport) {
|
|
|
2610
2757
|
}
|
|
2611
2758
|
function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
2612
2759
|
const projected = cloth.positions.map((point) => projectPoint(point, camera, viewport));
|
|
2613
|
-
|
|
2614
|
-
ctx.
|
|
2615
|
-
|
|
2760
|
+
const material = cloth.material ?? {};
|
|
2761
|
+
ctx.strokeStyle = `rgba(255, 241, 226, ${material.foldAlpha ?? 0.32})`;
|
|
2762
|
+
ctx.lineWidth = 1.8;
|
|
2763
|
+
for (let row = 0; row < cloth.grid.rows; row += Math.max(1, Math.floor(cloth.grid.rows / 6))) {
|
|
2616
2764
|
ctx.beginPath();
|
|
2617
2765
|
let started = false;
|
|
2618
2766
|
for (let column = 0; column < cloth.grid.cols; column += 1) {
|
|
@@ -2631,6 +2779,27 @@ function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
|
2631
2779
|
ctx.stroke();
|
|
2632
2780
|
}
|
|
2633
2781
|
}
|
|
2782
|
+
ctx.strokeStyle = `rgba(255, 228, 204, ${material.weaveAlpha ?? 0.22})`;
|
|
2783
|
+
ctx.lineWidth = 0.85;
|
|
2784
|
+
for (let column = 1; column < cloth.grid.cols - 1; column += Math.max(1, Math.floor(cloth.grid.cols / 8))) {
|
|
2785
|
+
ctx.beginPath();
|
|
2786
|
+
let started = false;
|
|
2787
|
+
for (let row = 0; row < cloth.grid.rows; row += 1) {
|
|
2788
|
+
const point = projected[row * cloth.grid.cols + column];
|
|
2789
|
+
if (!point) {
|
|
2790
|
+
continue;
|
|
2791
|
+
}
|
|
2792
|
+
if (!started) {
|
|
2793
|
+
ctx.moveTo(point.x, point.y);
|
|
2794
|
+
started = true;
|
|
2795
|
+
} else {
|
|
2796
|
+
ctx.lineTo(point.x, point.y);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
if (started) {
|
|
2800
|
+
ctx.stroke();
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2634
2803
|
const borderIndices = [
|
|
2635
2804
|
0,
|
|
2636
2805
|
cloth.grid.cols - 1,
|
|
@@ -2638,6 +2807,25 @@ function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
|
2638
2807
|
(cloth.grid.rows - 1) * cloth.grid.cols
|
|
2639
2808
|
];
|
|
2640
2809
|
ctx.fillStyle = colorToRgba(cloth.color, 0.95);
|
|
2810
|
+
ctx.strokeStyle = `rgba(255, 246, 236, ${material.edgeHighlightAlpha ?? 0.5})`;
|
|
2811
|
+
ctx.lineWidth = 1.4;
|
|
2812
|
+
ctx.beginPath();
|
|
2813
|
+
let borderStarted = false;
|
|
2814
|
+
for (let column = 0; column < cloth.grid.cols; column += 1) {
|
|
2815
|
+
const point = projected[column];
|
|
2816
|
+
if (!point) {
|
|
2817
|
+
continue;
|
|
2818
|
+
}
|
|
2819
|
+
if (!borderStarted) {
|
|
2820
|
+
ctx.moveTo(point.x, point.y);
|
|
2821
|
+
borderStarted = true;
|
|
2822
|
+
} else {
|
|
2823
|
+
ctx.lineTo(point.x, point.y);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
if (borderStarted) {
|
|
2827
|
+
ctx.stroke();
|
|
2828
|
+
}
|
|
2641
2829
|
for (const index of borderIndices) {
|
|
2642
2830
|
const point = projected[index];
|
|
2643
2831
|
if (!point) {
|
|
@@ -2653,14 +2841,22 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
2653
2841
|
if (band.band === "horizon") {
|
|
2654
2842
|
continue;
|
|
2655
2843
|
}
|
|
2656
|
-
const interval = band.band === "near" ?
|
|
2657
|
-
const alpha = band.band === "near" ? 0.22 : 0.14;
|
|
2844
|
+
const interval = band.band === "near" ? 4 : 5;
|
|
2845
|
+
const alpha = band.material?.highlightAlpha ?? (band.band === "near" ? 0.22 : 0.14);
|
|
2658
2846
|
ctx.strokeStyle = `rgba(232, 247, 255, ${alpha})`;
|
|
2659
|
-
ctx.lineWidth = band.band === "near" ?
|
|
2847
|
+
ctx.lineWidth = band.band === "near" ? 0.9 : 0.65;
|
|
2660
2848
|
for (let row = interval; row < band.rows - 1; row += interval) {
|
|
2661
|
-
ctx.beginPath();
|
|
2662
2849
|
let started = false;
|
|
2663
|
-
|
|
2850
|
+
ctx.beginPath();
|
|
2851
|
+
for (let column = 0; column < band.cols; column += band.band === "near" ? 2 : 3) {
|
|
2852
|
+
if (pseudoRandom(row * 47 + column * 13) < 0.18) {
|
|
2853
|
+
if (started) {
|
|
2854
|
+
ctx.stroke();
|
|
2855
|
+
ctx.beginPath();
|
|
2856
|
+
started = false;
|
|
2857
|
+
}
|
|
2858
|
+
continue;
|
|
2859
|
+
}
|
|
2664
2860
|
const point = projectPoint(
|
|
2665
2861
|
band.positions[row * band.cols + column],
|
|
2666
2862
|
camera,
|
|
@@ -2680,7 +2876,55 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
2680
2876
|
ctx.stroke();
|
|
2681
2877
|
}
|
|
2682
2878
|
}
|
|
2879
|
+
if (band.band === "near") {
|
|
2880
|
+
ctx.fillStyle = `rgba(236, 249, 255, ${(band.material?.foamAlpha ?? 0.28) * 0.72})`;
|
|
2881
|
+
for (let column = 3; column < band.cols - 3; column += 10) {
|
|
2882
|
+
const point = projectPoint(
|
|
2883
|
+
band.positions[Math.floor(band.rows * 0.42) * band.cols + column],
|
|
2884
|
+
camera,
|
|
2885
|
+
viewport
|
|
2886
|
+
);
|
|
2887
|
+
if (!point) {
|
|
2888
|
+
continue;
|
|
2889
|
+
}
|
|
2890
|
+
ctx.beginPath();
|
|
2891
|
+
ctx.ellipse(point.x, point.y, 1.8, 0.75, -0.2, 0, Math.PI * 2);
|
|
2892
|
+
ctx.fill();
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
function renderShorelineFoamSegments(ctx, segments, camera, viewport) {
|
|
2898
|
+
ctx.save();
|
|
2899
|
+
ctx.globalCompositeOperation = "screen";
|
|
2900
|
+
ctx.lineCap = "round";
|
|
2901
|
+
ctx.lineJoin = "round";
|
|
2902
|
+
for (const segment of segments) {
|
|
2903
|
+
const half = scaleVec3(segment.direction, segment.length * 0.5);
|
|
2904
|
+
const start = projectPoint(subVec3(segment.center, half), camera, viewport);
|
|
2905
|
+
const end = projectPoint(addVec3(segment.center, half), camera, viewport);
|
|
2906
|
+
const center = projectPoint(segment.center, camera, viewport);
|
|
2907
|
+
if (!start || !end || !center) {
|
|
2908
|
+
continue;
|
|
2909
|
+
}
|
|
2910
|
+
const depthScale = clamp(140 / Math.max(12, center.depth), 3, 10);
|
|
2911
|
+
ctx.strokeStyle = `rgba(232, 242, 238, ${segment.opacity})`;
|
|
2912
|
+
ctx.lineWidth = clamp(segment.width * depthScale, 0.8, 2.8);
|
|
2913
|
+
ctx.beginPath();
|
|
2914
|
+
ctx.moveTo(start.x, start.y);
|
|
2915
|
+
ctx.quadraticCurveTo(
|
|
2916
|
+
center.x,
|
|
2917
|
+
center.y + Math.sin(segment.center.x * 1.7) * 2.4,
|
|
2918
|
+
end.x,
|
|
2919
|
+
end.y
|
|
2920
|
+
);
|
|
2921
|
+
ctx.stroke();
|
|
2922
|
+
ctx.fillStyle = `rgba(248, 251, 246, ${segment.opacity * 0.68})`;
|
|
2923
|
+
ctx.beginPath();
|
|
2924
|
+
ctx.ellipse(center.x, center.y, depthScale * 0.18, depthScale * 0.08, -0.2, 0, Math.PI * 2);
|
|
2925
|
+
ctx.fill();
|
|
2683
2926
|
}
|
|
2927
|
+
ctx.restore();
|
|
2684
2928
|
}
|
|
2685
2929
|
function readPhysicsNumber(physics, key, fallback) {
|
|
2686
2930
|
const value = physics?.[key];
|
|
@@ -2712,14 +2956,17 @@ function getShipInverseInertia(ship, shipModel) {
|
|
|
2712
2956
|
return 1 / Math.max(1, inertia);
|
|
2713
2957
|
}
|
|
2714
2958
|
function spawnSpray(state, point, intensity) {
|
|
2715
|
-
const count =
|
|
2959
|
+
const count = Math.max(
|
|
2960
|
+
3,
|
|
2961
|
+
Math.ceil(state.fluidDetail.getSnapshot().currentLevel.config.splashCount * 0.32)
|
|
2962
|
+
);
|
|
2716
2963
|
for (let index = 0; index < count; index += 1) {
|
|
2717
2964
|
const angle = index / count * Math.PI * 2;
|
|
2718
|
-
const speed = 0.
|
|
2965
|
+
const speed = 0.46 + Math.random() * intensity * 0.24;
|
|
2719
2966
|
state.sprays.push({
|
|
2720
2967
|
position: vec3(point.x, point.y, point.z),
|
|
2721
|
-
velocity: vec3(Math.cos(angle) * speed * 0.
|
|
2722
|
-
life:
|
|
2968
|
+
velocity: vec3(Math.cos(angle) * speed * 0.24, 0.46 + Math.random() * 0.34, Math.sin(angle) * speed * 0.18),
|
|
2969
|
+
life: 0.72 + Math.random() * 0.22
|
|
2723
2970
|
});
|
|
2724
2971
|
}
|
|
2725
2972
|
}
|
|
@@ -2734,7 +2981,7 @@ function resolveShipRoute(ship, state, radius) {
|
|
|
2734
2981
|
}
|
|
2735
2982
|
const wander = Math.sin(state.time * 0.22 + readVisualNumber(ship.wanderPhase, 0));
|
|
2736
2983
|
const crossCurrent = Math.cos(state.time * 0.31 + readVisualNumber(ship.wanderPhase, 0));
|
|
2737
|
-
const laneCenter = ship.id === "northwind" ?
|
|
2984
|
+
const laneCenter = ship.id === "northwind" ? 11.6 + wander * 0.82 + crossCurrent * 0.24 : 5.4 + wander * 0.94 - crossCurrent * 0.32;
|
|
2738
2985
|
const targetX = ship.routeDirection > 0 ? HARBOR_BOUNDS.maxX - radius * 1.7 : HARBOR_BOUNDS.minX + radius * 1.7;
|
|
2739
2986
|
return vec3(targetX, 0, clamp(laneCenter, HARBOR_BOUNDS.minZ + 1.8, HARBOR_BOUNDS.maxZ - 1.8));
|
|
2740
2987
|
}
|
|
@@ -2747,7 +2994,7 @@ function updateShipMotion(state, ship, dt, shipModel) {
|
|
|
2747
2994
|
const angularDamping = readPhysicsNumber(physics, "angularDamping", 0.08);
|
|
2748
2995
|
const throttleResponse = readVisualNumber(ship.throttleResponse, 0.58);
|
|
2749
2996
|
const rudderResponse = readVisualNumber(ship.rudderResponse, 0.62);
|
|
2750
|
-
const cruiseSpeed = readVisualNumber(ship.cruiseSpeed,
|
|
2997
|
+
const cruiseSpeed = readVisualNumber(ship.cruiseSpeed, 1.25);
|
|
2751
2998
|
ship.collisionCooldown = Math.max(0, readVisualNumber(ship.collisionCooldown, 0) - dt);
|
|
2752
2999
|
const forward = directionFromYaw(ship.rotationY);
|
|
2753
3000
|
const lateral = perpendicularOnWater(forward);
|
|
@@ -2842,7 +3089,7 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
|
2842
3089
|
b.position = addVec3(b.position, scaleVec3(correction, invMassB));
|
|
2843
3090
|
const relativeVelocity = subVec3(b.velocity, a.velocity);
|
|
2844
3091
|
const velocityAlongNormal = dotVec3(relativeVelocity, normal);
|
|
2845
|
-
const restitution = (readPhysicsNumber(shipModelA.physics, "restitution", 0.22) + readPhysicsNumber(shipModelB.physics, "restitution", 0.22)) / 2 * 0.
|
|
3092
|
+
const restitution = (readPhysicsNumber(shipModelA.physics, "restitution", 0.22) + readPhysicsNumber(shipModelB.physics, "restitution", 0.22)) / 2 * 0.42;
|
|
2846
3093
|
if (velocityAlongNormal < 0) {
|
|
2847
3094
|
const impulseMagnitude = -(1 + restitution) * velocityAlongNormal / Math.max(1e-4, invMassSum);
|
|
2848
3095
|
const impulse = scaleVec3(normal, impulseMagnitude);
|
|
@@ -2860,27 +3107,27 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
|
2860
3107
|
a.angularVelocity -= tangentSpeed * radiusA * getShipInverseInertia(a, shipModelA) * 0.2 + impulseMagnitude * 24e-5;
|
|
2861
3108
|
b.angularVelocity += tangentSpeed * radiusB * getShipInverseInertia(b, shipModelB) * 0.2 + impulseMagnitude * 24e-5;
|
|
2862
3109
|
const impactSpeed = Math.abs(velocityAlongNormal);
|
|
2863
|
-
if (impactSpeed > 0.
|
|
3110
|
+
if (impactSpeed > 0.36 && Math.max(readVisualNumber(a.collisionCooldown, 0), readVisualNumber(b.collisionCooldown, 0)) <= 0) {
|
|
2864
3111
|
const contactPoint = vec3(
|
|
2865
3112
|
(a.position.x + b.position.x) * 0.5,
|
|
2866
3113
|
(a.position.y + b.position.y) * 0.5 + 0.14,
|
|
2867
3114
|
(a.position.z + b.position.z) * 0.5
|
|
2868
3115
|
);
|
|
2869
|
-
spawnSpray(state, contactPoint, impactSpeed *
|
|
3116
|
+
spawnSpray(state, contactPoint, impactSpeed * 0.9 + penetration * 2.4);
|
|
2870
3117
|
state.waveImpulses.push({
|
|
2871
3118
|
x: contactPoint.x,
|
|
2872
3119
|
z: contactPoint.z,
|
|
2873
|
-
strength: clamp(0.
|
|
2874
|
-
radius: 0.
|
|
3120
|
+
strength: clamp(0.1 + impactSpeed * 0.18 + penetration * 0.28, 0.08, 0.52),
|
|
3121
|
+
radius: 0.72 + penetration * 0.72,
|
|
2875
3122
|
life: 1
|
|
2876
3123
|
});
|
|
2877
3124
|
state.collisionCount += 1;
|
|
2878
3125
|
state.collisionFlash = Math.max(
|
|
2879
3126
|
state.collisionFlash,
|
|
2880
|
-
clamp(impactSpeed * 0.
|
|
3127
|
+
clamp(impactSpeed * 0.14 + penetration * 0.32, 0.04, 0.24)
|
|
2881
3128
|
);
|
|
2882
|
-
a.collisionCooldown = 0.
|
|
2883
|
-
b.collisionCooldown = 0.
|
|
3129
|
+
a.collisionCooldown = 0.72;
|
|
3130
|
+
b.collisionCooldown = 0.72;
|
|
2884
3131
|
}
|
|
2885
3132
|
}
|
|
2886
3133
|
state.contactCount += 1;
|
|
@@ -2903,7 +3150,7 @@ function updateShips(state, dt, shipModel) {
|
|
|
2903
3150
|
collided = resolveShipCollision(state, shipA, shipB, shipModelA, shipModelB) || collided;
|
|
2904
3151
|
}
|
|
2905
3152
|
}
|
|
2906
|
-
state.collisionFlash = collided ? Math.max(0.
|
|
3153
|
+
state.collisionFlash = collided ? Math.max(0.04, state.collisionFlash) : Math.max(0, state.collisionFlash - dt * 1.7);
|
|
2907
3154
|
}
|
|
2908
3155
|
function updateWaveImpulses(state, dt) {
|
|
2909
3156
|
state.waveImpulses = state.waveImpulses.map((impulse) => ({
|
|
@@ -3265,7 +3512,8 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3265
3512
|
for (const wake of effects.wakeTrails) {
|
|
3266
3513
|
const projected = wake.points.map((point) => ({
|
|
3267
3514
|
projected: projectPoint(point.center, camera, viewport),
|
|
3268
|
-
width: point.width
|
|
3515
|
+
width: point.width,
|
|
3516
|
+
foam: point.foam
|
|
3269
3517
|
})).filter((entry) => entry.projected);
|
|
3270
3518
|
if (projected.length < 2) {
|
|
3271
3519
|
continue;
|
|
@@ -3273,8 +3521,8 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3273
3521
|
const averageDepth = projected.reduce((total, entry) => total + entry.projected.depth, 0) / projected.length;
|
|
3274
3522
|
const averageWidth = projected.reduce((total, entry) => total + entry.width, 0) / projected.length;
|
|
3275
3523
|
const baseWidth = clamp(averageWidth / Math.max(0.25, averageDepth) * 180, 1.6, 5.4);
|
|
3276
|
-
ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.
|
|
3277
|
-
ctx.lineWidth = baseWidth * 1.
|
|
3524
|
+
ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.34})`;
|
|
3525
|
+
ctx.lineWidth = baseWidth * 1.45;
|
|
3278
3526
|
ctx.lineCap = "round";
|
|
3279
3527
|
ctx.lineJoin = "round";
|
|
3280
3528
|
ctx.beginPath();
|
|
@@ -3283,8 +3531,8 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3283
3531
|
ctx.lineTo(projected[index].projected.x, projected[index].projected.y);
|
|
3284
3532
|
}
|
|
3285
3533
|
ctx.stroke();
|
|
3286
|
-
ctx.strokeStyle = `rgba(234, 247, 255, ${wake.opacity})`;
|
|
3287
|
-
ctx.lineWidth = baseWidth;
|
|
3534
|
+
ctx.strokeStyle = `rgba(234, 247, 255, ${wake.opacity * 0.72})`;
|
|
3535
|
+
ctx.lineWidth = baseWidth * 0.72;
|
|
3288
3536
|
ctx.lineCap = "round";
|
|
3289
3537
|
ctx.lineJoin = "round";
|
|
3290
3538
|
ctx.beginPath();
|
|
@@ -3294,13 +3542,14 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3294
3542
|
}
|
|
3295
3543
|
ctx.stroke();
|
|
3296
3544
|
for (const entry of projected.slice(1, 5)) {
|
|
3297
|
-
|
|
3545
|
+
const foam = entry.foam ?? 0.3;
|
|
3546
|
+
ctx.fillStyle = `rgba(239, 248, 255, ${wake.opacity * foam * 0.92})`;
|
|
3298
3547
|
ctx.beginPath();
|
|
3299
3548
|
ctx.ellipse(
|
|
3300
3549
|
entry.projected.x,
|
|
3301
3550
|
entry.projected.y,
|
|
3302
|
-
baseWidth * 0.
|
|
3303
|
-
baseWidth * 0.
|
|
3551
|
+
baseWidth * 0.54,
|
|
3552
|
+
baseWidth * 0.28,
|
|
3304
3553
|
0,
|
|
3305
3554
|
0,
|
|
3306
3555
|
Math.PI * 2
|
|
@@ -3318,13 +3567,22 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3318
3567
|
const radiusX = Math.hypot(xAxis.x - center.x, xAxis.y - center.y);
|
|
3319
3568
|
const radiusY = Math.hypot(zAxis.x - center.x, zAxis.y - center.y);
|
|
3320
3569
|
ctx.strokeStyle = `rgba(216, 235, 255, ${ring.opacity})`;
|
|
3321
|
-
ctx.lineWidth = clamp((radiusX + radiusY) * 0.
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3570
|
+
ctx.lineWidth = clamp((radiusX + radiusY) * 0.014, 0.65, 1.8);
|
|
3571
|
+
for (let segment = 0; segment < 5; segment += 1) {
|
|
3572
|
+
if (pseudoRandom(segment * 31 + radiusX * 0.7 + radiusY * 0.3) < 0.32) {
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
const startAngle = segment * 1.22 + stateTimePhase(center.x, center.y) * 0.04;
|
|
3576
|
+
ctx.beginPath();
|
|
3577
|
+
ctx.ellipse(center.x, center.y, radiusX, radiusY, 0, startAngle, startAngle + 0.48);
|
|
3578
|
+
ctx.stroke();
|
|
3579
|
+
}
|
|
3325
3580
|
}
|
|
3326
3581
|
ctx.restore();
|
|
3327
3582
|
}
|
|
3583
|
+
function stateTimePhase(x, y) {
|
|
3584
|
+
return Math.sin(x * 0.013 + y * 0.017);
|
|
3585
|
+
}
|
|
3328
3586
|
function renderScene(ctx, canvas, state, shipModel, dom, lightingFeatures, fluidFeatures, clothFeatures) {
|
|
3329
3587
|
const viewport = { width: canvas.width, height: canvas.height };
|
|
3330
3588
|
const camera = buildCamera(state, canvas);
|
|
@@ -3392,6 +3650,7 @@ function renderScene(ctx, canvas, state, shipModel, dom, lightingFeatures, fluid
|
|
|
3392
3650
|
}
|
|
3393
3651
|
}
|
|
3394
3652
|
const waterMotionEffects = buildWaterMotionEffects(state);
|
|
3653
|
+
const shorelineFoamSegments = buildShorelineFoamSegments(state);
|
|
3395
3654
|
const lightSources = collectSceneLightSources(state, visuals);
|
|
3396
3655
|
pushHarborGeometry(camera, viewport, sceneTriangles, state);
|
|
3397
3656
|
const cloth = buildClothSurface(
|
|
@@ -3463,6 +3722,7 @@ function renderScene(ctx, canvas, state, shipModel, dom, lightingFeatures, fluid
|
|
|
3463
3722
|
}
|
|
3464
3723
|
renderWaterMotionEffects(ctx, waterMotionEffects, camera, viewport);
|
|
3465
3724
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
3725
|
+
renderShorelineFoamSegments(ctx, shorelineFoamSegments, camera, viewport);
|
|
3466
3726
|
drawTriangles(
|
|
3467
3727
|
ctx,
|
|
3468
3728
|
sceneTriangles,
|
|
@@ -3559,7 +3819,7 @@ function updateSceneState(state, dt, shipModel, featureAdapters) {
|
|
|
3559
3819
|
advanceShowcaseClothSimulationState(clothState, {
|
|
3560
3820
|
dt,
|
|
3561
3821
|
time: state.time,
|
|
3562
|
-
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.
|
|
3822
|
+
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.58),
|
|
3563
3823
|
waveInfluence: sampleWave(state, FLAG_LAYOUT.origin.x + FLAG_LAYOUT.width * 0.55, FLAG_LAYOUT.origin.z + FLAG_LAYOUT.width * 0.48, state.time)
|
|
3564
3824
|
});
|
|
3565
3825
|
updatePhysicsSnapshot(state, shipModel, featureAdapters.physics);
|
|
@@ -3776,6 +4036,8 @@ function updatePhysicsSnapshot(state, shipModel, physicsFeatures) {
|
|
|
3776
4036
|
}
|
|
3777
4037
|
export {
|
|
3778
4038
|
advanceShowcaseClothSimulationState as __testOnlyAdvanceShowcaseClothSimulationState,
|
|
4039
|
+
buildClothSurface as __testOnlyBuildClothSurface,
|
|
4040
|
+
buildShorelineFoamSegments as __testOnlyBuildShorelineFoamSegments,
|
|
3779
4041
|
buildWaterBands as __testOnlyBuildWaterBands,
|
|
3780
4042
|
buildWaterMotionEffects as __testOnlyBuildWaterMotionEffects,
|
|
3781
4043
|
collectSceneLightSources as __testOnlyCollectSceneLightSources,
|
|
@@ -3783,4 +4045,4 @@ export {
|
|
|
3783
4045
|
mountGpuShowcase,
|
|
3784
4046
|
showcaseFocusModes
|
|
3785
4047
|
};
|
|
3786
|
-
//# sourceMappingURL=showcase-runtime-
|
|
4048
|
+
//# sourceMappingURL=showcase-runtime-INRAPCXW.js.map
|