光伏园区3d系统管理

发布于:2025-07-08 ⋅ 阅读:(14) ⋅ 点赞:(0)
<template>
  <div id="webgl-box" ref="webglBox" style="width: 100vw; height: 100vh;"></div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

const webglBox = ref(null)
let renderer, scene, camera, animationId, controls

function createSolarPanel(x, y, z) {
  // 太阳能板:用多个倾斜的矩形模拟
  const group = new THREE.Group()
  const panelGeometry = new THREE.BoxGeometry(4, 0.2, 2)
  const panelMaterial = new THREE.MeshPhongMaterial({ color: 0x1e88e5 })
  for (let i = 0; i < 3; i++) {
    const panel = new THREE.Mesh(panelGeometry, panelMaterial)
    panel.position.set(x + i * 4.5, y + 0.2, z)
    panel.rotation.x = -Math.PI / 6
    group.add(panel)
  }
  return group
}

function createWindTurbine(x, y, z) {
  // 风机:底座++三片叶片
  const group = new THREE.Group()
  // 杆
  const poleGeometry = new THREE.CylinderGeometry(0.2, 0.4, 8, 16)
  const poleMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff })
  const pole = new THREE.Mesh(poleGeometry, poleMaterial)
  pole.position.set(x, y + 4, z)
  group.add(pole)
  // 机舱
  const nacelleGeometry = new THREE.SphereGeometry(0.5, 16, 16)
  const nacelle = new THREE.Mesh(nacelleGeometry, poleMaterial)
  nacelle.position.set(x, y + 8, z)
  group.add(nacelle)
  // 叶片
  for (let i = 0; i < 3; i++) {
    const bladeGeometry = new THREE.BoxGeometry(0.1, 3, 0.3)
    const blade = new THREE.Mesh(bladeGeometry, new THREE.MeshPhongMaterial({ color: 0x90caf9 }))
    blade.position.set(x, y + 9.5, z)
    blade.rotation.z = (i * 2 * Math.PI) / 3
    group.add(blade)
  }
  return group
}

function createBuilding(x, z, w, h, d, color = 0x8d6e63) {
  // 厂房/办公楼
  const geometry = new THREE.BoxGeometry(w, h, d)
  const material = new THREE.MeshPhongMaterial({ color })
  const mesh = new THREE.Mesh(geometry, material)
  mesh.position.set(x, h / 2, z)
  return mesh
}

function createGround() {
  // 地面
  const geometry = new THREE.PlaneGeometry(120, 80)
  const material = new THREE.MeshPhongMaterial({ color: 0xa5d6a7 })
  const mesh = new THREE.Mesh(geometry, material)
  mesh.rotation.x = -Math.PI / 2
  mesh.position.y = 0
  return mesh
}

function createSolarPanelArray(rows, cols, startX, startZ, gapX, gapZ) {
  // 批量生成光伏板阵列
  const group = new THREE.Group()
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      const geometry = new THREE.BoxGeometry(4, 0.2, 2)
      const material = new THREE.MeshPhongMaterial({ color: 0x1e88e5 })
      const mesh = new THREE.Mesh(geometry, material)
      mesh.position.set(startX + j * gapX, 0.2, startZ + i * gapZ)
      mesh.rotation.x = -Math.PI / 6
      group.add(mesh)
    }
  }
  return group
}

function createInverter(x, z) {
  // 逆变器/配电箱
  const geometry = new THREE.BoxGeometry(2, 1, 1)
  const material = new THREE.MeshPhongMaterial({ color: 0xffc107 })
  const mesh = new THREE.Mesh(geometry, material)
  mesh.position.set(x, 0.5, z)
  return mesh
}

function createRoad(x, z, w, d) {
  // 道路
  const geometry = new THREE.BoxGeometry(w, 0.05, d)
  const material = new THREE.MeshPhongMaterial({ color: 0x757575 })
  const mesh = new THREE.Mesh(geometry, material)
  mesh.position.set(x, 0.025, z)
  return mesh
}

