// ========== Three.js 相关模块引入 ==========
// 引入 Three.js 核心库及各类常用扩展
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; // 相机轨道控制器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; // GLTF模型加载器
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; // Draco模型解压器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; // HDR环境贴图加载器
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js"; // 后处理合成器
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js"; // 渲染通道
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js"; // 着色器通道
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js"; // 抗锯齿着色器
import { Water } from "three/examples/jsm/objects/Water.js"; // 水面对象

// Three.js 线框及文本相关扩展
import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry.js";
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";

// css3
import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js'
/* css3d 渲染 */
function setCss3DRenderer(DOM) {

  const css3DRender = new CSS3DRenderer()

  css3DRender.resize = () => {

    css3DRender.setSize(DOM.clientWidth, DOM.clientHeight)

    css3DRender.domElement.style.zIndex = 0

    css3DRender.domElement.style.position = 'relative'

    css3DRender.domElement.style.top = -DOM.clientHeight + 'px'

    css3DRender.domElement.style.height = DOM.clientHeight + 'px'

    css3DRender.domElement.style.width = DOM.clientWidth + 'px'

    css3DRender.domElement.style.pointerEvents = 'none'

  }

  css3DRender.resize()

  DOM.appendChild(css3DRender.domElement)

  return css3DRender

}


import {
  Line,
  LineDashedMaterial,
  BufferGeometry,
  Float32BufferAttribute,
} from "three";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader.js"; // 字体加载器
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js"; // 文本几何体

/* ==========  3条虚线辅助函数  ========== */
/**
 * 为一个球体对象生成三条（x、y、z方向）虚线，指示该点到对应坐标平面的投影。
 * 每条虚线用LineDashedMaterial表示，便于后续动态更新端点坐标。
 * @param {Object3D} ball 目标球体
 * @param {Scene} scene   所属场景
 * @param {Object} opts   虚线样式参数
 */
function createDashedLinesForBall(
  ball,
  scene,
  { dashSize = 1, gapSize = 1, color = 0x7ec8ff, arrowSize = 1 } = {}
) {
  const makeLine = (direction) => {
    // 用 BufferGeometry+LineDashedMaterial 创建一条虚线
    const geo = new BufferGeometry();
    geo.setAttribute(
      "position",
      new Float32BufferAttribute(new Float32Array(6), 3)
    ); // 两端点(共6元素)
    const mat = new LineDashedMaterial({
      color,
      dashSize,
      gapSize,
      transparent: true,
      opacity: 0.6,
    });
    const line = new Line(geo, mat);
    line.computeLineDistances();
    scene.add(line);
    const dir = new THREE.Vector3();
    if (direction === "back") dir.set(0, 0, -1);
    else if (direction === "left") dir.set(1, 0, 0);

    if (direction != "down") {
      const arrowHelper = new THREE.ArrowHelper(
        dir,
        new THREE.Vector3(),
        0 * arrowSize, // 线段长度
        color,
        0.2 * arrowSize, // 箭头头部长度
        0.1 * arrowSize // 箭头头部宽度
      );
      arrowHelper.line.material.transparent = true;
      arrowHelper.line.material.opacity = 0.6;
      // 设置箭头头部透明度
      arrowHelper.cone.material = new THREE.MeshBasicMaterial({
        color: color,
        transparent: true, // 必须设置为true
        opacity: 0.6, // 与线段相同的透明度
      });
      scene.add(arrowHelper);
      return { line, arrow: arrowHelper };
    }
    return { line };
  };

  // // 创建 z-y 面上的投影点
  // const createZYPlaneProjection = () => {
  //   // 方法1：使用圆形平面
  //   const geometry = new THREE.CircleGeometry(0.1 * arrowSize, 16);
  //   const material = new THREE.MeshBasicMaterial({
  //     color: color,
  //     transparent: true,
  //     opacity: 0.6,
  //     side: THREE.DoubleSide, // 确保两面可见
  //   });
  //   const circle = new THREE.Mesh(geometry, material);
  //   circle.position.copy(ball.position);
  //   circle.position.x = 0; // 放置在 x=0 平面
  //   circle.rotation.y = Math.PI / 2; // 旋转 90 度，使其平行于 z-y 平面
  //   scene.add(circle);
  //   return circle;

  //   // 方法2：使用点精灵（Points）
  //   /*
  //   const geometry = new THREE.BufferGeometry();
  //   const vertices = new Float32Array([ball.position.x, ball.position.y, 0]);
  //   geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
  //   const material = new THREE.PointsMaterial({
  //     color: color,
  //     size: 0.2 * arrowSize,
  //     transparent: true,
  //     opacity: 0.6
  //   });
  //   const point = new THREE.Points(geometry, material);
  //   scene.add(point);
  //   return point;
  //   */
  // };
  const createZYPlaneProjection = () => {
    const group = new THREE.Group(); // 用 Group 管理投影点和外圈

    // 1. 核心投影点（实心圆）
    const coreGeometry = new THREE.CircleGeometry(0.08 * arrowSize, 16);
    const coreMaterial = new THREE.MeshBasicMaterial({
      color: color,
      transparent: true,
      opacity: 0.8,
    });
    const core = new THREE.Mesh(coreGeometry, coreMaterial);
    core.rotation.y = Math.PI / 2; // 平行于 z-y 平面
    group.add(core);

    // 2. 外圈淡化圆环（比核心稍大）
    const ringGeometry = new THREE.RingGeometry(
      0.08 * arrowSize, // 内半径（比核心稍大）
      0.16 * arrowSize, // 外半径
      32
    );
    const ringMaterial = new THREE.MeshBasicMaterial({
      color: color,
      transparent: true,
      opacity: 0.3, // 更淡
      side: THREE.DoubleSide,
    });
    const ring = new THREE.Mesh(ringGeometry, ringMaterial);
    ring.rotation.y = Math.PI / 2; // 平行于 z-y 平面
    group.add(ring);

    // 初始位置与小球的 x 相同（不再固定 x=0）
    group.position.copy(ball.position);
    group.position.x = 0;
    scene.add(group);
    return group; // 返回整个组
  };

  // 记录在球体的 userData 里，便于后续动画时更新
  ball.userData.dashedLines = {
    down: makeLine("down"), // y方向到底面
    back: makeLine("back"), // z方向到后面
    left: makeLine("left"), // x方向到侧面
    zPlaneProjection: createZYPlaneProjection(), // z-y面投影点
  };
}
function updateDashedLines(ball) {
  const { dashedLines } = ball.userData;
  const ballPos = ball.position;
  const radius = ball.geometry.parameters.radius;

  // // 更新下方向虚线及箭头
  // updateLineAndArrow(
  //   dashedLines.down,
  //   [ballPos.x, ballPos.y, ballPos.z],
  //   [ballPos.x, 0, ballPos.z], // 假设地面在y=0
  //   radius
  // );

  // 更新后方向虚线及箭头
  updateLineAndArrow(
    dashedLines.back,
    [ballPos.x, ballPos.y, ballPos.z - 1],
    [ballPos.x, ballPos.y, 0], // 假设后面在z=0
    radius
  );

  // 更新左方向虚线及箭头
  updateLineAndArrow(
    dashedLines.left,
    [ballPos.x, ballPos.y, ballPos.z],
    [0, ballPos.y, ballPos.z], // 假设左面在x=0
    radius
  );
}

