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

849 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* @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);
}