web 3d场景构建+three.js+室内围墙,仓库,楼梯,货架模型等,第一人称进入场景案例

翻到了之前的一个案例,基于three.js做的仓库布局模拟,地图元素除了大模型外,其他都是通过JSON数据解析动态生成的,例如墙体,柱子门口,地标等,集成了第一人称的插件可以第一人称进入场景有需要的可以下载看看,对想入门的朋友应该有一些参考价值。

/**

   *创建自定义几何体

   *输入参数几何体底面逆时针坐标组、几何体高度

   * 目前只支持凸多边形 逆时针则连线,顺时针不连线

   */

function createCustomBufferGeometry(planeArr, height, color) {

    let planes = planeArr;

    let planes2 = [];

    //组装顶面坐标

    for (let i = 0; i < planes.length; i++) {

        planes2.push(new THREE.Vector3(planes[i].x, planes[i].y + height, planes[i].z));

    }

    planes = planes.concat(planes2);

    let arr = [];

    //循环组成三角面

    for (let i = 0; i < planes.length; i++) {

        let j = i + 1, k2 = j + planes2.length;

        let xLength = planes2.length;

        if (j >= planes2.length && i < planes2.length) {

            j = 0; k2 = j + planes2.length;

        }

        if (i >= planes2.length) {

            if (j >= planes.length) {

                j = planes2.length;

            }

            k2 = i - planes2.length;

            xLength = planes.length;

        }

        for (let x = i + 2; x < xLength; x++) {

            arr = arr.concat([planes[i].x, planes[i].y, planes[i].z]);

            arr = arr.concat([planes[j].x, planes[j].y, planes[j].z]);

            arr = arr.concat([planes[x].x, planes[x].y, planes[x].z]);

            // if (((planes[j].x - planes[i].x) * (planes[x].z - planes[i].z) - (planes[x].x - planes[i].x) * ( planes[j].z - planes[i].z)) < 0) {

            //     arr = arr.concat([planes[i].x, planes[i].y, planes[i].z]);

            //     arr = arr.concat([planes[j].x, planes[j].y, planes[j].z]);

            //     arr = arr.concat([planes[x].x, planes[x].y, planes[x].z]);

            // }

        }

        arr = arr.concat([planes[i].x, planes[i].y, planes[i].z]);

        if (i < planes2.length) {

            arr = arr.concat([planes[j].x, planes[j].y, planes[j].z]);

            arr = arr.concat([planes[k2].x, planes[k2].y, planes[k2].z]);

        } else {

            arr = arr.concat([planes[k2].x, planes[k2].y, planes[k2].z]);

            arr = arr.concat([planes[j].x, planes[j].y, planes[j].z]);

        }

    }

    let bufferGeometry = new THREE.BufferGeometry();

    let vertices = new Float32Array(arr);

    // itemSize = 3 因为每个顶点都是一个三元组。

    bufferGeometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));

    bufferGeometry.computeFaceNormals();//计算法向量,会对光照产生影响

    bufferGeometry.computeVertexNormals();//自动设置法向量

    let material = new THREE.MeshLambertMaterial({ color: color });

    let mesh = new THREE.Mesh(bufferGeometry, material);

    _bufferGeometry = bufferGeometry;

    //worldScene.add(mesh);

    return mesh;

}

var _bufferGeometry;

/**

    *地图数据坐标是左上角为原点开始的二维坐标系x,y,绘制以左上角开始

    * web 3d坐标原点是屏幕中心点,绘制的时候也是以中心为相对位置

    * MapXLength:地图最长距离,MapZLength 地图最宽距离

    * 转换规则 3d.position.x =  2d.width/2 -  maxWidth/2 +2d.position.x

    * 3d.position.z = 2d.height/2 - maxHeight/2 +2d.position.z;

    * 3d.position.y y轴高度 2D地图无需设置,默认为0,如果有高度, 3d.position.y =  2d.高度.y/2+2d.高度位置+MapYLength/2

    */

