three.js 空间坐标绘制多边形围栏(结合react)

空间坐标点绘制多边形,实际上可以理解为是由 “点” 到 “线” 到 “面” 的一个过程。将空间坐标点通过THREE.Shape绘制多条线并闭合而得到一个封闭的二维形状平面对象,使用THREE.ShapeGeometryShape对象转换为Geometry对象添加Mesh,最终得到我们想要的多边形几何体。要得到一个围栏就需要添加“墙体”,“墙体”则是通过THREE.BoxGeometry计算偏移角度绘制多个几何体得到。
在这里插入图片描述

使用技术简介

实现一个多边形围栏主要用到THREE.ShapeTHREE.ShapeGeometryTHREE.BoxGeometry

THREE.Shape

Shape是用于创建平面形状的类。Shape可以用来创建一个简单的二维形状,然后使用ShapeGeometry将其转换为可呈现的封闭形状。它可以和ExtrudeGeometryShapeGeometry一起使用,获取点,或者获取三角面。

常用属性

  • uuid: 该类所创建的实例的UUID。自动被指定的,因此它不应当被编辑、更改
  • holes: 表示形状内部的零或多个孔的数组。即表示包含所有内部空洞(也是Shape对象)的数组。默认值是一个空数组。
  • autoClose: 表示路径是否自动关闭的属性。默认false。

常用方法

  • moveTo: 将绘图点的起点移动到一个新的位置(x,y)并在Shape路径的路径中创建一个新的点。
  • lineTo:向Shape路径中添加一条直线,从当前点到新点(x,y)。

THREE.ShapeGeometry

ShapeGeometry是用于从Shape对象创建几何体的类。Shape 对象可以用来创建二维形状,这些形状可以用于创建网格(Mesh)或线框(Line)。

THREE.BoxGeometry

BoxGeometry是用于创建三维立方体的几何体。‌它允许用户指定立方体的宽度、‌高度、‌深度以及这三个方向上的分段数,‌通过这些参数可以灵活地调整立方体的尺寸和细节级别。‌BoxGeometry的构造函数通常包含以下参数:‌

  • width:‌立方体的宽度。‌
  • height:‌立方体的高度。‌
  • depth:‌立方体的深度。‌
  • widthSegments:‌在X轴方向上将立方体的面分成的段数。‌
  • heightSegments:‌在Y轴方向上将立方体的面分成的段数。‌
  • depthSegments:‌在Z轴方向上将立方体的面分成的段数。‌

注意:围栏是一个闭环,初始数据也是实现围栏的基础和关键,必须为一个闭环数据,即数组第一条数据和最后条数据相同。

点数据处理

在three.js中,长度总是从(0, 0, 0)到(x, y, z)的Euclidean distance(欧几里德距离,即直线距离),方向也是从(0, 0, 0)到(x, y, z)的方向。所以我们需要先转换一下。

const points = [
    [5, 0, 2],
    [8, 0, 2],
    [7, 0, 3],
    [6, 0, 3],
    [5, 0, 2]
];
const pointVector = [];
for (let i = 0; i < points.length; i++) {
    const item = points[i];
    pointVector.push(new THREE.Vector3(item[0], item[1], item[2]));
}

将点绘制成一个二维平面

将转换后的空间坐标,通过Shape moveTolineTo绘制多条线,多条线收尾相连,从而得到一个二维平面。

const shape = new THREE.Shape();
shape.moveTo(pointVector[0].x, pointVector[0].z);
for (let i = 1; i < pointVector.length; i++) {
    shape.lineTo(pointVector[i].x, pointVector[i].z);
}
shape.autoClose = true;

将二维平面转成多面几何体

Shape得到的二维平面转成Geometry并设置网格样式。

// 从一个或多个路径形状中创建一个单面多边形几何体。
const shapeGeometry = new THREE.ShapeGeometry(shape, 25);
const shapeMaterial = new THREE.MeshBasicMaterial({
    color: 0xFF0018,
    side: THREE.DoubleSide,
    transparent: true,
    opacity: 0.5
});
const shapeMesh = new THREE.Mesh(shapeGeometry, shapeMaterial);
shapeMesh.rotateX(Math.PI / 2);

