<!--
 * @Description: 模型与Web的交互
 * @Autor: 刘 相卿
 * @Date: 2025-11-17 16:07:33
 * @LastEditors: 刘 相卿
 * @LastEditTime: 2025-11-21 10:04:12
-->
<script lang="ts" setup>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { onMounted, onUnmounted, ref, shallowRef } from 'vue'

// 物体信息类型
interface ObjectInfo {
  name: string
  material: string
  position: THREE.Vector3
  mesh: THREE.Mesh
}

const threeRef = shallowRef<HTMLDivElement>()

let scene: THREE.Scene
let camera: THREE.PerspectiveCamera
let renderer: THREE.WebGLRenderer
let controls: OrbitControls
let animationId: number
let raycaster: THREE.Raycaster
let mouse: THREE.Vector2
let model: THREE.Group

// 后期处理相关
let composer: EffectComposer
let outlinePass: OutlinePass

// 交互状态
const hoveredObject = shallowRef<THREE.Mesh | null>(null)
const selectedObject = ref<ObjectInfo | null>(null)
const labelPosition = ref({ x: 0, y: 0 })

// 辅助线
let axesHelper: THREE.AxesHelper | null = null

function init() {
  const width = threeRef.value!.clientWidth
  const height = threeRef.value!.clientHeight

  scene = new THREE.Scene()
  scene.background = new THREE.Color(0x1A1A2E)

  camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
  camera.position.set(0, 5, 15)

  renderer = new THREE.WebGLRenderer({ antialias: true })
  renderer.setSize(width, height)
  renderer.setPixelRatio(window.devicePixelRatio)
  threeRef.value!.appendChild(renderer.domElement)

  controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true
  controls.dampingFactor = 0.05

  // 射线检测器
  raycaster = new THREE.Raycaster()
  mouse = new THREE.Vector2()

  // 添加灯光 - 增强照明
  // 环境光 - 提供基础亮度
  const ambientLight = new THREE.AmbientLight(0xFFFFFF, 5)
  scene.add(ambientLight)

  // 主方向光 - 从右上方照射
  const mainLight = new THREE.DirectionalLight(0xFFFFFF, 1.5)
  mainLight.position.set(10, 15, 10)
  mainLight.castShadow = true
  scene.add(mainLight)

  // 辅助方向光 - 从左侧照射，补充阴影区域
  const fillLight = new THREE.DirectionalLight(0xFFFFFF, 0.8)
  fillLight.position.set(-10, 10, -10)
  scene.add(fillLight)

  // 背光 - 从后方照射，增加轮廓感
  const backLight = new THREE.DirectionalLight(0xFFFFFF, 0.6)
  backLight.position.set(0, 5, -15)
  scene.add(backLight)

  // 点光源 - 增加局部亮度
  const pointLight = new THREE.PointLight(0xFFFFFF, 1, 50)
  pointLight.position.set(0, 10, 0)
  scene.add(pointLight)

  // 添加地面
  const groundGeometry = new THREE.PlaneGeometry(100, 100)
  const groundMaterial = new THREE.MeshStandardMaterial({
    color: 0x555555,
    roughness: 0.8,
    metalness: 0.2,
  })
  const ground = new THREE.Mesh(groundGeometry, groundMaterial)
  ground.rotation.x = -Math.PI / 2
  ground.position.y = 0
  scene.add(ground)

  // 设置后期处理
  setupPostProcessing()

  // 加载模型
  const loader = new GLTFLoader()
  loader.load('/mzjc_bansw.glb', (gltf) => {
    model = gltf.scene
    model.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        child.userData.objectType = '建筑构件'
        child.userData.objectName = child.name || '未命名物体'
      }
    })
    scene.add(model)
  })

  // 事件监听
  renderer.domElement.addEventListener('mousemove', onMouseMove)
  renderer.domElement.addEventListener('click', onCanvasClick)
  window.addEventListener('resize', onResize)

  animate()
}

// 设置后期处理（轮廓描边）
function setupPostProcessing() {
  composer = new EffectComposer(renderer)

  const renderPass = new RenderPass(scene, camera)
  composer.addPass(renderPass)

  outlinePass = new OutlinePass(
    new THREE.Vector2(threeRef.value!.clientWidth, threeRef.value!.clientHeight),
    scene,
    camera,
  )
  outlinePass.edgeStrength = 3
  outlinePass.edgeGlow = 0.5
  outlinePass.edgeThickness = 2
  outlinePass.pulsePeriod = 0
  outlinePass.visibleEdgeColor.set('#00ff00')
  outlinePass.hiddenEdgeColor.set('#00ff00')

  composer.addPass(outlinePass)
}

function animate() {
  animationId = requestAnimationFrame(animate)
  controls.update()

  // 更新标签位置
  if (selectedObject.value) {
    updateLabelPosition()
  }

  composer.render()
}

// 更新标签位置
function updateLabelPosition() {
  if (!selectedObject.value)
    return

  const position = selectedObject.value.position.clone()
  // 获取物体的包围盒来计算高度
  const box = new THREE.Box3().setFromObject(selectedObject.value.mesh)
  const height = box.max.y - box.min.y
  position.y = box.max.y + height * 0.3

  position.project(camera)

  labelPosition.value = {
    x: (position.x * 0.5 + 0.5) * threeRef.value!.clientWidth,
    y: (-(position.y * 0.5) + 0.5) * threeRef.value!.clientHeight,
  }
}

