Socket
Socket
Sign inDemoInstall

@luma.gl/engine

Package Overview
Dependencies
Maintainers
7
Versions
173
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@luma.gl/engine - npm Package Compare versions

Comparing version 9.0.0-beta.4 to 9.0.0-beta.5

dist/index.cjs.map

22

dist/animation-loop/animation-loop-template.js

@@ -0,7 +1,19 @@

/**
* Minimal class that represents a "componentized" rendering life cycle
* (resource construction, repeated rendering, resource destruction)
*
* @note A motivation for this class compared to the raw animation loop is
* that it simplifies TypeScript code by allowing resources to be typed unconditionally
* since they are allocated in the constructor rather than in onInitialized
*
* @note Introduced in luma.gl v9
*
* @example AnimationLoopTemplate is intended to be subclassed,
* but the subclass should not be instantiated directly. Instead the subclass
* (i.e. the constructor of the subclass) should be used
* as an argument to create an AnimationLoop.
*/
export class AnimationLoopTemplate {
constructor(animationProps) {}
async onInitialize(animationProps) {
return null;
}
constructor(animationProps) { }
async onInitialize(animationProps) { return null; }
}
//# sourceMappingURL=animation-loop-template.js.map

@@ -0,1 +1,3 @@

// luma.gl, MIT license
// Copyright (c) vis.gl contributors
import { luma } from '@luma.gl/core';

@@ -6,361 +8,428 @@ import { requestAnimationFrame, cancelAnimationFrame } from '@luma.gl/core';

