three.js 3D球体树状结构的实现

3D球体树状结构的实现

1.场景
// 场景
const createScene = () => {
    return new THREE.Scene();
};
2.相机
// 相机(透视投影相机)
const createPerspectiveCamera = ({ fov, aspect, near, far }) => {
    /* 
        fov — 摄像机视锥体垂直视野角度
        aspect — 摄像机视锥体长宽比
        near — 摄像机视锥体近端面
        far — 摄像机视锥体远端面
    */
    return new THREE.PerspectiveCamera(fov, aspect, near, far);
};
3.渲染器
// 渲染器
const createWebGLRenderer = ({ dom, width, height }) => {
    /* 
        renderDom — dom
        width — 渲染宽度 一般取domclientWidth
        height — 渲染高度 一般取clientHeight
    */
    if (width === undefined) {
        width = dom.clientWidth;
    }
    if (height === undefined) {
        height = dom.clientHeight;
    }
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    dom.appendChild(renderer.domElement);

    return renderer;
};
4.辅助线
// 辅助线
const createAxesHelper = (length) => {
    return new THREE.AxesHelper(length);
    //return new THREE.AxisHelper(length);
};
5.环境光
// 环境光
const createAmbientLight = ({ color, intensity }) => {
    // color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
    // intensity - (可选参数) 光照强度。 缺省值 1。
    return new THREE.AmbientLight(color, intensity);
};
6.电光
// 点光
const createPointLight = ({ color, intensity, distance, decay }) => {
    /*
        color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
        intensity - (可选参数) 光照强度。 缺省值 1。
        distance - 这个距离表示从光源到光照强度为0的位置。 当设置为0时,光永远不会消失(距离无穷大)。缺省值 0.
        decay - 沿着光照距离的衰退量。缺省值 2。
    */
    return new THREE.PointLight(color, intensity, distance, decay);
};
7.球形几何体
// 球形几何体
const createSphereGeometry = ({
    radius,
    widthSegments,
    heightSegments,
    phiStart,
    phiLength,
    thetaStart,
    thetaLength,
}) => {
    /*
        radius — 球体半径,默认为1。
        widthSegments — 水平分段数(沿着经线分段),最小值为3,默认值为32。
        heightSegments — 垂直分段数(沿着纬线分段),最小值为2,默认值为16。
        phiStart — 指定水平(经线)起始角度,默认值为0。。
        phiLength — 指定水平(经线)扫描角度的大小,默认值为 Math.PI * 2。
        thetaStart — 指定垂直(纬线)起始角度,默认值为0。
        thetaLength — 指定垂直(纬线)扫描角度大小,默认值为 Math.PI。
    */
    return new THREE.SphereGeometry(
        radius,
        widthSegments,
        heightSegments,
        phiStart,
        phiLength,
        thetaStart,
        thetaLength
    );
};
8.球形几何体的材质
// 球形几何体的材质
const createMeshLambertMaterial = (data) => {
    return new THREE.MeshLambertMaterial(data);
};
9.球形背景几何体的材质
// 球形背景几何体的材质
const createMeshPhongMaterialTexture = (data) => {
    let materialData = {
        map: new THREE.TextureLoader().load(data.url)
    }
    if(data.side){
        //materialData.side = THREE.BackSide
        materialData.side = THREE.DoubleSide
    }
    return new THREE.MeshPhysicalMaterial(materialData);
}
10.// 线几何体
// 线几何体
const createLineGeometry = (points) => {
    const pointsVector3 = [];
    for (let i = 0; i < points.length; i++) {
        pointsVector3.push(
            new THREE.Vector3(points[i].x, points[i].y, points[i].z)
        );
    }
    const geometry = new THREE.BufferGeometry().setFromPoints(pointsVector3);
    const line = new MeshLine();
    line.setGeometry(geometry);
    return line;
};
11.线几何体的材质
// 线几何体的材质
const createMeshLineMaterial = (data) => {
    return new MeshLineMaterial({
        lineWidth: data.linewidth,
        color: data.color || "white",
        dashArray: data.dashArray || 0,
        transparent: true,
    });
};
12.物体
// 物体
const createMesh = (geometry, materialBasic) => {
    return new THREE.Mesh(geometry, materialBasic);
};
13.文本
// 文本
const createText = ({ text, size, color }) => {
    let textClass = new SpriteText(text, size);
    textClass.color = color;
    return textClass;
};
14.轨道控制
// 轨道控制
const createControl = (camera, dom) => {
    return new THREE.OrbitControls(camera, dom);
};
15.完整代码
// 场景
const createScene = () => {
    return new THREE.Scene();
};
// 相机(透视投影相机)
const createPerspectiveCamera = ({ fov, aspect, near, far }) => {
    /* 
        fov — 摄像机视锥体垂直视野角度
        aspect — 摄像机视锥体长宽比
        near — 摄像机视锥体近端面
        far — 摄像机视锥体远端面
    */
    return new THREE.PerspectiveCamera(fov, aspect, near, far);
};
// 渲染器
const createWebGLRenderer = ({ dom, width, height }) => {
    /* 
        renderDom — dom
        width — 渲染宽度 一般取domclientWidth
        height — 渲染高度 一般取clientHeight
    */
    if (width === undefined) {
        width = dom.clientWidth;
    }
    if (height === undefined) {
        height = dom.clientHeight;
    }
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    dom.appendChild(renderer.domElement);

    return renderer;
};
// 辅助线
const createAxesHelper = (length) => {
    return new THREE.AxesHelper(length);
    //return new THREE.AxisHelper(length);
};
// 环境光
const createAmbientLight = ({ color, intensity }) => {
    // color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
    // intensity - (可选参数) 光照强度。 缺省值 1。
    return new THREE.AmbientLight(color, intensity);
};
// 点光
const createPointLight = ({ color, intensity, distance, decay }) => {
    /*
        color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
        intensity - (可选参数) 光照强度。 缺省值 1。
        distance - 这个距离表示从光源到光照强度为0的位置。 当设置为0时,光永远不会消失(距离无穷大)。缺省值 0.
        decay - 沿着光照距离的衰退量。缺省值 2。
    */
    return new THREE.PointLight(color, intensity, distance, decay);
};