// 鼠标移动事件
function onMouseMove(event: MouseEvent) {
  const rect = renderer.domElement.getBoundingClientRect()
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1

  raycaster.setFromCamera(mouse, camera)

  if (model) {
    const intersects = raycaster.intersectObject(model, true)

    if (intersects.length > 0 && intersects[0]) {
      const object = intersects[0].object as THREE.Mesh

      // 如果是新的悬停物体
      if (hoveredObject.value !== object) {
        hoveredObject.value = object

        // 添加轮廓描边
        outlinePass.selectedObjects = [object]

        // 添加辅助坐标线
        removeAxesHelper()
        const box = new THREE.Box3().setFromObject(object)
        const center = box.getCenter(new THREE.Vector3())
        axesHelper = new THREE.AxesHelper(2)
        axesHelper.position.copy(center)
        scene.add(axesHelper)
      }
    }
    else {
      // 鼠标没有悬停在物体上
      if (hoveredObject.value && hoveredObject.value !== selectedObject.value?.mesh) {
        clearHoverEffects()
      }
    }
  }
}

// 画布点击事件
function onCanvasClick(event: MouseEvent) {
  const rect = renderer.domElement.getBoundingClientRect()
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1

  raycaster.setFromCamera(mouse, camera)

  if (model) {
    const intersects = raycaster.intersectObject(model, true)

    if (intersects.length > 0 && intersects[0]) {
      const object = intersects[0].object as THREE.Mesh
      const point = intersects[0].point

      // 获取材质信息
      let materialName = '未知材质'
      if (object.material) {
        if (Array.isArray(object.material)) {
          materialName = object.material.map((m: THREE.Material) => m.type).join(', ')
        }
        else {
          materialName = object.material.type
        }
      }

      // 设置选中物体
      selectedObject.value = {
        name: object.userData.objectName || object.name || '未命名物体',
        material: materialName,
        position: point.clone(),
        mesh: object,
      }

      // 保持轮廓描边和坐标线
      hoveredObject.value = object
      outlinePass.selectedObjects = [object]

      updateLabelPosition()
    }
    else {
      // 点击空白处，清除所有效果
      clearAllEffects()
    }
  }
}

// 清除悬停效果
function clearHoverEffects() {
  hoveredObject.value = null
  if (!selectedObject.value) {
    outlinePass.selectedObjects = []
    removeAxesHelper()
  }
}

// 清除所有效果
function clearAllEffects() {
  hoveredObject.value = null
  selectedObject.value = null
  outlinePass.selectedObjects = []
  removeAxesHelper()
}

// 移除辅助坐标线
function removeAxesHelper() {
  if (axesHelper) {
    scene.remove(axesHelper)
    axesHelper = null
  }
}

function onResize() {
  if (!threeRef.value)
    return
  const width = threeRef.value.clientWidth
  const height = threeRef.value.clientHeight
  camera.aspect = width / height
  camera.updateProjectionMatrix()
  renderer.setSize(width, height)
  composer.setSize(width, height)
}

onMounted(init)
onUnmounted(() => {
  cancelAnimationFrame(animationId)
  window.removeEventListener('resize', onResize)
  if (renderer.domElement) {
    renderer.domElement.removeEventListener('mousemove', onMouseMove)
    renderer.domElement.removeEventListener('click', onCanvasClick)
  }
  renderer.dispose()
  composer.dispose()
})
</script>

<template>
  <div ref="threeRef" class="w-full h-full" />

  <!-- 物体信息标签 -->
  <div
    v-if="selectedObject"
    class="object-label"
    :style="{
      left: `${labelPosition.x}px`,
      top: `${labelPosition.y}px`,
    }"
  >
    <div class="label-content">
      <div class="label-title">
        {{ selectedObject.name }}
      </div>
      <div class="label-item">
        <span class="label-key">材质：</span>
        <span class="label-value">{{ selectedObject.material }}</span>
      </div>
      <div class="label-item">
        <span class="label-key">坐标：</span>
        <span class="label-value">
          ({{ selectedObject.position.x.toFixed(2) }},
          {{ selectedObject.position.y.toFixed(2) }},
          {{ selectedObject.position.z.toFixed(2) }})
        </span>
      </div>
    </div>
  </div>

  <router-link to="/three/11" class="position-fixed left-20px top-20px link-btn">
    后期处理效果
  </router-link>

  <div class="position-fixed right-20px top-20px info-panel">
    <div class="info-text">
      鼠标移入物体：显示轮廓和坐标轴
    </div>
    <div class="info-text">
      点击物体：显示详细信息
    </div>
    <div class="info-text">
      点击空白处：取消选择
    </div>
  </div>
</template>

<style scoped>
.object-label {
  position: absolute;
  transform: translate(-50%, -100%);
  pointer-events: none;
  z-index: 100;
  animation: fadeIn 0.3s ease-in-out;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translate(-50%, -100%) scale(0.8);
  }
  to {
    opacity: 1;
    transform: translate(-50%, -100%) scale(1);
  }
}

.label-content {
  background: rgba(0, 0, 0, 0.85);
  color: white;
  padding: 12px 16px;
  border-radius: 6px;
  border: 2px solid #00ff00;
  box-shadow: 0 4px 12px rgba(0, 255, 0, 0.3);
  min-width: 200px;
}

.label-title {
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 8px;
  color: #00ff00;
  border-bottom: 1px solid rgba(0, 255, 0, 0.3);
  padding-bottom: 6px;
}

.label-item {
  font-size: 13px;
  margin: 6px 0;
  line-height: 1.5;
}

.label-key {
  color: #aaa;
  font-weight: 500;
}

.label-value {
  color: #fff;
}

.info-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.info-text {
  background: rgba(0, 0, 0, 0.75);
  color: white;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 12px;
  border-left: 3px solid #00ff00;
}
</style>