绘制围栏

围栏可以理解为给一个房间添加“墙体”。通过BoxGeometry添加多个几何体来代实现多面墙,计算墙体依据多边形的边的偏移角度来实现,从而实现拼凑出围栏的效果。

// 墙体数据处理
const wallArr = [...pointArr];
for (let i = 0; i < wallArr.length; i++) {
    if (i !== wallArr.length - 1) {
        let params = {
            startX: wallArr[i].x,
            endX: wallArr[i + 1].x,
            startZ: wallArr[i].z,
            endZ: wallArr[i + 1].z
        };
        const wallHeight = 1; // 墙体高度,默认1
        // 计算墙体宽度
        const lens = Math.sqrt(Math.pow((Number(params.endZ) - Number(params.startZ)), 2) + Math.pow((Number(params.endX) - Number(params.startX)), 2));
        // 绘制网格模型,设置墙体样式
        const textureLoader = new THREE.TextureLoader();
        const texture = textureLoader.load(wall);
        texture.wrapS = THREE.RepeatWrapping; //水平方向如何包裹
        texture.wrapT = THREE.RepeatWrapping; // 垂直方向如何包裹
        // uv两个方向纹理重复数量、看板中重复数量
        texture.repeat.set(10, 1);
        // 设置偏移 纹理在单次重复时,从一开始将分别在U、V方向上偏移多少。 这个值的范围通常在0.0之间1.0
        texture.offset = new THREE.Vector2(0, 0);
        // 绘制墙体
        const box = new THREE.BoxGeometry(lens, wallHeight, 0); //- 墙体参数 墙体高度1
        const material = new THREE.MeshBasicMaterial({
            map: texture,
            transparent: true,
            opacity: 1
        });
        const mesh = new THREE.Mesh(box, material);
        // 设置单面墙体位置
        const posx = (params.endX + params.startX) / 2;
        const posz = (params.endZ + params.startZ) / 2;
        mesh.position.set(posx, points[0][1] + (wallHeight / 2), posz);
        // 设置墙体旋转角度
        const rotate = -Math.atan2((params.endZ - params.startZ), (params.endX - params.startX));
        mesh.rotation.y = rotate;
        // 将墙体添加到场景中
        scene.add(mesh);
    }
}

完整实例代码

注意:我这里的数据是一个闭环,如果你们的数据不是闭环需要处理一下,否者平面墙体都没有闭合就达不到围栏效果。