const DEFAULT_ANIMATION_LOOP_PROPS = {
device: null,
onAddHTML: () => '',
onInitialize: async () => {
return null;
},
onRender: () => {},
onFinalize: () => {},
onError: error => console.error(error),
stats: luma.stats.get(`animation-loop-${statIdCounter++}`),
useDevicePixels: true,
autoResizeViewport: false,
autoResizeDrawingBuffer: false
device: null,
onAddHTML: () => '',
onInitialize: async () => { return null; },
onRender: () => { },
onFinalize: () => { },
onError: (error) => console.error(error), // eslint-disable-line no-console
stats: luma.stats.get(`animation-loop-${statIdCounter++}`),
// view parameters
useDevicePixels: true,
autoResizeViewport: false,
autoResizeDrawingBuffer: false,
};
/** Convenient animation loop */
export class AnimationLoop {
constructor(props) {
this.device = null;
this.canvas = null;
this.props = void 0;
this.animationProps = null;
this.timeline = null;
this.stats = void 0;
this.cpuTime = void 0;
this.gpuTime = void 0;
this.frameRate = void 0;
this.display = void 0;
this.needsRedraw = 'initialized';
this._initialized = false;
this._running = false;
this._animationFrameId = null;
this._nextFramePromise = null;
this._resolveNextFrame = null;
this._cpuStartTime = 0;
this.props = {
...DEFAULT_ANIMATION_LOOP_PROPS,
...props
};
props = this.props;
if (!props.device) {
throw new Error('No device provided');
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;
// state
this.stats = props.stats || new 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
});
// Bind methods
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this._onMousemove = this._onMousemove.bind(this);
this._onMouseleave = this._onMouseleave.bind(this);
}
const {
useDevicePixels = true
} = this.props;
this.stats = props.stats || new 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);
}
delete() {
this.destroy();
}
setNeedsRedraw(reason) {
this.needsRedraw = this.needsRedraw || reason;
return this;
}
setProps(props) {
if ('autoResizeViewport' in props) {
this.props.autoResizeViewport = props.autoResizeViewport || false;
destroy() {
this.stop();
this._setDisplay(null);
}
if ('autoResizeDrawingBuffer' in props) {
this.props.autoResizeDrawingBuffer = props.autoResizeDrawingBuffer || false;
/** @deprecated Use .destroy() */
delete() {
this.destroy();
}
if ('useDevicePixels' in props) {
this.props.useDevicePixels = props.useDevicePixels || false;
/** Flags this animation loop as needing redraw */
setNeedsRedraw(reason) {
this.needsRedraw = this.needsRedraw || reason;
return this;
}
return this;
}
async start() {
if (this._running) {
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;
}
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;
/** 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;
// Create the WebGL context
await this._initDevice();
this._initialize();
// Note: onIntialize can return a promise (e.g. in case app needs to load resources)
await this.props.onInitialize(this._getAnimationProps());
}
// check that we haven't been stopped
if (!this._running) {
return null;
}
// Start the loop
if (appContext !== false) {
// cancel any pending renders to ensure only one loop can ever run
this._cancelAnimationFrame();
this._requestAnimationFrame();
}
return this;
}
catch (err) {
const error = err instanceof Error ? err : new Error('Unknown error');
this.props.onError(error);
// this._running = false; // TODO
throw error;
}
}
}
stop() {
if (this._running) {
if (this.animationProps) {
this.props.onFinalize(this.animationProps);
}
this._cancelAnimationFrame();
this._nextFramePromise = null;
this._resolveNextFrame = null;
this._running = false;
/** Stops a render loop if already running, finalizing */
stop() {
// console.debug(`Stopping ${this.constructor.name}`);
if (this._running) {
// call callback
// If stop is called immediately, we can end up in a state where props haven't been initialized...
if (this.animationProps) {
this.props.onFinalize(this.animationProps);
}
this._cancelAnimationFrame();
this._nextFramePromise = null;
this._resolveNextFrame = null;
this._running = false;
}
return this;
}
return this;
}
redraw() {
var _this$device;
if ((_this$device = this.device) !== null && _this$device !== void 0 && _this$device.isLost) {
return this;
/** Explicitly draw a frame */
redraw() {
if (this.device?.isLost) {
return this;
}
this._beginFrameTimers();
this._setupFrame();
this._updateAnimationProps();
this._renderFrame(this._getAnimationProps());
// clear needsRedraw flag
this._clearNeedsRedraw();
if (this._resolveNextFrame) {
this._resolveNextFrame(this);
this._nextFramePromise = null;
this._resolveNextFrame = null;
}
this._endFrameTimers();
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;
/** Add a timeline, it will be automatically updated by the animation loop. */
attachTimeline(timeline) {
this.timeline = timeline;
return this.timeline;
}
this._endFrameTimers();
return this;
}
attachTimeline(timeline) {
this.timeline = timeline;
return this.timeline;
}
detachTimeline() {
this.timeline = null;
}
waitForRender() {
this.setNeedsRedraw('waitForRender');
if (!this._nextFramePromise) {
this._nextFramePromise = new Promise(resolve => {
this._resolveNextFrame = resolve;
});
/** Remove a timeline */
detachTimeline() {
this.timeline = null;
}
return this._nextFramePromise;
}
async toDataURL() {
this.setNeedsRedraw('toDataURL');
await this.waitForRender();
if (this.canvas instanceof HTMLCanvasElement) {
return this.canvas.toDataURL();
/** Wait until a render completes */
waitForRender() {
this.setNeedsRedraw('waitForRender');
if (!this._nextFramePromise) {
this._nextFramePromise = new Promise((resolve) => {
this._resolveNextFrame = resolve;
});
}
return this._nextFramePromise;
}
throw new Error('OffscreenCanvas');
}
_initialize() {
this._startEventHandling();
this._initializeAnimationProps();
this._updateAnimationProps();
this._resizeCanvasDrawingBuffer();
this._resizeViewport();
}
_setDisplay(display) {
if (this.display) {
this.display.destroy();
this.display.animationLoop = null;
/** 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');
}
if (display) {
display.animationLoop = this;
// PRIVATE METHODS
_initialize() {
this._startEventHandling();
// Initialize the callback data
this._initializeAnimationProps();
this._updateAnimationProps();
// Default viewport setup, in case onInitialize wants to render
this._resizeCanvasDrawingBuffer();
this._resizeViewport();
// this._gpuTimeQuery = Query.isSupported(this.gl, ['timers']) ? new Query(this.gl) : null;
}
this.display = display;
}
_requestAnimationFrame() {
if (!this._running) {
return;
_setDisplay(display) {
if (this.display) {
this.display.destroy();
this.display.animationLoop = null;
}
// store animation loop on the display
if (display) {
display.animationLoop = this;
}
this.display = display;
}
this._animationFrameId = requestAnimationFrame(this._animationFrame.bind(this));
}
_cancelAnimationFrame() {
if (this._animationFrameId === null) {
return;
_requestAnimationFrame() {
if (!this._running) {
return;
}
// VR display has a separate animation frame to sync with headset
// TODO WebVR API discontinued, replaced by WebXR: https://immersive-web.github.io/webxr/
// See https://developer.mozilla.org/en-US/docs/Web/API/VRDisplay/requestAnimationFrame
// if (this.display && this.display.requestAnimationFrame) {
// this._animationFrameId = this.display.requestAnimationFrame(this._animationFrame.bind(this));
// }
this._animationFrameId = requestAnimationFrame(this._animationFrame.bind(this));
}
cancelAnimationFrame(this._animationFrameId);
this._animationFrameId = null;
}
_animationFrame() {
if (!this._running) {
return;
_cancelAnimationFrame() {
if (this._animationFrameId === null) {
return;
}
// VR display has a separate animation frame to sync with headset
// TODO WebVR API discontinued, replaced by WebXR: https://immersive-web.github.io/webxr/
// See https://developer.mozilla.org/en-US/docs/Web/API/VRDisplay/requestAnimationFrame
// if (this.display && this.display.cancelAnimationFrame) {
// this.display.cancelAnimationFrame(this._animationFrameId);
// }
cancelAnimationFrame(this._animationFrameId);
this._animationFrameId = null;
}
this.redraw();
this._requestAnimationFrame();
}
_renderFrame(animationProps) {
if (this.display) {
this.display._renderFrame(animationProps);
return;
_animationFrame() {
if (!this._running) {
return;
}
this.redraw();
this._requestAnimationFrame();
}
this.props.onRender(this._getAnimationProps());
this.device.submit();
}
_clearNeedsRedraw() {
this.needsRedraw = false;
}
_setupFrame() {
this._resizeCanvasDrawingBuffer();
this._resizeViewport();
}
_initializeAnimationProps() {
var _this$device2;
if (!this.device) {
throw new Error('loop');
// Called on each frame, can be overridden to call onRender multiple times
// to support e.g. stereoscopic rendering
_renderFrame(animationProps) {
// Allow e.g. VR display to render multiple frames.
if (this.display) {
this.display._renderFrame(animationProps);
return;
}
// call callback
this.props.onRender(this._getAnimationProps());
// end callback
// Submit commands (necessary on WebGPU)
this.device.submit();
}
this.animationProps = {
animationLoop: this,
device: this.device,
canvas: (_this$device2 = this.device) === null || _this$device2 === void 0 || (_this$device2 = _this$device2.canvasContext) === null || _this$device2 === void 0 ? void 0 : _this$device2.canvas,
timeline: this.timeline,
useDevicePixels: this.props.useDevicePixels,
needsRedraw: false,
width: 1,
height: 1,
aspect: 1,
time: 0,
startTime: Date.now(),
engineTime: 0,
tick: 0,
tock: 0,
_mousePosition: null
};
}
_getAnimationProps() {
if (!this.animationProps) {
throw new Error('animationProps');
_clearNeedsRedraw() {
this.needsRedraw = false;
}
return this.animationProps;
}
_updateAnimationProps() {
if (!this.animationProps) {
return;
_setupFrame() {
this._resizeCanvasDrawingBuffer();
this._resizeViewport();
}
const {
width,
height,
aspect
} = this._getSizeAndAspect();
if (width !== this.animationProps.width || height !== this.animationProps.height) {
this.setNeedsRedraw('drawing buffer resized');
// Initialize the object that will be passed to app callbacks
_initializeAnimationProps() {
if (!this.device) {
throw new Error('loop');
}
this.animationProps = {
animationLoop: this,
device: this.device,
canvas: this.device?.canvasContext?.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
};
}
if (aspect !== this.animationProps.aspect) {
this.setNeedsRedraw('drawing buffer aspect changed');
_getAnimationProps() {
if (!this.animationProps) {
throw new Error('animationProps');
}
return this.animationProps;
}
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);
// Update the context object that will be passed to app callbacks
_updateAnimationProps() {
if (!this.animationProps) {
return;
}
// Can this be replaced with canvas context?
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;
// Update time properties
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 / 1000) * 60);
this.animationProps.tock++;
// For back compatibility
this.animationProps.time = this.timeline
? this.timeline.getTime()
: this.animationProps.engineTime;
}
this.animationProps.tick = Math.floor(this.animationProps.time / 1000 * 60);
this.animationProps.tock++;
this.animationProps.time = this.timeline ? this.timeline.getTime() : this.animationProps.engineTime;
}
async _initDevice() {
var _this$device$canvasCo;
this.device = await this.props.device;
if (!this.device) {
throw new Error('No device provided');
/** 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.canvasContext?.canvas || null;
// this._createInfoDiv();
}
this.canvas = ((_this$device$canvasCo = this.device.canvasContext) === null || _this$device$canvasCo === void 0 ? void 0 : _this$device$canvasCo.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;
}
_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 _this$device3, _this$device4;
if (!this.device) {
return {
width: 1,
height: 1,
aspect: 1
};
_getSizeAndAspect() {
if (!this.device) {
return { width: 1, height: 1, aspect: 1 };
}
// https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
const [width, height] = this.device?.canvasContext?.getPixelSize() || [1, 1];
// https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
let aspect = 1;
const canvas = this.device?.canvasContext?.canvas;
// @ts-expect-error
if (canvas && canvas.clientHeight) {
// @ts-expect-error
aspect = canvas.clientWidth / canvas.clientHeight;
}
else if (width > 0 && height > 0) {
aspect = width / height;
}
return { width, height, aspect };
}
const [width, height] = ((_this$device3 = this.device) === null || _this$device3 === void 0 || (_this$device3 = _this$device3.canvasContext) === null || _this$device3 === void 0 ? void 0 : _this$device3.getPixelSize()) || [1, 1];
let aspect = 1;
const canvas = (_this$device4 = this.device) === null || _this$device4 === void 0 || (_this$device4 = _this$device4.canvasContext) === null || _this$device4 === void 0 ? void 0 : _this$device4.canvas;
if (canvas && canvas.clientHeight) {
aspect = canvas.clientWidth / canvas.clientHeight;
} else if (width > 0 && height > 0) {
aspect = width / height;
/** Default viewport setup */
_resizeViewport() {
// @ts-expect-error Expose on canvasContext
if (this.props.autoResizeViewport && this.device.gl) {
// @ts-expect-error Expose canvasContext
this.device.gl.viewport(0, 0, this.device.gl.drawingBufferWidth, this.device.gl.drawingBufferHeight);
}
}
return {
width,
height,
aspect
};
}
_resizeViewport() {
if (this.props.autoResizeViewport && this.device.gl) {
this.device.gl.viewport(0, 0, this.device.gl.drawingBufferWidth, this.device.gl.drawingBufferHeight);
/**
* Resize the render buffer of the canvas to match canvas client size
* Optionally multiplying with devicePixel ratio
*/
_resizeCanvasDrawingBuffer() {
if (this.props.autoResizeDrawingBuffer) {
this.device?.canvasContext?.resize({ useDevicePixels: this.props.useDevicePixels });
}
}
}
_resizeCanvasDrawingBuffer() {
if (this.props.autoResizeDrawingBuffer) {
var _this$device5;
(_this$device5 = this.device) === null || _this$device5 === void 0 || (_this$device5 = _this$device5.canvasContext) === null || _this$device5 === void 0 ? void 0 : _this$device5.resize({
useDevicePixels: this.props.useDevicePixels
});
_beginFrameTimers() {
this.frameRate.timeEnd();
this.frameRate.timeStart();
// Check if timer for last frame has completed.
// GPU timer results are never available in the same
// frame they are captured.
// if (
// this._gpuTimeQuery &&
// this._gpuTimeQuery.isResultAvailable() &&
// !this._gpuTimeQuery.isTimerDisjoint()
// ) {
// this.stats.get('GPU Time').addTime(this._gpuTimeQuery.getTimerMilliseconds());
// }
// if (this._gpuTimeQuery) {
// // GPU time query start
// this._gpuTimeQuery.beginTimeElapsedQuery();
// }
this.cpuTime.timeStart();
}
}
_beginFrameTimers() {
this.frameRate.timeEnd();
this.frameRate.timeStart();
this.cpuTime.timeStart();
}
_endFrameTimers() {
this.cpuTime.timeEnd();
}
_startEventHandling() {
if (this.canvas) {
this.canvas.addEventListener('mousemove', this._onMousemove.bind(this));
this.canvas.addEventListener('mouseleave', this._onMouseleave.bind(this));
_endFrameTimers() {
this.cpuTime.timeEnd();
// if (this._gpuTimeQuery) {
// // GPU time query end. Results will be available on next frame.
// this._gpuTimeQuery.end();
// }
}
}
_onMousemove(event) {
if (event instanceof MouseEvent) {
this._getAnimationProps()._mousePosition = [event.offsetX, event.offsetY];
// Event handling
_startEventHandling() {
if (this.canvas) {
this.canvas.addEventListener('mousemove', this._onMousemove.bind(this));
this.canvas.addEventListener('mouseleave', this._onMouseleave.bind(this));
}
}
}
_onMouseleave(event) {
this._getAnimationProps()._mousePosition = null;
}
_onMousemove(event) {
if (event instanceof MouseEvent) {
this._getAnimationProps()._mousePosition = [event.offsetX, event.offsetY];
}
}
_onMouseleave(event) {
this._getAnimationProps()._mousePosition = null;
}
}
//# sourceMappingURL=animation-loop.js.map
export {};
//# sourceMappingURL=animation-props.js.map

@@ -0,28 +1,30 @@

// luma.gl, MIT license
import { luma } from '@luma.gl/core';
import { AnimationLoop } from "./animation-loop.js";
import { AnimationLoop } from './animation-loop';
/** Instantiates and runs the render loop */
export function makeAnimationLoop(AnimationLoopTemplateCtor, props) {
let renderLoop = null;
const device = (props === null || props === void 0 ? void 0 : props.device) || luma.createDevice();
const animationLoop = new AnimationLoop({
...props,
device,
async onInitialize(animationProps) {
var _renderLoop;
renderLoop = new AnimationLoopTemplateCtor(animationProps);
return await ((_renderLoop = renderLoop) === null || _renderLoop === void 0 ? void 0 : _renderLoop.onInitialize(animationProps));
},
onRender: animationProps => {
var _renderLoop2;
return (_renderLoop2 = renderLoop) === null || _renderLoop2 === void 0 ? void 0 : _renderLoop2.onRender(animationProps);
},
onFinalize: animationProps => {
var _renderLoop3;
return (_renderLoop3 = renderLoop) === null || _renderLoop3 === void 0 ? void 0 : _renderLoop3.onFinalize(animationProps);
}
});
animationLoop.getInfo = () => {
return this.AnimationLoopTemplateCtor.info;
};
return animationLoop;
let renderLoop = null;
const device = props?.device || luma.createDevice();
// Create an animation loop;
const animationLoop = new AnimationLoop({
...props,
device,
async onInitialize(animationProps) {
// @ts-expect-error abstract to prevent instantiation
renderLoop = new AnimationLoopTemplateCtor(animationProps);
// Any async loading can be handled here
return await renderLoop?.onInitialize(animationProps);
},
onRender: (animationProps) => renderLoop?.onRender(animationProps),
onFinalize: (animationProps) => renderLoop?.onFinalize(animationProps)
});
// @ts-expect-error Hack: adds info for the website to find
animationLoop.getInfo = () => {
// @ts-ignore
// eslint-disable-next-line no-invalid-this
return this.AnimationLoopTemplateCtor.info;
};
// Start the loop automatically
// animationLoop.start();
return animationLoop;
}
//# sourceMappingURL=make-animation-loop.js.map

@@ -0,56 +1,56 @@

/** Holds a list of key frames (timestamped values) */
export class KeyFrames {
constructor(keyFrames) {
this.startIndex = -1;
this.endIndex = -1;
this.factor = 0;
this.times = [];
this.values = [];
this._lastTime = -1;
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];
startIndex = -1;
endIndex = -1;
factor = 0;
times = [];
values = [];
_lastTime = -1;
constructor(keyFrames) {
this.setKeyFrames(keyFrames);
this.setTime(0);
}
this._calculateKeys(this._lastTime);
}
setTime(time) {
time = Math.max(0, time);
if (time !== this._lastTime) {
this._calculateKeys(time);
this._lastTime = time;
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);
}
}
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;
}
setTime(time) {
time = Math.max(0, time);
if (time !== this._lastTime) {
this._calculateKeys(time);
this._lastTime = time;
}
}
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);
}
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);
}
}
//# sourceMappingURL=key-frames.js.map

@@ -0,112 +1,107 @@

// luma.gl, MIT license
// Copyright (c) vis.gl contributors
let channelHandles = 1;
let animationHandles = 1;
export class Timeline {
constructor() {
this.time = 0;
this.channels = new Map();
this.animations = new Map();
this.playing = false;
this.lastEngineTime = -1;
}
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);
}
time = 0;
channels = new Map();
animations = new Map();
playing = false;
lastEngineTime = -1;
constructor() {
}
}
isFinished(channelId) {
const channel = this.channels.get(channelId);
if (channel === undefined) {
return false;
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;
}
return this.time >= channel.delay + channel.duration * channel.repeat;
}
getTime(channelId) {
if (channelId === undefined) {
return this.time;
removeChannel(channelId) {
this.channels.delete(channelId);
for (const [animationHandle, animation] of this.animations) {
if (animation.channel === channelId) {
this.detachAnimation(animationHandle);
}
}
}
const channel = this.channels.get(channelId);
if (channel === undefined) {
return -1;
isFinished(channelId) {
const channel = this.channels.get(channelId);
if (channel === undefined) {
return false;
}
return this.time >= channel.delay + channel.duration * channel.repeat;
}
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);
getTime(channelId) {
if (channelId === undefined) {
return this.time;
}
const channel = this.channels.get(channelId);
if (channel === undefined) {
return -1;
}
return channel.time;
}
const animations = this.animations.values();
for (const animationData of animations) {
const {
animation,
channel
} = animationData;
animation.setTime(this.getTime(channel));
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;
play() {
this.playing = true;
}
}
_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;
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;
// Note(Tarek): Don't loop on final repeat.
if (offsetTime >= totalDuration) {
channel.time = channel.duration * channel.rate;
}
else {
channel.time = Math.max(0, offsetTime) % channel.duration;
channel.time *= channel.rate;
}
}
}
//# sourceMappingURL=timeline.js.map

@@ -1,46 +0,42 @@

import { flipRows, scalePixels } from "./pixel-data-utils.js";
import { GL } from '@luma.gl/constants';
import { flipRows, scalePixels } from './pixel-data-utils';
/**
* Reads pixels from a Framebuffer or Texture object into an HTML Image
* @todo - can we move this to @luma.gl/core?
* @param source
* @param options options passed to copyToDataUrl
* @returns
*/
export function copyTextureToImage(source, options) {
const dataUrl = copyTextureToDataUrl(source, options);
const targetImage = (options === null || options === void 0 ? void 0 : options.targetImage) || new Image();
targetImage.src = dataUrl;
return targetImage;
const dataUrl = copyTextureToDataUrl(source, options);
const targetImage = options?.targetImage || new Image();
targetImage.src = dataUrl;
return targetImage;
}
export function copyTextureToDataUrl(source) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const {
sourceAttachment = 36064,
targetMaxHeight = Number.MAX_SAFE_INTEGER
} = options;
let data = source.device.readPixelsToArrayWebGL(source, {
sourceAttachment
});
let {
width,
height
} = source;
while (height > targetMaxHeight) {
({
data,
width,
height
} = scalePixels({
data,
width,
height
}));
}
flipRows({
data,
width,
height
});
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
const imageData = context.createImageData(width, height);
imageData.data.set(data);
context.putImageData(imageData, 0, 0);
return canvas.toDataURL('image/png');
/**
* Reads pixels from a Framebuffer or Texture object to a dataUrl
* @todo - can we move this to @luma.gl/core?
* @param source texture or framebuffer to read from
* @param options
*/
export function copyTextureToDataUrl(source, options = {}) {
const { sourceAttachment = GL.COLOR_ATTACHMENT0, // TODO - support gl.readBuffer
targetMaxHeight = Number.MAX_SAFE_INTEGER } = options;
let data = source.device.readPixelsToArrayWebGL(source, { sourceAttachment });
// Scale down
let { width, height } = source;
while (height > targetMaxHeight) {
({ data, width, height } = scalePixels({ data, width, height }));
}
// Flip to top down coordinate system
flipRows({ data, width, height });
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
// Copy the pixels to a 2D canvas
const imageData = context.createImageData(width, height);
imageData.data.set(data);
context.putImageData(imageData, 0, 0);
return canvas.toDataURL('image/png');
}
//# sourceMappingURL=copy-texture-to-image.js.map

@@ -0,43 +1,47 @@

// import {copyTextureToImage} from '../debug/copy-texture-to-image';
/** Only works with 1st device? */
let canvas = null;
let ctx = null;
export function debugFramebuffer(fbo, _ref) {
let {
id,
minimap,
opaque,
top = '0',
left = '0',
rgbaScale = 1
} = _ref;
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);
// let targetImage: HTMLImageElement | null = null;
/** Debug utility to draw FBO contents onto screen */
// eslint-disable-next-line
export 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');
// targetImage = new Image();
}
// const canvasHeight = (minimap ? 2 : 1) * fbo.height;
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 image = copyTextureToImage(fbo, {targetMaxHeight: 100, targetImage});
// ctx.drawImage(image, 0, 0);
const color = fbo.device.readPixelsToArrayWebGL(fbo);
const imageData = ctx.createImageData(fbo.width, fbo.height);
// Full map
const offset = 0;
// if (color.some((v) => v > 0)) {
// console.error('THERE IS NON-ZERO DATA IN THE FBO!');
// }
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);
}
;
//# sourceMappingURL=debug-framebuffer.js.map

