@itwin/core-frontend
Version:
iTwin.js frontend components
428 lines • 19.4 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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