@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.
@@ -2022,9 +2022,12 @@ var __require2 = /* @__PURE__ */ ((x) => typeof __require !== "undefined" ? __re
2022
2022
  if (typeof __require !== "undefined") return __require.apply(this, arguments);
2023
2023
  throw Error('Dynamic require of "' + x + '" is not supported');
2024
2024
  });
2025
+ function createModuleBaseUrl(metaUrl) {
2026
+ return Reflect.construct(URL, [String(metaUrl)]);
2027
+ }
2025
2028
  var baseUrl = (() => {
2026
2029
  if (typeof import.meta.url !== "undefined") {
2027
- return new URL("./index.js", import.meta.url);
2030
+ return createModuleBaseUrl(import.meta.url);
2028
2031
  }
2029
2032
  if (typeof __filename !== "undefined" && typeof __require2 !== "undefined") {
2030
2033
  const { pathToFileURL } = __require2("url");
@@ -6209,6 +6212,12 @@ var HARBOR_TORCHES = Object.freeze([
6209
6212
  Object.freeze({ x: -8.6, y: 2.48, z: -0.72, glow: 1 }),
6210
6213
  Object.freeze({ x: -10.4, y: 1.28, z: 0.82, glow: 0.92 })
6211
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
+ });
6212
6221
  function injectStyles() {
6213
6222
  if (document.getElementById(STYLE_ID)) {
6214
6223
  return;
@@ -6781,6 +6790,250 @@ function normalizeColorOverride(color, fallback) {
6781
6790
  function readVisualNumber(value, fallback) {
6782
6791
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
6783
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
+ }
6784
7037
  function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {}) {
6785
7038
  const premiumShadows = nearLighting.primaryShadowSource === "ray-traced-primary";
6786
7039
  const defaults = {
@@ -6864,58 +7117,17 @@ function resolveVisualConfig(nearLighting, lightingSnapshot, customVisuals = {})
6864
7117
  };
6865
7118
  }
6866
7119
  function buildClothSurface(model, state, meshDetail, visuals) {
6867
- const clothPlan = createClothRepresentationPlan({
6868
- garmentId: "shore-flag",
6869
- kind: state.focus === "cloth" ? "flag" : clothGarmentKinds[0],
6870
- profile: state.focus === "cloth" ? "cinematic" : clothProfileNames[0],
6871
- supportsRayTracing: true,
6872
- nearFieldMaxMeters: 18,
6873
- midFieldMaxMeters: 55,
6874
- farFieldMaxMeters: 180
6875
- });
6876
- const cameraDistance = lengthVec3(subVec3(state.camera.target, state.camera.eye ?? vec3(...CAMERA_PRESETS[state.focus].target)));
6877
- const band = selectClothRepresentationBand(cameraDistance, clothPlan.thresholds);
6878
- const representation = clothPlan.representations.find((entry) => entry.band === band) ?? clothPlan.representations[0];
6879
- const continuity = createClothContinuityEnvelope({ garmentId: "shore-flag" });
6880
- const cols = meshDetail.cols;
6881
- const rows = meshDetail.rows;
6882
- const origin = vec3(-3.5, 5.9, 2.4);
6883
- const width = 4.8;
6884
- const height = 2.7;
6885
- const positions = [];
6886
- const indices = [];
6887
- const time = state.time;
6888
- for (let row = 0; row < rows; row += 1) {
6889
- for (let column = 0; column < cols; column += 1) {
6890
- const u = column / (cols - 1);
6891
- const v = row / (rows - 1);
6892
- const gust = Math.sin(time * 1.9 + v * 3.2 + u * 2.1) * continuity.broadMotionFloor * visuals.flagMotion;
6893
- const wrinkle = Math.sin(time * 4.4 + u * 9.2 + v * 5.6) * continuity.wrinkleFloor * 0.22 * Math.max(0.55, visuals.flagMotion);
6894
- const x = origin.x + u * 1.8 + gust * 0.55 * (u * 0.9);
6895
- const y = origin.y - height * v + wrinkle * 0.2;
6896
- const z = origin.z + width * u + gust * 0.72 * (u * 0.85);
6897
- const flap = Math.cos(time * 2.7 + u * 7.4 + v * 3.8) * continuity.broadMotionFloor * 0.28 * visuals.flagMotion;
6898
- positions.push(vec3(x + flap, y, z));
6899
- }
6900
- }
6901
- for (let row = 0; row < rows - 1; row += 1) {
6902
- for (let column = 0; column < cols - 1; column += 1) {
6903
- const a = row * cols + column;
6904
- const b = a + 1;
6905
- const c = a + cols + 1;
6906
- const d = a + cols;
6907
- indices.push(a, b, c, a, c, d);
6908
- }
6909
- }
7120
+ const clothPresentation = resolveClothPresentation(state, meshDetail);
7121
+ const clothState = ensureShowcaseClothState(state, meshDetail, clothPresentation);
6910
7122
  return {
6911
- clothPlan,
6912
- band,
6913
- representation,
6914
- continuity,
7123
+ clothPlan: clothPresentation.clothPlan,
7124
+ band: clothPresentation.band,
7125
+ representation: clothPresentation.representation,
7126
+ continuity: clothPresentation.continuity,
6915
7127
  color: visuals.flagColor,
6916
- positions,
6917
- indices,
6918
- 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 }
6919
7131
  };
6920
7132
  }
6921
7133
  function resolveWaveDirection(state) {
@@ -6979,6 +7191,59 @@ function sampleWave(state, x, z, time) {
6979
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;
6980
7192
  return base * amplitude + sampleShipWake(state, x, z, time) + sampleWaveImpulses(state, x, z, time);
6981
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
+ }
6982
7247
  function buildWaterBands(state, fluidDetail, visuals) {
6983
7248
  const fluidPlan = createFluidRepresentationPlan({
6984
7249
  fluidBodyId: "harbor",
@@ -7131,6 +7396,7 @@ function createSceneState(options) {
7131
7396
  contactCount: 0,
7132
7397
  collisionCount: 0,
7133
7398
  collisionFlash: 0,
7399
+ clothState: null,
7134
7400
  physics: {
7135
7401
  profile: physicsProfile,
7136
7402
  plan: physicsPlan,
@@ -7766,16 +8032,73 @@ function renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength
7766
8032
  blur: 12 + shadowStrength * 20
7767
8033
  });
7768
8034
  }
7769
- function renderGlowLight(ctx, point, camera, viewport, coreColor, glowColor, glowScale, reflectionStrength = 0, state = null) {
7770
- const projected = projectPoint(point, camera, viewport);
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);
7771
8094
  if (!projected) {
7772
8095
  return;
7773
8096
  }
7774
- const radius = clamp(1 / projected.depth * 420 * glowScale, 4, 34);
8097
+ const radius = clamp(1 / projected.depth * 420 * source.glowScale, 4, 34);
7775
8098
  const halo = ctx.createRadialGradient(projected.x, projected.y, radius * 0.12, projected.x, projected.y, radius);
7776
- halo.addColorStop(0, colorToRgba(coreColor, 0.98));
7777
- halo.addColorStop(0.5, colorToRgba(glowColor, 0.42));
7778
- 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));
7779
8102
  ctx.save();
7780
8103
  ctx.globalCompositeOperation = "screen";
7781
8104
  ctx.fillStyle = halo;
@@ -7783,79 +8106,119 @@ function renderGlowLight(ctx, point, camera, viewport, coreColor, glowColor, glo
7783
8106
  ctx.arc(projected.x, projected.y, radius, 0, Math.PI * 2);
7784
8107
  ctx.fill();
7785
8108
  ctx.restore();
7786
- ctx.fillStyle = colorToRgba(coreColor, 0.98);
8109
+ ctx.fillStyle = colorToRgba(source.coreColor, 0.98);
7787
8110
  ctx.beginPath();
7788
8111
  ctx.arc(projected.x, projected.y, Math.max(1.2, radius * 0.16), 0, Math.PI * 2);
7789
8112
  ctx.fill();
7790
- if (state && reflectionStrength > 0) {
7791
- const waterline = sampleWave(state, point.x, point.z, state.time) * 0.22;
7792
- const reflectedPoint = vec3(point.x, waterline - (point.y - waterline) * 0.58, point.z + 0.08);
7793
- const reflected = projectPoint(reflectedPoint, camera, viewport);
7794
- if (reflected) {
7795
- const reflectionRadius = radius * 0.72;
7796
- const glow = ctx.createRadialGradient(
7797
- reflected.x,
7798
- reflected.y,
7799
- reflectionRadius * 0.1,
7800
- reflected.x,
7801
- reflected.y,
7802
- reflectionRadius
7803
- );
7804
- glow.addColorStop(0, colorToRgba(coreColor, reflectionStrength * 0.34));
7805
- glow.addColorStop(1, colorToRgba(glowColor, 0));
7806
- ctx.save();
7807
- ctx.globalCompositeOperation = "screen";
7808
- ctx.fillStyle = glow;
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})`;
7809
8193
  ctx.beginPath();
7810
8194
  ctx.ellipse(
7811
- reflected.x,
7812
- reflected.y,
7813
- reflectionRadius * 0.34,
7814
- reflectionRadius,
8195
+ entry.projected.x,
8196
+ entry.projected.y,
8197
+ baseWidth * 0.72,
8198
+ baseWidth * 0.44,
7815
8199
  0,
7816
8200
  0,
7817
8201
  Math.PI * 2
7818
8202
  );
7819
8203
  ctx.fill();
7820
- ctx.restore();
7821
8204
  }
7822
8205
  }
7823
- }
7824
- function renderShipLanterns(ctx, ship, state, camera, viewport, visuals) {
7825
- const lanterns = Array.isArray(ship.lanterns) ? ship.lanterns : SHIP_LANTERNS;
7826
- const strength = readVisualNumber(ship.lanternStrength, 1);
7827
- for (const lantern of lanterns) {
7828
- const position = transformPoint(
7829
- vec3(lantern.x, lantern.y, lantern.z),
7830
- { position: ship.position, rotationY: ship.rotationY, scale: SHIP_SCALE }
7831
- );
7832
- renderGlowLight(
7833
- ctx,
7834
- position,
7835
- camera,
7836
- viewport,
7837
- visuals.lanternCore,
7838
- visuals.lanternGlow,
7839
- lantern.glow * strength,
7840
- visuals.lanternReflectionStrength,
7841
- state
7842
- );
7843
- }
7844
- }
7845
- function renderHarborTorches(ctx, state, camera, viewport, visuals) {
7846
- for (const torch of HARBOR_TORCHES) {
7847
- renderGlowLight(
7848
- ctx,
7849
- vec3(torch.x, torch.y, torch.z),
7850
- camera,
7851
- viewport,
7852
- visuals.torchCore,
7853
- visuals.torchGlow,
7854
- torch.glow,
7855
- visuals.lanternReflectionStrength * 0.55,
7856
- state
7857
- );
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();
7858
8220
  }
8221
+ ctx.restore();
7859
8222
  }
7860
8223
  function renderScene(ctx, canvas, state, shipModel, dom) {
7861
8224
  const viewport = { width: canvas.width, height: canvas.height };
@@ -7885,8 +8248,8 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
7885
8248
  shadowStrength,
7886
8249
  visuals
7887
8250
  );
7888
- const triangles = [];
7889
- pushHarborGeometry(camera, viewport, triangles, visuals);
8251
+ const waterTriangles = [];
8252
+ const sceneTriangles = [];
7890
8253
  const water = buildWaterBands(
7891
8254
  state,
7892
8255
  state.fluidDetail.getSnapshot().currentLevel.config,
@@ -7903,7 +8266,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
7903
8266
  if (projected.some((value) => value === null)) {
7904
8267
  continue;
7905
8268
  }
7906
- triangles.push({
8269
+ waterTriangles.push({
7907
8270
  points: projected,
7908
8271
  depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
7909
8272
  worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
@@ -7913,6 +8276,9 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
7913
8276
  });
7914
8277
  }
7915
8278
  }
8279
+ const waterMotionEffects = buildWaterMotionEffects(state);
8280
+ const lightSources = collectSceneLightSources(state, visuals);
8281
+ pushHarborGeometry(camera, viewport, sceneTriangles, visuals);
7916
8282
  const cloth = buildClothSurface(
7917
8283
  state,
7918
8284
  state,
@@ -7928,7 +8294,7 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
7928
8294
  if (projected.some((value) => value === null)) {
7929
8295
  continue;
7930
8296
  }
7931
- triangles.push({
8297
+ sceneTriangles.push({
7932
8298
  points: projected,
7933
8299
  depth: (projected[0].depth + projected[1].depth + projected[2].depth) / 3,
7934
8300
  worldCenter: scaleVec3(addVec3(addVec3(a, b), c), 1 / 3),
@@ -7944,22 +8310,28 @@ function renderScene(ctx, canvas, state, shipModel, dom) {
7944
8310
  ship.tint,
7945
8311
  camera,
7946
8312
  viewport,
7947
- triangles,
8313
+ sceneTriangles,
7948
8314
  nearLighting.rtParticipation.directShadows === "premium" ? 0.08 : 0.02
7949
8315
  );
7950
8316
  }
8317
+ drawTriangles(ctx, waterTriangles, lightDir, reflectionStrength, camera, shadowStrength);
7951
8318
  for (const ship of state.ships) {
7952
8319
  renderShipShadow(ctx, shipModel, ship, state, camera, viewport, lightDir, shadowStrength);
7953
8320
  }
7954
8321
  renderFlagShadow(ctx, cloth, camera, viewport, lightDir, shadowStrength);
7955
- drawTriangles(ctx, triangles, lightDir, reflectionStrength, camera, shadowStrength);
8322
+ for (const source of lightSources.reflectionLights) {
8323
+ renderWaterLightReflection(ctx, source, state, camera, viewport);
8324
+ }
8325
+ renderWaterMotionEffects(ctx, waterMotionEffects, camera, viewport);
7956
8326
  renderWaterHighlights(ctx, water.bandMeshes, camera, viewport);
7957
- renderHarborTorches(ctx, state, camera, viewport, visuals);
8327
+ drawTriangles(ctx, sceneTriangles, lightDir, reflectionStrength, camera, shadowStrength);
7958
8328
  renderFlagPole(ctx, camera, viewport);
7959
8329
  renderClothAccent(ctx, cloth, camera, viewport);
8330
+ for (const source of lightSources.directLights) {
8331
+ renderDirectLightGlow(ctx, source, camera, viewport);
8332
+ }
7960
8333
  for (const ship of state.ships) {
7961
8334
  renderShipRigging(ctx, ship, camera, viewport);
7962
- renderShipLanterns(ctx, ship, state, camera, viewport, visuals);
7963
8335
  }
7964
8336
  renderSprays(ctx, state.sprays, camera, viewport);
7965
8337
  const debugSnapshot = state.debugSession.getSnapshot();
@@ -8021,6 +8393,21 @@ function updateSceneState(state, dt, shipModel) {
8021
8393
  updateShips(state, dt, shipModel);
8022
8394
  updateWaveImpulses(state, dt);
8023
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
+ });
8024
8411
  updatePhysicsSnapshot(state, shipModel);
8025
8412
  }
8026
8413
  function syncTextState(state, shipModel) {
@@ -8188,7 +8575,11 @@ function updatePhysicsSnapshot(state, shipModel) {
8188
8575
  });
8189
8576
  }
8190
8577
  export {
8578
+ advanceShowcaseClothSimulationState as __testOnlyAdvanceShowcaseClothSimulationState,
8579
+ buildWaterMotionEffects as __testOnlyBuildWaterMotionEffects,
8580
+ collectSceneLightSources as __testOnlyCollectSceneLightSources,
8581
+ createShowcaseClothSimulationState as __testOnlyCreateShowcaseClothSimulationState,
8191
8582
  mountGpuShowcase,
8192
8583
  showcaseFocusModes
8193
8584
  };
8194
- //# sourceMappingURL=showcase-runtime-JZIYGQAU.js.map
8585
+ //# sourceMappingURL=showcase-runtime-55OVDYWT.js.map