@itwin/core-backend
Version:
iTwin.js backend components
422 lines • 21.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.
*--------------------------------------------------------------------------------------------*/
import { assert, expect } from "chai";
import { DbResult, Id64 } from "@itwin/core-bentley";
import { ImageSourceFormat, IModel } from "@itwin/core-common";
import { ChannelControl, RenderMaterialElement, SnapshotDb, Texture } from "../../core-backend";
import { IModelTestUtils } from "../IModelTestUtils";
function removeUndefined(assetProps) {
const input = assetProps;
for (const key of Object.keys(input))
if (input[key] === undefined)
delete input[key];
const maps = assetProps.Map;
if (maps) {
for (const mapsKey of Object.keys(maps)) {
const map = maps[mapsKey];
for (const mapKey of Object.keys(map))
if (map[mapKey] === undefined)
delete map[mapKey];
}
}
return assetProps;
}
function defaultBooleans(assetProps) {
const boolKeys = ["HasBaseColor", "HasDiffuse", "HasFinish", "HasReflect", "HasReflectColor", "HasSpecular", "HasSpecularColor"];
for (const boolKey of boolKeys)
if (undefined === assetProps[boolKey])
assetProps[boolKey] = false;
return assetProps;
}
describe("RenderMaterialElement", () => {
let imodel;
let materialNumber = 0;
let textureNumber = 0;
before(() => {
const seedFileName = IModelTestUtils.resolveAssetFile("CompatibilityTestSeed.bim");
const testFileName = IModelTestUtils.prepareOutputFile("ExportGraphics", "ExportGraphicsTest.bim");
imodel = IModelTestUtils.createSnapshotFromSeed(testFileName, seedFileName);
});
after(() => imodel.close());
function test(params, expected) {
const name = `material${++materialNumber}`;
const paletteName = "palette";
const id = RenderMaterialElement.insert(imodel, IModel.dictionaryId, name, { ...params, paletteName });
expect(Id64.isValidId64(id)).to.be.true;
const mat = imodel.elements.getElement(id);
const json = mat.toJSON();
expect(json.jsonProperties?.materialAssets?.renderMaterial).not.to.be.undefined;
const actual = removeUndefined(json.jsonProperties.materialAssets.renderMaterial);
if (expected !== undefined) {
expected = defaultBooleans(expected);
expect(actual).to.deep.equal(expected);
}
return mat;
}
function insertTexture() {
// This is an encoded png containing a 3x3 square with white in top left pixel, blue in middle pixel, and green in
// bottom right pixel. The rest of the square is red.
const pngData = new Uint8Array([
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 3, 0, 0, 0, 3, 8, 2, 0, 0, 0, 217,
74, 34, 232, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 4, 103, 65, 77, 65, 0, 0, 177, 143, 11, 252,
97, 5, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 14, 195, 0, 0, 14, 195, 1, 199, 111, 168, 100, 0, 0, 0, 24, 73, 68, 65,
84, 24, 87, 99, 248, 15, 4, 12, 12, 64, 4, 198, 64, 46, 132, 5, 162, 254, 51, 0, 0, 195, 90, 10, 246, 127, 175, 154, 145, 0,
0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
]);
const name = `texture${++textureNumber}`;
const textureId = Texture.insertTexture(imodel, IModel.dictionaryId, name, ImageSourceFormat.Png, pngData);
expect(Id64.isValidId64(textureId)).to.be.true;
return textureId;
}
describe("read", () => {
it("should be able to convert TextureId to hexadecimal string outside javascript's double precision range of 2^53", async () => {
// LargeNumericRenderMaterialTextureId is a database with a single RenderMaterial whose jsonProperties look something like:
// {"materialAssets":{"renderMaterial":{"Map":{"Diffuse":{"TextureId":9223372036854775807},"Bump":{"TextureId":18446744073709551615},"Finish":{"TextureId":13835058055282163712}}}}}
// 9223372036854775807 is equivalent to 2^63 - 1 which is equivalent to the hexadecimal string 0x7fffffffffffffff.
// 18446744073709551615 is equivalent to 2^64 - 1 which is equivalent to the hexadecimal string 0xffffffffffffffff.
// 13835058055282163712 is equivalent to 2^63 + (2^63 / 2) which is equivalent to the hexadecimal string 0xc000000000000000.
const seedFileName = IModelTestUtils.resolveAssetFile("LargeNumericRenderMaterialTextureId.bim");
const db = SnapshotDb.openFile(seedFileName);
let id;
// eslint-disable-next-line @typescript-eslint/no-deprecated
db.withStatement(`SELECT ECInstanceId from Bis.RenderMaterial`, (stmt) => {
expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);
id = stmt.getRow().id;
expect(stmt.step()).to.equal(DbResult.BE_SQLITE_DONE);
});
const renderMatElement = db.elements.getElement(id).toJSON();
expect(renderMatElement.jsonProperties?.materialAssets?.renderMaterial?.Map?.Diffuse?.TextureId).to.equal("0x7fffffffffffffff");
expect(renderMatElement.jsonProperties?.materialAssets?.renderMaterial?.Map?.Bump?.TextureId).to.equal("0xffffffffffffffff");
expect(renderMatElement.jsonProperties?.materialAssets?.renderMaterial?.Map?.Finish?.TextureId).to.equal("0xc000000000000000");
db.close();
});
});
describe("insert", () => {
it("with default values", () => {
test({}, {});
});
it("should convert TextureIds to hexadecimal string when loading element", () => {
/* eslint-disable @typescript-eslint/naming-convention */
const maps = {
Pattern: { TextureId: 1 },
Normal: { TextureId: 0x0000bc0000001234 }, // TextureId has a non-zero briefcase Id
Bump: { TextureId: 0x000000123456789a }, // TextureId has non-zero digits in the upper 32 bits
Diffuse: { TextureId: 0x0000bc123456789a }, // TextureId has a non-zero briefcase Id and non-zero digits in the upper 32 bits.
Finish: { TextureId: 5 },
GlowColor: { TextureId: 6 },
Reflect: { TextureId: 7 },
Specular: { TextureId: 8 },
TranslucencyColor: { OtherProp2: "test" }, // Should be unchanged, still present.
TransparentColor: { TextureId: 10, OtherProp: 1 }, // OtherProp should be unchanged, still present.
Displacement: { TextureId: "0x1" },
OtherProperty: { OtherProperty: "test" }, // Should be unchanged.
};
/* eslint-enable @typescript-eslint/naming-convention */
const material = test({});
const jsonProps = material.jsonProperties;
assert(jsonProps?.materialAssets?.renderMaterial && jsonProps.materialAssets.renderMaterial.Map === undefined);
jsonProps.materialAssets.renderMaterial.Map = maps;
material.update();
const pathName = imodel.pathName;
imodel.saveChanges();
imodel.close(); // Need to close so we can load the element back in with strings instead of numbers.
imodel = SnapshotDb.openForApplyChangesets(pathName);
imodel.channels.addAllowedChannel(ChannelControl.sharedChannelName);
const mat = imodel.elements.getElement(material.id);
const props = mat.toJSON();
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map).to.not.be.undefined;
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Pattern?.TextureId).to.equal("0x1");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Normal?.TextureId).to.equal("0xbc0000001234");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Bump?.TextureId).to.equal("0x123456789a");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Diffuse?.TextureId).to.equal("0xbc123456789a");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Finish?.TextureId).to.equal("0x5");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.GlowColor?.TextureId).to.equal("0x6");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Reflect?.TextureId).to.equal("0x7");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Specular?.TextureId).to.equal("0x8");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.TranslucencyColor?.TextureId).to.be.undefined;
expect((props.jsonProperties?.materialAssets?.renderMaterial?.Map.TranslucencyColor).OtherProp2).to.equal("test");
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.TransparentColor?.TextureId).to.equal("0xa");
expect((props.jsonProperties?.materialAssets?.renderMaterial?.Map.TransparentColor).OtherProp).to.equal(1);
expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Displacement?.TextureId).to.equal("0x1");
expect(((props.jsonProperties?.materialAssets?.renderMaterial?.Map).OtherProperty).OtherProperty).to.equal("test");
});
it("with custom values", () => {
const params = {
color: [1, 0, 0],
specularColor: [0, 1, 0],
finish: 21,
transmit: 0.5,
diffuse: 0.2,
specular: 0.8,
reflect: 0.5,
reflectColor: [0, 0, 1],
};
/* eslint-disable @typescript-eslint/naming-convention */
test(params, {
HasBaseColor: true, color: params.color,
HasSpecularColor: true, specular_color: params.specularColor,
HasFinish: true, finish: params.finish,
HasTransmit: true, transmit: params.transmit,
HasDiffuse: true, diffuse: params.diffuse,
HasSpecular: true, specular: params.specular,
HasReflect: true, reflect: params.reflect,
HasReflectColor: true, reflect_color: params.reflectColor,
});
params.transmit = undefined;
/* eslint-disable @typescript-eslint/naming-convention */
test(params, {
HasBaseColor: true, color: params.color,
HasSpecularColor: true, specular_color: params.specularColor,
HasFinish: true, finish: params.finish,
HasDiffuse: true, diffuse: params.diffuse,
HasSpecular: true, specular: params.specular,
HasReflect: true, reflect: params.reflect,
HasReflectColor: true, reflect_color: params.reflectColor,
});
params.transmit = 0.0;
/* eslint-disable @typescript-eslint/naming-convention */
test(params, {
HasBaseColor: true, color: params.color,
HasSpecularColor: true, specular_color: params.specularColor,
HasFinish: true, finish: params.finish,
HasTransmit: true, transmit: params.transmit,
HasDiffuse: true, diffuse: params.diffuse,
HasSpecular: true, specular: params.specular,
HasReflect: true, reflect: params.reflect,
HasReflectColor: true, reflect_color: params.reflectColor,
});
});
it("pattern map with default values", () => {
const textureId = insertTexture();
test({
patternMap: {
TextureId: textureId,
},
}, {
Map: {
Pattern: {
TextureId: textureId,
},
},
});
});
it("pattern map with custom values", () => {
const patternMap = {
TextureId: insertTexture(),
pattern_angle: 1,
pattern_u_flip: true,
pattern_flip: true,
pattern_scale: [-1, 2],
pattern_offset: [-2, 1],
pattern_scalemode: 3,
pattern_mapping: 4,
pattern_weight: 0.5,
};
test({ patternMap }, { Map: { Pattern: patternMap } });
});
it("pattern map with constant lod parameters", () => {
const patternMap = {
TextureId: insertTexture(),
pattern_angle: 1,
pattern_u_flip: true,
pattern_flip: true,
pattern_scale: [-1, 2],
pattern_offset: [-2, 1],
pattern_scalemode: 3,
pattern_mapping: 4,
pattern_weight: 0.5,
pattern_useconstantlod: true,
pattern_constantlod_repetitions: 0.333,
pattern_constantlod_offset: [1000, 2000],
pattern_constantlod_mindistanceclamp: 4,
pattern_constantlod_maxdistanceclamp: 256,
};
test({ patternMap }, { Map: { Pattern: patternMap } });
});
it("normal and pattern maps with default values", () => {
const normalId = insertTexture();
const patternId = insertTexture();
expect(normalId).not.to.equal(patternId);
test({
patternMap: { TextureId: patternId },
normalMap: { TextureId: normalId },
}, {
Map: {
Pattern: { TextureId: patternId },
Normal: {
TextureId: normalId,
},
},
});
});
it("merges mapping params from normal and pattern maps", () => {
const normalId = insertTexture();
const patternId = insertTexture();
expect(normalId).not.to.equal(patternId);
const patternMap = {
TextureId: patternId,
pattern_scale: [-1, 2],
pattern_weight: 0.5,
};
const normalMap = {
TextureId: normalId,
pattern_angle: 0.8,
pattern_flip: true,
pattern_weight: 1.5,
};
const sharedProps = {
pattern_scale: patternMap.pattern_scale,
pattern_weight: patternMap.pattern_weight,
pattern_angle: normalMap.pattern_angle,
pattern_flip: normalMap.pattern_flip,
};
test({ patternMap, normalMap }, {
Map: {
Pattern: {
...sharedProps,
TextureId: patternId,
},
Normal: {
...sharedProps,
TextureId: normalId,
},
},
});
});
it("normal map with default values", () => {
const id = insertTexture();
test({ normalMap: { TextureId: id } }, { Map: { Normal: { TextureId: id } } });
});
it("normal map with inverted green channel", () => {
const id = insertTexture();
test({
normalMap: {
TextureId: id,
NormalFlags: 1,
},
}, {
Map: {
Normal: {
NormalFlags: 1,
TextureId: id,
},
},
});
});
it("normal map with scale", () => {
const id = insertTexture();
const scale = 2.5;
test({
normalMap: {
TextureId: id,
scale,
},
}, {
pbr_normal: 2.5,
Map: {
Normal: {
TextureId: id,
},
},
});
});
it("normal map with flags", () => {
const id = insertTexture();
test({
normalMap: {
TextureId: id,
NormalFlags: 0xff00,
},
}, {
Map: {
Normal: {
TextureId: id,
NormalFlags: 0xff00,
},
},
});
});
it("normal map with constant lod parameters", () => {
const normalMap = {
TextureId: insertTexture(),
NormalFlags: 2,
pattern_constantlod_repetitions: 0.333,
pattern_constantlod_offset: [1000, 2000],
pattern_constantlod_mindistanceclamp: 4,
pattern_constantlod_maxdistanceclamp: 256,
};
test({ normalMap }, { Map: { Normal: normalMap } });
});
});
describe("clone", () => {
it("clone maps", async () => {
const textureId = insertTexture();
const unknownTextureId = "0xffffff";
expect(Id64.isValidId64(unknownTextureId)).to.be.true;
expect(imodel.elements.tryGetElementProps(unknownTextureId)).to.be.undefined;
const maps = {
Pattern: { TextureId: textureId },
Normal: { TextureId: textureId },
Bump: { TextureId: textureId },
Diffuse: { TextureId: textureId },
Finish: { TextureId: textureId },
GlowColor: { TextureId: textureId },
Reflect: { TextureId: textureId },
Specular: { TextureId: textureId },
TranslucencyColor: { TextureId: textureId },
TransparentColor: { TextureId: textureId },
Displacement: { TextureId: textureId },
["Unknown"]: { TextureId: textureId },
["InvalidTexture"]: { TextureId: Id64.invalid },
["UnknownTexture"]: { TextureId: unknownTextureId },
["NoTextureId"]: { OtherProp: 1 },
};
const material = test({});
const jsonProps = material.jsonProperties;
assert(jsonProps?.materialAssets?.renderMaterial && jsonProps.materialAssets.renderMaterial.Map === undefined);
jsonProps.materialAssets.renderMaterial.Map = maps;
material.update();
const context = {
findTargetElementId: (sourceId) => {
expect(typeof sourceId, `bad id: ${sourceId}`).to.equal("string");
expect(Id64.isId64(sourceId), `bad id: ${sourceId}`).to.be.true;
return "CLONED";
},
};
const sourceProps = material.toJSON();
const targetProps = structuredClone(sourceProps);
// eslint-disable-next-line @typescript-eslint/dot-notation
await RenderMaterialElement["onCloned"](context, sourceProps, targetProps);
expect(targetProps.jsonProperties?.materialAssets?.renderMaterial?.Map).to.deep.equal({
Pattern: { TextureId: "CLONED" },
Normal: { TextureId: "CLONED" },
Bump: { TextureId: "CLONED" },
Diffuse: { TextureId: "CLONED" },
Finish: { TextureId: "CLONED" },
GlowColor: { TextureId: "CLONED" },
Reflect: { TextureId: "CLONED" },
Specular: { TextureId: "CLONED" },
TranslucencyColor: { TextureId: "CLONED" },
TransparentColor: { TextureId: "CLONED" },
Displacement: { TextureId: "CLONED" },
Unknown: { TextureId: "CLONED" },
InvalidTexture: { TextureId: Id64.invalid },
UnknownTexture: { TextureId: "CLONED" },
NoTextureId: { OtherProp: 1 },
});
jsonProps.materialAssets.renderMaterial.Map = { Pattern: undefined };
// eslint-disable-next-line @typescript-eslint/dot-notation
await RenderMaterialElement["onCloned"](context, sourceProps, targetProps);
// keep the sourceMap the same in targetProps
expect(targetProps.jsonProperties?.materialAssets?.renderMaterial?.Map).to.have.property("Pattern").that.is.undefined;
jsonProps.materialAssets.renderMaterial.Map = { Pattern: null };
// eslint-disable-next-line @typescript-eslint/dot-notation
await RenderMaterialElement["onCloned"](context, sourceProps, targetProps);
// keep the sourceMap the same in targetProps
expect(targetProps.jsonProperties?.materialAssets?.renderMaterial?.Map).to.have.property("Pattern").that.is.null;
});
});
});
//# sourceMappingURL=RenderMaterialElement.test.js.map