@snailicid3/gbt-scope 0.0.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/helpers.ts","../src/materials/shader-radial-symmetry.ts","../src/motion/curve.ts","../src/motion/animator.ts","../src/motion/driver.ts","../src/types.ts","../src/components/GbtScopeMaterial.tsx","../src/motion/pointer.ts","../src/motion/scroll.ts","../src/components/GbtScopeFlatViewer.tsx","../src/components/GbtScopeMeshViewer.tsx"],"sourcesContent":["import {\n type ArcRotateCamera,\n Camera,\n type Color3,\n type FreeCamera,\n type Scene,\n type Vector2,\n Vector3,\n Vector4,\n} from '@babylonjs/core'\n\nexport type Dimensions = {\n height: number\n width: number\n}\nexport type Point = { x: number; y: number }\nexport type XY = [number, number]\nexport const getResolution = (dimensions: Dimensions): Vector4 => {\n const { height: _height, width: _width } = dimensions\n const aA = _height / _width > 1 ? _width / _height : 1\n const aB = _height / _width > 1 ? 1 : _height / _width\n return new Vector4(_width, _height, aA, aB)\n}\nexport const distanceBetweenPoints = (pointA: Point, pointB: Point): number =>\n Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y)\n\nexport type RGBColor = ConstructorParameters<typeof Color3>\nexport type Vector2Params = ConstructorParameters<typeof Vector2>\nexport type Vector3Params = ConstructorParameters<typeof Vector3>\nexport type Vector4Params = ConstructorParameters<typeof Vector4>\nconst { x, y, z } = Vector3.Zero()\n\nexport type CameraConfigPosition = Partial<{\n enabled: boolean\n hRotation: number /** Alpha Math.PI / 2, // Alpha (horizontal rotation) */\n /** Slow down the zoom speed */\n mouseWheelSpeed: number\n position: Vector3Params\n radius: number\n target: Vector3Params\n vRotation: number /** Beta Math.PI / 4, // Beta (vertical rotation) */\n}>\nexport type CameraOrthoConfig = Pick<\n CameraConfigPosition,\n 'enabled' | 'target'\n> & { ortho?: true }\nexport const setOrthoCamera = (\n scene: Scene,\n camera: FreeCamera,\n { enabled = true, ortho = true, target = [0, 0, 0] }: CameraOrthoConfig,\n): FreeCamera => {\n camera.setTarget(new Vector3(...target))\n camera.mode = Camera.ORTHOGRAPHIC_CAMERA\n\n if (enabled) {\n camera.attachControl(scene.getEngine().getRenderingCanvas(), true)\n } else {\n camera.detachControl()\n }\n return camera\n}\nexport const setRotateCameraPosition = (\n camera: ArcRotateCamera,\n scene: Scene,\n {\n enabled = true,\n hRotation = 0,\n mouseWheelSpeed = 0.01,\n position = [x, y, z],\n radius = 10,\n target = [0, 0, 0],\n vRotation = 0,\n }: CameraConfigPosition,\n): void => {\n camera.alpha = hRotation\n camera.beta = vRotation\n camera.radius = radius\n camera.wheelDeltaPercentage = mouseWheelSpeed\n //TODO: Fix this scamera.setPosition(new Vector3(...position))\n camera.setTarget(new Vector3(...target))\n if (enabled) {\n camera.attachControl(scene.getEngine().getRenderingCanvas(), true)\n } else {\n camera.detachControl()\n }\n}\nexport default {}\n","export default {}\nexport const vertexShader = `\nprecision highp float;\n\n// Attributes\nattribute vec3 position;\nattribute vec2 uv;\n\n// Uniforms\nuniform mat4 worldViewProjection;\n\n// Varying\nvarying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = worldViewProjection * vec4(position, 1.0);\n}\n`\n\nexport const fragmentShader = `\nprecision mediump float;\n\n// Uniforms\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nuniform float uOpacity;\nuniform float segments;\nuniform vec2 uOffset;\nuniform float uRotation;\nuniform float uOffsetAmount;\nuniform float uRotationAmount;\nuniform float uScaleFactor;\nuniform float uImageAspect;\nuniform float uTiling; // Tiling factor for the entire pattern\nuniform float uTileMode; // 0 = none, 1 = repeat, 2 = mirror\n\n// Varying\nvarying vec2 vUv;\n\nconst float PI = 3.14159265359;\n\nvec2 adjustUV(vec2 uv, vec2 offset, float rotation) {\n float cosRot = cos(rotation * uRotationAmount);\n float sinRot = sin(rotation * uRotationAmount);\n mat2 rotMat = mat2(cosRot, -sinRot, sinRot, cosRot);\n vec2 rotatedUV = rotMat * (uv - vec2(0.5)) + vec2(0.5); // Apply rotation first\n return rotatedUV + offset * uOffsetAmount; // Apply offset after rotation\n}\n\n// Mirrored repeat: even tiles use fract, odd tiles use the reversed fract,\n// producing seamless mirrored edges instead of hard wraps.\nvec2 mirrorUV(vec2 uv) {\n vec2 tile = floor(uv);\n vec2 odd = mod(tile, 2.0);\n return mix(fract(uv), 1.0 - fract(uv), odd);\n}\n\nvoid main() {\n // Adjust UV coordinates for resolution\n vec2 newUV = (vUv - vec2(0.5)) * resolution.zw + vec2(0.5);\n vec2 uv = newUV * 2.0 - 1.0;\n\n // Convert to polar coordinates\n float angle = atan(uv.y, uv.x);\n float radius = length(uv);\n\n // Apply kaleidoscope effect\n float segment = PI * 2.0 / segments;\n angle = mod(angle, segment);\n angle = segment - abs(segment / 2.0 - angle);\n uv = radius * vec2(cos(angle), sin(angle));\n\n // Scale the pattern\n float scale = 1.0 / uScaleFactor;\n \n // Apply tiling to the entire pattern based on the selected tile mode\n vec2 scaledUV = uv * uTiling;\n vec2 tiledUV;\n if (uTileMode < 0.5) {\n tiledUV = uv; // none: no wrapping\n } else if (uTileMode < 1.5) {\n tiledUV = fract(scaledUV); // repeat: square repeats (historical behavior)\n } else {\n tiledUV = mirrorUV(scaledUV); // mirror: seamless mirrored repeats\n }\n\n // Adjust UV for texture sampling\n vec2 adjustedUV = adjustUV(tiledUV * scale + scale, uOffset, uRotation);\n vec2 aspectCorrectedUV = vec2(adjustedUV.x, adjustedUV.y * uImageAspect);\n\n // Sample the texture\n vec4 color = texture2D(uTexture, aspectCorrectedUV);\n color.a *= uOpacity;\n\n // Output the final color\n gl_FragColor = color;\n}\n`\n","import { type GbtScopeCurve } from '../types.ts'\n\n/**\n * Maps an input value through a {@link GbtScopeCurve}: deadzone → gain → exponent → clamp → optional invert. The input\n * is treated by magnitude (`Math.abs`), so direction is supplied by `invert`, not the sign of `value`.\n */\nexport const applyCurve = (\n value: number,\n curve: GbtScopeCurve = {},\n): number => {\n const {\n deadzone = 0,\n exponent = 1,\n invert = false,\n max = 1,\n min = 0,\n multiplier = 1,\n } = curve\n const normalized = Math.max(0, Math.abs(value) - deadzone)\n const curved = Math.pow(normalized * multiplier, exponent)\n const clamped = Math.min(Math.max(curved, min), max)\n return invert ? -clamped : clamped\n}\n\n/**\n * Type guard distinguishing the legacy `[min, max]` tuple form of `mouse_curve` from the richer {@link GbtScopeCurve}\n * object form.\n */\nexport const isTupleCurve = (\n value: [number, number] | GbtScopeCurve,\n): value is [number, number] => {\n if (!Array.isArray(value)) return false\n // Widen so the runtime length/type checks aren't \"always true\" for the tuple type.\n const items: ReadonlyArray<unknown> = value\n return (\n items.length === 2 &&\n typeof items[0] === 'number' &&\n typeof items[1] === 'number'\n )\n}\n\n/**\n * Bridges the legacy `[min, max]` tuple (plus a separate `mouse_multiplier`) into an equivalent {@link GbtScopeCurve}.\n * With the historical defaults `[0, 0.015]` and multiplier `0.01`, `applyCurve` reproduces the old inline\n * `Math.min(Math.max(dist * mult, min), max)` clamp exactly.\n */\nexport const tupleToGbtScopeCurve = (\n tuple: [number, number],\n multiplier = 1,\n): GbtScopeCurve => ({\n max: tuple[1],\n min: tuple[0],\n multiplier,\n})\n","import { applyCurve } from './curve.ts'\nimport { type GbtScopeCurve } from '../types.ts'\n\n/**\n * A single declarative animation rule: read `source`, shape it through `curve`, scale by `speed * delta`, then `add` to\n * (default) or `set` the `target`.\n */\nexport type GbtScopeAnimator = {\n curve?: GbtScopeCurve\n mode?: 'add' | 'set'\n source: GbtScopeAnimatorSource\n speed?: number\n target: GbtScopeAnimatorTarget\n}\n\n/** Input signal an animator reads from. */\nexport type GbtScopeAnimatorSource =\n 'mouseDistance' | 'scrollProgress' | 'scrollVelocity' | 'time'\n\n/** Uniform-backed value an animator can drive. */\nexport type GbtScopeAnimatorTarget =\n 'offset.x' | 'offset.y' | 'opacity' | 'rotation' | 'scaleFactor'\n\n/**\n * Fixed values that replace the live pointer/scroll signals in the driver — for mocking motion input (device-free\n * testing, deterministic demos). A defined field wins over the real input; `undefined` fields fall through.\n */\nexport type GbtScopeInputOverrides = Partial<\n Pick<GbtScopeInputs, 'mouseDistance' | 'scrollProgress' | 'scrollVelocity'>\n>\n\n/** Per-frame inputs fed to the animators. */\nexport type GbtScopeInputs = {\n delta: number\n mouseDistance: number\n scrollProgress: number\n scrollVelocity: number\n time: number\n}\n\n/** Mutable, uniform-facing animation state. */\nexport type GbtScopeState = {\n offset: [number, number]\n opacity: number\n rotation: number\n scaleFactor: number\n}\n\n/**\n * Applies every animator to a copy of `state` for one frame and returns the new state. Each animator's source value is\n * curved, scaled by `speed * delta` (frame-rate independent), then added to or set on its target.\n */\nexport const applyAnimators = (\n state: GbtScopeState,\n animators: Array<GbtScopeAnimator>,\n inputs: GbtScopeInputs,\n): GbtScopeState => {\n const next: GbtScopeState = { ...state, offset: [...state.offset] }\n\n const getSourceValue = (source: GbtScopeAnimatorSource): number => {\n switch (source) {\n case 'mouseDistance':\n return inputs.mouseDistance\n case 'scrollProgress':\n return inputs.scrollProgress\n case 'scrollVelocity':\n return inputs.scrollVelocity\n case 'time':\n return inputs.time\n }\n }\n\n animators.forEach((anim) => {\n const raw = getSourceValue(anim.source)\n const curved = applyCurve(raw, anim.curve)\n const value = (anim.speed ?? 1) * curved * inputs.delta\n const apply = (current: number): number =>\n anim.mode === 'set' ? value : current + value\n switch (anim.target) {\n case 'offset.x':\n next.offset = [apply(next.offset[0]), next.offset[1]]\n break\n case 'offset.y':\n next.offset = [next.offset[0], apply(next.offset[1])]\n break\n case 'opacity':\n next.opacity = apply(next.opacity)\n break\n case 'rotation':\n next.rotation = apply(next.rotation)\n break\n case 'scaleFactor':\n next.scaleFactor = apply(next.scaleFactor)\n break\n }\n })\n\n return next\n}\n","import { type Scene, type ShaderMaterial, Vector2 } from '@babylonjs/core'\nimport {\n applyAnimators,\n type GbtScopeAnimator,\n type GbtScopeInputOverrides,\n type GbtScopeState,\n} from './animator.ts'\nimport { type PointerStateHandle } from './pointer.ts'\nimport { type ScrollStateHandle } from './scroll.ts'\n\nexport type GbtScopeDriverOptions = {\n /** Live list of animators (read every frame). */\n animatorsRef: MutableRef<Array<GbtScopeAnimator>>\n /** Optional live overrides that replace the pointer/scroll signals (read every frame). */\n overridesRef?: MutableRef<GbtScopeInputOverrides | undefined>\n pointer: PointerStateHandle\n scroll: ScrollStateHandle\n /**\n * Persistent runtime state. The driver accumulates into it each frame; the owner re-seeds `current` from base props\n * to make changes live.\n */\n stateRef: MutableRef<GbtScopeState>\n}\n\n/** Minimal mutable-ref shape (compatible with React's useRef result). */\nexport type MutableRef<Type> = { current: Type }\n\n/** Pushes a {@link GbtScopeState} onto the shader material's uniforms. */\nconst writeState = (material: ShaderMaterial, state: GbtScopeState): void => {\n material.setFloat('uRotation', state.rotation)\n material.setFloat('uScaleFactor', state.scaleFactor)\n material.setFloat('uOpacity', state.opacity)\n material.setVector2(\n 'uOffset',\n new Vector2(state.offset[0], state.offset[1]),\n )\n}\n\n/**\n * Registers a single render-loop observer that drives the material's animated uniforms from the animators + live\n * inputs, frame-rate independent via `engine.getDeltaTime()`. Replaces Babylon's Animation API. Returns a dispose\n * function that removes the observer.\n */\nexport const createGbtScopeDriver = (\n scene: Scene,\n material: ShaderMaterial,\n {\n animatorsRef,\n overridesRef,\n pointer,\n scroll,\n stateRef,\n }: GbtScopeDriverOptions,\n): (() => void) => {\n let time = 0\n\n const observer = scene.onBeforeRenderObservable.add(() => {\n const delta = scene.getEngine().getDeltaTime() / 1000\n time += delta\n\n const overrides = overridesRef?.current\n const inputs = {\n delta,\n mouseDistance:\n overrides?.mouseDistance ??\n Math.hypot(pointer.state.x, pointer.state.y),\n scrollProgress: overrides?.scrollProgress ?? scroll.state.progress,\n scrollVelocity: overrides?.scrollVelocity ?? scroll.state.velocity,\n time,\n }\n\n stateRef.current = applyAnimators(\n stateRef.current,\n animatorsRef.current,\n inputs,\n )\n writeState(material, stateRef.current)\n\n // Velocity is impulse-based; bleed it off each frame.\n scroll.decay()\n })\n\n return (): void => {\n scene.onBeforeRenderObservable.remove(observer)\n }\n}\n","import { type Dimensions } from './helpers.ts'\nimport {\n type GbtScopeAnimator,\n type GbtScopeInputOverrides,\n} from './motion/animator.ts'\n\n/**\n * Curve parameters controlling how an input value (eg. pointer distance from center, scroll velocity) maps to an effect\n * amount. Replaces the old `[min, max]` tuple form of `mouse_curve` with a richer, named shape.\n *\n * @see applyCurve in ./motion/curve.ts\n */\nexport type GbtScopeCurve = {\n /** Input magnitude below this is treated as 0. Default 0. */\n deadzone?: number\n /** Shaping exponent (1 = linear, >1 = ease-in). Default 1. */\n exponent?: number\n /** Negate the result. Default false. */\n invert?: boolean\n /** Upper clamp applied after curving. Default 1. */\n max?: number\n /** Lower clamp applied after curving. Default 0. */\n min?: number\n /** Linear gain applied before the exponent. Default 1. */\n multiplier?: number\n}\n\n/**\n * Shared, serializable material props for all GbtScope viewers (flat + 3D mesh). camelCase only — animation is\n * data-driven via {@link GbtScopeAnimator}, not speed fields. `rotation`/`offset`/`scaleFactor`/`opacity` are the\n * resting (base) values the animators build on.\n */\nexport type GbtScopeMaterialProps = {\n /** Pre-resolved texture dimensions; viewers derive this from `resolution`. */\n dimensions?: Dimensions\n imageAspect?: number\n offset?: [number, number]\n /** Multiplier on the offset uniform (`uOffsetAmount`). */\n offsetScale?: number\n opacity?: number\n rotation?: number\n /** Multiplier on the rotation uniform (`uRotationAmount`). */\n rotationScale?: number\n scaleFactor?: number\n segments?: number\n src: string\n tileMode?: GbtScopeTileMode\n tiling?: number\n}\n\n/**\n * Tiling strategy applied to the kaleidoscope pattern after the radial fold.\n *\n * - `none` — no wrapping; the pattern is sampled directly.\n * - `repeat` — `fract(uv * tiling)` square repeats (the historical behavior).\n * - `mirror` — mirrored repeats for seamless edges.\n */\nexport type GbtScopeTileMode = 'mirror' | 'none' | 'repeat'\n\n/**\n * Canonical default values for {@link GbtScopeMaterialProps}. `src` is required and has no default. Imported by\n * component defaults and Storybook args so the defaults live in a single place.\n */\nexport const defaultGbtScopeMaterialProps = {\n imageAspect: 1,\n offset: [0, 0] as [number, number],\n offsetScale: 1,\n opacity: 1,\n rotation: 0,\n rotationScale: 1,\n scaleFactor: 1,\n segments: 6,\n tileMode: 'repeat' as GbtScopeTileMode,\n tiling: 1,\n} satisfies Omit<GbtScopeMaterialProps, 'src'>\n\n/**\n * Viewer-level props shared by both the flat and mesh viewers. Camera config is viewer-specific and declared on each\n * component. Material props are forwarded down to {@link GbtScopeMaterialProps}.\n */\nexport type GbtScopeViewerBaseProps = {\n /** Declarative motion rules applied each frame. */\n animators?: Array<GbtScopeAnimator>\n /** Aspect ratio of the host canvas. */\n aspect_ratio?: 'parent' | number\n bg_color?: string\n /** Fixed values replacing the live pointer/scroll inputs (mock/testing). */\n inputOverrides?: GbtScopeInputOverrides\n /** Canvas background; `'screen'` resolution matches the viewport. */\n resolution?: 'screen' | Dimensions | null\n}\n","import { type Mesh, ShaderMaterial, Texture } from '@babylonjs/core'\nimport { type ReactElement, useEffect, useRef } from 'react'\nimport { type Dimensions, getResolution } from '../helpers.ts'\nimport {\n fragmentShader,\n vertexShader,\n} from '../materials/shader-radial-symmetry.ts'\nimport {\n type GbtScopeAnimator,\n type GbtScopeInputOverrides,\n type GbtScopeState,\n} from '../motion/animator.ts'\nimport { createGbtScopeDriver } from '../motion/driver.ts'\nimport { type PointerStateHandle } from '../motion/pointer.ts'\nimport { type ScrollStateHandle } from '../motion/scroll.ts'\nimport {\n defaultGbtScopeMaterialProps,\n type GbtScopeMaterialProps,\n type GbtScopeTileMode,\n} from '../types.ts'\n\nexport type GbtScopeMaterialComponentProps = GbtScopeMaterialProps & {\n /** Declarative motion rules driven each frame. */\n animators?: Array<GbtScopeAnimator>\n /** Fixed values replacing the live pointer/scroll inputs (mock/testing). */\n inputOverrides?: GbtScopeInputOverrides\n /** Mesh the material is applied to. */\n mesh: Mesh | null\n name?: string\n onInit?: (material: ShaderMaterial) => void\n onUpdate?: (material: ShaderMaterial) => void\n /** Live pointer + scroll inputs (created by the viewer). */\n pointer: PointerStateHandle\n scroll: ScrollStateHandle\n}\n\nconst DEFAULT_DIMENSIONS: Dimensions = { height: 1200, width: 1200 }\n\n/** Maps a {@link GbtScopeTileMode} to the shader's `uTileMode` float. */\nconst tileModeToFloat = (mode: GbtScopeTileMode): number =>\n mode === 'none' ? 0 : mode === 'mirror' ? 2 : 1\n\nconst UNIFORMS = [\n 'worldViewProjection',\n 'uTexture',\n 'resolution',\n 'uOpacity',\n 'segments',\n 'uOffset',\n 'uRotation',\n 'uOffsetAmount',\n 'uRotationAmount',\n 'uScaleFactor',\n 'uImageAspect',\n 'uTiling',\n 'uTileMode',\n]\n\n/**\n * Kaleidoscope shader material applied to a Babylon mesh. Static uniforms update reactively from props; the animated\n * uniforms (rotation/offset/scaleFactor/ opacity) are driven each frame by {@link createGbtScopeDriver} from the\n * `animators` + live pointer/scroll inputs. No Babylon Animation is used.\n */\nconst GbtScopeMaterial = ({\n animators = [],\n dimensions = DEFAULT_DIMENSIONS,\n imageAspect = defaultGbtScopeMaterialProps.imageAspect,\n inputOverrides,\n mesh,\n name = 'kaleidoscope',\n offset = defaultGbtScopeMaterialProps.offset,\n offsetScale = defaultGbtScopeMaterialProps.offsetScale,\n onInit,\n onUpdate,\n opacity = defaultGbtScopeMaterialProps.opacity,\n pointer,\n rotation = defaultGbtScopeMaterialProps.rotation,\n rotationScale = defaultGbtScopeMaterialProps.rotationScale,\n scaleFactor = defaultGbtScopeMaterialProps.scaleFactor,\n scroll,\n segments = defaultGbtScopeMaterialProps.segments,\n src,\n tileMode = defaultGbtScopeMaterialProps.tileMode,\n tiling = defaultGbtScopeMaterialProps.tiling,\n}: GbtScopeMaterialComponentProps): null | ReactElement => {\n const materialRef = useRef<null | ShaderMaterial>(null)\n\n // Live refs read by the render-loop driver.\n const animatorsRef = useRef<Array<GbtScopeAnimator>>(animators)\n const overridesRef = useRef<GbtScopeInputOverrides | undefined>(\n inputOverrides,\n )\n const stateRef = useRef<GbtScopeState>({\n offset: [offset[0], offset[1]],\n opacity,\n rotation,\n scaleFactor,\n })\n\n // Create the material + driver once per mesh/src.\n // eslint-disable-next-line react-hooks/immutability -- Babylon meshes are imperative; assigning mesh.material inside the effect is the API.\n useEffect(() => {\n if (!src || !mesh) return undefined\n\n const scene = mesh.getScene()\n const material = new ShaderMaterial(\n name,\n scene,\n { fragmentSource: fragmentShader, vertexSource: vertexShader },\n { attributes: ['position', 'uv'], uniforms: UNIFORMS },\n )\n material.setTexture('uTexture', new Texture(src, scene, true, false))\n // eslint-disable-next-line react-hooks/immutability -- see above; Babylon mutation, not React state.\n mesh.material = material\n materialRef.current = material\n\n const disposeDriver = createGbtScopeDriver(scene, material, {\n animatorsRef,\n overridesRef,\n pointer,\n scroll,\n stateRef,\n })\n\n onInit?.(material)\n\n return (): void => {\n disposeDriver()\n material.dispose()\n materialRef.current = null\n }\n // Pointer/scroll are stable handles from the viewer; animators/state via refs.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mesh, src, name])\n\n // Keep animators live.\n useEffect(() => {\n animatorsRef.current = animators\n }, [animators])\n\n // Keep input overrides live.\n useEffect(() => {\n overridesRef.current = inputOverrides\n }, [inputOverrides])\n\n // Re-seed the runtime base state when resting values change (live controls).\n useEffect(() => {\n stateRef.current = {\n offset: [offset[0], offset[1]],\n opacity,\n rotation,\n scaleFactor,\n }\n }, [rotation, offset, scaleFactor, opacity])\n\n // Static uniforms — updated reactively (the driver owns the animated ones).\n useEffect(() => {\n const material = materialRef.current\n if (!material) return\n material.setVector4('resolution', getResolution(dimensions))\n material.setFloat('segments', segments)\n material.setFloat('uOffsetAmount', offsetScale)\n material.setFloat('uRotationAmount', rotationScale)\n material.setFloat('uImageAspect', imageAspect)\n material.setFloat('uTiling', tiling || 1)\n material.setFloat('uTileMode', tileModeToFloat(tileMode))\n onUpdate?.(material)\n }, [\n dimensions,\n segments,\n offsetScale,\n rotationScale,\n imageAspect,\n tiling,\n tileMode,\n onUpdate,\n ])\n\n return null\n}\n\nexport default GbtScopeMaterial\n","/** Current normalized pointer position, centered on the canvas, range [-1, 1]. */\nexport type PointerState = {\n readonly x: number\n readonly y: number\n}\n\nexport type PointerStateHandle = {\n /** Attach pointerdown/pointermove/pointerleave listeners to a canvas. */\n attach: (canvas: HTMLCanvasElement) => void\n /** Remove the listeners. Call from the scene's onDisposeObservable. */\n detach: (canvas: HTMLCanvasElement) => void\n /** Live pointer position. Mutated internally; read it inside a render loop. */\n state: PointerState\n}\n\n/**\n * Creates a mutable pointer-state object for use inside a Babylon.js scene setup callback. Intentionally NOT a React\n * hook: `onSceneReady` runs outside React's render cycle, so a hook's state would be stale inside the render\n * observable. The returned `state` object is mutated in place and is safe to read every frame.\n *\n * Position is normalized to [-1, 1] on both axes (canvas-relative). Uses pointer events so mouse and touch behave\n * uniformly: a mouse resets to [0, 0] on leaving the canvas, while a touch latches at the last tap/drag position (tap\n * center to zero it) — hover-less devices would otherwise never produce input.\n */\nexport const createPointerState = (): PointerStateHandle => {\n const _state = { x: 0, y: 0 }\n\n const handlePointerMove = (event: PointerEvent): void => {\n const canvas = event.currentTarget as HTMLCanvasElement\n const rect = canvas.getBoundingClientRect()\n _state.x = ((event.clientX - rect.left) / rect.width) * 2 - 1\n _state.y = ((event.clientY - rect.top) / rect.height) * 2 - 1\n }\n\n const handlePointerLeave = (event: PointerEvent): void => {\n // Touch pointers \"leave\" on every finger lift; keep their latch and only reset for mouse hover-out.\n if (event.pointerType !== 'mouse') return\n _state.x = 0\n _state.y = 0\n }\n\n return {\n attach: (canvas: HTMLCanvasElement): void => {\n canvas.addEventListener('pointerdown', handlePointerMove)\n canvas.addEventListener('pointermove', handlePointerMove)\n canvas.addEventListener('pointerleave', handlePointerLeave)\n },\n detach: (canvas: HTMLCanvasElement): void => {\n canvas.removeEventListener('pointerdown', handlePointerMove)\n canvas.removeEventListener('pointermove', handlePointerMove)\n canvas.removeEventListener('pointerleave', handlePointerLeave)\n },\n state: _state,\n }\n}\n","/** Current scroll signals. `progress` is [0,1]; `velocity` decays toward 0. */\nexport type ScrollState = {\n readonly progress: number\n readonly velocity: number\n}\n\nexport type ScrollStateHandle = {\n /** Attach scroll/wheel listeners (defaults to window). */\n attach: (target?: HTMLElement | Window) => void\n /**\n * Decay the velocity by one frame's worth (call once per frame from the driver after reading). `factor` in [0,1];\n * lower = faster decay.\n */\n decay: (factor?: number) => void\n /** Remove listeners. Call from the scene's onDisposeObservable. */\n detach: (target?: HTMLElement | Window) => void\n /** Live scroll signals. Mutated internally; read inside a render loop. */\n state: ScrollState\n}\n\nconst readProgress = (): number => {\n if (typeof window === 'undefined' || typeof document === 'undefined')\n return 0\n const doc = document.documentElement\n const max = doc.scrollHeight - doc.clientHeight\n return max > 0 ? Math.min(Math.max(window.scrollY / max, 0), 1) : 0\n}\n\n/**\n * Creates a mutable scroll-state object for use inside a Babylon.js scene setup callback. Tracks page scroll `progress`\n * [0,1] and a wheel-driven `velocity` that the driver decays each frame. Plain factory (not a React hook) — mirrors\n * {@link ./pointer.createPointerState} so it can be read from the render observable.\n */\nexport const createScrollState = (): ScrollStateHandle => {\n const _state = { progress: readProgress(), velocity: 0 }\n\n const handleWheel = (event: WheelEvent): void => {\n // Normalize wheel delta to a small per-event velocity contribution.\n _state.velocity += event.deltaY / 1000\n }\n\n const handleScroll = (): void => {\n _state.progress = readProgress()\n }\n\n const resolve = (target?: HTMLElement | Window): HTMLElement | Window =>\n target ?? (typeof window !== 'undefined' ? window : ({} as Window))\n\n return {\n attach: (target?: HTMLElement | Window): void => {\n const t = resolve(target)\n t.addEventListener('wheel', handleWheel as EventListener, {\n passive: true,\n })\n t.addEventListener('scroll', handleScroll, {\n passive: true,\n })\n },\n decay: (factor = 0.9): void => {\n _state.velocity *= factor\n if (Math.abs(_state.velocity) < 1e-4) _state.velocity = 0\n },\n detach: (target?: HTMLElement | Window): void => {\n const t = resolve(target)\n t.removeEventListener('wheel', handleWheel as EventListener)\n t.removeEventListener('scroll', handleScroll)\n },\n state: _state,\n }\n}\n","import {\n Color4,\n FreeCamera,\n HemisphericLight,\n type Mesh,\n MeshBuilder,\n type Scene,\n Vector3,\n} from '@babylonjs/core'\nimport { isValidColor, parseColorToHexStrict } from '@snailicid3/color'\nimport SceneComponent from 'babylonjs-hook'\nimport { type CSSProperties, type ReactElement, useState } from 'react'\nimport GbtScopeMaterial from './GbtScopeMaterial.tsx'\nimport {\n type CameraOrthoConfig,\n type Dimensions,\n setOrthoCamera,\n} from '../helpers.ts'\nimport { createPointerState } from '../motion/pointer.ts'\nimport { createScrollState } from '../motion/scroll.ts'\nimport {\n defaultGbtScopeMaterialProps,\n type GbtScopeMaterialProps,\n type GbtScopeViewerBaseProps,\n} from '../types.ts'\n\nexport type GbtScopeFlatViewerProps = GbtScopeViewerBaseProps &\n Omit<GbtScopeMaterialProps, 'dimensions'> & {\n cameraSettings?: CameraOrthoConfig\n name?: string\n }\n\n/** Default props for the flat viewer — single source of truth for Storybook args. */\n// eslint-disable-next-line react-refresh/only-export-components -- Storybook args belong beside the component; costs only HMR granularity.\nexport const defaultGbtScopeFlatViewerProps = {\n ...defaultGbtScopeMaterialProps,\n animators: [],\n aspect_ratio: 1 as 'parent' | number,\n bg_color: 'black',\n cameraSettings: {\n enabled: false,\n ortho: true,\n target: [0, 0, 0],\n } as CameraOrthoConfig,\n name: 'gbt-scope-flat',\n resolution: 'screen' as 'screen' | Dimensions | null,\n src: 'uv-checker.png',\n} satisfies GbtScopeFlatViewerProps\n\nconst GbtScopeFlatViewer = ({\n animators = [],\n aspect_ratio = 1,\n bg_color = 'black',\n cameraSettings = { enabled: false, ortho: true, target: [0, 0, 0] },\n imageAspect = defaultGbtScopeMaterialProps.imageAspect,\n inputOverrides,\n name = 'gbt-scope-flat',\n offset = defaultGbtScopeMaterialProps.offset,\n offsetScale = defaultGbtScopeMaterialProps.offsetScale,\n opacity = defaultGbtScopeMaterialProps.opacity,\n resolution = 'screen',\n rotation = defaultGbtScopeMaterialProps.rotation,\n rotationScale = defaultGbtScopeMaterialProps.rotationScale,\n scaleFactor = defaultGbtScopeMaterialProps.scaleFactor,\n segments = defaultGbtScopeMaterialProps.segments,\n src,\n tileMode = defaultGbtScopeMaterialProps.tileMode,\n tiling = defaultGbtScopeMaterialProps.tiling,\n}: GbtScopeFlatViewerProps): ReactElement => {\n const [scene, setScene] = useState<null | Scene>(null)\n const [plane, setPlane] = useState<Mesh | null>(null)\n\n // Stable input handles read by the material's render-loop driver.\n const [pointerState] = useState(createPointerState)\n const [scrollState] = useState(createScrollState)\n\n const customStyle: CSSProperties = {\n backgroundColor: isValidColor(bg_color)\n ? parseColorToHexStrict(bg_color)\n : 'initial',\n border: '2px solid green',\n margin: 0,\n padding: 0,\n position: 'relative',\n ...(aspect_ratio !== 'parent' ? { aspectRatio: aspect_ratio } : {}),\n }\n\n // Derived from resolution + scene; no state needed.\n const dimensions: Dimensions | undefined =\n resolution === 'screen'\n ? scene !== null\n ? {\n height: scene.getEngine().getRenderHeight(),\n width: scene.getEngine().getRenderWidth(),\n }\n : undefined\n : (resolution ?? undefined)\n\n const onSceneReady = (_scene: Scene): void => {\n _scene.clearColor = new Color4(0, 0, 0, 1)\n setScene(_scene)\n\n const camera = new FreeCamera(\n `camera_${name}`,\n new Vector3(0, 0, -10),\n _scene,\n )\n setOrthoCamera(_scene, camera, cameraSettings)\n\n new HemisphericLight(`light_${name}`, new Vector3(0, 1, 0), _scene)\n\n const planeMesh = MeshBuilder.CreatePlane(\n `plane_${name}`,\n {\n height: _scene.getEngine().getRenderHeight(),\n width: _scene.getEngine().getRenderWidth(),\n },\n _scene,\n )\n setPlane(planeMesh)\n\n const canvas = _scene.getEngine().getRenderingCanvas()\n if (canvas) {\n canvas.tabIndex = 1\n canvas.addEventListener('keydown', (event) => {\n if (event.key === 'Escape')\n setOrthoCamera(_scene, camera, cameraSettings)\n })\n pointerState.attach(canvas)\n scrollState.attach()\n _scene.onDisposeObservable.add(() => {\n pointerState.detach(canvas)\n scrollState.detach()\n })\n }\n }\n\n return (\n <div style={customStyle}>\n <SceneComponent\n antialias\n id=\"my-canvas\"\n onSceneReady={onSceneReady}\n style={{ height: '100%', width: '100%' }}>\n {scene && plane && (\n <GbtScopeMaterial\n animators={animators}\n dimensions={dimensions}\n imageAspect={imageAspect}\n inputOverrides={inputOverrides}\n mesh={plane}\n name={`material_${name}`}\n offset={offset}\n offsetScale={offsetScale}\n opacity={opacity}\n pointer={pointerState}\n rotation={rotation}\n rotationScale={rotationScale}\n scaleFactor={scaleFactor}\n scroll={scrollState}\n segments={segments}\n src={src}\n tileMode={tileMode}\n tiling={tiling}\n />\n )}\n </SceneComponent>\n </div>\n )\n}\n\nexport default GbtScopeFlatViewer\n","import {\n ArcRotateCamera,\n Color4,\n HemisphericLight,\n type Mesh,\n MeshBuilder,\n type Scene,\n Vector3,\n} from '@babylonjs/core'\nimport { isValidColor, parseColorToHexStrict } from '@snailicid3/color'\nimport SceneComponent from 'babylonjs-hook'\nimport { type CSSProperties, type ReactElement, useState } from 'react'\nimport GbtScopeMaterial from './GbtScopeMaterial.tsx'\nimport {\n type CameraConfigPosition,\n type Dimensions,\n setRotateCameraPosition,\n} from '../helpers.ts'\nimport { createPointerState } from '../motion/pointer.ts'\nimport { createScrollState } from '../motion/scroll.ts'\nimport {\n defaultGbtScopeMaterialProps,\n type GbtScopeMaterialProps,\n type GbtScopeViewerBaseProps,\n} from '../types.ts'\n\nexport type GbtScopeMeshViewerProps = GbtScopeViewerBaseProps &\n Omit<GbtScopeMaterialProps, 'dimensions'> & {\n cameraSettings?: CameraConfigPosition\n name?: string\n }\n\n/** Default props for the 3D mesh viewer — single source of truth for Storybook args. */\n// eslint-disable-next-line react-refresh/only-export-components -- Storybook args belong beside the component; costs only HMR granularity.\nexport const defaultGbtScopeMeshViewerProps = {\n ...defaultGbtScopeMaterialProps,\n animators: [],\n aspect_ratio: 1 as 'parent' | number,\n bg_color: 'black',\n cameraSettings: {\n enabled: true,\n hRotation: Math.PI / 2,\n vRotation: Math.PI / 4,\n } as CameraConfigPosition,\n name: 'gbt-scope-mesh',\n resolution: null as 'screen' | Dimensions | null,\n src: 'uv-checker.png',\n} satisfies GbtScopeMeshViewerProps\n\nconst GbtScopeMeshViewer = ({\n animators = [],\n aspect_ratio = 1,\n bg_color = 'black',\n cameraSettings = {\n enabled: true,\n hRotation: Math.PI / 2,\n vRotation: Math.PI / 4,\n },\n imageAspect = defaultGbtScopeMaterialProps.imageAspect,\n inputOverrides,\n name = 'gbt-scope-mesh',\n offset = defaultGbtScopeMaterialProps.offset,\n offsetScale = defaultGbtScopeMaterialProps.offsetScale,\n opacity = defaultGbtScopeMaterialProps.opacity,\n resolution = null,\n rotation = defaultGbtScopeMaterialProps.rotation,\n rotationScale = defaultGbtScopeMaterialProps.rotationScale,\n scaleFactor = defaultGbtScopeMaterialProps.scaleFactor,\n segments = defaultGbtScopeMaterialProps.segments,\n src,\n tileMode = defaultGbtScopeMaterialProps.tileMode,\n tiling = defaultGbtScopeMaterialProps.tiling,\n}: GbtScopeMeshViewerProps): ReactElement => {\n const [scene, setScene] = useState<null | Scene>(null)\n const [box, setBox] = useState<Mesh | null>(null)\n\n // Stable input handles read by the material's render-loop driver.\n const [pointerState] = useState(createPointerState)\n const [scrollState] = useState(createScrollState)\n\n const customStyle: CSSProperties = {\n backgroundColor: isValidColor(bg_color)\n ? parseColorToHexStrict(bg_color)\n : 'initial',\n border: '2px solid green',\n ...(aspect_ratio !== 'parent' ? { aspectRatio: aspect_ratio } : {}),\n }\n\n // Derived from resolution + scene; no state needed.\n const dimensions: Dimensions | undefined =\n resolution === 'screen'\n ? scene !== null\n ? {\n height: scene.getEngine().getRenderHeight(),\n width: scene.getEngine().getRenderWidth(),\n }\n : undefined\n : (resolution ?? undefined)\n\n const onSceneReady = (_scene: Scene): void => {\n _scene.clearColor = new Color4(0, 0, 0, 0)\n setScene(_scene)\n\n const camera = new ArcRotateCamera(\n `camera_${name}`,\n cameraSettings.hRotation ?? Math.PI / 2,\n cameraSettings.vRotation ?? Math.PI / 4,\n 10,\n Vector3.Zero(),\n _scene,\n )\n setRotateCameraPosition(camera, _scene, cameraSettings)\n\n new HemisphericLight(`light_${name}`, new Vector3(0, 1, 0), _scene)\n\n const boxMesh = MeshBuilder.CreateBox(\n `box_${name}`,\n { size: 2 },\n _scene,\n )\n boxMesh.position.y = 1\n setBox(boxMesh)\n\n const canvas = _scene.getEngine().getRenderingCanvas()\n if (canvas) {\n canvas.tabIndex = 1\n canvas.addEventListener('keydown', (event) => {\n if (event.key === 'Escape')\n setRotateCameraPosition(camera, _scene, cameraSettings)\n })\n pointerState.attach(canvas)\n scrollState.attach()\n _scene.onDisposeObservable.add(() => {\n pointerState.detach(canvas)\n scrollState.detach()\n })\n }\n }\n\n return (\n <div style={customStyle}>\n <SceneComponent\n antialias\n id=\"my-canvas\"\n onSceneReady={onSceneReady}\n style={{ height: '100%', width: '100%' }}>\n {scene && box && (\n <GbtScopeMaterial\n animators={animators}\n dimensions={dimensions}\n imageAspect={imageAspect}\n inputOverrides={inputOverrides}\n mesh={box}\n name={`material_${name}`}\n offset={offset}\n offsetScale={offsetScale}\n opacity={opacity}\n pointer={pointerState}\n rotation={rotation}\n rotationScale={rotationScale}\n scaleFactor={scaleFactor}\n scroll={scrollState}\n segments={segments}\n src={src}\n tileMode={tileMode}\n tiling={tiling}\n />\n )}\n </SceneComponent>\n </div>\n )\n}\n\nexport default GbtScopeMeshViewer\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,119 @@
1
+ {
2
+ "name": "@snailicid3/gbt-scope",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "description": "React components and hooks for the operator user interface.",
6
+ "nx": {
7
+ "targets": {
8
+ "dev": {},
9
+ "dev:ts": {},
10
+ "dev:tsdown": {},
11
+ "dev:vite": {},
12
+ "check:ts": {},
13
+ "build:tsdown": {},
14
+ "build:ts": {},
15
+ "build": {
16
+ "dependsOn": [
17
+ "build:ts",
18
+ "build:tsdown"
19
+ ]
20
+ },
21
+ "build:storybook": {},
22
+ "chromatic": {
23
+ "command": "pnpm exec chromatic --project-token=$CHROMATIC_GBT_SCOPE_PROJECT_TOKEN --build-script-name build:storybook:nx"
24
+ },
25
+ "test": {},
26
+ "test:watch": {},
27
+ "lint": {},
28
+ "fix": {},
29
+ "clean": {},
30
+ "clean:ts": {},
31
+ "clean:build": {}
32
+ }
33
+ },
34
+ "dependencies": {
35
+ "@babylonjs/core": "^9.18.1",
36
+ "@babylonjs/gui": "^9.18.1",
37
+ "@snailicid3/color": "^0.0.7",
38
+ "babylonjs-hook": "^0.1.1",
39
+ "type-fest": "^5.8.0",
40
+ "use-resize-observer": "^10.0.0",
41
+ "zod": "^4.4.3"
42
+ },
43
+ "devDependencies": {
44
+ "@chromatic-com/storybook": "^5.3.1",
45
+ "@snailicid3/build-config": "^0.2.0",
46
+ "@snailicid3/config": "^0.3.2",
47
+ "@snailicid3/storybook-config": "^0.1.3",
48
+ "@storybook/addon-a11y": "^10.6.0",
49
+ "@storybook/addon-docs": "^10.6.0",
50
+ "@storybook/addon-vitest": "^10.6.0",
51
+ "@storybook/react-vite": "^10.6.0",
52
+ "@testing-library/jest-dom": "^7.0.1",
53
+ "@testing-library/react": "^16.3.3",
54
+ "@types/node": "^26.5.0",
55
+ "@types/react": "^19.2.18",
56
+ "@types/react-dom": "^19.2.7",
57
+ "@vitejs/plugin-react": "^6.1.1",
58
+ "@vitest/coverage-v8": "5.0.0",
59
+ "chromatic": "^18.7.2",
60
+ "cross-var": "^1.1.0",
61
+ "dotenv-cli": "^11.0.0",
62
+ "jsdom": "^30.0.1",
63
+ "react": "^19.2.8",
64
+ "react-dom": "^19.2.8",
65
+ "storybook": "^10.6.0",
66
+ "tsdown": "^0.22.14",
67
+ "typescript": "~6.0.3",
68
+ "vitest": "^5.0.0"
69
+ },
70
+ "type": "module",
71
+ "main": "./dist/index.cjs",
72
+ "module": "./dist/index.js",
73
+ "types": "./dist/index.d.ts",
74
+ "exports": {
75
+ ".": {
76
+ "types": "./dist/index.d.ts",
77
+ "import": "./dist/index.js",
78
+ "require": "./dist/index.cjs",
79
+ "default": "./dist/index.js"
80
+ },
81
+ "./package.json": "./package.json"
82
+ },
83
+ "author": {
84
+ "name": "Gillian Tunney",
85
+ "email": "gbtunney@mac.com"
86
+ },
87
+ "license": "MIT",
88
+ "repository": {
89
+ "type": "git",
90
+ "url": "https://github.com/gbtunney/gbt-monorepov2",
91
+ "directory": "packages/gbt-scope"
92
+ },
93
+ "homepage": "https://github.com/gbtunney/gbt-monorepov2/tree/main/packages/gbt-scope#readme",
94
+ "publishConfig": {
95
+ "access": "public"
96
+ },
97
+ "files": [
98
+ "CHANGELOG.md",
99
+ "dist",
100
+ "types"
101
+ ],
102
+ "keywords": [
103
+ "boilerplate"
104
+ ],
105
+ "peerDependencies": {
106
+ "react": "^19.2.0",
107
+ "react-dom": "^19.2.0"
108
+ },
109
+ "scripts": {
110
+ "build:nx": "nx run $npm_package_name:build",
111
+ "build:storybook:nx": "nx run $npm_package_name:build:storybook",
112
+ "dev:nx": "nx run $npm_package_name:dev",
113
+ "dev:storybook": "pnpm exec nx run -p $npm_package_name -t build:ts check:ts && pnpm exec storybook dev -p 6006",
114
+ "test:nx": "nx run $npm_package_name:test",
115
+ "clean:nx": "nx run $npm_package_name:clean",
116
+ "chromatic:nx": "nx run $npm_package_name:chromatic",
117
+ "test:chromatic": "pnpm exec dotenv -- pnpm chromatic:nx"
118
+ }
119
+ }
@@ -0,0 +1,30 @@
1
+ import { type ReactElement } from 'react';
2
+ import { type CameraOrthoConfig, type Dimensions } from '../helpers.ts';
3
+ import { type GbtScopeMaterialProps, type GbtScopeViewerBaseProps } from '../types.ts';
4
+ export type GbtScopeFlatViewerProps = GbtScopeViewerBaseProps & Omit<GbtScopeMaterialProps, 'dimensions'> & {
5
+ cameraSettings?: CameraOrthoConfig;
6
+ name?: string;
7
+ };
8
+ /** Default props for the flat viewer — single source of truth for Storybook args. */
9
+ export declare const defaultGbtScopeFlatViewerProps: {
10
+ animators: never[];
11
+ aspect_ratio: "parent" | number;
12
+ bg_color: string;
13
+ cameraSettings: CameraOrthoConfig;
14
+ name: string;
15
+ resolution: "screen" | Dimensions | null;
16
+ src: string;
17
+ imageAspect: number;
18
+ offset: [number, number];
19
+ offsetScale: number;
20
+ opacity: number;
21
+ rotation: number;
22
+ rotationScale: number;
23
+ scaleFactor: number;
24
+ segments: number;
25
+ tileMode: import("../types.ts").GbtScopeTileMode;
26
+ tiling: number;
27
+ };
28
+ declare const GbtScopeFlatViewer: ({ animators, aspect_ratio, bg_color, cameraSettings, imageAspect, inputOverrides, name, offset, offsetScale, opacity, resolution, rotation, rotationScale, scaleFactor, segments, src, tileMode, tiling, }: GbtScopeFlatViewerProps) => ReactElement;
29
+ export default GbtScopeFlatViewer;
30
+ //# sourceMappingURL=GbtScopeFlatViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GbtScopeFlatViewer.d.ts","sourceRoot":"","sources":["../../src/components/GbtScopeFlatViewer.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAsB,KAAK,YAAY,EAAY,MAAM,OAAO,CAAA;AAEvE,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,UAAU,EAElB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAEH,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC/B,MAAM,aAAa,CAAA;AAEpB,MAAM,MAAM,uBAAuB,GAAG,uBAAuB,GACzD,IAAI,CAAC,qBAAqB,EAAE,YAAY,CAAC,GAAG;IACxC,cAAc,CAAC,EAAE,iBAAiB,CAAA;IAClC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAEL,qFAAqF;AAErF,eAAO,MAAM,8BAA8B;;kBAGpB,QAAQ,GAAG,MAAM;;oBAM/B,iBAAiB;;gBAEE,QAAQ,GAAG,UAAU,GAAG,IAAI;;;;;;;;;;;;CAErB,CAAA;AAEnC,QAAA,MAAM,kBAAkB,GAAI,4MAmBzB,uBAAuB,KAAG,YAqG5B,CAAA;AAED,eAAe,kBAAkB,CAAA"}
@@ -0,0 +1,28 @@
1
+ import { type Mesh, ShaderMaterial } from '@babylonjs/core';
2
+ import { type ReactElement } from 'react';
3
+ import { type GbtScopeAnimator, type GbtScopeInputOverrides } from '../motion/animator.ts';
4
+ import { type PointerStateHandle } from '../motion/pointer.ts';
5
+ import { type ScrollStateHandle } from '../motion/scroll.ts';
6
+ import { type GbtScopeMaterialProps } from '../types.ts';
7
+ export type GbtScopeMaterialComponentProps = GbtScopeMaterialProps & {
8
+ /** Declarative motion rules driven each frame. */
9
+ animators?: Array<GbtScopeAnimator>;
10
+ /** Fixed values replacing the live pointer/scroll inputs (mock/testing). */
11
+ inputOverrides?: GbtScopeInputOverrides;
12
+ /** Mesh the material is applied to. */
13
+ mesh: Mesh | null;
14
+ name?: string;
15
+ onInit?: (material: ShaderMaterial) => void;
16
+ onUpdate?: (material: ShaderMaterial) => void;
17
+ /** Live pointer + scroll inputs (created by the viewer). */
18
+ pointer: PointerStateHandle;
19
+ scroll: ScrollStateHandle;
20
+ };
21
+ /**
22
+ * Kaleidoscope shader material applied to a Babylon mesh. Static uniforms update reactively from props; the animated
23
+ * uniforms (rotation/offset/scaleFactor/ opacity) are driven each frame by {@link createGbtScopeDriver} from the
24
+ * `animators` + live pointer/scroll inputs. No Babylon Animation is used.
25
+ */
26
+ declare const GbtScopeMaterial: ({ animators, dimensions, imageAspect, inputOverrides, mesh, name, offset, offsetScale, onInit, onUpdate, opacity, pointer, rotation, rotationScale, scaleFactor, scroll, segments, src, tileMode, tiling, }: GbtScopeMaterialComponentProps) => null | ReactElement;
27
+ export default GbtScopeMaterial;
28
+ //# sourceMappingURL=GbtScopeMaterial.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GbtScopeMaterial.d.ts","sourceRoot":"","sources":["../../src/components/GbtScopeMaterial.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,cAAc,EAAW,MAAM,iBAAiB,CAAA;AACpE,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,OAAO,CAAA;AAM5D,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAE9B,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAEH,KAAK,qBAAqB,EAE7B,MAAM,aAAa,CAAA;AAEpB,MAAM,MAAM,8BAA8B,GAAG,qBAAqB,GAAG;IACjE,kDAAkD;IAClD,SAAS,CAAC,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAA;IACnC,4EAA4E;IAC5E,cAAc,CAAC,EAAE,sBAAsB,CAAA;IACvC,uCAAuC;IACvC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAA;IAC7C,4DAA4D;IAC5D,OAAO,EAAE,kBAAkB,CAAA;IAC3B,MAAM,EAAE,iBAAiB,CAAA;CAC5B,CAAA;AAwBD;;;;GAIG;AACH,QAAA,MAAM,gBAAgB,GAAI,6MAqBvB,8BAA8B,KAAG,IAAI,GAAG,YA+F1C,CAAA;AAED,eAAe,gBAAgB,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { type ReactElement } from 'react';
2
+ import { type CameraConfigPosition, type Dimensions } from '../helpers.ts';
3
+ import { type GbtScopeMaterialProps, type GbtScopeViewerBaseProps } from '../types.ts';
4
+ export type GbtScopeMeshViewerProps = GbtScopeViewerBaseProps & Omit<GbtScopeMaterialProps, 'dimensions'> & {
5
+ cameraSettings?: CameraConfigPosition;
6
+ name?: string;
7
+ };
8
+ /** Default props for the 3D mesh viewer — single source of truth for Storybook args. */
9
+ export declare const defaultGbtScopeMeshViewerProps: {
10
+ animators: never[];
11
+ aspect_ratio: "parent" | number;
12
+ bg_color: string;
13
+ cameraSettings: CameraConfigPosition;
14
+ name: string;
15
+ resolution: "screen" | Dimensions | null;
16
+ src: string;
17
+ imageAspect: number;
18
+ offset: [number, number];
19
+ offsetScale: number;
20
+ opacity: number;
21
+ rotation: number;
22
+ rotationScale: number;
23
+ scaleFactor: number;
24
+ segments: number;
25
+ tileMode: import("../types.ts").GbtScopeTileMode;
26
+ tiling: number;
27
+ };
28
+ declare const GbtScopeMeshViewer: ({ animators, aspect_ratio, bg_color, cameraSettings, imageAspect, inputOverrides, name, offset, offsetScale, opacity, resolution, rotation, rotationScale, scaleFactor, segments, src, tileMode, tiling, }: GbtScopeMeshViewerProps) => ReactElement;
29
+ export default GbtScopeMeshViewer;
30
+ //# sourceMappingURL=GbtScopeMeshViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GbtScopeMeshViewer.d.ts","sourceRoot":"","sources":["../../src/components/GbtScopeMeshViewer.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAsB,KAAK,YAAY,EAAY,MAAM,OAAO,CAAA;AAEvE,OAAO,EACH,KAAK,oBAAoB,EACzB,KAAK,UAAU,EAElB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAEH,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC/B,MAAM,aAAa,CAAA;AAEpB,MAAM,MAAM,uBAAuB,GAAG,uBAAuB,GACzD,IAAI,CAAC,qBAAqB,EAAE,YAAY,CAAC,GAAG;IACxC,cAAc,CAAC,EAAE,oBAAoB,CAAA;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAEL,wFAAwF;AAExF,eAAO,MAAM,8BAA8B;;kBAGpB,QAAQ,GAAG,MAAM;;oBAM/B,oBAAoB;;gBAEL,QAAQ,GAAG,UAAU,GAAG,IAAI;;;;;;;;;;;;CAEjB,CAAA;AAEnC,QAAA,MAAM,kBAAkB,GAAI,4MAuBzB,uBAAuB,KAAG,YAmG5B,CAAA;AAED,eAAe,kBAAkB,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { type ArcRotateCamera, type Color3, type FreeCamera, type Scene, type Vector2, Vector3, Vector4 } from '@babylonjs/core';
2
+ export type Dimensions = {
3
+ height: number;
4
+ width: number;
5
+ };
6
+ export type Point = {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ export type XY = [number, number];
11
+ export declare const getResolution: (dimensions: Dimensions) => Vector4;
12
+ export declare const distanceBetweenPoints: (pointA: Point, pointB: Point) => number;
13
+ export type RGBColor = ConstructorParameters<typeof Color3>;
14
+ export type Vector2Params = ConstructorParameters<typeof Vector2>;
15
+ export type Vector3Params = ConstructorParameters<typeof Vector3>;
16
+ export type Vector4Params = ConstructorParameters<typeof Vector4>;
17
+ export type CameraConfigPosition = Partial<{
18
+ enabled: boolean;
19
+ hRotation: number; /** Alpha Math.PI / 2, // Alpha (horizontal rotation) */
20
+ /** Slow down the zoom speed */
21
+ mouseWheelSpeed: number;
22
+ position: Vector3Params;
23
+ radius: number;
24
+ target: Vector3Params;
25
+ vRotation: number; /** Beta Math.PI / 4, // Beta (vertical rotation) */
26
+ }>;
27
+ export type CameraOrthoConfig = Pick<CameraConfigPosition, 'enabled' | 'target'> & {
28
+ ortho?: true;
29
+ };
30
+ export declare const setOrthoCamera: (scene: Scene, camera: FreeCamera, { enabled, ortho, target }: CameraOrthoConfig) => FreeCamera;
31
+ export declare const setRotateCameraPosition: (camera: ArcRotateCamera, scene: Scene, { enabled, hRotation, mouseWheelSpeed, position, radius, target, vRotation, }: CameraConfigPosition) => void;
32
+ declare const _default: {};
33
+ export default _default;
34
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,eAAe,EAEpB,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,OAAO,EACZ,OAAO,EACP,OAAO,EACV,MAAM,iBAAiB,CAAA;AAExB,MAAM,MAAM,UAAU,GAAG;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CAChB,CAAA;AACD,MAAM,MAAM,KAAK,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC5C,MAAM,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACjC,eAAO,MAAM,aAAa,GAAI,YAAY,UAAU,KAAG,OAKtD,CAAA;AACD,eAAO,MAAM,qBAAqB,GAAI,QAAQ,KAAK,EAAE,QAAQ,KAAK,KAAG,MACb,CAAA;AAExD,MAAM,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAA;AAC3D,MAAM,MAAM,aAAa,GAAG,qBAAqB,CAAC,OAAO,OAAO,CAAC,CAAA;AACjE,MAAM,MAAM,aAAa,GAAG,qBAAqB,CAAC,OAAO,OAAO,CAAC,CAAA;AACjE,MAAM,MAAM,aAAa,GAAG,qBAAqB,CAAC,OAAO,OAAO,CAAC,CAAA;AAGjE,MAAM,MAAM,oBAAoB,GAAG,OAAO,CAAC;IACvC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA,CAAC,wDAAwD;IAC1E,+BAA+B;IAC/B,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,aAAa,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,aAAa,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA,CAAC,oDAAoD;CACzE,CAAC,CAAA;AACF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAChC,oBAAoB,EACpB,SAAS,GAAG,QAAQ,CACvB,GAAG;IAAE,KAAK,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AACpB,eAAO,MAAM,cAAc,GACvB,OAAO,KAAK,EACZ,QAAQ,UAAU,EAClB,4BAAsD,iBAAiB,KACxE,UAUF,CAAA;AACD,eAAO,MAAM,uBAAuB,GAChC,QAAQ,eAAe,EACvB,OAAO,KAAK,EACZ,+EAQG,oBAAoB,KACxB,IAYF,CAAA;;AACD,wBAAiB"}
@@ -0,0 +1,18 @@
1
+ /** Public API barrel for `@snailicid3/gbt-scope` */
2
+ /** Flat (non-3D) viewer */
3
+ export { defaultGbtScopeFlatViewerProps, default as GbtScopeFlatViewer, type GbtScopeFlatViewerProps, } from './components/GbtScopeFlatViewer.tsx';
4
+ /** Core shader material */
5
+ export { default as GbtScopeMaterial, type GbtScopeMaterialComponentProps, } from './components/GbtScopeMaterial.tsx';
6
+ /** 3D mesh viewer */
7
+ export { defaultGbtScopeMeshViewerProps, default as GbtScopeMeshViewer, type GbtScopeMeshViewerProps, } from './components/GbtScopeMeshViewer.tsx';
8
+ /** Helpers */
9
+ export { type Dimensions, type Point, type XY } from './helpers.ts';
10
+ /** Motion: animator system */
11
+ export { applyAnimators, type GbtScopeAnimator, type GbtScopeAnimatorSource, type GbtScopeAnimatorTarget, type GbtScopeInputOverrides, type GbtScopeInputs, type GbtScopeState, } from './motion/animator.ts';
12
+ /** Motion: curve + inputs */
13
+ export { applyCurve, isTupleCurve, tupleToGbtScopeCurve, } from './motion/curve.ts';
14
+ export { createPointerState, type PointerState, type PointerStateHandle, } from './motion/pointer.ts';
15
+ export { createScrollState, type ScrollState, type ScrollStateHandle, } from './motion/scroll.ts';
16
+ /** Shared types + material defaults */
17
+ export { defaultGbtScopeMaterialProps, type GbtScopeCurve, type GbtScopeMaterialProps, type GbtScopeTileMode, type GbtScopeViewerBaseProps, } from './types.ts';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AAEpD,2BAA2B;AAC3B,OAAO,EACH,8BAA8B,EAC9B,OAAO,IAAI,kBAAkB,EAC7B,KAAK,uBAAuB,GAC/B,MAAM,qCAAqC,CAAA;AAE5C,2BAA2B;AAC3B,OAAO,EACH,OAAO,IAAI,gBAAgB,EAC3B,KAAK,8BAA8B,GACtC,MAAM,mCAAmC,CAAA;AAE1C,qBAAqB;AACrB,OAAO,EACH,8BAA8B,EAC9B,OAAO,IAAI,kBAAkB,EAC7B,KAAK,uBAAuB,GAC/B,MAAM,qCAAqC,CAAA;AAE5C,cAAc;AACd,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAA;AAEnE,8BAA8B;AAC9B,OAAO,EACH,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,aAAa,GACrB,MAAM,sBAAsB,CAAA;AAE7B,6BAA6B;AAC7B,OAAO,EACH,UAAU,EACV,YAAY,EACZ,oBAAoB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,kBAAkB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,iBAAiB,GACzB,MAAM,oBAAoB,CAAA;AAE3B,uCAAuC;AACvC,OAAO,EACH,4BAA4B,EAC5B,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC/B,MAAM,YAAY,CAAA"}
@@ -0,0 +1,5 @@
1
+ declare const _default: {};
2
+ export default _default;
3
+ export declare const vertexShader = "\nprecision highp float;\n\n// Attributes\nattribute vec3 position;\nattribute vec2 uv;\n\n// Uniforms\nuniform mat4 worldViewProjection;\n\n// Varying\nvarying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = worldViewProjection * vec4(position, 1.0);\n}\n";
4
+ export declare const fragmentShader = "\nprecision mediump float;\n\n// Uniforms\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nuniform float uOpacity;\nuniform float segments;\nuniform vec2 uOffset;\nuniform float uRotation;\nuniform float uOffsetAmount;\nuniform float uRotationAmount;\nuniform float uScaleFactor;\nuniform float uImageAspect;\nuniform float uTiling; // Tiling factor for the entire pattern\nuniform float uTileMode; // 0 = none, 1 = repeat, 2 = mirror\n\n// Varying\nvarying vec2 vUv;\n\nconst float PI = 3.14159265359;\n\nvec2 adjustUV(vec2 uv, vec2 offset, float rotation) {\n float cosRot = cos(rotation * uRotationAmount);\n float sinRot = sin(rotation * uRotationAmount);\n mat2 rotMat = mat2(cosRot, -sinRot, sinRot, cosRot);\n vec2 rotatedUV = rotMat * (uv - vec2(0.5)) + vec2(0.5); // Apply rotation first\n return rotatedUV + offset * uOffsetAmount; // Apply offset after rotation\n}\n\n// Mirrored repeat: even tiles use fract, odd tiles use the reversed fract,\n// producing seamless mirrored edges instead of hard wraps.\nvec2 mirrorUV(vec2 uv) {\n vec2 tile = floor(uv);\n vec2 odd = mod(tile, 2.0);\n return mix(fract(uv), 1.0 - fract(uv), odd);\n}\n\nvoid main() {\n // Adjust UV coordinates for resolution\n vec2 newUV = (vUv - vec2(0.5)) * resolution.zw + vec2(0.5);\n vec2 uv = newUV * 2.0 - 1.0;\n\n // Convert to polar coordinates\n float angle = atan(uv.y, uv.x);\n float radius = length(uv);\n\n // Apply kaleidoscope effect\n float segment = PI * 2.0 / segments;\n angle = mod(angle, segment);\n angle = segment - abs(segment / 2.0 - angle);\n uv = radius * vec2(cos(angle), sin(angle));\n\n // Scale the pattern\n float scale = 1.0 / uScaleFactor;\n \n // Apply tiling to the entire pattern based on the selected tile mode\n vec2 scaledUV = uv * uTiling;\n vec2 tiledUV;\n if (uTileMode < 0.5) {\n tiledUV = uv; // none: no wrapping\n } else if (uTileMode < 1.5) {\n tiledUV = fract(scaledUV); // repeat: square repeats (historical behavior)\n } else {\n tiledUV = mirrorUV(scaledUV); // mirror: seamless mirrored repeats\n }\n\n // Adjust UV for texture sampling\n vec2 adjustedUV = adjustUV(tiledUV * scale + scale, uOffset, uRotation);\n vec2 aspectCorrectedUV = vec2(adjustedUV.x, adjustedUV.y * uImageAspect);\n\n // Sample the texture\n vec4 color = texture2D(uTexture, aspectCorrectedUV);\n color.a *= uOpacity;\n\n // Output the final color\n gl_FragColor = color;\n}\n";
5
+ //# sourceMappingURL=shader-radial-symmetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shader-radial-symmetry.d.ts","sourceRoot":"","sources":["../../src/materials/shader-radial-symmetry.ts"],"names":[],"mappings":";AAAA,wBAAiB;AACjB,eAAO,MAAM,YAAY,kRAiBxB,CAAA;AAED,eAAO,MAAM,cAAc,+9EA8E1B,CAAA"}
@@ -0,0 +1,42 @@
1
+ import { type GbtScopeCurve } from '../types.ts';
2
+ /**
3
+ * A single declarative animation rule: read `source`, shape it through `curve`, scale by `speed * delta`, then `add` to
4
+ * (default) or `set` the `target`.
5
+ */
6
+ export type GbtScopeAnimator = {
7
+ curve?: GbtScopeCurve;
8
+ mode?: 'add' | 'set';
9
+ source: GbtScopeAnimatorSource;
10
+ speed?: number;
11
+ target: GbtScopeAnimatorTarget;
12
+ };
13
+ /** Input signal an animator reads from. */
14
+ export type GbtScopeAnimatorSource = 'mouseDistance' | 'scrollProgress' | 'scrollVelocity' | 'time';
15
+ /** Uniform-backed value an animator can drive. */
16
+ export type GbtScopeAnimatorTarget = 'offset.x' | 'offset.y' | 'opacity' | 'rotation' | 'scaleFactor';
17
+ /**
18
+ * Fixed values that replace the live pointer/scroll signals in the driver — for mocking motion input (device-free
19
+ * testing, deterministic demos). A defined field wins over the real input; `undefined` fields fall through.
20
+ */
21
+ export type GbtScopeInputOverrides = Partial<Pick<GbtScopeInputs, 'mouseDistance' | 'scrollProgress' | 'scrollVelocity'>>;
22
+ /** Per-frame inputs fed to the animators. */
23
+ export type GbtScopeInputs = {
24
+ delta: number;
25
+ mouseDistance: number;
26
+ scrollProgress: number;
27
+ scrollVelocity: number;
28
+ time: number;
29
+ };
30
+ /** Mutable, uniform-facing animation state. */
31
+ export type GbtScopeState = {
32
+ offset: [number, number];
33
+ opacity: number;
34
+ rotation: number;
35
+ scaleFactor: number;
36
+ };
37
+ /**
38
+ * Applies every animator to a copy of `state` for one frame and returns the new state. Each animator's source value is
39
+ * curved, scaled by `speed * delta` (frame-rate independent), then added to or set on its target.
40
+ */
41
+ export declare const applyAnimators: (state: GbtScopeState, animators: Array<GbtScopeAnimator>, inputs: GbtScopeInputs) => GbtScopeState;
42
+ //# sourceMappingURL=animator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animator.d.ts","sourceRoot":"","sources":["../../src/motion/animator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC3B,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;IACpB,MAAM,EAAE,sBAAsB,CAAA;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,sBAAsB,CAAA;CACjC,CAAA;AAED,2CAA2C;AAC3C,MAAM,MAAM,sBAAsB,GAC9B,eAAe,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,MAAM,CAAA;AAElE,kDAAkD;AAClD,MAAM,MAAM,sBAAsB,GAC9B,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;AAEpE;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,CACxC,IAAI,CAAC,cAAc,EAAE,eAAe,GAAG,gBAAgB,GAAG,gBAAgB,CAAC,CAC9E,CAAA;AAED,6CAA6C;AAC7C,MAAM,MAAM,cAAc,GAAG;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;CACf,CAAA;AAED,+CAA+C;AAC/C,MAAM,MAAM,aAAa,GAAG;IACxB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACtB,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,GACvB,OAAO,aAAa,EACpB,WAAW,KAAK,CAAC,gBAAgB,CAAC,EAClC,QAAQ,cAAc,KACvB,aA0CF,CAAA"}
@@ -0,0 +1,18 @@
1
+ import { type GbtScopeCurve } from '../types.ts';
2
+ /**
3
+ * Maps an input value through a {@link GbtScopeCurve}: deadzone → gain → exponent → clamp → optional invert. The input
4
+ * is treated by magnitude (`Math.abs`), so direction is supplied by `invert`, not the sign of `value`.
5
+ */
6
+ export declare const applyCurve: (value: number, curve?: GbtScopeCurve) => number;
7
+ /**
8
+ * Type guard distinguishing the legacy `[min, max]` tuple form of `mouse_curve` from the richer {@link GbtScopeCurve}
9
+ * object form.
10
+ */
11
+ export declare const isTupleCurve: (value: [number, number] | GbtScopeCurve) => value is [number, number];
12
+ /**
13
+ * Bridges the legacy `[min, max]` tuple (plus a separate `mouse_multiplier`) into an equivalent {@link GbtScopeCurve}.
14
+ * With the historical defaults `[0, 0.015]` and multiplier `0.01`, `applyCurve` reproduces the old inline
15
+ * `Math.min(Math.max(dist * mult, min), max)` clamp exactly.
16
+ */
17
+ export declare const tupleToGbtScopeCurve: (tuple: [number, number], multiplier?: number) => GbtScopeCurve;
18
+ //# sourceMappingURL=curve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curve.d.ts","sourceRoot":"","sources":["../../src/motion/curve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD;;;GAGG;AACH,eAAO,MAAM,UAAU,GACnB,OAAO,MAAM,EACb,QAAO,aAAkB,KAC1B,MAaF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,GACrB,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,KACxC,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM,CAS1B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC7B,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,EACvB,mBAAc,KACf,aAID,CAAA"}
@@ -0,0 +1,28 @@
1
+ import { type Scene, type ShaderMaterial } from '@babylonjs/core';
2
+ import { type GbtScopeAnimator, type GbtScopeInputOverrides, type GbtScopeState } from './animator.ts';
3
+ import { type PointerStateHandle } from './pointer.ts';
4
+ import { type ScrollStateHandle } from './scroll.ts';
5
+ export type GbtScopeDriverOptions = {
6
+ /** Live list of animators (read every frame). */
7
+ animatorsRef: MutableRef<Array<GbtScopeAnimator>>;
8
+ /** Optional live overrides that replace the pointer/scroll signals (read every frame). */
9
+ overridesRef?: MutableRef<GbtScopeInputOverrides | undefined>;
10
+ pointer: PointerStateHandle;
11
+ scroll: ScrollStateHandle;
12
+ /**
13
+ * Persistent runtime state. The driver accumulates into it each frame; the owner re-seeds `current` from base props
14
+ * to make changes live.
15
+ */
16
+ stateRef: MutableRef<GbtScopeState>;
17
+ };
18
+ /** Minimal mutable-ref shape (compatible with React's useRef result). */
19
+ export type MutableRef<Type> = {
20
+ current: Type;
21
+ };
22
+ /**
23
+ * Registers a single render-loop observer that drives the material's animated uniforms from the animators + live
24
+ * inputs, frame-rate independent via `engine.getDeltaTime()`. Replaces Babylon's Animation API. Returns a dispose
25
+ * function that removes the observer.
26
+ */
27
+ export declare const createGbtScopeDriver: (scene: Scene, material: ShaderMaterial, { animatorsRef, overridesRef, pointer, scroll, stateRef, }: GbtScopeDriverOptions) => (() => void);
28
+ //# sourceMappingURL=driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/motion/driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,cAAc,EAAW,MAAM,iBAAiB,CAAA;AAC1E,OAAO,EAEH,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EACrB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpD,MAAM,MAAM,qBAAqB,GAAG;IAChC,iDAAiD;IACjD,YAAY,EAAE,UAAU,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAA;IACjD,0FAA0F;IAC1F,YAAY,CAAC,EAAE,UAAU,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAAA;IAC7D,OAAO,EAAE,kBAAkB,CAAA;IAC3B,MAAM,EAAE,iBAAiB,CAAA;IACzB;;;OAGG;IACH,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,CAAA;CACtC,CAAA;AAED,yEAAyE;AACzE,MAAM,MAAM,UAAU,CAAC,IAAI,IAAI;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,CAAA;AAahD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC7B,OAAO,KAAK,EACZ,UAAU,cAAc,EACxB,4DAMG,qBAAqB,KACzB,CAAC,MAAM,IAAI,CAgCb,CAAA"}
@@ -0,0 +1,24 @@
1
+ /** Current normalized pointer position, centered on the canvas, range [-1, 1]. */
2
+ export type PointerState = {
3
+ readonly x: number;
4
+ readonly y: number;
5
+ };
6
+ export type PointerStateHandle = {
7
+ /** Attach pointerdown/pointermove/pointerleave listeners to a canvas. */
8
+ attach: (canvas: HTMLCanvasElement) => void;
9
+ /** Remove the listeners. Call from the scene's onDisposeObservable. */
10
+ detach: (canvas: HTMLCanvasElement) => void;
11
+ /** Live pointer position. Mutated internally; read it inside a render loop. */
12
+ state: PointerState;
13
+ };
14
+ /**
15
+ * Creates a mutable pointer-state object for use inside a Babylon.js scene setup callback. Intentionally NOT a React
16
+ * hook: `onSceneReady` runs outside React's render cycle, so a hook's state would be stale inside the render
17
+ * observable. The returned `state` object is mutated in place and is safe to read every frame.
18
+ *
19
+ * Position is normalized to [-1, 1] on both axes (canvas-relative). Uses pointer events so mouse and touch behave
20
+ * uniformly: a mouse resets to [0, 0] on leaving the canvas, while a touch latches at the last tap/drag position (tap
21
+ * center to zero it) — hover-less devices would otherwise never produce input.
22
+ */
23
+ export declare const createPointerState: () => PointerStateHandle;
24
+ //# sourceMappingURL=pointer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pointer.d.ts","sourceRoot":"","sources":["../../src/motion/pointer.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,MAAM,YAAY,GAAG;IACvB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC7B,yEAAyE;IACzE,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC3C,uEAAuE;IACvE,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC3C,+EAA+E;IAC/E,KAAK,EAAE,YAAY,CAAA;CACtB,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,QAAO,kBA8BrC,CAAA"}
@@ -0,0 +1,25 @@
1
+ /** Current scroll signals. `progress` is [0,1]; `velocity` decays toward 0. */
2
+ export type ScrollState = {
3
+ readonly progress: number;
4
+ readonly velocity: number;
5
+ };
6
+ export type ScrollStateHandle = {
7
+ /** Attach scroll/wheel listeners (defaults to window). */
8
+ attach: (target?: HTMLElement | Window) => void;
9
+ /**
10
+ * Decay the velocity by one frame's worth (call once per frame from the driver after reading). `factor` in [0,1];
11
+ * lower = faster decay.
12
+ */
13
+ decay: (factor?: number) => void;
14
+ /** Remove listeners. Call from the scene's onDisposeObservable. */
15
+ detach: (target?: HTMLElement | Window) => void;
16
+ /** Live scroll signals. Mutated internally; read inside a render loop. */
17
+ state: ScrollState;
18
+ };
19
+ /**
20
+ * Creates a mutable scroll-state object for use inside a Babylon.js scene setup callback. Tracks page scroll `progress`
21
+ * [0,1] and a wheel-driven `velocity` that the driver decays each frame. Plain factory (not a React hook) — mirrors
22
+ * {@link ./pointer.createPointerState} so it can be read from the render observable.
23
+ */
24
+ export declare const createScrollState: () => ScrollStateHandle;
25
+ //# sourceMappingURL=scroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../../src/motion/scroll.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC5B,0DAA0D;IAC1D,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,KAAK,IAAI,CAAA;IAC/C;;;OAGG;IACH,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,mEAAmE;IACnE,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,KAAK,IAAI,CAAA;IAC/C,0EAA0E;IAC1E,KAAK,EAAE,WAAW,CAAA;CACrB,CAAA;AAUD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,QAAO,iBAoCpC,CAAA"}