UNPKG

@itwin/core-frontend

Version:
428 lines • 19.4 kB
/*--------------------------------------------------------------------------------------------- * 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 */ import { assert, Id64, JsonUtils } from "@itwin/core-bentley"; import { ClipVector, Point2d, Point3d, Range3d, Transform } from "@itwin/core-geometry"; import { ColorDef, Gradient, ImageSource, RenderMaterialParams, RenderTexture, RenderTextureParams, TextureMapping, } from "@itwin/core-common"; import { AuxChannelTable } from "../../common/internal/render/AuxChannelTable"; import { createSurfaceMaterial } from "../../common/internal/render/SurfaceParams"; import { convertFeatureTable, edgeParamsFromImdl, toMaterialParams, toVertexTable } from "../../common/imdl/ParseImdlDocument"; import { VertexIndices } from "../../common/internal/render/VertexIndices"; import { GraphicBranch } from "../../render/GraphicBranch"; import { InstancedGraphicParams } from "../../common/render/InstancedGraphicParams"; import { isGraphicDescription } from "../../common/internal/render/GraphicDescriptionBuilderImpl"; import { _textures } from "../../common/internal/Symbols"; import { createGraphicTemplate } from "../../internal/render/GraphicTemplateImpl"; async function loadNamedTexture(name, namedTex, options) { // Reasons a texture could be embedded in the tile content instead of requested separately from the backend: // - external textures are disabled // - the texture name is not a valid Id64 string // - the texture is below a certain backend-hardcoded size threshold // The bufferViewJson being defined signifies any of the above conditions. In that case, the image content // has been embedded in the tile contents. Otherwise, we will attempt to request the image content separately // from the backend. try { let textureType = RenderTexture.Type.Normal; const isGlyph = JsonUtils.asBool(namedTex.isGlyph); const isTileSection = !isGlyph && JsonUtils.asBool(namedTex.isTileSection); if (isGlyph) textureType = RenderTexture.Type.Glyph; else if (isTileSection) textureType = RenderTexture.Type.TileSection; // We produce unique tile sections for very large (> 8 megapixel) textures, and unique glyph atlases for raster text. // Neither should be cached. const cacheable = !isGlyph && !isTileSection; const ownership = cacheable ? { iModel: options.iModel, key: name } : undefined; const bufferViewId = JsonUtils.asString(namedTex.bufferView); const bufferViewJson = 0 !== bufferViewId.length ? options.document.json.bufferViews[bufferViewId] : undefined; if (undefined !== bufferViewJson) { // presence of bufferViewJson signifies we should read the texture from the tile content const byteOffset = JsonUtils.asInt(bufferViewJson.byteOffset); const byteLength = JsonUtils.asInt(bufferViewJson.byteLength); if (0 === byteLength) return undefined; const texBytes = options.document.binaryData.subarray(byteOffset, byteOffset + byteLength); const format = namedTex.format; const source = new ImageSource(texBytes, format); return await options.system.createTextureFromSource({ source, ownership, type: textureType, transparency: namedTex.transparency }); } // bufferViewJson was undefined, so attempt to request the texture directly from the backend const params = new RenderTextureParams(cacheable ? name : undefined, textureType); return options.system.createTextureFromElement(name, options.iModel, params, namedTex.format); } catch { return undefined; } } async function loadNamedTextures(options) { const result = new Map(); const namedTextures = options.document.json.namedTextures; if (!namedTextures) return result; const promises = new Array(); for (const [name, namedTexture] of Object.entries(namedTextures)) { const texture = options.system.findTexture(name, options.iModel); if (texture) { result.set(name, texture); continue; } else if (namedTexture) { promises.push(loadNamedTexture(name, namedTexture, options).then((tx) => { if (tx) result.set(name, tx); })); } } if (promises.length > 0) await Promise.all(promises); return result; } function constantLodParamPropsFromJson(propsJson) { if (undefined === propsJson) return undefined; const constantLodPops = { repetitions: JsonUtils.asDouble(propsJson.repetitions, 1.0), offset: { x: propsJson.offset ? JsonUtils.asDouble(propsJson.offset[0]) : 0.0, y: propsJson.offset ? JsonUtils.asDouble(propsJson.offset[1]) : 0.0 }, minDistClamp: JsonUtils.asDouble(propsJson.minDistClamp, 1.0), maxDistClamp: JsonUtils.asDouble(propsJson.maxDistClamp, 4096.0 * 1024.0 * 1024.0), }; return constantLodPops; } function textureMappingFromJson(json, options) { if (!json) return undefined; const texture = options.textures.get(JsonUtils.asString(json.name)); if (!texture) return undefined; const paramsJson = json.params; const tf = paramsJson.transform; const paramProps = { textureMat2x3: new TextureMapping.Trans2x3(tf[0][0], tf[0][1], tf[0][2], tf[1][0], tf[1][1], tf[1][2]), textureWeight: JsonUtils.asDouble(paramsJson.weight, 1.0), mapMode: JsonUtils.asInt(paramsJson.mode), worldMapping: JsonUtils.asBool(paramsJson.worldMapping), useConstantLod: JsonUtils.asBool(paramsJson.useConstantLod), constantLodProps: constantLodParamPropsFromJson(paramsJson.constantLodParams), }; const textureMapping = new TextureMapping(texture, new TextureMapping.Params(paramProps)); const normalMapJson = json.normalMapParams; if (normalMapJson) { let normalMap; const normalTexName = JsonUtils.asString(normalMapJson.textureName); if (normalTexName.length === 0 || undefined !== (normalMap = options.textures.get(normalTexName))) { textureMapping.normalMapParams = { normalMap, greenUp: JsonUtils.asBool(normalMapJson.greenUp), scale: JsonUtils.asDouble(normalMapJson.scale, 1), useConstantLod: JsonUtils.asBool(normalMapJson.useConstantLod), }; } } return textureMapping; } function getMaterial(mat, options) { if (typeof mat !== "string") { const args = toMaterialParams(mat); return options.system.createRenderMaterial(args); } if (!options.iModel) { return undefined; } const material = options.system.findMaterial(mat, options.iModel); if (material || !options.document?.json.renderMaterials) return material; const json = options.document.json.renderMaterials[mat]; if (!json) return undefined; function colorDefFromJson(col) { return col ? ColorDef.from(col[0] * 255 + 0.5, col[1] * 255 + 0.5, col[2] * 255 + 0.5) : undefined; } const params = new RenderMaterialParams(mat); params.diffuseColor = colorDefFromJson(json.diffuseColor); if (json.diffuse !== undefined) params.diffuse = JsonUtils.asDouble(json.diffuse); params.specularColor = colorDefFromJson(json.specularColor); if (json.specular !== undefined) params.specular = JsonUtils.asDouble(json.specular); params.reflectColor = colorDefFromJson(json.reflectColor); if (json.reflect !== undefined) params.reflect = JsonUtils.asDouble(json.reflect); if (json.specularExponent !== undefined) params.specularExponent = json.specularExponent; if (undefined !== json.transparency) params.alpha = 1.0 - json.transparency; params.refract = JsonUtils.asDouble(json.refract); params.shadows = JsonUtils.asBool(json.shadows); params.ambient = JsonUtils.asDouble(json.ambient); if (undefined !== json.textureMapping) params.textureMapping = textureMappingFromJson(json.textureMapping.texture, options); return options.system.createRenderMaterial({ diffuse: { color: params.diffuseColor, weight: params.diffuse }, specular: { color: params.specularColor, weight: params.specular, exponent: params.specularExponent }, alpha: params.alpha, textureMapping: params.textureMapping }); ; } function getModifiers(primitive) { const mod = primitive.modifier; switch (mod?.type) { case "instances": return { instances: InstancedGraphicParams.fromProps(mod) }; case "viewIndependentOrigin": return { viOrigin: Point3d.fromJSON(mod.origin), }; default: return {}; } } function createPrimitiveGeometry(primitive, options, viOrigin) { switch (primitive.type) { case "point": return options.system.createPointStringGeometry({ ...primitive.params, vertices: toVertexTable(primitive.params.vertices), indices: new VertexIndices(primitive.params.indices), }, viOrigin); case "polyline": return options.system.createPolylineGeometry({ ...primitive.params, vertices: toVertexTable(primitive.params.vertices), polyline: { ...primitive.params.polyline, indices: new VertexIndices(primitive.params.polyline.indices), prevIndices: new VertexIndices(primitive.params.polyline.prevIndices), }, }, viOrigin); case "mesh": { const surf = primitive.params.surface; let material; if (surf.material) { if (!surf.material.isAtlas) material = createSurfaceMaterial(getMaterial(surf.material.material, options)); else material = surf.material; } let textureMapping; if (surf.textureMapping) { let texture; if (typeof surf.textureMapping.texture === "string") { texture = options.textures.get(surf.textureMapping.texture); } else { const gradient = Gradient.Symb.fromJSON(surf.textureMapping.texture); texture = options.system.getGradientTexture(gradient, options.iModel); } if (texture) textureMapping = { texture, alwaysDisplayed: surf.textureMapping.alwaysDisplayed }; } return options.system.createMeshGeometry({ ...primitive.params, edges: primitive.params.edges ? edgeParamsFromImdl(primitive.params.edges) : undefined, vertices: toVertexTable(primitive.params.vertices), auxChannels: primitive.params.auxChannels ? AuxChannelTable.fromJSON(primitive.params.auxChannels) : undefined, surface: { ...primitive.params.surface, material, textureMapping, indices: new VertexIndices(primitive.params.surface.indices), }, tileData: options.tileData, }, viOrigin); } } } function createPrimitiveGraphic(primitive, options) { const mods = getModifiers(primitive); const geometry = createPrimitiveGeometry(primitive, options, mods.viOrigin); return geometry ? options.system.createRenderGraphic(geometry, mods.instances) : undefined; } function createPatternGeometries(primitives, options) { const geometries = []; for (const primitive of primitives) { const geometry = createPrimitiveGeometry(primitive, options, undefined); if (geometry) geometries.push(geometry); } return geometries; } function createPatternGraphic(params, options) { const geometries = options.patterns.get(params.symbolName); if (!geometries || geometries.length === 0) return undefined; const clip = ClipVector.fromJSON(params.clip); const clipVolume = clip?.isValid ? options.system.createClipVolume(clip) : undefined; if (!clipVolume) return undefined; const viewIndependentOrigin = params.viewIndependentOrigin ? Point3d.fromJSON(params.viewIndependentOrigin) : undefined; const pattern = options.system.createAreaPattern({ xyOffsets: params.xyOffsets, featureId: params.featureId, orgTransform: Transform.fromJSON(params.orgTransform), origin: Point2d.fromJSON(params.origin), scale: params.scale, spacing: Point2d.fromJSON(params.spacing), patternToModel: Transform.fromJSON(params.modelTransform), range: Range3d.fromJSON(params.range), symbolTranslation: Point3d.fromJSON(params.symbolTranslation), viewIndependentOrigin, }); if (!pattern) return undefined; const branch = new GraphicBranch(true); for (const geometry of geometries) { const graphic = options.system.createRenderGraphic(geometry, pattern); if (graphic) branch.add(graphic); } return branch.isEmpty ? undefined : options.system.createGraphicBranch(branch, Transform.createIdentity(), { clipVolume }); } function createNodeGraphics(node, options) { if (undefined === node.groupId) return createPrimitivesNodeGraphics(node, options); const graphics = []; for (const child of node.nodes) { graphics.push(...createPrimitivesNodeGraphics(child, options)); } if (graphics.length === 0) return graphics; const branch = new GraphicBranch(true); branch.groupNodeId = node.groupId; branch.entries.push(...graphics); return [options.system.createBranch(branch, Transform.createIdentity())]; } function createPrimitivesNodeGraphics(node, options) { let graphics = []; for (const primitive of node.primitives) { const graphic = primitive.type === "pattern" ? createPatternGraphic(primitive.params, options) : createPrimitiveGraphic(primitive, options); if (graphic) graphics.push(graphic); } if (!graphics.length) return graphics; if (undefined !== node.layerId) { const layerGraphic = 1 === graphics.length ? graphics[0] : options.system.createGraphicList(graphics); graphics = [options.system.createGraphicLayer(layerGraphic, node.layerId)]; } else if (undefined !== node.animationNodeId) { const branch = new GraphicBranch(true); branch.animationId = node.animationId; branch.animationNodeId = node.animationNodeId; branch.entries.push(...graphics); graphics = [options.system.createBranch(branch, Transform.createIdentity())]; } return graphics; } export async function decodeImdlGraphics(options) { const textures = await loadNamedTextures(options); if (options.isCanceled && options.isCanceled()) return undefined; const patterns = new Map(); const graphicsOptions = { ...options, textures, patterns }; graphicsOptions.tileData = options.tileData; for (const [name, primitives] of options.document.patterns) patterns.set(name, createPatternGeometries(primitives, graphicsOptions)); const system = options.system; const graphics = []; for (const node of options.document.nodes) { graphics.push(...createNodeGraphics(node, graphicsOptions)); } switch (graphics.length) { case 0: return undefined; case 1: return graphics[0]; default: return system.createGraphicList(graphics); } } function remapGraphicDescription(descr, context) { if (descr.remapContext === context) { // Already remapped. return; } else if (descr.remapContext) { throw new Error("You cannot create a graphic from a GraphicDescription using two different contexts"); } descr.remapContext = context; const batch = descr.batch; if (!batch) { return; } if (Id64.isTransient(batch.modelId)) { const modelLocalId = context.remapTransientLocalId(Id64.getLocalId(batch.modelId)); batch.modelId = Id64.fromLocalAndBriefcaseIds(modelLocalId, 0xffffff); } const data = batch.featureTable.data; const remapId = (offset) => { const hi = data[offset + 1]; if (hi >= 0xffffff00) { const hi8 = hi & 0xff; const lo = data[offset]; // Avoid bitwise operators because they truncate at 32 bits (we need 40) and are stupid about the sign bit. const sourceLocalId = lo + (hi8 * 0xffffffff); const localId = context.remapTransientLocalId(sourceLocalId); data[offset] = localId & 0xffffffff; data[offset + 1] = 0xffffff00 + hi8; } }; const subCatsOffset = 3 * batch.featureTable.numFeatures; for (let i = 0; i < subCatsOffset; i += 3) { remapId(i); } const subCatsEnd = undefined !== batch.featureTable.numSubCategories ? subCatsOffset + 2 * batch.featureTable.numSubCategories : data.length; for (let i = subCatsOffset; i < subCatsEnd; i++) { remapId(i); } if (undefined !== batch.featureTable.numSubCategories) { for (let i = subCatsEnd; i < data.length; i++) { remapId(i + 1); } } } export function createGraphicTemplateFromDescription(descr, context, system) { if (!isGraphicDescription(descr)) { throw new Error("Invalid GraphicDescription"); } remapGraphicDescription(descr, context); const graphicsOptions = { system, textures: context[_textures], patterns: new Map(), }; const geometry = []; for (const primitive of descr.primitives) { const mods = getModifiers(primitive); // GraphicDescriptionBuilder providers no way to include instances in a GraphicDescription. assert(undefined === mods?.instances); const geom = createPrimitiveGeometry(primitive, graphicsOptions, mods.viOrigin); if (geom) { geometry.push(geom); } } let batch; if (descr.batch) { const featureTable = convertFeatureTable(descr.batch.featureTable, descr.batch.modelId); batch = { options: { ...descr.batch }, range: Range3d.fromJSON(descr.batch.range), featureTable, }; } let branch; if (descr.translation) { branch = { transform: Transform.createTranslation(Point3d.fromJSON(descr.translation)) }; } return createGraphicTemplate({ nodes: [{ geometry }], batch, branch, noDispose: true, }); } export function createGraphicFromDescription(descr, context, system) { const template = createGraphicTemplateFromDescription(descr, context, system); return system.createGraphicFromTemplate({ template }); } //# sourceMappingURL=ImdlGraphicsCreator.js.map