@plasius/gpu-shared 0.1.17 → 0.1.19

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 CHANGED
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ - **Added**
8
+ - (placeholder)
9
+
10
+ - **Changed**
11
+ - (placeholder)
12
+
13
+ - **Fixed**
14
+ - (placeholder)
15
+
16
+ - **Security**
17
+ - (placeholder)
18
+
19
+ ## [0.1.19] - 2026-06-22
20
+
7
21
  - **Added**
8
22
  - Deterministic showcase asset generation for a richer shared brigantine,
9
23
  cutter, lighthouse, and harbor-dock catalog.
@@ -16,6 +30,9 @@ All notable changes to this project will be documented in this file.
16
30
  delegation to the `@plasius/gpu-renderer` WebGPU wavefront renderer.
17
31
 
18
32
  - **Changed**
33
+ - Lowered Product Studio wavefront renderer defaults to a host-safe `640x360`
34
+ frame at `maxDepth: 2` so browser demos do not start with a heavy 720p
35
+ depth-6 GPU workload.
19
36
  - Updated the optional `@plasius/gpu-renderer` peer range to the 0.2 line so
20
37
  Product Studio and demo consumers can use the released realtime wavefront
21
38
  camera update API without npm peer conflicts.
@@ -273,3 +290,4 @@ All notable changes to this project will be documented in this file.
273
290
  [0.1.9]: https://github.com/Plasius-LTD/gpu-shared/releases/tag/v0.1.9
274
291
  [0.1.10]: https://github.com/Plasius-LTD/gpu-shared/releases/tag/v0.1.10
275
292
  [0.1.11]: https://github.com/Plasius-LTD/gpu-shared/releases/tag/v0.1.11
293
+ [0.1.19]: https://github.com/Plasius-LTD/gpu-shared/releases/tag/v0.1.19
package/README.md CHANGED
@@ -214,6 +214,9 @@ surface for these family demos.
214
214
 
215
215
  - `mountGpuShowcase(options)`
216
216
  - Returns `{ state, shipModel, canvas, destroy() }`
217
+ - Product Studio mode defaults to a host-safe `640x360` wavefront render at
218
+ `maxDepth: 2`; callers can raise `width`, `height`, or `maxDepth`
219
+ deliberately for capture or high-end review hardware.
217
220
  - `captureMode: true` enables fullscreen scene-only presentation for local
218
221
  screenshots and video capture.
219
222
  - `renderScale` overrides the canvas backing scale when a capture workflow
@@ -7,9 +7,9 @@ var STYLE_ID = "plasius-product-studio-wavefront-style";
7
7
  var DEFAULT_PRODUCT_ASSET_URL = "/data/models/eames-lounge-chair-ottoman/Eames_Lounge_Chair_Ottoman.gltf";
8
8
  var DEFAULT_TARGET_CENTER = Object.freeze([0, 0.74, 0]);
9
9
  var DEFAULT_TARGET_SIZE = 2.25;
10
- function clamp(value, min, max) {
11
- return Math.max(min, Math.min(max, value));
12
- }
10
+ var DEFAULT_RENDER_WIDTH = 640;
11
+ var DEFAULT_RENDER_HEIGHT = 360;
12
+ var DEFAULT_RENDER_MAX_DEPTH = 2;
13
13
  function isFiniteVector(value) {
14
14
  return Array.isArray(value) && value.length >= 3 && Number.isFinite(value[0]) && Number.isFinite(value[1]) && Number.isFinite(value[2]);
15
15
  }
@@ -305,16 +305,10 @@ function resolveRoot(options) {
305
305
  }
306
306
  return root;
307
307
  }