// 球形几何体缓冲器
const createSphereBufferGeometry = ({
    radius,
    widthSegments,
    heightSegments,
    phiStart,
    phiLength,
    thetaStart,
    thetaLength,
}) => {
    return new THREE.SphereBufferGeometry(
        radius,
        widthSegments,
        heightSegments,
        phiStart,
        phiLength,
        thetaStart,
        thetaLength
    );
};


// 球形几何体
const createSphereGeometry = ({
    radius,
    widthSegments,
    heightSegments,
    phiStart,
    phiLength,
    thetaStart,
    thetaLength,
}) => {
    /*
        radius — 球体半径,默认为1。
        widthSegments — 水平分段数(沿着经线分段),最小值为3,默认值为32。
        heightSegments — 垂直分段数(沿着纬线分段),最小值为2,默认值为16。
        phiStart — 指定水平(经线)起始角度,默认值为0。。
        phiLength — 指定水平(经线)扫描角度的大小,默认值为 Math.PI * 2。
        thetaStart — 指定垂直(纬线)起始角度,默认值为0。
        thetaLength — 指定垂直(纬线)扫描角度大小,默认值为 Math.PI。
    */
    return new THREE.SphereGeometry(
        radius,
        widthSegments,
        heightSegments,
        phiStart,
        phiLength,
        thetaStart,
        thetaLength
    );
};

// 球形几何体的材质
const createMeshLambertMaterial = (data) => {
    return new THREE.MeshLambertMaterial(data);
};
// 球形背景几何体的材质
const createMeshPhongMaterialTexture = (data) => {
    let materialData = {
        map: new THREE.TextureLoader().load(data.url)
    }
    if(data.side){
        //materialData.side = THREE.BackSide
        materialData.side = THREE.DoubleSide
    }
    return new THREE.MeshPhysicalMaterial(materialData);
}
// 线几何体
const createLineGeometry = (points) => {
    const pointsVector3 = [];
    for (let i = 0; i < points.length; i++) {
        pointsVector3.push(
            new THREE.Vector3(points[i].x, points[i].y, points[i].z)
        );
    }
    const geometry = new THREE.BufferGeometry().setFromPoints(pointsVector3);
    const line = new MeshLine();
    line.setGeometry(geometry);
    return line;
};
// 线几何体的材质
const createMeshLineMaterial = (data) => {
    return new MeshLineMaterial({
        lineWidth: data.linewidth,
        color: data.color || "white",
        dashArray: data.dashArray || 0,
        transparent: true,
    });
};
// 物体
const createMesh = (geometry, materialBasic) => {
    return new THREE.Mesh(geometry, materialBasic);
};
// 文本
const createText = ({ text, size, color }) => {
    let textClass = new SpriteText(text, size);
    textClass.color = color;
    return textClass;
};
// 轨道控制
const createControl = (camera, dom) => {
    return new THREE.OrbitControls(camera, dom);
};

