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

news2025/1/9 16:24:18

翻到了之前的一个案例,基于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.coloradmin.cn/o/845315.html

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

相关文章

FreeRTOS源码分析-10 互斥信号量

目录 1 事件标志组概念及其应用 1.1 事件标志组定义 1.2 FreeRTOS事件标志组介绍 1.3 FreeRTOS事件标志组工作原理 2 事件标志组应用 2.1 功能需求 2.2 API 2.3 功能实现 3 事件标志组原理 3.1 事件标志组控制块 3.2 事件标志组获取标志位 3.3 等待事件标志触发 3.4…

Mid journey V5.2 终极指南

MidJourney 是迄今为止最高质量的人工智能文本到图像生成器之一。我想你们中的大多数人都可以创建一些简单的提示&#xff0c;例如“留着胡子的男人”或“森林里的狼”&#xff0c;并能够在那里生成一些相当不错的图像......但是...... 如果你想用Midjourney创造出杰出的艺术&a…

Spring MVCSpring Boot

文章目录 Spring MVC什么是MVC模式Spring MVC优点SpringMVC 运行流程SpringMVC组件SpringMVC常用的注解有哪些SpringMVC的拦截器和过滤器有什么区别&#xff1f;执行顺序是什么 SpringBoot对SpringBoot的理解Spring和SpringBoot的关系&#xff1f;SpringBoot有哪些核心注解Spri…

IMv1.0

一、背景内容 总结golang基础内容&#xff0c;通过一个实例实时 IM系统简进行总结知识 二、简要的图 简要说明&#xff1a; 1.在server.go中&#xff0c;创建一个Newserver返回server指针的结构体 2.正对这个指针结构体实现两个方法 Handler&#xff08;处理方法&#xff0…

Vue2与Vue3—引入DataV

一、文档参考 vue2使用DataV文档 vue3使用DataV文档 二、开始 1、npm安装DateV 支持npm、pnpm、yarn V2&#xff1a; npm install jiaminghi/data-viewV3&#xff1a; npm install kjgl77/datav-vue32、全局引入DataV // main.ts中全局引入 v2: import dataV from jiaminghi/…

nginx+flask+uwsgi部署遇到的坑

文章目录 1.环境&#xff1a;2.uwsgi_conf.ini具体配置内容3.nginx 具体配置4.具体命令(注意使用pip3命令安装)5.服务异常排查 1.环境&#xff1a; centos8 uWSGI 2.0.22 gmssl 3.2.2 nginx version: nginx/1.18.0 项目目录&#xff1a; 2.uwsgi_conf.ini具体配置内容 [uws…

Linux CEF(Chromium Embedded Framework)源码下载编译详细记录

Linux CEF&#xff08;Chromium Embedded Framework&#xff09;源码下载编译 背景 由于CEF默认的二进制分发包不支持音视频播放&#xff0c;需要自行编译源码&#xff0c;将ffmpeg开关打开才能支持。这里介绍的是Linux平台下的CEF源码下载编译过程。 前置条件 下载的过程非…

Idea小操作

Idea操作 idea提取内容构成一个方法 idea提取内容构成一个方法

git clean 命令

git clean -n //显示要删除的文件&#xff0c;clean的演习&#xff0c;告诉哪些文件删除&#xff0c;只是一个提醒。 git clean -dn //显示要删除的文件和目录 git clean -f //删除未追踪的文件 git clean -dff //删除未追踪的目录 git clean -df //清除所有未跟踪文件&#xf…

【MySQL】聚合函数与分组查询

文章目录 一、聚合函数1.1 count 返回查询到的数据的数量1.2 sum 返回查询到的数据的总和1.3 avg 返回查询到的数据的平均值1.4 max 返回查询到的数据的最大值1.5 min 返回查询到的数据的最小值 二、分组查询group by2.1 导入雇员信息表2.2 找到最高薪资和员工平均薪资2.3 显示…

小型双轮差速底盘机器人实现红外跟随功能

1. 功能说明 本文示例将实现R023样机小型双轮差速底盘跟随人移动的功能。在小型双轮差速底盘前方按下图所示安装3个 近红外传感器&#xff0c;制作一个红外线发射源&#xff0c;实现当红外发射源在机器人的检测范围内任意放置或移动时&#xff0c;机器人能追踪该发射源。 2. 电…

ffmpeg工具实用命令

说明&#xff1a;ffmpeg是一款非常好用的媒体操作工具&#xff0c;包含了许多对于视频、音频的操作&#xff0c;有些视频播放器里面实际上就是使用了ffmpeg。本文介绍ffmpeg的使用以及一些较为实用的命令。 安装 ffmpeg是命令行操作的&#xff0c;不需要安装&#xff0c;可在…

ADSP21569之开发笔记(一)

CLDP烧写SigmStudio融合程序到Flash实现脱机步骤&#xff1a; 1、配置CCES属性&#xff0c;生成ldr文件。 ADI的flash烧写都需要驱动&#xff0c;这个驱动并不是通用的&#xff0c;每一个型号的flash都会有自己对应的驱动&#xff0c;ADI提供了一个例程&#xff0c;即IS25LP512…

CS61B Spring 2021 proj2 gitlet

Gitlet 项目简介整体结构对象概念&#xff08;object concept&#xff09;追踪文件&#xff08;track file&#xff09;分支管理&#xff08;branch management&#xff09;持久化目录结构&#xff08;folder structure&#xff09; 命令功能与具体实现initaddcommitrmloggloba…

设计模式之三大类

目录 设计模式分类 创建型模式(Creational Patters) 结构型模式(Structural Patterns) 行为型模式(Behavioral Patterns) 命令模式(The Command Pattern) 适配器模式 Object and Class Adapters 设计模式分类 1.创建型模式(Creational Patters) Fatory Method - 本质&…

直线电机模组在激光切割机上的作用

激光切割机是将从激光器发射出的激光&#xff0c;经光路系统&#xff0c;聚焦成高功率密度的激光束。激光束照射到工件表面&#xff0c;使工件达到熔点或沸点&#xff0c;同时与光束同轴的高压气体将熔化或气化金属吹走。激光切割加工是用不可见的光束代替了传统的机械刀&#…

一起学数据结构(3)——万字解析:链表的概念及单链表的实现

上篇文章介绍了数据结构的一些基本概念&#xff0c;以及顺序表的概念和实现&#xff0c;本文来介绍链表的概念和单链表的实现&#xff0c;在此之前&#xff0c;首先来回顾以下顺序表的特点&#xff1a; 1.顺序表特点回顾&#xff1a; 1. 顺序表是一组地址连续的存储单元依次存…

<STM32>STM32F103ZET6-可调参数定时器1互补PWM输出

&#xff1c;STM32&#xff1e;STM32F103ZET6-可调参数定时器1互补PWM输出 一 基础工程 本例基础工程以正点原子战舰V3开发板配套 库函数 开发例程《实验9 PWM输出实验》&#xff1b; 在此例程基础上进行 定时器1互补PWM输出。 二 代码修改 基于例程&#xff0c;只需修改ma…

Uniapp基于微信小程序以及web端文件、图片下载,带在线文件测试地址

一、效果 传送门 二、UI视图 <scroll-view scroll-x="true" scroll-y="true" :style

第一章-数据结构绪论

第一章-数据结构绪论 数据结构的起源和相关概念 数据结构是一门研究非数值计算的程序设计问题中的操作对象&#xff0c;以及它们之间的关系和操作等相关问题的学科。 程序设计的实质是选择一个好的结构&#xff0c;再设计一种好的算法。 数据&#xff1a;是描述客观事物的符…