@wave3d/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/config/model.d.ts +240 -0
- package/dist/config/model.js +496 -0
- package/dist/config/model.js.map +1 -0
- package/dist/core-loader.d.ts +10 -0
- package/dist/core-loader.js +12 -0
- package/dist/core-loader.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/presets.d.ts +8 -0
- package/dist/presets.js +544 -0
- package/dist/presets.js.map +1 -0
- package/dist/renderer/WaveGeometry.d.ts +31 -0
- package/dist/renderer/WaveGeometry.js +92 -0
- package/dist/renderer/WaveGeometry.js.map +1 -0
- package/dist/renderer/WaveRenderer.d.ts +232 -0
- package/dist/renderer/WaveRenderer.js +1118 -0
- package/dist/renderer/WaveRenderer.js.map +1 -0
- package/dist/renderer/heroPalette.d.ts +10 -0
- package/dist/renderer/heroPalette.js +34 -0
- package/dist/renderer/heroPalette.js.map +1 -0
- package/dist/renderer/index.d.ts +4 -0
- package/dist/renderer/index.js +4 -0
- package/dist/renderer/palette.d.ts +67 -0
- package/dist/renderer/palette.js +535 -0
- package/dist/renderer/palette.js.map +1 -0
- package/dist/renderer/shaders.js +545 -0
- package/dist/renderer/shaders.js.map +1 -0
- package/dist/shell/createWave.d.ts +58 -0
- package/dist/shell/createWave.js +161 -0
- package/dist/shell/createWave.js.map +1 -0
- package/dist/shell/poster.js +64 -0
- package/dist/shell/poster.js.map +1 -0
- package/dist/shell/probe.js +34 -0
- package/dist/shell/probe.js.map +1 -0
- package/dist/standalone/wave3d.standalone.js +13507 -0
- package/dist/standalone.d.ts +13 -0
- package/dist/standalone.js +17 -0
- package/dist/standalone.js.map +1 -0
- package/dist/studio/StudioWaveRenderer.d.ts +187 -0
- package/dist/studio/StudioWaveRenderer.js +905 -0
- package/dist/studio/StudioWaveRenderer.js.map +1 -0
- package/dist/studio/index.d.ts +3 -0
- package/dist/studio/index.js +3 -0
- package/dist/studio/randomize.d.ts +28 -0
- package/dist/studio/randomize.js +264 -0
- package/dist/studio/randomize.js.map +1 -0
- package/dist/util/base64.js +14 -0
- package/dist/util/base64.js.map +1 -0
- package/dist/util/math.js +18 -0
- package/dist/util/math.js.map +1 -0
- package/package.json +76 -0
- package/skills/wave3d/SKILL.md +158 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StudioWaveRenderer.js","names":[],"sources":["../../src/studio/StudioWaveRenderer.ts"],"sourcesContent":["// The studio's editor-enabled renderer: WaveRenderer + orbit/zoom/pan, the light & wave drag\n// gizmos, and the camera-rig minimap. Lives in @wave3d/core/studio so the drop-in shell and the\n// standalone build (which never edit) don't pay for ~1,000 lines of editor code. Every member here\n// moved verbatim out of WaveRenderer; the base exposes exactly five protected hook points the\n// overrides below plug into.\nimport * as THREE from \"three\";\nimport type { OrbitControls } from \"three/addons/controls/OrbitControls.js\";\nimport type { TransformControls } from \"three/addons/controls/TransformControls.js\";\nimport { WaveRenderer, FRAME_W, FRAME_H, hexToLinearVec3 } from \"../renderer/WaveRenderer\";\nimport { createLight, DEFAULT_LIGHT_POSITION, MAX_LIGHTS } from \"../config/model\";\nimport type { LightConfig } from \"../config/model\";\nimport { roundTo } from \"../util/math\";\n\n// The minimap's fixed 3/4 vantage direction.\nconst MINIMAP_VANTAGE = new THREE.Vector3(0.85, 0.6, 1).normalize();\n\nexport class StudioWaveRenderer extends WaveRenderer {\n /** Set while the panel drives the camera, so orbit's 'change' doesn't re-refresh the\n * panel mid-drag (the panel already knows the new value). */\n private suppressCameraChange = false;\n\n // Same for the minimap, which renders every frame while the camera rig is open.\n private readonly miniBox = new THREE.Box3();\n private readonly miniSphere = new THREE.Sphere();\n private readonly miniTmpA = new THREE.Vector3();\n private readonly miniTmpB = new THREE.Vector3();\n private readonly miniPrevColor = new THREE.Color();\n private readonly miniSize = new THREE.Vector2();\n // Per-wave blend/transparent stash for the minimap's forced-opaque draw (index-parallel to waves).\n private readonly miniBlendPrev: THREE.Blending[] = [];\n private readonly miniTransPrev: boolean[] = [];\n\n // --- Camera-rig minimap (corner inset: the wave + a little camera/light marker) ---\n private cameraRigOn = false;\n private cameraRigCollapsed = false;\n private minimapCamera?: THREE.PerspectiveCamera;\n private camMarker?: THREE.Group;\n /** Gold markers in the minimap, one per rig light (positions/colours tracked live). */\n private minimapLights: THREE.Mesh[] = [];\n /** Shown in the rig when no light has been added yet, so the light is always visible there.\n * Matches where \"drag in 3D\" creates the first light, so the marker doesn't jump when added. */\n private readonly defaultRigLight: LightConfig = createLight({ ...DEFAULT_LIGHT_POSITION }, 1);\n private minimapBtn?: HTMLButtonElement;\n\n // --- Camera controls (orbit/zoom/pan) + light-editing gizmo ---\n private readonly overlay = new THREE.Scene();\n private readonly raycaster = new THREE.Raycaster();\n private orbit?: OrbitControls;\n private transform?: TransformControls;\n /** Whether the main view orbit/zoom/pan is on (studio); off for the embed. */\n private mainOrbitOn = false;\n private lightHelpers: THREE.Mesh[] = [];\n /** Which 3D-editing gizmo is active: none, dragging lights, or dragging the wave/waves. */\n private editMode: \"none\" | \"light\" | \"wave\" = \"none\";\n private selectedLight = 0;\n /** Wave/wave drag handles: index 0 = the whole-wave box (moves config.position); 1..N =\n * per-wave spheres (move each layer's offset), shown only when there's >1 wave. */\n private waveHelpers: THREE.Mesh[] = [];\n private selectedWave = 0;\n /** Gizmo operation: \"translate\" moves the handle, \"rotate\" spins the whole wave. */\n private gizmoMode: \"translate\" | \"rotate\" = \"translate\";\n /** Active free screen-plane drag of a handle (grab anywhere on the marker, camera locked). */\n private dragState?: { helper: THREE.Mesh; offset: THREE.Vector3 };\n private readonly dragPlane = new THREE.Plane();\n /** Active left-drag camera pan in edit mode (the press missed every handle → move the view). */\n private panState?: { lastNdc: THREE.Vector2 };\n /** Camera snapshot taken when entering a 3D-edit mode and restored verbatim on exit, so the\n * view returns exactly where it was (position + ortho zoom + up) rather than snapping to the\n * authored hero framing. */\n private returnCamera: {\n pos: THREE.Vector3;\n target: THREE.Vector3;\n zoom: number;\n up: THREE.Vector3;\n } | null = null;\n\n /** Set by the panel: fired after a gizmo drag/selection so sliders can refresh. */\n onLightsChanged?: (selected: number) => void;\n /** Set by the panel: fired after orbit moves the camera so sliders can refresh. */\n onCameraChanged?: () => void;\n /** Set by the panel: fired after a wave/wave gizmo drag/selection so the position and\n * per-wave offset sliders can refresh. */\n onWaveChanged?: () => void;\n\n // ---------------- 3D editing (draggable gizmo: lights or wave/waves) ----------------\n\n /** True while any drag-in-3D gizmo owns the camera (light or wave). */\n private get editing(): boolean {\n return this.editMode !== \"none\";\n }\n\n isLightEditMode(): boolean {\n return this.editMode === \"light\";\n }\n\n isWaveEditMode(): boolean {\n return this.editMode === \"wave\";\n }\n\n /** Toggle 3D light editing: show draggable light handles; off restores the prior view. */\n async setLightEditMode(on: boolean): Promise<void> {\n await this.setEditMode(on ? \"light\" : \"none\");\n }\n\n /** Toggle 3D wave/wave editing: drag the whole wave (and each wave when there's >1). */\n async setWaveEditMode(on: boolean): Promise<void> {\n await this.setEditMode(on ? \"wave\" : \"none\");\n }\n\n /** Enter/leave/switch a 3D-edit mode. Modes are mutually exclusive — turning one on turns\n * the other off. The camera is snapshotted on the first entry and restored on the final exit\n * (so light↔wave switches keep the same return view). */\n private async setEditMode(mode: \"none\" | \"light\" | \"wave\"): Promise<void> {\n if (mode === this.editMode) return;\n const prev = this.editMode;\n // Tear down the previous mode's handles + gizmo.\n if (prev !== \"none\") {\n if (this.transform) this.transform.enabled = false;\n this.transform?.detach();\n if (prev === \"light\") this.clearLightHelpers();\n else this.clearWaveHelpers();\n }\n this.editMode = mode;\n // Leaving LIGHT mode undoes its transient 3/4 framing (back to the pre-edit camera). Wave\n // editing never moves the camera, so there's nothing to restore when leaving it.\n if (prev === \"light\") this.restoreReturnCamera();\n if (mode === \"none\") {\n if (this.orbit) this.orbit.enabled = this.mainOrbitOn; // keep main-view orbit on\n this.setOrbitForEdit(false); // restore left-drag camera pan\n this.dragState = undefined;\n this.renderOnce();\n return;\n }\n await this.ensureGizmo();\n if (this.editMode !== mode) return; // toggled away while controls lazy-loaded\n if (this.orbit) this.orbit.enabled = true;\n this.setOrbitForEdit(true); // left-drag on a handle moves it; on empty space it pans\n this.gizmoMode = \"translate\"; // each mode entry starts in move mode (rotate is wave-only)\n this.transform?.setMode(\"translate\");\n this.transform?.setSpace(\"world\"); // reset to world-space translate on every entry\n if (this.transform) this.transform.enabled = true;\n if (mode === \"light\") {\n // Light editing reframes to a 3/4 working angle, so snapshot the current view first and\n // restore it on exit (the framing is transient, not part of the authored composition).\n this.captureReturnCamera();\n this.syncLightHelpers();\n this.frameEditCamera();\n this.selectLight(Math.min(this.selectedLight, Math.max(0, this.lightHelpers.length - 1)));\n } else {\n // Wave editing leaves the camera exactly where it is — no reframing, no zoom — so the view\n // stays put on enter AND exit; you pan/rotate/zoom normally to reach a handle and drag it.\n this.syncWaveHelpers();\n this.selectWaveHandle(Math.min(this.selectedWave, Math.max(0, this.waveHelpers.length - 1)));\n }\n this.renderOnce();\n }\n\n /** In edit mode, take PAN off OrbitControls' left button so left-drag is free to grab handles;\n * onPointerDown/Move pan the camera manually when a left-press misses every handle (so panning\n * never fights the object drag). Right-drag still rotates the camera; scroll/middle zooms. */\n private setOrbitForEdit(editing: boolean): void {\n if (!this.orbit) return;\n this.orbit.mouseButtons = editing\n ? { MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.ROTATE }\n : { LEFT: THREE.MOUSE.PAN, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.ROTATE };\n }\n\n /** Switch the wave-edit gizmo between moving handles and rotating the whole wave. Rotate\n * targets the whole-wave box (config.rotation), so selecting it makes the intent obvious. */\n setGizmoMode(mode: \"translate\" | \"rotate\"): void {\n this.gizmoMode = mode;\n this.transform?.setMode(mode);\n // Rotate in LOCAL space so the gizmo rings reorient with the wave — a visual read-out of its\n // current rotation; translate stays in world space so the arrows track the world axes.\n this.transform?.setSpace(mode === \"rotate\" ? \"local\" : \"world\");\n if (mode === \"rotate\" && this.editMode === \"wave\") {\n const waveIdx = this.waveHelpers.findIndex((h) => h.userData.kind === \"wave\");\n if (waveIdx >= 0) this.selectWaveHandle(waveIdx);\n }\n if (!this.running) this.renderOnce();\n }\n\n getGizmoMode(): \"translate\" | \"rotate\" {\n return this.gizmoMode;\n }\n\n /** Snapshot the live camera so leaving edit mode returns exactly here (incl. ortho zoom). */\n private captureReturnCamera(): void {\n this.returnCamera = {\n pos: this.camera.position.clone(),\n target: this.orbit\n ? this.orbit.target.clone()\n : this.camera.getWorldDirection(new THREE.Vector3()).add(this.camera.position),\n zoom: this.camera.zoom,\n up: this.camera.up.clone(),\n };\n }\n\n /** Restore the snapshot from captureReturnCamera (falls back to the authored hero camera). */\n private restoreReturnCamera(): void {\n const s = this.returnCamera;\n this.returnCamera = null;\n if (!s) {\n this.restoreHeroCamera();\n return;\n }\n this.camera.position.copy(s.pos);\n this.camera.up.copy(s.up);\n this.camera.zoom = s.zoom;\n this.camera.updateProjectionMatrix();\n if (this.orbit) {\n this.orbit.target.copy(s.target);\n this.orbit.update(); // fires onControlsChange → writes the restored view back to config\n } else {\n this.camera.lookAt(s.target);\n }\n }\n\n /** Turn on mouse/trackpad orbit + zoom + pan + arrow-key orbit (studio only). */\n async enableOrbit(): Promise<void> {\n this.mainOrbitOn = true;\n this.renderer.domElement.style.cursor = \"move\"; // 4-way move arrows: left-drag pans the view\n window.addEventListener(\"keydown\", this.onKeyDown);\n await this.ensureOrbit();\n if (this.orbit && !this.editing) this.orbit.enabled = true;\n }\n\n /** Arrow keys orbit the camera around the target (←/→ azimuth, ↑/↓ elevation). */\n private onKeyDown = (e: KeyboardEvent): void => {\n if (!this.mainOrbitOn || !this.orbit || this.editing) return;\n const t = e.target instanceof HTMLElement ? e.target : null;\n if (t && (t.closest(\"#panel\") || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))) return; // let the panel keep arrows\n const step = e.shiftKey ? 0.015 : 0.05;\n let az = 0;\n let pol = 0;\n if (e.key === \"ArrowLeft\") az = -step;\n else if (e.key === \"ArrowRight\") az = step;\n else if (e.key === \"ArrowUp\") pol = -step;\n else if (e.key === \"ArrowDown\") pol = step;\n else return;\n e.preventDefault();\n const offset = this.camera.position.clone().sub(this.orbit.target);\n const sph = new THREE.Spherical().setFromVector3(offset);\n sph.theta += az;\n sph.phi = THREE.MathUtils.clamp(sph.phi + pol, 0.05, Math.PI - 0.05);\n offset.setFromSpherical(sph);\n this.camera.position.copy(this.orbit.target).add(offset);\n this.orbit.update(); // fires 'change' → writes camera to config + renders\n };\n\n /** Reset the camera to the straight-on hero framing at the configured distance. */\n resetView(): void {\n this.camera.position.copy(this.homeCamPos);\n this.camera.up.set(0, 1, 0);\n this.camera.lookAt(this.homeCamTarget);\n if (this.orbit) {\n this.orbit.target.copy(this.homeCamTarget);\n this.orbit.update();\n }\n this.writeCameraToConfig();\n this.onCameraChanged?.();\n if (!this.running) this.renderOnce();\n }\n\n /** Dolly/aim the camera so the whole wave fills the viewport (keeps the view angle).\n * Fits the geometry box's actual *projected* screen extent — tighter than a bounding\n * sphere for a flat, diagonal ribbon. */\n fitToView(): void {\n const box = new THREE.Box3();\n for (const s of this.waves) {\n s.mesh.updateWorldMatrix(true, false);\n if (!s.mesh.geometry.boundingBox) s.mesh.geometry.computeBoundingBox();\n const bb = s.mesh.geometry.boundingBox;\n if (bb) box.union(bb.clone().applyMatrix4(s.mesh.matrixWorld));\n }\n if (box.isEmpty()) return;\n\n const center = box.getCenter(new THREE.Vector3());\n\n // Aim at the centre, then measure how much of the viewport the box spans (in NDC).\n if (this.orbit) this.orbit.target.copy(center);\n this.camera.up.set(0, 1, 0);\n this.camera.lookAt(center);\n this.camera.updateMatrixWorld(true);\n const c = box.min,\n m = box.max;\n let frac = 0;\n const v = new THREE.Vector3();\n for (let i = 0; i < 8; i++) {\n v.set(i & 1 ? m.x : c.x, i & 2 ? m.y : c.y, i & 4 ? m.z : c.z).project(this.camera);\n frac = Math.max(frac, Math.abs(v.x), Math.abs(v.y)); // |ndc| 0→1 = half-viewport\n }\n // Overfill slightly (>1): the folded geometry's bounding box has empty diagonal\n // corners, so filling past the box edges lets the actual ribbon fill the frame.\n // Ortho: framing is the zoom, not the distance.\n const target = 1.18;\n this.config.cameraZoom = ((this.config.cameraZoom ?? 1) * target) / Math.max(0.001, frac);\n this.applyZoom();\n if (this.orbit) this.orbit.update();\n this.writeCameraToConfig();\n this.onCameraChanged?.();\n if (!this.running) this.renderOnce();\n }\n\n /** Dolly the camera to a distance from the orbit target (or set z when no orbit). */\n setCameraDistance(d: number): void {\n if (this.orbit) {\n const dir = this.camera.position.clone().sub(this.orbit.target);\n const len = dir.length() || 1;\n this.camera.position.copy(this.orbit.target).addScaledVector(dir.multiplyScalar(1 / len), d);\n this.orbit.update();\n } else {\n this.camera.position.z = d;\n }\n this.writeCameraToConfig();\n if (!this.running) this.renderOnce();\n }\n\n /** The current look-at target (orbit's if present, else from config). */\n private camTarget(): THREE.Vector3 {\n if (this.orbit) return this.orbit.target;\n const t = this.config.cameraTarget;\n return new THREE.Vector3(t.x, t.y, t.z);\n }\n\n /** Read the camera as orbit values for the panel (angles in degrees). */\n getCameraOrbit(): {\n azimuth: number;\n elevation: number;\n distance: number;\n panX: number;\n panY: number;\n } {\n const t = this.camTarget();\n const sph = new THREE.Spherical().setFromVector3(this.camera.position.clone().sub(t));\n return {\n azimuth: THREE.MathUtils.radToDeg(sph.theta),\n elevation: 90 - THREE.MathUtils.radToDeg(sph.phi),\n distance: sph.radius,\n panX: t.x,\n panY: t.y,\n };\n }\n\n /** Place the camera at azimuth/elevation (degrees) + distance around the target. */\n setCameraOrbit(azimuthDeg: number, elevationDeg: number, distance: number): void {\n const target = this.camTarget();\n const sph = new THREE.Spherical(\n Math.max(0.01, distance),\n THREE.MathUtils.degToRad(90 - elevationDeg),\n THREE.MathUtils.degToRad(azimuthDeg),\n );\n sph.makeSafe();\n this.suppressCameraChange = true;\n this.camera.position.copy(target).add(new THREE.Vector3().setFromSpherical(sph));\n this.camera.up.set(0, 1, 0);\n this.camera.lookAt(target);\n if (this.orbit) this.orbit.update();\n this.suppressCameraChange = false;\n this.writeCameraToConfig();\n if (!this.running) this.renderOnce();\n }\n\n /** Roll the camera around its view axis (degrees) — tilts the composition without\n * moving the camera. Applied after positioning; reset by any orbit interaction. */\n rollView(deg: number): void {\n this.camera.rotateZ(THREE.MathUtils.degToRad(deg));\n this.camera.updateMatrixWorld();\n if (!this.running) this.renderOnce();\n }\n\n /** Pan: move the look-at target (and camera with it) to (x, y) in world units. */\n setCameraTarget(x: number, y: number): void {\n const target = this.camTarget();\n const delta = new THREE.Vector3(x - target.x, y - target.y, 0);\n this.suppressCameraChange = true;\n this.camera.position.add(delta);\n if (this.orbit) this.orbit.target.add(delta);\n else this.config.cameraTarget = { x, y, z: target.z };\n this.camera.lookAt(this.camTarget());\n if (this.orbit) this.orbit.update();\n this.suppressCameraChange = false;\n this.writeCameraToConfig();\n if (!this.running) this.renderOnce();\n }\n\n /** Ortho zoom MULTIPLIER (the camera has no real fov). 1 = the responsive base framing (the hero crop). */\n getZoom(): number {\n return this.config.cameraZoom ?? 1;\n }\n\n setZoom(zoom: number): void {\n this.config.cameraZoom = THREE.MathUtils.clamp(zoom, 0.1, 6);\n this.applyZoom();\n if (!this.running) this.renderOnce();\n }\n\n duplicateOffset(): { x: number; y: number; z: number } {\n this.camera.updateMatrixWorld();\n const worldW = (this.camera.right - this.camera.left) / this.camera.zoom; // visible world span\n const worldH = (this.camera.top - this.camera.bottom) / this.camera.zoom;\n const right = new THREE.Vector3().setFromMatrixColumn(this.camera.matrixWorld, 0).normalize();\n const up = new THREE.Vector3().setFromMatrixColumn(this.camera.matrixWorld, 1).normalize();\n // Screen-left ~40% + screen-down ~15% of the frame: enough to clearly separate the copy, small\n // enough that a few successive adds cascade diagonally and stay on-screen before running off.\n const off = right.multiplyScalar(-0.4 * worldW).add(up.multiplyScalar(-0.15 * worldH));\n return { x: off.x, y: off.y, z: 0 };\n }\n\n /** Toggle the corner camera-rig minimap (studio aid; off in the embed). */\n setCameraRig(on: boolean): void {\n this.cameraRigOn = on;\n if (on) this.ensureMinimap();\n if (this.minimapBtn) this.minimapBtn.style.display = on ? \"\" : \"none\";\n if (on) this.positionMinimapBtn();\n if (!this.running) this.renderOnce();\n }\n\n /** Corner rectangle (logical px) for the minimap viewport. */\n private minimapRect(): { x: number; y: number; size: number; pad: number } {\n const sz = this.renderer.getSize(new THREE.Vector2());\n const size = Math.round(Math.min(sz.x, sz.y) * 0.27);\n const pad = Math.round(size * 0.06);\n return { x: sz.x - size - pad, y: pad, size, pad };\n }\n\n /** Place the collapse button at the minimap's top-right (or bottom corner when collapsed). */\n private positionMinimapBtn(): void {\n const b = this.minimapBtn;\n if (!b) return;\n const { size, pad } = this.minimapRect();\n // minimapRect() is in renderer BUFFER px (= the export size); the canvas is CSS-scaled to\n // fill the container, so convert to on-screen px or the button detaches from the minimap.\n const canvas = this.renderer.domElement;\n const buf = this.renderer.getSize(new THREE.Vector2());\n const sx = buf.x > 0 ? canvas.clientWidth / buf.x : 1;\n const sy = buf.y > 0 ? canvas.clientHeight / buf.y : 1;\n b.style.right = pad * sx + \"px\";\n b.style.bottom = (this.cameraRigCollapsed ? pad * sy : (pad + size) * sy - 22) + \"px\";\n b.textContent = this.cameraRigCollapsed ? \"▴ camera\" : \"▾ camera\";\n }\n\n /** Build the minimap's fixed 3rd-person camera + the camera/light markers (once). */\n private ensureMinimap(): void {\n if (this.minimapCamera) return;\n // Pose/near/far are recomputed every frame by frameMinimap() to fit the current scene\n // (the wave sits in a ×10 ortho world, so a fixed vantage can't frame it).\n this.minimapCamera = new THREE.PerspectiveCamera(42, 1, 1, 10000);\n\n // A little camera (body + lens) marking where the main camera views the wave from.\n const marker = new THREE.Group();\n marker.add(\n new THREE.Mesh(\n new THREE.BoxGeometry(3.4, 2.4, 4.2),\n new THREE.MeshBasicMaterial({ color: 0x2a2f3d }),\n ),\n );\n const lens = new THREE.Mesh(\n new THREE.ConeGeometry(1.3, 2.4, 18),\n new THREE.MeshBasicMaterial({ color: 0x6ea8fe }),\n );\n lens.rotation.x = -Math.PI / 2; // cone points -Z (the camera's forward)\n lens.position.z = -2.7;\n marker.add(lens);\n marker.visible = false;\n this.scene.add(marker);\n this.camMarker = marker;\n\n // Collapse/expand toggle overlaid on the minimap corner.\n const btn = document.createElement(\"button\");\n btn.style.cssText =\n \"position:absolute;z-index:30;padding:2px 8px;border-radius:5px;cursor:pointer;\" +\n \"font:11px ui-sans-serif,system-ui,-apple-system,sans-serif;color:#cdd0d6;\" +\n \"background:rgba(18,18,26,0.85);border:1px solid rgba(255,255,255,0.16);\";\n btn.addEventListener(\"click\", () => {\n this.cameraRigCollapsed = !this.cameraRigCollapsed;\n this.positionMinimapBtn();\n this.renderOnce();\n });\n this.container.appendChild(btn);\n this.minimapBtn = btn;\n this.positionMinimapBtn();\n }\n\n /** The lights the rig should show: the configured lights, or a single default-position\n * marker when none has been added yet — so the light is always visible in the rig. */\n private rigLights(): LightConfig[] {\n const lights = this.config.lights ?? [];\n return lights.length ? lights : [this.defaultRigLight];\n }\n\n /** Reconcile the minimap's light markers with the rig lights (count, position, colour). */\n private syncMinimapLights(visible: boolean): void {\n const lights = this.rigLights();\n while (this.minimapLights.length < lights.length) {\n const m = new THREE.Mesh(\n new THREE.SphereGeometry(2.2, 16, 12),\n new THREE.MeshBasicMaterial({ color: 0xffd24a }),\n );\n m.visible = false;\n this.scene.add(m);\n this.minimapLights.push(m);\n }\n while (this.minimapLights.length > lights.length) {\n const m = this.minimapLights.pop();\n if (!m) break;\n this.scene.remove(m);\n m.geometry.dispose();\n (m.material as THREE.Material).dispose();\n }\n lights.forEach((l, i) => {\n const m = this.minimapLights[i];\n m.position.set(l.position.x, l.position.y, l.position.z);\n (m.material as THREE.MeshBasicMaterial).color.set(l.color); // track the light's colour\n m.visible = visible;\n });\n }\n\n /** Fit the minimap's 3rd-person camera to the wave (+ lights) and size/place the camera\n * proxy, so the rig reads at any scene scale. The main camera is orthographic — its literal\n * distance is arbitrary — so the proxy sits a fixed multiple of the scene radius back along\n * the true view direction rather than at the far-away ortho position. */\n private frameMinimap(): void {\n const cam = this.minimapCamera;\n const marker = this.camMarker;\n if (!cam || !marker) return;\n const box = this.miniBox.setFromObject(this.group);\n if (box.isEmpty()) return; // geometry not built yet\n for (const l of this.rigLights()) {\n box.expandByPoint(this.miniTmpA.set(l.position.x, l.position.y, l.position.z));\n }\n const subject = box.getBoundingSphere(this.miniSphere);\n const radius = Math.max(subject.radius, 1);\n\n // Point the little camera at the wave from its real view direction, kept a sane distance\n // away (using the ortho camera's actual z would push it thousands of units off and dwarf\n // the wave).\n const viewDir = this.camera.getWorldDirection(this.miniTmpA); // points toward the wave\n const markerPos = this.miniTmpB.copy(subject.center).addScaledVector(viewDir, -radius * 1.5);\n marker.position.copy(markerPos);\n marker.quaternion.copy(this.camera.quaternion);\n marker.scale.setScalar(radius * 0.05);\n for (const m of this.minimapLights) m.scale.setScalar(radius * 0.045);\n\n // Frame the whole rig (wave + proxy) from a fixed 3/4 vantage. (`rig` reuses the sphere\n // behind `subject`, which has no readers past this point.)\n box.expandByPoint(markerPos);\n const rig = box.getBoundingSphere(this.miniSphere);\n const frameR = Math.max(rig.radius, 1);\n cam.position.copy(rig.center).addScaledVector(MINIMAP_VANTAGE, frameR * 2.9);\n cam.near = Math.max(1, frameR * 0.02);\n cam.far = frameR * 10;\n cam.up.set(0, 1, 0);\n cam.lookAt(rig.center);\n cam.updateProjectionMatrix();\n }\n\n /** Draw the camera-rig minimap into a corner viewport (called after the main render). */\n private renderMinimap(): void {\n if (!this.cameraRigOn || this.cameraRigCollapsed || !this.mainOrbitOn || this.capturing) return;\n if (!this.minimapCamera || !this.camMarker) return;\n // setViewport/setScissor take LOGICAL (CSS) pixels — three applies pixelRatio itself.\n const { x, y, size } = this.minimapRect();\n\n this.camMarker.visible = true;\n this.syncMinimapLights(true);\n this.frameMinimap();\n\n const r = this.renderer;\n const prevColor = this.miniPrevColor;\n r.getClearColor(prevColor);\n const prevAlpha = r.getClearAlpha();\n r.autoClear = false;\n r.setRenderTarget(null); // draw to the screen — NOT a leftover composer buffer\n r.setScissorTest(true);\n r.setViewport(x, y, size, size);\n r.setScissor(x, y, size, size);\n r.setClearColor(0x12121a, 0.92);\n r.clear(true, true);\n // scene.background (the wave's page colour / image / gradient) fills ANY camera's view, so\n // hide it while drawing the minimap — otherwise it covers the 3rd-person wave.\n const prevBg = this.scene.background;\n this.scene.background = null;\n // The wave's own blend mode (additive for the neon / Spider-Man presets) makes it vanish on\n // the dark minimap backdrop; force opaque normal blending just for this draw so the shape\n // always reads. The main render already happened, so we restore immediately after.\n for (let i = 0; i < this.waves.length; i++) {\n const m = this.waves[i].material;\n this.miniBlendPrev[i] = m.blending;\n this.miniTransPrev[i] = m.transparent;\n m.blending = THREE.NormalBlending;\n m.transparent = false;\n }\n r.render(this.scene, this.minimapCamera);\n for (let i = 0; i < this.waves.length; i++) {\n const m = this.waves[i].material;\n m.blending = this.miniBlendPrev[i];\n m.transparent = this.miniTransPrev[i];\n }\n this.scene.background = prevBg;\n r.setScissorTest(false);\n const full = this.renderer.getSize(this.miniSize);\n r.setViewport(0, 0, full.x, full.y);\n r.setClearColor(prevColor, prevAlpha);\n r.autoClear = true;\n\n this.camMarker.visible = false;\n for (const m of this.minimapLights) m.visible = false;\n }\n\n private async ensureOrbit(): Promise<void> {\n if (this.orbit) return;\n const { OrbitControls } = await import(\"three/addons/controls/OrbitControls.js\");\n if (this.orbit) return; // a concurrent call already set it up\n this.orbit = new OrbitControls(this.camera, this.renderer.domElement);\n this.orbit.enableDamping = false;\n this.orbit.enabled = false; // enabled by enableOrbit() / light-edit\n this.orbit.screenSpacePanning = true;\n this.orbit.zoomToCursor = true;\n this.orbit.minDistance = 12;\n this.orbit.maxDistance = 600;\n // Left drag PANS (moves the view around); right drag ROTATES — swapped from the\n // OrbitControls default so the primary drag moves the scene rather than orbiting it.\n this.orbit.mouseButtons = {\n LEFT: THREE.MOUSE.PAN,\n MIDDLE: THREE.MOUSE.DOLLY,\n RIGHT: THREE.MOUSE.ROTATE,\n };\n this.orbit.target.set(\n this.config.cameraTarget.x,\n this.config.cameraTarget.y,\n this.config.cameraTarget.z,\n );\n this.orbit.update();\n this.orbit.addEventListener(\"change\", this.onControlsChange);\n // Cursor feedback by drag type: left-drag pans → 4-way move arrows; right-drag rotates →\n // grab/closed-hand. Idle stays on the move arrows (the primary drag pans). OrbitControls\n // doesn't expose the button, so we read it from pointerdown directly.\n this.renderer.domElement.addEventListener(\"pointerdown\", this.onCursorDown);\n window.addEventListener(\"pointerup\", this.onCursorUp);\n }\n\n private onCursorDown = (e: PointerEvent): void => {\n if (this.mainOrbitOn) {\n this.renderer.domElement.style.cursor = e.button === 2 ? \"grabbing\" : \"move\";\n }\n };\n\n private onCursorUp = (): void => {\n if (this.mainOrbitOn) this.renderer.domElement.style.cursor = \"move\";\n };\n\n private async ensureGizmo(): Promise<void> {\n await this.ensureOrbit();\n if (this.transform) return;\n const { TransformControls } = await import(\"three/addons/controls/TransformControls.js\");\n if (this.transform) return; // a concurrent call already set it up\n this.transform = new TransformControls(this.camera, this.renderer.domElement);\n this.transform.setMode(\"translate\");\n this.transform.addEventListener(\"dragging-changed\", (e) => {\n if (this.orbit) this.orbit.enabled = !(e as unknown as { value: boolean }).value;\n });\n this.transform.addEventListener(\"objectChange\", this.onGizmoMoved);\n // (onGizmoMoved routes to the light- or wave-drag handler based on the active mode.)\n this.transform.addEventListener(\"change\", this.onControlsChange);\n const tc = this.transform as unknown as { getHelper?: () => THREE.Object3D };\n this.overlay.add(tc.getHelper ? tc.getHelper() : (this.transform as unknown as THREE.Object3D));\n\n this.renderer.domElement.addEventListener(\"pointerdown\", this.onPointerDown);\n this.renderer.domElement.addEventListener(\"pointermove\", this.onPointerMove);\n this.renderer.domElement.addEventListener(\"pointerup\", this.onPointerUp);\n this.renderer.domElement.addEventListener(\"pointercancel\", this.onPointerUp);\n }\n\n private onControlsChange = (): void => {\n // Orbit/zoom/pan moved the camera → capture it so exports match the view. Wave editing keeps\n // the live view (persist it); light editing uses a transient 3/4 working view (don't persist).\n if (\n this.orbit &&\n this.orbit.enabled &&\n this.editMode !== \"light\" &&\n !this.suppressCameraChange\n ) {\n this.writeCameraToConfig();\n this.onCameraChanged?.();\n }\n if (!this.running) this.renderOnce();\n };\n\n /** Persist the live camera (position/target/distance) into the config. */\n private writeCameraToConfig(): void {\n const p = this.camera.position;\n this.config.cameraPosition = {\n x: roundTo(p.x, 3),\n y: roundTo(p.y, 3),\n z: roundTo(p.z, 3),\n };\n // Capture the LIVE ortho zoom (mouse-scroll changes camera.zoom directly) back into\n // config.cameraZoom — the user multiplier — by inverting applyZoom's responsive COVER\n // factor. Without this, scroll-zoom changed the view but was never saved/exported, so a\n // framing tuned at a scrolled zoom didn't reproduce (its pan made sense only at that zoom).\n const cover = Math.max(\n (this.camera.right - this.camera.left) / FRAME_W,\n (this.camera.top - this.camera.bottom) / FRAME_H,\n );\n if (cover > 0) this.config.cameraZoom = roundTo(this.camera.zoom / cover, 3);\n if (this.orbit) {\n const t = this.orbit.target;\n this.config.cameraTarget = {\n x: roundTo(t.x, 3),\n y: roundTo(t.y, 3),\n z: roundTo(t.z, 3),\n };\n this.config.cameraDistance = roundTo(p.distanceTo(this.orbit.target), 3);\n }\n }\n\n /** Pointer position in normalized device coords (-1..1), from a canvas-relative event. */\n private pointerNdc(ev: PointerEvent): THREE.Vector2 {\n const rect = this.renderer.domElement.getBoundingClientRect();\n return new THREE.Vector2(\n ((ev.clientX - rect.left) / rect.width) * 2 - 1,\n -((ev.clientY - rect.top) / rect.height) * 2 + 1,\n );\n }\n\n private onPointerDown = (ev: PointerEvent): void => {\n if (!this.editing || !this.transform) return;\n if (ev.button !== 0) return; // only left-drag moves objects; right-drag rotates the camera\n if (this.transform.dragging || this.transform.axis) return; // on a gizmo handle → let it move\n this.raycaster.setFromCamera(this.pointerNdc(ev), this.camera);\n const helpers = this.editMode === \"wave\" ? this.waveHelpers : this.lightHelpers;\n const hit = this.raycaster.intersectObjects(helpers, false)[0];\n if (!hit) {\n // Missed every handle → pan the view (the tool's normal left-drag). OrbitControls' LEFT is\n // unmapped in edit mode, so onPointerMove pans manually without fighting the object drag.\n if (this.orbit) {\n this.panState = { lastNdc: this.pointerNdc(ev) };\n this.renderer.domElement.setPointerCapture?.(ev.pointerId);\n }\n return;\n }\n const idx = helpers.indexOf(hit.object as THREE.Mesh);\n if (idx < 0) return;\n if (this.editMode === \"wave\") this.selectWaveHandle(idx);\n else this.selectLight(idx);\n // Free screen-plane drag: the WHOLE marker is grabbable (not just the thin gizmo arrows) and\n // the camera stays locked. Rotate mode uses the gizmo's rings instead, so skip it there.\n if (this.gizmoMode !== \"translate\") return;\n const helper = helpers[idx];\n const normal = this.camera.getWorldDirection(new THREE.Vector3());\n this.dragPlane.setFromNormalAndCoplanarPoint(normal, helper.position);\n const grab = new THREE.Vector3();\n if (!this.raycaster.ray.intersectPlane(this.dragPlane, grab)) return;\n this.dragState = { helper, offset: helper.position.clone().sub(grab) };\n if (this.orbit) this.orbit.enabled = false; // lock the camera for the whole drag\n this.renderer.domElement.setPointerCapture?.(ev.pointerId);\n };\n\n private onPointerMove = (ev: PointerEvent): void => {\n if (this.panState) {\n // Ortho pan: unproject the pointer delta into world units (auto-handles zoom/aspect/dpr),\n // then shift camera + orbit target together so the grabbed point tracks the cursor.\n const ndc = this.pointerNdc(ev);\n const before = new THREE.Vector3(this.panState.lastNdc.x, this.panState.lastNdc.y, 0);\n const after = new THREE.Vector3(ndc.x, ndc.y, 0);\n const delta = before.unproject(this.camera).sub(after.unproject(this.camera)); // opposite the cursor\n this.camera.position.add(delta);\n this.panState.lastNdc = ndc;\n if (this.orbit) {\n this.orbit.target.add(delta);\n this.orbit.update(); // fires 'change' → persists the new framing + renders\n } else {\n this.camera.updateProjectionMatrix();\n if (!this.running) this.renderOnce();\n }\n return;\n }\n if (!this.dragState) return;\n this.raycaster.setFromCamera(this.pointerNdc(ev), this.camera);\n const p = new THREE.Vector3();\n if (!this.raycaster.ray.intersectPlane(this.dragPlane, p)) return;\n this.dragState.helper.position.copy(p.add(this.dragState.offset));\n this.onGizmoMoved(); // write the new position into config + uniforms\n if (!this.running) this.renderOnce();\n };\n\n private onPointerUp = (ev: PointerEvent): void => {\n if (this.panState) {\n this.panState = undefined;\n this.renderer.domElement.releasePointerCapture?.(ev.pointerId);\n return;\n }\n if (!this.dragState) return;\n this.dragState = undefined;\n if (this.orbit) this.orbit.enabled = true; // still editing → keep right-drag camera rotate\n this.renderer.domElement.releasePointerCapture?.(ev.pointerId);\n };\n\n private selectLight(i: number): void {\n this.selectedLight = i;\n const h = this.lightHelpers[i];\n if (h && this.transform) this.transform.attach(h);\n else this.transform?.detach();\n this.onLightsChanged?.(i);\n if (!this.running) this.renderOnce();\n }\n\n private selectWaveHandle(i: number): void {\n this.selectedWave = i;\n const h = this.waveHelpers[i];\n if (h && this.transform) this.transform.attach(h);\n else this.transform?.detach();\n this.onWaveChanged?.();\n if (!this.running) this.renderOnce();\n }\n\n /** Gizmo drag → route to the active mode's writer. */\n private onGizmoMoved = (): void => {\n if (this.editMode === \"wave\") this.onWaveGizmoMoved();\n else this.onLightGizmoMoved();\n };\n\n /** Light gizmo drag → write the moved handle back into the config + uniforms. */\n private onLightGizmoMoved(): void {\n const h = this.lightHelpers[this.selectedLight];\n const light = this.config.lights?.[this.selectedLight];\n if (!h || !light) return;\n light.position.x = roundTo(h.position.x, 2);\n light.position.y = roundTo(h.position.y, 2);\n light.position.z = roundTo(h.position.z, 2);\n this.pushLightUniforms();\n this.onLightsChanged?.(this.selectedLight);\n }\n\n /** Wave gizmo drag → the whole-wave box writes config.position (and the wave handles\n * follow it); a per-wave sphere writes that layer's offset (relative to config.position). */\n private onWaveGizmoMoved(): void {\n const h = this.waveHelpers[this.selectedWave];\n if (!h) return;\n const wave = this.config.waves[h.userData.index as number];\n if (!wave) return;\n // Mutate the wave's vectors IN PLACE (don't reassign a new object): the panel's Transform\n // sliders hold a reference to these objects, so replacing them would leave the sliders\n // reading the stale old object even though the wave moved.\n if (this.gizmoMode === \"rotate\") {\n wave.rotation.x = roundTo(THREE.MathUtils.radToDeg(h.rotation.x), 2);\n wave.rotation.y = roundTo(THREE.MathUtils.radToDeg(h.rotation.y), 2);\n wave.rotation.z = roundTo(THREE.MathUtils.radToDeg(h.rotation.z), 2);\n } else {\n wave.position.x = roundTo(h.position.x, 2);\n wave.position.y = roundTo(h.position.y, 2);\n wave.position.z = roundTo(h.position.z, 2);\n }\n this.pushWaveTransforms();\n this.onWaveChanged?.();\n }\n\n /** Reconcile the helper spheres with config.lights (count, position, colour). */\n private syncLightHelpers(): void {\n const lights = this.config.lights ?? [];\n while (this.lightHelpers.length < lights.length) {\n const mesh = new THREE.Mesh(\n new THREE.SphereGeometry(0.28, 16, 12),\n new THREE.MeshBasicMaterial({ color: 0xffffff, depthTest: false, transparent: true }),\n );\n mesh.renderOrder = 999;\n this.overlay.add(mesh);\n this.lightHelpers.push(mesh);\n }\n while (this.lightHelpers.length > lights.length) {\n const mesh = this.lightHelpers.pop();\n if (!mesh) break;\n this.overlay.remove(mesh);\n mesh.geometry.dispose();\n (mesh.material as THREE.Material).dispose();\n }\n lights.forEach((l, i) => {\n const h = this.lightHelpers[i];\n h.position.set(l.position.x, l.position.y, l.position.z);\n (h.material as THREE.MeshBasicMaterial).color.set(l.color);\n });\n if (this.selectedLight >= this.lightHelpers.length) {\n this.selectedLight = Math.max(0, this.lightHelpers.length - 1);\n }\n const sel = this.lightHelpers[this.selectedLight];\n if (sel && this.transform && this.transform.object !== sel) this.transform.attach(sel);\n if (!sel) this.transform?.detach();\n }\n\n private clearLightHelpers(): void {\n for (const mesh of this.lightHelpers) {\n this.overlay.remove(mesh);\n mesh.geometry.dispose();\n (mesh.material as THREE.Material).dispose();\n }\n this.lightHelpers = [];\n }\n\n /** Reconcile the wave drag handles with config: one box handle per wave, sitting at that\n * wave's absolute position (and oriented to its rotation so the rotate gizmo starts there). */\n private syncWaveHelpers(): void {\n if (this.transform?.dragging) return; // don't yank a handle out from under an active drag\n const waves = this.config.waves ?? [];\n const wantTotal = waves.length;\n if (this.waveHelpers.length !== wantTotal) {\n this.clearWaveHelpers();\n for (let i = 0; i < wantTotal; i++) {\n const box = new THREE.Mesh(\n new THREE.BoxGeometry(0.55, 0.55, 0.55),\n new THREE.MeshBasicMaterial({ color: 0x39d0ff, depthTest: false, transparent: true }),\n );\n box.renderOrder = 999;\n box.userData = { kind: \"wave\", index: i };\n this.overlay.add(box);\n this.waveHelpers.push(box);\n }\n }\n for (const h of this.waveHelpers) {\n const sc = waves[h.userData.index as number];\n if (!sc) continue;\n h.position.set(sc.position.x, sc.position.y, sc.position.z);\n h.rotation.set(\n THREE.MathUtils.degToRad(sc.rotation.x),\n THREE.MathUtils.degToRad(sc.rotation.y),\n THREE.MathUtils.degToRad(sc.rotation.z),\n );\n }\n if (this.selectedWave >= this.waveHelpers.length) this.selectedWave = 0;\n const sel = this.waveHelpers[this.selectedWave];\n if (sel && this.transform && this.transform.object !== sel) this.transform.attach(sel);\n if (!sel) this.transform?.detach();\n }\n\n private clearWaveHelpers(): void {\n for (const mesh of this.waveHelpers) {\n this.overlay.remove(mesh);\n mesh.geometry.dispose();\n (mesh.material as THREE.Material).dispose();\n }\n this.waveHelpers = [];\n }\n\n /** Reposition just the wave MESHES from each wave's absolute transform — the transform\n * subset of refresh(), used live during a wave gizmo drag so the ribbon follows without a\n * full uniform re-push or a helper resync. */\n private pushWaveTransforms(): void {\n this.waves.forEach((wave, i) => {\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n wave.mesh.scale.set(sc.scale.x, sc.scale.y, sc.scale.z);\n wave.mesh.rotation.set(\n THREE.MathUtils.degToRad(sc.rotation.x),\n THREE.MathUtils.degToRad(sc.rotation.y),\n THREE.MathUtils.degToRad(sc.rotation.z),\n );\n wave.mesh.position.set(sc.position.x, sc.position.y, sc.position.z);\n });\n if (!this.running) this.renderOnce();\n }\n\n /** Pull the camera back to a 3/4 angle that frames the edit target: the origin wave + all\n * lights (light mode), or a region around config.position + the handles (wave mode). */\n private frameEditCamera(): void {\n const box = new THREE.Box3();\n if (this.editMode === \"wave\") {\n // The wave can sit far from the origin (some presets push position to the hundreds), so\n // frame around it — a ~200-unit margin shows a good chunk of the ×10-scaled ribbon.\n const target = this.config.waves[this.selectedWave] ?? this.config.waves[0];\n const c = new THREE.Vector3(target.position.x, target.position.y, target.position.z);\n box.expandByPoint(c.clone().addScalar(200));\n box.expandByPoint(c.clone().addScalar(-200));\n for (const h of this.waveHelpers) box.expandByPoint(h.position);\n } else {\n // The baked + scaled wave spans ~±25 units; frame that plus any lights.\n box.expandByPoint(new THREE.Vector3(25, 25, 25));\n box.expandByPoint(new THREE.Vector3(-25, -25, -25));\n for (const l of this.config.lights ?? []) {\n box.expandByPoint(new THREE.Vector3(l.position.x, l.position.y, l.position.z));\n }\n }\n const sphere = box.getBoundingSphere(new THREE.Sphere());\n const radius = Math.max(sphere.radius, 2);\n const dir = new THREE.Vector3(0.45, 0.35, 1).normalize();\n this.camera.position.copy(sphere.center).addScaledVector(dir, radius * 3 + 200);\n this.camera.up.set(0, 1, 0);\n this.camera.lookAt(sphere.center);\n // Ortho: frame by zoom (frustum is in px), not distance.\n this.camera.zoom = (this.camera.right - this.camera.left) / Math.max(1, radius * 2.6);\n this.camera.updateProjectionMatrix();\n if (this.orbit) {\n this.orbit.target.copy(sphere.center);\n this.orbit.update();\n }\n }\n\n /** Restore the authored camera (from config) — used when leaving light-edit. */\n private restoreHeroCamera(): void {\n const p = this.config.cameraPosition;\n const t = this.config.cameraTarget;\n this.camera.position.set(p.x, p.y, p.z);\n this.camera.up.set(0, 1, 0);\n this.camera.lookAt(t.x, t.y, t.z);\n if (this.orbit) {\n this.orbit.target.set(t.x, t.y, t.z);\n this.orbit.update();\n }\n }\n\n /** Push only the light uniforms (used live during a gizmo drag). */\n private pushLightUniforms(): void {\n const lights = this.config.lights ?? [];\n for (const wave of this.waves) {\n const u = wave.material.uniforms;\n u.uNumLights.value = Math.min(lights.length, MAX_LIGHTS);\n const lPos = u.uLightPos.value as THREE.Vector3[];\n const lCol = u.uLightColor.value as THREE.Vector3[];\n const lInt = u.uLightIntensity.value as number[];\n for (let li = 0; li < MAX_LIGHTS; li++) {\n const light = lights[li];\n if (light) {\n lPos[li].set(light.position.x, light.position.y, light.position.z);\n hexToLinearVec3(light.color, lCol[li]);\n lInt[li] = light.intensity;\n } else {\n lInt[li] = 0;\n }\n }\n }\n if (!this.running) this.renderOnce();\n }\n\n // ---- Hook overrides: plug the editor behavior into the base render pipeline (5 hook points) ----\n\n protected override isCameraExternallyDriven(): boolean {\n return !!this.orbit || this.editing;\n }\n\n protected override onAfterRefresh(): void {\n if (this.editMode === \"light\") this.syncLightHelpers();\n else if (this.editMode === \"wave\") this.syncWaveHelpers();\n }\n\n protected override onAfterRenderFrame(): void {\n // Draw the light/wave gizmo helpers on top, crisp (not through the post pass), and never into\n // exports. Guarded on `overlay` because the base constructor renders a first frame before this\n // subclass's own fields are initialized (base-ctor-calls-virtual-hook order).\n if (this.overlay && this.editing && !this.capturing && this.overlay.children.length > 0) {\n const helpers = this.editMode === \"wave\" ? this.waveHelpers : this.lightHelpers;\n for (const h of helpers) {\n h.scale.setScalar(Math.max(0.1, this.camera.position.distanceTo(h.position) * 0.09));\n }\n this.renderer.autoClear = false;\n this.renderer.setRenderTarget(null); // draw to the screen, not a leftover composer buffer\n this.renderer.render(this.overlay, this.camera);\n this.renderer.autoClear = true;\n }\n this.renderMinimap();\n }\n\n protected override onAfterResize(): void {\n if (this.cameraRigOn) this.positionMinimapBtn();\n }\n\n protected override applyCameraFromConfig(): void {\n if (this.editing) return; // a 3D-edit gizmo owns the camera; don't fight it\n const p = this.config.cameraPosition;\n const tg = this.config.cameraTarget;\n this.suppressCameraChange = true;\n this.camera.position.set(p.x, p.y, p.z);\n this.camera.up.set(0, 1, 0);\n this.camera.lookAt(tg.x, tg.y, tg.z);\n if (this.orbit) {\n this.orbit.target.set(tg.x, tg.y, tg.z);\n this.orbit.update();\n }\n this.applyZoom();\n this.suppressCameraChange = false;\n this.onCameraChanged?.(); // keep the panel's camera sliders in sync\n if (!this.running) this.renderOnce();\n }\n\n override dispose(): void {\n window.removeEventListener(\"keydown\", this.onKeyDown);\n this.renderer.domElement.removeEventListener(\"pointerdown\", this.onPointerDown);\n this.renderer.domElement.removeEventListener(\"pointerdown\", this.onCursorDown);\n window.removeEventListener(\"pointerup\", this.onCursorUp);\n this.transform?.detach();\n this.transform?.dispose();\n this.orbit?.dispose();\n this.minimapBtn?.remove();\n this.clearLightHelpers();\n this.clearWaveHelpers();\n for (const m of this.minimapLights) {\n m.geometry.dispose();\n (m.material as THREE.Material).dispose();\n }\n super.dispose();\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { randomizeBackground, randomizeColor, randomizeConfig, randomizeFinish, randomizeGlobal, randomizeGradient, randomizeLights, randomizeSpine, randomizeTransform, randomizeTwist, randomizeWave } from "./randomize.js";
|
|
2
|
+
import { StudioWaveRenderer } from "./StudioWaveRenderer.js";
|
|
3
|
+
export { StudioWaveRenderer, randomizeBackground, randomizeColor, randomizeConfig, randomizeFinish, randomizeGlobal, randomizeGradient, randomizeLights, randomizeSpine, randomizeTransform, randomizeTwist, randomizeWave };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { randomizeBackground, randomizeColor, randomizeConfig, randomizeFinish, randomizeGlobal, randomizeGradient, randomizeLights, randomizeSpine, randomizeTransform, randomizeTwist, randomizeWave } from "./randomize.js";
|
|
2
|
+
import { StudioWaveRenderer } from "./StudioWaveRenderer.js";
|
|
3
|
+
export { StudioWaveRenderer, randomizeBackground, randomizeColor, randomizeConfig, randomizeFinish, randomizeGlobal, randomizeGradient, randomizeLights, randomizeSpine, randomizeTransform, randomizeTwist, randomizeWave };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StudioConfig, WaveConfig } from "../config/model.js";
|
|
2
|
+
|
|
3
|
+
//#region src/studio/randomize.d.ts
|
|
4
|
+
/** "Randomize All": keep the scene (background, camera, lights, quality) and randomize the
|
|
5
|
+
* post-fx plus every wave independently — so a multi-wave stack becomes visibly varied.
|
|
6
|
+
* The camera is deliberately left alone so the result always lands in view. */
|
|
7
|
+
declare function randomizeConfig(base: StudioConfig): StudioConfig;
|
|
8
|
+
/** Randomize a whole wave: colour, shape (displacement/twist), transform, finish + the
|
|
9
|
+
* compositing knobs (speed/opacity/seed/blend). Used by each wave's 🎲 and by "Randomize All". */
|
|
10
|
+
declare function randomizeWave(s: WaveConfig): void;
|
|
11
|
+
declare function randomizeGradient(c: WaveConfig): void;
|
|
12
|
+
/** "Background" folder: a fresh random gradient (linear/radial/conic) or mesh backdrop. Left out
|
|
13
|
+
* of "Randomize All", which deliberately preserves the background. */
|
|
14
|
+
declare function randomizeBackground(c: StudioConfig): void;
|
|
15
|
+
/** "Color" folder: hue / contrast / saturation grading. */
|
|
16
|
+
declare function randomizeColor(c: WaveConfig): void;
|
|
17
|
+
declare function randomizeSpine(c: WaveConfig): void;
|
|
18
|
+
declare function randomizeTransform(c: WaveConfig): void;
|
|
19
|
+
declare function randomizeTwist(c: WaveConfig): void;
|
|
20
|
+
/** A wave's surface finish: fibers/texture + roundness/crease/edge. Grain & blur are scene post-fx
|
|
21
|
+
* (see randomizeGlobal), so they're not touched here. */
|
|
22
|
+
declare function randomizeFinish(c: WaveConfig): void;
|
|
23
|
+
declare function randomizeLights(c: StudioConfig): void;
|
|
24
|
+
/** Scene knobs: the post-fx (grain/blur) + camera framing (zoom). */
|
|
25
|
+
declare function randomizeGlobal(c: StudioConfig): void;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { randomizeBackground, randomizeColor, randomizeConfig, randomizeFinish, randomizeGlobal, randomizeGradient, randomizeLights, randomizeSpine, randomizeTransform, randomizeTwist, randomizeWave };
|
|
28
|
+
//# sourceMappingURL=randomize.d.ts.map
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { clamp, roundTo } from "../util/math.js";
|
|
2
|
+
//#region src/studio/randomize.ts
|
|
3
|
+
/**
|
|
4
|
+
* The "Randomize" helpers: `randomizeConfig` ("Randomize All") plus the per-section randomizers
|
|
5
|
+
* the studio wires to each folder's 🎲. Studio-facing (not part of the renderer core), exposed via
|
|
6
|
+
* the `@wave3d/core/studio` entry.
|
|
7
|
+
*/
|
|
8
|
+
const RANDOM_PALETTES = [
|
|
9
|
+
[
|
|
10
|
+
"#9bb0ff",
|
|
11
|
+
"#ffb14e",
|
|
12
|
+
"#ff6aa8",
|
|
13
|
+
"#b15cff",
|
|
14
|
+
"#6f7bff"
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
"#ffd166",
|
|
18
|
+
"#ff6b6b",
|
|
19
|
+
"#c44dff",
|
|
20
|
+
"#4d79ff",
|
|
21
|
+
"#22d3ee"
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"#f97316",
|
|
25
|
+
"#ec4899",
|
|
26
|
+
"#8b5cf6",
|
|
27
|
+
"#3b82f6",
|
|
28
|
+
"#06b6d4"
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
"#00f5d4",
|
|
32
|
+
"#00bbf9",
|
|
33
|
+
"#9b5de5",
|
|
34
|
+
"#f15bb5",
|
|
35
|
+
"#fee440"
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
"#7c3aed",
|
|
39
|
+
"#db2777",
|
|
40
|
+
"#f59e0b",
|
|
41
|
+
"#10b981",
|
|
42
|
+
"#06b6d4"
|
|
43
|
+
]
|
|
44
|
+
];
|
|
45
|
+
function rand(min, max) {
|
|
46
|
+
return min + Math.random() * (max - min);
|
|
47
|
+
}
|
|
48
|
+
function pick(arr) {
|
|
49
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
50
|
+
}
|
|
51
|
+
/** "Randomize All": keep the scene (background, camera, lights, quality) and randomize the
|
|
52
|
+
* post-fx plus every wave independently — so a multi-wave stack becomes visibly varied.
|
|
53
|
+
* The camera is deliberately left alone so the result always lands in view. */
|
|
54
|
+
function randomizeConfig(base) {
|
|
55
|
+
const cfg = structuredClone(base);
|
|
56
|
+
cfg.grain = r2(rand(0, 1.5));
|
|
57
|
+
cfg.blur = r3(rand(0, .02));
|
|
58
|
+
for (const s of cfg.waves) randomizeWave(s);
|
|
59
|
+
return cfg;
|
|
60
|
+
}
|
|
61
|
+
/** Randomize a whole wave: colour, shape (displacement/twist), transform, finish + the
|
|
62
|
+
* compositing knobs (speed/opacity/seed/blend). Used by each wave's 🎲 and by "Randomize All". */
|
|
63
|
+
function randomizeWave(s) {
|
|
64
|
+
randomizeGradient(s);
|
|
65
|
+
randomizeColor(s);
|
|
66
|
+
randomizeSpine(s);
|
|
67
|
+
randomizeTransform(s);
|
|
68
|
+
randomizeTwist(s);
|
|
69
|
+
randomizeFinish(s);
|
|
70
|
+
s.speed = r2(rand(.02, .15));
|
|
71
|
+
s.opacity = r2(rand(.6, 1));
|
|
72
|
+
s.seed = r2(rand(0, 12));
|
|
73
|
+
s.blendMode = pick([
|
|
74
|
+
"squared",
|
|
75
|
+
"squared",
|
|
76
|
+
"normal",
|
|
77
|
+
"additive"
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
function r2(x) {
|
|
81
|
+
return roundTo(x, 2);
|
|
82
|
+
}
|
|
83
|
+
function r3(x) {
|
|
84
|
+
return roundTo(x, 3);
|
|
85
|
+
}
|
|
86
|
+
const LIGHT_TINTS = [
|
|
87
|
+
"#ffffff",
|
|
88
|
+
"#ffffff",
|
|
89
|
+
"#fff0e0",
|
|
90
|
+
"#e2e8ff",
|
|
91
|
+
"#ffe2f0",
|
|
92
|
+
"#e2fff4",
|
|
93
|
+
"#fff6cc"
|
|
94
|
+
];
|
|
95
|
+
/** Random sorted positions in [0,1] with the ends pinned to 0 and 1. */
|
|
96
|
+
function randomSortedPositions(n) {
|
|
97
|
+
if (n <= 1) return [0];
|
|
98
|
+
if (n === 2) return [0, 1];
|
|
99
|
+
return [
|
|
100
|
+
0,
|
|
101
|
+
...Array.from({ length: n - 2 }, () => r2(rand(.06, .94))).sort((a, b) => a - b),
|
|
102
|
+
1
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
function randomMeshPoints(colors) {
|
|
106
|
+
return colors.map((color) => ({
|
|
107
|
+
color,
|
|
108
|
+
x: r2(rand(.08, .92)),
|
|
109
|
+
y: r2(rand(.08, .92)),
|
|
110
|
+
influence: r2(rand(.5, .95))
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
function randomStops(colors) {
|
|
114
|
+
const positions = randomSortedPositions(colors.length);
|
|
115
|
+
return colors.map((color, i) => ({
|
|
116
|
+
color,
|
|
117
|
+
pos: positions[i]
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
function randomizeGradient(c) {
|
|
121
|
+
const colors = pick(RANDOM_PALETTES);
|
|
122
|
+
const count = clamp(Math.round(rand(3, colors.length)), 3, colors.length);
|
|
123
|
+
const chosen = colors.slice(0, count);
|
|
124
|
+
c.palette = randomStops(chosen);
|
|
125
|
+
c.gradientType = pick([
|
|
126
|
+
"linear",
|
|
127
|
+
"linear",
|
|
128
|
+
"linear",
|
|
129
|
+
"radial",
|
|
130
|
+
"conic",
|
|
131
|
+
"mesh"
|
|
132
|
+
]);
|
|
133
|
+
c.gradientAngle = Math.round(rand(0, 180));
|
|
134
|
+
c.gradientShift = r2(rand(0, .4));
|
|
135
|
+
c.meshGradientPoints = randomMeshPoints(colors);
|
|
136
|
+
c.meshGradientSoftness = r2(rand(.45, .85));
|
|
137
|
+
c.paletteEdgeColor = pick(chosen);
|
|
138
|
+
c.paletteEdgeAmount = r2(rand(0, .5));
|
|
139
|
+
c.paletteImageUrl = void 0;
|
|
140
|
+
c.paletteVideoUrl = void 0;
|
|
141
|
+
const engine = pick([
|
|
142
|
+
"stops-tex",
|
|
143
|
+
"stops-tex",
|
|
144
|
+
"procedural",
|
|
145
|
+
"hero"
|
|
146
|
+
]);
|
|
147
|
+
c.usePaletteTexture = engine !== "procedural";
|
|
148
|
+
c.paletteSource = engine === "hero" ? "hero" : "stops";
|
|
149
|
+
c.paletteDriftX = rand(0, 1) < .25 ? r2(rand(-.25, .25)) : 0;
|
|
150
|
+
c.paletteDriftY = 0;
|
|
151
|
+
}
|
|
152
|
+
/** "Background" folder: a fresh random gradient (linear/radial/conic) or mesh backdrop. Left out
|
|
153
|
+
* of "Randomize All", which deliberately preserves the background. */
|
|
154
|
+
function randomizeBackground(c) {
|
|
155
|
+
const colors = pick(RANDOM_PALETTES);
|
|
156
|
+
c.transparentBackground = false;
|
|
157
|
+
c.backgroundMode = "gradient";
|
|
158
|
+
c.backgroundGradientType = pick([
|
|
159
|
+
"linear",
|
|
160
|
+
"radial",
|
|
161
|
+
"conic",
|
|
162
|
+
"mesh"
|
|
163
|
+
]);
|
|
164
|
+
c.backgroundGradientAngle = Math.round(rand(0, 360));
|
|
165
|
+
c.background = colors[0];
|
|
166
|
+
if (c.backgroundGradientType === "mesh") {
|
|
167
|
+
c.backgroundMeshPoints = randomMeshPoints(colors);
|
|
168
|
+
c.backgroundMeshSoftness = r2(rand(.45, .85));
|
|
169
|
+
} else {
|
|
170
|
+
c.backgroundGradientSource = "stops";
|
|
171
|
+
c.backgroundPalette = randomStops(colors);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** "Color" folder: hue / contrast / saturation grading. */
|
|
175
|
+
function randomizeColor(c) {
|
|
176
|
+
c.hueShift = Math.round(rand(0, 360));
|
|
177
|
+
c.colorContrast = r2(rand(.9, 1.3));
|
|
178
|
+
c.colorSaturation = r2(rand(.8, 1.35));
|
|
179
|
+
}
|
|
180
|
+
function randomizeSpine(c) {
|
|
181
|
+
c.displaceFrequency = {
|
|
182
|
+
x: r3(rand(.002, .016)),
|
|
183
|
+
y: r3(rand(.004, .02))
|
|
184
|
+
};
|
|
185
|
+
c.displaceAmount = r2(rand(3, 10)) * pick([1, -1]);
|
|
186
|
+
if (rand(0, 1) < .35) {
|
|
187
|
+
c.detailFrequency = r3(rand(.03, .08));
|
|
188
|
+
c.detailAmount = r2(rand(.8, 2.5)) * pick([1, -1]);
|
|
189
|
+
} else c.detailAmount = 0;
|
|
190
|
+
}
|
|
191
|
+
function randomizeTransform(c) {
|
|
192
|
+
c.rotation = {
|
|
193
|
+
x: r2(rand(-20, 5)),
|
|
194
|
+
y: r2(rand(-25, 10)),
|
|
195
|
+
z: r2(rand(-170, -150))
|
|
196
|
+
};
|
|
197
|
+
const s = r2(rand(6, 14));
|
|
198
|
+
c.scale = {
|
|
199
|
+
x: s,
|
|
200
|
+
y: s,
|
|
201
|
+
z: r2(s * rand(.6, .8))
|
|
202
|
+
};
|
|
203
|
+
c.position = {
|
|
204
|
+
x: r2(rand(-60, 60)),
|
|
205
|
+
y: r2(rand(-60, 60)),
|
|
206
|
+
z: r2(rand(-20, 20))
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function randomizeTwist(c) {
|
|
210
|
+
c.twistFrequency = {
|
|
211
|
+
x: r3(rand(-.5, .5)),
|
|
212
|
+
y: r3(rand(-.3, .5)),
|
|
213
|
+
z: r3(rand(-1.6, -.6))
|
|
214
|
+
};
|
|
215
|
+
c.twistPower = {
|
|
216
|
+
x: r2(rand(2, 6)),
|
|
217
|
+
y: r2(rand(2, 6)),
|
|
218
|
+
z: r2(rand(2, 7))
|
|
219
|
+
};
|
|
220
|
+
c.twistMotion = rand(0, 1) < .25;
|
|
221
|
+
}
|
|
222
|
+
/** A wave's surface finish: fibers/texture + roundness/crease/edge. Grain & blur are scene post-fx
|
|
223
|
+
* (see randomizeGlobal), so they're not touched here. */
|
|
224
|
+
function randomizeFinish(c) {
|
|
225
|
+
c.fiberCount = Math.round(rand(200, 900));
|
|
226
|
+
c.fiberStrength = r2(rand(.1, .35));
|
|
227
|
+
c.texture = r2(rand(0, .35));
|
|
228
|
+
c.roundness = r2(rand(.3, .8));
|
|
229
|
+
c.sheen = r2(rand(.2, .9));
|
|
230
|
+
c.iridescence = rand(0, 1) < .35 ? r2(rand(.15, .6)) : 0;
|
|
231
|
+
c.creaseLight = r2(rand(.4, 1));
|
|
232
|
+
c.creaseSharpness = r2(rand(.45, .8));
|
|
233
|
+
c.creaseSoftness = r2(rand(.8, 1.2));
|
|
234
|
+
c.edgeFade = r2(rand(0, .08));
|
|
235
|
+
c.edgeFeather = r2(rand(.03, .22));
|
|
236
|
+
c.depthTint = rand(0, 1) < .3 ? r2(rand(.2, .6)) : 0;
|
|
237
|
+
c.lineAmount = Math.round(rand(200, 900));
|
|
238
|
+
c.lineThickness = r2(rand(.5, 2));
|
|
239
|
+
c.lineDerivativePower = r2(rand(.4, 1.2));
|
|
240
|
+
c.maxWidth = Math.round(rand(400, 1600));
|
|
241
|
+
c.theme = rand(0, 1) < .2 ? "wireframe" : "solid";
|
|
242
|
+
}
|
|
243
|
+
function randomizeLights(c) {
|
|
244
|
+
c.ambient = r2(rand(.25, .6));
|
|
245
|
+
for (const l of c.lights) {
|
|
246
|
+
l.position = {
|
|
247
|
+
x: r2(rand(-1e3, 1e3)),
|
|
248
|
+
y: r2(rand(-500, 1e3)),
|
|
249
|
+
z: r2(rand(-500, 1200))
|
|
250
|
+
};
|
|
251
|
+
l.intensity = r2(rand(.5, 1.6));
|
|
252
|
+
l.color = pick(LIGHT_TINTS);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** Scene knobs: the post-fx (grain/blur) + camera framing (zoom). */
|
|
256
|
+
function randomizeGlobal(c) {
|
|
257
|
+
c.grain = r2(rand(0, 1.5));
|
|
258
|
+
c.blur = r3(rand(0, .02));
|
|
259
|
+
c.cameraZoom = r2(rand(1.2, 2.6));
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
export { randomizeBackground, randomizeColor, randomizeConfig, randomizeFinish, randomizeGlobal, randomizeGradient, randomizeLights, randomizeSpine, randomizeTransform, randomizeTwist, randomizeWave };
|
|
263
|
+
|
|
264
|
+
//# sourceMappingURL=randomize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"randomize.js","names":[],"sources":["../../src/studio/randomize.ts"],"sourcesContent":["/**\n * The \"Randomize\" helpers: `randomizeConfig` (\"Randomize All\") plus the per-section randomizers\n * the studio wires to each folder's 🎲. Studio-facing (not part of the renderer core), exposed via\n * the `@wave3d/core/studio` entry.\n */\nimport { clamp, roundTo } from \"../util/math\";\nimport type {\n StudioConfig,\n WaveConfig,\n MeshGradientPoint,\n ColorStop,\n GradientType,\n} from \"../config/model\";\n\nconst RANDOM_PALETTES: string[][] = [\n [\"#9bb0ff\", \"#ffb14e\", \"#ff6aa8\", \"#b15cff\", \"#6f7bff\"],\n [\"#ffd166\", \"#ff6b6b\", \"#c44dff\", \"#4d79ff\", \"#22d3ee\"],\n [\"#f97316\", \"#ec4899\", \"#8b5cf6\", \"#3b82f6\", \"#06b6d4\"],\n [\"#00f5d4\", \"#00bbf9\", \"#9b5de5\", \"#f15bb5\", \"#fee440\"],\n [\"#7c3aed\", \"#db2777\", \"#f59e0b\", \"#10b981\", \"#06b6d4\"],\n];\n\nfunction rand(min: number, max: number): number {\n return min + Math.random() * (max - min);\n}\n\nfunction pick<T>(arr: T[]): T {\n return arr[Math.floor(Math.random() * arr.length)];\n}\n\n/** \"Randomize All\": keep the scene (background, camera, lights, quality) and randomize the\n * post-fx plus every wave independently — so a multi-wave stack becomes visibly varied.\n * The camera is deliberately left alone so the result always lands in view. */\nexport function randomizeConfig(base: StudioConfig): StudioConfig {\n const cfg = structuredClone(base);\n cfg.grain = r2(rand(0, 1.5));\n cfg.blur = r3(rand(0, 0.02));\n for (const s of cfg.waves) randomizeWave(s);\n return cfg;\n}\n\n/** Randomize a whole wave: colour, shape (displacement/twist), transform, finish + the\n * compositing knobs (speed/opacity/seed/blend). Used by each wave's 🎲 and by \"Randomize All\". */\nexport function randomizeWave(s: WaveConfig): void {\n randomizeGradient(s);\n randomizeColor(s);\n randomizeSpine(s);\n randomizeTransform(s);\n randomizeTwist(s);\n randomizeFinish(s);\n s.speed = r2(rand(0.02, 0.15));\n s.opacity = r2(rand(0.6, 1));\n s.seed = r2(rand(0, 12));\n // Bias to the vivid squared blend; occasionally normal/additive (skip multiply — it muddies\n // the wave on light/transparent backgrounds).\n s.blendMode = pick([\"squared\", \"squared\", \"normal\", \"additive\"]);\n}\n\n// ---- Per-section randomizers (each mutates only its own fields, in place) ----\n\nfunction r2(x: number): number {\n return roundTo(x, 2);\n}\nfunction r3(x: number): number {\n return roundTo(x, 3);\n}\n\nconst LIGHT_TINTS = [\"#ffffff\", \"#ffffff\", \"#fff0e0\", \"#e2e8ff\", \"#ffe2f0\", \"#e2fff4\", \"#fff6cc\"];\n\n/** Random sorted positions in [0,1] with the ends pinned to 0 and 1. */\nfunction randomSortedPositions(n: number): number[] {\n if (n <= 1) return [0];\n if (n === 2) return [0, 1];\n const inner = Array.from({ length: n - 2 }, () => r2(rand(0.06, 0.94))).sort((a, b) => a - b);\n return [0, ...inner, 1];\n}\n\nfunction randomMeshPoints(colors: string[]): MeshGradientPoint[] {\n return colors.map((color) => ({\n color,\n x: r2(rand(0.08, 0.92)),\n y: r2(rand(0.08, 0.92)),\n influence: r2(rand(0.5, 0.95)),\n }));\n}\n\nfunction randomStops(colors: string[]): ColorStop[] {\n const positions = randomSortedPositions(colors.length);\n return colors.map((color, i) => ({ color, pos: positions[i] }));\n}\n\nexport function randomizeGradient(c: WaveConfig): void {\n const colors = pick(RANDOM_PALETTES);\n const count = clamp(Math.round(rand(3, colors.length)), 3, colors.length);\n const chosen = colors.slice(0, count);\n c.palette = randomStops(chosen);\n // Bias toward linear, occasionally radial/conic/mesh for variety.\n c.gradientType = pick([\n \"linear\",\n \"linear\",\n \"linear\",\n \"radial\",\n \"conic\",\n \"mesh\",\n ] as GradientType[]);\n c.gradientAngle = Math.round(rand(0, 180));\n c.gradientShift = r2(rand(0, 0.4)); // 2D warp\n // Also refresh the mesh field + edge tint so the whole colour section changes regardless of the\n // active source (both are inert unless gradientType is \"mesh\" / the source is \"stops\").\n c.meshGradientPoints = randomMeshPoints(colors);\n c.meshGradientSoftness = r2(rand(0.45, 0.85));\n c.paletteEdgeColor = pick(chosen);\n c.paletteEdgeAmount = r2(rand(0, 0.5));\n // Colour engine: mostly the editable stops (as a 2D texture or procedurally), occasionally the\n // baked hero LUT. Clear any loaded custom image/video so the picked source actually shows.\n c.paletteImageUrl = undefined;\n c.paletteVideoUrl = undefined;\n const engine = pick([\"stops-tex\", \"stops-tex\", \"procedural\", \"hero\"]);\n c.usePaletteTexture = engine !== \"procedural\";\n c.paletteSource = engine === \"hero\" ? \"hero\" : \"stops\";\n // Occasional slow colour flow along the ribbon (inert on mesh / procedural palettes).\n c.paletteDriftX = rand(0, 1) < 0.25 ? r2(rand(-0.25, 0.25)) : 0;\n c.paletteDriftY = 0;\n}\n\n/** \"Background\" folder: a fresh random gradient (linear/radial/conic) or mesh backdrop. Left out\n * of \"Randomize All\", which deliberately preserves the background. */\nexport function randomizeBackground(c: StudioConfig): void {\n const colors = pick(RANDOM_PALETTES);\n c.transparentBackground = false;\n c.backgroundMode = \"gradient\";\n c.backgroundGradientType = pick([\"linear\", \"radial\", \"conic\", \"mesh\"] as GradientType[]);\n c.backgroundGradientAngle = Math.round(rand(0, 360));\n c.background = colors[0]; // matte fallback (shown only if a gradient ever fails to cover)\n if (c.backgroundGradientType === \"mesh\") {\n c.backgroundMeshPoints = randomMeshPoints(colors);\n c.backgroundMeshSoftness = r2(rand(0.45, 0.85));\n } else {\n c.backgroundGradientSource = \"stops\";\n c.backgroundPalette = randomStops(colors);\n }\n}\n\n/** \"Color\" folder: hue / contrast / saturation grading. */\nexport function randomizeColor(c: WaveConfig): void {\n c.hueShift = Math.round(rand(0, 360));\n c.colorContrast = r2(rand(0.9, 1.3));\n c.colorSaturation = r2(rand(0.8, 1.35));\n}\n\nexport function randomizeSpine(c: WaveConfig): void {\n // Wider frequency spread than before so a re-roll is actually visible (the old range barely\n // moved). Amount takes either sign so the ribbon folds either way.\n c.displaceFrequency = { x: r3(rand(0.002, 0.016)), y: r3(rand(0.004, 0.02)) };\n c.displaceAmount = r2(rand(3, 10)) * pick([1, -1]);\n // Occasionally layer a finer second octave for a richer, two-scale shape.\n if (rand(0, 1) < 0.35) {\n c.detailFrequency = r3(rand(0.03, 0.08));\n c.detailAmount = r2(rand(0.8, 2.5)) * pick([1, -1]);\n } else {\n c.detailAmount = 0;\n }\n}\n\nexport function randomizeTransform(c: WaveConfig): void {\n c.rotation = { x: r2(rand(-20, 5)), y: r2(rand(-25, 10)), z: r2(rand(-170, -150)) };\n // Full scale (×10) — the mesh lives in the tens, not fractions.\n const s = r2(rand(6, 14));\n c.scale = { x: s, y: s, z: r2(s * rand(0.6, 0.8)) };\n // z is modest — the ortho camera barely shows depth, and a big z can clip the near/far planes.\n c.position = { x: r2(rand(-60, 60)), y: r2(rand(-60, 60)), z: r2(rand(-20, 20)) };\n}\n\nexport function randomizeTwist(c: WaveConfig): void {\n c.twistFrequency = { x: r3(rand(-0.5, 0.5)), y: r3(rand(-0.3, 0.5)), z: r3(rand(-1.6, -0.6)) };\n c.twistPower = { x: r2(rand(2, 6)), y: r2(rand(2, 6)), z: r2(rand(2, 7)) };\n c.twistMotion = rand(0, 1) < 0.25; // occasionally enable the breathing wobble\n}\n\n/** A wave's surface finish: fibers/texture + roundness/crease/edge. Grain & blur are scene post-fx\n * (see randomizeGlobal), so they're not touched here. */\nexport function randomizeFinish(c: WaveConfig): void {\n c.fiberCount = Math.round(rand(200, 900));\n c.fiberStrength = r2(rand(0.1, 0.35));\n c.texture = r2(rand(0, 0.35));\n c.roundness = r2(rand(0.3, 0.8)); // rounded-solid shading\n c.sheen = r2(rand(0.2, 0.9));\n c.iridescence = rand(0, 1) < 0.35 ? r2(rand(0.15, 0.6)) : 0; // occasional thin-film sheen\n c.creaseLight = r2(rand(0.4, 1.0)); // crease strength (where streaks appear)\n c.creaseSharpness = r2(rand(0.45, 0.8));\n c.creaseSoftness = r2(rand(0.8, 1.2));\n c.edgeFade = r2(rand(0, 0.08));\n c.edgeFeather = r2(rand(0.03, 0.22)); // crisp graphic ↔ soft vapor edges\n c.depthTint = rand(0, 1) < 0.3 ? r2(rand(0.2, 0.6)) : 0; // occasional atmospheric depth fade\n // Wireframe line params (inert unless theme is \"wireframe\") — randomized too so a wireframe\n // wave's Finish 🎲 refreshes its whole look, not just the solid-shader knobs.\n c.lineAmount = Math.round(rand(200, 900));\n c.lineThickness = r2(rand(0.5, 2));\n c.lineDerivativePower = r2(rand(0.4, 1.2));\n c.maxWidth = Math.round(rand(400, 1600));\n c.theme = rand(0, 1) < 0.2 ? \"wireframe\" : \"solid\"; // occasionally flip the material\n}\n\nexport function randomizeLights(c: StudioConfig): void {\n c.ambient = r2(rand(0.25, 0.6));\n for (const l of c.lights) {\n l.position = { x: r2(rand(-1000, 1000)), y: r2(rand(-500, 1000)), z: r2(rand(-500, 1200)) };\n l.intensity = r2(rand(0.5, 1.6));\n l.color = pick(LIGHT_TINTS);\n }\n}\n\n/** Scene knobs: the post-fx (grain/blur) + camera framing (zoom). */\nexport function randomizeGlobal(c: StudioConfig): void {\n c.grain = r2(rand(0, 1.5));\n c.blur = r3(rand(0, 0.02));\n // Ortho: vary the zoom (framing), keep the camera pose/target — distance doesn't size it.\n c.cameraZoom = r2(rand(1.2, 2.6));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/util/base64.ts
|
|
2
|
+
/** Dependency-free — a private copy so the renderer core carries no cross-package imports
|
|
3
|
+
* (the studio keeps its own copy under apps/studio/src/util). */
|
|
4
|
+
/** Decode base64 — standard or url-safe alphabet — into bytes. */
|
|
5
|
+
function base64ToBytes(b64) {
|
|
6
|
+
const bin = atob(b64.replace(/-/g, "+").replace(/_/g, "/"));
|
|
7
|
+
const out = new Uint8Array(new ArrayBuffer(bin.length));
|
|
8
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { base64ToBytes };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=base64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.js","names":[],"sources":["../../src/util/base64.ts"],"sourcesContent":["/** Dependency-free — a private copy so the renderer core carries no cross-package imports\n * (the studio keeps its own copy under apps/studio/src/util). */\n\n/** Decode base64 — standard or url-safe alphabet — into bytes. */\nexport function base64ToBytes(b64: string): Uint8Array<ArrayBuffer> {\n const bin = atob(b64.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const out = new Uint8Array(new ArrayBuffer(bin.length));\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n"],"mappings":";;;;AAIA,SAAgB,cAAc,KAAsC;CAClE,MAAM,MAAM,KAAK,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,GAAG,CAAC;CAC1D,MAAM,MAAM,IAAI,WAAW,IAAI,YAAY,IAAI,MAAM,CAAC;CACtD,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,IAAI,KAAK,IAAI,WAAW,CAAC;CAC9D,OAAO;AACT"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/util/math.ts
|
|
2
|
+
/** Shared numeric helpers. Dependency-free — a private copy so the renderer core carries no
|
|
3
|
+
* cross-package imports (the studio keeps its own copy under apps/studio/src/util). */
|
|
4
|
+
function clamp(value, min, max) {
|
|
5
|
+
return Math.min(max, Math.max(min, value));
|
|
6
|
+
}
|
|
7
|
+
function clamp01(value) {
|
|
8
|
+
return clamp(value, 0, 1);
|
|
9
|
+
}
|
|
10
|
+
/** Round to `decimals` places, e.g. roundTo(x, 2) → hundredths. */
|
|
11
|
+
function roundTo(value, decimals) {
|
|
12
|
+
const factor = 10 ** decimals;
|
|
13
|
+
return Math.round(value * factor) / factor;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { clamp, clamp01, roundTo };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=math.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"math.js","names":[],"sources":["../../src/util/math.ts"],"sourcesContent":["/** Shared numeric helpers. Dependency-free — a private copy so the renderer core carries no\n * cross-package imports (the studio keeps its own copy under apps/studio/src/util). */\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nexport function clamp01(value: number): number {\n return clamp(value, 0, 1);\n}\n\n/** Round to `decimals` places, e.g. roundTo(x, 2) → hundredths. */\nexport function roundTo(value: number, decimals: number): number {\n const factor = 10 ** decimals;\n return Math.round(value * factor) / factor;\n}\n"],"mappings":";;;AAGA,SAAgB,MAAM,OAAe,KAAa,KAAqB;CACrE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;AAEA,SAAgB,QAAQ,OAAuB;CAC7C,OAAO,MAAM,OAAO,GAAG,CAAC;AAC1B;;AAGA,SAAgB,QAAQ,OAAe,UAA0B;CAC/D,MAAM,SAAS,MAAM;CACrB,OAAO,KAAK,MAAM,QAAQ,MAAM,IAAI;AACtC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wave3d/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic 3D gradient-wave renderer + config model (the engine behind Wave Studio)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"animation",
|
|
7
|
+
"background",
|
|
8
|
+
"gradient",
|
|
9
|
+
"shader",
|
|
10
|
+
"tanstack-intent",
|
|
11
|
+
"three",
|
|
12
|
+
"threejs",
|
|
13
|
+
"wave",
|
|
14
|
+
"webgl"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/Amir-Abushanab/wave3d/tree/main/packages/core#readme",
|
|
17
|
+
"bugs": "https://github.com/Amir-Abushanab/wave3d/issues",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Amir Abushanab (https://github.com/Amir-Abushanab)",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Amir-Abushanab/wave3d.git",
|
|
23
|
+
"directory": "packages/core"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"skills"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./renderer": {
|
|
37
|
+
"types": "./dist/renderer/index.d.ts",
|
|
38
|
+
"default": "./dist/renderer/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./presets": {
|
|
41
|
+
"types": "./dist/presets.d.ts",
|
|
42
|
+
"default": "./dist/presets.js"
|
|
43
|
+
},
|
|
44
|
+
"./studio": {
|
|
45
|
+
"types": "./dist/studio/index.d.ts",
|
|
46
|
+
"default": "./dist/studio/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./standalone": {
|
|
49
|
+
"types": "./dist/standalone.d.ts",
|
|
50
|
+
"default": "./dist/standalone/wave3d.standalone.js"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@tanstack/intent": "^0.3.4",
|
|
58
|
+
"@types/three": "^0.185.0",
|
|
59
|
+
"three": "^0.185.0",
|
|
60
|
+
"vite": "^8.1.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@types/three": ">=0.180",
|
|
64
|
+
"three": ">=0.180 <1"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@types/three": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"typecheck": "tsc --noEmit",
|
|
73
|
+
"build": "tsdown && pnpm build:standalone",
|
|
74
|
+
"build:standalone": "vite build --config vite.standalone.config.ts"
|
|
75
|
+
}
|
|
76
|
+
}
|