function updateLineAndArrow(lineObj, start, end, radius) {
  // 更新虚线
  const positions = lineObj.line.geometry.attributes.position.array;
  positions[0] = start[0];
  positions[1] = start[1];
  positions[2] = start[2];
  positions[3] = end[0];
  positions[4] = end[1];
  positions[5] = end[2];
  lineObj.line.geometry.attributes.position.needsUpdate = true;
  lineObj.line.computeLineDistances();

  // 计算中点位置（从球表面开始）
  const midPoint = [
    start[0] + (end[0] - start[0]) * 0.5,
    start[1] + (end[1] - start[1]) * 0.5,
    start[2] + (end[2] - start[2]) * 0.5,
  ];

  // 调整箭头位置，使其从球表面开始
  const dir = new THREE.Vector3(
    end[0] - start[0],
    end[1] - start[1],
    end[2] - start[2]
  ).normalize();

  // 箭头位置从中点稍微向球偏移一点，确保在虚线中间
  lineObj.arrow.position.set(
    midPoint[0] - dir.x * radius * 0.5,
    midPoint[1] - dir.y * radius * 0.5,
    midPoint[2] - dir.z * radius * 0.5
  );
}

/* ========== 创建自定义网格线（抗锯齿） ========== */
/**
 * 构建网格线条对象，可用于三维空间的不同面，支持自定义分段数与线宽。
 * @param {number} w      网格宽度
 * @param {number} h      网格高度
 * @param {number} divX   x方向分段数
 * @param {number} divY   y方向分段数
 * @param {color}  color  网格线颜色
 * @param {number} px     线宽（单位像素）
 * @returns {LineSegments2} 线段对象
 */
function createCustomGridAA(w, h, divX, divY, color = 0xffffff, px = 1.5) {
  // 按分段数生成所有线的端点坐标
  const pos = [];
  const stepX = w / divX,
    stepY = h / divY;
  for (let i = 0; i <= divX; i++) {
    const x = i * stepX;
    pos.push(x, 0, 0, x, h, 0); // 垂直线
  }
  for (let j = 0; j <= divY; j++) {
    const y = j * stepY;
    pos.push(0, y, 0, w, y, 0); // 水平线
  }
  // 线条材质用LineMaterial，支持屏幕分辨率感知，线宽不随缩放变化
  const geo = new LineSegmentsGeometry().setPositions(pos);
  const mat = new LineMaterial({
    color,
    linewidth: px,
    worldUnits: false,
    transparent: true,
    opacity: 0.5,
  });
  mat.resolution.set(window.innerWidth, window.innerHeight);
  return new LineSegments2(geo, mat);
}

/* ========== 坐标轴刻度文本构建 ========== */
/**
 * 创建带刻度的向量轴标签（支持中文自动换行）
 * @param {Object} 配置参数
 *   axis: 轴方向（'x'/'y'/'z'）
 *   length: 轴总长度
 *   labels: 自定义标签数组（可选）
 *   divisions: 分段数量
 *   fontUrl: 字体文件路径（支持中文）
 *   size: 文字大小
 *   height: 文字厚度
 *   color: 文字颜色
 *   colors: 按索引指定颜色数组
 *   offset: 位置偏移量
 *   parent: 父容器（必须）
 *   opacity: 透明度
 *   maxWidth: 单行最大宽度（默认10）
 *   lineHeight: 行间距倍数（默认1.2）
 */
