diff --git a/index.html b/index.html
new file mode 100644
index 000000000..9e69c3963
--- /dev/null
+++ b/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/api/chat.js b/src/api/chat.js
new file mode 100644
index 000000000..66acbabd1
--- /dev/null
+++ b/src/api/chat.js
@@ -0,0 +1,241 @@
+
+import request from '@/utils/request';
+
+export function postDataBd(data) {
+ return request({
+ url: `/api/audio/bd?group=${data.group}&conversationId=${data.conversationId}`,
+ method: 'post',
+ data: data.file,
+ });
+}
+
+// 创建/获取会话id
+export function getConversation(data) {
+ return request({
+ url: `/api/audio/conversation`,
+ method: 'get',
+ params: {
+ group: data.group,
+ userId: data.userId,
+ code: data.code
+ }
+ });
+}
+
+export function postDataXf(data) {
+ return request({
+ url: `/api/audio/xf`,
+ method: 'post',
+ data: data
+ });
+}
+
+// 获取语音历史记录
+export function getHistoryVoice(code) {
+ return request({
+ url: `/api/audio/${code}/history`,
+ method: 'get'
+ });
+}
+
+// 文字聊天 发送文字
+export function chatWithText(data, group) {
+ return request({
+ url: `/api/jointTraining/chatWithText?group=${group}`,
+ method: 'post',
+ data: {
+ message: data,
+ }
+ });
+}
+
+// 文字聊天 发送文字
+export function chatWithAudio(file, group) {
+ return request({
+ url: `/api/jointTraining/chatWithAudio?group=${group}`,
+ method: 'post',
+ data: file
+ });
+}
+
+// 文字聊天 发送文字
+export function getJoinTrainCode(data, group) {
+ return request({
+ url: `/api/jointTraining/qrCode?group=${group}`,
+ method: 'post',
+ data: data
+ });
+}
+
+
+// 创建房间
+export function postCreateRoom(data) {
+ return request({
+ url: `/api/jointTraining/room`,
+ method: 'post',
+ data: data
+ });
+}
+
+// 检查房间存在
+export function checkRoomExist(params) {
+ return request({
+ url: `/api/simulationRoom`,
+ method: 'get',
+ params
+ });
+}
+
+// 获取房间详情
+export function postRoomDetail(group) {
+ return request({
+ url: `/api/jointTraining/room/${group}`,
+ method: 'get',
+ });
+}
+
+// 销毁房间
+export function deljointTrainRoom(group) {
+ return request({
+ url: `/api/jointTraining/room`,
+ method: 'delete',
+ params: {
+ group: group,
+ }
+ });
+}
+
+// 查询有权限房间列表
+export function getjointTrainList() {
+ return request({
+ url: `/api/jointTraining/room/list`,
+ method: 'get'
+ });
+}
+
+// 加入房间
+export function getjointTraining(group) {
+ return request({
+ url: `/api/jointTraining/room/join`,
+ method: 'put',
+ params: {
+ group: group,
+ }
+ });
+}
+
+// 设置人员角色
+export function putUserRoles(data, group) {
+ return request({
+ url: `/api/jointTraining/room/user/role?group=${group}`,
+ method: 'put',
+ data: data
+ });
+}
+
+// 设置人员角色
+export function getJointTrainRoomUserList(group) {
+ return request({
+ url: `/api/jointTraining/room/${group}/user/list`,
+ method: 'get',
+ });
+}
+
+// 离开房间接口
+export function putJointTrainingExit(group) {
+ return request({
+ url: `/api/jointTraining/room/exit`,
+ method: 'put',
+ params: {
+ group: group,
+ }
+ });
+}
+
+// 开始联合演练
+export function startJointTraining(group) {
+ return request({
+ url: `/api/jointTraining/room/simulation`,
+ method: 'post',
+ params: {
+ group: group
+ }
+ });
+}
+
+// 获取个人信息
+export function getUserRoles(group) {
+ return request({
+ url: `/api/jointTraining/room/user/role`,
+ method: 'get',
+ params: {
+ group: group
+ }
+ });
+}
+
+// 返回房间
+export function putJointTrainingState() {
+ return request({
+ url: `/api/jointTraining/room/back`,
+ method: 'put',
+ });
+}
+
+// 踢出用户
+export function putJointTrainingUserkicked(userId, group) {
+ return request({
+ url: `/api/jointTraining/room/user`,
+ method: 'put',
+ params: {
+ userId: userId,
+ group: group,
+ }
+ });
+}
+
+// 管理员结束所有人的仿真
+export function putJointTrainingSimulation(group) {
+ return request({
+ url: `/api/jointTraining/room/simulation/all`,
+ method: 'put',
+ params: {
+ group: group,
+ }
+ });
+}
+
+// 结束仿真返回房间
+export function putJointTrainingSimulationUser(group) {
+ return request({
+ url: `/api/jointTraining/room/simulation/user/exit`,
+ method: 'put',
+ params: {
+ group: group,
+ }
+ });
+}
+
+// 进入仿真
+export function putJointTrainingSimulationEntrance(group) {
+ return request({
+ url: `/api/jointTraining/room/simulation/user/entrance`,
+ method: 'put',
+ params: {
+ group: group,
+ }
+ });
+}
+
+/**
+ * 权限获取(房间权限)
+ */
+export function getPermissionJoint(group) {
+ return request({
+ url: '/api/jointTraining/qrCode',
+ method: 'get',
+ params: {
+ group: group
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/api/jlmap3d/load3ddata.js b/src/api/jlmap3d/load3ddata.js
new file mode 100644
index 000000000..b003b30b6
--- /dev/null
+++ b/src/api/jlmap3d/load3ddata.js
@@ -0,0 +1,77 @@
+import request from '@/utils/request';
+
+/** 获取地图版本信息*/
+export function getPublishMapVersion(skinStyle) {
+ return request({
+ url: `/api/map/${skinStyle}/version`,
+ method: 'get'
+ });
+}
+
+/** 获取发布地图详细内容*/
+export function getPublishMapDetail(skinStyle) {
+ let datad = request({
+ url: `/api/map/${skinStyle}/details`,
+ method: 'get'
+ });
+ return datad.then();
+}
+
+
+/** 根据地图id获取地图信息*/
+export function getPublishMapInfo(mapId) {
+ return request({
+ url: `/api/map/${mapId}`,
+ method: 'get'
+ });
+}
+
+
+/** 获取草稿地图详细内容*/
+export function getMapDetail(id) {
+ return request({
+ url: `/api/mapBuild/${id}/mapDataDetail`,
+ method: 'get'
+ });
+}
+
+/** 创建地图3d数据*/
+export function set3dMapData(data) {
+ return request({
+ url: `/api/mapBuild/3dMapData`,
+ method: 'post',
+ data: data
+ });
+}
+
+/**通过地图id获取地图3d数据*/
+export function get3dMapData(mapId) {
+ return request({
+ url: `/api/mapBuild/3dMapData/${mapId}`,
+ method: 'get'
+ });
+}
+
+/** 更新地图3d数据*/
+export function update3dMapData(data) {
+ return request({
+ url: `/api/mapBuild/3dMapData/${data.id}`,
+ method: 'put',
+ data: data
+ });
+}
+
+/** 获取模型资源列表*/
+export function loadmap3dModel() {
+ return request({
+ url: `/api/map3dModel/all`,
+ method: 'get'
+ });
+}
+export function getPublish3dMapDetail(skinStyle) {
+ let datad = request({
+ url: `/api/map/${skinStyle}/3dMapData`,
+ method: 'get'
+ });
+ return datad.then();
+}
diff --git a/src/api/jmap/lesson.js b/src/api/jmap/lesson.js
index fbb3187b4..5a55d45f4 100644
--- a/src/api/jmap/lesson.js
+++ b/src/api/jmap/lesson.js
@@ -2,67 +2,67 @@ import request from '@/utils/request';
/** 获取发布的课程列表*/
export function getPublishLessonList() {
- return request({
- url: '/api/lesson',
- method: 'get'
- });
+ return request({
+ url: '/api/lesson',
+ method: 'get'
+ });
}
/** 获取发布列表树*/
export function getPublishLessonTree(params) {
- return request({
- url: '/api/lesson/tree',
- method: 'get',
- params: params || {}
- });
+ return request({
+ url: '/api/lesson/tree',
+ method: 'get',
+ params: params || {}
+ });
}
/** 获取发布课程列表*/
export function getPublishLessonDetail(data) {
- return request({
- url: `/api/lesson/${data.id}`,
- method: 'get'
- });
+ return request({
+ url: `/api/lesson/${data.id}`,
+ method: 'get'
+ });
}
/** 发布课程分页列表列表*/
export function publishLessonList(param) {
- return request({
- url: `/api/lesson/publishedLesson`,
- method: 'get',
- params: param
- });
+ return request({
+ url: `/api/lesson/publishedLesson`,
+ method: 'get',
+ params: param
+ });
}
/** 删除发布课程*/
export function delPublishLesson(lessonId) {
- return request({
- url: `/api/lesson/publishedLesson/${lessonId}`,
- method: 'delete'
- });
+ return request({
+ url: `/api/lesson/publishedLesson/${lessonId}`,
+ method: 'delete'
+ });
}
-/** 发布课程上架*/
+/**发布课程上架*/
export function putLessonOnLine(id) {
- return request({
- url: `/api/lesson/${id}/onLine`,
- method: 'put'
- });
+ return request({
+ url: `/api/lesson/${id}/onLine`,
+ method: 'put',
+ });
}
-/** 发布课程下架*/
+/**发布课程下架*/
export function putLessonOffLine(id) {
- return request({
- url: `/api/lesson/${id}/offLine`,
- method: 'put'
- });
+ return request({
+ url: `/api/lesson/${id}/offLine`,
+ method: 'put',
+ });
}
/**
* 获取地图产品下的课程列表
*/
export function getCommodityProductLesson(prdCode) {
- return request({
- url: `/api/lesson/${prdCode}/list`,
- method: 'get'
- });
-}
+ return request({
+ url: `/api/lesson/${prdCode}/list`,
+ method: 'get',
+ });
+}
\ No newline at end of file
diff --git a/src/api/login.js b/src/api/login.js
index eb1297b44..60f1e6463 100644
--- a/src/api/login.js
+++ b/src/api/login.js
@@ -1,77 +1,68 @@
import request from '@/utils/request';
-
-// 修改密码
-export function changePassword(userId, data) {
- return request({
- url: `/api/login/${userId}/password`,
- method: 'put',
- data: data
- });
-}
-
// 账号密码 其他系统
export function login(params) {
- return request({
- url: '/api/login',
- method: 'post',
- data: params
- });
+ return request({
+ url: '/api/login',
+ method: 'post',
+ data: params
+ });
}
// 获取登陆url 二维码 其他系统
export function getLoginUrl(params) {
- return request({
- url: '/api/login/url',
- method: 'get',
- params: params
- });
+ return request({
+ url: '/api/login/url',
+ method: 'get',
+ params: params
+ });
}
+
// 获取登录信息
export function getInfo(token) {
- return request({
- url: '/api/login/getUserInfo',
- method: 'get',
- params: { token }
- });
+ return request({
+ url: '/api/login/getUserInfo',
+ method: 'get',
+ params: { token }
+ });
}
// 登出
export function logout(token) {
- return request({
- url: '/api/login/logout',
- method: 'get',
- params: {
- token
- }
- });
+ return request({
+ url: '/api/login/logout',
+ method: 'get',
+ params: {
+ token
+ }
+ });
}
// 检查登陆状态
export function checkLoginStatus(sessionId) {
- return new Promise((resolve, reject) => {
- request({
- url: '/api/login/checkStatus',
- method: 'get',
- params: {
- sessionId: sessionId
- }
- }).then(response => {
- if (response.data.status === '2') {
- resolve(response);
- } else {
- reject(response);
- }
- }).catch(error => {
- reject(error);
- });
- });
+ return new Promise((resolve, reject) => {
+ request({
+ url: '/api/login/checkStatus',
+ method: 'get',
+ params: {
+ sessionId: sessionId
+ }
+ }).then(response => {
+ if (response.data.status === '2') {
+ resolve(response);
+ } else {
+ reject(response);
+ }
+ }).catch(error => {
+ reject(error);
+ });
+ });
}
// 检测持续在线 防止掉线在大屏或者仿真系统下
export function checkLoginLine() {
- return request({
- url: '/api/cache/heartBeat',
- method: 'get'
- });
+ return request({
+ url: '/api/cache/heartBeat',
+ method: 'get',
+ });
}
diff --git a/src/api/management/mapskin.js b/src/api/management/mapskin.js
index 1c17e8d0c..98cf524c4 100644
--- a/src/api/management/mapskin.js
+++ b/src/api/management/mapskin.js
@@ -2,68 +2,68 @@ import request from '@/utils/request';
/** 分页查询皮肤*/
export function getSkinStylePageList(params) {
- return request({
- url: `/api/mapSkin`,
- method: 'get',
- params: params
- });
+ return request({
+ url: `/api/mapSkin`,
+ method: 'get',
+ params: params
+ });
}
/** 添加皮肤*/
export function addSkinStyle(data) {
- return request({
- url: `/api/mapSkin`,
- method: 'post',
- data: data
- });
+ return request({
+ url: `/api/mapSkin`,
+ method: 'post',
+ data: data
+ });
}
/** 删除皮肤*/
export function delSkinStyle(id) {
- return request({
- url: `/api/mapSkin/${id}`,
- method: 'delete'
- });
+ return request({
+ url: `/api/mapSkin/${id}`,
+ method: 'delete'
+ });
}
/** 查询地图皮肤 */
export function querySkinStyle(id) {
- return request({
- url: `/api/mapSkin/${id}`,
- method: 'get'
- });
+ return request({
+ url: `/api/mapSkin/${id}`,
+ method: 'get'
+ });
}
/** 修改地图皮肤*/
export function updateSkinStyle(data) {
- return request({
- url: `/api/mapSkin/${data.id}`,
- method: 'put',
- data: data
- });
+ return request({
+ url: `/api/mapSkin/${data.id}`,
+ method: 'put',
+ data: data
+ });
}
/** 通过皮肤Code更新地图皮肤*/
export function updateSkinStyleByCode(data) {
- return request({
- url: `/api/mapSkin/${data.code}/update`,
- method: 'put',
- data: data
- });
+ return request({
+ url: `/api/mapSkin/${data.code}/update`,
+ method: 'put',
+ data: data
+ });
}
/** 查询皮肤是否存在*/
export function querySkinStyleExistByCode(code) {
- return request({
- url: `/api/mapSkin/${code}/exist`,
- method: 'get'
- });
+ return request({
+ url: `/api/mapSkin/${code}/exist`,
+ method: 'get'
+ });
}
/** 获取皮肤列表*/
export function getSkinStyleList() {
- return request({
- url: `/api/mapSkin/list`,
- method: 'get'
- });
+ return request({
+ url: `/api/mapSkin/list`,
+ method: 'get'
+ });
}
diff --git a/src/api/quest.js b/src/api/quest.js
new file mode 100644
index 000000000..6491751e3
--- /dev/null
+++ b/src/api/quest.js
@@ -0,0 +1,40 @@
+import request from '@/utils/request';
+
+/** 分页查找仿真任务*/
+export function getQuestPageList(params) {
+ return request({
+ url: `/api/quest/paging`,
+ method: 'get',
+ params: params
+ });
+}
+/** 创建任务 */
+export function createQuest(data) {
+ return request({
+ url: `/api/quest`,
+ method: 'post',
+ data
+ });
+}
+/** 根据任务id删除任务 */
+export function deleteQuest(id) {
+ return request({
+ url: `/api/quest/${id}`,
+ method: 'delete'
+ });
+}
+/** 根据id查询任务基础信息 */
+export function getQuestById(id) {
+ return request({
+ url: `/api/quest/${id}`,
+ method: 'get',
+ });
+}
+/** 更新任务基本信息 */
+export function updateQuest(id, data) {
+ return request({
+ url: `/api/quest/${id}`,
+ method: 'put',
+ data
+ });
+}
diff --git a/src/api/runplan.js b/src/api/runplan.js
new file mode 100644
index 000000000..76165e6f0
--- /dev/null
+++ b/src/api/runplan.js
@@ -0,0 +1,379 @@
+import request from '@/utils/request';
+
+/**
+ * 获取运行图列表
+ */
+export function getRunPlanList() {
+ return request({
+ url: '/api/rp/tree',
+ method: 'get'
+ });
+}
+
+/**
+ * 获取地图速度等级列表
+ */
+export function getSpeedLevels(skinStyle) {
+ return request({
+ url: `/api/rp/${skinStyle}/speed`,
+ method: 'get',
+ });
+}
+
+/**
+ * 新建地图速度等级列表
+ */
+export function newSpeedLevels(data) {
+ return request({
+ url: '/api/rp/speed',
+ method: 'post',
+ data: data
+ });
+}
+
+/**
+ * 获取运行图的车站列表
+ */
+export function getStationList(mapId) {
+ return request({
+ url: `/api/rp/station/${mapId}`,
+ method: 'get',
+ });
+}
+
+/**
+ * 通过皮肤获取运行图车站列表
+ */
+export function getStationListBySkinStyle(skinStyle) {
+ return request({
+ url: `/api/rp/station/${skinStyle}/bySkin`,
+ method: 'get',
+ });
+}
+
+/**
+ * 创建运行图
+ */
+export function newRunPlan(data) {
+ return request({
+ url: '/api/rp',
+ method: 'post',
+ data: data
+ });
+}
+
+/**
+ * 查询运行图获取数据
+ */
+export function queryRunPlan(planId) {
+ return request({
+ url: `/api/rp/${planId}`,
+ method: 'get'
+ });
+}
+
+/**
+ * 删除运行图
+ */
+export function deleteRunPlan(planId) {
+ return request({
+ url: `/api/rp/${planId}`,
+ method: 'delete'
+ });
+}
+
+/**
+ * 发布运行图
+ */
+export function publishRunPlan(data) {
+ return request({
+ url: `/api/rp/${data.planId}/publish`,
+ method: 'post',
+ data: data
+ });
+}
+
+
+/**
+ * 导入真实运行图
+ */
+export function importRunPlan(data) {
+ return request({
+ url: `/api/rp/${data.skinStyle}/prdPlan`,
+ method: 'post',
+ data: data.runPlanList
+ });
+}
+
+/** 获取运行图停车点列表*/
+export function getRunPlanStopPointList(skinStyle) {
+ return request({
+ url: `/api/rp/stopPoint/${skinStyle}`,
+ method: 'get',
+ });
+}
+
+
+/** 运行图*/
+export function getRpListByMapId(mapId) {
+ return request({
+ url: `/api/rp/${mapId}/list`,
+ method: 'get'
+ });
+}
+
+/** 获取站间运行时间*/
+export function getStationRunning(skinStyle) {
+ return request({
+ url: `/api/rp/${skinStyle}/stationRunning`,
+ method: 'get'
+ });
+}
+
+/** 设置站间运行时间*/
+export function setStationRunning(skinStyle, data) {
+ return request({
+ url: `/api/rp/${skinStyle}/stationRunning`,
+ method: 'put',
+ data: data
+ });
+}
+
+/** 创建运行图*/
+export function createEmptyPlan(data) {
+ return request({
+ url: `/api/rp`,
+ method: 'post',
+ data: data
+ });
+}
+
+/** 查询运行图服务号是否存在*/
+export function checkServiceNumberExist({ planId, serviceNumber }) {
+ return request({
+ url: `/api/rp/${planId}/${serviceNumber}/service`,
+ method: 'get'
+ });
+}
+
+/** 查询交路列表*/
+export function getRoutingList(planId) {
+ return request({
+ url: `/api/rp/${planId}/routingList`,
+ method: 'get'
+ });
+}
+
+/** 根据交路查询交路区段列表*/
+export function querySectionListByRouting({ planId, routingCode }) {
+ return request({
+ url: `/api/rp/${planId}/${routingCode}/routingSectionList`,
+ method: 'get'
+ });
+}
+
+/** 有效性检查*/
+export function planEffectiveCheck(planId) {
+ return request({
+ url: `/api/rp/${planId}/check`,
+ method: 'get'
+ });
+}
+
+/** 增加计划*/
+export function addPlanService(data) {
+ return request({
+ url: `/api/rp/${data.planId}/service`,
+ method: 'post',
+ data: data,
+ });
+}
+
+/** 删除计划*/
+export function deletePlanService(data) {
+ return request({
+ url: `/api/rp/${data.planId}/service/${data.serviceNumber}`,
+ method: 'delete'
+ });
+}
+
+/** 复制计划*/
+export function duplicateService(data) {
+ return request({
+ url: `/api/rp/${data.planId}/service/${data.serviceNumber}`,
+ method: 'post',
+ data: data
+ });
+}
+
+/** 修改计划*/
+// export function updatePlanService(data) {
+// return request({
+// url: `/api/rp/${data.planId}/service/${data.serviceNumber}`,
+// method: 'put',
+// data: data
+// })
+// }
+
+/** 增加任务*/
+export function addPlanTrip(data) {
+ return request({
+ url: `/api/rp/${data.planId}/${data.serviceNumber}/trip`,
+ method: 'post',
+ data: data,
+ });
+}
+
+/** 删除任务*/
+export function deletePlanTrip(params) {
+ return request({
+ url: `/api/rp/${params.planId}/trip/${params.SDTNumber}`,
+ method: 'delete',
+ params: { deleteBefore: params.deleteBefore }
+ });
+}
+
+/** 修改任务*/
+export function updatePlanTrip(data) {
+ return request({
+ url: `/api/rp/${data.planId}/trip/${data.SDTNumber}`,
+ method: 'put',
+ data: data
+ });
+}
+
+/** 根据车次号查询交路*/
+export function getRoutingBySDTNumber(params) {
+ return request({
+ url: `/api/rp/${params.planId}/routing`,
+ method: 'get',
+ params: {
+ SDTNumber: params.SDTNumber
+ }
+ });
+}
+
+
+/** 运行图仿真测试*/
+export function runPlanNotify({ planId }) {
+ return request({
+ url: `/api/rp/${planId}/simulation`,
+ method: 'get',
+ });
+}
+
+/** 获取运行计划模板列表*/
+export function runPlanTemplateList(params) {
+ return request({
+ url: '/api/runPlan/template',
+ method: 'get',
+ params: params
+ });
+}
+
+/** 删除运行图模板*/
+export function deleteRunPlanTemplate(planId) {
+ return request({
+ url: `/api/runPlan/template/${planId}`,
+ method: 'delete'
+ });
+}
+
+/** 生成通用每日运行图*/
+export function generateCommonRunPlanEveryDay(planId) {
+ return request({
+ url: `/api/runPlan/template/generate/${planId}`,
+ method: 'post'
+ });
+}
+
+/** 生成用户每日运行图*/
+export function generateUserRunPlanEveryDay(planId, group) {
+ return request({
+ url: `/api/runPlan/daily/privilege/${planId}?group=${group}`,
+ method: 'post'
+ });
+}
+/** 获取运行计划每日列表*/
+export function runPlanEveryDayList(params) {
+ return request({
+ url: '/api/runPlan/daily',
+ method: 'get',
+ params: params
+ });
+}
+
+/** 删除运行图每日计划*/
+export function deleteRunPlanEveryDay(planId) {
+ return request({
+ url: `/api/runPlan/daily/${planId}`,
+ method: 'delete'
+ });
+}
+
+/** 获取地图运行图的车次号*/
+export function getPublishMapTrainNos(skinStyle) {
+ return request({
+ url: `/api/runPlan/daily/${skinStyle}/trainNos`,
+ method: 'get'
+ });
+}
+
+/** 获取服务号、车组号*/
+export function getPublishMapTrainServerNos(skinStyle) {
+ return request({
+ url: `/api/runPlan/daily/${skinStyle}/serverNos`,
+ method: 'get'
+ });
+}
+
+/** 分页查询加载计划*/
+export function getRunPlanLoadList(params) {
+ return request({
+ url: `/api/runPlan/daily/runPlanLoad`,
+ method: 'get',
+ params: params
+ });
+}
+
+/** 创建加载计划*/
+export function createRunPlanLoad(data) {
+ return request({
+ url: `/api/runPlan/daily/runPlanLoad`,
+ method: 'post',
+ data: data
+ });
+}
+
+/** 管理创建通用加载计划*/
+export function createRunPlanCommon(data) {
+ return request({
+ url: `/api/runPlan/daily/runPlanLoad/common`,
+ method: 'post',
+ data: data
+ });
+}
+
+/** 删除加载计划*/
+export function deleteRunPlanLoad(planId) {
+ return request({
+ url: `/api/runPlan/daily/runPlanLoad/${planId}`,
+ method: 'delete'
+ });
+}
+
+/** 查询模板运行图数据*/
+export function queryRunPlanTemplate(planId) {
+ return request({
+ url: `/api/runPlan/template/${planId}`,
+ method: 'get'
+ })
+}
+
+/** 查询当日运行图数据*/
+export function queryRunPlanDaily(planId) {
+ return request({
+ url: `/api/runPlan/daily/${planId}`,
+ method: 'get'
+ })
+}
\ No newline at end of file
diff --git a/src/api/simulation.js b/src/api/simulation.js
new file mode 100644
index 000000000..8deaed2ec
--- /dev/null
+++ b/src/api/simulation.js
@@ -0,0 +1,370 @@
+import request from '@/utils/request';
+
+/** 获取故障规则列表*/
+export function getFailureGenerateRules(params) {
+ return request({
+ url: `/api/simulation/failureGenerateRules`,
+ method: 'get',
+ params: params
+ });
+}
+
+/** 设置自动故障*/
+export function setFailureMode(data, group) {
+ return request({
+ url: `/api/simulation/${group}/failureMode`,
+ method: 'post',
+ data: data,
+ });
+}
+
+
+/**
+ * 仿真系统按计划行车
+ */
+export function runDiagramStart(params, group) {
+ return request({
+ url: `/api/simulation/${group}/start`,
+ method: 'put',
+ params: params
+ });
+}
+
+/**
+ * 仿真系统结束计划行车
+ */
+export function runDiagramOver(group) {
+ return request({
+ url: `/api/simulation/${group}/over`,
+ method: 'put',
+ });
+}
+
+/**
+ * 退出仿真系统
+ */
+export function runDiagramQuit(group) {
+ return request({
+ url: `/api/simulation/${group}/quit`,
+ method: 'put',
+ });
+}
+
+/** 获取仿真系统时间*/
+export function runDiagramGetTime(group) {
+ return request({
+ url: `/api/simulation/${group}/systemTime`,
+ method: 'get'
+ });
+}
+
+// 查看是否开始按计划行车
+export function runDiagramIsStart(group) {
+ return request({
+ url: `/api/simulation/${group}/isRunPlanStart`,
+ method: 'get'
+ });
+}
+
+
+/**
+ * 仿真系统CBTC
+ * @param {*} mapId
+ */
+export function simulationNotify({ mapId, code }) {
+ return request({
+ url: `/api/simulation/${mapId}/${code}`,
+ method: 'get'
+ });
+}
+
+/**
+ * 大屏系统CBTC
+ * @param {*} mapId
+ */
+export function bitScreenNotify({ mapId }) {
+ return request({
+ url: `/api/simulation/bigScreen/${mapId}`,
+ method: 'get'
+ });
+}
+
+/**
+ * 实训系统CBTC
+ * @param {*} data
+ */
+export function trainingNotify({ trainingId }) {
+ return request({
+ url: `/api/simulation/training/${trainingId}`,
+ method: 'get'
+ });
+}
+
+/**
+ * 考试系统CBTC
+ * @param {*} data
+ */
+export function examNotify({ examId }) {
+ return request({
+ url: `/api/simulation/exam/${examId}`,
+ method: 'get'
+ });
+}
+
+
+/**获取用户实训列表*/
+export function getSimulationList(data) {
+ return request({
+ url: `/api/simulation/stats`,
+ method: 'get',
+ params: data
+ });
+}
+
+/**添加用户仿真数据*/
+export function postSimulationStats(data) {
+ return request({
+ url: `/api/simulation/stats`,
+ method: 'post',
+ data: data
+ });
+}
+
+/**更新用户仿真数据*/
+export function putSimulationStats(data) {
+ return request({
+ url: `/api/simulation/${data.id}/stats`,
+ method: 'put',
+ data: data
+ });
+}
+
+/**删除用户仿真数据*/
+export function deleteSimulationStats(statsId) {
+ return request({
+ url: `/api/simulation/${statsId}`,
+ method: 'delete',
+ });
+}
+
+
+/** 获取用户鼠标左键选中的设备信息*/
+export function letfMouseSelectDevice(deviceCode, group) {
+ return request({
+ url: `/api/simulation/${group}/device/${deviceCode}`,
+ method: 'get',
+ });
+}
+
+/** 获取每日运行图*/
+export function getEveryDayRunPlanData(group) {
+ return request({
+ url: `/api/simulation/${group}/runPlan`,
+ method: 'get',
+ });
+}
+
+/** 生成手机同步仿真二维码*/
+// export function getSimulationQrCodeUrl(group) {
+// return request({
+// url: `/api/simulation/${group}/qrCode`,
+// method: 'post'
+// });
+// }
+
+/** 录制脚本仿真*/
+export function scriptRecordNotify(questId) {
+ return request({
+ url: `/api/simulation/questRecord/${questId}`,
+ method: 'get'
+ })
+}
+
+/** 保存剧本背景*/
+export function saveScriptScenes(group) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/scenes`,
+ method: 'post'
+ })
+}
+
+
+/** 保存录制任务数据*/
+export function saveScriptData(group) {
+ return request({
+ url: `/api/simulation/${group}/questRecord`,
+ method: 'post',
+ });
+}
+
+
+
+/** 清除仿真剧本数据*/
+export function dumpScriptData(scriptId) {
+ return request({
+ url: `/api/simulation/${scriptId}/clearScriptRecord`,
+ method: 'post'
+ });
+}
+
+
+/** 查询录制剧本步骤*/
+export function queryScriptStep(group) {
+ return request({
+ url: `/api/simulation/${group}/scriptRecordStage`,
+ method: 'get'
+ });
+}
+
+/** 获取指定时间里可加载列车的个数*/
+export function getDesignatedTimeTrainNum(params, group) {
+ return request({
+ url: `/api/simulation/${group}/plan/trainNum`,
+ method: 'get',
+ params
+ })
+}
+
+/** 根据成员角色查询设备列表*/
+export function getDevicesByRole(group, params) {
+ return request({
+ url: `/api/simulation/${group}/role/devices`,
+ method: 'get',
+ params
+ })
+}
+/** 获取任务录制的成员角色列表 */
+export function getMembersByGroup(group) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/members`,
+ method: 'get',
+ })
+}
+
+/** 添加任务角色成员 */
+export function postQuestMember(group, data) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/member`,
+ method: 'post',
+ data
+ })
+}
+
+/** 删除任务角色成员 */
+export function deleteQuestMember(group, memberId) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/member/${memberId}`,
+ method: 'delete',
+ })
+}
+
+// /** 添加/修改任务角色行为 */
+export function postMemberBehavior(group, memberId, data) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/${memberId}/behavior`,
+ method: 'post',
+ data
+ })
+}
+/** 删除任务角色行为 */
+export function deleteMemberBehavior(group, memberId, behaviorId) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/${memberId}/behavior/${behaviorId}`,
+ method: 'delete'
+ })
+}
+/** 删除任务角色行为动作 */
+export function deleteMemberBehaviorAction(group, memberId, behaviorId, actionId) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/${memberId}/behavior/${behaviorId}/action/${actionId}`,
+ method: 'delete'
+ })
+}
+/** 添加/修改任务角色行为动作 */
+export function postMemberBehaviorAction(group, memberId, behaviorId, data) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/${memberId}/behavior/${behaviorId}/action`,
+ method: 'post',
+ data
+ })
+}
+/** 添加/修改任务目标条件*/
+export function postTargetConditionAction(group, data) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/targetCondition`,
+ method: 'post',
+ data
+ })
+}
+/** 删除任务目标条件*/
+export function deleteTargetConditionAction(group, tcId) {
+ return request({
+ url: `/api/simulation/${group}/questRecord/targetCondition/${tcId}`,
+ method: 'delete'
+ })
+}
+
+/** 分页查询存在的仿真 */
+export function getExistingSimulation(params) {
+ return request({
+ url: `/api/simulation/manage/page`,
+ method: 'get',
+ params,
+ });
+}
+
+/** 删除存在的仿真 */
+export function deleteExistingSimulation(group) {
+ return request({
+ url: `/api/simulation/manage/${group}`,
+ method: 'delete',
+ });
+}
+/** 根据设备类型获取设备列表 */
+export function getDeviceCodeByDeviceType(group, params) {
+ return request({
+ url: `/api/simulation/${group}/deviceType/devices`,
+ method: 'get',
+ params
+ });
+}
+/** 根据设备类型获取设备条件列表 */
+export function getDeviceCoditionByDeviceType(params) {
+ return request({
+ url: `/api/simulation/deviceType/conditions`,
+ method: 'get',
+ params
+ });
+}
+/** 获取任务录制的数据*/
+export function getQuestRecord(group) {
+ return request({
+ url: `/api/simulation/${group}/questRecord`,
+ method: 'get',
+ });
+}
+
+
+/** 加载任务*/
+export function loadQuest(questId, group) {
+ return request({
+ url: `/api/simulation/${group}/quest/${questId}`,
+ method: 'post',
+ });
+}
+
+/** 退出任务*/
+export function quitQuest(group) {
+ return request({
+ url: `/api/simulation/${group}/quest`,
+ method: 'put'
+ })
+}
+
+/** 根据group获取仿真对象*/
+export function getSimulationInfo(group) {
+ return request({
+ url: `/api/simulation/${group}`,
+ method: 'get'
+ })
+}
\ No newline at end of file
diff --git a/src/api/simulationRecord.js b/src/api/simulationRecord.js
new file mode 100644
index 000000000..f5e68fb7f
--- /dev/null
+++ b/src/api/simulationRecord.js
@@ -0,0 +1,67 @@
+
+import request from '@/utils/request';
+
+/** 分页查询仿真记录*/
+export function getSimulationRelpayList(params) {
+ return request({
+ url: `/api/simulationRecord`,
+ method: 'get',
+ params: params
+ });
+}
+
+// 删除回放
+export function delSimulationRecord(replayId) {
+ return request({
+ url: `/api/simulationRecord/${replayId}`,
+ method: 'delete',
+ });
+}
+
+/** 回放*/
+export function simulationRelpay(replayId) {
+ return request({
+ url: `/api/simulationRecord/${replayId}/playBack`,
+ method: 'get'
+ });
+}
+
+// 结束回放
+export function putsSimulationRecord(replayId) {
+ return request({
+ url: `/api/simulationRecord/${replayId}/over`,
+ method: 'put',
+ });
+}
+
+// 暂停回放
+export function putsSimulationRecordPause(replayId) {
+ return request({
+ url: `/api/simulationRecord/${replayId}/pause`,
+ method: 'put',
+ });
+}
+
+// 播放回放
+export function putsSimulationRecordPlay(replayId) {
+ return request({
+ url: `/api/simulationRecord/${replayId}/play`,
+ method: 'put',
+ });
+}
+
+// 设置速度回放
+export function putsSimulationRecordPlaySpeed(replayId, speed) {
+ return request({
+ url: `/api/simulationRecord/${replayId}/playSpeed?playSpeed=${speed}`,
+ method: 'put',
+ });
+}
+
+// 播放
+export function putsSimulationRecordplayTime(replayId, offsetSeconds) {
+ return request({
+ url: `/api/simulationRecord/${replayId}/playTime?offsetSeconds=${offsetSeconds}`,
+ method: 'put',
+ });
+}
\ No newline at end of file
diff --git a/src/api/table.js b/src/api/table.js
new file mode 100644
index 000000000..10bac1b1b
--- /dev/null
+++ b/src/api/table.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request';
+
+export function getList(params) {
+ return request({
+ url: '/table/list',
+ method: 'get',
+ params
+ });
+}
diff --git a/src/api/test.js b/src/api/test.js
deleted file mode 100644
index a5b85f944..000000000
--- a/src/api/test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import request from '@/utils/request';
-
-/** 获取发布地图详细内容*/
-export function getPublishMapDetail(skinStyle) {
- return request({
- url: `/api/map/${skinStyle}/details`,
- method: 'get'
- });
-}
\ No newline at end of file
diff --git a/src/api/user.js b/src/api/user.js
deleted file mode 100644
index dd4041fee..000000000
--- a/src/api/user.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import request from '@/utils/request';
-
-
-/** 查询用户参数*/
-export function getUserConfigInfo() {
- return request({
- url: '/api/user/config',
- method: 'get'
- });
-}
-
-// /** 注册用户*/
-// export function createUserInfo(data) {
-// return request({
-// url: '/api/userinfo/create',
-// method: 'put',
-// data: data
-// });
-// }
-// /** 根据用户Id获取用户信息*/
-// export function getUserInfoByOpenId(params) {
-// return request({
-// url: '/api/userinfo/getByOpenId',
-// method: 'get',
-// params: params
-// });
-// }
-
-// /** 根据姓名或者手机号查询用户*/
-// export function getUserInfoByNameOrMobile(params) {
-// return request({
-// url: '/api/userinfo/nameOrMobile',
-// method: 'get',
-// params: params
-// });
-// }
-
-// /** 设置用户参数*/
-// export function setUserConfigInfo(data) {
-// return request({
-// url: '/api/user/config',
-// method: 'post',
-// data: data
-// });
-// }
-
-// /** 获取销售列表*/
-// export function getSellerList() {
-// return request({
-// url: `/api/user/seller`,
-// method: 'get'
-// });
-// }
-
-// /** 查询用户列表*/
-// export function getUserList(params) {
-// return request({
-// url: `/api/user`,
-// method: 'get',
-// params: params
-// });
-// }
-
-// /** 模糊查询用户 昵称、名称、手机号*/
-// export function getDimUserList(params) {
-// return request({
-// url: `/api/user/fuzzy`,
-// method: 'get',
-// params: params
-// });
-// }
-
-
-// /** 获取用户订阅地图列表*/
-// export function getUserSubscribe(userId) {
-// return request({
-// url: `/api/user/subscribe/${userId}`,
-// method: 'get'
-// });
-// }
-
-// /** 保存用户订阅地图列表*/
-// export function saveUserSubscribe(data) {
-// return request({
-// url: '/api/user/subscribe',
-// method: 'post',
-// data: data
-// });
-// }
-
-
-// // 修改用户权限
-// export function putRoles(data) {
-// return request({
-// url: `/api/user/${data.id}/role`,
-// method: 'put',
-// data: data
-// });
-// }
diff --git a/src/assets/downSection.png b/src/assets/downSection.png
new file mode 100644
index 000000000..58dc1d231
Binary files /dev/null and b/src/assets/downSection.png differ
diff --git a/src/assets/home/demon1.jpg b/src/assets/home/demon1.jpg
new file mode 100644
index 000000000..5fc72eb33
Binary files /dev/null and b/src/assets/home/demon1.jpg differ
diff --git a/src/assets/home/demon2.jpg b/src/assets/home/demon2.jpg
new file mode 100644
index 000000000..39b0c7915
Binary files /dev/null and b/src/assets/home/demon2.jpg differ
diff --git a/src/assets/home/home1.jpg b/src/assets/home/home1.jpg
new file mode 100644
index 000000000..0c79290b5
Binary files /dev/null and b/src/assets/home/home1.jpg differ
diff --git a/src/assets/home/home1.png b/src/assets/home/home1.png
new file mode 100644
index 000000000..ebc0a0ca5
Binary files /dev/null and b/src/assets/home/home1.png differ
diff --git a/src/assets/home/home2.jpg b/src/assets/home/home2.jpg
new file mode 100644
index 000000000..1bab106ef
Binary files /dev/null and b/src/assets/home/home2.jpg differ
diff --git a/src/assets/home/home2.png b/src/assets/home/home2.png
new file mode 100644
index 000000000..6cf1cc6d8
Binary files /dev/null and b/src/assets/home/home2.png differ
diff --git a/src/assets/home/home3.jpg b/src/assets/home/home3.jpg
new file mode 100644
index 000000000..5a53945b6
Binary files /dev/null and b/src/assets/home/home3.jpg differ
diff --git a/src/assets/home/home3.png b/src/assets/home/home3.png
new file mode 100644
index 000000000..95fb124e7
Binary files /dev/null and b/src/assets/home/home3.png differ
diff --git a/src/assets/home/home4.png b/src/assets/home/home4.png
new file mode 100644
index 000000000..08d5b13cc
Binary files /dev/null and b/src/assets/home/home4.png differ
diff --git a/src/assets/home/plan1.png b/src/assets/home/plan1.png
new file mode 100644
index 000000000..ab5383aca
Binary files /dev/null and b/src/assets/home/plan1.png differ
diff --git a/src/assets/home/plan2.png b/src/assets/home/plan2.png
new file mode 100644
index 000000000..73247f7ee
Binary files /dev/null and b/src/assets/home/plan2.png differ
diff --git a/src/assets/home/plan3.png b/src/assets/home/plan3.png
new file mode 100644
index 000000000..23fcecf27
Binary files /dev/null and b/src/assets/home/plan3.png differ
diff --git a/src/assets/home/tring1.png b/src/assets/home/tring1.png
new file mode 100644
index 000000000..9b5db769e
Binary files /dev/null and b/src/assets/home/tring1.png differ
diff --git a/src/assets/home/tring2.png b/src/assets/home/tring2.png
new file mode 100644
index 000000000..e515a4de5
Binary files /dev/null and b/src/assets/home/tring2.png differ
diff --git a/src/assets/home/tring3.jpg b/src/assets/home/tring3.jpg
new file mode 100644
index 000000000..af23babf2
Binary files /dev/null and b/src/assets/home/tring3.jpg differ
diff --git a/src/assets/home/tring4.jpg b/src/assets/home/tring4.jpg
new file mode 100644
index 000000000..82a3a9485
Binary files /dev/null and b/src/assets/home/tring4.jpg differ
diff --git a/src/assets/home/tring5.jpg b/src/assets/home/tring5.jpg
new file mode 100644
index 000000000..8b07d2e57
Binary files /dev/null and b/src/assets/home/tring5.jpg differ
diff --git a/src/assets/home/tring6.jpg b/src/assets/home/tring6.jpg
new file mode 100644
index 000000000..570155aba
Binary files /dev/null and b/src/assets/home/tring6.jpg differ
diff --git a/src/assets/jl3d/mmi.png b/src/assets/jl3d/mmi.png
new file mode 100644
index 000000000..d39e506f0
Binary files /dev/null and b/src/assets/jl3d/mmi.png differ
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 000000000..08735de1b
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/assets/logo.svg b/src/assets/logo.svg
new file mode 100644
index 000000000..d52bf7e15
--- /dev/null
+++ b/src/assets/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/logo1.png b/src/assets/logo1.png
new file mode 100644
index 000000000..dbe8da485
Binary files /dev/null and b/src/assets/logo1.png differ
diff --git a/src/assets/logo_.png b/src/assets/logo_.png
new file mode 100644
index 000000000..e250916ab
Binary files /dev/null and b/src/assets/logo_.png differ
diff --git a/src/assets/pay_images/AliPayLogo.png b/src/assets/pay_images/AliPayLogo.png
new file mode 100644
index 000000000..37123dfeb
Binary files /dev/null and b/src/assets/pay_images/AliPayLogo.png differ
diff --git a/src/assets/pay_images/AliPayLogoInfo.png b/src/assets/pay_images/AliPayLogoInfo.png
new file mode 100644
index 000000000..78685cabc
Binary files /dev/null and b/src/assets/pay_images/AliPayLogoInfo.png differ
diff --git a/src/assets/pay_images/PayFail.png b/src/assets/pay_images/PayFail.png
new file mode 100644
index 000000000..e9ab0dbc6
Binary files /dev/null and b/src/assets/pay_images/PayFail.png differ
diff --git a/src/assets/pay_images/PaySuccess.png b/src/assets/pay_images/PaySuccess.png
new file mode 100644
index 000000000..e33b05cb2
Binary files /dev/null and b/src/assets/pay_images/PaySuccess.png differ
diff --git a/src/assets/pay_images/WePayLogo.png b/src/assets/pay_images/WePayLogo.png
new file mode 100644
index 000000000..544e58a7d
Binary files /dev/null and b/src/assets/pay_images/WePayLogo.png differ
diff --git a/src/assets/pay_images/WePayLogoInfo.png b/src/assets/pay_images/WePayLogoInfo.png
new file mode 100644
index 000000000..f2c66bf6c
Binary files /dev/null and b/src/assets/pay_images/WePayLogoInfo.png differ
diff --git a/src/assets/upSection.png b/src/assets/upSection.png
new file mode 100644
index 000000000..c30ac58be
Binary files /dev/null and b/src/assets/upSection.png differ
diff --git a/src/directives.js b/src/directives.js
new file mode 100644
index 000000000..dba79ca51
--- /dev/null
+++ b/src/directives.js
@@ -0,0 +1,283 @@
+/* eslint-disable no-useless-escape */
+import Vue from 'vue';
+import store from '@/store';
+
+/**
+ *元素获取焦点:v-dialogDrag
+ * @param {*} el
+ * @param {*} binding
+ */
+Vue.directive('focus', {
+ // 当被绑定的元素插入到 DOM 中时
+ inserted: function (el, obj) {
+ // 这是需要页面刚加载就能进行聚焦操作使用的钩子函数,可以省略的,视具体需求而定
+ // 对值进行判断
+ if (obj.value) {
+ // 聚焦元素
+ el.focus();
+ }
+ },
+ // 当指令所在组件的 VNode 及其子 VNode 全部更新后调用
+ // 这是每当绑定的值发生改变时触发的钩子函数
+ componentUpdated: function (el, obj) {
+ if (obj.value) {
+ el.focus();
+ }
+ }
+});
+
+/**
+ *弹窗拖拽:v-dialogDrag
+ * @param {*} el
+ * @param {*} binding
+ * @param {*} vnode
+ * @param {*} oldvNode
+ */
+Vue.directive('dialogDrag', {
+ bind(el) {
+ const dialogHeaderEl = el.querySelector('.el-dialog__header');
+ const dragDom = el.querySelector('.el-dialog');
+ dialogHeaderEl.style.cursor = 'move';
+
+ /** 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);*/
+ const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+
+ dialogHeaderEl.onmousedown = (e) => {
+ /** 鼠标按下,计算当前元素距离可视区的距离*/
+ const disX = e.clientX - dialogHeaderEl.offsetLeft;
+ const disY = e.clientY - dialogHeaderEl.offsetTop;
+
+ /** 获取到的值带px 正则匹配替换*/
+ let styL, styT;
+
+ /** 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px*/
+ if (sty.left.includes('%')) {
+ // eslint-disable-next-line no-useless-escape
+ styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
+ styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
+ } else {
+ // eslint-disable-next-line no-useless-escape
+ styL = +sty.left.replace(/\px/g, '');
+ // eslint-disable-next-line no-useless-escape
+ styT = +sty.top.replace(/\px/g, '');
+ }
+
+ document.onmousemove = function (e) {
+ /** 通过事件委托,计算移动的距离*/
+ const l = e.clientX - disX;
+ const t = e.clientY - disY;
+
+ /** 移动当前元素*/
+ dragDom.style.left = `${l + styL}px`;
+ dragDom.style.top = `${t + styT}px`;
+
+ /** 刷新提示标签位置*/
+ store.dispatch('training/emitTipFresh');
+
+ /** 将此时的位置传出去*/
+ // binding.value({ x: e.pageX, y: e.pageY });
+ };
+
+ document.onmouseup = function () {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ };
+ };
+ }
+});
+
+/**
+ *弹窗宽度拖大,拖小:dialogDragWidth
+ * @param {*} el
+ * @param {*} binding
+ * @param {*} vnode
+ * @param {*} oldvNode
+ */
+Vue.directive('dialogDragWidth', {
+ bind(el, binding) {
+ const dragDom = binding.value.$el.querySelector('.el-dialog');
+
+ el.onmousedown = (e) => {
+
+ /** 鼠标按下,计算当前元素距离可视区的距离*/
+ const disX = e.clientX - el.offsetLeft;
+
+ document.onmousemove = function (e) {
+ /** 移动时禁用默认事件*/
+ e.preventDefault();
+
+ /** 通过事件委托,计算移动的距离*/
+ const l = e.clientX - disX;
+ dragDom.style.width = `${l}px`;
+ };
+
+ document.onmouseup = function () {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ };
+ };
+ }
+});
+
+/**
+ * 弹窗拖拽:v-quickEntryDrag
+ * @param {*} el
+ * @param {*} binding
+ * @param {*} vnode
+ * @param {*} oldvNode
+ */
+Vue.directive('quickMenuDrag', {
+ bind(el) {
+ const dragDom = el;
+ dragDom.style.cursor = 'move';
+
+ /** 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);*/
+ const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+
+ dragDom.onmousedown = (e) => {
+ /** 鼠标按下,计算当前元素距离可视区的距离*/
+ const disX = e.clientX;
+ const disY = e.clientY;
+
+ /** 获取到的值带px 正则匹配替换*/
+ let styL, styT;
+
+ /** 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px*/
+ if (sty.left.includes('%')) {
+ styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
+ styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
+ } else {
+ styL = +sty.left.replace(/\px/g, '');
+ styT = +sty.top.replace(/\px/g, '');
+ }
+
+ document.onmousemove = function (e) {
+ /** 通过事件委托,计算移动的距离*/
+ const l = e.clientX - disX;
+ const t = e.clientY - disY;
+
+ /** 移动当前元素*/
+ dragDom.style.left = `${l + styL}px`;
+ dragDom.style.top = `${t + styT}px`;
+
+ /** 将此时的位置传出去*/
+ // binding.value({ x: e.pageX, y: e.pageY });
+ };
+
+ document.onmouseup = function () {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ };
+ };
+ }
+});
+
+/**
+ *vue-自定义指令-拖拽 v-drag
+ */
+Vue.directive('drag', {
+ bind(el) {
+ const dragDom = el.querySelector('.reminder-box');
+ const dragRight = el.querySelector('.drag-right');
+ const dragLeft = el.querySelector('.drag-left');
+ const dragBottom = el.querySelector('.drag-bottom');
+ const dragTop = el.querySelector('.drag-top');
+ const dragBody = el.querySelector('.tip-body');
+ const body = el.querySelector('.tip-body-box');
+
+ dragRight.onmousedown = (e) => {
+ document.onselectstart = function () {
+ return false;
+ };
+ // 宽度拖拽
+ var iEvent = e || event;
+ var disX = iEvent.clientX;
+ var disW = dragDom.offsetWidth;
+ document.onmousemove = function (e) {
+
+ var iEvent = e || event;
+ if (disW + (iEvent.clientX - disX) > 350) {
+ dragDom.style.width = disW + (iEvent.clientX - disX) + 'px';
+ }
+ };
+
+ document.onmouseup = function () {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ document.onselectstart = null;
+ };
+ };
+ dragLeft.onmousedown = (e) => {
+ document.onselectstart = function () {
+ return false;
+ };
+ // 宽度拖拽
+ var iEvent = e || event;
+ var disX = iEvent.clientX;
+ var disW = dragDom.offsetWidth;
+ var OFFLeft = dragDom.offsetLeft;
+ document.onmousemove = function (e) {
+ const iEvent = e || event;
+ const width = disW - (iEvent.clientX - disX);
+ if (width > 350) {
+ dragDom.style.width = disW - (iEvent.clientX - disX) + 'px';
+ dragDom.style.left = OFFLeft + (iEvent.clientX - disX) + 'px';
+ }
+ };
+
+ document.onmouseup = function () {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ document.onselectstart = null;
+ };
+ };
+ dragBottom.onmousedown = (e) => {
+ document.onselectstart = function () {
+ return false;
+ };
+ // 宽度拖拽
+ var iEvent = e || event;
+ var disY = iEvent.clientY;
+ var disH = dragDom.offsetHeight;
+ document.onmousemove = function (e) {
+ var iEvent = e || event;
+ if (disH + (iEvent.clientY - disY) > 200) {
+ dragDom.style.height = disH + (iEvent.clientY - disY) + 'px';
+ body.style.height = disH + (iEvent.clientY - disY) - 40 + 'px';
+ dragBody.style.height = disH + (iEvent.clientY - disY) - 100 + 'px';
+ }
+ };
+
+ document.onmouseup = function () {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ document.onselectstart = null;
+ };
+ };
+ dragTop.onmousedown = (e) => {
+ document.onselectstart = function () {
+ return false;
+ };
+ // 宽度拖拽
+ var iEvent = e || event;
+ var disY = iEvent.clientY;
+ var disH = dragDom.offsetHeight;
+ var OOFTop = dragDom.offsetTop;
+ document.onmousemove = function (e) {
+ var iEvent = e || event;
+ if (disH - (iEvent.clientY - disY) > 200) {
+ dragDom.style.height = disH - (iEvent.clientY - disY) + 'px';
+ body.style.height = disH - (iEvent.clientY - disY) - 40 + 'px';
+ dragBody.style.height = disH - (iEvent.clientY - disY) - 100 + 'px';
+ dragDom.style.top = OOFTop + (iEvent.clientY - disY) + 'px';
+ }
+ };
+
+ document.onmouseup = function () {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ document.onselectstart = null;
+ };
+ };
+ }
+});
diff --git a/src/jlmap3d/component/Helper.js b/src/jlmap3d/component/Helper.js
new file mode 100644
index 000000000..bc51e08be
--- /dev/null
+++ b/src/jlmap3d/component/Helper.js
@@ -0,0 +1,30 @@
+export function Helper(scene){
+ //创建辅助网格平面
+ // let planeGeometry = new THREE.PlaneBufferGeometry( 40000, 40000 );
+ // planeGeometry.rotateX( - Math.PI / 2 );
+ // let planeMaterial = new THREE.ShadowMaterial( { opacity: 0.2 } );
+ // let plane = new THREE.Mesh( planeGeometry, planeMaterial );
+ // plane.position.y = 10;
+ // plane.receiveShadow = true;
+ // jlmap3dedit.meshplane = plane;
+
+ // jlmap3dedit.scene.add(plane );
+ // planeGeometry.dispose();
+ // planeMaterial.dispose();
+ //定义全局光
+ let ambientLight = new THREE.AmbientLight( 0xC5C1AA, 1.3);
+ scene.add( ambientLight );
+
+ let light = new THREE.HemisphereLight( 0xffffff, 0x444444 );
+ light.position.set( 0, 200, 0 );
+ scene.add( light );
+
+ // //辅助网格
+ // let helper = new THREE.GridHelper( 20000, 10 );
+ // helper.position.y = -2;
+ // helper.material.opacity = 0.25;
+ // helper.material.transparent = true;
+ // jlmap3dedit.scene.add( helper );
+
+
+}
diff --git a/src/jlmap3d/component/Materialload.js b/src/jlmap3d/component/Materialload.js
new file mode 100644
index 000000000..20379acd1
--- /dev/null
+++ b/src/jlmap3d/component/Materialload.js
@@ -0,0 +1,36 @@
+export function Materialload(jlmap3dedit){
+
+
+ settexture(jlmap3dedit.materiallist,"red",'../../static/material/signal/1.jpg');
+
+ settexture(jlmap3dedit.materiallist,"yellow",'../../static/material/signal/2.jpg');
+
+ settexture( jlmap3dedit.materiallist,"green",'../../static/material/signal/3.jpg');
+
+ settexture( jlmap3dedit.materiallist,"black",'../../static/material/signal/5.jpg');
+ //console.log(jlmap3dedit.materiallist);
+}
+
+function settexture(materiallist,name,textureurl){
+ var loader = new THREE.TextureLoader();
+
+ // 加载一个资源
+ loader.load(
+ // 资源URL
+ textureurl,
+
+ // onLoad回调
+ function ( texture ) {
+ texture.name = name;
+ materiallist.push(texture);
+ },
+
+ // 目前暂不支持onProgress的回调
+ undefined,
+
+ // onError回调
+ function ( err ) {
+ console.error( 'An error happened.' );
+ }
+ );
+}
diff --git a/src/jlmap3d/config/Camera.js b/src/jlmap3d/config/Camera.js
new file mode 100644
index 000000000..488066c58
--- /dev/null
+++ b/src/jlmap3d/config/Camera.js
@@ -0,0 +1,7 @@
+export function SetCamera(dom) {
+ var camera = new THREE.PerspectiveCamera(70, dom.clientWidth/dom.clientHeight, 1, 2000);
+ camera.position.set( 0, 0, 0 );
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ return camera;
+}
diff --git a/src/jlmap3d/config/Render.js b/src/jlmap3d/config/Render.js
new file mode 100644
index 000000000..afa9b038b
--- /dev/null
+++ b/src/jlmap3d/config/Render.js
@@ -0,0 +1,6 @@
+export function SetRender(dom) {
+ var renderer = new THREE.WebGLRenderer({antialias: true});
+ renderer.setSize(dom.clientWidth, dom.clientHeight);
+ renderer.setSize( window.innerWidth, window.innerHeight );
+ return renderer;
+}
diff --git a/src/jlmap3d/config/Scene.js b/src/jlmap3d/config/Scene.js
new file mode 100644
index 000000000..c2d61f955
--- /dev/null
+++ b/src/jlmap3d/config/Scene.js
@@ -0,0 +1,16 @@
+export function SetScene() {
+ var scene = new THREE.Scene();
+ var cubeTextureLoader = new THREE.CubeTextureLoader();
+
+ cubeTextureLoader.setPath( '../../static/skybox/city/' );
+
+ var cubeTexture = cubeTextureLoader.load( [
+ 'px.jpg', 'nx.jpg',
+ 'py.jpg', 'ny.jpg',
+ 'pz.jpg', 'nz.jpg',
+ ] );
+
+ scene.background = cubeTexture;
+ cubeTexture.dispose();
+ return scene;
+}
diff --git a/src/jlmap3d/connect/TrainingConnect.js b/src/jlmap3d/connect/TrainingConnect.js
new file mode 100644
index 000000000..6452115b3
--- /dev/null
+++ b/src/jlmap3d/connect/TrainingConnect.js
@@ -0,0 +1,685 @@
+import StompClient from '@/utils/sock';
+
+import { getTrainingCbtcDemon, runDiagramStart, runDiagramOver, setTrainingCbtcInitTime } from '@/api/simulation';
+
+import { creatSubscribe, clearSubscribe, displayTopic, screenTopic, } from '@/utils/stomp';
+import { handleToken } from '@/utils/auth';
+import router from '@/router';
+
+//定于仿真socket接口
+export function Jlmap3dSubscribe(jlmap3d) {
+
+ let scope = this;
+ this.map = null;
+
+ var trainlisttest = null;
+ var sectionlist = null;
+ var signallist = null;
+ var stationstandlist = null;
+ var sectionlist = null;
+ var materials = null;
+ var actions = null;
+ var scenes = null;
+
+ var code = null;
+
+ var drivingcode = null;
+ var drivingspeed = null;
+ var drivingaptspeed = null;
+
+ let driverswitch = false;
+
+ let stoptimer = null;
+ let num = 30;
+ let pointstand = null;
+
+ //run as plane = 01;
+ //reset = 02;
+ var datatype = "00";
+ this.teststomp = new StompClient();
+ this.topic = "/user/queue/simulation/jl3d";
+ let header = {'X-Token': handleToken() };
+
+ this.updatamap = function(mapdata,materiallist,nowaction,scene){
+ //console.log(mapdata);
+
+ scope.map = mapdata;
+ trainlisttest = this.map.trainlisttest;
+ sectionlist = this.map.sectionlist;
+ signallist = this.map.signallist;
+ stationstandlist = this.map.stationstandlist;
+ sectionlist = this.map.sectionlist;
+ materials = materiallist;
+ scenes = scene;
+ actions = nowaction;
+
+ }
+
+ this.socketon = function(topic){
+ try {
+ //console.log("teststomp");
+ scope.teststomp.subscribe(topic, callback,header);
+ } catch (error) {
+ console.error('websocket订阅失败');
+ }
+
+ }
+
+ this.socketoff = function(topic){
+ scope.teststomp.unsubscribe(topic);
+ for(let i=0;i=0;i--){
+ if(data.body[i]._type == "Train"){
+
+ //console.log(data.body[i]);
+ //遍历列车对象组
+ if(trainlisttest){
+ code = data.body[i].code;
+ //剔除不显示的车
+ //找到对应列车
+
+ if( trainlisttest.list[code]){
+ if(code == drivingcode){
+ driverswitch = data.body[i].runMode;
+ jlmap3d.updatetrainnum(data.body[i].groupNumber);
+ // console.log(trainlisttest.list[code].progress);
+ let syncdata = {
+ type:"Train",
+ code:code,
+ sectionCode:data.body[i].sectionCode,
+ percent:0,
+ };
+
+ if(trainlisttest.list[code].isStandTrack == true && trainlisttest.list[code].progress>0.95 && trainlisttest.list[code].speed<=0){
+ if(data.body[i].directionType == "02"){
+ syncdata.percent = 1-(sectionlist.sections.datalist[data.body[i].sectionCode].rstop/trainlisttest.list[code].len);
+ }else{
+ syncdata.percent = sectionlist.sections.datalist[data.body[i].sectionCode].lstop/trainlisttest.list[code].len;
+ }
+ scope.teststomp.send("/app/topic/simulation/wgu3d",syncdata);
+ }else{
+ if(data.body[i].directionType == "02"){
+ syncdata.percent = trainlisttest.list[code].progress;
+ }else{
+ syncdata.percent = 1 - trainlisttest.list[code].progress;
+ }
+ scope.teststomp.send("/app/topic/simulation/wgu3d",syncdata);
+ }
+
+ if(data.body[i].parkRemainTime>0){
+ jlmap3d.updatestoptime("停站时间:"+parseInt(data.body[i].parkRemainTime));
+ }else{
+ if(data.body[i].runMode == "02"){
+ jlmap3d.updatestoptime("列车自动驾驶中");
+ }else{
+ if(trainlisttest.list[code].isStandTrack == true && trainlisttest.list[code].progress>0.95 && trainlisttest.list[code].speed<=0){
+ // if(stoptimer){
+ //
+ // }else{
+ // stoptimer = setInterval(function(){
+ // if(num >=0){
+ // jlmap3d.updatestoptime("停站时间:"+num);
+ // num --;
+ // }
+ // },1000);
+ // }
+
+ }else{
+ jlmap3d.updatestoptime("列车人工驾驶中");
+ }
+
+ }
+ }
+ // if(trainlisttest.list[code].speed > 0){
+ // if(stoptimer){
+ // clearInterval( stoptimer );
+ // stoptimer = null;
+ // num = 30;
+ // }
+ // }
+
+ }
+ trainlisttest.list[code].runMode = data.body[i].runMode;
+ //车门开关验证
+ // if(data.body[i].directionType == "02"){
+ if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "01"){
+ //console.log("close");
+ trainlisttest.list[code].doorStatus = "01";
+ for(let an=actions[code].top.length-1;an>=0;an--){
+ actions[code].top[an].reset();
+ actions[code].top[an].time = actions[code].top[an]._clip.duration;
+ actions[code].top[an].timeScale = -1;
+ actions[code].top[an].play();
+ }
+ }else if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "02"){
+ //console.log("open");
+ trainlisttest.list[code].doorStatus = "02";
+ for(let an=actions[code].top.length-1;an>=0;an--){
+ actions[code].top[an].reset();
+ actions[code].top[an].time = 0;
+ actions[code].top[an].timeScale = 1;
+ actions[code].top[an].play();
+ }
+ }
+ // }else{
+ // if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "01"){
+ // //console.log("close");
+ // trainlisttest.list[code].doorStatus = "01";
+ // for(let an=actions[code].down.length-1;an>=0;an--){
+ // actions[code].down[an].reset();
+ // actions[code].down[an].time = actions[code].top[an]._clip.duration;
+ // actions[code].down[an].timeScale = -1;
+ // actions[code].down[an].play();
+ // }
+ // }else if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "02"){
+ // //console.log("open");
+ // trainlisttest.list[code].doorStatus = "02";
+ // for(let an=actions[code].down.length-1;an>=0;an--){
+ // actions[code].down[an].reset();
+ // actions[code].down[an].time = 0;
+ // actions[code].down[an].timeScale = 1;
+ // actions[code].down[an].play();
+ // }
+ // }
+ // }
+ //遍历获取所在轨道
+
+ if(trainlisttest.list[code].dispose != data.body[i].dispose && data.body[i].dispose == false){
+
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].code){
+ trainlisttest.group.add(trainlisttest.list[code]);
+ trainlisttest.list[code].position.y = 0;
+ trainlisttest.list[code].progress = 0;
+ trainlisttest.list[code].oldoffset = data.body[i].sectionOffsetPercent;
+ trainlisttest.list[code].dispose = false;
+ trainlisttest.list[code].nowcode = data.body[i].sectionCode;
+ trainlisttest.list[code].nextcode = data.body[i].nextSectionCode;
+ let vexlist = [];
+ let endrotation = null;
+
+ if(data.body[i].directionType == "02"){//向右
+ let offset = null;
+ let rotaposx = null;
+
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x>sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+ offset = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x-sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x;
+ rotaposx = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x+offset*data.body[i].sectionOffsetPercent;
+ }else{
+ offset = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x-sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x;
+ rotaposx = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x+offset*data.body[i].sectionOffsetPercent;
+ }
+
+ let rotaposz = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].z;
+ trainlisttest.list[code].rotation.y = 0;
+ trainlisttest.list[code].position.x = rotaposx;
+ trainlisttest.list[code].position.y = 0;
+ for(let tl=0;tl<6;tl++){
+ trainlisttest.list[code].children[tl].position.z = rotaposz;
+ }
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x>sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+ vexlist.push(new THREE.Vector3(rotaposx,0,rotaposz));
+ for(let m=sectionlist.sections.datalist[data.body[i].sectionCode].rail.length-1;m>=0;m--){
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x>rotaposx){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+
+ }
+ }else{
+ vexlist.push(new THREE.Vector3(rotaposx,0,rotaposz));
+ for(let m=0;mrotaposx){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }
+ trainlisttest.list[code].status = "02";
+
+ }else if(data.body[i].directionType == "03"){//向左
+
+ trainlisttest.list[code].dispose = false;
+ trainlisttest.group.add(trainlisttest.list[code]);
+ let offset = null;
+ let rotaposx = null;
+
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x>sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+
+ offset = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x-sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x;
+ rotaposx = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x+offset*data.body[i].sectionOffsetPercent;
+ }else{
+
+ offset = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x-sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x;
+ rotaposx = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x+offset*data.body[i].sectionOffsetPercent;
+ }
+ let rotaposz = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].z;
+ trainlisttest.list[code].rotation.y = Math.PI;
+ trainlisttest.list[code].position.x = rotaposx;
+ trainlisttest.list[code].position.y = 0;
+ for(let tl=0;tl<6;tl++){
+ trainlisttest.list[code].children[tl].position.z = rotaposz;
+ }
+ if(data.body[i].groupNumber == "001"){
+ }
+
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x=0;m--){
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].xsectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+ vexlist.push(new THREE.Vector3(trainlisttest.list[code].position.x,0,trainlisttest.list[code].children[0].matrixWorld.elements[14]));
+ for(let m=sectionlist.sections.datalist[data.body[i].sectionCode].rail.length-1;m>=0;m--){
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x>trainlisttest.list[code].position.x){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }else{
+ vexlist.push(new THREE.Vector3(trainlisttest.list[code].position.x,0,trainlisttest.list[code].children[0].matrixWorld.elements[14]));
+ for(let m=0;mtrainlisttest.list[code].position.x){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }
+ }else{
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x>sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+ for(let m=sectionlist.sections.datalist[data.body[i].sectionCode].rail.length-1;m>=0;m--){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }else{
+ for(let m=0;m=0;m--){
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x=0;m--){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+
+ }else{
+ for(let m=0;m=0;i--){
+ //0xFFFFFF
+ //0xCD0000 红
+ //0xEEEE00 黄
+ //0x32CD32 绿
+ if(data.body[i]._type == "Signal"){
+ if(signallist){
+ signalupdate(data.body[i]);
+ }
+ }
+
+ if(data.body[i]._type == "StationStand"){
+ if(actions){
+ standupdate(data.body[i]);
+ }
+ }
+
+ if(data.body[i]._type == "Switch"){
+ if(sectionlist){
+ switchupdate(data.body[i]);
+ }
+ }
+
+ }
+ }
+ }
+
+ function standupdate(data){
+ code = data.code;
+ if( actions[code]){
+ if(data.screenDoorOpenStatus == "02" && actions[code].status == "01"){
+ actions[code].status = "00";
+ }
+ if(data.screenDoorOpenStatus == "02" && actions[code].status == "00"){
+ actions[code].status = "02";
+ actions[code].action.reset();
+ actions[code].action.time = 0;
+ actions[code].action.timeScale = 1;
+ actions[code].action.play();
+ }
+
+ if(data.screenDoorOpenStatus == "01" && actions[code].status == "02"){
+ actions[code].status = "00";
+ }
+ if(data.screenDoorOpenStatus == "01" && actions[code].status == "00"){
+ actions[code].status = "01";
+ actions[code].action.reset();
+ actions[code].action.time = actions[code].action._clip.duration;
+ actions[code].action.timeScale = -1;
+ actions[code].action.play();
+ }
+ }
+
+
+ }
+
+ function signalupdate(data){
+ code = data.code;
+ if(data.lightType == "01"){
+ if(signallist.list[code].mesh.code){
+ signallist.list[code].mesh.status = data.status;
+
+ //55
+ //33
+ //77
+ //关闭
+ if(data.status == "01"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[0];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //开放
+ if(data.status == "02"){
+
+ if(data.switchLocateType == "01"){
+ signallist.list[code].mesh.children[0].material.map = materials[2];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ if(data.switchLocateType == "02"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[1];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ }
+ //引导
+ if(data.status == "03"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[0];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[1];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //封锁
+ if(data.status == "04"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //故障
+ if(data.status == "05"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ }
+
+ }else if(data.lightType == "01"){
+ if(signallist.list[code].mesh.code){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ }
+
+ }
+
+ function switchupdate(data){
+ code = data.code;
+ for(let j=sectionlist.switchs.modellist.length-1;j>=0;j--){
+ if(sectionlist.switchs.modellist[j].code == code){
+ if(sectionlist.switchs.modellist[j].locateType != data.locateType){
+ if(data.locateType == "02"){
+ if(actions[sectionlist.switchs.modellist[j].code]){
+ sectionlist.switchs.modellist[j].locateType = data.locateType;
+ actions[sectionlist.switchs.modellist[j].code].reset();
+ actions[sectionlist.switchs.modellist[j].code].time = 0;
+ actions[sectionlist.switchs.modellist[j].code].timeScale = 1;
+ actions[sectionlist.switchs.modellist[j].code].play();
+ }
+ }else if(data.locateType == "01"){
+ if(actions[sectionlist.switchs.modellist[j].code]){
+ sectionlist.switchs.modellist[j].locateType = data.locateType;
+ actions[sectionlist.switchs.modellist[j].code].reset();
+ actions[sectionlist.switchs.modellist[j].code].time = actions[sectionlist.switchs.modellist[j].code]._clip.duration;
+ actions[sectionlist.switchs.modellist[j].code].timeScale = -1;
+ actions[sectionlist.switchs.modellist[j].code].play();
+ }
+ }
+ }
+ j = 0;
+ }
+ }
+ }
+
+
+}
diff --git a/src/jlmap3d/connect/TrainingConnectTest.js b/src/jlmap3d/connect/TrainingConnectTest.js
new file mode 100644
index 000000000..2a683b12e
--- /dev/null
+++ b/src/jlmap3d/connect/TrainingConnectTest.js
@@ -0,0 +1,452 @@
+import StompClient from '@/utils/sock';
+import { displayTopic } from '@/utils/stomp';
+import { handleToken } from '@/utils/auth';
+import router from '@/router';
+
+//定于仿真socket接口
+export function Jlmap3dSubscribe(worker) {
+
+ let scope = this;
+ this.map = null;
+
+ let webworker = worker;
+ var trainlisttest = null;
+ var sectionlist = null;
+ var signallist = null;
+ var stationstandlist = null;
+ var sectionlist = null;
+ var materials = null;
+ var actions = null;
+ var scenes = null;
+
+ var code = null;
+
+ this.teststomp = new StompClient();
+ this.topic = displayTopic;
+ let header = { group: router.currentRoute.query.group, 'X-Token': handleToken() };
+
+ this.updatamap = function (mapdata, materiallist, nowaction, scene) {
+ //console.log(mapdata);
+ scope.map = mapdata;
+ trainlisttest = this.map.trainlisttest;
+ sectionlist = this.map.sectionlist;
+ signallist = this.map.signallist;
+ stationstandlist = this.map.stationstandlist;
+ sectionlist = this.map.sectionlist;
+ materials = materiallist;
+ scenes = scene;
+ actions = nowaction;
+ }
+
+ this.socketon = function (topic) {
+ try {
+ //console.log("teststomp");
+ scope.teststomp.subscribe(topic, callback, header);
+ } catch (error) {
+ console.error('websocket订阅失败');
+ }
+
+ }
+
+ this.socketoff = function (topic) {
+ scope.teststomp.unsubscribe(topic);
+ }
+
+ // 仿真socket接口回调函数
+ function callback(Response) {
+ //对象化数据
+ let data = JSON.parse(Response.body);
+ //遍历后台数据
+
+ //判断消息类型
+ // if(data.type == "Simulation_TrainPosition"){
+ //
+ //
+ // }
+ if (data.type == "Simulation_DeviceStatus") {
+
+ if (data.body.length < 200) {
+
+ for (let i = data.body.length - 1; i >= 0; i--) {
+
+ if (data.body[i]._type == "Train") {
+
+ //console.log(data.body[i]);
+ //遍历列车对象组
+ //console.log(data.body[i]);
+ if (trainlisttest) {
+ code = data.body[i].code;
+ //剔除不显示的车
+ //找到对应列车
+
+ if (trainlisttest.list[code]) {
+ //车门开关验证
+ // if(data.body[i].directionType == "02"){
+ if (trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "01") {
+ //console.log("close");
+ trainlisttest.list[code].doorStatus = "01";
+ for (let an = actions[code].top.length - 1; an >= 0; an--) {
+ actions[code].top[an].reset();
+ actions[code].top[an].time = actions[code].top[an]._clip.duration;
+ actions[code].top[an].timeScale = -1;
+ actions[code].top[an].play();
+ }
+ } else if (trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "02") {
+ //console.log("open");
+ trainlisttest.list[code].doorStatus = "02";
+ for (let an = actions[code].top.length - 1; an >= 0; an--) {
+ actions[code].top[an].reset();
+ actions[code].top[an].time = 0;
+ actions[code].top[an].timeScale = 1;
+ actions[code].top[an].play();
+ }
+ }
+ // }else{
+ // if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "01"){
+ // //console.log("close");
+ // trainlisttest.list[code].doorStatus = "01";
+ // for(let an=actions[code].down.length-1;an>=0;an--){
+ // actions[code].down[an].reset();
+ // actions[code].down[an].time = actions[code].top[an]._clip.duration;
+ // actions[code].down[an].timeScale = -1;
+ // actions[code].down[an].play();
+ // }
+ // }else if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "02"){
+ // //console.log("open");
+ // trainlisttest.list[code].doorStatus = "02";
+ // for(let an=actions[code].down.length-1;an>=0;an--){
+ // actions[code].down[an].reset();
+ // actions[code].down[an].time = 0;
+ // actions[code].down[an].timeScale = 1;
+ // actions[code].down[an].play();
+ // }
+ // }
+ // }
+ //遍历获取所在轨道
+ if (trainlisttest.list[code].dispose != data.body[i].dispose && data.body[i].dispose == false) {
+ trainlisttest.group.add(trainlisttest.list[code]);
+
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].code) {
+
+ trainlisttest.list[code].position.y = 0;
+ trainlisttest.list[code].progress = 0;
+ trainlisttest.list[code].oldoffset = data.body[i].sectionOffsetPercent;
+ trainlisttest.list[code].dispose = false;
+
+
+ if (data.body[i].directionType == "02") {
+ trainlisttest.list[code].status = "02";
+ trainlisttest.list[code].rotation.y = 0;
+ } else if (data.body[i].directionType == "03") {
+ trainlisttest.list[code].status = "03";
+ trainlisttest.list[code].rotation.y = Math.PI;
+ }
+
+ }
+
+ } else if (trainlisttest.list[code].dispose != data.body[i].dispose && data.body[i].dispose == true) {
+ trainlisttest.group.remove(trainlisttest.list[code]);
+ trainlisttest.list[code].progress = null;
+ trainlisttest.list[code].dispose = true;
+
+ } else if (trainlisttest.list[code].dispose == data.body[i].dispose && data.body[i].dispose == false) {
+
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].code) {
+ if (trainlisttest.list[code].nowcode != data.body[i].sectionCode || trainlisttest.list[code].nextcode != data.body[i].nextSectionCode) {
+ trainlisttest.list[code].nowcode = data.body[i].sectionCode;
+ trainlisttest.list[code].nextcode = data.body[i].nextSectionCode;
+ let vexlist = [];
+ let endrotation = null;
+ if (data.body[i].directionType == "02") {//向右
+ if (trainlisttest.list[code].status != data.body[i].directionType) {
+ console.log(trainlisttest.list[code].position);
+ console.log(trainlisttest.list[code].children[5].matrixWorld);
+ trainlisttest.list[code].position.x = trainlisttest.list[code].children[5].matrixWorld.elements[12];
+ trainlisttest.list[code].children[0].position.z = trainlisttest.list[code].children[0].matrixWorld.elements[14];
+ trainlisttest.list[code].children[1].position.z = trainlisttest.list[code].children[1].matrixWorld.elements[14];
+ trainlisttest.list[code].children[2].position.z = trainlisttest.list[code].children[2].matrixWorld.elements[14];
+ trainlisttest.list[code].children[3].position.z = trainlisttest.list[code].children[3].matrixWorld.elements[14];
+ trainlisttest.list[code].children[4].position.z = trainlisttest.list[code].children[4].matrixWorld.elements[14];
+ trainlisttest.list[code].children[5].position.z = trainlisttest.list[code].children[5].matrixWorld.elements[14];
+
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x > sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x) {
+ vexlist.push(new THREE.Vector3(trainlisttest.list[code].position.x, 0, trainlisttest.list[code].children[0].matrixWorld.elements[14]));
+ for (let m = sectionlist.sections.datalist[data.body[i].sectionCode].rail.length - 1; m >= 0; m--) {
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x > trainlisttest.list[code].position.x) {
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+
+ }
+ } else {
+ vexlist.push(new THREE.Vector3(trainlisttest.list[code].position.x, 0, trainlisttest.list[code].children[0].matrixWorld.elements[14]));
+ for (let m = 0; m < sectionlist.sections.datalist[data.body[i].sectionCode].rail.length; m++) {
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x > trainlisttest.list[code].position.x) {
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }
+ } else {
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x > sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x) {
+ for (let m = sectionlist.sections.datalist[data.body[i].sectionCode].rail.length - 1; m >= 0; m--) {
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ } else {
+ for (let m = 0; m < sectionlist.sections.datalist[data.body[i].sectionCode].rail.length; m++) {
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }
+ trainlisttest.list[code].status = "02";
+ trainlisttest.list[code].rotation.y = 0;
+
+ } else if (data.body[i].directionType == "03") {//向左
+
+ if (trainlisttest.list[code].status != data.body[i].directionType) {
+ trainlisttest.list[code].position.x = trainlisttest.list[code].children[5].matrixWorld.elements[12];
+ trainlisttest.list[code].children[0].position.z = trainlisttest.list[code].children[0].matrixWorld.elements[14];
+ trainlisttest.list[code].children[1].position.z = trainlisttest.list[code].children[1].matrixWorld.elements[14];
+ trainlisttest.list[code].children[2].position.z = trainlisttest.list[code].children[2].matrixWorld.elements[14];
+ trainlisttest.list[code].children[3].position.z = trainlisttest.list[code].children[3].matrixWorld.elements[14];
+ trainlisttest.list[code].children[4].position.z = trainlisttest.list[code].children[4].matrixWorld.elements[14];
+ trainlisttest.list[code].children[5].position.z = trainlisttest.list[code].children[5].matrixWorld.elements[14];
+ //
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x < sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x) {
+ vexlist.push(new THREE.Vector3(trainlisttest.list[code].position.x, 0, trainlisttest.list[code].children[0].matrixWorld.elements[14]));
+ for (let m = sectionlist.sections.datalist[data.body[i].sectionCode].rail.length - 1; m >= 0; m--) {
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x < trainlisttest.list[code].position.x) {
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ } else {
+ vexlist.push(new THREE.Vector3(trainlisttest.list[code].position.x, 0, trainlisttest.list[code].children[0].matrixWorld.elements[14]));
+ for (let m = 0; m < sectionlist.sections.datalist[data.body[i].sectionCode].rail.length; m++) {
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x < trainlisttest.list[code].position.x) {
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }
+ } else {
+ //
+ if (sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x < sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x) {
+ for (let m = sectionlist.sections.datalist[data.body[i].sectionCode].rail.length - 1; m >= 0; m--) {
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+
+ } else {
+ for (let m = 0; m < sectionlist.sections.datalist[data.body[i].sectionCode].rail.length; m++) {
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }
+ trainlisttest.list[code].status = "03";
+ trainlisttest.list[code].rotation.y = Math.PI;
+
+ }
+
+ trainlisttest.list[code].curve = new THREE.CatmullRomCurve3(vexlist);
+ // console.log(trainlisttest.list[code].curve.points);
+ // console.log(data.body[i].speed)
+ // console.log(sectionlist.sections.datalist[data.body[i].sectionCode].distance);
+ trainlisttest.list[code].speeds = data.body[i].speed * 10 / 36 / 11 / sectionlist.sections.datalist[data.body[i].sectionCode].distance;
+
+ trainlisttest.list[code].progress = 0;
+ }
+ // console.log("speed");
+ // console.log(data.body[i].speed);
+ // console.log(trainlisttest.list[code].speed);
+ if (data.body[i].speed != trainlisttest.list[code].speed) {
+ trainlisttest.list[code].speed = data.body[i].speed;
+ trainlisttest.list[code].speeds = data.body[i].speed * 10 / 36 / 11 / sectionlist.sections.datalist[data.body[i].sectionCode].distance;
+ // if(trainlisttest.list[code].speeds > 0.1){
+ // console.log(data.body[i]);
+ // console.log(data.body[i].speed)
+ // console.log(sectionlist.sections.datalist[data.body[i].sectionCode].distance);
+ // console.log(trainlisttest.list[code].speeds);
+ // }
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+ }
+
+
+ //0xFFFFFF
+ //0xCD0000 红
+ //0xEEEE00 黄
+ //0x32CD32 绿
+ if (data.body[i]._type == "Signal") {
+
+ if (signallist) {
+ code = data.body[i].code;
+ if (data.body[i].lightType == "01") {
+ if (signallist.list[code].mesh.code) {
+ signallist.list[code].mesh.status = data.body[i].status;
+
+ //55
+ //33
+ //77
+ //关闭
+ if (data.body[i].status == "01") {
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[0];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //开放
+ if (data.body[i].status == "02") {
+
+ if (data.body[i].switchLocateType == "01") {
+ signallist.list[code].mesh.children[0].material.map = materials[2];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ if (data.body[i].switchLocateType == "02") {
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[1];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ }
+ //引导
+ if (data.body[i].status == "03") {
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[0];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[1];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //封锁
+ if (data.body[i].status == "04") {
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //故障
+ if (data.body[i].status == "05") {
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+
+
+ }
+
+ } else if (data.body[i].lightType == "01") {
+ if (signallist.list[code].mesh.code) {
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ }
+
+ }
+
+ }
+
+
+ if (data.body[i]._type == "StationStand") {
+ if (actions) {
+ code = data.body[i].code;
+ if (data.body[i].screenDoorOpenStatus == "02" && actions[code].status == "01") {
+ actions[code].status = "00";
+ }
+ if (data.body[i].screenDoorOpenStatus == "02" && actions[code].status == "00") {
+ actions[code].status = "02";
+ actions[code].action.reset();
+ actions[code].action.time = 0;
+ actions[code].action.timeScale = 1;
+ actions[code].action.play();
+ }
+
+ if (data.body[i].screenDoorOpenStatus == "01" && actions[code].status == "02") {
+ actions[code].status = "00";
+ }
+ if (data.body[i].screenDoorOpenStatus == "01" && actions[code].status == "00") {
+ actions[code].status = "01";
+ actions[code].action.reset();
+ actions[code].action.time = actions[code].action._clip.duration;
+ actions[code].action.timeScale = -1;
+ actions[code].action.play();
+ }
+
+ }
+
+ }
+
+ if (data.body[i]._type == "Switch") {
+
+ if (sectionlist) {
+ code = data.body[i].code;
+ for (let j = sectionlist.switchs.modellist.length - 1; j >= 0; j--) {
+ if (sectionlist.switchs.modellist[j].code == code) {
+ if (sectionlist.switchs.modellist[j].locateType != data.body[i].locateType) {
+ if (data.body[i].locateType == "02") {
+ sectionlist.switchs.modellist[j].locateType = data.body[i].locateType;
+ actions[sectionlist.switchs.modellist[j].code].reset();
+ actions[sectionlist.switchs.modellist[j].code].time = 0;
+ actions[sectionlist.switchs.modellist[j].code].timeScale = 1;
+ actions[sectionlist.switchs.modellist[j].code].play();
+
+ } else if (data.body[i].locateType == "01") {
+ sectionlist.switchs.modellist[j].locateType = data.body[i].locateType;
+ actions[sectionlist.switchs.modellist[j].code].reset();
+ actions[sectionlist.switchs.modellist[j].code].time = actions[sectionlist.switchs.modellist[j].code]._clip.duration;
+ actions[sectionlist.switchs.modellist[j].code].timeScale = -1;
+ actions[sectionlist.switchs.modellist[j].code].play();
+
+ }
+ }
+
+ //console.log(sectionlist.group[j].name);
+ j = 0;
+ }
+ }
+ }
+
+ }
+ }
+
+ } else if (data.body.length > 200) {
+
+ }
+
+
+ }
+
+
+
+ }
+}
diff --git a/src/jlmap3d/control/FirstControls.js b/src/jlmap3d/control/FirstControls.js
new file mode 100644
index 000000000..9ed0ce03b
--- /dev/null
+++ b/src/jlmap3d/control/FirstControls.js
@@ -0,0 +1,390 @@
+/**
+ * 摄像机控制(用户交互),此类只实现键盘交互(移动),如果需要鼠标交互(转动视角)使用其子类PointerLockControls或者MouseControls
+ * @constructor
+ * @param {THREE.Camera} camera 需要控制的摄像机
+ * @param {Number} height 高度,相当于观察者身高,默认1.6
+ */
+function FreeControls(camera, height) {
+
+ var _scope = this;
+ var _PI_2 = Math.PI / 2;
+
+ var _forward = false;
+ var _backward = false;
+ var _right = false;
+ var _left = false;
+ var _up = false;
+ var _down = false;
+
+ var _up_whirl = false;
+ var _down_whirl = false;
+ var _left_whirl = false;
+ var _right_whirl = false;
+
+ this._camera = camera;
+ this._pitchObject = new THREE.Object3D();
+ this._yawObject = new THREE.Object3D();
+
+ /**
+ * 控制器使能
+ * @type {Boolean}
+ */
+ this.enabled = false;
+ /**
+ * 摄像机移动速度,默认2
+ * @type {Number}
+ */
+ this.v_delta =0.5;
+ /**
+ * 上帝模式开关,默认false
+ * @type {Boolean}
+ */
+ this.godView = false;
+ /**
+ * 摄像机高度(观察者身高)
+ * @type {Number}
+ */
+ this.height = (height ? height : 1.6);
+ /**
+ * 移动控制按键
+ * @type {Object}
+ * @property {Number} FORWARD 向前走按键的KEYCODE
+ * @property {Number} BACKWARD 向后走按键的KEYCODE
+ * @property {Number} LEFT 向左走按键的KEYCODE
+ * @property {Number} RIGHT 向右走按键的KEYCODE
+ */
+ this.keys = {
+ FORWARD: 87,
+ BACKWARD: 83,
+ LEFT: 65,
+ RIGHT: 68
+ }
+
+
+
+ this._camera.rotation.set(0, 0, 0);
+
+ this._pitchObject.add(this._camera);
+
+ this._yawObject.position.y = this.height;
+ this._yawObject.add(this._pitchObject);
+
+ this._pitchObject.rotation.set(0, 0, 0);
+ this._yawObject.rotation.set(0, 0, 0);
+
+ document.addEventListener('keydown', onKeyDown, false);
+ document.addEventListener('keyup', onKeyUp, false);
+
+ // Touch handling code
+ document.addEventListener('touchstart', onTouchStart, false);
+ document.addEventListener('touchend', onTouchEnd, false);
+ document.addEventListener('touchmove', onTouchMove, false);
+ //viewport.addEventListener('touchstart', function(event) {
+ function onTouchStart(event) {
+ var touches = event.touches;
+ switch(touches.length) {
+ case 1: // Single finger looks around
+ //startLook(touches[0].pageX, touches[0].pageY);
+ break;
+ case 2: // Two fingers moves
+ //startMove(touches[0].pageX, touches[0].pageY);
+ break;
+ case 3: // Three finger tap jumps
+ //playerMover.jump();
+ break;
+ default:
+ return;
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ //viewport.addEventListener('touchend', function(event) {
+ function onTouchEnd(event) {
+ //endLook();
+ return false;
+ }
+ //viewport.addEventListener('touchmove', function(event) {
+ function onTouchMove(event) {
+ var touches = event.touches;
+ switch(touches.length) {
+ case 1:
+ //moveLook(touches[0].pageX, touches[0].pageY);
+ break;
+ case 2:
+ //moveUpdate(touches[0].pageX, touches[0].pageY, 16);
+ break;
+ default:
+ return;
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ /**
+ * 更新相机位置
+ * @param {Number} delta 两次调用之间的时间差
+ * @return {undefined}
+ */
+ var rotation_delta_x = 0;
+ var rotation_delta_y = 0;
+ this.update = function (delta) {
+ delta = 4;
+ if (!_scope.enabled) return;
+ var rotation_ratio = 0.5;
+
+ if ( !_up_whirl && !_down_whirl && !_left_whirl && !_right_whirl) {
+
+ // if(mode == 2 ){
+ // _scope._pitchObject.rotation.x = headControls.getPitchObject().rotation.x + rotation_delta_x;
+ // _scope._pitchObject.rotation.x = Math.max(-_PI_2, Math.min(_PI_2, _scope._pitchObject.rotation.x));
+ // _scope._yawObject.rotation.y = headControls.getObject().rotation.y + rotation_delta_y;
+ // _scope._yawObject.rotation.z = headControls.getObject().rotation.z ;
+ // }
+ }
+ if (_forward || _backward || _left || _right || _up || _down
+ || _up_whirl || _down_whirl || _left_whirl || _right_whirl) {
+ var delta_new = 0.4;
+ var translation = new THREE.Vector3(0, 0, 0);
+ if (_scope.godView) {
+ if (_forward) {
+
+ translation.z -= _scope.v_delta * Math.cos(_scope._pitchObject.rotation.x) * delta;
+ translation.y += _scope.v_delta * Math.sin(_scope._pitchObject.rotation.x) * delta;
+ }
+ if (_backward) {
+
+ translation.z += _scope.v_delta * Math.cos(_scope._pitchObject.rotation.x) * delta;
+ translation.y -= _scope.v_delta * Math.sin(_scope._pitchObject.rotation.x) * delta;
+ }
+ } else {
+ if (_forward)
+ {
+ translation.z -= _scope.v_delta * delta * delta_new;
+ }
+ if (_backward)
+ {
+ translation.z += _scope.v_delta * delta * delta_new;
+ }
+ }
+ if (_left){
+ translation.x -= _scope.v_delta * delta * delta_new;
+ }
+ if (_right){
+ translation.x += _scope.v_delta * delta * delta_new;
+ }
+ if(_up){
+ translation.y += _scope.v_delta * delta * delta_new;
+ }
+ if(_down){
+ translation.y -= _scope.v_delta * delta * delta_new;
+ }
+
+ if(_up_whirl)
+ {
+ _scope._pitchObject.rotation.x += _scope.v_delta * delta * rotation_ratio;
+ //限制向上抬头90度
+ _scope._pitchObject.rotation.x = Math.max(-_PI_2, Math.min(_PI_2, _scope._pitchObject.rotation.x));
+ rotation_delta_x += _scope.v_delta * delta * rotation_ratio;
+ }
+ if(_down_whirl)
+ {
+ _scope._pitchObject.rotation.x -= _scope.v_delta * delta * rotation_ratio;
+ //限制向下低头90度
+ _scope._pitchObject.rotation.x = Math.max(-_PI_2, Math.min(_PI_2, _scope._pitchObject.rotation.x));
+ rotation_delta_x -= _scope.v_delta * delta * rotation_ratio;
+ }
+ if(_left_whirl)
+ {
+ _scope._yawObject.rotation.y += _scope.v_delta * delta * rotation_ratio;
+ rotation_delta_y += _scope.v_delta * delta * rotation_ratio;
+ }
+ if(_right_whirl)
+ {
+ _scope._yawObject.rotation.y -= _scope.v_delta * delta * rotation_ratio;
+ rotation_delta_y -= _scope.v_delta * delta * rotation_ratio;
+ }
+
+ _scope._yawObject.translateX(translation.x);
+ _scope._yawObject.translateY(translation.y);
+ _scope._yawObject.translateZ(translation.z);
+ //console.log(_scope._yawObject.position);
+ }
+
+ if (!_scope.godView) {
+ // _scope._yawObject.position.y = _scope.height;
+ }
+ }
+
+ /**
+ * 获取相机云台,关于相机位置的调整应该实施于由此函数返回的Object3D
+ * @return {THREE.Object3D} 相机云台
+ */
+ this.getObject = function () {
+ return this._yawObject;
+ };
+
+ /**
+ * 获取相机空间朝向,只读
+ * @return {THREE.Vector3} 相机空间朝向
+ */
+ this.getDirection = function () {
+ var direction = new THREE.Vector3(0, 0, -1);
+ var rotation = new THREE.Euler(0, 0, 0, 'YXZ');
+ var v = new THREE.Vector3();
+ rotation.set(this._pitchObject.rotation.x, this._yawObject.rotation.y, 0);
+ v.copy(direction).applyEuler(rotation);
+ return v;
+ };
+
+ function onKeyDown(event) {
+ switch(event.keyCode) {
+ case _scope.keys.FORWARD:
+ _forward = true;
+ break;
+ case 38: // up
+ _up_whirl = true;
+ break;
+
+ case _scope.keys.BACKWARD:
+ _backward = true;
+ break;
+ case 40: // down
+ _down_whirl = true;
+ break;
+
+ case _scope.keys.LEFT:
+ _left = true;
+ break;
+ case 37: // _left
+ _left_whirl = true;
+ break;
+
+ case _scope.keys.RIGHT:
+ _right = true;
+ break;
+ case 39: // _right
+ _right_whirl = true;
+ break;
+
+ case 81:
+ _up = true;
+ break;
+ case 69:
+ _down = true;
+ break;
+ }
+ }
+
+ function onKeyUp(event) {
+ switch(event.keyCode) {
+ case _scope.keys.FORWARD:
+ _forward = false;
+ break;
+ case 38: // up
+ _up_whirl = false;
+ break;
+
+ case _scope.keys.BACKWARD:
+ _backward = false;
+ break;
+ case 40: // down
+ _down_whirl = false;
+ break;
+
+ case _scope.keys.LEFT:
+ _left = false;
+ break;
+ case 37: // _left
+ _left_whirl = false;
+ break;
+
+ case _scope.keys.RIGHT:
+ _right = false;
+ break;
+ case 39: // _right
+ _right_whirl = false;
+ break;
+
+ case 81:
+ _up = false;
+ break;
+ case 69:
+ _down = false;
+ break;
+ }
+ }
+}
+
+export function MouseControls(camera, height) {
+ var caozuo = 1;
+ var lastX,lastY;
+ FreeControls.call(this, camera, height);
+
+ var _scope = this;
+ var _PI_2 = Math.PI / 2;
+
+ var _mouseDown = false;
+ document.addEventListener('keydown', FreeControls.onKeyDown, false);
+ document.addEventListener('mousemove', onMouseMove, false);
+ document.addEventListener('mousedown', onMouseDown, false);
+ document.addEventListener('mouseup', onMouseUp, false);
+
+ this.removeEventListeners = function (){
+ document.removeEventListener('mousemove', onMouseMove, false);
+ document.removeEventListener('mousedown', onMouseDown, false);
+ document.removeEventListener('mouseup', onMouseUp, false);
+ document.removeEventListener('keydown', FreeControls.onKeyDown, false);
+ }
+ function onMouseMove(event) {
+ if(caozuo ==1){
+ if (_scope.enabled === false || _mouseDown === false) return;
+ event.preventDefault();
+ event.stopPropagation();
+
+ var currentX = event.clientX;
+ var currentY = event.clientY;
+ var movementX = currentX - lastX;
+ var movementY = currentY - lastY;
+
+ _scope._yawObject.rotation.y -= movementX * 0.001;
+ _scope._pitchObject.rotation.x -= movementY * 0.001;
+
+ _scope._pitchObject.rotation.x = Math.max(-_PI_2, Math.min(_PI_2, _scope._pitchObject.rotation.x));
+
+ lastX = currentX;
+ lastY = currentY;
+ }else{
+
+
+
+ }
+ }
+
+ function onMouseDown (event) {
+ if(caozuo ==1){
+ if (_scope.enabled === false) return;
+
+ _mouseDown = true;
+ event.preventDefault();
+ event.stopPropagation();
+
+ lastX = event.clientX;
+ lastY = event.clientY;
+
+ }else{
+
+
+ }
+ }
+
+ function onMouseUp (event) {
+ if(caozuo ==1){
+ _mouseDown = false;
+ event.preventDefault();
+ event.stopPropagation();
+ }else{
+
+
+ }
+ }
+}
diff --git a/src/jlmap3d/control/OrbitControls.js b/src/jlmap3d/control/OrbitControls.js
new file mode 100644
index 000000000..06ce9a338
--- /dev/null
+++ b/src/jlmap3d/control/OrbitControls.js
@@ -0,0 +1,1100 @@
+/**
+ * @author qiao / https://github.com/qiao
+ * @author mrdoob / http://mrdoob.com
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author erich666 / http://erichaines.com
+ */
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+//
+// Orbit - left mouse / touch: one-finger move
+// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+
+THREE.OrbitControls = function ( object, domElement ) {
+ this.object = object;
+
+ this.data = null;
+
+ this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+ // Set to false to disable this control
+ this.enabled = true;
+
+ // "target" sets the location of focus, where the object orbits around
+ this.target = new THREE.Vector3();
+
+ // How far you can dolly in and out ( PerspectiveCamera only )
+ this.minDistance = 0;
+ this.maxDistance = Infinity;
+
+ // How far you can zoom in and out ( OrthographicCamera only )
+ this.minZoom = 0;
+ this.maxZoom = Infinity;
+
+ // How far you can orbit vertically, upper and lower limits.
+ // Range is 0 to Math.PI radians.
+ this.minPolarAngle = 0; // radians
+ this.maxPolarAngle = Math.PI; // radians
+
+ // How far you can orbit horizontally, upper and lower limits.
+ // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
+ this.minAzimuthAngle = - Infinity; // radians
+ this.maxAzimuthAngle = Infinity; // radians
+
+ // Set to true to enable damping (inertia)
+ // If damping is enabled, you must call controls.update() in your animation loop
+ this.enableDamping = false;
+ this.dampingFactor = 0.25;
+
+ // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+ // Set to false to disable zooming
+ this.enableZoom = true;
+ this.zoomSpeed = 1.0;
+
+ // Set to false to disable rotating
+ this.enableRotate = true;
+ this.rotateSpeed = 1.0;
+
+ // Set to false to disable panning
+ this.enablePan = true;
+ this.panSpeed = 1.0;
+ this.screenSpacePanning = false; // if true, pan in screen-space
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+
+ // Set to true to automatically rotate around the target
+ // If auto-rotate is enabled, you must call controls.update() in your animation loop
+ this.autoRotate = false;
+ this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
+
+ // Set to false to disable use of the keys
+ this.enableKeys = true;
+
+ // The four arrow keys
+ this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
+
+ // Mouse buttons
+ this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT };
+
+ // for reset
+ this.target0 = this.target.clone();
+ this.position0 = this.object.position.clone();
+ this.zoom0 = this.object.zoom;
+
+ //
+ // public methods
+ //
+
+ this.getPolarAngle = function () {
+
+ return spherical.phi;
+
+ };
+
+ this.getAzimuthalAngle = function () {
+
+ return spherical.theta;
+
+ };
+
+ this.saveState = function () {
+
+ scope.target0.copy( scope.target );
+ scope.position0.copy( scope.object.position );
+ scope.zoom0 = scope.object.zoom;
+
+ };
+
+ this.reset = function () {
+
+ scope.target.copy( scope.target0 );
+ scope.object.position.copy( scope.position0 );
+ scope.object.zoom = scope.zoom0;
+
+ scope.object.updateProjectionMatrix();
+ scope.dispatchEvent( changeEvent );
+
+ scope.update();
+
+ state = STATE.NONE;
+
+ };
+
+ this.upmodeldirect = function(data){
+ scope.data = data;
+ }
+
+ // this method is exposed, but perhaps it would be better if we can make it private...
+ this.update = function () {
+
+
+ var offset = new THREE.Vector3();
+
+ // so camera.up is the orbit axis
+ var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+ var quatInverse = quat.clone().inverse();
+
+ var lastPosition = new THREE.Vector3();
+ var lastQuaternion = new THREE.Quaternion();
+
+ return function update() {
+
+
+ if(scope.data){
+ //站台牌自动跟随相机
+ if(scope.data.stationstandlist){
+ for(let i = scope.data.stationstandlist.textlist.length-1; i>=0; i --){
+ scope.data.stationstandlist.textlist[i].lookAt(scope.object.position );
+ }
+ }
+
+
+ if(scope.data.trainlisttest){
+ //列车牌自动跟随相机
+ for(let ii = scope.data.trainlisttest.textlist.length-1; ii>=0; ii --){
+ scope.data.trainlisttest.textlist[ii].lookAt(scope.object.position );
+ }
+ }
+
+ }
+
+ var position = scope.object.position;
+
+ offset.copy( position ).sub( scope.target );
+
+ // rotate offset to "y-axis-is-up" space
+ offset.applyQuaternion( quat );
+
+ // angle from z-axis around y-axis
+ spherical.setFromVector3( offset );
+
+ if ( scope.autoRotate && state === STATE.NONE ) {
+
+ rotateLeft( getAutoRotationAngle() );
+
+ }
+
+ spherical.theta += sphericalDelta.theta;
+ spherical.phi += sphericalDelta.phi;
+
+ // restrict theta to be between desired limits
+ spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
+
+ // restrict phi to be between desired limits
+ spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+
+ spherical.makeSafe();
+
+
+ spherical.radius *= scale;
+
+ // restrict radius to be between desired limits
+ spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+
+ // move target to panned location
+ scope.target.add( panOffset );
+
+ offset.setFromSpherical( spherical );
+
+ // rotate offset back to "camera-up-vector-is-up" space
+ offset.applyQuaternion( quatInverse );
+
+ position.copy( scope.target ).add( offset );
+
+ scope.object.lookAt( scope.target );
+
+ if ( scope.enableDamping === true ) {
+
+ sphericalDelta.theta *= ( 1 - scope.dampingFactor );
+ sphericalDelta.phi *= ( 1 - scope.dampingFactor );
+
+ panOffset.multiplyScalar( 1 - scope.dampingFactor );
+
+ } else {
+
+ sphericalDelta.set( 0, 0, 0 );
+
+ panOffset.set( 0, 0, 0 );
+
+ }
+
+ scale = 1;
+
+ // update condition is:
+ // min(camera displacement, camera rotation in radians)^2 > EPS
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+ if ( zoomChanged ||
+ lastPosition.distanceToSquared( scope.object.position ) > EPS ||
+ 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+
+ scope.dispatchEvent( changeEvent );
+
+ lastPosition.copy( scope.object.position );
+ lastQuaternion.copy( scope.object.quaternion );
+ zoomChanged = false;
+
+ return true;
+
+ }
+
+ return false;
+
+ };
+
+ }();
+
+ this.dispose = function () {
+
+ scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
+ scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+ scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
+
+ scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
+ scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
+ scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+ document.removeEventListener( 'mouseup', onMouseUp, false );
+
+ window.removeEventListener( 'keydown', onKeyDown, false );
+
+ //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+ };
+
+ //
+ // internals
+ //
+
+ var scope = this;
+
+ var changeEvent = { type: 'change' };
+ var startEvent = { type: 'start' };
+ var endEvent = { type: 'end' };
+
+ var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 };
+
+ var state = STATE.NONE;
+
+ var EPS = 0.000001;
+
+ // current position in spherical coordinates
+ var spherical = new THREE.Spherical();
+ var sphericalDelta = new THREE.Spherical();
+
+ var scale = 1;
+ var panOffset = new THREE.Vector3();
+ var zoomChanged = false;
+
+ var rotateStart = new THREE.Vector2();
+ var rotateEnd = new THREE.Vector2();
+ var rotateDelta = new THREE.Vector2();
+
+ var panStart = new THREE.Vector2();
+ var panEnd = new THREE.Vector2();
+ var panDelta = new THREE.Vector2();
+
+ var dollyStart = new THREE.Vector2();
+ var dollyEnd = new THREE.Vector2();
+ var dollyDelta = new THREE.Vector2();
+
+ function getAutoRotationAngle() {
+
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+ }
+
+ function getZoomScale() {
+
+ return Math.pow( 0.95, scope.zoomSpeed );
+
+ }
+
+ function rotateLeft( angle ) {
+
+ sphericalDelta.theta -= angle;
+
+ }
+
+ function rotateUp( angle ) {
+
+ sphericalDelta.phi -= angle;
+
+ }
+
+ var panLeft = function () {
+
+ var v = new THREE.Vector3();
+
+ return function panLeft( distance, objectMatrix ) {
+
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+ v.multiplyScalar( - distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ var panUp = function () {
+
+ var v = new THREE.Vector3();
+
+ return function panUp( distance, objectMatrix ) {
+
+ if ( scope.screenSpacePanning === true ) {
+
+ v.setFromMatrixColumn( objectMatrix, 1 );
+
+ } else {
+
+ v.setFromMatrixColumn( objectMatrix, 0 );
+ v.crossVectors( scope.object.up, v );
+
+ }
+
+ v.multiplyScalar( distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ // deltaX and deltaY are in pixels; right and down are positive
+ var pan = function () {
+
+ var offset = new THREE.Vector3();
+
+ return function pan( deltaX, deltaY ) {
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ // perspective
+ var position = scope.object.position;
+ offset.copy( position ).sub( scope.target );
+ var targetDistance = offset.length();
+
+ // half of the fov is center to top of screen
+ targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
+
+ // we use only clientHeight here so aspect ratio does not distort speed
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ // orthographic
+ panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+ panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+
+ } else {
+
+ // camera neither orthographic nor perspective
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+ scope.enablePan = false;
+
+ }
+
+ };
+
+ }();
+
+ function dollyIn( dollyScale ) {
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale /= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ function dollyOut( dollyScale ) {
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale *= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ //
+ // event callbacks - update the object state
+ //
+
+ function handleMouseDownRotate( event ) {
+
+ //console.log( 'handleMouseDownRotate' );
+
+ rotateStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownDolly( event ) {
+
+ //console.log( 'handleMouseDownDolly' );
+
+ dollyStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownPan( event ) {
+
+ //console.log( 'handleMouseDownPan' );
+
+ panStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseMoveRotate( event ) {
+
+ //console.log( 'handleMouseMoveRotate' );
+
+ rotateEnd.set( event.clientX, event.clientY );
+
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMoveDolly( event ) {
+
+ //console.log( 'handleMouseMoveDolly' );
+
+ dollyEnd.set( event.clientX, event.clientY );
+
+ dollyDelta.subVectors( dollyEnd, dollyStart );
+
+ if ( dollyDelta.y > 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ } else if ( dollyDelta.y < 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ }
+
+ dollyStart.copy( dollyEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMovePan( event ) {
+
+ //console.log( 'handleMouseMovePan' );
+
+ panEnd.set( event.clientX, event.clientY );
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseUp( event ) {
+
+ // console.log( 'handleMouseUp' );
+
+ }
+
+ function handleMouseWheel( event ) {
+
+ // console.log( 'handleMouseWheel' );
+
+ if ( event.deltaY < 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ } else if ( event.deltaY > 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ }
+
+ scope.update();
+
+ }
+
+ function handleKeyDown( event ) {
+
+ // console.log( 'handleKeyDown' );
+
+ var needsUpdate = false;
+
+ switch ( event.keyCode ) {
+
+ case scope.keys.UP:
+ pan( 0, scope.keyPanSpeed );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.BOTTOM:
+ pan( 0, - scope.keyPanSpeed );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.LEFT:
+ pan( scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.RIGHT:
+ pan( - scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
+
+ }
+
+ if ( needsUpdate ) {
+
+ // prevent the browser from scrolling on cursor keys
+ event.preventDefault();
+
+ scope.update();
+
+ }
+
+
+ }
+
+ function handleTouchStartRotate( event ) {
+
+ //console.log( 'handleTouchStartRotate' );
+
+ rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ }
+
+ function handleTouchStartDollyPan( event ) {
+
+ //console.log( 'handleTouchStartDollyPan' );
+
+ if ( scope.enableZoom ) {
+
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+ var distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyStart.set( 0, distance );
+
+ }
+
+ if ( scope.enablePan ) {
+
+ var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+ var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+
+ panStart.set( x, y );
+
+ }
+
+ }
+
+ function handleTouchMoveRotate( event ) {
+
+ //console.log( 'handleTouchMoveRotate' );
+
+ rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleTouchMoveDollyPan( event ) {
+
+ //console.log( 'handleTouchMoveDollyPan' );
+
+ if ( scope.enableZoom ) {
+
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+ var distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyEnd.set( 0, distance );
+
+ dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
+
+ dollyIn( dollyDelta.y );
+
+ dollyStart.copy( dollyEnd );
+
+ }
+
+ if ( scope.enablePan ) {
+
+ var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+ var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+
+ panEnd.set( x, y );
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ }
+
+ scope.update();
+
+ }
+
+ function handleTouchEnd( event ) {
+
+ //console.log( 'handleTouchEnd' );
+
+ }
+
+ //
+ // event handlers - FSM: listen for events and reset state
+ //
+
+ function onMouseDown( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ // Prevent the browser from scrolling.
+
+ event.preventDefault();
+
+ // Manually set the focus since calling preventDefault above
+ // prevents the browser from setting it automatically.
+
+ scope.domElement.focus ? scope.domElement.focus() : window.focus();
+
+ switch ( event.button ) {
+
+ case scope.mouseButtons.RIGHT:
+
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ } else {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseDownRotate( event );
+
+ state = STATE.ROTATE;
+
+ }
+
+ break;
+
+ case scope.mouseButtons.MIDDLE:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseDownDolly( event );
+
+ state = STATE.DOLLY;
+
+ break;
+
+ case scope.mouseButtons.LEFT:
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ break;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ document.addEventListener( 'mousemove', onMouseMove, false );
+ document.addEventListener( 'mouseup', onMouseUp, false );
+
+ scope.dispatchEvent( startEvent );
+
+ }
+
+ }
+
+ function onMouseMove( event ) {
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ switch ( state ) {
+
+ case STATE.ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseMoveRotate( event );
+
+ break;
+
+ case STATE.DOLLY:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseMoveDolly( event );
+
+ break;
+
+ case STATE.PAN:
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseMovePan( event );
+
+ break;
+
+ }
+
+ }
+
+ function onMouseUp( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ handleMouseUp( event );
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+ document.removeEventListener( 'mouseup', onMouseUp, false );
+
+ scope.dispatchEvent( endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onMouseWheel( event ) {
+
+ if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
+
+ if (event.cancelable) {
+ // 判断默认行为是否已经被禁用
+ if (!event.defaultPrevented) {
+ event.preventDefault();
+ }
+ }
+
+ event.stopPropagation();
+
+ scope.dispatchEvent( startEvent );
+
+ handleMouseWheel( event );
+
+ scope.dispatchEvent( endEvent );
+
+ }
+
+ function onKeyDown( event ) {
+
+ if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
+
+ handleKeyDown( event );
+
+ }
+
+ function onTouchStart( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ switch ( event.touches.length ) {
+
+ case 1: // one-fingered touch: rotate
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchStartRotate( event );
+
+ state = STATE.TOUCH_ROTATE;
+
+ break;
+
+ case 2: // two-fingered touch: dolly-pan
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+ handleTouchStartDollyPan( event );
+
+ state = STATE.TOUCH_DOLLY_PAN;
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ scope.dispatchEvent( startEvent );
+
+ }
+
+ }
+
+ function onTouchMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ switch ( event.touches.length ) {
+
+ case 1: // one-fingered touch: rotate
+
+ if ( scope.enableRotate === false ) return;
+ if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?
+
+ handleTouchMoveRotate( event );
+
+ break;
+
+ case 2: // two-fingered touch: dolly-pan
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+ if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed?
+
+ handleTouchMoveDollyPan( event );
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ }
+
+ function onTouchEnd( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ handleTouchEnd( event );
+
+ scope.dispatchEvent( endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onContextMenu( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ }
+
+ //
+
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
+
+ scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
+
+ scope.domElement.addEventListener( 'touchstart', onTouchStart, { passive: false } );
+ scope.domElement.addEventListener( 'touchend', onTouchEnd, { passive: false } );
+ scope.domElement.addEventListener( 'touchmove', onTouchMove, { passive: false } );
+
+ window.addEventListener( 'keydown', onKeyDown, false );
+
+ // force an update at start
+
+ this.update();
+
+};
+
+THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
+
+Object.defineProperties( THREE.OrbitControls.prototype, {
+
+ center: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
+ return this.target;
+
+ }
+
+ },
+
+ // backward compatibility
+
+ noZoom: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
+ return ! this.enableZoom;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
+ this.enableZoom = ! value;
+
+ }
+
+ },
+
+ noRotate: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
+ return ! this.enableRotate;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
+ this.enableRotate = ! value;
+
+ }
+
+ },
+
+ noPan: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
+ return ! this.enablePan;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
+ this.enablePan = ! value;
+
+ }
+
+ },
+
+ noKeys: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
+ return ! this.enableKeys;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
+ this.enableKeys = ! value;
+
+ }
+
+ },
+
+ staticMoving: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
+ return ! this.enableDamping;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
+ this.enableDamping = ! value;
+
+ }
+
+ },
+
+ dynamicDampingFactor: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+ return this.dampingFactor;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+ this.dampingFactor = value;
+
+ }
+
+ }
+
+} );
diff --git a/src/jlmap3d/control/OribitControlstest.js b/src/jlmap3d/control/OribitControlstest.js
new file mode 100644
index 000000000..219cdff32
--- /dev/null
+++ b/src/jlmap3d/control/OribitControlstest.js
@@ -0,0 +1,1016 @@
+/**
+ * @author qiao / https://github.com/qiao
+ * @author mrdoob / http://mrdoob.com
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author erich666 / http://erichaines.com
+ */
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+//
+// Orbit - left mouse / touch: one finger move
+// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
+// Pan - right mouse, or arrow keys / touch: three finger swipe
+
+THREE.OrbitControls = function ( object, domElement ) {
+
+ this.object = object;
+
+ this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+ // Set to false to disable this control
+ this.enabled = true;
+
+ // "target" sets the location of focus, where the object orbits around
+ this.target = new THREE.Vector3();
+
+ // How far you can dolly in and out ( PerspectiveCamera only )
+ this.minDistance = 0;
+ this.maxDistance = Infinity;
+
+ // How far you can zoom in and out ( OrthographicCamera only )
+ this.minZoom = 0;
+ this.maxZoom = Infinity;
+
+ // How far you can orbit vertically, upper and lower limits.
+ // Range is 0 to Math.PI radians.
+ this.minPolarAngle = 0; // radians
+ this.maxPolarAngle = Math.PI; // radians
+
+ // How far you can orbit horizontally, upper and lower limits.
+ // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
+ this.minAzimuthAngle = - Infinity; // radians
+ this.maxAzimuthAngle = Infinity; // radians
+
+ // Set to true to enable damping (inertia)
+ // If damping is enabled, you must call controls.update() in your animation loop
+ this.enableDamping = false;
+ this.dampingFactor = 0.25;
+
+ // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+ // Set to false to disable zooming
+ this.enableZoom = true;
+ this.zoomSpeed = 1.0;
+
+ // Set to false to disable rotating
+ this.enableRotate = true;
+ this.rotateSpeed = 1.0;
+
+ // Set to false to disable panning
+ this.enablePan = true;
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+
+ // Set to true to automatically rotate around the target
+ // If auto-rotate is enabled, you must call controls.update() in your animation loop
+ this.autoRotate = false;
+ this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
+
+ // Set to false to disable use of the keys
+ this.enableKeys = true;
+
+ // The four arrow keys
+ this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
+
+ // Mouse buttons
+ this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
+
+ // for reset
+ this.target0 = this.target.clone();
+ this.position0 = this.object.position.clone();
+ this.zoom0 = this.object.zoom;
+
+ //
+ // public methods
+ //
+
+ this.getPolarAngle = function () {
+
+ return spherical.phi;
+
+ };
+
+ this.getAzimuthalAngle = function () {
+
+ return spherical.theta;
+
+ };
+
+ this.reset = function () {
+
+ scope.target.copy( scope.target0 );
+ scope.object.position.copy( scope.position0 );
+ scope.object.zoom = scope.zoom0;
+
+ scope.object.updateProjectionMatrix();
+ scope.dispatchEvent( changeEvent );
+
+ scope.update();
+
+ state = STATE.NONE;
+
+ };
+
+ // this method is exposed, but perhaps it would be better if we can make it private...
+ this.update = function () {
+
+ var offset = new THREE.Vector3();
+
+ // so camera.up is the orbit axis
+ var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+ var quatInverse = quat.clone().inverse();
+
+ var lastPosition = new THREE.Vector3();
+ var lastQuaternion = new THREE.Quaternion();
+
+ return function update() {
+
+ var position = scope.object.position;
+
+ offset.copy( position ).sub( scope.target );
+
+ // rotate offset to "y-axis-is-up" space
+ offset.applyQuaternion( quat );
+
+ // angle from z-axis around y-axis
+ spherical.setFromVector3( offset );
+
+ if ( scope.autoRotate && state === STATE.NONE ) {
+
+ rotateLeft( getAutoRotationAngle() );
+
+ }
+
+ spherical.theta += sphericalDelta.theta;
+ spherical.phi += sphericalDelta.phi;
+
+ // restrict theta to be between desired limits
+ spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
+
+ // restrict phi to be between desired limits
+ spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+
+ spherical.makeSafe();
+
+
+ spherical.radius *= scale;
+
+ // restrict radius to be between desired limits
+ spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+
+ // move target to panned location
+ scope.target.add( panOffset );
+
+ offset.setFromSpherical( spherical );
+
+ // rotate offset back to "camera-up-vector-is-up" space
+ offset.applyQuaternion( quatInverse );
+
+ position.copy( scope.target ).add( offset );
+
+ scope.object.lookAt( scope.target );
+
+ if ( scope.enableDamping === true ) {
+
+ sphericalDelta.theta *= ( 1 - scope.dampingFactor );
+ sphericalDelta.phi *= ( 1 - scope.dampingFactor );
+
+ } else {
+
+ sphericalDelta.set( 0, 0, 0 );
+
+ }
+
+ scale = 1;
+ panOffset.set( 0, 0, 0 );
+
+ // update condition is:
+ // min(camera displacement, camera rotation in radians)^2 > EPS
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+ if ( zoomChanged ||
+ lastPosition.distanceToSquared( scope.object.position ) > EPS ||
+ 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+
+ scope.dispatchEvent( changeEvent );
+
+ lastPosition.copy( scope.object.position );
+ lastQuaternion.copy( scope.object.quaternion );
+ zoomChanged = false;
+
+ return true;
+
+ }
+
+ return false;
+
+ };
+
+ }();
+
+ this.dispose = function () {
+
+ scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
+ scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+ scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
+
+ scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
+ scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
+ scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+ document.removeEventListener( 'mouseup', onMouseUp, false );
+
+ //window.removeEventListener( 'keydown', onKeyDown, false );
+
+ //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+ };
+
+ //
+ // internals
+ //
+
+ var scope = this;
+
+ var changeEvent = { type: 'change' };
+ var startEvent = { type: 'start' };
+ var endEvent = { type: 'end' };
+
+ var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };
+
+ var state = STATE.NONE;
+
+ var EPS = 0.000001;
+
+ // current position in spherical coordinates
+ var spherical = new THREE.Spherical();
+ var sphericalDelta = new THREE.Spherical();
+
+ var scale = 1;
+ var panOffset = new THREE.Vector3();
+ var zoomChanged = false;
+
+ var rotateStart = new THREE.Vector2();
+ var rotateEnd = new THREE.Vector2();
+ var rotateDelta = new THREE.Vector2();
+
+ var panStart = new THREE.Vector2();
+ var panEnd = new THREE.Vector2();
+ var panDelta = new THREE.Vector2();
+
+ var dollyStart = new THREE.Vector2();
+ var dollyEnd = new THREE.Vector2();
+ var dollyDelta = new THREE.Vector2();
+
+ function getAutoRotationAngle() {
+
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+ }
+
+ function getZoomScale() {
+
+ return Math.pow( 0.95, scope.zoomSpeed );
+
+ }
+
+ function rotateLeft( angle ) {
+
+ sphericalDelta.theta -= angle;
+
+ }
+
+ function rotateUp( angle ) {
+
+ sphericalDelta.phi -= angle;
+
+ }
+
+ var panLeft = function () {
+
+ var v = new THREE.Vector3();
+
+ return function panLeft( distance, objectMatrix ) {
+
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+ v.multiplyScalar( - distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ var panUp = function () {
+
+ var v = new THREE.Vector3();
+
+ return function panUp( distance, objectMatrix ) {
+
+ v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
+ v.multiplyScalar( distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ // deltaX and deltaY are in pixels; right and down are positive
+ var pan = function () {
+
+ var offset = new THREE.Vector3();
+
+ return function pan( deltaX, deltaY ) {
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ if ( scope.object instanceof THREE.PerspectiveCamera ) {
+
+ // perspective
+ var position = scope.object.position;
+ offset.copy( position ).sub( scope.target );
+ var targetDistance = offset.length();
+
+ // half of the fov is center to top of screen
+ targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
+
+ // we actually don't use screenWidth, since perspective camera is fixed to screen height
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+
+ } else if ( scope.object instanceof THREE.OrthographicCamera ) {
+
+ // orthographic
+ panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+ panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+
+ } else {
+
+ // camera neither orthographic nor perspective
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+ scope.enablePan = false;
+
+ }
+
+ };
+
+ }();
+
+ function dollyIn( dollyScale ) {
+
+ if ( scope.object instanceof THREE.PerspectiveCamera ) {
+
+ scale /= dollyScale;
+
+ } else if ( scope.object instanceof THREE.OrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ function dollyOut( dollyScale ) {
+
+ if ( scope.object instanceof THREE.PerspectiveCamera ) {
+
+ scale *= dollyScale;
+
+ } else if ( scope.object instanceof THREE.OrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ //
+ // event callbacks - update the object state
+ //
+
+ function handleMouseDownRotate( event ) {
+
+ //console.log( 'handleMouseDownRotate' );
+
+ rotateStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownDolly( event ) {
+
+ //console.log( 'handleMouseDownDolly' );
+
+ dollyStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownPan( event ) {
+
+ //console.log( 'handleMouseDownPan' );
+
+ panStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseMoveRotate( event ) {
+
+ //console.log( 'handleMouseMoveRotate' );
+
+ rotateEnd.set( event.clientX, event.clientY );
+ rotateDelta.subVectors( rotateEnd, rotateStart );
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ // rotating across whole screen goes 360 degrees around
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
+
+ // rotating up and down along whole screen attempts to go 360, but limited to 180
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMoveDolly( event ) {
+
+ //console.log( 'handleMouseMoveDolly' );
+
+ dollyEnd.set( event.clientX, event.clientY );
+
+ dollyDelta.subVectors( dollyEnd, dollyStart );
+
+ if ( dollyDelta.y > 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ } else if ( dollyDelta.y < 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ }
+
+ dollyStart.copy( dollyEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMovePan( event ) {
+
+ //console.log( 'handleMouseMovePan' );
+
+ panEnd.set( event.clientX, event.clientY );
+
+ panDelta.subVectors( panEnd, panStart );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseUp( event ) {
+
+ // console.log( 'handleMouseUp' );
+
+ }
+
+ function handleMouseWheel( event ) {
+
+ // console.log( 'handleMouseWheel' );
+
+ if ( event.deltaY < 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ } else if ( event.deltaY > 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ }
+
+ scope.update();
+
+ }
+
+ function handleKeyDown( event ) {
+
+ //console.log( 'handleKeyDown' );
+
+ switch ( event.keyCode ) {
+
+ case scope.keys.UP:
+ pan( 0, scope.keyPanSpeed );
+ scope.update();
+ break;
+
+ case scope.keys.BOTTOM:
+ pan( 0, - scope.keyPanSpeed );
+ scope.update();
+ break;
+
+ case scope.keys.LEFT:
+ pan( scope.keyPanSpeed, 0 );
+ scope.update();
+ break;
+
+ case scope.keys.RIGHT:
+ pan( - scope.keyPanSpeed, 0 );
+ scope.update();
+ break;
+
+ }
+
+ }
+
+ function handleTouchStartRotate( event ) {
+
+ //console.log( 'handleTouchStartRotate' );
+
+ rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ }
+
+ function handleTouchStartDolly( event ) {
+
+ //console.log( 'handleTouchStartDolly' );
+
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+ var distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyStart.set( 0, distance );
+
+ }
+
+ function handleTouchStartPan( event ) {
+
+ //console.log( 'handleTouchStartPan' );
+
+ panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ }
+
+ function handleTouchMoveRotate( event ) {
+
+ //console.log( 'handleTouchMoveRotate' );
+
+ rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+ rotateDelta.subVectors( rotateEnd, rotateStart );
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ // rotating across whole screen goes 360 degrees around
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
+
+ // rotating up and down along whole screen attempts to go 360, but limited to 180
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleTouchMoveDolly( event ) {
+
+ //console.log( 'handleTouchMoveDolly' );
+
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+ var distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyEnd.set( 0, distance );
+
+ dollyDelta.subVectors( dollyEnd, dollyStart );
+
+ if ( dollyDelta.y > 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ } else if ( dollyDelta.y < 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ }
+
+ dollyStart.copy( dollyEnd );
+
+ scope.update();
+
+ }
+
+ function handleTouchMovePan( event ) {
+
+ //console.log( 'handleTouchMovePan' );
+
+ panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ panDelta.subVectors( panEnd, panStart );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ scope.update();
+
+ }
+
+ function handleTouchEnd( event ) {
+
+ //console.log( 'handleTouchEnd' );
+
+ }
+
+ //
+ // event handlers - FSM: listen for events and reset state
+ //
+
+ function onMouseDown( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ if ( event.button === scope.mouseButtons.ORBIT ) {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseDownRotate( event );
+
+ state = STATE.ROTATE;
+
+ } else if ( event.button === scope.mouseButtons.ZOOM ) {
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseDownDolly( event );
+
+ state = STATE.DOLLY;
+
+ } else if ( event.button === scope.mouseButtons.PAN ) {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ document.addEventListener( 'mousemove', onMouseMove, false );
+ document.addEventListener( 'mouseup', onMouseUp, false );
+
+ scope.dispatchEvent( startEvent );
+
+ }
+
+ }
+
+ function onMouseMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ if ( state === STATE.ROTATE ) {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseMoveRotate( event );
+
+ } else if ( state === STATE.DOLLY ) {
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseMoveDolly( event );
+
+ } else if ( state === STATE.PAN ) {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseMovePan( event );
+
+ }
+
+ }
+
+ function onMouseUp( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ handleMouseUp( event );
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+ document.removeEventListener( 'mouseup', onMouseUp, false );
+
+ scope.dispatchEvent( endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onMouseWheel( event ) {
+
+ if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ handleMouseWheel( event );
+
+ scope.dispatchEvent( startEvent ); // not sure why these are here...
+ scope.dispatchEvent( endEvent );
+
+ }
+
+ function onKeyDown( event ) {
+
+ if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
+
+ handleKeyDown( event );
+
+ }
+
+ function onTouchStart( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ switch ( event.touches.length ) {
+
+ case 1: // one-fingered touch: rotate
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchStartRotate( event );
+
+ state = STATE.TOUCH_ROTATE;
+
+ break;
+
+ case 2: // two-fingered touch: dolly
+
+ if ( scope.enableZoom === false ) return;
+
+ handleTouchStartDolly( event );
+
+ state = STATE.TOUCH_DOLLY;
+
+ break;
+
+ case 3: // three-fingered touch: pan
+
+ if ( scope.enablePan === false ) return;
+
+ handleTouchStartPan( event );
+
+ state = STATE.TOUCH_PAN;
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ scope.dispatchEvent( startEvent );
+
+ }
+
+ }
+
+ function onTouchMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ switch ( event.touches.length ) {
+
+ case 1: // one-fingered touch: rotate
+
+ if ( scope.enableRotate === false ) return;
+ if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...
+
+ handleTouchMoveRotate( event );
+
+ break;
+
+ case 2: // two-fingered touch: dolly
+
+ if ( scope.enableZoom === false ) return;
+ if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...
+
+ handleTouchMoveDolly( event );
+
+ break;
+
+ case 3: // three-fingered touch: pan
+
+ if ( scope.enablePan === false ) return;
+ if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...
+
+ handleTouchMovePan( event );
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ }
+
+ function onTouchEnd( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ handleTouchEnd( event );
+
+ scope.dispatchEvent( endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onContextMenu( event ) {
+
+ event.preventDefault();
+
+ }
+
+ //
+
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
+
+ scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
+
+ scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
+ scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
+ scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
+
+ //window.addEventListener( 'keydown', onKeyDown, false );
+
+ // force an update at start
+
+ this.update();
+
+};
+
+THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
+
+Object.defineProperties( THREE.OrbitControls.prototype, {
+
+ center: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
+ return this.target;
+
+ }
+
+ },
+
+ // backward compatibility
+
+ noZoom: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
+ return ! this.enableZoom;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
+ this.enableZoom = ! value;
+
+ }
+
+ },
+
+ noRotate: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
+ return ! this.enableRotate;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
+ this.enableRotate = ! value;
+
+ }
+
+ },
+
+ noPan: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
+ return ! this.enablePan;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
+ this.enablePan = ! value;
+
+ }
+
+ },
+
+ noKeys: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
+ return ! this.enableKeys;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
+ this.enableKeys = ! value;
+
+ }
+
+ },
+
+ staticMoving: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
+ return ! this.enableDamping;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
+ this.enableDamping = ! value;
+
+ }
+
+ },
+
+ dynamicDampingFactor: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+ return this.dampingFactor;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+ this.dampingFactor = value;
+
+ }
+
+ }
+
+} );
diff --git a/src/jlmap3d/edit/Jlmap3ddata.js b/src/jlmap3d/edit/Jlmap3ddata.js
new file mode 100644
index 000000000..ae69109b3
--- /dev/null
+++ b/src/jlmap3d/edit/Jlmap3ddata.js
@@ -0,0 +1,164 @@
+import { getPublishMapDetail,getMapDetail,set3dMapData,get3dMapData } from '@/api/jlmap3d/load3ddata';
+
+//原始信息obj化提供给三维建模
+import {SetObj} from '@/jlmap3d/edit/utils/SetObj.js';
+//componnent
+import {SectionList} from '@/jlmap3d/edit/testmodel/SectionList.js';
+import {SignalList} from '@/jlmap3d/edit/testmodel/SignalList.js';
+import {StationStandList} from '@/jlmap3d/edit/testmodel/StationStandList.js';
+import {TrainList} from '@/jlmap3d/edit/testmodel/TrainList.js';
+import {TrainListTest} from '@/jlmap3d/edit/testmodel/TrainListTest.js';
+import {LinkList} from '@/jlmap3d/edit/testmodel/LinkList.js';
+
+import {RealSectionList} from '@/jlmap3d/edit/testmodel/RealSectionList.js';
+
+import axios from 'axios';
+import {getmodels} from '@/jlmap3d/edit/connect/getmodels';
+import { Loading } from 'element-ui';
+// import {SwitchModel} from '@/jlmap3d/model/SwitchModel.js';
+
+export function Jlmap3ddata(mapid,scope){
+ let editmapid = mapid;
+ let scene = scope.scene;
+ let jlmap3ddata = scope.mapdata;
+ let assetloader = scope.assetloader;
+ let loadingInstance = Loading.service({ fullscreen: true });
+ get3dMapData(editmapid).then(data => {
+ //console.log(data);
+ if(data.code == "200"){
+
+ //console.log("数据存在");
+
+ let isSection = false;
+ //console.log(data.data);
+ if(data.data.assets){
+ let assets = JSON.parse(data.data.assets);
+ for(let i=0;i {
+ //console.log(error);
+ if(error.code == "30001"){
+ //console.log("没有三维数据");
+ let params = {mapId:editmapid};
+
+ set3dMapData(params).then(data => {
+ if(data.code == "200"){
+ //console.log("创建三维数据成功");
+ //console.log(data);
+ initobj(editmapid,data.data.id);
+
+ }
+ }).catch(error => {
+ //console.log(error);
+ });
+
+ }
+ });
+
+ function initobj(mapid,data3did){
+ console.log(mapid);
+ getMapDetail(mapid).then(data => {
+ let mapdata = data.data;
+ //console.log(data3did);
+ jlmap3ddata.id = data3did;
+ jlmap3ddata.mapId = mapid;
+ console.log(jlmap3ddata.mapId);
+ //初始化轨道和道岔 暂时
+ jlmap3ddata.sectionlist = new SectionList();
+ jlmap3ddata.signallist = new SignalList();
+ //初始化站台
+ jlmap3ddata.stationstandlist = new StationStandList();
+ jlmap3ddata.trainlisttest = new TrainListTest();
+
+ assetloader.assetinit(scene)
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.sectionlist.initpromise(mapdata.sectionList,mapdata.switchList,scene);
+ })
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.signallist.initpromise(mapdata.signalList,scene,assetloader);
+ })
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.stationstandlist.initpromise(mapdata.stationList,mapdata.stationStandList,scene,assetloader);
+ })
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.trainlisttest.initpromise(mapdata.trainList,scene,assetloader);
+ })
+ .then(function(data){
+ //console.log(data);
+ loadingInstance.close();
+ });
+ //初始化信号
+ SetObj(mapdata,scope);
+
+ });
+ }
+
+ function init3d(mapid,netdata){
+ getMapDetail(mapid).then(data => {
+ jlmap3ddata.id = netdata.id;
+ jlmap3ddata.mapId = mapid;
+ let mapdata = data.data;
+
+ //初始化轨道和道岔 暂时
+ jlmap3ddata.sectionlist = new SectionList();
+ jlmap3ddata.signallist = new SignalList();
+ //初始化站台
+ jlmap3ddata.stationstandlist = new StationStandList();
+ //初始化测试列车
+ jlmap3ddata.trainlisttest = new TrainListTest();
+
+ jlmap3ddata.realsectionlist = new RealSectionList();
+
+ assetloader.setmodellist(netdata.assets);
+
+ assetloader.assetpromise(scene)
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.sectionlist.initpromise(mapdata.sectionList,mapdata.switchList,scene);
+ })
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.signallist.initpromise(mapdata.signalList,scene,assetloader,netdata.signals);
+ })
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.stationstandlist.initpromise(mapdata.stationList,mapdata.stationStandList,scene,assetloader,netdata.stands);
+ })
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.trainlisttest.initpromise(mapdata.trainList,scene,assetloader);
+ })
+ .then(function(data){
+ //console.log(data);
+ return jlmap3ddata.realsectionlist.initpromise(jlmap3ddata,scene,assetloader);
+ })
+ .then(function(data){
+ //console.log(data);
+ loadingInstance.close();
+ });
+
+ });
+ }
+
+
+
+}
diff --git a/src/jlmap3d/edit/action/pathaction.js b/src/jlmap3d/edit/action/pathaction.js
new file mode 100644
index 000000000..4d9332b25
--- /dev/null
+++ b/src/jlmap3d/edit/action/pathaction.js
@@ -0,0 +1,389 @@
+//轨道对象操作
+export function Pathaction(){
+
+ let scope = this;
+ //开关
+ this.on = true;
+
+ //修改名称
+ this.setname = function(){
+ if(scope.on == true){
+
+ }
+ }
+ //改变类型
+ this.settype = function(){
+ if(scope.on == true){
+
+ }
+ }
+ //改变坐标
+ this.setpos = function(){
+ if(scope.on == true){
+
+ }
+ }
+ //改变角度
+ this.setrota = function(){
+ if(scope.on == true){
+
+ }
+ }
+ //改变拉伸值
+ this.setscle = function(){
+ if(scope.on == true){
+
+ }
+ }
+
+ this.drawline = function(jlmap3dedit,splineHelperObjects){
+
+ if(scope.on == true){
+
+ var rayhelper;//画图辅助线
+
+ let lineswitch = 0;
+
+ var linenew;
+
+ document.onmousedown = function(event){
+ jlmap3dedit.selectswitch = false;
+ console.log(event.button);
+
+ //辅助线段
+
+ let point1;
+
+ if(event.button == 2){
+ jlmap3dedit.selectswitch = true;
+ document.onmousedown = null;
+
+ lineswitch = null;
+
+ console.log(linenew);
+
+ if(linenew){
+
+ jlmap3dedit.scene.remove( linenew );
+
+ }
+
+ }else if(event.button == 0){
+
+ if(lineswitch == 0){
+
+ point1 = mouserray(event,jlmap3dedit);
+
+ console.log("new");
+ if(point1){
+ linenew = addline(point1,1,jlmap3dedit.splineHelperObjects,jlmap3dedit.scene);
+ jlmap3dedit.mapdata.path.push(linenew);
+ jlmap3dedit.scene.add( linenew );
+
+ lineswitch = 1;
+ }
+
+
+ }else if(lineswitch == 1){
+
+ let point = {
+ x:(linenew.geometry.vertices[0].x+linenew.geometry.vertices[1].x)/2,
+ y:linenew.geometry.vertices[1].y,
+ z:(linenew.geometry.vertices[0].z+linenew.geometry.vertices[1].z)/2
+ };
+ var raycaster = new THREE.Raycaster(new THREE.Vector3(point.x,point.y,point.z),new THREE.Vector3(0,-1,0).normalize());
+
+ for(let i=0;i(point1.x-6)&&point2.x<(point1.x+6))&& point1.z != point2.z){
+
+ point2.x = point1.x;
+
+ }else if((point2.z>(point1.z-6)&&point2.z<(point1.z+6)) && point1.x != point2.x){
+
+ point2.z = point1.z;
+
+ }
+
+ //更新辅助线段
+ linenew.children[1].position.x = point2.x;
+ linenew.children[1].position.z = point2.z;
+
+ linenew.geometry.vertices[1].x = point2.x;
+
+ linenew.geometry.vertices[1].z = point2.z;
+
+ linenew.geometry.verticesNeedUpdate = true;
+ }
+
+
+ }
+
+ document.onmouseup = function(event){
+
+ if(lineswitch == 0){
+
+ document.onmousemove = null;
+
+ document.onmouseup = null;
+
+ }
+
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ this.drawcurve = function(jlmap3dedit){
+
+ if(scope.on == true){
+
+ var rayhelper;//画图辅助线
+
+ let lineswitch = 0;
+
+ var linenew;
+
+ document.onmousedown = function(event){
+
+ console.log(event.button);
+
+ //辅助线段
+
+ let point1;
+
+ if(event.button == 2){
+
+ document.onmousedown = null;
+
+ lineswitch = null;
+
+ console.log(linenew);
+
+ if(linenew){
+
+ jlmap3dedit.scene.remove( linenew );
+
+ }
+
+ }else if(event.button == 0){
+
+ if(lineswitch == 0){
+
+ point1 = mouserray(event,jlmap3dedit);
+
+ if(point1){
+ linenew = addline(point1,2,jlmap3dedit.splineHelperObjects,jlmap3dedit.scene);
+
+
+ jlmap3dedit.scene.add( linenew );
+
+ lineswitch = 1;
+ }
+
+
+
+
+ }else if(lineswitch == 1){
+
+ let point = {
+ x:linenew.geometry.vertices[1].x,
+ y:linenew.geometry.vertices[1].y,
+ z:linenew.geometry.vertices[1].z
+ };
+ var raycaster = new THREE.Raycaster(new THREE.Vector3(point.x,point.y,point.z),new THREE.Vector3(0,-1,0).normalize());
+
+ for(let i=0;i(window.innerWidth*0.8) ){
+
+ }else{
+ mouse.x = (mousep.clientX / window.innerWidth) * 2 - 1;
+ mouse.y = -(mousep.clientY / window.innerHeight) * 2 + 1;
+
+ raycaster.setFromCamera( mouse, jlmap3dedit.camera );
+ console.log(jlmap3dedit.assetloader.modellist[3].mesh);
+ var intersects = raycaster.intersectObjects(jlmap3dedit.assetloader.modellist[3].mesh.children);
+ console.log(intersects);
+ if(intersects[0]){
+ resultpoint = intersects[0].point;
+ return resultpoint;
+ }
+ }
+
+ }
+
+ function addline(point1,num,splineHelperObjects,scene){
+
+ let material = new THREE.LineBasicMaterial({color: 0xEEC900});
+
+ let geometry = new THREE.Geometry();
+
+ let object;
+
+ if(num == 1){
+ geometry.vertices.push(new THREE.Vector3(point1.x, 20, point1.z ),new THREE.Vector3( point1.x+1, 20, point1.z+1 ));
+ object = new THREE.Line( geometry, material );
+ for(let i=0;i 0 ) {
+
+ var object = intersects[ 0 ].object;
+
+ _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
+
+ if ( _hovered !== object ) {
+
+ scope.dispatchEvent( { type: 'hoveron', object: object } );
+
+ _domElement.style.cursor = 'pointer';
+ _hovered = object;
+
+ }
+
+ } else {
+
+ if ( _hovered !== null ) {
+
+ scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
+
+ _domElement.style.cursor = 'auto';
+ _hovered = null;
+
+ }
+
+ }
+
+ }
+
+ function onDocumentMouseDown( event ) {
+
+ event.preventDefault();
+
+ _raycaster.setFromCamera( _mouse, _camera );
+
+ var intersects = _raycaster.intersectObjects( _objects );
+
+ if ( intersects.length > 0 ) {
+
+ _selected = intersects[ 0 ].object;
+
+ if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+
+ _inverseMatrix.getInverse( _selected.parent.matrixWorld );
+ _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+
+ }
+
+ _domElement.style.cursor = 'move';
+
+ scope.dispatchEvent( { type: 'dragstart', object: _selected } );
+
+ }
+
+
+ }
+
+ function onDocumentMouseCancel( event ) {
+
+ event.preventDefault();
+
+ if ( _selected ) {
+
+ scope.dispatchEvent( { type: 'dragend', object: _selected } );
+
+ _selected = null;
+
+ }
+
+ _domElement.style.cursor = _hovered ? 'pointer' : 'auto';
+
+ }
+
+ function onDocumentTouchMove( event ) {
+
+ event.preventDefault();
+ event = event.changedTouches[ 0 ];
+
+ var rect = _domElement.getBoundingClientRect();
+
+ _mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
+ _mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+
+ _raycaster.setFromCamera( _mouse, _camera );
+
+ if ( _selected && scope.enabled ) {
+
+ if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+
+ _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
+
+ }
+
+ scope.dispatchEvent( { type: 'drag', object: _selected } );
+
+ return;
+
+ }
+
+ }
+
+ function onDocumentTouchStart( event ) {
+
+ event.preventDefault();
+ event = event.changedTouches[ 0 ];
+
+ var rect = _domElement.getBoundingClientRect();
+
+ _mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
+ _mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+
+ _raycaster.setFromCamera( _mouse, _camera );
+
+ var intersects = _raycaster.intersectObjects( _objects );
+
+ if ( intersects.length > 0 ) {
+
+ _selected = intersects[ 0 ].object;
+
+ _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+
+ if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+
+ _inverseMatrix.getInverse( _selected.parent.matrixWorld );
+ _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+
+ }
+
+ _domElement.style.cursor = 'move';
+
+ scope.dispatchEvent( { type: 'dragstart', object: _selected } );
+
+ }
+
+
+ }
+
+ function onDocumentTouchEnd( event ) {
+
+ event.preventDefault();
+
+ if ( _selected ) {
+
+ scope.dispatchEvent( { type: 'dragend', object: _selected } );
+
+ _selected = null;
+
+ }
+
+ _domElement.style.cursor = 'auto';
+
+ }
+
+ activate();
+
+ // API
+
+ this.enabled = true;
+
+ this.activate = activate;
+ this.deactivate = deactivate;
+ this.dispose = dispose;
+
+ // Backward compatibility
+
+ this.setObjects = function () {
+
+ console.error( 'THREE.DragControls: setObjects() has been removed.' );
+
+ };
+
+ this.on = function ( type, listener ) {
+
+ console.warn( 'THREE.DragControls: on() has been deprecated. Use addEventListener() instead.' );
+ scope.addEventListener( type, listener );
+
+ };
+
+ this.off = function ( type, listener ) {
+
+ console.warn( 'THREE.DragControls: off() has been deprecated. Use removeEventListener() instead.' );
+ scope.removeEventListener( type, listener );
+
+ };
+
+ this.notify = function ( type ) {
+
+ console.error( 'THREE.DragControls: notify() has been deprecated. Use dispatchEvent() instead.' );
+ scope.dispatchEvent( { type: type } );
+
+ };
+
+};
+
+THREE.DragControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+THREE.DragControls.prototype.constructor = THREE.DragControls;
diff --git a/src/jlmap3d/edit/control/OrbitControlsEdit.js b/src/jlmap3d/edit/control/OrbitControlsEdit.js
new file mode 100644
index 000000000..ca378b794
--- /dev/null
+++ b/src/jlmap3d/edit/control/OrbitControlsEdit.js
@@ -0,0 +1,1076 @@
+/**
+ * @author qiao / https://github.com/qiao
+ * @author mrdoob / http://mrdoob.com
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author erich666 / http://erichaines.com
+ */
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+//
+// Orbit - left mouse / touch: one-finger move
+// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+
+THREE.OrbitControlsedit = function ( object, domElement ) {
+ this.object = object;
+
+ this.data = null;
+
+ this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+ // Set to false to disable this control
+ this.enabled = true;
+
+ // "target" sets the location of focus, where the object orbits around
+ this.target = new THREE.Vector3();
+
+ // How far you can dolly in and out ( PerspectiveCamera only )
+ this.minDistance = 0;
+ this.maxDistance = Infinity;
+
+ // How far you can zoom in and out ( OrthographicCamera only )
+ this.minZoom = 0;
+ this.maxZoom = Infinity;
+
+ // How far you can orbit vertically, upper and lower limits.
+ // Range is 0 to Math.PI radians.
+ this.minPolarAngle = 0; // radians
+ this.maxPolarAngle = Math.PI; // radians
+
+ // How far you can orbit horizontally, upper and lower limits.
+ // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
+ this.minAzimuthAngle = - Infinity; // radians
+ this.maxAzimuthAngle = Infinity; // radians
+
+ // Set to true to enable damping (inertia)
+ // If damping is enabled, you must call controls.update() in your animation loop
+ this.enableDamping = false;
+ this.dampingFactor = 0.25;
+
+ // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+ // Set to false to disable zooming
+ this.enableZoom = true;
+ this.zoomSpeed = 1.0;
+
+ // Set to false to disable rotating
+ this.enableRotate = true;
+ this.rotateSpeed = 1.0;
+
+ // Set to false to disable panning
+ this.enablePan = true;
+ this.panSpeed = 1.0;
+ this.screenSpacePanning = false; // if true, pan in screen-space
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+
+ // Set to true to automatically rotate around the target
+ // If auto-rotate is enabled, you must call controls.update() in your animation loop
+ this.autoRotate = false;
+ this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
+
+ // Set to false to disable use of the keys
+ this.enableKeys = true;
+
+ // The four arrow keys
+ this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
+
+ // Mouse buttons
+ this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT };
+
+ // for reset
+ this.target0 = this.target.clone();
+ this.position0 = this.object.position.clone();
+ this.zoom0 = this.object.zoom;
+
+ //
+ // public methods
+ //
+
+ this.getPolarAngle = function () {
+
+ return spherical.phi;
+
+ };
+
+ this.getAzimuthalAngle = function () {
+
+ return spherical.theta;
+
+ };
+
+ this.saveState = function () {
+
+ scope.target0.copy( scope.target );
+ scope.position0.copy( scope.object.position );
+ scope.zoom0 = scope.object.zoom;
+
+ };
+
+ this.reset = function () {
+
+ scope.target.copy( scope.target0 );
+ scope.object.position.copy( scope.position0 );
+ scope.object.zoom = scope.zoom0;
+
+ scope.object.updateProjectionMatrix();
+ scope.dispatchEvent( changeEvent );
+
+ scope.update();
+
+ state = STATE.NONE;
+
+ };
+
+ this.upmodeldirect = function(data){
+ scope.data = data;
+ }
+
+ // this method is exposed, but perhaps it would be better if we can make it private...
+ this.update = function () {
+
+
+ var offset = new THREE.Vector3();
+
+ // so camera.up is the orbit axis
+ var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+ var quatInverse = quat.clone().inverse();
+
+ var lastPosition = new THREE.Vector3();
+ var lastQuaternion = new THREE.Quaternion();
+
+ return function update() {
+
+ var position = scope.object.position;
+
+ offset.copy( position ).sub( scope.target );
+
+ // rotate offset to "y-axis-is-up" space
+ offset.applyQuaternion( quat );
+
+ // angle from z-axis around y-axis
+ spherical.setFromVector3( offset );
+
+ if ( scope.autoRotate && state === STATE.NONE ) {
+ rotateLeft( getAutoRotationAngle() );
+
+ }
+
+ spherical.theta += sphericalDelta.theta;
+ spherical.phi += sphericalDelta.phi;
+
+ // restrict theta to be between desired limits
+ spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
+
+ // restrict phi to be between desired limits
+ spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+
+ spherical.makeSafe();
+
+
+ spherical.radius *= scale;
+
+ // restrict radius to be between desired limits
+ spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+
+ // move target to panned location
+ scope.target.add( panOffset );
+
+ offset.setFromSpherical( spherical );
+
+ // rotate offset back to "camera-up-vector-is-up" space
+ offset.applyQuaternion( quatInverse );
+
+ position.copy( scope.target ).add( offset );
+
+ scope.object.lookAt( scope.target );
+
+ if ( scope.enableDamping === true ) {
+ sphericalDelta.theta *= ( 1 - scope.dampingFactor );
+ sphericalDelta.phi *= ( 1 - scope.dampingFactor );
+
+ panOffset.multiplyScalar( 1 - scope.dampingFactor );
+
+ } else {
+ sphericalDelta.set( 0, 0, 0 );
+
+ panOffset.set( 0, 0, 0 );
+
+ }
+
+ scale = 1;
+
+ // update condition is:
+ // min(camera displacement, camera rotation in radians)^2 > EPS
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+ if ( zoomChanged ||
+ lastPosition.distanceToSquared( scope.object.position ) > EPS ||
+ 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+ scope.dispatchEvent( changeEvent );
+
+ lastPosition.copy( scope.object.position );
+ lastQuaternion.copy( scope.object.quaternion );
+ zoomChanged = false;
+
+ return true;
+
+ }
+
+ return false;
+
+ };
+
+ }();
+
+ this.dispose = function () {
+
+ scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
+ scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+ scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
+
+ scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
+ scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
+ scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+ document.removeEventListener( 'mouseup', onMouseUp, false );
+
+ window.removeEventListener( 'keydown', onKeyDown, false );
+
+ //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+ };
+
+ //
+ // internals
+ //
+
+ var scope = this;
+
+ var changeEvent = { type: 'change' };
+ var startEvent = { type: 'start' };
+ var endEvent = { type: 'end' };
+
+ var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 };
+
+ var state = STATE.NONE;
+
+ var EPS = 0.000001;
+
+ // current position in spherical coordinates
+ var spherical = new THREE.Spherical();
+ var sphericalDelta = new THREE.Spherical();
+
+ var scale = 1;
+ var panOffset = new THREE.Vector3();
+ var zoomChanged = false;
+
+ var rotateStart = new THREE.Vector2();
+ var rotateEnd = new THREE.Vector2();
+ var rotateDelta = new THREE.Vector2();
+
+ var panStart = new THREE.Vector2();
+ var panEnd = new THREE.Vector2();
+ var panDelta = new THREE.Vector2();
+
+ var dollyStart = new THREE.Vector2();
+ var dollyEnd = new THREE.Vector2();
+ var dollyDelta = new THREE.Vector2();
+
+ function getAutoRotationAngle() {
+
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+ }
+
+ function getZoomScale() {
+
+ return Math.pow( 0.95, scope.zoomSpeed );
+
+ }
+
+ function rotateLeft( angle ) {
+
+ sphericalDelta.theta -= angle;
+
+ }
+
+ function rotateUp( angle ) {
+
+ sphericalDelta.phi -= angle;
+
+ }
+
+ var panLeft = function () {
+
+ var v = new THREE.Vector3();
+
+ return function panLeft( distance, objectMatrix ) {
+
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+ v.multiplyScalar( - distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ var panUp = function () {
+
+ var v = new THREE.Vector3();
+
+ return function panUp( distance, objectMatrix ) {
+
+ if ( scope.screenSpacePanning === true ) {
+
+ v.setFromMatrixColumn( objectMatrix, 1 );
+
+ } else {
+
+ v.setFromMatrixColumn( objectMatrix, 0 );
+ v.crossVectors( scope.object.up, v );
+
+ }
+
+ v.multiplyScalar( distance );
+
+ panOffset.add( v );
+
+ };
+
+ }();
+
+ // deltaX and deltaY are in pixels; right and down are positive
+ var pan = function () {
+
+ var offset = new THREE.Vector3();
+
+ return function pan( deltaX, deltaY ) {
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ // perspective
+ var position = scope.object.position;
+ offset.copy( position ).sub( scope.target );
+ var targetDistance = offset.length();
+
+ // half of the fov is center to top of screen
+ targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
+
+ // we use only clientHeight here so aspect ratio does not distort speed
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ // orthographic
+ panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+ panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+
+ } else {
+
+ // camera neither orthographic nor perspective
+ console.warn( 'WARNING: OrbitControlsedit.js encountered an unknown camera type - pan disabled.' );
+ scope.enablePan = false;
+
+ }
+
+ };
+
+ }();
+
+ function dollyIn( dollyScale ) {
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale /= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControlsedit.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ function dollyOut( dollyScale ) {
+
+ if ( scope.object.isPerspectiveCamera ) {
+
+ scale *= dollyScale;
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ } else {
+
+ console.warn( 'WARNING: OrbitControlsedit.js encountered an unknown camera type - dolly/zoom disabled.' );
+ scope.enableZoom = false;
+
+ }
+
+ }
+
+ //
+ // event callbacks - update the object state
+ //
+
+ function handleMouseDownRotate( event ) {
+
+ //console.log( 'handleMouseDownRotate' );
+
+ rotateStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownDolly( event ) {
+
+ //console.log( 'handleMouseDownDolly' );
+
+ dollyStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseDownPan( event ) {
+
+ //console.log( 'handleMouseDownPan' );
+
+ panStart.set( event.clientX, event.clientY );
+
+ }
+
+ function handleMouseMoveRotate( event ) {
+
+ //console.log( 'handleMouseMoveRotate' );
+
+ rotateEnd.set( event.clientX, event.clientY );
+
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMoveDolly( event ) {
+
+ //console.log( 'handleMouseMoveDolly' );
+
+ dollyEnd.set( event.clientX, event.clientY );
+
+ dollyDelta.subVectors( dollyEnd, dollyStart );
+
+ if ( dollyDelta.y > 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ } else if ( dollyDelta.y < 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ }
+
+ dollyStart.copy( dollyEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseMovePan( event ) {
+
+ //console.log( 'handleMouseMovePan' );
+
+ panEnd.set( event.clientX, event.clientY );
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ scope.update();
+
+ }
+
+ function handleMouseUp( event ) {
+
+ // console.log( 'handleMouseUp' );
+
+ }
+
+ function handleMouseWheel( event ) {
+
+ // console.log( 'handleMouseWheel' );
+
+ if ( event.deltaY < 0 ) {
+
+ dollyOut( getZoomScale() );
+
+ } else if ( event.deltaY > 0 ) {
+
+ dollyIn( getZoomScale() );
+
+ }
+
+ scope.update();
+
+ }
+
+ function handleKeyDown( event ) {
+
+ // console.log( 'handleKeyDown' );
+
+ var needsUpdate = false;
+
+ switch ( event.keyCode ) {
+
+ case scope.keys.UP:
+ pan( 0, scope.keyPanSpeed );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.BOTTOM:
+ pan( 0, - scope.keyPanSpeed );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.LEFT:
+ pan( scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
+
+ case scope.keys.RIGHT:
+ pan( - scope.keyPanSpeed, 0 );
+ needsUpdate = true;
+ break;
+
+ }
+
+ if ( needsUpdate ) {
+
+ // prevent the browser from scrolling on cursor keys
+ event.preventDefault();
+
+ scope.update();
+
+ }
+
+
+ }
+
+ function handleTouchStartRotate( event ) {
+
+ //console.log( 'handleTouchStartRotate' );
+
+ rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ }
+
+ function handleTouchStartDollyPan( event ) {
+
+ //console.log( 'handleTouchStartDollyPan' );
+
+ if ( scope.enableZoom ) {
+
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+ var distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyStart.set( 0, distance );
+
+ }
+
+ if ( scope.enablePan ) {
+
+ var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+ var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+
+ panStart.set( x, y );
+
+ }
+
+ }
+
+ function handleTouchMoveRotate( event ) {
+
+ //console.log( 'handleTouchMoveRotate' );
+
+ rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+
+ var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+
+ rotateStart.copy( rotateEnd );
+
+ scope.update();
+
+ }
+
+ function handleTouchMoveDollyPan( event ) {
+
+ //console.log( 'handleTouchMoveDollyPan' );
+
+ if ( scope.enableZoom ) {
+
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+ var distance = Math.sqrt( dx * dx + dy * dy );
+
+ dollyEnd.set( 0, distance );
+
+ dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
+
+ dollyIn( dollyDelta.y );
+
+ dollyStart.copy( dollyEnd );
+
+ }
+
+ if ( scope.enablePan ) {
+
+ var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+ var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+
+ panEnd.set( x, y );
+
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+
+ pan( panDelta.x, panDelta.y );
+
+ panStart.copy( panEnd );
+
+ }
+
+ scope.update();
+
+ }
+
+ function handleTouchEnd( event ) {
+
+ //console.log( 'handleTouchEnd' );
+
+ }
+
+ //
+ // event handlers - FSM: listen for events and reset state
+ //
+
+ function onMouseDown( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ // Prevent the browser from scrolling.
+
+ event.preventDefault();
+
+ // Manually set the focus since calling preventDefault above
+ // prevents the browser from setting it automatically.
+
+ scope.domElement.focus ? scope.domElement.focus() : window.focus();
+
+ switch ( event.button ) {
+
+ case scope.mouseButtons.RIGHT:
+
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ } else {
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseDownRotate( event );
+
+ state = STATE.ROTATE;
+
+ }
+
+ break;
+
+ case scope.mouseButtons.MIDDLE:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseDownDolly( event );
+
+ state = STATE.DOLLY;
+
+ break;
+
+ case scope.mouseButtons.LEFT:
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseDownPan( event );
+
+ state = STATE.PAN;
+
+ break;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ document.addEventListener( 'mousemove', onMouseMove, false );
+ document.addEventListener( 'mouseup', onMouseUp, false );
+
+ scope.dispatchEvent( startEvent );
+
+ }
+
+ }
+
+ function onMouseMove( event ) {
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+ switch ( state ) {
+
+ case STATE.ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleMouseMoveRotate( event );
+
+ break;
+
+ case STATE.DOLLY:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseMoveDolly( event );
+
+ break;
+
+ case STATE.PAN:
+
+ if ( scope.enablePan === false ) return;
+
+ handleMouseMovePan( event );
+
+ break;
+
+ }
+
+ }
+
+ function onMouseUp( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ handleMouseUp( event );
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+ document.removeEventListener( 'mouseup', onMouseUp, false );
+
+ scope.dispatchEvent( endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onMouseWheel( event ) {
+
+ if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
+
+ if (event.cancelable) {
+ // 判断默认行为是否已经被禁用
+ if (!event.defaultPrevented) {
+ event.preventDefault();
+ }
+ }
+
+ event.stopPropagation();
+
+ scope.dispatchEvent( startEvent );
+
+ handleMouseWheel( event );
+
+ scope.dispatchEvent( endEvent );
+
+ }
+
+ function onKeyDown( event ) {
+
+ if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
+
+ handleKeyDown( event );
+
+ }
+
+ function onTouchStart( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ switch ( event.touches.length ) {
+
+ case 1: // one-fingered touch: rotate
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchStartRotate( event );
+
+ state = STATE.TOUCH_ROTATE;
+
+ break;
+
+ case 2: // two-fingered touch: dolly-pan
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+ handleTouchStartDollyPan( event );
+
+ state = STATE.TOUCH_DOLLY_PAN;
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ if ( state !== STATE.NONE ) {
+
+ scope.dispatchEvent( startEvent );
+
+ }
+
+ }
+
+ function onTouchMove( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ switch ( event.touches.length ) {
+
+ case 1: // one-fingered touch: rotate
+
+ if ( scope.enableRotate === false ) return;
+ if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?
+
+ handleTouchMoveRotate( event );
+
+ break;
+
+ case 2: // two-fingered touch: dolly-pan
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+ if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed?
+
+ handleTouchMoveDollyPan( event );
+
+ break;
+
+ default:
+
+ state = STATE.NONE;
+
+ }
+
+ }
+
+ function onTouchEnd( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ handleTouchEnd( event );
+
+ scope.dispatchEvent( endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ function onContextMenu( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ event.preventDefault();
+
+ }
+
+ //
+
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
+
+ scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
+
+ scope.domElement.addEventListener( 'touchstart', onTouchStart, { passive: false } );
+ scope.domElement.addEventListener( 'touchend', onTouchEnd, { passive: false } );
+ scope.domElement.addEventListener( 'touchmove', onTouchMove, { passive: false } );
+
+ window.addEventListener( 'keydown', onKeyDown, false );
+
+ // force an update at start
+
+ this.update();
+
+};
+
+THREE.OrbitControlsedit.prototype = Object.create( THREE.EventDispatcher.prototype );
+THREE.OrbitControlsedit.prototype.constructor = THREE.OrbitControlsedit;
+
+Object.defineProperties( THREE.OrbitControlsedit.prototype, {
+
+ center: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControlsedit: .center has been renamed to .target' );
+ return this.target;
+
+ }
+
+ },
+
+ // backward compatibility
+
+ noZoom: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControlsedit: .noZoom has been deprecated. Use .enableZoom instead.' );
+ return ! this.enableZoom;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControlsedit: .noZoom has been deprecated. Use .enableZoom instead.' );
+ this.enableZoom = ! value;
+
+ }
+
+ },
+
+ noRotate: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControlsedit: .noRotate has been deprecated. Use .enableRotate instead.' );
+ return ! this.enableRotate;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControlsedit: .noRotate has been deprecated. Use .enableRotate instead.' );
+ this.enableRotate = ! value;
+
+ }
+
+ },
+
+ noPan: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControlsedit: .noPan has been deprecated. Use .enablePan instead.' );
+ return ! this.enablePan;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControlsedit: .noPan has been deprecated. Use .enablePan instead.' );
+ this.enablePan = ! value;
+
+ }
+
+ },
+
+ noKeys: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControlsedit: .noKeys has been deprecated. Use .enableKeys instead.' );
+ return ! this.enableKeys;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControlsedit: .noKeys has been deprecated. Use .enableKeys instead.' );
+ this.enableKeys = ! value;
+
+ }
+
+ },
+
+ staticMoving: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControlsedit: .staticMoving has been deprecated. Use .enableDamping instead.' );
+ return ! this.enableDamping;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControlsedit: .staticMoving has been deprecated. Use .enableDamping instead.' );
+ this.enableDamping = ! value;
+
+ }
+
+ },
+
+ dynamicDampingFactor: {
+
+ get: function () {
+
+ console.warn( 'THREE.OrbitControlsedit: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+ return this.dampingFactor;
+
+ },
+
+ set: function ( value ) {
+
+ console.warn( 'THREE.OrbitControlsedit: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+ this.dampingFactor = value;
+
+ }
+
+ }
+
+} );
diff --git a/src/jlmap3d/edit/control/TransformControls.js b/src/jlmap3d/edit/control/TransformControls.js
new file mode 100644
index 000000000..2770d3886
--- /dev/null
+++ b/src/jlmap3d/edit/control/TransformControls.js
@@ -0,0 +1,1460 @@
+/**
+ * @author arodic / https://github.com/arodic
+ */
+
+THREE.TransformControls = function ( camera, domElement ) {
+
+ THREE.Object3D.call( this );
+
+ domElement = ( domElement !== undefined ) ? domElement : document;
+
+ this.visible = false;
+
+ var _gizmo = new THREE.TransformControlsGizmo();
+ this.add( _gizmo );
+
+ var _plane = new THREE.TransformControlsPlane();
+ this.add( _plane );
+
+ var scope = this;
+
+ // Define properties with getters/setter
+ // Setting the defined property will automatically trigger change event
+ // Defined properties are passed down to gizmo and plane
+
+ defineProperty( "camera", camera );
+ defineProperty( "object", undefined );
+ defineProperty( "enabled", true );
+ defineProperty( "axis", null );
+ defineProperty( "mode", "translate" );
+ defineProperty( "translationSnap", null );
+ defineProperty( "rotationSnap", null );
+ defineProperty( "space", "world" );
+ defineProperty( "size", 1 );
+ defineProperty( "dragging", false );
+ defineProperty( "showX", true );
+ defineProperty( "showY", true );
+ defineProperty( "showZ", true );
+
+ var changeEvent = { type: "change" };
+ var mouseDownEvent = { type: "mouseDown" };
+ var mouseUpEvent = { type: "mouseUp", mode: scope.mode };
+ var objectChangeEvent = { type: "objectChange" };
+
+ // Reusable utility variables
+
+ var ray = new THREE.Raycaster();
+
+ var _tempVector = new THREE.Vector3();
+ var _tempVector2 = new THREE.Vector3();
+ var _tempQuaternion = new THREE.Quaternion();
+ var _unit = {
+ X: new THREE.Vector3( 1, 0, 0 ),
+ Y: new THREE.Vector3( 0, 1, 0 ),
+ Z: new THREE.Vector3( 0, 0, 1 )
+ };
+ var _identityQuaternion = new THREE.Quaternion();
+ var _alignVector = new THREE.Vector3();
+
+ var pointStart = new THREE.Vector3();
+ var pointEnd = new THREE.Vector3();
+ var offset = new THREE.Vector3();
+ var rotationAxis = new THREE.Vector3();
+ var startNorm = new THREE.Vector3();
+ var endNorm = new THREE.Vector3();
+ var rotationAngle = 0;
+
+ var cameraPosition = new THREE.Vector3();
+ var cameraQuaternion = new THREE.Quaternion();
+ var cameraScale = new THREE.Vector3();
+
+ var parentPosition = new THREE.Vector3();
+ var parentQuaternion = new THREE.Quaternion();
+ var parentQuaternionInv = new THREE.Quaternion();
+ var parentScale = new THREE.Vector3();
+
+ var worldPositionStart = new THREE.Vector3();
+ var worldQuaternionStart = new THREE.Quaternion();
+ var worldScaleStart = new THREE.Vector3();
+
+ var worldPosition = new THREE.Vector3();
+ var worldQuaternion = new THREE.Quaternion();
+ var worldQuaternionInv = new THREE.Quaternion();
+ var worldScale = new THREE.Vector3();
+
+ var eye = new THREE.Vector3();
+
+ var positionStart = new THREE.Vector3();
+ var quaternionStart = new THREE.Quaternion();
+ var scaleStart = new THREE.Vector3();
+
+ // TODO: remove properties unused in plane and gizmo
+
+ defineProperty( "worldPosition", worldPosition );
+ defineProperty( "worldPositionStart", worldPositionStart );
+ defineProperty( "worldQuaternion", worldQuaternion );
+ defineProperty( "worldQuaternionStart", worldQuaternionStart );
+ defineProperty( "cameraPosition", cameraPosition );
+ defineProperty( "cameraQuaternion", cameraQuaternion );
+ defineProperty( "pointStart", pointStart );
+ defineProperty( "pointEnd", pointEnd );
+ defineProperty( "rotationAxis", rotationAxis );
+ defineProperty( "rotationAngle", rotationAngle );
+ defineProperty( "eye", eye );
+
+ {
+
+ domElement.addEventListener( "mousedown", onPointerDown, false );
+ domElement.addEventListener( "touchstart", onPointerDown, false );
+ domElement.addEventListener( "mousemove", onPointerHover, false );
+ domElement.addEventListener( "touchmove", onPointerHover, false );
+ domElement.addEventListener( "touchmove", onPointerMove, false );
+ document.addEventListener( "mouseup", onPointerUp, false );
+ domElement.addEventListener( "touchend", onPointerUp, false );
+ domElement.addEventListener( "touchcancel", onPointerUp, false );
+ domElement.addEventListener( "touchleave", onPointerUp, false );
+
+ }
+
+ this.dispose = function () {
+
+ domElement.removeEventListener( "mousedown", onPointerDown );
+ domElement.removeEventListener( "touchstart", onPointerDown );
+ domElement.removeEventListener( "mousemove", onPointerHover );
+ domElement.removeEventListener( "touchmove", onPointerHover );
+ domElement.removeEventListener( "touchmove", onPointerMove );
+ document.removeEventListener( "mouseup", onPointerUp );
+ domElement.removeEventListener( "touchend", onPointerUp );
+ domElement.removeEventListener( "touchcancel", onPointerUp );
+ domElement.removeEventListener( "touchleave", onPointerUp );
+
+ };
+
+ // Set current object
+ this.attach = function ( object ) {
+
+ this.object = object;
+ this.visible = true;
+
+ };
+
+ // Detatch from object
+ this.detach = function () {
+
+ this.object = undefined;
+ this.visible = false;
+ this.axis = null;
+
+ };
+
+ // Defined getter, setter and store for a property
+ function defineProperty( propName, defaultValue ) {
+
+ var propValue = defaultValue;
+
+ Object.defineProperty( scope, propName, {
+
+ get: function() {
+
+ return propValue !== undefined ? propValue : defaultValue;
+
+ },
+
+ set: function( value ) {
+
+ if ( propValue !== value ) {
+
+ propValue = value;
+ _plane[ propName ] = value;
+ _gizmo[ propName ] = value;
+
+ scope.dispatchEvent( { type: propName + "-changed", value: value } );
+ scope.dispatchEvent( changeEvent );
+
+ }
+
+ }
+
+ });
+
+ scope[ propName ] = defaultValue;
+ _plane[ propName ] = defaultValue;
+ _gizmo[ propName ] = defaultValue;
+
+ }
+
+ // updateMatrixWorld updates key transformation variables
+ this.updateMatrixWorld = function () {
+
+ if ( this.object !== undefined ) {
+
+ this.object.updateMatrixWorld();
+ this.object.parent.matrixWorld.decompose( parentPosition, parentQuaternion, parentScale );
+ this.object.matrixWorld.decompose( worldPosition, worldQuaternion, worldScale );
+
+ parentQuaternionInv.copy( parentQuaternion ).inverse();
+ worldQuaternionInv.copy( worldQuaternion ).inverse();
+
+ }
+
+ this.camera.updateMatrixWorld();
+ this.camera.matrixWorld.decompose( cameraPosition, cameraQuaternion, cameraScale );
+
+ if ( this.camera instanceof THREE.PerspectiveCamera ) {
+
+ eye.copy( cameraPosition ).sub( worldPosition ).normalize();
+
+ } else if ( this.camera instanceof THREE.OrthographicCamera ) {
+
+ eye.copy( cameraPosition ).normalize();
+
+ }
+
+ THREE.Object3D.prototype.updateMatrixWorld.call( this );
+
+ };
+
+ this.pointerHover = function( pointer ) {
+
+ if ( this.object === undefined || this.dragging === true || ( pointer.button !== undefined && pointer.button !== 0 ) ) return;
+
+ ray.setFromCamera( pointer, this.camera );
+
+ var intersect = ray.intersectObjects( _gizmo.picker[ this.mode ].children, true )[ 0 ] || false;
+
+ if ( intersect ) {
+
+ this.axis = intersect.object.name;
+
+ } else {
+
+ this.axis = null;
+
+ }
+
+ };
+
+ this.pointerDown = function( pointer ) {
+
+ if ( this.object === undefined || this.dragging === true || ( pointer.button !== undefined && pointer.button !== 0 ) ) return;
+
+ if ( ( pointer.button === 0 || pointer.button === undefined ) && this.axis !== null ) {
+
+ ray.setFromCamera( pointer, this.camera );
+
+ var planeIntersect = ray.intersectObjects( [ _plane ], true )[ 0 ] || false;
+
+ if ( planeIntersect ) {
+
+ var space = this.space;
+
+ if ( this.mode === 'scale') {
+
+ space = 'local';
+
+ } else if ( this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ' ) {
+
+ space = 'world';
+
+ }
+
+ if ( space === 'local' && this.mode === 'rotate' ) {
+
+ var snap = this.rotationSnap;
+
+ if ( this.axis === 'X' && snap ) this.object.rotation.x = Math.round( this.object.rotation.x / snap ) * snap;
+ if ( this.axis === 'Y' && snap ) this.object.rotation.y = Math.round( this.object.rotation.y / snap ) * snap;
+ if ( this.axis === 'Z' && snap ) this.object.rotation.z = Math.round( this.object.rotation.z / snap ) * snap;
+
+ }
+
+ this.object.updateMatrixWorld();
+ this.object.parent.updateMatrixWorld();
+
+ positionStart.copy( this.object.position );
+ quaternionStart.copy( this.object.quaternion );
+ scaleStart.copy( this.object.scale );
+
+ this.object.matrixWorld.decompose( worldPositionStart, worldQuaternionStart, worldScaleStart );
+
+ pointStart.copy( planeIntersect.point ).sub( worldPositionStart );
+
+ }
+
+ this.dragging = true;
+ mouseDownEvent.mode = this.mode;
+ this.dispatchEvent( mouseDownEvent );
+
+ }
+
+ };
+
+ this.pointerMove = function( pointer ) {
+
+ var axis = this.axis;
+ var mode = this.mode;
+ var object = this.object;
+ var space = this.space;
+
+ if ( mode === 'scale') {
+
+ space = 'local';
+
+ } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) {
+
+ space = 'world';
+
+ }
+
+ if ( object === undefined || axis === null || this.dragging === false || ( pointer.button !== undefined && pointer.button !== 0 ) ) return;
+
+ ray.setFromCamera( pointer, this.camera );
+
+ var planeIntersect = ray.intersectObjects( [ _plane ], true )[ 0 ] || false;
+
+ if ( planeIntersect === false ) return;
+
+ pointEnd.copy( planeIntersect.point ).sub( worldPositionStart );
+
+ if ( mode === 'translate' ) {
+
+ // Apply translate
+
+ offset.copy( pointEnd ).sub( pointStart );
+
+ if ( space === 'local' && axis !== 'XYZ' ) {
+ offset.applyQuaternion( worldQuaternionInv );
+ }
+
+ if ( axis.indexOf( 'X' ) === -1 ) offset.x = 0;
+ if ( axis.indexOf( 'Y' ) === -1 ) offset.y = 0;
+ if ( axis.indexOf( 'Z' ) === -1 ) offset.z = 0;
+
+ if ( space === 'local' && axis !== 'XYZ') {
+ offset.applyQuaternion( quaternionStart ).divide( parentScale );
+ } else {
+ offset.applyQuaternion( parentQuaternionInv ).divide( parentScale );
+ }
+
+ object.position.copy( offset ).add( positionStart );
+
+ // Apply translation snap
+
+ if ( this.translationSnap ) {
+
+ if ( space === 'local' ) {
+
+ object.position.applyQuaternion(_tempQuaternion.copy( quaternionStart ).inverse() );
+
+ if ( axis.search( 'X' ) !== -1 ) {
+ object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
+ }
+
+ if ( axis.search( 'Y' ) !== -1 ) {
+ object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
+ }
+
+ if ( axis.search( 'Z' ) !== -1 ) {
+ object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
+ }
+
+ object.position.applyQuaternion( quaternionStart );
+
+ }
+
+ if ( space === 'world' ) {
+
+ if ( object.parent ) {
+ object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
+ }
+
+ if ( axis.search( 'X' ) !== -1 ) {
+ object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
+ }
+
+ if ( axis.search( 'Y' ) !== -1 ) {
+ object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
+ }
+
+ if ( axis.search( 'Z' ) !== -1 ) {
+ object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
+ }
+
+ if ( object.parent ) {
+ object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
+ }
+
+ }
+
+ }
+
+ } else if ( mode === 'scale' ) {
+
+ if ( axis.search( 'XYZ' ) !== -1 ) {
+
+ var d = pointEnd.length() / pointStart.length();
+
+ if ( pointEnd.dot( pointStart ) < 0 ) d *= -1;
+
+ _tempVector.set( d, d, d );
+
+ } else {
+
+ _tempVector.copy( pointEnd ).divide( pointStart );
+
+ if ( axis.search( 'X' ) === -1 ) {
+ _tempVector.x = 1;
+ }
+ if ( axis.search( 'Y' ) === -1 ) {
+ _tempVector.y = 1;
+ }
+ if ( axis.search( 'Z' ) === -1 ) {
+ _tempVector.z = 1;
+ }
+
+ }
+
+ // Apply scale
+
+ object.scale.copy( scaleStart ).multiply( _tempVector );
+
+ } else if ( mode === 'rotate' ) {
+
+ offset.copy( pointEnd ).sub( pointStart );
+
+ var ROTATION_SPEED = 20 / worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
+
+ if ( axis === 'E' ) {
+
+ rotationAxis.copy( eye );
+ rotationAngle = pointEnd.angleTo( pointStart );
+
+ startNorm.copy( pointStart ).normalize();
+ endNorm.copy( pointEnd ).normalize();
+
+ rotationAngle *= ( endNorm.cross( startNorm ).dot( eye ) < 0 ? 1 : -1);
+
+ } else if ( axis === 'XYZE' ) {
+
+ rotationAxis.copy( offset ).cross( eye ).normalize( );
+ rotationAngle = offset.dot( _tempVector.copy( rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
+
+ } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) {
+
+ rotationAxis.copy( _unit[ axis ] );
+
+ _tempVector.copy( _unit[ axis ] );
+
+ if ( space === 'local' ) {
+ _tempVector.applyQuaternion( worldQuaternion );
+ }
+
+ rotationAngle = offset.dot( _tempVector.cross( eye ).normalize() ) * ROTATION_SPEED;
+
+ }
+
+ // Apply rotation snap
+
+ if ( this.rotationSnap ) rotationAngle = Math.round( rotationAngle / this.rotationSnap ) * this.rotationSnap;
+
+ this.rotationAngle = rotationAngle;
+
+ // Apply rotate
+ if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) {
+
+ object.quaternion.copy( quaternionStart );
+ object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) ).normalize();
+
+ } else {
+
+ rotationAxis.applyQuaternion( parentQuaternionInv );
+ object.quaternion.copy( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) );
+ object.quaternion.multiply( quaternionStart ).normalize();
+
+ }
+
+ }
+
+ this.dispatchEvent( changeEvent );
+ this.dispatchEvent( objectChangeEvent );
+
+ };
+
+ this.pointerUp = function( pointer ) {
+
+ if ( pointer.button !== undefined && pointer.button !== 0 ) return;
+
+ if ( this.dragging && ( this.axis !== null ) ) {
+
+ mouseUpEvent.mode = this.mode;
+ this.dispatchEvent( mouseUpEvent );
+
+ }
+
+ this.dragging = false;
+
+ if ( pointer.button === undefined ) this.axis = null;
+
+ };
+
+ // normalize mouse / touch pointer and remap {x,y} to view space.
+
+ function getPointer( event ) {
+
+ var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
+
+ var rect = domElement.getBoundingClientRect();
+
+ return {
+ x: ( pointer.clientX - rect.left ) / rect.width * 2 - 1,
+ y: - ( pointer.clientY - rect.top ) / rect.height * 2 + 1,
+ button: event.button
+ };
+
+ }
+
+ // mouse / touch event handlers
+
+ function onPointerHover( event ) {
+
+ if ( !scope.enabled ) return;
+
+ scope.pointerHover( getPointer( event ) );
+
+ }
+
+ function onPointerDown( event ) {
+
+ if ( !scope.enabled ) return;
+
+ document.addEventListener( "mousemove", onPointerMove, false );
+
+ scope.pointerHover( getPointer( event ) );
+ scope.pointerDown( getPointer( event ) );
+
+ }
+
+ function onPointerMove( event ) {
+
+ if ( !scope.enabled ) return;
+
+ scope.pointerMove( getPointer( event ) );
+
+ }
+
+ function onPointerUp( event ) {
+
+ if ( !scope.enabled ) return;
+
+ document.removeEventListener( "mousemove", onPointerMove, false );
+
+ scope.pointerUp( getPointer( event ) );
+
+ }
+
+ // TODO: depricate
+
+ this.getMode = function () {
+
+ return scope.mode;
+
+ };
+
+ this.setMode = function ( mode ) {
+
+ scope.mode = mode;
+
+ };
+
+ this.setTranslationSnap = function ( translationSnap ) {
+
+ scope.translationSnap = translationSnap;
+
+ };
+
+ this.setRotationSnap = function ( rotationSnap ) {
+
+ scope.rotationSnap = rotationSnap;
+
+ };
+
+ this.setSize = function ( size ) {
+
+ scope.size = size;
+
+ };
+
+ this.setSpace = function ( space ) {
+
+ scope.space = space;
+
+ };
+
+ this.update = function () {
+
+ console.warn( 'THREE.TransformControls: update function has been depricated.' );
+
+ };
+
+};
+
+THREE.TransformControls.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
+
+ constructor: THREE.TransformControls,
+
+ isTransformControls: true
+
+} );
+
+
+THREE.TransformControlsGizmo = function () {
+
+ 'use strict';
+
+ THREE.Object3D.call( this );
+
+ this.type = 'TransformControlsGizmo';
+
+ // shared materials
+
+ var gizmoMaterial = new THREE.MeshBasicMaterial({
+ depthTest: false,
+ depthWrite: false,
+ transparent: true,
+ side: THREE.DoubleSide,
+ fog: false
+ });
+
+ var gizmoLineMaterial = new THREE.LineBasicMaterial({
+ depthTest: false,
+ depthWrite: false,
+ transparent: true,
+ linewidth: 1,
+ fog: false
+ });
+
+ // Make unique material for each axis/color
+
+ var matInvisible = gizmoMaterial.clone();
+ matInvisible.opacity = 0.15;
+
+ var matHelper = gizmoMaterial.clone();
+ matHelper.opacity = 0.33;
+
+ var matRed = gizmoMaterial.clone();
+ matRed.color.set( 0xff0000 );
+
+ var matGreen = gizmoMaterial.clone();
+ matGreen.color.set( 0x00ff00 );
+
+ var matBlue = gizmoMaterial.clone();
+ matBlue.color.set( 0x0000ff );
+
+ var matWhiteTransperent = gizmoMaterial.clone();
+ matWhiteTransperent.opacity = 0.25;
+
+ var matYellowTransparent = matWhiteTransperent.clone();
+ matYellowTransparent.color.set( 0xffff00 );
+
+ var matCyanTransparent = matWhiteTransperent.clone();
+ matCyanTransparent.color.set( 0x00ffff );
+
+ var matMagentaTransparent = matWhiteTransperent.clone();
+ matMagentaTransparent.color.set( 0xff00ff );
+
+ var matYellow = gizmoMaterial.clone();
+ matYellow.color.set( 0xffff00 );
+
+ var matLineRed = gizmoLineMaterial.clone();
+ matLineRed.color.set( 0xff0000 );
+
+ var matLineGreen = gizmoLineMaterial.clone();
+ matLineGreen.color.set( 0x00ff00 );
+
+ var matLineBlue = gizmoLineMaterial.clone();
+ matLineBlue.color.set( 0x0000ff );
+
+ var matLineCyan = gizmoLineMaterial.clone();
+ matLineCyan.color.set( 0x00ffff );
+
+ var matLineMagenta = gizmoLineMaterial.clone();
+ matLineMagenta.color.set( 0xff00ff );
+
+ var matLineYellow = gizmoLineMaterial.clone();
+ matLineYellow.color.set( 0xffff00 );
+
+ var matLineGray = gizmoLineMaterial.clone();
+ matLineGray.color.set( 0x787878);
+
+ var matLineYellowTransparent = matLineYellow.clone();
+ matLineYellowTransparent.opacity = 0.25;
+
+ // reusable geometry
+
+ var arrowGeometry = new THREE.CylinderBufferGeometry( 0, 0.05, 0.2, 12, 1, false);
+
+ var scaleHandleGeometry = new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125);
+
+ var lineGeometry = new THREE.BufferGeometry( );
+ lineGeometry.addAttribute('position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) );
+
+ var CircleGeometry = function( radius, arc ) {
+
+ var geometry = new THREE.BufferGeometry( );
+ var vertices = [];
+
+ for ( var i = 0; i <= 64 * arc; ++i ) {
+
+ vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius );
+
+ }
+
+ geometry.addAttribute('position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+
+ return geometry;
+
+ };
+
+ // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position
+
+ var TranslateHelperGeometry = function( radius, arc ) {
+
+ var geometry = new THREE.BufferGeometry()
+
+ geometry.addAttribute('position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) );
+
+ return geometry;
+
+ };
+
+ // Gizmo definitions - custom hierarchy definitions for setupGizmo() function
+
+ var gizmoTranslate = {
+ X: [
+ [ new THREE.Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, -Math.PI / 2 ], null, 'fwd' ],
+ [ new THREE.Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, Math.PI / 2 ], null, 'bwd' ],
+ [ new THREE.Line( lineGeometry, matLineRed ) ]
+ ],
+ Y: [
+ [ new THREE.Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], null, null, 'fwd' ],
+ [ new THREE.Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], [ Math.PI, 0, 0 ], null, 'bwd' ],
+ [ new THREE.Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ] ]
+ ],
+ Z: [
+ [ new THREE.Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ Math.PI / 2, 0, 0 ], null, 'fwd' ],
+ [ new THREE.Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ -Math.PI / 2, 0, 0 ], null, 'bwd' ],
+ [ new THREE.Line( lineGeometry, matLineBlue ), null, [ 0, -Math.PI / 2, 0 ] ]
+ ],
+ XYZ: [
+ [ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.1, 0 ), matWhiteTransperent ), [ 0, 0, 0 ], [ 0, 0, 0 ] ]
+ ],
+ XY: [
+ [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.295, 0.295 ), matYellowTransparent ), [ 0.15, 0.15, 0 ] ],
+ [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.18, 0.3, 0 ], null, [ 0.125, 1, 1 ] ],
+ [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.3, 0.18, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ] ]
+ ],
+ YZ: [
+ [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.295, 0.295 ), matCyanTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ] ],
+ [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.18, 0.3 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ] ],
+ [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.3, 0.18 ], [ 0, -Math.PI / 2, 0 ], [ 0.125, 1, 1 ] ]
+ ],
+ XZ: [
+ [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.295, 0.295 ), matMagentaTransparent ), [ 0.15, 0, 0.15 ], [ -Math.PI / 2, 0, 0 ] ],
+ [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.18, 0, 0.3 ], null, [ 0.125, 1, 1 ] ],
+ [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.3, 0, 0.18 ], [ 0, -Math.PI / 2, 0 ], [ 0.125, 1, 1 ] ]
+ ]
+ };
+
+ var pickerTranslate = {
+ X: [
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
+ ],
+ Y: [
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0.6, 0 ] ]
+ ],
+ Z: [
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ]
+ ],
+ XYZ: [
+ [ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.2, 0 ), matInvisible ) ]
+ ],
+ XY: [
+ [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0.2, 0 ] ]
+ ],
+ YZ: [
+ [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ] ]
+ ],
+ XZ: [
+ [ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0, 0.2 ], [ -Math.PI / 2, 0, 0 ] ]
+ ]
+ };
+
+ var helperTranslate = {
+ START: [
+ [ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
+ ],
+ END: [
+ [ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
+ ],
+ DELTA: [
+ [ new THREE.Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]
+ ],
+ X: [
+ [ new THREE.Line( lineGeometry, matHelper.clone() ), [ -1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+ ],
+ Y: [
+ [ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, -1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
+ ],
+ Z: [
+ [ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, -1e3 ], [ 0, -Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
+ ]
+ };
+
+ var gizmoRotate = {
+ X: [
+ [ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineRed ) ],
+ [ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.04, 0 ), matRed ), [ 0, 0, 0.99 ], null, [ 1, 3, 1 ] ],
+ ],
+ Y: [
+ [ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineGreen ), null, [ 0, 0, -Math.PI / 2 ] ],
+ [ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.04, 0 ), matGreen ), [ 0, 0, 0.99 ], null, [ 3, 1, 1 ] ],
+ ],
+ Z: [
+ [ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineBlue ), null, [ 0, Math.PI / 2, 0 ] ],
+ [ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.04, 0 ), matBlue ), [ 0.99, 0, 0 ], null, [ 1, 3, 1 ] ],
+ ],
+ E: [
+ [ new THREE.Line( CircleGeometry( 1.25, 1 ), matLineYellowTransparent ), null, [ 0, Math.PI / 2, 0 ] ],
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 1.17, 0, 0 ], [ 0, 0, -Math.PI / 2 ], [ 1, 1, 0.001 ]],
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ -1.17, 0, 0 ], [ 0, 0, Math.PI / 2 ], [ 1, 1, 0.001 ]],
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, -1.17, 0 ], [ Math.PI, 0, 0 ], [ 1, 1, 0.001 ]],
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, 1.17, 0 ], [ 0, 0, 0 ], [ 1, 1, 0.001 ]],
+ ],
+ XYZE: [
+ [ new THREE.Line( CircleGeometry( 1, 1 ), matLineGray ), null, [ 0, Math.PI / 2, 0 ] ]
+ ]
+ };
+
+ var helperRotate = {
+ AXIS: [
+ [ new THREE.Line( lineGeometry, matHelper.clone() ), [ -1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+ ]
+ };
+
+ var pickerRotate = {
+ X: [
+ [ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, -Math.PI / 2, -Math.PI / 2 ] ],
+ ],
+ Y: [
+ [ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ] ],
+ ],
+ Z: [
+ [ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ],
+ ],
+ E: [
+ [ new THREE.Mesh( new THREE.TorusBufferGeometry( 1.25, 0.1, 2, 24 ), matInvisible ) ]
+ ],
+ XYZE: [
+ [ new THREE.Mesh( new THREE.SphereBufferGeometry( 0.7, 10, 8 ), matInvisible ) ]
+ ]
+ };
+
+ var gizmoScale = {
+ X: [
+ [ new THREE.Mesh( scaleHandleGeometry, matRed ), [ 0.8, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ],
+ [ new THREE.Line( lineGeometry, matLineRed ), null, null, [ 0.8, 1, 1 ] ]
+ ],
+ Y: [
+ [ new THREE.Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.8, 0 ] ],
+ [ new THREE.Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ], [ 0.8, 1, 1 ] ]
+ ],
+ Z: [
+ [ new THREE.Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.8 ], [ Math.PI / 2, 0, 0 ] ],
+ [ new THREE.Line( lineGeometry, matLineBlue ), null, [ 0, -Math.PI / 2, 0 ], [ 0.8, 1, 1 ] ]
+ ],
+ XY: [
+ [ new THREE.Mesh( scaleHandleGeometry, matYellowTransparent ), [ 0.85, 0.85, 0 ], null, [ 2, 2, 0.2 ] ],
+ [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.855, 0.98, 0 ], null, [ 0.125, 1, 1 ] ],
+ [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.98, 0.855, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ] ]
+ ],
+ YZ: [
+ [ new THREE.Mesh( scaleHandleGeometry, matCyanTransparent ), [ 0, 0.85, 0.85 ], null, [ 0.2, 2, 2 ] ],
+ [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.855, 0.98 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ] ],
+ [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.98, 0.855 ], [ 0, -Math.PI / 2, 0 ], [ 0.125, 1, 1 ] ]
+ ],
+ XZ: [
+ [ new THREE.Mesh( scaleHandleGeometry, matMagentaTransparent ), [ 0.85, 0, 0.85 ], null, [ 2, 0.2, 2 ] ],
+ [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.855, 0, 0.98 ], null, [ 0.125, 1, 1 ] ],
+ [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.98, 0, 0.855 ], [ 0, -Math.PI / 2, 0 ], [ 0.125, 1, 1 ] ]
+ ],
+ XYZX: [
+ [ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 1.1, 0, 0 ] ],
+ ],
+ XYZY: [
+ [ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 0, 1.1, 0 ] ],
+ ],
+ XYZZ: [
+ [ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 0, 0, 1.1 ] ],
+ ]
+ };
+
+ var pickerScale = {
+ X: [
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
+ ],
+ Y: [
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0.5, 0 ] ]
+ ],
+ Z: [
+ [ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ]
+ ],
+ XY: [
+ [ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0.85, 0 ], null, [ 3, 3, 0.2 ] ],
+ ],
+ YZ: [
+ [ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0, 0.85, 0.85 ], null, [ 0.2, 3, 3 ] ],
+ ],
+ XZ: [
+ [ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0, 0.85 ], null, [ 3, 0.2, 3 ] ],
+ ],
+ XYZX: [
+ [ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 1.1, 0, 0 ] ],
+ ],
+ XYZY: [
+ [ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 1.1, 0 ] ],
+ ],
+ XYZZ: [
+ [ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 1.1 ] ],
+ ]
+ };
+
+ var helperScale = {
+ X: [
+ [ new THREE.Line( lineGeometry, matHelper.clone() ), [ -1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+ ],
+ Y: [
+ [ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, -1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
+ ],
+ Z: [
+ [ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, -1e3 ], [ 0, -Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
+ ]
+ };
+
+ // Creates an Object3D with gizmos described in custom hierarchy definition.
+
+ var setupGizmo = function( gizmoMap ) {
+
+ var gizmo = new THREE.Object3D();
+
+ for ( var name in gizmoMap ) {
+
+ for ( var i = gizmoMap[ name ].length; i --; ) {
+
+ var object = gizmoMap[ name ][ i ][ 0 ].clone();
+ var position = gizmoMap[ name ][ i ][ 1 ];
+ var rotation = gizmoMap[ name ][ i ][ 2 ];
+ var scale = gizmoMap[ name ][ i ][ 3 ];
+ var tag = gizmoMap[ name ][ i ][ 4 ];
+
+ // name and tag properties are essential for picking and updating logic.
+ object.name = name;
+ object.tag = tag;
+
+ if (position) {
+ object.position.set(position[ 0 ], position[ 1 ], position[ 2 ]);
+ }
+ if (rotation) {
+ object.rotation.set(rotation[ 0 ], rotation[ 1 ], rotation[ 2 ]);
+ }
+ if (scale) {
+ object.scale.set(scale[ 0 ], scale[ 1 ], scale[ 2 ]);
+ }
+
+ object.updateMatrix();
+
+ var tempGeometry = object.geometry.clone();
+ tempGeometry.applyMatrix(object.matrix);
+ object.geometry = tempGeometry;
+
+ object.position.set( 0, 0, 0 );
+ object.rotation.set( 0, 0, 0 );
+ object.scale.set(1, 1, 1);
+
+ gizmo.add(object);
+
+ }
+
+ }
+
+ return gizmo;
+
+ };
+
+ // Reusable utility variables
+
+ var tempVector = new THREE.Vector3( 0, 0, 0 );
+ var tempEuler = new THREE.Euler();
+ var alignVector = new THREE.Vector3( 0, 1, 0 );
+ var zeroVector = new THREE.Vector3( 0, 0, 0 );
+ var lookAtMatrix = new THREE.Matrix4();
+ var tempQuaternion = new THREE.Quaternion();
+ var tempQuaternion2 = new THREE.Quaternion();
+ var identityQuaternion = new THREE.Quaternion();
+
+ var unitX = new THREE.Vector3( 1, 0, 0 );
+ var unitY = new THREE.Vector3( 0, 1, 0 );
+ var unitZ = new THREE.Vector3( 0, 0, 1 );
+
+ // Gizmo creation
+
+ this.gizmo = {};
+ this.picker = {};
+ this.helper = {};
+
+ this.add( this.gizmo[ "translate" ] = setupGizmo( gizmoTranslate ) );
+ this.add( this.gizmo[ "rotate" ] = setupGizmo( gizmoRotate ) );
+ this.add( this.gizmo[ "scale" ] = setupGizmo( gizmoScale ) );
+ this.add( this.picker[ "translate" ] = setupGizmo( pickerTranslate ) );
+ this.add( this.picker[ "rotate" ] = setupGizmo( pickerRotate ) );
+ this.add( this.picker[ "scale" ] = setupGizmo( pickerScale ) );
+ this.add( this.helper[ "translate" ] = setupGizmo( helperTranslate ) );
+ this.add( this.helper[ "rotate" ] = setupGizmo( helperRotate ) );
+ this.add( this.helper[ "scale" ] = setupGizmo( helperScale ) );
+
+ // Pickers should be hidden always
+
+ this.picker[ "translate" ].visible = false;
+ this.picker[ "rotate" ].visible = false;
+ this.picker[ "scale" ].visible = false;
+
+ // updateMatrixWorld will update transformations and appearance of individual handles
+
+ this.updateMatrixWorld = function () {
+
+ var space = this.space;
+
+ if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
+
+ var quaternion = space === "local" ? this.worldQuaternion : identityQuaternion;
+
+ // Show only gizmos for current transform mode
+
+ this.gizmo[ "translate" ].visible = this.mode === "translate";
+ this.gizmo[ "rotate" ].visible = this.mode === "rotate";
+ this.gizmo[ "scale" ].visible = this.mode === "scale";
+
+ this.helper[ "translate" ].visible = this.mode === "translate";
+ this.helper[ "rotate" ].visible = this.mode === "rotate";
+ this.helper[ "scale" ].visible = this.mode === "scale";
+
+
+ var handles = [];
+ handles = handles.concat( this.picker[ this.mode ].children );
+ handles = handles.concat( this.gizmo[ this.mode ].children );
+ handles = handles.concat( this.helper[ this.mode ].children );
+
+ for ( var i = 0; i < handles.length; i++ ) {
+
+ var handle = handles[i];
+
+ // hide aligned to camera
+
+ handle.visible = true;
+ handle.rotation.set( 0, 0, 0 );
+ handle.position.copy( this.worldPosition );
+
+ var eyeDistance = this.worldPosition.distanceTo( this.cameraPosition);
+ handle.scale.set( 1, 1, 1 ).multiplyScalar( eyeDistance * this.size / 7 );
+
+ // TODO: simplify helpers and consider decoupling from gizmo
+
+ if ( handle.tag === 'helper' ) {
+
+ handle.visible = false;
+
+ if ( handle.name === 'AXIS' ) {
+
+ handle.position.copy( this.worldPositionStart );
+ handle.visible = !!this.axis;
+
+ if ( this.axis === 'X' ) {
+
+ tempQuaternion.setFromEuler( tempEuler.set( 0, 0, 0 ) );
+ handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
+
+ if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+ handle.visible = false;
+ }
+
+ }
+
+ if ( this.axis === 'Y' ) {
+
+ tempQuaternion.setFromEuler( tempEuler.set( 0, 0, Math.PI / 2 ) );
+ handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
+
+ if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+ handle.visible = false;
+ }
+
+ }
+
+ if ( this.axis === 'Z' ) {
+
+ tempQuaternion.setFromEuler( tempEuler.set( 0, Math.PI / 2, 0 ) );
+ handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
+
+ if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+ handle.visible = false;
+ }
+
+ }
+
+ if ( this.axis === 'XYZE' ) {
+
+ tempQuaternion.setFromEuler( tempEuler.set( 0, Math.PI / 2, 0 ) );
+ alignVector.copy( this.rotationAxis );
+ handle.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( zeroVector, alignVector, unitY ) );
+ handle.quaternion.multiply( tempQuaternion );
+ handle.visible = this.dragging;
+
+ }
+
+ if ( this.axis === 'E' ) {
+
+ handle.visible = false;
+
+ }
+
+
+ } else if ( handle.name === 'START' ) {
+
+ handle.position.copy( this.worldPositionStart );
+ handle.visible = this.dragging;
+
+ } else if ( handle.name === 'END' ) {
+
+ handle.position.copy( this.worldPosition );
+ handle.visible = this.dragging;
+
+ } else if ( handle.name === 'DELTA' ) {
+
+ handle.position.copy( this.worldPositionStart );
+ handle.quaternion.copy( this.worldQuaternionStart );
+ tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( -1 );
+ tempVector.applyQuaternion( this.worldQuaternionStart.clone().inverse() );
+ handle.scale.copy( tempVector );
+ handle.visible = this.dragging;
+
+ } else {
+
+ handle.quaternion.copy( quaternion );
+
+ if ( this.dragging ) {
+
+ handle.position.copy( this.worldPositionStart );
+
+ } else {
+
+ handle.position.copy( this.worldPosition );
+
+ }
+
+ if ( this.axis ) {
+
+ handle.visible = this.axis.search( handle.name ) !== -1;
+
+ }
+
+ }
+
+ // If updating helper, skip rest of the loop
+ continue;
+
+ }
+
+ // Align handles to current local or world rotation
+
+ handle.quaternion.copy( quaternion );
+
+ if ( this.mode === 'translate' || this.mode === 'scale' ) {
+
+ // Hide translate and scale axis facing the camera
+
+ var AXIS_HIDE_TRESHOLD = 0.99;
+ var PLANE_HIDE_TRESHOLD = 0.2;
+ var AXIS_FLIP_TRESHOLD = -0.4;
+
+
+ if ( handle.name === 'X' || handle.name === 'XYZX' ) {
+ if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+ }
+ }
+ if ( handle.name === 'Y' || handle.name === 'XYZY' ) {
+ if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+ }
+ }
+ if ( handle.name === 'Z' || handle.name === 'XYZZ' ) {
+ if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+ }
+ }
+ if ( handle.name === 'XY' ) {
+ if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+ }
+ }
+ if ( handle.name === 'YZ' ) {
+ if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+ }
+ }
+ if ( handle.name === 'XZ' ) {
+ if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+ handle.scale.set( 1e-10, 1e-10, 1e-10 );
+ handle.visible = false;
+ }
+ }
+
+ // Flip translate and scale axis ocluded behind another axis
+
+ if ( handle.name.search( 'X' ) !== -1 ) {
+ if ( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+ if ( handle.tag === 'fwd' ) {
+ handle.visible = false;
+ } else {
+ handle.scale.x *= -1;
+ }
+ } else if ( handle.tag === 'bwd' ) {
+ handle.visible = false;
+ }
+ }
+
+ if ( handle.name.search( 'Y' ) !== -1 ) {
+ if ( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+ if ( handle.tag === 'fwd' ) {
+ handle.visible = false;
+ } else {
+ handle.scale.y *= -1;
+ }
+ } else if ( handle.tag === 'bwd' ) {
+ handle.visible = false;
+ }
+ }
+
+ if ( handle.name.search( 'Z' ) !== -1 ) {
+ if ( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+ if ( handle.tag === 'fwd' ) {
+ handle.visible = false;
+ } else {
+ handle.scale.z *= -1;
+ }
+ } else if ( handle.tag === 'bwd' ) {
+ handle.visible = false;
+ }
+ }
+
+ } else if ( this.mode === 'rotate' ) {
+
+ // Align handles to current local or world rotation
+
+ tempQuaternion2.copy( quaternion );
+ alignVector.copy( this.eye ).applyQuaternion( tempQuaternion.copy( quaternion ).inverse() );
+
+ if ( handle.name.search( "E" ) !== - 1 ) {
+
+ handle.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( this.eye, zeroVector, unitY ) );
+
+ }
+
+ if ( handle.name === 'X' ) {
+
+ tempQuaternion.setFromAxisAngle( unitX, Math.atan2( -alignVector.y, alignVector.z ) );
+ tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
+ handle.quaternion.copy( tempQuaternion );
+
+ }
+
+ if ( handle.name === 'Y' ) {
+
+ tempQuaternion.setFromAxisAngle( unitY, Math.atan2( alignVector.x, alignVector.z ) );
+ tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
+ handle.quaternion.copy( tempQuaternion );
+
+ }
+
+ if ( handle.name === 'Z' ) {
+
+ tempQuaternion.setFromAxisAngle( unitZ, Math.atan2( alignVector.y, alignVector.x ) );
+ tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
+ handle.quaternion.copy( tempQuaternion );
+
+ }
+
+ }
+
+ // Hide disabled axes
+ handle.visible = handle.visible && ( handle.name.indexOf( "X" ) === -1 || this.showX );
+ handle.visible = handle.visible && ( handle.name.indexOf( "Y" ) === -1 || this.showY );
+ handle.visible = handle.visible && ( handle.name.indexOf( "Z" ) === -1 || this.showZ );
+ handle.visible = handle.visible && ( handle.name.indexOf( "E" ) === -1 || ( this.showX && this.showY && this.showZ ) );
+
+ // highlight selected axis
+
+ handle.material._opacity = handle.material._opacity || handle.material.opacity;
+ handle.material._color = handle.material._color || handle.material.color.clone();
+
+ handle.material.color.copy( handle.material._color );
+ handle.material.opacity = handle.material._opacity;
+
+ if ( !this.enabled ) {
+
+ handle.material.opacity *= 0.5;
+ handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
+
+ } else if ( this.axis ) {
+
+ if ( handle.name === this.axis ) {
+
+ handle.material.opacity = 1.0;
+ handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
+
+ } else if ( this.axis.split('').some( function( a ) { return handle.name === a; } ) ) {
+
+ handle.material.opacity = 1.0;
+ handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
+
+ } else {
+
+ handle.material.opacity *= 0.25;
+ handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
+
+ }
+
+ }
+
+ }
+
+ THREE.Object3D.prototype.updateMatrixWorld.call( this );
+
+ };
+
+};
+
+THREE.TransformControlsGizmo.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
+
+ constructor: THREE.TransformControlsGizmo,
+
+ isTransformControlsGizmo: true
+
+} );
+
+
+THREE.TransformControlsPlane = function () {
+
+ 'use strict';
+
+ THREE.Mesh.call( this,
+ new THREE.PlaneBufferGeometry( 100000, 100000, 2, 2 ),
+ new THREE.MeshBasicMaterial( { visible: false, wireframe: true, side: THREE.DoubleSide, transparent: true, opacity: 0.1 } )
+ );
+
+ this.type = 'TransformControlsPlane';
+
+ var unitX = new THREE.Vector3( 1, 0, 0 );
+ var unitY = new THREE.Vector3( 0, 1, 0 );
+ var unitZ = new THREE.Vector3( 0, 0, 1 );
+
+ var tempVector = new THREE.Vector3();
+ var dirVector = new THREE.Vector3();
+ var alignVector = new THREE.Vector3();
+ var tempMatrix = new THREE.Matrix4();
+ var identityQuaternion = new THREE.Quaternion();
+
+ this.updateMatrixWorld = function() {
+
+ var space = this.space;
+
+ this.position.copy( this.worldPosition );
+
+ if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
+
+ unitX.set( 1, 0, 0 ).applyQuaternion( space === "local" ? this.worldQuaternion : identityQuaternion );
+ unitY.set( 0, 1, 0 ).applyQuaternion( space === "local" ? this.worldQuaternion : identityQuaternion );
+ unitZ.set( 0, 0, 1 ).applyQuaternion( space === "local" ? this.worldQuaternion : identityQuaternion );
+
+ // Align the plane for current transform mode, axis and space.
+
+ alignVector.copy( unitY );
+
+ switch ( this.mode ) {
+ case 'translate':
+ case 'scale':
+ switch ( this.axis ) {
+ case 'X':
+ alignVector.copy( this.eye ).cross( unitX );
+ dirVector.copy( unitX ).cross( alignVector );
+ break;
+ case 'Y':
+ alignVector.copy( this.eye ).cross( unitY );
+ dirVector.copy( unitY ).cross( alignVector );
+ break;
+ case 'Z':
+ alignVector.copy( this.eye ).cross( unitZ );
+ dirVector.copy( unitZ ).cross( alignVector );
+ break;
+ case 'XY':
+ dirVector.copy( unitZ );
+ break;
+ case 'YZ':
+ dirVector.copy( unitX );
+ break;
+ case 'XZ':
+ alignVector.copy( unitZ );
+ dirVector.copy( unitY );
+ break;
+ case 'XYZ':
+ case 'E':
+ dirVector.set( 0, 0, 0 );
+ break;
+ }
+ break;
+ case 'rotate':
+ default:
+ // special case for rotate
+ dirVector.set( 0, 0, 0 );
+ }
+
+ if ( dirVector.length() === 0 ) {
+
+ // If in rotate mode, make the plane parallel to camera
+ this.quaternion.copy( this.cameraQuaternion );
+
+ } else {
+
+ tempMatrix.lookAt( tempVector.set( 0, 0, 0 ), dirVector, alignVector );
+
+ this.quaternion.setFromRotationMatrix( tempMatrix );
+
+ }
+
+ THREE.Object3D.prototype.updateMatrixWorld.call( this );
+
+ };
+
+};
+
+THREE.TransformControlsPlane.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
+
+ constructor: THREE.TransformControlsPlane,
+
+ isTransformControlsPlane: true
+
+} );
diff --git a/src/jlmap3d/edit/testmodel/LinkList.js b/src/jlmap3d/edit/testmodel/LinkList.js
new file mode 100644
index 000000000..306393f51
--- /dev/null
+++ b/src/jlmap3d/edit/testmodel/LinkList.js
@@ -0,0 +1,31 @@
+
+export function LinkList(data,scene){
+
+ let groups = new THREE.Group();
+ let linklist = [];
+ for(let i=0;i=3){
+ console.log(data.sectionList[i]);
+
+ for(let j=1;jpoint2.y){
+ section.rotation.z = rotenum;
+ }else {
+ section.rotation.z = -rotenum;
+ }
+ section.position.set(posx,1,posy);
+ group.add(section);
+ }
+
+ }else{
+ //获取区段长度
+ let dx = Math.abs(point1.x - point2.x);
+ let dy = Math.abs(point1.y - point2.y);
+ //let distance = Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2));
+ let distance = data.sectionList[i].offsetRight - data.sectionList[i].offsetLeft;
+ //获取区段中心点坐标
+ let posx = (point1.x+point2.x)/2;
+ let posy = (point1.y+point2.y)/2;
+ //判断是否道岔
+
+ //创建模型
+
+ let sectiongeometry = new THREE.PlaneBufferGeometry( distance, 5, 32 );
+ let section = new THREE.Mesh( sectiongeometry, sectionmaterial );
+ section.name = data.sectionList[i].code;
+
+ section.rotation.x = -Math.PI/2;
+
+
+ //判断方向 PS:方法待提炼
+ let axix = new THREE.Vector3(1,0,0);
+ let axixnow = new THREE.Vector3(point2.x-point1.x,0,point2.y-point1.y);
+ let rotenum = axixnow.angleTo(axix);
+ //不同坐标系方向值不同
+ if(point1.y>point2.y){
+ section.rotation.z = rotenum;
+ }else {
+ section.rotation.z = -rotenum;
+ }
+ section.position.set(posx,1,posy);
+ group.add(section);
+
+
+ }
+ }
+
+ }
+
+
+ //定义区分道岔组
+ let switchlist = [];
+ //遍历道岔数据
+ for(let i=0;i {
+ ModelLoad(data,scope,netdata.data,mapdata,camera,controls3,scene);
+ });
+
+ //let stats = new Stats();
+ //dom.appendChild( stats.dom );
+ //开启渲染
+ animate();
+ startWorker();
+ //动画时间
+ let delta;
+ //循环渲染函数
+ function animate() {
+ //循环渲染
+ //requestAnimationFrame(animate);
+ //renderer.setAnimationLoop(animate);
+ requestAnimationFrame(animate);
+ //判断渲染是否开启
+ if(scope.animateswitch == true){
+ //根据相机渲染场景
+ renderer.render(scene,camera2);
+ //updatcontrols();
+ controls3.update();
+ //检测动画构造器播放动画
+
+ }
+
+ }
+
+ function updatcontrols(){
+ if(drivingcode){
+ controls3.getObject().position.x = mapdata.trainlisttest.list[drivingcode].matrixWorld.elements[12]-27;
+ controls3.getObject().position.y=10;
+ controls3.getObject().position.z = mapdata.trainlisttest.list[drivingcode].children[0].matrixWorld.elements[14];
+
+ }
+ }
+
+ function startWorker(){
+
+ if(typeof(Worker)!=="undefined"){
+
+
+ scope.webwork.onmessage = function (event) {
+ //更新列车位置
+ // stats.update();
+ UpdateTrain(camera,mapdata.trainlisttest);
+ delta = clock.getDelta();
+ for(let i=scope.mixers.length-1;i>=0;i--){
+ if ( scope.mixers[i] ){
+ scope.mixers[i].update( delta );
+ }
+ }
+
+ };
+ }
+
+ }
+ this.updatenowspeed = function(speed){
+ scope.nowspeed = speed;
+ }
+ this.updatenowlen = function(maLen){
+ scope.nowmxlen = maLen;
+ }
+ this.updateatpspeed = function(atpspeed){
+ scope.atpspeed = atpspeed;
+ }
+ this.updateatospeed = function(atospeed){
+ scope.atospeed = atospeed;
+ }
+ this.updatetrainnum = function(trainnum){
+ scope.trainnum = trainnum;
+ }
+ this.updatestoptime = function(stime){
+ scope.stime = stime;
+ }
+
+ this.updatedrivingcode = function(code){
+ drivingcode = code;
+ mapdata.trainlisttest.list[drivingcode].children[0].add(controls3.getObject());
+ mapdata.trainlisttest.list[drivingcode].children[0].add(scope.assetloader.modellist[4].mesh);
+ controls3.getObject().position.x = 40;
+ controls3.getObject().position.y= 12.5;
+ controls3.getObject().rotation.y = -Math.PI/2;
+ scope.assetloader.modellist[4].mesh.position.x = 34;
+ scope.assetloader.modellist[4].mesh.position.y = 0;
+ }
+
+ this.dispose = function(){
+ renderer.setAnimationLoop(null);
+ renderer.dispose();
+ scene.dispose();
+ // controls.dispose();
+ camera = null;
+ scope.assetloader = null;
+
+ mapdata = null;
+ scope.selectmodel = null;
+
+ scope.materiallist = null;
+ scope.selectmodel = null;
+ scope.helpbox = null;
+ scope.mixers = null;
+ scope.actions = null;
+ scope.Subscribe = null;
+ //console.log(scope);
+ //scope = null;
+ }
+
+ this.rayswitch = function(value){
+ this.raycasterswitch = value;
+ if(scope.helpbox){
+ scene.remove( scope.helpbox );
+ scope.helpbox = null;
+ }
+ };
+
+ this.showstationmsg = function(showtype){
+ if(showtype == "show"){
+ for(let st=0;st0){
+ jlmap3d.updatenowspeed(data.body[tl].v);
+ }else{
+ jlmap3d.updatenowspeed(Math.abs(data.body[tl].v));
+ }
+
+ if(data.body[tl].atpv){
+ jlmap3d.updateatpspeed(data.body[tl].atpv);
+ }else{
+ jlmap3d.updateatpspeed(-1);
+ }
+ if(data.body[tl].atov){
+ jlmap3d.updateatospeed(data.body[tl].atov);
+ }else{
+ jlmap3d.updateatospeed(-1);
+ }
+ }
+ }
+ }
+ }
+
+ if(data.type == "SJL3D_TrainStatus"){
+ for(let i=data.body.length-1;i>=0;i--){
+ if(data.body[i]._type == "Train"){
+ //遍历列车对象组
+ if(trainlisttest){
+ code = data.body[i].code;
+ //剔除不显示的车
+ //找到对应列车
+
+ if( trainlisttest.list[code]){
+ if(code == drivingcode){
+ driverswitch = data.body[i].runMode;
+ jlmap3d.updatetrainnum(data.body[i].groupNumber);
+ // console.log(trainlisttest.list[code].progress);
+ let syncdata = {
+ type:"Train",
+ code:code,
+ sectionCode:data.body[i].sectionCode,
+ percent:0,
+ };
+
+ if(trainlisttest.list[code].isStandTrack == true && trainlisttest.list[code].progress>0.95 && trainlisttest.list[code].speed<=0){
+ if(data.body[i].directionType == "02"){
+ syncdata.percent = 1-(sectionlist.sections.datalist[data.body[i].sectionCode].rstop/trainlisttest.list[code].len);
+ }else{
+ syncdata.percent = sectionlist.sections.datalist[data.body[i].sectionCode].lstop/trainlisttest.list[code].len;
+ }
+ scope.teststomp.send("/app/topic/simulation/wgu3d",syncdata);
+ }else{
+ if(data.body[i].directionType == "02"){
+ syncdata.percent = trainlisttest.list[code].progress;
+ }else{
+ syncdata.percent = 1 - trainlisttest.list[code].progress;
+ }
+ scope.teststomp.send("/app/topic/simulation/wgu3d",syncdata);
+ }
+
+ if(data.body[i].parkRemainTime>0){
+ jlmap3d.updatestoptime("停站时间:"+parseInt(data.body[i].parkRemainTime));
+ }else{
+ if(data.body[i].runMode == "02"){
+ jlmap3d.updatestoptime("列车自动驾驶中");
+ }else{
+ if(trainlisttest.list[code].isStandTrack == true && trainlisttest.list[code].progress>0.95 && trainlisttest.list[code].speed<=0){
+ // if(stoptimer){
+ //
+ // }else{
+ // stoptimer = setInterval(function(){
+ // if(num >=0){
+ // jlmap3d.updatestoptime("停站时间:"+num);
+ // num --;
+ // }
+ // },1000);
+ // }
+
+ }else{
+ jlmap3d.updatestoptime("列车人工驾驶中");
+ }
+
+ }
+ }
+ // if(trainlisttest.list[code].speed > 0){
+ // if(stoptimer){
+ // clearInterval( stoptimer );
+ // stoptimer = null;
+ // num = 30;
+ // }
+ // }
+
+ }
+ trainlisttest.list[code].runMode = data.body[i].runMode;
+ //车门开关验证
+ if(data.body[i].directionType == "02"){
+ if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "01"){
+ //console.log("close");
+ trainlisttest.list[code].doorStatus = "01";
+ for(let an=actions[code].top.length-1;an>=0;an--){
+ actions[code].top[an].reset();
+ actions[code].top[an].time = actions[code].top[an]._clip.duration;
+ actions[code].top[an].timeScale = -1;
+ actions[code].top[an].play();
+ }
+ }else if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "02"){
+ //console.log("open");
+ trainlisttest.list[code].doorStatus = "02";
+ for(let an=actions[code].top.length-1;an>=0;an--){
+ actions[code].top[an].reset();
+ actions[code].top[an].time = 0;
+ actions[code].top[an].timeScale = 1;
+ actions[code].top[an].play();
+ }
+ }
+ }else{
+ if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "01"){
+ //console.log("close");
+ trainlisttest.list[code].doorStatus = "01";
+ for(let an=actions[code].down.length-1;an>=0;an--){
+ actions[code].down[an].reset();
+ actions[code].down[an].time = actions[code].top[an]._clip.duration;
+ actions[code].down[an].timeScale = -1;
+ actions[code].down[an].play();
+ }
+ }else if(trainlisttest.list[code].doorStatus != data.body[i].doorStatus && data.body[i].doorStatus == "02"){
+ //console.log("open");
+ trainlisttest.list[code].doorStatus = "02";
+ for(let an=actions[code].down.length-1;an>=0;an--){
+ actions[code].down[an].reset();
+ actions[code].down[an].time = 0;
+ actions[code].down[an].timeScale = 1;
+ actions[code].down[an].play();
+ }
+ }
+ }
+ //遍历获取所在轨道
+
+ if(trainlisttest.list[code].dispose != data.body[i].dispose && data.body[i].dispose == false){
+
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].code){
+ trainlisttest.group.add(trainlisttest.list[code]);
+ trainlisttest.list[code].position.y = 0;
+ trainlisttest.list[code].progress = 0;
+ trainlisttest.list[code].oldoffset = data.body[i].sectionOffsetPercent;
+ trainlisttest.list[code].dispose = false;
+ trainlisttest.list[code].nowcode = data.body[i].sectionCode;
+ trainlisttest.list[code].nextcode = data.body[i].nextSectionCode;
+ let vexlist = [];
+ let endrotation = null;
+
+ if(data.body[i].directionType == "02"){//向右
+ let offset = null;
+ let rotaposx = null;
+
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x>sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+ offset = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x-sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x;
+ rotaposx = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x+offset*data.body[i].sectionOffsetPercent;
+ }else{
+ offset = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x-sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x;
+ rotaposx = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x+offset*data.body[i].sectionOffsetPercent;
+ }
+
+ let rotaposz = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].z;
+ trainlisttest.list[code].rotation.y = 0;
+ trainlisttest.list[code].position.x = rotaposx;
+ trainlisttest.list[code].position.y = 0;
+ for(let tl=0;tl<6;tl++){
+ trainlisttest.list[code].children[tl].position.z = rotaposz;
+ }
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x>sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+ vexlist.push(new THREE.Vector3(rotaposx,0,rotaposz));
+ for(let m=sectionlist.sections.datalist[data.body[i].sectionCode].rail.length-1;m>=0;m--){
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x>rotaposx){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+
+ }
+ }else{
+ vexlist.push(new THREE.Vector3(rotaposx,0,rotaposz));
+ for(let m=0;mrotaposx){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }
+ trainlisttest.list[code].status = "02";
+
+ }else if(data.body[i].directionType == "03"){//向左
+
+ trainlisttest.list[code].dispose = false;
+ trainlisttest.group.add(trainlisttest.list[code]);
+ let offset = null;
+ let rotaposx = null;
+
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x>sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+
+ offset = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x-sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x;
+ rotaposx = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x+offset*data.body[i].sectionOffsetPercent;
+ }else{
+
+ offset = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x-sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x;
+ rotaposx = sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x+offset*data.body[i].sectionOffsetPercent;
+ }
+ let rotaposz = sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].z;
+ trainlisttest.list[code].rotation.y = Math.PI;
+ trainlisttest.list[code].position.x = rotaposx;
+ trainlisttest.list[code].position.y = 0;
+ for(let tl=0;tl<6;tl++){
+ trainlisttest.list[code].children[tl].position.z = rotaposz;
+ }
+ if(data.body[i].groupNumber == "001"){
+ }
+
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x=0;m--){
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].xsectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+ vexlist.push(new THREE.Vector3(trainlisttest.list[code].position.x,0,trainlisttest.list[code].children[0].matrixWorld.elements[14]));
+ for(let m=sectionlist.sections.datalist[data.body[i].sectionCode].rail.length-1;m>=0;m--){
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x>trainlisttest.list[code].position.x){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }else{
+ vexlist.push(new THREE.Vector3(trainlisttest.list[code].position.x,0,trainlisttest.list[code].children[0].matrixWorld.elements[14]));
+ for(let m=0;mtrainlisttest.list[code].position.x){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }
+ }
+ }else{
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[0].x>sectionlist.sections.datalist[data.body[i].sectionCode].rail[1].x){
+ for(let m=sectionlist.sections.datalist[data.body[i].sectionCode].rail.length-1;m>=0;m--){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+ }else{
+ for(let m=0;m=0;m--){
+ if(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m].x=0;m--){
+ vexlist.push(sectionlist.sections.datalist[data.body[i].sectionCode].rail[m]);
+ }
+
+ }else{
+ for(let m=0;m=0;i--){
+ //0xFFFFFF
+ //0xCD0000 红
+ //0xEEEE00 黄
+ //0x32CD32 绿
+ if(data.body[i]._type == "Signal"){
+ if(signallist){
+ signalupdate(data.body[i]);
+ }
+ }
+
+ if(data.body[i]._type == "StationStand"){
+ if(actions){
+ standupdate(data.body[i]);
+ }
+ }
+
+ if(data.body[i]._type == "Switch"){
+ if(sectionlist){
+ switchupdate(data.body[i]);
+ }
+ }
+
+ }
+ }
+ }
+
+ function standupdate(data){
+ code = data.code;
+ if( actions[code]){
+ if(data.screenDoorOpenStatus == "02" && actions[code].status == "01"){
+ actions[code].status = "00";
+ }
+ if(data.screenDoorOpenStatus == "02" && actions[code].status == "00"){
+ actions[code].status = "02";
+ actions[code].action.reset();
+ actions[code].action.time = 0;
+ actions[code].action.timeScale = 1;
+ actions[code].action.play();
+ }
+
+ if(data.screenDoorOpenStatus == "01" && actions[code].status == "02"){
+ actions[code].status = "00";
+ }
+ if(data.screenDoorOpenStatus == "01" && actions[code].status == "00"){
+ actions[code].status = "01";
+ actions[code].action.reset();
+ actions[code].action.time = actions[code].action._clip.duration;
+ actions[code].action.timeScale = -1;
+ actions[code].action.play();
+ }
+ }
+
+
+ }
+
+ function signalupdate(data){
+ code = data.code;
+ if(data.lightType == "01"){
+ if(signallist.list[code].mesh.code){
+ signallist.list[code].mesh.status = data.status;
+
+ //55
+ //33
+ //77
+ //关闭
+ if(data.status == "01"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[0];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //开放
+ if(data.status == "02"){
+
+ if(data.switchLocateType == "01"){
+ signallist.list[code].mesh.children[0].material.map = materials[2];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ if(data.switchLocateType == "02"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[1];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ }
+ //引导
+ if(data.status == "03"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[0];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[1];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //封锁
+ if(data.status == "04"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+ //故障
+ if(data.status == "05"){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ }
+
+ }else if(data.lightType == "01"){
+ if(signallist.list[code].mesh.code){
+ signallist.list[code].mesh.children[0].material.map = materials[3];
+ signallist.list[code].mesh.children[0].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[1].material.map = materials[3];
+ signallist.list[code].mesh.children[1].material.map.needsUpdate = true;
+ signallist.list[code].mesh.children[2].material.map = materials[3];
+ signallist.list[code].mesh.children[2].material.map.needsUpdate = true;
+
+ }
+
+ }
+
+ }
+
+ function switchupdate(data){
+ code = data.code;
+ for(let j=sectionlist.switchs.modellist.length-1;j>=0;j--){
+ if(sectionlist.switchs.modellist[j].code == code){
+ if(sectionlist.switchs.modellist[j].locateType != data.locateType){
+ if(data.locateType == "02"){
+ if(actions[sectionlist.switchs.modellist[j].code]){
+ sectionlist.switchs.modellist[j].locateType = data.locateType;
+ actions[sectionlist.switchs.modellist[j].code].reset();
+ actions[sectionlist.switchs.modellist[j].code].time = 0;
+ actions[sectionlist.switchs.modellist[j].code].timeScale = 1;
+ actions[sectionlist.switchs.modellist[j].code].play();
+ }
+ }else if(data.locateType == "01"){
+ if(actions[sectionlist.switchs.modellist[j].code]){
+ sectionlist.switchs.modellist[j].locateType = data.locateType;
+ actions[sectionlist.switchs.modellist[j].code].reset();
+ actions[sectionlist.switchs.modellist[j].code].time = actions[sectionlist.switchs.modellist[j].code]._clip.duration;
+ actions[sectionlist.switchs.modellist[j].code].timeScale = -1;
+ actions[sectionlist.switchs.modellist[j].code].play();
+ }
+ }
+ }
+ j = 0;
+ }
+ }
+ }
+
+
+}
diff --git a/src/jlmap3d/jl3dsimulation/moveupdate/UpdateTrain.js b/src/jlmap3d/jl3dsimulation/moveupdate/UpdateTrain.js
new file mode 100644
index 000000000..5e6d272a6
--- /dev/null
+++ b/src/jlmap3d/jl3dsimulation/moveupdate/UpdateTrain.js
@@ -0,0 +1,275 @@
+
+export function UpdateTrain(camera,traindata,control){
+ if(traindata != undefined ){
+
+ for(let j=traindata.group.children.length-1;j>=0;j--){
+ //判断是否有移动事件
+ if(traindata.group.children[j].dispose == false){
+
+ if(traindata.group.children[j].progress != null){
+
+ let trainmodel = traindata.group.children[j];
+ if(trainmodel.speeds > 0 && trainmodel.speeds){
+ let speed = null;
+ if(traindata.group.children[j].progress<1){
+
+ let movecurve = trainmodel.curve;
+
+ if(trainmodel.status == "03"){
+ if(movecurve.points.length>1){
+ let point = movecurve.getPointAt(traindata.group.children[j].progress);
+ if(Math.abs( point.z -trainmodel.children[0].matrixWorld.elements[14]) >0.1){
+ trainmodel.children[0].up = new THREE.Vector3(-1,0,0);
+ let tangent = movecurve.getTangentAt(traindata.group.children[j].progress).normalize();
+ trainmodel.children[0].axis.crossVectors(trainmodel.children[0].up, tangent).normalize();
+ let radians = Math.acos(trainmodel.children[0].up.dot(tangent));
+ trainmodel.children[0].quaternion.setFromAxisAngle(trainmodel.children[0].axis, radians);
+ let rotas = {
+ posr:point,
+ rota:trainmodel.children[0].rotation.y
+ }
+ trainmodel.children[1].rotalist.push(rotas);
+
+ let offsetz = parseFloat(trainmodel.children[0].matrixWorld.elements[14]) - parseFloat(point.z);
+ trainmodel.children[0].position.z += offsetz;
+ //trainmodel.position.z = point.z;
+ }
+
+ trainmodel.position.x = point.x;
+ trainmodel.position.y = 0;
+
+ if(trainmodel.children[1].rotalist.length > 0 || trainmodel.children[2].rotalist.length > 0 || trainmodel.children[3].rotalist.length > 0 || trainmodel.children[4].rotalist.length > 0|| trainmodel.children[5].rotalist.length > 0){
+ for(let rs = 1;rs<6;rs++){
+ //console.log(rs);
+ if(trainmodel.children[rs].rotalist[0]){
+ let offsetz = parseFloat(trainmodel.children[rs].matrixWorld.elements[14]) - parseFloat(trainmodel.children[rs].rotalist[0].posr.z);
+
+ trainmodel.children[rs].position.z += offsetz;
+
+ for(let xh=0;xh1){
+ let point = movecurve.getPointAt(trainmodel.progress);
+ if(Math.abs( point.z -trainmodel.children[0].matrixWorld.elements[14]) >0.1){
+ trainmodel.children[0].up = new THREE.Vector3(1,0,0);
+ let tangent = movecurve.getTangentAt(traindata.group.children[j].progress).normalize();
+ trainmodel.children[0].axis.crossVectors(trainmodel.children[0].up, tangent).normalize();
+ let radians = Math.acos(trainmodel.children[0].up.dot(tangent));
+ trainmodel.children[0].quaternion.setFromAxisAngle(trainmodel.children[0].axis, radians);
+
+ let rotas = {
+ posr:point,
+ rota:trainmodel.children[0].rotation.y
+ }
+ trainmodel.children[1].rotalist.push(rotas);
+ let offsetz = parseFloat(point.z) - parseFloat(trainmodel.children[0].matrixWorld.elements[14]);
+ trainmodel.children[0].position.z += offsetz;
+ //trainmodel.position.z = point.z;
+ }
+
+ trainmodel.position.x = point.x;
+ trainmodel.position.y = 0;
+ if(trainmodel.children[1].rotalist.length > 0 || trainmodel.children[2].rotalist.length > 0 || trainmodel.children[3].rotalist.length > 0 || trainmodel.children[4].rotalist.length > 0|| trainmodel.children[5].rotalist.length > 0){
+
+ for(let rs = 1;rs<6;rs++){
+ //console.log(rs);
+ if(trainmodel.children[rs].rotalist[0]){
+
+ let offsetz = parseFloat(trainmodel.children[rs].rotalist[0].posr.z) - parseFloat(trainmodel.children[rs].matrixWorld.elements[14]);
+ trainmodel.children[rs].position.z += offsetz;
+
+ for(let xh=0;xh=trainmodel.children[rs].rotalist[0].posr.x){
+
+ if(rs != 5){
+ let asd = trainmodel.children[rs].rotalist[0];
+ trainmodel.children[rs+1].rotalist.push(asd);
+
+ }
+ //let offsetx = trainmodel.children[1].matrixWorld.elements[12]-trainmodel.children[0].children[3].matrixWorld.elements[12];
+
+ trainmodel.children[rs].rotation.y = trainmodel.children[rs].rotalist[0].rota;
+ trainmodel.children[rs].rotalist.splice(0,1)
+ xh--;
+ }else{
+ xh = trainmodel.children[rs].rotalist.length;
+ }
+ }
+ //console.log(trainmodel.children[rs].rotalist.length);
+
+ }
+
+
+ }
+ // console.log(trainmodel.rotalist);
+ }
+
+
+ trainmodel.progress += trainmodel.speeds;
+ }
+ }
+
+ }
+
+ }else if(trainmodel.speeds < 0 && trainmodel.speeds){
+ let speed = null;
+ if(traindata.group.children[j].progress<1){
+
+ let movecurve = trainmodel.curve;
+
+ if(trainmodel.status == "03" && trainmodel.progress>0){
+ if(movecurve.points.length>1){
+ let point = movecurve.getPointAt(traindata.group.children[j].progress);
+ if(Math.abs( point.z -trainmodel.children[0].matrixWorld.elements[14]) >0.1){
+ trainmodel.children[0].up = new THREE.Vector3(-1,0,0);
+ let tangent = movecurve.getTangentAt(traindata.group.children[j].progress).normalize();
+ trainmodel.children[0].axis.crossVectors(trainmodel.children[0].up, tangent).normalize();
+ let radians = Math.acos(trainmodel.children[0].up.dot(tangent));
+ trainmodel.children[0].quaternion.setFromAxisAngle(trainmodel.children[0].axis, radians);
+ let rotas = {
+ posr:point,
+ rota:trainmodel.children[0].rotation.y
+ }
+ trainmodel.children[1].rotalist.push(rotas);
+
+ let offsetz = parseFloat(trainmodel.children[0].matrixWorld.elements[14]) - parseFloat(point.z);
+ trainmodel.children[0].position.z += offsetz;
+ //trainmodel.position.z = point.z;
+ }
+
+ trainmodel.position.x = point.x;
+ trainmodel.position.y = 0;
+
+ if(trainmodel.children[1].rotalist.length > 0 || trainmodel.children[2].rotalist.length > 0 || trainmodel.children[3].rotalist.length > 0 || trainmodel.children[4].rotalist.length > 0|| trainmodel.children[5].rotalist.length > 0){
+ for(let rs = 1;rs<6;rs++){
+ //console.log(rs);
+ if(trainmodel.children[rs].rotalist[0]){
+ let offsetz = parseFloat(trainmodel.children[rs].matrixWorld.elements[14]) - parseFloat(trainmodel.children[rs].rotalist[0].posr.z);
+
+ trainmodel.children[rs].position.z += offsetz;
+
+ for(let xh=0;xh -(trainmodel.speeds)){
+ trainmodel.progress += trainmodel.speeds;
+ }
+
+ }
+ }
+
+ if(trainmodel.status == "02"){
+ if(movecurve.points.length>1 && trainmodel.progress>0){
+
+ let point = movecurve.getPointAt(trainmodel.progress);
+ if(Math.abs( point.z -trainmodel.children[0].matrixWorld.elements[14]) >0.1){
+ trainmodel.children[0].up = new THREE.Vector3(1,0,0);
+ let tangent = movecurve.getTangentAt(traindata.group.children[j].progress).normalize();
+ trainmodel.children[0].axis.crossVectors(trainmodel.children[0].up, tangent).normalize();
+ let radians = Math.acos(trainmodel.children[0].up.dot(tangent));
+ trainmodel.children[0].quaternion.setFromAxisAngle(trainmodel.children[0].axis, radians);
+
+ let rotas = {
+ posr:point,
+ rota:trainmodel.children[0].rotation.y
+ }
+ trainmodel.children[1].rotalist.push(rotas);
+ let offsetz = parseFloat(point.z) - parseFloat(trainmodel.children[0].matrixWorld.elements[14]);
+ trainmodel.children[0].position.z += offsetz;
+ //trainmodel.position.z = point.z;
+ }
+
+ trainmodel.position.x = point.x;
+ trainmodel.position.y = 0;
+ if(trainmodel.children[1].rotalist.length > 0 || trainmodel.children[2].rotalist.length > 0 || trainmodel.children[3].rotalist.length > 0 || trainmodel.children[4].rotalist.length > 0|| trainmodel.children[5].rotalist.length > 0){
+
+ for(let rs = 1;rs<6;rs++){
+ //console.log(rs);
+ if(trainmodel.children[rs].rotalist[0]){
+
+ let offsetz = parseFloat(trainmodel.children[rs].rotalist[0].posr.z) - parseFloat(trainmodel.children[rs].matrixWorld.elements[14]);
+ trainmodel.children[rs].position.z += offsetz;
+
+ for(let xh=0;xh=trainmodel.children[rs].rotalist[0].posr.x){
+
+ if(rs != 5){
+ let asd = trainmodel.children[rs].rotalist[0];
+ trainmodel.children[rs+1].rotalist.push(asd);
+
+ }
+ //let offsetx = trainmodel.children[1].matrixWorld.elements[12]-trainmodel.children[0].children[3].matrixWorld.elements[12];
+
+ trainmodel.children[rs].rotation.y = trainmodel.children[rs].rotalist[0].rota;
+ trainmodel.children[rs].rotalist.splice(0,1)
+ xh--;
+ }else{
+ xh = trainmodel.children[rs].rotalist.length;
+ }
+ }
+ //console.log(trainmodel.children[rs].rotalist.length);
+
+ }
+
+
+ }
+ // console.log(trainmodel.rotalist);
+ }
+
+
+ if(trainmodel.progress > -(trainmodel.speeds)){
+ trainmodel.progress += trainmodel.speeds;
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+ }
+
+ }
+
+}
diff --git a/src/jlmap3d/jl3dsimulation/simulationloader.js b/src/jlmap3d/jl3dsimulation/simulationloader.js
new file mode 100644
index 000000000..c7dc41945
--- /dev/null
+++ b/src/jlmap3d/jl3dsimulation/simulationloader.js
@@ -0,0 +1,193 @@
+//componnent
+import {SectionList} from '@/jlmap3d/model/SectionList.js';
+import {SignalList} from '@/jlmap3d/model/SignalList.js';
+import {StationStandList} from '@/jlmap3d/model/StationStandList.js';
+import {TrainList} from '@/jlmap3d/model/TrainList.js';
+import {RealSectionList} from '@/jlmap3d/model/RealSectionList.js';
+
+import {Materialload} from '@/jlmap3d/component/Materialload.js';
+
+import { Loading } from 'element-ui';
+// import {SwitchModel} from '@/jlmap3d/model/SwitchModel.js';
+
+export function ModelLoad(data,scope,netdata,mapdata,camera,controls,scene){
+ //console.log(mapdata);
+ Materialload(scope);
+ //console.log(data);
+ //console.log(scope);
+ let sceneload = scene;
+ let jlmap3ddata = mapdata;
+ let assetloader = scope.assetloader;
+ let animateswitch = scope.animateswitch;
+
+ let mixers = scope.mixers;
+ let actions = scope.actions;
+
+ let loadingInstance = Loading.service({ fullscreen: true });
+
+
+ let isSection = false;
+ if(netdata.assets){
+ let assetsdata = JSON.parse(netdata.assets);
+ for(let i=0;i {
+ ModelLoad(data,scope,netdata.data,mapdata,camera,controls,scene);
+
+
+ });
+ //
+ // let stats = new Stats();
+ // dom.appendChild( stats.dom );
+ //开启渲染
+ animate();
+ startWorker();
+ //动画时间
+ let delta;
+ //循环渲染函数
+ function animate() {
+ //循环渲染
+ //requestAnimationFrame(animate);
+ //renderer.setAnimationLoop(animate);
+ requestAnimationFrame(animate);
+ //判断渲染是否开启
+ if(scope.animateswitch == true){
+ //根据相机渲染场景
+ renderer.render(scene,camera);
+ //检测动画构造器播放动画
+ //stats.update();
+ }
+
+ }
+
+ function startWorker(){
+
+ if(typeof(Worker)!=="undefined"){
+
+
+ scope.webwork.onmessage = function (event) {
+ //更新列车位置
+ UpdateTrain(camera,mapdata.trainlisttest);
+ delta = clock.getDelta();
+ for(let i=scope.mixers.length-1;i>=0;i--){
+ if ( scope.mixers[i] ){
+ scope.mixers[i].update( delta );
+ }
+ }
+
+ };
+ }
+
+ }
+
+ this.dispose = function(){
+ renderer.setAnimationLoop(null);
+ renderer.dispose();
+ scene.dispose();
+ controls.dispose();
+ camera = null;
+ scope.assetloader = null;
+
+ mapdata = null;
+ scope.selectmodel = null;
+
+ scope.materiallist = null;
+ scope.selectmodel = null;
+ scope.helpbox = null;
+ scope.mixers = null;
+ scope.actions = null;
+ scope.Subscribe = null;
+ //console.log(scope);
+ //scope = null;
+ }
+
+ this.rayswitch = function(value){
+ this.raycasterswitch = value;
+ if(scope.helpbox){
+ scene.remove( scope.helpbox );
+ scope.helpbox = null;
+ }
+ };
+
+ this.showstationmsg = function(showtype){
+ if(showtype == "show"){
+ for(let st=0;sta&&(a=c[p]),c[p]>=1;x=g<<16|p;for(s=m;s>>=1;switch(c){case 0:var d=this.input,a=this.a,b=this.c,e=this.b,f=d.length,g=l,h=l,k=b.length,m=l;this.d=this.f=0;if(a+1>=f)throw Error("invalid uncompressed block header: LEN");g=d[a++]|d[a++]<<8;if(a+1>=f)throw Error("invalid uncompressed block header: NLEN");h=d[a++]|d[a++]<<8;if(g===~h)throw Error("invalid uncompressed block header: length verify");if(a+g>d.length)throw Error("input buffer is broken");switch(this.i){case A:for(;e+
+g>b.length;){m=k-e;g-=m;if(t)b.set(d.subarray(a,a+m),e),e+=m,a+=m;else for(;m--;)b[e++]=d[a++];this.b=e;b=this.e();e=this.b}break;case y:for(;e+g>b.length;)b=this.e({p:2});break;default:throw Error("invalid inflate mode");}if(t)b.set(d.subarray(a,a+g),e),e+=g,a+=g;else for(;g--;)b[e++]=d[a++];this.a=a;this.b=e;this.c=b;break;case 1:this.j(ba,ca);break;case 2:for(var n=C(this,5)+257,p=C(this,5)+1,s=C(this,4)+4,x=new (t?Uint8Array:Array)(D.length),S=l,T=l,U=l,u=l,M=l,F=l,z=l,q=l,V=l,q=0;q=P?8:255>=P?9:279>=P?7:8;var ba=v(O),Q=new (t?Uint8Array:Array)(30),R,ga;R=0;for(ga=Q.length;R=g)throw Error("input buffer is broken");a|=e[f++]<>>d;c.d=b-d;c.a=f;return h}
+function E(c,d){for(var a=c.f,b=c.d,e=c.input,f=c.a,g=e.length,h=d[0],k=d[1],m,n;b=g);)a|=e[f++]<>>16;if(n>b)throw Error("invalid code length: "+n);c.f=a>>n;c.d=b-n;c.a=f;return m&65535}
+w.prototype.j=function(c,d){var a=this.c,b=this.b;this.o=c;for(var e=a.length-258,f,g,h,k;256!==(f=E(this,c));)if(256>f)b>=e&&(this.b=b,a=this.e(),b=this.b),a[b++]=f;else{g=f-257;k=I[g];0=e&&(this.b=b,a=this.e(),b=this.b);for(;k--;)a[b]=a[b++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=b};
+w.prototype.w=function(c,d){var a=this.c,b=this.b;this.o=c;for(var e=a.length,f,g,h,k;256!==(f=E(this,c));)if(256>f)b>=e&&(a=this.e(),e=a.length),a[b++]=f;else{g=f-257;k=I[g];0e&&(a=this.e(),e=a.length);for(;k--;)a[b]=a[b++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=b};
+w.prototype.e=function(){var c=new (t?Uint8Array:Array)(this.b-32768),d=this.b-32768,a,b,e=this.c;if(t)c.set(e.subarray(32768,c.length));else{a=0;for(b=c.length;aa;++a)e[a]=e[d+a];this.b=32768;return e};
+w.prototype.z=function(c){var d,a=this.input.length/this.a+1|0,b,e,f,g=this.input,h=this.c;c&&("number"===typeof c.p&&(a=c.p),"number"===typeof c.u&&(a+=c.u));2>a?(b=(g.length-this.a)/this.o[2],f=258*(b/2)|0,e=fd&&(this.c.length=d),c=this.c);return this.buffer=c};function W(c,d){var a,b;this.input=c;this.a=0;if(d||!(d={}))d.index&&(this.a=d.index),d.verify&&(this.A=d.verify);a=c[this.a++];b=c[this.a++];switch(a&15){case ha:this.method=ha;break;default:throw Error("unsupported compression method");}if(0!==((a<<8)+b)%31)throw Error("invalid fcheck flag:"+((a<<8)+b)%31);if(b&32)throw Error("fdict flag is not supported");this.q=new w(c,{index:this.a,bufferSize:d.bufferSize,bufferType:d.bufferType,resize:d.resize})}
+W.prototype.k=function(){var c=this.input,d,a;d=this.q.k();this.a=this.q.a;if(this.A){a=(c[this.a++]<<24|c[this.a++]<<16|c[this.a++]<<8|c[this.a++])>>>0;var b=d;if("string"===typeof b){var e=b.split(""),f,g;f=0;for(g=e.length;f>>0;b=e}for(var h=1,k=0,m=b.length,n,p=0;0>>0)throw Error("invalid adler-32 checksum");}return d};var ha=8;r("Zlib.Inflate",W);r("Zlib.Inflate.prototype.decompress",W.prototype.k);var X={ADAPTIVE:B.s,BLOCK:B.t},Y,Z,$,ia;if(Object.keys)Y=Object.keys(X);else for(Z in Y=[],$=0,X)Y[$++]=Z;$=0;for(ia=Y.length;$e+1E3&&(r.update(1E3*a/(c-e),100),e=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){g=this.end()},domElement:c,setMode:k}};
+Stats.Panel=function(h,k,l){var c=Infinity,g=0,e=Math.round,a=e(window.devicePixelRatio||1),r=80*a,f=48*a,t=3*a,u=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=f;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,f);b.fillStyle=k;b.fillText(h,t,u);b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(f,
+v){c=Math.min(c,f);g=Math.max(g,f);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=k;b.fillText(e(f)+" "+h+" ("+e(c)+"-"+e(g)+")",t,u);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,e((1-f/v)*p))}}};"object"===typeof module;
diff --git a/src/jlmap3d/main/loaders/AssetLoader.js b/src/jlmap3d/main/loaders/AssetLoader.js
new file mode 100644
index 000000000..d952e6d79
--- /dev/null
+++ b/src/jlmap3d/main/loaders/AssetLoader.js
@@ -0,0 +1,418 @@
+import { AssetModel } from '@/jlmap3d/main/loaders/model/Assetmodel';
+import { Assetmaterial } from '@/jlmap3d/main/loaders/model/Assetmaterial';
+
+import { Loading } from 'element-ui';
+
+let defaultsignal = {
+ id:"3",
+ name:"低位三灯",
+ deviceType:"signal",
+ type:"low",
+ picUrl:"",
+ assetUrl:"https://joylink.club/oss/models/signal/d3d.FBX"
+}
+
+let defaulttrain = {
+ id:"9",
+ name:"6节列车",
+ deviceType:"train",
+ type:"num4",
+ picUrl:"",
+ assetUrl:"https://joylink.club/oss/models/train/train.FBX"
+}
+
+
+let defaultstation = {
+ id:"8",
+ name:"福州车站",
+ deviceType:"stand",
+ type:"num4",
+ picUrl:"",
+ assetUrl:"https://joylink.club/oss/models/station/fuzhou/fuzhou.FBX"
+}//https://joylink.club/oss/models/station/fuzhou/fuzhou.FBX
+//../../static/model/station/zhantai715(2).FBX
+
+let defaultswitch = {
+ id:"11",
+ name:"道岔",
+ deviceType:"switch",
+ type:"fuzhou",
+ picUrl:"",
+ assetUrl:"../../static/model/daocha/daocha.FBX"
+}
+
+let defaultdriver = {
+ id:"15",
+ name:"驾驶台",
+ deviceType:"driver",
+ type:"driver",
+ picUrl:"",
+ assetUrl:"https://joylink.club/oss/models/driver/driver.FBX"
+}
+
+let defaultsuidao = {
+ id:"16",
+ name:"suidao",
+ deviceType:"suidao",
+ type:"suidao",
+ picUrl:"",
+ assetUrl:"https://joylink.club/oss/models/suidao/suidao.FBX"
+}//https://joylink.club/oss/models/suidao/suidao.FBX
+//../../static/model/
+
+
+
+
+
+export function AssetLoader(){
+
+ let scope = this;
+
+ this.name = null;
+
+ this.modellist = [];
+
+ this.materiallist = [];
+ //初始化资源加载器
+ this.setmodellist = function (data){
+
+ let defaultmodel1 = new AssetModel(defaultsignal);
+ scope.modellist.push(defaultmodel1);
+
+ let defaultmodel2 = new AssetModel(defaulttrain);
+ scope.modellist.push(defaultmodel2);
+
+ let defaultmodel3 = new AssetModel(defaultstation);
+ scope.modellist.push(defaultmodel3);
+
+ let defaultmodel4 = new AssetModel(defaultswitch);
+ scope.modellist.push(defaultmodel4);
+
+ let driver = new AssetModel(defaultdriver);
+ scope.modellist.push(driver);
+
+ let suidao = new AssetModel(defaultsuidao);
+ scope.modellist.push(suidao);
+
+
+ let modeldata = JSON.parse(data);
+
+ for(let j=0;j {
+ ////console.log(result);
+ resolve("loaderassets"); //['成功了', 'success']
+ }).catch((error) => {
+ //console.log(error);
+ });
+
+ });
+ }
+
+ this.assetinit = function(scene){
+ return new Promise(function(resolve, reject){
+ let signal = new AssetModel(defaultsignal);
+ scope.modellist.push(signal);
+
+ let train = new AssetModel(defaulttrain);
+ scope.modellist.push(train);
+
+ let station = new AssetModel(defaultstation);
+ scope.modellist.push(station);
+
+ fbxpromise(signal)
+ .then(function(data){
+ ////console.log(data);
+ return fbxpromise(train);
+ })
+ .then(function(data){
+ ////console.log(data);
+ return fbxpromise(station);
+ })
+ .then(function(data){
+ ////console.log(scope.modellist);
+ resolve("loaderassets");
+ });
+ });
+ }
+
+ this.loadnewsection = function(newdata,jlmap3dedit){
+ let loadingInstance = Loading.service({ fullscreen: true });
+ let scene = jlmap3dedit.scene;
+ let newmodel = new AssetModel(newdata);
+ let jlmap3ddata = jlmap3dedit.mapdata;
+ scope.modellist.push(newmodel);
+
+ scene.remove(jlmap3dedit.exportmodel);
+ var loader = new THREE.FBXLoader();
+ loader.load( newdata.assetUrl, function ( object ) {
+
+ let newmesh = object;
+
+ //let mixer = new THREE.AnimationMixer( newmesh );
+ ////console.log(jlmap3ddata.sectionlist);
+ for(let i=0;i0;j--){
+ let name = "c"+j;
+ for(let i=0;i> 8 ) & 0xff,
+ ( value >> 16 ) & 0xff,
+ ( value >> 24 ) & 0xff
+ );
+
+ }
+
+ function loadARGBMip( buffer, dataOffset, width, height ) {
+
+ var dataLength = width * height * 4;
+ var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength );
+ var byteArray = new Uint8Array( dataLength );
+ var dst = 0;
+ var src = 0;
+ for ( var y = 0; y < height; y ++ ) {
+
+ for ( var x = 0; x < width; x ++ ) {
+
+ var b = srcBuffer[ src ]; src ++;
+ var g = srcBuffer[ src ]; src ++;
+ var r = srcBuffer[ src ]; src ++;
+ var a = srcBuffer[ src ]; src ++;
+ byteArray[ dst ] = r; dst ++; //r
+ byteArray[ dst ] = g; dst ++; //g
+ byteArray[ dst ] = b; dst ++; //b
+ byteArray[ dst ] = a; dst ++; //a
+
+ }
+
+ }
+ return byteArray;
+
+ }
+
+ var FOURCC_DXT1 = fourCCToInt32( "DXT1" );
+ var FOURCC_DXT3 = fourCCToInt32( "DXT3" );
+ var FOURCC_DXT5 = fourCCToInt32( "DXT5" );
+ var FOURCC_ETC1 = fourCCToInt32( "ETC1" );
+
+ var headerLengthInt = 31; // The header length in 32 bit ints
+
+ // Offsets into the header array
+
+ var off_magic = 0;
+
+ var off_size = 1;
+ var off_flags = 2;
+ var off_height = 3;
+ var off_width = 4;
+
+ var off_mipmapCount = 7;
+
+ var off_pfFlags = 20;
+ var off_pfFourCC = 21;
+ var off_RGBBitCount = 22;
+ var off_RBitMask = 23;
+ var off_GBitMask = 24;
+ var off_BBitMask = 25;
+ var off_ABitMask = 26;
+
+ var off_caps = 27;
+ var off_caps2 = 28;
+ var off_caps3 = 29;
+ var off_caps4 = 30;
+
+ // Parse header
+
+ var header = new Int32Array( buffer, 0, headerLengthInt );
+
+ if ( header[ off_magic ] !== DDS_MAGIC ) {
+
+ console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' );
+ return dds;
+
+ }
+
+ if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
+
+ console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' );
+ return dds;
+
+ }
+
+ var blockBytes;
+
+ var fourCC = header[ off_pfFourCC ];
+
+ var isRGBAUncompressed = false;
+
+ switch ( fourCC ) {
+
+ case FOURCC_DXT1:
+
+ blockBytes = 8;
+ dds.format = THREE.RGB_S3TC_DXT1_Format;
+ break;
+
+ case FOURCC_DXT3:
+
+ blockBytes = 16;
+ dds.format = THREE.RGBA_S3TC_DXT3_Format;
+ break;
+
+ case FOURCC_DXT5:
+
+ blockBytes = 16;
+ dds.format = THREE.RGBA_S3TC_DXT5_Format;
+ break;
+
+ case FOURCC_ETC1:
+
+ blockBytes = 8;
+ dds.format = THREE.RGB_ETC1_Format;
+ break;
+
+ default:
+
+ if ( header[ off_RGBBitCount ] === 32
+ && header[ off_RBitMask ] & 0xff0000
+ && header[ off_GBitMask ] & 0xff00
+ && header[ off_BBitMask ] & 0xff
+ && header[ off_ABitMask ] & 0xff000000 ) {
+
+ isRGBAUncompressed = true;
+ blockBytes = 64;
+ dds.format = THREE.RGBAFormat;
+
+ } else {
+
+ console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) );
+ return dds;
+
+ }
+
+ }
+
+ dds.mipmapCount = 1;
+
+ if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
+
+ dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
+
+ }
+
+ var caps2 = header[ off_caps2 ];
+ dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false;
+ if ( dds.isCubemap && (
+ ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) ||
+ ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) ||
+ ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) ||
+ ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) ||
+ ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) ||
+ ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ )
+ ) ) {
+
+ console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' );
+ return dds;
+
+ }
+
+ dds.width = header[ off_width ];
+ dds.height = header[ off_height ];
+
+ var dataOffset = header[ off_size ] + 4;
+
+ // Extract mipmaps buffers
+
+ var faces = dds.isCubemap ? 6 : 1;
+
+ for ( var face = 0; face < faces; face ++ ) {
+
+ var width = dds.width;
+ var height = dds.height;
+
+ for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+ if ( isRGBAUncompressed ) {
+
+ var byteArray = loadARGBMip( buffer, dataOffset, width, height );
+ var dataLength = byteArray.length;
+
+ } else {
+
+ var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
+ var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
+
+ }
+
+ var mipmap = { "data": byteArray, "width": width, "height": height };
+ dds.mipmaps.push( mipmap );
+
+ dataOffset += dataLength;
+
+ width = Math.max( width >> 1, 1 );
+ height = Math.max( height >> 1, 1 );
+
+ }
+
+ }
+
+ return dds;
+
+};
diff --git a/src/jlmap3d/main/loaders/FBXLoader.js b/src/jlmap3d/main/loaders/FBXLoader.js
new file mode 100644
index 000000000..d48df4bcc
--- /dev/null
+++ b/src/jlmap3d/main/loaders/FBXLoader.js
@@ -0,0 +1,4134 @@
+
+/**
+ * @author Kyle-Larson https://github.com/Kyle-Larson
+ * @author Takahiro https://github.com/takahirox
+ * @author Lewy Blue https://github.com/looeee
+ *
+ * Loader loads FBX file and generates Group representing FBX scene.
+ * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format
+ * Versions lower than this may load but will probably have errors
+ *
+ * Needs Support:
+ * Morph normals / blend shape normals
+ *
+ * FBX format references:
+ * https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
+ * http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference)
+ *
+ * Binary format specification:
+ * https://code.blender.org/2013/08/fbx-binary-file-format-specification/
+ */
+
+
+THREE.FBXLoader = ( function () {
+
+ var fbxTree;
+ var connections;
+ var sceneGraph;
+
+ function FBXLoader( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+ }
+
+ FBXLoader.prototype = {
+
+ constructor: FBXLoader,
+
+ crossOrigin: 'anonymous',
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var self = this;
+
+ var path = ( self.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : self.path;
+
+ var loader = new THREE.FileLoader( this.manager );
+ loader.setPath( self.path );
+ loader.setResponseType( 'arraybuffer' );
+
+ loader.load( url, function ( buffer ) {
+
+ try {
+
+ onLoad( self.parse( buffer, path ) );
+
+ } catch ( error ) {
+
+ setTimeout( function () {
+
+ if ( onError ) onError( error );
+
+ self.manager.itemError( url );
+
+ }, 0 );
+
+ }
+
+ }, onProgress, onError );
+
+ },
+
+ setPath: function ( value ) {
+
+ this.path = value;
+ return this;
+
+ },
+
+ setResourcePath: function ( value ) {
+
+ this.resourcePath = value;
+ return this;
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+ return this;
+
+ },
+
+ parse: function ( FBXBuffer, path ) {
+
+ if ( isFbxFormatBinary( FBXBuffer ) ) {
+
+ fbxTree = new BinaryParser().parse( FBXBuffer );
+
+ } else {
+
+ var FBXText = convertArrayBufferToString( FBXBuffer );
+
+ if ( ! isFbxFormatASCII( FBXText ) ) {
+
+ throw new Error( 'THREE.FBXLoader: Unknown format.' );
+
+ }
+
+ if ( getFbxVersion( FBXText ) < 7000 ) {
+
+ throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion( FBXText ) );
+
+ }
+
+ fbxTree = new TextParser().parse( FBXText );
+
+ }
+
+ var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( 'anonymous' );
+
+ return new FBXTreeParser( textureLoader ).parse( fbxTree );
+
+ }
+
+ };
+
+ // Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group
+ function FBXTreeParser( textureLoader ) {
+
+ this.textureLoader = textureLoader;
+
+ }
+
+ FBXTreeParser.prototype = {
+
+ constructor: FBXTreeParser,
+
+ parse: function () {
+
+ connections = this.parseConnections();
+
+ var images = this.parseImages();
+ var textures = this.parseTextures( images );
+ var materials = this.parseMaterials( textures );
+ var deformers = this.parseDeformers();
+ var geometryMap = new GeometryParser().parse( deformers );
+
+ this.parseScene( deformers, geometryMap, materials );
+
+ return sceneGraph;
+
+ },
+
+ // Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry )
+ // and details the connection type
+ parseConnections: function () {
+
+ var connectionMap = new Map();
+
+ if ( 'Connections' in fbxTree ) {
+
+ var rawConnections = fbxTree.Connections.connections;
+
+ rawConnections.forEach( function ( rawConnection ) {
+
+ var fromID = rawConnection[ 0 ];
+ var toID = rawConnection[ 1 ];
+ var relationship = rawConnection[ 2 ];
+
+ if ( ! connectionMap.has( fromID ) ) {
+
+ connectionMap.set( fromID, {
+ parents: [],
+ children: []
+ } );
+
+ }
+
+ var parentRelationship = { ID: toID, relationship: relationship };
+ connectionMap.get( fromID ).parents.push( parentRelationship );
+
+ if ( ! connectionMap.has( toID ) ) {
+
+ connectionMap.set( toID, {
+ parents: [],
+ children: []
+ } );
+
+ }
+
+ var childRelationship = { ID: fromID, relationship: relationship };
+ connectionMap.get( toID ).children.push( childRelationship );
+
+ } );
+
+ }
+
+ return connectionMap;
+
+ },
+
+ // Parse FBXTree.Objects.Video for embedded image data
+ // These images are connected to textures in FBXTree.Objects.Textures
+ // via FBXTree.Connections.
+ parseImages: function () {
+
+ var images = {};
+ var blobs = {};
+
+ if ( 'Video' in fbxTree.Objects ) {
+
+ var videoNodes = fbxTree.Objects.Video;
+
+ for ( var nodeID in videoNodes ) {
+
+ var videoNode = videoNodes[ nodeID ];
+
+ var id = parseInt( nodeID );
+
+ images[ id ] = videoNode.RelativeFilename || videoNode.Filename;
+
+ // raw image data is in videoNode.Content
+ if ( 'Content' in videoNode ) {
+
+ var arrayBufferContent = ( videoNode.Content instanceof ArrayBuffer ) && ( videoNode.Content.byteLength > 0 );
+ var base64Content = ( typeof videoNode.Content === 'string' ) && ( videoNode.Content !== '' );
+
+ if ( arrayBufferContent || base64Content ) {
+
+ var image = this.parseImage( videoNodes[ nodeID ] );
+
+ blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ for ( var id in images ) {
+
+ var filename = images[ id ];
+
+ if ( blobs[ filename ] !== undefined ) images[ id ] = blobs[ filename ];
+ else images[ id ] = images[ id ].split( '\\' ).pop();
+
+ }
+
+ return images;
+
+ },
+
+ // Parse embedded image data in FBXTree.Video.Content
+ parseImage: function ( videoNode ) {
+
+ var content = videoNode.Content;
+ var fileName = videoNode.RelativeFilename || videoNode.Filename;
+ var extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase();
+
+ var type;
+
+ switch ( extension ) {
+
+ case 'bmp':
+
+ type = 'image/bmp';
+ break;
+
+ case 'jpg':
+ case 'jpeg':
+
+ type = 'image/jpeg';
+ break;
+
+ case 'png':
+
+ type = 'image/png';
+ break;
+
+ case 'tif':
+
+ type = 'image/tiff';
+ break;
+
+ case 'tga':
+
+ if ( typeof THREE.TGALoader !== 'function' ) {
+
+ console.warn( 'FBXLoader: THREE.TGALoader is required to load TGA textures' );
+ return;
+
+ } else {
+
+ if ( THREE.Loader.Handlers.get( '.tga' ) === null ) {
+
+ var tgaLoader = new THREE.TGALoader();
+ tgaLoader.setPath( this.textureLoader.path );
+
+ THREE.Loader.Handlers.add( /\.tga$/i, tgaLoader );
+
+ }
+
+ type = 'image/tga';
+ break;
+
+ }
+
+ default:
+
+ console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' );
+ return;
+
+ }
+
+ if ( typeof content === 'string' ) { // ASCII format
+
+ return 'data:' + type + ';base64,' + content;
+
+ } else { // Binary Format
+
+ var array = new Uint8Array( content );
+ return window.URL.createObjectURL( new Blob( [ array ], { type: type } ) );
+
+ }
+
+ },
+
+ // Parse nodes in FBXTree.Objects.Texture
+ // These contain details such as UV scaling, cropping, rotation etc and are connected
+ // to images in FBXTree.Objects.Video
+ parseTextures: function ( images ) {
+
+ var textureMap = new Map();
+
+ if ( 'Texture' in fbxTree.Objects ) {
+
+ var textureNodes = fbxTree.Objects.Texture;
+ for ( var nodeID in textureNodes ) {
+
+ var texture = this.parseTexture( textureNodes[ nodeID ], images );
+ textureMap.set( parseInt( nodeID ), texture );
+
+ }
+
+ }
+
+ return textureMap;
+
+ },
+
+ // Parse individual node in FBXTree.Objects.Texture
+ parseTexture: function ( textureNode, images ) {
+
+ var texture = this.loadTexture( textureNode, images );
+
+ texture.ID = textureNode.id;
+
+ texture.name = textureNode.attrName;
+
+ var wrapModeU = textureNode.WrapModeU;
+ var wrapModeV = textureNode.WrapModeV;
+
+ var valueU = wrapModeU !== undefined ? wrapModeU.value : 0;
+ var valueV = wrapModeV !== undefined ? wrapModeV.value : 0;
+
+ // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a
+ // 0: repeat(default), 1: clamp
+
+ texture.wrapS = valueU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
+ texture.wrapT = valueV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
+
+ if ( 'Scaling' in textureNode ) {
+
+ var values = textureNode.Scaling.value;
+
+ texture.repeat.x = values[ 0 ];
+ texture.repeat.y = values[ 1 ];
+
+ }
+
+ return texture;
+
+ },
+
+ // load a texture specified as a blob or data URI, or via an external URL using THREE.TextureLoader
+ loadTexture: function ( textureNode, images ) {
+
+ var fileName;
+
+ var currentPath = this.textureLoader.path;
+
+ var children = connections.get( textureNode.id ).children;
+
+ if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) {
+
+ fileName = images[ children[ 0 ].ID ];
+
+ if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) {
+
+ this.textureLoader.setPath( undefined );
+
+ }
+
+ }
+
+ var texture;
+
+ var extension = textureNode.FileName.slice( - 3 ).toLowerCase();
+
+ if ( extension === 'tga' ) {
+
+ var loader = THREE.Loader.Handlers.get( '.tga' );
+
+ if ( loader === null ) {
+
+ console.warn( 'FBXLoader: TGALoader not found, creating empty placeholder texture for', fileName );
+ texture = new THREE.Texture();
+
+ } else {
+
+ texture = loader.load( fileName );
+
+ }
+
+ } else if ( extension === 'psd' ) {
+
+ console.warn( 'FBXLoader: PSD textures are not supported, creating empty placeholder texture for', fileName );
+ texture = new THREE.Texture();
+
+ } else {
+
+ texture = this.textureLoader.load( fileName );
+
+ }
+
+ this.textureLoader.setPath( currentPath );
+
+ return texture;
+
+ },
+
+ // Parse nodes in FBXTree.Objects.Material
+ parseMaterials: function ( textureMap ) {
+
+ var materialMap = new Map();
+
+ if ( 'Material' in fbxTree.Objects ) {
+
+ var materialNodes = fbxTree.Objects.Material;
+
+ for ( var nodeID in materialNodes ) {
+
+ var material = this.parseMaterial( materialNodes[ nodeID ], textureMap );
+
+ if ( material !== null ) materialMap.set( parseInt( nodeID ), material );
+
+ }
+
+ }
+
+ return materialMap;
+
+ },
+
+ // Parse single node in FBXTree.Objects.Material
+ // Materials are connected to texture maps in FBXTree.Objects.Textures
+ // FBX format currently only supports Lambert and Phong shading models
+ parseMaterial: function ( materialNode, textureMap ) {
+
+ var ID = materialNode.id;
+ var name = materialNode.attrName;
+ var type = materialNode.ShadingModel;
+
+ // Case where FBX wraps shading model in property object.
+ if ( typeof type === 'object' ) {
+
+ type = type.value;
+
+ }
+
+ // Ignore unused materials which don't have any connections.
+ if ( ! connections.has( ID ) ) return null;
+
+ var parameters = this.parseParameters( materialNode, textureMap, ID );
+
+ var material;
+
+ switch ( type.toLowerCase() ) {
+
+ case 'phong':
+ material = new THREE.MeshPhongMaterial();
+ break;
+ case 'lambert':
+ material = new THREE.MeshLambertMaterial();
+ break;
+ default:
+ console.warn( 'THREE.FBXLoader: unknown material type "%s". Defaulting to MeshPhongMaterial.', type );
+ material = new THREE.MeshPhongMaterial();
+ break;
+
+ }
+
+ material.setValues( parameters );
+ material.name = name;
+
+ return material;
+
+ },
+
+ // Parse FBX material and return parameters suitable for a three.js material
+ // Also parse the texture map and return any textures associated with the material
+ parseParameters: function ( materialNode, textureMap, ID ) {
+
+ var parameters = {};
+
+ if ( materialNode.BumpFactor ) {
+
+ parameters.bumpScale = materialNode.BumpFactor.value;
+
+ }
+ if ( materialNode.Diffuse ) {
+
+ parameters.color = new THREE.Color().fromArray( materialNode.Diffuse.value );
+
+ } else if ( materialNode.DiffuseColor && materialNode.DiffuseColor.type === 'Color' ) {
+
+ // The blender exporter exports diffuse here instead of in materialNode.Diffuse
+ parameters.color = new THREE.Color().fromArray( materialNode.DiffuseColor.value );
+
+ }
+
+ if ( materialNode.DisplacementFactor ) {
+
+ parameters.displacementScale = materialNode.DisplacementFactor.value;
+
+ }
+
+ if ( materialNode.Emissive ) {
+
+ parameters.emissive = new THREE.Color().fromArray( materialNode.Emissive.value );
+
+ } else if ( materialNode.EmissiveColor && materialNode.EmissiveColor.type === 'Color' ) {
+
+ // The blender exporter exports emissive color here instead of in materialNode.Emissive
+ parameters.emissive = new THREE.Color().fromArray( materialNode.EmissiveColor.value );
+
+ }
+
+ if ( materialNode.EmissiveFactor ) {
+
+ parameters.emissiveIntensity = parseFloat( materialNode.EmissiveFactor.value );
+
+ }
+
+ if ( materialNode.Opacity ) {
+
+ parameters.opacity = parseFloat( materialNode.Opacity.value );
+
+ }
+
+ if ( parameters.opacity < 1.0 ) {
+
+ parameters.transparent = true;
+ }
+
+ if ( materialNode.ReflectionFactor ) {
+
+ parameters.reflectivity = materialNode.ReflectionFactor.value;
+
+ }
+
+ if ( materialNode.Shininess ) {
+
+ parameters.shininess = materialNode.Shininess.value;
+
+ }
+
+ if ( materialNode.Specular ) {
+
+ parameters.specular = new THREE.Color().fromArray( materialNode.Specular.value );
+
+ } else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) {
+
+ // The blender exporter exports specular color here instead of in materialNode.Specular
+ parameters.specular = new THREE.Color().fromArray( materialNode.SpecularColor.value );
+
+ }
+
+ var self = this;
+ connections.get( ID ).children.forEach( function ( child ) {
+
+ var type = child.relationship;
+
+ switch ( type ) {
+
+ case 'Bump':
+ parameters.bumpMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'Maya|TEX_ao_map':
+ parameters.aoMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'DiffuseColor':
+ case 'Maya|TEX_color_map':
+ parameters.map = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'DisplacementColor':
+ parameters.displacementMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'EmissiveColor':
+ parameters.emissiveMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'NormalMap':
+ case 'Maya|TEX_normal_map':
+ parameters.normalMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'ReflectionColor':
+ parameters.envMap = self.getTexture( textureMap, child.ID );
+ parameters.envMap.mapping = THREE.EquirectangularReflectionMapping;
+ break;
+
+ case 'SpecularColor':
+ parameters.specularMap = self.getTexture( textureMap, child.ID );
+ break;
+
+ case 'TransparentColor':
+ parameters.alphaMap = self.getTexture( textureMap, child.ID );
+ parameters.transparent = true;
+ break;
+
+ case 'AmbientColor':
+ case 'ShininessExponent': // AKA glossiness map
+ case 'SpecularFactor': // AKA specularLevel
+ case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor
+ default:
+ console.warn( 'THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type );
+ break;
+
+ }
+
+ } );
+
+ return parameters;
+
+ },
+
+ // get a texture from the textureMap for use by a material.
+ getTexture: function ( textureMap, id ) {
+
+ // if the texture is a layered texture, just use the first layer and issue a warning
+ if ( 'LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture ) {
+
+ console.warn( 'THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.' );
+ id = connections.get( id ).children[ 0 ].ID;
+
+ }
+
+ return textureMap.get( id );
+
+ },
+
+ // Parse nodes in FBXTree.Objects.Deformer
+ // Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here
+ // Generates map of Skeleton-like objects for use later when generating and binding skeletons.
+ parseDeformers: function () {
+
+ var skeletons = {};
+ var morphTargets = {};
+
+ if ( 'Deformer' in fbxTree.Objects ) {
+
+ var DeformerNodes = fbxTree.Objects.Deformer;
+
+ for ( var nodeID in DeformerNodes ) {
+
+ var deformerNode = DeformerNodes[ nodeID ];
+
+ var relationships = connections.get( parseInt( nodeID ) );
+
+ if ( deformerNode.attrType === 'Skin' ) {
+
+ var skeleton = this.parseSkeleton( relationships, DeformerNodes );
+ skeleton.ID = nodeID;
+
+ if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: skeleton attached to more than one geometry is not supported.' );
+ skeleton.geometryID = relationships.parents[ 0 ].ID;
+
+ skeletons[ nodeID ] = skeleton;
+
+ } else if ( deformerNode.attrType === 'BlendShape' ) {
+
+ var morphTarget = {
+ id: nodeID,
+ };
+
+ morphTarget.rawTargets = this.parseMorphTargets( relationships, DeformerNodes );
+ morphTarget.id = nodeID;
+
+ if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' );
+
+ morphTargets[ nodeID ] = morphTarget;
+
+ }
+
+ }
+
+ }
+
+ return {
+
+ skeletons: skeletons,
+ morphTargets: morphTargets,
+
+ };
+
+ },
+
+ // Parse single nodes in FBXTree.Objects.Deformer
+ // The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster'
+ // Each skin node represents a skeleton and each cluster node represents a bone
+ parseSkeleton: function ( relationships, deformerNodes ) {
+
+ var rawBones = [];
+
+ relationships.children.forEach( function ( child ) {
+
+ var boneNode = deformerNodes[ child.ID ];
+
+ if ( boneNode.attrType !== 'Cluster' ) return;
+
+ var rawBone = {
+
+ ID: child.ID,
+ indices: [],
+ weights: [],
+ transformLink: new THREE.Matrix4().fromArray( boneNode.TransformLink.a ),
+ // transform: new THREE.Matrix4().fromArray( boneNode.Transform.a ),
+ // linkMode: boneNode.Mode,
+
+ };
+
+ if ( 'Indexes' in boneNode ) {
+
+ rawBone.indices = boneNode.Indexes.a;
+ rawBone.weights = boneNode.Weights.a;
+
+ }
+
+ rawBones.push( rawBone );
+
+ } );
+
+ return {
+
+ rawBones: rawBones,
+ bones: []
+
+ };
+
+ },
+
+ // The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel"
+ parseMorphTargets: function ( relationships, deformerNodes ) {
+
+ var rawMorphTargets = [];
+
+ for ( var i = 0; i < relationships.children.length; i ++ ) {
+
+ var child = relationships.children[ i ];
+
+ var morphTargetNode = deformerNodes[ child.ID ];
+
+ var rawMorphTarget = {
+
+ name: morphTargetNode.attrName,
+ initialWeight: morphTargetNode.DeformPercent,
+ id: morphTargetNode.id,
+ fullWeights: morphTargetNode.FullWeights.a
+
+ };
+
+ if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return;
+
+ rawMorphTarget.geoID = connections.get( parseInt( child.ID ) ).children.filter( function ( child ) {
+
+ return child.relationship === undefined;
+
+ } )[ 0 ].ID;
+
+ rawMorphTargets.push( rawMorphTarget );
+
+ }
+
+ return rawMorphTargets;
+
+ },
+
+ // create the main THREE.Group() to be returned by the loader
+ parseScene: function ( deformers, geometryMap, materialMap ) {
+
+ sceneGraph = new THREE.Group();
+
+ var modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap );
+
+ var modelNodes = fbxTree.Objects.Model;
+
+ var self = this;
+ modelMap.forEach( function ( model ) {
+
+ var modelNode = modelNodes[ model.ID ];
+ self.setLookAtProperties( model, modelNode );
+
+ var parentConnections = connections.get( model.ID ).parents;
+
+ parentConnections.forEach( function ( connection ) {
+
+ var parent = modelMap.get( connection.ID );
+ if ( parent !== undefined ) parent.add( model );
+
+ } );
+
+ if ( model.parent === null ) {
+
+ sceneGraph.add( model );
+
+ }
+
+
+ } );
+
+ this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
+
+ this.createAmbientLight();
+
+ this.setupMorphMaterials();
+
+ sceneGraph.traverse( function ( node ) {
+
+ if ( node.userData.transformData ) {
+
+ if ( node.parent ) node.userData.transformData.parentMatrixWorld = node.parent.matrix;
+
+ var transform = generateTransform( node.userData.transformData );
+
+ node.applyMatrix( transform );
+
+ }
+
+ } );
+
+ var animations = new AnimationParser().parse();
+
+ // if all the models where already combined in a single group, just return that
+ if ( sceneGraph.children.length === 1 && sceneGraph.children[ 0 ].isGroup ) {
+
+ sceneGraph.children[ 0 ].animations = animations;
+ sceneGraph = sceneGraph.children[ 0 ];
+
+ }
+
+ sceneGraph.animations = animations;
+
+ },
+
+ // parse nodes in FBXTree.Objects.Model
+ parseModels: function ( skeletons, geometryMap, materialMap ) {
+
+ var modelMap = new Map();
+ var modelNodes = fbxTree.Objects.Model;
+
+ for ( var nodeID in modelNodes ) {
+
+ var id = parseInt( nodeID );
+ var node = modelNodes[ nodeID ];
+ var relationships = connections.get( id );
+
+ var model = this.buildSkeleton( relationships, skeletons, id, node.attrName );
+
+ if ( ! model ) {
+
+ switch ( node.attrType ) {
+
+ case 'Camera':
+ model = this.createCamera( relationships );
+ break;
+ case 'Light':
+ model = this.createLight( relationships );
+ break;
+ case 'Mesh':
+ model = this.createMesh( relationships, geometryMap, materialMap );
+ break;
+ case 'NurbsCurve':
+ model = this.createCurve( relationships, geometryMap );
+ break;
+ case 'LimbNode':
+ case 'Root':
+ model = new THREE.Bone();
+ break;
+ case 'Null':
+ default:
+ model = new THREE.Group();
+ break;
+
+ }
+
+ model.name = THREE.PropertyBinding.sanitizeNodeName( node.attrName );
+ model.ID = id;
+
+ }
+
+ this.getTransformData( model, node );
+ modelMap.set( id, model );
+
+ }
+
+ return modelMap;
+
+ },
+
+ buildSkeleton: function ( relationships, skeletons, id, name ) {
+
+ var bone = null;
+
+ relationships.parents.forEach( function ( parent ) {
+
+ for ( var ID in skeletons ) {
+
+ var skeleton = skeletons[ ID ];
+
+ skeleton.rawBones.forEach( function ( rawBone, i ) {
+
+ if ( rawBone.ID === parent.ID ) {
+
+ var subBone = bone;
+ bone = new THREE.Bone();
+
+ bone.matrixWorld.copy( rawBone.transformLink );
+
+ // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id
+ bone.name = THREE.PropertyBinding.sanitizeNodeName( name );
+ bone.ID = id;
+
+ skeleton.bones[ i ] = bone;
+
+ // In cases where a bone is shared between multiple meshes
+ // duplicate the bone here and and it as a child of the first bone
+ if ( subBone !== null ) {
+
+ bone.add( subBone );
+
+ }
+
+ }
+
+ } );
+
+ }
+
+ } );
+
+ return bone;
+
+ },
+
+ // create a THREE.PerspectiveCamera or THREE.OrthographicCamera
+ createCamera: function ( relationships ) {
+
+ var model;
+ var cameraAttribute;
+
+ relationships.children.forEach( function ( child ) {
+
+ var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
+
+ if ( attr !== undefined ) {
+
+ cameraAttribute = attr;
+
+ }
+
+ } );
+
+ if ( cameraAttribute === undefined ) {
+
+ model = new THREE.Object3D();
+
+ } else {
+
+ var type = 0;
+ if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) {
+
+ type = 1;
+
+ }
+
+ var nearClippingPlane = 1;
+ if ( cameraAttribute.NearPlane !== undefined ) {
+
+ nearClippingPlane = cameraAttribute.NearPlane.value / 1000;
+
+ }
+
+ var farClippingPlane = 1000;
+ if ( cameraAttribute.FarPlane !== undefined ) {
+
+ farClippingPlane = cameraAttribute.FarPlane.value / 1000;
+
+ }
+
+
+ var width = window.innerWidth;
+ var height = window.innerHeight;
+
+ if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) {
+
+ width = cameraAttribute.AspectWidth.value;
+ height = cameraAttribute.AspectHeight.value;
+
+ }
+
+ var aspect = width / height;
+
+ var fov = 45;
+ if ( cameraAttribute.FieldOfView !== undefined ) {
+
+ fov = cameraAttribute.FieldOfView.value;
+
+ }
+
+ var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null;
+
+ switch ( type ) {
+
+ case 0: // Perspective
+ model = new THREE.PerspectiveCamera( fov, aspect, nearClippingPlane, farClippingPlane );
+ if ( focalLength !== null ) model.setFocalLength( focalLength );
+ break;
+
+ case 1: // Orthographic
+ model = new THREE.OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane );
+ break;
+
+ default:
+ console.warn( 'THREE.FBXLoader: Unknown camera type ' + type + '.' );
+ model = new THREE.Object3D();
+ break;
+
+ }
+
+ }
+
+ return model;
+
+ },
+
+ // Create a THREE.DirectionalLight, THREE.PointLight or THREE.SpotLight
+ createLight: function ( relationships ) {
+
+ var model;
+ var lightAttribute;
+
+ relationships.children.forEach( function ( child ) {
+
+ var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
+
+ if ( attr !== undefined ) {
+
+ lightAttribute = attr;
+
+ }
+
+ } );
+
+ if ( lightAttribute === undefined ) {
+
+ model = new THREE.Object3D();
+
+ } else {
+
+ var type;
+
+ // LightType can be undefined for Point lights
+ if ( lightAttribute.LightType === undefined ) {
+
+ type = 0;
+
+ } else {
+
+ type = lightAttribute.LightType.value;
+
+ }
+
+ var color = 0xffffff;
+
+ if ( lightAttribute.Color !== undefined ) {
+
+ color = new THREE.Color().fromArray( lightAttribute.Color.value );
+
+ }
+
+ var intensity = ( lightAttribute.Intensity === undefined ) ? 1 : lightAttribute.Intensity.value / 100;
+
+ // light disabled
+ if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) {
+
+ intensity = 0;
+
+ }
+
+ var distance = 0;
+ if ( lightAttribute.FarAttenuationEnd !== undefined ) {
+
+ if ( lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0 ) {
+
+ distance = 0;
+
+ } else {
+
+ distance = lightAttribute.FarAttenuationEnd.value;
+
+ }
+
+ }
+
+ // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd?
+ var decay = 1;
+
+ switch ( type ) {
+
+ case 0: // Point
+ model = new THREE.PointLight( color, intensity, distance, decay );
+ break;
+
+ case 1: // Directional
+ model = new THREE.DirectionalLight( color, intensity );
+ break;
+
+ case 2: // Spot
+ var angle = Math.PI / 3;
+
+ if ( lightAttribute.InnerAngle !== undefined ) {
+
+ angle = THREE.Math.degToRad( lightAttribute.InnerAngle.value );
+
+ }
+
+ var penumbra = 0;
+ if ( lightAttribute.OuterAngle !== undefined ) {
+
+ // TODO: this is not correct - FBX calculates outer and inner angle in degrees
+ // with OuterAngle > InnerAngle && OuterAngle <= Math.PI
+ // while three.js uses a penumbra between (0, 1) to attenuate the inner angle
+ penumbra = THREE.Math.degToRad( lightAttribute.OuterAngle.value );
+ penumbra = Math.max( penumbra, 1 );
+
+ }
+
+ model = new THREE.SpotLight( color, intensity, distance, angle, penumbra, decay );
+ break;
+
+ default:
+ console.warn( 'THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a THREE.PointLight.' );
+ model = new THREE.PointLight( color, intensity );
+ break;
+
+ }
+
+ if ( lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1 ) {
+
+ model.castShadow = true;
+
+ }
+
+ }
+
+ return model;
+
+ },
+
+ createMesh: function ( relationships, geometryMap, materialMap ) {
+
+ var model;
+ var geometry = null;
+ var material = null;
+ var materials = [];
+
+ // get geometry and materials(s) from connections
+ relationships.children.forEach( function ( child ) {
+
+ if ( geometryMap.has( child.ID ) ) {
+
+ geometry = geometryMap.get( child.ID );
+
+ }
+
+ if ( materialMap.has( child.ID ) ) {
+
+ materials.push( materialMap.get( child.ID ) );
+
+ }
+
+ } );
+
+ if ( materials.length > 1 ) {
+
+ material = materials;
+
+ } else if ( materials.length > 0 ) {
+
+ material = materials[ 0 ];
+
+ } else {
+
+ material = new THREE.MeshPhongMaterial( { color: 0xcccccc } );
+ materials.push( material );
+
+ }
+
+ if ( 'color' in geometry.attributes ) {
+
+ materials.forEach( function ( material ) {
+
+ material.vertexColors = THREE.VertexColors;
+
+ } );
+
+ }
+
+ if ( geometry.FBX_Deformer ) {
+
+ materials.forEach( function ( material ) {
+
+ material.skinning = true;
+
+ } );
+
+ model = new THREE.SkinnedMesh( geometry, material );
+ model.normalizeSkinWeights();
+
+ } else {
+
+ model = new THREE.Mesh( geometry, material );
+
+ }
+
+ return model;
+
+ },
+
+ createCurve: function ( relationships, geometryMap ) {
+
+ var geometry = relationships.children.reduce( function ( geo, child ) {
+
+ if ( geometryMap.has( child.ID ) ) geo = geometryMap.get( child.ID );
+
+ return geo;
+
+ }, null );
+
+ // FBX does not list materials for Nurbs lines, so we'll just put our own in here.
+ var material = new THREE.LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } );
+ return new THREE.Line( geometry, material );
+
+ },
+
+ // parse the model node for transform data
+ getTransformData: function ( model, modelNode ) {
+
+ var transformData = {};
+
+ if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
+
+ if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
+ else transformData.eulerOrder = 'ZYX';
+
+ if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
+
+ if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
+ if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
+ if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
+
+ if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
+
+ if ( 'ScalingOffset' in modelNode ) transformData.scalingOffset = modelNode.ScalingOffset.value;
+ if ( 'ScalingPivot' in modelNode ) transformData.scalingPivot = modelNode.ScalingPivot.value;
+
+ if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
+ if ( 'RotationPivot' in modelNode ) transformData.rotationPivot = modelNode.RotationPivot.value;
+
+ model.userData.transformData = transformData;
+
+ },
+
+ setLookAtProperties: function ( model, modelNode ) {
+
+ if ( 'LookAtProperty' in modelNode ) {
+
+ var children = connections.get( model.ID ).children;
+
+ children.forEach( function ( child ) {
+
+ if ( child.relationship === 'LookAtProperty' ) {
+
+ var lookAtTarget = fbxTree.Objects.Model[ child.ID ];
+
+ if ( 'Lcl_Translation' in lookAtTarget ) {
+
+ var pos = lookAtTarget.Lcl_Translation.value;
+
+ // DirectionalLight, SpotLight
+ if ( model.target !== undefined ) {
+
+ model.target.position.fromArray( pos );
+ sceneGraph.add( model.target );
+
+ } else { // Cameras and other Object3Ds
+
+ model.lookAt( new THREE.Vector3().fromArray( pos ) );
+
+ }
+
+ }
+
+ }
+
+ } );
+
+ }
+
+ },
+
+ bindSkeleton: function ( skeletons, geometryMap, modelMap ) {
+
+ var bindMatrices = this.parsePoseNodes();
+
+ for ( var ID in skeletons ) {
+
+ var skeleton = skeletons[ ID ];
+
+ var parents = connections.get( parseInt( skeleton.ID ) ).parents;
+
+ parents.forEach( function ( parent ) {
+
+ if ( geometryMap.has( parent.ID ) ) {
+
+ var geoID = parent.ID;
+ var geoRelationships = connections.get( geoID );
+
+ geoRelationships.parents.forEach( function ( geoConnParent ) {
+
+ if ( modelMap.has( geoConnParent.ID ) ) {
+
+ var model = modelMap.get( geoConnParent.ID );
+
+ model.bind( new THREE.Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] );
+
+ }
+
+ } );
+
+ }
+
+ } );
+
+ }
+
+ },
+
+ parsePoseNodes: function () {
+
+ var bindMatrices = {};
+
+ if ( 'Pose' in fbxTree.Objects ) {
+
+ var BindPoseNode = fbxTree.Objects.Pose;
+
+ for ( var nodeID in BindPoseNode ) {
+
+ if ( BindPoseNode[ nodeID ].attrType === 'BindPose' ) {
+
+ var poseNodes = BindPoseNode[ nodeID ].PoseNode;
+
+ if ( Array.isArray( poseNodes ) ) {
+
+ poseNodes.forEach( function ( poseNode ) {
+
+ bindMatrices[ poseNode.Node ] = new THREE.Matrix4().fromArray( poseNode.Matrix.a );
+
+ } );
+
+ } else {
+
+ bindMatrices[ poseNodes.Node ] = new THREE.Matrix4().fromArray( poseNodes.Matrix.a );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ return bindMatrices;
+
+ },
+
+ // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
+ createAmbientLight: function () {
+
+ if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) {
+
+ var ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
+ var r = ambientColor[ 0 ];
+ var g = ambientColor[ 1 ];
+ var b = ambientColor[ 2 ];
+
+ if ( r !== 0 || g !== 0 || b !== 0 ) {
+
+ var color = new THREE.Color( r, g, b );
+ sceneGraph.add( new THREE.AmbientLight( color, 1 ) );
+
+ }
+
+ }
+
+ },
+
+ setupMorphMaterials: function () {
+
+ var self = this;
+ sceneGraph.traverse( function ( child ) {
+
+ if ( child.isMesh ) {
+
+ if ( child.geometry.morphAttributes.position && child.geometry.morphAttributes.position.length ) {
+
+ if ( Array.isArray( child.material ) ) {
+
+ child.material.forEach( function ( material, i ) {
+
+ self.setupMorphMaterial( child, material, i );
+
+ } );
+
+ } else {
+
+ self.setupMorphMaterial( child, child.material );
+
+ }
+
+ }
+
+ }
+
+ } );
+
+ },
+
+ setupMorphMaterial: function ( child, material, index ) {
+
+ var uuid = child.uuid;
+ var matUuid = material.uuid;
+
+ // if a geometry has morph targets, it cannot share the material with other geometries
+ var sharedMat = false;
+
+ sceneGraph.traverse( function ( node ) {
+
+ if ( node.isMesh ) {
+
+ if ( Array.isArray( node.material ) ) {
+
+ node.material.forEach( function ( mat ) {
+
+ if ( mat.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
+
+ } );
+
+ } else if ( node.material.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
+
+ }
+
+ } );
+
+ if ( sharedMat === true ) {
+
+ var clonedMat = material.clone();
+ clonedMat.morphTargets = true;
+
+ if ( index === undefined ) child.material = clonedMat;
+ else child.material[ index ] = clonedMat;
+
+ } else material.morphTargets = true;
+
+ }
+
+ };
+
+ // parse Geometry data from FBXTree and return map of BufferGeometries
+ function GeometryParser() {}
+
+ GeometryParser.prototype = {
+
+ constructor: GeometryParser,
+
+ // Parse nodes in FBXTree.Objects.Geometry
+ parse: function ( deformers ) {
+
+ var geometryMap = new Map();
+
+ if ( 'Geometry' in fbxTree.Objects ) {
+
+ var geoNodes = fbxTree.Objects.Geometry;
+
+ for ( var nodeID in geoNodes ) {
+
+ var relationships = connections.get( parseInt( nodeID ) );
+ var geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers );
+
+ geometryMap.set( parseInt( nodeID ), geo );
+
+ }
+
+ }
+
+ return geometryMap;
+
+ },
+
+ // Parse single node in FBXTree.Objects.Geometry
+ parseGeometry: function ( relationships, geoNode, deformers ) {
+
+ switch ( geoNode.attrType ) {
+
+ case 'Mesh':
+ return this.parseMeshGeometry( relationships, geoNode, deformers );
+ break;
+
+ case 'NurbsCurve':
+ return this.parseNurbsGeometry( geoNode );
+ break;
+
+ }
+
+ },
+
+ // Parse single node mesh geometry in FBXTree.Objects.Geometry
+ parseMeshGeometry: function ( relationships, geoNode, deformers ) {
+
+ var skeletons = deformers.skeletons;
+ var morphTargets = deformers.morphTargets;
+
+ var modelNodes = relationships.parents.map( function ( parent ) {
+
+ return fbxTree.Objects.Model[ parent.ID ];
+
+ } );
+
+ // don't create geometry if it is not associated with any models
+ if ( modelNodes.length === 0 ) return;
+
+ var skeleton = relationships.children.reduce( function ( skeleton, child ) {
+
+ if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ];
+
+ return skeleton;
+
+ }, null );
+
+ var morphTarget = relationships.children.reduce( function ( morphTarget, child ) {
+
+ if ( morphTargets[ child.ID ] !== undefined ) morphTarget = morphTargets[ child.ID ];
+
+ return morphTarget;
+
+ }, null );
+
+ // Assume one model and get the preRotation from that
+ // if there is more than one model associated with the geometry this may cause problems
+ var modelNode = modelNodes[ 0 ];
+
+ var transformData = {};
+
+ if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
+ if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
+
+ if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value;
+ if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value;
+ if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value;
+
+ var transform = generateTransform( transformData );
+
+ return this.genGeometry( geoNode, skeleton, morphTarget, transform );
+
+ },
+
+ // Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry
+ genGeometry: function ( geoNode, skeleton, morphTarget, preTransform ) {
+
+ var geo = new THREE.BufferGeometry();
+ if ( geoNode.attrName ) geo.name = geoNode.attrName;
+
+ var geoInfo = this.parseGeoNode( geoNode, skeleton );
+ var buffers = this.genBuffers( geoInfo );
+
+ var positionAttribute = new THREE.Float32BufferAttribute( buffers.vertex, 3 );
+
+ preTransform.applyToBufferAttribute( positionAttribute );
+
+ geo.addAttribute( 'position', positionAttribute );
+
+ if ( buffers.colors.length > 0 ) {
+
+ geo.addAttribute( 'color', new THREE.Float32BufferAttribute( buffers.colors, 3 ) );
+
+ }
+
+ if ( skeleton ) {
+
+ geo.addAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( buffers.weightsIndices, 4 ) );
+
+ geo.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( buffers.vertexWeights, 4 ) );
+
+ // used later to bind the skeleton to the model
+ geo.FBX_Deformer = skeleton;
+
+ }
+
+ if ( buffers.normal.length > 0 ) {
+
+ var normalAttribute = new THREE.Float32BufferAttribute( buffers.normal, 3 );
+
+ var normalMatrix = new THREE.Matrix3().getNormalMatrix( preTransform );
+ normalMatrix.applyToBufferAttribute( normalAttribute );
+
+ geo.addAttribute( 'normal', normalAttribute );
+
+ }
+
+ buffers.uvs.forEach( function ( uvBuffer, i ) {
+
+ // subsequent uv buffers are called 'uv1', 'uv2', ...
+ var name = 'uv' + ( i + 1 ).toString();
+
+ // the first uv buffer is just called 'uv'
+ if ( i === 0 ) {
+
+ name = 'uv';
+
+ }
+
+ geo.addAttribute( name, new THREE.Float32BufferAttribute( buffers.uvs[ i ], 2 ) );
+
+ } );
+
+ if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+
+ // Convert the material indices of each vertex into rendering groups on the geometry.
+ var prevMaterialIndex = buffers.materialIndex[ 0 ];
+ var startIndex = 0;
+
+ buffers.materialIndex.forEach( function ( currentIndex, i ) {
+
+ if ( currentIndex !== prevMaterialIndex ) {
+
+ geo.addGroup( startIndex, i - startIndex, prevMaterialIndex );
+
+ prevMaterialIndex = currentIndex;
+ startIndex = i;
+
+ }
+
+ } );
+
+ // the loop above doesn't add the last group, do that here.
+ if ( geo.groups.length > 0 ) {
+
+ var lastGroup = geo.groups[ geo.groups.length - 1 ];
+ var lastIndex = lastGroup.start + lastGroup.count;
+
+ if ( lastIndex !== buffers.materialIndex.length ) {
+
+ geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex );
+
+ }
+
+ }
+
+ // case where there are multiple materials but the whole geometry is only
+ // using one of them
+ if ( geo.groups.length === 0 ) {
+
+ geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] );
+
+ }
+
+ }
+
+ this.addMorphTargets( geo, geoNode, morphTarget, preTransform );
+
+ return geo;
+
+ },
+
+ parseGeoNode: function ( geoNode, skeleton ) {
+
+ var geoInfo = {};
+
+ geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : [];
+ geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : [];
+
+ if ( geoNode.LayerElementColor ) {
+
+ geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] );
+
+ }
+
+ if ( geoNode.LayerElementMaterial ) {
+
+ geoInfo.material = this.parseMaterialIndices( geoNode.LayerElementMaterial[ 0 ] );
+
+ }
+
+ if ( geoNode.LayerElementNormal ) {
+
+ geoInfo.normal = this.parseNormals( geoNode.LayerElementNormal[ 0 ] );
+
+ }
+
+ if ( geoNode.LayerElementUV ) {
+
+ geoInfo.uv = [];
+
+ var i = 0;
+ while ( geoNode.LayerElementUV[ i ] ) {
+
+ geoInfo.uv.push( this.parseUVs( geoNode.LayerElementUV[ i ] ) );
+ i ++;
+
+ }
+
+ }
+
+ geoInfo.weightTable = {};
+
+ if ( skeleton !== null ) {
+
+ geoInfo.skeleton = skeleton;
+
+ skeleton.rawBones.forEach( function ( rawBone, i ) {
+
+ // loop over the bone's vertex indices and weights
+ rawBone.indices.forEach( function ( index, j ) {
+
+ if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = [];
+
+ geoInfo.weightTable[ index ].push( {
+
+ id: i,
+ weight: rawBone.weights[ j ],
+
+ } );
+
+ } );
+
+ } );
+
+ }
+
+ return geoInfo;
+
+ },
+
+ genBuffers: function ( geoInfo ) {
+
+ var buffers = {
+ vertex: [],
+ normal: [],
+ colors: [],
+ uvs: [],
+ materialIndex: [],
+ vertexWeights: [],
+ weightsIndices: [],
+ };
+
+ var polygonIndex = 0;
+ var faceLength = 0;
+ var displayedWeightsWarning = false;
+
+ // these will hold data for a single face
+ var facePositionIndexes = [];
+ var faceNormals = [];
+ var faceColors = [];
+ var faceUVs = [];
+ var faceWeights = [];
+ var faceWeightIndices = [];
+
+ var self = this;
+ geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) {
+
+ var endOfFace = false;
+
+ // Face index and vertex index arrays are combined in a single array
+ // A cube with quad faces looks like this:
+ // PolygonVertexIndex: *24 {
+ // a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5
+ // }
+ // Negative numbers mark the end of a face - first face here is 0, 1, 3, -3
+ // to find index of last vertex bit shift the index: ^ - 1
+ if ( vertexIndex < 0 ) {
+
+ vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1
+ endOfFace = true;
+
+ }
+
+ var weightIndices = [];
+ var weights = [];
+
+ facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 );
+
+ if ( geoInfo.color ) {
+
+ var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color );
+
+ faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] );
+
+ }
+
+ if ( geoInfo.skeleton ) {
+
+ if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) {
+
+ geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) {
+
+ weights.push( wt.weight );
+ weightIndices.push( wt.id );
+
+ } );
+
+
+ }
+
+ if ( weights.length > 4 ) {
+
+ if ( ! displayedWeightsWarning ) {
+
+ console.warn( 'THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' );
+ displayedWeightsWarning = true;
+
+ }
+
+ var wIndex = [ 0, 0, 0, 0 ];
+ var Weight = [ 0, 0, 0, 0 ];
+
+ weights.forEach( function ( weight, weightIndex ) {
+
+ var currentWeight = weight;
+ var currentIndex = weightIndices[ weightIndex ];
+
+ Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) {
+
+ if ( currentWeight > comparedWeight ) {
+
+ comparedWeightArray[ comparedWeightIndex ] = currentWeight;
+ currentWeight = comparedWeight;
+
+ var tmp = wIndex[ comparedWeightIndex ];
+ wIndex[ comparedWeightIndex ] = currentIndex;
+ currentIndex = tmp;
+
+ }
+
+ } );
+
+ } );
+
+ weightIndices = wIndex;
+ weights = Weight;
+
+ }
+
+ // if the weight array is shorter than 4 pad with 0s
+ while ( weights.length < 4 ) {
+
+ weights.push( 0 );
+ weightIndices.push( 0 );
+
+ }
+
+ for ( var i = 0; i < 4; ++ i ) {
+
+ faceWeights.push( weights[ i ] );
+ faceWeightIndices.push( weightIndices[ i ] );
+
+ }
+
+ }
+
+ if ( geoInfo.normal ) {
+
+ var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal );
+
+ faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] );
+
+ }
+
+ if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+
+ var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ];
+
+ }
+
+ if ( geoInfo.uv ) {
+
+ geoInfo.uv.forEach( function ( uv, i ) {
+
+ var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv );
+
+ if ( faceUVs[ i ] === undefined ) {
+
+ faceUVs[ i ] = [];
+
+ }
+
+ faceUVs[ i ].push( data[ 0 ] );
+ faceUVs[ i ].push( data[ 1 ] );
+
+ } );
+
+ }
+
+ faceLength ++;
+
+ if ( endOfFace ) {
+
+ self.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength );
+
+ polygonIndex ++;
+ faceLength = 0;
+
+ // reset arrays for the next face
+ facePositionIndexes = [];
+ faceNormals = [];
+ faceColors = [];
+ faceUVs = [];
+ faceWeights = [];
+ faceWeightIndices = [];
+
+ }
+
+ } );
+
+ return buffers;
+
+ },
+
+ // Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris
+ genFace: function ( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) {
+
+ for ( var i = 2; i < faceLength; i ++ ) {
+
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] );
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] );
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] );
+
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] );
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] );
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] );
+
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] );
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] );
+ buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] );
+
+ if ( geoInfo.skeleton ) {
+
+ buffers.vertexWeights.push( faceWeights[ 0 ] );
+ buffers.vertexWeights.push( faceWeights[ 1 ] );
+ buffers.vertexWeights.push( faceWeights[ 2 ] );
+ buffers.vertexWeights.push( faceWeights[ 3 ] );
+
+ buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] );
+ buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] );
+ buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] );
+ buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] );
+
+ buffers.vertexWeights.push( faceWeights[ i * 4 ] );
+ buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] );
+ buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] );
+ buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] );
+
+ buffers.weightsIndices.push( faceWeightIndices[ 0 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ 1 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ 2 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ 3 ] );
+
+ buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] );
+
+ buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] );
+ buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] );
+
+ }
+
+ if ( geoInfo.color ) {
+
+ buffers.colors.push( faceColors[ 0 ] );
+ buffers.colors.push( faceColors[ 1 ] );
+ buffers.colors.push( faceColors[ 2 ] );
+
+ buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] );
+ buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] );
+ buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] );
+
+ buffers.colors.push( faceColors[ i * 3 ] );
+ buffers.colors.push( faceColors[ i * 3 + 1 ] );
+ buffers.colors.push( faceColors[ i * 3 + 2 ] );
+
+ }
+
+ if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+
+ buffers.materialIndex.push( materialIndex );
+ buffers.materialIndex.push( materialIndex );
+ buffers.materialIndex.push( materialIndex );
+
+ }
+
+ if ( geoInfo.normal ) {
+
+ buffers.normal.push( faceNormals[ 0 ] );
+ buffers.normal.push( faceNormals[ 1 ] );
+ buffers.normal.push( faceNormals[ 2 ] );
+
+ buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] );
+ buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] );
+ buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] );
+
+ buffers.normal.push( faceNormals[ i * 3 ] );
+ buffers.normal.push( faceNormals[ i * 3 + 1 ] );
+ buffers.normal.push( faceNormals[ i * 3 + 2 ] );
+
+ }
+
+ if ( geoInfo.uv ) {
+
+ geoInfo.uv.forEach( function ( uv, j ) {
+
+ if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = [];
+
+ buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] );
+ buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] );
+
+ buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] );
+ buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] );
+
+ buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] );
+ buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] );
+
+ } );
+
+ }
+
+ }
+
+ },
+
+ addMorphTargets: function ( parentGeo, parentGeoNode, morphTarget, preTransform ) {
+
+ if ( morphTarget === null ) return;
+
+ parentGeo.morphAttributes.position = [];
+ // parentGeo.morphAttributes.normal = []; // not implemented
+
+ var self = this;
+ morphTarget.rawTargets.forEach( function ( rawTarget ) {
+
+ var morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ];
+
+ if ( morphGeoNode !== undefined ) {
+
+ self.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name );
+
+ }
+
+ } );
+
+ },
+
+ // a morph geometry node is similar to a standard node, and the node is also contained
+ // in FBXTree.Objects.Geometry, however it can only have attributes for position, normal
+ // and a special attribute Index defining which vertices of the original geometry are affected
+ // Normal and position attributes only have data for the vertices that are affected by the morph
+ genMorphGeometry: function ( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) {
+
+ var morphGeo = new THREE.BufferGeometry();
+ if ( morphGeoNode.attrName ) morphGeo.name = morphGeoNode.attrName;
+
+ var vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : [];
+
+ // make a copy of the parent's vertex positions
+ var vertexPositions = ( parentGeoNode.Vertices !== undefined ) ? parentGeoNode.Vertices.a.slice() : [];
+
+ var morphPositions = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : [];
+ var indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : [];
+
+ for ( var i = 0; i < indices.length; i ++ ) {
+
+ var morphIndex = indices[ i ] * 3;
+
+ // FBX format uses blend shapes rather than morph targets. This can be converted
+ // by additively combining the blend shape positions with the original geometry's positions
+ vertexPositions[ morphIndex ] += morphPositions[ i * 3 ];
+ vertexPositions[ morphIndex + 1 ] += morphPositions[ i * 3 + 1 ];
+ vertexPositions[ morphIndex + 2 ] += morphPositions[ i * 3 + 2 ];
+
+ }
+
+ // TODO: add morph normal support
+ var morphGeoInfo = {
+ vertexIndices: vertexIndices,
+ vertexPositions: vertexPositions,
+ };
+
+ var morphBuffers = this.genBuffers( morphGeoInfo );
+
+ var positionAttribute = new THREE.Float32BufferAttribute( morphBuffers.vertex, 3 );
+ positionAttribute.name = name || morphGeoNode.attrName;
+
+ preTransform.applyToBufferAttribute( positionAttribute );
+
+ parentGeo.morphAttributes.position.push( positionAttribute );
+
+ },
+
+ // Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists
+ parseNormals: function ( NormalNode ) {
+
+ var mappingType = NormalNode.MappingInformationType;
+ var referenceType = NormalNode.ReferenceInformationType;
+ var buffer = NormalNode.Normals.a;
+ var indexBuffer = [];
+ if ( referenceType === 'IndexToDirect' ) {
+
+ if ( 'NormalIndex' in NormalNode ) {
+
+ indexBuffer = NormalNode.NormalIndex.a;
+
+ } else if ( 'NormalsIndex' in NormalNode ) {
+
+ indexBuffer = NormalNode.NormalsIndex.a;
+
+ }
+
+ }
+
+ return {
+ dataSize: 3,
+ buffer: buffer,
+ indices: indexBuffer,
+ mappingType: mappingType,
+ referenceType: referenceType
+ };
+
+ },
+
+ // Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists
+ parseUVs: function ( UVNode ) {
+
+ var mappingType = UVNode.MappingInformationType;
+ var referenceType = UVNode.ReferenceInformationType;
+ var buffer = UVNode.UV.a;
+ var indexBuffer = [];
+ if ( referenceType === 'IndexToDirect' ) {
+
+ indexBuffer = UVNode.UVIndex.a;
+
+ }
+
+ return {
+ dataSize: 2,
+ buffer: buffer,
+ indices: indexBuffer,
+ mappingType: mappingType,
+ referenceType: referenceType
+ };
+
+ },
+
+ // Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists
+ parseVertexColors: function ( ColorNode ) {
+
+ var mappingType = ColorNode.MappingInformationType;
+ var referenceType = ColorNode.ReferenceInformationType;
+ var buffer = ColorNode.Colors.a;
+ var indexBuffer = [];
+ if ( referenceType === 'IndexToDirect' ) {
+
+ indexBuffer = ColorNode.ColorIndex.a;
+
+ }
+
+ return {
+ dataSize: 4,
+ buffer: buffer,
+ indices: indexBuffer,
+ mappingType: mappingType,
+ referenceType: referenceType
+ };
+
+ },
+
+ // Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists
+ parseMaterialIndices: function ( MaterialNode ) {
+
+ var mappingType = MaterialNode.MappingInformationType;
+ var referenceType = MaterialNode.ReferenceInformationType;
+
+ if ( mappingType === 'NoMappingInformation' ) {
+
+ return {
+ dataSize: 1,
+ buffer: [ 0 ],
+ indices: [ 0 ],
+ mappingType: 'AllSame',
+ referenceType: referenceType
+ };
+
+ }
+
+ var materialIndexBuffer = MaterialNode.Materials.a;
+
+ // Since materials are stored as indices, there's a bit of a mismatch between FBX and what
+ // we expect.So we create an intermediate buffer that points to the index in the buffer,
+ // for conforming with the other functions we've written for other data.
+ var materialIndices = [];
+
+ for ( var i = 0; i < materialIndexBuffer.length; ++ i ) {
+
+ materialIndices.push( i );
+
+ }
+
+ return {
+ dataSize: 1,
+ buffer: materialIndexBuffer,
+ indices: materialIndices,
+ mappingType: mappingType,
+ referenceType: referenceType
+ };
+
+ },
+
+ // Generate a NurbGeometry from a node in FBXTree.Objects.Geometry
+ parseNurbsGeometry: function ( geoNode ) {
+
+ if ( THREE.NURBSCurve === undefined ) {
+
+ console.error( 'THREE.FBXLoader: The loader relies on THREE.NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.' );
+ return new THREE.BufferGeometry();
+
+ }
+
+ var order = parseInt( geoNode.Order );
+
+ if ( isNaN( order ) ) {
+
+ console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id );
+ return new THREE.BufferGeometry();
+
+ }
+
+ var degree = order - 1;
+
+ var knots = geoNode.KnotVector.a;
+ var controlPoints = [];
+ var pointsValues = geoNode.Points.a;
+
+ for ( var i = 0, l = pointsValues.length; i < l; i += 4 ) {
+
+ controlPoints.push( new THREE.Vector4().fromArray( pointsValues, i ) );
+
+ }
+
+ var startKnot, endKnot;
+
+ if ( geoNode.Form === 'Closed' ) {
+
+ controlPoints.push( controlPoints[ 0 ] );
+
+ } else if ( geoNode.Form === 'Periodic' ) {
+
+ startKnot = degree;
+ endKnot = knots.length - 1 - startKnot;
+
+ for ( var i = 0; i < degree; ++ i ) {
+
+ controlPoints.push( controlPoints[ i ] );
+
+ }
+
+ }
+
+ var curve = new THREE.NURBSCurve( degree, knots, controlPoints, startKnot, endKnot );
+ var vertices = curve.getPoints( controlPoints.length * 7 );
+
+ var positions = new Float32Array( vertices.length * 3 );
+
+ vertices.forEach( function ( vertex, i ) {
+
+ vertex.toArray( positions, i * 3 );
+
+ } );
+
+ var geometry = new THREE.BufferGeometry();
+ geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+
+ return geometry;
+
+ },
+
+ };
+
+ // parse animation data from FBXTree
+ function AnimationParser() {}
+
+ AnimationParser.prototype = {
+
+ constructor: AnimationParser,
+
+ // take raw animation clips and turn them into three.js animation clips
+ parse: function () {
+
+ var animationClips = [];
+
+ var rawClips = this.parseClips();
+
+ if ( rawClips !== undefined ) {
+
+ for ( var key in rawClips ) {
+
+ var rawClip = rawClips[ key ];
+
+ var clip = this.addClip( rawClip );
+
+ animationClips.push( clip );
+
+ }
+
+ }
+
+ return animationClips;
+
+ },
+
+ parseClips: function () {
+
+ // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve,
+ // if this is undefined we can safely assume there are no animations
+ if ( fbxTree.Objects.AnimationCurve === undefined ) return undefined;
+
+ var curveNodesMap = this.parseAnimationCurveNodes();
+
+ this.parseAnimationCurves( curveNodesMap );
+
+ var layersMap = this.parseAnimationLayers( curveNodesMap );
+ var rawClips = this.parseAnimStacks( layersMap );
+
+ return rawClips;
+
+ },
+
+ // parse nodes in FBXTree.Objects.AnimationCurveNode
+ // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation )
+ // and is referenced by an AnimationLayer
+ parseAnimationCurveNodes: function () {
+
+ var rawCurveNodes = fbxTree.Objects.AnimationCurveNode;
+
+ var curveNodesMap = new Map();
+
+ for ( var nodeID in rawCurveNodes ) {
+
+ var rawCurveNode = rawCurveNodes[ nodeID ];
+
+ if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
+
+ var curveNode = {
+
+ id: rawCurveNode.id,
+ attr: rawCurveNode.attrName,
+ curves: {},
+
+ };
+
+ curveNodesMap.set( curveNode.id, curveNode );
+
+ }
+
+ }
+
+ return curveNodesMap;
+
+ },
+
+ // parse nodes in FBXTree.Objects.AnimationCurve and connect them up to
+ // previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated
+ // axis ( e.g. times and values of x rotation)
+ parseAnimationCurves: function ( curveNodesMap ) {
+
+ var rawCurves = fbxTree.Objects.AnimationCurve;
+
+ // TODO: Many values are identical up to roundoff error, but won't be optimised
+ // e.g. position times: [0, 0.4, 0. 8]
+ // position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809]
+ // clearly, this should be optimised to
+ // times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809]
+ // this shows up in nearly every FBX file, and generally time array is length > 100
+
+ for ( var nodeID in rawCurves ) {
+
+ var animationCurve = {
+
+ id: rawCurves[ nodeID ].id,
+ times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ),
+ values: rawCurves[ nodeID ].KeyValueFloat.a,
+
+ };
+
+ var relationships = connections.get( animationCurve.id );
+
+ if ( relationships !== undefined ) {
+
+ var animationCurveID = relationships.parents[ 0 ].ID;
+ var animationCurveRelationship = relationships.parents[ 0 ].relationship;
+
+ if ( animationCurveRelationship.match( /X/ ) ) {
+
+ curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve;
+
+ } else if ( animationCurveRelationship.match( /Y/ ) ) {
+
+ curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve;
+
+ } else if ( animationCurveRelationship.match( /Z/ ) ) {
+
+ curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
+
+ } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
+
+ curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
+
+ }
+
+ }
+
+ }
+
+ },
+
+ // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references
+ // to various AnimationCurveNodes and is referenced by an AnimationStack node
+ // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack
+ parseAnimationLayers: function ( curveNodesMap ) {
+
+ var rawLayers = fbxTree.Objects.AnimationLayer;
+
+ var layersMap = new Map();
+
+ for ( var nodeID in rawLayers ) {
+
+ var layerCurveNodes = [];
+
+ var connection = connections.get( parseInt( nodeID ) );
+
+ if ( connection !== undefined ) {
+
+ // all the animationCurveNodes used in the layer
+ var children = connection.children;
+
+ children.forEach( function ( child, i ) {
+
+ if ( curveNodesMap.has( child.ID ) ) {
+
+ var curveNode = curveNodesMap.get( child.ID );
+
+ // check that the curves are defined for at least one axis, otherwise ignore the curveNode
+ if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) {
+
+ if ( layerCurveNodes[ i ] === undefined ) {
+
+ var modelID = connections.get( child.ID ).parents.filter( function ( parent ) {
+
+ return parent.relationship !== undefined;
+
+ } )[ 0 ].ID;
+
+ if ( modelID !== undefined ) {
+
+ var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
+
+ var node = {
+
+ modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ),
+ ID: rawModel.id,
+ initialPosition: [ 0, 0, 0 ],
+ initialRotation: [ 0, 0, 0 ],
+ initialScale: [ 1, 1, 1 ],
+
+ };
+
+ sceneGraph.traverse( function ( child ) {
+
+ if ( child.ID === rawModel.id ) {
+
+ node.transform = child.matrix;
+
+ if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder;
+
+ }
+
+ } );
+
+ if ( ! node.transform ) node.transform = new THREE.Matrix4();
+
+ // if the animated model is pre rotated, we'll have to apply the pre rotations to every
+ // animation value as well
+ if ( 'PreRotation' in rawModel ) node.preRotation = rawModel.PreRotation.value;
+ if ( 'PostRotation' in rawModel ) node.postRotation = rawModel.PostRotation.value;
+
+ layerCurveNodes[ i ] = node;
+
+ }
+
+ }
+
+ if ( layerCurveNodes[ i ] ) layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
+
+ } else if ( curveNode.curves.morph !== undefined ) {
+
+ if ( layerCurveNodes[ i ] === undefined ) {
+
+ var deformerID = connections.get( child.ID ).parents.filter( function ( parent ) {
+
+ return parent.relationship !== undefined;
+
+ } )[ 0 ].ID;
+
+ var morpherID = connections.get( deformerID ).parents[ 0 ].ID;
+ var geoID = connections.get( morpherID ).parents[ 0 ].ID;
+
+ // assuming geometry is not used in more than one model
+ var modelID = connections.get( geoID ).parents[ 0 ].ID;
+
+ var rawModel = fbxTree.Objects.Model[ modelID ];
+
+ var node = {
+
+ modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ),
+ morphName: fbxTree.Objects.Deformer[ deformerID ].attrName,
+
+ };
+
+ layerCurveNodes[ i ] = node;
+
+ }
+
+ layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
+
+ }
+
+ }
+
+ } );
+
+ layersMap.set( parseInt( nodeID ), layerCurveNodes );
+
+ }
+
+ }
+
+ return layersMap;
+
+ },
+
+ // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation
+ // hierarchy. Each Stack node will be used to create a THREE.AnimationClip
+ parseAnimStacks: function ( layersMap ) {
+
+ var rawStacks = fbxTree.Objects.AnimationStack;
+
+ // connect the stacks (clips) up to the layers
+ var rawClips = {};
+
+ for ( var nodeID in rawStacks ) {
+
+ var children = connections.get( parseInt( nodeID ) ).children;
+
+ if ( children.length > 1 ) {
+
+ // it seems like stacks will always be associated with a single layer. But just in case there are files
+ // where there are multiple layers per stack, we'll display a warning
+ console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' );
+
+ }
+
+ var layer = layersMap.get( children[ 0 ].ID );
+
+ rawClips[ nodeID ] = {
+
+ name: rawStacks[ nodeID ].attrName,
+ layer: layer,
+
+ };
+
+ }
+
+ return rawClips;
+
+ },
+
+ addClip: function ( rawClip ) {
+
+ var tracks = [];
+
+ var self = this;
+ rawClip.layer.forEach( function ( rawTracks ) {
+
+ tracks = tracks.concat( self.generateTracks( rawTracks ) );
+
+ } );
+
+ return new THREE.AnimationClip( rawClip.name, - 1, tracks );
+
+ },
+
+ generateTracks: function ( rawTracks ) {
+
+ var tracks = [];
+
+ var initialPosition = new THREE.Vector3();
+ var initialRotation = new THREE.Quaternion();
+ var initialScale = new THREE.Vector3();
+
+ if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
+
+ initialPosition = initialPosition.toArray();
+ initialRotation = new THREE.Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray();
+ initialScale = initialScale.toArray();
+
+ if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
+
+ var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
+ if ( positionTrack !== undefined ) tracks.push( positionTrack );
+
+ }
+
+ if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
+
+ var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
+ if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
+
+ }
+
+ if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) {
+
+ var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
+ if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
+
+ }
+
+ if ( rawTracks.DeformPercent !== undefined ) {
+
+ var morphTrack = this.generateMorphTrack( rawTracks );
+ if ( morphTrack !== undefined ) tracks.push( morphTrack );
+
+ }
+
+ return tracks;
+
+ },
+
+ generateVectorTrack: function ( modelName, curves, initialValue, type ) {
+
+ var times = this.getTimesForAllAxes( curves );
+ var values = this.getKeyframeTrackValues( times, curves, initialValue );
+
+ return new THREE.VectorKeyframeTrack( modelName + '.' + type, times, values );
+
+ },
+
+ generateRotationTrack: function ( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
+
+ if ( curves.x !== undefined ) {
+
+ this.interpolateRotations( curves.x );
+ curves.x.values = curves.x.values.map( THREE.Math.degToRad );
+
+ }
+ if ( curves.y !== undefined ) {
+
+ this.interpolateRotations( curves.y );
+ curves.y.values = curves.y.values.map( THREE.Math.degToRad );
+
+ }
+ if ( curves.z !== undefined ) {
+
+ this.interpolateRotations( curves.z );
+ curves.z.values = curves.z.values.map( THREE.Math.degToRad );
+
+ }
+
+ var times = this.getTimesForAllAxes( curves );
+ var values = this.getKeyframeTrackValues( times, curves, initialValue );
+
+ if ( preRotation !== undefined ) {
+
+ preRotation = preRotation.map( THREE.Math.degToRad );
+ preRotation.push( eulerOrder );
+
+ preRotation = new THREE.Euler().fromArray( preRotation );
+ preRotation = new THREE.Quaternion().setFromEuler( preRotation );
+
+ }
+
+ if ( postRotation !== undefined ) {
+
+ postRotation = postRotation.map( THREE.Math.degToRad );
+ postRotation.push( eulerOrder );
+
+ postRotation = new THREE.Euler().fromArray( postRotation );
+ postRotation = new THREE.Quaternion().setFromEuler( postRotation ).inverse();
+
+ }
+
+ var quaternion = new THREE.Quaternion();
+ var euler = new THREE.Euler();
+
+ var quaternionValues = [];
+
+ for ( var i = 0; i < values.length; i += 3 ) {
+
+ euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder );
+
+ quaternion.setFromEuler( euler );
+
+ if ( preRotation !== undefined ) quaternion.premultiply( preRotation );
+ if ( postRotation !== undefined ) quaternion.multiply( postRotation );
+
+ quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
+
+ }
+
+ return new THREE.QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
+
+ },
+
+ generateMorphTrack: function ( rawTracks ) {
+
+ var curves = rawTracks.DeformPercent.curves.morph;
+ var values = curves.values.map( function ( val ) {
+
+ return val / 100;
+
+ } );
+
+ var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ];
+
+ return new THREE.NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values );
+
+ },
+
+ // For all animated objects, times are defined separately for each axis
+ // Here we'll combine the times into one sorted array without duplicates
+ getTimesForAllAxes: function ( curves ) {
+
+ var times = [];
+
+ // first join together the times for each axis, if defined
+ if ( curves.x !== undefined ) times = times.concat( curves.x.times );
+ if ( curves.y !== undefined ) times = times.concat( curves.y.times );
+ if ( curves.z !== undefined ) times = times.concat( curves.z.times );
+
+ // then sort them and remove duplicates
+ times = times.sort( function ( a, b ) {
+
+ return a - b;
+
+ } ).filter( function ( elem, index, array ) {
+
+ return array.indexOf( elem ) == index;
+
+ } );
+
+ return times;
+
+ },
+
+ getKeyframeTrackValues: function ( times, curves, initialValue ) {
+
+ var prevValue = initialValue;
+
+ var values = [];
+
+ var xIndex = - 1;
+ var yIndex = - 1;
+ var zIndex = - 1;
+
+ times.forEach( function ( time ) {
+
+ if ( curves.x ) xIndex = curves.x.times.indexOf( time );
+ if ( curves.y ) yIndex = curves.y.times.indexOf( time );
+ if ( curves.z ) zIndex = curves.z.times.indexOf( time );
+
+ // if there is an x value defined for this frame, use that
+ if ( xIndex !== - 1 ) {
+
+ var xValue = curves.x.values[ xIndex ];
+ values.push( xValue );
+ prevValue[ 0 ] = xValue;
+
+ } else {
+
+ // otherwise use the x value from the previous frame
+ values.push( prevValue[ 0 ] );
+
+ }
+
+ if ( yIndex !== - 1 ) {
+
+ var yValue = curves.y.values[ yIndex ];
+ values.push( yValue );
+ prevValue[ 1 ] = yValue;
+
+ } else {
+
+ values.push( prevValue[ 1 ] );
+
+ }
+
+ if ( zIndex !== - 1 ) {
+
+ var zValue = curves.z.values[ zIndex ];
+ values.push( zValue );
+ prevValue[ 2 ] = zValue;
+
+ } else {
+
+ values.push( prevValue[ 2 ] );
+
+ }
+
+ } );
+
+ return values;
+
+ },
+
+ // Rotations are defined as Euler angles which can have values of any size
+ // These will be converted to quaternions which don't support values greater than
+ // PI, so we'll interpolate large rotations
+ interpolateRotations: function ( curve ) {
+
+ for ( var i = 1; i < curve.values.length; i ++ ) {
+
+ var initialValue = curve.values[ i - 1 ];
+ var valuesSpan = curve.values[ i ] - initialValue;
+
+ var absoluteSpan = Math.abs( valuesSpan );
+
+ if ( absoluteSpan >= 180 ) {
+
+ var numSubIntervals = absoluteSpan / 180;
+
+ var step = valuesSpan / numSubIntervals;
+ var nextValue = initialValue + step;
+
+ var initialTime = curve.times[ i - 1 ];
+ var timeSpan = curve.times[ i ] - initialTime;
+ var interval = timeSpan / numSubIntervals;
+ var nextTime = initialTime + interval;
+
+ var interpolatedTimes = [];
+ var interpolatedValues = [];
+
+ while ( nextTime < curve.times[ i ] ) {
+
+ interpolatedTimes.push( nextTime );
+ nextTime += interval;
+
+ interpolatedValues.push( nextValue );
+ nextValue += step;
+
+ }
+
+ curve.times = inject( curve.times, i, interpolatedTimes );
+ curve.values = inject( curve.values, i, interpolatedValues );
+
+ }
+
+ }
+
+ },
+
+ };
+
+ // parse an FBX file in ASCII format
+ function TextParser() {}
+
+ TextParser.prototype = {
+
+ constructor: TextParser,
+
+ getPrevNode: function () {
+
+ return this.nodeStack[ this.currentIndent - 2 ];
+
+ },
+
+ getCurrentNode: function () {
+
+ return this.nodeStack[ this.currentIndent - 1 ];
+
+ },
+
+ getCurrentProp: function () {
+
+ return this.currentProp;
+
+ },
+
+ pushStack: function ( node ) {
+
+ this.nodeStack.push( node );
+ this.currentIndent += 1;
+
+ },
+
+ popStack: function () {
+
+ this.nodeStack.pop();
+ this.currentIndent -= 1;
+
+ },
+
+ setCurrentProp: function ( val, name ) {
+
+ this.currentProp = val;
+ this.currentPropName = name;
+
+ },
+
+ parse: function ( text ) {
+
+ this.currentIndent = 0;
+
+ this.allNodes = new FBXTree();
+ this.nodeStack = [];
+ this.currentProp = [];
+ this.currentPropName = '';
+
+ var self = this;
+
+ var split = text.split( /[\r\n]+/ );
+
+ split.forEach( function ( line, i ) {
+
+ var matchComment = line.match( /^[\s\t]*;/ );
+ var matchEmpty = line.match( /^[\s\t]*$/ );
+
+ if ( matchComment || matchEmpty ) return;
+
+ var matchBeginning = line.match( '^\\t{' + self.currentIndent + '}(\\w+):(.*){', '' );
+ var matchProperty = line.match( '^\\t{' + ( self.currentIndent ) + '}(\\w+):[\\s\\t\\r\\n](.*)' );
+ var matchEnd = line.match( '^\\t{' + ( self.currentIndent - 1 ) + '}}' );
+
+ if ( matchBeginning ) {
+
+ self.parseNodeBegin( line, matchBeginning );
+
+ } else if ( matchProperty ) {
+
+ self.parseNodeProperty( line, matchProperty, split[ ++ i ] );
+
+ } else if ( matchEnd ) {
+
+ self.popStack();
+
+ } else if ( line.match( /^[^\s\t}]/ ) ) {
+
+ // large arrays are split over multiple lines terminated with a ',' character
+ // if this is encountered the line needs to be joined to the previous line
+ self.parseNodePropertyContinued( line );
+
+ }
+
+ } );
+
+ return this.allNodes;
+
+ },
+
+ parseNodeBegin: function ( line, property ) {
+
+ var nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' );
+
+ var nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) {
+
+ return attr.trim().replace( /^"/, '' ).replace( /"$/, '' );
+
+ } );
+
+ var node = { name: nodeName };
+ var attrs = this.parseNodeAttr( nodeAttrs );
+
+ var currentNode = this.getCurrentNode();
+
+ // a top node
+ if ( this.currentIndent === 0 ) {
+
+ this.allNodes.add( nodeName, node );
+
+ } else { // a subnode
+
+ // if the subnode already exists, append it
+ if ( nodeName in currentNode ) {
+
+ // special case Pose needs PoseNodes as an array
+ if ( nodeName === 'PoseNode' ) {
+
+ currentNode.PoseNode.push( node );
+
+ } else if ( currentNode[ nodeName ].id !== undefined ) {
+
+ currentNode[ nodeName ] = {};
+ currentNode[ nodeName ][ currentNode[ nodeName ].id ] = currentNode[ nodeName ];
+
+ }
+
+ if ( attrs.id !== '' ) currentNode[ nodeName ][ attrs.id ] = node;
+
+ } else if ( typeof attrs.id === 'number' ) {
+
+ currentNode[ nodeName ] = {};
+ currentNode[ nodeName ][ attrs.id ] = node;
+
+ } else if ( nodeName !== 'Properties70' ) {
+
+ if ( nodeName === 'PoseNode' ) currentNode[ nodeName ] = [ node ];
+ else currentNode[ nodeName ] = node;
+
+ }
+
+ }
+
+ if ( typeof attrs.id === 'number' ) node.id = attrs.id;
+ if ( attrs.name !== '' ) node.attrName = attrs.name;
+ if ( attrs.type !== '' ) node.attrType = attrs.type;
+
+ this.pushStack( node );
+
+ },
+
+ parseNodeAttr: function ( attrs ) {
+
+ var id = attrs[ 0 ];
+
+ if ( attrs[ 0 ] !== '' ) {
+
+ id = parseInt( attrs[ 0 ] );
+
+ if ( isNaN( id ) ) {
+
+ id = attrs[ 0 ];
+
+ }
+
+ }
+
+ var name = '', type = '';
+
+ if ( attrs.length > 1 ) {
+
+ name = attrs[ 1 ].replace( /^(\w+)::/, '' );
+ type = attrs[ 2 ];
+
+ }
+
+ return { id: id, name: name, type: type };
+
+ },
+
+ parseNodeProperty: function ( line, property, contentLine ) {
+
+ var propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
+ var propValue = property[ 2 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
+
+ // for special case: base64 image data follows "Content: ," line
+ // Content: ,
+ // "/9j/4RDaRXhpZgAATU0A..."
+ if ( propName === 'Content' && propValue === ',' ) {
+
+ propValue = contentLine.replace( /"/g, '' ).replace( /,$/, '' ).trim();
+
+ }
+
+ var currentNode = this.getCurrentNode();
+ var parentName = currentNode.name;
+
+ if ( parentName === 'Properties70' ) {
+
+ this.parseNodeSpecialProperty( line, propName, propValue );
+ return;
+
+ }
+
+ // Connections
+ if ( propName === 'C' ) {
+
+ var connProps = propValue.split( ',' ).slice( 1 );
+ var from = parseInt( connProps[ 0 ] );
+ var to = parseInt( connProps[ 1 ] );
+
+ var rest = propValue.split( ',' ).slice( 3 );
+
+ rest = rest.map( function ( elem ) {
+
+ return elem.trim().replace( /^"/, '' );
+
+ } );
+
+ propName = 'connections';
+ propValue = [ from, to ];
+ append( propValue, rest );
+
+ if ( currentNode[ propName ] === undefined ) {
+
+ currentNode[ propName ] = [];
+
+ }
+
+ }
+
+ // Node
+ if ( propName === 'Node' ) currentNode.id = propValue;
+
+ // connections
+ if ( propName in currentNode && Array.isArray( currentNode[ propName ] ) ) {
+
+ currentNode[ propName ].push( propValue );
+
+ } else {
+
+ if ( propName !== 'a' ) currentNode[ propName ] = propValue;
+ else currentNode.a = propValue;
+
+ }
+
+ this.setCurrentProp( currentNode, propName );
+
+ // convert string to array, unless it ends in ',' in which case more will be added to it
+ if ( propName === 'a' && propValue.slice( - 1 ) !== ',' ) {
+
+ currentNode.a = parseNumberArray( propValue );
+
+ }
+
+ },
+
+ parseNodePropertyContinued: function ( line ) {
+
+ var currentNode = this.getCurrentNode();
+
+ currentNode.a += line;
+
+ // if the line doesn't end in ',' we have reached the end of the property value
+ // so convert the string to an array
+ if ( line.slice( - 1 ) !== ',' ) {
+
+ currentNode.a = parseNumberArray( currentNode.a );
+
+ }
+
+ },
+
+ // parse "Property70"
+ parseNodeSpecialProperty: function ( line, propName, propValue ) {
+
+ // split this
+ // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1
+ // into array like below
+ // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ]
+ var props = propValue.split( '",' ).map( function ( prop ) {
+
+ return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' );
+
+ } );
+
+ var innerPropName = props[ 0 ];
+ var innerPropType1 = props[ 1 ];
+ var innerPropType2 = props[ 2 ];
+ var innerPropFlag = props[ 3 ];
+ var innerPropValue = props[ 4 ];
+
+ // cast values where needed, otherwise leave as strings
+ switch ( innerPropType1 ) {
+
+ case 'int':
+ case 'enum':
+ case 'bool':
+ case 'ULongLong':
+ case 'double':
+ case 'Number':
+ case 'FieldOfView':
+ innerPropValue = parseFloat( innerPropValue );
+ break;
+
+ case 'Color':
+ case 'ColorRGB':
+ case 'Vector3D':
+ case 'Lcl_Translation':
+ case 'Lcl_Rotation':
+ case 'Lcl_Scaling':
+ innerPropValue = parseNumberArray( innerPropValue );
+ break;
+
+ }
+
+ // CAUTION: these props must append to parent's parent
+ this.getPrevNode()[ innerPropName ] = {
+
+ 'type': innerPropType1,
+ 'type2': innerPropType2,
+ 'flag': innerPropFlag,
+ 'value': innerPropValue
+
+ };
+
+ this.setCurrentProp( this.getPrevNode(), innerPropName );
+
+ },
+
+ };
+
+ // Parse an FBX file in Binary format
+ function BinaryParser() {}
+
+ BinaryParser.prototype = {
+
+ constructor: BinaryParser,
+
+ parse: function ( buffer ) {
+
+ var reader = new BinaryReader( buffer );
+ reader.skip( 23 ); // skip magic 23 bytes
+
+ var version = reader.getUint32();
+
+ //console.log( 'THREE.FBXLoader: FBX binary version: ' + version );
+
+ var allNodes = new FBXTree();
+
+ while ( ! this.endOfContent( reader ) ) {
+
+ var node = this.parseNode( reader, version );
+ if ( node !== null ) allNodes.add( node.name, node );
+
+ }
+
+ return allNodes;
+
+ },
+
+ // Check if reader has reached the end of content.
+ endOfContent: function ( reader ) {
+
+ // footer size: 160bytes + 16-byte alignment padding
+ // - 16bytes: magic
+ // - padding til 16-byte alignment (at least 1byte?)
+ // (seems like some exporters embed fixed 15 or 16bytes?)
+ // - 4bytes: magic
+ // - 4bytes: version
+ // - 120bytes: zero
+ // - 16bytes: magic
+ if ( reader.size() % 16 === 0 ) {
+
+ return ( ( reader.getOffset() + 160 + 16 ) & ~ 0xf ) >= reader.size();
+
+ } else {
+
+ return reader.getOffset() + 160 + 16 >= reader.size();
+
+ }
+
+ },
+
+ // recursively parse nodes until the end of the file is reached
+ parseNode: function ( reader, version ) {
+
+ var node = {};
+
+ // The first three data sizes depends on version.
+ var endOffset = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+ var numProperties = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+
+ // note: do not remove this even if you get a linter warning as it moves the buffer forward
+ var propertyListLen = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+
+ var nameLen = reader.getUint8();
+ var name = reader.getString( nameLen );
+
+ // Regards this node as NULL-record if endOffset is zero
+ if ( endOffset === 0 ) return null;
+
+ var propertyList = [];
+
+ for ( var i = 0; i < numProperties; i ++ ) {
+
+ propertyList.push( this.parseProperty( reader ) );
+
+ }
+
+ // Regards the first three elements in propertyList as id, attrName, and attrType
+ var id = propertyList.length > 0 ? propertyList[ 0 ] : '';
+ var attrName = propertyList.length > 1 ? propertyList[ 1 ] : '';
+ var attrType = propertyList.length > 2 ? propertyList[ 2 ] : '';
+
+ // check if this node represents just a single property
+ // like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]}
+ node.singleProperty = ( numProperties === 1 && reader.getOffset() === endOffset ) ? true : false;
+
+ while ( endOffset > reader.getOffset() ) {
+
+ var subNode = this.parseNode( reader, version );
+
+ if ( subNode !== null ) this.parseSubNode( name, node, subNode );
+
+ }
+
+ node.propertyList = propertyList; // raw property list used by parent
+
+ if ( typeof id === 'number' ) node.id = id;
+ if ( attrName !== '' ) node.attrName = attrName;
+ if ( attrType !== '' ) node.attrType = attrType;
+ if ( name !== '' ) node.name = name;
+
+ return node;
+
+ },
+
+ parseSubNode: function ( name, node, subNode ) {
+
+ // special case: child node is single property
+ if ( subNode.singleProperty === true ) {
+
+ var value = subNode.propertyList[ 0 ];
+
+ if ( Array.isArray( value ) ) {
+
+ node[ subNode.name ] = subNode;
+
+ subNode.a = value;
+
+ } else {
+
+ node[ subNode.name ] = value;
+
+ }
+
+ } else if ( name === 'Connections' && subNode.name === 'C' ) {
+
+ var array = [];
+
+ subNode.propertyList.forEach( function ( property, i ) {
+
+ // first Connection is FBX type (OO, OP, etc.). We'll discard these
+ if ( i !== 0 ) array.push( property );
+
+ } );
+
+ if ( node.connections === undefined ) {
+
+ node.connections = [];
+
+ }
+
+ node.connections.push( array );
+
+ } else if ( subNode.name === 'Properties70' ) {
+
+ var keys = Object.keys( subNode );
+
+ keys.forEach( function ( key ) {
+
+ node[ key ] = subNode[ key ];
+
+ } );
+
+ } else if ( name === 'Properties70' && subNode.name === 'P' ) {
+
+ var innerPropName = subNode.propertyList[ 0 ];
+ var innerPropType1 = subNode.propertyList[ 1 ];
+ var innerPropType2 = subNode.propertyList[ 2 ];
+ var innerPropFlag = subNode.propertyList[ 3 ];
+ var innerPropValue;
+
+ if ( innerPropName.indexOf( 'Lcl ' ) === 0 ) innerPropName = innerPropName.replace( 'Lcl ', 'Lcl_' );
+ if ( innerPropType1.indexOf( 'Lcl ' ) === 0 ) innerPropType1 = innerPropType1.replace( 'Lcl ', 'Lcl_' );
+
+ if ( innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf( 'Lcl_' ) === 0 ) {
+
+ innerPropValue = [
+ subNode.propertyList[ 4 ],
+ subNode.propertyList[ 5 ],
+ subNode.propertyList[ 6 ]
+ ];
+
+ } else {
+
+ innerPropValue = subNode.propertyList[ 4 ];
+
+ }
+
+ // this will be copied to parent, see above
+ node[ innerPropName ] = {
+
+ 'type': innerPropType1,
+ 'type2': innerPropType2,
+ 'flag': innerPropFlag,
+ 'value': innerPropValue
+
+ };
+
+ } else if ( node[ subNode.name ] === undefined ) {
+
+ if ( typeof subNode.id === 'number' ) {
+
+ node[ subNode.name ] = {};
+ node[ subNode.name ][ subNode.id ] = subNode;
+
+ } else {
+
+ node[ subNode.name ] = subNode;
+
+ }
+
+ } else {
+
+ if ( subNode.name === 'PoseNode' ) {
+
+ if ( ! Array.isArray( node[ subNode.name ] ) ) {
+
+ node[ subNode.name ] = [ node[ subNode.name ] ];
+
+ }
+
+ node[ subNode.name ].push( subNode );
+
+ } else if ( node[ subNode.name ][ subNode.id ] === undefined ) {
+
+ node[ subNode.name ][ subNode.id ] = subNode;
+
+ }
+
+ }
+
+ },
+
+ parseProperty: function ( reader ) {
+
+ var type = reader.getString( 1 );
+
+ switch ( type ) {
+
+ case 'C':
+ return reader.getBoolean();
+
+ case 'D':
+ return reader.getFloat64();
+
+ case 'F':
+ return reader.getFloat32();
+
+ case 'I':
+ return reader.getInt32();
+
+ case 'L':
+ return reader.getInt64();
+
+ case 'R':
+ var length = reader.getUint32();
+ return reader.getArrayBuffer( length );
+
+ case 'S':
+ var length = reader.getUint32();
+ return reader.getString( length );
+
+ case 'Y':
+ return reader.getInt16();
+
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'f':
+ case 'i':
+ case 'l':
+
+ var arrayLength = reader.getUint32();
+ var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed
+ var compressedLength = reader.getUint32();
+
+ if ( encoding === 0 ) {
+
+ switch ( type ) {
+
+ case 'b':
+ case 'c':
+ return reader.getBooleanArray( arrayLength );
+
+ case 'd':
+ return reader.getFloat64Array( arrayLength );
+
+ case 'f':
+ return reader.getFloat32Array( arrayLength );
+
+ case 'i':
+ return reader.getInt32Array( arrayLength );
+
+ case 'l':
+ return reader.getInt64Array( arrayLength );
+
+ }
+
+ }
+
+ if ( typeof Zlib === 'undefined' ) {
+
+ console.error( 'THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js' );
+
+ }
+
+ var inflate = new Zlib.Inflate( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef
+ var reader2 = new BinaryReader( inflate.decompress().buffer );
+
+ switch ( type ) {
+
+ case 'b':
+ case 'c':
+ return reader2.getBooleanArray( arrayLength );
+
+ case 'd':
+ return reader2.getFloat64Array( arrayLength );
+
+ case 'f':
+ return reader2.getFloat32Array( arrayLength );
+
+ case 'i':
+ return reader2.getInt32Array( arrayLength );
+
+ case 'l':
+ return reader2.getInt64Array( arrayLength );
+
+ }
+
+ default:
+ throw new Error( 'THREE.FBXLoader: Unknown property type ' + type );
+
+ }
+
+ }
+
+ };
+
+ function BinaryReader( buffer, littleEndian ) {
+
+ this.dv = new DataView( buffer );
+ this.offset = 0;
+ this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true;
+
+ }
+
+ BinaryReader.prototype = {
+
+ constructor: BinaryReader,
+
+ getOffset: function () {
+
+ return this.offset;
+
+ },
+
+ size: function () {
+
+ return this.dv.buffer.byteLength;
+
+ },
+
+ skip: function ( length ) {
+
+ this.offset += length;
+
+ },
+
+ // seems like true/false representation depends on exporter.
+ // true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54)
+ // then sees LSB.
+ getBoolean: function () {
+
+ return ( this.getUint8() & 1 ) === 1;
+
+ },
+
+ getBooleanArray: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getBoolean() );
+
+ }
+
+ return a;
+
+ },
+
+ getUint8: function () {
+
+ var value = this.dv.getUint8( this.offset );
+ this.offset += 1;
+ return value;
+
+ },
+
+ getInt16: function () {
+
+ var value = this.dv.getInt16( this.offset, this.littleEndian );
+ this.offset += 2;
+ return value;
+
+ },
+
+ getInt32: function () {
+
+ var value = this.dv.getInt32( this.offset, this.littleEndian );
+ this.offset += 4;
+ return value;
+
+ },
+
+ getInt32Array: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getInt32() );
+
+ }
+
+ return a;
+
+ },
+
+ getUint32: function () {
+
+ var value = this.dv.getUint32( this.offset, this.littleEndian );
+ this.offset += 4;
+ return value;
+
+ },
+
+ // JavaScript doesn't support 64-bit integer so calculate this here
+ // 1 << 32 will return 1 so using multiply operation instead here.
+ // There's a possibility that this method returns wrong value if the value
+ // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER.
+ // TODO: safely handle 64-bit integer
+ getInt64: function () {
+
+ var low, high;
+
+ if ( this.littleEndian ) {
+
+ low = this.getUint32();
+ high = this.getUint32();
+
+ } else {
+
+ high = this.getUint32();
+ low = this.getUint32();
+
+ }
+
+ // calculate negative value
+ if ( high & 0x80000000 ) {
+
+ high = ~ high & 0xFFFFFFFF;
+ low = ~ low & 0xFFFFFFFF;
+
+ if ( low === 0xFFFFFFFF ) high = ( high + 1 ) & 0xFFFFFFFF;
+
+ low = ( low + 1 ) & 0xFFFFFFFF;
+
+ return - ( high * 0x100000000 + low );
+
+ }
+
+ return high * 0x100000000 + low;
+
+ },
+
+ getInt64Array: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getInt64() );
+
+ }
+
+ return a;
+
+ },
+
+ // Note: see getInt64() comment
+ getUint64: function () {
+
+ var low, high;
+
+ if ( this.littleEndian ) {
+
+ low = this.getUint32();
+ high = this.getUint32();
+
+ } else {
+
+ high = this.getUint32();
+ low = this.getUint32();
+
+ }
+
+ return high * 0x100000000 + low;
+
+ },
+
+ getFloat32: function () {
+
+ var value = this.dv.getFloat32( this.offset, this.littleEndian );
+ this.offset += 4;
+ return value;
+
+ },
+
+ getFloat32Array: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getFloat32() );
+
+ }
+
+ return a;
+
+ },
+
+ getFloat64: function () {
+
+ var value = this.dv.getFloat64( this.offset, this.littleEndian );
+ this.offset += 8;
+ return value;
+
+ },
+
+ getFloat64Array: function ( size ) {
+
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a.push( this.getFloat64() );
+
+ }
+
+ return a;
+
+ },
+
+ getArrayBuffer: function ( size ) {
+
+ var value = this.dv.buffer.slice( this.offset, this.offset + size );
+ this.offset += size;
+ return value;
+
+ },
+
+ getString: function ( size ) {
+
+ // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
+ var a = [];
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ a[ i ] = this.getUint8();
+
+ }
+
+ var nullByte = a.indexOf( 0 );
+ if ( nullByte >= 0 ) a = a.slice( 0, nullByte );
+
+ return THREE.LoaderUtils.decodeText( new Uint8Array( a ) );
+
+ }
+
+ };
+
+ // FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format)
+ // and BinaryParser( FBX Binary format)
+ function FBXTree() {}
+
+ FBXTree.prototype = {
+
+ constructor: FBXTree,
+
+ add: function ( key, val ) {
+
+ this[ key ] = val;
+
+ },
+
+ };
+
+ // ************** UTILITY FUNCTIONS **************
+
+ function isFbxFormatBinary( buffer ) {
+
+ var CORRECT = 'Kaydara FBX Binary \0';
+
+ return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length );
+
+ }
+
+ function isFbxFormatASCII( text ) {
+
+ var CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ];
+
+ var cursor = 0;
+
+ function read( offset ) {
+
+ var result = text[ offset - 1 ];
+ text = text.slice( cursor + offset );
+ cursor ++;
+ return result;
+
+ }
+
+ for ( var i = 0; i < CORRECT.length; ++ i ) {
+
+ var num = read( 1 );
+ if ( num === CORRECT[ i ] ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ function getFbxVersion( text ) {
+
+ var versionRegExp = /FBXVersion: (\d+)/;
+ var match = text.match( versionRegExp );
+ if ( match ) {
+
+ var version = parseInt( match[ 1 ] );
+ return version;
+
+ }
+ throw new Error( 'THREE.FBXLoader: Cannot find the version number for the file given.' );
+
+ }
+
+ // Converts FBX ticks into real time seconds.
+ function convertFBXTimeToSeconds( time ) {
+
+ return time / 46186158000;
+
+ }
+
+ var dataArray = [];
+
+ // extracts the data from the correct position in the FBX array based on indexing type
+ function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+ var index;
+
+ switch ( infoObject.mappingType ) {
+
+ case 'ByPolygonVertex' :
+ index = polygonVertexIndex;
+ break;
+ case 'ByPolygon' :
+ index = polygonIndex;
+ break;
+ case 'ByVertice' :
+ index = vertexIndex;
+ break;
+ case 'AllSame' :
+ index = infoObject.indices[ 0 ];
+ break;
+ default :
+ console.warn( 'THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType );
+
+ }
+
+ if ( infoObject.referenceType === 'IndexToDirect' ) index = infoObject.indices[ index ];
+
+ var from = index * infoObject.dataSize;
+ var to = from + infoObject.dataSize;
+
+ return slice( dataArray, infoObject.buffer, from, to );
+
+ }
+
+ var tempEuler = new THREE.Euler();
+ var tempVec = new THREE.Vector3();
+
+ // generate transformation from FBX transform data
+ // ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm
+ // ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e
+ function generateTransform( transformData ) {
+
+ var lTranslationM = new THREE.Matrix4();
+ var lPreRotationM = new THREE.Matrix4();
+ var lRotationM = new THREE.Matrix4();
+ var lPostRotationM = new THREE.Matrix4();
+
+ var lScalingM = new THREE.Matrix4();
+ var lScalingPivotM = new THREE.Matrix4();
+ var lScalingOffsetM = new THREE.Matrix4();
+ var lRotationOffsetM = new THREE.Matrix4();
+ var lRotationPivotM = new THREE.Matrix4();
+
+ var lParentGX = new THREE.Matrix4();
+ var lGlobalT = new THREE.Matrix4();
+
+ var inheritType = ( transformData.inheritType ) ? transformData.inheritType : 0;
+
+ if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) );
+
+ if ( transformData.preRotation ) {
+
+ var array = transformData.preRotation.map( THREE.Math.degToRad );
+ array.push( transformData.eulerOrder );
+ lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+ }
+
+ if ( transformData.rotation ) {
+
+ var array = transformData.rotation.map( THREE.Math.degToRad );
+ array.push( transformData.eulerOrder );
+ lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+ }
+
+ if ( transformData.postRotation ) {
+
+ var array = transformData.postRotation.map( THREE.Math.degToRad );
+ array.push( transformData.eulerOrder );
+ lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+ }
+
+ if ( transformData.scale ) lScalingM.scale( tempVec.fromArray( transformData.scale ) );
+
+ // Pivots and offsets
+ if ( transformData.scalingOffset ) lScalingOffsetM.setPosition( tempVec.fromArray( transformData.scalingOffset ) );
+ if ( transformData.scalingPivot ) lScalingPivotM.setPosition( tempVec.fromArray( transformData.scalingPivot ) );
+ if ( transformData.rotationOffset ) lRotationOffsetM.setPosition( tempVec.fromArray( transformData.rotationOffset ) );
+ if ( transformData.rotationPivot ) lRotationPivotM.setPosition( tempVec.fromArray( transformData.rotationPivot ) );
+
+ // parent transform
+ if ( transformData.parentMatrixWorld ) lParentGX = transformData.parentMatrixWorld;
+
+ // Global Rotation
+ var lLRM = lPreRotationM.multiply( lRotationM ).multiply( lPostRotationM );
+ var lParentGRM = new THREE.Matrix4();
+ lParentGX.extractRotation( lParentGRM );
+
+ // Global Shear*Scaling
+ var lParentTM = new THREE.Matrix4();
+ var lLSM;
+ var lParentGSM;
+ var lParentGRSM;
+
+ lParentTM.copyPosition( lParentGX );
+ lParentGRSM = lParentTM.getInverse( lParentTM ).multiply( lParentGX );
+ lParentGSM = lParentGRM.getInverse( lParentGRM ).multiply( lParentGRSM );
+ lLSM = lScalingM;
+
+ var lGlobalRS;
+ if ( inheritType === 0 ) {
+
+ lGlobalRS = lParentGRM.multiply( lLRM ).multiply( lParentGSM ).multiply( lLSM );
+
+ } else if ( inheritType === 1 ) {
+
+ lGlobalRS = lParentGRM.multiply( lParentGSM ).multiply( lLRM ).multiply( lLSM );
+
+ } else {
+
+ var lParentLSM = new THREE.Matrix4().copy( lScalingM );
+
+ var lParentGSM_noLocal = lParentGSM.multiply( lParentLSM.getInverse( lParentLSM ) );
+
+ lGlobalRS = lParentGRM.multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lLSM );
+
+ }
+
+ // Calculate the local transform matrix
+ var lTransform = lTranslationM.multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM.getInverse( lRotationPivotM ) ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM.getInverse( lScalingPivotM ) );
+
+ var lLocalTWithAllPivotAndOffsetInfo = new THREE.Matrix4().copyPosition( lTransform );
+
+ var lGlobalTranslation = lParentGX.multiply( lLocalTWithAllPivotAndOffsetInfo );
+ lGlobalT.copyPosition( lGlobalTranslation );
+
+ lTransform = lGlobalT.multiply( lGlobalRS );
+
+ return lTransform;
+
+ }
+
+ // Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order
+ // ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
+ function getEulerOrder( order ) {
+
+ order = order || 0;
+
+ var enums = [
+ 'ZYX', // -> XYZ extrinsic
+ 'YZX', // -> XZY extrinsic
+ 'XZY', // -> YZX extrinsic
+ 'ZXY', // -> YXZ extrinsic
+ 'YXZ', // -> ZXY extrinsic
+ 'XYZ', // -> ZYX extrinsic
+ //'SphericXYZ', // not possible to support
+ ];
+
+ if ( order === 6 ) {
+
+ console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
+ return enums[ 0 ];
+
+ }
+
+ return enums[ order ];
+
+ }
+
+ // Parses comma separated list of numbers and returns them an array.
+ // Used internally by the TextParser
+ function parseNumberArray( value ) {
+
+ var array = value.split( ',' ).map( function ( val ) {
+
+ return parseFloat( val );
+
+ } );
+
+ return array;
+
+ }
+
+ function convertArrayBufferToString( buffer, from, to ) {
+
+ if ( from === undefined ) from = 0;
+ if ( to === undefined ) to = buffer.byteLength;
+
+ return THREE.LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) );
+
+ }
+
+ function append( a, b ) {
+
+ for ( var i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) {
+
+ a[ j ] = b[ i ];
+
+ }
+
+ }
+
+ function slice( a, b, from, to ) {
+
+ for ( var i = from, j = 0; i < to; i ++, j ++ ) {
+
+ a[ j ] = b[ i ];
+
+ }
+
+ return a;
+
+ }
+
+ // inject array a2 into array a1 at index
+ function inject( a1, index, a2 ) {
+
+ return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) );
+
+ }
+
+ return FBXLoader;
+
+} )();
diff --git a/src/jlmap3d/main/loaders/FBXLoader2.js b/src/jlmap3d/main/loaders/FBXLoader2.js
new file mode 100644
index 000000000..fed5c2ed1
--- /dev/null
+++ b/src/jlmap3d/main/loaders/FBXLoader2.js
@@ -0,0 +1,3861 @@
+/**
+ * @author Kyle-Larson https://github.com/Kyle-Larson
+ *
+ * Loader loads FBX file and generates Group representing FBX scene.
+ * Requires FBX file to be >= 7.0 and in ASCII format.
+ *
+ * Supports:
+ * Mesh Generation (Positional Data)
+ * Normal Data (Per Vertex Drawing Instance)
+ * UV Data (Per Vertex Drawing Instance)
+ * Skinning
+ * Animation
+ * - Separated Animations based on stacks.
+ * - Skeletal & Non-Skeletal Animations
+ *
+ * Needs Support:
+ * Indexed Buffers
+ * PreRotation support.
+ */
+
+
+( function () {
+
+ /**
+ * Generates a loader for loading FBX files from URL and parsing into
+ * a THREE.Group.
+ * @param {THREE.LoadingManager} manager - Loading Manager for loader to use.
+ */
+ THREE.FBXLoader = function ( manager ) {
+
+ THREE.Loader.call( this );
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+ this.fileLoader = new THREE.FileLoader( this.manager );
+ this.textureLoader = new THREE.TextureLoader( this.manager );
+
+ };
+
+ Object.assign( THREE.FBXLoader.prototype, THREE.Loader.prototype );
+
+ THREE.FBXLoader.prototype.constructor = THREE.FBXLoader;
+
+ Object.assign( THREE.FBXLoader.prototype, {
+
+ /**
+ * Loads an ASCII FBX file from URL and parses into a THREE.Group.
+ * THREE.Group will have an animations property of AnimationClips
+ * of the different animations exported with the FBX.
+ * @param {string} url - URL of the FBX file.
+ * @param {function(THREE.Group):void} onLoad - Callback for when FBX file is loaded and parsed.
+ * @param {function(ProgressEvent):void} onProgress - Callback fired periodically when file is being retrieved from server.
+ * @param {function(Event):void} onError - Callback fired when error occurs (Currently only with retrieving file, not with parsing errors).
+ */
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var self = this;
+
+ var resourceDirectory = url.split( /[\\\/]/ );
+ resourceDirectory.pop();
+ resourceDirectory = resourceDirectory.join( '/' );
+
+ this.fileLoader.load( url, function ( text ) {
+
+ if ( ! isFbxFormatASCII( text ) ) {
+
+ console.error( 'FBXLoader: FBX Binary format not supported.' );
+ self.manager.itemError( url );
+ return;
+
+ }
+ if ( getFbxVersion( text ) < 7000 ) {
+
+ console.error( 'FBXLoader: FBX version not supported for file at ' + url + ', FileVersion: ' + getFbxVersion( text ) );
+ self.manager.itemError( url );
+ return;
+
+ }
+
+ var scene = self.parse( text, resourceDirectory );
+ onLoad( scene );
+
+ }, onProgress, onError );
+
+ },
+
+ /**
+ * Parses an ASCII FBX file and returns a THREE.Group.
+ * THREE.Group will have an animations property of AnimationClips
+ * of the different animations within the FBX file.
+ * @param {string} FBXText - Contents of FBX file to parse.
+ * @param {string} resourceDirectory - Directory to load external assets (e.g. textures ) from.
+ * @returns {THREE.Group}
+ */
+ parse: function ( FBXText, resourceDirectory ) {
+
+ var loader = this;
+
+ var FBXTree = new TextParser().parse( FBXText );
+
+ var connections = parseConnections( FBXTree );
+
+ var textures = parseTextures( FBXTree );
+
+ var materials = parseMaterials( FBXTree, textures, connections );
+
+ var deformerMap = parseDeformers( FBXTree, connections );
+
+ var geometryMap = parseGeometries( FBXTree, connections, deformerMap );
+
+ var sceneGraph = parseScene( FBXTree, connections, deformerMap, geometryMap, materials );
+
+ return sceneGraph;
+
+
+ /**
+ * @typedef {{value: number}} FBXValue
+ */
+ /**
+ * @typedef {{value: {x: string, y: string, z: string}}} FBXVector3
+ */
+ /**
+ * @typedef {{properties: {a: string}}} FBXArrayNode
+ */
+ /**
+ * @typedef {{properties: {MappingInformationType: string, ReferenceInformationType: string }, subNodes: Object}} FBXMappedArrayNode
+ */
+ /**
+ * @typedef {{id: number, name: string, properties: {FileName: string}}} FBXTextureNode
+ */
+ /**
+ * @typedef {{id: number, attrName: string, properties: {ShadingModel: string, Diffuse: FBXVector3, Specular: FBXVector3, Shininess: FBXValue, Emissive: FBXVector3, EmissiveFactor: FBXValue, Opacity: FBXValue}}} FBXMaterialNode
+ */
+ /**
+ * @typedef {{subNodes: {Indexes: FBXArrayNode, Weights: FBXArrayNode, Transform: FBXArrayNode, TransformLink: FBXArrayNode}, properties: { Mode: string }}} FBXSubDeformerNode
+ */
+ /**
+ * @typedef {{id: number, attrName: string, attrType: string, subNodes: {Vertices: FBXArrayNode, PolygonVertexIndex: FBXArrayNode, LayerElementNormal: FBXMappedArrayNode[], LayerElementMaterial: FBXMappedArrayNode[], LayerElementUV: FBXMappedArrayNode[]}}} FBXGeometryNode
+ */
+ /**
+ * @typedef {{id: number, attrName: string, attrType: string, properties: {Lcl_Translation: FBXValue, Lcl_Rotation: FBXValue, Lcl_Scaling: FBXValue}}} FBXModelNode
+ */
+
+
+
+
+
+
+
+
+ /**
+ * Parses map of relationships between objects.
+ * @param {{Connections: { properties: { connections: [number, number, string][]}}}} FBXTree
+ * @returns {Map}
+ */
+ function parseConnections( FBXTree ) {
+
+ /**
+ * @type {Map}
+ */
+ var connectionMap = new Map();
+
+ if ( 'Connections' in FBXTree ) {
+
+ /**
+ * @type {[number, number, string][]}
+ */
+ var connectionArray = FBXTree.Connections.properties.connections;
+ connectionArray.forEach( function ( connection ) {
+
+ if ( ! connectionMap.has( connection[ 0 ] ) ) {
+
+ connectionMap.set( connection[ 0 ], {
+ parents: [],
+ children: []
+ } );
+
+ }
+
+ var parentRelationship = { ID: connection[ 1 ], relationship: connection[ 2 ] };
+ connectionMap.get( connection[ 0 ] ).parents.push( parentRelationship );
+
+ if ( ! connectionMap.has( connection[ 1 ] ) ) {
+
+ connectionMap.set( connection[ 1 ], {
+ parents: [],
+ children: []
+ } );
+
+ }
+
+ var childRelationship = { ID: connection[ 0 ], relationship: connection[ 2 ] };
+ connectionMap.get( connection[ 1 ] ).children.push( childRelationship );
+
+ } );
+
+ }
+
+ return connectionMap;
+
+ }
+
+ /**
+ * Parses map of textures referenced in FBXTree.
+ * @param {{Objects: {subNodes: {Texture: Object.}}}} FBXTree
+ * @returns {Map}
+ */
+ function parseTextures( FBXTree ) {
+
+ /**
+ * @type {Map}
+ */
+ var textureMap = new Map();
+
+ if ( 'Texture' in FBXTree.Objects.subNodes ) {
+
+ var textureNodes = FBXTree.Objects.subNodes.Texture;
+ for ( var nodeID in textureNodes ) {
+
+ var texture = parseTexture( textureNodes[ nodeID ] );
+ textureMap.set( parseInt( nodeID ), texture );
+
+ }
+
+ }
+
+ return textureMap;
+
+ /**
+ * @param {textureNode} textureNode - Node to get texture information from.
+ * @returns {THREE.Texture}
+ */
+ function parseTexture( textureNode ) {
+
+ var FBX_ID = textureNode.id;
+ var name = textureNode.name;
+ var filePath = textureNode.properties.FileName;
+ var split = filePath.split( /[\\\/]/ );
+ if ( split.length > 0 ) {
+
+ var fileName = split[ split.length - 1 ];
+
+ } else {
+
+ var fileName = filePath;
+
+ }
+ /**
+ * @type {THREE.Texture}
+ */
+ var texture = loader.textureLoader.load( resourceDirectory + '/' + fileName );
+ texture.name = name;
+ texture.FBX_ID = FBX_ID;
+
+ return texture;
+
+ }
+
+ }
+
+ /**
+ * Parses map of Material information.
+ * @param {{Objects: {subNodes: {Material: Object.}}}} FBXTree
+ * @param {Map} textureMap
+ * @param {Map} connections
+ * @returns {Map}
+ */
+ function parseMaterials( FBXTree, textureMap, connections ) {
+
+ var materialMap = new Map();
+
+ if ( 'Material' in FBXTree.Objects.subNodes ) {
+
+ var materialNodes = FBXTree.Objects.subNodes.Material;
+ for ( var nodeID in materialNodes ) {
+
+ var material = parseMaterial( materialNodes[ nodeID ], textureMap, connections );
+ materialMap.set( parseInt( nodeID ), material );
+
+ }
+
+ }
+
+ return materialMap;
+
+ /**
+ * Takes information from Material node and returns a generated THREE.Material
+ * @param {FBXMaterialNode} materialNode
+ * @param {Map} textureMap
+ * @param {Map} connections
+ * @returns {THREE.Material}
+ */
+ function parseMaterial( materialNode, textureMap, connections ) {
+
+ var FBX_ID = materialNode.id;
+ var name = materialNode.attrName;
+ var type = materialNode.properties.ShadingModel;
+
+ var children = connections.get( FBX_ID ).children;
+
+ var parameters = parseParameters( materialNode.properties, textureMap, children );
+
+ var material;
+ switch ( type ) {
+
+ case 'phong':
+ material = new THREE.MeshPhongMaterial();
+ break;
+ case 'lambert':
+ material = new THREE.MeshLambertMaterial();
+ break;
+ default:
+ console.warn( 'No implementation given for material type ' + type + ' in FBXLoader.js. Defaulting to basic material' );
+ material = new THREE.MeshBasicMaterial( { color: 0x3300ff } );
+ break;
+
+ }
+
+ material.setValues( parameters );
+ material.name = name;
+
+ return material;
+
+ /**
+ * @typedef {{Diffuse: FBXVector3, Specular: FBXVector3, Shininess: FBXValue, Emissive: FBXVector3, EmissiveFactor: FBXValue, Opacity: FBXValue}} FBXMaterialProperties
+ */
+ /**
+ * @typedef {{color: THREE.Color=, specular: THREE.Color=, shininess: number=, emissive: THREE.Color=, emissiveIntensity: number=, opacity: number=, transparent: boolean=, map: THREE.Texture=}} THREEMaterialParameterPack
+ */
+ /**
+ * @param {FBXMaterialProperties} properties
+ * @param {Map} textureMap
+ * @param {{ID: number, relationship: string}[]} childrenRelationships
+ * @returns {THREEMaterialParameterPack}
+ */
+ function parseParameters( properties, textureMap, childrenRelationships ) {
+
+ var parameters = {};
+
+ if ( properties.Diffuse ) {
+
+ parameters.color = parseColor( properties.Diffuse );
+
+ }
+ if ( properties.Specular ) {
+
+ parameters.specular = parseColor( properties.Specular );
+
+ }
+ if ( properties.Shininess ) {
+
+ parameters.shininess = properties.Shininess.value;
+
+ }
+ if ( properties.Emissive ) {
+
+ parameters.emissive = parseColor( properties.Emissive );
+
+ }
+ if ( properties.EmissiveFactor ) {
+
+ parameters.emissiveIntensity = properties.EmissiveFactor.value;
+
+ }
+ if ( properties.Opacity ) {
+
+ parameters.opacity = properties.Opacity.value;
+
+ }
+ if ( parameters.opacity < 1.0 ) {
+
+ parameters.transparent = true;
+
+ }
+
+ childrenRelationships.forEach( function ( relationship ) {
+
+ var type = relationship.relationship;
+ switch ( type ) {
+
+ case " \"AmbientColor":
+ //TODO: Support AmbientColor textures
+ break;
+
+ case " \"DiffuseColor":
+ parameters.map = textureMap.get( relationship.ID );
+ break;
+
+ default:
+ console.warn( 'Unknown texture application of type ' + type + ', skipping texture' );
+ break;
+
+ }
+
+ } );
+
+ return parameters;
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Generates map of Skeleton-like objects for use later when generating and binding skeletons.
+ * @param {{Objects: {subNodes: {Deformer: Object.}}}} FBXTree
+ * @param {Map} connections
+ * @returns {Map, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[], skeleton: THREE.Skeleton|null}>}
+ */
+ function parseDeformers( FBXTree, connections ) {
+
+ var skeletonMap = new Map();
+
+ if ( 'Deformer' in FBXTree.Objects.subNodes ) {
+
+ var DeformerNodes = FBXTree.Objects.subNodes.Deformer;
+ for ( var nodeID in DeformerNodes ) {
+
+ var deformerNode = DeformerNodes[ nodeID ];
+ if ( deformerNode.attrType === 'Skin' ) {
+
+ var conns = connections.get( parseInt( nodeID ) );
+ var skeleton = parseSkeleton( conns, DeformerNodes );
+ skeleton.FBX_ID = parseInt( nodeID );
+ skeletonMap.set( parseInt( nodeID ), skeleton );
+
+ }
+
+ }
+
+ }
+
+ return skeletonMap;
+
+ /**
+ * Generates a "Skeleton Representation" of FBX nodes based on an FBX Skin Deformer's connections and an object containing SubDeformer nodes.
+ * @param {{parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}} connections
+ * @param {Object.} DeformerNodes
+ * @returns {{map: Map, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[], skeleton: THREE.Skeleton|null}}
+ */
+ function parseSkeleton( connections, DeformerNodes ) {
+
+ var subDeformers = new Map();
+ var subDeformerArray = [];
+ connections.children.forEach( function ( child ) {
+
+ var subDeformerNode = DeformerNodes[ child.ID ];
+ var subDeformer = {
+ FBX_ID: child.ID,
+ indices: parseIntArray( subDeformerNode.subNodes.Indexes.properties.a ),
+ weights: parseFloatArray( subDeformerNode.subNodes.Weights.properties.a ),
+ transform: parseMatrixArray( subDeformerNode.subNodes.Transform.properties.a ),
+ transformLink: parseMatrixArray( subDeformerNode.subNodes.TransformLink.properties.a ),
+ linkMode: subDeformerNode.properties.Mode
+ };
+ subDeformers.set( child.ID, subDeformer );
+ subDeformerArray.push( subDeformer );
+
+ } );
+
+ return {
+ map: subDeformers,
+ array: subDeformerArray,
+ bones: []
+ };
+
+ }
+
+ }
+
+ /**
+ * Generates Buffer geometries from geometry information in FBXTree, and generates map of THREE.BufferGeometries
+ * @param {{Objects: {subNodes: {Geometry: Object.} connections
+ * @param {Map, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[], skeleton: THREE.Skeleton|null}>} deformerMap
+ * @returns {Map}
+ */
+ function parseGeometries( FBXTree, connections, deformerMap ) {
+
+ var geometryMap = new Map();
+
+ if ( 'Geometry' in FBXTree.Objects.subNodes ) {
+
+ var geometryNodes = FBXTree.Objects.subNodes.Geometry;
+ for ( var nodeID in geometryNodes ) {
+
+ var relationships = connections.get( parseInt( nodeID ) );
+ var geo = parseGeometry( geometryNodes[ nodeID ], relationships, deformerMap );
+ geometryMap.set( parseInt( nodeID ), geo );
+
+ }
+
+ }
+
+ return geometryMap;
+
+ /**
+ * Generates BufferGeometry from FBXGeometryNode.
+ * @param {FBXGeometryNode} geometryNode
+ * @param {{parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}} relationships
+ * @param {Map, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[]}>} deformerMap
+ * @returns {THREE.BufferGeometry}
+ */
+ function parseGeometry( geometryNode, relationships, deformerMap ) {
+
+ switch ( geometryNode.attrType ) {
+
+ case 'Mesh':
+ return parseMeshGeometry( geometryNode, relationships, deformerMap );
+ break;
+
+ case 'NurbsCurve':
+ return parseNurbsGeometry( geometryNode, relationships, deformerMap );
+ break;
+
+ }
+
+ /**
+ * Specialty function for parsing Mesh based Geometry Nodes.
+ * @param {FBXGeometryNode} geometryNode
+ * @param {{parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}} relationships - Object representing relationships between specific geometry node and other nodes.
+ * @param {Map, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[]}>} deformerMap - Map object of deformers and subDeformers by ID.
+ * @returns {THREE.BufferGeometry}
+ */
+ function parseMeshGeometry( geometryNode, relationships, deformerMap ) {
+
+ var FBX_ID = geometryNode.id;
+ var name = geometryNode.attrName;
+ for ( var i = 0; i < relationships.children.length; ++ i ) {
+
+ if ( deformerMap.has( relationships.children[ i ].ID ) ) {
+
+ var deformer = deformerMap.get( relationships.children[ i ].ID );
+ break;
+
+ }
+
+ }
+
+ var geometry = genGeometry( geometryNode, deformer );
+
+ return geometry;
+
+ /**
+ * @param {{map: Map, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[]}} deformer - Skeleton representation for geometry instance.
+ * @returns {THREE.BufferGeometry}
+ */
+ function genGeometry( geometryNode, deformer ) {
+
+ var geometry = new Geometry();
+
+ //First, each index is going to be its own vertex.
+ var vertexBuffer = parseFloatArray( geometryNode.subNodes.Vertices.properties.a );
+ var indexBuffer = parseIntArray( geometryNode.subNodes.PolygonVertexIndex.properties.a );
+
+ if ( 'LayerElementNormal' in geometryNode.subNodes ) {
+
+ var normalInfo = getNormals( geometryNode );
+
+ }
+
+ if ( 'LayerElementUV' in geometryNode.subNodes ) {
+
+ var uvInfo = getUVs( geometryNode );
+
+ }
+
+ if ( 'LayerElementMaterial' in geometryNode.subNodes ) {
+
+ var materialInfo = getMaterials( geometryNode );
+
+ }
+
+ var faceVertexBuffer = [];
+ var polygonIndex = 0;
+ for ( var polygonVertexIndex = 0; polygonVertexIndex < indexBuffer.length; ++ polygonVertexIndex ) {
+
+ var endOfFace;
+ var vertexIndex = indexBuffer[ polygonVertexIndex ];
+ if ( indexBuffer[ polygonVertexIndex ] < 0 ) {
+
+ vertexIndex = vertexIndex ^ - 1;
+ indexBuffer[ polygonVertexIndex ] = vertexIndex;
+ endOfFace = true;
+
+ }
+ var vertex = new Vertex();
+ var weightIndices = [];
+ var weights = [];
+ vertex.position.fromArray( vertexBuffer, vertexIndex * 3 );
+
+ // If we have a deformer for this geometry, get the skinIndex and skinWeights for this object.
+ // They are stored as vertex indices on each deformer, and we need them as deformer indices
+ // for each vertex.
+ if ( deformer ) {
+
+ for ( var j = 0; j < deformer.array.length; ++ j ) {
+
+ var index = deformer.array[ j ].indices.findIndex( function ( index ) {
+
+ return index === indexBuffer[ polygonVertexIndex ];
+
+ } );
+
+ if ( index !== - 1 ) {
+
+ weights.push( deformer.array[ j ].weights[ index ] );
+ weightIndices.push( j );
+
+ }
+
+ }
+
+ if ( weights.length > 4 ) {
+
+ console.warn( 'FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' );
+
+ var WIndex = [ 0, 0, 0, 0 ];
+ var Weight = [ 0, 0, 0, 0 ];
+
+ for ( var polygonVertexIndex = 0; polygonVertexIndex < weights.length; ++ polygonVertexIndex ) {
+
+ var currentWeight = weights[ polygonVertexIndex ];
+ var currentIndex = weightIndices[ polygonVertexIndex ];
+ for ( var j = 0; j < Weight.length; ++ j ) {
+
+ if ( currentWeight > Weight[ j ] ) {
+
+ var tmp = Weight[ j ];
+ Weight[ j ] = currentWeight;
+ currentWeight = tmp;
+
+ tmp = WIndex[ j ];
+ WIndex[ j ] = currentIndex;
+ currentIndex = tmp;
+
+ }
+
+ }
+
+ }
+
+ weightIndices = WIndex;
+ weights = Weight;
+
+ }
+
+ for ( var i = weights.length; i < 4; i ++ ) {
+
+ weights[ i ] = 0;
+ weightIndices[ i ] = 0;
+
+ }
+
+ vertex.skinWeights.fromArray( weights );
+ vertex.skinIndices.fromArray( weightIndices );
+
+ //vertex.skinWeights.normalize();
+
+ }
+
+ if ( normalInfo ) {
+
+ vertex.normal.fromArray( getData( polygonVertexIndex, polygonIndex, vertexIndex, normalInfo ) );
+
+ }
+
+ if ( uvInfo ) {
+
+ vertex.uv.fromArray( getData( polygonVertexIndex, polygonIndex, vertexIndex, uvInfo ) );
+
+ }
+
+
+
+ //Add vertex to face buffer.
+ faceVertexBuffer.push( vertex );
+
+ // If index was negative to start with, we have finished this individual face
+ // and can generate the face data to the geometry.
+ if ( endOfFace ) {
+
+ var face = new Face();
+ var materials = getData( polygonVertexIndex, polygonIndex, vertexIndex, materialInfo );
+ face.genTrianglesFromVertices( faceVertexBuffer );
+ face.materialIndex = materials[ 0 ];
+ geometry.faces.push( face );
+ faceVertexBuffer = [];
+ polygonIndex ++;
+ endOfFace = false;
+
+ }
+
+ }
+
+ /**
+ * @type {{vertexBuffer: number[], normalBuffer: number[], uvBuffer: number[], skinIndexBuffer: number[], skinWeightBuffer: number[], materialIndexBuffer: number[]}}
+ */
+ var bufferInfo = geometry.flattenToBuffers();
+
+ var geo = new THREE.BufferGeometry();
+ geo.name = geometryNode.name;
+ geo.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( bufferInfo.vertexBuffer ), 3 ) );
+
+ if ( bufferInfo.normalBuffer.length > 0 ) {
+
+ geo.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( bufferInfo.normalBuffer ), 3 ) );
+
+ }
+ if ( bufferInfo.uvBuffer.length > 0 ) {
+
+ geo.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( bufferInfo.uvBuffer ), 2 ) );
+
+ }
+
+ if ( deformer ) {
+
+ geo.addAttribute( 'skinIndex', new THREE.BufferAttribute( new Float32Array( bufferInfo.skinIndexBuffer ), 4 ) );
+
+ geo.addAttribute( 'skinWeight', new THREE.BufferAttribute( new Float32Array( bufferInfo.skinWeightBuffer ), 4 ) );
+
+ geo.FBX_Deformer = deformer;
+
+ }
+
+ // Convert the material indices of each vertex into rendering groups on the geometry.
+ var prevMaterialIndex = bufferInfo.materialIndexBuffer[ 0 ];
+ var startIndex = 0;
+ for ( var materialBufferIndex = 0; materialBufferIndex < bufferInfo.materialIndexBuffer.length; ++ materialBufferIndex ) {
+
+ if ( bufferInfo.materialIndexBuffer[ materialBufferIndex ] !== prevMaterialIndex ) {
+
+ geo.addGroup( startIndex, materialBufferIndex - startIndex, prevMaterialIndex );
+ startIndex = materialBufferIndex;
+ prevMaterialIndex = bufferInfo.materialIndexBuffer[ materialBufferIndex ];
+
+ }
+
+ }
+
+ return geo;
+
+ /**
+ * Parses normal information for geometry.
+ * @param {FBXGeometryNode} geometryNode
+ * @returns {{dataSize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}}
+ */
+ function getNormals( geometryNode ) {
+
+ var NormalNode = geometryNode.subNodes.LayerElementNormal[ 0 ];
+
+ var mappingType = NormalNode.properties.MappingInformationType;
+ var referenceType = NormalNode.properties.ReferenceInformationType;
+ var buffer = parseFloatArray( NormalNode.subNodes.Normals.properties.a );
+ var indexBuffer = [];
+ if ( referenceType === 'IndexToDirect' ) {
+
+ indexBuffer = parseIntArray( NormalNode.subNodes.NormalIndex.properties.a );
+
+ }
+
+ return {
+ dataSize: 3,
+ buffer: buffer,
+ indices: indexBuffer,
+ mappingType: mappingType,
+ referenceType: referenceType
+ };
+
+ }
+
+ /**
+ * Parses UV information for geometry.
+ * @param {FBXGeometryNode} geometryNode
+ * @returns {{dataSize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}}
+ */
+ function getUVs( geometryNode ) {
+
+ var UVNode = geometryNode.subNodes.LayerElementUV[ 0 ];
+
+ var mappingType = UVNode.properties.MappingInformationType;
+ var referenceType = UVNode.properties.ReferenceInformationType;
+ var buffer = parseFloatArray( UVNode.subNodes.UV.properties.a );
+ var indexBuffer = [];
+ if ( referenceType === 'IndexToDirect' ) {
+
+ indexBuffer = parseIntArray( UVNode.subNodes.UVIndex.properties.a );
+
+ }
+
+ return {
+ dataSize: 2,
+ buffer: buffer,
+ indices: indexBuffer,
+ mappingType: mappingType,
+ referenceType: referenceType
+ };
+
+ }
+
+ /**
+ * Parses material application information for geometry.
+ * @param {FBXGeometryNode}
+ * @returns {{dataSize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}}
+ */
+ function getMaterials( geometryNode ) {
+
+ var MaterialNode = geometryNode.subNodes.LayerElementMaterial[ 0 ];
+ var mappingType = MaterialNode.properties.MappingInformationType;
+ var referenceType = MaterialNode.properties.ReferenceInformationType;
+ var materialIndexBuffer = parseIntArray( MaterialNode.subNodes.Materials.properties.a );
+
+ // Since materials are stored as indices, there's a bit of a mismatch between FBX and what
+ // we expect. So we create an intermediate buffer that points to the index in the buffer,
+ // for conforming with the other functions we've written for other data.
+ var materialIndices = [];
+ materialIndexBuffer.forEach( function ( materialIndex, index ) {
+
+ materialIndices.push( index );
+
+ } );
+
+ return {
+ dataSize: 1,
+ buffer: materialIndexBuffer,
+ indices: materialIndices,
+ mappingType: mappingType,
+ referenceType: referenceType
+ };
+
+ }
+
+ /**
+ * Function uses the infoObject and given indices to return value array of object.
+ * @param {number} polygonVertexIndex - Index of vertex in draw order (which index of the index buffer refers to this vertex).
+ * @param {number} polygonIndex - Index of polygon in geometry.
+ * @param {number} vertexIndex - Index of vertex inside vertex buffer (used because some data refers to old index buffer that we don't use anymore).
+ * @param {{datasize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}} infoObject - Object containing data and how to access data.
+ * @returns {number[]}
+ */
+ function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+ var GetData = {
+
+ ByPolygonVertex: {
+
+ /**
+ * Function uses the infoObject and given indices to return value array of object.
+ * @param {number} polygonVertexIndex - Index of vertex in draw order (which index of the index buffer refers to this vertex).
+ * @param {number} polygonIndex - Index of polygon in geometry.
+ * @param {number} vertexIndex - Index of vertex inside vertex buffer (used because some data refers to old index buffer that we don't use anymore).
+ * @param {{datasize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}} infoObject - Object containing data and how to access data.
+ * @returns {number[]}
+ */
+ Direct: function ( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+ return infoObject.buffer.slice( ( polygonVertexIndex * infoObject.dataSize ), ( polygonVertexIndex * infoObject.dataSize ) + infoObject.dataSize );
+
+ },
+
+ /**
+ * Function uses the infoObject and given indices to return value array of object.
+ * @param {number} polygonVertexIndex - Index of vertex in draw order (which index of the index buffer refers to this vertex).
+ * @param {number} polygonIndex - Index of polygon in geometry.
+ * @param {number} vertexIndex - Index of vertex inside vertex buffer (used because some data refers to old index buffer that we don't use anymore).
+ * @param {{datasize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}} infoObject - Object containing data and how to access data.
+ * @returns {number[]}
+ */
+ IndexToDirect: function ( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+ var index = infoObject.indices[ polygonVertexIndex ];
+ return infoObject.buffer.slice( ( index * infoObject.dataSize ), ( index * infoObject.dataSize ) + infoObject.dataSize );
+
+ }
+
+ },
+
+ ByPolygon: {
+
+ /**
+ * Function uses the infoObject and given indices to return value array of object.
+ * @param {number} polygonVertexIndex - Index of vertex in draw order (which index of the index buffer refers to this vertex).
+ * @param {number} polygonIndex - Index of polygon in geometry.
+ * @param {number} vertexIndex - Index of vertex inside vertex buffer (used because some data refers to old index buffer that we don't use anymore).
+ * @param {{datasize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}} infoObject - Object containing data and how to access data.
+ * @returns {number[]}
+ */
+ Direct: function ( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+ return infoObject.buffer.slice( polygonIndex * infoObject.dataSize, polygonIndex * infoObject.dataSize + infoObject.dataSize );
+
+ },
+
+ /**
+ * Function uses the infoObject and given indices to return value array of object.
+ * @param {number} polygonVertexIndex - Index of vertex in draw order (which index of the index buffer refers to this vertex).
+ * @param {number} polygonIndex - Index of polygon in geometry.
+ * @param {number} vertexIndex - Index of vertex inside vertex buffer (used because some data refers to old index buffer that we don't use anymore).
+ * @param {{datasize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}} infoObject - Object containing data and how to access data.
+ * @returns {number[]}
+ */
+ IndexToDirect: function ( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+ var index = infoObject.indices[ polygonIndex ];
+ return infoObject.buffer.slice( index * infoObject.dataSize, index * infoObject.dataSize + infoObject.dataSize );
+
+ }
+
+ },
+
+ AllSame: {
+
+ /**
+ * Function uses the infoObject and given indices to return value array of object.
+ * @param {number} polygonVertexIndex - Index of vertex in draw order (which index of the index buffer refers to this vertex).
+ * @param {number} polygonIndex - Index of polygon in geometry.
+ * @param {number} vertexIndex - Index of vertex inside vertex buffer (used because some data refers to old index buffer that we don't use anymore).
+ * @param {{datasize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}} infoObject - Object containing data and how to access data.
+ * @returns {number[]}
+ */
+ IndexToDirect: function ( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+ return infoObject.buffer.slice( infoObject.indices[ 0 ] * infoObject.dataSize, infoObject.indices[ 0 ] * infoObject.dataSize + infoObject.dataSize );
+
+ }
+
+ }
+
+ };
+
+ return GetData[ infoObject.mappingType ][ infoObject.referenceType ]( polygonVertexIndex, polygonIndex, vertexIndex, infoObject );
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Specialty function for parsing NurbsCurve based Geometry Nodes.
+ * @param {FBXGeometryNode} geometryNode
+ * @param {{parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}} relationships
+ * @returns {THREE.BufferGeometry}
+ */
+ function parseNurbsGeometry( geometryNode, relationships ) {
+
+ if ( THREE.NURBSCurve === undefined ) {
+
+ console.error( "THREE.FBXLoader relies on THREE.NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry." );
+ return new THREE.BufferGeometry();
+
+ }
+
+ var order = parseInt( geometryNode.properties.Order );
+
+ if ( isNaN( order ) ) {
+
+ console.error( "FBXLoader: Invalid Order " + geometryNode.properties.Order + " given for geometry ID: " + geometryNode.id );
+ return new THREE.BufferGeometry();
+
+ }
+
+ var knots = parseFloatArray( geometryNode.subNodes.KnotVector.properties.a );
+ var controlPoints = [];
+ var pointsValues = parseFloatArray( geometryNode.subNodes.Points.properties.a );
+
+ for ( var i = 0; i < pointsValues.length; i += 4 ) {
+
+ controlPoints.push( new THREE.Vector4( pointsValues[ i ], pointsValues[ i + 1 ], pointsValues[ i + 2 ], pointsValues[ i + 3 ] ) );
+
+ }
+
+ if ( geometryNode.properties.Form === 'Closed' ) {
+
+ controlPoints.push( controlPoints[ 0 ] );
+
+ }
+
+ var curve = new THREE.NURBSCurve( order - 1, knots, controlPoints );
+ var vertices = curve.getPoints( controlPoints.length * 1.5 );
+
+ var vertexBuffer = [];
+ vertices.forEach( function ( position ) {
+
+ var array = position.toArray();
+ vertexBuffer = vertexBuffer.concat( array );
+
+ } );
+
+ var geometry = new THREE.BufferGeometry();
+ geometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( vertexBuffer ), 3 ) );
+
+ return geometry;
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Finally generates Scene graph and Scene graph Objects.
+ * @param {{Objects: {subNodes: {Model: Object.}}}} FBXTree
+ * @param {Map} connections
+ * @param {Map, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[], skeleton: THREE.Skeleton|null}>} deformerMap
+ * @param {Map} geometryMap
+ * @param {Map} materialMap
+ * @returns {THREE.Group}
+ */
+ function parseScene( FBXTree, connections, deformerMap, geometryMap, materialMap ) {
+
+ var sceneGraph = new THREE.Group();
+
+ var ModelNode = FBXTree.Objects.subNodes.Model;
+
+ /**
+ * @type {Array.}
+ */
+ var modelArray = [];
+
+ /**
+ * @type {Map.}
+ */
+ var modelMap = new Map();
+
+ for ( var nodeID in ModelNode ) {
+
+ var id = parseInt( nodeID );
+ var node = ModelNode[ nodeID ];
+ var conns = connections.get( id );
+ var model = null;
+ for ( var i = 0; i < conns.parents.length; ++ i ) {
+
+ deformerMap.forEach( function ( deformer ) {
+
+ if ( deformer.map.has( conns.parents[ i ].ID ) ) {
+
+ model = new THREE.Bone();
+ var index = deformer.array.findIndex( function ( subDeformer ) {
+
+ return subDeformer.FBX_ID === conns.parents[ i ].ID;
+
+ } );
+ deformer.bones[ index ] = model;
+
+ }
+
+ } );
+
+ }
+ if ( ! model ) {
+
+ switch ( node.attrType ) {
+
+ case "Mesh":
+ /**
+ * @type {?THREE.BufferGeometry}
+ */
+ var geometry = null;
+
+ /**
+ * @type {THREE.MultiMaterial|THREE.Material}
+ */
+ var material = null;
+
+ /**
+ * @type {Array.}
+ */
+ var materials = [];
+
+ conns.children.forEach( function ( child ) {
+
+ if ( geometryMap.has( child.ID ) ) {
+
+ geometry = geometryMap.get( child.ID );
+
+ }
+
+ if ( materialMap.has( child.ID ) ) {
+
+ materials.push( materialMap.get( child.ID ) );
+
+ }
+
+ } );
+ if ( materials.length > 1 ) {
+
+ material = new THREE.MultiMaterial( materials );
+
+ } else if ( materials.length > 0 ) {
+
+ material = materials[ 0 ];
+
+ } else {
+
+ material = new THREE.MeshBasicMaterial( { color: 0x3300ff } );
+
+ }
+ if ( geometry.FBX_Deformer ) {
+
+ materials.forEach( function ( material ) {
+
+ material.skinning = true;
+
+ } );
+ material.skinning = true;
+ model = new THREE.SkinnedMesh( geometry, material );
+
+ } else {
+
+ model = new THREE.Mesh( geometry, material );
+
+ }
+ break;
+
+ case "NurbsCurve":
+ var geometry = null;
+
+ conns.children.forEach( function ( child ) {
+
+ if ( geometryMap.has( child.ID ) ) {
+
+ geometry = geometryMap.get( child.ID );
+
+ }
+
+ } );
+
+ // FBX does not list materials for Nurbs lines, so we'll just put our own in here.
+ material = new THREE.LineBasicMaterial( { color: 0x3300ff, linewidth: 5 } );
+ model = new THREE.Line( geometry, material );
+ break;
+
+ default:
+ model = new THREE.Object3D();
+ break;
+
+ }
+
+ }
+
+ model.name = node.attrName.replace( /:/, '' ).replace( /_/, '' ).replace( /-/, '' );
+ model.FBX_ID = id;
+
+ modelArray.push( model );
+ modelMap.set( id, model );
+
+ }
+
+ modelArray.forEach( function ( model ) {
+
+ var node = ModelNode[ model.FBX_ID ];
+
+ if ( 'Lcl_Translation' in node.properties ) {
+
+ model.position.fromArray( parseFloatArray( node.properties.Lcl_Translation.value ) );
+
+ }
+
+ if ( 'Lcl_Rotation' in node.properties ) {
+
+ var rotation = parseFloatArray( node.properties.Lcl_Rotation.value ).map( function ( value ) {
+
+ return value * Math.PI / 180;
+
+ } );
+ rotation.push( 'ZYX' );
+ model.rotation.fromArray( rotation );
+
+ }
+
+ if ( 'Lcl_Scaling' in node.properties ) {
+
+ model.scale.fromArray( parseFloatArray( node.properties.Lcl_Scaling.value ) );
+
+ }
+
+ var conns = connections.get( model.FBX_ID );
+ for ( var parentIndex = 0; parentIndex < conns.parents.length; parentIndex ++ ) {
+
+ var pIndex = modelArray.findIndex( function ( mod ) {
+
+ return mod.FBX_ID === conns.parents[ parentIndex ].ID;
+
+ } );
+ if ( pIndex > - 1 ) {
+
+ modelArray[ pIndex ].add( model );
+ break;
+
+ }
+
+ }
+ if ( model.parent === null ) {
+
+ sceneGraph.add( model );
+
+ }
+
+ } );
+
+
+ // Now with the bones created, we can update the skeletons and bind them to the skinned meshes.
+ sceneGraph.updateMatrixWorld( true );
+
+ // Put skeleton into bind pose.
+ var BindPoseNode = FBXTree.Objects.subNodes.Pose;
+ for ( var nodeID in BindPoseNode ) {
+
+ if ( BindPoseNode[ nodeID ].attrType === 'BindPose' ) {
+
+ BindPoseNode = BindPoseNode[ nodeID ];
+ break;
+
+ }
+
+ }
+ if ( BindPoseNode ) {
+
+ var PoseNode = BindPoseNode.subNodes.PoseNode;
+ var worldMatrices = new Map();
+
+ PoseNode.forEach( function ( node ) {
+
+ var rawMatWrd = parseMatrixArray( node.subNodes.Matrix.properties.a );
+
+ worldMatrices.set( parseInt( node.id ), rawMatWrd );
+
+ } );
+
+ }
+
+ deformerMap.forEach( function ( deformer, FBX_ID ) {
+
+ deformer.array.forEach( function ( subDeformer, subDeformerIndex ) {
+
+ /**
+ * @type {THREE.Bone}
+ */
+ var bone = deformer.bones[ subDeformerIndex ];
+ if ( ! worldMatrices.has( bone.FBX_ID ) ) {
+
+ return;
+
+ }
+ var mat = worldMatrices.get( bone.FBX_ID );
+ bone.matrixWorld.copy( mat );
+
+ } );
+
+ // Now that skeleton is in bind pose, bind to model.
+ deformer.skeleton = new THREE.Skeleton( deformer.bones );
+ var conns = connections.get( FBX_ID );
+ conns.parents.forEach( function ( parent ) {
+
+ if ( geometryMap.has( parent.ID ) ) {
+
+ var geoID = parent.ID;
+ var geoConns = connections.get( geoID );
+ for ( var i = 0; i < geoConns.parents.length; ++ i ) {
+
+ if ( modelMap.has( geoConns.parents[ i ].ID ) ) {
+
+ var model = modelMap.get( geoConns.parents[ i ].ID );
+ //ASSERT model typeof SkinnedMesh
+ model.bind( deformer.skeleton, model.matrixWorld );
+ break;
+
+ }
+
+ }
+
+ }
+
+ } );
+
+ } );
+
+ // Skeleton is now bound, we are now free to set up the
+ // scene graph.
+ modelArray.forEach( function ( model ) {
+
+ var node = ModelNode[ model.FBX_ID ];
+
+ if ( 'Lcl_Translation' in node.properties ) {
+
+ model.position.fromArray( parseFloatArray( node.properties.Lcl_Translation.value ) );
+
+ }
+
+ if ( 'Lcl_Rotation' in node.properties ) {
+
+ var rotation = parseFloatArray( node.properties.Lcl_Rotation.value ).map( function ( value ) {
+
+ return value * Math.PI / 180;
+
+ } );
+ rotation.push( 'ZYX' );
+ model.rotation.fromArray( rotation );
+
+ }
+
+ if ( 'Lcl_Scaling' in node.properties ) {
+
+ model.scale.fromArray( parseFloatArray( node.properties.Lcl_Scaling.value ) );
+
+ }
+
+ } );
+
+ // Silly hack with the animation parsing. We're gonna pretend the scene graph has a skeleton
+ // to attach animations to, since FBXs treat animations as animations for the entire scene,
+ // not just for individual objects.
+ sceneGraph.skeleton = {
+ bones: modelArray
+ };
+
+ var animations = parseAnimations( FBXTree, connections, sceneGraph );
+
+ addAnimations( sceneGraph, animations );
+
+ return sceneGraph;
+
+ }
+
+ /**
+ * Parses animation information from FBXTree and generates an AnimationInfoObject.
+ * @param {{Objects: {subNodes: {AnimationCurveNode: any, AnimationCurve: any, AnimationLayer: any, AnimationStack: any}}}} FBXTree
+ * @param {Map} connections
+ */
+ function parseAnimations( FBXTree, connections, sceneGraph ) {
+
+ var rawNodes = FBXTree.Objects.subNodes.AnimationCurveNode;
+ var rawCurves = FBXTree.Objects.subNodes.AnimationCurve;
+ var rawLayers = FBXTree.Objects.subNodes.AnimationLayer;
+ var rawStacks = FBXTree.Objects.subNodes.AnimationStack;
+
+ /**
+ * @type {{
+ curves: Map,
+ layers: Map,
+ stacks: Map,
+ length: number,
+ fps: number,
+ frames: number
+ }}
+ */
+ var returnObject = {
+ curves: new Map(),
+ layers: new Map(),
+ stacks: new Map(),
+ length: 0,
+ fps: 30,
+ frames: 0
+ };
+
+ /**
+ * @type {Array.<{
+ id: number;
+ attr: string;
+ internalID: number;
+ attrX: boolean;
+ attrY: boolean;
+ attrZ: boolean;
+ containerBoneID: number;
+ containerID: number;
+ }>}
+ */
+ var animationCurveNodes = [];
+ for ( var nodeID in rawNodes ) {
+
+ if ( nodeID.match( /\d+/ ) ) {
+
+ var animationNode = parseAnimationNode( FBXTree, rawNodes[ nodeID ], connections, sceneGraph );
+ animationCurveNodes.push( animationNode );
+
+ }
+
+ }
+
+ /**
+ * @type {Map.}
+ */
+ var tmpMap = new Map();
+ for ( var animationCurveNodeIndex = 0; animationCurveNodeIndex < animationCurveNodes.length; ++ animationCurveNodeIndex ) {
+
+ if ( animationCurveNodes[ animationCurveNodeIndex ] === null ) {
+
+ continue;
+
+ }
+ tmpMap.set( animationCurveNodes[ animationCurveNodeIndex ].id, animationCurveNodes[ animationCurveNodeIndex ] );
+
+ }
+
+
+ /**
+ * @type {{
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ }[]}
+ */
+ var animationCurves = [];
+ for ( nodeID in rawCurves ) {
+
+ if ( nodeID.match( /\d+/ ) ) {
+
+ var animationCurve = parseAnimationCurve( rawCurves[ nodeID ] );
+ animationCurves.push( animationCurve );
+
+ var firstParentConn = connections.get( animationCurve.id ).parents[ 0 ];
+ var firstParentID = firstParentConn.ID;
+ var firstParentRelationship = firstParentConn.relationship;
+ var axis = '';
+
+ if ( firstParentRelationship.match( /X/ ) ) {
+
+ axis = 'x';
+
+ } else if ( firstParentRelationship.match( /Y/ ) ) {
+
+ axis = 'y';
+
+ } else if ( firstParentRelationship.match( /Z/ ) ) {
+
+ axis = 'z';
+
+ } else {
+
+ continue;
+
+ }
+
+ tmpMap.get( firstParentID ).curves[ axis ] = animationCurve;
+
+ }
+
+ }
+
+ tmpMap.forEach( function ( curveNode ) {
+
+ var id = curveNode.containerBoneID;
+ if ( ! returnObject.curves.has( id ) ) {
+
+ returnObject.curves.set( id, { T: null, R: null, S: null } );
+
+ }
+ returnObject.curves.get( id )[ curveNode.attr ] = curveNode;
+
+ } );
+
+ for ( var nodeID in rawLayers ) {
+
+ /**
+ * @type {{
+ T: {
+ id: number;
+ attr: string;
+ internalID: number;
+ attrX: boolean;
+ attrY: boolean;
+ attrZ: boolean;
+ containerBoneID: number;
+ containerID: number;
+ curves: {
+ x: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ y: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ z: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ },
+ },
+ R: {
+ id: number;
+ attr: string;
+ internalID: number;
+ attrX: boolean;
+ attrY: boolean;
+ attrZ: boolean;
+ containerBoneID: number;
+ containerID: number;
+ curves: {
+ x: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ y: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ z: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ },
+ },
+ S: {
+ id: number;
+ attr: string;
+ internalID: number;
+ attrX: boolean;
+ attrY: boolean;
+ attrZ: boolean;
+ containerBoneID: number;
+ containerID: number;
+ curves: {
+ x: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ y: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ z: {
+ version: any;
+ id: number;
+ internalID: number;
+ times: number[];
+ values: number[];
+ attrFlag: number[];
+ attrData: number[];
+ };
+ },
+ }
+ }[]}
+ */
+ var layer = [];
+ var children = connections.get( parseInt( nodeID ) ).children;
+ for ( var childIndex = 0; childIndex < children.length; childIndex ++ ) {
+
+ // Skip lockInfluenceWeights
+ if ( tmpMap.has( children[ childIndex ].ID ) ) {
+
+ var curveNode = tmpMap.get( children[ childIndex ].ID );
+ var boneID = curveNode.containerBoneID;
+ if ( layer[ boneID ] === undefined ) {
+
+ layer[ boneID ] = {
+ T: null,
+ R: null,
+ S: null
+ };
+
+ }
+
+ layer[ boneID ][ curveNode.attr ] = curveNode;
+
+ }
+
+ }
+
+ returnObject.layers.set( parseInt( nodeID ), layer );
+
+ }
+
+ for ( var nodeID in rawStacks ) {
+
+ var layers = [];
+ var children = connections.get( parseInt( nodeID ) ).children;
+ var maxTimeStamp = 0;
+ var minTimeStamp = Number.MAX_VALUE;
+ for ( var childIndex = 0; childIndex < children.length; ++ childIndex ) {
+
+ if ( returnObject.layers.has( children[ childIndex ].ID ) ) {
+
+ var currentLayer = returnObject.layers.get( children[ childIndex ].ID );
+ layers.push( currentLayer );
+
+ currentLayer.forEach( function ( layer ) {
+
+ if ( layer ) {
+
+ getCurveNodeMaxMinTimeStamps( layer );
+
+ }
+
+ /**
+ * Sets the maxTimeStamp and minTimeStamp variables if it has timeStamps that are either larger or smaller
+ * than the max or min respectively.
+ * @param {{
+ T: {
+ id: number,
+ attr: string,
+ internalID: number,
+ attrX: boolean,
+ attrY: boolean,
+ attrZ: boolean,
+ containerBoneID: number,
+ containerID: number,
+ curves: {
+ x: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ y: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ z: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ },
+ },
+ R: {
+ id: number,
+ attr: string,
+ internalID: number,
+ attrX: boolean,
+ attrY: boolean,
+ attrZ: boolean,
+ containerBoneID: number,
+ containerID: number,
+ curves: {
+ x: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ y: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ z: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ },
+ },
+ S: {
+ id: number,
+ attr: string,
+ internalID: number,
+ attrX: boolean,
+ attrY: boolean,
+ attrZ: boolean,
+ containerBoneID: number,
+ containerID: number,
+ curves: {
+ x: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ y: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ z: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ },
+ },
+ }} layer
+ */
+ function getCurveNodeMaxMinTimeStamps( layer ) {
+
+ /**
+ * Sets the maxTimeStamp and minTimeStamp if one of the curve's time stamps
+ * exceeds the maximum or minimum.
+ * @param {{
+ x: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ y: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ },
+ z: {
+ version: any,
+ id: number,
+ internalID: number,
+ times: number[],
+ values: number[],
+ attrFlag: number[],
+ attrData: number[],
+ }
+ }} curve
+ */
+ function getCurveMaxMinTimeStamp( curve ) {
+
+ /**
+ * Sets the maxTimeStamp and minTimeStamp if one of its timestamps exceeds the maximum or minimum.
+ * @param {{times: number[]}} axis
+ */
+ function getCurveAxisMaxMinTimeStamps( axis ) {
+
+ maxTimeStamp = axis.times[ axis.times.length - 1 ] > maxTimeStamp ? axis.times[ axis.times.length - 1 ] : maxTimeStamp;
+ minTimeStamp = axis.times[ 0 ] < minTimeStamp ? axis.times[ 0 ] : minTimeStamp;
+
+ }
+
+ if ( curve.x ) {
+
+ getCurveAxisMaxMinTimeStamps( curve.x );
+
+ }
+ if ( curve.y ) {
+
+ getCurveAxisMaxMinTimeStamps( curve.y );
+
+ }
+ if ( curve.z ) {
+
+ getCurveAxisMaxMinTimeStamps( curve.z );
+
+ }
+
+ }
+
+ if ( layer.R ) {
+
+ getCurveMaxMinTimeStamp( layer.R.curves );
+
+ }
+ if ( layer.S ) {
+
+ getCurveMaxMinTimeStamp( layer.S.curves );
+
+ }
+ if ( layer.T ) {
+
+ getCurveMaxMinTimeStamp( layer.T.curves );
+
+ }
+
+ }
+
+ } );
+
+ }
+
+ }
+
+ // Do we have an animation clip with actual length?
+ if ( maxTimeStamp > minTimeStamp ) {
+
+ returnObject.stacks.set( parseInt( nodeID ), {
+ name: rawStacks[ nodeID ].attrName,
+ layers: layers,
+ length: maxTimeStamp - minTimeStamp,
+ frames: ( maxTimeStamp - minTimeStamp ) * 30
+ } );
+
+ }
+
+ }
+
+ return returnObject;
+
+ /**
+ * @param {Object} FBXTree
+ * @param {{id: number, attrName: string, properties: Object}} animationCurveNode
+ * @param {Map} connections
+ * @param {{skeleton: {bones: {FBX_ID: number}[]}}} sceneGraph
+ */
+ function parseAnimationNode( FBXTree, animationCurveNode, connections, sceneGraph ) {
+
+ var returnObject = {
+ /**
+ * @type {number}
+ */
+ id: animationCurveNode.id,
+
+ /**
+ * @type {string}
+ */
+ attr: animationCurveNode.attrName,
+
+ /**
+ * @type {number}
+ */
+ internalID: animationCurveNode.id,
+
+ /**
+ * @type {boolean}
+ */
+ attrX: false,
+
+ /**
+ * @type {boolean}
+ */
+ attrY: false,
+
+ /**
+ * @type {boolean}
+ */
+ attrZ: false,
+
+ /**
+ * @type {number}
+ */
+ containerBoneID: - 1,
+
+ /**
+ * @type {number}
+ */
+ containerID: - 1,
+
+ curves: {
+ x: null,
+ y: null,
+ z: null
+ }
+ };
+
+ if ( returnObject.attr.match( /S|R|T/ ) ) {
+
+ for ( var attributeKey in animationCurveNode.properties ) {
+
+ if ( attributeKey.match( /X/ ) ) {
+
+ returnObject.attrX = true;
+
+ }
+ if ( attributeKey.match( /Y/ ) ) {
+
+ returnObject.attrY = true;
+
+ }
+ if ( attributeKey.match( /Z/ ) ) {
+
+ returnObject.attrZ = true;
+
+ }
+
+ }
+
+ } else {
+
+ return null;
+
+ }
+
+ var conns = connections.get( returnObject.id );
+ var containerIndices = conns.parents;
+
+ for ( var containerIndicesIndex = containerIndices.length - 1; containerIndicesIndex >= 0; -- containerIndicesIndex ) {
+
+ var boneID = sceneGraph.skeleton.bones.findIndex( function ( bone ) {
+
+ return bone.FBX_ID === containerIndices[ containerIndicesIndex ].ID;
+
+ } );
+ if ( boneID > - 1 ) {
+
+ returnObject.containerBoneID = boneID;
+ returnObject.containerID = containerIndices[ containerIndicesIndex ].ID;
+ break;
+
+ }
+
+ }
+
+ return returnObject;
+
+ }
+
+ /**
+ * @param {{id: number, subNodes: {KeyTime: {properties: {a: string}}, KeyValueFloat: {properties: {a: string}}, KeyAttrFlags: {properties: {a: string}}, KeyAttrDataFloat: {properties: {a: string}}}}} animationCurve
+ */
+ function parseAnimationCurve( animationCurve ) {
+
+ return {
+ version: null,
+ id: animationCurve.id,
+ internalID: animationCurve.id,
+ times: parseFloatArray( animationCurve.subNodes.KeyTime.properties.a ).map( function ( time ) {
+
+ return ConvertFBXTimeToSeconds( time );
+
+ } ),
+ values: parseFloatArray( animationCurve.subNodes.KeyValueFloat.properties.a ),
+
+ attrFlag: parseIntArray( animationCurve.subNodes.KeyAttrFlags.properties.a ),
+ attrData: parseFloatArray( animationCurve.subNodes.KeyAttrDataFloat.properties.a )
+ };
+
+ }
+
+ }
+
+ /**
+ * @param {{
+ curves: Map;
+ layers: Map;
+ stacks: Map;
+ length: number;
+ fps: number;
+ frames: number;
+ }} animations,
+ * @param {{skeleton: { bones: THREE.Bone[]}}} group
+ */
+ function addAnimations( group, animations ) {
+
+ if ( group.animations === undefined ) {
+
+ group.animations = [];
+
+ }
+
+ animations.stacks.forEach( function ( stack ) {
+
+ var animationData = {
+ name: stack.name,
+ fps: 30,
+ length: stack.length,
+ hierarchy: []
+ };
+
+ var bones = group.skeleton.bones;
+
+ bones.forEach( function ( bone ) {
+
+ var name = bone.name.replace( /.*:/, '' );
+ var parentIndex = bones.findIndex( function ( parentBone ) {
+
+ return bone.parent === parentBone;
+
+ } );
+ animationData.hierarchy.push( { parent: parentIndex, name: name, keys: [] } );
+
+ } );
+
+ for ( var frame = 0; frame < stack.frames; frame ++ ) {
+
+ bones.forEach( function ( bone, boneIndex ) {
+
+ var animationNode = stack.layers[ 0 ][ boneIndex ];
+
+ animationData.hierarchy.forEach( function ( node ) {
+
+ if ( node.name === bone.name ) {
+
+ node.keys.push( generateKey( animationNode, bone, frame ) );
+
+ }
+
+ } );
+
+ } );
+
+ }
+
+ group.animations.push( THREE.AnimationClip.parseAnimation( animationData, bones ) );
+
+
+ /**
+ * @param {THREE.Bone} bone
+ */
+ function generateKey( animationNode, bone, frame ) {
+
+ var key = {
+ time: frame / animations.fps,
+ pos: bone.position.toArray(),
+ rot: bone.quaternion.toArray(),
+ scl: bone.scale.toArray()
+ };
+
+ if ( animationNode === undefined ) {
+
+ return key;
+
+ }
+
+ try {
+
+ if ( hasCurve( animationNode, 'T' ) && hasKeyOnFrame( animationNode.T, frame ) ) {
+
+ key.pos = [ animationNode.T.curves.x.values[ frame ], animationNode.T.curves.y.values[ frame ], animationNode.T.curves.z.values[ frame ] ];
+
+ }
+
+ if ( hasCurve( animationNode, 'R' ) && hasKeyOnFrame( animationNode.R, frame ) ) {
+
+ var rotationX = degreeToRadian( animationNode.R.curves.x.values[ frame ] );
+ var rotationY = degreeToRadian( animationNode.R.curves.y.values[ frame ] );
+ var rotationZ = degreeToRadian( animationNode.R.curves.z.values[ frame ] );
+ var euler = new THREE.Euler( rotationX, rotationY, rotationZ, 'ZYX' );
+ key.rot = new THREE.Quaternion().setFromEuler( euler ).toArray();
+
+ }
+
+ if ( hasCurve( animationNode, 'S' ) && hasKeyOnFrame( animationNode.S, frame ) ) {
+
+ key.scl = [ animationNode.S.curves.x.values[ frame ], animationNode.S.curves.y.values[ frame ], animationNode.S.curves.z.values[ frame ] ];
+
+ }
+
+ } catch ( error ) {
+
+ // Curve is not fully plotted.
+ console.log( bone );
+ console.log( error );
+
+ }
+
+ return key;
+
+ function hasCurve( animationNode, attribute ) {
+
+ if ( animationNode === undefined ) {
+
+ return false;
+
+ }
+
+ var attributeNode = animationNode[ attribute ];
+ if ( ! attributeNode ) {
+
+ return false;
+
+ }
+
+ return [ 'x', 'y', 'z' ].every( function ( key ) {
+
+ return attributeNode.curves[ key ] !== undefined;
+
+ } );
+
+ }
+
+ function hasKeyOnFrame( attributeNode, frame ) {
+
+ return [ 'x', 'y', 'z' ].every( function ( key ) {
+
+ return isKeyExistOnFrame( attributeNode.curves[ key ], frame );
+
+ function isKeyExistOnFrame( curve, frame ) {
+
+ return curve.values[ frame ] !== undefined;
+
+ }
+
+ } );
+
+ }
+
+ }
+
+ } );
+
+ }
+
+
+
+ // UTILS
+ /**
+ * Parses Vector3 property from FBXTree. Property is given as .value.x, .value.y, etc.
+ * @param {FBXVector3} property - Property to parse as Vector3.
+ * @returns {THREE.Vector3}
+ */
+ function parseVector3( property ) {
+
+ return new THREE.Vector3( parseFloat( property.value.x ), parseFloat( property.value.y ), parseFloat( property.value.z ) );
+
+ }
+
+ /**
+ * Parses Color property from FBXTree. Property is given as .value.x, .value.y, etc.
+ * @param {FBXVector3} property - Property to parse as Color.
+ * @returns {THREE.Color}
+ */
+ function parseColor( property ) {
+
+ return new THREE.Color().fromArray( parseVector3( property ).toArray() );
+
+ }
+
+ }
+
+ } );
+
+ /**
+ * An instance of a Vertex with data for drawing vertices to the screen.
+ * @constructor
+ */
+ function Vertex() {
+
+ /**
+ * Position of the vertex.
+ * @type {THREE.Vector3}
+ */
+ this.position = new THREE.Vector3( );
+
+ /**
+ * Normal of the vertex
+ * @type {THREE.Vector3}
+ */
+ this.normal = new THREE.Vector3( );
+
+ /**
+ * UV coordinates of the vertex.
+ * @type {THREE.Vector2}
+ */
+ this.uv = new THREE.Vector2( );
+
+ /**
+ * Indices of the bones vertex is influenced by.
+ * @type {THREE.Vector4}
+ */
+ this.skinIndices = new THREE.Vector4( 0, 0, 0, 0 );
+
+ /**
+ * Weights that each bone influences the vertex.
+ * @type {THREE.Vector4}
+ */
+ this.skinWeights = new THREE.Vector4( 0, 0, 0, 0 );
+
+ }
+
+ Object.assign( Vertex.prototype, {
+
+ copy: function ( target ) {
+
+ var returnVar = target || new Vertex();
+
+ returnVar.position.copy( this.position );
+ returnVar.normal.copy( this.normal );
+ returnVar.uv.copy( this.uv );
+ returnVar.skinIndices.copy( this.skinIndices );
+ returnVar.skinWeights.copy( this.skinWeights );
+
+ return returnVar;
+
+ },
+
+ flattenToBuffers: function () {
+
+ var vertexBuffer = this.position.toArray();
+ var normalBuffer = this.normal.toArray();
+ var uvBuffer = this.uv.toArray();
+ var skinIndexBuffer = this.skinIndices.toArray();
+ var skinWeightBuffer = this.skinWeights.toArray();
+
+ return {
+ vertexBuffer: vertexBuffer,
+ normalBuffer: normalBuffer,
+ uvBuffer: uvBuffer,
+ skinIndexBuffer: skinIndexBuffer,
+ skinWeightBuffer: skinWeightBuffer,
+ };
+
+ }
+
+ } );
+
+ /**
+ * @constructor
+ */
+ function Triangle() {
+
+ /**
+ * @type {{position: THREE.Vector3, normal: THREE.Vector3, uv: THREE.Vector2, skinIndices: THREE.Vector4, skinWeights: THREE.Vector4}[]}
+ */
+ this.vertices = [ ];
+
+ }
+
+ Object.assign( Triangle.prototype, {
+
+ copy: function ( target ) {
+
+ var returnVar = target || new Triangle();
+
+ for ( var i = 0; i < this.vertices.length; ++ i ) {
+
+ this.vertices[ i ].copy( returnVar.vertices[ i ] );
+
+ }
+
+ return returnVar;
+
+ },
+
+ flattenToBuffers: function () {
+
+ var vertexBuffer = [];
+ var normalBuffer = [];
+ var uvBuffer = [];
+ var skinIndexBuffer = [];
+ var skinWeightBuffer = [];
+
+ this.vertices.forEach( function ( vertex ) {
+
+ var flatVertex = vertex.flattenToBuffers();
+ vertexBuffer = vertexBuffer.concat( flatVertex.vertexBuffer );
+ normalBuffer = normalBuffer.concat( flatVertex.normalBuffer );
+ uvBuffer = uvBuffer.concat( flatVertex.uvBuffer );
+ skinIndexBuffer = skinIndexBuffer.concat( flatVertex.skinIndexBuffer );
+ skinWeightBuffer = skinWeightBuffer.concat( flatVertex.skinWeightBuffer );
+
+ } );
+
+ return {
+ vertexBuffer: vertexBuffer,
+ normalBuffer: normalBuffer,
+ uvBuffer: uvBuffer,
+ skinIndexBuffer: skinIndexBuffer,
+ skinWeightBuffer: skinWeightBuffer,
+ };
+
+ }
+
+ } );
+
+ /**
+ * @constructor
+ */
+ function Face() {
+
+ /**
+ * @type {{vertices: {position: THREE.Vector3, normal: THREE.Vector3, uv: THREE.Vector2, skinIndices: THREE.Vector4, skinWeights: THREE.Vector4}[]}[]}
+ */
+ this.triangles = [ ];
+ this.materialIndex = 0;
+
+ }
+
+ Object.assign( Face.prototype, {
+
+ copy: function ( target ) {
+
+ var returnVar = target || new Face();
+
+ for ( var i = 0; i < this.triangles.length; ++ i ) {
+
+ this.triangles[ i ].copy( returnVar.triangles[ i ] );
+
+ }
+
+ returnVar.materialIndex = this.materialIndex;
+
+ return returnVar;
+
+ },
+
+ genTrianglesFromVertices: function ( vertexArray ) {
+
+ for ( var i = 2; i < vertexArray.length; ++ i ) {
+
+ var triangle = new Triangle();
+ triangle.vertices[ 0 ] = vertexArray[ 0 ];
+ triangle.vertices[ 1 ] = vertexArray[ i - 1 ];
+ triangle.vertices[ 2 ] = vertexArray[ i ];
+ this.triangles.push( triangle );
+
+ }
+
+ },
+
+ flattenToBuffers: function () {
+
+ var vertexBuffer = [];
+ var normalBuffer = [];
+ var uvBuffer = [];
+ var skinIndexBuffer = [];
+ var skinWeightBuffer = [];
+
+ var materialIndexBuffer = [];
+
+ var materialIndex = this.materialIndex;
+
+ this.triangles.forEach( function ( triangle ) {
+
+ var flatTriangle = triangle.flattenToBuffers();
+ vertexBuffer = vertexBuffer.concat( flatTriangle.vertexBuffer );
+ normalBuffer = normalBuffer.concat( flatTriangle.normalBuffer );
+ uvBuffer = uvBuffer.concat( flatTriangle.uvBuffer );
+ skinIndexBuffer = skinIndexBuffer.concat( flatTriangle.skinIndexBuffer );
+ skinWeightBuffer = skinWeightBuffer.concat( flatTriangle.skinWeightBuffer );
+ materialIndexBuffer = materialIndexBuffer.concat( [ materialIndex, materialIndex, materialIndex ] );
+
+ } );
+
+ return {
+ vertexBuffer: vertexBuffer,
+ normalBuffer: normalBuffer,
+ uvBuffer: uvBuffer,
+ skinIndexBuffer: skinIndexBuffer,
+ skinWeightBuffer: skinWeightBuffer,
+ materialIndexBuffer: materialIndexBuffer
+ };
+
+ }
+
+ } );
+
+ /**
+ * @constructor
+ */
+ function Geometry() {
+
+ /**
+ * @type {{triangles: {vertices: {position: THREE.Vector3, normal: THREE.Vector3, uv: THREE.Vector2, skinIndices: THREE.Vector4, skinWeights: THREE.Vector4}[]}[], materialIndex: number}[]}
+ */
+ this.faces = [ ];
+
+ /**
+ * @type {{}|THREE.Skeleton}
+ */
+ this.skeleton = null;
+
+ }
+
+ Object.assign( Geometry.prototype, {
+
+ /**
+ * @returns {{vertexBuffer: number[], normalBuffer: number[], uvBuffer: number[], skinIndexBuffer: number[], skinWeightBuffer: number[], materialIndexBuffer: number[]}}
+ */
+ flattenToBuffers: function () {
+
+ var vertexBuffer = [];
+ var normalBuffer = [];
+ var uvBuffer = [];
+ var skinIndexBuffer = [];
+ var skinWeightBuffer = [];
+
+ var materialIndexBuffer = [];
+
+ this.faces.forEach( function ( face ) {
+
+ var flatFace = face.flattenToBuffers();
+ vertexBuffer = vertexBuffer.concat( flatFace.vertexBuffer );
+ normalBuffer = normalBuffer.concat( flatFace.normalBuffer );
+ uvBuffer = uvBuffer.concat( flatFace.uvBuffer );
+ skinIndexBuffer = skinIndexBuffer.concat( flatFace.skinIndexBuffer );
+ skinWeightBuffer = skinWeightBuffer.concat( flatFace.skinWeightBuffer );
+ materialIndexBuffer = materialIndexBuffer.concat( flatFace.materialIndexBuffer );
+
+ } );
+
+ return {
+ vertexBuffer: vertexBuffer,
+ normalBuffer: normalBuffer,
+ uvBuffer: uvBuffer,
+ skinIndexBuffer: skinIndexBuffer,
+ skinWeightBuffer: skinWeightBuffer,
+ materialIndexBuffer: materialIndexBuffer
+ };
+
+ }
+
+ } );
+
+ function TextParser() {}
+
+ Object.assign( TextParser.prototype, {
+
+ getPrevNode: function () {
+
+ return this.nodeStack[ this.currentIndent - 2 ];
+
+ },
+
+ getCurrentNode: function () {
+
+ return this.nodeStack[ this.currentIndent - 1 ];
+
+ },
+
+ getCurrentProp: function () {
+
+ return this.currentProp;
+
+ },
+
+ pushStack: function ( node ) {
+
+ this.nodeStack.push( node );
+ this.currentIndent += 1;
+
+ },
+
+ popStack: function () {
+
+ this.nodeStack.pop();
+ this.currentIndent -= 1;
+
+ },
+
+ setCurrentProp: function ( val, name ) {
+
+ this.currentProp = val;
+ this.currentPropName = name;
+
+ },
+
+ // ----------parse ---------------------------------------------------
+ parse: function ( text ) {
+
+ this.currentIndent = 0;
+ this.allNodes = new FBXTree();
+ this.nodeStack = [];
+ this.currentProp = [];
+ this.currentPropName = '';
+
+ var split = text.split( "\n" );
+ for ( var line in split ) {
+
+ var l = split[ line ];
+
+ // short cut
+ if ( l.match( /^[\s\t]*;/ ) ) {
+
+ continue;
+
+ } // skip comment line
+ if ( l.match( /^[\s\t]*$/ ) ) {
+
+ continue;
+
+ } // skip empty line
+
+ // beginning of node
+ var beginningOfNodeExp = new RegExp( "^\\t{" + this.currentIndent + "}(\\w+):(.*){", '' );
+ var match = l.match( beginningOfNodeExp );
+ if ( match ) {
+
+ var nodeName = match[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, "" );
+ var nodeAttrs = match[ 2 ].split( ',' ).map( function ( element ) {
+
+ return element.trim().replace( /^"/, '' ).replace( /"$/, '' );
+
+ } );
+
+ this.parseNodeBegin( l, nodeName, nodeAttrs || null );
+ continue;
+
+ }
+
+ // node's property
+ var propExp = new RegExp( "^\\t{" + ( this.currentIndent ) + "}(\\w+):[\\s\\t\\r\\n](.*)" );
+ var match = l.match( propExp );
+ if ( match ) {
+
+ var propName = match[ 1 ].replace( /^"/, '' ).replace( /"$/, "" ).trim();
+ var propValue = match[ 2 ].replace( /^"/, '' ).replace( /"$/, "" ).trim();
+
+ this.parseNodeProperty( l, propName, propValue );
+ continue;
+
+ }
+
+ // end of node
+ var endOfNodeExp = new RegExp( "^\\t{" + ( this.currentIndent - 1 ) + "}}" );
+ if ( l.match( endOfNodeExp ) ) {
+
+ this.nodeEnd();
+ continue;
+
+ }
+
+ // for special case,
+ //
+ // Vertices: *8670 {
+ // a: 0.0356229953467846,13.9599733352661,-0.399196773.....(snip)
+ // -0.0612030513584614,13.960485458374,-0.409748703241348,-0.10.....
+ // 0.12490539252758,13.7450733184814,-0.454119384288788,0.09272.....
+ // 0.0836158767342567,13.5432004928589,-0.435397416353226,0.028.....
+ //
+ // these case the lines must contiue with previous line
+ if ( l.match( /^[^\s\t}]/ ) ) {
+
+ this.parseNodePropertyContinued( l );
+
+ }
+
+ }
+
+ return this.allNodes;
+
+ },
+
+ parseNodeBegin: function ( line, nodeName, nodeAttrs ) {
+
+ // var nodeName = match[1];
+ var node = { 'name': nodeName, properties: {}, 'subNodes': {} };
+ var attrs = this.parseNodeAttr( nodeAttrs );
+ var currentNode = this.getCurrentNode();
+
+ // a top node
+ if ( this.currentIndent === 0 ) {
+
+ this.allNodes.add( nodeName, node );
+
+ } else {
+
+ // a subnode
+
+ // already exists subnode, then append it
+ if ( nodeName in currentNode.subNodes ) {
+
+ var tmp = currentNode.subNodes[ nodeName ];
+
+ // console.log( "duped entry found\nkey: " + nodeName + "\nvalue: " + propValue );
+ if ( this.isFlattenNode( currentNode.subNodes[ nodeName ] ) ) {
+
+
+ if ( attrs.id === '' ) {
+
+ currentNode.subNodes[ nodeName ] = [];
+ currentNode.subNodes[ nodeName ].push( tmp );
+
+ } else {
+
+ currentNode.subNodes[ nodeName ] = {};
+ currentNode.subNodes[ nodeName ][ tmp.id ] = tmp;
+
+ }
+
+ }
+
+ if ( attrs.id === '' ) {
+
+ currentNode.subNodes[ nodeName ].push( node );
+
+ } else {
+
+ currentNode.subNodes[ nodeName ][ attrs.id ] = node;
+
+ }
+
+ } else if ( typeof attrs.id === 'number' || attrs.id.match( /^\d+$/ ) ) {
+
+ currentNode.subNodes[ nodeName ] = {};
+ currentNode.subNodes[ nodeName ][ attrs.id ] = node;
+
+ } else {
+
+ currentNode.subNodes[ nodeName ] = node;
+
+ }
+
+ }
+
+ // for this ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
+ // NodeAttribute: 1001463072, "NodeAttribute::", "LimbNode" {
+ if ( nodeAttrs ) {
+
+ node.id = attrs.id;
+ node.attrName = attrs.name;
+ node.attrType = attrs.type;
+
+ }
+
+ this.pushStack( node );
+
+ },
+
+ parseNodeAttr: function ( attrs ) {
+
+ var id = attrs[ 0 ];
+
+ if ( attrs[ 0 ] !== "" ) {
+
+ id = parseInt( attrs[ 0 ] );
+
+ if ( isNaN( id ) ) {
+
+ // PolygonVertexIndex: *16380 {
+ id = attrs[ 0 ];
+
+ }
+
+ }
+
+ var name;
+ var type;
+ if ( attrs.length > 1 ) {
+
+ name = attrs[ 1 ].replace( /^(\w+)::/, '' );
+ type = attrs[ 2 ];
+
+ }
+
+ return { id: id, name: name || '', type: type || '' };
+
+ },
+
+ parseNodeProperty: function ( line, propName, propValue ) {
+
+ var currentNode = this.getCurrentNode();
+ var parentName = currentNode.name;
+
+ // special case parent node's is like "Properties70"
+ // these chilren nodes must treat with careful
+ if ( parentName !== undefined ) {
+
+ var propMatch = parentName.match( /Properties(\d)+/ );
+ if ( propMatch ) {
+
+ this.parseNodeSpecialProperty( line, propName, propValue );
+ return;
+
+ }
+
+ }
+
+ // special case Connections
+ if ( propName == 'C' ) {
+
+ var connProps = propValue.split( ',' ).slice( 1 );
+ var from = parseInt( connProps[ 0 ] );
+ var to = parseInt( connProps[ 1 ] );
+
+ var rest = propValue.split( ',' ).slice( 3 );
+
+ propName = 'connections';
+ propValue = [ from, to ];
+ propValue = propValue.concat( rest );
+
+ if ( currentNode.properties[ propName ] === undefined ) {
+
+ currentNode.properties[ propName ] = [];
+
+ }
+
+ }
+
+ // special case Connections
+ if ( propName == 'Node' ) {
+
+ var id = parseInt( propValue );
+ currentNode.properties.id = id;
+ currentNode.id = id;
+
+ }
+
+ // already exists in properties, then append this
+ if ( propName in currentNode.properties ) {
+
+ // console.log( "duped entry found\nkey: " + propName + "\nvalue: " + propValue );
+ if ( Array.isArray( currentNode.properties[ propName ] ) ) {
+
+ currentNode.properties[ propName ].push( propValue );
+
+ } else {
+
+ currentNode.properties[ propName ] += propValue;
+
+ }
+
+ } else {
+
+ // console.log( propName + ": " + propValue );
+ if ( Array.isArray( currentNode.properties[ propName ] ) ) {
+
+ currentNode.properties[ propName ].push( propValue );
+
+ } else {
+
+ currentNode.properties[ propName ] = propValue;
+
+ }
+
+ }
+
+ this.setCurrentProp( currentNode.properties, propName );
+
+ },
+
+ // TODO:
+ parseNodePropertyContinued: function ( line ) {
+
+ this.currentProp[ this.currentPropName ] += line;
+
+ },
+
+ parseNodeSpecialProperty: function ( line, propName, propValue ) {
+
+ // split this
+ // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1
+ // into array like below
+ // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ]
+ var props = propValue.split( '",' ).map( function ( element ) {
+
+ return element.trim().replace( /^\"/, '' ).replace( /\s/, '_' );
+
+ } );
+
+ var innerPropName = props[ 0 ];
+ var innerPropType1 = props[ 1 ];
+ var innerPropType2 = props[ 2 ];
+ var innerPropFlag = props[ 3 ];
+ var innerPropValue = props[ 4 ];
+
+ /*
+ if ( innerPropValue === undefined ) {
+ innerPropValue = props[3];
+ }
+ */
+
+ // cast value in its type
+ switch ( innerPropType1 ) {
+
+ case "int":
+ innerPropValue = parseInt( innerPropValue );
+ break;
+
+ case "double":
+ innerPropValue = parseFloat( innerPropValue );
+ break;
+
+ case "ColorRGB":
+ case "Vector3D":
+ var tmp = innerPropValue.split( ',' );
+ innerPropValue = new THREE.Vector3( tmp[ 0 ], tmp[ 1 ], tmp[ 2 ] );
+ break;
+
+ }
+
+ // CAUTION: these props must append to parent's parent
+ this.getPrevNode().properties[ innerPropName ] = {
+
+ 'type': innerPropType1,
+ 'type2': innerPropType2,
+ 'flag': innerPropFlag,
+ 'value': innerPropValue
+
+ };
+
+ this.setCurrentProp( this.getPrevNode().properties, innerPropName );
+
+ },
+
+ nodeEnd: function () {
+
+ this.popStack();
+
+ },
+
+ /* ---------------------------------------------------------------- */
+ /* util */
+ isFlattenNode: function ( node ) {
+
+ return ( 'subNodes' in node && 'properties' in node ) ? true : false;
+
+ }
+
+ } );
+
+ function FBXTree() {}
+
+ Object.assign( FBXTree.prototype, {
+
+ add: function ( key, val ) {
+
+ this[ key ] = val;
+
+ },
+
+ searchConnectionParent: function ( id ) {
+
+ if ( this.__cache_search_connection_parent === undefined ) {
+
+ this.__cache_search_connection_parent = [];
+
+ }
+
+ if ( this.__cache_search_connection_parent[ id ] !== undefined ) {
+
+ return this.__cache_search_connection_parent[ id ];
+
+ } else {
+
+ this.__cache_search_connection_parent[ id ] = [];
+
+ }
+
+ var conns = this.Connections.properties.connections;
+
+ var results = [];
+ for ( var i = 0; i < conns.length; ++ i ) {
+
+ if ( conns[ i ][ 0 ] == id ) {
+
+ // 0 means scene root
+ var res = conns[ i ][ 1 ] === 0 ? - 1 : conns[ i ][ 1 ];
+ results.push( res );
+
+ }
+
+ }
+
+ if ( results.length > 0 ) {
+
+ this.__cache_search_connection_parent[ id ] = this.__cache_search_connection_parent[ id ].concat( results );
+ return results;
+
+ } else {
+
+ this.__cache_search_connection_parent[ id ] = [ - 1 ];
+ return [ - 1 ];
+
+ }
+
+ },
+
+ searchConnectionChildren: function ( id ) {
+
+ if ( this.__cache_search_connection_children === undefined ) {
+
+ this.__cache_search_connection_children = [];
+
+ }
+
+ if ( this.__cache_search_connection_children[ id ] !== undefined ) {
+
+ return this.__cache_search_connection_children[ id ];
+
+ } else {
+
+ this.__cache_search_connection_children[ id ] = [];
+
+ }
+
+ var conns = this.Connections.properties.connections;
+
+ var res = [];
+ for ( var i = 0; i < conns.length; ++ i ) {
+
+ if ( conns[ i ][ 1 ] == id ) {
+
+ // 0 means scene root
+ res.push( conns[ i ][ 0 ] === 0 ? - 1 : conns[ i ][ 0 ] );
+ // there may more than one kid, then search to the end
+
+ }
+
+ }
+
+ if ( res.length > 0 ) {
+
+ this.__cache_search_connection_children[ id ] = this.__cache_search_connection_children[ id ].concat( res );
+ return res;
+
+ } else {
+
+ this.__cache_search_connection_children[ id ] = [ ];
+ return [ ];
+
+ }
+
+ },
+
+ searchConnectionType: function ( id, to ) {
+
+ var key = id + ',' + to; // TODO: to hash
+ if ( this.__cache_search_connection_type === undefined ) {
+
+ this.__cache_search_connection_type = {};
+
+ }
+
+ if ( this.__cache_search_connection_type[ key ] !== undefined ) {
+
+ return this.__cache_search_connection_type[ key ];
+
+ } else {
+
+ this.__cache_search_connection_type[ key ] = '';
+
+ }
+
+ var conns = this.Connections.properties.connections;
+
+ for ( var i = 0; i < conns.length; ++ i ) {
+
+ if ( conns[ i ][ 0 ] == id && conns[ i ][ 1 ] == to ) {
+
+ // 0 means scene root
+ this.__cache_search_connection_type[ key ] = conns[ i ][ 2 ];
+ return conns[ i ][ 2 ];
+
+ }
+
+ }
+
+ this.__cache_search_connection_type[ id ] = null;
+ return null;
+
+ }
+
+ } );
+
+ /**
+ * @returns {boolean}
+ */
+ function isFbxFormatASCII( text ) {
+
+ var CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ];
+
+ var cursor = 0;
+ var read = function ( offset ) {
+
+ var result = text[ offset - 1 ];
+ text = text.slice( cursor + offset );
+ cursor ++;
+ return result;
+
+ };
+
+ for ( var i = 0; i < CORRECT.length; ++ i ) {
+
+ var num = read( 1 );
+ if ( num == CORRECT[ i ] ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ /**
+ * @returns {number}
+ */
+ function getFbxVersion( text ) {
+
+ var versionRegExp = /FBXVersion: (\d+)/;
+ var match = text.match( versionRegExp );
+ if ( match ) {
+
+ var version = parseInt( match[ 1 ] );
+ return version;
+
+ }
+ throw new Error( 'FBXLoader: Cannot find the version number for the file given.' );
+
+ }
+
+ /**
+ * Converts FBX ticks into real time seconds.
+ * @param {number} time - FBX tick timestamp to convert.
+ * @returns {number} - FBX tick in real world time.
+ */
+ function ConvertFBXTimeToSeconds( time ) {
+
+ // Constant is FBX ticks per second.
+ return time / 46186158000;
+
+ }
+
+ /**
+ * Parses comma separated list of float numbers and returns them in an array.
+ * @example
+ * // Returns [ 5.6, 9.4, 2.5, 1.4 ]
+ * parseFloatArray( "5.6,9.4,2.5,1.4" )
+ * @returns {number[]}
+ */
+ function parseFloatArray( floatString ) {
+
+ return floatString.split( ',' ).map( function ( stringValue ) {
+
+ return parseFloat( stringValue );
+
+ } );
+
+ }
+
+ /**
+ * Parses comma separated list of int numbers and returns them in an array.
+ * @example
+ * // Returns [ 5, 8, 2, 3 ]
+ * parseFloatArray( "5,8,2,3" )
+ * @returns {number[]}
+ */
+ function parseIntArray( intString ) {
+
+ return intString.split( ',' ).map( function ( stringValue ) {
+
+ return parseInt( stringValue );
+
+ } );
+
+ }
+
+ function parseMatrixArray( floatString ) {
+
+ return new THREE.Matrix4().fromArray( parseFloatArray( floatString ) );
+
+ }
+
+ /**
+ * Converts number from degrees into radians.
+ * @param {number} value
+ * @returns {number}
+ */
+ function degreeToRadian( value ) {
+
+ return value * Math.PI / 180;
+
+ }
+
+} )();
diff --git a/src/jlmap3d/main/loaders/MTLLoader.js b/src/jlmap3d/main/loaders/MTLLoader.js
new file mode 100644
index 000000000..50de5b936
--- /dev/null
+++ b/src/jlmap3d/main/loaders/MTLLoader.js
@@ -0,0 +1,583 @@
+/**
+ * Loads a Wavefront .mtl file specifying materials
+ *
+ * @author angelxuanchang
+ */
+
+THREE.MTLLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.MTLLoader.prototype = {
+
+ constructor: THREE.MTLLoader,
+
+ /**
+ * Loads and parses a MTL asset from a URL.
+ *
+ * @param {String} url - URL to the MTL file.
+ * @param {Function} [onLoad] - Callback invoked with the loaded object.
+ * @param {Function} [onProgress] - Callback for download progress.
+ * @param {Function} [onError] - Callback for download errors.
+ *
+ * @see setPath setResourcePath
+ *
+ * @note In order for relative texture references to resolve correctly
+ * you must call setResourcePath() explicitly prior to load.
+ */
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var path = ( this.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path;
+
+ var loader = new THREE.FileLoader( this.manager );
+ loader.setPath( this.path );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( text, path ) );
+
+ }, onProgress, onError );
+
+ },
+
+ /**
+ * Set base path for resolving references.
+ * If set this path will be prepended to each loaded and found reference.
+ *
+ * @see setResourcePath
+ * @param {String} path
+ * @return {THREE.MTLLoader}
+ *
+ * @example
+ * mtlLoader.setPath( 'assets/obj/' );
+ * mtlLoader.load( 'my.mtl', ... );
+ */
+ setPath: function ( path ) {
+
+ this.path = path;
+ return this;
+
+ },
+
+ /**
+ * Set base path for additional resources like textures.
+ *
+ * @see setPath
+ * @param {String} path
+ * @return {THREE.MTLLoader}
+ *
+ * @example
+ * mtlLoader.setPath( 'assets/obj/' );
+ * mtlLoader.setResourcePath( 'assets/textures/' );
+ * mtlLoader.load( 'my.mtl', ... );
+ */
+ setResourcePath: function ( path ) {
+
+ this.resourcePath = path;
+ return this;
+
+ },
+
+ setTexturePath: function ( path ) {
+
+ console.warn( 'THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath().' );
+ return this.setResourcePath( path );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+ return this;
+
+ },
+
+ setMaterialOptions: function ( value ) {
+
+ this.materialOptions = value;
+ return this;
+
+ },
+
+ /**
+ * Parses a MTL file.
+ *
+ * @param {String} text - Content of MTL file
+ * @return {THREE.MTLLoader.MaterialCreator}
+ *
+ * @see setPath setResourcePath
+ *
+ * @note In order for relative texture references to resolve correctly
+ * you must call setResourcePath() explicitly prior to parse.
+ */
+ parse: function ( text, path ) {
+
+ var lines = text.split( '\n' );
+ var info = {};
+ var delimiter_pattern = /\s+/;
+ var materialsInfo = {};
+
+ for ( var i = 0; i < lines.length; i ++ ) {
+
+ var line = lines[ i ];
+ line = line.trim();
+
+ if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
+
+ // Blank line or comment ignore
+ continue;
+
+ }
+
+ var pos = line.indexOf( ' ' );
+
+ var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
+ key = key.toLowerCase();
+
+ var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
+ value = value.trim();
+
+ if ( key === 'newmtl' ) {
+
+ // New material
+
+ info = { name: value };
+ materialsInfo[ value ] = info;
+
+ } else {
+
+ if ( key === 'ka' || key === 'kd' || key === 'ks' || key ==='ke' ) {
+
+ var ss = value.split( delimiter_pattern, 3 );
+ info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
+
+ } else {
+
+ info[ key ] = value;
+
+ }
+
+ }
+
+ }
+
+ var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions );
+ materialCreator.setCrossOrigin( this.crossOrigin );
+ materialCreator.setManager( this.manager );
+ materialCreator.setMaterials( materialsInfo );
+ return materialCreator;
+
+ }
+
+};
+
+/**
+ * Create a new THREE-MTLLoader.MaterialCreator
+ * @param baseUrl - Url relative to which textures are loaded
+ * @param options - Set of options on how to construct the materials
+ * side: Which side to apply the material
+ * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
+ * wrap: What type of wrapping to apply for textures
+ * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
+ * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
+ * Default: false, assumed to be already normalized
+ * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
+ * Default: false
+ * @constructor
+ */
+
+THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {
+
+ this.baseUrl = baseUrl || '';
+ this.options = options;
+ this.materialsInfo = {};
+ this.materials = {};
+ this.materialsArray = [];
+ this.nameLookup = {};
+
+ this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;
+ this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;
+
+};
+
+THREE.MTLLoader.MaterialCreator.prototype = {
+
+ constructor: THREE.MTLLoader.MaterialCreator,
+
+ crossOrigin: 'anonymous',
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+ return this;
+
+ },
+
+ setManager: function ( value ) {
+
+ this.manager = value;
+
+ },
+
+ setMaterials: function ( materialsInfo ) {
+
+ this.materialsInfo = this.convert( materialsInfo );
+ this.materials = {};
+ this.materialsArray = [];
+ this.nameLookup = {};
+
+ },
+
+ convert: function ( materialsInfo ) {
+
+ if ( ! this.options ) return materialsInfo;
+
+ var converted = {};
+
+ for ( var mn in materialsInfo ) {
+
+ // Convert materials info into normalized form based on options
+
+ var mat = materialsInfo[ mn ];
+
+ var covmat = {};
+
+ converted[ mn ] = covmat;
+
+ for ( var prop in mat ) {
+
+ var save = true;
+ var value = mat[ prop ];
+ var lprop = prop.toLowerCase();
+
+ switch ( lprop ) {
+
+ case 'kd':
+ case 'ka':
+ case 'ks':
+
+ // Diffuse color (color under white light) using RGB values
+
+ if ( this.options && this.options.normalizeRGB ) {
+
+ value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
+
+ }
+
+ if ( this.options && this.options.ignoreZeroRGBs ) {
+
+ if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
+
+ // ignore
+
+ save = false;
+
+ }
+
+ }
+
+ break;
+
+ default:
+
+ break;
+
+ }
+
+ if ( save ) {
+
+ covmat[ lprop ] = value;
+
+ }
+
+ }
+
+ }
+
+ return converted;
+
+ },
+
+ preload: function () {
+
+ for ( var mn in this.materialsInfo ) {
+
+ this.create( mn );
+
+ }
+
+ },
+
+ getIndex: function ( materialName ) {
+
+ return this.nameLookup[ materialName ];
+
+ },
+
+ getAsArray: function () {
+
+ var index = 0;
+
+ for ( var mn in this.materialsInfo ) {
+
+ this.materialsArray[ index ] = this.create( mn );
+ this.nameLookup[ mn ] = index;
+ index ++;
+
+ }
+
+ return this.materialsArray;
+
+ },
+
+ create: function ( materialName ) {
+
+ if ( this.materials[ materialName ] === undefined ) {
+
+ this.createMaterial_( materialName );
+
+ }
+
+ return this.materials[ materialName ];
+
+ },
+
+ createMaterial_: function ( materialName ) {
+
+ // Create material
+
+ var scope = this;
+ var mat = this.materialsInfo[ materialName ];
+ var params = {
+
+ name: materialName,
+ side: this.side
+
+ };
+
+ function resolveURL( baseUrl, url ) {
+
+ if ( typeof url !== 'string' || url === '' )
+ return '';
+
+ // Absolute URL
+ if ( /^https?:\/\//i.test( url ) ) return url;
+
+ return baseUrl + url;
+
+ }
+
+ function setMapForType( mapType, value ) {
+
+ if ( params[ mapType ] ) return; // Keep the first encountered texture
+
+ var texParams = scope.getTextureParams( value, params );
+ var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
+
+ map.repeat.copy( texParams.scale );
+ map.offset.copy( texParams.offset );
+
+ map.wrapS = scope.wrap;
+ map.wrapT = scope.wrap;
+
+ params[ mapType ] = map;
+
+ }
+
+ for ( var prop in mat ) {
+
+ var value = mat[ prop ];
+ var n;
+
+ if ( value === '' ) continue;
+
+ switch ( prop.toLowerCase() ) {
+
+ // Ns is material specular exponent
+
+ case 'kd':
+
+ // Diffuse color (color under white light) using RGB values
+
+ params.color = new THREE.Color().fromArray( value );
+
+ break;
+
+ case 'ks':
+
+ // Specular color (color when light is reflected from shiny surface) using RGB values
+ params.specular = new THREE.Color().fromArray( value );
+
+ break;
+
+ case 'ke':
+
+ // Emissive using RGB values
+ params.emissive = new THREE.Color().fromArray( value );
+
+ break;
+
+ case 'map_kd':
+
+ // Diffuse texture map
+
+ setMapForType( "map", value );
+
+ break;
+
+ case 'map_ks':
+
+ // Specular map
+
+ setMapForType( "specularMap", value );
+
+ break;
+
+ case 'map_ke':
+
+ // Emissive map
+
+ setMapForType( "emissiveMap", value );
+
+ break;
+
+ case 'norm':
+
+ setMapForType( "normalMap", value );
+
+ break;
+
+ case 'map_bump':
+ case 'bump':
+
+ // Bump texture map
+
+ setMapForType( "bumpMap", value );
+
+ break;
+
+ case 'map_d':
+
+ // Alpha map
+
+ setMapForType( "alphaMap", value );
+ params.transparent = true;
+
+ break;
+
+ case 'ns':
+
+ // The specular exponent (defines the focus of the specular highlight)
+ // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+
+ params.shininess = parseFloat( value );
+
+ break;
+
+ case 'd':
+ n = parseFloat( value );
+
+ if ( n < 1 ) {
+
+ params.opacity = n;
+ params.transparent = true;
+
+ }
+
+ break;
+
+ case 'tr':
+ n = parseFloat( value );
+
+ if ( this.options && this.options.invertTrProperty ) n = 1 - n;
+
+ if ( n > 0 ) {
+
+ params.opacity = 1 - n;
+ params.transparent = true;
+
+ }
+
+ break;
+
+ default:
+ break;
+
+ }
+
+ }
+
+ this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
+ return this.materials[ materialName ];
+
+ },
+
+ getTextureParams: function ( value, matParams ) {
+
+ var texParams = {
+
+ scale: new THREE.Vector2( 1, 1 ),
+ offset: new THREE.Vector2( 0, 0 )
+
+ };
+
+ var items = value.split( /\s+/ );
+ var pos;
+
+ pos = items.indexOf( '-bm' );
+
+ if ( pos >= 0 ) {
+
+ matParams.bumpScale = parseFloat( items[ pos + 1 ] );
+ items.splice( pos, 2 );
+
+ }
+
+ pos = items.indexOf( '-s' );
+
+ if ( pos >= 0 ) {
+
+ texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+ items.splice( pos, 4 ); // we expect 3 parameters here!
+
+ }
+
+ pos = items.indexOf( '-o' );
+
+ if ( pos >= 0 ) {
+
+ texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+ items.splice( pos, 4 ); // we expect 3 parameters here!
+
+ }
+
+ texParams.url = items.join( ' ' ).trim();
+ return texParams;
+
+ },
+
+ loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
+
+ var texture;
+ var loader = THREE.Loader.Handlers.get( url );
+ var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
+
+ if ( loader === null ) {
+
+ loader = new THREE.TextureLoader( manager );
+
+ }
+
+ if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
+ texture = loader.load( url, onLoad, onProgress, onError );
+
+ if ( mapping !== undefined ) texture.mapping = mapping;
+
+ return texture;
+
+ }
+
+};
diff --git a/src/jlmap3d/main/loaders/OBJLoader.js b/src/jlmap3d/main/loaders/OBJLoader.js
new file mode 100644
index 000000000..1408c1b4d
--- /dev/null
+++ b/src/jlmap3d/main/loaders/OBJLoader.js
@@ -0,0 +1,797 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.OBJLoader = ( function () {
+
+ // o object_name | g group_name
+ var object_pattern = /^[og]\s*(.+)?/;
+ // mtllib file_reference
+ var material_library_pattern = /^mtllib /;
+ // usemtl material_name
+ var material_use_pattern = /^usemtl /;
+
+ function ParserState() {
+
+ var state = {
+ objects: [],
+ object: {},
+
+ vertices: [],
+ normals: [],
+ colors: [],
+ uvs: [],
+
+ materialLibraries: [],
+
+ startObject: function ( name, fromDeclaration ) {
+
+ // If the current object (initial from reset) is not from a g/o declaration in the parsed
+ // file. We need to use it for the first parsed g/o to keep things in sync.
+ if ( this.object && this.object.fromDeclaration === false ) {
+
+ this.object.name = name;
+ this.object.fromDeclaration = ( fromDeclaration !== false );
+ return;
+
+ }
+
+ var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
+
+ if ( this.object && typeof this.object._finalize === 'function' ) {
+
+ this.object._finalize( true );
+
+ }
+
+ this.object = {
+ name: name || '',
+ fromDeclaration: ( fromDeclaration !== false ),
+
+ geometry: {
+ vertices: [],
+ normals: [],
+ colors: [],
+ uvs: []
+ },
+ materials: [],
+ smooth: true,
+
+ startMaterial: function ( name, libraries ) {
+
+ var previous = this._finalize( false );
+
+ // New usemtl declaration overwrites an inherited material, except if faces were declared
+ // after the material, then it must be preserved for proper MultiMaterial continuation.
+ if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
+
+ this.materials.splice( previous.index, 1 );
+
+ }
+
+ var material = {
+ index: this.materials.length,
+ name: name || '',
+ mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
+ smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
+ groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
+ groupEnd: - 1,
+ groupCount: - 1,
+ inherited: false,
+
+ clone: function ( index ) {
+
+ var cloned = {
+ index: ( typeof index === 'number' ? index : this.index ),
+ name: this.name,
+ mtllib: this.mtllib,
+ smooth: this.smooth,
+ groupStart: 0,
+ groupEnd: - 1,
+ groupCount: - 1,
+ inherited: false
+ };
+ cloned.clone = this.clone.bind( cloned );
+ return cloned;
+
+ }
+ };
+
+ this.materials.push( material );
+
+ return material;
+
+ },
+
+ currentMaterial: function () {
+
+ if ( this.materials.length > 0 ) {
+
+ return this.materials[ this.materials.length - 1 ];
+
+ }
+
+ return undefined;
+
+ },
+
+ _finalize: function ( end ) {
+
+ var lastMultiMaterial = this.currentMaterial();
+ if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
+
+ lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
+ lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
+ lastMultiMaterial.inherited = false;
+
+ }
+
+ // Ignore objects tail materials if no face declarations followed them before a new o/g started.
+ if ( end && this.materials.length > 1 ) {
+
+ for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) {
+
+ if ( this.materials[ mi ].groupCount <= 0 ) {
+
+ this.materials.splice( mi, 1 );
+
+ }
+
+ }
+
+ }
+
+ // Guarantee at least one empty material, this makes the creation later more straight forward.
+ if ( end && this.materials.length === 0 ) {
+
+ this.materials.push( {
+ name: '',
+ smooth: this.smooth
+ } );
+
+ }
+
+ return lastMultiMaterial;
+
+ }
+ };
+
+ // Inherit previous objects material.
+ // Spec tells us that a declared material must be set to all objects until a new material is declared.
+ // If a usemtl declaration is encountered while this new object is being parsed, it will
+ // overwrite the inherited material. Exception being that there was already face declarations
+ // to the inherited material, then it will be preserved for proper MultiMaterial continuation.
+
+ if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
+
+ var declared = previousMaterial.clone( 0 );
+ declared.inherited = true;
+ this.object.materials.push( declared );
+
+ }
+
+ this.objects.push( this.object );
+
+ },
+
+ finalize: function () {
+
+ if ( this.object && typeof this.object._finalize === 'function' ) {
+
+ this.object._finalize( true );
+
+ }
+
+ },
+
+ parseVertexIndex: function ( value, len ) {
+
+ var index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+ },
+
+ parseNormalIndex: function ( value, len ) {
+
+ var index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+ },
+
+ parseUVIndex: function ( value, len ) {
+
+ var index = parseInt( value, 10 );
+ return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
+
+ },
+
+ addVertex: function ( a, b, c ) {
+
+ var src = this.vertices;
+ var dst = this.object.geometry.vertices;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+
+ addVertexPoint: function ( a ) {
+
+ var src = this.vertices;
+ var dst = this.object.geometry.vertices;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+ },
+
+ addVertexLine: function ( a ) {
+
+ var src = this.vertices;
+ var dst = this.object.geometry.vertices;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+ },
+
+ addNormal: function ( a, b, c ) {
+
+ var src = this.normals;
+ var dst = this.object.geometry.normals;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+
+ addColor: function ( a, b, c ) {
+
+ var src = this.colors;
+ var dst = this.object.geometry.colors;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+ },
+
+ addUV: function ( a, b, c ) {
+
+ var src = this.uvs;
+ var dst = this.object.geometry.uvs;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ] );
+ dst.push( src[ b + 0 ], src[ b + 1 ] );
+ dst.push( src[ c + 0 ], src[ c + 1 ] );
+
+ },
+
+ addUVLine: function ( a ) {
+
+ var src = this.uvs;
+ var dst = this.object.geometry.uvs;
+
+ dst.push( src[ a + 0 ], src[ a + 1 ] );
+
+ },
+
+ addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
+
+ var vLen = this.vertices.length;
+
+ var ia = this.parseVertexIndex( a, vLen );
+ var ib = this.parseVertexIndex( b, vLen );
+ var ic = this.parseVertexIndex( c, vLen );
+
+ this.addVertex( ia, ib, ic );
+
+ if ( ua !== undefined && ua !== '' ) {
+
+ var uvLen = this.uvs.length;
+ ia = this.parseUVIndex( ua, uvLen );
+ ib = this.parseUVIndex( ub, uvLen );
+ ic = this.parseUVIndex( uc, uvLen );
+ this.addUV( ia, ib, ic );
+
+ }
+
+ if ( na !== undefined && na !== '' ) {
+
+ // Normals are many times the same. If so, skip function call and parseInt.
+ var nLen = this.normals.length;
+ ia = this.parseNormalIndex( na, nLen );
+
+ ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
+ ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
+
+ this.addNormal( ia, ib, ic );
+
+ }
+
+ if ( this.colors.length > 0 ) {
+
+ this.addColor( ia, ib, ic );
+
+ }
+
+ },
+
+ addPointGeometry: function ( vertices ) {
+
+ this.object.geometry.type = 'Points';
+
+ var vLen = this.vertices.length;
+
+ for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+ this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+ }
+
+ },
+
+ addLineGeometry: function ( vertices, uvs ) {
+
+ this.object.geometry.type = 'Line';
+
+ var vLen = this.vertices.length;
+ var uvLen = this.uvs.length;
+
+ for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+ this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+ }
+
+ for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
+
+ this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
+
+ }
+
+ }
+
+ };
+
+ state.startObject( '', false );
+
+ return state;
+
+ }
+
+ //
+
+ function OBJLoader( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+ this.materials = null;
+
+ }
+
+ OBJLoader.prototype = {
+
+ constructor: OBJLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.FileLoader( scope.manager );
+ loader.setPath( this.path );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( text ) );
+
+ }, onProgress, onError );
+
+ },
+
+ setPath: function ( value ) {
+
+ this.path = value;
+
+ return this;
+
+ },
+
+ setMaterials: function ( materials ) {
+
+ this.materials = materials;
+
+ return this;
+
+ },
+
+ parse: function ( text ) {
+
+ //console.time( 'OBJLoader' );
+
+ var state = new ParserState();
+
+ if ( text.indexOf( '\r\n' ) !== - 1 ) {
+
+ // This is faster than String.split with regex that splits on both
+ text = text.replace( /\r\n/g, '\n' );
+
+ }
+
+ if ( text.indexOf( '\\\n' ) !== - 1 ) {
+
+ // join lines separated by a line continuation character (\)
+ text = text.replace( /\\\n/g, '' );
+
+ }
+
+ var lines = text.split( '\n' );
+ var line = '', lineFirstChar = '';
+ var lineLength = 0;
+ var result = [];
+
+ // Faster to just trim left side of the line. Use if available.
+ var trimLeft = ( typeof ''.trimLeft === 'function' );
+
+ for ( var i = 0, l = lines.length; i < l; i ++ ) {
+
+ line = lines[ i ];
+
+ line = trimLeft ? line.trimLeft() : line.trim();
+
+ lineLength = line.length;
+
+ if ( lineLength === 0 ) continue;
+
+ lineFirstChar = line.charAt( 0 );
+
+ // @todo invoke passed in handler if any
+ if ( lineFirstChar === '#' ) continue;
+
+ if ( lineFirstChar === 'v' ) {
+
+ var data = line.split( /\s+/ );
+
+ switch ( data[ 0 ] ) {
+
+ case 'v':
+ state.vertices.push(
+ parseFloat( data[ 1 ] ),
+ parseFloat( data[ 2 ] ),
+ parseFloat( data[ 3 ] )
+ );
+ if ( data.length === 8 ) {
+
+ state.colors.push(
+ parseFloat( data[ 4 ] ),
+ parseFloat( data[ 5 ] ),
+ parseFloat( data[ 6 ] )
+
+ );
+
+ }
+ break;
+ case 'vn':
+ state.normals.push(
+ parseFloat( data[ 1 ] ),
+ parseFloat( data[ 2 ] ),
+ parseFloat( data[ 3 ] )
+ );
+ break;
+ case 'vt':
+ state.uvs.push(
+ parseFloat( data[ 1 ] ),
+ parseFloat( data[ 2 ] )
+ );
+ break;
+
+ }
+
+ } else if ( lineFirstChar === 'f' ) {
+
+ var lineData = line.substr( 1 ).trim();
+ var vertexData = lineData.split( /\s+/ );
+ var faceVertices = [];
+
+ // Parse the face vertex data into an easy to work with format
+
+ for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {
+
+ var vertex = vertexData[ j ];
+
+ if ( vertex.length > 0 ) {
+
+ var vertexParts = vertex.split( '/' );
+ faceVertices.push( vertexParts );
+
+ }
+
+ }
+
+ // Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+
+ var v1 = faceVertices[ 0 ];
+
+ for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
+
+ var v2 = faceVertices[ j ];
+ var v3 = faceVertices[ j + 1 ];
+
+ state.addFace(
+ v1[ 0 ], v2[ 0 ], v3[ 0 ],
+ v1[ 1 ], v2[ 1 ], v3[ 1 ],
+ v1[ 2 ], v2[ 2 ], v3[ 2 ]
+ );
+
+ }
+
+ } else if ( lineFirstChar === 'l' ) {
+
+ var lineParts = line.substring( 1 ).trim().split( " " );
+ var lineVertices = [], lineUVs = [];
+
+ if ( line.indexOf( "/" ) === - 1 ) {
+
+ lineVertices = lineParts;
+
+ } else {
+
+ for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {
+
+ var parts = lineParts[ li ].split( "/" );
+
+ if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] );
+ if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] );
+
+ }
+
+ }
+ state.addLineGeometry( lineVertices, lineUVs );
+
+ } else if ( lineFirstChar === 'p' ) {
+
+ var lineData = line.substr( 1 ).trim();
+ var pointData = lineData.split( " " );
+
+ state.addPointGeometry( pointData );
+
+ } else if ( ( result = object_pattern.exec( line ) ) !== null ) {
+
+ // o object_name
+ // or
+ // g group_name
+
+ // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
+ // var name = result[ 0 ].substr( 1 ).trim();
+ var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 );
+
+ state.startObject( name );
+
+ } else if ( material_use_pattern.test( line ) ) {
+
+ // material
+
+ state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
+
+ } else if ( material_library_pattern.test( line ) ) {
+
+ // mtl file
+
+ state.materialLibraries.push( line.substring( 7 ).trim() );
+
+ } else if ( lineFirstChar === 's' ) {
+
+ result = line.split( ' ' );
+
+ // smooth shading
+
+ // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
+ // but does not define a usemtl for each face set.
+ // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
+ // This requires some care to not create extra material on each smooth value for "normal" obj files.
+ // where explicit usemtl defines geometry groups.
+ // Example asset: examples/models/obj/cerberus/Cerberus.obj
+
+ /*
+ * http://paulbourke.net/dataformats/obj/
+ * or
+ * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
+ *
+ * From chapter "Grouping" Syntax explanation "s group_number":
+ * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
+ * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
+ * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
+ * than 0."
+ */
+ if ( result.length > 1 ) {
+
+ var value = result[ 1 ].trim().toLowerCase();
+ state.object.smooth = ( value !== '0' && value !== 'off' );
+
+ } else {
+
+ // ZBrush can produce "s" lines #11707
+ state.object.smooth = true;
+
+ }
+ var material = state.object.currentMaterial();
+ if ( material ) material.smooth = state.object.smooth;
+
+ } else {
+
+ // Handle null terminated files without exception
+ if ( line === '\0' ) continue;
+
+ throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
+
+ }
+
+ }
+
+ state.finalize();
+
+ var container = new THREE.Group();
+ container.materialLibraries = [].concat( state.materialLibraries );
+
+ for ( var i = 0, l = state.objects.length; i < l; i ++ ) {
+
+ var object = state.objects[ i ];
+ var geometry = object.geometry;
+ var materials = object.materials;
+ var isLine = ( geometry.type === 'Line' );
+ var isPoints = ( geometry.type === 'Points' );
+ var hasVertexColors = false;
+
+ // Skip o/g line declarations that did not follow with any faces
+ if ( geometry.vertices.length === 0 ) continue;
+
+ var buffergeometry = new THREE.BufferGeometry();
+
+ buffergeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) );
+
+ if ( geometry.normals.length > 0 ) {
+
+ buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) );
+
+ } else {
+
+ buffergeometry.computeVertexNormals();
+
+ }
+
+ if ( geometry.colors.length > 0 ) {
+
+ hasVertexColors = true;
+ buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) );
+
+ }
+
+ if ( geometry.uvs.length > 0 ) {
+
+ buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) );
+
+ }
+
+ // Create materials
+
+ var createdMaterials = [];
+
+ for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+ var sourceMaterial = materials[ mi ];
+ var material = undefined;
+
+ if ( this.materials !== null ) {
+
+ material = this.materials.create( sourceMaterial.name );
+
+ // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
+ if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {
+
+ var materialLine = new THREE.LineBasicMaterial();
+ THREE.Material.prototype.copy.call( materialLine, material );
+ materialLine.color.copy( material.color );
+ materialLine.lights = false;
+ material = materialLine;
+
+ } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) {
+
+ var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } );
+ THREE.Material.prototype.copy.call( materialPoints, material );
+ materialPoints.color.copy( material.color );
+ materialPoints.map = material.map;
+ materialPoints.lights = false;
+ material = materialPoints;
+
+ }
+
+ }
+
+ if ( ! material ) {
+
+ if ( isLine ) {
+
+ material = new THREE.LineBasicMaterial();
+
+ } else if ( isPoints ) {
+
+ material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+ } else {
+
+ material = new THREE.MeshPhongMaterial();
+
+ }
+
+ material.name = sourceMaterial.name;
+
+ }
+
+ material.flatShading = sourceMaterial.smooth ? false : true;
+ material.vertexColors = hasVertexColors ? THREE.VertexColors : THREE.NoColors;
+
+ createdMaterials.push( material );
+
+ }
+
+ // Create mesh
+
+ var mesh;
+
+ if ( createdMaterials.length > 1 ) {
+
+ for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+ var sourceMaterial = materials[ mi ];
+ buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
+
+ }
+
+ if ( isLine ) {
+
+ mesh = new THREE.LineSegments( buffergeometry, createdMaterials );
+
+ } else if ( isPoints ) {
+
+ mesh = new THREE.Points( buffergeometry, createdMaterials );
+
+ } else {
+
+ mesh = new THREE.Mesh( buffergeometry, createdMaterials );
+
+ }
+
+ } else {
+
+ if ( isLine ) {
+
+ mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] );
+
+ } else if ( isPoints ) {
+
+ mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] );
+
+ } else {
+
+ mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] );
+
+ }
+
+ }
+
+ mesh.name = object.name;
+
+ container.add( mesh );
+
+ }
+
+ //console.timeEnd( 'OBJLoader' );
+
+ return container;
+
+ }
+
+ };
+
+ return OBJLoader;
+
+} )();
diff --git a/src/jlmap3d/main/loaders/model/Assetmaterial.js b/src/jlmap3d/main/loaders/model/Assetmaterial.js
new file mode 100644
index 000000000..4f103890c
--- /dev/null
+++ b/src/jlmap3d/main/loaders/model/Assetmaterial.js
@@ -0,0 +1,23 @@
+export function Assetmaterial(){
+
+ let scope = this;
+
+ this.uuid = null;
+
+ this.name = null;
+
+ this.classtype = null;
+
+ this.modeltype = null;
+
+ this.asseturl = null;
+
+ this.create = function(){
+
+ }
+
+ this.dispose = function (){
+
+ }
+
+}
diff --git a/src/jlmap3d/main/loaders/model/Assetmodel.js b/src/jlmap3d/main/loaders/model/Assetmodel.js
new file mode 100644
index 000000000..bb5c5ad2c
--- /dev/null
+++ b/src/jlmap3d/main/loaders/model/Assetmodel.js
@@ -0,0 +1,27 @@
+export function AssetModel(data){
+
+ let scope = this;
+
+ this.id = data.id;
+
+ this.name = data.name;
+
+ this.deviceType = data.deviceType;
+
+ this.type = data.type;
+
+ this.assetUrl = data.assetUrl;
+
+ this.mesh = null;
+
+ this.animate = null;
+
+ this.create = function(){
+
+ }
+
+ this.dispose = function(){
+
+ }
+
+}
diff --git a/src/jlmap3d/main/three.min.js b/src/jlmap3d/main/three.min.js
new file mode 100644
index 000000000..87bc1451e
--- /dev/null
+++ b/src/jlmap3d/main/three.min.js
@@ -0,0 +1,971 @@
+// threejs.org/license
+(function(l,ka){"object"===typeof exports&&"undefined"!==typeof module?ka(exports):"function"===typeof define&&define.amd?define(["exports"],ka):(l=l||self,ka(l.THREE={}))})(this,function(l){function ka(){}function B(a,b){this.x=a||0;this.y=b||0}function J(){this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1];0b&&(b=a[c]);return b}function C(){Object.defineProperty(this,"id",{value:Xf+=2});this.uuid=H.generateUUID();this.name="";this.type="BufferGeometry";this.index=null;this.attributes={};this.morphAttributes={};this.groups=[];this.boundingSphere=this.boundingBox=null;this.drawRange={start:0,count:Infinity};this.userData={}}
+function Ob(a,b,c,d,e,f){G.call(this);this.type="BoxGeometry";this.parameters={width:a,height:b,depth:c,widthSegments:d,heightSegments:e,depthSegments:f};this.fromBufferGeometry(new rb(a,b,c,d,e,f));this.mergeVertices()}function rb(a,b,c,d,e,f){function g(a,b,c,d,e,f,g,l,fa,A,B){var r=f/fa,u=g/A,w=f/2,x=g/2,z=l/2;g=fa+1;var D=A+1,y=f=0,Q,Fa,ta=new n;for(Fa=0;Fam;m++){if(q=d[m])if(h=q[0],k=q[1]){p&&e.addAttribute("morphTarget"+m,
+p[h]);f&&e.addAttribute("morphNormal"+m,f[h]);c[m]=k;continue}c[m]=0}g.getUniforms().setValue(a,"morphTargetInfluences",c)}}}function ig(a,b){var c={};return{update:function(d){var e=b.render.frame,f=d.geometry,g=a.get(d,f);c[g.id]!==e&&(f.isGeometry&&g.updateFromObject(d),a.update(g),c[g.id]=e);return g},dispose:function(){c={}}}}function $a(a,b,c,d,e,f,g,h,k,m){a=void 0!==a?a:[];V.call(this,a,void 0!==b?b:301,c,d,e,f,void 0!==g?g:1022,h,k,m);this.flipY=!1}function Qb(a,b,c,d){V.call(this,null);
+this.image={data:a,width:b,height:c,depth:d};this.minFilter=this.magFilter=1003;this.wrapR=1001;this.flipY=this.generateMipmaps=!1}function Rb(a,b,c){var d=a[0];if(0>=d||0/gm,
+function(a,c){a=U[c];if(void 0===a)throw Error("Can not resolve #include <"+c+">");return fe(a)})}function kf(a){return a.replace(/#pragma unroll_loop[\s]+?for \( int i = (\d+); i < (\d+); i \+\+ \) \{([\s\S]+?)(?=\})\}/g,function(a,c,d,e){a="";for(c=parseInt(c);cd||a.height>d)e=d/Math.max(a.width,a.height);if(1>e||!0===b){if(a instanceof ImageBitmap||a instanceof HTMLImageElement||a instanceof HTMLCanvasElement)return d=b?H.floorPowerOfTwo:Math.floor,b=d(e*a.width),e=d(e*a.height),void 0===B&&(B=h(b,e)),c=c?h(b,e):B,c.width=b,c.height=e,c.getContext("2d").drawImage(a,0,0,b,e),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+
+a.width+"x"+a.height+") to ("+b+"x"+e+")."),F?c.transferToImageBitmap():c;"data"in a&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+a.width+"x"+a.height+").")}return a}function m(a){return H.isPowerOfTwo(a.width)&&H.isPowerOfTwo(a.height)}function p(a,b){return a.generateMipmaps&&b&&1003!==a.minFilter&&1006!==a.minFilter}function q(b,c,e,f){a.generateMipmap(b);d.get(c).__maxMipLevel=Math.log(Math.max(e,f))*Math.LOG2E}function v(a,c){if(!e.isWebGL2)return a;var d=a;6403===a&&
+(5126===c&&(d=33326),5131===c&&(d=33325),5121===c&&(d=33321));6407===a&&(5126===c&&(d=34837),5131===c&&(d=34843),5121===c&&(d=32849));6408===a&&(5126===c&&(d=34836),5131===c&&(d=34842),5121===c&&(d=32856));33325===d||33326===d||34842===d||34836===d?b.get("EXT_color_buffer_float"):(34843===d||34837===d)&&console.warn("THREE.WebGLRenderer: Floating point textures with RGB format not supported. Please use RGBA instead.");return d}function l(a){return 1003===a||1004===a||1005===a?9728:9729}function r(b){b=
+b.target;b.removeEventListener("dispose",r);var c=d.get(b);void 0!==c.__webglInit&&(a.deleteTexture(c.__webglTexture),d.remove(b));b.isVideoTexture&&delete A[b.id];g.memory.textures--}function u(b){b=b.target;b.removeEventListener("dispose",u);var c=d.get(b),e=d.get(b.texture);if(b){void 0!==e.__webglTexture&&a.deleteTexture(e.__webglTexture);b.depthTexture&&b.depthTexture.dispose();if(b.isWebGLRenderTargetCube)for(e=0;6>e;e++)a.deleteFramebuffer(c.__webglFramebuffer[e]),c.__webglDepthbuffer&&a.deleteRenderbuffer(c.__webglDepthbuffer[e]);
+else a.deleteFramebuffer(c.__webglFramebuffer),c.__webglDepthbuffer&&a.deleteRenderbuffer(c.__webglDepthbuffer);d.remove(b.texture);d.remove(b)}g.memory.textures--}function n(a,b){var e=d.get(a);if(a.isVideoTexture){var f=a.id,h=g.render.frame;A[f]!==h&&(A[f]=h,a.update())}if(0r;r++)t[r]=g||l?l?b.image[r].image:b.image[r]:k(b.image[r],!1,!0,e.maxCubemapSize);var u=t[0],n=m(u)||e.isWebGL2,w=f.convert(b.format),y=f.convert(b.type),D=v(w,y);
+z(34067,b,n);for(r=0;6>r;r++)if(g)for(var A,fa=t[r].mipmaps,Q=0,Ac=fa.length;Qr;r++)h.__webglFramebuffer[r]=a.createFramebuffer();else if(h.__webglFramebuffer=a.createFramebuffer(),r)if(e.isWebGL2){h.__webglMultisampledFramebuffer=a.createFramebuffer();h.__webglColorRenderbuffer=a.createRenderbuffer();a.bindRenderbuffer(36161,h.__webglColorRenderbuffer);r=f.convert(b.texture.format);var w=f.convert(b.texture.type);r=v(r,w);w=fa(b);a.renderbufferStorageMultisample(36161,w,r,b.width,
+b.height);a.bindFramebuffer(36160,h.__webglMultisampledFramebuffer);a.framebufferRenderbuffer(36160,36064,36161,h.__webglColorRenderbuffer);a.bindRenderbuffer(36161,null);b.depthBuffer&&(h.__webglDepthRenderbuffer=a.createRenderbuffer(),Q(h.__webglDepthRenderbuffer,b,!0));a.bindFramebuffer(36160,null)}else console.warn("THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.");if(l){c.bindTexture(34067,k.__webglTexture);z(34067,b.texture,t);for(r=0;6>r;r++)y(h.__webglFramebuffer[r],
+b,36064,34069+r);p(b.texture,t)&&q(34067,b.texture,b.width,b.height);c.bindTexture(34067,null)}else c.bindTexture(3553,k.__webglTexture),z(3553,b.texture,t),y(h.__webglFramebuffer,b,36064,3553),p(b.texture,t)&&q(3553,b.texture,b.width,b.height),c.bindTexture(3553,null);if(b.depthBuffer){h=d.get(b);k=!0===b.isWebGLRenderTargetCube;if(b.depthTexture){if(k)throw Error("target.depthTexture not supported in Cube render targets");if(b&&b.isWebGLRenderTargetCube)throw Error("Depth Texture with cube render targets is not supported");
+a.bindFramebuffer(36160,h.__webglFramebuffer);if(!b.depthTexture||!b.depthTexture.isDepthTexture)throw Error("renderTarget.depthTexture must be an instance of THREE.DepthTexture");d.get(b.depthTexture).__webglTexture&&b.depthTexture.image.width===b.width&&b.depthTexture.image.height===b.height||(b.depthTexture.image.width=b.width,b.depthTexture.image.height=b.height,b.depthTexture.needsUpdate=!0);n(b.depthTexture,0);h=d.get(b.depthTexture).__webglTexture;if(1026===b.depthTexture.format)a.framebufferTexture2D(36160,
+36096,3553,h,0);else if(1027===b.depthTexture.format)a.framebufferTexture2D(36160,33306,3553,h,0);else throw Error("Unknown depthTexture format");}else if(k)for(h.__webglDepthbuffer=[],k=0;6>k;k++)a.bindFramebuffer(36160,h.__webglFramebuffer[k]),h.__webglDepthbuffer[k]=a.createRenderbuffer(),Q(h.__webglDepthbuffer[k],b);else a.bindFramebuffer(36160,h.__webglFramebuffer),h.__webglDepthbuffer=a.createRenderbuffer(),Q(h.__webglDepthbuffer,b);a.bindFramebuffer(36160,null)}};this.updateRenderTargetMipmap=
+function(a){var b=a.texture,f=m(a)||e.isWebGL2;if(p(b,f)){f=a.isWebGLRenderTargetCube?34067:3553;var g=d.get(b).__webglTexture;c.bindTexture(f,g);q(f,b,a.width,a.height);c.bindTexture(f,null)}};this.updateMultisampleRenderTarget=function(b){if(b.isWebGLMultisampleRenderTarget)if(e.isWebGL2){var c=d.get(b);a.bindFramebuffer(36008,c.__webglMultisampledFramebuffer);a.bindFramebuffer(36009,c.__webglFramebuffer);c=b.width;var f=b.height,g=16384;b.depthBuffer&&(g|=256);b.stencilBuffer&&(g|=1024);a.blitFramebuffer(0,
+0,c,f,0,0,c,f,g,9728)}else console.warn("THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.")}}function of(a,b,c){return{convert:function(a){if(1E3===a)return 10497;if(1001===a)return 33071;if(1002===a)return 33648;if(1003===a)return 9728;if(1004===a)return 9984;if(1005===a)return 9986;if(1006===a)return 9729;if(1007===a)return 9985;if(1008===a)return 9987;if(1009===a)return 5121;if(1017===a)return 32819;if(1018===a)return 32820;if(1019===a)return 33635;if(1010===a)return 5120;
+if(1011===a)return 5122;if(1012===a)return 5123;if(1013===a)return 5124;if(1014===a)return 5125;if(1015===a)return 5126;if(1016===a){if(c.isWebGL2)return 5131;var d=b.get("OES_texture_half_float");if(null!==d)return d.HALF_FLOAT_OES}if(1021===a)return 6406;if(1022===a)return 6407;if(1023===a)return 6408;if(1024===a)return 6409;if(1025===a)return 6410;if(1026===a)return 6402;if(1027===a)return 34041;if(1028===a)return 6403;if(100===a)return 32774;if(101===a)return 32778;if(102===a)return 32779;if(200===
+a)return 0;if(201===a)return 1;if(202===a)return 768;if(203===a)return 769;if(204===a)return 770;if(205===a)return 771;if(206===a)return 772;if(207===a)return 773;if(208===a)return 774;if(209===a)return 775;if(210===a)return 776;if(33776===a||33777===a||33778===a||33779===a)if(d=b.get("WEBGL_compressed_texture_s3tc"),null!==d){if(33776===a)return d.COMPRESSED_RGB_S3TC_DXT1_EXT;if(33777===a)return d.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(33778===a)return d.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(33779===a)return d.COMPRESSED_RGBA_S3TC_DXT5_EXT}if(35840===
+a||35841===a||35842===a||35843===a)if(d=b.get("WEBGL_compressed_texture_pvrtc"),null!==d){if(35840===a)return d.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(35841===a)return d.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(35842===a)return d.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;if(35843===a)return d.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}if(36196===a&&(d=b.get("WEBGL_compressed_texture_etc1"),null!==d))return d.COMPRESSED_RGB_ETC1_WEBGL;if(37808===a||37809===a||37810===a||37811===a||37812===a||37813===a||37814===a||37815===a||37816===
+a||37817===a||37818===a||37819===a||37820===a||37821===a)if(d=b.get("WEBGL_compressed_texture_astc"),null!==d)return a;if(103===a||104===a){if(c.isWebGL2){if(103===a)return 32775;if(104===a)return 32776}d=b.get("EXT_blend_minmax");if(null!==d){if(103===a)return d.MIN_EXT;if(104===a)return d.MAX_EXT}}if(1020===a){if(c.isWebGL2)return 34042;d=b.get("WEBGL_depth_texture");if(null!==d)return d.UNSIGNED_INT_24_8_WEBGL}return 0}}}function Sb(){E.call(this);this.type="Group"}function Ua(){E.call(this);this.type=
+"Camera";this.matrixWorldInverse=new J;this.projectionMatrix=new J;this.projectionMatrixInverse=new J}function S(a,b,c,d){Ua.call(this);this.type="PerspectiveCamera";this.fov=void 0!==a?a:50;this.zoom=1;this.near=void 0!==c?c:.1;this.far=void 0!==d?d:2E3;this.focus=10;this.aspect=void 0!==b?b:1;this.view=null;this.filmGauge=35;this.filmOffset=0;this.updateProjectionMatrix()}function Ec(a){S.call(this);this.cameras=a||[]}function pf(a,b,c){qf.setFromMatrixPosition(b.matrixWorld);rf.setFromMatrixPosition(c.matrixWorld);
+var d=qf.distanceTo(rf),e=b.projectionMatrix.elements,f=c.projectionMatrix.elements,g=e[14]/(e[10]-1);c=e[14]/(e[10]+1);var h=(e[9]+1)/e[5],k=(e[9]-1)/e[5],m=(e[8]-1)/e[0],p=(f[8]+1)/f[0];e=g*m;f=g*p;p=d/(-m+p);m=p*-m;b.matrixWorld.decompose(a.position,a.quaternion,a.scale);a.translateX(m);a.translateZ(p);a.matrixWorld.compose(a.position,a.quaternion,a.scale);a.matrixWorldInverse.getInverse(a.matrixWorld);b=g+p;g=c+p;a.projectionMatrix.makePerspective(e-m,f+(d-m),h*c/g*b,k*c/g*b,b,g)}function sf(a){function b(){return null!==
+e&&!0===e.isPresenting}function c(){if(b()){var c=e.getEyeParameters("left"),f=c.renderWidth*p;c=c.renderHeight*p;D=a.getPixelRatio();a.getSize(x);a.setDrawingBufferSize(2*f,c,1);Q.start()}else d.enabled&&a.setDrawingBufferSize(x.width,x.height,D),Q.stop()}var d=this,e=null,f=null,g=null,h=[],k=new J,m=new J,p=1,q="stage";"undefined"!==typeof window&&"VRFrameData"in window&&(f=new window.VRFrameData,window.addEventListener("vrdisplaypresentchange",c,!1));var v=new J,l=new aa,r=new n,u=new S;u.bounds=
+new ba(0,0,.5,1);u.layers.enable(1);var w=new S;w.bounds=new ba(.5,0,.5,1);w.layers.enable(2);var z=new Ec([u,w]);z.layers.enable(1);z.layers.enable(2);var x=new B,D,y=[];this.enabled=!1;this.getController=function(a){var b=h[a];void 0===b&&(b=new Sb,b.matrixAutoUpdate=!1,b.visible=!1,h[a]=b);return b};this.getDevice=function(){return e};this.setDevice=function(a){void 0!==a&&(e=a);Q.setContext(a)};this.setFramebufferScaleFactor=function(a){p=a};this.setFrameOfReferenceType=function(a){q=a};this.setPoseTarget=
+function(a){void 0!==a&&(g=a)};this.getCamera=function(a){var c="stage"===q?1.6:0;if(!1===b())return a.position.set(0,c,0),a.rotation.set(0,0,0),a;e.depthNear=a.near;e.depthFar=a.far;e.getFrameData(f);if("stage"===q){var d=e.stageParameters;d?k.fromArray(d.sittingToStandingTransform):k.makeTranslation(0,c,0)}c=f.pose;d=null!==g?g:a;d.matrix.copy(k);d.matrix.decompose(d.position,d.quaternion,d.scale);null!==c.orientation&&(l.fromArray(c.orientation),d.quaternion.multiply(l));null!==c.position&&(l.setFromRotationMatrix(k),
+r.fromArray(c.position),r.applyQuaternion(l),d.position.add(r));d.updateMatrixWorld();u.near=a.near;w.near=a.near;u.far=a.far;w.far=a.far;u.matrixWorldInverse.fromArray(f.leftViewMatrix);w.matrixWorldInverse.fromArray(f.rightViewMatrix);m.getInverse(k);"stage"===q&&(u.matrixWorldInverse.multiply(m),w.matrixWorldInverse.multiply(m));a=d.parent;null!==a&&(v.getInverse(a.matrixWorld),u.matrixWorldInverse.multiply(v),w.matrixWorldInverse.multiply(v));u.matrixWorld.getInverse(u.matrixWorldInverse);w.matrixWorld.getInverse(w.matrixWorldInverse);
+u.projectionMatrix.fromArray(f.leftProjectionMatrix);w.projectionMatrix.fromArray(f.rightProjectionMatrix);pf(z,u,w);a=e.getLayers();a.length&&(a=a[0],null!==a.leftBounds&&4===a.leftBounds.length&&u.bounds.fromArray(a.leftBounds),null!==a.rightBounds&&4===a.rightBounds.length&&w.bounds.fromArray(a.rightBounds));a:for(a=0;af.matrixWorld.determinant();Z.setMaterial(e,
+h);var k=l(a,c,e,f),m=!1;if(b!==d.id||U!==k.id||ta!==(!0===e.wireframe))b=d.id,U=k.id,ta=!0===e.wireframe,m=!0;f.morphTargetInfluences&&(xa.update(f,d,e,k),m=!0);h=d.index;var p=d.attributes.position;c=1;!0===e.wireframe&&(h=ua.getWireframeAttribute(d),c=2);a=Aa;if(null!==h){var q=ra.get(h);a=Ba;a.setIndex(q)}if(m){if(d&&d.isInstancedBufferGeometry&&!za.isWebGL2&&null===la.get("ANGLE_instanced_arrays"))console.error("THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.");
+else{Z.initAttributes();m=d.attributes;k=k.getAttributes();var v=e.defaultAttributeValues;for(A in k){var r=k[A];if(0<=r){var t=m[A];if(void 0!==t){var n=t.normalized,u=t.itemSize,w=ra.get(t);if(void 0!==w){var x=w.buffer,z=w.type;w=w.bytesPerElement;if(t.isInterleavedBufferAttribute){var y=t.data,D=y.stride;t=t.offset;y&&y.isInstancedInterleavedBuffer?(Z.enableAttributeAndDivisor(r,y.meshPerAttribute),void 0===d.maxInstancedCount&&(d.maxInstancedCount=y.meshPerAttribute*y.count)):Z.enableAttribute(r);
+O.bindBuffer(34962,x);O.vertexAttribPointer(r,u,z,n,D*w,t*w)}else t.isInstancedBufferAttribute?(Z.enableAttributeAndDivisor(r,t.meshPerAttribute),void 0===d.maxInstancedCount&&(d.maxInstancedCount=t.meshPerAttribute*t.count)):Z.enableAttribute(r),O.bindBuffer(34962,x),O.vertexAttribPointer(r,u,z,n,0,0)}}else if(void 0!==v&&(n=v[A],void 0!==n))switch(n.length){case 2:O.vertexAttrib2fv(r,n);break;case 3:O.vertexAttrib3fv(r,n);break;case 4:O.vertexAttrib4fv(r,n);break;default:O.vertexAttrib1fv(r,n)}}}Z.disableUnusedAttributes()}null!==
+h&&O.bindBuffer(34963,q.buffer)}q=Infinity;null!==h?q=h.count:void 0!==p&&(q=p.count);h=d.drawRange.start*c;p=null!==g?g.start*c:0;var A=Math.max(h,p);g=Math.max(0,Math.min(q,h+d.drawRange.count*c,p+(null!==g?g.count*c:Infinity))-1-A+1);if(0!==g){if(f.isMesh)if(!0===e.wireframe)Z.setLineWidth(e.wireframeLinewidth*(null===G?T:1)),a.setMode(1);else switch(f.drawMode){case 0:a.setMode(4);break;case 1:a.setMode(5);break;case 2:a.setMode(6)}else f.isLine?(e=e.linewidth,void 0===e&&(e=1),Z.setLineWidth(e*
+(null===G?T:1)),f.isLineSegments?a.setMode(1):f.isLineLoop?a.setMode(2):a.setMode(3)):f.isPoints?a.setMode(0):f.isSprite&&a.setMode(4);d&&d.isInstancedBufferGeometry?0=za.maxTextures&&console.warn("THREE.WebGLRenderer: Trying to use "+a+" texture units while this GPU supports only "+za.maxTextures);ia+=1;return a};this.setTexture2D=function(){var a=!1;return function(b,c){b&&b.isWebGLRenderTarget&&(a||(console.warn("THREE.WebGLRenderer.setTexture2D: don't use render targets as textures. Use their .texture property instead."),
+a=!0),b=b.texture);da.setTexture2D(b,c)}}();this.setTexture3D=function(){return function(a,b){da.setTexture3D(a,b)}}();this.setTexture=function(){var a=!1;return function(b,c){a||(console.warn("THREE.WebGLRenderer: .setTexture is deprecated, use setTexture2D instead."),a=!0);da.setTexture2D(b,c)}}();this.setTextureCube=function(){var a=!1;return function(b,c){b&&b.isWebGLRenderTargetCube&&(a||(console.warn("THREE.WebGLRenderer.setTextureCube: don't use cube render targets as textures. Use their .texture property instead."),
+a=!0),b=b.texture);b&&b.isCubeTexture||Array.isArray(b.image)&&6===b.image.length?da.setTextureCube(b,c):da.setTextureCubeDynamic(b,c)}}();this.setFramebuffer=function(a){L=a};this.getRenderTarget=function(){return G};this.setRenderTarget=function(a,b,c){(G=a)&&void 0===Da.get(a).__webglFramebuffer&&da.setupRenderTarget(a);var d=L,e=!1;a?(d=Da.get(a).__webglFramebuffer,a.isWebGLRenderTargetCube?(d=d[b||0],e=!0):d=a.isWebGLMultisampleRenderTarget?Da.get(a).__webglMultisampledFramebuffer:d,V.copy(a.viewport),
+W.copy(a.scissor),ca=a.scissorTest):(V.copy(ea).multiplyScalar(T),W.copy(aa).multiplyScalar(T),ca=pa);M!==d&&(O.bindFramebuffer(36160,d),M=d);Z.viewport(V);Z.scissor(W);Z.setScissorTest(ca);e&&(a=Da.get(a.texture),O.framebufferTexture2D(36160,36064,34069+b||0,a.__webglTexture,c||0))};this.readRenderTargetPixels=function(a,b,c,d,e,f){if(a&&a.isWebGLRenderTarget){var g=Da.get(a).__webglFramebuffer;if(g){var h=!1;g!==M&&(O.bindFramebuffer(36160,g),h=!0);try{var k=a.texture,m=k.format,p=k.type;1023!==
+m&&ja.convert(m)!==O.getParameter(35739)?console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format."):1009===p||ja.convert(p)===O.getParameter(35738)||1015===p&&(za.isWebGL2||la.get("OES_texture_float")||la.get("WEBGL_color_buffer_float"))||1016===p&&(za.isWebGL2?la.get("EXT_color_buffer_float"):la.get("EXT_color_buffer_half_float"))?36053===O.checkFramebufferStatus(36160)?0<=b&&b<=a.width-d&&0<=c&&c<=a.height-e&&O.readPixels(b,c,d,e,ja.convert(m),
+ja.convert(p),f):console.error("THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete."):console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.")}finally{h&&O.bindFramebuffer(36160,M)}}}else console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.")};this.copyFramebufferToTexture=function(a,b,c){var d=b.image.width,e=b.image.height,
+f=ja.convert(b.format);this.setTexture2D(b,0);O.copyTexImage2D(3553,c||0,f,a.x,a.y,d,e,0)};this.copyTextureToTexture=function(a,b,c,d){var e=b.image.width,f=b.image.height,g=ja.convert(c.format),h=ja.convert(c.type);this.setTexture2D(c,0);b.isDataTexture?O.texSubImage2D(3553,d||0,a.x,a.y,e,f,g,h,b.image.data):O.texSubImage2D(3553,d||0,a.x,a.y,g,h,b.image)}}function yd(a,b){this.name="";this.color=new K(a);this.density=void 0!==b?b:2.5E-4}function zd(a,b,c){this.name="";this.color=new K(a);this.near=
+void 0!==b?b:1;this.far=void 0!==c?c:1E3}function Ad(){E.call(this);this.type="Scene";this.overrideMaterial=this.fog=this.background=null;this.autoUpdate=!0}function ub(a,b){this.array=a;this.stride=b;this.count=void 0!==a?a.length/b:0;this.dynamic=!1;this.updateRange={offset:0,count:-1};this.version=0}function Fc(a,b,c,d){this.data=a;this.itemSize=b;this.offset=c;this.normalized=!0===d}function jb(a){M.call(this);this.type="SpriteMaterial";this.color=new K(16777215);this.map=null;this.rotation=0;
+this.sizeAttenuation=!0;this.lights=!1;this.transparent=!0;this.setValues(a)}function Gc(a){E.call(this);this.type="Sprite";if(void 0===Tb){Tb=new C;var b=new Float32Array([-.5,-.5,0,0,0,.5,-.5,0,1,0,.5,.5,0,1,1,-.5,.5,0,0,1]);b=new ub(b,5);Tb.setIndex([0,1,2,0,2,3]);Tb.addAttribute("position",new Fc(b,3,0,!1));Tb.addAttribute("uv",new Fc(b,2,3,!1))}this.geometry=Tb;this.material=void 0!==a?a:new jb;this.center=new B(.5,.5)}function Hc(){E.call(this);this.type="LOD";Object.defineProperties(this,{levels:{enumerable:!0,
+value:[]}})}function Ic(a,b){a&&a.isGeometry&&console.error("THREE.SkinnedMesh no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.");va.call(this,a,b);this.type="SkinnedMesh";this.bindMode="attached";this.bindMatrix=new J;this.bindMatrixInverse=new J}function Bd(a,b){a=a||[];this.bones=a.slice(0);this.boneMatrices=new Float32Array(16*this.bones.length);if(void 0===b)this.calculateInverses();else if(this.bones.length===b.length)this.boneInverses=b.slice(0);else for(console.warn("THREE.Skeleton boneInverses is the wrong length."),
+this.boneInverses=[],a=0,b=this.bones.length;ac;c++){var q=p[h[c]];var l=p[h[(c+1)%3]];f[0]=Math.min(q,l);f[1]=Math.max(q,l);q=f[0]+
+","+f[1];void 0===g[q]&&(g[q]={index1:f[0],index2:f[1]})}}for(q in g)m=g[q],h=a.vertices[m.index1],b.push(h.x,h.y,h.z),h=a.vertices[m.index2],b.push(h.x,h.y,h.z)}else if(a&&a.isBufferGeometry)if(h=new n,null!==a.index){k=a.attributes.position;p=a.index;var t=a.groups;0===t.length&&(t=[{start:0,count:p.count,materialIndex:0}]);a=0;for(e=t.length;ac;c++)q=p.getX(m+c),l=p.getX(m+(c+1)%3),f[0]=Math.min(q,l),f[1]=Math.max(q,l),q=f[0]+","+
+f[1],void 0===g[q]&&(g[q]={index1:f[0],index2:f[1]});for(q in g)m=g[q],h.fromBufferAttribute(k,m.index1),b.push(h.x,h.y,h.z),h.fromBufferAttribute(k,m.index2),b.push(h.x,h.y,h.z)}else for(k=a.attributes.position,m=0,d=k.count/3;mc;c++)g=3*m+c,h.fromBufferAttribute(k,g),b.push(h.x,h.y,h.z),g=3*m+(c+1)%3,h.fromBufferAttribute(k,g),b.push(h.x,h.y,h.z);this.addAttribute("position",new F(b,3))}function Lc(a,b,c){G.call(this);this.type="ParametricGeometry";this.parameters={func:a,slices:b,
+stacks:c};this.fromBufferGeometry(new Xb(a,b,c));this.mergeVertices()}function Xb(a,b,c){C.call(this);this.type="ParametricBufferGeometry";this.parameters={func:a,slices:b,stacks:c};var d=[],e=[],f=[],g=[],h=new n,k=new n,m=new n,p=new n,q=new n,l,t;3>a.length&&console.error("THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.");var r=b+1;for(l=0;l<=c;l++){var u=l/c;for(t=0;t<=b;t++){var w=t/b;a(w,u,k);e.push(k.x,k.y,k.z);0<=w-1E-5?(a(w-1E-5,u,m),p.subVectors(k,m)):(a(w+
+1E-5,u,m),p.subVectors(m,k));0<=u-1E-5?(a(w,u-1E-5,m),q.subVectors(k,m)):(a(w,u+1E-5,m),q.subVectors(m,k));h.crossVectors(p,q).normalize();f.push(h.x,h.y,h.z);g.push(w,u)}}for(l=0;ld&&1===a.x&&(k[b]=a.x-1);0===c.x&&0===c.z&&(k[b]=d/2/Math.PI+.5)}C.call(this);this.type="PolyhedronBufferGeometry";this.parameters={vertices:a,indices:b,radius:c,detail:d};c=c||1;d=d||0;var h=[],k=[];(function(a){for(var c=new n,d=new n,g=new n,h=0;he&&(.2>b&&(k[a+0]+=1),.2>c&&(k[a+2]+=1),.2>d&&(k[a+4]+=1))})();this.addAttribute("position",new F(h,3));this.addAttribute("normal",new F(h.slice(),3));this.addAttribute("uv",new F(k,2));0===d?this.computeVertexNormals():this.normalizeNormals()}function Nc(a,b){G.call(this);this.type="TetrahedronGeometry";this.parameters={radius:a,detail:b};this.fromBufferGeometry(new Yb(a,b));this.mergeVertices()}
+function Yb(a,b){Aa.call(this,[1,1,1,-1,-1,1,-1,1,-1,1,-1,-1],[2,1,0,0,3,2,1,3,0,2,3,1],a,b);this.type="TetrahedronBufferGeometry";this.parameters={radius:a,detail:b}}function Oc(a,b){G.call(this);this.type="OctahedronGeometry";this.parameters={radius:a,detail:b};this.fromBufferGeometry(new vb(a,b));this.mergeVertices()}function vb(a,b){Aa.call(this,[1,0,0,-1,0,0,0,1,0,0,-1,0,0,0,1,0,0,-1],[0,2,4,0,4,3,0,3,5,0,5,2,1,2,5,1,5,3,1,3,4,1,4,2],a,b);this.type="OctahedronBufferGeometry";this.parameters=
+{radius:a,detail:b}}function Pc(a,b){G.call(this);this.type="IcosahedronGeometry";this.parameters={radius:a,detail:b};this.fromBufferGeometry(new Zb(a,b));this.mergeVertices()}function Zb(a,b){var c=(1+Math.sqrt(5))/2;Aa.call(this,[-1,c,0,1,c,0,-1,-c,0,1,-c,0,0,-1,c,0,1,c,0,-1,-c,0,1,-c,c,0,-1,c,0,1,-c,0,-1,-c,0,1],[0,11,5,0,5,1,0,1,7,0,7,10,0,10,11,1,5,9,5,11,4,11,10,2,10,7,6,7,1,8,3,9,4,3,4,2,3,2,6,3,6,8,3,8,9,4,9,5,2,4,11,6,2,10,8,6,7,9,8,1],a,b);this.type="IcosahedronBufferGeometry";this.parameters=
+{radius:a,detail:b}}function Qc(a,b){G.call(this);this.type="DodecahedronGeometry";this.parameters={radius:a,detail:b};this.fromBufferGeometry(new $b(a,b));this.mergeVertices()}function $b(a,b){var c=(1+Math.sqrt(5))/2,d=1/c;Aa.call(this,[-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-d,-c,0,-d,c,0,d,-c,0,d,c,-d,-c,0,-d,c,0,d,-c,0,d,c,0,-c,0,-d,c,0,-d,-c,0,d,c,0,d],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,
+6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],a,b);this.type="DodecahedronBufferGeometry";this.parameters={radius:a,detail:b}}function Rc(a,b,c,d,e,f){G.call(this);this.type="TubeGeometry";this.parameters={path:a,tubularSegments:b,radius:c,radialSegments:d,closed:e};void 0!==f&&console.warn("THREE.TubeGeometry: taper has been removed.");a=new wb(a,b,c,d,e);this.tangents=a.tangents;this.normals=
+a.normals;this.binormals=a.binormals;this.fromBufferGeometry(a);this.mergeVertices()}function wb(a,b,c,d,e){function f(e){p=a.getPointAt(e/b,p);var f=g.normals[e];e=g.binormals[e];for(l=0;l<=d;l++){var m=l/d*Math.PI*2,q=Math.sin(m);m=-Math.cos(m);k.x=m*f.x+q*e.x;k.y=m*f.y+q*e.y;k.z=m*f.z+q*e.z;k.normalize();r.push(k.x,k.y,k.z);h.x=p.x+c*k.x;h.y=p.y+c*k.y;h.z=p.z+c*k.z;t.push(h.x,h.y,h.z)}}C.call(this);this.type="TubeBufferGeometry";this.parameters={path:a,tubularSegments:b,radius:c,radialSegments:d,
+closed:e};b=b||64;c=c||1;d=d||8;e=e||!1;var g=a.computeFrenetFrames(b,e);this.tangents=g.tangents;this.normals=g.normals;this.binormals=g.binormals;var h=new n,k=new n,m=new B,p=new n,q,l,t=[],r=[],u=[],w=[];for(q=0;q=b;e-=d)f=vf(e,a[e],a[e+1],f);f&&xb(f,f.next)&&(Uc(f),f=f.next);return f}function Vc(a,b){if(!a)return a;b||(b=a);do{var c=!1;if(a.steiner||!xb(a,a.next)&&0!==qa(a.prev,a,a.next))a=a.next;else{Uc(a);a=b=a.prev;if(a===a.next)break;c=!0}}while(c||a!==b);return b}function Wc(a,b,c,d,e,f,g){if(a){if(!g&&f){var h=a,k=h;do null===k.z&&(k.z=ke(k.x,k.y,d,e,f)),k.prevZ=k.prev,k=k.nextZ=
+k.next;while(k!==h);k.prevZ.nextZ=null;k.prevZ=null;h=k;var m,p,q,l,t=1;do{k=h;var r=h=null;for(p=0;k;){p++;var n=k;for(m=q=0;mq.x?p.x>t.x?p.x:t.x:q.x>t.x?q.x:t.x,D=p.y>q.y?p.y>t.y?p.y:t.y:q.y>
+t.y?q.y:t.y;m=ke(p.x=m;){if(w!==r.prev&&w!==r.next&&Dd(p.x,p.y,q.x,q.y,t.x,t.y,w.x,w.y)&&0<=qa(w.prev,w,w.next)){r=!1;break a}w=w.prevZ}r=!0}}else a:if(r=a,p=r.prev,q=r,t=r.next,0<=qa(p,q,t))r=!1;else{for(m=r.next.next;m!==r.prev;){if(Dd(p.x,p.y,
+q.x,q.y,t.x,t.y,m.x,m.y)&&0<=qa(m.prev,m,m.next)){r=!1;break a}m=m.next}r=!0}if(r)b.push(k.i/c),b.push(a.i/c),b.push(n.i/c),Uc(a),h=a=n.next;else if(a=n,a===h){if(!g)Wc(Vc(a),b,c,d,e,f,1);else if(1===g){g=b;h=c;k=a;do n=k.prev,r=k.next.next,!xb(n,r)&&wf(n,k,k.next,r)&&Xc(n,r)&&Xc(r,n)&&(g.push(n.i/h),g.push(k.i/h),g.push(r.i/h),Uc(k),Uc(k.next),k=a=r),k=k.next;while(k!==a);a=k;Wc(a,b,c,d,e,f,2)}else if(2===g)a:{g=a;do{for(h=g.next.next;h!==g.prev;){if(k=g.i!==h.i){k=g;n=h;if(r=k.next.i!==n.i&&k.prev.i!==
+n.i){b:{r=k;do{if(r.i!==k.i&&r.next.i!==k.i&&r.i!==n.i&&r.next.i!==n.i&&wf(r,r.next,k,n)){r=!0;break b}r=r.next}while(r!==k);r=!1}r=!r}if(r=r&&Xc(k,n)&&Xc(n,k)){r=k;p=!1;q=(k.x+n.x)/2;n=(k.y+n.y)/2;do r.y>n!==r.next.y>n&&r.next.y!==r.y&&q<(r.next.x-r.x)*(n-r.y)/(r.next.y-r.y)+r.x&&(p=!p),r=r.next;while(r!==k);r=p}k=r}if(k){a=xf(g,h);g=Vc(g,g.next);a=Vc(a,a.next);Wc(g,b,c,d,e,f);Wc(a,b,c,d,e,f);break a}h=h.next}g=g.next}while(g!==a)}break}}}}function ah(a,b){return a.x-b.x}function bh(a,b){var c=b,
+d=a.x,e=a.y,f=-Infinity;do{if(e<=c.y&&e>=c.next.y&&c.next.y!==c.y){var g=c.x+(e-c.y)*(c.next.x-c.x)/(c.next.y-c.y);if(g<=d&&g>f){f=g;if(g===d){if(e===c.y)return c;if(e===c.next.y)return c.next}var h=c.x=c.x&&c.x>=g&&d!==c.x&&Dd(eh.x)&&Xc(c,a)&&(h=c,m=p)}c=c.next}return h}function ke(a,
+b,c,d,e){a=32767*(a-c)*e;b=32767*(b-d)*e;a=(a|a<<8)&16711935;a=(a|a<<4)&252645135;a=(a|a<<2)&858993459;b=(b|b<<8)&16711935;b=(b|b<<4)&252645135;b=(b|b<<2)&858993459;return(a|a<<1)&1431655765|((b|b<<1)&1431655765)<<1}function ch(a){var b=a,c=a;do b.xqa(a.prev,a,a.next)?0<=qa(a,b,a.next)&&0<=qa(a,a.prev,b):0>qa(a,b,a.prev)||0>qa(a,a.next,b)}function xf(a,b){var c=new le(a.i,a.x,a.y),d=new le(b.i,b.x,b.y),e=a.next,f=b.prev;a.next=b;b.prev=a;c.next=e;e.prev=c;d.next=c;c.prev=d;f.next=d;d.prev=f;return d}function vf(a,b,c,d){a=new le(a,b,c);d?(a.next=d.next,a.prev=d,d.next.prev=a,d.next=a):
+(a.prev=a,a.next=a);return a}function Uc(a){a.next.prev=a.prev;a.prev.next=a.next;a.prevZ&&(a.prevZ.nextZ=a.nextZ);a.nextZ&&(a.nextZ.prevZ=a.prevZ)}function le(a,b,c){this.i=a;this.x=b;this.y=c;this.nextZ=this.prevZ=this.z=this.next=this.prev=null;this.steiner=!1}function yf(a){var b=a.length;2Number.EPSILON){var k=Math.sqrt(h),m=Math.sqrt(f*f+g*g);h=b.x-e/k;b=b.y+d/k;g=((c.x-g/m-h)*g-(c.y+f/m-b)*f)/(d*g-e*f);f=h+d*g-a.x;d=b+e*g-a.y;e=f*f+d*d;if(2>=e)return new B(f,d);e=Math.sqrt(e/2)}else a=!1,d>Number.EPSILON?
+f>Number.EPSILON&&(a=!0):d<-Number.EPSILON?f<-Number.EPSILON&&(a=!0):Math.sign(e)===Math.sign(g)&&(a=!0),a?(f=-e,e=Math.sqrt(h)):(f=d,d=e,e=Math.sqrt(h/2));return new B(f/e,d/e)}function h(a,b){for(N=a.length;0<=--N;){var c=N;var f=N-1;0>f&&(f=a.length-1);var g,h=x+2*A;for(g=0;gp;p++){var l=m[f[p]];var n=m[f[(p+1)%3]];d[0]=Math.min(l,n);d[1]=Math.max(l,n);l=d[0]+","+d[1];void 0===e[l]?e[l]={index1:d[0],index2:d[1],face1:h,face2:void 0}:e[l].face2=h}for(l in e)if(d=e[l],void 0===d.face2||g[d.face1].normal.dot(g[d.face2].normal)<=b)f=a[d.index1],c.push(f.x,f.y,f.z),f=a[d.index2],c.push(f.x,f.y,f.z);this.addAttribute("position",new F(c,3))}function Cb(a,b,c,d,e,f,g,h){G.call(this);this.type="CylinderGeometry";this.parameters={radiusTop:a,
+radiusBottom:b,height:c,radialSegments:d,heightSegments:e,openEnded:f,thetaStart:g,thetaLength:h};this.fromBufferGeometry(new bb(a,b,c,d,e,f,g,h));this.mergeVertices()}function bb(a,b,c,d,e,f,g,h){function k(c){var e,f=new B,k=new n,q=0,u=!0===c?a:b,x=!0===c?1:-1;var C=r;for(e=1;e<=d;e++)l.push(0,w*x,0),v.push(0,x,0),t.push(.5,.5),r++;var E=r;for(e=0;e<=d;e++){var F=e/d*h+g,H=Math.cos(F);F=Math.sin(F);k.x=u*F;k.y=w*x;k.z=u*H;l.push(k.x,k.y,k.z);v.push(0,x,0);f.x=.5*H+.5;f.y=.5*F*x+.5;t.push(f.x,f.y);
+r++}for(e=0;ethis.duration&&this.resetDuration()}function eh(a){switch(a.toLowerCase()){case "scalar":case "double":case "float":case "number":case "integer":return ic;case "vector":case "vector2":case "vector3":case "vector4":return jc;case "color":return Hd;case "quaternion":return fd;case "bool":case "boolean":return Gd;case "string":return Jd}throw Error("THREE.KeyframeTrack: Unsupported typeName: "+
+a);}function fh(a){if(void 0===a.type)throw Error("THREE.KeyframeTrack: track type undefined, can not parse");var b=eh(a.type);if(void 0===a.times){var c=[],d=[];sa.flattenJSON(a.keys,c,d,"value");a.times=c;a.values=d}return void 0!==b.parse?b.parse(a):new b(a.name,a.times,a.values,a.interpolation)}function me(a,b,c){var d=this,e=!1,f=0,g=0,h=void 0;this.onStart=void 0;this.onLoad=a;this.onProgress=b;this.onError=c;this.itemStart=function(a){g++;if(!1===e&&void 0!==d.onStart)d.onStart(a,f,g);e=!0};
+this.itemEnd=function(a){f++;if(void 0!==d.onProgress)d.onProgress(a,f,g);if(f===g&&(e=!1,void 0!==d.onLoad))d.onLoad()};this.itemError=function(a){if(void 0!==d.onError)d.onError(a)};this.resolveURL=function(a){return h?h(a):a};this.setURLModifier=function(a){h=a;return this}}function Ka(a){this.manager=void 0!==a?a:Ba}function Cf(a){this.manager=void 0!==a?a:Ba}function Df(a){this.manager=void 0!==a?a:Ba;this._parser=null}function ne(a){this.manager=void 0!==a?a:Ba;this._parser=null}function gd(a){this.manager=
+void 0!==a?a:Ba}function oe(a){this.manager=void 0!==a?a:Ba}function Kd(a){this.manager=void 0!==a?a:Ba}function L(){this.type="Curve";this.arcLengthDivisions=200}function Ea(a,b,c,d,e,f,g,h){L.call(this);this.type="EllipseCurve";this.aX=a||0;this.aY=b||0;this.xRadius=c||1;this.yRadius=d||1;this.aStartAngle=e||0;this.aEndAngle=f||2*Math.PI;this.aClockwise=g||!1;this.aRotation=h||0}function kc(a,b,c,d,e,f){Ea.call(this,a,b,c,c,d,e,f);this.type="ArcCurve"}function pe(){var a=0,b=0,c=0,d=0;return{initCatmullRom:function(e,
+f,g,h,k){e=k*(g-e);h=k*(h-f);a=f;b=e;c=-3*f+3*g-2*e-h;d=2*f-2*g+e+h},initNonuniformCatmullRom:function(e,f,g,h,k,m,p){e=((f-e)/k-(g-e)/(k+m)+(g-f)/m)*m;h=((g-f)/m-(h-f)/(m+p)+(h-g)/p)*m;a=f;b=e;c=-3*f+3*g-2*e-h;d=2*f-2*g+e+h},calc:function(e){var f=e*e;return a+b*e+c*f+d*f*e}}}function oa(a,b,c,d){L.call(this);this.type="CatmullRomCurve3";this.points=a||[];this.closed=b||!1;this.curveType=c||"centripetal";this.tension=d||.5}function Ef(a,b,c,d,e){b=.5*(d-b);e=.5*(e-c);var f=a*a;return(2*c-2*d+b+e)*
+a*f+(-3*c+3*d-2*b-e)*f+b*a+c}function hd(a,b,c,d){var e=1-a;return e*e*b+2*(1-a)*a*c+a*a*d}function id(a,b,c,d,e){var f=1-a,g=1-a;return f*f*f*b+3*g*g*a*c+3*(1-a)*a*a*d+a*a*a*e}function La(a,b,c,d){L.call(this);this.type="CubicBezierCurve";this.v0=a||new B;this.v1=b||new B;this.v2=c||new B;this.v3=d||new B}function Xa(a,b,c,d){L.call(this);this.type="CubicBezierCurve3";this.v0=a||new n;this.v1=b||new n;this.v2=c||new n;this.v3=d||new n}function ja(a,b){L.call(this);this.type="LineCurve";this.v1=a||
+new B;this.v2=b||new B}function Ma(a,b){L.call(this);this.type="LineCurve3";this.v1=a||new n;this.v2=b||new n}function Na(a,b,c){L.call(this);this.type="QuadraticBezierCurve";this.v0=a||new B;this.v1=b||new B;this.v2=c||new B}function Ya(a,b,c){L.call(this);this.type="QuadraticBezierCurve3";this.v0=a||new n;this.v1=b||new n;this.v2=c||new n}function Oa(a){L.call(this);this.type="SplineCurve";this.points=a||[]}function cb(){L.call(this);this.type="CurvePath";this.curves=[];this.autoClose=!1}function Pa(a){cb.call(this);
+this.type="Path";this.currentPoint=new B;a&&this.setFromPoints(a)}function kb(a){Pa.call(this,a);this.uuid=H.generateUUID();this.type="Shape";this.holes=[]}function ia(a,b){E.call(this);this.type="Light";this.color=new K(a);this.intensity=void 0!==b?b:1;this.receiveShadow=void 0}function Ld(a,b,c){ia.call(this,a,c);this.type="HemisphereLight";this.castShadow=void 0;this.position.copy(E.DefaultUp);this.updateMatrix();this.groundColor=new K(b)}function Kb(a){this.camera=a;this.bias=0;this.radius=1;
+this.mapSize=new B(512,512);this.map=null;this.matrix=new J}function Md(){Kb.call(this,new S(50,1,.5,500))}function Nd(a,b,c,d,e,f){ia.call(this,a,b);this.type="SpotLight";this.position.copy(E.DefaultUp);this.updateMatrix();this.target=new E;Object.defineProperty(this,"power",{get:function(){return this.intensity*Math.PI},set:function(a){this.intensity=a/Math.PI}});this.distance=void 0!==c?c:0;this.angle=void 0!==d?d:Math.PI/3;this.penumbra=void 0!==e?e:0;this.decay=void 0!==f?f:1;this.shadow=new Md}
+function Od(a,b,c,d){ia.call(this,a,b);this.type="PointLight";Object.defineProperty(this,"power",{get:function(){return 4*this.intensity*Math.PI},set:function(a){this.intensity=a/(4*Math.PI)}});this.distance=void 0!==c?c:0;this.decay=void 0!==d?d:1;this.shadow=new Kb(new S(90,1,.5,500))}function jd(a,b,c,d,e,f){Ua.call(this);this.type="OrthographicCamera";this.zoom=1;this.view=null;this.left=void 0!==a?a:-1;this.right=void 0!==b?b:1;this.top=void 0!==c?c:1;this.bottom=void 0!==d?d:-1;this.near=void 0!==
+e?e:.1;this.far=void 0!==f?f:2E3;this.updateProjectionMatrix()}function Pd(){Kb.call(this,new jd(-5,5,5,-5,.5,500))}function Qd(a,b){ia.call(this,a,b);this.type="DirectionalLight";this.position.copy(E.DefaultUp);this.updateMatrix();this.target=new E;this.shadow=new Pd}function Rd(a,b){ia.call(this,a,b);this.type="AmbientLight";this.castShadow=void 0}function Sd(a,b,c,d){ia.call(this,a,b);this.type="RectAreaLight";this.width=void 0!==c?c:10;this.height=void 0!==d?d:10}function Td(a){this.manager=void 0!==
+a?a:Ba;this.textures={}}function qe(a){this.manager=void 0!==a?a:Ba}function re(a){this.manager=void 0!==a?a:Ba;this.resourcePath=""}function se(a){"undefined"===typeof createImageBitmap&&console.warn("THREE.ImageBitmapLoader: createImageBitmap() not supported.");"undefined"===typeof fetch&&console.warn("THREE.ImageBitmapLoader: fetch() not supported.");this.manager=void 0!==a?a:Ba;this.options=void 0}function te(){this.type="ShapePath";this.color=new K;this.subPaths=[];this.currentPath=null}function ue(a){this.type=
+"Font";this.data=a}function Ff(a){this.manager=void 0!==a?a:Ba}function kd(){}function ve(a){this.manager=void 0!==a?a:Ba}function Gf(){this.type="StereoCamera";this.aspect=1;this.eyeSep=.064;this.cameraL=new S;this.cameraL.layers.enable(1);this.cameraL.matrixAutoUpdate=!1;this.cameraR=new S;this.cameraR.layers.enable(2);this.cameraR.matrixAutoUpdate=!1}function ld(a,b,c,d){E.call(this);this.type="CubeCamera";var e=new S(90,1,a,b);e.up.set(0,-1,0);e.lookAt(new n(1,0,0));this.add(e);var f=new S(90,
+1,a,b);f.up.set(0,-1,0);f.lookAt(new n(-1,0,0));this.add(f);var g=new S(90,1,a,b);g.up.set(0,0,1);g.lookAt(new n(0,1,0));this.add(g);var h=new S(90,1,a,b);h.up.set(0,0,-1);h.lookAt(new n(0,-1,0));this.add(h);var k=new S(90,1,a,b);k.up.set(0,-1,0);k.lookAt(new n(0,0,1));this.add(k);var m=new S(90,1,a,b);m.up.set(0,-1,0);m.lookAt(new n(0,0,-1));this.add(m);d=d||{format:1022,magFilter:1006,minFilter:1006};this.renderTarget=new mb(c,c,d);this.renderTarget.texture.name="CubeCamera";this.update=function(a,
+b){null===this.parent&&this.updateMatrixWorld();var c=a.getRenderTarget(),d=this.renderTarget,p=d.texture.generateMipmaps;d.texture.generateMipmaps=!1;a.setRenderTarget(d,0);a.render(b,e);a.setRenderTarget(d,1);a.render(b,f);a.setRenderTarget(d,2);a.render(b,g);a.setRenderTarget(d,3);a.render(b,h);a.setRenderTarget(d,4);a.render(b,k);d.texture.generateMipmaps=p;a.setRenderTarget(d,5);a.render(b,m);a.setRenderTarget(c)};this.clear=function(a,b,c,d){for(var e=a.getRenderTarget(),f=this.renderTarget,
+g=0;6>g;g++)a.setRenderTarget(f,g),a.clear(b,c,d);a.setRenderTarget(e)}}function we(a){this.autoStart=void 0!==a?a:!0;this.elapsedTime=this.oldTime=this.startTime=0;this.running=!1}function xe(){E.call(this);this.type="AudioListener";this.context=ye.getContext();this.gain=this.context.createGain();this.gain.connect(this.context.destination);this.filter=null;this.timeDelta=0}function lc(a){E.call(this);this.type="Audio";this.listener=a;this.context=a.context;this.gain=this.context.createGain();this.gain.connect(a.getInput());
+this.autoplay=!1;this.buffer=null;this.detune=0;this.loop=!1;this.offset=this.startTime=0;this.playbackRate=1;this.isPlaying=!1;this.hasPlaybackControl=!0;this.sourceType="empty";this.filters=[]}function ze(a){lc.call(this,a);this.panner=this.context.createPanner();this.panner.connect(this.gain)}function Ae(a,b){this.analyser=a.context.createAnalyser();this.analyser.fftSize=void 0!==b?b:2048;this.data=new Uint8Array(this.analyser.frequencyBinCount);a.getOutput().connect(this.analyser)}function Be(a,
+b,c){this.binding=a;this.valueSize=c;a=Float64Array;switch(b){case "quaternion":b=this._slerp;break;case "string":case "bool":a=Array;b=this._select;break;default:b=this._lerp}this.buffer=new a(4*c);this._mixBufferRegion=b;this.referenceCount=this.useCount=this.cumulativeWeight=0}function Hf(a,b,c){c=c||ma.parseTrackName(b);this._targetGroup=a;this._bindings=a.subscribe_(b,c)}function ma(a,b,c){this.path=b;this.parsedPath=c||ma.parseTrackName(b);this.node=ma.findNode(a,this.parsedPath.nodeName)||
+a;this.rootNode=a}function If(){this.uuid=H.generateUUID();this._objects=Array.prototype.slice.call(arguments);this.nCachedObjects_=0;var a={};this._indicesByUUID=a;for(var b=0,c=arguments.length;b!==c;++b)a[arguments[b].uuid]=b;this._paths=[];this._parsedPaths=[];this._bindings=[];this._bindingsIndicesByPath={};var d=this;this.stats={objects:{get total(){return d._objects.length},get inUse(){return this.total-d.nCachedObjects_}},get bindingsPerObject(){return d._bindings.length}}}function Jf(a,b,
+c){this._mixer=a;this._clip=b;this._localRoot=c||null;a=b.tracks;b=a.length;c=Array(b);for(var d={endingStart:2400,endingEnd:2400},e=0;e!==b;++e){var f=a[e].createInterpolant(null);c[e]=f;f.settings=d}this._interpolantSettings=d;this._interpolants=c;this._propertyBindings=Array(b);this._weightInterpolant=this._timeScaleInterpolant=this._byClipCacheIndex=this._cacheIndex=null;this.loop=2201;this._loopCount=-1;this._startTime=null;this.time=0;this._effectiveWeight=this.weight=this._effectiveTimeScale=
+this.timeScale=1;this.repetitions=Infinity;this.paused=!1;this.enabled=!0;this.clampWhenFinished=!1;this.zeroSlopeAtEnd=this.zeroSlopeAtStart=!0}function Ce(a){this._root=a;this._initMemoryManager();this.time=this._accuIndex=0;this.timeScale=1}function Ud(a,b){"string"===typeof a&&(console.warn("THREE.Uniform: Type parameter is no longer needed."),a=b);this.value=a}function De(){C.call(this);this.type="InstancedBufferGeometry";this.maxInstancedCount=void 0}function Ee(a,b,c){ub.call(this,a,b);this.meshPerAttribute=
+c||1}function Fe(a,b,c,d){"number"===typeof c&&(d=c,c=!1,console.error("THREE.InstancedBufferAttribute: The constructor now expects normalized as the third argument."));P.call(this,a,b,c);this.meshPerAttribute=d||1}function Kf(a,b,c,d){this.ray=new tb(a,b);this.near=c||0;this.far=d||Infinity;this.params={Mesh:{},Line:{},LOD:{},Points:{threshold:1},Sprite:{}};Object.defineProperties(this.params,{PointCloud:{get:function(){console.warn("THREE.Raycaster: params.PointCloud has been renamed to params.Points.");
+return this.Points}}})}function Lf(a,b){return a.distance-b.distance}function Ge(a,b,c,d){if(!1!==a.visible&&(a.raycast(b,c),!0===d)){a=a.children;d=0;for(var e=a.length;dc;c++,d++){var e=c/32*Math.PI*2,f=d/32*Math.PI*2;b.push(Math.cos(e),Math.sin(e),1,Math.cos(f),Math.sin(f),1)}a.addAttribute("position",new F(b,3));b=new R({fog:!1});this.cone=new W(a,b);this.add(this.cone);this.update()}function Of(a){var b=[];a&&a.isBone&&b.push(a);for(var c=
+0;ca?-1:0b;b++)a[b]=(16>b?"0":"")+b.toString(16);return function(){var b=4294967295*Math.random()|0,d=4294967295*Math.random()|0,e=4294967295*Math.random()|0,f=4294967295*Math.random()|0;return(a[b&255]+a[b>>8&255]+a[b>>16&255]+a[b>>24&255]+"-"+a[d&255]+a[d>>8&255]+"-"+a[d>>
+16&15|64]+a[d>>24&255]+"-"+a[e&63|128]+a[e>>8&255]+"-"+a[e>>16&255]+a[e>>24&255]+a[f&255]+a[f>>8&255]+a[f>>16&255]+a[f>>24&255]).toUpperCase()}}(),clamp:function(a,b,c){return Math.max(b,Math.min(c,a))},euclideanModulo:function(a,b){return(a%b+b)%b},mapLinear:function(a,b,c,d,e){return d+(a-b)*(e-d)/(c-b)},lerp:function(a,b,c){return(1-c)*a+c*b},smoothstep:function(a,b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*(3-2*a)},smootherstep:function(a,b,c){if(a<=b)return 0;if(a>=c)return 1;
+a=(a-b)/(c-b);return a*a*a*(a*(6*a-15)+10)},randInt:function(a,b){return a+Math.floor(Math.random()*(b-a+1))},randFloat:function(a,b){return a+Math.random()*(b-a)},randFloatSpread:function(a){return a*(.5-Math.random())},degToRad:function(a){return a*H.DEG2RAD},radToDeg:function(a){return a*H.RAD2DEG},isPowerOfTwo:function(a){return 0===(a&a-1)&&0!==a},ceilPowerOfTwo:function(a){return Math.pow(2,Math.ceil(Math.log(a)/Math.LN2))},floorPowerOfTwo:function(a){return Math.pow(2,Math.floor(Math.log(a)/
+Math.LN2))}};Object.defineProperties(B.prototype,{width:{get:function(){return this.x},set:function(a){this.x=a}},height:{get:function(){return this.y},set:function(a){this.y=a}}});Object.assign(B.prototype,{isVector2:!0,set:function(a,b){this.x=a;this.y=b;return this},setScalar:function(a){this.y=this.x=a;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;default:throw Error("index is out of range: "+
+a);}return this},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;default:throw Error("index is out of range: "+a);}},clone:function(){return new this.constructor(this.x,this.y)},copy:function(a){this.x=a.x;this.y=a.y;return this},add:function(a,b){if(void 0!==b)return console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;return this},addScalar:function(a){this.x+=a;this.y+=a;return this},
+addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;return this},addScaledVector:function(a,b){this.x+=a.x*b;this.y+=a.y*b;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;return this},subScalar:function(a){this.x-=a;this.y-=a;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;return this},multiply:function(a){this.x*=a.x;this.y*=
+a.y;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;return this},divide:function(a){this.x/=a.x;this.y/=a.y;return this},divideScalar:function(a){return this.multiplyScalar(1/a)},applyMatrix3:function(a){var b=this.x,c=this.y;a=a.elements;this.x=a[0]*b+a[3]*c+a[6];this.y=a[1]*b+a[4]*c+a[7];return this},min:function(a){this.x=Math.min(this.x,a.x);this.y=Math.min(this.y,a.y);return this},max:function(a){this.x=Math.max(this.x,a.x);this.y=Math.max(this.y,a.y);return this},clamp:function(a,
+b){this.x=Math.max(a.x,Math.min(b.x,this.x));this.y=Math.max(a.y,Math.min(b.y,this.y));return this},clampScalar:function(){var a=new B,b=new B;return function(c,d){a.set(c,c);b.set(d,d);return this.clamp(a,b)}}(),clampLength:function(a,b){var c=this.length();return this.divideScalar(c||1).multiplyScalar(Math.max(a,Math.min(b,c)))},floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this},round:function(){this.x=
+Math.round(this.x);this.y=Math.round(this.y);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);return this},negate:function(){this.x=-this.x;this.y=-this.y;return this},dot:function(a){return this.x*a.x+this.y*a.y},cross:function(a){return this.x*a.y-this.y*a.x},lengthSq:function(){return this.x*this.x+this.y*this.y},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},manhattanLength:function(){return Math.abs(this.x)+
+Math.abs(this.y)},normalize:function(){return this.divideScalar(this.length()||1)},angle:function(){var a=Math.atan2(this.y,this.x);0>a&&(a+=2*Math.PI);return a},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x;a=this.y-a.y;return b*b+a*a},manhattanDistanceTo:function(a){return Math.abs(this.x-a.x)+Math.abs(this.y-a.y)},setLength:function(a){return this.normalize().multiplyScalar(a)},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=
+(a.y-this.y)*b;return this},lerpVectors:function(a,b,c){return this.subVectors(b,a).multiplyScalar(c).add(a)},equals:function(a){return a.x===this.x&&a.y===this.y},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;return a},fromBufferAttribute:function(a,b,c){void 0!==c&&console.warn("THREE.Vector2: offset has been removed from .fromBufferAttribute().");this.x=a.getX(b);this.y=a.getY(b);
+return this},rotateAround:function(a,b){var c=Math.cos(b);b=Math.sin(b);var d=this.x-a.x,e=this.y-a.y;this.x=d*c-e*b+a.x;this.y=d*b+e*c+a.y;return this}});Object.assign(J.prototype,{isMatrix4:!0,set:function(a,b,c,d,e,f,g,h,k,m,l,q,n,t,r,u){var p=this.elements;p[0]=a;p[4]=b;p[8]=c;p[12]=d;p[1]=e;p[5]=f;p[9]=g;p[13]=h;p[2]=k;p[6]=m;p[10]=l;p[14]=q;p[3]=n;p[7]=t;p[11]=r;p[15]=u;return this},identity:function(){this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return this},clone:function(){return(new J).fromArray(this.elements)},
+copy:function(a){var b=this.elements;a=a.elements;b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return this},copyPosition:function(a){var b=this.elements;a=a.elements;b[12]=a[12];b[13]=a[13];b[14]=a[14];return this},extractBasis:function(a,b,c){a.setFromMatrixColumn(this,0);b.setFromMatrixColumn(this,1);c.setFromMatrixColumn(this,2);return this},makeBasis:function(a,b,c){this.set(a.x,
+b.x,c.x,0,a.y,b.y,c.y,0,a.z,b.z,c.z,0,0,0,0,1);return this},extractRotation:function(){var a=new n;return function(b){var c=this.elements,d=b.elements,e=1/a.setFromMatrixColumn(b,0).length(),f=1/a.setFromMatrixColumn(b,1).length();b=1/a.setFromMatrixColumn(b,2).length();c[0]=d[0]*e;c[1]=d[1]*e;c[2]=d[2]*e;c[3]=0;c[4]=d[4]*f;c[5]=d[5]*f;c[6]=d[6]*f;c[7]=0;c[8]=d[8]*b;c[9]=d[9]*b;c[10]=d[10]*b;c[11]=0;c[12]=0;c[13]=0;c[14]=0;c[15]=1;return this}}(),makeRotationFromEuler:function(a){a&&a.isEuler||console.error("THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.");
+var b=this.elements,c=a.x,d=a.y,e=a.z,f=Math.cos(c);c=Math.sin(c);var g=Math.cos(d);d=Math.sin(d);var h=Math.cos(e);e=Math.sin(e);if("XYZ"===a.order){a=f*h;var k=f*e,m=c*h,p=c*e;b[0]=g*h;b[4]=-g*e;b[8]=d;b[1]=k+m*d;b[5]=a-p*d;b[9]=-c*g;b[2]=p-a*d;b[6]=m+k*d;b[10]=f*g}else"YXZ"===a.order?(a=g*h,k=g*e,m=d*h,p=d*e,b[0]=a+p*c,b[4]=m*c-k,b[8]=f*d,b[1]=f*e,b[5]=f*h,b[9]=-c,b[2]=k*c-m,b[6]=p+a*c,b[10]=f*g):"ZXY"===a.order?(a=g*h,k=g*e,m=d*h,p=d*e,b[0]=a-p*c,b[4]=-f*e,b[8]=m+k*c,b[1]=k+m*c,b[5]=f*h,b[9]=
+p-a*c,b[2]=-f*d,b[6]=c,b[10]=f*g):"ZYX"===a.order?(a=f*h,k=f*e,m=c*h,p=c*e,b[0]=g*h,b[4]=m*d-k,b[8]=a*d+p,b[1]=g*e,b[5]=p*d+a,b[9]=k*d-m,b[2]=-d,b[6]=c*g,b[10]=f*g):"YZX"===a.order?(a=f*g,k=f*d,m=c*g,p=c*d,b[0]=g*h,b[4]=p-a*e,b[8]=m*e+k,b[1]=e,b[5]=f*h,b[9]=-c*h,b[2]=-d*h,b[6]=k*e+m,b[10]=a-p*e):"XZY"===a.order&&(a=f*g,k=f*d,m=c*g,p=c*d,b[0]=g*h,b[4]=-e,b[8]=d*h,b[1]=a*e+p,b[5]=f*h,b[9]=k*e-m,b[2]=m*e-k,b[6]=c*h,b[10]=p*e+a);b[3]=0;b[7]=0;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return this},makeRotationFromQuaternion:function(){var a=
+new n(0,0,0),b=new n(1,1,1);return function(c){return this.compose(a,c,b)}}(),lookAt:function(){var a=new n,b=new n,c=new n;return function(d,e,f){var g=this.elements;c.subVectors(d,e);0===c.lengthSq()&&(c.z=1);c.normalize();a.crossVectors(f,c);0===a.lengthSq()&&(1===Math.abs(f.z)?c.x+=1E-4:c.z+=1E-4,c.normalize(),a.crossVectors(f,c));a.normalize();b.crossVectors(c,a);g[0]=a.x;g[4]=b.x;g[8]=c.x;g[1]=a.y;g[5]=b.y;g[9]=c.y;g[2]=a.z;g[6]=b.z;g[10]=c.z;return this}}(),multiply:function(a,b){return void 0!==
+b?(console.warn("THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead."),this.multiplyMatrices(a,b)):this.multiplyMatrices(this,a)},premultiply:function(a){return this.multiplyMatrices(a,this)},multiplyMatrices:function(a,b){var c=a.elements,d=b.elements;b=this.elements;a=c[0];var e=c[4],f=c[8],g=c[12],h=c[1],k=c[5],m=c[9],p=c[13],l=c[2],n=c[6],t=c[10],r=c[14],u=c[3],w=c[7],z=c[11];c=c[15];var x=d[0],D=d[4],y=d[8],Q=d[12],B=d[1],A=d[5],C=d[9],F=d[13],E=d[2],
+H=d[6],G=d[10],I=d[14],L=d[3],J=d[7],K=d[11];d=d[15];b[0]=a*x+e*B+f*E+g*L;b[4]=a*D+e*A+f*H+g*J;b[8]=a*y+e*C+f*G+g*K;b[12]=a*Q+e*F+f*I+g*d;b[1]=h*x+k*B+m*E+p*L;b[5]=h*D+k*A+m*H+p*J;b[9]=h*y+k*C+m*G+p*K;b[13]=h*Q+k*F+m*I+p*d;b[2]=l*x+n*B+t*E+r*L;b[6]=l*D+n*A+t*H+r*J;b[10]=l*y+n*C+t*G+r*K;b[14]=l*Q+n*F+t*I+r*d;b[3]=u*x+w*B+z*E+c*L;b[7]=u*D+w*A+z*H+c*J;b[11]=u*y+w*C+z*G+c*K;b[15]=u*Q+w*F+z*I+c*d;return this},multiplyScalar:function(a){var b=this.elements;b[0]*=a;b[4]*=a;b[8]*=a;b[12]*=a;b[1]*=a;b[5]*=
+a;b[9]*=a;b[13]*=a;b[2]*=a;b[6]*=a;b[10]*=a;b[14]*=a;b[3]*=a;b[7]*=a;b[11]*=a;b[15]*=a;return this},applyToBufferAttribute:function(){var a=new n;return function(b){for(var c=0,d=b.count;cthis.determinant()&&(g=-g);c.x=f[12];c.y=f[13];c.z=f[14];b.copy(this);c=1/g;f=1/h;var m=1/k;b.elements[0]*=c;b.elements[1]*=c;b.elements[2]*=c;b.elements[4]*=f;b.elements[5]*=f;b.elements[6]*=f;b.elements[8]*=m;b.elements[9]*=m;b.elements[10]*=m;d.setFromRotationMatrix(b);
+e.x=g;e.y=h;e.z=k;return this}}(),makePerspective:function(a,b,c,d,e,f){void 0===f&&console.warn("THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.");var g=this.elements;g[0]=2*e/(b-a);g[4]=0;g[8]=(b+a)/(b-a);g[12]=0;g[1]=0;g[5]=2*e/(c-d);g[9]=(c+d)/(c-d);g[13]=0;g[2]=0;g[6]=0;g[10]=-(f+e)/(f-e);g[14]=-2*f*e/(f-e);g[3]=0;g[7]=0;g[11]=-1;g[15]=0;return this},makeOrthographic:function(a,b,c,d,e,f){var g=this.elements,h=1/(b-a),k=1/(c-d),m=1/(f-e);g[0]=
+2*h;g[4]=0;g[8]=0;g[12]=-((b+a)*h);g[1]=0;g[5]=2*k;g[9]=0;g[13]=-((c+d)*k);g[2]=0;g[6]=0;g[10]=-2*m;g[14]=-((f+e)*m);g[3]=0;g[7]=0;g[11]=0;g[15]=1;return this},equals:function(a){var b=this.elements;a=a.elements;for(var c=0;16>c;c++)if(b[c]!==a[c])return!1;return!0},fromArray:function(a,b){void 0===b&&(b=0);for(var c=0;16>c;c++)this.elements[c]=a[c+b];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);var c=this.elements;a[b]=c[0];a[b+1]=c[1];a[b+2]=c[2];a[b+3]=c[3];a[b+4]=c[4];
+a[b+5]=c[5];a[b+6]=c[6];a[b+7]=c[7];a[b+8]=c[8];a[b+9]=c[9];a[b+10]=c[10];a[b+11]=c[11];a[b+12]=c[12];a[b+13]=c[13];a[b+14]=c[14];a[b+15]=c[15];return a}});Object.assign(aa,{slerp:function(a,b,c,d){return c.copy(a).slerp(b,d)},slerpFlat:function(a,b,c,d,e,f,g){var h=c[d+0],k=c[d+1],m=c[d+2];c=c[d+3];d=e[f+0];var l=e[f+1],q=e[f+2];e=e[f+3];if(c!==e||h!==d||k!==l||m!==q){f=1-g;var n=h*d+k*l+m*q+c*e,t=0<=n?1:-1,r=1-n*n;r>Number.EPSILON&&(r=Math.sqrt(r),n=Math.atan2(r,n*t),f=Math.sin(f*n)/r,g=Math.sin(g*
+n)/r);t*=g;h=h*f+d*t;k=k*f+l*t;m=m*f+q*t;c=c*f+e*t;f===1-g&&(g=1/Math.sqrt(h*h+k*k+m*m+c*c),h*=g,k*=g,m*=g,c*=g)}a[b]=h;a[b+1]=k;a[b+2]=m;a[b+3]=c}});Object.defineProperties(aa.prototype,{x:{get:function(){return this._x},set:function(a){this._x=a;this.onChangeCallback()}},y:{get:function(){return this._y},set:function(a){this._y=a;this.onChangeCallback()}},z:{get:function(){return this._z},set:function(a){this._z=a;this.onChangeCallback()}},w:{get:function(){return this._w},set:function(a){this._w=
+a;this.onChangeCallback()}}});Object.assign(aa.prototype,{isQuaternion:!0,set:function(a,b,c,d){this._x=a;this._y=b;this._z=c;this._w=d;this.onChangeCallback();return this},clone:function(){return new this.constructor(this._x,this._y,this._z,this._w)},copy:function(a){this._x=a.x;this._y=a.y;this._z=a.z;this._w=a.w;this.onChangeCallback();return this},setFromEuler:function(a,b){if(!a||!a.isEuler)throw Error("THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.");
+var c=a._x,d=a._y,e=a._z;a=a.order;var f=Math.cos,g=Math.sin,h=f(c/2),k=f(d/2);f=f(e/2);c=g(c/2);d=g(d/2);e=g(e/2);"XYZ"===a?(this._x=c*k*f+h*d*e,this._y=h*d*f-c*k*e,this._z=h*k*e+c*d*f,this._w=h*k*f-c*d*e):"YXZ"===a?(this._x=c*k*f+h*d*e,this._y=h*d*f-c*k*e,this._z=h*k*e-c*d*f,this._w=h*k*f+c*d*e):"ZXY"===a?(this._x=c*k*f-h*d*e,this._y=h*d*f+c*k*e,this._z=h*k*e+c*d*f,this._w=h*k*f-c*d*e):"ZYX"===a?(this._x=c*k*f-h*d*e,this._y=h*d*f+c*k*e,this._z=h*k*e-c*d*f,this._w=h*k*f+c*d*e):"YZX"===a?(this._x=
+c*k*f+h*d*e,this._y=h*d*f+c*k*e,this._z=h*k*e-c*d*f,this._w=h*k*f-c*d*e):"XZY"===a&&(this._x=c*k*f-h*d*e,this._y=h*d*f-c*k*e,this._z=h*k*e+c*d*f,this._w=h*k*f+c*d*e);if(!1!==b)this.onChangeCallback();return this},setFromAxisAngle:function(a,b){b/=2;var c=Math.sin(b);this._x=a.x*c;this._y=a.y*c;this._z=a.z*c;this._w=Math.cos(b);this.onChangeCallback();return this},setFromRotationMatrix:function(a){var b=a.elements,c=b[0];a=b[4];var d=b[8],e=b[1],f=b[5],g=b[9],h=b[2],k=b[6];b=b[10];var m=c+f+b;0f&&c>b?(c=2*Math.sqrt(1+c-f-b),this._w=(k-g)/c,this._x=.25*c,this._y=(a+e)/c,this._z=(d+h)/c):f>b?(c=2*Math.sqrt(1+f-c-b),this._w=(d-h)/c,this._x=(a+e)/c,this._y=.25*c,this._z=(g+k)/c):(c=2*Math.sqrt(1+b-c-f),this._w=(e-a)/c,this._x=(d+h)/c,this._y=(g+k)/c,this._z=.25*c);this.onChangeCallback();return this},setFromUnitVectors:function(){var a=new n,b;return function(c,d){void 0===a&&(a=new n);b=c.dot(d)+1;1E-6>b?
+(b=0,Math.abs(c.x)>Math.abs(c.z)?a.set(-c.y,c.x,0):a.set(0,-c.z,c.y)):a.crossVectors(c,d);this._x=a.x;this._y=a.y;this._z=a.z;this._w=b;return this.normalize()}}(),angleTo:function(a){return 2*Math.acos(Math.abs(H.clamp(this.dot(a),-1,1)))},rotateTowards:function(a,b){var c=this.angleTo(a);if(0===c)return this;this.slerp(a,Math.min(1,b/c));return this},inverse:function(){return this.conjugate()},conjugate:function(){this._x*=-1;this._y*=-1;this._z*=-1;this.onChangeCallback();return this},dot:function(a){return this._x*
+a._x+this._y*a._y+this._z*a._z+this._w*a._w},lengthSq:function(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w},length:function(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)},normalize:function(){var a=this.length();0===a?(this._z=this._y=this._x=0,this._w=1):(a=1/a,this._x*=a,this._y*=a,this._z*=a,this._w*=a);this.onChangeCallback();return this},multiply:function(a,b){return void 0!==b?(console.warn("THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),
+this.multiplyQuaternions(a,b)):this.multiplyQuaternions(this,a)},premultiply:function(a){return this.multiplyQuaternions(a,this)},multiplyQuaternions:function(a,b){var c=a._x,d=a._y,e=a._z;a=a._w;var f=b._x,g=b._y,h=b._z;b=b._w;this._x=c*b+a*f+d*h-e*g;this._y=d*b+a*g+e*f-c*h;this._z=e*b+a*h+c*g-d*f;this._w=a*b-c*f-d*g-e*h;this.onChangeCallback();return this},slerp:function(a,b){if(0===b)return this;if(1===b)return this.copy(a);var c=this._x,d=this._y,e=this._z,f=this._w,g=f*a._w+c*a._x+d*a._y+e*a._z;
+0>g?(this._w=-a._w,this._x=-a._x,this._y=-a._y,this._z=-a._z,g=-g):this.copy(a);if(1<=g)return this._w=f,this._x=c,this._y=d,this._z=e,this;a=1-g*g;if(a<=Number.EPSILON)return g=1-b,this._w=g*f+b*this._w,this._x=g*c+b*this._x,this._y=g*d+b*this._y,this._z=g*e+b*this._z,this.normalize();a=Math.sqrt(a);var h=Math.atan2(a,g);g=Math.sin((1-b)*h)/a;b=Math.sin(b*h)/a;this._w=f*g+this._w*b;this._x=c*g+this._x*b;this._y=d*g+this._y*b;this._z=e*g+this._z*b;this.onChangeCallback();return this},equals:function(a){return a._x===
+this._x&&a._y===this._y&&a._z===this._z&&a._w===this._w},fromArray:function(a,b){void 0===b&&(b=0);this._x=a[b];this._y=a[b+1];this._z=a[b+2];this._w=a[b+3];this.onChangeCallback();return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this._x;a[b+1]=this._y;a[b+2]=this._z;a[b+3]=this._w;return a},onChange:function(a){this.onChangeCallback=a;return this},onChangeCallback:function(){}});Object.assign(n.prototype,{isVector3:!0,set:function(a,b,c){this.x=a;this.y=b;this.z=c;return this},
+setScalar:function(a){this.z=this.y=this.x=a;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;default:throw Error("index is out of range: "+a);}return this},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw Error("index is out of range: "+a);}},clone:function(){return new this.constructor(this.x,
+this.y,this.z)},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;return this},addScaledVector:function(a,b){this.x+=a.x*b;this.y+=a.y*b;this.z+=
+a.z*b;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;return this},subScalar:function(a){this.x-=a;this.y-=a;this.z-=a;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;return this},multiply:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),
+this.multiplyVectors(a,b);this.x*=a.x;this.y*=a.y;this.z*=a.z;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;return this},multiplyVectors:function(a,b){this.x=a.x*b.x;this.y=a.y*b.y;this.z=a.z*b.z;return this},applyEuler:function(){var a=new aa;return function(b){b&&b.isEuler||console.error("THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.");return this.applyQuaternion(a.setFromEuler(b))}}(),applyAxisAngle:function(){var a=new aa;return function(b,
+c){return this.applyQuaternion(a.setFromAxisAngle(b,c))}}(),applyMatrix3:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[3]*c+a[6]*d;this.y=a[1]*b+a[4]*c+a[7]*d;this.z=a[2]*b+a[5]*c+a[8]*d;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;var e=1/(a[3]*b+a[7]*c+a[11]*d+a[15]);this.x=(a[0]*b+a[4]*c+a[8]*d+a[12])*e;this.y=(a[1]*b+a[5]*c+a[9]*d+a[13])*e;this.z=(a[2]*b+a[6]*c+a[10]*d+a[14])*e;return this},applyQuaternion:function(a){var b=this.x,
+c=this.y,d=this.z,e=a.x,f=a.y,g=a.z;a=a.w;var h=a*b+f*d-g*c,k=a*c+g*b-e*d,m=a*d+e*c-f*b;b=-e*b-f*c-g*d;this.x=h*a+b*-e+k*-g-m*-f;this.y=k*a+b*-f+m*-e-h*-g;this.z=m*a+b*-g+h*-f-k*-e;return this},project:function(a){return this.applyMatrix4(a.matrixWorldInverse).applyMatrix4(a.projectionMatrix)},unproject:function(){var a=new J;return function(b){return this.applyMatrix4(a.getInverse(b.projectionMatrix)).applyMatrix4(b.matrixWorld)}}(),transformDirection:function(a){var b=this.x,c=this.y,d=this.z;a=
+a.elements;this.x=a[0]*b+a[4]*c+a[8]*d;this.y=a[1]*b+a[5]*c+a[9]*d;this.z=a[2]*b+a[6]*c+a[10]*d;return this.normalize()},divide:function(a){this.x/=a.x;this.y/=a.y;this.z/=a.z;return this},divideScalar:function(a){return this.multiplyScalar(1/a)},min:function(a){this.x=Math.min(this.x,a.x);this.y=Math.min(this.y,a.y);this.z=Math.min(this.z,a.z);return this},max:function(a){this.x=Math.max(this.x,a.x);this.y=Math.max(this.y,a.y);this.z=Math.max(this.z,a.z);return this},clamp:function(a,b){this.x=Math.max(a.x,
+Math.min(b.x,this.x));this.y=Math.max(a.y,Math.min(b.y,this.y));this.z=Math.max(a.z,Math.min(b.z,this.z));return this},clampScalar:function(){var a=new n,b=new n;return function(c,d){a.set(c,c,c);b.set(d,d,d);return this.clamp(a,b)}}(),clampLength:function(a,b){var c=this.length();return this.divideScalar(c||1).multiplyScalar(Math.max(a,Math.min(b,c)))},floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);this.z=Math.floor(this.z);return this},ceil:function(){this.x=Math.ceil(this.x);
+this.y=Math.ceil(this.y);this.z=Math.ceil(this.z);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);this.z=Math.round(this.z);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z);return this},negate:function(){this.x=-this.x;this.y=-this.y;this.z=-this.z;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z},lengthSq:function(){return this.x*
+this.x+this.y*this.y+this.z*this.z},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},manhattanLength:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)},normalize:function(){return this.divideScalar(this.length()||1)},setLength:function(a){return this.normalize().multiplyScalar(a)},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;return this},lerpVectors:function(a,b,c){return this.subVectors(b,a).multiplyScalar(c).add(a)},
+cross:function(a,b){return void 0!==b?(console.warn("THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(a,b)):this.crossVectors(this,a)},crossVectors:function(a,b){var c=a.x,d=a.y;a=a.z;var e=b.x,f=b.y;b=b.z;this.x=d*b-a*f;this.y=a*e-c*b;this.z=c*f-d*e;return this},projectOnVector:function(a){var b=a.dot(this)/a.lengthSq();return this.copy(a).multiplyScalar(b)},projectOnPlane:function(){var a=new n;return function(b){a.copy(this).projectOnVector(b);
+return this.sub(a)}}(),reflect:function(){var a=new n;return function(b){return this.sub(a.copy(b).multiplyScalar(2*this.dot(b)))}}(),angleTo:function(a){a=this.dot(a)/Math.sqrt(this.lengthSq()*a.lengthSq());return Math.acos(H.clamp(a,-1,1))},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,c=this.y-a.y;a=this.z-a.z;return b*b+c*c+a*a},manhattanDistanceTo:function(a){return Math.abs(this.x-a.x)+Math.abs(this.y-a.y)+Math.abs(this.z-
+a.z)},setFromSpherical:function(a){return this.setFromSphericalCoords(a.radius,a.phi,a.theta)},setFromSphericalCoords:function(a,b,c){var d=Math.sin(b)*a;this.x=d*Math.sin(c);this.y=Math.cos(b)*a;this.z=d*Math.cos(c);return this},setFromCylindrical:function(a){return this.setFromCylindricalCoords(a.radius,a.theta,a.y)},setFromCylindricalCoords:function(a,b,c){this.x=a*Math.sin(b);this.y=c;this.z=a*Math.cos(b);return this},setFromMatrixPosition:function(a){a=a.elements;this.x=a[12];this.y=a[13];this.z=
+a[14];return this},setFromMatrixScale:function(a){var b=this.setFromMatrixColumn(a,0).length(),c=this.setFromMatrixColumn(a,1).length();a=this.setFromMatrixColumn(a,2).length();this.x=b;this.y=c;this.z=a;return this},setFromMatrixColumn:function(a,b){return this.fromArray(a.elements,4*b)},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];this.z=a[b+2];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===
+b&&(b=0);a[b]=this.x;a[b+1]=this.y;a[b+2]=this.z;return a},fromBufferAttribute:function(a,b,c){void 0!==c&&console.warn("THREE.Vector3: offset has been removed from .fromBufferAttribute().");this.x=a.getX(b);this.y=a.getY(b);this.z=a.getZ(b);return this}});Object.assign(pa.prototype,{isMatrix3:!0,set:function(a,b,c,d,e,f,g,h,k){var m=this.elements;m[0]=a;m[1]=d;m[2]=g;m[3]=b;m[4]=e;m[5]=h;m[6]=c;m[7]=f;m[8]=k;return this},identity:function(){this.set(1,0,0,0,1,0,0,0,1);return this},clone:function(){return(new this.constructor).fromArray(this.elements)},
+copy:function(a){var b=this.elements;a=a.elements;b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return this},setFromMatrix4:function(a){a=a.elements;this.set(a[0],a[4],a[8],a[1],a[5],a[9],a[2],a[6],a[10]);return this},applyToBufferAttribute:function(){var a=new n;return function(b){for(var c=0,d=b.count;cc;c++)if(b[c]!==a[c])return!1;return!0},fromArray:function(a,b){void 0===b&&(b=0);for(var c=0;9>c;c++)this.elements[c]=a[c+b];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);var c=
+this.elements;a[b]=c[0];a[b+1]=c[1];a[b+2]=c[2];a[b+3]=c[3];a[b+4]=c[4];a[b+5]=c[5];a[b+6]=c[6];a[b+7]=c[7];a[b+8]=c[8];return a}});var tc,lb={getDataURL:function(a){if("undefined"==typeof HTMLCanvasElement)return a.src;if(!(a instanceof HTMLCanvasElement)){void 0===tc&&(tc=document.createElementNS("http://www.w3.org/1999/xhtml","canvas"));tc.width=a.width;tc.height=a.height;var b=tc.getContext("2d");a instanceof ImageData?b.putImageData(a,0,0):b.drawImage(a,0,0,a.width,a.height);a=tc}return 2048<
+a.width||2048a.x||1a.x?0:1;break;case 1002:a.x=1===Math.abs(Math.floor(a.x)%2)?Math.ceil(a.x)-a.x:a.x-Math.floor(a.x)}if(0>a.y||1a.y?0:1;break;case 1002:a.y=1===Math.abs(Math.floor(a.y)%2)?Math.ceil(a.y)-a.y:a.y-Math.floor(a.y)}this.flipY&&(a.y=1-a.y);return a}});Object.defineProperty(V.prototype,
+"needsUpdate",{set:function(a){!0===a&&this.version++}});Object.assign(ba.prototype,{isVector4:!0,set:function(a,b,c,d){this.x=a;this.y=b;this.z=c;this.w=d;return this},setScalar:function(a){this.w=this.z=this.y=this.x=a;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setW:function(a){this.w=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;case 3:this.w=
+b;break;default:throw Error("index is out of range: "+a);}return this},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw Error("index is out of range: "+a);}},clone:function(){return new this.constructor(this.x,this.y,this.z,this.w)},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;this.w=void 0!==a.w?a.w:1;return this},add:function(a,b){if(void 0!==b)return console.warn("THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),
+this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;this.w+=a.w;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;this.w+=a;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;this.w=a.w+b.w;return this},addScaledVector:function(a,b){this.x+=a.x*b;this.y+=a.y*b;this.z+=a.z*b;this.w+=a.w*b;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,
+b);this.x-=a.x;this.y-=a.y;this.z-=a.z;this.w-=a.w;return this},subScalar:function(a){this.x-=a;this.y-=a;this.z-=a;this.w-=a;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;this.w=a.w-b.w;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;this.w*=a;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z,e=this.w;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12]*e;this.y=a[1]*b+a[5]*c+a[9]*d+a[13]*e;this.z=a[2]*b+a[6]*c+a[10]*d+a[14]*
+e;this.w=a[3]*b+a[7]*c+a[11]*d+a[15]*e;return this},divideScalar:function(a){return this.multiplyScalar(1/a)},setAxisAngleFromQuaternion:function(a){this.w=2*Math.acos(a.w);var b=Math.sqrt(1-a.w*a.w);1E-4>b?(this.x=1,this.z=this.y=0):(this.x=a.x/b,this.y=a.y/b,this.z=a.z/b);return this},setAxisAngleFromRotationMatrix:function(a){a=a.elements;var b=a[0];var c=a[4];var d=a[8],e=a[1],f=a[5],g=a[9];var h=a[2];var k=a[6];var m=a[10];if(.01>Math.abs(c-e)&&.01>Math.abs(d-h)&&.01>Math.abs(g-k)){if(.1>Math.abs(c+
+e)&&.1>Math.abs(d+h)&&.1>Math.abs(g+k)&&.1>Math.abs(b+f+m-3))return this.set(1,0,0,0),this;a=Math.PI;b=(b+1)/2;f=(f+1)/2;m=(m+1)/2;c=(c+e)/4;d=(d+h)/4;g=(g+k)/4;b>f&&b>m?.01>b?(k=0,c=h=.707106781):(k=Math.sqrt(b),h=c/k,c=d/k):f>m?.01>f?(k=.707106781,h=0,c=.707106781):(h=Math.sqrt(f),k=c/h,c=g/h):.01>m?(h=k=.707106781,c=0):(c=Math.sqrt(m),k=d/c,h=g/c);this.set(k,h,c,a);return this}a=Math.sqrt((k-g)*(k-g)+(d-h)*(d-h)+(e-c)*(e-c));.001>Math.abs(a)&&(a=1);this.x=(k-g)/a;this.y=(d-h)/a;this.z=(e-c)/a;
+this.w=Math.acos((b+f+m-1)/2);return this},min:function(a){this.x=Math.min(this.x,a.x);this.y=Math.min(this.y,a.y);this.z=Math.min(this.z,a.z);this.w=Math.min(this.w,a.w);return this},max:function(a){this.x=Math.max(this.x,a.x);this.y=Math.max(this.y,a.y);this.z=Math.max(this.z,a.z);this.w=Math.max(this.w,a.w);return this},clamp:function(a,b){this.x=Math.max(a.x,Math.min(b.x,this.x));this.y=Math.max(a.y,Math.min(b.y,this.y));this.z=Math.max(a.z,Math.min(b.z,this.z));this.w=Math.max(a.w,Math.min(b.w,
+this.w));return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new ba,b=new ba);a.set(c,c,c,c);b.set(d,d,d,d);return this.clamp(a,b)}}(),clampLength:function(a,b){var c=this.length();return this.divideScalar(c||1).multiplyScalar(Math.max(a,Math.min(b,c)))},floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);this.z=Math.floor(this.z);this.w=Math.floor(this.w);return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);this.z=Math.ceil(this.z);
+this.w=Math.ceil(this.w);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);this.z=Math.round(this.z);this.w=Math.round(this.w);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z);this.w=0>this.w?Math.ceil(this.w):Math.floor(this.w);return this},negate:function(){this.x=-this.x;this.y=-this.y;this.z=-this.z;this.w=-this.w;return this},
+dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z+this.w*a.w},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w)},manhattanLength:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)+Math.abs(this.w)},normalize:function(){return this.divideScalar(this.length()||1)},setLength:function(a){return this.normalize().multiplyScalar(a)},lerp:function(a,b){this.x+=
+(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;this.w+=(a.w-this.w)*b;return this},lerpVectors:function(a,b,c){return this.subVectors(b,a).multiplyScalar(c).add(a)},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z&&a.w===this.w},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];this.z=a[b+2];this.w=a[b+3];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;a[b+2]=this.z;a[b+3]=this.w;return a},fromBufferAttribute:function(a,
+b,c){void 0!==c&&console.warn("THREE.Vector4: offset has been removed from .fromBufferAttribute().");this.x=a.getX(b);this.y=a.getY(b);this.z=a.getZ(b);this.w=a.getW(b);return this}});Ra.prototype=Object.assign(Object.create(ka.prototype),{constructor:Ra,isWebGLRenderTarget:!0,setSize:function(a,b){if(this.width!==a||this.height!==b)this.width=a,this.height=b,this.dispose();this.viewport.set(0,0,a,b);this.scissor.set(0,0,a,b)},clone:function(){return(new this.constructor).copy(this)},copy:function(a){this.width=
+a.width;this.height=a.height;this.viewport.copy(a.viewport);this.texture=a.texture.clone();this.depthBuffer=a.depthBuffer;this.stencilBuffer=a.stencilBuffer;this.depthTexture=a.depthTexture;return this},dispose:function(){this.dispatchEvent({type:"dispose"})}});Zd.prototype=Object.assign(Object.create(Ra.prototype),{constructor:Zd,isWebGLMultisampleRenderTarget:!0,copy:function(a){Ra.prototype.copy.call(this,a);this.samples=a.samples;return this}});mb.prototype=Object.create(Ra.prototype);mb.prototype.constructor=
+mb;mb.prototype.isWebGLRenderTargetCube=!0;nb.prototype=Object.create(V.prototype);nb.prototype.constructor=nb;nb.prototype.isDataTexture=!0;Object.assign(Za.prototype,{isBox3:!0,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromArray:function(a){for(var b=Infinity,c=Infinity,d=Infinity,e=-Infinity,f=-Infinity,g=-Infinity,h=0,k=a.length;he&&(e=m);l>f&&(f=l);q>g&&(g=q)}this.min.set(b,c,d);this.max.set(e,
+f,g);return this},setFromBufferAttribute:function(a){for(var b=Infinity,c=Infinity,d=Infinity,e=-Infinity,f=-Infinity,g=-Infinity,h=0,k=a.count;he&&(e=m);l>f&&(f=l);q>g&&(g=q)}this.min.set(b,c,d);this.max.set(e,f,g);return this},setFromPoints:function(a){this.makeEmpty();for(var b=0,c=a.length;bthis.max.x||a.ythis.max.y||a.zthis.max.z?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y&&this.min.z<=a.min.z&&a.max.z<=this.max.z},getParameter:function(a,b){void 0===b&&(console.warn("THREE.Box3: .getParameter() target is now required"),b=new n);return b.set((a.x-this.min.x)/(this.max.x-this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y),(a.z-this.min.z)/(this.max.z-this.min.z))},
+intersectsBox:function(a){return a.max.xthis.max.x||a.max.ythis.max.y||a.max.zthis.max.z?!1:!0},intersectsSphere:function(){var a=new n;return function(b){this.clampPoint(b.center,a);return a.distanceToSquared(b.center)<=b.radius*b.radius}}(),intersectsPlane:function(a){if(0=-a.constant},intersectsTriangle:function(){function a(a){var e;var f=0;for(e=a.length-3;f<=e;f+=3){h.fromArray(a,f);var g=m.x*Math.abs(h.x)+m.y*Math.abs(h.y)+m.z*Math.abs(h.z),k=b.dot(h),l=c.dot(h),p=d.dot(h);if(Math.max(-Math.max(k,l,p),Math.min(k,l,p))>g)return!1}return!0}var b=new n,
+c=new n,d=new n,e=new n,f=new n,g=new n,h=new n,k=new n,m=new n,l=new n;return function(h){if(this.isEmpty())return!1;this.getCenter(k);m.subVectors(this.max,k);b.subVectors(h.a,k);c.subVectors(h.b,k);d.subVectors(h.c,k);e.subVectors(c,b);f.subVectors(d,c);g.subVectors(b,d);h=[0,-e.z,e.y,0,-f.z,f.y,0,-g.z,g.y,e.z,0,-e.x,f.z,0,-f.x,g.z,0,-g.x,-e.y,e.x,0,-f.y,f.x,0,-g.y,g.x,0];if(!a(h))return!1;h=[1,0,0,0,1,0,0,0,1];if(!a(h))return!1;l.crossVectors(e,f);h=[l.x,l.y,l.z];return a(h)}}(),clampPoint:function(a,
+b){void 0===b&&(console.warn("THREE.Box3: .clampPoint() target is now required"),b=new n);return b.copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new n;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),getBoundingSphere:function(){var a=new n;return function(b){void 0===b&&(console.warn("THREE.Box3: .getBoundingSphere() target is now required"),b=new Ha);this.getCenter(b.center);b.radius=.5*this.getSize(a).length();return b}}(),intersect:function(a){this.min.max(a.min);
+this.max.min(a.max);this.isEmpty()&&this.makeEmpty();return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},applyMatrix4:function(){var a=[new n,new n,new n,new n,new n,new n,new n,new n];return function(b){if(this.isEmpty())return this;a[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(b);a[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(b);a[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(b);a[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(b);
+a[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(b);a[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(b);a[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(b);a[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(b);this.setFromPoints(a);return this}}(),translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)}});Object.assign(Ha.prototype,{set:function(a,b){this.center.copy(a);this.radius=b;return this},
+setFromPoints:function(){var a=new Za;return function(b,c){var d=this.center;void 0!==c?d.copy(c):a.setFromPoints(b).getCenter(d);for(var e=c=0,f=b.length;e=this.radius},containsPoint:function(a){return a.distanceToSquared(this.center)<=this.radius*this.radius},
+distanceToPoint:function(a){return a.distanceTo(this.center)-this.radius},intersectsSphere:function(a){var b=this.radius+a.radius;return a.center.distanceToSquared(this.center)<=b*b},intersectsBox:function(a){return a.intersectsSphere(this)},intersectsPlane:function(a){return Math.abs(a.distanceToPoint(this.center))<=this.radius},clampPoint:function(a,b){var c=this.center.distanceToSquared(a);void 0===b&&(console.warn("THREE.Sphere: .clampPoint() target is now required"),b=new n);b.copy(a);c>this.radius*
+this.radius&&(b.sub(this.center).normalize(),b.multiplyScalar(this.radius).add(this.center));return b},getBoundingBox:function(a){void 0===a&&(console.warn("THREE.Sphere: .getBoundingBox() target is now required"),a=new Za);a.set(this.center,this.center);a.expandByScalar(this.radius);return a},applyMatrix4:function(a){this.center.applyMatrix4(a);this.radius*=a.getMaxScaleOnAxis();return this},translate:function(a){this.center.add(a);return this},equals:function(a){return a.center.equals(this.center)&&
+a.radius===this.radius}});Object.assign(Sa.prototype,{set:function(a,b){this.normal.copy(a);this.constant=b;return this},setComponents:function(a,b,c,d){this.normal.set(a,b,c);this.constant=d;return this},setFromNormalAndCoplanarPoint:function(a,b){this.normal.copy(a);this.constant=-b.dot(this.normal);return this},setFromCoplanarPoints:function(){var a=new n,b=new n;return function(c,d,e){d=a.subVectors(e,d).cross(b.subVectors(c,d)).normalize();this.setFromNormalAndCoplanarPoint(d,c);return this}}(),
+clone:function(){return(new this.constructor).copy(this)},copy:function(a){this.normal.copy(a.normal);this.constant=a.constant;return this},normalize:function(){var a=1/this.normal.length();this.normal.multiplyScalar(a);this.constant*=a;return this},negate:function(){this.constant*=-1;this.normal.negate();return this},distanceToPoint:function(a){return this.normal.dot(a)+this.constant},distanceToSphere:function(a){return this.distanceToPoint(a.center)-a.radius},projectPoint:function(a,b){void 0===
+b&&(console.warn("THREE.Plane: .projectPoint() target is now required"),b=new n);return b.copy(this.normal).multiplyScalar(-this.distanceToPoint(a)).add(a)},intersectLine:function(){var a=new n;return function(b,c){void 0===c&&(console.warn("THREE.Plane: .intersectLine() target is now required"),c=new n);var d=b.delta(a),e=this.normal.dot(d);if(0===e){if(0===this.distanceToPoint(b.start))return c.copy(b.start)}else if(e=-(b.start.dot(this.normal)+this.constant)/e,!(0>e||1b&&0a&&0c;c++)b[c].copy(a.planes[c]);return this},setFromMatrix:function(a){var b=this.planes,c=a.elements;a=c[0];var d=c[1],e=c[2],f=c[3],g=c[4],h=c[5],k=c[6],m=c[7],l=c[8],q=c[9],n=c[10],t=c[11],r=c[12],u=c[13],w=c[14];c=c[15];b[0].setComponents(f-a,m-g,t-l,c-r).normalize();b[1].setComponents(f+a,m+g,t+l,c+r).normalize();b[2].setComponents(f+d,m+h,t+q,c+u).normalize();b[3].setComponents(f-d,m-h,t-q,c-u).normalize();b[4].setComponents(f-e,m-k,t-n,c-w).normalize();b[5].setComponents(f+e,
+m+k,t+n,c+w).normalize();return this},intersectsObject:function(){var a=new Ha;return function(b){var c=b.geometry;null===c.boundingSphere&&c.computeBoundingSphere();a.copy(c.boundingSphere).applyMatrix4(b.matrixWorld);return this.intersectsSphere(a)}}(),intersectsSprite:function(){var a=new Ha;return function(b){a.center.set(0,0,0);a.radius=.7071067811865476;a.applyMatrix4(b.matrixWorld);return this.intersectsSphere(a)}}(),intersectsSphere:function(a){var b=this.planes,c=a.center;a=-a.radius;for(var d=
+0;6>d;d++)if(b[d].distanceToPoint(c)d;d++){var e=c[d];a.x=0e.distanceToPoint(a))return!1}return!0}}(),containsPoint:function(a){for(var b=this.planes,c=0;6>c;c++)if(0>b[c].distanceToPoint(a))return!1;return!0}});var U={alphamap_fragment:"#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif",
+alphamap_pars_fragment:"#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",alphatest_fragment:"#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif",aomap_fragment:"#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif",
+aomap_pars_fragment:"#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif",begin_vertex:"vec3 transformed = vec3( position );",beginnormal_vertex:"vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif",bsdfs:"vec2 integrateSpecularBRDF( const in float dotNV, const in float roughness ) {\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\treturn vec2( -1.04, 1.04 ) * a004 + r.zw;\n}\nfloat punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n#else\n\tif( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t}\n\treturn 1.0;\n#endif\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNL = saturate( dot( geometry.normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nvec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\treturn specularColor * brdf.x + brdf.y;\n}\nvoid BRDF_Specular_Multiscattering_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tvec3 F = F_Schlick( specularColor, dotNV );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\tvec3 FssEss = F * brdf.x + brdf.y;\n\tfloat Ess = brdf.x + brdf.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}",
+bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 );\n\t\tfDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif",
+clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vViewPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vViewPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\tif ( clipped ) discard;\n\t#endif\n#endif",
+clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\t#if ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( MATCAP )\n\t\tvarying vec3 vViewPosition;\n\t#endif\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( MATCAP )\n\tvarying vec3 vViewPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( MATCAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif",
+color_fragment:"#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif",color_vertex:"#ifdef USE_COLOR\n\tvColor.xyz = color.xyz;\n#endif",common:"#define PI 3.14159265359\n#define PI2 6.28318530718\n#define PI_HALF 1.5707963267949\n#define RECIPROCAL_PI 0.31830988618\n#define RECIPROCAL_PI2 0.15915494\n#define LOG2 1.442695\n#define EPSILON 1e-6\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#define whiteCompliment(a) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract(sin(sn) * c);\n}\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}",
+cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n#define cubeUV_textureSize (1024.0)\nint getFaceFromDirection(vec3 direction) {\n\tvec3 absDirection = abs(direction);\n\tint face = -1;\n\tif( absDirection.x > absDirection.z ) {\n\t\tif(absDirection.x > absDirection.y )\n\t\t\tface = direction.x > 0.0 ? 0 : 3;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\telse {\n\t\tif(absDirection.z > absDirection.y )\n\t\t\tface = direction.z > 0.0 ? 2 : 5;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\treturn face;\n}\n#define cubeUV_maxLods1 (log2(cubeUV_textureSize*0.25) - 1.0)\n#define cubeUV_rangeClamp (exp2((6.0 - 1.0) * 2.0))\nvec2 MipLevelInfo( vec3 vec, float roughnessLevel, float roughness ) {\n\tfloat scale = exp2(cubeUV_maxLods1 - roughnessLevel);\n\tfloat dxRoughness = dFdx(roughness);\n\tfloat dyRoughness = dFdy(roughness);\n\tvec3 dx = dFdx( vec * scale * dxRoughness );\n\tvec3 dy = dFdy( vec * scale * dyRoughness );\n\tfloat d = max( dot( dx, dx ), dot( dy, dy ) );\n\td = clamp(d, 1.0, cubeUV_rangeClamp);\n\tfloat mipLevel = 0.5 * log2(d);\n\treturn vec2(floor(mipLevel), fract(mipLevel));\n}\n#define cubeUV_maxLods2 (log2(cubeUV_textureSize*0.25) - 2.0)\n#define cubeUV_rcpTextureSize (1.0 / cubeUV_textureSize)\nvec2 getCubeUV(vec3 direction, float roughnessLevel, float mipLevel) {\n\tmipLevel = roughnessLevel > cubeUV_maxLods2 - 3.0 ? 0.0 : mipLevel;\n\tfloat a = 16.0 * cubeUV_rcpTextureSize;\n\tvec2 exp2_packed = exp2( vec2( roughnessLevel, mipLevel ) );\n\tvec2 rcp_exp2_packed = vec2( 1.0 ) / exp2_packed;\n\tfloat powScale = exp2_packed.x * exp2_packed.y;\n\tfloat scale = rcp_exp2_packed.x * rcp_exp2_packed.y * 0.25;\n\tfloat mipOffset = 0.75*(1.0 - rcp_exp2_packed.y) * rcp_exp2_packed.x;\n\tbool bRes = mipLevel == 0.0;\n\tscale = bRes && (scale < a) ? a : scale;\n\tvec3 r;\n\tvec2 offset;\n\tint face = getFaceFromDirection(direction);\n\tfloat rcpPowScale = 1.0 / powScale;\n\tif( face == 0) {\n\t\tr = vec3(direction.x, -direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 1) {\n\t\tr = vec3(direction.y, direction.x, direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 2) {\n\t\tr = vec3(direction.z, direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 3) {\n\t\tr = vec3(direction.x, direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse if( face == 4) {\n\t\tr = vec3(direction.y, direction.x, -direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse {\n\t\tr = vec3(direction.z, -direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\tr = normalize(r);\n\tfloat texelOffset = 0.5 * cubeUV_rcpTextureSize;\n\tvec2 s = ( r.yz / abs( r.x ) + vec2( 1.0 ) ) * 0.5;\n\tvec2 base = offset + vec2( texelOffset );\n\treturn base + s * ( scale - 2.0 * texelOffset );\n}\n#define cubeUV_maxLods3 (log2(cubeUV_textureSize*0.25) - 3.0)\nvec4 textureCubeUV( sampler2D envMap, vec3 reflectedDirection, float roughness ) {\n\tfloat roughnessVal = roughness* cubeUV_maxLods3;\n\tfloat r1 = floor(roughnessVal);\n\tfloat r2 = r1 + 1.0;\n\tfloat t = fract(roughnessVal);\n\tvec2 mipInfo = MipLevelInfo(reflectedDirection, r1, roughness);\n\tfloat s = mipInfo.y;\n\tfloat level0 = mipInfo.x;\n\tfloat level1 = level0 + 1.0;\n\tlevel1 = level1 > 5.0 ? 5.0 : level1;\n\tlevel0 += min( floor( s + 0.5 ), 5.0 );\n\tvec2 uv_10 = getCubeUV(reflectedDirection, r1, level0);\n\tvec4 color10 = envMapTexelToLinear(texture2D(envMap, uv_10));\n\tvec2 uv_20 = getCubeUV(reflectedDirection, r2, level0);\n\tvec4 color20 = envMapTexelToLinear(texture2D(envMap, uv_20));\n\tvec4 result = mix(color10, color20, t);\n\treturn vec4(result.rgb, 1.0);\n}\n#endif",
+defaultnormal_vertex:"vec3 transformedNormal = normalMatrix * objectNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = normalMatrix * objectTangent;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );\n#endif",
+emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",encodings_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",encodings_pars_fragment:"\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * value.a * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = min( floor( D ) / 255.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = cLogLuvM * value.rgb;\n\tXp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract( Le );\n\tvResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = cLogLuvInverseM * Xp_Y_XYZp.rgb;\n\treturn vec4( max( vRGB, 0.0 ), 1.0 );\n}",
+envmap_fragment:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\tvec2 sampleUV;\n\t\treflectVec = normalize( reflectVec );\n\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\tvec4 envColor = texture2D( envMap, sampleUV );\n\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\treflectVec = normalize( reflectVec );\n\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );\n\t\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\tenvColor = envMapTexelToLinear( envColor );\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",
+envmap_pars_fragment:"#if defined( USE_ENVMAP ) || defined( PHYSICAL )\n\tuniform float reflectivity;\n\tuniform float envMapIntensity;\n#endif\n#ifdef USE_ENVMAP\n\t#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )\n\t\tvarying vec3 vWorldPosition;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",
+envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, queryVec, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, queryReflectVec, BlinnExponentToGGXRoughness(blinnShininessExponent ));\n\t\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\t\tvec2 sampleUV;\n\t\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif",
+envmap_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",
+fog_vertex:"#ifdef USE_FOG\n\tfogDepth = -mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float fogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 ) );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",
+gradientmap_pars_fragment:"#ifdef TOON\n\tuniform sampler2D gradientMap;\n\tvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\t\tfloat dotNL = dot( normal, lightDirection );\n\t\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t\t#ifdef USE_GRADIENTMAP\n\t\t\treturn texture2D( gradientMap, coord ).rgb;\n\t\t#else\n\t\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t\t#endif\n\t}\n#endif",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\treflectedLight.indirectDiffuse += PI * texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n#endif",
+lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_vertex:"vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n\tvIndirectBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n#endif",
+lights_pars_begin:"uniform vec3 ambientLightColor;\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t\tfloat shadowCameraNear;\n\t\tfloat shadowCameraFar;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif",
+lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3\tdiffuseColor;\n\tvec3\tspecularColor;\n\tfloat\tspecularShininess;\n\tfloat\tspecularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifdef TOON\n\t\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#else\n\t\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\t\tvec3 irradiance = dotNL * directLight.color;\n\t#endif\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)",
+lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nmaterial.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );\n#ifdef STANDARD\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.clearCoat = saturate( clearCoat );\tmaterial.clearCoatRoughness = clamp( clearCoatRoughness, 0.04, 1.0 );\n#endif",
+lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3\tdiffuseColor;\n\tfloat\tspecularRoughness;\n\tvec3\tspecularColor;\n\t#ifndef STANDARD\n\t\tfloat clearCoat;\n\t\tfloat clearCoatRoughness;\n\t#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearCoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.specularRoughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifndef STANDARD\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );\n\treflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\t#ifndef STANDARD\n\t\treflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifndef ENVMAP_TYPE_CUBE_UV\n\t\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\t#endif\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifndef STANDARD\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\tfloat dotNL = dotNV;\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\tfloat clearCoatInv = 1.0 - clearCoatDHR;\n\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec3 singleScattering = vec3( 0.0 );\n\t\tvec3 multiScattering = vec3( 0.0 );\n\t\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t\tBRDF_Specular_Multiscattering_Environment( geometry, material.specularColor, material.specularRoughness, singleScattering, multiScattering );\n\t\tvec3 diffuse = material.diffuseColor;\n\t\treflectedLight.indirectSpecular += clearCoatInv * radiance * singleScattering;\n\t\treflectedLight.indirectDiffuse += multiScattering * cosineWeightedIrradiance;\n\t\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n\t#else\n\t\treflectedLight.indirectSpecular += clearCoatInv * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness );\n\t#endif\n\t#ifndef STANDARD\n\t\treflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\n#define Material_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.specularRoughness )\n#define Material_ClearCoat_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.clearCoatRoughness )\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",
+lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = normalize( vViewPosition );\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( pointLight.shadow, directLight.visible ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( spotLight.shadow, directLight.visible ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearCoatRadiance = vec3( 0.0 );\n#endif",
+lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec3 lightMapIrradiance = texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tirradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry, Material_BlinnShininessExponent( material ), maxMipLevel );\n\t#ifndef STANDARD\n\t\tclearCoatRadiance += getLightProbeIndirectRadiance( geometry, Material_ClearCoat_BlinnShininessExponent( material ), maxMipLevel );\n\t#endif\n#endif",
+lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, irradiance, clearCoatRadiance, geometry, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n#endif",
+logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t#else\n\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\tgl_Position.z *= gl_Position.w;\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif",
+map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#ifdef USE_MAP\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif",map_particle_pars_fragment:"#ifdef USE_MAP\n\tuniform mat3 uvTransform;\n\tuniform sampler2D map;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",
+metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\n\tobjectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\n\tobjectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\n\tobjectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\t#ifndef USE_MORPHNORMALS\n\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif",
+morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\n\ttransformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\n\ttransformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\n\ttransformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\ttransformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\n\ttransformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\n\ttransformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\n\ttransformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n\t#endif\n#endif",
+normal_fragment_begin:"#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t\tbitangent = bitangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t#endif\n\t#endif\n#endif",
+normal_fragment_maps:"#ifdef USE_NORMALMAP\n\t#ifdef OBJECTSPACE_NORMALMAP\n\t\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\t#ifdef FLIP_SIDED\n\t\t\tnormal = - normal;\n\t\t#endif\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t#endif\n\t\tnormal = normalize( normalMatrix * normal );\n\t#else\n\t\t#ifdef USE_TANGENT\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\t\tmapN.xy = normalScale * mapN.xy;\n\t\t\tnormal = normalize( vTBN * mapN );\n\t\t#else\n\t\t\tnormal = perturbNormal2Arb( -vViewPosition, normal );\n\t\t#endif\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif",
+normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n\t#ifdef OBJECTSPACE_NORMALMAP\n\t\tuniform mat3 normalMatrix;\n\t#else\n\t\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\n\t\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\t\tvec2 st0 = dFdx( vUv.st );\n\t\t\tvec2 st1 = dFdy( vUv.st );\n\t\t\tfloat scale = sign( st1.t * st0.s - st0.t * st1.s );\n\t\t\tvec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );\n\t\t\tvec3 T = normalize( ( - q0 * st1.s + q1 * st0.s ) * scale );\n\t\t\tvec3 N = normalize( surf_norm );\n\t\t\tmat3 tsn = mat3( S, T, N );\n\t\t\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\t\tmapN.xy *= normalScale;\n\t\t\tmapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t\treturn normalize( tsn * mapN );\n\t\t}\n\t#endif\n#endif",
+packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}",
+premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#if defined( DITHERING )\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#if defined( DITHERING )\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",
+roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tfloat texture2DShadowLerp( sampler2D depths, vec2 size, vec2 uv, float compare ) {\n\t\tconst vec2 offset = vec2( 0.0, 1.0 );\n\t\tvec2 texelSize = vec2( 1.0 ) / size;\n\t\tvec2 centroidUV = floor( uv * size + 0.5 ) / size;\n\t\tfloat lb = texture2DCompare( depths, centroidUV + texelSize * offset.xx, compare );\n\t\tfloat lt = texture2DCompare( depths, centroidUV + texelSize * offset.xy, compare );\n\t\tfloat rb = texture2DCompare( depths, centroidUV + texelSize * offset.yx, compare );\n\t\tfloat rt = texture2DCompare( depths, centroidUV + texelSize * offset.yy, compare );\n\t\tvec2 f = fract( uv * size + 0.5 );\n\t\tfloat a = mix( lb, lt, f.y );\n\t\tfloat b = mix( rb, rt, f.y );\n\t\tfloat c = mix( a, b, f.x );\n\t\treturn c;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tshadow = (\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",
+shadowmap_pars_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n#endif",
+shadowmap_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n#endif",
+shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tshadow *= bool( directionalLight.shadow ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tshadow *= bool( spotLight.shadow ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tshadow *= bool( pointLight.shadow ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#endif\n\t#endif\n\treturn shadow;\n}",
+skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif",
+skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",
+specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n\t#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nuniform float toneMappingWhitePoint;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\n#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )\nvec3 Uncharted2ToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( ( color * ( 2.51 * color + 0.03 ) ) / ( color * ( 2.43 * color + 0.59 ) + 0.14 ) );\n}",
+uv_pars_fragment:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n#endif",uv_pars_vertex:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif",
+uv_vertex:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif",uv2_pars_fragment:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif",uv2_pars_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n#endif",
+uv2_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = uv2;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );\n#endif",background_frag:"uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",
+cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",
+depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - gl_FragCoord.z ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( gl_FragCoord.z );\n\t#endif\n}",
+depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",
+distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",
+distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",
+equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV;\n\tsampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\tsampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;\n\tvec4 texColor = texture2D( tEquirect, sampleUV );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}",
+equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}",
+linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvLineDistance = scale * lineDistance;\n\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",
+meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\treflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}",
+meshbasic_vert:"#include