// Note: This file will either be moved back to deck.gl or reformatted to web-monorepo standards // Disabling lint temporarily to facilitate copying code in and out of this repo /* eslint-disable */ // deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { Layer, project32, picking, log } from '@deck.gl/core'; import { Texture } from '@luma.gl/core'; import { Model, Geometry } from '@luma.gl/engine'; import { phongMaterial } from '@luma.gl/shadertools'; import { MATRIX_ATTRIBUTES, shouldComposeModelMatrix } from "../utils/matrix.js"; import { simpleMeshUniforms } from "./simple-mesh-layer-uniforms.js"; import vs from "./simple-mesh-layer-vertex.glsl.js"; import fs from "./simple-mesh-layer-fragment.glsl.js"; import { getMeshBoundingBox } from '@loaders.gl/schema'; function normalizeGeometryAttributes(attributes) { const positionAttribute = attributes.positions || attributes.POSITION; log.assert(positionAttribute, 'no "postions" or "POSITION" attribute in mesh'); const vertexCount = positionAttribute.value.length / positionAttribute.size; let colorAttribute = attributes.COLOR_0 || attributes.colors; if (!colorAttribute) { colorAttribute = { size: 3, value: new Float32Array(vertexCount * 3).fill(1) }; } let normalAttribute = attributes.NORMAL || attributes.normals; if (!normalAttribute) { normalAttribute = { size: 3, value: new Float32Array(vertexCount * 3).fill(0) }; } let texCoordAttribute = attributes.TEXCOORD_0 || attributes.texCoords; if (!texCoordAttribute) { texCoordAttribute = { size: 2, value: new Float32Array(vertexCount * 2).fill(0) }; } return { positions: positionAttribute, colors: colorAttribute, normals: normalAttribute, texCoords: texCoordAttribute }; } /* * Convert mesh data into geometry * @returns {Geometry} geometry */ function getGeometry(data) { if (data instanceof Geometry) { // @ts-expect-error data.attributes is readonly data.attributes = normalizeGeometryAttributes(data.attributes); return data; } else if (data.attributes) { return new Geometry({ ...data, topology: 'triangle-list', attributes: normalizeGeometryAttributes(data.attributes) }); } else { return new Geometry({ topology: 'triangle-list', attributes: normalizeGeometryAttributes(data) }); } } const DEFAULT_COLOR = [0, 0, 0, 255]; const defaultProps = { mesh: { type: 'object', value: null, async: true }, texture: { type: 'image', value: null, async: true }, sizeScale: { type: 'number', value: 1, min: 0 }, // _instanced is a hack to use world position instead of meter offsets in mesh // TODO - formalize API _instanced: true, // NOTE(Tarek): Quick and dirty wireframe. Just draws // the same mesh with LINE_STRIPS. Won't follow edges // of the original mesh. wireframe: false, // Optional material for 'lighting' shader module material: true, getPosition: { type: 'accessor', value: (x) => x.position }, getColor: { type: 'accessor', value: DEFAULT_COLOR }, // yaw, pitch and roll are in degrees // https://en.wikipedia.org/wiki/Euler_angles // [pitch, yaw, roll] getOrientation: { type: 'accessor', value: [0, 0, 0] }, getScale: { type: 'accessor', value: [1, 1, 1] }, getTranslation: { type: 'accessor', value: [0, 0, 0] }, // 4x4 matrix getTransformMatrix: { type: 'accessor', value: [] }, textureParameters: { type: 'object', ignore: true, value: null } }; /** Render a number of instances of an arbitrary 3D geometry. */ class SimpleMeshLayer extends Layer { getShaders() { return super.getShaders({ vs, fs, modules: [project32, phongMaterial, picking, simpleMeshUniforms] }); } getBounds() { if (this.props._instanced) { return super.getBounds(); } let result = this.state.positionBounds; if (result) { return result; } const { mesh } = this.props; if (!mesh) { return null; } // @ts-ignore Detect if mesh is generated by loaders.gl result = mesh.header?.boundingBox; if (!result) { // Otherwise, calculate bounding box from positions const { attributes } = getGeometry(mesh); attributes.POSITION = attributes.POSITION || attributes.positions; //@ts-expect-error result = getMeshBoundingBox(attributes); } this.state.positionBounds = result; return result; } initializeState() { const attributeManager = this.getAttributeManager(); // attributeManager is always defined in a primitive layer attributeManager.addInstanced({ instancePositions: { transition: true, type: 'float64', fp64: this.use64bitPositions(), size: 3, accessor: 'getPosition' }, instanceColors: { type: 'unorm8', transition: true, size: this.props.colorFormat.length, accessor: 'getColor', defaultValue: [0, 0, 0, 255] }, instanceModelMatrix: MATRIX_ATTRIBUTES }); this.setState({ // Avoid luma.gl's missing uniform warning // TODO - add feature to luma.gl to specify ignored uniforms? emptyTexture: this.context.device.createTexture({ data: new Uint8Array(4), width: 1, height: 1 }) }); } updateState(params) { super.updateState(params); const { props, oldProps, changeFlags } = params; if (props.mesh !== oldProps.mesh || changeFlags.extensionsChanged) { this.state.positionBounds = null; this.state.model?.destroy(); if (props.mesh) { this.state.model = this.getModel(props.mesh); const attributes = props.mesh.attributes || props.mesh; this.setState({ hasNormals: Boolean(attributes.NORMAL || attributes.normals) }); } // attributeManager is always defined in a primitive layer this.getAttributeManager().invalidateAll(); } if (props.texture !== oldProps.texture && props.texture instanceof Texture) { this.setTexture(props.texture); } if (this.state.model) { this.state.model.setTopology(this.props.wireframe ? 'line-strip' : 'triangle-list'); } } finalizeState(context) { super.finalizeState(context); this.state.emptyTexture.delete(); } draw({ uniforms }) { const { model } = this.state; if (!model) { return; } const { viewport, renderPass } = this.context; const { sizeScale, coordinateSystem, _instanced } = this.props; const simpleMeshProps = { sizeScale, composeModelMatrix: !_instanced || shouldComposeModelMatrix(viewport, coordinateSystem), flatShading: !this.state.hasNormals }; model.shaderInputs.setProps({ simpleMesh: simpleMeshProps }); model.draw(renderPass); } get isLoaded() { return Boolean(this.state?.model && super.isLoaded); } getModel(mesh) { const model = new Model(this.context.device, { ...this.getShaders(), id: this.props.id, bufferLayout: this.getAttributeManager().getBufferLayouts(), geometry: getGeometry(mesh), isInstanced: true }); const { texture } = this.props; const { emptyTexture } = this.state; const simpleMeshProps = { sampler: texture || emptyTexture, hasTexture: Boolean(texture) }; model.shaderInputs.setProps({ simpleMesh: simpleMeshProps }); return model; } setTexture(texture) { const { emptyTexture, model } = this.state; // props.mesh may not be ready at this time. // The sampler will be set when `getModel` is called if (model) { const simpleMeshProps = { sampler: texture || emptyTexture, hasTexture: Boolean(texture) }; model.shaderInputs.setProps({ simpleMesh: simpleMeshProps }); } } } SimpleMeshLayer.defaultProps = defaultProps; SimpleMeshLayer.layerName = 'SimpleMeshLayer'; export default SimpleMeshLayer; //# sourceMappingURL=simple-mesh-layer.js.map