function buildVectorTicks({
  axis = "x",
  length = 50,
  labels = null,
  divisions = 5,
  fontUrl = "/fonts/FangSong_Regular.json", // 确保字体支持中文
  size = 2,
  height = 0.2,
  color = 0xffffff,
  colors = [],
  offset = new THREE.Vector3(),
  parent = null,
  opacity = 0.8,
  maxWidth = 10,     // 新增：单行最大宽度
  lineHeight = 1.2,  // 新增：行高倍数
} = {}) {
  if (!parent) {
    console.warn("未指定父容器，无法创建刻度标签");
    return;
  }

  new FontLoader().load(fontUrl, (font) => {
    const group = new THREE.Group();
    parent.add(group);
    const arr = labels ?? Array.from({ length: divisions + 1 }, (_, i) => i.toString());
    let step
    if (axis === "y") {
      step = length / (arr.length || 1);
    } else {
      step = length / (arr.length - 1 || 1);
    }

    /**
     * 中文换行处理（按字符而非英文单词）
     * @param {string} text 待处理文本
     * @returns {string[]} 分割后的行数组（最多3行）
     */
    const wrapChineseText = (text) => {
      const chars = text.split(''); // 按字符分割（适用于中文）
      const lines = [];
      let currentLine = '';

      for (const char of chars) {
        const testLine = currentLine + char;
        const tempGeo = new TextGeometry(testLine, { font, size, height: 0 });
        tempGeo.computeBoundingBox();
        const testWidth = tempGeo.boundingBox.max.x - tempGeo.boundingBox.min.x;

        if (axis === "y") {
          currentLine = testLine;
        } else {
          if (testWidth <= maxWidth) {
            currentLine = testLine;
          } else {
            lines.push(currentLine);
            currentLine = char;
            if (lines.length >= 2) break; // 限制最多3行
          }
        }
      }
      if (currentLine) lines.push(currentLine);
      return lines.slice(0, 3);
    };

    // 创建所有标签
    arr.forEach((text, i) => {
      const lines = wrapChineseText(text);
      const material = new THREE.MeshBasicMaterial({
        color: colors[i] || color, // 优先使用colors数组中的指定颜色
        toneMapped: false,
        transparent: true,
        opacity,
      });

      // 每个标签的容器（用于多行文本）
      const labelGroup = new THREE.Group();

      // 创建每行文本
      lines.forEach((line, lineIndex) => {
        const geo = new TextGeometry(line, {
          font,
          size,
          height,
          curveSegments: 12, // 提高曲线分段数使中文更平滑
        });

        geo.computeBoundingBox();
        geo.center();

        const mesh = new THREE.Mesh(geo, material);

        if (axis != "y") {
          // 垂直偏移计算（首行在上，向下排列）
          const verticalOffset =
            ((lines.length - 1) * size * lineHeight) / 2 -
            lineIndex * size * lineHeight;
          mesh.position.y = verticalOffset;
        }

        labelGroup.add(mesh);
      });

      // 设置标签组位置
      const position = i * step;
      if (axis === "x") {
        labelGroup.rotation.y = Math.PI / 2; // X轴标签旋转90度
        labelGroup.position.set(offset.x, offset.y, offset.z + position);
      } else if (axis === "y") {
        labelGroup.rotation.y = Math.PI / 4; // Y轴标签旋转45度
        labelGroup.position.set(offset.x, offset.y - position, offset.z);
      } else {
        labelGroup.position.set(offset.x + position, offset.y, offset.z); // Z轴
      }

      group.add(labelGroup);
    });
  });
}



/* ========== Draco解压和GLTF加载器 ========== */
const dracoLoader = new DRACOLoader().setDecoderPath("/draco/"); // 设置Draco解码器路径
const gltfLoader = new GLTFLoader().setDRACOLoader(dracoLoader); // GLTF加载器关联Draco


/* ========== 主函数：画三维坐标轴及交互 ========== */
/**
 * 在传入的DOM元素内创建完整的三维坐标系、网格面、刻度、球体模型、交互、后处理、UI按钮等。
 * @param {Element} element 渲染容器
 * @param {Object}  options 配置参数
 * @returns {Object}        {scene, camera, renderer, dispose}
 */