308
- function resolveRenderSize(root, options) {
309
- const rect = root.getBoundingClientRect?.() ?? { width: 1280, height: 720 };
310
- const devicePixelRatio = Number.isFinite(options.devicePixelRatio) ? options.devicePixelRatio : Number.isFinite(globalThis.window?.devicePixelRatio) ? globalThis.window.devicePixelRatio : 1;
311
- const cssWidth = Number.isFinite(rect.width) && rect.width > 0 ? rect.width : 1280;
312
- const cssHeight = Number.isFinite(rect.height) && rect.height > 0 ? rect.height : cssWidth * (9 / 16);
313
- const width = Number.isFinite(options.width) ? Math.trunc(options.width) : clamp(Math.round(cssWidth * devicePixelRatio), 640, 1920);
314
- const height = Number.isFinite(options.height) ? Math.trunc(options.height) : clamp(Math.round(cssHeight * devicePixelRatio), 360, 1080);
308
+ function resolveRenderSize(options) {
315
309
  return {
316
- width,
317
- height
310
+ width: Number.isFinite(options.width) ? Math.trunc(options.width) : DEFAULT_RENDER_WIDTH,
311
+ height: Number.isFinite(options.height) ? Math.trunc(options.height) : DEFAULT_RENDER_HEIGHT
318
312
  };
319
313
  }
320
314
  function installSnapshotHook(state) {
@@ -385,13 +379,13 @@ async function mountGpuProductStudio(options = {}, featureFlags = null) {
385
379
  if (typeof rendererModule.createWavefrontPathTracingComputeRenderer !== "function") {
386
380
  throw new Error("Product Studio renderer loader must provide createWavefrontPathTracingComputeRenderer.");
387
381
  }
388
- const size = resolveRenderSize(root, options);
382
+ const size = resolveRenderSize(options);
389
383
  const lightingOptions = await resolveWavefrontLightingOptions(options);
390
384
  const renderer = await rendererModule.createWavefrontPathTracingComputeRenderer({
391
385
  canvas,
392
386
  width: size.width,
393
387
  height: size.height,
394
- maxDepth: Number.isFinite(options.maxDepth) ? options.maxDepth : 6,
388
+ maxDepth: Number.isFinite(options.maxDepth) ? options.maxDepth : DEFAULT_RENDER_MAX_DEPTH,
395
389
  tileSize: Number.isFinite(options.tileSize) ? options.tileSize : 128,
396
390
  samplesPerPixel: Number.isFinite(options.samplesPerPixel) ? options.samplesPerPixel : 8,
397
391
  denoise: options.denoise !== false,
@@ -438,4 +432,4 @@ export {
438
432
  createProductStudioMeshes,
439
433
  mountGpuProductStudio
440
434
  };
441
- //# sourceMappingURL=chunk-BXTHIQOO.js.map
435
+ //# sourceMappingURL=chunk-EZHA3MH7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/product-studio-runtime.js"],"sourcesContent":["import { loadGltfModel } from \"./gltf-loader.js\";\n\nconst STYLE_ID = \"plasius-product-studio-wavefront-style\";\nconst DEFAULT_PRODUCT_ASSET_URL =\n \"/data/models/eames-lounge-chair-ottoman/Eames_Lounge_Chair_Ottoman.gltf\";\nconst DEFAULT_TARGET_CENTER = Object.freeze([0, 0.74, 0]);\nconst DEFAULT_TARGET_SIZE = 2.25;\nconst DEFAULT_RENDER_WIDTH = 640;\nconst DEFAULT_RENDER_HEIGHT = 360;\nconst DEFAULT_RENDER_MAX_DEPTH = 2;\n\nfunction isFiniteVector(value) {\n return (\n Array.isArray(value) &&\n value.length >= 3 &&\n Number.isFinite(value[0]) &&\n Number.isFinite(value[1]) &&\n Number.isFinite(value[2])\n );\n}\n\nfunction createEmptyBounds() {\n return {\n min: [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],\n max: [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],\n };\n}\n\nfunction expandBounds(bounds, point) {\n bounds.min[0] = Math.min(bounds.min[0], point[0]);\n bounds.min[1] = Math.min(bounds.min[1], point[1]);\n bounds.min[2] = Math.min(bounds.min[2], point[2]);\n bounds.max[0] = Math.max(bounds.max[0], point[0]);\n bounds.max[1] = Math.max(bounds.max[1], point[1]);\n bounds.max[2] = Math.max(bounds.max[2], point[2]);\n}\n\nfunction getBoundsSize(bounds) {\n return [\n bounds.max[0] - bounds.min[0],\n bounds.max[1] - bounds.min[1],\n bounds.max[2] - bounds.min[2],\n ];\n}\n\nfunction getBoundsCenter(bounds) {\n return [\n (bounds.min[0] + bounds.max[0]) * 0.5,\n (bounds.min[1] + bounds.max[1]) * 0.5,\n (bounds.min[2] + bounds.max[2]) * 0.5,\n ];\n}\n\nfunction getModelBounds(model) {\n if (isFiniteVector(model?.bounds?.min) && isFiniteVector(model?.bounds?.max)) {\n return {\n min: [...model.bounds.min],\n max: [...model.bounds.max],\n };\n }\n\n const bounds = createEmptyBounds();\n for (const primitive of model?.primitives ?? []) {\n for (let index = 0; index < primitive.positions.length; index += 3) {\n expandBounds(bounds, [\n primitive.positions[index],\n primitive.positions[index + 1],\n primitive.positions[index + 2],\n ]);\n }\n }\n return bounds;\n}\n\nfunction transformPoint(point, modelCenter, scale, targetCenter) {\n return [\n (point[0] - modelCenter[0]) * scale + targetCenter[0],\n (point[1] - modelCenter[1]) * scale + targetCenter[1],\n (point[2] - modelCenter[2]) * scale + targetCenter[2],\n ];\n}\n\nfunction readMaterialColor(material) {\n const color = material?.color ?? {};\n return [\n Number.isFinite(color.r) ? color.r : 0.62,\n Number.isFinite(color.g) ? color.g : 0.56,\n Number.isFinite(color.b) ? color.b : 0.48,\n Number.isFinite(color.a) ? color.a : 1,\n ];\n}\n\nfunction readMaterialKind(material) {\n const emissive = material?.emissive ?? {};\n if ((emissive.r ?? 0) + (emissive.g ?? 0) + (emissive.b ?? 0) > 0.001) {\n return \"emissive\";\n }\n if ((material?.metallic ?? 0) >= 0.5) {\n return \"metal\";\n }\n const alpha = readMaterialColor(material)[3];\n if (alpha < 0.9) {\n return \"transparent\";\n }\n return \"diffuse\";\n}\n\nfunction readEmission(material) {\n const emissive = material?.emissive ?? {};\n return [\n Number.isFinite(emissive.r) ? emissive.r : 0,\n Number.isFinite(emissive.g) ? emissive.g : 0,\n Number.isFinite(emissive.b) ? emissive.b : 0,\n 1,\n ];\n}\n\nfunction createQuadMesh({\n id,\n corners,\n color,\n emission = [0, 0, 0, 1],\n materialKind = \"diffuse\",\n roughness = 0.72,\n metallic = 0,\n opacity = color[3] ?? 1,\n}) {\n const [a, b, c] = corners;\n const edge1 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];\n const edge2 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];\n const normal = [\n edge1[1] * edge2[2] - edge1[2] * edge2[1],\n edge1[2] * edge2[0] - edge1[0] * edge2[2],\n edge1[0] * edge2[1] - edge1[1] * edge2[0],\n ];\n const length = Math.hypot(normal[0], normal[1], normal[2]) || 1;\n const unitNormal = normal.map((value) => value / length);\n\n return Object.freeze({\n id,\n positions: Object.freeze(corners.flat()),\n indices: Object.freeze([0, 1, 2, 0, 2, 3]),\n normals: Object.freeze([unitNormal, unitNormal, unitNormal, unitNormal].flat()),\n color: Object.freeze([...color]),\n emission: Object.freeze([...emission]),\n materialKind,\n roughness,\n metallic,\n opacity,\n });\n}\n\nfunction createProductStudioEnvironmentMeshes() {\n return [\n createQuadMesh({\n id: 1,\n corners: [\n [-3.2, -0.08, 2.4],\n [3.2, -0.08, 2.4],\n [3.2, -0.08, -3.1],\n [-3.2, -0.08, -3.1],\n ],\n color: [0.48, 0.55, 0.55, 1],\n roughness: 0.82,\n }),\n createQuadMesh({\n id: 2,\n corners: [\n [-3.2, -0.08, -2.45],\n [3.2, -0.08, -2.45],\n [3.2, 2.65, -2.45],\n [-3.2, 2.65, -2.45],\n ],\n color: [0.43, 0.42, 0.38, 1],\n roughness: 0.86,\n }),\n createQuadMesh({\n id: 3,\n corners: [\n [-2.85, -0.08, -2.45],\n [-2.85, 2.55, -2.45],\n [-2.85, 2.55, 2.15],\n [-2.85, -0.08, 2.15],\n ],\n color: [0.36, 0.42, 0.45, 1],\n roughness: 0.8,\n }),\n createQuadMesh({\n id: 4,\n corners: [\n [0.78, 2.55, -0.82],\n [-0.78, 2.55, -0.82],\n [-0.78, 2.55, -1.78],\n [0.78, 2.55, -1.78],\n ],\n color: [1, 0.94, 0.78, 1],\n emission: [8.5, 7.2, 4.8, 1],\n materialKind: \"emissive\",\n roughness: 0,\n }),\n ];\n}\n\nfunction createProductStudioMeshFromPrimitive(primitive, primitiveIndex, transform) {\n if (!Array.isArray(primitive?.positions) || primitive.positions.length < 9) {\n return null;\n }\n\n const positions = [];\n for (let index = 0; index < primitive.positions.length; index += 3) {\n const point = transform([\n primitive.positions[index],\n primitive.positions[index + 1],\n primitive.positions[index + 2],\n ]);\n positions.push(point[0], point[1], point[2]);\n }\n\n const indices =\n Array.isArray(primitive.indices) && primitive.indices.length >= 3\n ? [...primitive.indices]\n : Array.from({ length: positions.length / 3 }, (_, index) => index);\n const material = primitive.material ?? {};\n const color = readMaterialColor(material);\n const uvs = Array.isArray(primitive.uvs) ? [...primitive.uvs] : null;\n\n return Object.freeze({\n id: 1000 + primitiveIndex,\n positions: Object.freeze(positions),\n indices: Object.freeze(indices),\n normals: Array.isArray(primitive.normals) ? Object.freeze([...primitive.normals]) : null,\n uvs: uvs ? Object.freeze(uvs) : null,\n material: Object.freeze({ ...material }),\n color: Object.freeze(color),\n emission: Object.freeze(readEmission(material)),\n materialKind: readMaterialKind(material),\n materialRefId: 1000 + primitiveIndex,\n roughness: Number.isFinite(material.roughness) ? material.roughness : 0.72,\n metallic: Number.isFinite(material.metallic) ? material.metallic : 0,\n opacity: color[3],\n baseColorTexture: material.baseColorTexture ?? null,\n metallicRoughnessTexture: material.metallicRoughnessTexture ?? null,\n normalTexture: material.normalTexture ?? null,\n occlusionTexture: material.occlusionTexture ?? null,\n emissiveTexture: material.emissiveTexture ?? null,\n specular: Number.isFinite(material.specular) ? material.specular : 1,\n specularColor: Array.isArray(material.specularColor)\n ? Object.freeze([...material.specularColor])\n : undefined,\n specularTexture: material.specularTexture ?? null,\n specularColorTexture: material.specularColorTexture ?? null,\n transmission: Number.isFinite(material.transmission) ? material.transmission : 0,\n transmissionTexture: material.transmissionTexture ?? null,\n ior: Number.isFinite(material.ior) ? material.ior : undefined,\n thickness: Number.isFinite(material.thickness) ? material.thickness : undefined,\n thicknessTexture: material.thicknessTexture ?? null,\n attenuationDistance:\n Number.isFinite(material.attenuationDistance) ? material.attenuationDistance : null,\n attenuationColor: Array.isArray(material.attenuationColor)\n ? Object.freeze([...material.attenuationColor])\n : undefined,\n clearcoat: Number.isFinite(material.clearcoat) ? material.clearcoat : undefined,\n clearcoatTexture: material.clearcoatTexture ?? null,\n clearcoatRoughness: Number.isFinite(material.clearcoatRoughness)\n ? material.clearcoatRoughness\n : undefined,\n clearcoatRoughnessTexture: material.clearcoatRoughnessTexture ?? null,\n clearcoatNormalTexture: material.clearcoatNormalTexture ?? null,\n sheenColor: Array.isArray(material.sheenColor)\n ? Object.freeze([...material.sheenColor])\n : undefined,\n sheenColorTexture: material.sheenColorTexture ?? null,\n sheenRoughness: Number.isFinite(material.sheenRoughness) ? material.sheenRoughness : undefined,\n sheenRoughnessTexture: material.sheenRoughnessTexture ?? null,\n iridescence: Number.isFinite(material.iridescence) ? material.iridescence : undefined,\n iridescenceTexture: material.iridescenceTexture ?? null,\n iridescenceIor: Number.isFinite(material.iridescenceIor) ? material.iridescenceIor : undefined,\n iridescenceThicknessMinimum: Number.isFinite(material.iridescenceThicknessMinimum)\n ? material.iridescenceThicknessMinimum\n : undefined,\n iridescenceThicknessMaximum: Number.isFinite(material.iridescenceThicknessMaximum)\n ? material.iridescenceThicknessMaximum\n : undefined,\n iridescenceThicknessTexture: material.iridescenceThicknessTexture ?? null,\n anisotropy: Number.isFinite(material.anisotropy) ? material.anisotropy : undefined,\n anisotropyRotation: Number.isFinite(material.anisotropyRotation)\n ? material.anisotropyRotation\n : undefined,\n anisotropyTexture: material.anisotropyTexture ?? null,\n dispersion: Number.isFinite(material.dispersion) ? material.dispersion : undefined,\n });\n}\n\nexport function createProductStudioMeshes(model, options = {}) {\n const primitives = Array.isArray(model?.primitives) ? model.primitives : [];\n if (primitives.length === 0) {\n throw new Error(\"Product Studio model must contain at least one renderable primitive.\");\n }\n\n const targetCenter = isFiniteVector(options.targetCenter)\n ? [...options.targetCenter]\n : [...DEFAULT_TARGET_CENTER];\n const targetSize = Number.isFinite(options.targetSize)\n ? Math.max(options.targetSize, 0.25)\n : DEFAULT_TARGET_SIZE;\n const modelBounds = getModelBounds(model);\n const modelSize = getBoundsSize(modelBounds);\n const modelCenter = getBoundsCenter(modelBounds);\n const scale = targetSize / Math.max(modelSize[0], modelSize[1], modelSize[2], 0.000001);\n const transform = (point) => transformPoint(point, modelCenter, scale, targetCenter);\n const modelMeshes = primitives\n .map((primitive, index) => createProductStudioMeshFromPrimitive(primitive, index, transform))\n .filter(Boolean);\n\n return Object.freeze([...createProductStudioEnvironmentMeshes(), ...modelMeshes]);\n}\n\nfunction ensureStyles(documentRef) {\n if (documentRef.getElementById?.(STYLE_ID)) {\n return;\n }\n const style = documentRef.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = `\n .plasius-product-studio-wavefront {\n position: relative;\n width: 100%;\n min-height: 420px;\n overflow: hidden;\n background: #0f1418;\n display: grid;\n place-items: center;\n }\n\n .plasius-product-studio-wavefront canvas {\n display: block;\n width: 100%;\n height: auto;\n max-height: 100%;\n aspect-ratio: 16 / 9;\n min-height: 420px;\n object-fit: contain;\n }\n `;\n documentRef.head?.appendChild?.(style);\n}\n\nfunction resolveRoot(options) {\n const documentRef = options.document ?? globalThis.document;\n if (options.root) {\n return options.root;\n }\n const root =\n documentRef?.querySelector?.(\"[data-plasius-gpu-product-studio]\") ?? documentRef?.body;\n if (!root) {\n throw new Error(\"Product Studio requires a DOM root.\");\n }\n return root;\n}\n\nfunction resolveRenderSize(options) {\n return {\n width: Number.isFinite(options.width) ? Math.trunc(options.width) : DEFAULT_RENDER_WIDTH,\n height: Number.isFinite(options.height) ? Math.trunc(options.height) : DEFAULT_RENDER_HEIGHT,\n };\n}\n\nfunction installSnapshotHook(state) {\n if (typeof globalThis.window === \"undefined\") {\n return () => {};\n }\n const previous = globalThis.window.render_game_to_text;\n globalThis.window.render_game_to_text = () =>\n JSON.stringify({\n surface: \"gpu-product-studio-wavefront\",\n model: state.modelName,\n sourceTriangles: state.sourceTriangleCount,\n meshCount: state.meshCount,\n geometryMode: state.geometryMode,\n requiresTriangleMeshRenderer: state.requiresTriangleMeshRenderer,\n displayQuality: state.displayQuality,\n requiresMeshBvhForDisplayQuality: state.requiresMeshBvhForDisplayQuality,\n renderer: state.rendererStats,\n });\n return () => {\n if (previous === undefined) {\n delete globalThis.window.render_game_to_text;\n } else {\n globalThis.window.render_game_to_text = previous;\n }\n };\n}\n\nfunction countSourceTriangles(model) {\n return (model.primitives ?? []).reduce(\n (total, primitive) => total + Math.floor((primitive.indices?.length ?? 0) / 3),\n 0\n );\n}\n\nasync function resolveWavefrontLightingOptions(options) {\n const fallback = {\n environmentColor: [0.35, 0.43, 0.49, 1],\n ambientColor: [0.02, 0.024, 0.028, 1],\n };\n const lightingLoader =\n typeof options.__lightingLoader === \"function\"\n ? options.__lightingLoader\n : () => import(\"@plasius/gpu-lighting\").catch(() => null);\n const lightingModule = await lightingLoader();\n\n if (\n typeof lightingModule?.createWavefrontEnvironmentLightingOptions !== \"function\"\n ) {\n return fallback;\n }\n\n return lightingModule.createWavefrontEnvironmentLightingOptions({\n preset: options.lightingPreset ?? \"product-studio\",\n intensity: options.lightingIntensity,\n });\n}\n\nexport async function mountGpuProductStudio(options = {}, featureFlags = null) {\n const root = resolveRoot(options);\n const documentRef = options.document ?? root.ownerDocument ?? globalThis.document;\n ensureStyles(documentRef);\n const previousMarkup = root.innerHTML;\n root.innerHTML = \"\";\n root.classList?.add?.(\"plasius-product-studio-wavefront\");\n\n const canvas = documentRef.createElement(\"canvas\");\n canvas.dataset.plasiusGpuProductStudio = \"wavefront\";\n root.appendChild(canvas);\n\n const modelLoader =\n typeof options.__modelLoader === \"function\" ? options.__modelLoader : loadGltfModel;\n const rendererLoader =\n typeof options.__rendererLoader === \"function\"\n ? options.__rendererLoader\n : () => import(\"@plasius/gpu-renderer\");\n const assetUrl = options.productAssetUrl ?? options.assetUrl ?? DEFAULT_PRODUCT_ASSET_URL;\n const model = await modelLoader(assetUrl);\n const meshes = createProductStudioMeshes(model, {\n targetCenter: options.targetCenter,\n targetSize: options.targetSize,\n });\n const rendererModule = await rendererLoader();\n if (typeof rendererModule.createWavefrontPathTracingComputeRenderer !== \"function\") {\n throw new Error(\"Product Studio renderer loader must provide createWavefrontPathTracingComputeRenderer.\");\n }\n\n const size = resolveRenderSize(options);\n const lightingOptions = await resolveWavefrontLightingOptions(options);\n const renderer = await rendererModule.createWavefrontPathTracingComputeRenderer({\n canvas,\n width: size.width,\n height: size.height,\n maxDepth: Number.isFinite(options.maxDepth) ? options.maxDepth : DEFAULT_RENDER_MAX_DEPTH,\n tileSize: Number.isFinite(options.tileSize) ? options.tileSize : 128,\n samplesPerPixel: Number.isFinite(options.samplesPerPixel) ? options.samplesPerPixel : 8,\n denoise: options.denoise !== false,\n displayQuality: true,\n meshes,\n camera: {\n position: [0, 1.12, 5.05],\n target: [0, 0.72, 0],\n up: [0, 1, 0],\n fovYDegrees: 43,\n },\n ...lightingOptions,\n });\n const rendererStats = renderer.renderOnce();\n const state = Object.freeze({\n featureFlags,\n modelName: model.name,\n sourceTriangleCount: countSourceTriangles(model),\n meshCount: meshes.length,\n geometryMode: \"mesh-bvh-display-quality\",\n requiresTriangleMeshRenderer: true,\n displayQuality: true,\n requiresMeshBvhForDisplayQuality: true,\n rendererStats,\n });\n const restoreSnapshotHook = installSnapshotHook(state);\n\n return Object.freeze({\n state,\n model,\n productModel: model,\n canvas,\n renderer,\n meshes,\n destroy() {\n restoreSnapshotHook();\n renderer.destroy();\n root.classList?.remove?.(\"plasius-product-studio-wavefront\");\n root.innerHTML = previousMarkup;\n },\n });\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -2410,9 +2410,6 @@ __export(product_studio_runtime_exports, {
2410
2410
  createProductStudioMeshes: () => createProductStudioMeshes,
2411
2411
  mountGpuProductStudio: () => mountGpuProductStudio
2412
2412
  });
2413
- function clamp(value, min, max) {
2414
- return Math.max(min, Math.min(max, value));
2415
- }
2416
2413
  function isFiniteVector(value) {
2417
2414
  return Array.isArray(value) && value.length >= 3 && Number.isFinite(value[0]) && Number.isFinite(value[1]) && Number.isFinite(value[2]);
2418
2415
  }
@@ -2708,16 +2705,10 @@ function resolveRoot(options) {
2708
2705
  }
2709
2706
  return root;
2710
2707
  }
2711
- function resolveRenderSize(root, options) {
2712
- const rect = root.getBoundingClientRect?.() ?? { width: 1280, height: 720 };
2713
- const devicePixelRatio = Number.isFinite(options.devicePixelRatio) ? options.devicePixelRatio : Number.isFinite(globalThis.window?.devicePixelRatio) ? globalThis.window.devicePixelRatio : 1;
2714
- const cssWidth = Number.isFinite(rect.width) && rect.width > 0 ? rect.width : 1280;
2715
- const cssHeight = Number.isFinite(rect.height) && rect.height > 0 ? rect.height : cssWidth * (9 / 16);
2716
- const width = Number.isFinite(options.width) ? Math.trunc(options.width) : clamp(Math.round(cssWidth * devicePixelRatio), 640, 1920);
2717
- const height = Number.isFinite(options.height) ? Math.trunc(options.height) : clamp(Math.round(cssHeight * devicePixelRatio), 360, 1080);
2708
+ function resolveRenderSize(options) {
2718
2709
  return {
2719
- width,
2720
- height
2710
+ width: Number.isFinite(options.width) ? Math.trunc(options.width) : DEFAULT_RENDER_WIDTH,
2711
+ height: Number.isFinite(options.height) ? Math.trunc(options.height) : DEFAULT_RENDER_HEIGHT
2721
2712
  };
2722
2713
  }
2723
2714
  function installSnapshotHook(state) {
@@ -2788,13 +2779,13 @@ async function mountGpuProductStudio(options = {}, featureFlags = null) {
2788
2779
  if (typeof rendererModule.createWavefrontPathTracingComputeRenderer !== "function") {
2789
2780
  throw new Error("Product Studio renderer loader must provide createWavefrontPathTracingComputeRenderer.");
2790
2781
  }
2791
- const size = resolveRenderSize(root, options);
2782
+ const size = resolveRenderSize(options);
2792
2783
  const lightingOptions = await resolveWavefrontLightingOptions(options);
2793
2784
  const renderer = await rendererModule.createWavefrontPathTracingComputeRenderer({
2794
2785
  canvas,
2795
2786
  width: size.width,
2796
2787
  height: size.height,
2797
- maxDepth: Number.isFinite(options.maxDepth) ? options.maxDepth : 6,
2788
+ maxDepth: Number.isFinite(options.maxDepth) ? options.maxDepth : DEFAULT_RENDER_MAX_DEPTH,
2798
2789
  tileSize: Number.isFinite(options.tileSize) ? options.tileSize : 128,
2799
2790
  samplesPerPixel: Number.isFinite(options.samplesPerPixel) ? options.samplesPerPixel : 8,
2800
2791
  denoise: options.denoise !== false,
@@ -2836,7 +2827,7 @@ async function mountGpuProductStudio(options = {}, featureFlags = null) {
2836
2827
  }
2837
2828
  });
2838
2829
  }
2839
- var STYLE_ID, DEFAULT_PRODUCT_ASSET_URL, DEFAULT_TARGET_CENTER, DEFAULT_TARGET_SIZE;
2830
+ var STYLE_ID, DEFAULT_PRODUCT_ASSET_URL, DEFAULT_TARGET_CENTER, DEFAULT_TARGET_SIZE, DEFAULT_RENDER_WIDTH, DEFAULT_RENDER_HEIGHT, DEFAULT_RENDER_MAX_DEPTH;
2840
2831
  var init_product_studio_runtime = __esm({
2841
2832
  "src/product-studio-runtime.js"() {
2842
2833
  init_gltf_loader();
@@ -2844,6 +2835,9 @@ var init_product_studio_runtime = __esm({
2844
2835
  DEFAULT_PRODUCT_ASSET_URL = "/data/models/eames-lounge-chair-ottoman/Eames_Lounge_Chair_Ottoman.gltf";
2845
2836
  DEFAULT_TARGET_CENTER = Object.freeze([0, 0.74, 0]);
2846
2837
  DEFAULT_TARGET_SIZE = 2.25;
2838
+ DEFAULT_RENDER_WIDTH = 640;
2839
+ DEFAULT_RENDER_HEIGHT = 360;
2840
+ DEFAULT_RENDER_MAX_DEPTH = 2;
2847
2841
  }
2848
2842
  });
2849
2843
 
@@ -2964,17 +2958,17 @@ function createFallbackPerformanceFeatureModule() {
2964
2958
  let pressureLevel = "stable";
2965
2959
  let frameSamples = 0;
2966
2960
  let averageMs = 16.67;
2967
- const clamp3 = (next = 16.67) => Number.isFinite(next) ? Math.max(1, next) : 16.67;
2961
+ const clamp2 = (next = 16.67) => Number.isFinite(next) ? Math.max(1, next) : 16.67;
2968
2962
  const target = Object.freeze({
2969
2963
  targetFrameTimeMs: 16.67,
2970
- downgradeFrameTimeMs: clamp3(adaptation?.degradeThresholdMs ?? 20),
2971
- upgradeFrameTimeMs: clamp3(adaptation?.upgradeThresholdMs ?? 14)
2964
+ downgradeFrameTimeMs: clamp2(adaptation?.degradeThresholdMs ?? 20),
2965
+ upgradeFrameTimeMs: clamp2(adaptation?.upgradeThresholdMs ?? 14)
2972
2966
  });
2973
2967
  return {
2974
2968
  recordFrame({ frameTimeMs = averageMs } = {}) {
2975
2969
  const sample = Number.isFinite(Number(frameTimeMs)) ? Number(frameTimeMs) : averageMs;
2976
2970
  frameSamples += 1;
2977
- averageMs = clamp3((averageMs * (frameSamples - 1) + sample) / frameSamples);
2971
+ averageMs = clamp2((averageMs * (frameSamples - 1) + sample) / frameSamples);
2978
2972
  const fps = 1e3 / averageMs;
2979
2973
  pressureLevel = sample > target.downgradeFrameTimeMs ? "degrade" : pressureLevel === "degrade" && sample <= target.upgradeFrameTimeMs ? "stable" : pressureLevel;
2980
2974
  return {
@@ -3655,14 +3649,14 @@ function injectStyles() {
3655
3649
  `;
3656
3650
  document.head.appendChild(style);
3657
3651
  }
3658
- function clamp2(value, min, max) {
3652
+ function clamp(value, min, max) {
3659
3653
  return Math.max(min, Math.min(max, value));
3660
3654
  }
3661
3655
  function mix(a, b, t) {
3662
3656
  return a + (b - a) * t;
3663
3657
  }
3664
3658
  function smoothstep(min, max, value) {
3665
- const t = clamp2((value - min) / Math.max(1e-4, max - min), 0, 1);
3659
+ const t = clamp((value - min) / Math.max(1e-4, max - min), 0, 1);
3666
3660
  return t * t * (3 - 2 * t);
3667
3661
  }
3668
3662
  function pseudoRandom(seed) {
@@ -3746,10 +3740,10 @@ function projectPoint(point, camera, viewport) {
3746
3740
  };
3747
3741
  }
3748
3742
  function colorToRgba(color, alpha = 1) {
3749
- const r = Math.round(clamp2(color.r, 0, 1) * 255);
3750
- const g = Math.round(clamp2(color.g, 0, 1) * 255);
3751
- const b = Math.round(clamp2(color.b, 0, 1) * 255);
3752
- return `rgba(${r}, ${g}, ${b}, ${clamp2(alpha, 0, 1)})`;
3743
+ const r = Math.round(clamp(color.r, 0, 1) * 255);
3744
+ const g = Math.round(clamp(color.g, 0, 1) * 255);
3745
+ const b = Math.round(clamp(color.b, 0, 1) * 255);
3746
+ return `rgba(${r}, ${g}, ${b}, ${clamp(alpha, 0, 1)})`;
3753
3747
  }
3754
3748
  function mixColor(a, b, t) {
3755
3749
  return {
@@ -3823,12 +3817,12 @@ function projectShadowPoint(point, lightDir, planeY) {
3823
3817
  return addVec3(point, scaleVec3(shadowDir, distance));
3824
3818
  }
3825
3819
  function shadeColor(base, normal, lightDir, heightBias = 0, accent = 0) {
3826
- const diffuse = clamp2(dotVec3(normalizeVec3(normal), lightDir), 0, 1);
3820
+ const diffuse = clamp(dotVec3(normalizeVec3(normal), lightDir), 0, 1);
3827
3821
  const brightness = 0.24 + diffuse * 0.72 + heightBias * 0.08 + accent;
3828
3822
  return {
3829
- r: clamp2(base.r * brightness, 0, 1),
3830
- g: clamp2(base.g * brightness, 0, 1),
3831
- b: clamp2(base.b * (brightness + 0.03), 0, 1)
3823
+ r: clamp(base.r * brightness, 0, 1),
3824
+ g: clamp(base.g * brightness, 0, 1),
3825
+ b: clamp(base.b * (brightness + 0.03), 0, 1)
3832
3826
  };
3833
3827
  }
3834
3828
  function getMaterialSeed(materialName) {
@@ -3866,13 +3860,13 @@ function applyMaterialDetail(color, material, worldCenter, normal, surfaceType)
3866
3860
  const sample = worldCenter.x * 3.17 + worldCenter.y * 5.29 + worldCenter.z * 7.83 + getMaterialSeed(materialName) * 0.013;
3867
3861
  const grain = (pseudoRandom(sample) - 0.5) * detailStrength;
3868
3862
  const lowerSurface = smoothstep(7.5, -0.8, worldCenter.y);
3869
- const verticalSurface = 1 - clamp2(Math.abs(normal.y), 0, 1);
3863
+ const verticalSurface = 1 - clamp(Math.abs(normal.y), 0, 1);
3870
3864
  const materialLowerWear = /stone|concrete|plaster|paint|wood|timber|plank|crate/.test(materialName.toLowerCase()) ? lowerSurface * verticalSurface * 0.055 : 0;
3871
3865
  const wetlineWear = surfaceType === "ship" && worldCenter.y < 0.72 ? smoothstep(0.72, -0.1, worldCenter.y) * 0.05 : 0;
3872
3866
  return {
3873
- r: clamp2(color.r * (1 + grain) - materialLowerWear - wetlineWear, 0, 1),
3874
- g: clamp2(color.g * (1 + grain * 0.82) - materialLowerWear * 0.9 - wetlineWear, 0, 1),
3875
- b: clamp2(color.b * (1 + grain * 0.62) - materialLowerWear * 0.68 - wetlineWear * 0.75, 0, 1)
3867
+ r: clamp(color.r * (1 + grain) - materialLowerWear - wetlineWear, 0, 1),
3868
+ g: clamp(color.g * (1 + grain * 0.82) - materialLowerWear * 0.9 - wetlineWear, 0, 1),
3869
+ b: clamp(color.b * (1 + grain * 0.62) - materialLowerWear * 0.68 - wetlineWear * 0.75, 0, 1)
3876
3870
  };
3877
3871
  }
3878
3872
  function buildCamera(state, canvas) {
@@ -4298,7 +4292,7 @@ function resizeCanvasToDisplaySize(canvas, state) {
4298
4292
  const deviceScale = readPositiveNumber(globalThis.devicePixelRatio, 1);
4299
4293
  const requestedScale = readPositiveNumber(state.renderScale, deviceScale);
4300
4294
  const maxScale = state.captureMode ? 2 : 1.5;
4301
- let scale = clamp2(requestedScale, 1, maxScale);
4295
+ let scale = clamp(requestedScale, 1, maxScale);
4302
4296
  const pixelBudget = state.captureMode ? CAPTURE_CANVAS_PIXEL_BUDGET : DEFAULT_CANVAS_WIDTH * DEFAULT_CANVAS_HEIGHT * 1.5;
4303
4297
  const projectedPixels = width * height * scale * scale;
4304
4298
  if (projectedPixels > pixelBudget) {
@@ -4483,12 +4477,12 @@ function satisfyClothConstraint(clothState, constraint) {
4483
4477
  }
4484
4478
  }
4485
4479
  function advanceShowcaseClothSimulationState(clothState, options = {}) {
4486
- const dt = clamp2(options.dt ?? 1 / 60, 1 / 240, 1 / 18);
4480
+ const dt = clamp(options.dt ?? 1 / 60, 1 / 240, 1 / 18);
4487
4481
  const time = readVisualNumber(options.time, 0);
4488
4482
  const flagMotion = readVisualNumber(options.flagMotion, 0.92);
4489
4483
  const waveInfluence = readVisualNumber(options.waveInfluence, 0);
4490
4484
  const wrinkleLayers = Math.max(1, clothState.representation.mesh?.wrinkleLayers ?? 2);
4491
- const solverIterations = clamp2(
4485
+ const solverIterations = clamp(
4492
4486
  Math.round(clothState.representation.mesh?.solverIterations ?? 6),
4493
4487
  2,
4494
4488
  10
@@ -4731,7 +4725,7 @@ function buildWaterMotionEffects(state) {
4731
4725
  impulse.z
4732
4726
  ),
4733
4727
  radius,
4734
- opacity: clamp2(impulse.life * 0.28, 0.08, 0.3)
4728
+ opacity: clamp(impulse.life * 0.28, 0.08, 0.3)
4735
4729
  });
4736
4730
  });
4737
4731
  for (const ship of state.ships) {
@@ -4763,7 +4757,7 @@ function buildWaterMotionEffects(state) {
4763
4757
  }
4764
4758
  wakeTrails.push(
4765
4759
  Object.freeze({
4766
- opacity: clamp2(0.18 + speed * 0.09, 0.22, 0.46),
4760
+ opacity: clamp(0.18 + speed * 0.09, 0.22, 0.46),
4767
4761
  points: Object.freeze(points)
4768
4762
  })
4769
4763
  );
@@ -4977,7 +4971,7 @@ function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, s
4977
4971
  const y = pseudoRandom(index * 7 + 5) * canvas.height * 0.42;
4978
4972
  const twinkle = 0.45 + Math.sin(state.time * 1.4 + index * 0.73) * 0.25;
4979
4973
  const radius = 0.6 + pseudoRandom(index * 11 + 2) * 1.9;
4980
- ctx.fillStyle = visuals.starColor.replace(/[\d.]+\)$/u, `${clamp2(twinkle, 0.16, 0.92)})`);
4974
+ ctx.fillStyle = visuals.starColor.replace(/[\d.]+\)$/u, `${clamp(twinkle, 0.16, 0.92)})`);
4981
4975
  ctx.beginPath();
4982
4976
  ctx.arc(x, y, radius, 0, Math.PI * 2);
4983
4977
  ctx.fill();
@@ -5029,7 +5023,7 @@ function drawSkyAndShore(ctx, canvas, state, nearLighting, reflectionStrength, s
5029
5023
  if (state.collisionFlash > 0.01) {
5030
5024
  ctx.fillStyle = visuals.collisionFlash.replace(
5031
5025
  /[\d.]+\)$/u,
5032
- `${clamp2(state.collisionFlash * 0.22, 0, 0.26)})`
5026
+ `${clamp(state.collisionFlash * 0.22, 0, 0.26)})`
5033
5027
  );
5034
5028
  ctx.fillRect(0, 0, canvas.width, canvas.height);
5035
5029
  }
@@ -5048,7 +5042,7 @@ function resolveLocalLightContribution(triangle, lightSources) {
5048
5042
  continue;
5049
5043
  }
5050
5044
  const lightDir = normalizeVec3(delta);
5051
- const facing = clamp2(dotVec3(normal, lightDir), 0, 1);
5045
+ const facing = clamp(dotVec3(normal, lightDir), 0, 1);
5052
5046
  const response = attenuation * (0.18 + facing * 0.82);
5053
5047
  const glowColor = source.glowColor ?? source.coreColor ?? { r: 1, g: 0.72, b: 0.4 };
5054
5048
  contribution.r += glowColor.r * response * 0.32;
@@ -5070,31 +5064,31 @@ function drawTriangles(ctx, triangles, lightDir, reflectionStrength, camera, sha
5070
5064
  triangle.baseColor,
5071
5065
  surfaceNormal,
5072
5066
  lightDir,
5073
- clamp2((triangle.worldCenter.y + 3) / 10, 0, 1),
5067
+ clamp((triangle.worldCenter.y + 3) / 10, 0, 1),
5074
5068
  triangle.accent
5075
5069
  );
5076
5070
  const reflection = reflectionStrength * (triangle.reflection ?? 0);
5077
5071
  const viewDir = normalizeVec3(subVec3(camera.eye, triangle.worldCenter));
5078
5072
  const reflectedLight = reflectVec3(scaleVec3(lightDir, -1), surfaceNormal);
5079
- const gloss = mix(0.78, 0.14, clamp2(material.roughness ?? 0.88, 0, 1)) + (material.metallic ?? 0) * 0.18;
5080
- const specularPower = mix(26, 7, clamp2(material.roughness ?? 0.88, 0, 1));
5081
- const specular = Math.pow(clamp2(dotVec3(reflectedLight, viewDir), 0, 1), specularPower) * gloss;
5073
+ const gloss = mix(0.78, 0.14, clamp(material.roughness ?? 0.88, 0, 1)) + (material.metallic ?? 0) * 0.18;
5074
+ const specularPower = mix(26, 7, clamp(material.roughness ?? 0.88, 0, 1));
5075
+ const specular = Math.pow(clamp(dotVec3(reflectedLight, viewDir), 0, 1), specularPower) * gloss;
5082
5076
  const emissive = material.emissive ?? { r: 0, g: 0, b: 0 };
5083
5077
  const localLight = resolveLocalLightContribution(triangle, localLights);
5084
5078
  const occlusion = triangle.surfaceType === "water" ? shadowStrength * 0.018 : shadowStrength * 0.04;
5085
5079
  const detailed = applyMaterialDetail(
5086
5080
  {
5087
- r: clamp2(
5081
+ r: clamp(
5088
5082
  shaded.r + reflection * 0.08 + specular * 0.16 + emissive.r * 0.42 + localLight.r - occlusion,
5089
5083
  0,
5090
5084
  1
5091
5085
  ),
5092
- g: clamp2(
5086
+ g: clamp(
5093
5087
  shaded.g + reflection * 0.08 + specular * 0.16 + emissive.g * 0.42 + localLight.g - occlusion,
5094
5088
  0,
5095
5089
  1
5096
5090
  ),
5097
- b: clamp2(
5091
+ b: clamp(
5098
5092
  shaded.b + reflection * 0.16 + specular * 0.22 + emissive.b * 0.46 + localLight.b - occlusion * 0.5,
5099
5093
  0,
5100
5094
  1
@@ -5126,7 +5120,7 @@ function renderProjectedShadow(ctx, worldPoints, camera, viewport, lightDir, opt
5126
5120
  }
5127
5121
  ctx.save();
5128
5122
  ctx.globalCompositeOperation = "multiply";
5129
- ctx.fillStyle = options.color ?? `rgba(12, 24, 36, ${clamp2(options.alpha ?? 0.16, 0, 0.5)})`;
5123
+ ctx.fillStyle = options.color ?? `rgba(12, 24, 36, ${clamp(options.alpha ?? 0.16, 0, 0.5)})`;
5130
5124
  ctx.shadowColor = options.color ?? "rgba(12, 24, 36, 0.22)";
5131
5125
  ctx.shadowBlur = options.blur ?? 18;
5132
5126
  ctx.beginPath();
@@ -5363,7 +5357,7 @@ function resolveShipRoute(ship, state, radius) {
5363
5357
  const crossCurrent = Math.cos(state.time * 0.31 + readVisualNumber(ship.wanderPhase, 0));
5364
5358
  const laneCenter = ship.id === "northwind" ? 10.2 + wander * 2.1 + crossCurrent * 0.6 : 7 + wander * 3.3 - crossCurrent * 1.1;
5365
5359
  const targetX = ship.routeDirection > 0 ? HARBOR_BOUNDS.maxX - radius * 1.7 : HARBOR_BOUNDS.minX + radius * 1.7;
5366
- return vec3(targetX, 0, clamp2(laneCenter, HARBOR_BOUNDS.minZ + 1.8, HARBOR_BOUNDS.maxZ - 1.8));
5360
+ return vec3(targetX, 0, clamp(laneCenter, HARBOR_BOUNDS.minZ + 1.8, HARBOR_BOUNDS.maxZ - 1.8));
5367
5361
  }
5368
5362
  function updateShipMotion(state, ship, dt, shipModel) {
5369
5363
  const physics = shipModel.physics;
@@ -5476,7 +5470,7 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
5476
5470
  a.velocity = subVec3(a.velocity, scaleVec3(impulse, invMassA));
5477
5471
  b.velocity = addVec3(b.velocity, scaleVec3(impulse, invMassB));
5478
5472
  const tangentSpeed = dotVec3(relativeVelocity, tangent);
5479
- const frictionMagnitude = clamp2(
5473
+ const frictionMagnitude = clamp(
5480
5474
  -tangentSpeed / Math.max(1e-4, invMassSum),
5481
5475
  -impulseMagnitude * 0.16,
5482
5476
  impulseMagnitude * 0.16
@@ -5497,14 +5491,14 @@ function resolveShipCollision(state, a, b, shipModelA, shipModelB) {
5497
5491
  state.waveImpulses.push({
5498
5492
  x: contactPoint.x,
5499
5493
  z: contactPoint.z,
5500
- strength: clamp2(0.24 + impactSpeed * 0.46 + penetration * 0.9, 0.2, 1.7),
5494
+ strength: clamp(0.24 + impactSpeed * 0.46 + penetration * 0.9, 0.2, 1.7),
5501
5495
  radius: 0.9 + penetration * 1.4,
5502
5496
  life: 1
5503
5497
  });
5504
5498
  state.collisionCount += 1;
5505
5499
  state.collisionFlash = Math.max(
5506
5500
  state.collisionFlash,
5507
- clamp2(impactSpeed * 0.55 + penetration * 1.8, 0.16, 1)
5501
+ clamp(impactSpeed * 0.55 + penetration * 1.8, 0.16, 1)
5508
5502
  );
5509
5503
  a.collisionCooldown = 0.2;
5510
5504
  b.collisionCooldown = 0.2;
@@ -5620,8 +5614,8 @@ function renderSprays(ctx, sprays, camera, viewport) {
5620
5614
  if (!projected) {
5621
5615
  continue;
5622
5616
  }
5623
- const radius = clamp2(1 / projected.depth * 260, 1.5, 7.5);
5624
- ctx.fillStyle = `rgba(225, 243, 250, ${clamp2(spray.life / 1.6, 0, 0.9)})`;
5617
+ const radius = clamp(1 / projected.depth * 260, 1.5, 7.5);
5618
+ ctx.fillStyle = `rgba(225, 243, 250, ${clamp(spray.life / 1.6, 0, 0.9)})`;
5625
5619
  ctx.beginPath();
5626
5620
  ctx.arc(projected.x, projected.y, radius, 0, Math.PI * 2);
5627
5621
  ctx.fill();
@@ -5731,7 +5725,7 @@ function renderDirectLightGlow(ctx, source, camera, viewport) {
5731
5725
  if (!projected) {
5732
5726
  return;
5733
5727
  }
5734
- const radius = clamp2(1 / projected.depth * 420 * source.glowScale, 4, 34);
5728
+ const radius = clamp(1 / projected.depth * 420 * source.glowScale, 4, 34);
5735
5729
  const halo = ctx.createRadialGradient(projected.x, projected.y, radius * 0.12, projected.x, projected.y, radius);
5736
5730
  halo.addColorStop(0, colorToRgba(source.coreColor, 0.98));
5737
5731
  halo.addColorStop(0.5, colorToRgba(source.glowColor, 0.42));
@@ -5753,7 +5747,7 @@ function renderWaterLightReflection(ctx, source, state, camera, viewport) {
5753
5747
  if (!projected) {
5754
5748
  return;
5755
5749
  }
5756
- const radius = clamp2(1 / projected.depth * 420 * source.glowScale, 4, 34);
5750
+ const radius = clamp(1 / projected.depth * 420 * source.glowScale, 4, 34);
5757
5751
  const waterline = sampleWave(state, source.point.x, source.point.z, state.time) * 0.22;
5758
5752
  const reflectedPoint = vec3(
5759
5753
  source.point.x,
@@ -5842,14 +5836,14 @@ function renderLighthouseBeam(ctx, state, camera, viewport, visuals) {
5842
5836
  2,
5843
5837
  projectedSource.x,
5844
5838
  projectedSource.y,
5845
- clamp2(beamLength * 0.22, 18, 80)
5839
+ clamp(beamLength * 0.22, 18, 80)
5846
5840
  );
5847
5841
  core.addColorStop(0, colorToRgba(visuals.torchCore, 0.58));
5848
5842
  core.addColorStop(0.5, colorToRgba(visuals.torchGlow, 0.18));
5849
5843
  core.addColorStop(1, colorToRgba(visuals.torchGlow, 0));
5850
5844
  ctx.fillStyle = core;
5851
5845
  ctx.beginPath();
5852
- ctx.arc(projectedSource.x, projectedSource.y, clamp2(beamLength * 0.18, 14, 64), 0, Math.PI * 2);
5846
+ ctx.arc(projectedSource.x, projectedSource.y, clamp(beamLength * 0.18, 14, 64), 0, Math.PI * 2);
5853
5847
  ctx.fill();
5854
5848
  ctx.restore();
5855
5849
  }
@@ -5899,7 +5893,7 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
5899
5893
  }
5900
5894
  const averageDepth = projected.reduce((total, entry) => total + entry.projected.depth, 0) / projected.length;
5901
5895
  const averageWidth = projected.reduce((total, entry) => total + entry.width, 0) / projected.length;
5902
- const baseWidth = clamp2(averageWidth / Math.max(0.25, averageDepth) * 180, 1.6, 5.4);
5896
+ const baseWidth = clamp(averageWidth / Math.max(0.25, averageDepth) * 180, 1.6, 5.4);
5903
5897
  ctx.strokeStyle = `rgba(146, 194, 236, ${wake.opacity * 0.52})`;
5904
5898
  ctx.lineWidth = baseWidth * 1.9;
5905
5899
  ctx.lineCap = "round";
@@ -5945,7 +5939,7 @@ function renderWaterMotionEffects(ctx, effects, camera, viewport) {
5945
5939
  const radiusX = Math.hypot(xAxis.x - center.x, xAxis.y - center.y);
5946
5940
  const radiusY = Math.hypot(zAxis.x - center.x, zAxis.y - center.y);
5947
5941
  ctx.strokeStyle = `rgba(216, 235, 255, ${ring.opacity})`;
5948
- ctx.lineWidth = clamp2((radiusX + radiusY) * 0.02, 1, 3.1);
5942
+ ctx.lineWidth = clamp((radiusX + radiusY) * 0.02, 1, 3.1);
5949
5943
  ctx.beginPath();
5950
5944
  ctx.ellipse(center.x, center.y, radiusX, radiusY, 0, 0, Math.PI * 2);
5951
5945
  ctx.stroke();