threejs效果之樓房展示
效果:
基礎數據:
樓房每一個樓的底邊數據,數據結構為geojson。properties屬性中Floor屬性為樓房層數,本示例默認所有樓每層等高。
數據示例:

{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ 120.32088059979766, 31.542322389378242 ], ...... [ 120.32088059979766, 31.542322389378242 ] ] ] }, "properties": { "FID": 0, "Id": 0, "Floor": 4 }, "id": 0 }, { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ 120.32076281379749, 31.541746604232802 ], ...... [ 120.32076281379749, 31.541746604232802 ] ] ] }, "properties": { "FID": 1, "Id": 0, "Floor": 6 }, "id": 1 } ] }
實現原理:
通過樓的底邊數據生成每個樓每層的面、每個面添加邊框、以及牆體邊框線等要素,拼接為樓群建築。
先將底邊的經緯度轉換為米,舍去小數點后數據。
注意事項:
1.threejs生成面、體等為三角面模型,所以本例子的面都為手動繪制
2.基礎數據中底邊為經緯度點組成。若直接使用經緯度生成樓層面,因為經緯度小數點位數過多會導致threejs經度處理達不到,出現邊框被抽稀的效果。
經緯度直接生成模型后,因為經緯度區別在小數點后幾位,會導致模型過小。放大后會導致整個模型抖動嚴重。
代碼解析:
1.initContainer() 方法為入口方法,混入vue組件后調用即可。
2.modelBox為threejs的canvas容器
源碼:
1.vue組件:

