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

news2024/9/23 11:18:04

空间坐标点绘制多边形,实际上可以理解为是由 “点” 到 “线” 到 “面” 的一个过程。将空间坐标点通过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;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1993748.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全国多地公布2024下半年软考报名具体时间

下半年开考科目&#xff1a; 高级资格&#xff1a;系统分析师、系统架构设计师、网络规划设计师、系统规划与管理师 中级资格&#xff1a;软件设计师、网络工程师、信息安全工程师、信息系统监理师、多媒体应用设计师、系统集成项目管理工程师 初级资格&#xff1a;网络管理…

【时时三省】(C语言基础)操作符2

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 赋值操作符 它可以重新赋值 示例: 使用赋值操作符赋值 复合赋值符 &#xff0b;&#xff1d; -&#xff1d; &#xff0a;&#xff1d; /&#xff1d; &#xff05;&#xff1d; &g…

Linux磁盘管理_LVM逻辑卷_SWAP交换分区_Centos-LVM格式磁盘扩容

目录 一、基本磁盘管理1.1 创建分区1.2 创建文件系统1.3 挂载mount1.4 查看挂载信息1.5 重启失效解决方式 二、逻辑卷LVM2.1 LVM2.2 创建LVM2.3 扩大卷组VG2.4 命令汇总 三、交换分区SWAP管理3.1 SWAP3.2 查看swap3.3 增加交换分区 四、Centos调整分区&#xff0c;在线调整分区…

05 数据类型

目录 分类数值类型小数类型字符串类型日期和时间类型集合类型 1. 分类 2. 数值类型 tinyint create table t1 (num tinyint); insert into t1 values (1); insert into t1 values (128); – 越界插入&#xff0c;报错 select * from t1; 说明: 在mysql中&#xff0c;整形可以指…

LeetCode面试150——14最长公共前缀

题目难度&#xff1a;简单 默认优化目标&#xff1a;最小化平均时间复杂度。 Python默认为Python3。 目录 1 题目描述 2 题目解析 3 算法原理及代码实现 3.1 横向扫描 3.2 纵向扫描 3.3 分治 3.4 二分查找 参考文献 1 题目描述 编写一个函数来查找字符串数组中的最长…

MyBatis 基本操作 - 注解版

目录 一&#xff0c;查询 - select 1.1 全列查询 1.2 指定列查询 1.3 赋值问题 方法一&#xff1a;起别名 方法二&#xff1a;结果映射 方法三&#xff1a;添加配置 二&#xff0c;新增 - Insert 2.1 使用对象插入 2.2 获取主键 三&#xff0c;删除 - Delete 四&am…

使用Gitlab实现monorepo多项目CICD

CI/CD是什么 CI/CD&#xff08;Continuous Intergration/Continuous Delpoy&#xff09;&#xff0c;即持续集成/持续部署&#xff0c;或称为持续集成/持续交付&#xff0c;作为一套面向开发和运维团队的解决方案&#xff0c;CI/CD 主要解决集成新代码和向用户频繁交付应用的问…

SQL注入实例(sqli-labs/less-22)

0、初始页面 1、确定闭合字符 闭合字符为单引号双引号 2、爆库名 3、爆表名 4、爆列名 5、查询最终目标

初识redis:String类型

在Redis中的所有key都是字符串&#xff0c;而value的类型是存在差异的。本文介绍的就是value中的string类型。 首先要知道&#xff0c;Redis中的字符串&#xff0c;直接就是按照二进制数据的方式存储的&#xff0c;不会做任何的编码转换。也就是说&#xff0c;redis不仅仅可以…

JMeter——异步请求性能测试

前段时间任务要求要对一种异步请求做性能测试&#xff0c;异步请求步骤如下&#xff1a; step1: 发一个数据计算的请求&#xff0c;response里面返回一个jobId step2: 带上这个jobId&#xff0c;就可以实时查看这个请求返回的jobStatus, 如果jobStatus0, 则成功返回计算结果&…

29_反序列化漏洞、反序列化概念、反序列化原理、反序列化漏洞防御、序列化

概念 序列化和反序列化 序列化 将对象型转换成字符串的过程。 反序列化 将字符串还原成对象型的过程。 反序列化漏洞&#xff08;了解&#xff09; 便于传输和存储 接下来上代码进行测试&#xff0c;先搞个类&#xff0c; <?phpheader("content-type:text/html;…

基于SpringBoot+Vue的校园失物招领系统(带1w+文档)

基于SpringBootVue的校园失物招领系统(带1w文档) 基于SpringBootVue的校园失物招领系统(带1w文档) 本课题研发的校园失物招领系统管理系统&#xff0c;就是提供校园失物招领系统信息处理的解决方案&#xff0c;它可以短时间处理完信息&#xff0c;并且这些信息都有专门的存储设…

51单片机个人学习笔记16(红外遥控)

前言 本篇文章属于STC89C52单片机&#xff08;以下简称单片机&#xff09;的学习笔记&#xff0c;来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记&#xff0c;只能做参考&#xff0c;细节方面建议观看视频&#xff0c;肯定受益匪浅。 [1-1] 课程简介_哔哩…

Qt编程技巧小知识点(2)GPIB缓存区数据读取

文章目录 Qt编程技巧小知识点&#xff08;2&#xff09;GPIB缓存区数据读取小结 Qt编程技巧小知识点&#xff08;2&#xff09;GPIB缓存区数据读取 大端小端的问题&#xff0c;其主要表现如下例子&#xff1a; 例如&#xff1a;输入为QByteArray str "#14M\xB6q\xC1\n&qu…

ImageNet_2014数据集下载与解压

前言 最近在配OpenLongTailRecognition-OLTR代码用的ImageNet_2014&#xff0c;因为数据集较大的原因&#xff0c;导致下载和数据集配置一直被耽误&#xff0c;进度很满&#xff0c;故此记录&#xff0c;以背不时只用。 进入imageNet主页 注: 需要注册账号&#xff0c;教育邮箱…

【论文阅读】MobileNetV4 - Universal Models for the Mobile Ecosystem

文章目录 摘要一、介绍二、相关工作三、与硬件无关的帕累托效率四、通用倒置瓶颈五、移动MQA六、MNv4模型的设计6.1 为增强的体系结构改进NAS6.2 MNv4模型的优化 7. 结果7.1 ImageNet分类7.2 COCO目标检测 8. 强化蒸馏配方9. 结论 MobileNetV4 - 移动生态系统的通用模型 摘要 …

linux系统编程:(4)

1.系统时间的获取函数 1. time函数 功能: 获得1970年到现在的秒数 参数: t:存放秒数的空间首地址 返回值: 成功返回1970年到现在的秒数 失败返回-1 2.localtime 函数 功能: 将一个秒数转化成日历时间 参数: timep:保存秒数空间的地址 返回值: 成功…

Node.js异步编程

【图书介绍】《Node.jsMongoDBVue.js全栈开发实战》-CSDN博客 《Node.jsMongoDBVue.js全栈开发实战&#xff08;Web前端技术丛书&#xff09;》(邹琼俊)【摘要 书评 试读】- 京东图书 (jd.com) 本节主要介绍Node.js异步编程的相关内容。内容包括 同步API、异步API、同步API与…

拿捏!远程观影之详细操作教程

碎碎念 相信不少小伙伴是有收藏影片的&#xff0c;时不时会取出来进行观看。大多时候&#xff0c;我们都是在局域网中观影&#xff0c;局域网中是直连&#xff0c;所以可以一直流畅进行观影&#xff0c;但是有不少朋友是有远程观影需求的&#xff0c;那么怎么实现能随时在手机…

squidpy学习总结

下载安装 首先不要使用pip install squidpy[interactive] 安装&#xff0c;因为我在base环境里python版本是python3.11.5, 导致安装narapi包的时候出现问题&#xff0c;所以我选择的办法是 conda create -n sp_env python3.9.12 注意这个有个问题&#xff0c;我的mac为啥建立不…