function handCoordinate(data) {

    data.x = data.x / 10;

    data.y = data.y / 10;

    data.z = data.z / 10;

    if (data.positionY)

        data.positionY = data.positionY / 10;

    else

        data.positionY = 0;

    data.width = data.width / 10;

    data.height = data.height / 10;

    data.x = data.width / 2 - MapXLength / 2 + data.x;

    data.z = data.height / 2 - MapZLength / 2 + data.z;

    if (data.groupOption) {

        data.groupOption.offSetX = data.groupOption.offSetX / 10;

        data.groupOption.offSetY = data.groupOption.offSetY / 10;

        data.groupOption.offSetZ = data.groupOption.offSetZ / 10;

    }

}

var _baseBox;

var  _floorType;

//最底层的box

function createBaseBox(data,floorType) {

    data.width = data.width / 10;

    data.height = data.height / 10;

    MapXLength = data.width;

    MapZLength = data.height;

    sizeRatio = MapXLength / MapXLength;

    if(data.floorType){

        _floorType = data.floorType;

        if(!floorModels[floorType])

        {

            floorModels[floorType] = [];

        }

    }

    var geometry = new THREE.BoxBufferGeometry(data.width, 1, data.height);

    var material = new THREE.MeshLambertMaterial({ color: data.color });

    var cube = new THREE.Mesh(geometry, material);

    cube.position.set(0, 0, 0);

    //cube.castShadow = true;//开启投影

    cube.receiveShadow = true;//接收阴影

    cube.geometry.computeBoundingBox();

    _baseBox = cube.geometry.boundingBox;

    clickObjects.push(cube);//加入点击对象组

    worldScene.add(cube);

    floorModels[floorType].push(cube);

    //console.log(cube);

    //地图标注

    //  worldScene.add(createTextTextureBySprite(data))

}

//创建几何体

function createBox(data,_floorType) {

   

    handCoordinate(data);

    var geometry = new THREE.BoxGeometry(data.width, data.y, data.height);

    var material = new THREE.MeshLambertMaterial({ color: data.color, vertexColors: THREE.FaceColors });

    var cube = new THREE.Mesh(geometry, material);

    cube.castShadow = true;//开启投影

    //cube.receiveShadow = true;//接收阴影

    cube.position.set(data.x, 0.55 + data.positionY / 2 + data.y / 2, data.z);

    var newMesh;

    //几何体组合处理

    if (data.bspMesh) {

        newMesh = cube;

        data.bspMesh.forEach(x => {

            handCoordinate(x);

            let tempMesh;

            if (x.geometryType == 'box') {

                tempGeometry = new THREE.BoxGeometry(x.width, x.y, x.height);

                tempMesh = new THREE.Mesh(tempGeometry, new THREE.MeshLambertMaterial({ color: x.color }));

            }

            tempMesh.position.set(x.x, 0.55 + x.positionY / 2 + x.y / 2, x.z);

            newMesh = bspMesh(x.type, newMesh, tempMesh);

        })


 

    } else {

    }

    let finalMesh;

    if (newMesh) {

        newMesh.castShadow = true;//开启投影

        // worldScene.add(newMesh);

        finalMesh = newMesh;

    } else {

        finalMesh = cube;

        // worldScene.add(cube);

    }

    //多个相同模型组合

    if (data.type && data.type == 'group') {

        for (let i = 0; i < data.groupOption.total; i++) {

            let tempMesh = finalMesh.clone();

            if (data.groupOption.offSetX != 0) {

                tempMesh.position.x = finalMesh.position.x + (data.width + data.groupOption.offSetX) * i;

            }

            if (data.groupOption.offSetY != 0) {

                tempMesh.position.y = finalMesh.position.y + (data.y + data.groupOption.offSety) * i;

            }

            if (data.groupOption.offSetZ != 0) {

                tempMesh.position.z = finalMesh.position.z + (data.height + data.groupOption.offSetZ) * i;

            }

            // tempMesh.position.set(

            //    ( data.width+tempMesh.position.x+data.groupOption.offSetX)*i,

            //    ( data.y+tempMesh.position.y+data.groupOption.offSetY)*i,

            //     (data.height+tempMesh.position.z+data.groupOption.offSetZ)*i);

            worldScene.add(tempMesh);

            floorModels[_floorType].push(tempMesh);

        }

    } else {

        worldScene.add(finalMesh);

        floorModels[_floorType].push(finalMesh);

    }

    //地图标注

    let sprite = createTextureBySprite(data);

    if (sprite != null)

    worldScene.add(sprite);

}




 

