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;

}

相关推荐
上趣工作室3 分钟前
vue2在el-dialog打开的时候使该el-dialog中的某个输入框获得焦点方法总结
前端·javascript·vue.js
家里有只小肥猫3 分钟前
el-tree 父节点隐藏
前端·javascript·vue.js
fkalis4 分钟前
【海外SRC漏洞挖掘】谷歌语法发现XSS+Waf Bypass
前端·xss
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
亿牛云爬虫专家1 小时前
Puppeteer教程:使用CSS选择器点击和爬取动态数据
javascript·css·爬虫·爬虫代理·puppeteer·代理ip
2401_857610032 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
雾散声声慢2 小时前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫2 小时前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript