@plasius/gpu-shared 0.1.6 → 0.1.9
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 +54 -0
- package/README.md +6 -0
- package/dist/index.cjs +517 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{showcase-runtime-JZIYGQAU.js → showcase-runtime-55OVDYWT.js} +517 -126
- package/dist/showcase-runtime-55OVDYWT.js.map +1 -0
- package/package.json +2 -2
- package/src/showcase-runtime.js +569 -151
- package/dist/showcase-runtime-JZIYGQAU.js.map +0 -1
package/src/showcase-runtime.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clothGarmentKinds,
|
|
3
3
|
clothProfileNames,
|
|
4
|
-
createClothContinuityEnvelope,
|
|
5
4
|
createClothRepresentationPlan,
|
|
6
5
|
selectClothRepresentationBand,
|
|
7
6
|
} from "@plasius/gpu-cloth";
|
|
@@ -97,6 +96,12 @@ const HARBOR_TORCHES = Object.freeze([
|
|
|
97
96
|
Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
|
|
98
97
|
Object.freeze({ x: -10.4, y: 1.28, z: 0.82, glow: 0.92 }),
|
|
99
98
|
]);
|
|
99
|
+
const FLAG_LAYOUT = Object.freeze({
|
|
100
|
+
origin: Object.freeze({ x: -3.5, y: 5.9, z: 2.4 }),
|
|
101
|
+
width: 4.8,
|
|
102
|
+
height: 2.7,
|
|
103
|
+
mastOffsetX: 1.8,
|
|
104
|
+
});
|
|
100
105
|
function injectStyles() {
|
|
101
106
|
if (document.getElementById(STYLE_ID)) {
|
|
102
107
|
return;
|
|
@@ -722,6 +727,282 @@ function readVisualNumber(value, fallback) {
|
|
|
722
727
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
723
728
|
}
|
|
724
729
|
|
|
730
|
+
function resolveClothPresentation(state, meshDetail) {
|
|
731
|
+
const clothPlan = createClothRepresentationPlan({
|
|
732
|
+
garmentId: "shore-flag",
|
|
733
|
+
kind: state.focus === "cloth" ? "flag" : clothGarmentKinds[0],
|
|
734
|
+
profile: state.focus === "cloth" ? "cinematic" : clothProfileNames[0],
|
|
735
|
+
supportsRayTracing: true,
|
|
736
|
+
nearFieldMaxMeters: 18,
|
|
737
|
+
midFieldMaxMeters: 55,
|
|
738
|
+
farFieldMaxMeters: 180,
|
|
739
|
+
});
|
|
740
|
+
const preset = CAMERA_PRESETS[state.focus] ?? CAMERA_PRESETS.integrated;
|
|
741
|
+
const fallbackEye = state.camera.eye
|
|
742
|
+
? state.camera.eye
|
|
743
|
+
: addVec3(
|
|
744
|
+
state.camera.target,
|
|
745
|
+
vec3(
|
|
746
|
+
Math.sin(state.camera.yaw ?? preset.yaw) * Math.cos(state.camera.pitch ?? preset.pitch) * (state.camera.distance ?? preset.distance),
|
|
747
|
+
Math.sin(state.camera.pitch ?? preset.pitch) * (state.camera.distance ?? preset.distance),
|
|
748
|
+
Math.cos(state.camera.yaw ?? preset.yaw) * Math.cos(state.camera.pitch ?? preset.pitch) * (state.camera.distance ?? preset.distance)
|
|
749
|
+
)
|
|
750
|
+
);
|
|
751
|
+
const cameraDistance = lengthVec3(subVec3(state.camera.target, fallbackEye));
|
|
752
|
+
const band = selectClothRepresentationBand(cameraDistance, clothPlan.thresholds);
|
|
753
|
+
const representation =
|
|
754
|
+
clothPlan.representations.find((entry) => entry.band === band) ?? clothPlan.representations[0];
|
|
755
|
+
return {
|
|
756
|
+
clothPlan,
|
|
757
|
+
band,
|
|
758
|
+
continuity: representation.continuity,
|
|
759
|
+
representation,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function getFlagRestPosition(rows, cols, row, column) {
|
|
764
|
+
const u = cols <= 1 ? 0 : column / (cols - 1);
|
|
765
|
+
const v = rows <= 1 ? 0 : row / (rows - 1);
|
|
766
|
+
return vec3(
|
|
767
|
+
FLAG_LAYOUT.origin.x + u * FLAG_LAYOUT.mastOffsetX,
|
|
768
|
+
FLAG_LAYOUT.origin.y - FLAG_LAYOUT.height * v - u * u * 0.08,
|
|
769
|
+
FLAG_LAYOUT.origin.z + FLAG_LAYOUT.width * u
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function buildClothConstraints(rows, cols, restPositions) {
|
|
774
|
+
const constraints = [];
|
|
775
|
+
const indexFor = (row, column) => row * cols + column;
|
|
776
|
+
const pushConstraint = (a, b, stiffness) => {
|
|
777
|
+
constraints.push(
|
|
778
|
+
Object.freeze({
|
|
779
|
+
a,
|
|
780
|
+
b,
|
|
781
|
+
restLength: lengthVec3(subVec3(restPositions[a], restPositions[b])),
|
|
782
|
+
stiffness,
|
|
783
|
+
})
|
|
784
|
+
);
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
for (let row = 0; row < rows; row += 1) {
|
|
788
|
+
for (let column = 0; column < cols; column += 1) {
|
|
789
|
+
const index = indexFor(row, column);
|
|
790
|
+
if (column + 1 < cols) {
|
|
791
|
+
pushConstraint(index, indexFor(row, column + 1), 0.92);
|
|
792
|
+
}
|
|
793
|
+
if (row + 1 < rows) {
|
|
794
|
+
pushConstraint(index, indexFor(row + 1, column), 0.9);
|
|
795
|
+
}
|
|
796
|
+
if (column + 1 < cols && row + 1 < rows) {
|
|
797
|
+
pushConstraint(index, indexFor(row + 1, column + 1), 0.66);
|
|
798
|
+
}
|
|
799
|
+
if (column - 1 >= 0 && row + 1 < rows) {
|
|
800
|
+
pushConstraint(index, indexFor(row + 1, column - 1), 0.66);
|
|
801
|
+
}
|
|
802
|
+
if (column + 2 < cols) {
|
|
803
|
+
pushConstraint(index, indexFor(row, column + 2), 0.22);
|
|
804
|
+
}
|
|
805
|
+
if (row + 2 < rows) {
|
|
806
|
+
pushConstraint(index, indexFor(row + 2, column), 0.18);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return Object.freeze(constraints);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function createShowcaseClothSimulationState(options = {}) {
|
|
815
|
+
const rows = Math.max(4, options.rows ?? 11);
|
|
816
|
+
const cols = Math.max(4, options.cols ?? 16);
|
|
817
|
+
const continuity = options.continuity ?? {
|
|
818
|
+
broadMotionFloor: 0.72,
|
|
819
|
+
wrinkleFloor: 0.56,
|
|
820
|
+
};
|
|
821
|
+
const representation = options.representation ?? {
|
|
822
|
+
mesh: {
|
|
823
|
+
solverIterations: 6,
|
|
824
|
+
wrinkleLayers: 2,
|
|
825
|
+
},
|
|
826
|
+
};
|
|
827
|
+
const restPositions = [];
|
|
828
|
+
const positions = [];
|
|
829
|
+
const previousPositions = [];
|
|
830
|
+
const uvs = [];
|
|
831
|
+
const phaseOffsets = [];
|
|
832
|
+
const pinned = [];
|
|
833
|
+
|
|
834
|
+
for (let row = 0; row < rows; row += 1) {
|
|
835
|
+
for (let column = 0; column < cols; column += 1) {
|
|
836
|
+
const index = row * cols + column;
|
|
837
|
+
const u = cols <= 1 ? 0 : column / (cols - 1);
|
|
838
|
+
const v = rows <= 1 ? 0 : row / (rows - 1);
|
|
839
|
+
const rest = getFlagRestPosition(rows, cols, row, column);
|
|
840
|
+
const preload = vec3(
|
|
841
|
+
u * 0.04,
|
|
842
|
+
Math.sin(v * Math.PI) * 0.02 * continuity.wrinkleFloor,
|
|
843
|
+
-u * 0.12
|
|
844
|
+
);
|
|
845
|
+
const pinnedPoint = column === 0;
|
|
846
|
+
restPositions.push(rest);
|
|
847
|
+
positions.push(pinnedPoint ? vec3(rest.x, rest.y, rest.z) : addVec3(rest, preload));
|
|
848
|
+
previousPositions.push(
|
|
849
|
+
pinnedPoint
|
|
850
|
+
? vec3(rest.x, rest.y, rest.z)
|
|
851
|
+
: addVec3(rest, scaleVec3(preload, 0.35))
|
|
852
|
+
);
|
|
853
|
+
uvs.push(Object.freeze({ u, v }));
|
|
854
|
+
phaseOffsets.push(pseudoRandom(index + 17) * Math.PI * 2);
|
|
855
|
+
pinned.push(pinnedPoint);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return {
|
|
860
|
+
rows,
|
|
861
|
+
cols,
|
|
862
|
+
continuity,
|
|
863
|
+
representation,
|
|
864
|
+
restPositions,
|
|
865
|
+
positions,
|
|
866
|
+
previousPositions,
|
|
867
|
+
constraints: buildClothConstraints(rows, cols, restPositions),
|
|
868
|
+
indices: Object.freeze(
|
|
869
|
+
Array.from({ length: (rows - 1) * (cols - 1) * 6 }, (_, listIndex) => listIndex)
|
|
870
|
+
.map((_, listIndex, source) => {
|
|
871
|
+
if (listIndex >= source.length) {
|
|
872
|
+
return 0;
|
|
873
|
+
}
|
|
874
|
+
const quadIndex = Math.floor(listIndex / 6);
|
|
875
|
+
const quadColumn = quadIndex % (cols - 1);
|
|
876
|
+
const quadRow = Math.floor(quadIndex / (cols - 1));
|
|
877
|
+
const base = quadRow * cols + quadColumn;
|
|
878
|
+
return [base, base + 1, base + cols + 1, base, base + cols + 1, base + cols][listIndex % 6];
|
|
879
|
+
})
|
|
880
|
+
),
|
|
881
|
+
uvs,
|
|
882
|
+
phaseOffsets,
|
|
883
|
+
pinned,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function resetPinnedClothPoints(clothState) {
|
|
888
|
+
for (let index = 0; index < clothState.positions.length; index += 1) {
|
|
889
|
+
if (!clothState.pinned[index]) {
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
const anchor = clothState.restPositions[index];
|
|
893
|
+
clothState.positions[index] = vec3(anchor.x, anchor.y, anchor.z);
|
|
894
|
+
clothState.previousPositions[index] = vec3(anchor.x, anchor.y, anchor.z);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function satisfyClothConstraint(clothState, constraint) {
|
|
899
|
+
const a = clothState.positions[constraint.a];
|
|
900
|
+
const b = clothState.positions[constraint.b];
|
|
901
|
+
const delta = subVec3(b, a);
|
|
902
|
+
const distance = lengthVec3(delta);
|
|
903
|
+
if (distance <= 0.0001) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const correctionScale =
|
|
908
|
+
((distance - constraint.restLength) / distance) * 0.5 * constraint.stiffness;
|
|
909
|
+
const correction = scaleVec3(delta, correctionScale);
|
|
910
|
+
if (!clothState.pinned[constraint.a]) {
|
|
911
|
+
clothState.positions[constraint.a] = addVec3(a, correction);
|
|
912
|
+
}
|
|
913
|
+
if (!clothState.pinned[constraint.b]) {
|
|
914
|
+
clothState.positions[constraint.b] = subVec3(b, correction);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function advanceShowcaseClothSimulationState(clothState, options = {}) {
|
|
919
|
+
const dt = clamp(options.dt ?? 1 / 60, 1 / 240, 1 / 18);
|
|
920
|
+
const time = readVisualNumber(options.time, 0);
|
|
921
|
+
const flagMotion = readVisualNumber(options.flagMotion, 0.92);
|
|
922
|
+
const waveInfluence = readVisualNumber(options.waveInfluence, 0);
|
|
923
|
+
const wrinkleLayers = Math.max(1, clothState.representation.mesh?.wrinkleLayers ?? 2);
|
|
924
|
+
const solverIterations = clamp(
|
|
925
|
+
Math.round(clothState.representation.mesh?.solverIterations ?? 6),
|
|
926
|
+
2,
|
|
927
|
+
10
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
for (let index = 0; index < clothState.positions.length; index += 1) {
|
|
931
|
+
if (clothState.pinned[index]) {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const current = clothState.positions[index];
|
|
936
|
+
const previous = clothState.previousPositions[index];
|
|
937
|
+
const { u, v } = clothState.uvs[index];
|
|
938
|
+
const phase = clothState.phaseOffsets[index];
|
|
939
|
+
const broadMotion = clothState.continuity.broadMotionFloor;
|
|
940
|
+
const wrinkleMotion = clothState.continuity.wrinkleFloor;
|
|
941
|
+
const gustPhase = time * 2.1 + phase + u * 4.4 + v * 2.3;
|
|
942
|
+
const wrinklePhase = time * 5.3 + phase * 0.72 + u * 9.6 + v * 7.1;
|
|
943
|
+
const windDirection = normalizeVec3(
|
|
944
|
+
vec3(
|
|
945
|
+
0.18 + Math.sin(gustPhase) * (0.12 + broadMotion * 0.09),
|
|
946
|
+
Math.cos(time * 1.4 + phase + v * 4.8) * 0.06 * wrinkleMotion,
|
|
947
|
+
1 + Math.sin(gustPhase * 0.74) * 0.18
|
|
948
|
+
)
|
|
949
|
+
);
|
|
950
|
+
const windStrength =
|
|
951
|
+
(1.6 + broadMotion * 1.25 + wrinkleLayers * 0.12) *
|
|
952
|
+
flagMotion *
|
|
953
|
+
(0.44 + u * 1.14);
|
|
954
|
+
const wrinkleForce = vec3(
|
|
955
|
+
Math.sin(wrinklePhase) * 0.22 * wrinkleMotion * flagMotion,
|
|
956
|
+
Math.cos(wrinklePhase * 0.7) * 0.08 * wrinkleMotion,
|
|
957
|
+
Math.cos(wrinklePhase) * 0.14 * broadMotion * flagMotion
|
|
958
|
+
);
|
|
959
|
+
const acceleration = addVec3(
|
|
960
|
+
vec3(0, -0.48 - u * 0.08, 0),
|
|
961
|
+
addVec3(
|
|
962
|
+
scaleVec3(windDirection, windStrength),
|
|
963
|
+
addVec3(
|
|
964
|
+
wrinkleForce,
|
|
965
|
+
vec3(waveInfluence * (0.04 + u * 0.08), 0, waveInfluence * 0.16)
|
|
966
|
+
)
|
|
967
|
+
)
|
|
968
|
+
);
|
|
969
|
+
const inertia = scaleVec3(subVec3(current, previous), 0.987);
|
|
970
|
+
const next = addVec3(addVec3(current, inertia), scaleVec3(acceleration, dt * dt));
|
|
971
|
+
clothState.previousPositions[index] = vec3(current.x, current.y, current.z);
|
|
972
|
+
clothState.positions[index] = next;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
resetPinnedClothPoints(clothState);
|
|
976
|
+
for (let iteration = 0; iteration < solverIterations; iteration += 1) {
|
|
977
|
+
for (const constraint of clothState.constraints) {
|
|
978
|
+
satisfyClothConstraint(clothState, constraint);
|
|
979
|
+
}
|
|
980
|
+
resetPinnedClothPoints(clothState);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return clothState;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function ensureShowcaseClothState(state, meshDetail, clothPresentation) {
|
|
987
|
+
if (
|
|
988
|
+
!state.clothState ||
|
|
989
|
+
state.clothState.rows !== meshDetail.rows ||
|
|
990
|
+
state.clothState.cols !== meshDetail.cols
|
|
991
|
+
) {
|
|
992
|
+
state.clothState = createShowcaseClothSimulationState({
|
|
993
|
+
rows: meshDetail.rows,
|
|
994
|
+
cols: meshDetail.cols,
|
|
995
|
+
continuity: clothPresentation.continuity,
|
|
996
|
+
representation: clothPresentation.representation,
|
|
997
|
+
});
|
|
998
|
+
} else {
|
|
999
|
+
state.clothState.continuity = clothPresentation.continuity;
|
|
1000
|
+
state.clothState.representation = clothPresentation.representation;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return state.clothState;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
725
1006
|
function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {}) {
|
|
726
1007
|
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
727
1008
|
const defaults = {
|
|
@@ -831,74 +1112,18 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
831
1112
|
}
|
|
832
1113
|
|
|
833
1114
|
function buildClothSurface(model, state, meshDetail, visuals) {
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
kind: state.focus === "cloth" ? "flag" : clothGarmentKinds[0],
|
|
837
|
-
profile: state.focus === "cloth" ? "cinematic" : clothProfileNames[0],
|
|
838
|
-
supportsRayTracing: true,
|
|
839
|
-
nearFieldMaxMeters: 18,
|
|
840
|
-
midFieldMaxMeters: 55,
|
|
841
|
-
farFieldMaxMeters: 180,
|
|
842
|
-
});
|
|
843
|
-
const cameraDistance = lengthVec3(subVec3(state.camera.target, state.camera.eye ?? vec3(...CAMERA_PRESETS[state.focus].target)));
|
|
844
|
-
const band = selectClothRepresentationBand(cameraDistance, clothPlan.thresholds);
|
|
845
|
-
const representation =
|
|
846
|
-
clothPlan.representations.find((entry) => entry.band === band) ?? clothPlan.representations[0];
|
|
847
|
-
const continuity = createClothContinuityEnvelope({ garmentId: "shore-flag" });
|
|
848
|
-
|
|
849
|
-
const cols = meshDetail.cols;
|
|
850
|
-
const rows = meshDetail.rows;
|
|
851
|
-
const origin = vec3(-3.5, 5.9, 2.4);
|
|
852
|
-
const width = 4.8;
|
|
853
|
-
const height = 2.7;
|
|
854
|
-
const positions = [];
|
|
855
|
-
const indices = [];
|
|
856
|
-
const time = state.time;
|
|
857
|
-
|
|
858
|
-
for (let row = 0; row < rows; row += 1) {
|
|
859
|
-
for (let column = 0; column < cols; column += 1) {
|
|
860
|
-
const u = column / (cols - 1);
|
|
861
|
-
const v = row / (rows - 1);
|
|
862
|
-
const gust =
|
|
863
|
-
Math.sin(time * 1.9 + v * 3.2 + u * 2.1) *
|
|
864
|
-
continuity.broadMotionFloor *
|
|
865
|
-
visuals.flagMotion;
|
|
866
|
-
const wrinkle =
|
|
867
|
-
Math.sin(time * 4.4 + u * 9.2 + v * 5.6) *
|
|
868
|
-
continuity.wrinkleFloor *
|
|
869
|
-
0.22 *
|
|
870
|
-
Math.max(0.55, visuals.flagMotion);
|
|
871
|
-
const x = origin.x + u * 1.8 + gust * 0.55 * (u * 0.9);
|
|
872
|
-
const y = origin.y - height * v + wrinkle * 0.2;
|
|
873
|
-
const z = origin.z + width * u + gust * 0.72 * (u * 0.85);
|
|
874
|
-
const flap =
|
|
875
|
-
Math.cos(time * 2.7 + u * 7.4 + v * 3.8) *
|
|
876
|
-
continuity.broadMotionFloor *
|
|
877
|
-
0.28 *
|
|
878
|
-
visuals.flagMotion;
|
|
879
|
-
positions.push(vec3(x + flap, y, z));
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
for (let row = 0; row < rows - 1; row += 1) {
|
|
884
|
-
for (let column = 0; column < cols - 1; column += 1) {
|
|
885
|
-
const a = row * cols + column;
|
|
886
|
-
const b = a + 1;
|
|
887
|
-
const c = a + cols + 1;
|
|
888
|
-
const d = a + cols;
|
|
889
|
-
indices.push(a, b, c, a, c, d);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
1115
|
+
const clothPresentation = resolveClothPresentation(state, meshDetail);
|
|
1116
|
+
const clothState = ensureShowcaseClothState(state, meshDetail, clothPresentation);
|
|
892
1117
|
|
|
893
1118
|
return {
|
|
894
|
-
clothPlan,
|
|
895
|
-
band,
|
|
896
|
-
representation,
|
|
897
|
-
continuity,
|
|
1119
|
+
clothPlan: clothPresentation.clothPlan,
|
|
1120
|
+
band: clothPresentation.band,
|
|
1121
|
+
representation: clothPresentation.representation,
|
|
1122
|
+
continuity: clothPresentation.continuity,
|
|
898
1123
|
color: visuals.flagColor,
|
|
899
|
-
positions,
|
|
900
|
-
indices,
|
|
901
|
-
grid: { rows, cols },
|
|
1124
|
+
positions: clothState.positions.map((point) => vec3(point.x, point.y, point.z)),
|
|
1125
|
+
indices: clothState.indices,
|
|
1126
|
+
grid: { rows: clothState.rows, cols: clothState.cols },
|
|
902
1127
|
};
|
|
903
1128
|
}
|
|
904
1129
|
|
|
@@ -990,6 +1215,64 @@ function sampleWave(state, x, z, time) {
|
|
|
990
1215
|
);
|
|
991
1216
|
}
|
|
992
1217
|
|
|
1218
|
+
function buildWaterMotionEffects(state) {
|
|
1219
|
+
const wakeTrails = [];
|
|
1220
|
+
const rippleRings = state.waveImpulses.map((impulse) => {
|
|
1221
|
+
const radius = impulse.radius + (1 - impulse.life) * 4.8;
|
|
1222
|
+
return Object.freeze({
|
|
1223
|
+
center: vec3(
|
|
1224
|
+
impulse.x,
|
|
1225
|
+
sampleWave(state, impulse.x, impulse.z, state.time) * 0.24 + 0.06,
|
|
1226
|
+
impulse.z
|
|
1227
|
+
),
|
|
1228
|
+
radius,
|
|
1229
|
+
opacity: clamp(impulse.life * 0.28, 0.08, 0.3),
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
for (const ship of state.ships) {
|
|
1234
|
+
const speed = Math.hypot(ship.velocity.x, ship.velocity.z);
|
|
1235
|
+
if (speed <= 0.18) {
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const direction = normalizeVec3(vec3(ship.velocity.x, 0, ship.velocity.z));
|
|
1240
|
+
const behind = scaleVec3(direction, -1);
|
|
1241
|
+
const lateral = vec3(-direction.z, 0, direction.x);
|
|
1242
|
+
const points = [];
|
|
1243
|
+
for (let sampleIndex = 0; sampleIndex < 6; sampleIndex += 1) {
|
|
1244
|
+
const along = 1 + sampleIndex * 1.45;
|
|
1245
|
+
const lateralOffset =
|
|
1246
|
+
Math.sin(state.time * 1.2 + sampleIndex * 0.8 + readVisualNumber(ship.wanderPhase, 0)) * 0.12;
|
|
1247
|
+
const worldPoint = addVec3(
|
|
1248
|
+
ship.position,
|
|
1249
|
+
addVec3(scaleVec3(behind, along), scaleVec3(lateral, lateralOffset))
|
|
1250
|
+
);
|
|
1251
|
+
points.push(
|
|
1252
|
+
Object.freeze({
|
|
1253
|
+
center: vec3(
|
|
1254
|
+
worldPoint.x,
|
|
1255
|
+
sampleWave(state, worldPoint.x, worldPoint.z, state.time) * 0.24 + 0.04,
|
|
1256
|
+
worldPoint.z
|
|
1257
|
+
),
|
|
1258
|
+
width: 0.34 + sampleIndex * 0.13,
|
|
1259
|
+
})
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
wakeTrails.push(
|
|
1263
|
+
Object.freeze({
|
|
1264
|
+
opacity: clamp(0.18 + speed * 0.09, 0.22, 0.46),
|
|
1265
|
+
points: Object.freeze(points),
|
|
1266
|
+
})
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
return Object.freeze({
|
|
1271
|
+
wakeTrails: Object.freeze(wakeTrails),
|
|
1272
|
+
rippleRings: Object.freeze(rippleRings),
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
993
1276
|
function buildWaterBands(state, fluidDetail, visuals) {
|
|
994
1277
|
const fluidPlan = createFluidRepresentationPlan({
|
|
995
1278
|
fluidBodyId: "harbor",
|
|
@@ -1168,6 +1451,7 @@ function createSceneState(options) {
|
|
|
1168
1451
|
contactCount: 0,
|
|
1169
1452
|
collisionCount: 0,
|
|
1170
1453
|
collisionFlash: 0,
|
|
1454
|
+
clothState: null,
|
|
1171
1455
|
physics: {
|
|
1172
1456
|
profile: physicsProfile,
|
|
1173
1457
|
plan: physicsPlan,
|
|
@@ -1902,27 +2186,78 @@ function renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength
|
|
|
1902
2186
|
});
|
|
1903
2187
|
}
|
|
1904
2188
|
|
|
1905
|
-
function
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
2189
|
+
function collectSceneLightSources(state, visuals) {
|
|
2190
|
+
const directLights = [];
|
|
2191
|
+
const reflectionLights = [];
|
|
2192
|
+
const pushLight = (point, glowScale, reflectionStrength, coreColor, glowColor) => {
|
|
2193
|
+
directLights.push(
|
|
2194
|
+
Object.freeze({
|
|
2195
|
+
pass: "direct-glow",
|
|
2196
|
+
point,
|
|
2197
|
+
coreColor,
|
|
2198
|
+
glowColor,
|
|
2199
|
+
glowScale,
|
|
2200
|
+
})
|
|
2201
|
+
);
|
|
2202
|
+
if (reflectionStrength > 0) {
|
|
2203
|
+
reflectionLights.push(
|
|
2204
|
+
Object.freeze({
|
|
2205
|
+
pass: "water-reflection",
|
|
2206
|
+
point,
|
|
2207
|
+
coreColor,
|
|
2208
|
+
glowColor,
|
|
2209
|
+
glowScale,
|
|
2210
|
+
reflectionStrength,
|
|
2211
|
+
})
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
|
|
2216
|
+
for (const torch of HARBOR_TORCHES) {
|
|
2217
|
+
pushLight(
|
|
2218
|
+
vec3(torch.x, torch.y, torch.z),
|
|
2219
|
+
torch.glow,
|
|
2220
|
+
visuals.lanternReflectionStrength * 0.55,
|
|
2221
|
+
visuals.torchCore,
|
|
2222
|
+
visuals.torchGlow
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
for (const ship of state.ships) {
|
|
2227
|
+
const lanterns = Array.isArray(ship.lanterns) ? ship.lanterns : SHIP_LANTERNS;
|
|
2228
|
+
const strength = readVisualNumber(ship.lanternStrength, 1);
|
|
2229
|
+
for (const lantern of lanterns) {
|
|
2230
|
+
const point = transformPoint(
|
|
2231
|
+
vec3(lantern.x, lantern.y, lantern.z),
|
|
2232
|
+
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE }
|
|
2233
|
+
);
|
|
2234
|
+
pushLight(
|
|
2235
|
+
point,
|
|
2236
|
+
lantern.glow * strength,
|
|
2237
|
+
visuals.lanternReflectionStrength,
|
|
2238
|
+
visuals.lanternCore,
|
|
2239
|
+
visuals.lanternGlow
|
|
2240
|
+
);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
return Object.freeze({
|
|
2245
|
+
directLights: Object.freeze(directLights),
|
|
2246
|
+
reflectionLights: Object.freeze(reflectionLights),
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
function renderDirectLightGlow(ctx, source, camera, viewport) {
|
|
2251
|
+
const projected = projectPoint(source.point, camera, viewport);
|
|
1917
2252
|
if (!projected) {
|
|
1918
2253
|
return;
|
|
1919
2254
|
}
|
|
1920
2255
|
|
|
1921
|
-
const radius = clamp((1 / projected.depth) * 420 * glowScale, 4, 34);
|
|
2256
|
+
const radius = clamp((1 / projected.depth) * 420 * source.glowScale, 4, 34);
|
|
1922
2257
|
const halo = ctx.createRadialGradient(projected.x, projected.y, radius * 0.12, projected.x, projected.y, radius);
|
|
1923
|
-
halo.addColorStop(0, colorToRgba(coreColor, 0.98));
|
|
1924
|
-
halo.addColorStop(0.5, colorToRgba(glowColor, 0.42));
|
|
1925
|
-
halo.addColorStop(1, colorToRgba(glowColor, 0));
|
|
2258
|
+
halo.addColorStop(0, colorToRgba(source.coreColor, 0.98));
|
|
2259
|
+
halo.addColorStop(0.5, colorToRgba(source.glowColor, 0.42));
|
|
2260
|
+
halo.addColorStop(1, colorToRgba(source.glowColor, 0));
|
|
1926
2261
|
ctx.save();
|
|
1927
2262
|
ctx.globalCompositeOperation = "screen";
|
|
1928
2263
|
ctx.fillStyle = halo;
|
|
@@ -1931,82 +2266,134 @@ function renderGlowLight(
|
|
|
1931
2266
|
ctx.fill();
|
|
1932
2267
|
ctx.restore();
|
|
1933
2268
|
|
|
1934
|
-
ctx.fillStyle = colorToRgba(coreColor, 0.98);
|
|
2269
|
+
ctx.fillStyle = colorToRgba(source.coreColor, 0.98);
|
|
1935
2270
|
ctx.beginPath();
|
|
1936
2271
|
ctx.arc(projected.x, projected.y, Math.max(1.2, radius * 0.16), 0, Math.PI * 2);
|
|
1937
2272
|
ctx.fill();
|
|
2273
|
+
}
|
|
1938
2274
|
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2275
|
+
function renderWaterLightReflection(ctx, source, state, camera, viewport) {
|
|
2276
|
+
const projected = projectPoint(source.point, camera, viewport);
|
|
2277
|
+
if (!projected) {
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
const radius = clamp((1 / projected.depth) * 420 * source.glowScale, 4, 34);
|
|
2282
|
+
const waterline = sampleWave(state, source.point.x, source.point.z, state.time) * 0.22;
|
|
2283
|
+
const reflectedPoint = vec3(
|
|
2284
|
+
source.point.x,
|
|
2285
|
+
waterline - (source.point.y - waterline) * 0.58,
|
|
2286
|
+
source.point.z + 0.08
|
|
2287
|
+
);
|
|
2288
|
+
const reflected = projectPoint(reflectedPoint, camera, viewport);
|
|
2289
|
+
if (!reflected) {
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
const reflectionRadius = radius * 0.72;
|
|
2294
|
+
const glow = ctx.createRadialGradient(
|
|
2295
|
+
reflected.x,
|
|
2296
|
+
reflected.y,
|
|
2297
|
+
reflectionRadius * 0.1,
|
|
2298
|
+
reflected.x,
|
|
2299
|
+
reflected.y,
|
|
2300
|
+
reflectionRadius
|
|
2301
|
+
);
|
|
2302
|
+
glow.addColorStop(0, colorToRgba(source.coreColor, source.reflectionStrength * 0.34));
|
|
2303
|
+
glow.addColorStop(1, colorToRgba(source.glowColor, 0));
|
|
2304
|
+
ctx.save();
|
|
2305
|
+
ctx.globalCompositeOperation = "screen";
|
|
2306
|
+
ctx.fillStyle = glow;
|
|
2307
|
+
ctx.beginPath();
|
|
2308
|
+
ctx.ellipse(
|
|
2309
|
+
reflected.x,
|
|
2310
|
+
reflected.y,
|
|
2311
|
+
reflectionRadius * 0.34,
|
|
2312
|
+
reflectionRadius,
|
|
2313
|
+
0,
|
|
2314
|
+
0,
|
|
2315
|
+
Math.PI * 2
|
|
2316
|
+
);
|
|
2317
|
+
ctx.fill();
|
|
2318
|
+
ctx.restore();
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
2322
|
+
ctx.save();
|
|
2323
|
+
ctx.globalCompositeOperation = "screen";
|
|
2324
|
+
|
|
2325
|
+
for (const wake of effects.wakeTrails) {
|
|
2326
|
+
const projected = wake.points
|
|
2327
|
+
.map((point) => ({
|
|
2328
|
+
projected: projectPoint(point.center, camera, viewport),
|
|
2329
|
+
width: point.width,
|
|
2330
|
+
}))
|
|
2331
|
+
.filter((entry) => entry.projected);
|
|
2332
|
+
if (projected.length < 2) {
|
|
2333
|
+
continue;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
const averageDepth =
|
|
2337
|
+
projected.reduce((total, entry) => total + entry.projected.depth, 0) / projected.length;
|
|
2338
|
+
const averageWidth =
|
|
2339
|
+
projected.reduce((total, entry) => total + entry.width, 0) / projected.length;
|
|
2340
|
+
const baseWidth = clamp((averageWidth / Math.max(0.25, averageDepth)) * 180, 1.6, 5.4);
|
|
2341
|
+
ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.52})`;
|
|
2342
|
+
ctx.lineWidth = baseWidth * 1.9;
|
|
2343
|
+
ctx.lineCap = "round";
|
|
2344
|
+
ctx.lineJoin = "round";
|
|
2345
|
+
ctx.beginPath();
|
|
2346
|
+
ctx.moveTo(projected[0].projected.x, projected[0].projected.y);
|
|
2347
|
+
for (let index = 1; index < projected.length; index += 1) {
|
|
2348
|
+
ctx.lineTo(projected[index].projected.x, projected[index].projected.y);
|
|
2349
|
+
}
|
|
2350
|
+
ctx.stroke();
|
|
2351
|
+
|
|
2352
|
+
ctx.strokeStyle = `rgba(234, 247, 255, ${wake.opacity})`;
|
|
2353
|
+
ctx.lineWidth = baseWidth;
|
|
2354
|
+
ctx.lineCap = "round";
|
|
2355
|
+
ctx.lineJoin = "round";
|
|
2356
|
+
ctx.beginPath();
|
|
2357
|
+
ctx.moveTo(projected[0].projected.x, projected[0].projected.y);
|
|
2358
|
+
for (let index = 1; index < projected.length; index += 1) {
|
|
2359
|
+
ctx.lineTo(projected[index].projected.x, projected[index].projected.y);
|
|
2360
|
+
}
|
|
2361
|
+
ctx.stroke();
|
|
2362
|
+
|
|
2363
|
+
for (const entry of projected.slice(1, 5)) {
|
|
2364
|
+
ctx.fillStyle = `rgba(239, 248, 255, ${wake.opacity * 0.76})`;
|
|
1958
2365
|
ctx.beginPath();
|
|
1959
2366
|
ctx.ellipse(
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2367
|
+
entry.projected.x,
|
|
2368
|
+
entry.projected.y,
|
|
2369
|
+
baseWidth * 0.72,
|
|
2370
|
+
baseWidth * 0.44,
|
|
1964
2371
|
0,
|
|
1965
2372
|
0,
|
|
1966
2373
|
Math.PI * 2
|
|
1967
2374
|
);
|
|
1968
2375
|
ctx.fill();
|
|
1969
|
-
ctx.restore();
|
|
1970
2376
|
}
|
|
1971
2377
|
}
|
|
1972
|
-
}
|
|
1973
2378
|
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
);
|
|
1982
|
-
renderGlowLight(
|
|
1983
|
-
ctx,
|
|
1984
|
-
position,
|
|
1985
|
-
camera,
|
|
1986
|
-
viewport,
|
|
1987
|
-
visuals.lanternCore,
|
|
1988
|
-
visuals.lanternGlow,
|
|
1989
|
-
lantern.glow * strength,
|
|
1990
|
-
visuals.lanternReflectionStrength,
|
|
1991
|
-
state
|
|
1992
|
-
);
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
2379
|
+
for (const ring of effects.rippleRings) {
|
|
2380
|
+
const center = projectPoint(ring.center, camera, viewport);
|
|
2381
|
+
const xAxis = projectPoint(addVec3(ring.center, vec3(ring.radius, 0, 0)), camera, viewport);
|
|
2382
|
+
const zAxis = projectPoint(addVec3(ring.center, vec3(0, 0, ring.radius)), camera, viewport);
|
|
2383
|
+
if (!center || !xAxis || !zAxis) {
|
|
2384
|
+
continue;
|
|
2385
|
+
}
|
|
1995
2386
|
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
visuals.torchCore,
|
|
2004
|
-
visuals.torchGlow,
|
|
2005
|
-
torch.glow,
|
|
2006
|
-
visuals.lanternReflectionStrength * 0.55,
|
|
2007
|
-
state
|
|
2008
|
-
);
|
|
2387
|
+
const radiusX = Math.hypot(xAxis.x - center.x, xAxis.y - center.y);
|
|
2388
|
+
const radiusY = Math.hypot(zAxis.x - center.x, zAxis.y - center.y);
|
|
2389
|
+
ctx.strokeStyle = `rgba(216, 235, 255, ${ring.opacity})`;
|
|
2390
|
+
ctx.lineWidth = clamp((radiusX + radiusY) * 0.02, 1, 3.1);
|
|
2391
|
+
ctx.beginPath();
|
|
2392
|
+
ctx.ellipse(center.x, center.y, radiusX, radiusY, 0, 0, Math.PI * 2);
|
|
2393
|
+
ctx.stroke();
|
|
2009
2394
|
}
|
|
2395
|
+
|
|
2396
|
+
ctx.restore();
|
|
2010
2397
|
}
|
|
2011
2398
|
|
|
2012
2399
|
function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
@@ -2038,8 +2425,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
2038
2425
|
visuals
|
|
2039
2426
|
);
|
|
2040
2427
|
|
|
2041
|
-
const
|
|
2042
|
-
|
|
2428
|
+
const waterTriangles = [];
|
|
2429
|
+
const sceneTriangles = [];
|
|
2043
2430
|
const water = buildWaterBands(
|
|
2044
2431
|
state,
|
|
2045
2432
|
state.fluidDetail.getSnapshot().currentLevel.config,
|
|
@@ -2056,7 +2443,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
2056
2443
|
if (projected.some((value) => value === null)) {
|
|
2057
2444
|
continue;
|
|
2058
2445
|
}
|
|
2059
|
-
|
|
2446
|
+
waterTriangles.push({
|
|
2060
2447
|
points: projected,
|
|
2061
2448
|
depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
|
|
2062
2449
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
@@ -2067,6 +2454,10 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
2067
2454
|
}
|
|
2068
2455
|
}
|
|
2069
2456
|
|
|
2457
|
+
const waterMotionEffects = buildWaterMotionEffects(state);
|
|
2458
|
+
const lightSources = collectSceneLightSources(state, visuals);
|
|
2459
|
+
|
|
2460
|
+
pushHarborGeometry(camera, viewport, sceneTriangles, visuals);
|
|
2070
2461
|
const cloth = buildClothSurface(
|
|
2071
2462
|
state,
|
|
2072
2463
|
state,
|
|
@@ -2082,7 +2473,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
2082
2473
|
if (projected.some((value) => value === null)) {
|
|
2083
2474
|
continue;
|
|
2084
2475
|
}
|
|
2085
|
-
|
|
2476
|
+
sceneTriangles.push({
|
|
2086
2477
|
points: projected,
|
|
2087
2478
|
depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
|
|
2088
2479
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
@@ -2099,24 +2490,29 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
2099
2490
|
ship.tint,
|
|
2100
2491
|
camera,
|
|
2101
2492
|
viewport,
|
|
2102
|
-
|
|
2493
|
+
sceneTriangles,
|
|
2103
2494
|
nearLighting.rtParticipation.directShadows === "premium" ? 0.08 : 0.02
|
|
2104
2495
|
);
|
|
2105
2496
|
}
|
|
2106
2497
|
|
|
2498
|
+
drawTriangles(ctx, waterTriangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
2107
2499
|
for (const ship of state.ships) {
|
|
2108
2500
|
renderShipShadow(ctx, shipModel, ship, state, camera, viewport, lightDir, shadowStrength);
|
|
2109
2501
|
}
|
|
2110
2502
|
renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength);
|
|
2111
|
-
|
|
2112
|
-
|
|
2503
|
+
for (const source of lightSources.reflectionLights) {
|
|
2504
|
+
renderWaterLightReflection(ctx, source, state, camera, viewport);
|
|
2505
|
+
}
|
|
2506
|
+
renderWaterMotionEffects(ctx, waterMotionEffects, camera, viewport);
|
|
2113
2507
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
2114
|
-
|
|
2508
|
+
drawTriangles(ctx, sceneTriangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
2115
2509
|
renderFlagPole(ctx, camera, viewport);
|
|
2116
2510
|
renderClothAccent(ctx, cloth, camera, viewport);
|
|
2511
|
+
for (const source of lightSources.directLights) {
|
|
2512
|
+
renderDirectLightGlow(ctx, source, camera, viewport);
|
|
2513
|
+
}
|
|
2117
2514
|
for (const ship of state.ships) {
|
|
2118
2515
|
renderShipRigging(ctx, ship, camera, viewport);
|
|
2119
|
-
renderShipLanterns(ctx, ship, state, camera, viewport, visuals);
|
|
2120
2516
|
}
|
|
2121
2517
|
renderSprays(ctx, state.sprays, camera, viewport);
|
|
2122
2518
|
|
|
@@ -2194,6 +2590,21 @@ function updateSceneState(state, dt, shipModel) {
|
|
|
2194
2590
|
updateShips(state, dt, shipModel);
|
|
2195
2591
|
updateWaveImpulses(state, dt);
|
|
2196
2592
|
updateSpray(state, dt);
|
|
2593
|
+
const clothPresentation = resolveClothPresentation(
|
|
2594
|
+
state,
|
|
2595
|
+
state.clothDetail.getSnapshot().currentLevel.config
|
|
2596
|
+
);
|
|
2597
|
+
const clothState = ensureShowcaseClothState(
|
|
2598
|
+
state,
|
|
2599
|
+
state.clothDetail.getSnapshot().currentLevel.config,
|
|
2600
|
+
clothPresentation
|
|
2601
|
+
);
|
|
2602
|
+
advanceShowcaseClothSimulationState(clothState, {
|
|
2603
|
+
dt,
|
|
2604
|
+
time: state.time,
|
|
2605
|
+
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.92),
|
|
2606
|
+
waveInfluence: sampleWave(state, FLAG_LAYOUT.origin.x + FLAG_LAYOUT.width * 0.55, FLAG_LAYOUT.origin.z + FLAG_LAYOUT.width * 0.48, state.time),
|
|
2607
|
+
});
|
|
2197
2608
|
updatePhysicsSnapshot(state, shipModel);
|
|
2198
2609
|
}
|
|
2199
2610
|
|
|
@@ -2370,3 +2781,10 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
2370
2781
|
},
|
|
2371
2782
|
});
|
|
2372
2783
|
}
|
|
2784
|
+
|
|
2785
|
+
export {
|
|
2786
|
+
advanceShowcaseClothSimulationState as __testOnlyAdvanceShowcaseClothSimulationState,
|
|
2787
|
+
buildWaterMotionEffects as __testOnlyBuildWaterMotionEffects,
|
|
2788
|
+
collectSceneLightSources as __testOnlyCollectSceneLightSources,
|
|
2789
|
+
createShowcaseClothSimulationState as __testOnlyCreateShowcaseClothSimulationState,
|
|
2790
|
+
};
|