Skip to content

OBJ格式

示例

示例源码
tsx
import React, { useCallback, useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import * as THREE from 'three'
import { OBJLoader } from 'OBJLoader'
import { MTLLoader } from 'MTLLoader'
import { MbMap, MbTiandituLayer } from '@mapbox-react/core'

const App = () => {
  const [mapCenter] = useState([116.3466, 39.8704])
  const [zoom, setZoom] = useState(14)
  const [pitch, setPitch] = useState(45)
  const mapInst = useRef<any>()

  let map: any
  const altitude = 0

  const mapCreated = (mapbox: any) => {
    map = mapbox
  }
  const add3DLayer = () => {
    loaderModels()
  }
  const loaderModels = () => {
    const modelAsMercatorCoordinate =
      map.mapboxgl.MercatorCoordinate.fromLngLat(mapCenter, altitude)

    // transformation parameters to position, rotate and scale the 3D model onto the map
    const modelTransform = {
      translateX: modelAsMercatorCoordinate.x,
      translateY: modelAsMercatorCoordinate.y,
      translateZ: modelAsMercatorCoordinate.z,
      /* Since our 3D model is in real world meters, a scale transform needs to be
       * applied since the CustomLayerInterface expects units in MercatorCoordinates.
       */
      scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits(),
    }

    const mtlLoader = new MTLLoader(new THREE.LoadingManager())

    mtlLoader.load(
      `https://mapbox-web.github.io/mapbox-react/obj/windmill_2/windmill-fixed.mtl`,
      (materials) => {
        materials.preload()
        const objLoader = new OBJLoader(new THREE.LoadingManager())
        objLoader.setMaterials(materials)

        objLoader.load(
          `https://mapbox-web.github.io/mapbox-react/obj/windmill_2/windmill.obj`,
          (obj) => {
            obj.traverse((child) => {
              if (child.isMesh) child.geometry.computeVertexNormals()
            })
            const meshes = obj
            meshes.scale.multiplyScalar(0.15)

            const otherObj = meshes.children[0].clone()
            otherObj.scale.multiplyScalar(0.2)

            addThreeLayer(
              modelTransform,
              modelAsMercatorCoordinate,
              meshes,
              otherObj
            )
          },
          null,
          null,
          null,
          false
        )
      }
    )
  }
  const addThreeLayer = (
    modelTransform,
    modelAsMercatorCoordinate,
    obj1,
    obj2
  ) => {
    const threeLayer = {
      id: '3d-model',
      type: 'custom',
      renderingMode: '3d',
      camera: null as unknown as THREE.PerspectiveCamera,
      scene: null as unknown as THREE.Scene,
      renderer: null as unknown as THREE.WebGLRenderer,
      onAdd(map, gl) {
        this.camera = new THREE.PerspectiveCamera()
        this.scene = new THREE.Scene()

        const [x, y] = getThreePoint(
          modelAsMercatorCoordinate,
          mapCenter.map((c) => (c -= 0.0015))
        )
        obj1.translateZ(300)

        obj2.translateX(x)
        obj2.translateY(-y)
        obj2.rotation.x = Math.PI / 2
        obj1.rotation.x = Math.PI / 2
        this.scene.add(obj1)
        this.scene.add(obj2)

        {
          const skyColor = 0xb1e1ff // light blue
          const groundColor = 0xb97a20 // brownish orange
          const intensity = 3
          const light = new THREE.HemisphereLight(
            skyColor,
            groundColor,
            intensity
          )
          this.scene.add(light)
        }

        // use the Mapbox GL JS map canvas for three.js
        this.renderer = new THREE.WebGLRenderer({
          canvas: map.getCanvas(),
          context: gl,
          antialias: true,
        })

        this.renderer.autoClear = false
      },
      render(gl, matrix) {
        const m = new THREE.Matrix4().fromArray(matrix)
        const l = new THREE.Matrix4()
          .makeTranslation(
            modelTransform.translateX,
            modelTransform.translateY,
            modelTransform.translateZ
          )
          .scale(
            new THREE.Vector3(
              modelTransform.scale,
              -modelTransform.scale,
              modelTransform.scale
            )
          )

        this.camera.projectionMatrix = m.clone().multiply(l)
        this.camera.matrixWorldInverse = new THREE.Matrix4()
        this.renderer.resetState()

        this.renderer.render(this.scene, this.camera)

        // this.map.triggerRepaint()
      },
    }
    map.addLayer(threeLayer as CustomLayerInterface)
  }
  const getThreePoint = (modelAsMercatorCoordinate, anotherPoint) => {
    const anotherPointModel = map.mapboxgl.MercatorCoordinate.fromLngLat(
      anotherPoint,
      altitude
    )
    const x =
      (anotherPointModel.x - modelAsMercatorCoordinate.x) /
      modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
    const y =
      (anotherPointModel.y - modelAsMercatorCoordinate.y) /
      modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
    return [x, y]
  }

  return (
    <div className="map-wrapper">
      <MbMap
        ref={mapInst}
        center={mapCenter}
        zoom={zoom}
        pitch={pitch}
        onCreated={mapCreated}
      >
        <MbTiandituLayer types={['vec', 'cva']} onCreated={add3DLayer} />
      </MbMap>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector('#root'))