Fence
Example
Example Source Code
tsx
import React, { useCallback, useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import * as THREE from 'three'
import { MbMap, MbTiandituLayer } from '@mapbox-react/core'
const App = () => {
const [mapCenter] = useState([116.390714, 39.916535])
const [zoom, setZoom] = useState(13)
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(),
}
addThreeLayer(modelTransform, modelAsMercatorCoordinate)
}
const addThreeLayer = (modelTransform, modelAsMercatorCoordinate) => {
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()
// fence range
const boundaryArr = [
[116.385394, 39.92098],
[116.38591, 39.911632],
[116.389942, 39.911565],
[116.390457, 39.908734],
[116.392174, 39.908733],
[116.39226, 39.911959],
[116.396294, 39.911894],
[116.395691, 39.921438],
[116.385394, 39.92098],
]
const outer = boundaryArr
.map((b) => getThreePoint(modelAsMercatorCoordinate, b))
.map((p) => [p[0], -p[1], 0])
const polygonMesh = addFencing(outer)
polygonMesh.translateY(-200)
this.scene.add(polygonMesh)
{
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)
},
}
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]
}
const addFencing = (points: number[][]) => {
const height = 500
const pointDistance: number[] = []
const distance = points.reduce((totalDistance, point, index) => {
let segmentDistance = 0
if (index > 0) {
const lastPoint = new THREE.Vector3(...points[index - 1])
const currPoint = new THREE.Vector3(...point)
segmentDistance = lastPoint.distanceTo(currPoint)
}
totalDistance += segmentDistance
pointDistance.push(totalDistance)
return totalDistance
}, 0)
const geometry = new THREE.BufferGeometry()
const posArr: number[] = []
const uvArr: number[] = []
points.forEach((point, index) => {
if (index == 0) return
const lastPoint = points[index - 1]
// 三角面1 triangle1
posArr.push(...lastPoint)
uvArr.push(pointDistance[index - 1] / distance, 0)
posArr.push(...point)
uvArr.push(pointDistance[index] / distance, 0)
posArr.push(lastPoint[0], lastPoint[1], lastPoint[2] + height)
uvArr.push(pointDistance[index - 1] / distance, 1)
// 三角面2 triangle2
posArr.push(...point)
uvArr.push(pointDistance[index] / distance, 0)
posArr.push(point[0], point[1], point[2] + height)
uvArr.push(pointDistance[index] / distance, 1)
posArr.push(lastPoint[0], lastPoint[1], lastPoint[2] + height)
uvArr.push(pointDistance[index - 1] / distance, 1)
})
geometry.setAttribute(
'position',
new THREE.BufferAttribute(new Float32Array(posArr), 3)
)
geometry.setAttribute(
'uv',
new THREE.BufferAttribute(new Float32Array(uvArr), 2)
)
geometry.computeVertexNormals()
const texture = new THREE.TextureLoader().load(
`https://mapbox-web.github.io/mapbox-react/images/wall.png`
)
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
// 材质
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
map: texture,
transparent: true,
opacity: 0.9,
depthWrite: false,
side: THREE.DoubleSide,
})
const mesh = new THREE.Mesh(geometry, material)
return mesh
}
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'))