UNPKG

@luma.gl/engine

Version:

3D Engine Components for luma.gl

1,552 lines (1,528 loc) 202 kB
"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, AsyncTexture: () => AsyncTexture, BackgroundTextureModel: () => BackgroundTextureModel, BufferTransform: () => BufferTransform, ClipSpace: () => ClipSpace, Computation: () => Computation, ConeGeometry: () => ConeGeometry, CubeGeometry: () => CubeGeometry, CylinderGeometry: () => CylinderGeometry, DirectionalLightModel: () => DirectionalLightModel, DynamicTexture: () => DynamicTexture, GPUGeometry: () => GPUGeometry, Geometry: () => Geometry, GroupNode: () => GroupNode, IcoSphereGeometry: () => IcoSphereGeometry, KeyFrames: () => KeyFrames, LegacyPickingManager: () => LegacyPickingManager, Material: () => Material, MaterialFactory: () => MaterialFactory, Model: () => Model, ModelNode: () => ModelNode, PickingManager: () => PickingManager, PlaneGeometry: () => PlaneGeometry, PointLightModel: () => PointLightModel, ScenegraphNode: () => ScenegraphNode, ShaderInputs: () => ShaderInputs, ShaderPassRenderer: () => ShaderPassRenderer, SphereGeometry: () => SphereGeometry, SpotLightModel: () => SpotLightModel, Swap: () => Swap, SwapBuffers: () => SwapBuffers, SwapFramebuffers: () => SwapFramebuffers, TextureTransform: () => TextureTransform, Timeline: () => Timeline, TruncatedConeGeometry: () => TruncatedConeGeometry, cancelAnimationFramePolyfill: () => cancelAnimationFramePolyfill, colorPicking: () => picking, indexPicking: () => picking2, legacyColorPicking: () => legacyColorPicking, loadImage: () => loadImage, loadImageBitmap: () => loadImageBitmap, makeAnimationLoop: () => makeAnimationLoop, makeRandomGenerator: () => makeRandomGenerator, picking: () => picking3, requestAnimationFramePolyfill: () => requestAnimationFramePolyfill, resolvePickingBackend: () => resolvePickingBackend, resolvePickingMode: () => resolvePickingMode, setPathPrefix: () => setPathPrefix, supportsIndexPicking: () => supportsIndexPicking }); 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"); // dist/animation-loop/request-animation-frame.js function requestAnimationFramePolyfill(callback) { const browserRequestAnimationFrame = typeof window !== "undefined" ? window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame : null; if (browserRequestAnimationFrame) { return browserRequestAnimationFrame.call(window, callback); } return setTimeout(() => callback(typeof performance !== "undefined" ? performance.now() : Date.now()), 1e3 / 60); } function cancelAnimationFramePolyfill(timerId) { const browserCancelAnimationFrame = typeof window !== "undefined" ? window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame : null; if (browserCancelAnimationFrame) { browserCancelAnimationFrame.call(window, timerId); return; } clearTimeout(timerId); } // dist/animation-loop/animation-loop.js var import_stats = require("@probe.gl/stats"); var statIdCounter = 0; var ANIMATION_LOOP_STATS = "Animation Loop"; var _AnimationLoop = class { device = null; canvas = null; props; animationProps = null; timeline = null; stats; sharedStats; cpuTime; gpuTime; frameRate; display; _needsRedraw = "initialized"; _initialized = false; _running = false; _animationFrameId = null; _nextFramePromise = null; _resolveNextFrame = null; _cpuStartTime = 0; _error = null; _lastFrameTime = 0; /* * @param {HTMLCanvasElement} canvas - if provided, width and height will be passed to context */ constructor(props) { this.props = { ..._AnimationLoop.defaultAnimationLoopProps, ...props }; props = this.props; if (!props.device) { throw new Error("No device provided"); } this.stats = props.stats || new import_stats.Stats({ id: `animation-loop-${statIdCounter++}` }); this.sharedStats = import_core.luma.stats.get(ANIMATION_LOOP_STATS); this.frameRate = this.stats.get("Frame Rate"); this.frameRate.setSampleSize(1); this.cpuTime = this.stats.get("CPU Time"); this.gpuTime = this.stats.get("GPU Time"); this.setProps({ autoResizeViewport: props.autoResizeViewport }); 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() { var _a; this.stop(); this._setDisplay(null); (_a = this.device) == null ? void 0 : _a._disableDebugGPUTime(); } /** @deprecated Use .destroy() */ delete() { this.destroy(); } reportError(error) { this.props.onError(error); this._error = error; } /** Flags this animation loop as needing redraw */ setNeedsRedraw(reason) { this._needsRedraw = this._needsRedraw || reason; return this; } /** Query redraw status. Clears the flag. */ needsRedraw() { const reason = this._needsRedraw; this._needsRedraw = false; return reason; } setProps(props) { if ("autoResizeViewport" in props) { this.props.autoResizeViewport = props.autoResizeViewport || 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(); if (!this._running) { return null; } 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._error) { this.props.onFinalize(this.animationProps); } this._cancelAnimationFrame(); this._nextFramePromise = null; this._resolveNextFrame = null; this._running = false; this._lastFrameTime = 0; } return this; } /** Explicitly draw a frame */ redraw(time) { var _a; if (((_a = this.device) == null ? void 0 : _a.isLost) || this._error) { return this; } this._beginFrameTimers(time); 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() { var _a; this._startEventHandling(); this._initializeAnimationProps(); this._updateAnimationProps(); this._resizeViewport(); (_a = this.device) == null ? void 0 : _a._enableDebugGPUTime(); } _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 = requestAnimationFramePolyfill(this._animationFrame.bind(this)); } _cancelAnimationFrame() { if (this._animationFrameId === null) { return; } cancelAnimationFramePolyfill(this._animationFrameId); this._animationFrameId = null; } _animationFrame(time) { if (!this._running) { return; } this.redraw(time); this._requestAnimationFrame(); } // Called on each frame, can be overridden to call onRender multiple times // to support e.g. stereoscopic rendering _renderFrame(animationProps) { var _a; if (this.display) { this.display._renderFrame(animationProps); return; } this.props.onRender(this._getAnimationProps()); (_a = this.device) == null ? void 0 : _a.submit(); } _clearNeedsRedraw() { this._needsRedraw = false; } _setupFrame() { this._resizeViewport(); } // Initialize the object that will be passed to app callbacks _initializeAnimationProps() { var _a; const canvasContext = (_a = this.device) == null ? void 0 : _a.getDefaultCanvasContext(); if (!this.device || !canvasContext) { throw new Error("loop"); } const canvas = canvasContext == null ? void 0 : canvasContext.canvas; const useDevicePixels = canvasContext.props.useDevicePixels; this.animationProps = { animationLoop: this, device: this.device, canvasContext, canvas, // @ts-expect-error Deprecated useDevicePixels, timeline: this.timeline, 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() { this.device = await this.props.device; if (!this.device) { throw new Error("No device provided"); } this.canvas = this.device.getDefaultCanvasContext().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() { if (!this.device) { return { width: 1, height: 1, aspect: 1 }; } const [width, height] = this.device.getDefaultCanvasContext().getDrawingBufferSize(); const aspect = width > 0 && height > 0 ? width / height : 1; return { width, height, aspect }; } /** @deprecated 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 ); } } _beginFrameTimers(time) { var _a; const now = time ?? (typeof performance !== "undefined" ? performance.now() : Date.now()); if (this._lastFrameTime) { const frameTime = now - this._lastFrameTime; if (frameTime > 0) { this.frameRate.addTime(frameTime); } } this._lastFrameTime = now; if ((_a = this.device) == null ? void 0 : _a._isDebugGPUTimeEnabled()) { this._consumeEncodedGpuTime(); } this.cpuTime.timeStart(); } _endFrameTimers() { var _a; if ((_a = this.device) == null ? void 0 : _a._isDebugGPUTimeEnabled()) { this._consumeEncodedGpuTime(); } this.cpuTime.timeEnd(); this._updateSharedStats(); } _consumeEncodedGpuTime() { if (!this.device) { return; } const gpuTimeMs = this.device.commandEncoder._gpuTimeMs; if (gpuTimeMs !== void 0) { this.gpuTime.addTime(gpuTimeMs); this.device.commandEncoder._gpuTimeMs = void 0; } } _updateSharedStats() { if (this.stats === this.sharedStats) { return; } for (const name of Object.keys(this.sharedStats.stats)) { if (!this.stats.stats[name]) { delete this.sharedStats.stats[name]; } } this.stats.forEach((sourceStat) => { const targetStat = this.sharedStats.get(sourceStat.name, sourceStat.type); targetStat.sampleSize = sourceStat.sampleSize; targetStat.time = sourceStat.time; targetStat.count = sourceStat.count; targetStat.samples = sourceStat.samples; targetStat.lastTiming = sourceStat.lastTiming; targetStat.lastSampleTime = sourceStat.lastSampleTime; targetStat.lastSampleCount = sourceStat.lastSampleCount; targetStat._count = sourceStat._count; targetStat._time = sourceStat._time; targetStat._samples = sourceStat._samples; targetStat._startTime = sourceStat._startTime; targetStat._timerPending = sourceStat._timerPending; }); } // 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; } }; var AnimationLoop = _AnimationLoop; __publicField(AnimationLoop, "defaultAnimationLoopProps", { device: null, onAddHTML: () => "", onInitialize: async () => null, onRender: () => { }, onFinalize: () => { }, onError: (error) => console.error(error), // eslint-disable-line no-console stats: void 0, // view parameters autoResizeViewport: false }); // dist/animation-loop/make-animation-loop.js var import_core2 = require("@luma.gl/core"); function makeAnimationLoop(AnimationLoopTemplateCtor, props) { let renderLoop = null; const device = (props == null ? void 0 : props.device) || import_core2.luma.createDevice({ id: "animation-loop", adapters: props == null ? void 0 : props.adapters, createCanvasContext: true }); const animationLoop = new AnimationLoop({ ...props, device, async onInitialize(animationProps) { clearError(animationProps.animationLoop.device); try { renderLoop = new AnimationLoopTemplateCtor(animationProps); return await (renderLoop == null ? void 0 : renderLoop.onInitialize(animationProps)); } catch (error) { console.error(error); setError(animationProps.animationLoop.device, error); return null; } }, 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; } function setError(device, error) { var _a; if (!device) { return; } const canvas = device.getDefaultCanvasContext().canvas; if (canvas instanceof HTMLCanvasElement) { canvas.style.overflow = "visible"; let errorDiv = document.getElementById("animation-loop-error"); errorDiv == null ? void 0 : errorDiv.remove(); errorDiv = document.createElement("h1"); errorDiv.id = "animation-loop-error"; errorDiv.innerHTML = error.message; errorDiv.style.position = "absolute"; errorDiv.style.top = "10px"; errorDiv.style.left = "10px"; errorDiv.style.color = "black"; errorDiv.style.backgroundColor = "red"; (_a = canvas.parentElement) == null ? void 0 : _a.appendChild(errorDiv); } } function clearError(device) { if (!device) { return; } const errorDiv = document.getElementById("animation-loop-error"); if (errorDiv) { errorDiv.remove(); } } // dist/model/model.js var import_core8 = require("@luma.gl/core"); var import_shadertools2 = require("@luma.gl/shadertools"); // dist/geometry/gpu-geometry.js var import_core3 = require("@luma.gl/core"); // dist/utils/uid.js var uidCounters = {}; function uid(id = "id") { uidCounters[id] = uidCounters[id] || 1; const count = uidCounters[id]++; return `${id}-${count}`; } // dist/geometry/gpu-geometry.js 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 || 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) { if (!(this.indices.usage & import_core3.Buffer.INDEX)) { throw new Error("Index buffer must have INDEX usage"); } } } 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 || null; } _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_core3.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 "TEXCOORD_1": name = "texCoords1"; break; case "COLOR_0": name = "colors"; break; } if (attribute) { attributes[name] = device.createBuffer({ data: attribute.value, id: `${attributeName}-buffer` }); const { value, size, normalized } = attribute; if (size === void 0) { throw new Error(`Attribute ${attributeName} is missing a size`); } bufferLayout.push({ name, format: import_core3.vertexFormatDecoder.getVertexFormatFromAttribute(value, size, normalized) }); } } const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices); return { attributes, bufferLayout, vertexCount }; } // 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) }; } return table; } // dist/debug/debug-framebuffer.js var DEBUG_FRAMEBUFFER_STATE_KEY = "__debugFramebufferState"; var DEFAULT_MARGIN_PX = 8; function debugFramebuffer(renderPass, source3, options) { if (renderPass.device.type !== "webgl") { return; } const state = getDebugFramebufferState(renderPass.device); if (state.flushing) { return; } if (isDefaultRenderPass(renderPass)) { flushDebugFramebuffers(renderPass, options, state); return; } if (source3 && isFramebuffer(source3) && source3.handle !== null) { if (!state.queuedFramebuffers.includes(source3)) { state.queuedFramebuffers.push(source3); } } } function flushDebugFramebuffers(renderPass, options, state) { if (state.queuedFramebuffers.length === 0) { return; } const webglDevice = renderPass.device; const { gl } = webglDevice; const previousReadFramebuffer = gl.getParameter(36010); const previousDrawFramebuffer = gl.getParameter(36006); const [targetWidth, targetHeight] = renderPass.device.getDefaultCanvasContext().getDrawingBufferSize(); let topPx = parseCssPixel(options.top, DEFAULT_MARGIN_PX); const leftPx = parseCssPixel(options.left, DEFAULT_MARGIN_PX); state.flushing = true; try { for (const framebuffer of state.queuedFramebuffers) { const [targetX0, targetY0, targetX1, targetY1, previewHeight] = getOverlayRect({ framebuffer, targetWidth, targetHeight, topPx, leftPx, minimap: options.minimap }); gl.bindFramebuffer(36008, framebuffer.handle); gl.bindFramebuffer(36009, null); gl.blitFramebuffer(0, 0, framebuffer.width, framebuffer.height, targetX0, targetY0, targetX1, targetY1, 16384, 9728); topPx += previewHeight + DEFAULT_MARGIN_PX; } } finally { gl.bindFramebuffer(36008, previousReadFramebuffer); gl.bindFramebuffer(36009, previousDrawFramebuffer); state.flushing = false; } } function getOverlayRect(options) { const { framebuffer, targetWidth, targetHeight, topPx, leftPx, minimap } = options; const maxWidth = minimap ? Math.max(Math.floor(targetWidth / 4), 1) : targetWidth; const maxHeight = minimap ? Math.max(Math.floor(targetHeight / 4), 1) : targetHeight; const scale = Math.min(maxWidth / framebuffer.width, maxHeight / framebuffer.height); const previewWidth = Math.max(Math.floor(framebuffer.width * scale), 1); const previewHeight = Math.max(Math.floor(framebuffer.height * scale), 1); const targetX0 = leftPx; const targetY0 = Math.max(targetHeight - topPx - previewHeight, 0); const targetX1 = targetX0 + previewWidth; const targetY1 = targetY0 + previewHeight; return [targetX0, targetY0, targetX1, targetY1, previewHeight]; } function getDebugFramebufferState(device) { device.userData[DEBUG_FRAMEBUFFER_STATE_KEY] ||= { flushing: false, queuedFramebuffers: [] }; return device.userData[DEBUG_FRAMEBUFFER_STATE_KEY]; } function isFramebuffer(value) { return "colorAttachments" in value; } function isDefaultRenderPass(renderPass) { const framebuffer = renderPass.props.framebuffer; return !framebuffer || framebuffer.handle === null; } function parseCssPixel(value, defaultValue) { if (!value) { return defaultValue; } const parsedValue = Number.parseInt(value, 10); return Number.isFinite(parsedValue) ? parsedValue : defaultValue; } // dist/utils/deep-equal.js function deepEqual(a, b, depth) { if (a === b) { return true; } if (!depth || !a || !b) { return false; } if (Array.isArray(a)) { if (!Array.isArray(b) || a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { if (!deepEqual(a[i], b[i], depth - 1)) { return false; } } return true; } if (Array.isArray(b)) { return false; } if (typeof a === "object" && typeof b === "object") { const aKeys = Object.keys(a); const bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) { return false; } for (const key of aKeys) { if (!b.hasOwnProperty(key)) { return false; } if (!deepEqual(a[key], b[key], depth - 1)) { return false; } } return true; } return false; } // dist/utils/buffer-layout-helper.js var import_core4 = require("@luma.gl/core"); var BufferLayoutHelper = class { bufferLayouts; constructor(bufferLayouts) { this.bufferLayouts = bufferLayouts; } getBufferLayout(name) { return this.bufferLayouts.find((layout) => layout.name === name) || null; } /** Get attribute names from a BufferLayout */ getAttributeNamesForBuffer(bufferLayout) { var _a; return bufferLayout.attributes ? (_a = bufferLayout.attributes) == null ? void 0 : _a.map((layout) => layout.attribute) : [bufferLayout.name]; } mergeBufferLayouts(bufferLayouts1, bufferLayouts2) { const mergedLayouts = [...bufferLayouts1]; for (const attribute of bufferLayouts2) { const index = mergedLayouts.findIndex((attribute2) => attribute2.name === attribute.name); if (index < 0) { mergedLayouts.push(attribute); } else { mergedLayouts[index] = attribute; } } return mergedLayouts; } getBufferIndex(bufferName) { const bufferIndex = this.bufferLayouts.findIndex((layout) => layout.name === bufferName); if (bufferIndex === -1) { import_core4.log.warn(`BufferLayout: Missing buffer for "${bufferName}".`)(); } return bufferIndex; } }; // dist/utils/buffer-layout-order.js function getMinLocation(attributeNames, shaderLayoutMap) { let minLocation = Infinity; for (const name of attributeNames) { const location = shaderLayoutMap[name]; if (location !== void 0) { minLocation = Math.min(minLocation, location); } } return minLocation; } function sortedBufferLayoutByShaderSourceLocations(shaderLayout, bufferLayout) { const shaderLayoutMap = Object.fromEntries(shaderLayout.attributes.map((attr) => [attr.name, attr.location])); const sortedLayout = bufferLayout.slice(); sortedLayout.sort((a, b) => { const attributeNamesA = a.attributes ? a.attributes.map((attr) => attr.attribute) : [a.name]; const attributeNamesB = b.attributes ? b.attributes.map((attr) => attr.attribute) : [b.name]; const minLocationA = getMinLocation(attributeNamesA, shaderLayoutMap); const minLocationB = getMinLocation(attributeNamesB, shaderLayoutMap); return minLocationA - minLocationB; }); return sortedLayout; } // dist/utils/shader-module-utils.js function mergeShaderModuleBindingsIntoLayout(shaderLayout, modules) { if (!shaderLayout || !modules.some((module2) => { var _a; return (_a = module2.bindingLayout) == null ? void 0 : _a.length; })) { return shaderLayout; } const mergedLayout = { ...shaderLayout, bindings: shaderLayout.bindings.map((binding) => ({ ...binding })) }; if ("attributes" in (shaderLayout || {})) { mergedLayout.attributes = (shaderLayout == null ? void 0 : shaderLayout.attributes) || []; } for (const module2 of modules) { for (const bindingLayout of module2.bindingLayout || []) { for (const relatedBindingName of getRelatedBindingNames(bindingLayout.name)) { const binding = mergedLayout.bindings.find((candidate) => candidate.name === relatedBindingName); if ((binding == null ? void 0 : binding.group) === 0) { binding.group = bindingLayout.group; } } } } return mergedLayout; } function shaderModuleHasUniforms(module2) { return Boolean(module2.uniformTypes && !isObjectEmpty(module2.uniformTypes)); } function getRelatedBindingNames(bindingName) { const bindingNames = /* @__PURE__ */ new Set([bindingName, `${bindingName}Uniforms`]); if (!bindingName.endsWith("Uniforms")) { bindingNames.add(`${bindingName}Sampler`); } return [...bindingNames]; } function isObjectEmpty(obj) { for (const key in obj) { return false; } return true; } // dist/shader-inputs.js var import_core5 = require("@luma.gl/core"); var import_shadertools = require("@luma.gl/shadertools"); // dist/model/split-uniforms-and-bindings.js var import_types = require("@math.gl/types"); function isUniformValue(value) { return (0, import_types.isNumericArray)(value) || typeof value === "number" || typeof value === "boolean"; } function splitUniformsAndBindings(uniforms, uniformTypes2 = {}) { const result = { bindings: {}, uniforms: {} }; Object.keys(uniforms).forEach((name) => { const uniform = uniforms[name]; if (Object.prototype.hasOwnProperty.call(uniformTypes2, name) || isUniformValue(uniform)) { result.uniforms[name] = uniform; } else { result.bindings[name] = uniform; } }); return result; } // dist/shader-inputs.js var ShaderInputs = class { options = { disableWarnings: false }; /** * The map of modules * @todo should should this include the resolved dependencies? */ // @ts-ignore Fix typings modules; /** Stores the uniform values for each module */ moduleUniforms; /** Stores the uniform bindings for each module */ moduleBindings; /** Tracks if uniforms have changed */ // moduleUniformsChanged: Record<keyof ShaderPropsT, false | string>; /** * Create a new UniformStore instance * @param modules */ constructor(modules, options) { Object.assign(this.options, options); const resolvedModules = (0, import_shadertools.getShaderModuleDependencies)(Object.values(modules).filter(isShaderInputsModuleWithDependencies)); 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)) { if (module2) { this._addModule(module2); if (module2.name && name !== module2.name && !this.options.disableWarnings) { import_core5.log.warn(`Module name: ${name} vs ${module2.name}`)(); } } } } /** 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) { if (!this.options.disableWarnings) { import_core5.log.warn(`Module ${name} not found`)(); } } else { 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 } = splitUniformsAndBindings(uniformsAndBindings, module2.uniformTypes); this.moduleUniforms[moduleName] = mergeModuleUniforms(oldUniforms, uniforms, module2.uniformTypes); this.moduleBindings[moduleName] = { ...oldBindings, ...bindings }; } } } /** * 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) */ getBindingValues() { const bindings = {}; for (const moduleBindings of Object.values(this.moduleBindings)) { Object.assign(bindings, moduleBindings); } return bindings; } // INTERNAL /** Return a debug table that can be used for console.table() or log.table() */ 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; } _addModule(module2) { const moduleName = module2.name; this.moduleUniforms[moduleName] = mergeModuleUniforms({}, module2.defaultUniforms || {}, module2.uniformTypes); this.moduleBindings[moduleName] = {}; } }; function mergeModuleUniforms(currentUniforms = {}, nextUniforms = {}, uniformTypes2 = {}) { const mergedUniforms = { ...currentUniforms }; for (const [key, value] of Object.entries(nextUniforms)) { if (value !== void 0) { mergedUniforms[key] = mergeModuleUniformValue(currentUniforms[key], value, uniformTypes2[key]); } } return mergedUniforms; } function mergeModuleUniformValue(currentValue, nextValue, uniformType) { if (!uniformType || typeof uniformType === "string") { return cloneModuleUniformValue(nextValue); } if (Array.isArray(uniformType)) { if (isPackedUniformArrayValue(nextValue) || !Array.isArray(nextValue)) { return cloneModuleUniformValue(nextValue); } const currentArray = Array.isArray(currentValue) && !isPackedUniformArrayValue(currentValue) ? [...currentValue] : []; const mergedArray = currentArray.slice(); for (let index = 0; index < nextValue.length; index++) { const elementValue = nextValue[index]; if (elementValue !== void 0) { mergedArray[index] = mergeModuleUniformValue(currentArray[index], elementValue, uniformType[0]); } } return mergedArray; } if (!isPlainUniformObject(nextValue)) { return cloneModuleUniformValue(nextValue); } const uniformStruct = uniformType; const currentObject = isPlainUniformObject(currentValue) ? currentValue : {}; const mergedObject = { ...currentObject }; for (const [key, value] of Object.entries(nextValue)) { if (value !== void 0) { mergedObject[key] = mergeModuleUniformValue(currentObject[key], value, uniformStruct[key]); } } return mergedObject; } function cloneModuleUniformValue(value) { if (ArrayBuffer.isView(value)) { return Array.prototype.slice.call(value); } if (Array.isArray(value)) { if (isPackedUniformArrayValue(value)) { return value.slice(); } const compositeArray = value; return compositeArray.map((element) => element === void 0 ? void 0 : cloneModuleUniformValue(element)); } if (isPlainUniformObject(value)) { return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [ key, nestedValue === void 0 ? void 0 : cloneModuleUniformValue(nestedValue) ])); } return value; } function isPackedUniformArrayValue(value) { return ArrayBuffer.isView(value) || Array.isArray(value) && (value.length === 0 || typeof value[0] === "number"); } function isPlainUniformObject(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !ArrayBuffer.isView(value); } function isShaderInputsModuleWithDependencies(module2) { return Boolean(module2 == null ? void 0 : module2.dependencies); } // dist/dynamic-texture/dynamic-texture.js var import_core7 = require("@luma.gl/core"); // dist/dynamic-texture/texture-data.js var import_core6 = require("@luma.gl/core"); var TEXTURE_CUBE_FACE_MAP = { "+X": 0, "-X": 1, "+Y": 2, "-Y": 3, "+Z": 4, "-Z": 5 }; function getFirstMipLevel(layer) { if (!layer) return null; return Array.isArray(layer) ? layer[0] ?? null : layer; } function getTextureSizeFromData(props) { const { dimension, data } = props; if (!data) { return null; } switch (dimension) { case "1d": { const mipLevel = getFirstMipLevel(data); if (!mipLevel) return null; const { width } = getTextureMipLevelSize(mipLevel); return { width, height: 1 }; } case "2d": { const mipLevel = getFirstMipLevel(data); return mipLevel ? getTextureMipLevelSize(mipLevel) : null; } case "3d": case "2d-array": { if (!Array.isArray(data) || data.length === 0) return null; const mipLevel = getFirstMipLevel(data[0]); return mipLevel ? getTextureMipLevelSize(mipLevel) : null; } case "cube": { const face = Object.keys(data)[0] ?? null; if (!face) return null; const faceData = data[face]; const mipLevel = getFirstMipLevel(faceData); return mipLevel ? getTextureMipLevelSize(mipLevel) : null; } case "cube-array": { if (!Array.isArray(data) || data.length === 0) return null; const firstCube = data[0]; const face = Object.keys(firstCube)[0] ?? null; if (!face) return null; const mipLevel = getFirstMipLevel(firstCube[face]); return mipLevel ? getTextureMipLevelSize(mipLevel) : null; } default: return null; } } function getTextureMipLevelSize(data) { if ((0, import_core6.isExternalImage)(data)) { return (0, import_core6.getExternalImageSize)(data); } if (typeof data === "object" && "width" in data && "height" in data) { return { width: data.width, height: data.height }; } throw new Error("Unsupported mip-level data"); } function isTextureImageData(data) { return typeof data === "object" && data !== null && "data" in data && "width" in data && "height" in data; } function isTypedArrayMipLevelData(data) { return ArrayBuffer.isView(data); } function resolveTextureImageFormat(data) { const { textureFormat, format } = data; if (textureFormat && format && textureFormat !== format) { throw new Error(`Conflicting texture formats "${textureFormat}" and "${format}" provided for the same mip level`); } return textureFormat ?? format; } function getCubeFaceIndex(face) { const idx = TEXTURE_CUBE_FACE_MAP[face]; if (idx === void 0) throw new Error(`Invalid cube face: ${face}`); return idx; } function getCubeArrayFaceIndex(cubeIndex, face) { return 6 * cubeIndex + getCubeFaceIndex(face); } function getTexture1DSubresources(data) { throw new Error("setTexture1DData not supported in WebGL."); } function _normalizeTexture2DData(data) { return Array.isArray(data) ? data : [data]; } function getTexture2DSubresources(slice, lodData, baseLevelSize, textureFormat) { const lodArray = _normalizeTexture2DData(lodData); const z = slice; const subresources = []; for (let mipLevel = 0; mipLevel < lodArray.length; mipLevel++) { const imageData = lodArray[mipLevel]; if ((0, import_core6.isExternalImage)(imageData)) { subresources.push({ type: "external-image", image: imageData, z, mipLevel }); } else if (isTextureImageData(imageData)) { subresources.push({ type: "texture-data", data: imageData, textureFormat: resolveTextureImageFormat(imageData), z, mipLevel }); } else if (isTypedArrayMipLevelData(imageData) && baseLevelSize) { subresources.push({ type: "texture-data", data: { data: imageData, width: Math.max(1, baseLevelSize.width >> mipLevel), height: Math.max(1, baseLevelSize.height >> mipLevel), ...textureFormat ? { format: textureFormat } : {} }, textureFormat, z, mipLevel }); } else { throw new Error("Unsupported 2D mip-level payload"); } } return subresources; } function getTexture3DSubresources(data) { const subresources = []; for (let depth = 0; depth < data.length; depth++) { subresources.push(...getTexture2DSubresources(depth, data[depth])); } return subresources; } function getTextureArraySubresources(data) { const subresources = []; for (let layer = 0; layer < data.length; layer++) { subresources.push(...getTexture2DSubresources(layer, data[layer])); } return subresources; } function getTextureCubeSubresources(data) { const subresources = []; for (const [face, faceData] of Object.entries(data)) { const faceDepth = getCubeFaceIndex(face); subresources.push(...getTexture2DSubresources(faceDepth, faceData)); } return subresources; } function getTextureCubeArraySubresources(data) { const subresources = []; data.forEach((cubeData, cubeIndex) => { for (const [face, faceData] of Object.entries(cubeData)) { const faceDepth = getCubeArrayFaceIndex(cubeIndex, face); subresources.push(...getTexture2DSubresources(faceDepth, faceData)); } }); return subresources; } // dist/dynamic-texture/dynamic-texture.js var _DynamicTexture = class { device; id; /** Props with defaults resolved (except `data` which is processed separately) */ props; /** Created resources */ _texture = null; _sampler = null; _view = null; /** Ready when GPU texture has been created and data (if any) uploaded */ ready; isReady = false; destroyed = false; resolveReady = () => { }; rejectReady = () => { }; get texture() { if (!this._texture) throw new Error("Texture not initialized yet"); return this._texture; } get sampler() { if (!this._sampler) throw new Error("Sampler not initialized yet"); return this._sampler; } get view() { if (!this._view) throw new Error("View not initialized yet"); return this._view; } get [Symbol.toStringTag]() { return "DynamicTexture"; } toString() { var _a, _b; const width = ((_a = this._texture) == null ? void 0 : _a.width) ?? this.props.width ?? "?"; const height = ((_b = this._texture) == null ? void 0 : _b.height) ?? this.props.height ?? "?"; return `DynamicTexture:"${this.id}":${width}x${height}px:(${this.isReady ? "ready" : "loading..."})`; } constructor(device, props) { this.device = device; const id = uid("dynamic-texture"); const originalPropsWithAsyncData = props; this.props = { ..._DynamicTexture.defaultProps, id, ...props, data: null }; this.id = this.props.id; this.ready = new Promise((resolve, reject) => { this.resolveReady = resolve; this.rejectReady = reject; }); this.initAsync(originalPropsWithAsyncData); } /** @note Fire and forget; caller can await `ready`