@@ -0,28 +1,27 @@

// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
/**
* Extracts a table suitable for `console.table()` from a shader layout to assist in debugging.
* @param layout shader layout
* @param name app should provide the most meaningful name, usually the model or pipeline name / id.
* @returns
*/
export function getDebugTableForShaderLayout(layout, name) {
var _layout$varyings;
const table = {};
const header = 'Values';
if (layout.attributes.length === 0 && !((_layout$varyings = layout.varyings) !== null && _layout$varyings !== void 0 && _layout$varyings.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'
};
const table = {};
const header = 'Values'; // '`Shader Layout for ${name}`;
if (layout.attributes.length === 0 && !layout.varyings?.length) {
return { 'No attributes or varyings': { [header]: 'N/A' } };
}
}
for (const varyingDeclaration of layout.varyings || []) {
const glslDeclaration = `${varyingDeclaration.location} ${varyingDeclaration.name}`;
table[`out ${glslDeclaration}`] = {
[header]: JSON.stringify(varyingDeclaration.accessor)
};
}
return table;
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;
}
//# sourceMappingURL=debug-shader-layout.js.map

@@ -0,41 +1,38 @@

// luma.gl, MIT license
// Copyright (c) vis.gl contributors
/**
* Flip rows (can be used on arrays returned from `Framebuffer.readPixels`)
* https: *stackoverflow.com/questions/41969562/
* how-can-i-flip-the-result-of-webglrenderingcontext-readpixels
* @param param0
*/
export function flipRows(options) {
const {
data,
width,
height,
bytesPerPixel = 4,
temp
} = options;
const bytesPerRow = width * bytesPerPixel;
const tempBuffer = temp || new Uint8Array(bytesPerRow);
for (let y = 0; y < height / 2; ++y) {
const topOffset = y * bytesPerRow;
const bottomOffset = (height - y - 1) * bytesPerRow;
tempBuffer.set(data.subarray(topOffset, topOffset + bytesPerRow));
data.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
data.set(tempBuffer, bottomOffset);
}
const { data, width, height, bytesPerPixel = 4, temp } = options;
const bytesPerRow = width * bytesPerPixel;
// make a temp buffer to hold one row
const tempBuffer = temp || new Uint8Array(bytesPerRow);
for (let y = 0; y < height / 2; ++y) {
const topOffset = y * bytesPerRow;
const bottomOffset = (height - y - 1) * bytesPerRow;
// make copy of a row on the top half
tempBuffer.set(data.subarray(topOffset, topOffset + bytesPerRow));
// copy a row from the bottom half to the top
data.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
// copy the copy of the top half row to the bottom half
data.set(tempBuffer, bottomOffset);
}
}
export function scalePixels(options) {
const {
data,
width,
height
} = options;
const newWidth = Math.round(width / 2);
const newHeight = Math.round(height / 2);
const newData = new Uint8Array(newWidth * newHeight * 4);
for (let y = 0; y < newHeight; y++) {
for (let x = 0; x < newWidth; x++) {
for (let c = 0; c < 4; c++) {
newData[(y * newWidth + x) * 4 + c] = data[(y * 2 * width + x * 2) * 4 + c];
}
const { data, width, height } = options;
const newWidth = Math.round(width / 2);
const newHeight = Math.round(height / 2);
const newData = new Uint8Array(newWidth * newHeight * 4);
for (let y = 0; y < newHeight; y++) {
for (let x = 0; x < newWidth; x++) {
for (let c = 0; c < 4; c++) {
newData[(y * newWidth + x) * 4 + c] = data[(y * 2 * width + x * 2) * 4 + c];
}
}
}
}
return {
data: newData,
width: newWidth,
height: newHeight
};
return { data: newData, width: newWidth, height: newHeight };
}
//# sourceMappingURL=pixel-data-utils.js.map
import { uid } from '@luma.gl/core';
import { TruncatedConeGeometry } from "./truncated-cone-geometry.js";
import { TruncatedConeGeometry } from './truncated-cone-geometry';
export class ConeGeometry extends TruncatedConeGeometry {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
id = uid('cone-geometry'),
radius = 1,
cap = true
} = props;
super({
...props,
id,
topRadius: 0,
topCap: Boolean(cap),
bottomCap: Boolean(cap),
bottomRadius: radius
});
}
constructor(props = {}) {
const { id = uid('cone-geometry'), radius = 1, cap = true } = props;
super({
...props,
id,
topRadius: 0,
topCap: Boolean(cap),
bottomCap: Boolean(cap),
bottomRadius: radius
});
}
}
//# sourceMappingURL=cone-geometry.js.map
import { uid } from '@luma.gl/core';
import { Geometry } from "../geometry/geometry.js";
import { Geometry } from '../geometry/geometry';
export class CubeGeometry extends Geometry {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
id = 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: undefined,
attributes: {
...NON_INDEXED_ATTRIBUTES,
...props.attributes
}
});
}
constructor(props = {}) {
const { id = 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: undefined,
attributes: { ...NON_INDEXED_ATTRIBUTES, ...props.attributes }
});
}
}
const 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]);
const 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]);
const CUBE_NORMALS = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0]);
const CUBE_TEX_COORDS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1]);
export const 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]);
export const 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]);
export const 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]);
// prettier-ignore
const 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
]);
// prettier-ignore
const 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
]);
// TODO - could be Uint8
// prettier-ignore
const 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
]);
// prettier-ignore
const 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
]);
// float4 position
// prettier-ignore
export const 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,
]);
// float2 uv,
// prettier-ignore
export const 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,
]);
// float4 color
// prettier-ignore
export const 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,
]);
const ATTRIBUTES = {
POSITION: {
size: 3,
value: CUBE_POSITIONS
},
NORMAL: {
size: 3,
value: CUBE_NORMALS
},
TEXCOORD_0: {
size: 2,
value: CUBE_TEX_COORDS
}
POSITION: { size: 3, value: CUBE_POSITIONS },
NORMAL: { size: 3, value: CUBE_NORMALS },
TEXCOORD_0: { size: 2, value: CUBE_TEX_COORDS }
};
const NON_INDEXED_ATTRIBUTES = {
POSITION: {
size: 3,
value: CUBE_NON_INDEXED_POSITIONS
},
TEXCOORD_0: {
size: 2,
value: CUBE_NON_INDEXED_TEX_COORDS
},
COLOR_0: {
size: 3,
value: CUBE_NON_INDEXED_COLORS
}
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 }
};
//# sourceMappingURL=cube-geometry.js.map
import { uid } from '@luma.gl/core';
import { TruncatedConeGeometry } from "./truncated-cone-geometry.js";
import { TruncatedConeGeometry } from './truncated-cone-geometry';
export class CylinderGeometry extends TruncatedConeGeometry {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
id = uid('cylinder-geometry'),
radius = 1
} = props;
super({
...props,
id,
bottomRadius: radius,
topRadius: radius
});
}
constructor(props = {}) {
const { id = uid('cylinder-geometry'), radius = 1 } = props;
super({
...props,
id,
bottomRadius: radius,
topRadius: radius
});
}
}
//# sourceMappingURL=cylinder-geometry.js.map
import { uid } from '@luma.gl/core';
import { Vector3 } from '@math.gl/core';
import { Geometry } from "../geometry/geometry.js";
import { Geometry } from '../geometry/geometry';
/* eslint-disable comma-spacing, max-statements, complexity */
const ICO_POSITIONS = [-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 1, 0, -1, 0, 1, 0, 0];
const 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];
export class IcoSphereGeometry extends Geometry {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
id = uid('ico-sphere-geometry')
} = props;
const {
indices,
attributes
} = tesselateIcosaHedron(props);
super({
...props,
id,
topology: 'triangle-list',
indices,
attributes: {
...attributes,
...props.attributes
}
});
}
constructor(props = {}) {
const { id = 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);
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;
}
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 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;
// Calculate texCoords and normals
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 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;
}
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)
}
}
};
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) }
}
};
}
//# sourceMappingURL=ico-sphere-geometry.js.map
import { uid } from '@luma.gl/core';
import { Geometry } from "../geometry/geometry.js";
import { unpackIndexedGeometry } from "../geometry/geometry-utils.js";
import { Geometry } from '../geometry/geometry';
import { unpackIndexedGeometry } from '../geometry/geometry-utils';
// Primitives inspired by TDL http://code.google.com/p/webglsamples/,
// copyright 2011 Google Inc. new BSD License
// (http://www.opensource.org/licenses/bsd-license.php).
export class PlaneGeometry extends Geometry {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
id = uid('plane-geometry')
} = props;
const {
indices,
attributes
} = tesselatePlane(props);
super({
...props,
id,
topology: 'triangle-list',
indices,
attributes: {
...attributes,
...props.attributes
}
});
}
constructor(props = {}) {
const { id = uid('plane-geometry') } = props;
const { indices, attributes } = tesselatePlane(props);
super({
...props,
id,
topology: 'triangle-list',
indices,
attributes: { ...attributes, ...props.attributes }
});
}
}
/* eslint-disable complexity, max-statements */
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 { type = 'x,y', offset = 0, flipCull = false, unpack = false } = props;
const coords = type.split(',');
// width, height
let c1len = props[`${coords[0]}len`] || 1;
const c2len = props[`${coords[1]}len`] || 1;
// subdivisionsWidth, subdivisionsDepth
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;
}
}
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;
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 geometry = {
indices: {
size: 1,
value: indices
},
attributes: {
POSITION: {
size: 3,
value: positions
},
NORMAL: {
size: 3,
value: normals
},
TEXCOORD_0: {
size: 2,
value: texCoords
}
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;
// Make triangle 1 of quad.
indices[index + 0] = (z + 0) * numVertsAcross + x;
indices[index + 1] = (z + 1) * numVertsAcross + x;
indices[index + 2] = (z + 0) * numVertsAcross + x + 1;
// Make triangle 2 of quad.
indices[index + 3] = (z + 1) * numVertsAcross + x;
indices[index + 4] = (z + 1) * numVertsAcross + x + 1;
indices[index + 5] = (z + 0) * numVertsAcross + x + 1;
}
}
};
return unpack ? unpackIndexedGeometry(geometry) : geometry;
const geometry = {
indices: { size: 1, value: indices },
attributes: {
POSITION: { size: 3, value: positions },
NORMAL: { size: 3, value: normals },
TEXCOORD_0: { size: 2, value: texCoords }
}
};
// Optionally, unpack indexed geometry
return unpack ? unpackIndexedGeometry(geometry) : geometry;
}
//# sourceMappingURL=plane-geometry.js.map
import { uid } from '@luma.gl/core';
import { Geometry } from "../geometry/geometry.js";
import { Geometry } from '../geometry/geometry';
// Primitives inspired by TDL http://code.google.com/p/webglsamples/,
// copyright 2011 Google Inc. new BSD License
// (http://www.opensource.org/licenses/bsd-license.php).
export class SphereGeometry extends Geometry {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
id = uid('sphere-geometry')
} = props;
const {
indices,
attributes
} = tesselateSphere(props);
super({
...props,
id,
topology: 'triangle-list',
indices,
attributes: {
...attributes,
...props.attributes
}
});
}
constructor(props = {}) {
const { id = uid('sphere-geometry') } = props;
const { indices, attributes } = tesselateSphere(props);
super({
...props,
id,
topology: 'triangle-list',
indices,
attributes: { ...attributes, ...props.attributes }
});
}
}
/* eslint-disable max-statements, complexity */
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 > 0xffff ? 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 { 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 > 0xffff ? Uint32Array : Uint16Array;
const indices = new IndexType(nlat * nlong * 6);
// Create positions, normals and texCoords
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;
// Create indices
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
}
}
};
return {
indices: { size: 1, value: indices },
attributes: {
POSITION: { size: 3, value: positions },
NORMAL: { size: 3, value: normals },
TEXCOORD_0: { size: 2, value: texCoords }
}
};
}
//# sourceMappingURL=sphere-geometry.js.map
import { uid } from '@luma.gl/core';
import { Geometry } from "../geometry/geometry.js";
import { Geometry } from '../geometry/geometry';
const INDEX_OFFSETS = {
x: [2, 0, 1],
y: [0, 1, 2],
z: [1, 2, 0]
x: [2, 0, 1],
y: [0, 1, 2],
z: [1, 2, 0]
};
/**
* Primitives inspired by TDL http://code.google.com/p/webglsamples/,
* copyright 2011 Google Inc. new BSD License
* (http://www.opensource.org/licenses/bsd-license.php).
*/
export class TruncatedConeGeometry extends Geometry {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
id = 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
}
});
}
constructor(props = {}) {
const { id = 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() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
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);
/* eslint-disable max-statements, complexity */
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;
}
}
if (i === -2 || i === nvertical + 2) {
ringRadius = 0;
v = 0;
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;
}
}
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
}
};
return {
indices,
attributes: {
POSITION: positions,
NORMAL: normals,
TEXCOORD_0: texCoords
}
};
}
//# sourceMappingURL=truncated-cone-geometry.js.map
export {};
//# sourceMappingURL=geometry-table.js.map

@@ -0,37 +1,40 @@

