Three.js实战项目-智慧楼宇

概述

如有不明白的可以加QQ:2781128388
源码获取:https://mbd.pub/o/bread/Y5uck5xt

先看看视频效果

Three.js 建筑可视化监控

分别具备楼层展示,楼层热力度分析,扩散波特效,无人机飞行,飞行路径点规划,扩散波特效,多场景下钻,多文本渲染,路径动画

初始化场景

这里和以前文章的加载场景一样,没有变化

app = new ZThree('screen');
        app.initThree();
        // app.initHelper();
        app.initOrbitControls();
        light = app.initLight();

        stats = app.initStatus();
        selectObj = app.initRaycaster();
        window.app = app;
        camera = app.camera;
        bloomComposer = app.bloomComposer();
        camera.position.set(...this.cameraPosition);
        scene = app.scene;
        renderer = app.renderer;
        renderer.logarithmicDepthBuffer = true;
        renderer.autoClear = false;

        controls = app.controls;
        controls.target.set(...this.target);
        controls.maxDistance = 2000;
        controls.maxPolarAngle = Math.PI / 2.2;
        clock = new THREE.Clock();

加载建筑数据和道路数据

import axios from "axios";
import * as THREE from 'three'
import {
  getDistance,
  getRhumbLineBearing
} from "geolib";
import {
  BufferGeometryUtils
} from "three/examples/jsm/utils/BufferGeometryUtils.js";
// 中心点数组
const center = [114.0504053235054, 22.532816043569957];

// 初始化创建组
let iR = new THREE.Group();
iR.scale.set(20, 20, 20);
iR.name = "建组";

let iR_Road = new THREE.Group();
iR_Road.name = "公路";
iR_Road.scale.set(20, 20, 20);

let iR_Line = new THREE.Group();
iR_Line.name = "公路动画线";
iR_Line.scale.set(20, 20, 20);

// 包围盒数组对象
let collider_building = [];

// 线条数组对象
let linelist = [];

// 建组数组对象
let geos_building = [];

let MAT_BUILDING;

// 线材质
let MAT_ROAD = new THREE.LineBasicMaterial({
  color: 0x00ff00,
  linewidth: 10
});

let MAT_ROAD_HIGEWAY;

/**
 * 获得地图数据
 */
export function createBuildingRoad(app, buildingMaterials, lineMaterial) {
  return new Promise(resolve => {
    axios({
      url: "/json/export.json"
    }).then(res => {
      MAT_BUILDING = buildingMaterials;
      MAT_ROAD_HIGEWAY = lineMaterial;
      LoadBuildings(res.data, app);
      app.scene.add(iR);
      app.scene.add(iR_Road);
      app.scene.add(iR_Line);

      resolve(true)
    });
  })
}

/**
 * 加载地图模型
 */
function LoadBuildings(data, app) {
  let features = data.features;

  for (let i = 0; i < features.length; i++) {
    let fel = features[i];
    if (!fel["properties"]) return;

    let info = fel.properties;

    // building 建筑   highway 公路
    if (info["building"]) {
      addBuilding(fel.geometry.coordinates, info, info["height"]);
    } else if (info["highway"]) {
      if (fel.geometry.type == "LineString") {
        // if (
        //   info["highway"] === "trunk_link" ||
        //   info["highway"] === "motorway"
        // ) {
        //   addRoadTrunk(fel.geometry.coordinates, info);
        // } else {
        //   // addRoad(fel.geometry.coordinates, info);
        // }
        addRoadTrunk(fel.geometry.coordinates, info);
      }
    }
  }

  linelist.forEach((e, i) => {
    const line2 = app.createAnimateLine({
      type: "pipe",
      pointList: e,
      material: MAT_ROAD_HIGEWAY,
      number: 100,
      radius: 0.01
    });
    line2.rotateZ(-Math.PI);
    iR_Road.add(line2);
  });

  let mergeGeometry = BufferGeometryUtils.mergeBufferGeometries(
    geos_building
  );

  let mesh = new THREE.Mesh(mergeGeometry, MAT_BUILDING);

  iR.add(mesh);
}

/**
 * 添加建筑
 * @param {Array} data 坐标数组
 * @param {Object} info 建筑信息
 * @param {Number} height 建组高度
 */
function addBuilding(data, info, height = 1) {
  height = height ? height : 1;

  let shape, geometry;
  // 建筑物的孔
  let holes = [];

  for (let i = 0; i < data.length; i++) {
    let el = data[i];

    if (i == 0) {
      // 建筑路劲
      shape = genShape(el, center);
    } else {
      // 当前孔的路径
      holes.push(genShape(el, center));
    }
  }

  // 将孔添加到当前建组中
  for (let i = 0; i < holes.length; i++) {
    shape.holes.push(holes[i]);
  }

  if (height > 10) {
    height = 0.1 * height
  } else {
    height = 8 * height
  }

  geometry = genGeometry(shape, {
    curveSegments: 1,
    // depth: height,
    depth: 0.1 * height,
    bevelEnabled: false
  });

  geometry.rotateX(Math.PI / 2);
  geometry.rotateZ(Math.PI);

  geos_building.push(geometry);

  let helper = genHelper(geometry);
  if (helper) {
    helper.name = info["name"] ? info["name"] : "Building";
    helper.info = info;
    collider_building.push(helper);
  }
}