//创建圆柱体

function createCylinder(data) {

    handCoordinate(data);

    var geometry = new THREE.CylinderGeometry(data.width / 2, data.width / 2, data.y, 32);

    var material = new THREE.MeshLambertMaterial({ color: data.color, vertexColors: THREE.FaceColors });

    var cylinder = new THREE.Mesh(geometry, material);

    cylinder.position.set(data.x, 0.55 + data.positionY / 2 + data.y / 2, data.z);

    for (let i = 0; i < 64; i++) {

        geometry.faces[i].color = new THREE.Color('#004892');

    }

    worldScene.add(cylinder);

}

/**

 * 创建网格

 * @param {几何体对象} geometry

 */

function createMesh(geometry, color) {

    if (!color) {

        color = '#4685C6';

    }

    return new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: color }));

}

/**

 * A,B模型type:'intersect' 交集  'union' 并集  subtract 差集

 * @param {A模型} geometryA

 * @param {B模型} geometryB

 */

function bspGeometry(type, geometryA, geometryB) {

    //生成ThreeBSP对象

    var a = new ThreeBSP(geometryA);

    var b = new ThreeBSP(geometryB);

    //进行算

    var resultBSP;

    if (type == 'intersect')

        resultBSP = a.intersect(b);

    else if (type == 'union')

        resultBSP = a.union(b);

    else

        resultBSP = a.subtract(b);

    //从BSP对象内获取到处理完后的mesh模型数据

    var result = resultBSP.toGeometry();

    //更新模型的面和顶点的数据

    result.computeFaceNormals();

    result.computeVertexNormals();

    return cresult;

}

/**

 * A,B模型type:'intersect' 交集  'union' 并集  subtract 差集

 * @param {A模型} meshA

 * @param {B模型} meshB

 */

function bspMesh(type, meshA, meshB) {

    //生成ThreeBSP对象

    var a = new ThreeBSP(meshA);

    var b = new ThreeBSP(meshB);

    //进行算

    var resultBSP;

    if (type == 'intersect')

        resultBSP = a.intersect(b);

    else if (type == 'union')

        resultBSP = a.union(b);

    else

        resultBSP = a.subtract(b);

    //从BSP对象内获取到处理完后的mesh模型数据

    var result = resultBSP.toMesh();

    result.material = meshA.material;

    //更新模型的面和顶点的数据

    result.geometry.computeFaceNormals();

    result.geometry.computeVertexNormals();

    testResult = result

    return result;

}

var testResult;



 

/**

 *创建地图标注

 *canvas地图标注的内容很小需要放大,放大会失真,后期调整其缩放大小,或者不采用canvas渲染

 */

function createTextureBySprite(data) {

    if ((data.title == '' && data.imageurl == '') || (!data.title && !data.imageurl)) {

        return null;

    }

    let canvas = document.createElement('canvas');

    canvas.width=3000;

    canvas.height=2000;

   

    let ctx = canvas.getContext('2d');

   

    ctx.lineWidth = 1;

    ctx.textAlign = "center";

    ctx.textBaseline = "middle";

    ctx.textAlign = 'center';

    if (data.font) {

        ctx.font = data.font;

    } else {

        ctx.font = "Normal 180px Arial"

    }

    if (data.textcolor) {

        ctx.fillStyle = data.textcolor;

    }

    //ctx.lineWidth = 4;

    if (data.imageurl) {

        let img = new Image();

        img.src = data.imageurl;

        img.onload = function () {

            ctx.drawImage(img, 30, 90);

            texture.needsUpdate = true;

        }

    }

    /*

    把整个 canvas 作为纹理,所以字尽量大一些,撑满整个 canvas 画布。

    但也要小心文字溢出画布。

    */

    ctx.fillText(data.title, 400, 200);

    let texture = new THREE.CanvasTexture(canvas);

    let material = new THREE.SpriteMaterial({

        map: texture,

        transparent: true, // 避免遮挡其他图形

       // sizeAttenuation:false

    });

    let textMesh = new THREE.Sprite(material);

    /*

    精灵很小,要放大

    */

   textMesh.scale.set(10, 10, 10);

    /*

    WebGL 3D 世界中的位置

    */

    textMesh.position.set(data.x,data.y + 1.5, data.z);//data.y + 3

    return textMesh;

}


 

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

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

