"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; // dist/index.js var dist_exports = {}; __export(dist_exports, { AnimationLoop: () => AnimationLoop, AnimationLoopTemplate: () => AnimationLoopTemplate, BufferTransform: () => BufferTransform, ClipSpace: () => ClipSpace, Computation: () => Computation, ConeGeometry: () => ConeGeometry, CubeGeometry: () => CubeGeometry, CylinderGeometry: () => CylinderGeometry, GPUGeometry: () => GPUGeometry, Geometry: () => Geometry, GroupNode: () => GroupNode, IcoSphereGeometry: () => IcoSphereGeometry, KeyFrames: () => KeyFrames, Model: () => Model, ModelNode: () => ModelNode, PipelineFactory: () => PipelineFactory, PlaneGeometry: () => PlaneGeometry, ScenegraphNode: () => ScenegraphNode, ShaderFactory: () => ShaderFactory, SphereGeometry: () => SphereGeometry, TextureTransform: () => TextureTransform, Timeline: () => Timeline, TruncatedConeGeometry: () => TruncatedConeGeometry, _ShaderInputs: () => ShaderInputs, makeAnimationLoop: () => makeAnimationLoop }); module.exports = __toCommonJS(dist_exports); // dist/animation/timeline.js var channelHandles = 1; var animationHandles = 1; var Timeline = class { time = 0; channels = /* @__PURE__ */ new Map(); animations = /* @__PURE__ */ new Map(); playing = false; lastEngineTime = -1; constructor() { } addChannel(props) { const { delay = 0, duration = Number.POSITIVE_INFINITY, rate = 1, repeat = 1 } = props; const channelId = channelHandles++; const channel = { time: 0, delay, duration, rate, repeat }; this._setChannelTime(channel, this.time); this.channels.set(channelId, channel); return channelId; } removeChannel(channelId) { this.channels.delete(channelId); for (const [animationHandle, animation] of this.animations) { if (animation.channel === channelId) { this.detachAnimation(animationHandle); } } } isFinished(channelId) { const channel = this.channels.get(channelId); if (channel === void 0) { return false; } return this.time >= channel.delay + channel.duration * channel.repeat; } getTime(channelId) { if (channelId === void 0) { return this.time; } const channel = this.channels.get(channelId); if (channel === void 0) { return -1; } return channel.time; } setTime(time) { this.time = Math.max(0, time); const channels = this.channels.values(); for (const channel of channels) { this._setChannelTime(channel, this.time); } const animations = this.animations.values(); for (const animationData of animations) { const { animation, channel } = animationData; animation.setTime(this.getTime(channel)); } } play() { this.playing = true; } pause() { this.playing = false; this.lastEngineTime = -1; } reset() { this.setTime(0); } attachAnimation(animation, channelHandle) { const animationHandle = animationHandles++; this.animations.set(animationHandle, { animation, channel: channelHandle }); animation.setTime(this.getTime(channelHandle)); return animationHandle; } detachAnimation(channelId) { this.animations.delete(channelId); } update(engineTime) { if (this.playing) { if (this.lastEngineTime === -1) { this.lastEngineTime = engineTime; } this.setTime(this.time + (engineTime - this.lastEngineTime)); this.lastEngineTime = engineTime; } } _setChannelTime(channel, time) { const offsetTime = time - channel.delay; const totalDuration = channel.duration * channel.repeat; if (offsetTime >= totalDuration) { channel.time = channel.duration * channel.rate; } else { channel.time = Math.max(0, offsetTime) % channel.duration; channel.time *= channel.rate; } } }; // dist/animation/key-frames.js var KeyFrames = class { startIndex = -1; endIndex = -1; factor = 0; times = []; values = []; _lastTime = -1; constructor(keyFrames) { this.setKeyFrames(keyFrames); this.setTime(0); } setKeyFrames(keyFrames) { const numKeys = keyFrames.length; this.times.length = numKeys; this.values.length = numKeys; for (let i = 0; i < numKeys; ++i) { this.times[i] = keyFrames[i][0]; this.values[i] = keyFrames[i][1]; } this._calculateKeys(this._lastTime); } setTime(time) { time = Math.max(0, time); if (time !== this._lastTime) { this._calculateKeys(time); this._lastTime = time; } } getStartTime() { return this.times[this.startIndex]; } getEndTime() { return this.times[this.endIndex]; } getStartData() { return this.values[this.startIndex]; } getEndData() { return this.values[this.endIndex]; } _calculateKeys(time) { let index = 0; const numKeys = this.times.length; for (index = 0; index < numKeys - 2; ++index) { if (this.times[index + 1] > time) { break; } } this.startIndex = index; this.endIndex = index + 1; const startTime = this.times[this.startIndex]; const endTime = this.times[this.endIndex]; this.factor = Math.min(Math.max(0, (time - startTime) / (endTime - startTime)), 1); } }; // dist/animation-loop/animation-loop-template.js var AnimationLoopTemplate = class { constructor(animationProps) { } async onInitialize(animationProps) { return null; } }; // dist/animation-loop/animation-loop.js var import_core = require("@luma.gl/core"); var import_core2 = require("@luma.gl/core"); var import_stats = require("@probe.gl/stats"); var statIdCounter = 0; var DEFAULT_ANIMATION_LOOP_PROPS = { device: null, onAddHTML: () => "", onInitialize: async () => { return null; }, onRender: () => { }, onFinalize: () => { }, onError: (error) => console.error(error), // eslint-disable-line no-console stats: import_core.luma.stats.get(`animation-loop-${statIdCounter++}`), // view parameters useDevicePixels: true, autoResizeViewport: false, autoResizeDrawingBuffer: false }; var AnimationLoop = class { device = null; canvas = null; props; animationProps = null; timeline = null; stats; cpuTime; gpuTime; frameRate; display; needsRedraw = "initialized"; _initialized = false; _running = false; _animationFrameId = null; _nextFramePromise = null; _resolveNextFrame = null; _cpuStartTime = 0; // _gpuTimeQuery: Query | null = null; /* * @param {HTMLCanvasElement} canvas - if provided, width and height will be passed to context */ constructor(props) { this.props = { ...DEFAULT_ANIMATION_LOOP_PROPS, ...props }; props = this.props; if (!props.device) { throw new Error("No device provided"); } const { useDevicePixels = true } = this.props; this.stats = props.stats || new import_stats.Stats({ id: "animation-loop-stats" }); this.cpuTime = this.stats.get("CPU Time"); this.gpuTime = this.stats.get("GPU Time"); this.frameRate = this.stats.get("Frame Rate"); this.setProps({ autoResizeViewport: props.autoResizeViewport, autoResizeDrawingBuffer: props.autoResizeDrawingBuffer, useDevicePixels }); this.start = this.start.bind(this); this.stop = this.stop.bind(this); this._onMousemove = this._onMousemove.bind(this); this._onMouseleave = this._onMouseleave.bind(this); } destroy() { this.stop(); this._setDisplay(null); } /** @deprecated Use .destroy() */ delete() { this.destroy(); } /** Flags this animation loop as needing redraw */ setNeedsRedraw(reason) { this.needsRedraw = this.needsRedraw || reason; return this; } /** TODO - move these props to CanvasContext? */ setProps(props) { if ("autoResizeViewport" in props) { this.props.autoResizeViewport = props.autoResizeViewport || false; } if ("autoResizeDrawingBuffer" in props) { this.props.autoResizeDrawingBuffer = props.autoResizeDrawingBuffer || false; } if ("useDevicePixels" in props) { this.props.useDevicePixels = props.useDevicePixels || false; } return this; } /** Starts a render loop if not already running */ async start() { if (this._running) { return this; } this._running = true; try { let appContext; if (!this._initialized) { this._initialized = true; await this._initDevice(); this._initialize(); await this.props.onInitialize(this._getAnimationProps()); } if (!this._running) { return null; } if (appContext !== false) { this._cancelAnimationFrame(); this._requestAnimationFrame(); } return this; } catch (err) { const error = err instanceof Error ? err : new Error("Unknown error"); this.props.onError(error); throw error; } } /** Stops a render loop if already running, finalizing */ stop() { if (this._running) { if (this.animationProps) { this.props.onFinalize(this.animationProps); } this._cancelAnimationFrame(); this._nextFramePromise = null; this._resolveNextFrame = null; this._running = false; } return this; } /** Explicitly draw a frame */ redraw() { var _a; if ((_a = this.device) == null ? void 0 : _a.isLost) { return this; } this._beginFrameTimers(); this._setupFrame(); this._updateAnimationProps(); this._renderFrame(this._getAnimationProps()); this._clearNeedsRedraw(); if (this._resolveNextFrame) { this._resolveNextFrame(this); this._nextFramePromise = null; this._resolveNextFrame = null; } this._endFrameTimers(); return this; } /** Add a timeline, it will be automatically updated by the animation loop. */ attachTimeline(timeline) { this.timeline = timeline; return this.timeline; } /** Remove a timeline */ detachTimeline() { this.timeline = null; } /** Wait until a render completes */ waitForRender() { this.setNeedsRedraw("waitForRender"); if (!this._nextFramePromise) { this._nextFramePromise = new Promise((resolve) => { this._resolveNextFrame = resolve; }); } return this._nextFramePromise; } /** TODO - should use device.deviceContext */ async toDataURL() { this.setNeedsRedraw("toDataURL"); await this.waitForRender(); if (this.canvas instanceof HTMLCanvasElement) { return this.canvas.toDataURL(); } throw new Error("OffscreenCanvas"); } // PRIVATE METHODS _initialize() { this._startEventHandling(); this._initializeAnimationProps(); this._updateAnimationProps(); this._resizeCanvasDrawingBuffer(); this._resizeViewport(); } _setDisplay(display) { if (this.display) { this.display.destroy(); this.display.animationLoop = null; } if (display) { display.animationLoop = this; } this.display = display; } _requestAnimationFrame() { if (!this._running) { return; } this._animationFrameId = (0, import_core2.requestAnimationFrame)(this._animationFrame.bind(this)); } _cancelAnimationFrame() { if (this._animationFrameId === null) { return; } (0, import_core2.cancelAnimationFrame)(this._animationFrameId); this._animationFrameId = null; } _animationFrame() { if (!this._running) { return; } this.redraw(); this._requestAnimationFrame(); } // Called on each frame, can be overridden to call onRender multiple times // to support e.g. stereoscopic rendering _renderFrame(animationProps) { if (this.display) { this.display._renderFrame(animationProps); return; } this.props.onRender(this._getAnimationProps()); this.device.submit(); } _clearNeedsRedraw() { this.needsRedraw = false; } _setupFrame() { this._resizeCanvasDrawingBuffer(); this._resizeViewport(); } // Initialize the object that will be passed to app callbacks _initializeAnimationProps() { var _a, _b; if (!this.device) { throw new Error("loop"); } this.animationProps = { animationLoop: this, device: this.device, canvas: (_b = (_a = this.device) == null ? void 0 : _a.canvasContext) == null ? void 0 : _b.canvas, timeline: this.timeline, // Initial values useDevicePixels: this.props.useDevicePixels, needsRedraw: false, // Placeholders width: 1, height: 1, aspect: 1, // Animation props time: 0, startTime: Date.now(), engineTime: 0, tick: 0, tock: 0, // Experimental _mousePosition: null // Event props }; } _getAnimationProps() { if (!this.animationProps) { throw new Error("animationProps"); } return this.animationProps; } // Update the context object that will be passed to app callbacks _updateAnimationProps() { if (!this.animationProps) { return; } const { width, height, aspect } = this._getSizeAndAspect(); if (width !== this.animationProps.width || height !== this.animationProps.height) { this.setNeedsRedraw("drawing buffer resized"); } if (aspect !== this.animationProps.aspect) { this.setNeedsRedraw("drawing buffer aspect changed"); } this.animationProps.width = width; this.animationProps.height = height; this.animationProps.aspect = aspect; this.animationProps.needsRedraw = this.needsRedraw; this.animationProps.engineTime = Date.now() - this.animationProps.startTime; if (this.timeline) { this.timeline.update(this.animationProps.engineTime); } this.animationProps.tick = Math.floor(this.animationProps.time / 1e3 * 60); this.animationProps.tock++; this.animationProps.time = this.timeline ? this.timeline.getTime() : this.animationProps.engineTime; } /** Wait for supplied device */ async _initDevice() { var _a; this.device = await this.props.device; if (!this.device) { throw new Error("No device provided"); } this.canvas = ((_a = this.device.canvasContext) == null ? void 0 : _a.canvas) || null; } _createInfoDiv() { if (this.canvas && this.props.onAddHTML) { const wrapperDiv = document.createElement("div"); document.body.appendChild(wrapperDiv); wrapperDiv.style.position = "relative"; const div = document.createElement("div"); div.style.position = "absolute"; div.style.left = "10px"; div.style.bottom = "10px"; div.style.width = "300px"; div.style.background = "white"; if (this.canvas instanceof HTMLCanvasElement) { wrapperDiv.appendChild(this.canvas); } wrapperDiv.appendChild(div); const html = this.props.onAddHTML(div); if (html) { div.innerHTML = html; } } } _getSizeAndAspect() { var _a, _b, _c, _d; if (!this.device) { return { width: 1, height: 1, aspect: 1 }; } const [width, height] = ((_b = (_a = this.device) == null ? void 0 : _a.canvasContext) == null ? void 0 : _b.getPixelSize()) || [1, 1]; let aspect = 1; const canvas2 = (_d = (_c = this.device) == null ? void 0 : _c.canvasContext) == null ? void 0 : _d.canvas; if (canvas2 && canvas2.clientHeight) { aspect = canvas2.clientWidth / canvas2.clientHeight; } else if (width > 0 && height > 0) { aspect = width / height; } return { width, height, aspect }; } /** Default viewport setup */ _resizeViewport() { if (this.props.autoResizeViewport && this.device.gl) { this.device.gl.viewport( 0, 0, // @ts-expect-error Expose canvasContext this.device.gl.drawingBufferWidth, // @ts-expect-error Expose canvasContext this.device.gl.drawingBufferHeight ); } } /** * Resize the render buffer of the canvas to match canvas client size * Optionally multiplying with devicePixel ratio */ _resizeCanvasDrawingBuffer() { var _a, _b; if (this.props.autoResizeDrawingBuffer) { (_b = (_a = this.device) == null ? void 0 : _a.canvasContext) == null ? void 0 : _b.resize({ useDevicePixels: this.props.useDevicePixels }); } } _beginFrameTimers() { this.frameRate.timeEnd(); this.frameRate.timeStart(); this.cpuTime.timeStart(); } _endFrameTimers() { this.cpuTime.timeEnd(); } // Event handling _startEventHandling() { if (this.canvas) { this.canvas.addEventListener("mousemove", this._onMousemove.bind(this)); this.canvas.addEventListener("mouseleave", this._onMouseleave.bind(this)); } } _onMousemove(event) { if (event instanceof MouseEvent) { this._getAnimationProps()._mousePosition = [event.offsetX, event.offsetY]; } } _onMouseleave(event) { this._getAnimationProps()._mousePosition = null; } }; // dist/animation-loop/make-animation-loop.js var import_core3 = require("@luma.gl/core"); function makeAnimationLoop(AnimationLoopTemplateCtor, props) { let renderLoop = null; const device = (props == null ? void 0 : props.device) || import_core3.luma.createDevice(); const animationLoop = new AnimationLoop({ ...props, device, async onInitialize(animationProps) { renderLoop = new AnimationLoopTemplateCtor(animationProps); return await (renderLoop == null ? void 0 : renderLoop.onInitialize(animationProps)); }, onRender: (animationProps) => renderLoop == null ? void 0 : renderLoop.onRender(animationProps), onFinalize: (animationProps) => renderLoop == null ? void 0 : renderLoop.onFinalize(animationProps) }); animationLoop.getInfo = () => { return this.AnimationLoopTemplateCtor.info; }; return animationLoop; } // dist/model/model.js var import_core8 = require("@luma.gl/core"); var import_core9 = require("@luma.gl/core"); var import_core10 = require("@luma.gl/core"); var import_core11 = require("@luma.gl/core"); var import_shadertools2 = require("@luma.gl/shadertools"); // dist/geometry/gpu-geometry.js var import_core4 = require("@luma.gl/core"); var GPUGeometry = class { id; userData = {}; /** Determines how vertices are read from the 'vertex' attributes */ topology; bufferLayout = []; vertexCount; indices; attributes; constructor(props) { this.id = props.id || (0, import_core4.uid)("geometry"); this.topology = props.topology; this.indices = props.indices || null; this.attributes = props.attributes; this.vertexCount = props.vertexCount; this.bufferLayout = props.bufferLayout || []; if (this.indices) { (0, import_core4.assert)(this.indices.usage === import_core4.Buffer.INDEX); } } destroy() { var _a; (_a = this.indices) == null ? void 0 : _a.destroy(); for (const attribute of Object.values(this.attributes)) { attribute.destroy(); } } getVertexCount() { return this.vertexCount; } getAttributes() { return this.attributes; } getIndexes() { return this.indices; } _calculateVertexCount(positions) { const vertexCount = positions.byteLength / 12; return vertexCount; } }; function makeGPUGeometry(device, geometry) { if (geometry instanceof GPUGeometry) { return geometry; } const indices = getIndexBufferFromGeometry(device, geometry); const { attributes, bufferLayout } = getAttributeBuffersFromGeometry(device, geometry); return new GPUGeometry({ topology: geometry.topology || "triangle-list", bufferLayout, vertexCount: geometry.vertexCount, indices, attributes }); } function getIndexBufferFromGeometry(device, geometry) { if (!geometry.indices) { return void 0; } const data = geometry.indices.value; return device.createBuffer({ usage: import_core4.Buffer.INDEX, data }); } function getAttributeBuffersFromGeometry(device, geometry) { const bufferLayout = []; const attributes = {}; for (const [attributeName, attribute] of Object.entries(geometry.attributes)) { let name = attributeName; switch (attributeName) { case "POSITION": name = "positions"; break; case "NORMAL": name = "normals"; break; case "TEXCOORD_0": name = "texCoords"; break; case "COLOR_0": name = "colors"; break; } attributes[name] = device.createBuffer({ data: attribute.value, id: `${attributeName}-buffer` }); const { value, size, normalized } = attribute; bufferLayout.push({ name, format: (0, import_core4.getVertexFormatFromAttribute)(value, size, normalized) }); } const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices); return { attributes, bufferLayout, vertexCount }; } // dist/shader-inputs.js var import_core5 = require("@luma.gl/core"); var import_shadertools = require("@luma.gl/shadertools"); var ShaderInputs = class { /** * The map of modules * @todo should should this include the resolved dependencies? */ modules; /** Stores the uniform values for each module */ moduleUniforms; /** Stores the uniform bindings for each module */ moduleBindings; /** Tracks if uniforms have changed */ moduleUniformsChanged; /** * Create a new UniformStore instance * @param modules */ constructor(modules) { const resolvedModules = (0, import_shadertools._resolveModules)(Object.values(modules).filter((module2) => module2.dependencies)); for (const resolvedModule of resolvedModules) { modules[resolvedModule.name] = resolvedModule; } import_core5.log.log(1, "Creating ShaderInputs with modules", Object.keys(modules))(); this.modules = modules; this.moduleUniforms = {}; this.moduleBindings = {}; for (const [name, module2] of Object.entries(modules)) { const moduleName = name; this.moduleUniforms[moduleName] = module2.defaultUniforms || {}; this.moduleBindings[moduleName] = {}; } } /** Destroy */ destroy() { } /** * Set module props */ setProps(props) { var _a; for (const name of Object.keys(props)) { const moduleName = name; const moduleProps = props[moduleName]; const module2 = this.modules[moduleName]; if (!module2) { import_core5.log.warn(`Module ${name} not found`)(); continue; } const oldUniforms = this.moduleUniforms[moduleName]; const oldBindings = this.moduleBindings[moduleName]; const uniformsAndBindings = ((_a = module2.getUniforms) == null ? void 0 : _a.call(module2, moduleProps, oldUniforms)) || moduleProps; const { uniforms, bindings } = (0, import_core5.splitUniformsAndBindings)(uniformsAndBindings); this.moduleUniforms[moduleName] = { ...oldUniforms, ...uniforms }; this.moduleBindings[moduleName] = { ...oldBindings, ...bindings }; } } /** Merges all bindings for the shader (from the various modules) */ // getUniformBlocks(): Record { // return this.moduleUniforms; // } /** * Return the map of modules * @todo should should this include the resolved dependencies? */ getModules() { return Object.values(this.modules); } /** Get all uniform values for all modules */ getUniformValues() { return this.moduleUniforms; } /** Merges all bindings for the shader (from the various modules) */ getBindings() { const bindings = {}; for (const moduleBindings of Object.values(this.moduleBindings)) { Object.assign(bindings, moduleBindings); } return bindings; } getDebugTable() { var _a; const table = {}; for (const [moduleName, module2] of Object.entries(this.moduleUniforms)) { for (const [key, value] of Object.entries(module2)) { table[`${moduleName}.${key}`] = { type: (_a = this.modules[moduleName].uniformTypes) == null ? void 0 : _a[key], value: String(value) }; } } return table; } }; // dist/lib/pipeline-factory.js var import_core6 = require("@luma.gl/core"); var _PipelineFactory = class { device; _hashCounter = 0; _hashes = {}; _renderPipelineCache = {}; _computePipelineCache = {}; /** Get the singleton default pipeline factory for the specified device */ static getDefaultPipelineFactory(device) { device._lumaData.defaultPipelineFactory = device._lumaData.defaultPipelineFactory || new _PipelineFactory(device); return device._lumaData.defaultPipelineFactory; } constructor(device) { this.device = device; } /** Return a RenderPipeline matching props. Reuses a similar pipeline if already created. */ createRenderPipeline(props) { const allProps = { ...import_core6.RenderPipeline.defaultProps, ...props }; const hash = this._hashRenderPipeline(allProps); if (!this._renderPipelineCache[hash]) { const pipeline = this.device.createRenderPipeline({ ...allProps, id: allProps.id ? `${allProps.id}-cached` : void 0 }); pipeline.hash = hash; this._renderPipelineCache[hash] = { pipeline, useCount: 0 }; } this._renderPipelineCache[hash].useCount++; return this._renderPipelineCache[hash].pipeline; } createComputePipeline(props) { const allProps = { ...import_core6.ComputePipeline.defaultProps, ...props }; const hash = this._hashComputePipeline(allProps); if (!this._computePipelineCache[hash]) { const pipeline = this.device.createComputePipeline({ ...allProps, id: allProps.id ? `${allProps.id}-cached` : void 0 }); pipeline.hash = hash; this._computePipelineCache[hash] = { pipeline, useCount: 0 }; } this._computePipelineCache[hash].useCount++; return this._computePipelineCache[hash].pipeline; } release(pipeline) { const hash = pipeline.hash; const cache = pipeline instanceof import_core6.ComputePipeline ? this._computePipelineCache : this._renderPipelineCache; cache[hash].useCount--; if (cache[hash].useCount === 0) { cache[hash].pipeline.destroy(); delete cache[hash]; } } // PRIVATE _hashComputePipeline(props) { const shaderHash = this._getHash(props.shader.source); return `${shaderHash}`; } /** Calculate a hash based on all the inputs for a render pipeline */ _hashRenderPipeline(props) { const vsHash = this._getHash(props.vs.source); const fsHash = props.fs ? this._getHash(props.fs.source) : 0; const varyingHash = "-"; const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout)); switch (this.device.type) { case "webgl": return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`; default: const parameterHash = this._getHash(JSON.stringify(props.parameters)); return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`; } } _getHash(key) { if (this._hashes[key] === void 0) { this._hashes[key] = this._hashCounter++; } return this._hashes[key]; } }; var PipelineFactory = _PipelineFactory; __publicField(PipelineFactory, "defaultProps", { ...import_core6.RenderPipeline.defaultProps }); // dist/lib/shader-factory.js var import_core7 = require("@luma.gl/core"); var _ShaderFactory = class { device; _cache = {}; /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */ static getDefaultShaderFactory(device) { device._lumaData.defaultShaderFactory ||= new _ShaderFactory(device); return device._lumaData.defaultShaderFactory; } /** @internal */ constructor(device) { this.device = device; } /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */ createShader(props) { const key = this._hashShader(props); let cacheEntry = this._cache[key]; if (!cacheEntry) { const shader = this.device.createShader({ ...props, id: props.id ? `${props.id}-cached` : void 0 }); this._cache[key] = cacheEntry = { shader, useCount: 0 }; } cacheEntry.useCount++; return cacheEntry.shader; } /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */ release(shader) { const key = this._hashShader(shader); const cacheEntry = this._cache[key]; if (cacheEntry) { cacheEntry.useCount--; if (cacheEntry.useCount === 0) { delete this._cache[key]; cacheEntry.shader.destroy(); } } } // PRIVATE _hashShader(value) { return `${value.stage}:${value.source}`; } }; var ShaderFactory = _ShaderFactory; __publicField(ShaderFactory, "defaultProps", { ...import_core7.Shader.defaultProps }); // dist/debug/debug-shader-layout.js function getDebugTableForShaderLayout(layout, name) { var _a; const table = {}; const header = "Values"; if (layout.attributes.length === 0 && !((_a = layout.varyings) == null ? void 0 : _a.length)) { return { "No attributes or varyings": { [header]: "N/A" } }; } for (const attributeDeclaration of layout.attributes) { if (attributeDeclaration) { const glslDeclaration = `${attributeDeclaration.location} ${attributeDeclaration.name}: ${attributeDeclaration.type}`; table[`in ${glslDeclaration}`] = { [header]: attributeDeclaration.stepMode || "vertex" }; } } for (const varyingDeclaration of layout.varyings || []) { const glslDeclaration = `${varyingDeclaration.location} ${varyingDeclaration.name}`; table[`out ${glslDeclaration}`] = { [header]: JSON.stringify(varyingDeclaration.accessor) }; } return table; } // dist/debug/debug-framebuffer.js var canvas = null; var ctx = null; function debugFramebuffer(fbo, { id, minimap, opaque, top = "0", left = "0", rgbaScale = 1 }) { if (!canvas) { canvas = document.createElement("canvas"); canvas.id = id; canvas.title = id; canvas.style.zIndex = "100"; canvas.style.position = "absolute"; canvas.style.top = top; canvas.style.left = left; canvas.style.border = "blue 1px solid"; canvas.style.transform = "scaleY(-1)"; document.body.appendChild(canvas); ctx = canvas.getContext("2d"); } if (canvas.width !== fbo.width || canvas.height !== fbo.height) { canvas.width = fbo.width / 2; canvas.height = fbo.height / 2; canvas.style.width = "400px"; canvas.style.height = "400px"; } const color = fbo.device.readPixelsToArrayWebGL(fbo); const imageData = ctx.createImageData(fbo.width, fbo.height); const offset = 0; for (let i = 0; i < color.length; i += 4) { imageData.data[offset + i + 0] = color[i + 0] * rgbaScale; imageData.data[offset + i + 1] = color[i + 1] * rgbaScale; imageData.data[offset + i + 2] = color[i + 2] * rgbaScale; imageData.data[offset + i + 3] = opaque ? 255 : color[i + 3] * rgbaScale; } ctx.putImageData(imageData, 0, 0); } // dist/model/model.js var LOG_DRAW_PRIORITY = 2; var LOG_DRAW_TIMEOUT = 1e4; var _Model = class { device; id; source; vs; fs; pipelineFactory; shaderFactory; userData = {}; // Fixed properties (change can trigger pipeline rebuild) /** The render pipeline GPU parameters, depth testing etc */ parameters; /** The primitive topology */ topology; /** Buffer layout */ bufferLayout; // Dynamic properties /** Use instanced rendering */ isInstanced = void 0; /** instance count. `undefined` means not instanced */ instanceCount = 0; /** Vertex count */ vertexCount; /** Index buffer */ indexBuffer = null; /** Buffer-valued attributes */ bufferAttributes = {}; /** Constant-valued attributes */ constantAttributes = {}; /** Bindings (textures, samplers, uniform buffers) */ bindings = {}; /** Sets uniforms @deprecated Use uniform buffers and setBindings() for portability*/ uniforms = {}; /** * VertexArray * @note not implemented: if bufferLayout is updated, vertex array has to be rebuilt! * @todo - allow application to define multiple vertex arrays? * */ vertexArray; /** TransformFeedback, WebGL 2 only. */ transformFeedback = null; /** The underlying GPU "program". @note May be recreated if parameters change */ pipeline; /** ShaderInputs instance */ shaderInputs; _uniformStore; _attributeInfos = {}; _gpuGeometry = null; _getModuleUniforms; props; _pipelineNeedsUpdate = "newly created"; _needsRedraw = "initializing"; _destroyed = false; /** "Time" of last draw. Monotonically increasing timestamp */ _lastDrawTimestamp = -1; constructor(device, props) { var _a, _b, _c; this.props = { ..._Model.defaultProps, ...props }; props = this.props; this.id = props.id || (0, import_core10.uid)("model"); this.device = device; Object.assign(this.userData, props.userData); const moduleMap = Object.fromEntries(((_a = this.props.modules) == null ? void 0 : _a.map((module2) => [module2.name, module2])) || []); this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap)); const platformInfo = getPlatformInfo(device); const modules = (((_b = this.props.modules) == null ? void 0 : _b.length) > 0 ? this.props.modules : (_c = this.shaderInputs) == null ? void 0 : _c.getModules()) || []; const isWebGPU = this.device.type === "webgpu"; if (isWebGPU && this.props.source) { this.props.shaderLayout ||= (0, import_shadertools2.getShaderLayoutFromWGSL)(this.props.source); const { source, getUniforms } = this.props.shaderAssembler.assembleShader({ platformInfo, ...this.props, modules }); this.source = source; this._getModuleUniforms = getUniforms; } else { const { vs, fs, getUniforms } = this.props.shaderAssembler.assembleShaderPair({ platformInfo, ...this.props, modules }); this.vs = vs; this.fs = fs; this._getModuleUniforms = getUniforms; } this.vertexCount = this.props.vertexCount; this.instanceCount = this.props.instanceCount; this.topology = this.props.topology; this.bufferLayout = this.props.bufferLayout; this.parameters = this.props.parameters; if (props.geometry) { this.setGeometry(props.geometry); } this.pipelineFactory = props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device); this.shaderFactory = props.shaderFactory || ShaderFactory.getDefaultShaderFactory(this.device); this.pipeline = this._updatePipeline(); this.vertexArray = device.createVertexArray({ renderPipeline: this.pipeline }); if (this._gpuGeometry) { this._setGeometryAttributes(this._gpuGeometry); } if ("isInstanced" in props) { this.isInstanced = props.isInstanced; } if (props.instanceCount) { this.setInstanceCount(props.instanceCount); } if (props.vertexCount) { this.setVertexCount(props.vertexCount); } if (props.indexBuffer) { this.setIndexBuffer(props.indexBuffer); } if (props.attributes) { this.setAttributes(props.attributes); } if (props.constantAttributes) { this.setConstantAttributes(props.constantAttributes); } if (props.bindings) { this.setBindings(props.bindings); } if (props.uniforms) { this.setUniforms(props.uniforms); } if (props.moduleSettings) { this.updateModuleSettings(props.moduleSettings); } if (props.transformFeedback) { this.transformFeedback = props.transformFeedback; } Object.seal(this); } destroy() { var _a; if (this._destroyed) return; this.pipelineFactory.release(this.pipeline); this.shaderFactory.release(this.pipeline.vs); if (this.pipeline.fs) { this.shaderFactory.release(this.pipeline.fs); } this._uniformStore.destroy(); (_a = this._gpuGeometry) == null ? void 0 : _a.destroy(); this._destroyed = true; } // Draw call /** Query redraw status. Clears the status. */ needsRedraw() { if (this._getBindingsUpdateTimestamp() > this._lastDrawTimestamp) { this.setNeedsRedraw("contents of bound textures or buffers updated"); } const needsRedraw = this._needsRedraw; this._needsRedraw = false; return needsRedraw; } /** Mark the model as needing a redraw */ setNeedsRedraw(reason) { this._needsRedraw ||= reason; } predraw() { this.updateShaderInputs(); this.pipeline = this._updatePipeline(); } draw(renderPass) { this.predraw(); let drawSuccess; try { this._logDrawCallStart(); this.pipeline = this._updatePipeline(); this.pipeline.setBindings(this.bindings, { disableWarnings: this.props.disableWarnings }); if (!(0, import_core10.isObjectEmpty)(this.uniforms)) { this.pipeline.setUniformsWebGL(this.uniforms); } const { indexBuffer } = this.vertexArray; const indexCount = indexBuffer ? indexBuffer.byteLength / (indexBuffer.indexType === "uint32" ? 4 : 2) : void 0; drawSuccess = this.pipeline.draw({ renderPass, vertexArray: this.vertexArray, isInstanced: this.isInstanced, vertexCount: this.vertexCount, instanceCount: this.instanceCount, indexCount, transformFeedback: this.transformFeedback || void 0, // WebGL shares underlying cached pipelines even for models that have different parameters and topology, // so we must provide our unique parameters to each draw // (In WebGPU most parameters are encoded in the pipeline and cannot be changed per draw call) parameters: this.parameters, topology: this.topology }); } finally { this._logDrawCallEnd(); } this._logFramebuffer(renderPass); if (drawSuccess) { this._lastDrawTimestamp = this.device.timestamp; this._needsRedraw = false; } else { this._needsRedraw = "waiting for resource initialization"; } return drawSuccess; } // Update fixed fields (can trigger pipeline rebuild) /** * Updates the optional geometry * Geometry, set topology and bufferLayout * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU */ setGeometry(geometry) { var _a; (_a = this._gpuGeometry) == null ? void 0 : _a.destroy(); const gpuGeometry = geometry && makeGPUGeometry(this.device, geometry); if (gpuGeometry) { this.setTopology(gpuGeometry.topology || "triangle-list"); this.bufferLayout = mergeBufferLayouts(gpuGeometry.bufferLayout, this.bufferLayout); if (this.vertexArray) { this._setGeometryAttributes(gpuGeometry); } } this._gpuGeometry = gpuGeometry; } /** * Updates the primitive topology ('triangle-list', 'triangle-strip' etc). * @note Triggers a pipeline rebuild / pipeline cache fetch on WebGPU */ setTopology(topology) { if (topology !== this.topology) { this.topology = topology; this._setPipelineNeedsUpdate("topology"); } } /** * Updates the buffer layout. * @note Triggers a pipeline rebuild / pipeline cache fetch */ setBufferLayout(bufferLayout) { this.bufferLayout = this._gpuGeometry ? mergeBufferLayouts(bufferLayout, this._gpuGeometry.bufferLayout) : bufferLayout; this._setPipelineNeedsUpdate("bufferLayout"); this.pipeline = this._updatePipeline(); this.vertexArray = this.device.createVertexArray({ renderPipeline: this.pipeline }); if (this._gpuGeometry) { this._setGeometryAttributes(this._gpuGeometry); } } /** * Set GPU parameters. * @note Can trigger a pipeline rebuild / pipeline cache fetch. * @param parameters */ setParameters(parameters) { if (!(0, import_core10.deepEqual)(parameters, this.parameters, 2)) { this.parameters = parameters; this._setPipelineNeedsUpdate("parameters"); } } // Update dynamic fields /** * Updates the instance count (used in draw calls) * @note Any attributes with stepMode=instance need to be at least this big */ setInstanceCount(instanceCount) { this.instanceCount = instanceCount; if (this.isInstanced === void 0 && instanceCount > 0) { this.isInstanced = true; } this.setNeedsRedraw("instanceCount"); } /** * Updates the vertex count (used in draw calls) * @note Any attributes with stepMode=vertex need to be at least this big */ setVertexCount(vertexCount) { this.vertexCount = vertexCount; this.setNeedsRedraw("vertexCount"); } /** Set the shader inputs */ setShaderInputs(shaderInputs) { this.shaderInputs = shaderInputs; this._uniformStore = new import_core9.UniformStore(this.shaderInputs.modules); for (const moduleName of Object.keys(this.shaderInputs.modules)) { const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName); this.bindings[`${moduleName}Uniforms`] = uniformBuffer; } this.setNeedsRedraw("shaderInputs"); } /** Update uniform buffers from the model's shader inputs */ updateShaderInputs() { this._uniformStore.setUniforms(this.shaderInputs.getUniformValues()); this.setBindings(this.shaderInputs.getBindings()); this.setNeedsRedraw("shaderInputs"); } /** * Sets bindings (textures, samplers, uniform buffers) */ setBindings(bindings) { Object.assign(this.bindings, bindings); this.setNeedsRedraw("bindings"); } /** * Updates optional transform feedback. WebGL only. */ setTransformFeedback(transformFeedback) { this.transformFeedback = transformFeedback; this.setNeedsRedraw("transformFeedback"); } /** * Sets the index buffer * @todo - how to unset it if we change geometry? */ setIndexBuffer(indexBuffer) { this.vertexArray.setIndexBuffer(indexBuffer); this.setNeedsRedraw("indexBuffer"); } /** * Sets attributes (buffers) * @note Overrides any attributes previously set with the same name */ setAttributes(buffers, options) { if (buffers.indices) { import_core10.log.warn(`Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`)(); } for (const [bufferName, buffer] of Object.entries(buffers)) { const bufferLayout = this.bufferLayout.find((layout) => getAttributeNames(layout).includes(bufferName)); if (!bufferLayout) { import_core10.log.warn(`Model(${this.id}): Missing layout for buffer "${bufferName}".`)(); continue; } const attributeNames = getAttributeNames(bufferLayout); let set = false; for (const attributeName of attributeNames) { const attributeInfo = this._attributeInfos[attributeName]; if (attributeInfo) { this.vertexArray.setBuffer(attributeInfo.location, buffer); set = true; } } if (!set && !((options == null ? void 0 : options.disableWarnings) ?? this.props.disableWarnings)) { import_core10.log.warn(`Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`)(); } } this.setNeedsRedraw("attributes"); } /** * Sets constant attributes * @note Overrides any attributes previously set with the same name * Constant attributes are only supported in WebGL, not in WebGPU * Any attribute that is disabled in the current vertex array object * is read from the context's global constant value for that attribute location. * @param constantAttributes */ setConstantAttributes(attributes, options) { for (const [attributeName, value] of Object.entries(attributes)) { const attributeInfo = this._attributeInfos[attributeName]; if (attributeInfo) { this.vertexArray.setConstantWebGL(attributeInfo.location, value); } else if (!((options == null ? void 0 : options.disableWarnings) ?? this.props.disableWarnings)) { import_core10.log.warn(`Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`)(); } } this.setNeedsRedraw("constants"); } // DEPRECATED METHODS /** * Sets individual uniforms * @deprecated WebGL only, use uniform buffers for portability * @param uniforms */ setUniforms(uniforms) { if (!(0, import_core10.isObjectEmpty)(uniforms)) { this.pipeline.setUniformsWebGL(uniforms); Object.assign(this.uniforms, uniforms); } this.setNeedsRedraw("uniforms"); } /** * @deprecated Updates shader module settings (which results in uniforms being set) */ updateModuleSettings(props) { const { bindings, uniforms } = (0, import_core10.splitUniformsAndBindings)(this._getModuleUniforms(props)); Object.assign(this.bindings, bindings); Object.assign(this.uniforms, uniforms); this.setNeedsRedraw("moduleSettings"); } // Internal methods /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */ _getBindingsUpdateTimestamp() { let timestamp = 0; for (const binding of Object.values(this.bindings)) { if (binding instanceof import_core8.TextureView) { timestamp = Math.max(timestamp, binding.texture.updateTimestamp); } else if (binding instanceof import_core8.Buffer || binding instanceof import_core8.Texture) { timestamp = Math.max(timestamp, binding.updateTimestamp); } else if (!(binding instanceof import_core8.Sampler)) { timestamp = Math.max(timestamp, binding.buffer.updateTimestamp); } } return timestamp; } /** * Updates the optional geometry attributes * Geometry, sets several attributes, indexBuffer, and also vertex count * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU */ _setGeometryAttributes(gpuGeometry) { const attributes = { ...gpuGeometry.attributes }; for (const [attributeName] of Object.entries(attributes)) { if (!this.pipeline.shaderLayout.attributes.find((layout) => layout.name === attributeName) && attributeName !== "positions") { delete attributes[attributeName]; } } this.vertexCount = gpuGeometry.vertexCount; this.setIndexBuffer(gpuGeometry.indices || null); this.setAttributes(gpuGeometry.attributes, { disableWarnings: true }); this.setAttributes(attributes, { disableWarnings: this.props.disableWarnings }); this.setNeedsRedraw("geometry attributes"); } /** Mark pipeline as needing update */ _setPipelineNeedsUpdate(reason) { this._pipelineNeedsUpdate ||= reason; this.setNeedsRedraw(reason); } /** Update pipeline if needed */ _updatePipeline() { if (this._pipelineNeedsUpdate) { let prevShaderVs = null; let prevShaderFs = null; if (this.pipeline) { import_core10.log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)(); prevShaderVs = this.pipeline.vs; prevShaderFs = this.pipeline.fs; } this._pipelineNeedsUpdate = false; const vs = this.shaderFactory.createShader({ id: `${this.id}-vertex`, stage: "vertex", source: this.source || this.vs, debug: this.props.debugShaders }); let fs = null; if (this.source) { fs = vs; } else if (this.fs) { fs = this.shaderFactory.createShader({ id: `${this.id}-fragment`, stage: "fragment", source: this.source || this.fs, debug: this.props.debugShaders }); } this.pipeline = this.pipelineFactory.createRenderPipeline({ ...this.props, bufferLayout: this.bufferLayout, topology: this.topology, parameters: this.parameters, vs, fs }); this._attributeInfos = (0, import_core11.getAttributeInfosFromLayouts)(this.pipeline.shaderLayout, this.bufferLayout); if (prevShaderVs) this.shaderFactory.release(prevShaderVs); if (prevShaderFs) this.shaderFactory.release(prevShaderFs); } return this.pipeline; } /** Throttle draw call logging */ _lastLogTime = 0; _logOpen = false; _logDrawCallStart() { const logDrawTimeout = import_core10.log.level > 3 ? 0 : LOG_DRAW_TIMEOUT; if (import_core10.log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) { return; } this._lastLogTime = Date.now(); this._logOpen = true; import_core10.log.group(LOG_DRAW_PRIORITY, `>>> DRAWING MODEL ${this.id}`, { collapsed: import_core10.log.level <= 2 })(); } _logDrawCallEnd() { if (this._logOpen) { const shaderLayoutTable = getDebugTableForShaderLayout(this.pipeline.shaderLayout, this.id); import_core10.log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)(); const uniformTable = this.shaderInputs.getDebugTable(); for (const [name, value] of Object.entries(this.uniforms)) { uniformTable[name] = { value }; } import_core10.log.table(LOG_DRAW_PRIORITY, uniformTable)(); const attributeTable = this._getAttributeDebugTable(); import_core10.log.table(LOG_DRAW_PRIORITY, this._attributeInfos)(); import_core10.log.table(LOG_DRAW_PRIORITY, attributeTable)(); import_core10.log.groupEnd(LOG_DRAW_PRIORITY)(); this._logOpen = false; } } _drawCount = 0; _logFramebuffer(renderPass) { const debugFramebuffers = import_core10.log.get("framebuffer"); this._drawCount++; if (!debugFramebuffers || this._drawCount++ > 3 && this._drawCount % 60) { return; } const framebuffer = renderPass.props.framebuffer; if (framebuffer) { debugFramebuffer(framebuffer, { id: framebuffer.id, minimap: true }); } } _getAttributeDebugTable() { const table = {}; for (const [name, attributeInfo] of Object.entries(this._attributeInfos)) { table[attributeInfo.location] = { name, type: attributeInfo.shaderType, values: this._getBufferOrConstantValues(this.vertexArray.attributes[attributeInfo.location], attributeInfo.bufferDataType) }; } if (this.vertexArray.indexBuffer) { const { indexBuffer } = this.vertexArray; const values = indexBuffer.indexType === "uint32" ? new Uint32Array(indexBuffer.debugData) : new Uint16Array(indexBuffer.debugData); table.indices = { name: "indices", type: indexBuffer.indexType, values: values.toString() }; } return table; } // TODO - fix typing of luma data types _getBufferOrConstantValues(attribute, dataType) { const TypedArrayConstructor = (0, import_core11.getTypedArrayFromDataType)(dataType); const typedArray = attribute instanceof import_core8.Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute; return typedArray.toString(); } }; var Model = _Model; __publicField(Model, "defaultProps", { ...import_core9.RenderPipeline.defaultProps, source: null, vs: null, fs: null, id: "unnamed", handle: void 0, userData: {}, defines: {}, modules: [], moduleSettings: void 0, geometry: null, indexBuffer: null, attributes: {}, constantAttributes: {}, varyings: [], isInstanced: void 0, instanceCount: 0, vertexCount: 0, shaderInputs: void 0, pipelineFactory: void 0, shaderFactory: void 0, transformFeedback: void 0, shaderAssembler: import_shadertools2.ShaderAssembler.getDefaultShaderAssembler(), debugShaders: void 0, disableWarnings: void 0 }); function mergeBufferLayouts(layouts1, layouts2) { const layouts = [...layouts1]; for (const attribute of layouts2) { const index = layouts.findIndex((attribute2) => attribute2.name === attribute.name); if (index < 0) { layouts.push(attribute); } else { layouts[index] = attribute; } } return layouts; } function getPlatformInfo(device) { return { type: device.type, shaderLanguage: device.info.shadingLanguage, shaderLanguageVersion: device.info.shadingLanguageVersion, gpu: device.info.gpu, // HACK - we pretend that the DeviceFeatures is a Set, it has a similar API features: device.features }; } function getAttributeNames(bufferLayout) { var _a; return bufferLayout.attributes ? (_a = bufferLayout.attributes) == null ? void 0 : _a.map((layout) => layout.attribute) : [bufferLayout.name]; } // dist/transform/buffer-transform.js var import_core12 = require("@luma.gl/core"); var import_shadertools3 = require("@luma.gl/shadertools"); var BufferTransform = class { device; model; transformFeedback; /** @deprecated Use device feature test. */ static isSupported(device) { var _a; return ((_a = device == null ? void 0 : device.info) == null ? void 0 : _a.type) === "webgl"; } constructor(device, props = Model.defaultProps) { (0, import_core12.assert)(BufferTransform.isSupported(device), "BufferTransform not yet implemented on WebGPU"); this.device = device; this.model = new Model(this.device, { id: props.id || "buffer-transform-model", fs: props.fs || (0, import_shadertools3.getPassthroughFS)(), topology: props.topology || "point-list", ...props }); this.transformFeedback = this.device.createTransformFeedback({ layout: this.model.pipeline.shaderLayout, buffers: props.feedbackBuffers }); this.model.setTransformFeedback(this.transformFeedback); Object.seal(this); } /** Destroy owned resources. */ destroy() { if (this.model) { this.model.destroy(); } } /** @deprecated Use {@link destroy}. */ delete() { this.destroy(); } /** Run one transform loop. */ run(options) { const renderPass = this.device.beginRenderPass(options); this.model.draw(renderPass); renderPass.end(); } /** @deprecated */ update(...args) { console.warn("TextureTransform#update() not implemented"); } /** Returns the {@link Buffer} or {@link BufferRange} for given varying name. */ getBuffer(varyingName) { return this.transformFeedback.getBuffer(varyingName); } readAsync(varyingName) { const result = this.getBuffer(varyingName); if (result instanceof import_core12.Buffer) { return result.readAsync(); } const { buffer, byteOffset = 0, byteLength = buffer.byteLength } = result; return buffer.readAsync(byteOffset, byteLength); } }; // dist/transform/texture-transform.js var import_shadertools4 = require("@luma.gl/shadertools"); var FS_OUTPUT_VARIABLE = "transform_output"; var TextureTransform = class { device; model; sampler; currentIndex = 0; samplerTextureMap = null; bindings = []; // each element is an object : {sourceTextures, targetTexture, framebuffer} resources = {}; // resources to be deleted constructor(device, props) { this.device = device; this.sampler = device.createSampler({ addressModeU: "clamp-to-edge", addressModeV: "clamp-to-edge", minFilter: "nearest", magFilter: "nearest", mipmapFilter: "nearest" }); this.model = new Model(this.device, { id: props.id || "texture-transform-model", fs: props.fs || (0, import_shadertools4.getPassthroughFS)({ input: props.targetTextureVarying, inputChannels: props.targetTextureChannels, output: FS_OUTPUT_VARIABLE }), vertexCount: props.vertexCount, // TODO(donmccurdy): Naming? ...props }); this._initialize(props); Object.seal(this); } // Delete owned resources. destroy() { } /** @deprecated Use {@link destroy}. */ delete() { this.destroy(); } run(options) { const { framebuffer } = this.bindings[this.currentIndex]; const renderPass = this.device.beginRenderPass({ framebuffer, ...options }); this.model.draw(renderPass); renderPass.end(); } /** @deprecated */ update(...args) { console.warn("TextureTransform#update() not implemented"); } getData({ packed = false } = {}) { throw new Error("getData() not implemented"); } getTargetTexture() { const { targetTexture } = this.bindings[this.currentIndex]; return targetTexture; } getFramebuffer() { const currentResources = this.bindings[this.currentIndex]; return currentResources.framebuffer; } // Private _initialize(props) { this._updateBindings(props); } _updateBindings(props) { this.bindings[this.currentIndex] = this._updateBinding(this.bindings[this.currentIndex], props); } _updateBinding(binding, { sourceBuffers, sourceTextures, targetTexture }) { if (!binding) { binding = { sourceBuffers: {}, sourceTextures: {}, targetTexture: null }; } Object.assign(binding.sourceTextures, sourceTextures); Object.assign(binding.sourceBuffers, sourceBuffers); if (targetTexture) { binding.targetTexture = targetTexture; const { width, height } = targetTexture; if (binding.framebuffer) { binding.framebuffer.destroy(); } binding.framebuffer = this.device.createFramebuffer({ id: "transform-framebuffer", width, height, colorAttachments: [targetTexture] }); binding.framebuffer.resize({ width, height }); } return binding; } // set texture filtering parameters on source textures. _setSourceTextureParameters() { const index = this.currentIndex; const { sourceTextures } = this.bindings[index]; for (const name in sourceTextures) { sourceTextures[name].sampler = this.sampler; } } }; // dist/lib/clip-space.js var import_core14 = require("@luma.gl/core"); // dist/geometry/geometry.js var import_core13 = require("@luma.gl/core"); var Geometry = class { id; /** Determines how vertices are read from the 'vertex' attributes */ topology; vertexCount; indices; attributes; userData = {}; constructor(props) { const { attributes = {}, indices = null, vertexCount = null } = props; this.id = props.id || (0, import_core13.uid)("geometry"); this.topology = props.topology; if (indices) { this.indices = ArrayBuffer.isView(indices) ? { value: indices, size: 1 } : indices; } this.attributes = {}; for (const [attributeName, attributeValue] of Object.entries(attributes)) { const attribute = ArrayBuffer.isView(attributeValue) ? { value: attributeValue } : attributeValue; (0, import_core13.assert)(ArrayBuffer.isView(attribute.value), `${this._print(attributeName)}: must be typed array or object with value as typed array`); if ((attributeName === "POSITION" || attributeName === "positions") && !attribute.size) { attribute.size = 3; } if (attributeName === "indices") { (0, import_core13.assert)(!this.indices); this.indices = attribute; } else { this.attributes[attributeName] = attribute; } } if (this.indices && this.indices.isIndexed !== void 0) { this.indices = Object.assign({}, this.indices); delete this.indices.isIndexed; } this.vertexCount = vertexCount || this._calculateVertexCount(this.attributes, this.indices); } getVertexCount() { return this.vertexCount; } /** * Return an object with all attributes plus indices added as a field. * TODO Geometry types are a mess */ getAttributes() { return this.indices ? { indices: this.indices, ...this.attributes } : this.attributes; } // PRIVATE _print(attributeName) { return `Geometry ${this.id} attribute ${attributeName}`; } /** * GeometryAttribute * value: typed array * type: indices, vertices, uvs * size: elements per vertex * target: WebGL buffer type (string or constant) * * @param attributes * @param indices * @returns */ _setAttributes(attributes, indices) { return this; } _calculateVertexCount(attributes, indices) { if (indices) { return indices.value.length; } let vertexCount = Infinity; for (const attribute of Object.values(attributes)) { const { value, size, constant } = attribute; if (!constant && value && size >= 1) { vertexCount = Math.min(vertexCount, value.length / size); } } (0, import_core13.assert)(Number.isFinite(vertexCount)); return vertexCount; } }; // dist/lib/clip-space.js var CLIPSPACE_VERTEX_SHADER = `#version 300 es in vec2 aClipSpacePosition; in vec2 aTexCoord; in vec2 aCoordinate; out vec2 position; out vec2 coordinate; out vec2 uv; void main(void) { gl_Position = vec4(aClipSpacePosition, 0., 1.); position = aClipSpacePosition; coordinate = aCoordinate; uv = aTexCoord; } `; var POSITIONS = [-1, -1, 1, -1, -1, 1, 1, 1]; var ClipSpace = class extends Model { constructor(device, opts) { const TEX_COORDS = POSITIONS.map((coord) => coord === -1 ? 0 : coord); super(device, { ...opts, vs: CLIPSPACE_VERTEX_SHADER, vertexCount: 4, geometry: new Geometry({ topology: "triangle-strip", vertexCount: 4, attributes: { aClipSpacePosition: { size: 2, value: new Float32Array(POSITIONS) }, aTexCoord: { size: 2, value: new Float32Array(TEX_COORDS) }, aCoordinate: { size: 2, value: new Float32Array(TEX_COORDS) } } }) }); } }; // dist/scenegraph/scenegraph-node.js var import_core15 = require("@luma.gl/core"); var import_core16 = require("@math.gl/core"); var ScenegraphNode = class { id; matrix = new import_core16.Matrix4(); display = true; position = new import_core16.Vector3(); rotation = new import_core16.Vector3(); scale = new import_core16.Vector3(1, 1, 1); userData = {}; props = {}; constructor(props = {}) { const { id } = props; this.id = id || (0, import_core15.uid)(this.constructor.name); this._setScenegraphNodeProps(props); } getBounds() { return null; } destroy() { } /** @deprecated use .destroy() */ delete() { this.destroy(); } setProps(props) { this._setScenegraphNodeProps(props); return this; } toString() { return `{type: ScenegraphNode, id: ${this.id})}`; } setPosition(position) { (0, import_core15.assert)(position.length === 3, "setPosition requires vector argument"); this.position = position; return this; } setRotation(rotation) { (0, import_core15.assert)(rotation.length === 3, "setRotation requires vector argument"); this.rotation = rotation; return this; } setScale(scale) { (0, import_core15.assert)(scale.length === 3, "setScale requires vector argument"); this.scale = scale; return this; } setMatrix(matrix, copyMatrix = true) { if (copyMatrix) { this.matrix.copy(matrix); } else { this.matrix = matrix; } } setMatrixComponents(components) { const { position, rotation, scale, update = true } = components; if (position) { this.setPosition(position); } if (rotation) { this.setRotation(rotation); } if (scale) { this.setScale(scale); } if (update) { this.updateMatrix(); } return this; } updateMatrix() { const pos = this.position; const rot = this.rotation; const scale = this.scale; this.matrix.identity(); this.matrix.translate(pos); this.matrix.rotateXYZ(rot); this.matrix.scale(scale); return this; } update(options = {}) { const { position, rotation, scale } = options; if (position) { this.setPosition(position); } if (rotation) { this.setRotation(rotation); } if (scale) { this.setScale(scale); } this.updateMatrix(); return this; } getCoordinateUniforms(viewMatrix, modelMatrix) { (0, import_core15.assert)(viewMatrix); modelMatrix = modelMatrix || this.matrix; const worldMatrix = new import_core16.Matrix4(viewMatrix).multiplyRight(modelMatrix); const worldInverse = worldMatrix.invert(); const worldInverseTranspose = worldInverse.transpose(); return { viewMatrix, modelMatrix, objectMatrix: modelMatrix, worldMatrix, worldInverseMatrix: worldInverse, worldInverseTransposeMatrix: worldInverseTranspose }; } // TODO - copied code, not yet vetted /* transform() { if (!this.parent) { this.endPosition.set(this.position); this.endRotation.set(this.rotation); this.endScale.set(this.scale); } else { const parent = this.parent; this.endPosition.set(this.position.add(parent.endPosition)); this.endRotation.set(this.rotation.add(parent.endRotation)); this.endScale.set(this.scale.add(parent.endScale)); } const ch = this.children; for (let i = 0; i < ch.length; ++i) { ch[i].transform(); } return this; } */ _setScenegraphNodeProps(props) { if ("display" in props) { this.display = props.display; } if ("position" in props) { this.setPosition(props.position); } if ("rotation" in props) { this.setRotation(props.rotation); } if ("scale" in props) { this.setScale(props.scale); } if ("matrix" in props) { this.setMatrix(props.matrix); } Object.assign(this.props, props); } }; // dist/scenegraph/group-node.js var import_core17 = require("@math.gl/core"); var import_core18 = require("@luma.gl/core"); var GroupNode = class extends ScenegraphNode { children; constructor(props = {}) { props = Array.isArray(props) ? { children: props } : props; const { children = [] } = props; import_core18.log.assert(children.every((child) => child instanceof ScenegraphNode), "every child must an instance of ScenegraphNode"); super(props); this.children = children; } getBounds() { const result = [ [Infinity, Infinity, Infinity], [-Infinity, -Infinity, -Infinity] ]; this.traverse((node, { worldMatrix }) => { const bounds = node.getBounds(); if (!bounds) { return; } const [min, max] = bounds; const center = new import_core17.Vector3(min).add(max).divide([2, 2, 2]); worldMatrix.transformAsPoint(center, center); const halfSize = new import_core17.Vector3(max).subtract(min).divide([2, 2, 2]); worldMatrix.transformAsVector(halfSize, halfSize); for (let v = 0; v < 8; v++) { const position = new import_core17.Vector3(v & 1 ? -1 : 1, v & 2 ? -1 : 1, v & 4 ? -1 : 1).multiply(halfSize).add(center); for (let i = 0; i < 3; i++) { result[0][i] = Math.min(result[0][i], position[i]); result[1][i] = Math.max(result[1][i], position[i]); } } }); if (!Number.isFinite(result[0][0])) { return null; } return result; } destroy() { this.children.forEach((child) => child.destroy()); this.removeAll(); super.destroy(); } // Unpacks arrays and nested arrays of children add(...children) { for (const child of children) { if (Array.isArray(child)) { this.add(...child); } else { this.children.push(child); } } return this; } remove(child) { const children = this.children; const indexOf = children.indexOf(child); if (indexOf > -1) { children.splice(indexOf, 1); } return this; } removeAll() { this.children = []; return this; } traverse(visitor, { worldMatrix = new import_core17.Matrix4() } = {}) { const modelMatrix = new import_core17.Matrix4(worldMatrix).multiplyRight(this.matrix); for (const child of this.children) { if (child instanceof GroupNode) { child.traverse(visitor, { worldMatrix: modelMatrix }); } else { visitor(child, { worldMatrix: modelMatrix }); } } } }; // dist/scenegraph/model-node.js var ModelNode = class extends ScenegraphNode { model; bounds = null; managedResources; // TODO - is this used? override callbacks to make sure we call them with this // onBeforeRender = null; // onAfterRender = null; // AfterRender = null; constructor(props) { super(props); this.model = props.model; this.managedResources = props.managedResources || []; this.bounds = props.bounds || null; this.setProps(props); } getBounds() { return this.bounds; } destroy() { if (this.model) { this.model.destroy(); this.model = null; } this.managedResources.forEach((resource) => resource.destroy()); this.managedResources = []; } // Expose model methods draw(renderPass) { return this.model.draw(renderPass); } }; // dist/geometries/cone-geometry.js var import_core20 = require("@luma.gl/core"); // dist/geometries/truncated-cone-geometry.js var import_core19 = require("@luma.gl/core"); var INDEX_OFFSETS = { x: [2, 0, 1], y: [0, 1, 2], z: [1, 2, 0] }; var TruncatedConeGeometry = class extends Geometry { constructor(props = {}) { const { id = (0, import_core19.uid)("truncated-code-geometry") } = props; const { indices, attributes } = tesselateTruncatedCone(props); super({ ...props, id, topology: "triangle-list", indices, attributes: { POSITION: { size: 3, value: attributes.POSITION }, NORMAL: { size: 3, value: attributes.NORMAL }, TEXCOORD_0: { size: 2, value: attributes.TEXCOORD_0 }, ...props.attributes } }); } }; function tesselateTruncatedCone(props = {}) { const { bottomRadius = 0, topRadius = 0, height = 1, nradial = 10, nvertical = 10, verticalAxis = "y", topCap = false, bottomCap = false } = props; const extra = (topCap ? 2 : 0) + (bottomCap ? 2 : 0); const numVertices = (nradial + 1) * (nvertical + 1 + extra); const slant = Math.atan2(bottomRadius - topRadius, height); const msin = Math.sin; const mcos = Math.cos; const mpi = Math.PI; const cosSlant = mcos(slant); const sinSlant = msin(slant); const start = topCap ? -2 : 0; const end = nvertical + (bottomCap ? 2 : 0); const vertsAroundEdge = nradial + 1; const indices = new Uint16Array(nradial * (nvertical + extra) * 6); const indexOffset = INDEX_OFFSETS[verticalAxis]; const positions = new Float32Array(numVertices * 3); const normals = new Float32Array(numVertices * 3); const texCoords = new Float32Array(numVertices * 2); let i3 = 0; let i2 = 0; for (let i = start; i <= end; i++) { let v = i / nvertical; let y = height * v; let ringRadius; if (i < 0) { y = 0; v = 1; ringRadius = bottomRadius; } else if (i > nvertical) { y = height; v = 1; ringRadius = topRadius; } else { ringRadius = bottomRadius + (topRadius - bottomRadius) * (i / nvertical); } if (i === -2 || i === nvertical + 2) { ringRadius = 0; v = 0; } y -= height / 2; for (let j = 0; j < vertsAroundEdge; j++) { const sin = msin(j * mpi * 2 / nradial); const cos = mcos(j * mpi * 2 / nradial); positions[i3 + indexOffset[0]] = sin * ringRadius; positions[i3 + indexOffset[1]] = y; positions[i3 + indexOffset[2]] = cos * ringRadius; normals[i3 + indexOffset[0]] = i < 0 || i > nvertical ? 0 : sin * cosSlant; normals[i3 + indexOffset[1]] = i < 0 ? -1 : i > nvertical ? 1 : sinSlant; normals[i3 + indexOffset[2]] = i < 0 || i > nvertical ? 0 : cos * cosSlant; texCoords[i2 + 0] = j / nradial; texCoords[i2 + 1] = v; i2 += 2; i3 += 3; } } for (let i = 0; i < nvertical + extra; i++) { for (let j = 0; j < nradial; j++) { const index = (i * nradial + j) * 6; indices[index + 0] = vertsAroundEdge * (i + 0) + 0 + j; indices[index + 1] = vertsAroundEdge * (i + 0) + 1 + j; indices[index + 2] = vertsAroundEdge * (i + 1) + 1 + j; indices[index + 3] = vertsAroundEdge * (i + 0) + 0 + j; indices[index + 4] = vertsAroundEdge * (i + 1) + 1 + j; indices[index + 5] = vertsAroundEdge * (i + 1) + 0 + j; } } return { indices, attributes: { POSITION: positions, NORMAL: normals, TEXCOORD_0: texCoords } }; } // dist/geometries/cone-geometry.js var ConeGeometry = class extends TruncatedConeGeometry { constructor(props = {}) { const { id = (0, import_core20.uid)("cone-geometry"), radius = 1, cap = true } = props; super({ ...props, id, topRadius: 0, topCap: Boolean(cap), bottomCap: Boolean(cap), bottomRadius: radius }); } }; // dist/geometries/cube-geometry.js var import_core21 = require("@luma.gl/core"); var CubeGeometry = class extends Geometry { constructor(props = {}) { const { id = (0, import_core21.uid)("cube-geometry"), indices = true } = props; super(indices ? { ...props, id, topology: "triangle-list", indices: { size: 1, value: CUBE_INDICES }, attributes: { ...ATTRIBUTES, ...props.attributes } } : { ...props, id, topology: "triangle-list", indices: void 0, attributes: { ...NON_INDEXED_ATTRIBUTES, ...props.attributes } }); } }; var CUBE_INDICES = new Uint16Array([ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23 ]); var CUBE_POSITIONS = new Float32Array([ -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1 ]); var CUBE_NORMALS = new Float32Array([ // Front face 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // Back face 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, // Top face 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // Bottom face 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, // Right face 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // Left face -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0 ]); var CUBE_TEX_COORDS = new Float32Array([ // Front face 0, 0, 1, 0, 1, 1, 0, 1, // Back face 1, 0, 1, 1, 0, 1, 0, 0, // Top face 0, 1, 0, 0, 1, 0, 1, 1, // Bottom face 1, 1, 0, 1, 0, 0, 1, 0, // Right face 1, 0, 1, 1, 0, 1, 0, 0, // Left face 0, 0, 1, 0, 1, 1, 0, 1 ]); var CUBE_NON_INDEXED_POSITIONS = new Float32Array([ 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1 ]); var CUBE_NON_INDEXED_TEX_COORDS = new Float32Array([ 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0 ]); var CUBE_NON_INDEXED_COLORS = new Float32Array([ 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1 ]); var ATTRIBUTES = { POSITION: { size: 3, value: CUBE_POSITIONS }, NORMAL: { size: 3, value: CUBE_NORMALS }, TEXCOORD_0: { size: 2, value: CUBE_TEX_COORDS } }; var NON_INDEXED_ATTRIBUTES = { POSITION: { size: 3, value: CUBE_NON_INDEXED_POSITIONS }, // NORMAL: {size: 3, value: CUBE_NON_INDEXED_NORMALS}, TEXCOORD_0: { size: 2, value: CUBE_NON_INDEXED_TEX_COORDS }, COLOR_0: { size: 3, value: CUBE_NON_INDEXED_COLORS } }; // dist/geometries/cylinder-geometry.js var import_core22 = require("@luma.gl/core"); var CylinderGeometry = class extends TruncatedConeGeometry { constructor(props = {}) { const { id = (0, import_core22.uid)("cylinder-geometry"), radius = 1 } = props; super({ ...props, id, bottomRadius: radius, topRadius: radius }); } }; // dist/geometries/ico-sphere-geometry.js var import_core23 = require("@luma.gl/core"); var import_core24 = require("@math.gl/core"); var ICO_POSITIONS = [-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 1, 0, -1, 0, 1, 0, 0]; var ICO_INDICES = [3, 4, 5, 3, 5, 1, 3, 1, 0, 3, 0, 4, 4, 0, 2, 4, 2, 5, 2, 0, 1, 5, 2, 1]; var IcoSphereGeometry = class extends Geometry { constructor(props = {}) { const { id = (0, import_core23.uid)("ico-sphere-geometry") } = props; const { indices, attributes } = tesselateIcosaHedron(props); super({ ...props, id, topology: "triangle-list", indices, attributes: { ...attributes, ...props.attributes } }); } }; function tesselateIcosaHedron(props) { const { iterations = 0 } = props; const PI = Math.PI; const PI2 = PI * 2; const positions = [...ICO_POSITIONS]; let indices = [...ICO_INDICES]; positions.push(); indices.push(); const getMiddlePoint = (() => { const pointMemo = {}; return (i1, i2) => { i1 *= 3; i2 *= 3; const mini = i1 < i2 ? i1 : i2; const maxi = i1 > i2 ? i1 : i2; const key = `${mini}|${maxi}`; if (key in pointMemo) { return pointMemo[key]; } const x1 = positions[i1]; const y1 = positions[i1 + 1]; const z1 = positions[i1 + 2]; const x2 = positions[i2]; const y2 = positions[i2 + 1]; const z2 = positions[i2 + 2]; let xm = (x1 + x2) / 2; let ym = (y1 + y2) / 2; let zm = (z1 + z2) / 2; const len = Math.sqrt(xm * xm + ym * ym + zm * zm); xm /= len; ym /= len; zm /= len; positions.push(xm, ym, zm); return pointMemo[key] = positions.length / 3 - 1; }; })(); for (let i = 0; i < iterations; i++) { const indices2 = []; for (let j = 0; j < indices.length; j += 3) { const a = getMiddlePoint(indices[j + 0], indices[j + 1]); const b = getMiddlePoint(indices[j + 1], indices[j + 2]); const c = getMiddlePoint(indices[j + 2], indices[j + 0]); indices2.push(c, indices[j + 0], a, a, indices[j + 1], b, b, indices[j + 2], c, a, b, c); } indices = indices2; } const normals = new Array(positions.length); const texCoords = new Array(positions.length / 3 * 2); const l = indices.length; for (let i = l - 3; i >= 0; i -= 3) { const i1 = indices[i + 0]; const i2 = indices[i + 1]; const i3 = indices[i + 2]; const in1 = i1 * 3; const in2 = i2 * 3; const in3 = i3 * 3; const iu1 = i1 * 2; const iu2 = i2 * 2; const iu3 = i3 * 2; const x1 = positions[in1 + 0]; const y1 = positions[in1 + 1]; const z1 = positions[in1 + 2]; const theta1 = Math.acos(z1 / Math.sqrt(x1 * x1 + y1 * y1 + z1 * z1)); const phi1 = Math.atan2(y1, x1) + PI; const v1 = theta1 / PI; const u1 = 1 - phi1 / PI2; const x2 = positions[in2 + 0]; const y2 = positions[in2 + 1]; const z2 = positions[in2 + 2]; const theta2 = Math.acos(z2 / Math.sqrt(x2 * x2 + y2 * y2 + z2 * z2)); const phi2 = Math.atan2(y2, x2) + PI; const v2 = theta2 / PI; const u2 = 1 - phi2 / PI2; const x3 = positions[in3 + 0]; const y3 = positions[in3 + 1]; const z3 = positions[in3 + 2]; const theta3 = Math.acos(z3 / Math.sqrt(x3 * x3 + y3 * y3 + z3 * z3)); const phi3 = Math.atan2(y3, x3) + PI; const v3 = theta3 / PI; const u3 = 1 - phi3 / PI2; const vec1 = [x3 - x2, y3 - y2, z3 - z2]; const vec2 = [x1 - x2, y1 - y2, z1 - z2]; const normal = new import_core24.Vector3(vec1).cross(vec2).normalize(); let newIndex; if ((u1 === 0 || u2 === 0 || u3 === 0) && (u1 === 0 || u1 > 0.5) && (u2 === 0 || u2 > 0.5) && (u3 === 0 || u3 > 0.5)) { positions.push(positions[in1 + 0], positions[in1 + 1], positions[in1 + 2]); newIndex = positions.length / 3 - 1; indices.push(newIndex); texCoords[newIndex * 2 + 0] = 1; texCoords[newIndex * 2 + 1] = v1; normals[newIndex * 3 + 0] = normal.x; normals[newIndex * 3 + 1] = normal.y; normals[newIndex * 3 + 2] = normal.z; positions.push(positions[in2 + 0], positions[in2 + 1], positions[in2 + 2]); newIndex = positions.length / 3 - 1; indices.push(newIndex); texCoords[newIndex * 2 + 0] = 1; texCoords[newIndex * 2 + 1] = v2; normals[newIndex * 3 + 0] = normal.x; normals[newIndex * 3 + 1] = normal.y; normals[newIndex * 3 + 2] = normal.z; positions.push(positions[in3 + 0], positions[in3 + 1], positions[in3 + 2]); newIndex = positions.length / 3 - 1; indices.push(newIndex); texCoords[newIndex * 2 + 0] = 1; texCoords[newIndex * 2 + 1] = v3; normals[newIndex * 3 + 0] = normal.x; normals[newIndex * 3 + 1] = normal.y; normals[newIndex * 3 + 2] = normal.z; } normals[in1 + 0] = normals[in2 + 0] = normals[in3 + 0] = normal.x; normals[in1 + 1] = normals[in2 + 1] = normals[in3 + 1] = normal.y; normals[in1 + 2] = normals[in2 + 2] = normals[in3 + 2] = normal.z; texCoords[iu1 + 0] = u1; texCoords[iu1 + 1] = v1; texCoords[iu2 + 0] = u2; texCoords[iu2 + 1] = v2; texCoords[iu3 + 0] = u3; texCoords[iu3 + 1] = v3; } return { indices: { size: 1, value: new Uint16Array(indices) }, attributes: { POSITION: { size: 3, value: new Float32Array(positions) }, NORMAL: { size: 3, value: new Float32Array(normals) }, TEXCOORD_0: { size: 2, value: new Float32Array(texCoords) } } }; } // dist/geometries/plane-geometry.js var import_core25 = require("@luma.gl/core"); // dist/geometry/geometry-utils.js function unpackIndexedGeometry(geometry) { const { indices, attributes } = geometry; if (!indices) { return geometry; } const vertexCount = indices.value.length; const unpackedAttributes = {}; for (const attributeName in attributes) { const attribute = attributes[attributeName]; const { constant, value, size } = attribute; if (constant || !size) { continue; } const unpackedValue = new value.constructor(vertexCount * size); for (let x = 0; x < vertexCount; ++x) { const index = indices.value[x]; for (let i = 0; i < size; i++) { unpackedValue[x * size + i] = value[index * size + i]; } } unpackedAttributes[attributeName] = { size, value: unpackedValue }; } return { attributes: Object.assign({}, attributes, unpackedAttributes) }; } // dist/geometries/plane-geometry.js var PlaneGeometry = class extends Geometry { constructor(props = {}) { const { id = (0, import_core25.uid)("plane-geometry") } = props; const { indices, attributes } = tesselatePlane(props); super({ ...props, id, topology: "triangle-list", indices, attributes: { ...attributes, ...props.attributes } }); } }; function tesselatePlane(props) { const { type = "x,y", offset = 0, flipCull = false, unpack = false } = props; const coords = type.split(","); let c1len = props[`${coords[0]}len`] || 1; const c2len = props[`${coords[1]}len`] || 1; const subdivisions1 = props[`n${coords[0]}`] || 1; const subdivisions2 = props[`n${coords[1]}`] || 1; const numVertices = (subdivisions1 + 1) * (subdivisions2 + 1); const positions = new Float32Array(numVertices * 3); const normals = new Float32Array(numVertices * 3); const texCoords = new Float32Array(numVertices * 2); if (flipCull) { c1len = -c1len; } let i2 = 0; let i3 = 0; for (let z = 0; z <= subdivisions2; z++) { for (let x = 0; x <= subdivisions1; x++) { const u = x / subdivisions1; const v = z / subdivisions2; texCoords[i2 + 0] = flipCull ? 1 - u : u; texCoords[i2 + 1] = v; switch (type) { case "x,y": positions[i3 + 0] = c1len * u - c1len * 0.5; positions[i3 + 1] = c2len * v - c2len * 0.5; positions[i3 + 2] = offset; normals[i3 + 0] = 0; normals[i3 + 1] = 0; normals[i3 + 2] = flipCull ? 1 : -1; break; case "x,z": positions[i3 + 0] = c1len * u - c1len * 0.5; positions[i3 + 1] = offset; positions[i3 + 2] = c2len * v - c2len * 0.5; normals[i3 + 0] = 0; normals[i3 + 1] = flipCull ? 1 : -1; normals[i3 + 2] = 0; break; case "y,z": positions[i3 + 0] = offset; positions[i3 + 1] = c1len * u - c1len * 0.5; positions[i3 + 2] = c2len * v - c2len * 0.5; normals[i3 + 0] = flipCull ? 1 : -1; normals[i3 + 1] = 0; normals[i3 + 2] = 0; break; default: throw new Error("PlaneGeometry: unknown type"); } i2 += 2; i3 += 3; } } const numVertsAcross = subdivisions1 + 1; const indices = new Uint16Array(subdivisions1 * subdivisions2 * 6); for (let z = 0; z < subdivisions2; z++) { for (let x = 0; x < subdivisions1; x++) { const index = (z * subdivisions1 + x) * 6; indices[index + 0] = (z + 0) * numVertsAcross + x; indices[index + 1] = (z + 1) * numVertsAcross + x; indices[index + 2] = (z + 0) * numVertsAcross + x + 1; indices[index + 3] = (z + 1) * numVertsAcross + x; indices[index + 4] = (z + 1) * numVertsAcross + x + 1; indices[index + 5] = (z + 0) * numVertsAcross + x + 1; } } const geometry = { indices: { size: 1, value: indices }, attributes: { POSITION: { size: 3, value: positions }, NORMAL: { size: 3, value: normals }, TEXCOORD_0: { size: 2, value: texCoords } } }; return unpack ? unpackIndexedGeometry(geometry) : geometry; } // dist/geometries/sphere-geometry.js var import_core26 = require("@luma.gl/core"); var SphereGeometry = class extends Geometry { constructor(props = {}) { const { id = (0, import_core26.uid)("sphere-geometry") } = props; const { indices, attributes } = tesselateSphere(props); super({ ...props, id, topology: "triangle-list", indices, attributes: { ...attributes, ...props.attributes } }); } }; function tesselateSphere(props) { const { nlat = 10, nlong = 10 } = props; const startLat = 0; const endLat = Math.PI; const latRange = endLat - startLat; const startLong = 0; const endLong = 2 * Math.PI; const longRange = endLong - startLong; const numVertices = (nlat + 1) * (nlong + 1); const radius = (n1, n2, n3, u, v) => props.radius || 1; const positions = new Float32Array(numVertices * 3); const normals = new Float32Array(numVertices * 3); const texCoords = new Float32Array(numVertices * 2); const IndexType = numVertices > 65535 ? Uint32Array : Uint16Array; const indices = new IndexType(nlat * nlong * 6); for (let y = 0; y <= nlat; y++) { for (let x = 0; x <= nlong; x++) { const u = x / nlong; const v = y / nlat; const index = x + y * (nlong + 1); const i2 = index * 2; const i3 = index * 3; const theta = longRange * u; const phi = latRange * v; const sinTheta = Math.sin(theta); const cosTheta = Math.cos(theta); const sinPhi = Math.sin(phi); const cosPhi = Math.cos(phi); const ux = cosTheta * sinPhi; const uy = cosPhi; const uz = sinTheta * sinPhi; const r = radius(ux, uy, uz, u, v); positions[i3 + 0] = r * ux; positions[i3 + 1] = r * uy; positions[i3 + 2] = r * uz; normals[i3 + 0] = ux; normals[i3 + 1] = uy; normals[i3 + 2] = uz; texCoords[i2 + 0] = u; texCoords[i2 + 1] = 1 - v; } } const numVertsAround = nlong + 1; for (let x = 0; x < nlong; x++) { for (let y = 0; y < nlat; y++) { const index = (x * nlat + y) * 6; indices[index + 0] = y * numVertsAround + x; indices[index + 1] = y * numVertsAround + x + 1; indices[index + 2] = (y + 1) * numVertsAround + x; indices[index + 3] = (y + 1) * numVertsAround + x; indices[index + 4] = y * numVertsAround + x + 1; indices[index + 5] = (y + 1) * numVertsAround + x + 1; } } return { indices: { size: 1, value: indices }, attributes: { POSITION: { size: 3, value: positions }, NORMAL: { size: 3, value: normals }, TEXCOORD_0: { size: 2, value: texCoords } } }; } // dist/computation.js var import_core27 = require("@luma.gl/core"); var import_core28 = require("@luma.gl/core"); var import_core29 = require("@luma.gl/core"); var import_shadertools5 = require("@luma.gl/shadertools"); var LOG_DRAW_PRIORITY2 = 2; var LOG_DRAW_TIMEOUT2 = 1e4; var _Computation = class { device; id; pipelineFactory; shaderFactory; userData = {}; /** Bindings (textures, samplers, uniform buffers) */ bindings = {}; /** The underlying GPU "program". @note May be recreated if parameters change */ pipeline; /** the underlying compiled compute shader */ shader; source; /** ShaderInputs instance */ shaderInputs; _uniformStore; _pipelineNeedsUpdate = "newly created"; _getModuleUniforms; props; _destroyed = false; constructor(device, props) { var _a, _b, _c; if (device.type !== "webgpu") { throw new Error("Computation is only supported in WebGPU"); } this.props = { ..._Computation.defaultProps, ...props }; props = this.props; this.id = props.id || (0, import_core28.uid)("model"); this.device = device; Object.assign(this.userData, props.userData); const moduleMap = Object.fromEntries(((_a = this.props.modules) == null ? void 0 : _a.map((module2) => [module2.name, module2])) || []); this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap)); this.props.shaderLayout ||= (0, import_shadertools5.getShaderLayoutFromWGSL)(this.props.source); const platformInfo = getPlatformInfo2(device); const modules = (((_b = this.props.modules) == null ? void 0 : _b.length) > 0 ? this.props.modules : (_c = this.shaderInputs) == null ? void 0 : _c.getModules()) || []; this.pipelineFactory = props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device); this.shaderFactory = props.shaderFactory || ShaderFactory.getDefaultShaderFactory(this.device); const { source, getUniforms } = this.props.shaderAssembler.assembleShader({ platformInfo, ...this.props, modules }); this.source = source; this._getModuleUniforms = getUniforms; this.pipeline = this._updatePipeline(); if (props.bindings) { this.setBindings(props.bindings); } Object.seal(this); } destroy() { if (this._destroyed) return; this.pipelineFactory.release(this.pipeline); this.shaderFactory.release(this.shader); this._uniformStore.destroy(); this._destroyed = true; } // Draw call predraw() { this.updateShaderInputs(); } dispatch(computePass, x, y, z) { try { this._logDrawCallStart(); this.pipeline = this._updatePipeline(); this.pipeline.setBindings(this.bindings); computePass.setPipeline(this.pipeline); computePass.setBindings([]); computePass.dispatch(x, y, z); } finally { this._logDrawCallEnd(); } } // Update fixed fields (can trigger pipeline rebuild) // Update dynamic fields /** * Updates the vertex count (used in draw calls) * @note Any attributes with stepMode=vertex need to be at least this big */ setVertexCount(vertexCount) { } /** * Updates the instance count (used in draw calls) * @note Any attributes with stepMode=instance need to be at least this big */ setInstanceCount(instanceCount) { } setShaderInputs(shaderInputs) { this.shaderInputs = shaderInputs; this._uniformStore = new import_core27.UniformStore(this.shaderInputs.modules); for (const moduleName of Object.keys(this.shaderInputs.modules)) { const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName); this.bindings[`${moduleName}Uniforms`] = uniformBuffer; } } /** * Updates shader module settings (which results in uniforms being set) */ setShaderModuleProps(props) { const uniforms = this._getModuleUniforms(props); const keys = Object.keys(uniforms).filter((k) => { const uniform = uniforms[k]; return !(0, import_core28.isNumberArray)(uniform) && typeof uniform !== "number" && typeof uniform !== "boolean"; }); const bindings = {}; for (const k of keys) { bindings[k] = uniforms[k]; delete uniforms[k]; } } updateShaderInputs() { this._uniformStore.setUniforms(this.shaderInputs.getUniformValues()); } /** * Sets bindings (textures, samplers, uniform buffers) */ setBindings(bindings) { Object.assign(this.bindings, bindings); } _setPipelineNeedsUpdate(reason) { this._pipelineNeedsUpdate = this._pipelineNeedsUpdate || reason; } _updatePipeline() { if (this._pipelineNeedsUpdate) { let prevShader = null; if (this.pipeline) { import_core28.log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)(); prevShader = this.shader; } this._pipelineNeedsUpdate = false; this.shader = this.shaderFactory.createShader({ id: `${this.id}-fragment`, stage: "compute", source: this.source, debug: this.props.debugShaders }); this.pipeline = this.pipelineFactory.createComputePipeline({ ...this.props, shader: this.shader }); if (prevShader) { this.shaderFactory.release(prevShader); } } return this.pipeline; } /** Throttle draw call logging */ _lastLogTime = 0; _logOpen = false; _logDrawCallStart() { const logDrawTimeout = import_core28.log.level > 3 ? 0 : LOG_DRAW_TIMEOUT2; if (import_core28.log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) { return; } this._lastLogTime = Date.now(); this._logOpen = true; import_core28.log.group(LOG_DRAW_PRIORITY2, `>>> DRAWING MODEL ${this.id}`, { collapsed: import_core28.log.level <= 2 })(); } _logDrawCallEnd() { if (this._logOpen) { const uniformTable = this.shaderInputs.getDebugTable(); import_core28.log.table(LOG_DRAW_PRIORITY2, uniformTable)(); import_core28.log.groupEnd(LOG_DRAW_PRIORITY2)(); this._logOpen = false; } } _drawCount = 0; // TODO - fix typing of luma data types _getBufferOrConstantValues(attribute, dataType) { const TypedArrayConstructor = (0, import_core29.getTypedArrayFromDataType)(dataType); const typedArray = attribute instanceof import_core27.Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute; return typedArray.toString(); } }; var Computation = _Computation; __publicField(Computation, "defaultProps", { ...import_core27.ComputePipeline.defaultProps, id: "unnamed", handle: void 0, userData: {}, source: "", modules: [], defines: {}, bindings: void 0, shaderInputs: void 0, pipelineFactory: void 0, shaderFactory: void 0, shaderAssembler: import_shadertools5.ShaderAssembler.getDefaultShaderAssembler(), debugShaders: void 0 }); function getPlatformInfo2(device) { return { type: device.type, shaderLanguage: device.info.shadingLanguage, shaderLanguageVersion: device.info.shadingLanguageVersion, gpu: device.info.gpu, // HACK - we pretend that the DeviceFeatures is a Set, it has a similar API features: device.features }; } //# sourceMappingURL=index.cjs.map