当前位置: 首页 > news >正文

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;

}


 

相关文章:

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

翻到了之前的一个案例&#xff0c;基于three.js做的仓库布局模拟&#xff0c;地图元素除了大模型外&#xff0c;其他都是通过JSON数据解析动态生成的&#xff0c;例如墙体&#xff0c;柱子门口&#xff0c;地标等&#xff0c;集成了第一人称的插件可以第一人称进入场景有需要的…...

EditPlus取消自动.bak备份

Tools->Preferences->File 将√取消...

LLM - Transformer LLaMA2 结构分析与 LoRA 详解

目录 一.引言 二.图说 LLM 1.Transformer 结构 ◆ Input、Output Embedding ◆ PositionEmbedding ◆ Multi-Head-Attention ◆ ADD & Norm ◆ Feed Forward ◆ Linear & Softmax 2.不同 LLM 结构 ◆ Encoder-Only ◆ Encoder-Decoder ◆ Decoder-Only …...

前端技术搭建五子棋游戏(内含源码)

The sand accumulates to form a pagoda ✨ 写在前面✨ 功能介绍✨ 页面搭建✨ 样式设置✨ 逻辑部分 ✨ 写在前面 上周我们实通过前端基础实现了拼图游戏&#xff0c;今天还是继续按照我们原定的节奏来带领大家完成一个五子棋游戏&#xff0c;功能也比较简单简单&#xff0c;也…...

AST入门与实战(三):if节点转switch节点(瑞数5)

原文地址:https://zhuoyue360.com/jsnx/110.html 1. 期望 这是一个瑞数5代解混淆的案例&#xff0c;我们本章节需要做的是把if节点的内容转换成switch-case内容.以此来熟悉AST对JS混淆的对抗. 原始代码: function whileState() {while (1) {aV cA[wU];if (aV < 4) {if (…...

小白到运维工程师自学之路 第七十一集 (kubernetes网络设置)

一、概述 Master 节点NotReady 的原因就是因为没有使用任何的网络插件&#xff0c;此时Node 和Master的连接还不正常。目前最流行的Kubernetes 网络插件有Flannel、Calico、Canal、Weave 这里选择使用flannel。 二、安装flannel 1、master下载kube-flannel.yml&#xff0c;所…...

day17 enum abstract interface 枚举 抽象 接口

一、枚举 enum 枚举本来的面目 创建Season类&#xff0c; 所有类都默认继承Object&#xff0c;写不写都一样 声明属性 &#xff1a;季节的名字、 季节的描述&#xff0c; 因为枚举的对象是看的见的客观事物&#xff0c; 想让它的属性不可修改 使用 final修饰表示最终的 &am…...

c刷题(二)

目录 加减混合运算 计算n的k次方 计算非负整数各位之和 字符串逆序 双指针 递归 矩阵计算 矩阵转置 加减混合运算 题目&#xff1a;计算1 / 1 - 1 / 2 1 / 3 - 1 / 4 1 / 5 …… 1 / 99 - 1 / 100 的值&#xff0c;打印出结果。 一般情况我们可以写个循环然后在用条…...

【leetcode】15. 三数之和(medium)

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 这题真…...

【css】属性选择器

有些场景中需要在相同元素中获取具有特定属性的元素&#xff0c;比如同为input&#xff0c;type属性有text、button&#xff0c;可以通过属性选择器设置text和button的不同样式。 代码&#xff1a; <style> input[typetext] {width: 150px;display: block;margin-bottom…...

Redis_概述

1.redis概述 1.1 简介 截止到2021年12月 数据库排名https://db-engines.com/en/ranking redis(Remote Dictionary Server) 一个开源的key-value存储系统它支持存储的Value类型&#xff1a;包括String(字符串),list(链表),set(集合),zset(sorted set 有序集合),hash(哈希类型…...

【从零学习python 】16. Python字符串的format方法(一)

文章目录 字符串的format方法1. 概念:2. 字段名2.1 简单字段名2.1.1 省略字段名2.1.2 数字字段名2.1.3 变量字段名2.1.4 简单字段名的混合使用2.1.5 使用元组和字典传参 进阶案例 字符串的format方法 1. 概念: str.format() 方法通过字符串中的大括号{}来识别替换字段 replac…...

python re 模块 正则表达式

一、正则表达式基本符号 ^ 表示匹配字符串的开始位置 (例外 用在中括号中[ ] 时,可以理解为取反,表示不匹配括号中字符串)$ 表示匹配字符串的结束位置* 表示匹配 零次到多次&#xff08;记忆方法&#xff1a;符号是星星&#xff0c;天上的星星可以是无数个也可以看不到&#x…...

c#设计模式-创建型模式 之 单例模式

目录 前言&#xff1a; 优点&#xff1a; 缺点: 饿汉式&#xff08;静态变量方式&#xff09; 懒汉式&#xff08;线程不安全&#xff09; 懒汉式&#xff08;双重检查锁定&#xff09; 推荐方式Lazy 总结&#xff1a; 前言&#xff1a; 这种模式涉及到一个单一的类&a…...

K-01BFS(2023河南萌新联赛第(五)场:郑州轻工业大学)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 思路&#xff1a; 直接枚举这个图中的拐点 这个拐点是经过左右平移到上下平移或者上下平移到左右平移 假设这个点事左到右后然后再从下到上 左到右就相当于走了个最长上升子序列&#xff0…...

CSP复习每日一题(四)

树的重心 给定一颗树&#xff0c;树中包含 n n n 个结点&#xff08;编号 1 ∼ n 1∼n 1∼n&#xff09;和 n − 1 n−1 n−1条无向边。请你找到树的重心&#xff0c;并输出将重心删除后&#xff0c;剩余各个连通块中点数的最大值。 重心定义&#xff1a; 重心是指树中的一…...

dubbo之整合SpringBoot

目录 zookeeper安装 1.拉取ZooKeeper镜像 2.新建文件夹 3.挂载本地文件夹并启动服务 4.查看容器 5.进入容器&#xff08;zookeeper&#xff09; Dubbo Admin安装 1.下载dubbo-admin 2.zip包解压 3.修改配置文件 4.打包项目 5.启动jar 6.访问 构建项目 api模块 1.创建…...

UE 5 GAS 在项目中处理AttributeSet相关

这一篇文章是个人的实战经验记录&#xff0c;如果对基础性的内容不了解的&#xff0c;可以看我前面一篇文章对基础的概念以及内容的讲解。 设置AttributeSet 使用GAS之前&#xff0c;首先需要设置参数集AS&#xff0c;这个是用于同步的一些参数&#xff0c;至于如何设置GAS&a…...

JDBC数据库连接

目录 引言 一&#xff0c;基本概念 二&#xff0c;常用操作步骤 三&#xff0c;连接操作 引言 JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API&#xff0c;可以为多种 关系数据库提供统一访问&#xff0c;它由一组用Java语言编写的类和接口…...

gitee分支合并

合并dev分支到master&#xff08;合并到主分支&#xff09; git checkout master git merge dev //这里的dev表示你的分支名称 git push //推送到远程仓库 效果如下图 不报错就表示推送成功了&#xff0c;希望能帮助各位小伙伴...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...