相关文章

vue手写多对多关联图,连线用leader-line

效果如图 鼠标滑动效果 关联性效果 <template ><div class"main" ref"predecessor"><div class"search"><div class"search-item"><div class"search-item-label">部门</div><Trees…

10种常见网站安全攻击手段及防御方法

随着互联网技术的发展&#xff0c;网站所遭受的网络攻击频率也在不断上升。某种程度上&#xff0c;我们可以说互联网上的每个网站都容易遭受安全攻击。因为网络攻击者最主要的动机是求财。无论你运营的是电子商务项目还是简单的小型商业网站&#xff0c;潜在攻击的风险就在那里…

数据结构顺序表

今天主要讲解顺序表&#xff0c;实现顺序表的尾插&#xff0c;头插&#xff0c;头删&#xff0c;还有尾删等操作&#xff0c;和我们之前写的通讯录的增删查改有类似的功能。接下来让我们开始我们的学习吧。 1.线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特…

04-1_Qt 5.9 C++开发指南_常用界面设计组件_字符串QString

本章主要介绍Qt中的常用界面设计组件&#xff0c;因为更多的是涉及如何使用&#xff0c;因此会强调使用&#xff0c;也就是更多针对实例&#xff0c;而对于一些细节问题&#xff0c;需要参考《Qt5.9 c开发指南》进行学习。 文章目录 1. 字符串与普通转换、进制转换1.1 可视化U…

【Tomcat】Tomcat部署及优化

Tomcat 它是一个免费、开源的web应用服务器&#xff1b;基于java代码开发的软件&#xff1b;处理动态请求和基于Java代码的页面开发&#xff1b; 可以在html当中写入Java代码&#xff0c;Tomcat可以解析html页面当中的Java代码&#xff0c;执行动态请求以及动态页面 缺点&#…

springboot文件上传和下载接口的简单思路

springboot文件上传和下载的简单思路 文件上传文件下载 文件上传 在springboot中&#xff0c;上传文件只需要在接口中通过 MultipartFile 对象来获取前端传递的数据&#xff0c;然后将数据存储&#xff0c;并且返回一个对外访问路径即可。一般对于上传文件的文件名&#xff0c…

多用户微商城多端智慧生态电商系统搭建

多用户微商城多端智慧生态电商系统的搭建步骤如下&#xff1a; 系统规划&#xff1a;在搭建多用户微商城多端智慧生态电商系统之前&#xff0c;需要进行系统规划。包括确定系统的目标、功能、架构、技术选型、开发流程等方面。市场调研&#xff1a;进行市场调研&#xff0c;了…

unity 修改默认脚本

using System.Collections; using System.Collections.Generic; using UnityEngine; //***************************************** //创建人&#xff1a; xxxx //功能说明&#xff1a; //***************************************** #ROOTNAMESPACEBEGIN# public class #SCRI…

C# PDF加盖电子章