import React, { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
const wall = require('@/static/image/wall.png');
let renderer, controls, scene, camera;

// three.js绘制多边形围栏
const Draw3DHollowCylinder = () => {
    const box = useRef(); // canvas盒子
    // 渲染动画
    function renderFn() {
        requestAnimationFrame(renderFn);
        // 用相机渲染一个场景
        renderer.render(scene, camera);
    }
    /**
     * 绘制墙体
     * @param {墙体坐标点} params
     * @param {区域位于空间y轴位置} yHei
     * @param {几何图形组} group
     */
    function drawPloygonWall(params, yHei, group) {
        const wallHeight = 1; // 墙体高度
        // 长度
        const lens = Math.sqrt(Math.pow((Number(params.endZ) - Number(params.startZ)), 2) + Math.pow((Number(params.endX) - Number(params.startX)), 2));
        // 绘制网格模型,设置墙体样式
        const textureLoader = new THREE.TextureLoader();
        const texture = textureLoader.load(wall);
        texture.wrapS = THREE.RepeatWrapping; //水平方向如何包裹
        texture.wrapT = THREE.RepeatWrapping; // 垂直方向如何包裹
        // uv两个方向纹理重复数量、看板中重复数量
        texture.repeat.set(10, 1);
        // 设置偏移 纹理在单次重复时,从一开始将分别在U、V方向上偏移多少。 这个值的范围通常在0.0之间1.0
        texture.offset = new THREE.Vector2(0, 0);
        // 设置墙体参数,深度为0
        const box = new THREE.BoxGeometry(lens, wallHeight, 0);
        const material = new THREE.MeshBasicMaterial({
            map: texture,
            transparent: true,
            opacity: 1
        });
        const mesh = new THREE.Mesh(box, material);
        // 设置单面墙体位置
        const posx = (params.endX + params.startX) / 2;
        const posz = (params.endZ + params.startZ) / 2;
        mesh.position.set(posx, yHei + (wallHeight / 2), posz);
        // 设置墙体旋转角度
        const rotate = -Math.atan2((params.endZ - params.startZ), (params.endX - params.startX));
        mesh.rotation.y = rotate;
        // 将墙体添加到组中
        group.add(mesh);
    }
    useEffect(() => {
        if (scene) {
            const points = [
                [5, 0, 2],
                [8, 0, 2],
                [7, 0, 3],
                [6, 0, 3],
                [5, 0, 2]
            ];
            const pointArr = [];
            for (let i = 0; i < points.length; i++) {
                const item = points[i];
                pointArr.push(new THREE.Vector3(item[0], item[1], item[2]));
            }

            // 点绘制成线,再到二维平面
            const shape = new THREE.Shape();
            shape.moveTo(pointArr[0].x, pointArr[0].z);
            for (let i = 1; i < pointArr.length; i++) {
                shape.lineTo(pointArr[i].x, pointArr[i].z);
            }
            shape.autoClose = true; // 设置路径自动关闭

            // 从一个或多个路径形状中创建一个多边形几何体。
            const shapeGeometry = new THREE.ShapeGeometry(shape, 25);
            const shapeMaterial = new THREE.MeshBasicMaterial({
                color: 0xFF0018,
                side: THREE.DoubleSide,
                transparent: true,
                opacity: 0.5
            });
            const shapeMesh = new THREE.Mesh(shapeGeometry, shapeMaterial);
            shapeMesh.rotateX(Math.PI / 2);

            const group = new THREE.Group();

            // 墙体数据处理
            const wallArr = [...pointArr];
            for (let i = 0; i < wallArr.length; i++) {
                if (i !== wallArr.length - 1) {
                    let params = {
                        startX: wallArr[i].x,
                        endX: wallArr[i + 1].x,
                        startZ: wallArr[i].z,
                        endZ: wallArr[i + 1].z
                    };
                    // 绘制墙体
                    drawPloygonWall(params, points[0][1], group);
                }
            }
            group.add(shapeMesh);
            scene.add(group);
        }
    }, [scene]);
    // 初始化环境、灯光、相机、渲染器
    useEffect(() => {
        scene = new THREE.Scene();
        // 添加光源
        const ambitlight = new THREE.AmbientLight(0x404040);
        scene.add(ambitlight)
        const sunlight = new THREE.DirectionalLight(0xffffff);
        sunlight.position.set(-20, 1, 1);
        scene.add(sunlight);
        // 获取宽高设置相机和渲染区域大小
        const width = box.current.offsetWidth;
        const height = box.current.offsetHeight;
        const k = width / height;
        // 投影相机
        camera = new THREE.PerspectiveCamera(75, k, 0.1, 1000);
        camera.position.set(1, 0, 25);
        camera.lookAt(scene.position);

        // 创建一个webGL对象
        renderer = new THREE.WebGLRenderer({
            //增加下面两个属性,可以抗锯齿
            antialias: true,
            alpha: true
        });
        renderer.setSize(width, height); // 设置渲染区域尺寸
        renderer.setClearColor(0x000000, 1); // 设置颜色透明度
        // 首先渲染器开启阴影
        renderer.shadowMap.enabled = true;
        box.current.appendChild(renderer.domElement);
        // 监听鼠标事件
        controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;//设置为true则启用阻尼(惯性),默认false
        controls.dampingFactor = 0.05;//值越小阻尼效果越强
        // 渲染
        renderFn();
    }, []);

    return <div className='ui_container_box'>
        three.js绘制3D多边形区域。
        <div style={{ width: '100%', height: '100%' }} ref={box}></div>
    </div>;
}

export default Draw3DHollowCylinder;
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值