"use strict";
var _ = require("lodash"),
Condicio = require("condicio"),
Matrix = require("gl-matrix"),
CvtForJson = require("../CvtForJson.js"),
Node = require("./Node.js"),
Interaction = require("./Interaction.js"),
Viewpoint = require("./Viewpoint.js"),
Utility = require("../Utility.js"),
ImageDisplay = require("../ImageDisplay.js"),
StreamDisplay = require("../StreamDisplay.js"),
ParamValidation = require("../ParamValidation.js"),
Roamer = require("./Roamer.js"),
jQuery = require("jquery"),
Filter = require("./Filter.js"),
Error = require("../Error.js"),
COLOR = require("color"),
Effect = require("./Effect.js"),
LightEffect = require("./LightEffect.js"),
NodeFactory = require("./NodeFactory.js"),
EffectFactory = require("./EffectFactory.js"),
InteractionFactory = require("./InteractionFactory.js"),
RoamerFactory = require("./RoamerFactory.js"),
cvtToNodeArray = require("./CvtToNodeArray"),
Command = require("./Command.js");
var InvalidArgumentError = Error.InvalidArgumentError;
/**
* @class View3D.View3D
* 三维渲染场景
*
* 创建节点工厂、创建效果工厂、创建交互工厂
*
* 提供HTML与三维显示窗口的通信接口
*
* 不应该直接创建,应该通过{@link Factory}提供的createView3D得到
*/
function View3D(session, elem, result, mode, enableSSL, proxyUri, callback) {
var _this = this;
this.session = session;
this.objectID = result.viewID;
this.rootNode = null;
/**
* @property {NodeFactory} NodeFactory 节点创建工厂
*/
this.NodeFactory = new NodeFactory(this.session, "NodeFactory");
/**
* @property {EffectFactory} EffectFactory 效果创建工厂
*/
this.EffectFactory = new EffectFactory(this.session, "EffectFactory");
/**
* @property {InteractionFactory} InteractionFactory 交互创建工厂
*/
this.InteractionFactory = new InteractionFactory(this.session, "InteractionFactory");
/**
* @property {RoamerFactory} RoamerFactory 漫游创建工厂
*/
this.RoamerFactory = new RoamerFactory(this.session, "RoamerFactory");
this.interaction = new Interaction("default interaction");
this.roamer = null;
this.lightEffects = {};
if (!Condicio.isUndefined(result.lightEffectIDs.skyLightEffect)) {
this.lightEffects.skyLightEffect = new LightEffect(this.session, result.lightEffectIDs.skyLightEffect);
this.addEffect(this.lightEffects.skyLightEffect);
}
if (!Condicio.isUndefined(result.lightEffectIDs.cameraLightEffect)) {
this.lightEffects.cameraLightEffect = new LightEffect(this.session, result.lightEffectIDs.cameraLightEffect);
this.addEffect(this.lightEffects.cameraLightEffect);
}
switch (mode) {
case 0:
this.display = new ImageDisplay(session, this.objectID, elem, enableSSL, proxyUri);
callback(null, this);
break;
case 1:
var callbackWrapper = function (error) {
if (!error)
callback(null, _this);
else {
_this.release();
callback(error, null);
}
};
this.display = new StreamDisplay(session, this.objectID, elem, result.turnServer, callbackWrapper);
break;
}
this.performanceListenerID = null;
};
View3D.prototype = _.create(View3D.prototype, {
constructor: View3D
});
View3D.prototype.release = function () {
if (this.performanceListenerID) {
this.session.unregisterCallback(this.performanceListenerID);
}
this.display.release();
};
View3D.prototype.getPresetLightEffects = function () {
return this.lightEffects;
};
/**
* 切换场景根节点
*
* @param {Node} node 新场景根节点,可以为空
*
* @return {Node}
*/
View3D.prototype.swapRootNode = function (node) {
if (Condicio.isNull(node))
this.session.request(this.objectID, "swapRootNode", { nodeID: "null" });
else {
Condicio.checkIsType(node, Node, "The type of rootNode must be 'Node'!");
this.session.request(this.objectID, "swapRootNode", { nodeID: node.objectID });
}
var oldRoot = this.rootNode;
this.rootNode = node;
return oldRoot;
};
/**
* 增加节点
*
* @param {Node} node 待增加的节点
* @param {Node} parentNode 父节点
*/
View3D.prototype.addNode = function (node, parentNode) {
Condicio.checkIsType(node, Node, "The type of childNodes must be 'Node'!");
Condicio.checkIsType(parentNode, Node, "The type of parentNode must be 'Node'!");
var params = {
nodeID: node.objectID,
parentNodeID: parentNode.objectID
};
this.session.request(this.objectID, "addNode", params);
};
/**
* 删除节点
*
* 只能对没有effect的节点生效
*
* @param {Node} node 待删除的节点
* @param {Node} parentNode 父节点
*/
View3D.prototype.removeNode = function (node, parentNode) {
Condicio.checkIsType(node, Node, "The type of childNodes must be 'Node'!");
Condicio.checkIsType(parentNode, Node, "The type of parentNode must be 'Node'!");
var params = {
nodeID: node.objectID,
parentNodeID: parentNode.objectID
};
this.session.request(this.objectID, "removeNode", params);
};
/**
* 增加效果
*
* 注意:只能对场景树中的节点调用此接口
* 注意:要移除添加了Effect的节点前,应该先调用removeEffect接口,再调用removeNode接口
*
*
* @param {View3D.Effect} effect 效果
*/
View3D.prototype.addEffect = function (effect) {
Condicio.checkNotNull(effect, "Effect must`t be NULL!");
Condicio.checkNotUndefined(effect, "Effect is undefined!");
Condicio.checkIsType(effect, Effect, "The type of effect must be 3DEffect");
this.session.request(this.objectID, "addEffect", { effectID: effect.objectID });
effect.on(this.session);
};
/**
* 删除效果
*
* @param {View3D.Effect} effect 效果
*/
View3D.prototype.removeEffect = function (effect) {
Condicio.checkNotNull(effect, "Effect must`t be NULL!");
Condicio.checkNotUndefined(effect, "Effect is undefined!");
Condicio.checkIsType(effect, Effect, "The type of effect must be 3DEffect");
this.session.request(this.objectID, "removeEffect", { effectID: effect.objectID });
effect.off(this.session);
};
/**
* 交换交互操作
*
* @param {Interaction} interaction 新的交互
*
* @return {Interaction} 前一个交互结果
*/
View3D.prototype.swapInteraction = function (interaction) {
Condicio.checkNotNull(interaction, "Interaction must`t be NULL!");
Condicio.checkNotUndefined(interaction, "Interaction is undefined!");
// 开启新交互,停止原交互
var preInteraction = this.interaction;
this.interaction = interaction;
if (!Condicio.isNull(preInteraction))
preInteraction.off(this.session);
interaction.on(this.session);
this.session.request(this.objectID, "swapInteraction", { interactionID: interaction.objectID });
return preInteraction;
};
/**
* 设置漫游器
*
* @param {Roamer} roamer 要设置的漫游器
*
* @return {Roamer} 返回之前的漫游器
*
*/
View3D.prototype.swapRoamer = function (roamer) {
Condicio.checkIsType(roamer, Roamer, "The type of roamer must be 'Roamer'");
var params = {
roamerID: roamer.objectID
};
this.session.request(this.objectID, "swapRoamer", params);
var preRoamer = this.roamer;
this.roamer = roamer;
return preRoamer;
};
/**
* 查询指定名称的子孙节点
*
* @param {Node} node 父节点(该节点必须是DrawingRootNode)
* @param {String[]} names 待查找节点的名字,要求名字不能重复,重复的名字在外部事先过滤
* @param {Function} callback 返回查询结果
* @param {Error} callback.error 错误
* @param {Node[]} callback.nodes 查找到的节点集合,如果某个名字对应的节点没有找到,对应位置返回null
*/
View3D.prototype.findDescendantNodes = function (node, names, callback) {
Condicio.checkIsType(node, Node, "The type of parentNode must be 'Node'!");
ParamValidation.checkIsTypeArray(names, String, "The type of names must be 'String Array' and valid");
Condicio.checkArgument(names.length != 0, "The names-Array must`t be NULL!");
Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");
var _this = this;
var callbackWrapper = function (error, result) {
if (error)
callback(error, []);
else {
var nodes = result.nodes;
var tempNodes = [];
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i].nodeID !== "")
tempNodes.push(new Node(_this.session, nodes[i].nodeID, nodes[i].name));
else
tempNodes.push(null); // 没找到给一个空
}
callback(null, tempNodes);
}
};
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "findDescendantNodes");
var params = {
nodeID: node.objectID,
names: names,
callbackID: callbackID
};
this.session.request(this.objectID, "findDescendantNodes", params);
};
/**
* 计算定位到一组节点的视角
*
* @param {Object} option 计算定位视角参数
* @param {Node[]} option.nodes 需要计算视角的节点集合(出现为空的节点返回错误)
* @param {vec3} [option.direction] 要求视角的方向,若指定该参数将会按照指定的视角方向计算视角,否则选择最适合的视角方向计算视角
* @param {Number} [option.daRatio=0.6] 以屏幕宽度或者高度的较小值为基数,乘以该百分比作为显示区域大小的边长(值:0<daRate<=1)传入0时会是默认值
* @param {Function} option.callback 计算完成后回调
* @param {InvalidArgumentError} option.callback.err 计算失败返回错误:节点集合中的节点存在一个节点有多个父节点,引发错误
* @param {Viewpoint} option.callback.viewpoint 返回计算的视角
*/
View3D.prototype.calcNodesFocusedViewpoint = function (option) {
option.direction = option.direction || new Matrix.vec3.fromValues(0, 0, 0);
option.daRatio = option.daRatio || 0.6;
Condicio.checkIsType(option, Object, "The type of option must be 'Object'!");
ParamValidation.checkIsTypeArray(option.nodes, Node, "The type of nodes must be 'Node Array' and valid");
Condicio.checkArgument(option.nodes.length != 0, "The nodes-Array must`t be NULL!");
ParamValidation.checkIsVec3(option.direction, "The type of viewpoint-direction must be 'vec3' and valid");
for (var i = 0; i < option.direction.length; ++i) {
Condicio.checkArgument(!isNaN(option.direction[i]), "The viewpoint-direction is invalid!");
Condicio.checkIsNumber(option.direction[i], "The viewpoint-direction must be valid!");
}
Condicio.checkIsNumber(option.daRatio, "The type of data-Ratio must be 'Number'!");
Condicio.checkArgument(option.daRatio <= 1 && option.daRatio > 0, "The data-Ratio must: > 0 && <= 1");
Condicio.checkIsFunction(option.callback, "The type of callback must be 'Function'!");
var nodeIDs = [];
for (var i = 0; i < option.nodes.length; ++i) {
nodeIDs.push(option.nodes[i].objectID);
}
var callbackWrapper = function (error, result) {
if (!error) {
var viewpoint = result.viewpoint;
var eye = CvtForJson.cvtToVec3(viewpoint.eye);
var center = CvtForJson.cvtToVec3(viewpoint.center);
var up = CvtForJson.cvtToVec3(viewpoint.up);
option.callback(null, new Viewpoint(eye, center, up));
}
else {
option.callback(error, null);
}
};
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "calcNodesFocusedViewpoint");
var params = {
nodeIDs: nodeIDs,
direction: [option.direction[0], option.direction[1], option.direction[2]],
daRatio: option.daRatio,
callbackID: callbackID
};
this.session.request(this.objectID, "calcNodesFocusedViewpoint", params);
};
/**
* 定位到某个视角
*
* @param {Viewpoint} viewpoint 需要定位到的视角
* @param {Number} [time = 0.0] 定位视角时移动的时间(单位:毫秒),会形成动画效果(不给时间默认为0,会直接定位过去)
* @param {Function} [callback] 定位视角完成之后回调,不给回调会有默认值
*/
View3D.prototype.translateViewpoint = function (viewpoint, time, callback) {
time = time || 0.0;
callback = callback || function () { };
Condicio.checkIsType(viewpoint, Viewpoint, "The type of viewpoint must be 'Viewpoint'!");
Condicio.checkIsNumber(time, Number, "The type of move-time must be 'Number'!");
Condicio.checkArgument(time >= 0, "The param time must '>=0");
Condicio.checkIsFunction(callback, Function, "The type of callback must be 'Function'!");
time = time / 1000;
// note:在不需要回调的时候这种调用是否合适
var callbackWrapper = function (error, result) {
callback();
};
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "translateViewpoint");
var params = {
viewpoint: {
eye: [viewpoint.eye[0], viewpoint.eye[1], viewpoint.eye[2]],
center: [viewpoint.center[0], viewpoint.center[1], viewpoint.center[2]],
up: [viewpoint.up[0], viewpoint.up[1], viewpoint.up[2]]
},
time: time,
callbackID: callbackID
};
this.session.request(this.objectID, "translateViewpoint", params);
};
/**
* 获取当前视角
*
* @param {Function} callback 获取视角完成后的回调
* @param {Error} callback.err 获取视角失败返回错误
* @param {Viewpoint} callback.viewpoint 获取视角成功返回视角
*/
View3D.prototype.acquireViewpoint = function (callback) {
Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");
var callbackWrapper = function (error, result) {
if (!error) {
var viewpoint = result.viewpoint;
var eye = CvtForJson.cvtToVec3(viewpoint.eye);
var center = CvtForJson.cvtToVec3(viewpoint.center);
var up = CvtForJson.cvtToVec3(viewpoint.up);
callback(null, new Viewpoint(eye, center, up));
}
else
callback(error, null);
}
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "acquireViewpoint");
this.session.request(this.objectID, "acquireViewpoint", { callbackID: callbackID });
};
/**
* 移动节点(该节点是MatrixTransform节点才支持移动)
* 支持平移、旋转
* 进行移动的所有点坐标信息,必须统一为世界坐标系坐标或局部坐标系坐标
* 注意:当使用matrix进行移动节点的时候,如果此时的matrix是一个复合的mat(有多个旋转、移动),且有移动时间时,
* 此时的动画效果中间过程是无法预估的运动轨迹,但是始末位置都是对的。
* 移动方式有两种:1.使用matrix进行节点移动, 2.使用translations或者rotation进行节点移动,二者必用其一。
* @param {Object} option 移动节点参数
* @param {Object[]} option.transformations 需要做的移动变换集合(同一组集合中要移动的节点不能重复)
* @param {Node} option.transformations.node 需要移动的节点
* @param {mat4} [option.transformations.matrix] 需要进行变换的矩阵信息(<a href="https://fulongtech.atlassian.net/wiki/spaces/RenderingTech/pages/75957738/VIC">参考文档详见</a>)
* @param {Boolean} [option.transformations.isLocalCoordinate = false] 移动信息坐标是否为局部坐标(默认处理方式为世界坐标)
* @param {vec3} [option.transformations.translation] 需要将目标节点向某个方向移动的位置
* @param {Object} [option.transformations.rotation] 目标节点旋转参数
* @param {vec3} option.transformations.rotation.position 旋转点
* @param {vec3} option.transformations.rotation.direction 旋转轴方向
* @param {Number} option.transformations.rotation.angle 旋转角度(角度大于零度,根据其旋转轴方向,使用右手法则,可确定模型旋转方向,负角度则相反)
* @param {Number} [option.duration = 0] 移动节点经过的时间(单位:毫秒)
* @param {Function} option.callback 节点移动完成时回调
* @param {InvalidArgumentError} option.callback.err 错误:某个节点是不可移动的
*/
View3D.prototype.transformNodes = function (option) {
option.duration = option.duration || 0;
Condicio.checkIsObject(option, "The type of option must be 'Object'!");
ParamValidation.checkIsTypeArray(option.transformations, Object, "The type of transformations must be 'Object Array'!");
Condicio.checkArgument(option.transformations.length !== 0, "The transformations-Array must`t be NULL!");
var defaultVec3 = Matrix.vec3.fromValues(0, 0, 0);
var transformations = [];
for (var i = 0; i < option.transformations.length; ++i) {
Condicio.checkIsType(option.transformations[i].node, Node, "Every node must be valid!");
var isLocalCoordinate = option.transformations[i].isLocalCoordinate || false;
//使用mat或者translation/rotation进行变换
if ((Condicio.isUndefined(option.transformations[i].matrix))
&& (Condicio.isUndefined(option.transformations[i].translation))
&& (Condicio.isUndefined(option.transformations[i].rotation))) {
console.error("参数错误");
return;
}
if (!Condicio.isUndefined(option.transformations[i].matrix)) {
ParamValidation.checkIsMat4(option.transformations[i].matrix, "The type of matrix must be 'mat4' and valid");
var transformMat = [];
for (var n = 0; n < 16; n++)
transformMat.push(option.transformations[i].matrix[n]);
transformations.push({ nodeID: option.transformations[i].node.objectID, matrix: transformMat });
}
var param = {
nodeID: option.transformations[i].node.objectID,
isLocalCoordinate: isLocalCoordinate
};
if (!Condicio.isUndefined(option.transformations[i].translation)) {
ParamValidation.checkIsVec3(option.transformations[i].translation, "The type of translation must be 'vec3' and valid");
var translation = [];
for (var j = 0; j < 3; ++j)
translation.push(option.transformations[i].translation[j]);
param.translation = translation;
}
if (!Condicio.isUndefined(option.transformations[i].rotation)) {
Condicio.checkIsType(option.transformations[i].rotation, Object, "The type of rotation must be 'Object'!");
ParamValidation.checkIsVec3(option.transformations[i].rotation.position, "The type of rotation.position must be 'vec3' and valid");
ParamValidation.checkIsVec3(option.transformations[i].rotation.direction, "The type of rotation.direction must be 'vec3' and valid");
var position = [];
for (var k = 0; k < 3; ++k)
position.push(option.transformations[i].rotation.position[k]);
var direction = [];
for (var l = 0; l < 3; ++l)
direction.push(option.transformations[i].rotation.direction[l]);
option.transformations[i].rotation.position = position;
option.transformations[i].rotation.direction = direction;
param.rotation = option.transformations[i].rotation;
}
transformations.push(param);
}
Condicio.checkIsNumber(option.duration, "The type of transform-node-time must be 'Number'!");
Condicio.checkIsFunction(option.callback, "The type of callback must be 'Function'!");
var callbackWrapper = function (error, result) {
if (error) {
option.callback(error);
}
else
option.callback(null);
};
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "transformNodes");
var params = {
transformations: transformations,
duration: option.duration / 1000,
callbackID: callbackID
};
this.session.request(this.objectID, "transformNodes", params);
};
/**
* 执行Command命令
*
* @param {Command} command 需要执行的Command
*
*/
View3D.prototype.executeCommand = function (command) {
Condicio.checkIsType(command, Command, "The type of command must be 'Command'!");
Condicio.checkNotNull(command, "command parameter cannot be null");
Condicio.checkNotUndefined(command, "command parameter cannot be undefined");
this.session.request(this.objectID, "executeCommand", { commandID: command.objectID });
};
/**
* 屏幕截图
*
* @param {Function} callback 获取屏幕截图完成后的回调
* @param {Error} callback.err 获取屏幕截图失败返回错误
* @param {String} callback.screenShot 获取屏幕截图成功返回截图数据
*/
View3D.prototype.captureScreenShot = function (callback) {
Condicio.checkIsFunction(callback, Function, "The type of 'callback' must be 'Function'!");
var callbackWrapper = function (error, result) {
if (error) {
callback(error, null);
}
else
callback(null, result.imageData);
};
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "captureScreenShot");
this.session.request(this.objectID, "captureScreenShot", { callbackID: callbackID });
};
//判断是否为逆时针顺序的点
function isClockWise(p1, p2, p3, norm) {
norm = norm || Matrix.vec3.fromValues(0, 0, 1);
var norm1 = Matrix.vec3.create();
Matrix.vec3.sub(norm1, p2, p1);
var norm2 = Matrix.vec3.create();
Matrix.vec3.sub(norm2, p3, p2);
var norm0 = Matrix.vec3.create();
Matrix.vec3.cross(norm0, norm1, norm2);
return Matrix.vec3.dot(norm0, norm) < 0;
};
//判断是否为逆时针顺序的点集合
function isPtArrayClockWise(pts) {
if (pts.length < 3) {
return true;
}
if (pts.length == 3) {
isClockWise(pts[0], pts[1], pts[2]);
}
var ptInit = pts[0];
var xMin = ptInit[0];
var xMax = ptInit[0];
var yMin = ptInit[1];
var yMax = ptInit[1];
var index1 = 0;
var index2 = 0;
var index3 = 0;
var index4 = 0;
for (var i = 1; i < 4; ++i) {
var ptCur = pts[i];
if (xMin > ptCur[0]) {
xMin = ptCur[0]
index1 = i;
}
if (xMax < ptCur[0]) {
xMax = ptCur[0];
index2 = i;
}
if (yMin > ptCur[1]) {
yMin = ptCur[1];
index3 = i;
}
if (yMax < ptCur[1]) {
yMax = ptCur[1];
index4 = i;
}
}
var indexArray = [];
for (var i = 0; i < 4; ++i) {
if (i === index1) {
indexArray.push(i);
}
if (i === index2) {
indexArray.push(i);
}
if (i === index3) {
indexArray.push(i);
}
if (i === index4) {
indexArray.push(i);
}
}
return isClockWise(pts[indexArray[0]], pts[indexArray[1]], pts[indexArray[2]]);
};
//将点序转为逆时针
function setPtsToAntiClock(pt3darr) {
var vpt3darr = [];
if (isPtArrayClockWise(pt3darr)) {
for (var i = 3; i >= 0; --i) {
vpt3darr.push(pt3darr[i]);
}
for (var i = 7; i >= 4; --i) {
vpt3darr.push(pt3darr[i]);
}
}
else {
vpt3darr = pt3darr;
}
return vpt3darr;
};
var analysePicked = function (picked, session) {
var pickedArray = [];
for (var i = 0; i < picked.length; ++i) {
var pick = new Object;
pick.scale = picked[i].scale;
pick.area = picked[i].area;
pick.nodePath = [];
var nodePath = picked[i].nodePath;
for (var j = 0; j < nodePath.length; ++j) {
pick.nodePath.push(new Node(session, nodePath[j].nodeID, nodePath[j].name));
}
pickedArray.push(pick);
}
return pickedArray;
};
/**
* 得到box范围内的节点以及节点在其中所占比例及面积
*
* @param {Object} option box求交参数
* @param {vec3[]} option.boxPoints Box八个顶点(按照先底面后顶面,每个面的点为连续(顺时针或逆时针),上下底面对应)
* @param {Function} callback 计算完成后的回调
* @param {Error} callback.err 求交失败返回错误
* @param {Object[]} callback.picked 计算结果
* @param {Number} callback.picked.scale 节点在所选区域中的部分占整体的比例
* @param {Number} callback.picked.area 节点在所选区域中的部分占的面积
* @param {Node[]} callback.picked.nodePath 选中的节点路径,即从被选中的节点到根节点的所有节点按序排列
*
*
*/
View3D.prototype.queryNodesInBox = function (option, callback) {
Condicio.checkIsType(option, Object, "The type of option must be 'Object'!");
ParamValidation.checkIsTypeArray(option.boxPoints, Matrix.glMatrix.ARRAY_TYPE, "The type of points must be 'Number Array' and valid!");
Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");
var boxPointArray = [];
for (var i = 0; i < option.boxPoints.length; ++i) {
var point = [];
for (var j = 0; j < 3; ++j) {
point.push(option.boxPoints[i][j]);
}
boxPointArray.push(point);
}
boxPointArray = setPtsToAntiClock(boxPointArray);
var _this = this;
var callbackWrapper = function (error, result) {
var picks = result.picked;
//解析点击结果
var picked = analysePicked(picks, _this.session);
callback(null, picked);
}
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "queryNodesInBox");
var params = {
boxPointArray: boxPointArray,
callbackID: callbackID
};
this.session.request(this.objectID, "queryNodesInBox", params);
};
/**
* 计算一组节点的AABB包围盒
* 注:只支持对当前加载的模型节点进行计算, 目前不支持对装置根节点计算AABB(就是nodes给一个drawingRoot)
* 计算的是能够包围所有这组节点的包围盒,而不是分别求出各自的包围盒
* <a href="https://baike.baidu.com/item/AABB%E7%9B%92/10087682?fr=aladdin">AABB相关解释请参考</a>
*
* @param {Node[]} nodes 需要包围的一组节点
* @param {Function} callback 计算完成后的回调
* @param {Error} callback.err 返回错误
* err {InvalidArgumentError} 节点不支持计算包围盒(如有两个父节点)
* @param {Object} callback.aabb 计算结果
* @param {vec3} callback.aabb.min 左下角坐标
* @param {vec3} callback.aabb.max 右上角坐标
*
*/
View3D.prototype.calcAABB = function (nodes, callback) {
ParamValidation.checkIsTypeArray(nodes, Node, "The type of nodes must be 'Node Array' and valid!");
var nodeIDs = CvtForJson.cvtNodeArray(nodes);
var callbackWrapper = function (error, result) {
if (error)
callback(error, null);
else {
var min = result.aabb.min;
var max = result.aabb.max;
var aabb = {
min: CvtForJson.cvtToVec3(min),
max: CvtForJson.cvtToVec3(max)
};
callback(null, aabb);
}
}
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "calcAABB");
var params = {
nodeIDs: nodeIDs,
callbackID: callbackID
};
this.session.request(this.objectID, "calcAABB", params);
};
/**
* 计算一个有边界面与指定节点的相交结果
* 只会对可见节点求交,可见节点是已加载并且没有被隐藏的节点
*
* @param {Object} option 求交需要的参数
* @param {vec3[]} option.polygon 一个平面上的闭合边界点组(必须是凸多边形的边界点)
* @param {Function} option.callback 计算完成后的回调
* @param {Error} option.callback.err 返回错误
* err {InvalidArgumentError} polygon不满足要求
* @param {Object[]} option.callback.results 求交结果
* @param {Node[]} option.callback.results.nodePath 求交到的节点路径,即从被选中的节点到根节点的所有节点按序排列
* @param {vec3[]} option.callback.results.polyline 交到的点集合
*
*/
View3D.prototype.queryNodesAcrossPolygon = function (option) {
Condicio.checkIsType(option, Object, "The type of option must be 'Object'!");
ParamValidation.checkIsVec3Array(option.polygon, "The type of option.polygon must be 'Number Array' and valid!");
Condicio.checkIsFunction(option.callback, "The type of option.callback must be 'Function'!");
var bndPointArray = CvtForJson.cvtVec3Array(option.polygon);
var _this = this;
var callbackWrapper = function (error, result) {
if (error)
option.callback(error, null);
else {
var len = result.intersections.length;
var itns = [];
for (var i = 0; i < len; ++i) {
var polyline = CvtForJson.cvtToVec3Array(result.intersections[i].polyline);
var np = result.intersections[i].nodePath;
var nodePath = cvtToNodeArray(np, _this.session);
var itn = {
nodePath: nodePath,
polyline: polyline
};
itns.push(itn);
}
option.callback(null, itns);
}
}
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "queryNodesAcrossPolygon");
var params = {
polygon: bndPointArray,
callbackID: callbackID
};
this.session.request(this.objectID, "queryNodesAcrossPolygon", params);
};
/**
* 改变场景的背景色
* @param {Color} color 背景颜色,颜色中的alpha值无效
*
*/
View3D.prototype.changeBackgroundColor = function (color) {
//Condicio.checkIsType(color, COLOR, "The type of color must be 'Color'");
var params = {
color: color.rgbaArray()
};
this.session.request(this.objectID, "changeBackgroundColor", params);
};
/**
* 创建增加效果命令
*
* @param {Effect} effect 想要创建增加效果的命令。 备注:此接口创建的command,当移除Effect时,应使用createRemoveEffectCommand接口,不支持使用removeEffect操作
*
* @return {Command}
*/
View3D.prototype.createAddEffectCommand = function (effect) {
Condicio.checkNotNull(effect, "Effect must`t be NULL!");
Condicio.checkNotUndefined(effect, "Effect is undefined!");
Condicio.checkIsType(effect, Effect, "The type of effect must be 3DEffect");
var commandID = Utility.genGUID();
var params = {
commandID: commandID,
effectID: effect.objectID
};
this.session.request(this.objectID, "createAddEffectCommand", params);
effect.on(this.session);
return new Command(commandID);
};
/**
* 创建删除效果命令
*
* @param {Effect} effect 想要创建删除效果的命令。 备注:此接口和createAddEffectCommand接口对应使用,
* createAddEffectCommand创建的command被应用后,若要移除Effect,则必须用此接口,不支持使用removeEffect操作!
*
* @return {Command}
*/
View3D.prototype.createRemoveEffectCommand = function (effect) {
Condicio.checkNotNull(effect, "Effect must`t be NULL!");
Condicio.checkNotUndefined(effect, "Effect is undefined!");
Condicio.checkIsType(effect, Effect, "The type of effect must be 3DEffect");
var commandID = Utility.genGUID();
var params = {
commandID: commandID,
effectID: effect.objectID
};
this.session.request(this.objectID, "createRemoveEffectCommand", params);
effect.off(this.session);
return new Command(commandID);
};
/**
* 创建组合命令
*
* @param {Command[]} commands 创建组合命令 commands是按顺序执行的。
* @return {Command}
*/
View3D.prototype.createCompositeCommand = function (commands) {
ParamValidation.checkIsTypeArray(commands, Command, "The type of commands must be 'Command Array' and valid")
Condicio.checkArgument((commands.length !== 0), "The commands-Array must`t be NULL!");
var commandIDs = CvtForJson.cvtCommandArray(commands)
var commandID = Utility.genGUID();
var params = {
commandID: commandID,
commandIDs: commandIDs
};
this.session.request(this.objectID, "createCompositeCommand", params);
return new Command(commandID);
};
var checkThreePointsCollinear = function (vec3Array) {
var y = (vec3Array[2][1] - vec3Array[0][1]) / (vec3Array[1][1] - vec3Array[0][1]);
var x = (vec3Array[2][0] - vec3Array[0][0]) / (vec3Array[1][0] - vec3Array[0][0]);
var z = (vec3Array[2][2] - vec3Array[0][2]) / (vec3Array[1][2] - vec3Array[0][2]);
Condicio.checkArgument(x != y || y != z || x != z, "The interface supports up to 32 planes");
};
/**
* 计算凸多面体范围内的节点以及节点在其中所占比例及面积(最大支持32个面数量的求交)
*
* @param {Object[]} planeArray 面存储面的数组
* @param {Object} planeArray.plane 面对象
* @param {vec3[]} planeArray.plane.points 组成面的点
* @param {vec3[]} planeArray.plane.normal 该面的法线(取该面三个点,三个点的存放顺序决定这个面的法线,顺序遵循右手定理)
*
* @param {Function} callback 计算完成后的回调
* @param {Error} callback.err 求交失败返回错误
* @param {Object[]} callback.picked 计算结果
* @param {Number} callback.picked.scale 节点在所选区域中的部分占整体的比例
* @param {Number} callback.picked.area 节点在所选区域中的部分占的面积
* @param {Node[]} callback.picked.nodePath 选中的节点路径,即从被选中的节点到根节点的所有节点按序排列
*/
View3D.prototype.queryNodesInConvexPolyhedron = function (planeArray, callback) {
ParamValidation.checkIsTypeArray(planeArray, Object, "The type of planeArray must be 'Object Array");
Condicio.checkArgument(planeArray.length <= 32, "The interface supports up to 32 planes");
for (var i = 0; i < planeArray.length; i++) {
var plane = planeArray[i];
Condicio.checkIsType(plane, Object, "The type of planeArray.plane must be 'Object'!");
Condicio.checkArgument(plane.points != undefined && plane.points.length != 0, "plane.points Cannot be empty");
ParamValidation.checkIsVec3Array(plane.points, "The type of planeArray.plane.points must be 'Vec3 Array' and valid!");
Condicio.checkArgument(plane.normal != undefined && plane.normal.length != 0, "plane.normal Cannot be empty");
ParamValidation.checkIsVec3Array(plane.normal, "The type of planeArray.plane.normal must be 'Vec3 Array' and valid!");
Condicio.checkArgument(plane.normal.length === 3, "plane.normal It should be three points");
checkThreePointsCollinear(plane.normal);
}
Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");
var planesArray = [];
for (var i = 0; i < planeArray.length; i++) {
var plane = planeArray[i];
for (var j = 0; j < plane.normal.length; j++) {
var normalPoint = plane.normal[j];
var arrayPoint = CvtForJson.cvtVec3(normalPoint);
planesArray.push(arrayPoint);
}
}
var _this = this;
var callbackWrapper = function (error, result) {
if (error)
callback(error, null);
else {
var picks = result.picked;
//解析点击结果
var picked = analysePicked(picks, _this.session);
callback(error, picked);
}
}
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "queryNodesInConvexPolyhedron");
var params = {
planeArray: planesArray,
callbackID: callbackID
};
this.session.request(this.objectID, "queryNodesInConvexPolyhedron", params);
};
/**
* 计算组成圆形的点的集合(圆形tracker和该接口组合使用时,二者pointNum需要一致)
*
* @param {Object} option 需要的参数
* @param {vec3} option.startPt 起始点
* @param {vec3} option.endPt 终点
* @param {Number} option.pointNum 画圆的点数量(整数 3-65536)
*
* @param {Function} callback 计算完成后的回调
* @param {Error} callback.err 求交失败返回错误
* @param {vec3[]} callback.circularPointsArray 计算结果数组
*/
View3D.prototype.calculateCircularPoints = function (option, callback) {
ParamValidation.checkIsVec3(option.startPt, "The type of option.startPt must be 'vec3' and valid");
ParamValidation.checkIsVec3(option.endPt, "The type of option.endPt must be 'vec3' and valid");
Condicio.checkArgument(option.startPt[0] != option.endPt[0] && option.startPt[1] != option.endPt[1], "option.startPt and option.endPt The XY of cannot be the same");
if (option.pointNum === undefined) { option.pointNum = 30; }
Condicio.checkIsNumber(option.pointNum, "option.pointNum must be a number");
Condicio.checkArgument(option.pointNum >= 3 && option.pointNum <= 65536 && option.pointNum % 1 === 0, "option.pointNum must be a positive integer with values from 3 to 65536");
Condicio.checkIsFunction(callback, "The type of callback must be 'Function'!");
var callbackID = Utility.genGUID();
var callbackWrapper = function (error, result) {
if (error)
callback(error, null);
else {
var circularPointsArray = CvtForJson.cvtToVec3Array(result.circularPointsArray);
callback(null, circularPointsArray);
}
}
this.session.registerCallback(callbackID, callbackWrapper, "calculateCircularPoints");
var startPoint = CvtForJson.cvtVec3(option.startPt);
var endPoint = CvtForJson.cvtVec3(option.endPt);
var params = {
callbackID: callbackID,
pointNum: option.pointNum,
startPoint: startPoint,
endPoint: endPoint
};
this.session.request(this.objectID, "calculateCircularPoints", params);
};
/**
* 此接口用于圆坑点集合向凸多面体求交接口的对接
*
* @param {vec3[]} topPts 圆柱或圆台的上底点数组
* @param {vec3[]} bottomPts 圆柱或圆台的下底点数组
* @return {Object[]} planesArray 返还用于queryNodesInConvexPolyhedron接口的数组
*/
View3D.prototype.coneCvtConvexPolyhedron = function (topPts, bottomPts) {
// 求交面数组
var planeArray = [];
// 顶面
planeArray.push(topPts[2]);
planeArray.push(topPts[1]);
planeArray.push(topPts[0]);
//底面
planeArray.push(bottomPts[0]);
planeArray.push(bottomPts[1]);
planeArray.push(bottomPts[2]);
// 侧面
var i2;
for (i2 = 1; i2 < topPts.length; i2++) {
planeArray.push(topPts[i2]);
planeArray.push(bottomPts[i2]);
planeArray.push(topPts[i2 - 1]);
}
planeArray.push(topPts[0]);
planeArray.push(bottomPts[0]);
planeArray.push(topPts[i2 - 1]);
var planesArray = [];
for (var i = 0; i < planeArray.length; i = i + 3) {
var normal = [];
normal.push(planeArray[i]);
normal.push(planeArray[i + 1]);
normal.push(planeArray[i + 2]);
var plane = {
points: normal,
normal: normal
}
planesArray.push(plane);
}
return planesArray;
};
/**
* 设置画质清晰度
*
* @param {Object} [option]
* @param {number} [option.staticQuality = 100] 客户端不进行三维场景漫游操作(拖动,放大等操作)时画质的清晰度(0,100]
* 值越大画质清晰度越高占用带宽越高
* @param {number} [option.dynamicQuality = 80] 客户端进行三维场景漫游操作(拖动,放大等操作)时画质的清晰度(0,100]
* 值越小画质清晰度越低占用带宽越低
*/
View3D.prototype.setPictureQuality = function (option) {
var defaultOption = {
staticQuality: 100,
dynamicQuality: 80
};
var newOption = jQuery.extend({}, defaultOption, option);
Condicio.checkIsNumber(newOption.staticQuality, "The type of staticQuality must be 'Number'!");
Condicio.checkIsNumber(newOption.dynamicQuality, "The type of dynamicQuality must be 'Number'!");
Condicio.checkArgument(newOption.staticQuality > 0 && newOption.staticQuality <= 100, "option.staticQuality must Less than or equal to 100 and greater than 0!");
Condicio.checkArgument(newOption.dynamicQuality > 0 && newOption.dynamicQuality <= 100, "option.dynamicQuality must Less than or equal to 100 and greater than 0!");
this.session.request(this.objectID, "setPictureQuality", newOption);
};
/**
* 设置性能监听器
* 当用户漫游浏览三维区域的时候按照固定频率通知当前传输性能
* @param {Object} performanceListener 性能监听器
* @param {Number} [performanceListener.interval] 通知的频率(x秒/次),默认为5.0,可以带小数,值要求大于0,推荐不低于1,不高于10
* @param {Functcion} performanceListener.notifier 性能情况通知回调
* @param {Object} performanceListener.notifier.info 性能信息
* @param {Number} performanceListener.notifier.info.imageTransferDelay 图片传输延时(单位:ms)
* --图片传输延时低于20ms认为是流畅的
* --图片传输延时低于60ms认为可用
* --图片传输延时大于60ms认为不可用为true,代表当前用户正在漫游浏览三维,反之则否
*/
View3D.prototype.setPerformanceListener = function (performanceListener) {
Condicio.checkIsObject(performanceListener, "The type of performanceMonitor must be 'Object'!");
performanceListener.interval = performanceListener.interval || 5;
Condicio.checkIsNumber(performanceListener.interval, "The type of interaval must be 'Number'!");
Condicio.checkArgument(performanceListener.interval > 0, "Interval must greater than 0!")
Condicio.checkIsFunction(performanceListener.notifier, "The type of notifier must be 'Function'!");
var notifierWrapper = function (error, result) {
performanceListener.notifier(result.info);
};
if (this.performanceListenerID) {
this.session.unregisterCallback(this.performanceListenerID);
}
var notifierID = Utility.genGUID();
this.session.registerCallback(notifierID, notifierWrapper, "setPerformanceListener", true);
this.performanceListenerID = notifierID;
var params = {};
params.performanceListener = {
notifierID: notifierID,
interval: performanceListener.interval
}
this.session.request(this.objectID, "setPerformanceListener", params);
};
/**
* 计算一组节点的面片面积和
* 注:只支持对当前加载的模型节点进行计算, 目前不支持对装置根节点计算面积
* 计算的是所有节点的面片面积和,不是表面积
*
* @param {Node[]} nodes 需要计算的一组节点
* @param {Function} callback 计算完成后的回调
* @param {Error} callback.err 返回错误
* err {InvalidArgumentError} 节点不支持计算面积(如有两个父节点、DrawingRoot节点等)
* @param {Number} callback.area 计算结果,单位平方毫米
*
*/
View3D.prototype.calcNodesArea = function (nodes, callback) {
ParamValidation.checkIsTypeArray(nodes, Node, "The type of nodes must be 'Node Array' and valid!");
var nodeIDs = CvtForJson.cvtNodeArray(nodes);
var callbackWrapper = function (error, result) {
if (error)
callback(error, null);
else {
callback(null, result.area);
}
}
var callbackID = Utility.genGUID();
this.session.registerCallback(callbackID, callbackWrapper, "calcNodesArea");
var params = {
nodeIDs: nodeIDs,
callbackID: callbackID
};
this.session.request(this.objectID, "calcNodesArea", params);
};
module.exports = View3D;