export function drawAxes(element, options = {}, ballCallBack) {
  // ========== 配置参数及默认值 ==========
  const {
    background = "#171717", // 背景色
    gridSize = { x: 50, y: 50, z: 50 }, // 三维网格尺寸
    // grid_uv_up = [5, 5],
    // grid_uv_down = [5, 5],
    grid_uv_left = [5, 5],
    // grid_uv_right = [5, 4],
    // grid_uv_front = [5, 4],
    grid_uv_back = [5, 5],
    show_water = true, // 是否显示水面
    water_height = 5, // 水面厚度
    data = {},
    labels_left = ["", "A", "B", "C", "D", "E"],
    labels_back = ["", "A", "B", "C", "D", "E"],
    labels_height = [],
    ztObj = {},
  } = options;

  /* ========== 场景/相机/渲染器初始化 ========== */
  const scene = new THREE.Scene();
  // scene.background = new THREE.Color(background);
  scene.position.set(-gridSize.x / 2, -gridSize.y / 2, -gridSize.z / 2); //  修改 场景 中心点
  const camera = new THREE.PerspectiveCamera(
    75,
    element.clientWidth / element.clientHeight,
    0.1,
    2000
  );
  camera.position.set(gridSize.x, gridSize.y, gridSize.z); // 俯视+侧视角

  const renderer = new THREE.WebGLRenderer({
    alpha: true, // 启用透明度
    antialias: true,
  });
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  renderer.setPixelRatio(dpr);
  renderer.setSize(element.clientWidth, element.clientHeight);
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  element.appendChild(renderer.domElement);

  const css3DRenderer = setCss3DRenderer(element);
  // 轨道控制器（支持旋转/缩放/平移相机）
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.minPolarAngle = 0;
  controls.maxPolarAngle = Math.PI / 2.8;

  /* ========== 后处理抗锯齿 ========== */
  const composer = new EffectComposer(renderer);
  composer.addPass(new RenderPass(scene, camera));
  const fxaa = new ShaderPass(FXAAShader);
  const updateFXAA = () =>
    fxaa.material.uniforms.resolution.value.set(
      1 / (element.clientWidth * dpr),
      1 / (element.clientHeight * dpr)
    );
  updateFXAA();
  composer.addPass(fxaa);

  /* ========== 环境光照/HDR环境贴图 ========== */
  console.log(process.env.VUE_APP_BASE_URL);

  new RGBELoader().load(process.env.VUE_APP_BASE_URL + "/hdr/basic.hdr", (tex) => {
    tex.mapping = THREE.EquirectangularReflectionMapping;
    scene.environment = tex;
  });

  /* ========== 网格和水面生成 ========== */
  const { x: sizeX, y: sizeY, z: sizeZ } = gridSize;
  const gridGroup = new THREE.Group();

  scene.add(gridGroup);
  scene.userData.waterList = [];

  // 可选：底部加一块水面（水面动画来自 Water.js）
  if (show_water) {
    const waterNormals = new THREE.TextureLoader().load(
      "/textures/water_1.png",
      (t) => {
        t.wrapS = t.wrapT = THREE.RepeatWrapping;
      }
    );
    nicoCustomFun(sizeX, sizeZ);

    const water = new Water(new THREE.BoxGeometry(sizeX, water_height, sizeZ), {

      // 纹理大小（影响波纹细节）
      textureWidth: 2048, // 可以增加到2048获取更高细节
      textureHeight: 2048,

      // 法线贴图（控制波纹样式）
      waterNormals: waterNormals,

      // 太阳方向（影响反光方向）
      sunDirection: new THREE.Vector3(1, 1, 1).normalize(),

      // 颜色相关
      sunColor: 0xffffff, // 阳光颜色（高光部分）
      // sunColor: 0xff0326, // 阳光颜色（高光部分）
      // waterColor: 0x1e90ff, // 水体基础颜色
      waterColor: 0x034b88, // 水体基础颜色
      // waterColor: 0x001e0f, // 水体基础颜色
      distortionScale: 30, // 波纹强度（0.5-5之间调整）distortionScale: 100.0,

      // 其他效果
      fog: scene.fog !== undefined, // 是否受雾效影响
      alpha: 0.1, // 透明度（0-1）
      // time: 0, // 可用于手动控制动画时间
    });
    water.position.set(sizeX / 2, -water_height / 2, sizeZ / 2);
    gridGroup.add(water);


    scene.userData.waterList.push(water);
  }

  function nicoCustomFun(sizeX, sizeZ) {
    const box = new THREE.BoxGeometry(1, 1, 1);
    const material = new THREE.MeshBasicMaterial({ color: "red" });
    const mesh = new THREE.Mesh(box, material);
    for (let i = 1; i <= 5; i++) {
      const clone = mesh.clone(true);
      clone.position.set(sizeX / 5 * i, 0, 0);
      setCss3dDOM(createDom(i, "x"), clone.position);
      const clone2 = clone.clone(true);
      clone2.position.set(0, 0, sizeZ / 5 * i);
      setCss3dDOM(createDom(i, "z"), clone2.position);

      gridGroup.add(clone);
      gridGroup.add(clone2);
    }
  }
  function setCss3dDOM(DOM, position) {

    const mesh = new CSS3DObject(DOM)

    mesh.position.copy(position)

    scene.add(mesh)

    return mesh

  }
  // 创建dom
  function createDom(i, axes) {

    const div = document.createElement('div')
    div.style.cursor = 'pointer'
    div.addEventListener('click', () => {
      console.log('click', i, axes)

    })
    div.addEventListener('mouseover', () => {
      console.log('mouseover', i, axes)
    })
    div.addEventListener('mouseout', () => {
      console.log('mouseout', i, axes)
    })
    const img = document.createElement('img')

    img.src = process.env.VUE_APP_BASE_URL + `/icon_xz/${axes}0${i}.png`

    img.style.width = '10px'

    img.style.height = '10px'

    div.appendChild(img)

    // div.innerHTML += text

    div.style.color = 'white'

    return div

  }

  // 根据配置添加每个面上的网格线
  // const color = 0xffffff,    //  0x244b78 2d578a
  const color = 0x2d578a,
    px = 1.0;
  if (typeof grid_uv_down !== "undefined" && grid_uv_down) {
    // 底面
    const g = createCustomGridAA(sizeX, sizeZ, ...grid_uv_down, color, px);
    g.rotation.x = Math.PI / 2;
    g.position.set(0, 0, 0);
    gridGroup.add(g);
  }
  if (typeof grid_uv_left !== "undefined" && grid_uv_left) {
    // 左侧
    const g = createCustomGridAA(sizeZ, sizeY, ...grid_uv_left, color, px);
    g.rotation.y = -Math.PI / 2;
    g.position.set(0, 0, 0);
    gridGroup.add(g);
  }
  // if (typeof grid_uv_right !== "undefined" && grid_uv_right) {
  //   // 右侧
  //   const g = createCustomGridAA(sizeZ, sizeY, ...grid_uv_right, color, px);
  //   g.rotation.y = -Math.PI / 2;
  //   g.position.set(sizeX, 0, 0);
  //   gridGroup.add(g);
  // }
  // if (typeof grid_uv_front !== "undefined" && grid_uv_front) {
  //   // 前侧
  //   const g = createCustomGridAA(sizeX, sizeY, ...grid_uv_front, color, px);
  //   g.position.set(0, 0, sizeZ);
  //   gridGroup.add(g);
  // }
  // 后侧 using
  let gris_back_g = null;
  if (typeof grid_uv_back !== "undefined" && grid_uv_back) {
    gris_back_g = createCustomGridAA(sizeX, sizeY, ...grid_uv_back, color, px);
    gris_back_g.position.set(0, 0, 0);
    gridGroup.add(gris_back_g);
  }

  // X轴、Z轴上的刻度文本
  buildVectorTicks({
    axis: "x",
    length: sizeZ,
    labels: labels_left,
    offset: new THREE.Vector3(0, sizeY + 5, 0),
    parent: gridGroup,
  });
  buildVectorTicks({
    axis: "z",
    length: sizeX,
    labels: labels_back,
    offset: new THREE.Vector3(0, sizeY + 5, 0),
    parent: gridGroup,
  });
  buildVectorTicks({
    axis: "y",
    length: sizeY,
    labels: labels_height,
    divisions: 10, // 0xf617d9, 0x779c6e, 0x3ee4bc, 0xca41e7, 0xc88d38, 0x7467ef, 0xc7bef7,
    colors: [
      0xf617d9, 0x779c6e, 0x3ee4bc, 0xca41e7, 0xc88d38, 0x7467ef, 0xc7bef7,
    ],
    offset: new THREE.Vector3(0, sizeY, 0),
    parent: gridGroup,
    opacity: 1,
  });

  buildVectorTicks({
    axis: "z", //  x 轴
    size: 2.5,
    maxWidth: 15,
    length: sizeX,
    labels: ["信宿"],
    offset: new THREE.Vector3(sizeX + 15, 0, 0),
    parent: gridGroup,
  });
  buildVectorTicks({
    axis: "y", //  y 轴
    size: 2.5,
    maxWidth: 15,
    length: sizeY,
    labels: ["信息类型"],
    offset: new THREE.Vector3(0, sizeY + 15, 0),
    parent: gridGroup,
  });
  buildVectorTicks({
    axis: "x", //  z 轴
    size: 2.5,
    maxWidth: 15,
    length: sizeZ,
    labels: ["信源"],
    offset: new THREE.Vector3(0, 0, sizeZ + 15),
    parent: gridGroup,
  });

  addPositiveAxes(scene, sizeX, sizeY, sizeZ);

  /* ========== 球体模型批量加载和放置 ========== */
  // 四种球用同一个glb，分别用不同位置数组、父分组存储
  let styleGroups = {};
  data.forEach((el) => {
    styleGroups[el.name] = null;
  });
  styleGroups["灰球"] = null;
  const lineGeometryColors = [
    "rgb(102,129,102)",
    "rgb(79,78,177)",
    "rgb(81,157,155)",
    "rgb(102,129,102)",
    "rgb(166,47,176)",

    "rgb(166,47,176)",
  ];
  const geometryColors = [
    "#888888",
    "#888888",
    "#888888",
    "#888888",
    "#888888",
    "#888888",
    "#888888",
  ];
  const ballScene = new THREE.Group();
  geometryColors.forEach((el) => {
    // 几何体
    const geometry = new THREE.SphereGeometry(1, 64, 64);

    // 材质（重点：metalness 和 roughness）
    const material = new THREE.MeshStandardMaterial({
      color: el, // 基础颜色
      metalness: 0.2, // 金属度
      roughness: 0.8, // 光滑度（数值越低越光滑）
    });

    //网孔(Mesh)是用来承载几何模型的一个对象，可以把材料应用到它上面
    const ball = new THREE.Mesh(geometry, material);
    ball.color = el;
    ballScene.children.push(ball);
  });
  const geometry = new THREE.SphereGeometry(1, 64, 64);
  // 材质（重点：metalness 和 roughness）
  const material = new THREE.MeshStandardMaterial({
    color: "#898184", // 基础颜色
    metalness: 0.2, // 金属度
    roughness: 0.8, // 光滑度（数值越低越光滑）
  });
  const greyBall = new THREE.Mesh(geometry, material); //  灰色小球

  const allBalls = []; //  所有点位对象
  const greyGroup = new THREE.Group();
  scene.add(greyGroup);
  styleGroups["灰球"] = greyGroup;
  styleGroups["灰球"].name = "灰球";
  styleGroups["灰球"].color = "#898184";

  const cloneBall = (idx, list, key, name) => {
    if (!list) return;
    const g = new THREE.Group();

    scene.add(g);

    styleGroups[key] = g;
    styleGroups[key].name = name;
    styleGroups[key].color = ballScene.children[idx].color;
    list.forEach((point) => {
      // 每个球体用 glb 里不同索引的 mesh clone 出来
      // const n = root.children[idx].clone(true);
      let n = ballScene.children[idx].clone(true);
      // console.log(ztObj, "ztObj");

      if (ztObj && ztObj[point.id] == false) {
        n = greyBall.clone(true);
      } else {
        if (point.fourLinks == false) {
          n = greyBall.clone(true);
        }
      }
      n.pointId = point.id;
      const p = point.pointData;
      n.position.set(...p);
      n.userData.originalPos = new THREE.Vector3(...p); // 原始位置
      n.userData.groupKey = `${p[0]}_${p[1]}`; // 分组唯一key
      n.userData.groupKeyXZ = `${p[0]}_${p[2]}`; // 分组唯一key 向x-z面收缩合并
      g.add(n);

      if (ztObj && ztObj[point.id] == false) {
        greyGroup.add(n);
      } else {
        if (point.fourLinks == false) {
          greyGroup.add(n);
        }
      }

      allBalls.push(n);
      if (p[0] != 0 && p[1] != 0 && p[2] != 0) {
        createDashedLinesForBall(n, scene, {
          dashSize: 1,
          gapSize: 1,
          color: lineGeometryColors[idx],
          arrowSize: 8,
        }); // 给每个球加三条投影虚线
        updateDashedLines(n);
      }
    });
  };
  data.forEach((el, index) => {
    cloneBall(index, el.point, el.name, el.name);
  });

  // 添加单个点位的方法
  function addSinglePoint(parent, position, modelIndex, groupName) {
    // const ball = root.children[modelIndex].clone(true);
    const ball = ballScene.children[modelIndex].clone(true);
    ball.pointId = position.id;
    const point = position.pointData;
    ball.position.set(...point);
    ball.userData = {
      originalPos: new THREE.Vector3(...point),
      groupKey: `${point[0]}_${point[1]}`,
      groupKeyXZ: `${point[0]}_${point[2]}`,
      groupName: groupName,
    };

    parent.add(ball);
    allBalls.push(ball);
    if (point[0] != 0 && point[1] != 0 && point[2] != 0) {
      createDashedLinesForBall(ball, scene, {
        dashSize: 1,
        gapSize: 1,
        color: lineGeometryColors[modelIndex],
      });
      updateDashedLines(ball);
    }

    return ball;
  }

  /**
   * 移除单个点位
   * @param {THREE.Object3D} pointObj 要移除的点位对象
   * @param {boolean} removeFromGroup 是否从所属组中移除（默认true）
   */
  function removeSinglePoint(pointObj, removeFromGroup = true) {
    if (!pointObj || !allBalls.includes(pointObj)) return;

    // 移除虚线
    if (pointObj.userData?.dashedLines) {
      Object.values(pointObj.userData.dashedLines).forEach((line) => {
        line.geometry.dispose();
        line.material.dispose();
        scene.remove(line);
      });
    }

    // 从allBalls中移除
    const index = allBalls.indexOf(pointObj);
    if (index !== -1) {
      allBalls.splice(index, 1);
    }

    // 从父组中移除
    if (removeFromGroup && pointObj.parent) {
      pointObj.parent.remove(pointObj);
    }

    // 如果所属组为空，移除整个组
    const groupName = pointObj.userData?.groupName;
    if (
      groupName &&
      styleGroups[groupName] &&
      styleGroups[groupName].children.length === 0
    ) {
      removeGroup(groupName);
    }
  }

  /**
   * 通过ID或位置查找点位
   * @param {string} groupName 组名
   * @param {Array} position 位置 [x,y,z]
   * @returns {THREE.Object3D|null} 找到的点位对象
   */
  function getPointById(groupName, position) {
    if (!styleGroups[groupName]) return null;

    const pos = new THREE.Vector3(...position);
    const tolerance = 0.001; // 位置匹配容差

    for (const ball of styleGroups[groupName].children) {
      if (ball.position.distanceTo(pos) < tolerance) {
        return ball;
      }
    }
    return null;
  }

  /* ========== 交互处理：射线检测吸附与复位 ========== */
  const raycaster = new THREE.Raycaster();
  const mouse = new THREE.Vector2();
  let hovered = null;

  // 组吸附与目标映射
  const activeGroups = new Set(); // 当前已吸附的分组key
  const groupTargets = new Map(); // 分组key -> 吸附目标位置（Vector3）
  const activeGroupsXZ = new Set(); // 当前已吸附的分组key xz水平面组
  const groupTargetsXZ = new Map(); // 分组key xz -> 吸附目标位置（Vector3）

  // 鼠标移动时，更新 mouse 向量（归一化NDC坐标）
  function onMouseMove(e) {
    const rect = renderer.domElement.getBoundingClientRect();
    mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
    mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
  }
  window.addEventListener("mousemove", onMouseMove);

  // 鼠标点击时，判断是否有球在back面被点中，并吸附/释放
  function onClick($event) {
    raycaster.setFromCamera(mouse, camera);

    const inter = raycaster.intersectObjects(allBalls);

    if (inter.length === 0) {
      if ($event.target === renderer.domElement) {
        // 确认点击的是Three.js的canvas
        ballCallBack(false, {});
      }

      return;
    }
    const obj = inter[0].object;
    // 只允许在z约等于0（back面）的球体才可点击
    if (Math.abs(obj.position.z) > 1e-3) {
      ballCallBack(true, obj);
      return;
    }
    // if (Math.abs(obj.position.z) > 1e-3) return;
    const key = obj.userData.groupKey;
    // console.log(obj.userData, "obj.userData");

    if (activeGroups.has(key)) {
      // 若已吸附，则取消吸附（恢复原位）
      activeGroups.delete(key);
      groupTargets.delete(key);
    } else {
      // 未吸附，则将目标z设为0（吸附到back面）
      activeGroups.add(key);
      groupTargets.set(
        key,
        new THREE.Vector3(obj.position.x, obj.position.y, 0)
      );
    }
  }

  window.addEventListener("click", onClick);

  let isCollapsedXY = false; // 点位小球是否处于回收状态
  let isCollapsedXZ = false; // 点位小球是否处于回收状态
  function setCollapsedXY(params) {
    if (isCollapsedXZ) return;
    isCollapsedXY = !isCollapsedXY; // 切换状态

    allBalls.forEach((ball) => {
      const key = ball.userData.groupKey;
      if (isCollapsedXY) {
        // // 回收状态：吸附到X-Y平面（z=0）
        groupTargets.set(
          key,
          new THREE.Vector3(ball.position.x, ball.position.y, 0)
        );
        activeGroups.add(key);
      } else {
        // 恢复状态：回到原始位置
        activeGroups.delete(key);
        groupTargets.delete(key);
      }
    });
  }
  function setCollapsedXZ(params) {
    if (isCollapsedXY) return;
    isCollapsedXZ = !isCollapsedXZ; // 切换状态

    allBalls.forEach((ball) => {
      const key = ball.userData.groupKeyXZ; //  groupKeyXZ
      if (isCollapsedXZ) {
        // // 回收状态：吸附到X-Y平面（z=0）
        groupTargetsXZ.set(
          key,
          new THREE.Vector3(ball.position.x, 0, ball.position.z)
        );
        activeGroupsXZ.add(key);
      } else {
        // 恢复状态：回到原始位置
        activeGroupsXZ.delete(key);
        groupTargetsXZ.delete(key);
      }
    });
  }
  /* ========== 视窗尺寸自适应处理 ========== */
  const onResize = () => {
    const w = element.clientWidth,
      h = element.clientHeight;
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
    renderer.setPixelRatio(dpr);
    renderer.setSize(w, h);
    composer.setSize(w, h);
    updateFXAA();
    // 需同步网格线材质分辨率，否则线宽异常
    gridGroup.traverse((o) => {
      if (o.material?.isLineMaterial) o.material.resolution.set(w, h);
    });
  };
  window.addEventListener("resize", onResize);

  /* ========== 动画循环与实时状态刷新 ========== */
  let rafId;
  const animate = () => {
    rafId = requestAnimationFrame(animate);
    css3DRenderer.render(scene, camera);
    controls.update();
    // 水面动画（推动uniform的time）
    scene.userData.waterList.forEach(
      (w) => (w.material.uniforms.time.value += 1 / 60)
    );

    // 鼠标悬浮放大效果
    raycaster.setFromCamera(mouse, camera);
    const inter = raycaster.intersectObjects(allBalls);
    if (inter.length) {
      if (hovered !== inter[0].object) {
        hovered?.scale.set(1, 1, 1);
        hovered = inter[0].object;
        hovered.scale.set(1.2, 1.2, 1.2);
      }
    } else {
      hovered?.scale.set(1, 1, 1);
      hovered = null;
    }

    // 球体吸附（z=0）/复位动画插值
    allBalls.forEach((obj) => {
      const key = obj.userData.groupKey;
      const keyXZ = obj.userData.groupKeyXZ;

      if (activeGroups.has(key)) {
        obj.position.lerp(groupTargets.get(key), 0.05); // 吸附到指定目标点
      }
      if (activeGroupsXZ.has(keyXZ)) {
        obj.position.lerp(groupTargetsXZ.get(keyXZ), 0.05); // 吸附到指定目标点
      }
      if (!activeGroups.has(key) && !activeGroupsXZ.has(keyXZ)) {
        obj.position.lerp(obj.userData.originalPos, 0.05); // 回到原始位置
      }
    });

    // 虚线端点动态刷新（始终连接球体当前坐标与投影点）
    allBalls.forEach((obj) => {
      const lines = obj.userData.dashedLines;
      // console.log(lines, "lineslineslineslines");

      if (!lines) return;
      const p = obj.position;

      // y方向（down）
      {
        const arr = lines.down.line.geometry.attributes.position.array;
        arr[0] = p.x;
        arr[1] = p.y;
        arr[2] = p.z;
        arr[3] = p.x;
        arr[4] = 0;
        arr[5] = p.z;
        lines.down.line.geometry.attributes.position.needsUpdate = true;
        lines.down.line.computeLineDistances();
      }
      // z方向（back）
      {
        const arr = lines.back.line.geometry.attributes.position.array;
        arr[0] = p.x;
        arr[1] = p.y;
        arr[2] = p.z;
        arr[3] = p.x;
        arr[4] = p.y;
        arr[5] = 0;
        lines.back.line.geometry.attributes.position.needsUpdate = true;
        lines.back.line.computeLineDistances();
      }
      // x方向（left）
      {
        const arr = lines.left.line.geometry.attributes.position.array;
        arr[0] = p.x;
        arr[1] = p.y;
        arr[2] = p.z;
        arr[3] = 0;
        arr[4] = p.y;
        arr[5] = p.z;
        lines.left.line.geometry.attributes.position.needsUpdate = true;
        lines.left.line.computeLineDistances();
      }
    });
    allBalls.forEach((obj) => {
      const lines = obj.userData.dashedLines;
      if (!lines) return;
      // console.log(lines, "lines");

      const { zPlaneProjection } = obj.userData.dashedLines;
      if (!zPlaneProjection) return;
      const p = obj.position;

      // 完全跟随小球的 x 和 y（z 固定为原始值，假设投影在 z=0 平面）
      zPlaneProjection.position.x = 0; // 跟随 x 移动
      zPlaneProjection.position.y = p.y; // 跟随 y 移动
      zPlaneProjection.position.z = p.z; // 固定 z=0（或其他平面）

      updateDashedLines(obj);
    });

    composer.render();
  };
  animate();

  /* ========== 销毁/资源回收接口 ========== */
  function dispose() {
    cancelAnimationFrame(rafId);
    window.removeEventListener("resize", onResize);
    window.removeEventListener("mousemove", onMouseMove);
    window.removeEventListener("click", onClick);
    controls.dispose();

    // 回收所有球的虚线资源
    allBalls.forEach((obj) => {
      const lines = obj.userData.dashedLines;
      if (!lines) return;

      // // 检查是否存在投影数据
      // if (!obj.userData.dashedLines) return;

      // const { down, back, left, zPlaneProjection } = obj.userData.dashedLines;

      // // 销毁虚线
      // if (down?.line) {
      //   scene.remove(down.line);
      //   down.line.geometry.dispose();
      //   down.line.material.dispose();
      // }

      // // 销毁箭头（back 和 left 方向）
      // [back, left].forEach((dir) => {
      //   if (dir?.line) {
      //     scene.remove(dir.line);
      //     dir.line.geometry.dispose();
      //     dir.line.material.dispose();
      //   }
      //   if (dir?.arrow) {
      //     scene.remove(dir.arrow);
      //     dir.arrow.cone.geometry.dispose();
      //     dir.arrow.cone.material.dispose();
      //     dir.arrow.line.geometry.dispose();
      //     dir.arrow.line.material.dispose();
      //   }
      // });

      // // 销毁 z-y 平面投影（可能是 Group 或 Points）
      // if (zPlaneProjection) {
      //   scene.remove(zPlaneProjection);

      //   // 如果是 Group（包含圆和圆环）
      //   if (zPlaneProjection instanceof THREE.Group) {
      //     zPlaneProjection.children.forEach((child) => {
      //       child.geometry.dispose();
      //       child.material.dispose();
      //     });
      //   }
      //   // 如果是 Points（点精灵）
      //   else if (zPlaneProjection instanceof THREE.Points) {
      //     zPlaneProjection.geometry.dispose();
      //     zPlaneProjection.material.dispose();
      //   }
      // }
      // // 清除 userData 中的引用
      // delete ball.userData.dashedLines;

      Object.values(lines).forEach((l) => {
        // 销毁虚线
        if (l?.line) {
          scene.remove(l.line);
          l.line.geometry.dispose();
          l.line.material.dispose();
          if (l?.arrow) {
            scene.remove(l.arrow);
            l.arrow.cone.geometry.dispose();
            l.arrow.cone.material.dispose();
            l.arrow.line.geometry.dispose();
            l.arrow.line.material.dispose();
          }
        }
        // 销毁 z-y 平面投影（可能是 Group 或 Points）
        // 如果是 Group（包含圆和圆环）
        if (l instanceof THREE.Group) {
          l.children.forEach((child) => {
            child.geometry.dispose();
            child.material.dispose();
          });
        }
        scene.remove(l);
      });
    });

    // 场景资源释放
    scene.traverse((o) => {
      o.geometry?.dispose();
      (Array.isArray(o.material) ? o.material : [o.material]).forEach((m) =>
        m?.dispose()
      );
      o.texture?.dispose?.();
    });
    composer.dispose();
    renderer.dispose();
    element.removeChild(renderer.domElement);
    // element.removeChild(btnContainer);
  }
  //  隐藏/显示 点位小球、虚线、箭头、映射
  function setGroupVisible(groupName, visible) {
    if (styleGroups[groupName]) {
      // 设置组内所有小球的可见性
      styleGroups[groupName].visible = visible;

      // 遍历组内所有小球，设置对应的虚线、箭头和映射的可见性
      styleGroups[groupName].children.forEach((ball) => {
        const lines = ball.userData.dashedLines;

        if (!lines) return;

        // 设置虚线、箭头和映射的可见性
        Object.values(lines).forEach((lineObj) => {
          if (lineObj?.line) lineObj.line.visible = visible;
          if (lineObj?.arrow) lineObj.arrow.visible = visible;
        });

        // 设置 z-y 平面投影的可见性
        if (lines.zPlaneProjection) {
          lines.zPlaneProjection.visible = visible;
        }
      });
    }
  }

  return {
    scene,
    camera,
    renderer,
    css3DRenderer,
    dispose, //  销毁 模型
    getGroups: () => styleGroups, //  获取 模型 分组点位
    collapsedXY: () => {
      return setCollapsedXY();
    },
    collapsedXZ: () => {
      return setCollapsedXZ();
    },
    //  添加 模型 分组点位
    addPoint: (groupName, position, modelIndex) => {
      if (!styleGroups[groupName]) {
        styleGroups[groupName] = new THREE.Group();
        scene.add(styleGroups[groupName]);
        styleGroups[groupName].name = groupName;
      }
      return addSinglePoint(
        styleGroups[groupName],
        position,
        modelIndex,
        groupName
      );
    },
    removePoint: removeSinglePoint, // 动态移除点位
    getPointById: getPointById, //  获取点位对象

    //   模型 分组点位 现隐
    setGroupVisible: (groupName, visible) => {
      return setGroupVisible(groupName, visible);
    },
  };
}


//  创建坐标轴
async function addPositiveAxes(scene, sizeX, sizeY, sizeZ) {

  // 创建X轴（红色，只显示正向）
  const xAxis = new THREE.ArrowHelper(
    new THREE.Vector3(1, 0, 0), // 方向
    new THREE.Vector3(0, 0, 0), // 起点
    sizeX + 10, // 长度
    0xf6b501, // 颜色（黄色）
    2, // 头部长度
    1 // 头部宽度
  );
  scene.add(xAxis);
  // 创建Y轴（绿色，只显示正向）
  const yAxis = new THREE.ArrowHelper(
    new THREE.Vector3(0, 1, 0),
    new THREE.Vector3(0, 0, 0),
    sizeY + 10,
    0xd9ebff, // 颜色（色）
    2,
    1
  );
  scene.add(yAxis);
  // 创建Z轴（蓝色，只显示正向）
  const zAxis = new THREE.ArrowHelper(
    new THREE.Vector3(0, 0, 1),
    new THREE.Vector3(0, 0, 0),
    sizeZ + 10,
    0x88f0ee, // 颜色（蓝色）
    2,
    1
  );
  scene.add(zAxis);

}
