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+室内围墙,仓库,楼梯,货架模型等,第一人称进入场景案例
翻到了之前的一个案例,基于three.js做的仓库布局模拟,地图元素除了大模型外,其他都是通过JSON数据解析动态生成的,例如墙体,柱子门口,地标等,集成了第一人称的插件可以第一人称进入场景有需要的…...

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 ✨ 写在前面✨ 功能介绍✨ 页面搭建✨ 样式设置✨ 逻辑部分 ✨ 写在前面 上周我们实通过前端基础实现了拼图游戏,今天还是继续按照我们原定的节奏来带领大家完成一个五子棋游戏,功能也比较简单简单,也…...

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

小白到运维工程师自学之路 第七十一集 (kubernetes网络设置)
一、概述 Master 节点NotReady 的原因就是因为没有使用任何的网络插件,此时Node 和Master的连接还不正常。目前最流行的Kubernetes 网络插件有Flannel、Calico、Canal、Weave 这里选择使用flannel。 二、安装flannel 1、master下载kube-flannel.yml,所…...

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

c刷题(二)
目录 加减混合运算 计算n的k次方 计算非负整数各位之和 字符串逆序 双指针 递归 矩阵计算 矩阵转置 加减混合运算 题目:计算1 / 1 - 1 / 2 1 / 3 - 1 / 4 1 / 5 …… 1 / 99 - 1 / 100 的值,打印出结果。 一般情况我们可以写个循环然后在用条…...
【leetcode】15. 三数之和(medium)
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 这题真…...

【css】属性选择器
有些场景中需要在相同元素中获取具有特定属性的元素,比如同为input,type属性有text、button,可以通过属性选择器设置text和button的不同样式。 代码: <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类型:包括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 模块 正则表达式
一、正则表达式基本符号 ^ 表示匹配字符串的开始位置 (例外 用在中括号中[ ] 时,可以理解为取反,表示不匹配括号中字符串)$ 表示匹配字符串的结束位置* 表示匹配 零次到多次(记忆方法:符号是星星,天上的星星可以是无数个也可以看不到&#x…...
c#设计模式-创建型模式 之 单例模式
目录 前言: 优点: 缺点: 饿汉式(静态变量方式) 懒汉式(线程不安全) 懒汉式(双重检查锁定) 推荐方式Lazy 总结: 前言: 这种模式涉及到一个单一的类&a…...
K-01BFS(2023河南萌新联赛第(五)场:郑州轻工业大学)
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 思路: 直接枚举这个图中的拐点 这个拐点是经过左右平移到上下平移或者上下平移到左右平移 假设这个点事左到右后然后再从下到上 左到右就相当于走了个最长上升子序列࿰…...

CSP复习每日一题(四)
树的重心 给定一颗树,树中包含 n n n 个结点(编号 1 ∼ n 1∼n 1∼n)和 n − 1 n−1 n−1条无向边。请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。 重心定义: 重心是指树中的一…...

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

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

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

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

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...