1 <template> 2 <div class="threeModel"> 3 <div id="modelBox"></div> 4 </div> 5 </template> 6 7 <script> 8 import borderData from './人民醫院建築.json' 9 import createModel from './createModel' 10 export default { 11 name: 'threeModel', 12 mixins:[createModel], 13 data() { 14 return { 15 borderData: borderData, 16 } 17 }, 18 mounted() { 19 this.initContainer(this.borderData) 20 } 21 } 22 </script> 23 24 <style scoped lang="scss"> 25 .threeModel { 26 background: url('../../../assets/images/loop.png') no-repeat; 27 background-size: 100% 100%; 28 width: 100%; 29 height: 100%; 30 #modelBox { 31 width: 100%; 32 height: 100%; 33 background: rgba(0, 0, 0, 0.5); 34 } 35 } 36 </style>
2.混入方法:

1 import * as THREE from 'three' 2 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' 3 export default { 4 data(){ 5 return{ 6 _borderData: [], // 樓房基礎數據 7 _bounds: [], // 樓房四至 8 _center: [] // 樓房中心點 9 } 10 }, 11 methods: { 12 13 // 創建場景 14 initContainer(borderData) { 15 /*創建場景對象Scene*/ 16 var scene = new THREE.Scene() 17 // 添加樓房樓層平面 18 this.addFloors(scene, borderData) 19 /* 光源設置*/ 20 // 環境光 21 var ambientLight = new THREE.AmbientLight(0xffffff) 22 scene.add(ambientLight) 23 /** 24 * 相機設置 25 */ 26 var width = window.innerWidth //窗口寬度 27 var height = window.innerHeight //窗口高度 28 var k = width / height //窗口寬高比 29 var s = 1500 //三維場景顯示范圍控制系數,系數越大,顯示的范圍越大 30 //創建相機對象 31 var camera = new THREE.PerspectiveCamera( 32 45, 33 window.innerWidth / window.innerHeight, 34 0.1, 35 1000000 36 ) 37 camera.position.set(3000,2000,3000) 38 camera.lookAt(scene.position) 39 /** 40 * 創建渲染器對象 41 */ 42 var renderer = new THREE.WebGLRenderer({ 43 antialias: true, 44 alpha: true 45 }) 46 renderer.setSize(window.innerWidth - 300, window.innerHeight) 47 document.getElementById('modelBox').appendChild(renderer.domElement) 48 49 var controls = new OrbitControls(camera, renderer.domElement) 50 controls.target = new THREE.Vector3(0, 0, 0) //控制焦點 51 controls.autoRotate = true;//將自動旋轉關閉 52 let clock = new THREE.Clock();//用於更新軌道控制器 53 54 function animate() { 55 renderer.render(scene, camera) 56 57 let delta = clock.getDelta(); 58 controls.update(delta); 59 requestAnimationFrame(animate) 60 } 61 animate() 62 }, 63 64 // 添加樓群 65 addFloors(scene, data) { 66 // 數據處理/基礎數據計算 67 this._dataAnalysis(data) 68 this._getModelBounds() 69 this._getModelCenter() 70 this._dataAnalysisAll() 71 // 新建樓房組 72 var group = new THREE.Group(); 73 group.rotation.set(-1.6,0,0); 74 scene.add(group) 75 // 添加樓層 76 this._borderData.forEach(res => { 77 this.addFloor(group,res) 78 // 添加樓房黃色邊框牆 79 this.createWall(group, res) 80 }) 81 }, 82 // 添加單個樓 83 addFloor (group, data) { 84 let shape = this.createShape(data.points) 85 let i = 0 86 let addG = setInterval(() => { 87 if (i<data.floor) { 88 // 添加透明層 89 let mesh; 90 if (i === data.floor || i === 0) { 91 // 添加樓層頂部和底部效果 92 // mesh = this.createPolyline(shape, i, 0.9, 'rgb(14,98,173)') 93 } else { 94 mesh = this.createPolyline(shape, i) 95 } 96 group.add(mesh); 97 // 添加樓層邊界 98 this.createPolygonline(group, data.points, i) 99 i++ 100 } else{ 101 if(addG){ 102 clearInterval(addG) 103 } 104 } 105 }, 30) 106 }, 107 // 數據轉換 108 _dataAnalysis (borderData) { 109 let data = [] 110 borderData.features.forEach(res => { 111 res.geometry.coordinates.forEach(r => { 112 // 將度轉換為米 113 r = r.map(re => { 114 return [(re[0]*1112000).toFixed(0)*1, (re[1]*1112000).toFixed(0)*1] 115 }) 116 data.push({ 117 floor: res.properties.Floor, 118 points: r 119 }) 120 }) 121 }) 122 this._borderData = data 123 }, 124 // 獲取模型四至 125 _getModelBounds () { 126 // 四至: 上右下左 127 let bounds = [0,0,0,0] 128 // 所有點數組 129 let pointArr = [] 130 // 所有點經度數組 131 let pointLonArr = [] 132 // 所有點緯度數組 133 let pointLatArr = [] 134 // 獲取所有點數組 135 this._borderData.forEach(res => { 136 pointArr = pointArr.concat(res.points) 137 }) 138 // 獲取經度、緯度數組 139 pointArr.forEach(res => { 140 pointLonArr.push(res[0]) 141 pointLatArr.push(res[1]) 142 }) 143 // 獲取四至 144 bounds = [Math.max(...pointLatArr), Math.min(...pointLonArr), Math.min(...pointLatArr), Math.max(...pointLonArr)] 145 this._bounds = bounds 146 }, 147 // 獲取模型中心點 148 _getModelCenter () { 149 let center = [(this._bounds[1] + this._bounds[3])/2,(this._bounds[0] + this._bounds[2])/2] 150 this._center = center 151 }, 152 // 數據轉換2-將數據移動至原點 153 _dataAnalysisAll(){ 154 this._borderData.forEach(res => { 155 res.points.forEach(re => { 156 re[0] = re[0] - this._center[0] 157 re[1] = re[1] - this._center[1] 158 }) 159 }) 160 }, 161 162 // 創建平面集合 163 createShape (data) { 164 var shape = new THREE.Shape(); 165 data.forEach((e,i) => { 166 if (i === 0) { 167 shape.moveTo( ...e); 168 } else { 169 shape.lineTo( ...e); 170 } 171 }) 172 return shape 173 }, 174 // 創建樓層平面組 175 createPolyoneGroup () { 176 var group = new THREE.Group(); 177 group.rotation.set(-1.6,0,0); 178 // group.position.set(-30,0,30); 179 // group.scale.set(5,5,5); 180 return group 181 }, 182 183 // 創建透明平面 184 createPolyline(shape, h, opacity = 0.1, color='rgb(0, 0,255, 0)'){ 185 var geometry = new THREE.ShapeGeometry( shape ); 186 var cubeMaterial=new THREE.MeshBasicMaterial({ 187 color: color, 188 transparent:true, 189 opacity:opacity, 190 side:THREE.DoubleSide // 強制雙面 191 }); 192 var mesh = new THREE.Mesh( geometry, cubeMaterial ); 193 194 mesh.position.z = h*20; 195 // mesh.scale.set(1,1,1); 196 mesh.scale.set(1,1,1); 197 mesh.rotation.set(0,0,0); 198 199 return mesh 200 }, 201 // 創建邊緣平面 202 createPolygonline(group, data, h=0){ 203 var material = new THREE.LineBasicMaterial({ 204 color: 'rgba(53,166,255,0.8)', 205 linewidth: 1, 206 side:THREE.DoubleSide // 強制雙面 207 }); 208 var geometry = new THREE.Geometry() 209 for (let item of data) { 210 geometry.vertices.push( 211 new THREE.Vector3(...item, h*20) 212 ) 213 } 214 var line = new THREE.Line(geometry, material) 215 // line.scale.set(0.8,0.8,1); 216 // line.position.set(-10,-10,0); 217 group.add(line) 218 }, 219 220 // 創建牆面 221 createWall(group, data){ 222 var material = new THREE.LineBasicMaterial({ 223 color: 0xffc000 224 }); 225 let points = data.points 226 let wallData = [] 227 for (let i=0;i< points.length;i++) { 228 if(i + 1 < points.length){ 229 wallData.push([points[i], points[i+1]]) 230 } 231 } 232 for (let item of wallData) { 233 var geometry = new THREE.Geometry(); 234 geometry.vertices.push( 235 new THREE.Vector3( ...item[1], 0 ), 236 new THREE.Vector3( ...item[1], data.floor*20 ), 237 new THREE.Vector3( ...item[0], data.floor*20 ), 238 new THREE.Vector3( ...item[0], 0 ), 239 new THREE.Vector3( ...item[1], 0 ) 240 ); 241 var line = new THREE.Line( geometry, material ); 242 group.add(line) 243 } 244 }, 245 246 } 247 }
鑽研不易,轉載請注明出處。。。。。。