PhysicalObject
即真实存在的物理对象,比如:电脑、桌子等。在webgl中,我们人眼能看到的模型都是PhysicalObject,由多种几何图元类型构成,如:网格、实体、曲面、曲线,点云等。
其中带索引的多边形网格(indexed mesh)是常用的构造模型的方法。其数据结构在我的上一篇博客有具体解释。
如何生成PhysicalObject
我们用到了iTwin.js一下几个npm包:
- @itwin/core-backend
- @itwin/core-common
- @itwin/core-geometry
1. 首先,需要初始化itwin,不然无法使用其itwin.js的API
const imHostConfig = new IModelHostConfiguration()
await IModelHost.startup(imHostConfig)
2. 创建一个空的模型,得到的db是支持读写模式的一个空的.bim模型
const db = StandaloneDb.createEmpty(filePath, { rootSubject: { name: "root" } })
3. 在空的模型里创建根节点、model、code、图层等,后面创建模型需要
db.codeSpecs.insert("Generic:PhysicalObject", CodeScopeSpec.Type.Model)
const subjectCode = Subject.createCode(db, IModel.rootSubjectId, "Subject")
const subjectId = Subject.insert(db, subjectCode.scope, subjectCode.value)
const physicalPartitionCode = PhysicalPartition.createCode(db, subjectId, "3DModels")
const physicalModelId = PhysicalModel.insert(db, physicalPartitionCode.scope, physicalPartitionCode.value)
const definitionPartitionCode = DefinitionPartition.createCode(db, subjectId, "Definitions")
const definitionModelId = DefinitionModel.insert(db, definitionPartitionCode.scope, definitionPartitionCode.value)
const modelSelectorCode = ModelSelector.createCode(db, definitionModelId, "Physical Models")
const modelSelectorId = ModelSelector.insert(db, definitionModelId, modelSelectorCode.value, [physicalModelId])
const categoryId = SpatialCategory.insert(db, definitionModelId, "Default", { color: ColorDef.white })
const specId = db.codeSpecs.getByName("Generic:PhysicalObject").id
const scopeId = physicalModelId.toString()
const codeTmp = new Code({ spec: specId, scope: scopeId })
4. 在db里插入材质,得到材质图层id,后面往模型上贴图需要使用
const imageBuffer = fs.readFileSync(imagePath)
const img = {
data: imageBuffer,
name: 'name',
label: 'label',
code: type,
type: undefined
}
const textureId = Texture.insertTexture(db, definitionModelId, img.label, img.type, img.data)
const patternMap = {
TextureId: textureId,
pattern_scale: [1, 1],
}
const materialId = RenderMaterialElement.insert(db, definitionModelId, img.name, { patternMap })
const subCategoryId = SubCategory.insert(db, categoryId, img.code, { material: materialId })
5. 画出模型,生成几何流
// 定义一个三角网格数据
const structure = {
points: [[0, 0, 0], [10, 0, 0], [10, 0, 10]],
pointIndices: [0, 1, 2],
params: [[0, 0], [1, 0], [1, 1]],
paramIndices: [0, 1, 2],
normals: [[0, 0, 1], [0, 0, 1], [0, 0, 1]],
normalIndices: [0, 1, 2]
}
const geometryStreamBuilder = new GeometryStreamBuilder()
const indexedPolyface = IndexedPolyface.create()
for (let i = 0; i < structure.pointIndices.length; i++) {
const point = structure.points[structure.pointIndices[i]]
indexedPolyface.addPoint(new Point3d(point[0], point[1], point[2]))
indexedPolyface.addPointIndex(i, true)
const param = structure.params[structure.paramIndices[i]]
indexedPolyface.addParam(new Point2d(param[0], param[1]))
indexedPolyface.addParamIndex(i)
const normal = structure.normals[structure.normalIndices[i]]
indexedPolyface.addNormal(new Vector3d(normal[0], normal[1], normal[2]))
indexedPolyface.addNormalIndex(i)
}
indexedPolyface.terminateFacet()
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(indexedPolyface)
const geometryStream = geometryStreamBuilder.geometryStream
6. 将几何流,插入进db里,生成PhysicalObject
function insertElement (db, props) {
const buildingProps = {
classFullName: PhysicalObject.classFullName,
model: props.physicalModelId,
category: props.categoryId
}
buildingProps.userLabel = props.userLabel
buildingProps.geom = props.geom
buildingProps.code = props.code
const elementId = db.elements.insertElement(buildingProps)
return elementId
}
7. 收尾工作,创建模型可视范围、显示样式、视图等,最后保存变更并关闭模型
function finish (db, props) {
const projectExtents = db.computeProjectExtents()
const extents = projectExtents.extents
const viewFlagProps = {
cameraLights: true,
sourceLights: true,
visibleEdges: false,
renderMode: RenderMode.SmoothShade
}
const viewFlags = ViewFlags.fromJSON(viewFlagProps)
const displayStyle = {
name: 'Default',
lights: {
solar: { direction: [-0.9833878378071199, -0.18098510351728977, 0.013883542698953828], intensity: 1.05 },
ambient: { intensity: 0.25 },
hemisphere: {
upperColor: { r: 100, g: 100, b: 100 },
intensity: 0.5,
},
portrait: { intensity: 0 },
},
viewFlags,
backgroundColor: ColorDef.create('#E1FFFF')
}
const displayStyle3dCode = DisplayStyle3d.createCode(db, props.definitionModelId, displayStyle.name)
const displayStyle3dId = DisplayStyle3d.insert(db, props.definitionModelId, displayStyle3dCode.value, displayStyle)
const spatialCategorySelectorCode = CategorySelector.createCode(db, props.definitionModelId, "Spatial Categories")
const spatialCategorySelectorId = CategorySelector.insert(db, props.definitionModelId, spatialCategorySelectorCode.value, [props.categoryId])
const viewCode = SpatialViewDefinition.createCode(db, props.definitionModelId, 'Default View')
const viewId = SpatialViewDefinition.insertWithCamera(db, props.definitionModelId, viewCode.value, props.modelSelectorId, spatialCategorySelectorId, displayStyle3dId, extents)
db.views.setDefaultViewId(viewId)
db.updateProjectExtents(extents)
db.saveChanges()
db.close()
}
8. 最后看一下效果
附上完整代码
nodejs脚本实现:
const { IModelHost, SubCategory, IModelJsFs, Subject, PhysicalPartition, PhysicalModel, DefinitionPartition, DefinitionModel, ModelSelector,
SpatialCategory, CategorySelector, DisplayStyle3d, SpatialViewDefinition, RenderMaterialElement, StandaloneDb, IModelHostConfiguration, PhysicalObject, Texture } = require('@itwin/core-backend');
const { CodeScopeSpec, IModel, ColorDef, Code, ViewFlags, RenderMode, GeometryStreamBuilder } = require('@itwin/core-common');
const { Matrix3d, Transform, Vector3d, Arc3d, Range3d, Point3d, IndexedPolyface, Sphere, Box, Cone, TorusPipe, AngleSweep, LineString3d, PointString3d, Point2d } = require('@itwin/core-geometry')
const math = require('mathjs')
const fs = require('fs')
//立方体mesh数据
const meshData = {
// 顶点数据
point: [
[0.0, 1.0, 0.0], //点A
[0.0, 0.0, 0.0], //点B
[1.0, 0.0, 0.0], //点C
[1.0, 1.0, 0.0], //点D
[0.0, 1.0, 1.0], //点E
[0.0, 0.0, 1.0], //点F
[1.0, 0.0, 1.0], //点G
[1.0, 1.0, 1.0] //点H
],
//顶点索引
pointIndex: [
[0, 1, 2, 3], //A-B-C-D 上面
[4, 5, 6, 7], //E-F-G-H 下面
[2, 3, 7, 6], //C-D-H-G 前面
[1, 0, 4, 5], //B-A-E-F 后面
[2, 1, 5, 6], //C-B-F-G 左面
[3, 0, 4, 7] //D-A-E-H 右面
]
}
/**
* 画图例子
*/
async function draw () {
// 初始化并启动imodelhost
await init()
//创建一个iModelDb
const db = createStandaloneDb("./demo.bim")
//获取db中必要的参数
const params = getDbParams(db)
const props = {
physicalModelId: params.physicalModelId,
categoryId: params.categoryId,
userLabel: undefined,
code: undefined,
geom: undefined
}
const elemets = []
const drawTypes = ['mesh', 'sphere', 'box', 'cone', 'cylinder', 'torusPipe', 'line', 'point', 'triangulated']
for (let i = 0; i < drawTypes.length; i++) {
params.codeTmp.value = drawTypes[i]
props.userLabel = drawTypes[i]
props.code = params.codeTmp
switch (drawTypes[i]) {
case 'mesh':
props.geom = drawMesh(db, params.definitionModelId, params.categoryId)
break;
case 'sphere':
const center = new Point3d(2, 0.5, 0.5)
const radius = 0.5
props.geom = drawSphere(db, params.categoryId, center, radius, drawTypes[i])
break;
case 'box':
const range = Range3d.create(...[new Point3d(3, 0, 0), new Point3d(4, 1, 1)])
props.geom = drawBox(db, params.categoryId, range, drawTypes[i])
break;
case 'cone':
const coneCenterA = new Point3d(5, 0.5, 0)
const coneCenterB = new Point3d(5, 0.5, 1)
const coneRadiusA = 0.5
const coneRadiusB = 0.25
props.geom = drawCone(db, params.categoryId, coneCenterA, coneCenterB, coneRadiusA, coneRadiusB, drawTypes[i])
break;
case 'cylinder':
const cylinderCenterA = new Point3d(6.5, 0.5, 0)
const cylinderCenterB = new Point3d(6.5, 0.5, 1)
const cylinderRadiusA = 0.5
const cylinderRadiusB = 0.5
props.geom = drawCone(db, params.categoryId, cylinderCenterA, cylinderCenterB, cylinderRadiusA, cylinderRadiusB, drawTypes[i])
break;
case 'torusPipe':
const PipeCenter = new Point3d(7.5, 0.5, 0)
const vector0 = new Vector3d(0, 1, 0)
const vector90 = new Vector3d(0, 0, 1)
const sweep = AngleSweep.createStartSweepDegrees(0, 180)
const arc = Arc3d.create(PipeCenter, vector0, vector90, sweep)
const minorRadius = 0.2
props.geom = drawPipe(db, params.categoryId, arc, minorRadius, drawTypes[i])
break;
case 'line':
const linePoints = [new Point3d(10, 0, 0), new Point3d(10.5, 2, 1), new Point3d(11, 5, -0.5)]
const index = [0, 2, 1]
props.geom = drawLine(db, params.categoryId, linePoints, index, drawTypes[i])
break;
case 'point':
const points = [new Point3d(12, 1, 1), new Point3d(13, 3, 3)]
props.geom = drawPoint(db, params.categoryId, points, drawTypes[i])
break;
case 'triangulated':
const subCategoryId = createTexture('./data.jpg', db, params.definitionModelId, params.categoryId, drawTypes[i])
props.geom = drawTriangulated(subCategoryId)
break;
}
const elementId = insertElement(db, props)
elemets.push(elementId)
}
finish(db, params)
console.log(`INSERT SUCCESS ElementIds: ${elemets}`)
}
async function init () {
const imHostConfig = new IModelHostConfiguration()
await IModelHost.startup(imHostConfig)
}
function createStandaloneDb (filePath) {
if (IModelJsFs.existsSync(filePath)) {
IModelJsFs.unlinkSync(filePath)
}
if (IModelJsFs.existsSync(`${filePath}.Tiles`)) {
IModelJsFs.unlinkSync(`${filePath}.Tiles`)
}
const db = StandaloneDb.createEmpty(filePath, { rootSubject: { name: "root" } })
return db
}
function getDbParams (db) {
db.codeSpecs.insert("Generic:PhysicalObject", CodeScopeSpec.Type.Model)
const subjectCode = Subject.createCode(db, IModel.rootSubjectId, "Subject")
const subjectId = Subject.insert(db, subjectCode.scope, subjectCode.value)
const physicalPartitionCode = PhysicalPartition.createCode(db, subjectId, "3DModels")
const physicalModelId = PhysicalModel.insert(db, physicalPartitionCode.scope, physicalPartitionCode.value)
const definitionPartitionCode = DefinitionPartition.createCode(db, subjectId, "Definitions")
const definitionModelId = DefinitionModel.insert(db, definitionPartitionCode.scope, definitionPartitionCode.value)
const modelSelectorCode = ModelSelector.createCode(db, definitionModelId, "Physical Models")
const modelSelectorId = ModelSelector.insert(db, definitionModelId, modelSelectorCode.value, [physicalModelId])
const categoryId = SpatialCategory.insert(db, definitionModelId, "Default", { color: ColorDef.white })
const specId = db.codeSpecs.getByName("Generic:PhysicalObject").id
const scopeId = physicalModelId.toString()
const codeTmp = new Code({ spec: specId, scope: scopeId })
return {
physicalModelId,
definitionModelId,
modelSelectorId,
categoryId,
codeTmp
}
}
function drawMesh (db, definitionModelId, categoryId) {
const geometryStreamBuilder = new GeometryStreamBuilder()
//1.画立方体
for (let i = 0; i < meshData.pointIndex.length; i++) {
//indexed mesh
const indexedPolyface = IndexedPolyface.create()
for (let j = 0; j < meshData.pointIndex[i].length; j++) {
//添加顶点
let point = meshData.point[meshData.pointIndex[i][j]]
indexedPolyface.addPoint(new Point3d(point[0], point[1], point[2]))
//添加顶点索引
indexedPolyface.addPointIndex(j, true)
}
indexedPolyface.terminateFacet()
//着色
const props = {
material: undefined,
color: undefined,
transp: 0.5
}
const colorMode = ['material', 'category']
switch (colorMode[1]) {
//漫反射颜色(将颜色以材质方式的贴上去)
case 'material':
const params = {
color: //颜色: 百分比格式的RGBA(100%,0%,0%,1.0)
[
math.fix(math.random(0, 1), 3), //R
math.fix(math.random(0, 1), 3), //G
math.fix(math.random(0, 1), 3), //B
1.0 //alpha
],
transmit: 0, //透明度:0为不透明, 1为完全透明
diffuse: 0.9 //颜色反射率:0最暗淡,1最鲜艳
}
const materialId = RenderMaterialElement.insert(db, definitionModelId, i, params)
props.material = materialId
break;
//常规颜色(直接画上去)
case 'category':
const color = ColorDef.from(math.randomInt(0, 255), math.randomInt(0, 255), math.randomInt(0, 255))
props.color = color.toJSON()
break;
}
const subCategoryId = SubCategory.insert(db, categoryId, i, props)
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(indexedPolyface)
}
return geometryStreamBuilder.geometryStream
}
function drawSphere (db, categoryId, center, radius, type) {
const geometryStreamBuilder = new GeometryStreamBuilder()
const sphere = Sphere.createCenterRadius(center, radius)
sphere.capped = true
const color = ColorDef.from(math.randomInt(0, 255), math.randomInt(0, 255), math.randomInt(0, 255))
const subCategoryId = SubCategory.insert(db, categoryId, type, { color: color.toJSON(), transp: 0 })
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(sphere)
return geometryStreamBuilder.geometryStream
}
function drawBox (db, categoryId, range, type) {
const geometryStreamBuilder = new GeometryStreamBuilder()
const box = Box.createRange(range, true)
const color = ColorDef.from(math.randomInt(0, 255), math.randomInt(0, 255), math.randomInt(0, 255))
const subCategoryId = SubCategory.insert(db, categoryId, type, { color: color.toJSON(), transp: 0 })
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(box)
return geometryStreamBuilder.geometryStream
}
function drawCone (db, categoryId, centerA, centerB, radiusA, radiusB, type) {
const geometryStreamBuilder = new GeometryStreamBuilder()
const cone = Cone.createAxisPoints(centerA, centerB, radiusA, radiusB, true)
const matrix = Matrix3d.create90DegreeRotationAroundAxis(0)
const transform = Transform.createOriginAndMatrix(new Point3d(0, 0, 0), matrix)
cone.tryTransformInPlace(transform)
const color = ColorDef.from(math.randomInt(0, 255), math.randomInt(0, 255), math.randomInt(0, 255))
const subCategoryId = SubCategory.insert(db, categoryId, type, { color: color.toJSON(), transp: 0 })
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(cone)
return geometryStreamBuilder.geometryStream
}
function drawPipe (db, categoryId, arc, minorRadius, type) {
const geometryStreamBuilder = new GeometryStreamBuilder()
const pipe = TorusPipe.createAlongArc(arc, minorRadius, true)
const color = ColorDef.from(math.randomInt(0, 255), math.randomInt(0, 255), math.randomInt(0, 255))
const subCategoryId = SubCategory.insert(db, categoryId, type, { color: color.toJSON(), transp: 0 })
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(pipe)
return geometryStreamBuilder.geometryStream
}
function drawLine (db, categoryId, points, index, type) {
const geometryStreamBuilder = new GeometryStreamBuilder()
const line = LineString3d.createIndexedPoints(points, index, false)
const color = ColorDef.from(math.randomInt(0, 255), math.randomInt(0, 255), math.randomInt(0, 255))
const subCategoryId = SubCategory.insert(db, categoryId, type, { color: color.toJSON(), transp: 0 })
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(line)
return geometryStreamBuilder.geometryStream
}
function drawPoint (db, categoryId, points, type) {
const geometryStreamBuilder = new GeometryStreamBuilder()
const point = PointString3d.createPoints(points)
const color = ColorDef.from(math.randomInt(0, 255), math.randomInt(0, 255), math.randomInt(0, 255))
const subCategoryId = SubCategory.insert(db, categoryId, type, { color: color.toJSON(), transp: 0 })
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(point)
return geometryStreamBuilder.geometryStream
}
function drawTriangulated (subCategoryId) {
const structure = {
points: [[0, 0, 0], [10, 0, 0], [10, 0, 10]],
pointIndices: [0, 1, 2],
params: [[0, 0], [1, 0], [1, 1]],
paramIndices: [0, 1, 2],
normals: [[0, 0, 1], [0, 0, 1], [0, 0, 1]],
normalIndices: [0, 1, 2]
}
const geometryStreamBuilder = new GeometryStreamBuilder()
const indexedPolyface = IndexedPolyface.create()
for (let i = 0; i < structure.pointIndices.length; i++) {
const point = structure.points[structure.pointIndices[i]]
indexedPolyface.addPoint(new Point3d(point[0], point[1], point[2]))
indexedPolyface.addPointIndex(i, true)
const param = structure.params[structure.paramIndices[i]]
indexedPolyface.addParam(new Point2d(param[0], param[1]))
indexedPolyface.addParamIndex(i)
const normal = structure.normals[structure.normalIndices[i]]
indexedPolyface.addNormal(new Vector3d(normal[0], normal[1], normal[2]))
indexedPolyface.addNormalIndex(i)
}
indexedPolyface.terminateFacet()
geometryStreamBuilder.appendSubCategoryChange(subCategoryId)
geometryStreamBuilder.appendGeometry(indexedPolyface)
return geometryStreamBuilder.geometryStream
}
function finish (db, props) {
const projectExtents = db.computeProjectExtents()
const extents = projectExtents.extents
const viewFlagProps = {
cameraLights: true,
sourceLights: true,
visibleEdges: false,
renderMode: RenderMode.SmoothShade
}
const viewFlags = ViewFlags.fromJSON(viewFlagProps)
const displayStyle = {
name: 'Default',
lights: {
solar: { direction: [-0.9833878378071199, -0.18098510351728977, 0.013883542698953828], intensity: 1.05 },
ambient: { intensity: 0.25 },
hemisphere: {
upperColor: { r: 100, g: 100, b: 100 },
intensity: 0.5,
},
portrait: { intensity: 0 },
},
viewFlags,
backgroundColor: ColorDef.create('#E1FFFF')
}
const displayStyle3dCode = DisplayStyle3d.createCode(db, props.definitionModelId, displayStyle.name)
const displayStyle3dId = DisplayStyle3d.insert(db, props.definitionModelId, displayStyle3dCode.value, displayStyle)
const spatialCategorySelectorCode = CategorySelector.createCode(db, props.definitionModelId, "Spatial Categories")
const spatialCategorySelectorId = CategorySelector.insert(db, props.definitionModelId, spatialCategorySelectorCode.value, [props.categoryId])
const viewCode = SpatialViewDefinition.createCode(db, props.definitionModelId, 'Default View')
const viewId = SpatialViewDefinition.insertWithCamera(db, props.definitionModelId, viewCode.value, props.modelSelectorId, spatialCategorySelectorId, displayStyle3dId, extents)
db.views.setDefaultViewId(viewId)
db.updateProjectExtents(extents)
db.saveChanges()
db.close()
}
function insertElement (db, props) {
const buildingProps = {
classFullName: PhysicalObject.classFullName,
model: props.physicalModelId,
category: props.categoryId
}
buildingProps.userLabel = props.userLabel
buildingProps.geom = props.geom
buildingProps.code = props.code
const elementId = db.elements.insertElement(buildingProps)
return elementId
}
function createTexture (imagePath, db, definitionModelId, categoryId, type) {
const imageBuffer = fs.readFileSync(imagePath)
const img = {
data: imageBuffer,
name: 'name',
label: 'label',
code: type,
type: undefined
}
const textureId = Texture.insertTexture(db, definitionModelId, img.label, img.type, img.data)
const patternMap = {
TextureId: textureId,
pattern_scale: [1, 1],
}
const materialId = RenderMaterialElement.insert(db, definitionModelId, img.name, { patternMap })
const subCategoryId = SubCategory.insert(db, categoryId, img.code, { material: materialId })
return subCategoryId
}
draw()