@plasius/gpu-shared 0.1.7 → 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 +37 -0
- package/README.md +6 -0
- package/dist/index.cjs +513 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{showcase-runtime-AIPDRK7G.js → showcase-runtime-55OVDYWT.js} +513 -125
- package/dist/{showcase-runtime-AIPDRK7G.js.map → showcase-runtime-55OVDYWT.js.map} +1 -1
- package/package.json +1 -1
- package/src/showcase-runtime.js +569 -151
|
@@ -6212,6 +6212,12 @@ var HARBOR_TORCHES = Object.freeze([
|
|
|
6212
6212
|
Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
|
|
6213
6213
|
Object.freeze({ x: -10.4, y: 1.28, z: 0.82, glow: 0.92 })
|
|
6214
6214
|
]);
|
|
6215
|
+
var FLAG_LAYOUT = Object.freeze({
|
|
6216
|
+
origin: Object.freeze({ x: -3.5, y: 5.9, z: 2.4 }),
|
|
6217
|
+
width: 4.8,
|
|
6218
|
+
height: 2.7,
|
|
6219
|
+
mastOffsetX: 1.8
|
|
6220
|
+
});
|
|
6215
6221
|
function injectStyles() {
|
|
6216
6222
|
if (document.getElementById(STYLE_ID)) {
|
|
6217
6223
|
return;
|
|
@@ -6784,6 +6790,250 @@ function normalizeColorOverride(color, fallback) {
|
|
|
6784
6790
|
function readVisualNumber(value, fallback) {
|
|
6785
6791
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
6786
6792
|
}
|
|
6793
|
+
function resolveClothPresentation(state, meshDetail) {
|
|
6794
|
+
const clothPlan = createClothRepresentationPlan({
|
|
6795
|
+
garmentId: "shore-flag",
|
|
6796
|
+
kind: state.focus === "cloth" ? "flag" : clothGarmentKinds[0],
|
|
6797
|
+
profile: state.focus === "cloth" ? "cinematic" : clothProfileNames[0],
|
|
6798
|
+
supportsRayTracing: true,
|
|
6799
|
+
nearFieldMaxMeters: 18,
|
|
6800
|
+
midFieldMaxMeters: 55,
|
|
6801
|
+
farFieldMaxMeters: 180
|
|
6802
|
+
});
|
|
6803
|
+
const preset = CAMERA_PRESETS[state.focus] ?? CAMERA_PRESETS.integrated;
|
|
6804
|
+
const fallbackEye = state.camera.eye ? state.camera.eye : addVec3(
|
|
6805
|
+
state.camera.target,
|
|
6806
|
+
vec3(
|
|
6807
|
+
Math.sin(state.camera.yaw ?? preset.yaw) * Math.cos(state.camera.pitch ?? preset.pitch) * (state.camera.distance ?? preset.distance),
|
|
6808
|
+
Math.sin(state.camera.pitch ?? preset.pitch) * (state.camera.distance ?? preset.distance),
|
|
6809
|
+
Math.cos(state.camera.yaw ?? preset.yaw) * Math.cos(state.camera.pitch ?? preset.pitch) * (state.camera.distance ?? preset.distance)
|
|
6810
|
+
)
|
|
6811
|
+
);
|
|
6812
|
+
const cameraDistance = lengthVec3(subVec3(state.camera.target, fallbackEye));
|
|
6813
|
+
const band = selectClothRepresentationBand(cameraDistance, clothPlan.thresholds);
|
|
6814
|
+
const representation = clothPlan.representations.find((entry) => entry.band === band) ?? clothPlan.representations[0];
|
|
6815
|
+
return {
|
|
6816
|
+
clothPlan,
|
|
6817
|
+
band,
|
|
6818
|
+
continuity: representation.continuity,
|
|
6819
|
+
representation
|
|
6820
|
+
};
|
|
6821
|
+
}
|
|
6822
|
+
function getFlagRestPosition(rows, cols, row, column) {
|
|
6823
|
+
const u = cols <= 1 ? 0 : column / (cols - 1);
|
|
6824
|
+
const v = rows <= 1 ? 0 : row / (rows - 1);
|
|
6825
|
+
return vec3(
|
|
6826
|
+
FLAG_LAYOUT.origin.x + u * FLAG_LAYOUT.mastOffsetX,
|
|
6827
|
+
FLAG_LAYOUT.origin.y - FLAG_LAYOUT.height * v - u * u * 0.08,
|
|
6828
|
+
FLAG_LAYOUT.origin.z + FLAG_LAYOUT.width * u
|
|
6829
|
+
);
|
|
6830
|
+
}
|
|
6831
|
+
function buildClothConstraints(rows, cols, restPositions) {
|
|
6832
|
+
const constraints = [];
|
|
6833
|
+
const indexFor = (row, column) => row * cols + column;
|
|
6834
|
+
const pushConstraint = (a, b, stiffness) => {
|
|
6835
|
+
constraints.push(
|
|
6836
|
+
Object.freeze({
|
|
6837
|
+
a,
|
|
6838
|
+
b,
|
|
6839
|
+
restLength: lengthVec3(subVec3(restPositions[a], restPositions[b])),
|
|
6840
|
+
stiffness
|
|
6841
|
+
})
|
|
6842
|
+
);
|
|
6843
|
+
};
|
|
6844
|
+
for (let row = 0; row < rows; row += 1) {
|
|
6845
|
+
for (let column = 0; column < cols; column += 1) {
|
|
6846
|
+
const index = indexFor(row, column);
|
|
6847
|
+
if (column + 1 < cols) {
|
|
6848
|
+
pushConstraint(index, indexFor(row, column + 1), 0.92);
|
|
6849
|
+
}
|
|
6850
|
+
if (row + 1 < rows) {
|
|
6851
|
+
pushConstraint(index, indexFor(row + 1, column), 0.9);
|
|
6852
|
+
}
|
|
6853
|
+
if (column + 1 < cols && row + 1 < rows) {
|
|
6854
|
+
pushConstraint(index, indexFor(row + 1, column + 1), 0.66);
|
|
6855
|
+
}
|
|
6856
|
+
if (column - 1 >= 0 && row + 1 < rows) {
|
|
6857
|
+
pushConstraint(index, indexFor(row + 1, column - 1), 0.66);
|
|
6858
|
+
}
|
|
6859
|
+
if (column + 2 < cols) {
|
|
6860
|
+
pushConstraint(index, indexFor(row, column + 2), 0.22);
|
|
6861
|
+
}
|
|
6862
|
+
if (row + 2 < rows) {
|
|
6863
|
+
pushConstraint(index, indexFor(row + 2, column), 0.18);
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
return Object.freeze(constraints);
|
|
6868
|
+
}
|
|
6869
|
+
function createShowcaseClothSimulationState(options = {}) {
|
|
6870
|
+
const rows = Math.max(4, options.rows ?? 11);
|
|
6871
|
+
const cols = Math.max(4, options.cols ?? 16);
|
|
6872
|
+
const continuity = options.continuity ?? {
|
|
6873
|
+
broadMotionFloor: 0.72,
|
|
6874
|
+
wrinkleFloor: 0.56
|
|
6875
|
+
};
|
|
6876
|
+
const representation = options.representation ?? {
|
|
6877
|
+
mesh: {
|
|
6878
|
+
solverIterations: 6,
|
|
6879
|
+
wrinkleLayers: 2
|
|
6880
|
+
}
|
|
6881
|
+
};
|
|
6882
|
+
const restPositions = [];
|
|
6883
|
+
const positions = [];
|
|
6884
|
+
const previousPositions = [];
|
|
6885
|
+
const uvs = [];
|
|
6886
|
+
const phaseOffsets = [];
|
|
6887
|
+
const pinned = [];
|
|
6888
|
+
for (let row = 0; row < rows; row += 1) {
|
|
6889
|
+
for (let column = 0; column < cols; column += 1) {
|
|
6890
|
+
const index = row * cols + column;
|
|
6891
|
+
const u = cols <= 1 ? 0 : column / (cols - 1);
|
|
6892
|
+
const v = rows <= 1 ? 0 : row / (rows - 1);
|
|
6893
|
+
const rest = getFlagRestPosition(rows, cols, row, column);
|
|
6894
|
+
const preload = vec3(
|
|
6895
|
+
u * 0.04,
|
|
6896
|
+
Math.sin(v * Math.PI) * 0.02 * continuity.wrinkleFloor,
|
|
6897
|
+
-u * 0.12
|
|
6898
|
+
);
|
|
6899
|
+
const pinnedPoint = column === 0;
|
|
6900
|
+
restPositions.push(rest);
|
|
6901
|
+
positions.push(pinnedPoint ? vec3(rest.x, rest.y, rest.z) : addVec3(rest, preload));
|
|
6902
|
+
previousPositions.push(
|
|
6903
|
+
pinnedPoint ? vec3(rest.x, rest.y, rest.z) : addVec3(rest, scaleVec3(preload, 0.35))
|
|
6904
|
+
);
|
|
6905
|
+
uvs.push(Object.freeze({ u, v }));
|
|
6906
|
+
phaseOffsets.push(pseudoRandom(index + 17) * Math.PI * 2);
|
|
6907
|
+
pinned.push(pinnedPoint);
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6910
|
+
return {
|
|
6911
|
+
rows,
|
|
6912
|
+
cols,
|
|
6913
|
+
continuity,
|
|
6914
|
+
representation,
|
|
6915
|
+
restPositions,
|
|
6916
|
+
positions,
|
|
6917
|
+
previousPositions,
|
|
6918
|
+
constraints: buildClothConstraints(rows, cols, restPositions),
|
|
6919
|
+
indices: Object.freeze(
|
|
6920
|
+
Array.from({ length: (rows - 1) * (cols - 1) * 6 }, (_, listIndex) => listIndex).map((_, listIndex, source) => {
|
|
6921
|
+
if (listIndex >= source.length) {
|
|
6922
|
+
return 0;
|
|
6923
|
+
}
|
|
6924
|
+
const quadIndex = Math.floor(listIndex / 6);
|
|
6925
|
+
const quadColumn = quadIndex % (cols - 1);
|
|
6926
|
+
const quadRow = Math.floor(quadIndex / (cols - 1));
|
|
6927
|
+
const base = quadRow * cols + quadColumn;
|
|
6928
|
+
return [base, base + 1, base + cols + 1, base, base + cols + 1, base + cols][listIndex % 6];
|
|
6929
|
+
})
|
|
6930
|
+
),
|
|
6931
|
+
uvs,
|
|
6932
|
+
phaseOffsets,
|
|
6933
|
+
pinned
|
|
6934
|
+
};
|
|
6935
|
+
}
|
|
6936
|
+
function resetPinnedClothPoints(clothState) {
|
|
6937
|
+
for (let index = 0; index < clothState.positions.length; index += 1) {
|
|
6938
|
+
if (!clothState.pinned[index]) {
|
|
6939
|
+
continue;
|
|
6940
|
+
}
|
|
6941
|
+
const anchor = clothState.restPositions[index];
|
|
6942
|
+
clothState.positions[index] = vec3(anchor.x, anchor.y, anchor.z);
|
|
6943
|
+
clothState.previousPositions[index] = vec3(anchor.x, anchor.y, anchor.z);
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6946
|
+
function satisfyClothConstraint(clothState, constraint) {
|
|
6947
|
+
const a = clothState.positions[constraint.a];
|
|
6948
|
+
const b = clothState.positions[constraint.b];
|
|
6949
|
+
const delta = subVec3(b, a);
|
|
6950
|
+
const distance = lengthVec3(delta);
|
|
6951
|
+
if (distance <= 1e-4) {
|
|
6952
|
+
return;
|
|
6953
|
+
}
|
|
6954
|
+
const correctionScale = (distance - constraint.restLength) / distance * 0.5 * constraint.stiffness;
|
|
6955
|
+
const correction = scaleVec3(delta, correctionScale);
|
|
6956
|
+
if (!clothState.pinned[constraint.a]) {
|
|
6957
|
+
clothState.positions[constraint.a] = addVec3(a, correction);
|
|
6958
|
+
}
|
|
6959
|
+
if (!clothState.pinned[constraint.b]) {
|
|
6960
|
+
clothState.positions[constraint.b] = subVec3(b, correction);
|
|
6961
|
+
}
|
|
6962
|
+
}
|
|
6963
|
+
function advanceShowcaseClothSimulationState(clothState, options = {}) {
|
|
6964
|
+
const dt = clamp(options.dt ?? 1 / 60, 1 / 240, 1 / 18);
|
|
6965
|
+
const time = readVisualNumber(options.time, 0);
|
|
6966
|
+
const flagMotion = readVisualNumber(options.flagMotion, 0.92);
|
|
6967
|
+
const waveInfluence = readVisualNumber(options.waveInfluence, 0);
|
|
6968
|
+
const wrinkleLayers = Math.max(1, clothState.representation.mesh?.wrinkleLayers ?? 2);
|
|
6969
|
+
const solverIterations = clamp(
|
|
6970
|
+
Math.round(clothState.representation.mesh?.solverIterations ?? 6),
|
|
6971
|
+
2,
|
|
6972
|
+
10
|
|
6973
|
+
);
|
|
6974
|
+
for (let index = 0; index < clothState.positions.length; index += 1) {
|
|
6975
|
+
if (clothState.pinned[index]) {
|
|
6976
|
+
continue;
|
|
6977
|
+
}
|
|
6978
|
+
const current = clothState.positions[index];
|
|
6979
|
+
const previous = clothState.previousPositions[index];
|
|
6980
|
+
const { u, v } = clothState.uvs[index];
|
|
6981
|
+
const phase = clothState.phaseOffsets[index];
|
|
6982
|
+
const broadMotion = clothState.continuity.broadMotionFloor;
|
|
6983
|
+
const wrinkleMotion = clothState.continuity.wrinkleFloor;
|
|
6984
|
+
const gustPhase = time * 2.1 + phase + u * 4.4 + v * 2.3;
|
|
6985
|
+
const wrinklePhase = time * 5.3 + phase * 0.72 + u * 9.6 + v * 7.1;
|
|
6986
|
+
const windDirection = normalizeVec3(
|
|
6987
|
+
vec3(
|
|
6988
|
+
0.18 + Math.sin(gustPhase) * (0.12 + broadMotion * 0.09),
|
|
6989
|
+
Math.cos(time * 1.4 + phase + v * 4.8) * 0.06 * wrinkleMotion,
|
|
6990
|
+
1 + Math.sin(gustPhase * 0.74) * 0.18
|
|
6991
|
+
)
|
|
6992
|
+
);
|
|
6993
|
+
const windStrength = (1.6 + broadMotion * 1.25 + wrinkleLayers * 0.12) * flagMotion * (0.44 + u * 1.14);
|
|
6994
|
+
const wrinkleForce = vec3(
|
|
6995
|
+
Math.sin(wrinklePhase) * 0.22 * wrinkleMotion * flagMotion,
|
|
6996
|
+
Math.cos(wrinklePhase * 0.7) * 0.08 * wrinkleMotion,
|
|
6997
|
+
Math.cos(wrinklePhase) * 0.14 * broadMotion * flagMotion
|
|
6998
|
+
);
|
|
6999
|
+
const acceleration = addVec3(
|
|
7000
|
+
vec3(0, -0.48 - u * 0.08, 0),
|
|
7001
|
+
addVec3(
|
|
7002
|
+
scaleVec3(windDirection, windStrength),
|
|
7003
|
+
addVec3(
|
|
7004
|
+
wrinkleForce,
|
|
7005
|
+
vec3(waveInfluence * (0.04 + u * 0.08), 0, waveInfluence * 0.16)
|
|
7006
|
+
)
|
|
7007
|
+
)
|
|
7008
|
+
);
|
|
7009
|
+
const inertia = scaleVec3(subVec3(current, previous), 0.987);
|
|
7010
|
+
const next = addVec3(addVec3(current, inertia), scaleVec3(acceleration, dt * dt));
|
|
7011
|
+
clothState.previousPositions[index] = vec3(current.x, current.y, current.z);
|
|
7012
|
+
clothState.positions[index] = next;
|
|
7013
|
+
}
|
|
7014
|
+
resetPinnedClothPoints(clothState);
|
|
7015
|
+
for (let iteration = 0; iteration < solverIterations; iteration += 1) {
|
|
7016
|
+
for (const constraint of clothState.constraints) {
|
|
7017
|
+
satisfyClothConstraint(clothState, constraint);
|
|
7018
|
+
}
|
|
7019
|
+
resetPinnedClothPoints(clothState);
|
|
7020
|
+
}
|
|
7021
|
+
return clothState;
|
|
7022
|
+
}
|
|
7023
|
+
function ensureShowcaseClothState(state, meshDetail, clothPresentation) {
|
|
7024
|
+
if (!state.clothState || state.clothState.rows !== meshDetail.rows || state.clothState.cols !== meshDetail.cols) {
|
|
7025
|
+
state.clothState = createShowcaseClothSimulationState({
|
|
7026
|
+
rows: meshDetail.rows,
|
|
7027
|
+
cols: meshDetail.cols,
|
|
7028
|
+
continuity: clothPresentation.continuity,
|
|
7029
|
+
representation: clothPresentation.representation
|
|
7030
|
+
});
|
|
7031
|
+
} else {
|
|
7032
|
+
state.clothState.continuity = clothPresentation.continuity;
|
|
7033
|
+
state.clothState.representation = clothPresentation.representation;
|
|
7034
|
+
}
|
|
7035
|
+
return state.clothState;
|
|
7036
|
+
}
|
|
6787
7037
|
function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {}) {
|
|
6788
7038
|
const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
|
|
6789
7039
|
const defaults = {
|
|
@@ -6867,58 +7117,17 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
|
|
|
6867
7117
|
};
|
|
6868
7118
|
}
|
|
6869
7119
|
function buildClothSurface(model, state, meshDetail, visuals) {
|
|
6870
|
-
const
|
|
6871
|
-
|
|
6872
|
-
kind: state.focus === "cloth" ? "flag" : clothGarmentKinds[0],
|
|
6873
|
-
profile: state.focus === "cloth" ? "cinematic" : clothProfileNames[0],
|
|
6874
|
-
supportsRayTracing: true,
|
|
6875
|
-
nearFieldMaxMeters: 18,
|
|
6876
|
-
midFieldMaxMeters: 55,
|
|
6877
|
-
farFieldMaxMeters: 180
|
|
6878
|
-
});
|
|
6879
|
-
const cameraDistance = lengthVec3(subVec3(state.camera.target, state.camera.eye ?? vec3(...CAMERA_PRESETS[state.focus].target)));
|
|
6880
|
-
const band = selectClothRepresentationBand(cameraDistance, clothPlan.thresholds);
|
|
6881
|
-
const representation = clothPlan.representations.find((entry) => entry.band === band) ?? clothPlan.representations[0];
|
|
6882
|
-
const continuity = createClothContinuityEnvelope({ garmentId: "shore-flag" });
|
|
6883
|
-
const cols = meshDetail.cols;
|
|
6884
|
-
const rows = meshDetail.rows;
|
|
6885
|
-
const origin = vec3(-3.5, 5.9, 2.4);
|
|
6886
|
-
const width = 4.8;
|
|
6887
|
-
const height = 2.7;
|
|
6888
|
-
const positions = [];
|
|
6889
|
-
const indices = [];
|
|
6890
|
-
const time = state.time;
|
|
6891
|
-
for (let row = 0; row < rows; row += 1) {
|
|
6892
|
-
for (let column = 0; column < cols; column += 1) {
|
|
6893
|
-
const u = column / (cols - 1);
|
|
6894
|
-
const v = row / (rows - 1);
|
|
6895
|
-
const gust = Math.sin(time * 1.9 + v * 3.2 + u * 2.1) * continuity.broadMotionFloor * visuals.flagMotion;
|
|
6896
|
-
const wrinkle = Math.sin(time * 4.4 + u * 9.2 + v * 5.6) * continuity.wrinkleFloor * 0.22 * Math.max(0.55, visuals.flagMotion);
|
|
6897
|
-
const x = origin.x + u * 1.8 + gust * 0.55 * (u * 0.9);
|
|
6898
|
-
const y = origin.y - height * v + wrinkle * 0.2;
|
|
6899
|
-
const z = origin.z + width * u + gust * 0.72 * (u * 0.85);
|
|
6900
|
-
const flap = Math.cos(time * 2.7 + u * 7.4 + v * 3.8) * continuity.broadMotionFloor * 0.28 * visuals.flagMotion;
|
|
6901
|
-
positions.push(vec3(x + flap, y, z));
|
|
6902
|
-
}
|
|
6903
|
-
}
|
|
6904
|
-
for (let row = 0; row < rows - 1; row += 1) {
|
|
6905
|
-
for (let column = 0; column < cols - 1; column += 1) {
|
|
6906
|
-
const a = row * cols + column;
|
|
6907
|
-
const b = a + 1;
|
|
6908
|
-
const c = a + cols + 1;
|
|
6909
|
-
const d = a + cols;
|
|
6910
|
-
indices.push(a, b, c, a, c, d);
|
|
6911
|
-
}
|
|
6912
|
-
}
|
|
7120
|
+
const clothPresentation = resolveClothPresentation(state, meshDetail);
|
|
7121
|
+
const clothState = ensureShowcaseClothState(state, meshDetail, clothPresentation);
|
|
6913
7122
|
return {
|
|
6914
|
-
clothPlan,
|
|
6915
|
-
band,
|
|
6916
|
-
representation,
|
|
6917
|
-
continuity,
|
|
7123
|
+
clothPlan: clothPresentation.clothPlan,
|
|
7124
|
+
band: clothPresentation.band,
|
|
7125
|
+
representation: clothPresentation.representation,
|
|
7126
|
+
continuity: clothPresentation.continuity,
|
|
6918
7127
|
color: visuals.flagColor,
|
|
6919
|
-
positions,
|
|
6920
|
-
indices,
|
|
6921
|
-
grid: { rows, cols }
|
|
7128
|
+
positions: clothState.positions.map((point) => vec3(point.x, point.y, point.z)),
|
|
7129
|
+
indices: clothState.indices,
|
|
7130
|
+
grid: { rows: clothState.rows, cols: clothState.cols }
|
|
6922
7131
|
};
|
|
6923
7132
|
}
|
|
6924
7133
|
function resolveWaveDirection(state) {
|
|
@@ -6982,6 +7191,59 @@ function sampleWave(state, x, z, time) {
|
|
|
6982
7191
|
const base = Math.sin(along * 0.22 - time * 1.12 * phaseSpeed) * 0.42 + Math.cos(along * 0.11 + cross * 0.07 - time * 0.78 * phaseSpeed) * 0.26 + Math.sin(cross * 0.19 - time * 1.34 * phaseSpeed) * 0.16;
|
|
6983
7192
|
return base * amplitude + sampleShipWake(state, x, z, time) + sampleWaveImpulses(state, x, z, time);
|
|
6984
7193
|
}
|
|
7194
|
+
function buildWaterMotionEffects(state) {
|
|
7195
|
+
const wakeTrails = [];
|
|
7196
|
+
const rippleRings = state.waveImpulses.map((impulse) => {
|
|
7197
|
+
const radius = impulse.radius + (1 - impulse.life) * 4.8;
|
|
7198
|
+
return Object.freeze({
|
|
7199
|
+
center: vec3(
|
|
7200
|
+
impulse.x,
|
|
7201
|
+
sampleWave(state, impulse.x, impulse.z, state.time) * 0.24 + 0.06,
|
|
7202
|
+
impulse.z
|
|
7203
|
+
),
|
|
7204
|
+
radius,
|
|
7205
|
+
opacity: clamp(impulse.life * 0.28, 0.08, 0.3)
|
|
7206
|
+
});
|
|
7207
|
+
});
|
|
7208
|
+
for (const ship of state.ships) {
|
|
7209
|
+
const speed = Math.hypot(ship.velocity.x, ship.velocity.z);
|
|
7210
|
+
if (speed <= 0.18) {
|
|
7211
|
+
continue;
|
|
7212
|
+
}
|
|
7213
|
+
const direction = normalizeVec3(vec3(ship.velocity.x, 0, ship.velocity.z));
|
|
7214
|
+
const behind = scaleVec3(direction, -1);
|
|
7215
|
+
const lateral = vec3(-direction.z, 0, direction.x);
|
|
7216
|
+
const points = [];
|
|
7217
|
+
for (let sampleIndex = 0; sampleIndex < 6; sampleIndex += 1) {
|
|
7218
|
+
const along = 1 + sampleIndex * 1.45;
|
|
7219
|
+
const lateralOffset = Math.sin(state.time * 1.2 + sampleIndex * 0.8 + readVisualNumber(ship.wanderPhase, 0)) * 0.12;
|
|
7220
|
+
const worldPoint = addVec3(
|
|
7221
|
+
ship.position,
|
|
7222
|
+
addVec3(scaleVec3(behind, along), scaleVec3(lateral, lateralOffset))
|
|
7223
|
+
);
|
|
7224
|
+
points.push(
|
|
7225
|
+
Object.freeze({
|
|
7226
|
+
center: vec3(
|
|
7227
|
+
worldPoint.x,
|
|
7228
|
+
sampleWave(state, worldPoint.x, worldPoint.z, state.time) * 0.24 + 0.04,
|
|
7229
|
+
worldPoint.z
|
|
7230
|
+
),
|
|
7231
|
+
width: 0.34 + sampleIndex * 0.13
|
|
7232
|
+
})
|
|
7233
|
+
);
|
|
7234
|
+
}
|
|
7235
|
+
wakeTrails.push(
|
|
7236
|
+
Object.freeze({
|
|
7237
|
+
opacity: clamp(0.18 + speed * 0.09, 0.22, 0.46),
|
|
7238
|
+
points: Object.freeze(points)
|
|
7239
|
+
})
|
|
7240
|
+
);
|
|
7241
|
+
}
|
|
7242
|
+
return Object.freeze({
|
|
7243
|
+
wakeTrails: Object.freeze(wakeTrails),
|
|
7244
|
+
rippleRings: Object.freeze(rippleRings)
|
|
7245
|
+
});
|
|
7246
|
+
}
|
|
6985
7247
|
function buildWaterBands(state, fluidDetail, visuals) {
|
|
6986
7248
|
const fluidPlan = createFluidRepresentationPlan({
|
|
6987
7249
|
fluidBodyId: "harbor",
|
|
@@ -7134,6 +7396,7 @@ function createSceneState(options) {
|
|
|
7134
7396
|
contactCount: 0,
|
|
7135
7397
|
collisionCount: 0,
|
|
7136
7398
|
collisionFlash: 0,
|
|
7399
|
+
clothState: null,
|
|
7137
7400
|
physics: {
|
|
7138
7401
|
profile: physicsProfile,
|
|
7139
7402
|
plan: physicsPlan,
|
|
@@ -7769,16 +8032,73 @@ function renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength
|
|
|
7769
8032
|
blur: 12 + shadowStrength * 20
|
|
7770
8033
|
});
|
|
7771
8034
|
}
|
|
7772
|
-
function
|
|
7773
|
-
const
|
|
8035
|
+
function collectSceneLightSources(state, visuals) {
|
|
8036
|
+
const directLights = [];
|
|
8037
|
+
const reflectionLights = [];
|
|
8038
|
+
const pushLight = (point, glowScale, reflectionStrength, coreColor, glowColor) => {
|
|
8039
|
+
directLights.push(
|
|
8040
|
+
Object.freeze({
|
|
8041
|
+
pass: "direct-glow",
|
|
8042
|
+
point,
|
|
8043
|
+
coreColor,
|
|
8044
|
+
glowColor,
|
|
8045
|
+
glowScale
|
|
8046
|
+
})
|
|
8047
|
+
);
|
|
8048
|
+
if (reflectionStrength > 0) {
|
|
8049
|
+
reflectionLights.push(
|
|
8050
|
+
Object.freeze({
|
|
8051
|
+
pass: "water-reflection",
|
|
8052
|
+
point,
|
|
8053
|
+
coreColor,
|
|
8054
|
+
glowColor,
|
|
8055
|
+
glowScale,
|
|
8056
|
+
reflectionStrength
|
|
8057
|
+
})
|
|
8058
|
+
);
|
|
8059
|
+
}
|
|
8060
|
+
};
|
|
8061
|
+
for (const torch of HARBOR_TORCHES) {
|
|
8062
|
+
pushLight(
|
|
8063
|
+
vec3(torch.x, torch.y, torch.z),
|
|
8064
|
+
torch.glow,
|
|
8065
|
+
visuals.lanternReflectionStrength * 0.55,
|
|
8066
|
+
visuals.torchCore,
|
|
8067
|
+
visuals.torchGlow
|
|
8068
|
+
);
|
|
8069
|
+
}
|
|
8070
|
+
for (const ship of state.ships) {
|
|
8071
|
+
const lanterns = Array.isArray(ship.lanterns) ? ship.lanterns : SHIP_LANTERNS;
|
|
8072
|
+
const strength = readVisualNumber(ship.lanternStrength, 1);
|
|
8073
|
+
for (const lantern of lanterns) {
|
|
8074
|
+
const point = transformPoint(
|
|
8075
|
+
vec3(lantern.x, lantern.y, lantern.z),
|
|
8076
|
+
{ position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE }
|
|
8077
|
+
);
|
|
8078
|
+
pushLight(
|
|
8079
|
+
point,
|
|
8080
|
+
lantern.glow * strength,
|
|
8081
|
+
visuals.lanternReflectionStrength,
|
|
8082
|
+
visuals.lanternCore,
|
|
8083
|
+
visuals.lanternGlow
|
|
8084
|
+
);
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
return Object.freeze({
|
|
8088
|
+
directLights: Object.freeze(directLights),
|
|
8089
|
+
reflectionLights: Object.freeze(reflectionLights)
|
|
8090
|
+
});
|
|
8091
|
+
}
|
|
8092
|
+
function renderDirectLightGlow(ctx, source, camera, viewport) {
|
|
8093
|
+
const projected = projectPoint(source.point, camera, viewport);
|
|
7774
8094
|
if (!projected) {
|
|
7775
8095
|
return;
|
|
7776
8096
|
}
|
|
7777
|
-
const radius = clamp(1 / projected.depth * 420 * glowScale, 4, 34);
|
|
8097
|
+
const radius = clamp(1 / projected.depth * 420 * source.glowScale, 4, 34);
|
|
7778
8098
|
const halo = ctx.createRadialGradient(projected.x, projected.y, radius * 0.12, projected.x, projected.y, radius);
|
|
7779
|
-
halo.addColorStop(0, colorToRgba(coreColor, 0.98));
|
|
7780
|
-
halo.addColorStop(0.5, colorToRgba(glowColor, 0.42));
|
|
7781
|
-
halo.addColorStop(1, colorToRgba(glowColor, 0));
|
|
8099
|
+
halo.addColorStop(0, colorToRgba(source.coreColor, 0.98));
|
|
8100
|
+
halo.addColorStop(0.5, colorToRgba(source.glowColor, 0.42));
|
|
8101
|
+
halo.addColorStop(1, colorToRgba(source.glowColor, 0));
|
|
7782
8102
|
ctx.save();
|
|
7783
8103
|
ctx.globalCompositeOperation = "screen";
|
|
7784
8104
|
ctx.fillStyle = halo;
|
|
@@ -7786,79 +8106,119 @@ function renderGlowLight(ctx, point, camera, viewport, coreColor, glowColor, glo
|
|
|
7786
8106
|
ctx.arc(projected.x, projected.y, radius, 0, Math.PI * 2);
|
|
7787
8107
|
ctx.fill();
|
|
7788
8108
|
ctx.restore();
|
|
7789
|
-
ctx.fillStyle = colorToRgba(coreColor, 0.98);
|
|
8109
|
+
ctx.fillStyle = colorToRgba(source.coreColor, 0.98);
|
|
7790
8110
|
ctx.beginPath();
|
|
7791
8111
|
ctx.arc(projected.x, projected.y, Math.max(1.2, radius * 0.16), 0, Math.PI * 2);
|
|
7792
8112
|
ctx.fill();
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
8113
|
+
}
|
|
8114
|
+
function renderWaterLightReflection(ctx, source, state, camera, viewport) {
|
|
8115
|
+
const projected = projectPoint(source.point, camera, viewport);
|
|
8116
|
+
if (!projected) {
|
|
8117
|
+
return;
|
|
8118
|
+
}
|
|
8119
|
+
const radius = clamp(1 / projected.depth * 420 * source.glowScale, 4, 34);
|
|
8120
|
+
const waterline = sampleWave(state, source.point.x, source.point.z, state.time) * 0.22;
|
|
8121
|
+
const reflectedPoint = vec3(
|
|
8122
|
+
source.point.x,
|
|
8123
|
+
waterline - (source.point.y - waterline) * 0.58,
|
|
8124
|
+
source.point.z + 0.08
|
|
8125
|
+
);
|
|
8126
|
+
const reflected = projectPoint(reflectedPoint, camera, viewport);
|
|
8127
|
+
if (!reflected) {
|
|
8128
|
+
return;
|
|
8129
|
+
}
|
|
8130
|
+
const reflectionRadius = radius * 0.72;
|
|
8131
|
+
const glow = ctx.createRadialGradient(
|
|
8132
|
+
reflected.x,
|
|
8133
|
+
reflected.y,
|
|
8134
|
+
reflectionRadius * 0.1,
|
|
8135
|
+
reflected.x,
|
|
8136
|
+
reflected.y,
|
|
8137
|
+
reflectionRadius
|
|
8138
|
+
);
|
|
8139
|
+
glow.addColorStop(0, colorToRgba(source.coreColor, source.reflectionStrength * 0.34));
|
|
8140
|
+
glow.addColorStop(1, colorToRgba(source.glowColor, 0));
|
|
8141
|
+
ctx.save();
|
|
8142
|
+
ctx.globalCompositeOperation = "screen";
|
|
8143
|
+
ctx.fillStyle = glow;
|
|
8144
|
+
ctx.beginPath();
|
|
8145
|
+
ctx.ellipse(
|
|
8146
|
+
reflected.x,
|
|
8147
|
+
reflected.y,
|
|
8148
|
+
reflectionRadius * 0.34,
|
|
8149
|
+
reflectionRadius,
|
|
8150
|
+
0,
|
|
8151
|
+
0,
|
|
8152
|
+
Math.PI * 2
|
|
8153
|
+
);
|
|
8154
|
+
ctx.fill();
|
|
8155
|
+
ctx.restore();
|
|
8156
|
+
}
|
|
8157
|
+
function renderWaterMotionEffects(ctx, effects, camera, viewport) {
|
|
8158
|
+
ctx.save();
|
|
8159
|
+
ctx.globalCompositeOperation = "screen";
|
|
8160
|
+
for (const wake of effects.wakeTrails) {
|
|
8161
|
+
const projected = wake.points.map((point) => ({
|
|
8162
|
+
projected: projectPoint(point.center, camera, viewport),
|
|
8163
|
+
width: point.width
|
|
8164
|
+
})).filter((entry) => entry.projected);
|
|
8165
|
+
if (projected.length < 2) {
|
|
8166
|
+
continue;
|
|
8167
|
+
}
|
|
8168
|
+
const averageDepth = projected.reduce((total, entry) => total + entry.projected.depth, 0) / projected.length;
|
|
8169
|
+
const averageWidth = projected.reduce((total, entry) => total + entry.width, 0) / projected.length;
|
|
8170
|
+
const baseWidth = clamp(averageWidth / Math.max(0.25, averageDepth) * 180, 1.6, 5.4);
|
|
8171
|
+
ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.52})`;
|
|
8172
|
+
ctx.lineWidth = baseWidth * 1.9;
|
|
8173
|
+
ctx.lineCap = "round";
|
|
8174
|
+
ctx.lineJoin = "round";
|
|
8175
|
+
ctx.beginPath();
|
|
8176
|
+
ctx.moveTo(projected[0].projected.x, projected[0].projected.y);
|
|
8177
|
+
for (let index = 1; index < projected.length; index += 1) {
|
|
8178
|
+
ctx.lineTo(projected[index].projected.x, projected[index].projected.y);
|
|
8179
|
+
}
|
|
8180
|
+
ctx.stroke();
|
|
8181
|
+
ctx.strokeStyle = `rgba(234, 247, 255, ${wake.opacity})`;
|
|
8182
|
+
ctx.lineWidth = baseWidth;
|
|
8183
|
+
ctx.lineCap = "round";
|
|
8184
|
+
ctx.lineJoin = "round";
|
|
8185
|
+
ctx.beginPath();
|
|
8186
|
+
ctx.moveTo(projected[0].projected.x, projected[0].projected.y);
|
|
8187
|
+
for (let index = 1; index < projected.length; index += 1) {
|
|
8188
|
+
ctx.lineTo(projected[index].projected.x, projected[index].projected.y);
|
|
8189
|
+
}
|
|
8190
|
+
ctx.stroke();
|
|
8191
|
+
for (const entry of projected.slice(1, 5)) {
|
|
8192
|
+
ctx.fillStyle = `rgba(239, 248, 255, ${wake.opacity * 0.76})`;
|
|
7812
8193
|
ctx.beginPath();
|
|
7813
8194
|
ctx.ellipse(
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
8195
|
+
entry.projected.x,
|
|
8196
|
+
entry.projected.y,
|
|
8197
|
+
baseWidth * 0.72,
|
|
8198
|
+
baseWidth * 0.44,
|
|
7818
8199
|
0,
|
|
7819
8200
|
0,
|
|
7820
8201
|
Math.PI * 2
|
|
7821
8202
|
);
|
|
7822
8203
|
ctx.fill();
|
|
7823
|
-
ctx.restore();
|
|
7824
8204
|
}
|
|
7825
8205
|
}
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
);
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
visuals.lanternCore,
|
|
7841
|
-
visuals.lanternGlow,
|
|
7842
|
-
lantern.glow * strength,
|
|
7843
|
-
visuals.lanternReflectionStrength,
|
|
7844
|
-
state
|
|
7845
|
-
);
|
|
7846
|
-
}
|
|
7847
|
-
}
|
|
7848
|
-
function renderHarborTorches(ctx, state, camera, viewport, visuals) {
|
|
7849
|
-
for (const torch of HARBOR_TORCHES) {
|
|
7850
|
-
renderGlowLight(
|
|
7851
|
-
ctx,
|
|
7852
|
-
vec3(torch.x, torch.y, torch.z),
|
|
7853
|
-
camera,
|
|
7854
|
-
viewport,
|
|
7855
|
-
visuals.torchCore,
|
|
7856
|
-
visuals.torchGlow,
|
|
7857
|
-
torch.glow,
|
|
7858
|
-
visuals.lanternReflectionStrength * 0.55,
|
|
7859
|
-
state
|
|
7860
|
-
);
|
|
8206
|
+
for (const ring of effects.rippleRings) {
|
|
8207
|
+
const center = projectPoint(ring.center, camera, viewport);
|
|
8208
|
+
const xAxis = projectPoint(addVec3(ring.center, vec3(ring.radius, 0, 0)), camera, viewport);
|
|
8209
|
+
const zAxis = projectPoint(addVec3(ring.center, vec3(0, 0, ring.radius)), camera, viewport);
|
|
8210
|
+
if (!center || !xAxis || !zAxis) {
|
|
8211
|
+
continue;
|
|
8212
|
+
}
|
|
8213
|
+
const radiusX = Math.hypot(xAxis.x - center.x, xAxis.y - center.y);
|
|
8214
|
+
const radiusY = Math.hypot(zAxis.x - center.x, zAxis.y - center.y);
|
|
8215
|
+
ctx.strokeStyle = `rgba(216, 235, 255, ${ring.opacity})`;
|
|
8216
|
+
ctx.lineWidth = clamp((radiusX + radiusY) * 0.02, 1, 3.1);
|
|
8217
|
+
ctx.beginPath();
|
|
8218
|
+
ctx.ellipse(center.x, center.y, radiusX, radiusY, 0, 0, Math.PI * 2);
|
|
8219
|
+
ctx.stroke();
|
|
7861
8220
|
}
|
|
8221
|
+
ctx.restore();
|
|
7862
8222
|
}
|
|
7863
8223
|
function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
7864
8224
|
const viewport = { width: canvas.width, height: canvas.height };
|
|
@@ -7888,8 +8248,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7888
8248
|
shadowStrength,
|
|
7889
8249
|
visuals
|
|
7890
8250
|
);
|
|
7891
|
-
const
|
|
7892
|
-
|
|
8251
|
+
const waterTriangles = [];
|
|
8252
|
+
const sceneTriangles = [];
|
|
7893
8253
|
const water = buildWaterBands(
|
|
7894
8254
|
state,
|
|
7895
8255
|
state.fluidDetail.getSnapshot().currentLevel.config,
|
|
@@ -7906,7 +8266,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7906
8266
|
if (projected.some((value) => value === null)) {
|
|
7907
8267
|
continue;
|
|
7908
8268
|
}
|
|
7909
|
-
|
|
8269
|
+
waterTriangles.push({
|
|
7910
8270
|
points: projected,
|
|
7911
8271
|
depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
|
|
7912
8272
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
@@ -7916,6 +8276,9 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7916
8276
|
});
|
|
7917
8277
|
}
|
|
7918
8278
|
}
|
|
8279
|
+
const waterMotionEffects = buildWaterMotionEffects(state);
|
|
8280
|
+
const lightSources = collectSceneLightSources(state, visuals);
|
|
8281
|
+
pushHarborGeometry(camera, viewport, sceneTriangles, visuals);
|
|
7919
8282
|
const cloth = buildClothSurface(
|
|
7920
8283
|
state,
|
|
7921
8284
|
state,
|
|
@@ -7931,7 +8294,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7931
8294
|
if (projected.some((value) => value === null)) {
|
|
7932
8295
|
continue;
|
|
7933
8296
|
}
|
|
7934
|
-
|
|
8297
|
+
sceneTriangles.push({
|
|
7935
8298
|
points: projected,
|
|
7936
8299
|
depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
|
|
7937
8300
|
worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
|
|
@@ -7947,22 +8310,28 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
|
|
|
7947
8310
|
ship.tint,
|
|
7948
8311
|
camera,
|
|
7949
8312
|
viewport,
|
|
7950
|
-
|
|
8313
|
+
sceneTriangles,
|
|
7951
8314
|
nearLighting.rtParticipation.directShadows === "premium" ? 0.08 : 0.02
|
|
7952
8315
|
);
|
|
7953
8316
|
}
|
|
8317
|
+
drawTriangles(ctx, waterTriangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
7954
8318
|
for (const ship of state.ships) {
|
|
7955
8319
|
renderShipShadow(ctx, shipModel, ship, state, camera, viewport, lightDir, shadowStrength);
|
|
7956
8320
|
}
|
|
7957
8321
|
renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength);
|
|
7958
|
-
|
|
8322
|
+
for (const source of lightSources.reflectionLights) {
|
|
8323
|
+
renderWaterLightReflection(ctx, source, state, camera, viewport);
|
|
8324
|
+
}
|
|
8325
|
+
renderWaterMotionEffects(ctx, waterMotionEffects, camera, viewport);
|
|
7959
8326
|
renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
|
|
7960
|
-
|
|
8327
|
+
drawTriangles(ctx, sceneTriangles, lightDir, reflectionStrength, camera, shadowStrength);
|
|
7961
8328
|
renderFlagPole(ctx, camera, viewport);
|
|
7962
8329
|
renderClothAccent(ctx, cloth, camera, viewport);
|
|
8330
|
+
for (const source of lightSources.directLights) {
|
|
8331
|
+
renderDirectLightGlow(ctx, source, camera, viewport);
|
|
8332
|
+
}
|
|
7963
8333
|
for (const ship of state.ships) {
|
|
7964
8334
|
renderShipRigging(ctx, ship, camera, viewport);
|
|
7965
|
-
renderShipLanterns(ctx, ship, state, camera, viewport, visuals);
|
|
7966
8335
|
}
|
|
7967
8336
|
renderSprays(ctx, state.sprays, camera, viewport);
|
|
7968
8337
|
const debugSnapshot = state.debugSession.getSnapshot();
|
|
@@ -8024,6 +8393,21 @@ function updateSceneState(state, dt, shipModel) {
|
|
|
8024
8393
|
updateShips(state, dt, shipModel);
|
|
8025
8394
|
updateWaveImpulses(state, dt);
|
|
8026
8395
|
updateSpray(state, dt);
|
|
8396
|
+
const clothPresentation = resolveClothPresentation(
|
|
8397
|
+
state,
|
|
8398
|
+
state.clothDetail.getSnapshot().currentLevel.config
|
|
8399
|
+
);
|
|
8400
|
+
const clothState = ensureShowcaseClothState(
|
|
8401
|
+
state,
|
|
8402
|
+
state.clothDetail.getSnapshot().currentLevel.config,
|
|
8403
|
+
clothPresentation
|
|
8404
|
+
);
|
|
8405
|
+
advanceShowcaseClothSimulationState(clothState, {
|
|
8406
|
+
dt,
|
|
8407
|
+
time: state.time,
|
|
8408
|
+
flagMotion: readVisualNumber(state.demoVisuals?.flagMotion, 0.92),
|
|
8409
|
+
waveInfluence: sampleWave(state, FLAG_LAYOUT.origin.x + FLAG_LAYOUT.width * 0.55, FLAG_LAYOUT.origin.z + FLAG_LAYOUT.width * 0.48, state.time)
|
|
8410
|
+
});
|
|
8027
8411
|
updatePhysicsSnapshot(state, shipModel);
|
|
8028
8412
|
}
|
|
8029
8413
|
function syncTextState(state, shipModel) {
|
|
@@ -8191,7 +8575,11 @@ function updatePhysicsSnapshot(state, shipModel) {
|
|
|
8191
8575
|
});
|
|
8192
8576
|
}
|
|
8193
8577
|
export {
|
|
8578
|
+
advanceShowcaseClothSimulationState as __testOnlyAdvanceShowcaseClothSimulationState,
|
|
8579
|
+
buildWaterMotionEffects as __testOnlyBuildWaterMotionEffects,
|
|
8580
|
+
collectSceneLightSources as __testOnlyCollectSceneLightSources,
|
|
8581
|
+
createShowcaseClothSimulationState as __testOnlyCreateShowcaseClothSimulationState,
|
|
8194
8582
|
mountGpuShowcase,
|
|
8195
8583
|
showcaseFocusModes
|
|
8196
8584
|
};
|
|
8197
|
-
//# sourceMappingURL=showcase-runtime-
|
|
8585
|
+
//# sourceMappingURL=showcase-runtime-55OVDYWT.js.map
|