// import type {Geometry} from './geometry';
export 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 { indices, attributes } = geometry;
if (!indices) {
return geometry;
}
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];
}
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; // eslint-disable-line
}
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 };
}
unpackedAttributes[attributeName] = {
size,
value: unpackedValue
return {
attributes: Object.assign({}, attributes, unpackedAttributes)
};
}
return {
attributes: Object.assign({}, attributes, unpackedAttributes)
};
}
//# sourceMappingURL=geometry-utils.js.map
// export function calculateVertexNormals(positions: Float32Array): Uint8Array {
// let normals = new Uint8Array(positions.length / 3);
// for (let i = 0; i < positions.length; i++) {
// const vec1 = new Vector3(positions.subarray(i * 3, i + 0, i + 3));
// const vec2 = new Vector3(positions.subarray(i + 3, i + 6));
// const vec3 = new Vector3(positions.subarray(i + 6, i + 9));
// const normal = new Vector3(vec1).cross(vec2).normalize();
// normals.set(normal[0], i + 4);
// normals.set(normal[1], i + 4 + 1);
// normals.set(normal[2], i + 2);
// }
// const normal = new Vector3(vec1).cross(vec2).normalize();
// }
import { uid, assert } from '@luma.gl/core';
export class Geometry {
constructor(props) {
this.id = void 0;
this.topology = void 0;
this.vertexCount = void 0;
this.indices = void 0;
this.attributes = void 0;
this.userData = {};
const {
attributes = {},
indices = null,
vertexCount = null
} = props;
this.id = props.id || uid('geometry');
this.topology = props.topology;
if (indices) {
this.indices = ArrayBuffer.isView(indices) ? {
value: indices,
size: 1
} : indices;
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 || uid('geometry');
this.topology = props.topology;
if (indices) {
this.indices = ArrayBuffer.isView(indices) ? { value: indices, size: 1 } : indices;
}
// @ts-expect-error
this.attributes = {};
for (const [attributeName, attributeValue] of Object.entries(attributes)) {
// Wrap "unwrapped" arrays and try to autodetect their type
const attribute = ArrayBuffer.isView(attributeValue)
? { value: attributeValue }
: attributeValue;
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;
}
// Move indices to separate field
if (attributeName === 'indices') {
assert(!this.indices);
this.indices = attribute;
}
else {
this.attributes[attributeName] = attribute;
}
}
if (this.indices && this.indices.isIndexed !== undefined) {
this.indices = Object.assign({}, this.indices);
delete this.indices.isIndexed;
}
this.vertexCount = vertexCount || this._calculateVertexCount(this.attributes, this.indices);
}
this.attributes = {};
for (const [attributeName, attributeValue] of Object.entries(attributes)) {
const attribute = ArrayBuffer.isView(attributeValue) ? {
value: attributeValue
} : attributeValue;
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') {
assert(!this.indices);
this.indices = attribute;
} else {
this.attributes[attributeName] = attribute;
}
getVertexCount() {
return this.vertexCount;
}
if (this.indices && this.indices.isIndexed !== undefined) {
this.indices = Object.assign({}, this.indices);
delete this.indices.isIndexed;
/**
* 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;
}
this.vertexCount = vertexCount || this._calculateVertexCount(this.attributes, this.indices);
}
getVertexCount() {
return this.vertexCount;
}
getAttributes() {
return this.indices ? {
indices: this.indices,
...this.attributes
} : this.attributes;
}
_print(attributeName) {
return `Geometry ${this.id} attribute ${attributeName}`;
}
_setAttributes(attributes, indices) {
return this;
}
_calculateVertexCount(attributes, indices) {
if (indices) {
return indices.value.length;
// PRIVATE
_print(attributeName) {
return `Geometry ${this.id} attribute ${attributeName}`;
}
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);
}
/**
* 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;
}
assert(Number.isFinite(vertexCount));
return vertexCount;
}
_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);
}
}
assert(Number.isFinite(vertexCount));
return vertexCount;
}
}
//# sourceMappingURL=geometry.js.map
import { Buffer, uid, assert, getVertexFormatFromAttribute } from '@luma.gl/core';
export class GPUGeometry {
constructor(props) {
this.id = void 0;
this.userData = {};
this.topology = void 0;
this.bufferLayout = [];
this.vertexCount = void 0;
this.indices = void 0;
this.attributes = void 0;
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) {
assert(this.indices.usage === Buffer.INDEX);
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) {
assert(this.indices.usage === Buffer.INDEX);
}
}
}
destroy() {
var _this$attributes$colo;
this.indices.destroy();
this.attributes.positions.destroy();
this.attributes.normals.destroy();
this.attributes.texCoords.destroy();
(_this$attributes$colo = this.attributes.colors) === null || _this$attributes$colo === void 0 ? void 0 : _this$attributes$colo.destroy();
}
getVertexCount() {
return this.vertexCount;
}
getAttributes() {
return this.attributes;
}
getIndexes() {
return this.indices;
}
_calculateVertexCount(positions) {
const vertexCount = positions.byteLength / 12;
return vertexCount;
}
destroy() {
this.indices.destroy();
this.attributes.positions.destroy();
this.attributes.normals.destroy();
this.attributes.texCoords.destroy();
this.attributes.colors?.destroy();
}
getVertexCount() {
return this.vertexCount;
}
getAttributes() {
return this.attributes;
}
getIndexes() {
return this.indices;
}
_calculateVertexCount(positions) {
// Assume that positions is a fully packed float32x3 buffer
const vertexCount = positions.byteLength / 12;
return vertexCount;
}
}
export 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
});
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
});
}
export function getIndexBufferFromGeometry(device, geometry) {
if (!geometry.indices) {
return undefined;
}
const data = geometry.indices.value;
return device.createBuffer({
usage: Buffer.INDEX,
data
});
if (!geometry.indices) {
return undefined;
}
const data = geometry.indices.value;
return device.createBuffer({ usage: Buffer.INDEX, data });
}
export 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;
const bufferLayout = [];
const attributes = {};
for (const [attributeName, attribute] of Object.entries(geometry.attributes)) {
let name = attributeName;
// TODO Map some GLTF attribute names (is this still needed?)
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: getVertexFormatFromAttribute(value, size, normalized) });
}
attributes[name] = device.createBuffer({
data: attribute.value,
id: `${attributeName}-buffer`
});
const {
value,
size,
normalized
} = attribute;
bufferLayout.push({
name,
format: getVertexFormatFromAttribute(value, size, normalized)
});
}
const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices);
return {
attributes,
bufferLayout,
vertexCount
};
const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices);
return { attributes, bufferLayout, vertexCount };
}
//# sourceMappingURL=gpu-geometry.js.map

@@ -0,2 +1,42 @@

"use strict";
/*
export function getAttributeLayoutsFromGeometry(geometry: Geometry) {
const layouts: Record<string, {}> = {};
let indices = geometry.indices;
//# sourceMappingURL=gpu-table.js.map
for (const [name, attribute] of Object.entries(geometry.attributes)) {
const remappedName = mapAttributeName(name);
if (attribute.constant) {
throw new Error('constant attributes not supported');
} else {
const typedArray = attribute.value;
// Create accessor by copying the attribute and removing `value``
const accessor = {...attribute};
delete accessor.value;
buffers[remappedName] = [device.createBuffer(typedArray), accessor];
inferAttributeAccessor(name, accessor);
}
}
}
export class Table {
length: number;
// columns: Record<string, TypedArray> = {};
}
export class GPUTable {
length: number;
columns: Record<string, Buffer> = {};
}
export function convertTableToGPUTable(table: Table) {
// for (const ) {}
}
export function renameTableColumns(table: Table, map: (name: string) => string) {
const newColumns = table.columns.reduce()
table.clone();
}
*/

@@ -1,24 +0,27 @@

export { Timeline } from "./animation/timeline.js";
export { KeyFrames } from "./animation/key-frames.js";
export { AnimationLoopTemplate } from "./animation-loop/animation-loop-template.js";
export { AnimationLoop } from "./animation-loop/animation-loop.js";
export { makeAnimationLoop } from "./animation-loop/make-animation-loop.js";
export { Model } from "./model/model.js";
export { BufferTransform } from "./transform/buffer-transform.js";
export { TextureTransform } from "./transform/texture-transform.js";
export { PipelineFactory } from "./lib/pipeline-factory.js";
export { ClipSpace } from "./lib/clip-space.js";
export { ScenegraphNode } from "./scenegraph/scenegraph-node.js";
export { GroupNode } from "./scenegraph/group-node.js";
export { ModelNode } from "./scenegraph/model-node.js";
export { Geometry } from "./geometry/geometry.js";
export { GPUGeometry } from "./geometry/gpu-geometry.js";
export { ConeGeometry } from "./geometries/cone-geometry.js";
export { CubeGeometry } from "./geometries/cube-geometry.js";
export { CylinderGeometry } from "./geometries/cylinder-geometry.js";
export { IcoSphereGeometry } from "./geometries/ico-sphere-geometry.js";
export { PlaneGeometry } from "./geometries/plane-geometry.js";
export { SphereGeometry } from "./geometries/sphere-geometry.js";
export { TruncatedConeGeometry } from "./geometries/truncated-cone-geometry.js";
export { ShaderInputs as _ShaderInputs } from "./shader-inputs.js";
//# sourceMappingURL=index.js.map
// luma.gl Engine API
// Animation
export { Timeline } from './animation/timeline';
export { KeyFrames } from './animation/key-frames';
export { AnimationLoopTemplate } from './animation-loop/animation-loop-template';
export { AnimationLoop } from './animation-loop/animation-loop';
export { makeAnimationLoop } from './animation-loop/make-animation-loop';
export { Model } from './model/model';
export { BufferTransform } from './transform/buffer-transform';
export { TextureTransform } from './transform/texture-transform';
export { PipelineFactory } from './lib/pipeline-factory';
// Utils
export { ClipSpace } from './lib/clip-space';
// Scenegraph Core nodes
export { ScenegraphNode } from './scenegraph/scenegraph-node';
export { GroupNode } from './scenegraph/group-node';
export { ModelNode } from './scenegraph/model-node';
export { Geometry } from './geometry/geometry';
export { GPUGeometry } from './geometry/gpu-geometry';
export { ConeGeometry } from './geometries/cone-geometry';
export { CubeGeometry } from './geometries/cube-geometry';
export { CylinderGeometry } from './geometries/cylinder-geometry';
export { IcoSphereGeometry } from './geometries/ico-sphere-geometry';
export { PlaneGeometry } from './geometries/plane-geometry';
export { SphereGeometry } from './geometries/sphere-geometry';
export { TruncatedConeGeometry } from './geometries/truncated-cone-geometry';
export { ShaderInputs as _ShaderInputs } from './shader-inputs';

@@ -0,5 +1,6 @@

// ClipSpace
import { glsl } from '@luma.gl/core';
import { Model } from "../model/model.js";
import { Geometry } from "../geometry/geometry.js";
const CLIPSPACE_VERTEX_SHADER = glsl`\
import { Model } from '../model/model';
import { Geometry } from '../geometry/geometry';
const CLIPSPACE_VERTEX_SHADER = glsl `\
#version 300 es

@@ -21,31 +22,25 @@ in vec2 aClipSpacePosition;

`;
/* eslint-disable indent, no-multi-spaces */
const POSITIONS = [-1, -1, 1, -1, -1, 1, 1, 1];
/**
* A flat geometry that covers the "visible area" that the GPU renders.
*/
export class ClipSpace 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)
}
}
})
});
}
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) }
}
})
});
}
}
//# sourceMappingURL=clip-space.js.map
import type { RenderPipelineProps } from '@luma.gl/core';
import { Device, RenderPipeline } from '@luma.gl/core';
/** Todo - should be same as RenderPipelineProps */
export type PipelineFactoryProps = Omit<RenderPipelineProps, 'vs' | 'fs'> & {
vs: string;
fs: string;
};
export type PipelineFactoryProps = RenderPipelineProps;
/**

@@ -9,0 +5,0 @@ * Efficiently creates / caches pipelines

import { RenderPipeline } from '@luma.gl/core';
/**
* Efficiently creates / caches pipelines
*/
export class PipelineFactory {
static getDefaultPipelineFactory(device) {
device._lumaData.defaultPipelineFactory = device._lumaData.defaultPipelineFactory || new PipelineFactory(device);
return device._lumaData.defaultPipelineFactory;
}
constructor(device) {
this.device = void 0;
this._hashCounter = 0;
this._hashes = {};
this._useCounts = {};
this._pipelineCache = {};
this.device = device;
}
createRenderPipeline(options) {
const props = {
...PipelineFactory.defaultProps,
...options
};
const hash = this._hashRenderPipeline({
...props
});
if (!this._pipelineCache[hash]) {
const pipeline = this.device.createRenderPipeline({
...props,
vs: this.device.createShader({
stage: 'vertex',
source: props.vs
}),
fs: props.fs ? this.device.createShader({
stage: 'fragment',
source: props.fs
}) : null
});
pipeline.hash = hash;
this._pipelineCache[hash] = pipeline;
this._useCounts[hash] = 0;
static defaultProps = { ...RenderPipeline.defaultProps };
device;
_hashCounter = 0;
_hashes = {};
_useCounts = {};
_pipelineCache = {};
static getDefaultPipelineFactory(device) {
device._lumaData.defaultPipelineFactory =
device._lumaData.defaultPipelineFactory || new PipelineFactory(device);
return device._lumaData.defaultPipelineFactory;
}
this._useCounts[hash]++;
return this._pipelineCache[hash];
}
release(pipeline) {
const hash = pipeline.hash;
this._useCounts[hash]--;
if (this._useCounts[hash] === 0) {
this._pipelineCache[hash].destroy();
delete this._pipelineCache[hash];
delete this._useCounts[hash];
constructor(device) {
this.device = device;
}
}
_hashRenderPipeline(props) {
const vsHash = this._getHash(props.vs);
const fsHash = props.fs ? this._getHash(props.fs) : 0;
const varyingHash = '-';
switch (this.device.info.type) {
case 'webgpu':
const parameterHash = this._getHash(JSON.stringify(props.parameters));
createRenderPipeline(options) {
const props = { ...PipelineFactory.defaultProps, ...options };
const hash = this._hashRenderPipeline({ ...props });
if (!this._pipelineCache[hash]) {
const pipeline = this.device.createRenderPipeline({ ...props });
pipeline.hash = hash;
this._pipelineCache[hash] = pipeline;
this._useCounts[hash] = 0;
}
this._useCounts[hash]++;
return this._pipelineCache[hash];
}
release(pipeline) {
const hash = pipeline.hash;
this._useCounts[hash]--;
if (this._useCounts[hash] === 0) {
this._pipelineCache[hash].destroy();
delete this._pipelineCache[hash];
delete this._useCounts[hash];
}
}
// PRIVATE
/** 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;
// WebGL specific
// const {varyings = [], bufferMode = {}} = props;
// const varyingHashes = varyings.map((v) => this._getHash(v));
const varyingHash = '-'; // `${varyingHashes.join('/')}B${bufferMode}`
const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}}`;
default:
return `${vsHash}/${fsHash}V${varyingHash}`;
switch (this.device.info.type) {
case 'webgpu':
// On WebGPU we need to rebuild the pipeline if topology, parameters or bufferLayout change
const parameterHash = this._getHash(JSON.stringify(props.parameters));
// TODO - Can json.stringify() generate different strings for equivalent objects if order of params is different?
// create a deepHash() to deduplicate?
return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
default:
// WebGL is more dynamic
return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
}
}
}
_getHash(key) {
if (this._hashes[key] === undefined) {
this._hashes[key] = this._hashCounter++;
_getHash(key) {
if (this._hashes[key] === undefined) {
this._hashes[key] = this._hashCounter++;
}
return this._hashes[key];
}
return this._hashes[key];
}
}
PipelineFactory.defaultProps = {
...RenderPipeline.defaultProps,
vs: undefined,
fs: undefined
};
//# sourceMappingURL=pipeline-factory.js.map

