emsApplication/applications/WebConfigure/web/js/mdc.3d.js

849 lines
25 KiB
JavaScript
Raw Permalink Normal View History

2024-05-24 12:19:45 +08:00
/**
* @Documents
*
* 配置3D时选择模型文件后使用 MDCThreeD.readCabinetList 读取模型文件中的柜子列表返回所有柜子的编号
* MDCThreeD.readCabinetList('./img/3d/molds/mdc.fbx').then(indexs => {
* console.log(indexs);
* });
*/
/**
* 3D页面
* @param {*} options 初始化选项
* @returns
*/
function MDCThreeD(options) {
this.options = options;
this.container = options.container;
this.touchHandle = this.onTouch.bind(this);
this.touchendHandle = this.onTouchEnd.bind(this);
this.touchMoveHandle = this.onTouchMove.bind(this);
this.mousemoveHandle = this.onMouseMove.bind(this);
this.mouseleaveHandle = this.onMouseLeave.bind(this);
this.scope = options.scope;
this.exitFlag = false;
if (this.options.buttons) {
for (var i = 0; i < this.options.buttons.length; i++) {
var self = this;
this.options.buttons[i].index = i;
this.options.buttons[i].onclick = function (arg) {
var index = arg[0];
var position = self.originPosition;
if (index === 0) {
if (self.camera.position.distanceTo(self.originPosition) < 1) {
position = self.getAnotherSide(self.originPosition);
}
}
self.camera.position.copy(position);
self.camera.updateProjectionMatrix();
for (var i = 0; i < self.options.buttons.length; i++) {
self.options.buttons[i].classList.remove('mdc-3d-button-active');
}
self.options.buttons[index].classList.add('mdc-3d-button-active');
for (var i = 0; i < self.actions.length; i++) {
self.actions[i].stop();
}
self.controls.autoRotate = index === 1;
if (index === 2) {
for (var i = 0; i < self.actions.length; i++) {
self.actions[i].play();
}
}
}.bind(this, [i])
}
}
if (this.isWebGL2Available()) {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
} else if (this.isWebGLAvailable()) {
this.renderer = new THREE.WebGL1Renderer({
antialias: true,
alpha: true
});
} else {
throw new Error('该浏览器不支持WEBGL3D渲染请尝试使用最新版本的Chrome或Edge浏览器。');
}
if (typeof this.renderer.domElement.onpointerdown != 'undefined') {
this.renderer.domElement.addEventListener('pointerdown', this.touchHandle, false);
} else {
this.renderer.domElement.addEventListener('mousedown', this.touchHandle, false);
}
if (typeof this.renderer.domElement.onpointerup != 'undefined') {
this.renderer.domElement.addEventListener('pointerup', this.touchendHandle, false);
} else {
this.renderer.domElement.addEventListener('mouseup', this.touchendHandle, false);
}
if (typeof this.renderer.domElement.onpointermove != 'undefined') {
this.renderer.domElement.addEventListener('pointermove', this.touchMoveHandle, false);
} else {
this.renderer.domElement.addEventListener('mousemove', this.touchMoveHandle, false);
}
// 鼠标停留
this.renderer.domElement.addEventListener('mousemove', this.mousemoveHandle, false);
// 鼠标离开
this.renderer.domElement.addEventListener('mouseleave', this.mouseleaveHandle, false);
// this.renderer.domElement.addEventListener('touchstart', this.touchHandle, false);
// this.renderer.domElement.addEventListener('touchmove', this.touchMoveHandle, false);
// this.renderer.domElement.addEventListener('touchend', this.touchendHandle, false);
this.container.appendChild(this.renderer.domElement);
this.renderer.domElement.style.width = '100%';
this.renderer.domElement.style.height = '100%';
this.initData();
this.loadCamera();
this.loadControls();
this.loadEnvironment();
this.loadTemplate();
requestAnimationFrame(this.render.bind(this));
return this;
}
/**
* 定义柜子对象的命名规则
*/
MDCThreeD.cabinetRegExp = /cabinet[1-9]+[0-9]*/;
/*:1
* 机柜对象表
*/
MDCThreeD.prototype.cabinets = {};
/**
* 3D配置选项
*/
MDCThreeD.prototype.options = {
/**
* 容器DIV对象初始化传入\
*/
container: null,
/**
* 静态背景图路径可选\
*/
background: null,
/**
* 控制按钮列表\
* 传入button对象数组
* [0:静止,1:旋转,2:动画]
*/
buttons: [],
/**
* 是否使用环境光贴图\
* 模拟房间的环境光
*/
environmentLight: true,
/**
* 模型文件
*/
modelFile: './img/3d/molds/mdc.fbx',
/**
* 机柜点击变色 \
* #007ACC
*/
touchDiscolor: null
}
/**
* 初始化数据
*/
MDCThreeD.prototype.initData = function () {
this.cabinets = {};
this.actions = [];
this.touchObject = null;
this.mixer = new THREE.AnimationMixer();
this.clock = new THREE.Clock();
this.scene = new THREE.Scene();
this.raycaster = new THREE.Raycaster();
}
/**
* 命中对象事件
* @param {*} object 命中对象
* @param {*} cabinetIndex 对象的柜子编号
*/
MDCThreeD.prototype.objectTouch = function (object, cabinetIndex) {
this.scope.cabinetClk(cabinetIndex);
}
/**
* 获取一个坐标围绕中心点的另一面
* Y轴旋转180度
* @param {*} position
* @returns
*/
MDCThreeD.prototype.getAnotherSide = function (position) {
var ps = position.clone().sub(this.controls.target);
this.transformGroup.rotation.set(0, 0, 0);
this.transformObject.position.copy(ps);
this.transformGroup.rotation.y = Math.PI;
this.transformGroup.updateMatrixWorld(true);
const pos = new THREE.Vector3();
pos.setFromMatrixPosition(this.transformObject.matrixWorld)
return pos.add(this.controls.target);
}
/**
* 鼠标按下事件处理
* @param {*} event
*/
MDCThreeD.prototype.onTouch = function (event) {
event.preventDefault();
const position = this.getPlatformMousePosition(event);
var rect = this.renderer.domElement.getBoundingClientRect();
position.sub(new THREE.Vector2(rect.x | rect.left, rect.y | rect.top));
var x = (position.x / rect.width) * 2 - 1;
var y = -(position.y / rect.height) * 2 + 1;
this.coord = new THREE.Vector2(x, y);
this.raycaster.setFromCamera(this.coord, this.camera);
var hitobjects = this.raycaster.intersectObjects(this.scene.children, true);
if (hitobjects.length > 0) {
const object = hitobjects[0].object;
if (this.isCabinet(object)) {
this.touchPosition = position;
this.touchObject = object;
this.discolor(this.touchObject, true);
}
}
}
/**
* 鼠标停留
* @Author: Eddy
* @Date: 2021-06-05 16:59:25
*/
MDCThreeD.prototype.onMouseMove = function (event) {
event.preventDefault();
const position = this.getPlatformMousePosition(event);
var rect = this.renderer.domElement.getBoundingClientRect();
position.sub(new THREE.Vector2(rect.x | rect.left, rect.y | rect.top));
var x = (position.x / rect.width) * 2 - 1;
var y = -(position.y / rect.height) * 2 + 1;
this.coord = new THREE.Vector2(x, y);
this.raycaster.setFromCamera(this.coord, this.camera);
var hitobjects = this.raycaster.intersectObjects(this.scene.children, true);
var currentObject = null;
if (hitobjects.length > 0) {
const object = hitobjects[0].object;
if (this.isCabinet(object)) {
currentObject = object;
}
}
if (this.nouseObject != currentObject) {
if (this.nouseObject != null) {
this.discolor(this.nouseObject, false);
this.nouseObject = null;
}
this.nouseObject = currentObject;
if (this.nouseObject != null) {
this.discolor(this.nouseObject, true);
}
}
}
/**
* 鼠标离开画布
* @Author: Eddy
* @Date: 2021-06-05 17:02:45
*/
MDCThreeD.prototype.onMouseLeave = function (event) {
if (this.nouseObject) {
this.discolor(this.nouseObject, false);
}
}
/**
* 鼠标移动事件
* @param {*} event
*/
MDCThreeD.prototype.onTouchMove = function (event) {
if (this.touchObject && this.touchPosition) {
const position = this.getPlatformMousePosition(event);
var rect = this.renderer.domElement.getBoundingClientRect();
position.sub(new THREE.Vector2(rect.x | rect.left, rect.y | rect.top));
/* 按下后拖动距离大于5像素取消点击处理 */
const distance = this.touchPosition.clone().distanceTo(position.clone());
if (distance > 5) {
this.discolor(this.touchObject, false);
this.touchPosition = null;
this.touchObject = null;
}
event.preventDefault();
}
}
/**
* 鼠标抬起事件
* @param {*} event
* @returns
*/
MDCThreeD.prototype.onTouchEnd = function (event) {
if (this.touchPosition == null || this.touchObject == null) return;
event.preventDefault();
this.discolor(this.touchObject, false);
this.objectTouch(this.touchObject, this.touchObject.userData.cabinetIndex);
this.touchPosition = null;
this.touchObject = null;
}
/**
* 变色
* @param {*} object
* @param {*} enabled
* @returns
*/
MDCThreeD.prototype.discolor = function (object, enabled) {
if (this.options.touchDiscolor == null) return;
var emissive = new THREE.Color(this.options.touchDiscolor);
if (enabled) {
if (object._material == null) {
object._material = object.material;
if (object._material instanceof THREE.Material) {
object.material = object.material.clone();
object.material.emissive = emissive;
} else if (object.material instanceof Array) {
const materials = [];
for (var i = 0; i < object.material.length; i++) {
const m = object.material[i].clone();
m.emissive = emissive;
materials.push(m);
}
object.material = materials;
}
}
} else {
if (object._material) {
if (object.material instanceof THREE.Material) {
object.material.dispose();
} else if (object.material instanceof Array) {
for (var i = 0; i < object.material.length; i++) {
object.material[i].dispose();
}
object.material.length = 0;
}
object.material = object._material;
delete object._material;
}
}
}
MDCThreeD.prototype.loadObjectLabels = function () {
var scope = this.options.scope;
var devices = scope.NewMdcConfigures;
var configs = scope.cabinets;
for (var key in this.cabinets) {
var cabinet = this.cabinets[key];
var object3d = cabinet.object;
var index = cabinet.index;
var txtobject = object3d.getObjectByName('txt' + index);
if (txtobject) {
var info = this.createCanvasMaterial(this.options.labelOptions.width, this.options.labelOptions.height);
var cfg = _.findWhere(configs, {
no: index
});
if (cfg) {
this.drawCanvasLabel(info, cfg.cabinetName);
// var dev = _.findWhere(devices, {
// configId: cfg.configId
// });
// if (dev) {
// this.drawCanvasLabel(info, dev.configName);
// }
}
//this.drawCanvasLabel(info, "Txt"+index);
txtobject.material = info.material;
}else
console.log("3D模型不正确");
}
}
/**
* 渲染标签文本
* @param {*} info 材质纹理等信息
* @param {*} text 渲染文本
*/
MDCThreeD.prototype.drawCanvasLabel = function (info, text) {
var options = this.options.labelOptions;
var ctx = info.context;
var fontsize = options.fontSize == null ? info.height * 0.8 : options.fontSize;
var fontstyle = fontsize + 'px ' + options.font;
if (options.bold) fontstyle += " bold";
ctx.font = fontstyle;
ctx.clearRect(0, 0, info.width, info.height);
ctx.fillStyle = options.color;
ctx.globalAlpha = 1;
ctx.textBaseline = "top";
var textwidth = ctx.measureText(text).width;
var left = (info.width - textwidth) / 2;
var top = (info.height - fontsize) / 2;
ctx.fillText(text, left, top);
info.texture.needsUpdate = true;
}
/**
* 创建材质和贴图
* @param {*} width
* @param {*} height
* @returns
*/
MDCThreeD.prototype.createCanvasMaterial = function (width, height) {
var canvas = document.createElement('canvas');
var devicePixelRatio = window.devicePixelRatio;
canvas.style.width = width + "px";
canvas.style.height = height + "px";
canvas.height = height * devicePixelRatio;
canvas.width = width * devicePixelRatio;
var context = canvas.getContext('2d');
context.scale(devicePixelRatio, devicePixelRatio);
context.imageSmoothingEnabled = true;
context.translate(0, 0);
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = "high";
context.clearRect(0, 0, width, height);
var texture = new THREE.CanvasTexture(canvas);
// texture.encoding = THREE.sRGBEncoding;
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.LinearMipmapLinearFilter;
var material = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
depthTest : false
});
return {
width: width,
height: height,
canvas: canvas,
context: context,
texture: texture,
material: material
}
}
/**
* 加载模型
*/
MDCThreeD.prototype.loadTemplate = function () {
const loader = new THREE.FBXLoader();
var self = this;
loader.load(this.options.modelFile, function (root) {
root.position.set(0, 0, 0);
root.traverse(function (e) {
if (e instanceof THREE.Light) {
e.intensity = 0.5;
}
self.initCabinet(e);
});
self.scene.add(root);
root.updateMatrixWorld();
// set the camera to frame the box
self.frameArea(root, 0.9);
// update the Trackball controls to handle the new size
if (root.animations && root.animations.length > 0) {
for (var i = 0; i < root.animations.length; i++) {
const action = self.mixer.clipAction(root.animations[i], root);
action.loop = THREE.LoopRepeat;
self.actions.push(action);
}
}
self.loadObjectLabels();
});
}
/**
* [静态方法]\
* 从模型文件读取柜子的列表
* @param {string} modelFile 模型文件路径
*/
// MDCThreeD.readCabinetList = async function (modelFile) {
// const result = [];
// const loader = new THREE.FBXLoader();
// const root = await loader.loadAsync(modelFile);
// root.traverse(function (object) {
// if (object instanceof THREE.Mesh) {
// const indexs = object.name.match(/\d+(\.\d+)?/g);
// if (MDCThreeD.cabinetRegExp.test(object.name) && indexs.length === 1) {
// const index = Number.parseInt(indexs[0]);
// result.push(index);
// }
// }
// });
// return result.sort(function (a, b) {
// return a < b ? -1 : (a === b) ? 0 : 1
// });
// }
/**
* 初始化柜子
* @param {*} object
* @returns
*/
MDCThreeD.prototype.initCabinet = function (object) {
if (object instanceof THREE.Mesh) {
const indexs = object.name.match(/\d+(\.\d+)?/g);
if (MDCThreeD.cabinetRegExp.test(object.name) && indexs.length === 1) {
const index = parseInt(indexs[0]);
object.userData.selectable = true;
object.userData.cabinetIndex = index;
this.cabinets[index] = {
index: index,
object: object,
};
}
}
}
/**
* 判断命中对象是否是柜子
* @param {*} object
* @returns
*/
MDCThreeD.prototype.isCabinet = function (object) {
return object.userData.selectable && object.userData.cabinetIndex;
}
MDCThreeD.prototype.dispose = function () {
this.exitFlag = true;
this.renderer.domElement.removeEventListener('pointerdown', this.touchHandle);
this.renderer.domElement.removeEventListener('pointermove', this.touchMoveHandle);
this.renderer.domElement.removeEventListener('pointerup', this.touchendHandle);
this.renderer.domElement.removeEventListener('touchstart', this.touchHandle);
this.renderer.domElement.removeEventListener('touchmove', this.touchMoveHandle);
this.renderer.domElement.removeEventListener('touchend', this.touchendHandle);
if (this.renderer) {
this.scene.traverse(function (e) {
if (e instanceof THREE.Mesh) {
if (e.geometry) e.geometry.dispose();
if (e.material instanceof Array) {
for (var i = 0; i < e.material.length; i++) {
e.material[i].dispose();
}
} else if (e.material) e.material.dispose();
}
});
this.renderer.dispose();
this.renderer = null;
}
};
MDCThreeD.prototype.render = function () {
if (this.exitFlag) return;
this.controls.update();
var delta = this.clock.getDelta();
this.mixer.update(delta);
if (this.resizeRendererToDisplaySize()) {
const canvas = this.renderer.domElement;
this.camera.aspect = canvas.clientWidth / canvas.clientHeight;
this.camera.updateProjectionMatrix();
}
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.render.bind(this));
};
MDCThreeD.prototype.isWebGLAvailable = function () {
try {
var canvas = document.createElement('canvas');
return !!(window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
} catch (e) {
return false;
}
};
MDCThreeD.prototype.getPlatformMousePosition = function (event) {
if (event.touches != null && event.touches.length > 0) {
return new THREE.Vector2(event.touches[0].clientX, event.touches[0].clientY);
} else {
var u = window.navigator.userAgent.toLocaleLowerCase();
var b = u.match(/(trident)\/([\d.]+)/);
if (b) {
var rect = event.target.getBoundingClientRect();
return new THREE.Vector2(event.pageX - rect.left, event.pageY - rect.top);
} else {
return new THREE.Vector2(event.clientX, event.clientY);
}
}
}
MDCThreeD.prototype.isWebGL2Available = function () {
try {
var canvas = document.createElement('canvas');
return !!(window.WebGL2RenderingContext && canvas.getContext('webgl2'));
} catch (e) {
return false;
}
};
MDCThreeD.prototype.resizeRendererToDisplaySize = function () {
const canvas = this.renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height, false);
}
return needResize;
};
MDCThreeD.prototype.frameArea = function (object, sizeToFitOnScreen) {
const box = new THREE.Box3().setFromObject(object);
const size = box.getSize(new THREE.Vector3());
const boxSize = size.length();
const boxCenter = box.getCenter(new THREE.Vector3());
boxCenter.setY(-size.y / 2); //起始位置
const halfSizeToFitOnScreen = boxSize * sizeToFitOnScreen * 0.5;
const halfFovY = THREE.MathUtils.degToRad(this.camera.fov * .5);
const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
const direction = (new THREE.Vector3())
.subVectors(this.camera.position, boxCenter)
.multiply(new THREE.Vector3(1, 0, 1))
.normalize();
this.camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
this.camera.near = boxSize / 100;
this.camera.far = boxSize * 100;
this.camera.position.setX(1300);
this.camera.position.setY(700);
this.camera.position.setZ(300);
this.originPosition = this.camera.position.clone();
this.camera.updateProjectionMatrix();
this.camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
this.controls.maxDistance = boxSize * 10;
this.controls.target.copy(boxCenter);
this.controls.update();
};
MDCThreeD.prototype.RoomEnvironment = function () {
const scene = new THREE.Scene();
const geometry = new THREE.BoxBufferGeometry();
geometry.deleteAttribute('uv');
const roomMaterial = new THREE.MeshStandardMaterial({
side: THREE.BackSide
});
const boxMaterial = new THREE.MeshStandardMaterial();
const mainLight = new THREE.PointLight(0xffffff, 5.0, 28, 2);
mainLight.position.set(0.418, 16.199, 0.300);
scene.add(mainLight);
const room = new THREE.Mesh(geometry, roomMaterial);
room.position.set(-0.757, 13.219, 0.717);
room.scale.set(31.713, 28.305, 28.591);
scene.add(room);
const box1 = new THREE.Mesh(geometry, boxMaterial);
box1.position.set(-10.906, 2.009, 1.846);
box1.rotation.set(0, -0.195, 0);
box1.scale.set(2.328, 7.905, 4.651);
scene.add(box1);
const box2 = new THREE.Mesh(geometry, boxMaterial);
box2.position.set(-5.607, -0.754, -0.758);
box2.rotation.set(0, 0.994, 0);
box2.scale.set(1.970, 1.534, 3.955);
scene.add(box2);
const box3 = new THREE.Mesh(geometry, boxMaterial);
box3.position.set(6.167, 0.857, 7.803);
box3.rotation.set(0, 0.561, 0);
box3.scale.set(3.927, 6.285, 3.687);
scene.add(box3);
const box4 = new THREE.Mesh(geometry, boxMaterial);
box4.position.set(-2.017, 0.018, 6.124);
box4.rotation.set(0, 0.333, 0);
box4.scale.set(2.002, 4.566, 2.064);
scene.add(box4);
const box5 = new THREE.Mesh(geometry, boxMaterial);
box5.position.set(2.291, -0.756, -2.621);
box5.rotation.set(0, -0.286, 0);
box5.scale.set(1.546, 1.552, 1.496);
scene.add(box5);
const box6 = new THREE.Mesh(geometry, boxMaterial);
box6.position.set(-2.193, -0.369, -5.547);
box6.rotation.set(0, 0.516, 0);
box6.scale.set(3.875, 3.487, 2.986);
scene.add(box6);
// -x right
const light1 = new THREE.Mesh(geometry, createAreaLightMaterial(50));
light1.position.set(-16.116, 14.37, 8.208);
light1.scale.set(0.1, 2.428, 2.739);
scene.add(light1);
// -x left
const light2 = new THREE.Mesh(geometry, createAreaLightMaterial(50));
light2.position.set(-16.109, 18.021, -8.207);
light2.scale.set(0.1, 2.425, 2.751);
scene.add(light2);
// +x
const light3 = new THREE.Mesh(geometry, createAreaLightMaterial(17));
light3.position.set(14.904, 12.198, -1.832);
light3.scale.set(0.15, 4.265, 6.331);
scene.add(light3);
// +z
const light4 = new THREE.Mesh(geometry, createAreaLightMaterial(43));
light4.position.set(-0.462, 8.89, 14.520);
light4.scale.set(4.38, 5.441, 0.088);
scene.add(light4);
// -z
const light5 = new THREE.Mesh(geometry, createAreaLightMaterial(20));
light5.position.set(3.235, 11.486, -12.541);
light5.scale.set(2.5, 2.0, 0.1);
scene.add(light5);
// +y
const light6 = new THREE.Mesh(geometry, createAreaLightMaterial(100));
light6.position.set(0.0, 20.0, 0.0);
light6.scale.set(1.0, 0.1, 1.0);
scene.add(light6);
function createAreaLightMaterial(intensity) {
const material = new THREE.MeshBasicMaterial();
material.color.setScalar(intensity);
return material;
}
return scene;
}
MDCThreeD.prototype.loadCamera = function () {
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
this.camera.position.set(1500, 700, 500);
}
MDCThreeD.prototype.loadControls = function () {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.target.set(0, 5, 0);
this.controls.enablePan = false;
this.controls.enableDamping = false;
this.controls.minPolarAngle = 0;
this.controls.maxPolarAngle = Math.PI * 0.5;
this.controls.autoRotate = false;
this.controls.update();
}
MDCThreeD.prototype.loadEnvironment = function () {
// if (this.options.environmentLight) {
// const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
// const texture = pmremGenerator.fromScene(this.RoomEnvironment()).texture;
// this.scene.environment = texture;
// pmremGenerator.dispose();
// }
if (this.options.background) {
this.scene.background = new THREE.TextureLoader().load(this.options.background);
}
//
const light = new THREE.AmbientLight(0xFFFFFF, 0.2);
this.scene.add(light);
//
const skyColor = 0xB1E1FF;
const groundColor = 0xB97A20;
const intensity = 0.5;
const hemisophereLight = new THREE.HemisphereLight(skyColor, groundColor, intensity);
this.scene.add(hemisophereLight);
/* 旋转变换 */
this.transformGroup = new THREE.Object3D();
this.transformObject = new THREE.Object3D();
this.transformGroup.add(this.transformObject);
}