// 3d树布局算法 灵感来源 梯形/三角形转换为圆锥
const computedDataWeight = (data) => {
    let weight = 0;
    for (let i = 0; i < data.length; i++) {
        let item = data[i];
        if (item ?.children ?.length) {
            item.weight = computedDataWeight(item.children);
            weight += item.weight;
        } else {
            item.weight = 1;
            weight += 1;
        }
    }
    return weight;
};
const computedArcRArr = (data, pointInterval) => {
    let ArcRArr = [];
    let ArcData = [];
    let ArcWeight = [];
    formatTreeToArcData(data, ArcData);

    for (let i = 0; i < ArcData.length; i++) {
        let item = ArcData[i];
        let weight = 0;
        for (let j = 0; j < item.length; j++) {
            weight += item[j].weight;
        }
        ArcWeight.push(weight);
    }
    let R = computedArcR(pointInterval, ArcWeight[0]);

    // 半径算法
    for (let i = 0; i < ArcData.length; i++) {
        let item = ArcData[i];
        if (ArcWeight[i] < ArcWeight[0]) {
            // 不是完全层
            ArcRArr.push(R);
        } else {
            if (item.length > 1) {
                // 完全层
                let DValue = 0;
                item.forEach((weight) => {
                    DValue += Math.floor(weight.weight / 2);
                });
                ArcRArr.push(((ArcWeight[i] - DValue) / ArcWeight[i]) * R);
            } else {
                ArcRArr.push(0);
            }
        }
    }
    return { ArcRArr, R };
};
const formatTreeToArcData = (data, ArcData, deep = 0) => {
    data.forEach((element) => {
        if (!ArcData[deep]) {
            ArcData[deep] = [];
        }
        ArcData[deep].push({
            label: element.label,
            point_uuid: element.point_uuid,
            weight: element.weight,
        });
        if (element?.children?.length) {
            formatTreeToArcData(element?.children, ArcData, deep + 1);
        }
    });
};
const computedArcR = (pointInterval, points) => {
    if (points === 1) {
        return pointInterval * 2;
    }
    let arcR =
        pointInterval /
        2 /
        Math.cos((Math.PI / 180) * (((points - 2) * 180) / points / 2));
    if (arcR < pointInterval) {
        arcR = pointInterval * 2;
    }
    return arcR;
};
const computedTreeStyleAuto = (style, ArcRArr, R) => {
    if (style.yr === "auto") {
        style.yr = ArcRArr.length === 1 ? R : R / (ArcRArr.length - 1);
    }
    style.startPositionY =
        ((ArcRArr.length - 1) / 2) * style.yr + style.centerXYZ[1];
};
const computedPointPosition = (
    data,
    style,
    ArcRArr,
    startAngle = 0,
    endAngle = Math.PI * 2,
    deep = 0
) => {
    let totalWight = 0;
    for (let i = 0; i < data.length; i++) {
        totalWight += data[i].weight;
    }
    let AngleScope = endAngle - startAngle;
    let curAngle = startAngle;
    let randTranslate = ArcRArr[deep] * 10 || 1000
    for (let i = 0; i < data.length; i++) {
        let item = data[i];
        let ratioAngle = (item.weight / totalWight) * AngleScope;
        item.targetPostion = {
            x: Math.sin(curAngle + ratioAngle / 2) * ArcRArr[deep] + style.centerXYZ[0],
            y: style.startPositionY - deep * (style.yr || 0) + style.centerXYZ[1],
            z: Math.cos(curAngle + ratioAngle / 2) * ArcRArr[deep] + style.centerXYZ[2]
        }
        item.translatePostion = {
            y: rand(-randTranslate, randTranslate),
            x: rand(-randTranslate, randTranslate),
            z: rand(-randTranslate, ArcRArr[deep])
        }
        if (item ?.children ?.length) {
            computedPointPosition(
                item ?.children,
                style,
                ArcRArr,
                curAngle,
                curAngle + ratioAngle,
                deep + 1
            );
        }
        curAngle += ratioAngle;
    }
};

// 计算camera初始位置
const computedCameraStyle = (style, dom, treeStyle, R) => {
    if (style.position === "auto") {
        style.position = {
            x: 0,
            y: treeStyle.yr * 1.5,
            z: R * 3,
        };
    }
    if (style.data === "auto") {
        style.data = {
            fov: 45,
            aspect: dom.clientWidth / dom.clientHeight,
            near: 0.1,
            far: R * 1000,
        };
    }
    if (style.lookAt === "auto") {
        style.lookAt = JSON.parse(JSON.stringify(treeStyle.centerXYZ));
    }
};

// 计算
const computedBjMeshStyleAuto = (style, R) => {
    style.geometry.radius = R 
}

// 随机数
const rand = (n, m) => {
    const c = m - n + 1
    return Math.floor(Math.random() * c + n)
}

// randTree
const randTree = (deep = 0, maxDeep = 3, parentName) => {
    let tree = []
    let length = rand(1,3)
    for(let i = 0; i < length; i++){
        let data = {
            name: parentName ? parentName + '-' + i : i + ''
        }
        if(rand(0,2) && deep < maxDeep){
            data.children = randTree(deep + 1, maxDeep, data.name)
        }
        tree.push(data)
    }
    return tree
}

const originTreeStyle = {
    centerXYZ: [0, 0, 0],
    yr: "auto",
    pointInterval: 10,
};
let treeStyle = null
const originCameraStyle = {
    position: "auto",
    data: "auto",
    lookAt: "auto",
};
let cameraStyle = null
const bjMeshStyle = {
    geometry: {
        radius: 'auto',
        widthSegments: 320,
        heightSegments: 160
    },
    material: {
        url: "./sphere-bg2.jpg",
        side: true
    }
}
const sphereMeshStyle = {
    geometry: {
        radius: 1,
        widthSegments: 320,
        heightSegments: 160,
    },
    material: {
        //url: "./sphere-bg2.jpg",
        //side: true
        color: "#ffffff",
        wireframe: false, //是否将几何体渲染为线框,默认值为false(即渲染为平面多边形)
    },
};
const lineMeshStyle = {
    material: {
        color: "#ffffff",
        linewidth: 0.3,
    },
};
const textMeshStyle = {
    material: {
        size: 0.5,
        //color: "#ffffff",
        color: "#0080ff",
    },
};

let scene = null;
let camera = null;
let renderer = null;
let control = null;
let bjMesh = null;
let axes = null;
let ambientLight = null;
let pointLight = null;
let animationIndex = 0
let targetAnimationIndex = 50
let sphereGeometrys = [];
let textGeometrys = [];
let lineGeometrys = [];
let old_sphereGeometrys = [];
let old_textGeometrys = [];
let cameraAnimationIndex = 0
let targetCameraAnimationIndex = 50

const init = (rendererDom, data) => {
    treeStyle = JSON.parse(JSON.stringify(originTreeStyle))
    cameraStyle = JSON.parse(JSON.stringify(originCameraStyle))

    computedDataWeight(data);
    let { ArcRArr, R } = computedArcRArr(data, treeStyle.pointInterval);
    computedTreeStyleAuto(treeStyle, ArcRArr, R);
    computedPointPosition(data, treeStyle, ArcRArr);

    computedCameraStyle(cameraStyle, rendererDom, treeStyle, R);

    if(!scene){
        scene = createScene();
    }
    if(!camera){
        camera = createPerspectiveCamera(cameraStyle.data);
        camera.position.set(
            cameraStyle.position.x,
            cameraStyle.position.y,
            cameraStyle.position.z
        );
    }
    camera.translate_camera_position = {
        x: camera.position.x - cameraStyle.position.x,
        y: camera.position.y - cameraStyle.position.y,
        z: camera.position.z - cameraStyle.position.z,
    }
    camera.camera_position = cameraStyle.position
    camera.init_camera_position = JSON.parse(JSON.stringify(cameraStyle.position))
    if(!renderer){
        renderer = createWebGLRenderer({
            dom: rendererDom,
        });
    }

    if(!control){
        control = createControl(camera, rendererDom);
    }

    //if(!bjMesh){
    //    computedBjMeshStyleAuto(bjMeshStyle, R * 12);
    //    const bjGeometry = createSphereGeometry(bjMeshStyle.geometry);
    //    const bjMaterial = createMeshPhongMaterialTexture(bjMeshStyle.material);
    //    bjMesh = createMesh(bjGeometry, bjMaterial);
    //    bjMesh.position.set(
    //        cameraStyle.lookAt[0],
    //        cameraStyle.lookAt[1],
    //        cameraStyle.lookAt[2]
    //    );
    //    scene.add(bjMesh);
    //}

    //显示辅助坐标系
    if(!axes){
        axes = createAxesHelper(R);
        //scene.add(axes);
    }

    if(!ambientLight){
        ambientLight = createAmbientLight({ color: "#fff", intensity: 0.2 });
        scene.add(ambientLight);
    }

    if(!pointLight){
        pointLight = createPointLight({ color: "#fff", intensity: 1 });
        let pointLightL = R * 10
        pointLight.rotateSpeed =  Math.PI / 100;
        pointLight.lightAngle = Math.atan(1);
        pointLight.arcR = Math.sqrt(pointLightL * pointLightL * 2);
        pointLight.init_position = {
            x: pointLightL,
            y: pointLightL,
            z: pointLightL,
        }
        pointLight.position.set(pointLightL, pointLightL, pointLightL);
        scene.add(pointLight);
    }

    animationIndex = 0
    cameraAnimationIndex = 0
    old_sphereGeometrys = [...sphereGeometrys];
    old_textGeometrys = [...textGeometrys];
    lineGeometrys.forEach(item => {
        scene.remove(item);
    })
    sphereGeometrys = [];
    textGeometrys = [];
    lineGeometrys = [];

    let geometry = createSphereGeometry(sphereMeshStyle.geometry);
    let material = createMeshLambertMaterial(sphereMeshStyle.material);
    initGeometrys(data, geometry, material, sphereGeometrys, textGeometrys, lineGeometrys);
    geometry.dispose();
    material.dispose();
    
    scene.add(...sphereGeometrys);
    scene.add(...textGeometrys);

    render();
};
const initGeometrys = (data, geometry, material, sphereGeometrys, textGeometrys, lineGeometrys, parentPosition) => {

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

        const mesh = createMesh(geometry, material);
        mesh.position.set(item.targetPostion.x + item.translatePostion.x, item.targetPostion.y + item.translatePostion.y, item.targetPostion.z + item.translatePostion.z);
        mesh.originData = item
        mesh.scale.set(1.3, 1.3, 1.3);
        sphereGeometrys.push(mesh);

        const text = createText({
            text: item.name,
            size: textMeshStyle.material.size,
            color: textMeshStyle.material.color,
        });
        text.position.x = item.targetPostion.x + item.translatePostion.x;
        text.position.y = item.targetPostion.y + item.translatePostion.y + sphereMeshStyle.geometry.radius * 2;
        text.position.z = item.targetPostion.z + item.translatePostion.z;
        textGeometrys.push(text);

        if (parentPosition) {
            const lineGeometry = createLineGeometry([
                parentPosition,
                { x: item.targetPostion.x, y: item.targetPostion.y, z: item.targetPostion.z },
            ]);
            const lineMaterial = createMeshLineMaterial(lineMeshStyle.material);
            const lineMesh = createMesh(lineGeometry, lineMaterial);
            lineGeometrys.push(lineMesh);
            lineGeometry.dispose();
            lineMaterial.dispose();
        }

        if (item?.children?.length) {
            initGeometrys(
                item.children,
                geometry,
                material,
                sphereGeometrys,
                textGeometrys,
                lineGeometrys,
                { x: item.targetPostion.x, y: item.targetPostion.y, z: item.targetPostion.z }
            );
        }
    }
    
};

