UNPKG

@itwin/core-frontend

Version:
231 lines • 10.9 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Tiles */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.readPntsColors = readPntsColors; exports.readPointCloudTileContent = readPointCloudTileContent; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const core_common_1 = require("@itwin/core-common"); const FrontendLoggerCategory_1 = require("../../common/FrontendLoggerCategory"); const MeshPrimitives_1 = require("../../common/internal/render/MeshPrimitives"); function readPntsColors(stream, dataOffset, pnts) { const nPts = pnts.POINTS_LENGTH; const nComponents = 3 * nPts; if (pnts.RGB) return new Uint8Array(stream.arrayBuffer, dataOffset + pnts.RGB.byteOffset, nComponents); if (pnts.RGBA) { // ###TODO We currently don't support transparency for point clouds, so convert RGBA to RGB by stripping out the alpha channel. const rgb = new Uint8Array(nComponents); const rgba = new Uint8Array(stream.arrayBuffer, dataOffset + pnts.RGBA.byteOffset, 4 * nPts); for (let i = 0; i < nPts; i++) { rgb[i * 3 + 0] = rgba[i * 4 + 0]; rgb[i * 3 + 1] = rgba[i * 4 + 1]; rgb[i * 3 + 2] = rgba[i * 4 + 2]; } return rgb; } else if (pnts.RGB565) { // Each color is 16 bits: 5 red, 6 green, 5 blue. const crgb = new Uint16Array(stream.arrayBuffer, dataOffset + pnts.RGB565.byteOffset, nPts); const rgb = new Uint8Array(nComponents); for (let i = 0; i < nPts; i++) { const c = crgb[i]; rgb[i + 0] = (c >> 11) & 0x1f; rgb[i + 1] = (c >> 5) & 0x3f; rgb[i + 2] = c & 0x1f; } return rgb; } return undefined; } function readPnts(stream, dataOffset, pnts) { const nPts = pnts.POINTS_LENGTH; let params; let points; if (pnts.POSITION_QUANTIZED) { const qpos = pnts.POSITION_QUANTIZED; const offset = pnts.QUANTIZED_VOLUME_OFFSET; const scale = pnts.QUANTIZED_VOLUME_SCALE; const qOrigin = new core_geometry_1.Point3d(offset[0], offset[1], offset[2]); const qScale = new core_geometry_1.Point3d(core_common_1.Quantization.computeScale(scale[0]), core_common_1.Quantization.computeScale(scale[1]), core_common_1.Quantization.computeScale(scale[2])); params = core_common_1.QParams3d.fromOriginAndScale(qOrigin, qScale); points = new Uint16Array(stream.arrayBuffer, dataOffset + qpos.byteOffset, 3 * nPts); } else { const qOrigin = new core_geometry_1.Point3d(0, 0, 0); const qScale = new core_geometry_1.Point3d(1, 1, 1); params = core_common_1.QParams3d.fromOriginAndScale(qOrigin, qScale); points = new Float32Array(stream.arrayBuffer, dataOffset + pnts.POSITION.byteOffset, 3 * nPts); } const colors = readPntsColors(stream, dataOffset, pnts); return { params, points, colors }; } async function decodeDracoPointCloud(buf) { try { const dracoLoader = (await Promise.resolve().then(() => __importStar(require("@loaders.gl/draco")))).DracoLoader; const mesh = await dracoLoader.parse(buf, {}); if (mesh.topology !== "point-list") return undefined; const pos = mesh.attributes.POSITION?.value; if (!pos || (pos.length % 3) !== 0) return undefined; let colors = mesh.attributes.RGB?.value ?? mesh.attributes.COLOR_0?.value; if (!colors) { // ###TODO support point cloud transparency. const rgba = mesh.attributes.RGBA?.value; if (rgba && (rgba.length % 4) === 0) { // We currently don't support alpha channel for point clouds - strip it. colors = new Uint8Array(3 * rgba.length / 4); let j = 0; for (let i = 0; i < rgba.length; i += 4) { colors[j++] = rgba[i]; colors[j++] = rgba[i + 1]; colors[j++] = rgba[i + 2]; } } } let posRange; const bbox = mesh.header?.boundingBox; if (bbox) { posRange = core_geometry_1.Range3d.createXYZXYZ(bbox[0][0], bbox[0][1], bbox[0][2], bbox[1][0], bbox[1][1], bbox[1][2]); } else { posRange = core_geometry_1.Range3d.createNull(); for (let i = 0; i < pos.length; i += 3) posRange.extendXYZ(pos[i], pos[i + 1], pos[i + 2]); } const params = core_common_1.QParams3d.fromRange(posRange); const pt = core_geometry_1.Point3d.createZero(); const qpt = core_common_1.QPoint3d.create(pt, params); const points = new Uint16Array(pos.length); for (let i = 0; i < pos.length; i += 3) { pt.set(pos[i], pos[i + 1], pos[i + 2]); qpt.init(pt, params); points[i] = qpt.x; points[i + 1] = qpt.y; points[i + 2] = qpt.z; } return { points, params, colors: colors instanceof Uint8Array ? colors : undefined }; } catch (err) { core_bentley_1.Logger.logWarning(FrontendLoggerCategory_1.FrontendLoggerCategory.Render, "Failed to decode draco-encoded point cloud"); core_bentley_1.Logger.logException(FrontendLoggerCategory_1.FrontendLoggerCategory.Render, err); return undefined; } } /** Deserialize a point cloud tile and return it as a RenderGraphic. */ async function readPointCloudTileContent(stream, iModel, modelId, _is3d, tile, system) { let graphic; let rtcCenter; const header = new core_common_1.PntsHeader(stream); if (!header.isValid) return { graphic, rtcCenter }; const range = tile.contentRange; const featureTableJsonOffset = stream.curPos; const featureStrData = stream.nextBytes(header.featureTableJsonLength); const featureStr = (0, core_bentley_1.utf8ToString)(featureStrData); const featureValue = JSON.parse(featureStr); if (undefined === featureValue) return { graphic, rtcCenter }; let props; const dataOffset = featureTableJsonOffset + header.featureTableJsonLength; const draco = featureValue.extensions ? featureValue.extensions["3DTILES_draco_point_compression"] : undefined; if (draco) { try { const buf = new Uint8Array(stream.arrayBuffer, dataOffset + draco.byteOffset, draco.byteLength); props = await decodeDracoPointCloud(buf); } catch { // } } else { props = readPnts(stream, dataOffset, featureValue); } if (!props) return { graphic, rtcCenter }; let batchRange = range; if (featureValue.RTC_CENTER) { rtcCenter = core_geometry_1.Point3d.fromJSON(featureValue.RTC_CENTER); batchRange = range.clone(); batchRange.low.minus(rtcCenter, batchRange.low); batchRange.high.minus(rtcCenter, batchRange.high); } if (!props.colors) { // ###TODO we really should support uniform color instead of allocating an RGB value per point... props.colors = new Uint8Array(3 * featureValue.POINTS_LENGTH); const rgba = featureValue.CONSTANT_RGBA; if (rgba) { // ###TODO support point cloud transparency. for (let i = 0; i < featureValue.POINTS_LENGTH * 3; i += 3) { props.colors[i] = rgba[0]; props.colors[i + 1] = rgba[1]; props.colors[i + 2] = rgba[2]; } } else { // Default to white. props.colors.fill(0xff, 0, props.colors.length); } } const featureTable = new core_common_1.FeatureTable(1, modelId, core_common_1.BatchType.Primary); const features = new MeshPrimitives_1.Mesh.Features(featureTable); features.add(new core_common_1.Feature(modelId), 1); let params = props.params; if (props.points instanceof Float32Array) { // we don't have a true range for unquantized points, so calc one here for voxelSize const rng = core_geometry_1.Range3d.createNull(); for (let i = 0; i < props.points.length; i += 3) rng.extendXYZ(props.points[i], props.points[i + 1], props.points[i + 2]); params = core_common_1.QParams3d.fromRange(rng); } // 256 here is tile.maximumSize (on non-additive refinement tiles) // If additiveRefinement, set voxelSize to 0 which will cause it draw to with minPixelsPerVoxel, which defaults to 2 // That way, it will draw as if in pixel mode, and voxelScale will still function // Checking across a variety of 10 point clouds, 2 to 4 seems to work well for pixel settings (depending on the // cloud), so 2 is a decent default // (If voxelSize is used normally in this case, it draws different size pixels for different tiles, and since // they can overlap ranges, no good way found to calculate a voxelSize) const voxelSize = tile.additiveRefinement ? 0 : params.rangeDiagonal.maxAbs() / 256; graphic = system.createPointCloud({ positions: props.points, qparams: props.params, colors: props.colors, features: features.toFeatureIndex(), voxelSize, colorFormat: "rgb", }, iModel); graphic = system.createBatch(graphic, core_common_1.PackedFeatureTable.pack(featureTable), batchRange); return { graphic, rtcCenter }; } //# sourceMappingURL=PntsReader.js.map