WebGL-iTwin.js】实战篇(二):用nodejs代码详解iTwin.js中PhysicalObject生成方法

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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔法战胜魔法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值