/**
 * 添加公路
 * @param {Array} d 坐标数组
 * @param {Object} info 建筑信息
 */
function addRoadTrunk(d, info) {
  // 点数组
  let points = [];

  for (let i = 0; i < d.length; i++) {
    if (!d[0][1]) return;

    let el = d[i];

    if (!el[0] || !el[1]) return;

    let elp = [el[0], el[1]];

    // 位置转换
    elp = GPSRelativePosition([elp[0], elp[1]], center);

    points.push([elp[0], 0, elp[1]]);
  }

  linelist.push(points);
}

/**
 * 添加公路
 * @param {Array} d 坐标数组
 * @param {Object} info 建筑信息
 */
function addRoad(d, info) {
  // 点数组
  let points = [];

  for (let i = 0; i < d.length; i++) {
    if (!d[0][1]) return;

    let el = d[i];

    if (!el[0] || !el[1]) return;

    let elp = [el[0], el[1]];

    // 位置转换
    elp = GPSRelativePosition([elp[0], elp[1]], center);

    points.push(new THREE.Vector3(elp[0], 0.5, elp[1]));
  }

  // 通过点队列设置该 BufferGeometry 的 attribute
  let geometry = new THREE.BufferGeometry().setFromPoints(points);

  geometry.rotateZ(Math.PI);

  let line = new THREE.Line(geometry, MAT_ROAD);
  line.info = info;
  // 计算LineDashedMaterial所需的距离的值的数组
  line.computeLineDistances();

  iR_Road.add(line);
  line.position.set(line.position.x, 0, line.position.z);
}

/**
 * 添加建筑
 * @param {Array} points 坐标数组
 * @param {Array} center 地理位置中心点
 * @return {Object}
 */
function genShape(points, center) {
  let shape = new THREE.Shape();

  for (let i = 0; i < points.length; i++) {
    let elp = points[i];
    elp = GPSRelativePosition(elp, center);

    if (i == 0) {
      shape.moveTo(elp[0], elp[1]);
    } else {
      shape.lineTo(elp[0], elp[1]);
    }
  }

  return shape;
}

/**
 * 生成Geometry
 * @param {Object} shape shape对象
 * @param {Object} settings 建筑配置项
 * @return {Object} 建组对象
 */
function genGeometry(shape, settings) {
  // 挤压缓冲几何体
  let geometry = new THREE.ExtrudeBufferGeometry(shape, settings);
  geometry.computeBoundingBox();

  return geometry;
}

/**
 * 建组包围盒子
 * @param {Object} geometry
 */
function genHelper(geometry) {
  if (!geometry.boundingBox) {
    geometry.computeBoundingBox();
  }

  let box3 = geometry.boundingBox;
  // 检测包围盒是不是无穷大
  if (!isFinite(box3.max.x)) {
    return false;
  }

  let helper = new THREE.Box3Helper(box3, 0xffff00);
  helper.updateMatrixWorld();
  return helper;
}

/**
 * GPS经纬度转化
 * @param {Array} objPosi 坐标数组
 * @param {Array} centerPosi 中心点
 * @return {Array} 转化后的数组
 */
function GPSRelativePosition(objPosi, centerPosi) {
  let dis = getDistance(objPosi, centerPosi);

  // Get bearing angle
  let bearing = getRhumbLineBearing(objPosi, centerPosi);

  // Calculate X by centerPosi.x + distance * cos(rad)
  let x = centerPosi[0] + dis * Math.cos((bearing * Math.PI) / 180);

  // Calculate Y by centerPosi.y + distance * sin(rad)
  let y = centerPosi[1] + dis * Math.sin((bearing * Math.PI) / 180);

  // Reverse X (it work)
  return [-x / 100, y / 100];
}

道路和建筑数据在开源地图OpenStreetMap上获取即可,可获取道路,建筑,水系等多种数据,虽然数据比较简陋,不过拿来做demo数据还是可以的

扩散波

cylinder = loaderCylinder(app);

export function loaderCylinder(app) {
  let texture = new THREE.TextureLoader().load("images/zhu.png")
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping; //每个都重复
  texture.repeat.set(1, 1)
  texture.needsUpdate = true

  let geometry = new THREE.CylinderGeometry(8, 8, 4, 64);

  let materials = [
    new THREE.MeshBasicMaterial({
      map: texture,
      side: THREE.DoubleSide,
      transparent: true
    }),
    new THREE.MeshBasicMaterial({
      transparent: true,
      opacity: 0,
      side: THREE.DoubleSide
    }),
    new THREE.MeshBasicMaterial({
      transparent: true,
      opacity: 0,
      side: THREE.DoubleSide
    })
  ]

  let mesh = new THREE.Mesh(geometry, materials)

  app.scene.add(mesh)

  return mesh;
}

猜你喜欢

转载自blog.csdn.net/qq_39503511/article/details/127823085