function loadModel(url, onLoad) {
  const loader = new GLTFLoader()
  loader.load(url, gltf => {
    const model = gltf.scene
    onLoad && onLoad(model)
  })
}

function initThree() {
  scene = new THREE.Scene()
  scene.background = new THREE.Color(0xe3f2fd)

  // 摄像机
  const width = webglBox.value.clientWidth
  const height = webglBox.value.clientHeight
  camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
  camera.position.set(-100, 80, 100)
  camera.lookAt(0, 0, 0)

  // 渲染器
  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
  renderer.setClearColor(0x000000, 0)
  renderer.setSize(width, height)
  webglBox.value.appendChild(renderer.domElement)

  // 灯光
  scene.add(new THREE.AmbientLight(0xffffff, 0.7))
  const dirLight = new THREE.DirectionalLight(0xffffff, 0.7)
  dirLight.position.set(30, 50, 30)
  scene.add(dirLight)

  // 地面
  scene.add(createGround())

  // 光伏板阵列(假设3组,每组5x10块)
  scene.add(createSolarPanelArray(5, 10, -40, -20, 6, 4))
  scene.add(createSolarPanelArray(5, 10, -40, 10, 6, 4))
  scene.add(createSolarPanelArray(5, 10, 20, -5, 6, 4))

  // 逆变器
  scene.add(createInverter(-10, -30))
  scene.add(createInverter(30, 30))

  // 厂房/办公楼
  scene.add(createBuilding(50, 30, 12, 6, 8, 0x607d8b))
  scene.add(createBuilding(-50, 30, 8, 4, 6, 0x8d6e63))

  // 道路
  scene.add(createRoad(0, 0, 100, 4))
  scene.add(createRoad(30, 20, 4, 40))

  // 坐标轴
  scene.add(new THREE.AxesHelper(40))

  // === 新增:初始化 OrbitControls ===
  controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true
  controls.dampingFactor = 0.05
  controls.minDistance = 10
  controls.maxDistance = 200
  controls.maxPolarAngle = Math.PI    // 允许看到场景下方
  controls.minPolarAngle = 0          // 允许看到场景上方
  controls.enablePan = true
  controls.enableZoom = true

  // 动画
  function animate() {
    animationId = requestAnimationFrame(animate)
    controls && controls.update() // === 新增:每帧更新控制器 ===
    // 可加风机叶片旋转等动画
    scene.traverse(obj => {
      if (obj.geometry && obj.geometry.type === 'BoxGeometry' && obj.material.color.getHex() === 0x90caf9) {
        obj.rotation.y += 0.05 // 让风机叶片转动
      }
    })
    renderer.render(scene, camera)
  }
  animate()
}

function onWindowResize() {
  if (!renderer || !camera || !webglBox.value) return
  const width = webglBox.value.clientWidth
  const height = webglBox.value.clientHeight
  camera.aspect = width / height
  camera.updateProjectionMatrix()
  renderer.setSize(width, height)
}

onMounted(() => {
  initThree()
  window.addEventListener('resize', onWindowResize)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', onWindowResize)
  if (animationId) cancelAnimationFrame(animationId)
  if (controls) controls.dispose()
  if (renderer) {
    renderer.dispose()
    renderer.forceContextLoss && renderer.forceContextLoss()
    renderer.domElement && renderer.domElement.remove()
  }
})
</script>

<style scoped>
#webgl-box {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  /* 科技感渐变+网格 */
  background:
    repeating-linear-gradient(0deg, rgba(76,192,141,0.08) 0, rgba(76,192,141,0.08) 1px, transparent 1px, transparent 40px),
    repeating-linear-gradient(90deg, rgba(76,192,141,0.08) 0, rgba(76,192,141,0.08) 1px, transparent 1px, transparent 40px),
    linear-gradient(135deg, #0f2027 0%, #2c5364 100%);
}
</style>

在这里插入图片描述
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到