// Copyright (c) 2015 - 2017 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import { Layer, project32, gouraudLighting, phongLighting, picking, UNIT } from '@deck.gl/core'; import { Model } from '@luma.gl/engine'; import ColumnGeometry from "./column-geometry.js"; import vs from "./column-layer-vertex.glsl.js"; import fs from "./column-layer-fragment.glsl.js"; const DEFAULT_COLOR = [0, 0, 0, 255]; const defaultProps = { diskResolution: { type: 'number', min: 4, value: 20 }, vertices: null, radius: { type: 'number', min: 0, value: 1000 }, angle: { type: 'number', value: 0 }, offset: { type: 'array', value: [0, 0] }, coverage: { type: 'number', min: 0, max: 1, value: 1 }, elevationScale: { type: 'number', min: 0, value: 1 }, radiusUnits: 'meters', lineWidthUnits: 'meters', lineWidthScale: 1, lineWidthMinPixels: 0, lineWidthMaxPixels: Number.MAX_SAFE_INTEGER, extruded: true, wireframe: false, filled: true, stroked: false, flatShading: false, getPosition: { type: 'accessor', value: (x) => x.position }, getFillColor: { type: 'accessor', value: DEFAULT_COLOR }, getLineColor: { type: 'accessor', value: DEFAULT_COLOR }, getLineWidth: { type: 'accessor', value: 1 }, getElevation: { type: 'accessor', value: 1000 }, material: true, getColor: { deprecatedFor: ['getFillColor', 'getLineColor'] } }; /** Render extruded cylinders (tessellated regular polygons) at given coordinates. */ class ColumnLayer extends Layer { getShaders() { const defines = {}; const { flatShading } = this.props; if (flatShading) { defines.FLAT_SHADING = 1; } return super.getShaders({ vs, fs, defines, modules: [project32, flatShading ? phongLighting : gouraudLighting, picking] }); } /** * DeckGL calls initializeState when GL context is available * Essentially a deferred constructor */ initializeState() { const attributeManager = this.getAttributeManager(); /* eslint-disable max-len */ attributeManager.addInstanced({ instancePositions: { size: 3, type: 'float64', fp64: this.use64bitPositions(), transition: true, accessor: 'getPosition' }, instanceElevations: { size: 1, transition: true, accessor: 'getElevation' }, instanceFillColors: { size: this.props.colorFormat.length, type: 'unorm8', transition: true, accessor: 'getFillColor', defaultValue: DEFAULT_COLOR }, instanceLineColors: { size: this.props.colorFormat.length, type: 'unorm8', transition: true, accessor: 'getLineColor', defaultValue: DEFAULT_COLOR }, instanceStrokeWidths: { size: 1, accessor: 'getLineWidth', transition: true } }); /* eslint-enable max-len */ } updateState(params) { super.updateState(params); const { props, oldProps, changeFlags } = params; const regenerateModels = changeFlags.extensionsChanged || props.flatShading !== oldProps.flatShading; if (regenerateModels) { this.state.models?.forEach(model => model.destroy()); this.setState(this._getModels()); this.getAttributeManager().invalidateAll(); } const instanceCount = this.getNumInstances(); this.state.fillModel.setInstanceCount(instanceCount); this.state.wireframeModel.setInstanceCount(instanceCount); if (regenerateModels || props.diskResolution !== oldProps.diskResolution || props.vertices !== oldProps.vertices || (props.extruded || props.stroked) !== (oldProps.extruded || oldProps.stroked)) { this._updateGeometry(props); } } getGeometry(diskResolution, vertices, hasThinkness) { const geometry = new ColumnGeometry({ radius: 1, height: hasThinkness ? 2 : 0, vertices, nradial: diskResolution }); let meanVertexDistance = 0; if (vertices) { for (let i = 0; i < diskResolution; i++) { const p = vertices[i]; const d = Math.sqrt(p[0] * p[0] + p[1] * p[1]); meanVertexDistance += d / diskResolution; } } else { meanVertexDistance = 1; } this.setState({ edgeDistance: Math.cos(Math.PI / diskResolution) * meanVertexDistance }); return geometry; } _getModels() { const shaders = this.getShaders(); const bufferLayout = this.getAttributeManager().getBufferLayouts(); const fillModel = new Model(this.context.device, { ...shaders, id: `${this.props.id}-fill`, bufferLayout, isInstanced: true }); const wireframeModel = new Model(this.context.device, { ...shaders, id: `${this.props.id}-wireframe`, bufferLayout, isInstanced: true }); return { fillModel, wireframeModel, models: [wireframeModel, fillModel] }; } _updateGeometry({ diskResolution, vertices, extruded, stroked }) { const geometry = this.getGeometry(diskResolution, vertices, extruded || stroked); this.setState({ fillVertexCount: geometry.attributes.POSITION.value.length / 3 }); const fillModel = this.state.fillModel; const wireframeModel = this.state.wireframeModel; fillModel.setGeometry(geometry); fillModel.setTopology('triangle-strip'); // Disable indices fillModel.setIndexBuffer(null); wireframeModel.setGeometry(geometry); wireframeModel.setTopology('line-list'); } draw({ uniforms }) { const { lineWidthUnits, lineWidthScale, lineWidthMinPixels, lineWidthMaxPixels, radiusUnits, elevationScale, extruded, filled, stroked, wireframe, offset, coverage, radius, angle } = this.props; const fillModel = this.state.fillModel; const wireframeModel = this.state.wireframeModel; const { fillVertexCount, edgeDistance } = this.state; const renderUniforms = { ...uniforms, radius, angle: (angle / 180) * Math.PI, offset, extruded, stroked, coverage, elevationScale, edgeDistance, radiusUnits: UNIT[radiusUnits], widthUnits: UNIT[lineWidthUnits], widthScale: lineWidthScale, widthMinPixels: lineWidthMinPixels, widthMaxPixels: lineWidthMaxPixels }; // When drawing 3d: draw wireframe first so it doesn't get occluded by depth test if (extruded && wireframe) { wireframeModel.setUniforms(renderUniforms); wireframeModel.setUniforms({ isStroke: true }); wireframeModel.draw(this.context.renderPass); } fillModel.setUniforms(renderUniforms); if (filled) { // model.setProps({isIndexed: false}); fillModel.setVertexCount(fillVertexCount); fillModel.setUniforms({ isStroke: false }); fillModel.draw(this.context.renderPass); } // When drawing 2d: draw fill before stroke so that the outline is always on top if (!extruded && stroked) { // model.setProps({isIndexed: false}); // The width of the stroke is achieved by flattening the side of the cylinder. // Skip the last 1/3 of the vertices which is the top. fillModel.setVertexCount((fillVertexCount * 2) / 3); fillModel.setUniforms({ isStroke: true }); fillModel.draw(this.context.renderPass); } } } ColumnLayer.layerName = 'ColumnLayer'; ColumnLayer.defaultProps = defaultProps; export default ColumnLayer;