@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
package/src/showcase-runtime.js
CHANGED
|
@@ -844,6 +844,13 @@ const LEGACY_HARBOR_LAYOUT = Object.freeze([
|
|
|
844
844
|
]);
|
|
845
845
|
|
|
846
846
|
const SHOWCASE_ENVIRONMENT_LAYOUT = Object.freeze([
|
|
847
|
+
Object.freeze({
|
|
848
|
+
assetKey: "shoreline",
|
|
849
|
+
position: Object.freeze({ x: 1.8, y: -0.04, z: 0.48 }),
|
|
850
|
+
rotationY: -0.03,
|
|
851
|
+
scale: 1.02,
|
|
852
|
+
accent: 0.03,
|
|
853
|
+
}),
|
|
847
854
|
Object.freeze({
|
|
848
855
|
assetKey: "harbor-dock",
|
|
849
856
|
position: Object.freeze({ x: -4.6, y: 0.16, z: 0.7 }),
|
|
@@ -876,6 +883,18 @@ const HARBOR_TORCHES = Object.freeze([
|
|
|
876
883
|
Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
|
|
877
884
|
Object.freeze({ x: -10.4, y: 1.28, z: 0.82, glow: 0.92 }),
|
|
878
885
|
]);
|
|
886
|
+
const SHORELINE_FOAM_ANCHORS = Object.freeze([
|
|
887
|
+
Object.freeze({ x: -7.8, z: 3.0, length: 1.25, angle: -0.12 }),
|
|
888
|
+
Object.freeze({ x: -6.3, z: 2.72, length: 0.92, angle: 0.08 }),
|
|
889
|
+
Object.freeze({ x: -4.9, z: 3.16, length: 1.08, angle: -0.2 }),
|
|
890
|
+
Object.freeze({ x: -3.2, z: 2.42, length: 0.76, angle: 0.16 }),
|
|
891
|
+
Object.freeze({ x: -1.4, z: 2.82, length: 1.18, angle: -0.04 }),
|
|
892
|
+
Object.freeze({ x: 0.4, z: 3.08, length: 0.88, angle: 0.14 }),
|
|
893
|
+
Object.freeze({ x: 2.1, z: 2.56, length: 1.34, angle: -0.18 }),
|
|
894
|
+
Object.freeze({ x: 3.8, z: 3.0, length: 0.94, angle: 0.1 }),
|
|
895
|
+
Object.freeze({ x: 5.5, z: 2.72, length: 1.12, angle: -0.08 }),
|
|
896
|
+
Object.freeze({ x: 7.0, z: 3.22, length: 0.72, angle: 0.18 }),
|
|
897
|
+
]);
|
|
879
898
|
const FLAG_LAYOUT = Object.freeze({
|
|
880
899
|
origin: Object.freeze({ x: -3.5, y: 5.9, z: 2.4 }),
|
|
881
900
|
width: 4.8,
|
|
@@ -918,31 +937,31 @@ function injectStyles() {
|
|
|
918
937
|
box-sizing: border-box;
|
|
919
938
|
}
|
|
920
939
|
.plasius-demo {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
gap: 20px;
|
|
926
|
-
}
|
|
927
|
-
.plasius-demo__hero,
|
|
928
|
-
.plasius-demo__layout {
|
|
929
|
-
display: grid;
|
|
930
|
-
gap: 20px;
|
|
931
|
-
}
|
|
932
|
-
.plasius-demo__hero {
|
|
933
|
-
grid-template-columns: minmax(0, 1.5fr) minmax(320px, 0.85fr);
|
|
934
|
-
align-items: start;
|
|
940
|
+
position: relative;
|
|
941
|
+
width: 100%;
|
|
942
|
+
min-height: 100dvh;
|
|
943
|
+
overflow: hidden;
|
|
935
944
|
}
|
|
936
945
|
.plasius-panel {
|
|
937
946
|
border: 1px solid var(--plasius-border);
|
|
938
|
-
border-radius:
|
|
947
|
+
border-radius: 8px;
|
|
939
948
|
background: var(--plasius-panel);
|
|
940
949
|
box-shadow: var(--plasius-shadow);
|
|
941
950
|
backdrop-filter: blur(12px);
|
|
942
951
|
}
|
|
943
952
|
.plasius-demo__hero-card,
|
|
944
953
|
.plasius-demo__status {
|
|
945
|
-
|
|
954
|
+
position: absolute;
|
|
955
|
+
z-index: 3;
|
|
956
|
+
padding: 10px 12px;
|
|
957
|
+
}
|
|
958
|
+
.plasius-demo__hero-card {
|
|
959
|
+
display: none;
|
|
960
|
+
}
|
|
961
|
+
.plasius-demo__status {
|
|
962
|
+
left: 16px;
|
|
963
|
+
bottom: 84px;
|
|
964
|
+
max-width: min(360px, calc(100vw - 32px));
|
|
946
965
|
}
|
|
947
966
|
.plasius-demo__eyebrow {
|
|
948
967
|
margin: 0 0 8px;
|
|
@@ -965,31 +984,36 @@ function injectStyles() {
|
|
|
965
984
|
.plasius-demo__status-badge {
|
|
966
985
|
width: fit-content;
|
|
967
986
|
margin: 0;
|
|
968
|
-
padding:
|
|
969
|
-
border-radius:
|
|
987
|
+
padding: 6px 9px;
|
|
988
|
+
border-radius: 6px;
|
|
970
989
|
background: rgba(243, 177, 106, 0.14);
|
|
971
990
|
color: var(--plasius-accent);
|
|
972
991
|
font-weight: 700;
|
|
992
|
+
font-size: 12px;
|
|
973
993
|
}
|
|
974
994
|
.plasius-demo__status-text {
|
|
975
995
|
margin: 10px 0 0;
|
|
976
996
|
color: var(--plasius-muted);
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
.plasius-demo__layout {
|
|
980
|
-
grid-template-columns: minmax(0, 1.45fr) minmax(320px, 0.68fr);
|
|
981
|
-
align-items: start;
|
|
997
|
+
font-size: 12px;
|
|
998
|
+
line-height: 1.45;
|
|
982
999
|
}
|
|
983
1000
|
.plasius-demo__canvas-panel {
|
|
984
|
-
|
|
985
|
-
|
|
1001
|
+
position: absolute;
|
|
1002
|
+
inset: 0;
|
|
1003
|
+
padding: 0;
|
|
1004
|
+
border: 0;
|
|
1005
|
+
border-radius: 0;
|
|
1006
|
+
background: transparent;
|
|
1007
|
+
box-shadow: none;
|
|
1008
|
+
backdrop-filter: none;
|
|
986
1009
|
}
|
|
987
1010
|
.plasius-demo__canvas {
|
|
988
1011
|
width: 100%;
|
|
989
|
-
|
|
1012
|
+
height: 100%;
|
|
1013
|
+
min-height: 100dvh;
|
|
990
1014
|
display: block;
|
|
991
|
-
border
|
|
992
|
-
border:
|
|
1015
|
+
border: 0;
|
|
1016
|
+
border-radius: 0;
|
|
993
1017
|
background: linear-gradient(180deg, #071220 0%, #132440 42%, #10344b 42%, #05111d 100%);
|
|
994
1018
|
}
|
|
995
1019
|
.${CAPTURE_CLASS} .plasius-demo {
|
|
@@ -1002,6 +1026,8 @@ function injectStyles() {
|
|
|
1002
1026
|
.${CAPTURE_CLASS} .plasius-demo__toolbar,
|
|
1003
1027
|
.${CAPTURE_CLASS} .plasius-demo__legend,
|
|
1004
1028
|
.${CAPTURE_CLASS} .plasius-demo__sidebar,
|
|
1029
|
+
.${CAPTURE_CLASS} .plasius-demo__diagnostics,
|
|
1030
|
+
.${CAPTURE_CLASS} .plasius-demo__status,
|
|
1005
1031
|
.${CAPTURE_CLASS} .plasius-demo__footer {
|
|
1006
1032
|
display: none;
|
|
1007
1033
|
}
|
|
@@ -1028,12 +1054,14 @@ function injectStyles() {
|
|
|
1028
1054
|
}
|
|
1029
1055
|
.plasius-demo__toolbar {
|
|
1030
1056
|
position: absolute;
|
|
1031
|
-
top:
|
|
1032
|
-
left:
|
|
1057
|
+
top: 84px;
|
|
1058
|
+
left: 16px;
|
|
1059
|
+
z-index: 4;
|
|
1033
1060
|
display: flex;
|
|
1034
|
-
gap:
|
|
1061
|
+
gap: 8px;
|
|
1035
1062
|
flex-wrap: wrap;
|
|
1036
1063
|
align-items: center;
|
|
1064
|
+
max-width: min(560px, calc(100vw - 32px));
|
|
1037
1065
|
}
|
|
1038
1066
|
.plasius-demo button,
|
|
1039
1067
|
.plasius-demo label,
|
|
@@ -1045,22 +1073,63 @@ function injectStyles() {
|
|
|
1045
1073
|
.plasius-demo .plasius-toggle,
|
|
1046
1074
|
.plasius-demo select {
|
|
1047
1075
|
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
1048
|
-
border-radius:
|
|
1076
|
+
border-radius: 6px;
|
|
1049
1077
|
background: rgba(9, 20, 34, 0.84);
|
|
1050
1078
|
color: var(--plasius-ink);
|
|
1051
|
-
padding: 10px
|
|
1079
|
+
padding: 8px 10px;
|
|
1052
1080
|
}
|
|
1053
1081
|
.plasius-toggle {
|
|
1054
1082
|
display: inline-flex;
|
|
1055
1083
|
align-items: center;
|
|
1056
1084
|
gap: 8px;
|
|
1057
1085
|
}
|
|
1086
|
+
.plasius-demo__diagnostics {
|
|
1087
|
+
position: absolute;
|
|
1088
|
+
right: 16px;
|
|
1089
|
+
bottom: 84px;
|
|
1090
|
+
z-index: 4;
|
|
1091
|
+
max-width: min(420px, calc(100vw - 32px));
|
|
1092
|
+
color: var(--plasius-ink);
|
|
1093
|
+
font-family: "JetBrains Mono", monospace;
|
|
1094
|
+
font-size: 12px;
|
|
1095
|
+
}
|
|
1096
|
+
.plasius-demo__diagnostics summary {
|
|
1097
|
+
width: fit-content;
|
|
1098
|
+
margin-left: auto;
|
|
1099
|
+
border: 1px solid rgba(159, 185, 223, 0.18);
|
|
1100
|
+
border-radius: 6px;
|
|
1101
|
+
padding: 8px 10px;
|
|
1102
|
+
background: rgba(9, 20, 34, 0.84);
|
|
1103
|
+
cursor: pointer;
|
|
1104
|
+
list-style: none;
|
|
1105
|
+
}
|
|
1106
|
+
.plasius-demo__diagnostics summary::-webkit-details-marker {
|
|
1107
|
+
display: none;
|
|
1108
|
+
}
|
|
1109
|
+
.plasius-demo__diagnostics[open] {
|
|
1110
|
+
width: min(420px, calc(100vw - 32px));
|
|
1111
|
+
}
|
|
1112
|
+
.plasius-demo__diagnostics[open] summary {
|
|
1113
|
+
margin-bottom: 8px;
|
|
1114
|
+
background: rgba(243, 177, 106, 0.14);
|
|
1115
|
+
color: var(--plasius-accent);
|
|
1116
|
+
}
|
|
1058
1117
|
.plasius-demo__sidebar {
|
|
1059
1118
|
display: grid;
|
|
1060
|
-
gap:
|
|
1119
|
+
gap: 8px;
|
|
1120
|
+
max-height: min(58vh, 520px);
|
|
1121
|
+
overflow: auto;
|
|
1061
1122
|
}
|
|
1062
1123
|
.plasius-demo__card {
|
|
1063
|
-
padding:
|
|
1124
|
+
padding: 10px;
|
|
1125
|
+
}
|
|
1126
|
+
.plasius-demo__card h2 {
|
|
1127
|
+
margin: 0;
|
|
1128
|
+
color: rgba(226, 236, 255, 0.72);
|
|
1129
|
+
font-family: "JetBrains Mono", monospace;
|
|
1130
|
+
font-size: 11px;
|
|
1131
|
+
letter-spacing: 0.08em;
|
|
1132
|
+
text-transform: uppercase;
|
|
1064
1133
|
}
|
|
1065
1134
|
.plasius-demo__metrics,
|
|
1066
1135
|
.plasius-demo__metrics li {
|
|
@@ -1069,27 +1138,19 @@ function injectStyles() {
|
|
|
1069
1138
|
list-style: none;
|
|
1070
1139
|
}
|
|
1071
1140
|
.plasius-demo__metrics {
|
|
1072
|
-
margin-top:
|
|
1141
|
+
margin-top: 8px;
|
|
1073
1142
|
display: grid;
|
|
1074
|
-
gap:
|
|
1143
|
+
gap: 5px;
|
|
1075
1144
|
color: var(--plasius-muted);
|
|
1076
|
-
|
|
1145
|
+
font-size: 12px;
|
|
1146
|
+
line-height: 1.35;
|
|
1077
1147
|
}
|
|
1078
1148
|
.plasius-demo__metrics li {
|
|
1079
1149
|
border-top: 1px solid rgba(21, 32, 40, 0.08);
|
|
1080
|
-
padding-top:
|
|
1150
|
+
padding-top: 5px;
|
|
1081
1151
|
}
|
|
1082
1152
|
.plasius-demo__legend {
|
|
1083
|
-
|
|
1084
|
-
right: 24px;
|
|
1085
|
-
bottom: 24px;
|
|
1086
|
-
padding: 10px 14px;
|
|
1087
|
-
border-radius: 16px;
|
|
1088
|
-
background: rgba(9, 20, 34, 0.82);
|
|
1089
|
-
border: 1px solid rgba(159, 185, 223, 0.16);
|
|
1090
|
-
color: var(--plasius-muted);
|
|
1091
|
-
font-size: 12px;
|
|
1092
|
-
line-height: 1.45;
|
|
1153
|
+
display: none;
|
|
1093
1154
|
}
|
|
1094
1155
|
.plasius-demo__legend strong {
|
|
1095
1156
|
display: block;
|
|
@@ -1097,15 +1158,62 @@ function injectStyles() {
|
|
|
1097
1158
|
margin-bottom: 4px;
|
|
1098
1159
|
}
|
|
1099
1160
|
.plasius-demo__footer {
|
|
1100
|
-
|
|
1101
|
-
color: rgba(226, 236, 255, 0.68);
|
|
1102
|
-
font-size: 13px;
|
|
1103
|
-
line-height: 1.6;
|
|
1161
|
+
display: none;
|
|
1104
1162
|
}
|
|
1105
1163
|
@media (max-width: 1200px) {
|
|
1106
|
-
.plasius-
|
|
1107
|
-
|
|
1108
|
-
|
|
1164
|
+
.plasius-demo__toolbar {
|
|
1165
|
+
top: 92px;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
@media (max-width: 640px) {
|
|
1169
|
+
.plasius-demo__status {
|
|
1170
|
+
left: 10px;
|
|
1171
|
+
bottom: 10px;
|
|
1172
|
+
max-width: calc(100vw - 126px);
|
|
1173
|
+
padding: 6px 8px;
|
|
1174
|
+
}
|
|
1175
|
+
.plasius-demo__status-text {
|
|
1176
|
+
display: none;
|
|
1177
|
+
}
|
|
1178
|
+
.plasius-demo__status-badge {
|
|
1179
|
+
max-width: calc(100vw - 142px);
|
|
1180
|
+
overflow: hidden;
|
|
1181
|
+
text-overflow: ellipsis;
|
|
1182
|
+
white-space: nowrap;
|
|
1183
|
+
}
|
|
1184
|
+
.plasius-demo__toolbar {
|
|
1185
|
+
top: 10px;
|
|
1186
|
+
left: 10px;
|
|
1187
|
+
right: 10px;
|
|
1188
|
+
max-width: calc(100vw - 20px);
|
|
1189
|
+
flex-wrap: nowrap;
|
|
1190
|
+
overflow-x: auto;
|
|
1191
|
+
padding-bottom: 4px;
|
|
1192
|
+
scrollbar-width: none;
|
|
1193
|
+
}
|
|
1194
|
+
.plasius-demo__toolbar::-webkit-scrollbar {
|
|
1195
|
+
display: none;
|
|
1196
|
+
}
|
|
1197
|
+
.plasius-demo button,
|
|
1198
|
+
.plasius-demo .plasius-toggle,
|
|
1199
|
+
.plasius-demo select {
|
|
1200
|
+
padding: 7px 8px;
|
|
1201
|
+
font-size: 12px;
|
|
1202
|
+
white-space: nowrap;
|
|
1203
|
+
}
|
|
1204
|
+
.plasius-demo__diagnostics {
|
|
1205
|
+
right: 10px;
|
|
1206
|
+
bottom: 10px;
|
|
1207
|
+
}
|
|
1208
|
+
.plasius-demo__diagnostics[open] {
|
|
1209
|
+
bottom: 56px;
|
|
1210
|
+
left: 10px;
|
|
1211
|
+
right: 10px;
|
|
1212
|
+
width: auto;
|
|
1213
|
+
max-width: none;
|
|
1214
|
+
}
|
|
1215
|
+
.plasius-demo__diagnostics[open] .plasius-demo__sidebar {
|
|
1216
|
+
max-height: min(42vh, 340px);
|
|
1109
1217
|
}
|
|
1110
1218
|
}
|
|
1111
1219
|
`;
|
|
@@ -1533,11 +1641,12 @@ function buildTrianglesFromMesh(
|
|
|
1533
1641
|
}
|
|
1534
1642
|
|
|
1535
1643
|
async function loadShowcaseAssetCatalog() {
|
|
1536
|
-
const [brigantine, cutter, lighthouse, harborDock] = await Promise.all([
|
|
1644
|
+
const [brigantine, cutter, lighthouse, harborDock, shoreline] = await Promise.all([
|
|
1537
1645
|
loadGltfModel(resolveShowcaseAssetUrl("brigantine")),
|
|
1538
1646
|
loadGltfModel(resolveShowcaseAssetUrl("cutter")),
|
|
1539
1647
|
loadGltfModel(resolveShowcaseAssetUrl("lighthouse")),
|
|
1540
1648
|
loadGltfModel(resolveShowcaseAssetUrl("harbor-dock")),
|
|
1649
|
+
loadGltfModel(resolveShowcaseAssetUrl("shoreline")),
|
|
1541
1650
|
]);
|
|
1542
1651
|
|
|
1543
1652
|
return Object.freeze({
|
|
@@ -1549,6 +1658,7 @@ async function loadShowcaseAssetCatalog() {
|
|
|
1549
1658
|
environment: Object.freeze({
|
|
1550
1659
|
lighthouse,
|
|
1551
1660
|
"harbor-dock": harborDock,
|
|
1661
|
+
shoreline,
|
|
1552
1662
|
}),
|
|
1553
1663
|
});
|
|
1554
1664
|
}
|
|
@@ -1690,24 +1800,27 @@ function buildDemoDom(root, options) {
|
|
|
1690
1800
|
${t(gpuSharedTranslationKeys.legendCollisions)}
|
|
1691
1801
|
</div>
|
|
1692
1802
|
</section>
|
|
1693
|
-
<
|
|
1694
|
-
<
|
|
1695
|
-
|
|
1696
|
-
<
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
<
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
<
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
<
|
|
1709
|
-
|
|
1710
|
-
|
|
1803
|
+
<details class="plasius-demo__diagnostics">
|
|
1804
|
+
<summary>${t(gpuSharedTranslationKeys.debugTelemetry)}</summary>
|
|
1805
|
+
<aside class="plasius-demo__sidebar">
|
|
1806
|
+
<section class="plasius-panel plasius-demo__card">
|
|
1807
|
+
<h2>${t(gpuSharedTranslationKeys.sceneState)}</h2>
|
|
1808
|
+
<ul id="sceneMetrics" class="plasius-demo__metrics"></ul>
|
|
1809
|
+
</section>
|
|
1810
|
+
<section class="plasius-panel plasius-demo__card">
|
|
1811
|
+
<h2>${t(gpuSharedTranslationKeys.qualityBudgets)}</h2>
|
|
1812
|
+
<ul id="qualityMetrics" class="plasius-demo__metrics"></ul>
|
|
1813
|
+
</section>
|
|
1814
|
+
<section class="plasius-panel plasius-demo__card">
|
|
1815
|
+
<h2>${t(gpuSharedTranslationKeys.debugTelemetry)}</h2>
|
|
1816
|
+
<ul id="debugMetrics" class="plasius-demo__metrics"></ul>
|
|
1817
|
+
</section>
|
|
1818
|
+
<section class="plasius-panel plasius-demo__card">
|
|
1819
|
+
<h2>${t(gpuSharedTranslationKeys.notes)}</h2>
|
|
1820
|
+
<ul id="sceneNotes" class="plasius-demo__metrics"></ul>
|
|
1821
|
+
</section>
|
|
1822
|
+
</aside>
|
|
1823
|
+
</details>
|
|
1711
1824
|
</section>
|
|
1712
1825
|
<p class="plasius-demo__footer">
|
|
1713
1826
|
This visual example is shared across the GPU packages to keep manual validation fast and consistent.
|
|
@@ -2125,13 +2238,13 @@ function advanceShowcaseClothSimulationState(clothState, options = {}) {
|
|
|
2125
2238
|
)
|
|
2126
2239
|
);
|
|
2127
2240
|
const windStrength =
|
|
2128
|
-
(
|
|
2241
|
+
(0.94 + broadMotion * 0.82 + wrinkleLayers * 0.08) *
|
|
2129
2242
|
flagMotion *
|
|
2130
|
-
(0.
|
|
2243
|
+
(0.36 + u * 0.92);
|
|
2131
2244
|
const wrinkleForce = vec3(
|
|
2132
|
-
Math.sin(wrinklePhase) * 0.
|
|
2133
|
-
Math.cos(wrinklePhase * 0.7) * 0.
|
|
2134
|
-
Math.cos(wrinklePhase) * 0.
|
|
2245
|
+
Math.sin(wrinklePhase) * 0.12 * wrinkleMotion * flagMotion,
|
|
2246
|
+
Math.cos(wrinklePhase * 0.7) * 0.045 * wrinkleMotion,
|
|
2247
|
+
Math.cos(wrinklePhase) * 0.08 * broadMotion * flagMotion
|
|
2135
2248
|
);
|
|
2136
2249
|
const acceleration = addVec3(
|
|
2137
2250
|
vec3(0, -0.48 - u * 0.08, 0),
|
|
@@ -2197,25 +2310,25 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
2197
2310
|
ambientMist: "rgba(41, 63, 97, 0.16)",
|
|
2198
2311
|
reflectionStrength: lightingSnapshot.currentLevel.config.reflectionStrength,
|
|
2199
2312
|
shadowAccent: lightingSnapshot.currentLevel.config.shadowStrength,
|
|
2200
|
-
waveAmplitude: 0.
|
|
2313
|
+
waveAmplitude: 0.82,
|
|
2201
2314
|
waveDirection: { x: 0.88, z: 0.28 },
|
|
2202
|
-
wavePhaseSpeed: 0.
|
|
2203
|
-
wakeStrength: 0.
|
|
2204
|
-
wakeLength:
|
|
2205
|
-
collisionRippleStrength: 0.
|
|
2206
|
-
waterNear: { r: 0.
|
|
2207
|
-
waterFar: { r: 0.
|
|
2315
|
+
wavePhaseSpeed: 0.74,
|
|
2316
|
+
wakeStrength: 0.24,
|
|
2317
|
+
wakeLength: 17,
|
|
2318
|
+
collisionRippleStrength: 0.22,
|
|
2319
|
+
waterNear: { r: 0.05, g: 0.2, b: 0.3 },
|
|
2320
|
+
waterFar: { r: 0.13, g: 0.31, b: 0.45 },
|
|
2208
2321
|
harborWall: { r: 0.26, g: 0.24, b: 0.28 },
|
|
2209
2322
|
harborDeck: { r: 0.33, g: 0.22, b: 0.16 },
|
|
2210
2323
|
harborTower: { r: 0.23, g: 0.24, b: 0.29 },
|
|
2211
|
-
flagColor: { r: 0.
|
|
2212
|
-
flagMotion: 0.
|
|
2324
|
+
flagColor: { r: 0.54, g: 0.13, b: 0.11 },
|
|
2325
|
+
flagMotion: 0.58,
|
|
2213
2326
|
lanternCore: { r: 0.98, g: 0.8, b: 0.48 },
|
|
2214
2327
|
lanternGlow: { r: 1, g: 0.56, b: 0.2 },
|
|
2215
2328
|
lanternReflectionStrength: 0.42,
|
|
2216
2329
|
torchCore: { r: 0.99, g: 0.72, b: 0.36 },
|
|
2217
2330
|
torchGlow: { r: 0.98, g: 0.38, b: 0.15 },
|
|
2218
|
-
collisionFlash: "rgba(255, 212, 168, 0.
|
|
2331
|
+
collisionFlash: "rgba(255, 212, 168, 0.08)",
|
|
2219
2332
|
};
|
|
2220
2333
|
|
|
2221
2334
|
return {
|
|
@@ -2299,6 +2412,11 @@ function buildClothSurface(model, state, meshDetail, visuals, clothFeatures) {
|
|
|
2299
2412
|
representation: clothPresentation.representation,
|
|
2300
2413
|
continuity: clothPresentation.continuity,
|
|
2301
2414
|
color: visuals.flagColor,
|
|
2415
|
+
material: Object.freeze({
|
|
2416
|
+
weaveAlpha: clothPresentation.band === "near" ? 0.22 : 0.12,
|
|
2417
|
+
foldAlpha: clothPresentation.band === "near" ? 0.3 : 0.18,
|
|
2418
|
+
edgeHighlightAlpha: clothPresentation.band === "near" ? 0.42 : 0.28,
|
|
2419
|
+
}),
|
|
2302
2420
|
positions: clothState.positions.map((point) => vec3(point.x, point.y, point.z)),
|
|
2303
2421
|
indices: clothState.indices,
|
|
2304
2422
|
grid: { rows: clothState.rows, cols: clothState.cols },
|
|
@@ -2412,7 +2530,7 @@ function buildWaterMotionEffects(state) {
|
|
|
2412
2530
|
impulse.z
|
|
2413
2531
|
),
|
|
2414
2532
|
radius,
|
|
2415
|
-
opacity: clamp(impulse.life * 0.
|
|
2533
|
+
opacity: clamp(impulse.life * 0.13, 0.035, 0.15),
|
|
2416
2534
|
});
|
|
2417
2535
|
});
|
|
2418
2536
|
|
|
@@ -2427,7 +2545,7 @@ function buildWaterMotionEffects(state) {
|
|
|
2427
2545
|
const lateral = vec3(-direction.z, 0, direction.x);
|
|
2428
2546
|
const points = [];
|
|
2429
2547
|
for (let sampleIndex = 0; sampleIndex < 6; sampleIndex += 1) {
|
|
2430
|
-
const along = 1 + sampleIndex * 1.
|
|
2548
|
+
const along = 1 + sampleIndex * 1.55;
|
|
2431
2549
|
const lateralOffset =
|
|
2432
2550
|
Math.sin(state.time * 1.2 + sampleIndex * 0.8 + readVisualNumber(ship.wanderPhase, 0)) * 0.12;
|
|
2433
2551
|
const worldPoint = addVec3(
|
|
@@ -2441,13 +2559,14 @@ function buildWaterMotionEffects(state) {
|
|
|
2441
2559
|
sampleWave(state, worldPoint.x, worldPoint.z, state.time) * 0.24 + 0.04,
|
|
2442
2560
|
worldPoint.z
|
|
2443
2561
|
),
|
|
2444
|
-
width: 0.
|
|
2562
|
+
width: 0.3 + sampleIndex * 0.11,
|
|
2563
|
+
foam: clamp(0.28 - sampleIndex * 0.028 + speed * 0.025, 0.1, 0.34),
|
|
2445
2564
|
})
|
|
2446
2565
|
);
|
|
2447
2566
|
}
|
|
2448
2567
|
wakeTrails.push(
|
|
2449
2568
|
Object.freeze({
|
|
2450
|
-
opacity: clamp(0.
|
|
2569
|
+
opacity: clamp(0.1 + speed * 0.048, 0.12, 0.24),
|
|
2451
2570
|
points: Object.freeze(points),
|
|
2452
2571
|
})
|
|
2453
2572
|
);
|
|
@@ -2459,6 +2578,28 @@ function buildWaterMotionEffects(state) {
|
|
|
2459
2578
|
});
|
|
2460
2579
|
}
|
|
2461
2580
|
|
|
2581
|
+
function buildShorelineFoamSegments(state) {
|
|
2582
|
+
return Object.freeze(
|
|
2583
|
+
SHORELINE_FOAM_ANCHORS.map((anchor, index) => {
|
|
2584
|
+
const pulse = 0.5 + Math.sin(state.time * 0.84 + index * 1.17) * 0.5;
|
|
2585
|
+
const drift = Math.sin(state.time * 0.38 + index * 0.61) * 0.1;
|
|
2586
|
+
const direction = normalizeVec3(vec3(Math.cos(anchor.angle), 0, Math.sin(anchor.angle)));
|
|
2587
|
+
const center = vec3(
|
|
2588
|
+
anchor.x + direction.x * drift,
|
|
2589
|
+
sampleWave(state, anchor.x, anchor.z, state.time) * 0.12 - 0.02,
|
|
2590
|
+
anchor.z + direction.z * drift
|
|
2591
|
+
);
|
|
2592
|
+
return Object.freeze({
|
|
2593
|
+
center,
|
|
2594
|
+
direction,
|
|
2595
|
+
length: anchor.length * (0.78 + pulse * 0.34),
|
|
2596
|
+
width: 0.16 + pulse * 0.12,
|
|
2597
|
+
opacity: 0.07 + pulse * 0.12,
|
|
2598
|
+
});
|
|
2599
|
+
})
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2462
2603
|
function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
2463
2604
|
const resolvedFluidFeatures = normalizeFluidFeatureAdapters(fluidFeatures);
|
|
2464
2605
|
const fluidPlan = resolvedFluidFeatures.createPlan({
|
|
@@ -2487,14 +2628,14 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
2487
2628
|
const bandContinuity = resolveFluidBandContinuity(continuity, bandSpec.band);
|
|
2488
2629
|
const bandResolution =
|
|
2489
2630
|
bandSpec.band === "near"
|
|
2490
|
-
? fluidDetail.nearResolution
|
|
2631
|
+
? Math.ceil(fluidDetail.nearResolution * 1.28)
|
|
2491
2632
|
: bandSpec.band === "mid"
|
|
2492
|
-
? fluidDetail.midResolution
|
|
2633
|
+
? Math.ceil(fluidDetail.midResolution * 1.2)
|
|
2493
2634
|
: bandSpec.band === "far"
|
|
2494
2635
|
? 5
|
|
2495
2636
|
: 3;
|
|
2496
|
-
const cols = Math.max(4, bandResolution * 2);
|
|
2497
|
-
const rows = Math.max(4, bandResolution + 2);
|
|
2637
|
+
const cols = Math.max(4, bandResolution * (bandSpec.band === "near" ? 3 : 2));
|
|
2638
|
+
const rows = Math.max(4, bandResolution + (bandSpec.band === "near" ? 5 : 2));
|
|
2498
2639
|
const positions = [];
|
|
2499
2640
|
const indices = [];
|
|
2500
2641
|
const originX = -bandSpec.width * 0.5;
|
|
@@ -2505,11 +2646,16 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
2505
2646
|
const v = row / (rows - 1);
|
|
2506
2647
|
const x = originX + bandSpec.width * u;
|
|
2507
2648
|
const z = originZ + bandSpec.depth * v;
|
|
2508
|
-
const
|
|
2649
|
+
const baseHeight =
|
|
2509
2650
|
bandSpec.y +
|
|
2510
2651
|
sampleWave(state, x, z, state.time) *
|
|
2511
2652
|
bandContinuity.amplitudeFloor *
|
|
2512
2653
|
(bandSpec.band === "near" ? 0.9 : bandSpec.band === "mid" ? 0.55 : 0.3);
|
|
2654
|
+
const detailHeight =
|
|
2655
|
+
bandSpec.band === "near"
|
|
2656
|
+
? Math.sin(x * 1.25 + z * 0.42 - state.time * 2.4) * 0.035
|
|
2657
|
+
: 0;
|
|
2658
|
+
const y = baseHeight + detailHeight;
|
|
2513
2659
|
positions.push(vec3(x, y, z));
|
|
2514
2660
|
}
|
|
2515
2661
|
}
|
|
@@ -2546,7 +2692,12 @@ function buildWaterBands(state, fluidDetail, visuals, fluidFeatures) {
|
|
|
2546
2692
|
r: mix(visuals.waterFar.r, 0.76, 0.2),
|
|
2547
2693
|
g: mix(visuals.waterFar.g, 0.78, 0.2),
|
|
2548
2694
|
b: mix(visuals.waterFar.b, 0.82, 0.2),
|
|
2549
|
-
|
|
2695
|
+
},
|
|
2696
|
+
material: Object.freeze({
|
|
2697
|
+
highlightAlpha: bandSpec.band === "near" ? 0.2 : bandSpec.band === "mid" ? 0.13 : 0.07,
|
|
2698
|
+
foamAlpha: bandSpec.band === "near" ? 0.28 : bandSpec.band === "mid" ? 0.14 : 0.05,
|
|
2699
|
+
microRippleScale: bandSpec.band === "near" ? 1 : bandSpec.band === "mid" ? 0.58 : 0.28,
|
|
2700
|
+
}),
|
|
2550
2701
|
});
|
|
2551
2702
|
}
|
|
2552
2703
|
|
|
@@ -2626,15 +2777,15 @@ function createSceneState(options, featureAdapters) {
|
|
|
2626
2777
|
{
|
|
2627
2778
|
id: "northwind",
|
|
2628
2779
|
modelKey: "brigantine",
|
|
2629
|
-
position: vec3(-
|
|
2630
|
-
velocity: vec3(
|
|
2631
|
-
rotationY:
|
|
2632
|
-
angularVelocity: 0.
|
|
2780
|
+
position: vec3(-7.8, 0, 11.2),
|
|
2781
|
+
velocity: vec3(1.08, 0, -0.18),
|
|
2782
|
+
rotationY: 1.38,
|
|
2783
|
+
angularVelocity: 0.025,
|
|
2633
2784
|
tint: { r: 0.62, g: 0.39, b: 0.23 },
|
|
2634
2785
|
massScale: 1.42,
|
|
2635
|
-
cruiseSpeed:
|
|
2636
|
-
throttleResponse: 0.
|
|
2637
|
-
rudderResponse: 0.
|
|
2786
|
+
cruiseSpeed: 1.22,
|
|
2787
|
+
throttleResponse: 0.36,
|
|
2788
|
+
rudderResponse: 0.4,
|
|
2638
2789
|
wanderPhase: 0.35,
|
|
2639
2790
|
lanterns: CUTTER_LANTERNS,
|
|
2640
2791
|
lanternStrength: 1.06,
|
|
@@ -2643,15 +2794,15 @@ function createSceneState(options, featureAdapters) {
|
|
|
2643
2794
|
{
|
|
2644
2795
|
id: "tidecaller",
|
|
2645
2796
|
modelKey: "cutter",
|
|
2646
|
-
position: vec3(
|
|
2647
|
-
velocity: vec3(-
|
|
2648
|
-
rotationY: -
|
|
2649
|
-
angularVelocity: -0.
|
|
2797
|
+
position: vec3(6.8, 0, 5.4),
|
|
2798
|
+
velocity: vec3(-0.82, 0, 0.14),
|
|
2799
|
+
rotationY: -1.34,
|
|
2800
|
+
angularVelocity: -0.035,
|
|
2650
2801
|
tint: { r: 0.58, g: 0.24, b: 0.16 },
|
|
2651
2802
|
massScale: 0.84,
|
|
2652
|
-
cruiseSpeed:
|
|
2653
|
-
throttleResponse: 0.
|
|
2654
|
-
rudderResponse: 0.
|
|
2803
|
+
cruiseSpeed: 1.36,
|
|
2804
|
+
throttleResponse: 0.52,
|
|
2805
|
+
rudderResponse: 0.58,
|
|
2655
2806
|
wanderPhase: 1.6,
|
|
2656
2807
|
lanterns: SHIP_LANTERNS,
|
|
2657
2808
|
lanternStrength: 1.18,
|
|
@@ -2995,13 +3146,14 @@ function renderShipRigging(ctx, ship, camera, viewport) {
|
|
|
2995
3146
|
|
|
2996
3147
|
function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
2997
3148
|
const projected = cloth.positions.map((point) => projectPoint(point, camera, viewport));
|
|
2998
|
-
|
|
2999
|
-
ctx.
|
|
3149
|
+
const material = cloth.material ?? {};
|
|
3150
|
+
ctx.strokeStyle = `rgba(255, 241, 226, ${material.foldAlpha ?? 0.32})`;
|
|
3151
|
+
ctx.lineWidth = 1.8;
|
|
3000
3152
|
|
|
3001
3153
|
for (
|
|
3002
3154
|
let row = 0;
|
|
3003
3155
|
row < cloth.grid.rows;
|
|
3004
|
-
row += Math.max(1, Math.floor(cloth.grid.rows /
|
|
3156
|
+
row += Math.max(1, Math.floor(cloth.grid.rows / 6))
|
|
3005
3157
|
) {
|
|
3006
3158
|
ctx.beginPath();
|
|
3007
3159
|
let started = false;
|
|
@@ -3022,6 +3174,32 @@ function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
|
3022
3174
|
}
|
|
3023
3175
|
}
|
|
3024
3176
|
|
|
3177
|
+
ctx.strokeStyle = `rgba(255, 228, 204, ${material.weaveAlpha ?? 0.22})`;
|
|
3178
|
+
ctx.lineWidth = 0.85;
|
|
3179
|
+
for (
|
|
3180
|
+
let column = 1;
|
|
3181
|
+
column < cloth.grid.cols - 1;
|
|
3182
|
+
column += Math.max(1, Math.floor(cloth.grid.cols / 8))
|
|
3183
|
+
) {
|
|
3184
|
+
ctx.beginPath();
|
|
3185
|
+
let started = false;
|
|
3186
|
+
for (let row = 0; row < cloth.grid.rows; row += 1) {
|
|
3187
|
+
const point = projected[row * cloth.grid.cols + column];
|
|
3188
|
+
if (!point) {
|
|
3189
|
+
continue;
|
|
3190
|
+
}
|
|
3191
|
+
if (!started) {
|
|
3192
|
+
ctx.moveTo(point.x, point.y);
|
|
3193
|
+
started = true;
|
|
3194
|
+
} else {
|
|
3195
|
+
ctx.lineTo(point.x, point.y);
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
if (started) {
|
|
3199
|
+
ctx.stroke();
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3025
3203
|
const borderIndices = [
|
|
3026
3204
|
0,
|
|
3027
3205
|
cloth.grid.cols - 1,
|
|
@@ -3029,6 +3207,26 @@ function renderClothAccent(ctx, cloth, camera, viewport) {
|
|
|
3029
3207
|
(cloth.grid.rows - 1) * cloth.grid.cols,
|
|
3030
3208
|
];
|
|
3031
3209
|
ctx.fillStyle = colorToRgba(cloth.color, 0.95);
|
|
3210
|
+
ctx.strokeStyle = `rgba(255, 246, 236, ${material.edgeHighlightAlpha ?? 0.5})`;
|
|
3211
|
+
ctx.lineWidth = 1.4;
|
|
3212
|
+
ctx.beginPath();
|
|
3213
|
+
let borderStarted = false;
|
|
3214
|
+
for (let column = 0; column < cloth.grid.cols; column += 1) {
|
|
3215
|
+
const point = projected[column];
|
|
3216
|
+
if (!point) {
|
|
3217
|
+
continue;
|
|
3218
|
+
}
|
|
3219
|
+
if (!borderStarted) {
|
|
3220
|
+
ctx.moveTo(point.x, point.y);
|
|
3221
|
+
borderStarted = true;
|
|
3222
|
+
} else {
|
|
3223
|
+
ctx.lineTo(point.x, point.y);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
if (borderStarted) {
|
|
3227
|
+
ctx.stroke();
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3032
3230
|
for (const index of borderIndices) {
|
|
3033
3231
|
const point = projected[index];
|
|
3034
3232
|
if (!point) {
|
|
@@ -3045,14 +3243,22 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
3045
3243
|
if (band.band === "horizon") {
|
|
3046
3244
|
continue;
|
|
3047
3245
|
}
|
|
3048
|
-
const interval = band.band === "near" ?
|
|
3049
|
-
const alpha = band.band === "near" ? 0.22 : 0.14;
|
|
3246
|
+
const interval = band.band === "near" ? 4 : 5;
|
|
3247
|
+
const alpha = band.material?.highlightAlpha ?? (band.band === "near" ? 0.22 : 0.14);
|
|
3050
3248
|
ctx.strokeStyle = `rgba(232, 247, 255, ${alpha})`;
|
|
3051
|
-
ctx.lineWidth = band.band === "near" ?
|
|
3249
|
+
ctx.lineWidth = band.band === "near" ? 0.9 : 0.65;
|
|
3052
3250
|
for (let row = interval; row < band.rows - 1; row += interval) {
|
|
3053
|
-
ctx.beginPath();
|
|
3054
3251
|
let started = false;
|
|
3055
|
-
|
|
3252
|
+
ctx.beginPath();
|
|
3253
|
+
for (let column = 0; column < band.cols; column += band.band === "near" ? 2 : 3) {
|
|
3254
|
+
if (pseudoRandom(row * 47 + column * 13) < 0.18) {
|
|
3255
|
+
if (started) {
|
|
3256
|
+
ctx.stroke();
|
|
3257
|
+
ctx.beginPath();
|
|
3258
|
+
started = false;
|
|
3259
|
+
}
|
|
3260
|
+
continue;
|
|
3261
|
+
}
|
|
3056
3262
|
const point = projectPoint(
|
|
3057
3263
|
band.positions[row * band.cols + column],
|
|
3058
3264
|
camera,
|
|
@@ -3072,7 +3278,61 @@ function renderWaterHighlights(ctx, waterBands, camera, viewport) {
|
|
|
3072
3278
|
ctx.stroke();
|
|
3073
3279
|
}
|
|
3074
3280
|
}
|
|
3281
|
+
|
|
3282
|
+
if (band.band === "near") {
|
|
3283
|
+
ctx.fillStyle = `rgba(236, 249, 255, ${(band.material?.foamAlpha ?? 0.28) * 0.72})`;
|
|
3284
|
+
for (let column = 3; column < band.cols - 3; column += 10) {
|
|
3285
|
+
const point = projectPoint(
|
|
3286
|
+
band.positions[Math.floor(band.rows * 0.42) * band.cols + column],
|
|
3287
|
+
camera,
|
|
3288
|
+
viewport
|
|
3289
|
+
);
|
|
3290
|
+
if (!point) {
|
|
3291
|
+
continue;
|
|
3292
|
+
}
|
|
3293
|
+
ctx.beginPath();
|
|
3294
|
+
ctx.ellipse(point.x, point.y, 1.8, 0.75, -0.2, 0, Math.PI * 2);
|
|
3295
|
+
ctx.fill();
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
function renderShorelineFoamSegments(ctx, segments, camera, viewport) {
|
|
3302
|
+
ctx.save();
|
|
3303
|
+
ctx.globalCompositeOperation = "screen";
|
|
3304
|
+
ctx.lineCap = "round";
|
|
3305
|
+
ctx.lineJoin = "round";
|
|
3306
|
+
|
|
3307
|
+
for (const segment of segments) {
|
|
3308
|
+
const half = scaleVec3(segment.direction, segment.length * 0.5);
|
|
3309
|
+
const start = projectPoint(subVec3(segment.center, half), camera, viewport);
|
|
3310
|
+
const end = projectPoint(addVec3(segment.center, half), camera, viewport);
|
|
3311
|
+
const center = projectPoint(segment.center, camera, viewport);
|
|
3312
|
+
if (!start || !end || !center) {
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
const depthScale = clamp(140 / Math.max(12, center.depth), 3, 10);
|
|
3317
|
+
ctx.strokeStyle = `rgba(232, 242, 238, ${segment.opacity})`;
|
|
3318
|
+
ctx.lineWidth = clamp(segment.width * depthScale, 0.8, 2.8);
|
|
3319
|
+
ctx.beginPath();
|
|
3320
|
+
ctx.moveTo(start.x, start.y);
|
|
3321
|
+
ctx.quadraticCurveTo(
|
|
3322
|
+
center.x,
|
|
3323
|
+
center.y + Math.sin(segment.center.x * 1.7) * 2.4,
|
|
3324
|
+
end.x,
|
|
3325
|
+
end.y
|
|
3326
|
+
);
|
|
3327
|
+
ctx.stroke();
|
|
3328
|
+
|
|
3329
|
+
ctx.fillStyle = `rgba(248, 251, 246, ${segment.opacity * 0.68})`;
|
|
3330
|
+
ctx.beginPath();
|
|
3331
|
+
ctx.ellipse(center.x, center.y, depthScale * 0.18, depthScale * 0.08, -0.2, 0, Math.PI * 2);
|
|
3332
|
+
ctx.fill();
|
|
3075
3333
|
}
|
|
3334
|
+
|
|
3335
|
+
ctx.restore();
|
|
3076
3336
|
}
|
|
3077
3337
|
|
|
3078
3338
|
function readPhysicsNumber(physics, key, fallback) {
|
|
@@ -3113,14 +3373,17 @@ function getShipInverseInertia(ship, shipModel) {
|
|
|
3113
3373
|
}
|
|
3114
3374
|
|
|
3115
3375
|
function spawnSpray(state, point, intensity) {
|
|
3116
|
-
const count =
|
|
3376
|
+
const count = Math.max(
|
|
3377
|
+
3,
|
|
3378
|
+
Math.ceil(state.fluidDetail.getSnapshot().currentLevel.config.splashCount * 0.32)
|
|
3379
|
+
);
|
|
3117
3380
|
for (let index = 0; index < count; index += 1) {
|
|
3118
3381
|
const angle = (index / count) * Math.PI * 2;
|
|
3119
|
-
const speed = 0.
|
|
3382
|
+
const speed = 0.46 + Math.random() * intensity * 0.24;
|
|
3120
3383
|
state.sprays.push({
|
|
3121
3384
|
position: vec3(point.x, point.y, point.z),
|
|
3122
|
-
velocity: vec3(Math.cos(angle) * speed * 0.
|
|
3123
|
-
life:
|
|
3385
|
+
velocity: vec3(Math.cos(angle) * speed * 0.24, 0.46 + Math.random() * 0.34, Math.sin(angle) * speed * 0.18),
|
|
3386
|
+
life: 0.72 + Math.random() * 0.22,
|
|
3124
3387
|
});
|
|
3125
3388
|
}
|
|
3126
3389
|
}
|
|
@@ -3140,8 +3403,8 @@ function resolveShipRoute(ship, state, radius) {
|
|
|
3140
3403
|
const crossCurrent = Math.cos(state.time * 0.31 + readVisualNumber(ship.wanderPhase, 0));
|
|
3141
3404
|
const laneCenter =
|
|
3142
3405
|
ship.id === "northwind"
|
|
3143
|
-
?
|
|
3144
|
-
:
|
|
3406
|
+
? 11.6 + wander * 0.82 + crossCurrent * 0.24
|
|
3407
|
+
: 5.4 + wander * 0.94 - crossCurrent * 0.32;
|
|
3145
3408
|
const targetX =
|
|
3146
3409
|
ship.routeDirection > 0
|
|
3147
3410
|
? HARBOR_BOUNDS.maxX - radius * 1.7
|
|
@@ -3158,7 +3421,7 @@ function updateShipMotion(state, ship, dt, shipModel) {
|
|
|
3158
3421
|
const angularDamping = readPhysicsNumber(physics, "angularDamping", 0.08);
|
|
3159
3422
|
const throttleResponse = readVisualNumber(ship.throttleResponse, 0.58);
|
|
3160
3423
|
const rudderResponse = readVisualNumber(ship.rudderResponse, 0.62);
|
|
3161
|
-
const cruiseSpeed = readVisualNumber(ship.cruiseSpeed,
|
|
3424
|
+
const cruiseSpeed = readVisualNumber(ship.cruiseSpeed, 1.25);
|
|
3162
3425
|
|
|
3163
3426
|
ship.collisionCooldown = Math.max(0, readVisualNumber(ship.collisionCooldown, 0) - dt);
|
|
3164
3427
|
|
|
@@ -3272,7 +3535,7 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
|
3272
3535
|
((readPhysicsNumber(shipModelA.physics, "restitution", 0.22) +
|
|
3273
3536
|
readPhysicsNumber(shipModelB.physics, "restitution", 0.22)) /
|
|
3274
3537
|
2) *
|
|
3275
|
-
0.
|
|
3538
|
+
0.42;
|
|
3276
3539
|
if (velocityAlongNormal < 0) {
|
|
3277
3540
|
const impulseMagnitude =
|
|
3278
3541
|
(-(1 + restitution) * velocityAlongNormal) / Math.max(0.0001, invMassSum);
|
|
@@ -3299,7 +3562,7 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
|
3299
3562
|
|
|
3300
3563
|
const impactSpeed = Math.abs(velocityAlongNormal);
|
|
3301
3564
|
if (
|
|
3302
|
-
impactSpeed > 0.
|
|
3565
|
+
impactSpeed > 0.36 &&
|
|
3303
3566
|
Math.max(readVisualNumber(a.collisionCooldown, 0), readVisualNumber(b.collisionCooldown, 0)) <= 0
|
|
3304
3567
|
) {
|
|
3305
3568
|
const contactPoint = vec3(
|
|
@@ -3307,21 +3570,21 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
|
|
|
3307
3570
|
(a.position.y + b.position.y) * 0.5 + 0.14,
|
|
3308
3571
|
(a.position.z + b.position.z) * 0.5
|
|
3309
3572
|
);
|
|
3310
|
-
spawnSpray(state, contactPoint, impactSpeed *
|
|
3573
|
+
spawnSpray(state, contactPoint, impactSpeed * 0.9 + penetration * 2.4);
|
|
3311
3574
|
state.waveImpulses.push({
|
|
3312
3575
|
x: contactPoint.x,
|
|
3313
3576
|
z: contactPoint.z,
|
|
3314
|
-
strength: clamp(0.
|
|
3315
|
-
radius: 0.
|
|
3577
|
+
strength: clamp(0.1 + impactSpeed * 0.18 + penetration * 0.28, 0.08, 0.52),
|
|
3578
|
+
radius: 0.72 + penetration * 0.72,
|
|
3316
3579
|
life: 1,
|
|
3317
3580
|
});
|
|
3318
3581
|
state.collisionCount += 1;
|
|
3319
3582
|
state.collisionFlash = Math.max(
|
|
3320
3583
|
state.collisionFlash,
|
|
3321
|
-
clamp(impactSpeed * 0.
|
|
3584
|
+
clamp(impactSpeed * 0.14 + penetration * 0.32, 0.04, 0.24)
|
|
3322
3585
|
);
|
|
3323
|
-
a.collisionCooldown = 0.
|
|
3324
|
-
b.collisionCooldown = 0.
|
|
3586
|
+
a.collisionCooldown = 0.72;
|
|
3587
|
+
b.collisionCooldown = 0.72;
|
|
3325
3588
|
}
|
|
3326
3589
|
}
|
|
3327
3590
|
|
|
@@ -3352,8 +3615,8 @@ function updateShips(state, dt, shipModel) {
|
|
|
3352
3615
|
}
|
|
3353
3616
|
|
|
3354
3617
|
state.collisionFlash = collided
|
|
3355
|
-
? Math.max(0.
|
|
3356
|
-
: Math.max(0, state.collisionFlash - dt * 1.
|
|
3618
|
+
? Math.max(0.04, state.collisionFlash)
|
|
3619
|
+
: Math.max(0, state.collisionFlash - dt * 1.7);
|
|
3357
3620
|
}
|
|
3358
3621
|
|
|
3359
3622
|
function updateWaveImpulses(state, dt) {
|
|
@@ -3749,6 +4012,7 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3749
4012
|
.map((point) => ({
|
|
3750
4013
|
projected: projectPoint(point.center, camera, viewport),
|
|
3751
4014
|
width: point.width,
|
|
4015
|
+
foam: point.foam,
|
|
3752
4016
|
}))
|
|
3753
4017
|
.filter((entry) => entry.projected);
|
|
3754
4018
|
if (projected.length < 2) {
|
|
@@ -3760,8 +4024,8 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3760
4024
|
const averageWidth =
|
|
3761
4025
|
projected.reduce((total, entry) => total + entry.width, 0) / projected.length;
|
|
3762
4026
|
const baseWidth = clamp((averageWidth / Math.max(0.25, averageDepth)) * 180, 1.6, 5.4);
|
|
3763
|
-
ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.
|
|
3764
|
-
ctx.lineWidth = baseWidth * 1.
|
|
4027
|
+
ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.34})`;
|
|
4028
|
+
ctx.lineWidth = baseWidth * 1.45;
|
|
3765
4029
|
ctx.lineCap = "round";
|
|
3766
4030
|
ctx.lineJoin = "round";
|
|
3767
4031
|
ctx.beginPath();
|
|
@@ -3771,8 +4035,8 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3771
4035
|
}
|
|
3772
4036
|
ctx.stroke();
|
|
3773
4037
|
|
|
3774
|
-
ctx.strokeStyle = `rgba(234, 247, 255, ${wake.opacity})`;
|
|
3775
|
-
ctx.lineWidth = baseWidth;
|
|
4038
|
+
ctx.strokeStyle = `rgba(234, 247, 255, ${wake.opacity * 0.72})`;
|
|
4039
|
+
ctx.lineWidth = baseWidth * 0.72;
|
|
3776
4040
|
ctx.lineCap = "round";
|
|
3777
4041
|
ctx.lineJoin = "round";
|
|
3778
4042
|
ctx.beginPath();
|
|
@@ -3783,13 +4047,14 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3783
4047
|
ctx.stroke();
|
|
3784
4048
|
|
|
3785
4049
|
for (const entry of projected.slice(1, 5)) {
|
|
3786
|
-
|
|
4050
|
+
const foam = entry.foam ?? 0.3;
|
|
4051
|
+
ctx.fillStyle = `rgba(239, 248, 255, ${wake.opacity * foam * 0.92})`;
|
|
3787
4052
|
ctx.beginPath();
|
|
3788
4053
|
ctx.ellipse(
|
|
3789
4054
|
entry.projected.x,
|
|
3790
4055
|
entry.projected.y,
|
|
3791
|
-
baseWidth * 0.
|
|
3792
|
-
baseWidth * 0.
|
|
4056
|
+
baseWidth * 0.54,
|
|
4057
|
+
baseWidth * 0.28,
|
|
3793
4058
|
0,
|
|
3794
4059
|
0,
|
|
3795
4060
|
Math.PI * 2
|
|
@@ -3809,15 +4074,25 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
|
3809
4074
|
const radiusX = Math.hypot(xAxis.x - center.x, xAxis.y - center.y);
|
|
3810
4075
|
const radiusY = Math.hypot(zAxis.x - center.x, zAxis.y - center.y);
|
|
3811
4076
|
ctx.strokeStyle = `rgba(216, 235, 255, ${ring.opacity})`;
|
|
3812
|
-
ctx.lineWidth = clamp((radiusX + radiusY) * 0.
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
4077
|
+
ctx.lineWidth = clamp((radiusX + radiusY) * 0.014, 0.65, 1.8);
|
|
4078
|
+
for (let segment = 0; segment < 5; segment += 1) {
|
|
4079
|
+
if (pseudoRandom(segment * 31 + radiusX * 0.7 + radiusY * 0.3) < 0.32) {
|
|
4080
|
+
continue;
|
|
4081
|
+
}
|
|
4082
|
+
const startAngle = segment * 1.22 + stateTimePhase(center.x, center.y) * 0.04;
|
|
4083
|
+
ctx.beginPath();
|
|
4084
|
+
ctx.ellipse(center.x, center.y, radiusX, radiusY, 0, startAngle, startAngle + 0.48);
|
|
4085
|
+
ctx.stroke();
|
|
4086
|
+
}
|
|
3816
4087
|
}
|
|
3817
4088
|
|
|
3818
4089
|
ctx.restore();
|
|
3819
4090
|
}
|
|
3820
4091
|
|
|
4092
|
+
function stateTimePhase(x, y) {
|
|
4093
|
+
return Math.sin(x * 0.013 + y * 0.017);
|
|
4094
|
+
}
|
|
4095
|
+
|
|
3821
4096
|
function renderScene(
|
|
3822
4097
|
ctx,
|
|
3823
4098
|
canvas,
|
|
@@ -3899,6 +4174,7 @@ function renderScene(
|
|
|
3899
4174
|
}
|
|
3900
4175
|
|
|
3901
4176
|
const waterMotionEffects = buildWaterMotionEffects(state);
|
|
4177
|
+
const shorelineFoamSegments = buildShorelineFoamSegments(state);
|
|
3902
4178
|
const lightSources = collectSceneLightSources(state, visuals);
|
|
3903
4179
|
|
|
3904
4180
|
pushHarborGeometry(camera, viewport, sceneTriangles, state);
|
|
@@ -3973,6 +4249,7 @@ function renderScene(
|
|
|
3973
4249
|
}
|
|
3974
4250
|
renderWaterMotionEffects(ctx, waterMotionEffects, camera, viewport);
|
|
3975
4251
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
4252
|
+
renderShorelineFoamSegments(ctx, shorelineFoamSegments, camera, viewport);
|
|
3976
4253
|
drawTriangles(
|
|
3977
4254
|
ctx,
|
|
3978
4255
|
sceneTriangles,
|
|
@@ -4087,7 +4364,7 @@ function updateSceneState(state, dt, shipModel, featureAdapters) {
|
|
|
4087
4364
|
advanceShowcaseClothSimulationState(clothState, {
|
|
4088
4365
|
dt,
|
|
4089
4366
|
time: state.time,
|
|
4090
|
-
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.
|
|
4367
|
+
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.58),
|
|
4091
4368
|
waveInfluence: sampleWave(state, FLAG_LAYOUT.origin.x + FLAG_LAYOUT.width * 0.55, FLAG_LAYOUT.origin.z + FLAG_LAYOUT.width * 0.48, state.time),
|
|
4092
4369
|
});
|
|
4093
4370
|
updatePhysicsSnapshot(state, shipModel, featureAdapters.physics);
|
|
@@ -4320,6 +4597,8 @@ function updatePhysicsSnapshot(state, shipModel, physicsFeatures) {
|
|
|
4320
4597
|
|
|
4321
4598
|
export {
|
|
4322
4599
|
advanceShowcaseClothSimulationState as __testOnlyAdvanceShowcaseClothSimulationState,
|
|
4600
|
+
buildClothSurface as __testOnlyBuildClothSurface,
|
|
4601
|
+
buildShorelineFoamSegments as __testOnlyBuildShorelineFoamSegments,
|
|
4323
4602
|
buildWaterBands as __testOnlyBuildWaterBands,
|
|
4324
4603
|
buildWaterMotionEffects as __testOnlyBuildWaterMotionEffects,
|
|
4325
4604
|
collectSceneLightSources as __testOnlyCollectSceneLightSources,
|