@@ -44,3 +44,3 @@ import type { TypedArray, RenderPipelineProps, RenderPipelineParameters } from '@luma.gl/core';

constantAttributes?: Record<string, TypedArray>;
/** @internal For use with {@link TransformFeedback}, WebGL 2 only. */
/** @internal For use with {@link TransformFeedback}, WebGL only. */
varyings?: string[];

@@ -176,3 +176,3 @@ transformFeedback?: TransformFeedback;

/**
* Updates optional transform feedback. WebGL 2 only.
* Updates optional transform feedback. WebGL only.
*/

@@ -179,0 +179,0 @@ setTransformFeedback(transformFeedback: TransformFeedback | null): void;

@@ -0,1 +1,3 @@

// luma.gl, MIT license
// Copyright (c) vis.gl contributors
import { Buffer, RenderPipeline, UniformStore, getTypedArrayFromDataType } from '@luma.gl/core';

@@ -5,437 +7,561 @@ import { log, uid, deepEqual, splitUniformsAndBindings, isNumberArray } from '@luma.gl/core';

import { ShaderAssembler, getShaderLayoutFromWGSL } from '@luma.gl/shadertools';
import { ShaderInputs } from "../shader-inputs.js";
import { makeGPUGeometry } from "../geometry/gpu-geometry.js";
import { PipelineFactory } from "../lib/pipeline-factory.js";
import { getDebugTableForShaderLayout } from "../debug/debug-shader-layout.js";
import { debugFramebuffer } from "../debug/debug-framebuffer.js";
import { ShaderInputs } from '../shader-inputs';
import { makeGPUGeometry } from '../geometry/gpu-geometry';
import { PipelineFactory } from '../lib/pipeline-factory';
import { getDebugTableForShaderLayout } from '../debug/debug-shader-layout';
import { debugFramebuffer } from '../debug/debug-framebuffer';
const LOG_DRAW_PRIORITY = 2;
const LOG_DRAW_TIMEOUT = 10000;
/**
* v9 Model API
* A model
* - automatically reuses pipelines (programs) when possible
* - automatically rebuilds pipelines if necessary to accommodate changed settings
* shadertools integration
* - accepts modules and performs shader transpilation
*/
export class Model {
constructor(device, props) {
var _this$props$modules, _this$props$modules2, _this$shaderInputs;
this.device = void 0;
this.id = void 0;
this.vs = void 0;
this.fs = void 0;
this.pipelineFactory = void 0;
this.userData = {};
this.parameters = void 0;
this.topology = void 0;
this.bufferLayout = void 0;
this.vertexCount = void 0;
this.instanceCount = 0;
this.indexBuffer = null;
this.bufferAttributes = {};
this.constantAttributes = {};
this.bindings = {};
this.uniforms = {};
this.vertexArray = void 0;
this.transformFeedback = null;
this.pipeline = void 0;
this.shaderInputs = void 0;
this._uniformStore = void 0;
this._pipelineNeedsUpdate = 'newly created';
this._attributeInfos = {};
this._gpuGeometry = null;
this._getModuleUniforms = void 0;
this.props = void 0;
this._lastLogTime = 0;
this._logOpen = false;
this._drawCount = 0;
this.props = {
...Model.defaultProps,
...props
static defaultProps = {
...RenderPipeline.defaultProps,
source: null,
vs: null,
fs: null,
id: 'unnamed',
handle: undefined,
userData: {},
defines: {},
modules: [],
moduleSettings: undefined,
geometry: null,
indexBuffer: null,
attributes: {},
constantAttributes: {},
varyings: [],
shaderInputs: undefined,
pipelineFactory: undefined,
transformFeedback: undefined,
shaderAssembler: ShaderAssembler.getDefaultShaderAssembler()
};
props = this.props;
this.id = props.id || uid('model');
this.device = device;
Object.assign(this.userData, props.userData);
const moduleMap = Object.fromEntries(((_this$props$modules = this.props.modules) === null || _this$props$modules === void 0 ? void 0 : _this$props$modules.map(module => [module.name, module])) || []);
this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap));
const isWebGPU = this.device.info.type === 'webgpu';
if (this.props.source) {
if (isWebGPU) {
var _this$props;
(_this$props = this.props).shaderLayout || (_this$props.shaderLayout = getShaderLayoutFromWGSL(this.props.source));
}
this.props.fs = this.props.source;
this.props.vs = this.props.source;
device;
id;
vs;
fs;
pipelineFactory;
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
/** Vertex count */
vertexCount;
/** instance count */
instanceCount = 0;
/** 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;
_pipelineNeedsUpdate = 'newly created';
_attributeInfos = {};
_gpuGeometry = null;
_getModuleUniforms;
props;
constructor(device, props) {
this.props = { ...Model.defaultProps, ...props };
props = this.props;
this.id = props.id || uid('model');
this.device = device;
Object.assign(this.userData, props.userData);
// Setup shader module inputs
const moduleMap = Object.fromEntries(this.props.modules?.map(module => [module.name, module]) || []);
this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap));
const isWebGPU = this.device.info.type === 'webgpu';
// TODO - hack to support unified WGSL shader
// TODO - this is wrong, compile a single shader
if (this.props.source) {
if (isWebGPU) {
this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.source);
}
this.props.fs = this.props.source;
this.props.vs = this.props.source;
}
// Support WGSL shader layout introspection
if (isWebGPU && typeof this.props.vs !== 'string') {
this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.vs.wgsl);
}
// Setup shader assembler
const platformInfo = getPlatformInfo(device);
// Extract modules from shader inputs if not supplied
const modules = (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
const { vs, fs, getUniforms } = this.props.shaderAssembler.assembleShaders({
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;
// Geometry, if provided, sets topology and vertex cound
if (props.geometry) {
this._gpuGeometry = this.setGeometry(props.geometry);
}
this.pipelineFactory =
props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
// Create the pipeline
// @note order is important
this.pipeline = this._updatePipeline();
this.vertexArray = device.createVertexArray({
renderPipeline: this.pipeline
});
// Now we can apply geometry attributes
if (this._gpuGeometry) {
this._setGeometryAttributes(this._gpuGeometry);
}
// Apply any dynamic settings that will not trigger pipeline change
if (props.vertexCount) {
this.setVertexCount(props.vertexCount);
}
if (props.instanceCount) {
this.setInstanceCount(props.instanceCount);
}
// @ts-expect-error
if (props.indices) {
throw new Error('Model.props.indices removed. Use props.indexBuffer');
}
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) {
log.warn('Model.props.moduleSettings is deprecated. Use Model.shaderInputs.setProps()')();
this.updateModuleSettings(props.moduleSettings);
}
if (props.transformFeedback) {
this.transformFeedback = props.transformFeedback;
}
// TODO - restore?
// this.setUniforms(this._getModuleUniforms()); // Get all default module uniforms
// Catch any access to non-standard props
Object.seal(this);
}
if (isWebGPU && typeof this.props.vs !== 'string') {
var _this$props2;
(_this$props2 = this.props).shaderLayout || (_this$props2.shaderLayout = getShaderLayoutFromWGSL(this.props.vs.wgsl));
destroy() {
this.pipelineFactory.release(this.pipeline);
this._uniformStore.destroy();
}
const platformInfo = getPlatformInfo(device);
const modules = (((_this$props$modules2 = this.props.modules) === null || _this$props$modules2 === void 0 ? void 0 : _this$props$modules2.length) > 0 ? this.props.modules : (_this$shaderInputs = this.shaderInputs) === null || _this$shaderInputs === void 0 ? void 0 : _this$shaderInputs.getModules()) || [];
const {
vs,
fs,
getUniforms
} = this.props.shaderAssembler.assembleShaders({
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._gpuGeometry = this.setGeometry(props.geometry);
// Draw call
predraw() {
// Update uniform buffers if needed
this.updateShaderInputs();
}
this.pipelineFactory = props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
this.pipeline = this._updatePipeline();
this.vertexArray = device.createVertexArray({
renderPipeline: this.pipeline
});
if (this._gpuGeometry) {
this._setGeometryAttributes(this._gpuGeometry);
draw(renderPass) {
this.predraw();
try {
this._logDrawCallStart();
// Check if the pipeline is invalidated
// TODO - this is likely the worst place to do this from performance perspective. Perhaps add a predraw()?
this.pipeline = this._updatePipeline();
// Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
// Any caching needs to be done inside the pipeline functions
this.pipeline.setBindings(this.bindings);
this.pipeline.setUniforms(this.uniforms);
const { indexBuffer } = this.vertexArray;
const indexCount = indexBuffer ? indexBuffer.byteLength / (indexBuffer.indexType === 'uint32' ? 4 : 2) : undefined;
this.pipeline.draw({
renderPass,
vertexArray: this.vertexArray,
vertexCount: this.vertexCount,
instanceCount: this.instanceCount,
indexCount,
transformFeedback: this.transformFeedback
});
}
finally {
this._logDrawCallEnd();
}
this._logFramebuffer(renderPass);
}
if (props.vertexCount) {
this.setVertexCount(props.vertexCount);
// 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) {
const gpuGeometry = geometry && makeGPUGeometry(this.device, geometry);
this.setTopology(gpuGeometry.topology || 'triangle-list');
this.bufferLayout = mergeBufferLayouts(gpuGeometry.bufferLayout, this.bufferLayout);
if (this.vertexArray) {
this._setGeometryAttributes(gpuGeometry);
}
return gpuGeometry;
}
if (props.instanceCount) {
this.setInstanceCount(props.instanceCount);
/**
* 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) {
// Filter geometry attribute so that we don't issue warnings for unused attributes
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];
}
}
// TODO - delete previous geometry?
this.vertexCount = gpuGeometry.vertexCount;
this.setIndexBuffer(gpuGeometry.indices);
this.setAttributes(gpuGeometry.attributes, 'ignore-unknown');
this.setAttributes(attributes);
}
if (props.indices) {
throw new Error('Model.props.indices removed. Use props.indexBuffer');
/**
* 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');
}
}
if (props.indexBuffer) {
this.setIndexBuffer(props.indexBuffer);
/**
* Updates the buffer layout.
* @note Triggers a pipeline rebuild / pipeline cache fetch on WebGPU
*/
setBufferLayout(bufferLayout) {
this.bufferLayout = this._gpuGeometry
? mergeBufferLayouts(bufferLayout, this._gpuGeometry.bufferLayout)
: bufferLayout;
this._setPipelineNeedsUpdate('bufferLayout');
// Recreate the pipeline
this.pipeline = this._updatePipeline();
// vertex array needs to be updated if we update buffer layout,
// but not if we update parameters
this.vertexArray = this.device.createVertexArray({
renderPipeline: this.pipeline
});
// Reapply geometry attributes to the new vertex array
if (this._gpuGeometry) {
this._setGeometryAttributes(this._gpuGeometry);
}
}
if (props.attributes) {
this.setAttributes(props.attributes);
/**
* Set GPU parameters.
* @note Can trigger a pipeline rebuild / pipeline cache fetch.
* @param parameters
*/
setParameters(parameters) {
if (!deepEqual(parameters, this.parameters, 2)) {
this.parameters = parameters;
this._setPipelineNeedsUpdate('parameters');
}
}
if (props.constantAttributes) {
this.setConstantAttributes(props.constantAttributes);
// 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) {
this.vertexCount = vertexCount;
}
if (props.bindings) {
this.setBindings(props.bindings);
/**
* 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 (props.uniforms) {
this.setUniforms(props.uniforms);
setShaderInputs(shaderInputs) {
this.shaderInputs = shaderInputs;
this._uniformStore = new UniformStore(this.shaderInputs.modules);
// Create uniform buffer bindings for all modules
for (const moduleName of Object.keys(this.shaderInputs.modules)) {
const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
}
}
if (props.moduleSettings) {
log.warn('Model.props.moduleSettings is deprecated. Use Model.shaderInputs.setProps()')();
this.updateModuleSettings(props.moduleSettings);
/**
* Updates shader module settings (which results in uniforms being set)
*/
setShaderModuleProps(props) {
const uniforms = this._getModuleUniforms(props);
// Extract textures & framebuffers set by the modules
// TODO better way to extract bindings
const keys = Object.keys(uniforms).filter(k => {
const uniform = uniforms[k];
return !isNumberArray(uniform) && typeof uniform !== 'number' && typeof uniform !== 'boolean';
});
const bindings = {};
for (const k of keys) {
bindings[k] = uniforms[k];
delete uniforms[k];
}
}
if (props.transformFeedback) {
this.transformFeedback = props.transformFeedback;
updateShaderInputs() {
this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
}
Object.seal(this);
}
destroy() {
this.pipelineFactory.release(this.pipeline);
this._uniformStore.destroy();
}
predraw() {
this.updateShaderInputs();
}
draw(renderPass) {
this.predraw();
try {
this._logDrawCallStart();
this.pipeline = this._updatePipeline();
this.pipeline.setBindings(this.bindings);
this.pipeline.setUniforms(this.uniforms);
const {
indexBuffer
} = this.vertexArray;
const indexCount = indexBuffer ? indexBuffer.byteLength / (indexBuffer.indexType === 'uint32' ? 4 : 2) : undefined;
this.pipeline.draw({
renderPass,
vertexArray: this.vertexArray,
vertexCount: this.vertexCount,
instanceCount: this.instanceCount,
indexCount,
transformFeedback: this.transformFeedback
});
} finally {
this._logDrawCallEnd();
/**
* @deprecated Updates shader module settings (which results in uniforms being set)
*/
updateModuleSettings(props) {
log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')();
const { bindings, uniforms } = splitUniformsAndBindings(this._getModuleUniforms(props));
Object.assign(this.bindings, bindings);
Object.assign(this.uniforms, uniforms);
}
this._logFramebuffer(renderPass);
}
setGeometry(geometry) {
const gpuGeometry = geometry && makeGPUGeometry(this.device, geometry);
this.setTopology(gpuGeometry.topology || 'triangle-list');
this.bufferLayout = mergeBufferLayouts(gpuGeometry.bufferLayout, this.bufferLayout);
if (this.vertexArray) {
this._setGeometryAttributes(gpuGeometry);
/**
* Sets bindings (textures, samplers, uniform buffers)
*/
setBindings(bindings) {
Object.assign(this.bindings, bindings);
}
return gpuGeometry;
}
_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];
}
/**
* Sets individual uniforms
* @deprecated WebGL only, use uniform buffers for portability
* @param uniforms
* @returns self for chaining
*/
setUniforms(uniforms) {
this.pipeline.setUniforms(uniforms);
Object.assign(this.uniforms, uniforms);
}
this.vertexCount = gpuGeometry.vertexCount;
this.setIndexBuffer(gpuGeometry.indices);
this.setAttributes(gpuGeometry.attributes, 'ignore-unknown');
this.setAttributes(attributes);
}
setTopology(topology) {
if (topology !== this.topology) {
this.topology = topology;
this._setPipelineNeedsUpdate('topology');
/**
* Sets the index buffer
* @todo - how to unset it if we change geometry?
*/
setIndexBuffer(indexBuffer) {
this.vertexArray.setIndexBuffer(indexBuffer);
}
}
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);
/**
* Updates optional transform feedback. WebGL only.
*/
setTransformFeedback(transformFeedback) {
this.transformFeedback = transformFeedback;
}
}
setParameters(parameters) {
if (!deepEqual(parameters, this.parameters, 2)) {
this.parameters = parameters;
this._setPipelineNeedsUpdate('parameters');
/**
* Sets attributes (buffers)
* @note Overrides any attributes previously set with the same name
*/
setAttributes(buffers, _option) {
if (buffers.indices) {
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) {
log.warn(`Model(${this.id}): Missing layout for buffer "${bufferName}".`)();
continue; // eslint-disable-line no-continue
}
// For an interleaved attribute we may need to set multiple attributes
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 && _option !== 'ignore-unknown') {
log.warn(`Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`)();
}
}
}
}
setVertexCount(vertexCount) {
this.vertexCount = vertexCount;
}
setInstanceCount(instanceCount) {
this.instanceCount = instanceCount;
}
setShaderInputs(shaderInputs) {
this.shaderInputs = shaderInputs;
this._uniformStore = new 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;
/**
* 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) {
for (const [attributeName, value] of Object.entries(attributes)) {
const attributeInfo = this._attributeInfos[attributeName];
if (attributeInfo) {
this.vertexArray.setConstant(attributeInfo.location, value);
}
else {
log.warn(`Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`)();
}
}
}
}
setShaderModuleProps(props) {
const uniforms = this._getModuleUniforms(props);
const keys = Object.keys(uniforms).filter(k => {
const uniform = uniforms[k];
return !isNumberArray(uniform) && typeof uniform !== 'number' && typeof uniform !== 'boolean';
});
const bindings = {};
for (const k of keys) {
bindings[k] = uniforms[k];
delete uniforms[k];
_setPipelineNeedsUpdate(reason) {
this._pipelineNeedsUpdate = this._pipelineNeedsUpdate || reason;
}
}
updateShaderInputs() {
this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
}
updateModuleSettings(props) {
log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')();
const {
bindings,
uniforms
} = splitUniformsAndBindings(this._getModuleUniforms(props));
Object.assign(this.bindings, bindings);
Object.assign(this.uniforms, uniforms);
}
setBindings(bindings) {
Object.assign(this.bindings, bindings);
}
setUniforms(uniforms) {
this.pipeline.setUniforms(uniforms);
Object.assign(this.uniforms, uniforms);
}
setIndexBuffer(indexBuffer) {
this.vertexArray.setIndexBuffer(indexBuffer);
}
setTransformFeedback(transformFeedback) {
this.transformFeedback = transformFeedback;
}
setAttributes(buffers, _option) {
if (buffers.indices) {
log.warn(`Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`)();
_updatePipeline() {
if (this._pipelineNeedsUpdate) {
if (this.pipeline) {
log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)();
}
this._pipelineNeedsUpdate = false;
const vs = this.device.createShader({
id: `${this.id}-vertex`,
stage: 'vertex',
source: this.vs
});
const fs = this.fs
? this.device.createShader({
id: `${this.id}-fragment`,
stage: 'fragment',
source: this.fs
})
: null;
this.pipeline = this.pipelineFactory.createRenderPipeline({
...this.props,
bufferLayout: this.bufferLayout,
topology: this.topology,
parameters: this.parameters,
vs,
fs
});
this._attributeInfos = getAttributeInfosFromLayouts(this.pipeline.shaderLayout, this.bufferLayout);
}
return this.pipeline;
}
for (const [bufferName, buffer] of Object.entries(buffers)) {
const bufferLayout = this.bufferLayout.find(layout => getAttributeNames(layout).includes(bufferName));
if (!bufferLayout) {
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;
/** Throttle draw call logging */
_lastLogTime = 0;
_logOpen = false;
_logDrawCallStart() {
// IF level is 4 or higher, log every frame.
const logDrawTimeout = log.level > 3 ? 0 : LOG_DRAW_TIMEOUT;
if (log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
return;
}
}
if (!set && _option !== 'ignore-unknown') {
log.warn(`Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`)();
}
this._lastLogTime = Date.now();
this._logOpen = true;
log.group(LOG_DRAW_PRIORITY, `>>> DRAWING MODEL ${this.id}`, { collapsed: log.level <= 2 })();
}
}
setConstantAttributes(attributes) {
for (const [attributeName, value] of Object.entries(attributes)) {
const attributeInfo = this._attributeInfos[attributeName];
if (attributeInfo) {
this.vertexArray.setConstant(attributeInfo.location, value);
} else {
log.warn(`Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`)();
}
_logDrawCallEnd() {
if (this._logOpen) {
const shaderLayoutTable = getDebugTableForShaderLayout(this.pipeline.shaderLayout, this.id);
// log.table(logLevel, attributeTable)();
// log.table(logLevel, uniformTable)();
log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)();
const uniformTable = this.shaderInputs.getDebugTable();
// Add any global uniforms
for (const [name, value] of Object.entries(this.uniforms)) {
uniformTable[name] = { value };
}
log.table(LOG_DRAW_PRIORITY, uniformTable)();
const attributeTable = this._getAttributeDebugTable();
log.table(LOG_DRAW_PRIORITY, this._attributeInfos)();
log.table(LOG_DRAW_PRIORITY, attributeTable)();
log.groupEnd(LOG_DRAW_PRIORITY)();
this._logOpen = false;
}
}
}
_setPipelineNeedsUpdate(reason) {
this._pipelineNeedsUpdate = this._pipelineNeedsUpdate || reason;
}
_updatePipeline() {
if (this._pipelineNeedsUpdate) {
if (this.pipeline) {
log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)();
}
this._pipelineNeedsUpdate = false;
const vs = this.device.createShader({
id: `${this.id}-vertex`,
stage: 'vertex',
source: this.vs
});
const fs = this.fs ? this.device.createShader({
id: `${this.id}-fragment`,
stage: 'fragment',
source: this.fs
}) : null;
this.pipeline = this.device.createRenderPipeline({
...this.props,
bufferLayout: this.bufferLayout,
topology: this.topology,
parameters: this.parameters,
vs,
fs
});
this._attributeInfos = getAttributeInfosFromLayouts(this.pipeline.shaderLayout, this.bufferLayout);
_drawCount = 0;
_logFramebuffer(renderPass) {
const debugFramebuffers = log.get('framebuffer');
this._drawCount++;
// Update first 3 frames and then every 60 frames
if (!debugFramebuffers || ((this._drawCount++ > 3) && (this._drawCount % 60))) {
return;
}
// TODO - display framebuffer output in debug window
const framebuffer = renderPass.props.framebuffer;
if (framebuffer) {
debugFramebuffer(framebuffer, { id: framebuffer.id, minimap: true });
// log.image({logLevel: LOG_DRAW_PRIORITY, message: `${framebuffer.id} %c sup?`, image})();
}
}
return this.pipeline;
}
_logDrawCallStart() {
const logDrawTimeout = log.level > 3 ? 0 : LOG_DRAW_TIMEOUT;
if (log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
return;
_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;
}
this._lastLogTime = Date.now();
this._logOpen = true;
log.group(LOG_DRAW_PRIORITY, `>>> DRAWING MODEL ${this.id}`, {
collapsed: log.level <= 2
})();
}
_logDrawCallEnd() {
if (this._logOpen) {
const shaderLayoutTable = getDebugTableForShaderLayout(this.pipeline.shaderLayout, this.id);
log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)();
const uniformTable = this.shaderInputs.getDebugTable();
for (const [name, value] of Object.entries(this.uniforms)) {
uniformTable[name] = {
value
};
}
log.table(LOG_DRAW_PRIORITY, uniformTable)();
const attributeTable = this._getAttributeDebugTable();
log.table(LOG_DRAW_PRIORITY, this._attributeInfos)();
log.table(LOG_DRAW_PRIORITY, attributeTable)();
log.groupEnd(LOG_DRAW_PRIORITY)();
this._logOpen = false;
// TODO - fix typing of luma data types
_getBufferOrConstantValues(attribute, dataType) {
const TypedArrayConstructor = getTypedArrayFromDataType(dataType);
const typedArray = attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
return typedArray.toString();
}
}
_logFramebuffer(renderPass) {
const debugFramebuffers = 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;
}
_getBufferOrConstantValues(attribute, dataType) {
const TypedArrayConstructor = getTypedArrayFromDataType(dataType);
const typedArray = attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
return typedArray.toString();
}
}
Model.defaultProps = {
...RenderPipeline.defaultProps,
source: null,
vs: null,
fs: null,
id: 'unnamed',
handle: undefined,
userData: {},
defines: {},
modules: [],
moduleSettings: undefined,
geometry: null,
indexBuffer: null,
attributes: {},
constantAttributes: {},
varyings: [],
shaderInputs: undefined,
pipelineFactory: undefined,
transformFeedback: undefined,
shaderAssembler: ShaderAssembler.getDefaultShaderAssembler()
};
// HELPERS
/** TODO - move to core, document add tests */
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;
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;
return layouts;
}
/** Create a shadertools platform info from the Device */
export function getPlatformInfo(device) {
return {
type: device.info.type,
shaderLanguage: device.info.shadingLanguage,
shaderLanguageVersion: device.info.shadingLanguageVersion,
gpu: device.info.gpu,
features: device.features
};
return {
type: device.info.type,
shaderLanguage: device.info.shadingLanguage,
shaderLanguageVersion: device.info.shadingLanguageVersion,
gpu: device.info.gpu,
features: device.features
};
}
/** Get attribute names from a BufferLayout */
function getAttributeNames(bufferLayout) {
var _bufferLayout$attribu;
return bufferLayout.attributes ? (_bufferLayout$attribu = bufferLayout.attributes) === null || _bufferLayout$attribu === void 0 ? void 0 : _bufferLayout$attribu.map(layout => layout.attribute) : [bufferLayout.name];
return bufferLayout.attributes
? bufferLayout.attributes?.map(layout => layout.attribute)
: [bufferLayout.name];
}
//# sourceMappingURL=model.js.map
import { Matrix4, Vector3 } from '@math.gl/core';
import { log } from '@luma.gl/core';
import { ScenegraphNode } from "./scenegraph-node.js";
import { ScenegraphNode } from './scenegraph-node';
export class GroupNode extends ScenegraphNode {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
props = Array.isArray(props) ? {
children: props
} : props;
const {
children = []
} = props;
log.assert(children.every(child => child instanceof ScenegraphNode), 'every child must an instance of ScenegraphNode');
super(props);
this.children = void 0;
this.children = children;
}
getBounds() {
const result = [[Infinity, Infinity, Infinity], [-Infinity, -Infinity, -Infinity]];
this.traverse((node, _ref) => {
let {
worldMatrix
} = _ref;
const bounds = node.getBounds();
if (!bounds) {
return;
}
const [min, max] = bounds;
const center = new Vector3(min).add(max).divide([2, 2, 2]);
worldMatrix.transformAsPoint(center, center);
const halfSize = new Vector3(max).subtract(min).divide([2, 2, 2]);
worldMatrix.transformAsVector(halfSize, halfSize);
for (let v = 0; v < 8; v++) {
const position = new Vector3(v & 0b001 ? -1 : 1, v & 0b010 ? -1 : 1, v & 0b100 ? -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]);
children;
constructor(props = {}) {
props = Array.isArray(props) ? { children: props } : props;
const { children = [] } = props;
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 Vector3(min).add(max).divide([2, 2, 2]);
worldMatrix.transformAsPoint(center, center);
const halfSize = new Vector3(max).subtract(min).divide([2, 2, 2]);
worldMatrix.transformAsVector(halfSize, halfSize);
for (let v = 0; v < 8; v++) {
// Test all 8 corners of the box
const position = new Vector3(v & 0b001 ? -1 : 1, v & 0b010 ? -1 : 1, v & 0b100 ? -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;
}
}
});
if (!Number.isFinite(result[0][0])) {
return null;
return result;
}
return result;
}
destroy() {
this.children.forEach(child => child.destroy());
this.removeAll();
super.destroy();
}
add() {
for (var _len = arguments.length, children = new Array(_len), _key = 0; _key < _len; _key++) {
children[_key] = arguments[_key];
destroy() {
this.children.forEach((child) => child.destroy());
this.removeAll();
super.destroy();
}
for (const child of children) {
if (Array.isArray(child)) {
this.add(...child);
} else {
this.children.push(child);
}
// 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;
}
return this;
}
remove(child) {
const children = this.children;
const indexOf = children.indexOf(child);
if (indexOf > -1) {
children.splice(indexOf, 1);
remove(child) {
const children = this.children;
const indexOf = children.indexOf(child);
if (indexOf > -1) {
children.splice(indexOf, 1);
}
return this;
}
return this;
}
removeAll() {
this.children = [];
return this;
}
traverse(visitor) {
let {
worldMatrix = new Matrix4()
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const modelMatrix = new 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
});
}
removeAll() {
this.children = [];
return this;
}
}
traverse(visitor, { worldMatrix = new Matrix4() } = {}) {
const modelMatrix = new 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 });
}
}
}
}
//# sourceMappingURL=group-node.js.map