winform界面 1.选择加签pdf按钮代码实现 private void button1_Click(object sender, EventArgs e){OpenFileDialog op new OpenFileDialog();op.Filter "PDF文件(*.pdf)|*.pdf";bool flag op.ShowDialog() DialogResult.OK;if (flag){string pdfPath Path.Get…

栈和队列详解(2)

目录 一、什么是队列&#xff1f; 二、创建一个我们自己的队列 1.前置准备 1.1需要的三个文件 1.2结构体的创建和头文件的引用 2.接口的实现 2.1初始化队列 2.2入队 2.3队列元素个数和判空 2.4取队头元素和队尾元素 2.5出队 2.6摧毁队列 2.7测试接口 三、所有代码 1.…

系列七、RocketMQ如何保证顺序消费消息

一、概述 所谓顺序消费指的是可以按照消息的发送顺序来进行消费。例如一笔订单产生了3条消息&#xff0c;即下订单》减库存》增加订单&#xff0c;消费时要按照顺序消费才有意义&#xff0c;要不然就乱套了&#xff08;PS&#xff1a;你总不能订单还没下&#xff0c;就开始减库…

既然jmeter也能做接口自动化,为什么还需要pytest自己搭框架?

今天这篇文章呢&#xff0c;我会从以下几个方面来介绍&#xff1a; 1、首先介绍一下pytest框架 2、带大家安装Pytest框架 3、使用pytest框架时需要注意的点 4、pytest的运行方式 5、pytest框架中常用的插件 一、pytest框架介绍 pytest 是 python 的第三方单元测试框架&a…

springBoot整合RabbitMq实现手动确认消息

如何保证消息的可靠性投递&#xff1f; 1.保证生产者向broke可靠性投递&#xff0c;开启ack投递成功确认&#xff0c;如果失败的话进行消息补偿 /*** author yueF_L* date 2023-08-10 01:32* ConfirmCallback&#xff1a;消息只要被 RabbitMQ broker 接收到就会触发confirm方…

整理mongodb文档:collation

文章连接 整理mongodb文档:collation 看前提示 对于mongodb的collation。个人主要用的范围是在createcollection&#xff0c;以及find的时候用&#xff0c;所以本片介绍的时候也是这两个地方入手&#xff0c;对新手个人觉得理解概念就好。不要求强制性掌握&#xff0c;但是要…

07 Ubuntu中使用poetry工具管理python环境——巨详细!!!

由于conda和ros2的环境实在太容易冲突了。我真的不敢再使用conda&#xff0c;着实是有些搞不明白这解释器之间的关系。 conda的卸载和ros2的安装暂不赘述&#xff0c;下面着重来说如何在Ubuntu中使用poetry进行包管理及遇到的问题。 1 安装poetry 由于在有写入权限的限制&am…

01:STM32点灯大师和蜂鸣器

目录 一:点亮1个LED 1:连接图 2:函数介绍 3:点灯代码 二:LED闪烁 1:函数介绍 2:闪烁代码 三:LED流水灯 1:连接图 2:函数介绍 3:流水灯代码 四:蜂鸣器 1:连接图 2:蜂鸣器代码 一:点亮1个LED 1:连接图 因为IO口与LED负极相连所以IO口输出低电频,点亮LED (采用的是低…

【LeetCode】122. 买卖股票的最佳时机 II - 贪婪算法

目录 2023-8-10 10:29:32 122. 买卖股票的最佳时机 II 2023-8-10 10:29:32 没错&#xff0c;还是用双指针思想来套出来的。 感觉步骤很复杂&#xff0c;还调试了半天。 class Solution {public int maxProfit(int[] prices) {int pre 0;int last 1;int maxProfit 0;int c…

vCenter Server Appliance(VCSA )7.0 部署指南

vCenter Server Appliance&#xff08;VCSA &#xff09;7.0 部署指南 vmware 服务器 网络 vCenter Server Appliance&#xff08;VCSA &#xff09;7.0 部署指南 部署准备 1、下载VMware-VCSA-all-7.0.0-xxxx.iso文件&#xff0c;用虚拟光驱挂载或者解压运行&#xff0c;本…

Ansible的安装和配置

安装和配置 Ansible 安装所需的软件包 创建名为 /home/greg/ansible/inventory 的静态清单文件&#xff0c;以满足以下要求&#xff1a; 172.25.250.9 是 dev 主机组的成员 172.25.250.10 是 test 主机组的成员 172.25.250.11 和 172.25.250.12 是 prod 主机组的成员 172.2…

Linux系统编程之信号(上)

一、信号概念 信号就是软件中断。每当程序收到一个信号&#xff0c;都需要按指定的方法去处理。以下是UNIX系统的信号表。 其中core表示产生一个复制了该进程内存映像的core文件&#xff0c;它保存了程序现场&#xff0c;可以使用gdb来调试。 二、signal() signal()函数用于改…