// 渲染
const render = () => {
    stats.update()
    //添加旋转灯光特效
    rotate()
    pointAnimation()
    cameraAnimation()
    //循环调用
    requestAnimationFrame(render);
    renderer.render(scene, camera);
};
const resetPointLightPosition = () => {
    pointLight.position.set(pointLight.init_position.x, pointLight.init_position.y, pointLight.init_position.z);
    pointLight.lightAngle = Math.atan(1);
}
const rotate = (angle) => {
    if(pointLight.rotateFlag){
        return
    }
    pointLight.lightAngle += angle || pointLight.rotateSpeed
    pointLight.lightAngle = pointLight.lightAngle % (Math.PI * 2)
    pointLight.position.set(Math.sin(pointLight.lightAngle) * pointLight.arcR, pointLight.position.y, Math.cos(pointLight.lightAngle) * pointLight.arcR);
}
const pointAnimation = () => {
    if(animationIndex === targetAnimationIndex){
        return
    }
    animationIndex++
    let scale = (targetAnimationIndex - animationIndex) / targetAnimationIndex
    let scale2 = animationIndex / targetAnimationIndex
    sphereGeometrys.forEach((item, index) => {
        let data = {
            x: item.originData.targetPostion.x + item.originData.targetPostion.x * scale,
            y: item.originData.targetPostion.y + item.originData.targetPostion.y * scale,
            z: item.originData.targetPostion.z + item.originData.targetPostion.z * scale
        }
        item.position.set(data.x, data.y, data.z);
        textGeometrys[index].position.set(data.x, data.y + sphereMeshStyle.geometry.radius * 2, data.z)
    })
    old_sphereGeometrys.forEach((item, index) => {
        let data = {
            x: item.originData.targetPostion.x + item.originData.targetPostion.x * scale2,
            y: item.originData.targetPostion.y + item.originData.targetPostion.y * scale2,
            z: item.originData.targetPostion.z + item.originData.targetPostion.z * scale2
        }
        item.position.set(data.x, data.y, data.z);
        old_textGeometrys[index].position.set(data.x, data.y + sphereMeshStyle.geometry.radius * 2, data.z)
    })

    if(animationIndex === targetAnimationIndex){
        old_sphereGeometrys.forEach((item,index) => {
            scene.remove(item);
            scene.remove(old_textGeometrys[index]);
        })
        old_sphereGeometrys = []
        old_textGeometrys = []

        if(lineGeometrys.length){
            scene.add(...lineGeometrys);
        }
    }
}
const cameraAnimation = () => {
    if(cameraAnimationIndex === targetCameraAnimationIndex){
        return
    }
    cameraAnimationIndex++
    let scale = (targetCameraAnimationIndex - cameraAnimationIndex) / targetCameraAnimationIndex
    camera.position.set(
        camera.camera_position.x + camera.translate_camera_position.x * scale,
        camera.camera_position.y + camera.translate_camera_position.y * scale,
        camera.camera_position.z + camera.translate_camera_position.z * scale,
    );
    camera.lookAt(
        cameraStyle.lookAt[0],
        cameraStyle.lookAt[1],
        cameraStyle.lookAt[2]
    );
}