@@ -1,28 +0,35 @@

import { ScenegraphNode } from "./scenegraph-node.js";
import { ScenegraphNode } from './scenegraph-node';
export class ModelNode extends ScenegraphNode {
constructor(props) {
super(props);
this.model = void 0;
this.bounds = null;
this.managedResources = void 0;
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;
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);
// Create new Model or used supplied Model
this.model = props.model;
this.managedResources = props.managedResources || [];
this.bounds = props.bounds || null;
this.setProps(props);
}
this.managedResources.forEach(resource => resource.destroy());
this.managedResources = [];
}
draw(renderPass) {
return this.model.draw(renderPass);
}
getBounds() {
return this.bounds;
}
destroy() {
if (this.model) {
this.model.destroy();
// @ts-expect-error
this.model = null;
}
this.managedResources.forEach((resource) => resource.destroy());
this.managedResources = [];
}
// Expose model methods
draw(renderPass) {
// Return value indicates if something was actually drawn
return this.model.draw(renderPass);
}
}
//# sourceMappingURL=model-node.js.map
import { assert, uid } from '@luma.gl/core';
import { Vector3, Matrix4 } from '@math.gl/core';
export class ScenegraphNode {
constructor() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.id = void 0;
this.matrix = new Matrix4();
this.display = true;
this.position = new Vector3();
this.rotation = new Vector3();
this.scale = new Vector3(1, 1, 1);
this.userData = {};
this.props = {};
const {
id
} = props;
this.id = id || uid(this.constructor.name);
this._setScenegraphNodeProps(props);
}
getBounds() {
return null;
}
destroy() {}
delete() {
this.destroy();
}
setProps(props) {
this._setScenegraphNodeProps(props);
return this;
}
toString() {
return `{type: ScenegraphNode, id: ${this.id})}`;
}
setPosition(position) {
assert(position.length === 3, 'setPosition requires vector argument');
this.position = position;
return this;
}
setRotation(rotation) {
assert(rotation.length === 3, 'setRotation requires vector argument');
this.rotation = rotation;
return this;
}
setScale(scale) {
assert(scale.length === 3, 'setScale requires vector argument');
this.scale = scale;
return this;
}
setMatrix(matrix) {
let copyMatrix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
if (copyMatrix) {
this.matrix.copy(matrix);
} else {
this.matrix = matrix;
id;
matrix = new Matrix4();
display = true;
position = new Vector3();
rotation = new Vector3();
scale = new Vector3(1, 1, 1);
userData = {};
props = {};
constructor(props = {}) {
const { id } = props;
this.id = id || uid(this.constructor.name);
this._setScenegraphNodeProps(props);
}
}
setMatrixComponents(components) {
const {
position,
rotation,
scale,
update = true
} = components;
if (position) {
this.setPosition(position);
getBounds() {
return null;
}
if (rotation) {
this.setRotation(rotation);
destroy() { }
/** @deprecated use .destroy() */
delete() {
this.destroy();
}
if (scale) {
this.setScale(scale);
setProps(props) {
this._setScenegraphNodeProps(props);
return this;
}
if (update) {
this.updateMatrix();
toString() {
return `{type: ScenegraphNode, id: ${this.id})}`;
}
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() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
position,
rotation,
scale
} = options;
if (position) {
this.setPosition(position);
setPosition(position) {
assert(position.length === 3, 'setPosition requires vector argument');
this.position = position;
return this;
}
if (rotation) {
this.setRotation(rotation);
setRotation(rotation) {
assert(rotation.length === 3, 'setRotation requires vector argument');
this.rotation = rotation;
return this;
}
if (scale) {
this.setScale(scale);
setScale(scale) {
assert(scale.length === 3, 'setScale requires vector argument');
this.scale = scale;
return this;
}
this.updateMatrix();
return this;
}
getCoordinateUniforms(viewMatrix, modelMatrix) {
assert(viewMatrix);
modelMatrix = modelMatrix || this.matrix;
const worldMatrix = new Matrix4(viewMatrix).multiplyRight(modelMatrix);
const worldInverse = worldMatrix.invert();
const worldInverseTranspose = worldInverse.transpose();
return {
viewMatrix,
modelMatrix,
objectMatrix: modelMatrix,
worldMatrix,
worldInverseMatrix: worldInverse,
worldInverseTransposeMatrix: worldInverseTranspose
};
}
_setScenegraphNodeProps(props) {
if ('display' in props) {
this.display = props.display;
setMatrix(matrix, copyMatrix = true) {
if (copyMatrix) {
this.matrix.copy(matrix);
}
else {
this.matrix = matrix;
}
}
if ('position' in props) {
this.setPosition(props.position);
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;
}
if ('rotation' in props) {
this.setRotation(props.rotation);
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;
}
if ('scale' in props) {
this.setScale(props.scale);
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;
}
if ('matrix' in props) {
this.setMatrix(props.matrix);
getCoordinateUniforms(viewMatrix, modelMatrix) {
// TODO - solve multiple class problem
// assert(viewMatrix instanceof Matrix4);
assert(viewMatrix);
modelMatrix = modelMatrix || this.matrix;
const worldMatrix = new Matrix4(viewMatrix).multiplyRight(modelMatrix);
const worldInverse = worldMatrix.invert();
const worldInverseTranspose = worldInverse.transpose();
return {
viewMatrix,
modelMatrix,
objectMatrix: modelMatrix,
worldMatrix,
worldInverseMatrix: worldInverse,
worldInverseTransposeMatrix: worldInverseTranspose
};
}
Object.assign(this.props, props);
}
// 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);
}
// Matrix overwrites other props
if ('matrix' in props) {
this.setMatrix(props.matrix);
}
Object.assign(this.props, props);
}
}
//# sourceMappingURL=scenegraph-node.js.map
import { log } from '@luma.gl/core';
// import type {ShaderUniformType, UniformValue, UniformFormat, UniformInfoDevice, Texture, Sampler} from '@luma.gl/core';
import { _resolveModules } from '@luma.gl/shadertools';
/**
* ShaderInputs holds uniform and binding values for one or more shader modules,
* - It can generate binary data for any uniform buffer
* - It can manage a uniform buffer for each block
* - It can update managed uniform buffers with a single call
* - It performs some book keeping on what has changed to minimize unnecessary writes to uniform buffers.
*/
export class ShaderInputs {
constructor(modules) {
this.modules = void 0;
this.moduleUniforms = void 0;
this.moduleBindings = void 0;
this.moduleUniformsChanged = void 0;
const allModules = _resolveModules(Object.values(modules));
log.log(1, 'Creating ShaderInputs with modules', allModules.map(m => m.name))();
this.modules = modules;
this.moduleUniforms = {};
this.moduleBindings = {};
for (const [name, module] of Object.entries(modules)) {
const moduleName = name;
this.moduleUniforms[moduleName] = module.defaultUniforms || {};
this.moduleBindings[moduleName] = {};
/**
* 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) {
// TODO - get all dependencies from modules
const allModules = _resolveModules(Object.values(modules));
log.log(1, 'Creating ShaderInputs with modules', allModules.map(m => m.name))();
// Store the module definitions and create storage for uniform values and binding values, per module
this.modules = modules;
this.moduleUniforms = {};
this.moduleBindings = {};
// Initialize the modules
for (const [name, module] of Object.entries(modules)) {
const moduleName = name;
// Get default uniforms from module
this.moduleUniforms[moduleName] = module.defaultUniforms || {};
this.moduleBindings[moduleName] = {};
}
}
}
destroy() {}
setProps(props) {
for (const name of Object.keys(props)) {
var _module$getUniforms;
const moduleName = name;
const moduleProps = props[moduleName];
const module = this.modules[moduleName];
if (!module) {
log.warn(`Module ${name} not found`)();
continue;
}
const oldUniforms = this.moduleUniforms[moduleName];
const uniforms = ((_module$getUniforms = module.getUniforms) === null || _module$getUniforms === void 0 ? void 0 : _module$getUniforms.call(module, moduleProps, this.moduleUniforms[moduleName])) || moduleProps;
this.moduleUniforms[moduleName] = {
...oldUniforms,
...uniforms
};
/** Destroy */
destroy() { }
/**
* Set module props
*/
setProps(props) {
for (const name of Object.keys(props)) {
const moduleName = name;
const moduleProps = props[moduleName];
const module = this.modules[moduleName];
if (!module) {
// Ignore props for unregistered modules
log.warn(`Module ${name} not found`)();
continue; // eslint-disable-line no-continue
}
const oldUniforms = this.moduleUniforms[moduleName];
const uniforms = module.getUniforms?.(moduleProps, this.moduleUniforms[moduleName]) || moduleProps;
// console.error(uniforms)
this.moduleUniforms[moduleName] = { ...oldUniforms, ...uniforms };
// this.moduleUniformsChanged ||= moduleName;
// console.log(`setProps(${String(moduleName)}`, moduleName, this.moduleUniforms[moduleName])
// TODO - Get Module bindings
// const bindings = module.getBindings?.(moduleProps);
// this.moduleUniforms[moduleName] = bindings;
}
}
}
getModules() {
return Object.values(this.modules);
}
getUniformValues() {
return this.moduleUniforms;
}
getBindings() {
const bindings = {};
for (const moduleBindings of Object.values(this.moduleBindings)) {
Object.assign(bindings, moduleBindings);
/** Merges all bindings for the shader (from the various modules) */
// getUniformBlocks(): Record<string, Texture | Sampler> {
// return this.moduleUniforms;
// }
/**
* Return the map of modules
* @todo should should this include the resolved dependencies?
*/
getModules() {
return Object.values(this.modules);
}
return bindings;
}
getDebugTable() {
const table = {};
for (const [moduleName, module] of Object.entries(this.moduleUniforms)) {
for (const [key, value] of Object.entries(module)) {
var _this$modules$moduleN;
table[`${moduleName}.${key}`] = {
type: (_this$modules$moduleN = this.modules[moduleName].uniformTypes) === null || _this$modules$moduleN === void 0 ? void 0 : _this$modules$moduleN[key],
value: String(value)
};
}
/** Get all uniform values for all modules */
getUniformValues() {
return this.moduleUniforms;
}
return table;
}
/** 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() {
const table = {};
for (const [moduleName, module] of Object.entries(this.moduleUniforms)) {
for (const [key, value] of Object.entries(module)) {
table[`${moduleName}.${key}`] = {
type: this.modules[moduleName].uniformTypes?.[key],
value: String(value)
};
}
}
return table;
}
}
//# sourceMappingURL=shader-inputs.js.map

@@ -0,62 +1,69 @@

// luma.gl, MIT license
// Copyright (c) vis.gl contributors
import { Buffer, assert } from '@luma.gl/core';
import { getPassthroughFS } from '@luma.gl/shadertools';
import { Model } from "../model/model.js";
import { Model } from '../model/model';
/**
* Creates a pipeline for buffer→buffer transforms.
* @deprecated
*/
export class BufferTransform {
static isSupported(device) {
return device.features.has('transform-feedback-webgl2');
}
constructor(device) {
let props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Model.defaultProps;
this.device = void 0;
this.model = void 0;
this.transformFeedback = void 0;
assert(device.features.has('transform-feedback-webgl2'), 'Device must support transform feedback');
this.device = device;
this.model = new Model(this.device, {
id: props.id || 'buffer-transform-model',
fs: props.fs || getPassthroughFS({
version: 300
}),
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() {
if (this.model) {
this.model.destroy();
device;
model;
transformFeedback;
/** @deprecated Use device feature test. */
static isSupported(device) {
return device.features.has('transform-feedback-webgl');
}
}
delete() {
this.destroy();
}
run(options) {
const renderPass = this.device.beginRenderPass(options);
this.model.draw(renderPass);
renderPass.end();
}
update() {
console.warn('TextureTransform#update() not implemented');
}
getBuffer(varyingName) {
return this.transformFeedback.getBuffer(varyingName);
}
readAsync(varyingName) {
const result = this.getBuffer(varyingName);
if (result instanceof Buffer) {
return result.readAsync();
constructor(device, props = Model.defaultProps) {
assert(device.features.has('transform-feedback-webgl'), 'Device must support transform feedback');
this.device = device;
this.model = new Model(this.device, {
id: props.id || 'buffer-transform-model',
fs: props.fs || 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);
}
const {
buffer,
byteOffset = 0,
byteLength = buffer.byteLength
} = result;
return buffer.readAsync(byteOffset, byteLength);
}
/** 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) {
// TODO(v9): Method should likely be removed for v9. Keeping a method stub
// to assist with migrating DeckGL usage.
// eslint-disable-next-line no-console
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 Buffer) {
return result.readAsync();
}
const { buffer, byteOffset = 0, byteLength = buffer.byteLength } = result;
return buffer.readAsync(byteOffset, byteLength);
}
}
//# sourceMappingURL=buffer-transform.js.map

@@ -1,122 +0,115 @@

import { Model } from "../model/model.js";
// luma.gl, MIT license
// Copyright (c) vis.gl contributors
import { Model } from '../model/model';
import { getPassthroughFS } from '@luma.gl/shadertools';
const FS_OUTPUT_VARIABLE = 'transform_output';
/**
* Creates a pipeline for texture→texture transforms.
* @deprecated
*/
export class TextureTransform {
constructor(device, props) {
this.device = void 0;
this.model = void 0;
this.sampler = void 0;
this.currentIndex = 0;
this.samplerTextureMap = null;
this.bindings = [];
this.resources = {};
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 || getPassthroughFS({
version: 300,
input: props.targetTextureVarying,
inputChannels: props.targetTextureChannels,
output: FS_OUTPUT_VARIABLE
}),
vertexCount: props.vertexCount,
...props
});
this._initialize(props);
Object.seal(this);
}
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();
}
update() {
console.warn('TextureTransform#update() not implemented');
}
getData() {
let {
packed = false
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
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;
}
_initialize(props) {
this._updateBindings(props);
}
_updateBindings(props) {
this.bindings[this.currentIndex] = this._updateBinding(this.bindings[this.currentIndex], props);
}
_updateBinding(binding, _ref) {
let {
sourceBuffers,
sourceTextures,
targetTexture
} = _ref;
if (!binding) {
binding = {
sourceBuffers: {},
sourceTextures: {},
targetTexture: null
};
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;
// For precise picking of element IDs.
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 || getPassthroughFS({
input: props.targetTextureVarying,
inputChannels: props.targetTextureChannels,
output: FS_OUTPUT_VARIABLE
}),
vertexCount: props.vertexCount, // TODO(donmccurdy): Naming?
...props
});
this._initialize(props);
Object.seal(this);
}
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
});
// Delete owned resources.
destroy() { }
/** @deprecated Use {@link destroy}. */
delete() {
this.destroy();
}
return binding;
}
_setSourceTextureParameters() {
const index = this.currentIndex;
const {
sourceTextures
} = this.bindings[index];
for (const name in sourceTextures) {
sourceTextures[name].sampler = this.sampler;
run(options) {
const { framebuffer } = this.bindings[this.currentIndex];
const renderPass = this.device.beginRenderPass({ framebuffer, ...options });
this.model.draw(renderPass);
renderPass.end();
}
}
/** @deprecated */
update(...args) {
// TODO(v9): Method should likely be removed for v9. Keeping a method stub
// to assist with migrating DeckGL usage.
// eslint-disable-next-line no-console
console.warn('TextureTransform#update() not implemented');
}
getData({ packed = false } = {}) {
// TODO(v9): Method should likely be removed for v9. Keeping a method stub
// to assist with migrating DeckGL usage.
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;
// TODO(donmccurdy): When is this called, and is this expected?
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;
}
}
}
//# sourceMappingURL=texture-transform.js.map
{
"name": "@luma.gl/engine",
"version": "9.0.0-beta.4",
"description": "WebGL2 Components for High Performance Rendering and Computation",
"version": "9.0.0-beta.5",
"description": "3D Engine Components for luma.gl",
"type": "module",

@@ -38,10 +38,11 @@ "license": "MIT",

"scripts": {
"build-bundle": "ocular-bundle ./src/index.ts",
"pre-build": "npm run build-bundle && npm run build-bundle -- --env=dev"
"build-minified-bundle": "ocular-bundle ./src/index.ts -output=dist/dist.min.js",
"build-dev-bundle": "ocular-bundle ./src/index.ts -output=dist/dist.dev.js -- --env=dev",
"prepublishOnly": "npm run build-minified-bundle && npm run build-dev-bundle"
},
"peerDependencies": {
"@luma.gl/core": "^9.0.0-beta.4",
"@luma.gl/shadertools": "^9.0.0-beta.4"
},
"dependencies": {
"@babel/runtime": "^7.0.0",
"@luma.gl/constants": "9.0.0-beta.4",
"@luma.gl/core": "9.0.0-beta.4",
"@luma.gl/shadertools": "9.0.0-beta.4",
"@math.gl/core": "^4.0.0",

@@ -51,3 +52,3 @@ "@probe.gl/log": "^4.0.2",

},
"gitHead": "bf6bb45b25d59de5b3d05dab4b2e91ad583059e6"
"gitHead": "793d3ab42f5a572b6cb603ea78aabaa73a873301"
}

@@ -5,8 +5,3 @@ // luma.gl, MIT license

/** Todo - should be same as RenderPipelineProps */
export type PipelineFactoryProps = Omit<RenderPipelineProps, 'vs' | 'fs'> & {
// Only accepts string shaders
vs: string;
fs: string;
};
export type PipelineFactoryProps = RenderPipelineProps;

@@ -17,7 +12,3 @@ /**

export class PipelineFactory {
static defaultProps: Required<PipelineFactoryProps> = {
...RenderPipeline.defaultProps,
vs: undefined!,
fs: undefined!
};
static defaultProps: Required<PipelineFactoryProps> = {...RenderPipeline.defaultProps};

@@ -47,7 +38,3 @@ readonly device: Device;

if (!this._pipelineCache[hash]) {
const pipeline = this.device.createRenderPipeline({
...props,
vs: this.device.createShader({stage: 'vertex', source: props.vs}),
fs: props.fs ? this.device.createShader({stage: 'fragment', source: props.fs}) : null
});
const pipeline = this.device.createRenderPipeline({...props});

@@ -78,4 +65,4 @@ pipeline.hash = hash;

private _hashRenderPipeline(props: PipelineFactoryProps): string {
const vsHash = this._getHash(props.vs);
const fsHash = props.fs ? this._getHash(props.fs) : 0;
const vsHash = this._getHash(props.vs.source);
const fsHash = props.fs ? this._getHash(props.fs.source) : 0;

@@ -86,2 +73,3 @@ // WebGL specific

const varyingHash = '-'; // `${varyingHashes.join('/')}B${bufferMode}`
const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));

@@ -92,9 +80,8 @@ switch (this.device.info.type) {

const parameterHash = this._getHash(JSON.stringify(props.parameters));
const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
// TODO - Can json.stringify() generate different strings for equivalent objects if order of params is different?
// create a deepHash() to deduplicate?
return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}}`;
return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
default:
// WebGL is more dynamic
return `${vsHash}/${fsHash}V${varyingHash}`;
return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
}

@@ -101,0 +88,0 @@ }

@@ -63,3 +63,3 @@ // luma.gl, MIT license

/** @internal For use with {@link TransformFeedback}, WebGL 2 only. */
/** @internal For use with {@link TransformFeedback}, WebGL only. */
varyings?: string[];

@@ -273,3 +273,3 @@

// WebGL1?
// TODO - restore?
// this.setUniforms(this._getModuleUniforms()); // Get all default module uniforms

@@ -502,3 +502,3 @@

/**
* Updates optional transform feedback. WebGL 2 only.
* Updates optional transform feedback. WebGL only.
*/

@@ -577,3 +577,3 @@ setTransformFeedback(transformFeedback: TransformFeedback | null): void {

}
this._pipelineNeedsUpdate = false;

@@ -595,3 +595,3 @@

this.pipeline = this.device.createRenderPipeline({
this.pipeline = this.pipelineFactory.createRenderPipeline({
...this.props,

@@ -598,0 +598,0 @@ bufferLayout: this.bufferLayout,

@@ -29,7 +29,7 @@ // luma.gl, MIT license

static isSupported(device: Device): boolean {
return device.features.has('transform-feedback-webgl2');
return device.features.has('transform-feedback-webgl');
}
constructor(device: Device, props: BufferTransformProps = Model.defaultProps) {
assert(device.features.has('transform-feedback-webgl2'), 'Device must support transform feedback');
assert(device.features.has('transform-feedback-webgl'), 'Device must support transform feedback');

@@ -40,3 +40,3 @@ this.device = device;

id: props.id || 'buffer-transform-model',
fs: props.fs || getPassthroughFS({version: 300}),
fs: props.fs || getPassthroughFS(),
topology: props.topology || 'point-list',

@@ -43,0 +43,0 @@ ...props,

@@ -66,3 +66,2 @@ // luma.gl, MIT license

fs: props.fs || getPassthroughFS({
version: 300,
input: props.targetTextureVarying,

@@ -103,3 +102,2 @@ inputChannels: props.targetTextureChannels,

getData({packed = false} = {}) {

@@ -106,0 +104,0 @@ // TODO(v9): Method should likely be removed for v9. Keeping a method stub

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc