翻到了之前的一个案例,基于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));
puteFaceNormals();//计算法向量,会对光照产生影响
puteVertexNormals();//自动设置法向量
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;//接收阴影
puteBoundingBox();
_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();
//更新模型的面和顶点的数据
puteFaceNormals();
puteVertexNormals();
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;
//更新模型的面和顶点的数据
puteFaceNormals();
puteVertexNormals();
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;
}