const visualAngleChange = (mesh) => {
    let dx = mesh.position.x - cameraStyle.lookAt[0]
    let dy = mesh.position.y - cameraStyle.lookAt[1]
    let dz = mesh.position.z - cameraStyle.lookAt[2]
    if (Math.abs(dx) < 0.001) {
        dx = 0
    }
    if (Math.abs(dy) < 0.001) {
        dy = 0
    }
    if (Math.abs(dz) < 0.001) {
        dz = 0
    }
    let targetPositon = {}
    if (dx === 0 && dy === 0 && dz === 0) {
        targetPositon = {
            x: mesh.position.x + treeStyle.pointInterval,
            y: mesh.position.y + treeStyle.pointInterval,
            z: mesh.position.z + treeStyle.pointInterval,
        }
    } else if (dx === 0 && dy === 0) {
        let dzPointInterval = dz / Math.abs(dz) * treeStyle.pointInterval * 2
        targetPositon = {
            x: mesh.position.x + dzPointInterval,
            y: mesh.position.y + dzPointInterval,
            z: mesh.position.z + dzPointInterval,
        }
    } else if (dy === 0 && dz === 0) {
        let dxPointInterval = dx / Math.abs(dx) * treeStyle.pointInterval * 2
        targetPositon = {
            x: mesh.position.x + dxPointInterval,
            y: mesh.position.y + dxPointInterval,
            z: mesh.position.z + dxPointInterval,
        }
    } else if (dx === 0 && dz === 0) {
        let dyPointInterval = dy / Math.abs(dy) * treeStyle.pointInterval * 2
        targetPositon = {
            x: mesh.position.x + dyPointInterval,
            y: mesh.position.y + dyPointInterval,
            z: mesh.position.z + dyPointInterval,
        }
    } else {
        let dd = null
        if (dx !== 0) {
            dd = dx
        } else if (dy !== 0) {
            dd = dy
        } else if (dz !== 0) {
            dd = dz
        }
        let translateL = treeStyle.pointInterval * dd / Math.abs(dd)
        let dxdd = dx / dd
        let dydd = dy / dd
        let dzdd = dz / dd
        let maxdd = dxdd > dydd ? dxdd : dydd
        maxdd = maxdd > dzdd ? maxdd : dzdd
        if (Math.abs(maxdd) > 1) {
            let scale = Math.abs(maxdd) / 1
            dxdd = dxdd / scale
            dydd = dydd / scale
            dzdd = dzdd / scale
        }
        targetPositon = {
            x: mesh.position.x + translateL * dxdd,
            y: mesh.position.y + translateL * dydd,
            z: mesh.position.z + translateL * dzdd,
        }
    }
    camera.translate_camera_position = {
        x: camera.position.x - targetPositon.x,
        y: camera.position.y - targetPositon.y,
        z: camera.position.z - targetPositon.z,
    }
    camera.camera_position = JSON.parse(JSON.stringify(targetPositon))
    let length = Math.sqrt(Math.pow(camera.translate_camera_position.x, 2) + Math.pow(camera.translate_camera_position.y, 2) + Math.pow(camera.translate_camera_position.z, 2))
    cameraAnimationIndex = length > treeStyle.pointInterval ? 0 : Math.floor(length / treeStyle.pointInterval * targetCameraAnimationIndex)
    
}
16.init部分——优化前
const init = (rendererDom, data) => {
    treeStyle = JSON.parse(JSON.stringify(originTreeStyle))
    cameraStyle = JSON.parse(JSON.stringify(originCameraStyle))

    computedDataWeight(data);
    let { ArcRArr, R } = computedArcRArr(data, treeStyle.pointInterval);
    computedTreeStyleAuto(treeStyle, ArcRArr, R);
    computedPointPosition(data, treeStyle, ArcRArr);

    computedCameraStyle(cameraStyle, rendererDom, treeStyle, R);

    if(!scene){
        scene = createScene();
    }
    if(!camera){
        camera = createPerspectiveCamera(cameraStyle.data);
        camera.position.set(
            cameraStyle.position.x,
            cameraStyle.position.y,
            cameraStyle.position.z
        );
    }
    camera.translate_camera_position = {
        x: camera.position.x - cameraStyle.position.x,
        y: camera.position.y - cameraStyle.position.y,
        z: camera.position.z - cameraStyle.position.z,
    }
    camera.camera_position = cameraStyle.position
    camera.init_camera_position = JSON.parse(JSON.stringify(cameraStyle.position))
    if(!renderer){
        renderer = createWebGLRenderer({
            dom: rendererDom,
        });
    }

    if(!control){
        control = createControl(camera, rendererDom);
    }

    //if(!bjMesh){
    //    computedBjMeshStyleAuto(bjMeshStyle, R * 12);
    //    const bjGeometry = createSphereGeometry(bjMeshStyle.geometry);
    //    const bjMaterial = createMeshPhongMaterialTexture(bjMeshStyle.material);
    //    bjMesh = createMesh(bjGeometry, bjMaterial);
    //    bjMesh.position.set(
    //        cameraStyle.lookAt[0],
    //        cameraStyle.lookAt[1],
    //        cameraStyle.lookAt[2]
    //    );
    //    scene.add(bjMesh);
    //}

    //显示辅助坐标系
    if(!axes){
        axes = createAxesHelper(R);
        //scene.add(axes);
    }

    if(!ambientLight){
        ambientLight = createAmbientLight({ color: "#fff", intensity: 0.2 });
        scene.add(ambientLight);
    }

    if(!pointLight){
        pointLight = createPointLight({ color: "#fff", intensity: 1 });
        let pointLightL = R * 10
        pointLight.rotateSpeed =  Math.PI / 100;
        pointLight.lightAngle = Math.atan(1);
        pointLight.arcR = Math.sqrt(pointLightL * pointLightL * 2);
        pointLight.init_position = {
            x: pointLightL,
            y: pointLightL,
            z: pointLightL,
        }
        pointLight.position.set(pointLightL, pointLightL, pointLightL);
        scene.add(pointLight);
    }

    animationIndex = 0
    cameraAnimationIndex = 0
    old_sphereGeometrys = [...sphereGeometrys];
    old_textGeometrys = [...textGeometrys];
    lineGeometrys.forEach(item => {
        scene.remove(item);
    })
    sphereGeometrys = [];
    textGeometrys = [];
    lineGeometrys = [];
        
    initGeometrys(data, sphereGeometrys, textGeometrys, lineGeometrys);

    scene.add(...sphereGeometrys);
    scene.add(...textGeometrys);

    render();
};
const initGeometrys = (data, sphereGeometrys, textGeometrys, lineGeometrys, parentPosition) => {

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

        const geometry = createSphereGeometry(sphereMeshStyle.geometry);
        const material = createMeshLambertMaterial(sphereMeshStyle.material);
        const mesh = createMesh(geometry, material);
        mesh.position.set(item.targetPostion.x + item.translatePostion.x, item.targetPostion.y + item.translatePostion.y, item.targetPostion.z + item.translatePostion.z);
        mesh.originData = item
        mesh.scale.set(1.3, 1.3, 1.3);
        sphereGeometrys.push(mesh);
        geometry.dispose();
        material.dispose();

        const text = createText({
            text: item.name,
            size: textMeshStyle.material.size,
            color: textMeshStyle.material.color,
        });
        text.position.x = item.targetPostion.x + item.translatePostion.x;
        text.position.y = item.targetPostion.y + item.translatePostion.y + sphereMeshStyle.geometry.radius * 2;
        text.position.z = item.targetPostion.z + item.translatePostion.z;
        textGeometrys.push(text);

        if (parentPosition) {
            const lineGeometry = createLineGeometry([
                parentPosition,
                { x: item.targetPostion.x, y: item.targetPostion.y, z: item.targetPostion.z },
            ]);
            const lineMaterial = createMeshLineMaterial(lineMeshStyle.material);
            const lineMesh = createMesh(lineGeometry, lineMaterial);
            lineGeometrys.push(lineMesh);
            lineGeometry.dispose();
            lineMaterial.dispose();
        }

        if (item ?.children ?.length) {
            initGeometrys(
                item.children,
                sphereGeometrys,
                textGeometrys,
                lineGeometrys,
                { x: item.targetPostion.x, y: item.targetPostion.y, z: item.targetPostion.z }
            );
        }
    }

};

17.init——优化后
const init = (rendererDom, data) => {
    treeStyle = JSON.parse(JSON.stringify(originTreeStyle))
    cameraStyle = JSON.parse(JSON.stringify(originCameraStyle))

    computedDataWeight(data);
    let { ArcRArr, R } = computedArcRArr(data, treeStyle.pointInterval);
    computedTreeStyleAuto(treeStyle, ArcRArr, R);
    computedPointPosition(data, treeStyle, ArcRArr);

    computedCameraStyle(cameraStyle, rendererDom, treeStyle, R);

    if(!scene){
        scene = createScene();
    }
    if(!camera){
        camera = createPerspectiveCamera(cameraStyle.data);
        camera.position.set(
            cameraStyle.position.x,
            cameraStyle.position.y,
            cameraStyle.position.z
        );
    }
    camera.translate_camera_position = {
        x: camera.position.x - cameraStyle.position.x,
        y: camera.position.y - cameraStyle.position.y,
        z: camera.position.z - cameraStyle.position.z,
    }
    camera.camera_position = cameraStyle.position
    camera.init_camera_position = JSON.parse(JSON.stringify(cameraStyle.position))
    if(!renderer){
        renderer = createWebGLRenderer({
            dom: rendererDom,
        });
    }

    if(!control){
        control = createControl(camera, rendererDom);
    }

    //if(!bjMesh){
    //    computedBjMeshStyleAuto(bjMeshStyle, R * 12);
    //    const bjGeometry = createSphereGeometry(bjMeshStyle.geometry);
    //    const bjMaterial = createMeshPhongMaterialTexture(bjMeshStyle.material);
    //    bjMesh = createMesh(bjGeometry, bjMaterial);
    //    bjMesh.position.set(
    //        cameraStyle.lookAt[0],
    //        cameraStyle.lookAt[1],
    //        cameraStyle.lookAt[2]
    //    );
    //    scene.add(bjMesh);
    //}

    //显示辅助坐标系
    if(!axes){
        axes = createAxesHelper(R);
        //scene.add(axes);
    }

    if(!ambientLight){
        ambientLight = createAmbientLight({ color: "#fff", intensity: 0.2 });
        scene.add(ambientLight);
    }

    if(!pointLight){
        pointLight = createPointLight({ color: "#fff", intensity: 1 });
        let pointLightL = R * 10
        pointLight.rotateSpeed =  Math.PI / 100;
        pointLight.lightAngle = Math.atan(1);
        pointLight.arcR = Math.sqrt(pointLightL * pointLightL * 2);
        pointLight.init_position = {
            x: pointLightL,
            y: pointLightL,
            z: pointLightL,
        }
        pointLight.position.set(pointLightL, pointLightL, pointLightL);
        scene.add(pointLight);
    }

    animationIndex = 0
    cameraAnimationIndex = 0
    old_sphereGeometrys = [...sphereGeometrys];
    old_textGeometrys = [...textGeometrys];
    lineGeometrys.forEach(item => {
        scene.remove(item);
    })
    sphereGeometrys = [];
    textGeometrys = [];
    lineGeometrys = [];

    let geometry = createSphereGeometry(sphereMeshStyle.geometry);
    let material = createMeshLambertMaterial(sphereMeshStyle.material);
    
    initGeometrys(data, geometry, material, sphereGeometrys, textGeometrys, lineGeometrys);
    
    geometry.dispose();
    material.dispose();

    scene.add(...sphereGeometrys);
    scene.add(...textGeometrys);

    render();
};
const initGeometrys = (data, geometry, material, sphereGeometrys, textGeometrys, lineGeometrys, parentPosition) => {

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

        const mesh = createMesh(geometry, material);
        mesh.position.set(item.targetPostion.x + item.translatePostion.x, item.targetPostion.y + item.translatePostion.y, item.targetPostion.z + item.translatePostion.z);
        mesh.originData = item
        mesh.scale.set(1.3, 1.3, 1.3);
        sphereGeometrys.push(mesh);

        const text = createText({
            text: item.name,
            size: textMeshStyle.material.size,
            color: textMeshStyle.material.color,
        });
        text.position.x = item.targetPostion.x + item.translatePostion.x;
        text.position.y = item.targetPostion.y + item.translatePostion.y + sphereMeshStyle.geometry.radius * 2;
        text.position.z = item.targetPostion.z + item.translatePostion.z;
        textGeometrys.push(text);

        if (parentPosition) {
            const lineGeometry = createLineGeometry([
                parentPosition,
                { x: item.targetPostion.x, y: item.targetPostion.y, z: item.targetPostion.z },
            ]);
            const lineMaterial = createMeshLineMaterial(lineMeshStyle.material);
            const lineMesh = createMesh(lineGeometry, lineMaterial);
            lineGeometrys.push(lineMesh);
            lineGeometry.dispose();
            lineMaterial.dispose();
        }

        if (item?.children?.length) {
            initGeometrys(
                item.children,
                geometry,
                material,
                sphereGeometrys,
                textGeometrys,
                lineGeometrys,
                { x: item.targetPostion.x, y: item.targetPostion.y, z: item.targetPostion.z }
            );
        }
    }
    
};

18.点击捕获
// 点击回调
        const meshOnClick = (event) => {
            const pointer = new THREE.Vector2();
            pointer.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
            pointer.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;

            let intersects = [];
            let raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(pointer, camera);
            intersects = raycaster.intersectObjects(sphereGeometrys, true);
            if (intersects.length > 0) {
                meshOnClickCb(intersects[0].object)  
            }
        }

        const meshOnClickCb =(mesh)=> {
            console.log("mesh.originData.name:" + mesh.originData.name);
            console.log("mesh.originData.ID:" + mesh.originData.ID);
            document.getElementById('selectedID').value = mesh.originData.ID;
            console.log("test:" + document.getElementById('selectedID').value);
            visualAngleChange(mesh);
            //repeatedGreetings();        
        }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Three.js是一个用于创建和展示3D图形的JavaScript库。它提供了丰富的功能和工具,可以轻松地在Web浏览器中创建各种类型的3D场景,包括曲面。 要使用Three.js实现3D曲面,你可以按照以下步骤进行操作: 1. 引入Three.js库:首先,在你的HTML文件中引入Three.js库。你可以从官方网站(https://threejs.org/)下载最新版本的库文件,并将其包含在你的项目中。 2. 创建场景和相机:使用Three.js创建一个场景和一个相机。场景是所有3D对象的容器,而相机定义了观察者的视角。 3. 创建曲面几何体:使用Three.js的几何体类之一来创建曲面几何体。例如,你可以使用Three.js的`PlaneGeometry`类来创建一个平面曲面,或者使用`SphereGeometry`类来创建一个球体曲面。 4. 创建材质:为曲面几何体创建一个材质。Three.js提供了各种类型的材质,包括基本材质、纹理材质和光线材质等。你可以根据需要选择合适的材质类型,并设置其属性。 5. 创建网格对象:将曲面几何体和材质结合起来,创建一个网格对象。网格对象是Three.js中用于渲染3D对象的基本单元。 6. 添加网格对象到场景:将网格对象添加到场景中,以便在渲染时显示出来。 7. 渲染场景:使用Three.js的渲染器类来渲染场景和相机。将渲染结果显示在HTML页面上。 下面是一个简单的示例代码,演示了如何使用Three.js创建一个平面曲面: ```javascript // 引入Three.js库 import * as THREE from 'three'; // 创建场景和相机 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; // 创建平面曲面几何体 const geometry = new THREE.PlaneGeometry(2, 2); // 创建材质 const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 创建网格对象 const plane = new THREE.Mesh(geometry, material); // 将网格对象添加到场景中 scene.add(plane); // 创建渲染器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 渲染场景和相机 function animate() { requestAnimationFrame(animate); plane.rotation.x += 0.01; plane.rotation.y += 0.01; renderer.render(scene, camera); } animate(); ``` 这是一个简单的示例,你可以根据自己的需求进行修改和扩展。通过使用Three.js的各种几何体和材质,你可以实现各种类型的3D曲面效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值