From e2d27b49c71c918ebb2da25aef5468cd8bbc27cc Mon Sep 17 00:00:00 2001 From: fan <18706759286@163.com> Date: Sat, 10 Oct 2020 17:58:14 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=96=B9=E5=90=91=E6=9D=86=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/simulation.js | 8 +- src/jmap/shape/directionRod/index.js | 5 - src/jmapNew/config/skinCode/bejing_01.js | 1 + src/jmapNew/config/skinCode/chengdu_01.js | 1 + src/jmapNew/config/skinCode/chengdu_03.js | 1 + src/jmapNew/config/skinCode/foshan_01.js | 1 + src/jmapNew/config/skinCode/fuzhou_01.js | 1 + src/jmapNew/config/skinCode/haerbin_01.js | 14 +- src/jmapNew/config/skinCode/ningbo_01.js | 1 + src/jmapNew/config/skinCode/ningbo_03.js | 1 + src/jmapNew/config/skinCode/xian_01.js | 1 + src/jmapNew/config/skinCode/xian_02.js | 1 + src/jmapNew/constant/deviceRender.js | 6 + src/jmapNew/constant/deviceState.js | 3 + src/jmapNew/constant/deviceType.js | 3 +- src/jmapNew/constant/stateTransition.js | 3 + src/jmapNew/shape/DirectionRod/index.js | 110 +++++++++ .../shape/StationStand/detain/EDetain.js | 4 +- src/jmapNew/shape/factory.js | 2 + .../theme/haerbin_01/menus/menuButton.vue | 2 - src/jmapNew/utils/parser.js | 5 + src/store/modules/map.js | 7 + .../newMapdraft/mapoperate/directionRod.vue | 218 ++++++++++++++++++ .../newMap/newMapdraft/mapoperate/index.vue | 7 +- 24 files changed, 393 insertions(+), 13 deletions(-) delete mode 100644 src/jmap/shape/directionRod/index.js create mode 100644 src/jmapNew/shape/DirectionRod/index.js create mode 100644 src/views/newMap/newMapdraft/mapoperate/directionRod.vue diff --git a/src/api/simulation.js b/src/api/simulation.js index 3deeccc2b..646c0b8a9 100644 --- a/src/api/simulation.js +++ b/src/api/simulation.js @@ -26,7 +26,13 @@ export function setFailureModeNew(data, group) { data: data }); } - +/** 取消自动故障 */ +export function cancelFailureModeNew(id, group) { + return request({ + url: `/simulation/${group}/faultMode/${id}`, + method: 'put' + }); +} /** 获取仿真待触发的故障列表*/ export function getSimulationFaultRules(group) { return request({ diff --git a/src/jmap/shape/directionRod/index.js b/src/jmap/shape/directionRod/index.js deleted file mode 100644 index 89c5fad15..000000000 --- a/src/jmap/shape/directionRod/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import Group from "zrender/src/container/Group"; - -class DirectionRod extends Group { - -} diff --git a/src/jmapNew/config/skinCode/bejing_01.js b/src/jmapNew/config/skinCode/bejing_01.js index 5144b9099..c06efa81f 100644 --- a/src/jmapNew/config/skinCode/bejing_01.js +++ b/src/jmapNew/config/skinCode/bejing_01.js @@ -756,6 +756,7 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/config/skinCode/chengdu_01.js b/src/jmapNew/config/skinCode/chengdu_01.js index b25fb15a4..6bf0a590a 100644 --- a/src/jmapNew/config/skinCode/chengdu_01.js +++ b/src/jmapNew/config/skinCode/chengdu_01.js @@ -683,6 +683,7 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/config/skinCode/chengdu_03.js b/src/jmapNew/config/skinCode/chengdu_03.js index 3327684b5..d57469786 100644 --- a/src/jmapNew/config/skinCode/chengdu_03.js +++ b/src/jmapNew/config/skinCode/chengdu_03.js @@ -872,6 +872,7 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/config/skinCode/foshan_01.js b/src/jmapNew/config/skinCode/foshan_01.js index ea3930d3a..80e729f99 100644 --- a/src/jmapNew/config/skinCode/foshan_01.js +++ b/src/jmapNew/config/skinCode/foshan_01.js @@ -710,6 +710,7 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/config/skinCode/fuzhou_01.js b/src/jmapNew/config/skinCode/fuzhou_01.js index 1cc5d8441..27969c9cf 100644 --- a/src/jmapNew/config/skinCode/fuzhou_01.js +++ b/src/jmapNew/config/skinCode/fuzhou_01.js @@ -718,6 +718,7 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/config/skinCode/haerbin_01.js b/src/jmapNew/config/skinCode/haerbin_01.js index 580d82b10..259d57b30 100644 --- a/src/jmapNew/config/skinCode/haerbin_01.js +++ b/src/jmapNew/config/skinCode/haerbin_01.js @@ -249,7 +249,7 @@ class SkinCode extends defaultStyle { andCenterTrainColor: '#F61107', // 车站+中心扣车颜色 detainTrainTextColor: '#E4EF50', // 车站扣除文字颜色 fontSize: 18, // 字体大小 - fontWeight: 'bold', + fontWeight: 'bolder', textVerticalAlign: 'middle' // 车站扣车 文字垂直对齐方式 }, // 运行等级 @@ -781,6 +781,18 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = { + displayCondition: '01', // 显示条件 (01所有模式下显示 02 行调显示 03现地显示) + fillColor: '#0F0', + fontColor: '#FFF', + rodLength: 90, + rodHeight: 10, + triangleLength: 25, + triangleHeight: 20, + fontSize: 14, + fontOffsetX: 45, + fontOffsetY: 24 + }; } } diff --git a/src/jmapNew/config/skinCode/ningbo_01.js b/src/jmapNew/config/skinCode/ningbo_01.js index e50e5c0bc..73a543f3b 100644 --- a/src/jmapNew/config/skinCode/ningbo_01.js +++ b/src/jmapNew/config/skinCode/ningbo_01.js @@ -775,6 +775,7 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/config/skinCode/ningbo_03.js b/src/jmapNew/config/skinCode/ningbo_03.js index af38498d6..ea4cf05a7 100644 --- a/src/jmapNew/config/skinCode/ningbo_03.js +++ b/src/jmapNew/config/skinCode/ningbo_03.js @@ -845,6 +845,7 @@ class SkinCode extends defaultStyle { allowCloseFillColor: '#FF0000', // 允许关门填充颜色 allowCLoseStrokeColor: '#FFFF00' // 允许关门描边颜色 }; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/config/skinCode/xian_01.js b/src/jmapNew/config/skinCode/xian_01.js index bcaa5b6ba..84323ae0c 100644 --- a/src/jmapNew/config/skinCode/xian_01.js +++ b/src/jmapNew/config/skinCode/xian_01.js @@ -783,6 +783,7 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/config/skinCode/xian_02.js b/src/jmapNew/config/skinCode/xian_02.js index df3089383..0dad06395 100644 --- a/src/jmapNew/config/skinCode/xian_02.js +++ b/src/jmapNew/config/skinCode/xian_02.js @@ -866,6 +866,7 @@ class SkinCode extends defaultStyle { } }; this[deviceType.FloodGate] = {}; + this[deviceType.DirectionRod] = {}; } } diff --git a/src/jmapNew/constant/deviceRender.js b/src/jmapNew/constant/deviceRender.js index 34e61a9e1..12744daa2 100644 --- a/src/jmapNew/constant/deviceRender.js +++ b/src/jmapNew/constant/deviceRender.js @@ -260,8 +260,14 @@ deviceRender[deviceType.OverAp] = { _type: deviceType.OverAp, zlevel: 1 }; +/** 防淹门 */ deviceRender[deviceType.FloodGate] = { _type: deviceType.FloodGate, zlevel: 1 }; +/** 方向杆 */ +deviceRender[deviceType.DirectionRod] = { + _type: deviceType.DirectionRod, + zlevel: 1 +}; export default deviceRender; diff --git a/src/jmapNew/constant/deviceState.js b/src/jmapNew/constant/deviceState.js index f772da5cb..7d691aff3 100644 --- a/src/jmapNew/constant/deviceState.js +++ b/src/jmapNew/constant/deviceState.js @@ -140,6 +140,9 @@ deviceState[deviceType.StationStand] = { deviceState[deviceType.FloodGate] = { /** 防淹门状态 */ }; +deviceState[deviceType.DirectionRod] = { + /** 方向杆状态 */ +}; deviceState[deviceType.Train] = { // /** 列车类型*/ // type: { diff --git a/src/jmapNew/constant/deviceType.js b/src/jmapNew/constant/deviceType.js index 3e5ff24fb..d914a977a 100644 --- a/src/jmapNew/constant/deviceType.js +++ b/src/jmapNew/constant/deviceType.js @@ -46,7 +46,8 @@ const deviceType = { Power: 'Power', StationTurnBack: 'StationTurnBack', OverAp: 'OverAp', - FloodGate: 'FloodGate' + FloodGate: 'FloodGate', + DirectionRod: 'DirectionRod' }; export default deviceType; diff --git a/src/jmapNew/constant/stateTransition.js b/src/jmapNew/constant/stateTransition.js index 5e1b95c15..91880ee96 100644 --- a/src/jmapNew/constant/stateTransition.js +++ b/src/jmapNew/constant/stateTransition.js @@ -124,6 +124,9 @@ class Status { handleFloodGate(device) { this.statusObj = { }; } + handleDirectionRod(device) { + this.statusObj = { }; + } getStatus() { return this.statusObj; } diff --git a/src/jmapNew/shape/DirectionRod/index.js b/src/jmapNew/shape/DirectionRod/index.js new file mode 100644 index 000000000..36d921cb7 --- /dev/null +++ b/src/jmapNew/shape/DirectionRod/index.js @@ -0,0 +1,110 @@ +import Group from 'zrender/src/container/Group'; +import Polygon from 'zrender/src/graphic/shape/Polygon'; +import {isShowThePrdType} from '../../utils/handlePath'; +import Text from 'zrender/src/graphic/Text'; + +export default class DirectionRod extends Group { + constructor(model, style) { + super(); + this.z = 5; + this._code = model.code; + this._type = model._type; + this.zlevel = model.zlevel; + this.model = model; + this.style = style; + this.isShowShape = true; + if (isShowThePrdType(model.prdType, style.DirectionRod.displayCondition) || model.previewOrMapDraw) { + this.create(); + this.createMouseEvent(); + this.setState(model); + } + if (model.previewOrMapDraw) { + this.setShowMode(); + } + } + create() { + const model = this.model; + const style = this.style; + this.rod = new Polygon({ + zlevel: this.zlevel, + z: this.z, + origin: [model.position.x + (style.DirectionRod.rodLength / 2), model.position.y], + rotation: 0, + shape: { + points: [ + [model.position.x, model.position.y], + [model.position.x + style.DirectionRod.triangleLength, model.position.y + (style.DirectionRod.triangleHeight / 2)], + [model.position.x + style.DirectionRod.triangleLength, model.position.y + (style.DirectionRod.rodHeight / 2)], + [model.position.x + style.DirectionRod.rodLength, model.position.y + (style.DirectionRod.rodHeight / 2)], + [model.position.x + style.DirectionRod.rodLength, model.position.y - (style.DirectionRod.rodHeight / 2)], + [model.position.x + style.DirectionRod.triangleLength, model.position.y - (style.DirectionRod.rodHeight / 2)], + [model.position.x + style.DirectionRod.triangleLength, model.position.y - (style.DirectionRod.triangleHeight / 2)] + ] + }, + style: { + fill: style.DirectionRod.fillColor + } + }); + this.tipText = new Text({ + zlevel: this.zlevel, + z: this.z, + style: { + x: model.position.x + style.DirectionRod.fontOffsetX, + y: model.position.y + style.DirectionRod.fontOffsetY, + fontWeight: 'normal', + fontSize: style.DirectionRod.fontSize, + fontFamily: this.style.fontFamily, + text: model.name, + textFill: style.DirectionRod.fontColor, + textAlign: 'center', + textPosition: 'inside', + textVerticalAlign: 'bottom' + } + }); + this.add(this.rod); + this.add(this.tipText); + // setTimeout(()=> { + // console.log('*************'); + // this.rod.attr('rotation', Math.PI); + // console.log(this.rod, '----'); + // }, 10000); + } + recover() { + } + // 设置状态 + setState(model) { + // if (!this.isShowShape) return; + // this.recover(); + } + + createMouseEvent() { + } + + setShowMode() { + const showMode = this.model.showMode; + const showConditions = this.style.DirectionRod.displayCondition; + if (!showConditions || showConditions === '01' || showMode === showConditions) { + this.showMode(); + } else { + this.hideMode(); + } + } + setShowStation(stationCode) { + if (!stationCode || this.model.stationCode === stationCode) { + this.isShowShape = true; + this.showMode(); + } else { + this.isShowShape = false; + this.hideMode(); + } + } + showMode() { + this.rod && this.rod.show(); + this.tipText && this.tipText.show(); + this.setState(this.model); + } + hideMode() { + this.rod && this.rod.hide(); + this.tipText && this.tipText.hide(); + } +} diff --git a/src/jmapNew/shape/StationStand/detain/EDetain.js b/src/jmapNew/shape/StationStand/detain/EDetain.js index 9b838771e..b2594f417 100644 --- a/src/jmapNew/shape/StationStand/detain/EDetain.js +++ b/src/jmapNew/shape/StationStand/detain/EDetain.js @@ -28,14 +28,16 @@ class EDetain extends Group { x: detainX, y: detainY, text: deviceParam.text, - fontSize: `${deviceParam.fontSize}px`, + fontSize: deviceParam.fontSize, fontFamily: style.fontFamily, + fontWeight: deviceParam.fontWeight, textFill: deviceParam.centerTrainColor, textStroke: style.backgroundColor, textAlign: style.textStyle.textAlign, textVerticalAlign: deviceParam.textVerticalAlign || style.textStyle.textVerticalAlign } }); + console.log(this.detain, '************'); this.add(this.detain); } } diff --git a/src/jmapNew/shape/factory.js b/src/jmapNew/shape/factory.js index 57b055926..306983e2f 100644 --- a/src/jmapNew/shape/factory.js +++ b/src/jmapNew/shape/factory.js @@ -28,6 +28,7 @@ import Power from './Power/index'; import StationTurnBack from './StationTurnBack/index'; import OverAp from './OverAp/index.js'; import FloodGate from './FloodGate/index'; +import DirectionRod from './DirectionRod/index'; /** 图库*/ const mapShape = {}; @@ -77,6 +78,7 @@ mapShape[deviceType.Power] = Power; mapShape[deviceType.StationTurnBack] = StationTurnBack; mapShape[deviceType.OverAp] = OverAp; mapShape[deviceType.FloodGate] = FloodGate; +mapShape[deviceType.DirectionRod] = DirectionRod; function shapefactory(device, jmap) { const type = device._type; diff --git a/src/jmapNew/theme/haerbin_01/menus/menuButton.vue b/src/jmapNew/theme/haerbin_01/menus/menuButton.vue index c5e6800ab..eef67ab7c 100644 --- a/src/jmapNew/theme/haerbin_01/menus/menuButton.vue +++ b/src/jmapNew/theme/haerbin_01/menus/menuButton.vue @@ -513,9 +513,7 @@ export default { { name: '强解区段', cmdType: CMD.Switch.CMD_SWITCH_FAULT_UNLOCK, operate: OperationEvent.Section.fault.menuButton, show: false, securityCommand: true } ] : [ { name: '封锁道岔', cmdType: CMD.Switch.CMD_SWITCH_BLOCK, operate: OperationEvent.Switch.block.menuButton, show: false }, - { name: '解封道岔', cmdType: CMD.Switch.CMD_SWITCH_UNBLOCK, operate: OperationEvent.Switch.unblock.menuButton, show: false, securityCommand: true }, { name: '单锁道岔', cmdType: CMD.Switch.CMD_SWITCH_SINGLE_LOCK, operate: OperationEvent.Switch.lock.menuButton, show: false }, - { name: '取消锁定', cmdType: CMD.Switch.CMD_SWITCH_SINGLE_UNLOCK, operate: OperationEvent.Switch.unlock.menuButton, show: false, securityCommand: true }, { name: '转换定位', cmdType: CMD.Switch.CMD_SWITCH_NORMAL_POSITION, operate: OperationEvent.Switch.locate.menuButton, show: false, disabledName: 'normalPosition' }, { name: '转换反位', cmdType: CMD.Switch.CMD_SWITCH_REVERSE_POSITION, operate: OperationEvent.Switch.reverse.menuButton, show: false, disabledName: 'reversePosition' } // { name: '封锁区段', cmdType: CMD.Switch.CMD_SWITCH_SECTION_BLOCK, operate: OperationEvent.Section.lock.menu, show: false } diff --git a/src/jmapNew/utils/parser.js b/src/jmapNew/utils/parser.js index 2d051b148..c329a76fa 100644 --- a/src/jmapNew/utils/parser.js +++ b/src/jmapNew/utils/parser.js @@ -234,6 +234,10 @@ export function parser(data, skinCode, showConfig) { zrUtil.each(data.floodGateList || [], elem=> { mapDevice[elem.code] = createDevice(deviceType.FloodGate, elem, propConvert, showConfig); }, this); + + zrUtil.each(data.directionRodList || [], elem=> { + mapDevice[elem.code] = createDevice(deviceType.DirectionRod, elem, propConvert, showConfig); + }, this); } return mapDevice; @@ -305,6 +309,7 @@ export function updateMapData(state, model) { case deviceType.Power: updateForList(model, state, 'powerLineList'); break; case deviceType.StationTurnBack : updateForList(model, state, 'tbStrategyList'); break; case deviceType.FloodGate: updateForList(model, state, 'floodGateList'); break; + case deviceType.DirectionRod: updateForList(model, state, 'directionRodList'); break; } } } diff --git a/src/store/modules/map.js b/src/store/modules/map.js index d1205f20c..80caf8496 100644 --- a/src/store/modules/map.js +++ b/src/store/modules/map.js @@ -542,6 +542,13 @@ const map = { return []; } }, + directionRodList: (state) => { + if (state.map) { + return state.map.directionRodList; + } else { + return []; + } + }, trainDetails: (state) => { return state.trainDetails; }, diff --git a/src/views/newMap/newMapdraft/mapoperate/directionRod.vue b/src/views/newMap/newMapdraft/mapoperate/directionRod.vue new file mode 100644 index 000000000..cff8e46a7 --- /dev/null +++ b/src/views/newMap/newMapdraft/mapoperate/directionRod.vue @@ -0,0 +1,218 @@ + + + + diff --git a/src/views/newMap/newMapdraft/mapoperate/index.vue b/src/views/newMap/newMapdraft/mapoperate/index.vue index 85b891688..cb94b7bf5 100644 --- a/src/views/newMap/newMapdraft/mapoperate/index.vue +++ b/src/views/newMap/newMapdraft/mapoperate/index.vue @@ -65,6 +65,7 @@ import SplitStation from './splitStation'; import Arrow from './arrow'; import SplitScreen from './splitScreen'; import FloodGate from './floodGate'; +import DirectionRod from './directionRod'; import { EventBus } from '@/scripts/event-bus'; export default { @@ -93,7 +94,8 @@ export default { SplitStation, Arrow, SplitScreen, - FloodGate + FloodGate, + DirectionRod }, props: { selected: { @@ -132,7 +134,8 @@ export default { {label: this.$t('map.boundingBox'), name:'CheckBox', menus:CheckboxDraft}, {label: '站间分隔', name:'SplitStation', menus:SplitStation}, {label: '箭头', name:'Arrow', menus:Arrow}, - {label: '防淹门', name: 'FloodGate', menus: FloodGate} + {label: '防淹门', name: 'FloodGate', menus: FloodGate}, + {label: '方向杆', name: 'DirectionRod', menus: DirectionRod} ], selectDevice:'', enabledTab: 'Section', From aa00f27ecc8080878c917af4817d0753ebb7cf39 Mon Sep 17 00:00:00 2001 From: fan <18706759286@163.com> Date: Sat, 10 Oct 2020 18:02:00 +0800 Subject: [PATCH 2/5] =?UTF-8?q?ibp=E7=9B=98=E9=97=AE=E9=A2=98=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ibp/ibpPan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ibp/ibpPan.js b/src/ibp/ibpPan.js index 58f166657..47cce3d14 100644 --- a/src/ibp/ibpPan.js +++ b/src/ibp/ibpPan.js @@ -198,7 +198,7 @@ class IbpPan { const deviceList = Object.values(this.ibpDevice); deviceList.forEach(elem =>{ for (var key in val) { - if (elem.model.mean === key) { + if (elem.model.mean === key.toUpperCase()) { const state = {}; state[key] = val[key]; elem.instance.setStatus(state); From 4ada7f987af8ecad7ad5a986f5c727f0a787e5e0 Mon Sep 17 00:00:00 2001 From: fan <18706759286@163.com> Date: Sat, 10 Oct 2020 18:05:34 +0800 Subject: [PATCH 3/5] =?UTF-8?q?ibp=E7=9B=98=E9=97=AE=E9=A2=98=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ibp/ibpPan.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ibp/ibpPan.js b/src/ibp/ibpPan.js index 47cce3d14..8e983bf06 100644 --- a/src/ibp/ibpPan.js +++ b/src/ibp/ibpPan.js @@ -198,7 +198,8 @@ class IbpPan { const deviceList = Object.values(this.ibpDevice); deviceList.forEach(elem =>{ for (var key in val) { - if (elem.model.mean === key.toUpperCase()) { + const mean = elem.model.mean || ''; + if (mean.toUpperCase() === key.toUpperCase()) { const state = {}; state[key] = val[key]; elem.instance.setStatus(state); From 895aae1438e371e08cb469b284fbfee282fb219a Mon Sep 17 00:00:00 2001 From: sunzhenyu Date: Sat, 10 Oct 2020 18:22:30 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=89=E7=BB=B4static?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=9C=B0=E5=9D=80=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=B8=89=E7=BB=B4=E9=A9=BE=E9=A9=B6=E8=BD=A6=E5=8E=A2=E5=86=85?= =?UTF-8?q?=E8=A7=86=E8=A7=92=E4=BD=8D=E7=BD=AE=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=88=97=E8=BD=A6=E5=B1=8F=E8=94=BD=E9=97=A8?= =?UTF-8?q?=E4=B8=89=E7=BB=B4=E5=8A=A8=E7=94=BB=E7=8A=B6=E6=80=81=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=89=E7=BB=B4=E6=B2=99=E7=9B=98=E4=BF=A1?= =?UTF-8?q?=E5=8F=B7=E6=9C=BA=E9=81=93=E5=B2=94=E5=8A=A8=E7=94=BB=E6=92=AD?= =?UTF-8?q?=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/jlmap3d/config/SetRender.js | 2 +- src/jlmap3d/config/SetScene.js | 6 +- src/jlmap3d/edit/connect/newgetmodels.js | 16 +- src/jlmap3d/edit/editmodel/TrainList.js | 4 +- src/jlmap3d/edit/jlmap3dedit.js | 4 +- src/jlmap3d/edit/neweditmodel/SectionList.js | 2 +- .../edit/testEditorModel/SectionList.js | 10 +- .../jl3ddevice/component/signallight.js | 8 +- src/jlmap3d/jl3ddevice/config.js | 16 +- src/jlmap3d/jl3ddevice/jl3ddevice.js | 2 +- src/jlmap3d/jl3ddevice/jl3ddeviceNew.js | 2 +- src/jlmap3d/jl3ddevicetrain/config.js | 2 +- .../jl3ddevicetrain/jl3ddevicetrain.js | 4 +- .../jl3ddevicetrain/jl3ddevicetrainold.js | 4 +- src/jlmap3d/jl3ddrive/jl3ddrive.js | 18 +- .../jl3ddrive/moveupdate/DrivingConnectNew.js | 120 +- src/jlmap3d/jl3dmaintainer/Jl3dfaultdevice.js | 2 +- .../jl3dmaintainer/Jl3dfaultdevicevr.js | 10 +- src/jlmap3d/jl3dmaintainer/Jl3dothervr.js | 4 +- .../jl3dTrainRescue/jl3dTrainRescueVr.js | 6 +- .../jl3dTrainRescue/trainrescueconfig.js | 12 +- .../jl3dmaintainer/jlmap3dmaintainer.js | 4 +- .../jl3dmaintainer/utils/trainControl.js | 2 +- src/jlmap3d/jl3dmaintainer/utils/vrgui.js | 36 +- src/jlmap3d/jl3dmaintainer/utils/vrloader.js | 12 +- src/jlmap3d/jl3dpassflow/config.js | 18 +- src/jlmap3d/jl3dpassflow/jl3dpassflownew.js | 6 +- .../connect/Jlmap3dSubscribeNew.js | 12 +- src/jlmap3d/jl3dsimulation/jlmap3d.js | 6 +- src/jlmap3d/main/loaders/AssetLoader.js | 38 +- src/jlmap3d/main/loaders/FBXLoader1.js | 4149 ----------------- src/jlmap3d/main/loaders/FBXLoader2.js | 3861 --------------- src/jlmap3d/main/loaders/Materialload.js | 176 +- src/jlmap3d/main/loaders/SimulationLoadNew.js | 2 +- src/jlmap3d/main/model/StationStandList.js | 2 +- src/jlmap3d/main/model/TrainList.js | 2 +- src/jlmap3d/main/newmodel/RailListN.js | 71 +- src/jlmap3d/main/newmodel/SectionListN.js | 44 +- .../main/newmodel/StationStandListN.js | 2 +- src/jlmap3d/main/newmodel/TrainListN.js | 62 +- .../jlmap3d/drive/drivecontrol/dirvespee.js | 0 41 files changed, 429 insertions(+), 8330 deletions(-) delete mode 100644 src/jlmap3d/main/loaders/FBXLoader1.js delete mode 100644 src/jlmap3d/main/loaders/FBXLoader2.js delete mode 100644 src/views/jlmap3d/drive/drivecontrol/dirvespee.js diff --git a/src/jlmap3d/config/SetRender.js b/src/jlmap3d/config/SetRender.js index d3ea52b76..e073e7d34 100644 --- a/src/jlmap3d/config/SetRender.js +++ b/src/jlmap3d/config/SetRender.js @@ -1,5 +1,5 @@ export function SetRender(dom) { - var renderer = new THREE.WebGLRenderer();//{antialias: true} + var renderer = new THREE.WebGLRenderer({logarithmicDepthBuffer: true});//{antialias: true} renderer.setSize(dom.offsetWidth, dom.offsetHeight); return renderer; } diff --git a/src/jlmap3d/config/SetScene.js b/src/jlmap3d/config/SetScene.js index 8953e0890..b6fcc8e9b 100644 --- a/src/jlmap3d/config/SetScene.js +++ b/src/jlmap3d/config/SetScene.js @@ -2,7 +2,7 @@ export function SetScene(project) { var scene = new THREE.Scene(); // var cubeTextureLoader = new THREE.CubeTextureLoader(); // - // cubeTextureLoader.setPath( '../../cbtc/static/skybox/star/' ); + // cubeTextureLoader.setPath( '/cbtc/static/skybox/star/' ); // // var cubeTexture = cubeTextureLoader.load( [ // 'px.png', 'nx.png', @@ -13,9 +13,9 @@ export function SetScene(project) { var bgTexture; // console.log(project); if(project == "login" || project == undefined){ - bgTexture = new THREE.TextureLoader().load("../../cbtc/static/background/other.jpg"); + bgTexture = new THREE.TextureLoader().load("/cbtc/static/background/other.jpg"); }else if(project == "heb"){ - bgTexture = new THREE.TextureLoader().load("../../cbtc/static/background/other.jpg"); + bgTexture = new THREE.TextureLoader().load("/cbtc/static/background/other.jpg"); } scene.background = bgTexture; // cubeTexture.dispos e(); diff --git a/src/jlmap3d/edit/connect/newgetmodels.js b/src/jlmap3d/edit/connect/newgetmodels.js index 3fd1d4391..65779a508 100644 --- a/src/jlmap3d/edit/connect/newgetmodels.js +++ b/src/jlmap3d/edit/connect/newgetmodels.js @@ -46,7 +46,7 @@ export function getmodels(data) { packageName:"高位三灯信号机", deviceType:"SimulationSignal", type:"signal", - url:"../../cbtc/static/MODEL/signal/d3d.FBX" + url:"/cbtc/static/MODEL/signal/d3d.FBX" } assets.push(assetsignal); @@ -57,7 +57,7 @@ export function getmodels(data) { packageName:"6节列车", deviceType:"SimulationTrain", type:"train", - url:"../../cbtc/static/MODEL/train/train.FBX" + url:"/cbtc/static/MODEL/train/train.FBX" } assets.push(assettrain); @@ -67,8 +67,8 @@ export function getmodels(data) { packageName:"沙盘驾驶内侧站台", deviceType:"SimulationStation", type:"stationInside", - url:"../../cbtc/static/MODEL/station/fuzhou.FBX" - }//"../../cbtc/static/MODEL/station/fuzhou/fuzhou.FBX" + url:"/cbtc/static/MODEL/station/fuzhou.FBX" + }//"/cbtc/static/MODEL/station/fuzhou/fuzhou.FBX" assets.push(assetneicestation); let assetswitch = { @@ -77,7 +77,7 @@ export function getmodels(data) { packageName:"轨道通用道岔", deviceType:"SimulationSwitch", type:"switch", - url:"../../cbtc/static/MODEL/auto/autoswitch.FBX" + url:"/cbtc/static/MODEL/auto/autoswitch.FBX" } assets.push(assetswitch); @@ -87,7 +87,7 @@ export function getmodels(data) { packageName:"沙盘驾驶外侧站台", deviceType:"SimulationStation", type:"stationOutside", - url:"../../cbtc/static/MODEL/station/waicestation.FBX" + url:"/cbtc/static/MODEL/station/waicestation.FBX" } assets.push(assetwaicestation); @@ -97,7 +97,7 @@ export function getmodels(data) { packageName:"沙盘驾驶三站台", deviceType:"SimulationStation", type:"stationThree", - url:"../../cbtc/static/MODEL/station/station3.FBX" + url:"/cbtc/static/MODEL/station/station3.FBX" } assets.push(assetstation3); @@ -107,7 +107,7 @@ export function getmodels(data) { packageName:"隧道背景", deviceType:"SimulationBg", type:"suidao", - url:"../../cbtc/static/MODEL/suidao/xiansuidao.FBX" + url:"/cbtc/static/MODEL/suidao/xiansuidao.FBX" } assets.push(backgroundmodel); diff --git a/src/jlmap3d/edit/editmodel/TrainList.js b/src/jlmap3d/edit/editmodel/TrainList.js index f1ee86c87..7e183275c 100644 --- a/src/jlmap3d/edit/editmodel/TrainList.js +++ b/src/jlmap3d/edit/editmodel/TrainList.js @@ -19,10 +19,10 @@ export function TrainList() { // model THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() ) ; for(let i=0;i=0; an--) { + actions["traindoor"].down[an].reset(); + actions["traindoor"].down[an].time = 0; + actions["traindoor"].down[an].timeScale = -1; + actions["traindoor"].down[an].play(); + } + }else{ + trainmodel.openright = "1"; + for(let an=actions["traindoor"].down.length-1;an>=0;an--){ + actions["traindoor"].down[an].reset(); + actions["traindoor"].down[an].time = actions["traindoor"].top[an]._clip.duration; + actions["traindoor"].down[an].timeScale = 1; + actions["traindoor"].down[an].play(); + } + + } + + if(data.leftDoorCanClose == false){ + trainmodel.openleft = "0"; + for(let an=actions["traindoor"].top.length-1;an>=0;an--){ + actions["traindoor"].top[an].reset(); + actions["traindoor"].top[an].time = 0; + actions["traindoor"].top[an].timeScale = -1; + actions["traindoor"].top[an].play(); + } + }else{ + trainmodel.openleft = "1"; + for(let an=actions["traindoor"].top.length-1;an>=0;an--){ + actions["traindoor"].top[an].reset(); + actions["traindoor"].top[an].time = actions["traindoor"].top[an]._clip.duration; + actions["traindoor"].top[an].timeScale = 1; + actions["traindoor"].top[an].play(); + } + } + + }else{ + if(data.leftDoorCanClose == false){ + + trainmodel.openleft = "0"; + for(let an=actions["traindoor"].top.length-1;an>=0;an--){ + actions["traindoor"].top[an].reset(); + actions["traindoor"].top[an].time = 0; + actions["traindoor"].top[an].timeScale = -1; + actions["traindoor"].top[an].play(); + } + }else{ + trainmodel.openleft = "1"; + for(let an=actions["traindoor"].top.length-1;an>=0;an--){ + actions["traindoor"].top[an].reset(); + actions["traindoor"].top[an].time = actions["traindoor"].top[an]._clip.duration; + actions["traindoor"].top[an].timeScale = 1; + actions["traindoor"].top[an].play(); + } + } + + if(data.rightDoorCanClose == false){ + trainmodel.openright = '0'; + for (let an=actions["traindoor"].down.length-1; an>=0; an--) { + actions["traindoor"].down[an].reset(); + actions["traindoor"].down[an].time = 0; + actions["traindoor"].down[an].timeScale = -1; + actions["traindoor"].down[an].play(); + } + + }else{ + trainmodel.openright = "1"; + for(let an=actions["traindoor"].down.length-1;an>=0;an--){ + actions["traindoor"].down[an].reset(); + actions["traindoor"].down[an].time = actions["traindoor"].top[an]._clip.duration; + actions["traindoor"].down[an].timeScale = 1; + actions["traindoor"].down[an].play(); + } + } + } + + } this.updatamap = function(newsectionlist,newlinklist,newsignallist,newstationstandlist,newtrainlisttest,newrealsectionlist,newrails, materiallist, nowaction, scene) { // console.log(mapdata); // console.log(newtrainlisttest); @@ -115,7 +206,7 @@ export function Jl3dDrivingNew(mixers,updatemmi,sound,translation,routegroup,dri this.socketon = function(topic) { try { // console.log("teststomp"); - scope.teststomp.subscribe(topic, callback, header); + // scope.teststomp.subscribe(topic, callback, header); } catch (error) { console.error('websocket订阅失败'); } @@ -153,9 +244,9 @@ export function Jl3dDrivingNew(mixers,updatemmi,sound,translation,routegroup,dri let data = JSON.parse(Response.body); // stats.update(); // 遍历后台数据 - // console.log(data); + console.log(data); if(data.type == "Train_Position"){ - trainrun(data.body); + nowTrainRun(data.body); return; } @@ -405,13 +496,10 @@ export function Jl3dDrivingNew(mixers,updatemmi,sound,translation,routegroup,dri } + //更新当前驾驶车辆 + function nowTrainRun(data){ - function trainrun(data){ - // console.log(data); - // if(data.code = "336"){ - // console.log(data); - // } - + //改变当前列车code if(data.code != trainmodel.code){ if(trainlisttest.otherTrainList[trainmodel.code] && trainmodel.code){ trainlisttest.otherTrainList[trainmodel.code].offset = 0; @@ -421,11 +509,13 @@ export function Jl3dDrivingNew(mixers,updatemmi,sound,translation,routegroup,dri trainmodel.nowcode = data.code; } + //改变当前列车行驶的区段code if(data.section != trainmodel.nowsection){ trainmodel.nowsection = data.section; trainmodel.curve = rails.sectionrail[data.section].lineleft; } - // console.log(trainmodel.right); + + //判断转向 if(trainmodel.right != data.right){ if(data.right == "0"){ @@ -528,8 +618,8 @@ export function Jl3dDrivingNew(mixers,updatemmi,sound,translation,routegroup,dri let offsetz = pos.z + trainmodel.children[0].position.z; trainmodel.children[0].position.z -= offsetz; - let offsety = pos.y - trainmodel.children[0].position.y; - trainmodel.children[0].position.y += offsetz; + // let offsety = pos.y - trainmodel.children[0].position.y; + // trainmodel.children[0].position.y += offsetz; // trainmodel.position.z = point.z; } @@ -768,8 +858,6 @@ export function Jl3dDrivingNew(mixers,updatemmi,sound,translation,routegroup,dri } } }else{ - - if (trainmodel.openright != data.open && data.open == '0') { trainmodel.openright = '0'; for (let an=actions["traindoor"].down.length-1; an>=0; an--) { @@ -787,11 +875,9 @@ export function Jl3dDrivingNew(mixers,updatemmi,sound,translation,routegroup,dri actions["traindoor"].down[an].play(); } } - } } - } } diff --git a/src/jlmap3d/jl3dmaintainer/Jl3dfaultdevice.js b/src/jlmap3d/jl3dmaintainer/Jl3dfaultdevice.js index b98713e99..425fe3f22 100644 --- a/src/jlmap3d/jl3dmaintainer/Jl3dfaultdevice.js +++ b/src/jlmap3d/jl3dmaintainer/Jl3dfaultdevice.js @@ -701,7 +701,7 @@ export function Jl3dfaultdevice(dom,group,token,skinCode) { } } var beauty = new Image(); - beauty.src = "../../cbtc/static/texture/guide.png"; + beauty.src = "/cbtc/static/texture/guide.png"; //canvas文字贴图方法 //PS:待提炼 增强功能 function getTextCanvas(text){ diff --git a/src/jlmap3d/jl3dmaintainer/Jl3dfaultdevicevr.js b/src/jlmap3d/jl3dmaintainer/Jl3dfaultdevicevr.js index 8541970ed..08680e6eb 100644 --- a/src/jlmap3d/jl3dmaintainer/Jl3dfaultdevicevr.js +++ b/src/jlmap3d/jl3dmaintainer/Jl3dfaultdevicevr.js @@ -73,7 +73,7 @@ export function Jl3dfaultdeviceVR(dom,group,skinCode) { let playerPosition = new THREE.Vector3( -3.5, 0.5, 5.5 ); let targetPosition = new THREE.Vector3(); let pathFinder = new THREE.Pathfinding(); - var textureNav = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/daohang.png' ); + var textureNav = new THREE.TextureLoader().load( '/cbtc/static/vrtest/daohang.png' ); textureNav.wrapS = THREE.RepeatWrapping; textureNav.wrapT=THREE.RepeatWrapping; textureNav.repeat.x = 40; @@ -195,7 +195,7 @@ export function Jl3dfaultdeviceVR(dom,group,skinCode) { // load a resource loaderObj.load( // resource URL - '../../cbtc/static/vrtest/path.obj', + '/cbtc/static/vrtest/path.obj', // called when resource is loaded function ( object ) { @@ -267,7 +267,7 @@ export function Jl3dfaultdeviceVR(dom,group,skinCode) { document.addEventListener( "mousedown", onselect, false ); - let vrwebworker = new Worker("../../cbtc/static/workertest/vrwebworker.js"); + let vrwebworker = new Worker("/cbtc/static/workertest/vrwebworker.js"); let connectmsg = { type:'init', @@ -1144,7 +1144,7 @@ export function Jl3dfaultdeviceVR(dom,group,skinCode) { var loader = new THREE.TextureLoader(); var circle; loader.load( - '../../cbtc/static/vrtest/ring.png', + '/cbtc/static/vrtest/ring.png', // onLoad callback function ( texture ) { @@ -1404,7 +1404,7 @@ export function Jl3dfaultdeviceVR(dom,group,skinCode) { } } var beauty = new Image(); - beauty.src = "../../cbtc/static/texture/scene.png"; + beauty.src = "/cbtc/static/texture/scene.png"; //canvas文字贴图方法 //PS:待提炼 增强功能 function getTextCanvas(text){ diff --git a/src/jlmap3d/jl3dmaintainer/Jl3dothervr.js b/src/jlmap3d/jl3dmaintainer/Jl3dothervr.js index f6f9ad5e6..8ba82c7d1 100644 --- a/src/jlmap3d/jl3dmaintainer/Jl3dothervr.js +++ b/src/jlmap3d/jl3dmaintainer/Jl3dothervr.js @@ -170,7 +170,7 @@ export function Jl3dOtherVR(dom,group,skinCode) { this.controls.update(); - let otherVrViewWorker = new Worker("../../cbtc/static/workertest/otherVrViewWorker.js"); + let otherVrViewWorker = new Worker("/cbtc/static/workertest/otherVrViewWorker.js"); let connectmsg = { type:'init', @@ -822,7 +822,7 @@ export function Jl3dOtherVR(dom,group,skinCode) { } } var beauty = new Image(); - beauty.src = "../../cbtc/static/texture/scene.png"; + beauty.src = "/cbtc/static/texture/scene.png"; //canvas文字贴图方法 //PS:待提炼 增强功能 function getTextCanvas(text){ diff --git a/src/jlmap3d/jl3dmaintainer/jl3dTrainRescue/jl3dTrainRescueVr.js b/src/jlmap3d/jl3dmaintainer/jl3dTrainRescue/jl3dTrainRescueVr.js index a3e87e29d..465878c0b 100644 --- a/src/jlmap3d/jl3dmaintainer/jl3dTrainRescue/jl3dTrainRescueVr.js +++ b/src/jlmap3d/jl3dmaintainer/jl3dTrainRescue/jl3dTrainRescueVr.js @@ -105,7 +105,7 @@ export function Jl3dTrainRescueVr(dom,group,skinCode) { this.controls.update(); this.modelmanager = new ModelManagerVR(); loader.load( - '../../cbtc/static/vrtest/ring.png', + '/cbtc/static/vrtest/ring.png', // onLoad callback function ( texture ) { @@ -163,7 +163,7 @@ export function Jl3dTrainRescueVr(dom,group,skinCode) { document.addEventListener( "mousedown", onselect, false ); - let vrwebworker = new Worker("../../cbtc/static/workertest/vrwebworker.js"); + let vrwebworker = new Worker("/cbtc/static/workertest/vrwebworker.js"); let connectmsg = { type:'init', @@ -611,7 +611,7 @@ export function Jl3dTrainRescueVr(dom,group,skinCode) { } } var beauty = new Image(); - beauty.src = "../../cbtc/static/texture/scene.png"; + beauty.src = "/cbtc/static/texture/scene.png"; //canvas文字贴图方法 //PS:待提炼 增强功能 function getTextCanvas(text){ diff --git a/src/jlmap3d/jl3dmaintainer/jl3dTrainRescue/trainrescueconfig.js b/src/jlmap3d/jl3dmaintainer/jl3dTrainRescue/trainrescueconfig.js index 75fe215e7..154f38538 100644 --- a/src/jlmap3d/jl3dmaintainer/jl3dTrainRescue/trainrescueconfig.js +++ b/src/jlmap3d/jl3dmaintainer/jl3dTrainRescue/trainrescueconfig.js @@ -4,36 +4,36 @@ var TrainRescueStatic = { name: "动画模型", deviceType: "animatemodel", type: "animatemodel", - url: "../../cbtc/static/vrtest/trainrescue/DH923.FBX" + url: "/cbtc/static/vrtest/trainrescue/DH923.FBX" },//"https://joylink.club/oss/wx/switch/switch.FBX" - //../../cbtc/static/MODEL/device/switch.FBX + ///cbtc/static/MODEL/device/switch.FBX floorplane: { id: "2", name: "行走范围", deviceType: "runplane", type: "runplane", - url: "../../cbtc/static/vrtest/trainrescue/diban.FBX" + url: "/cbtc/static/vrtest/trainrescue/diban.FBX" }, controlmodel: { id: "3", name: "控制台", deviceType: "controlmodel", type: "controlmodel", - url: "../../cbtc/static/vrtest/trainrescue/kongzhigan.FBX" + url: "/cbtc/static/vrtest/trainrescue/kongzhigan.FBX" }, other: { id: "4", name: "其他", deviceType: "other", type: "other", - url: "../../cbtc/static/vrtest/trainrescue/TK.FBX" + url: "/cbtc/static/vrtest/trainrescue/TK.FBX" }, handR: { id: "5", name: "handr", deviceType: "handr", type: "handr", - url: "../../cbtc/static/vrtest/trainrescue/handr.FBX" + url: "/cbtc/static/vrtest/trainrescue/handr.FBX" } } diff --git a/src/jlmap3d/jl3dmaintainer/jlmap3dmaintainer.js b/src/jlmap3d/jl3dmaintainer/jlmap3dmaintainer.js index 51ec22ccf..2c543452b 100644 --- a/src/jlmap3d/jl3dmaintainer/jlmap3dmaintainer.js +++ b/src/jlmap3d/jl3dmaintainer/jlmap3dmaintainer.js @@ -102,7 +102,7 @@ export function JLmap3dMaintainer(dom, data,skinCode,storemod,routegroup,project //地图模型数据 let mapdata = new Jl3ddata(); - this.webwork = new Worker("../../cbtc/static/workertest/trainworker.js"); + this.webwork = new Worker("/cbtc/static/workertest/trainworker.js"); //初始化加载数据和模型getPublishMapDetail getPublishMapDetail(skinCode).then(data => { let mapnetdata = data.data; @@ -124,7 +124,7 @@ export function JLmap3dMaintainer(dom, data,skinCode,storemod,routegroup,project }else{ scope.datatype = "new"; - scope.jsonwebworknew = new Worker("../../cbtc/static/workertest/maintainerworker.js"); + scope.jsonwebworknew = new Worker("/cbtc/static/workertest/maintainerworker.js"); scope.Subscribe = new Maintainerconnect(scope,routegroup,scope.jsonwebworknew,lablecodemap); scope.Subscribe.socketon(scope.Subscribe.topic); MaintainerLoad(mapnetdata,scope,netdata.data,mapdata,camera,controls,scenesimulation,storemod); diff --git a/src/jlmap3d/jl3dmaintainer/utils/trainControl.js b/src/jlmap3d/jl3dmaintainer/utils/trainControl.js index 2211e01e6..6436a67f7 100644 --- a/src/jlmap3d/jl3dmaintainer/utils/trainControl.js +++ b/src/jlmap3d/jl3dmaintainer/utils/trainControl.js @@ -7,7 +7,7 @@ export function TrainControl(){ // load a sound and set it as the Audio object's buffer let audioLoader = new THREE.AudioLoader(); - audioLoader.load( '../../cbtc/static/audio/trainmove.ogg', function( buffer ) { + audioLoader.load( '/cbtc/static/audio/trainmove.ogg', function( buffer ) { scope.sound.setBuffer( buffer ); scope.sound.setLoop( true ); scope.sound.setVolume( 3 ); diff --git a/src/jlmap3d/jl3dmaintainer/utils/vrgui.js b/src/jlmap3d/jl3dmaintainer/utils/vrgui.js index d439c8e40..ca953368d 100644 --- a/src/jlmap3d/jl3dmaintainer/utils/vrgui.js +++ b/src/jlmap3d/jl3dmaintainer/utils/vrgui.js @@ -17,7 +17,7 @@ export function VrGui(){ var geometryPlane = new THREE.PlaneBufferGeometry( 1.4, 2, 16); - var texturePlane = new THREE.TextureLoader().load( '../../cbtc/static/texture/devicemsgpane.png' ); + var texturePlane = new THREE.TextureLoader().load( '/cbtc/static/texture/devicemsgpane.png' ); var materialPlane = new THREE.MeshBasicMaterial( { map: texturePlane, side: THREE.DoubleSide, @@ -36,7 +36,7 @@ export function VrGui(){ var geometryButtonPane = new THREE.PlaneBufferGeometry( 0.4, 0.2, 16 ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/buttonHome.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/buttonHome.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -48,7 +48,7 @@ export function VrGui(){ buttonHome.position.z = 0.02; plane.add( buttonHome ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/buttonStationList.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/buttonStationList.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -60,7 +60,7 @@ export function VrGui(){ buttonStationList.position.z = 0.02; plane.add( buttonStationList ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/buttonFaultList.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/buttonFaultList.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -78,7 +78,7 @@ export function VrGui(){ var geometryButtonPage = new THREE.PlaneBufferGeometry( 0.3, 0.1, 16 ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/buttonPrevious.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/buttonPrevious.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -90,7 +90,7 @@ export function VrGui(){ buttonPreviousFault.position.z = 0.02; scope.faultListGroup.add( buttonPreviousFault ); // - // var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/nav.png' ); + // var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/nav.png' ); // var material = new THREE.MeshBasicMaterial( { // map: texture, // transparent: true, @@ -102,7 +102,7 @@ export function VrGui(){ // buttonPrevious.position.z = 0.02; // plane.add( buttonPrevious ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/buttonNext.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/buttonNext.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -114,7 +114,7 @@ export function VrGui(){ buttonNextFault.position.z = 0.02; scope.faultListGroup.add( buttonNextFault ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/buttonPrevious.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/buttonPrevious.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -127,7 +127,7 @@ export function VrGui(){ scope.stationListGroup.add( buttonPreviousStation ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/buttonNext.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/buttonNext.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -145,7 +145,7 @@ export function VrGui(){ var geometryButtonBox = new THREE.PlaneBufferGeometry( 0.25, 0.25, 16 ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/nav.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/nav.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -157,7 +157,7 @@ export function VrGui(){ button1.position.z = 0.02; scope.deviceGroup.add( button1 ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/anime.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/anime.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -171,7 +171,7 @@ export function VrGui(){ - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/reset.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/reset.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -183,7 +183,7 @@ export function VrGui(){ button2.position.z = 0.02; scope.deviceGroup.add( button2 ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/repire.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/repire.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -208,7 +208,7 @@ export function VrGui(){ var geometryButtonBox = new THREE.PlaneBufferGeometry( 0.3, 0.3, 16 ); - var textureshebei = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/shebei.png' ); + var textureshebei = new THREE.TextureLoader().load( '/cbtc/static/vrtest/shebei.png' ); var material = new THREE.MeshBasicMaterial( { map: textureshebei, transparent: true, @@ -220,7 +220,7 @@ export function VrGui(){ buttonShiBei.position.z = 0.02; plane.add( buttonShiBei ); - var texturezhankong = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/zhankong.png' ); + var texturezhankong = new THREE.TextureLoader().load( '/cbtc/static/vrtest/zhankong.png' ); var material = new THREE.MeshBasicMaterial( { map: texturezhankong, transparent: true, @@ -234,7 +234,7 @@ export function VrGui(){ - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/station1.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/station1.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -246,7 +246,7 @@ export function VrGui(){ buttonStation1.position.z = 0.02; plane.add( buttonStation1 ); - var texture = new THREE.TextureLoader().load( '../../cbtc/static/vrtest/station2.png' ); + var texture = new THREE.TextureLoader().load( '/cbtc/static/vrtest/station2.png' ); var material = new THREE.MeshBasicMaterial( { map: texture, transparent: true, @@ -452,7 +452,7 @@ export function VrGui(){ var beauty = new Image(); - beauty.src = "../../cbtc/static/texture/scene.png"; + beauty.src = "/cbtc/static/texture/scene.png"; //canvas文字贴图方法 //PS:待提炼 增强功能 function getTextCanvas(text,type){ diff --git a/src/jlmap3d/jl3dmaintainer/utils/vrloader.js b/src/jlmap3d/jl3dmaintainer/utils/vrloader.js index 66923e424..b100767f9 100644 --- a/src/jlmap3d/jl3dmaintainer/utils/vrloader.js +++ b/src/jlmap3d/jl3dmaintainer/utils/vrloader.js @@ -6,42 +6,42 @@ export function ModelManagerVR(){ // code:"test", // locateType:"01", // mesh:null, - // url:"../../cbtc/static/vrtest/vrtest.FBX" + // url:"/cbtc/static/vrtest/vrtest.FBX" // }; this.stationIn = { code:"stationIn", locateType:"1", mesh:null, - url:"../../cbtc/static/vrtest/stationIn.FBX" + url:"/cbtc/static/vrtest/stationIn.FBX" }; this.stationOut = { code:"stationOut", locateType:"2", mesh:null, - url:"../../cbtc/static/vrtest/stationIn.FBX" + url:"/cbtc/static/vrtest/stationIn.FBX" }; this.otherDevice = { code:"otherDevice", locateType:"3", mesh:null, - url:"../../cbtc/static/vrtest/otherDevice.FBX" + url:"/cbtc/static/vrtest/otherDevice.FBX" }; this.train = { code:"train", locateType:"4", mesh:null, - url:"../../cbtc/static/vrtest/vrTrain.FBX" + url:"/cbtc/static/vrtest/vrTrain.FBX" }; this.human = { code:"human", locateType:"5", mesh:null, - url:"../../cbtc/static/vrtest/vrHuman.FBX" + url:"/cbtc/static/vrtest/vrHuman.FBX" }; this.loadpromise = function (mixers){ diff --git a/src/jlmap3d/jl3dpassflow/config.js b/src/jlmap3d/jl3dpassflow/config.js index 690243e7d..badf7054d 100644 --- a/src/jlmap3d/jl3dpassflow/config.js +++ b/src/jlmap3d/jl3dpassflow/config.js @@ -5,16 +5,16 @@ var Staticmodel = { deviceType: "man", type: "man", picUrl: "", - assetUrl: "../../cbtc/static/MODEL/passflow/man1.FBX" + assetUrl: "/cbtc/static/MODEL/passflow/man1.FBX" },//"https://joylink.club/oss/wx/switch/switch.FBX" - //../../cbtc/static/MODEL/device/switch.FBX + ///cbtc/static/MODEL/device/switch.FBX man2: { id: "2", name: "人物2", deviceType: "man", type: "man", picUrl: "", - assetUrl: "../../cbtc/static/MODEL/passflow/man2.FBX" + assetUrl: "/cbtc/static/MODEL/passflow/man2.FBX" }, station: { id: "3", @@ -22,7 +22,7 @@ var Staticmodel = { deviceType: "station", type: "hrb", picUrl: "", - assetUrl: "../../cbtc/static/MODEL/passflow/station.FBX" + assetUrl: "/cbtc/static/MODEL/passflow/station.FBX" }, zhajiin: { id: "4", @@ -30,7 +30,7 @@ var Staticmodel = { deviceType: "zhajiin", type: "hrb", picUrl: "", - assetUrl: "../../cbtc/static/MODEL/passflow/zhajiin.FBX" + assetUrl: "/cbtc/static/MODEL/passflow/zhajiin.FBX" }, zhajiout: { id: "5", @@ -38,7 +38,7 @@ var Staticmodel = { deviceType: "zhajiout", type: "hrb", picUrl: "", - assetUrl: "../../cbtc/static/MODEL/passflow/zhajiout.FBX" + assetUrl: "/cbtc/static/MODEL/passflow/zhajiout.FBX" }, monitor: { id: "6", @@ -46,7 +46,7 @@ var Staticmodel = { deviceType: "monitor", type: "hrb", picUrl: "", - assetUrl: "../../cbtc/static/MODEL/passflow/monitor.FBX" + assetUrl: "/cbtc/static/MODEL/passflow/monitor.FBX" }, train: { id: "7", @@ -54,7 +54,7 @@ var Staticmodel = { deviceType: "train", type: "hrb", picUrl: "", - assetUrl: "../../cbtc/static/MODEL/train/train.FBX" + assetUrl: "/cbtc/static/MODEL/train/train.FBX" }, section: { id: "8", @@ -62,7 +62,7 @@ var Staticmodel = { deviceType: "section", type: "hrb", picUrl: "", - assetUrl: "../../cbtc/static/MODEL/passflow/section.FBX" + assetUrl: "/cbtc/static/MODEL/passflow/section.FBX" }, diff --git a/src/jlmap3d/jl3dpassflow/jl3dpassflownew.js b/src/jlmap3d/jl3dpassflow/jl3dpassflownew.js index 08e43ecff..b84f1aa8c 100644 --- a/src/jlmap3d/jl3dpassflow/jl3dpassflownew.js +++ b/src/jlmap3d/jl3dpassflow/jl3dpassflownew.js @@ -52,8 +52,8 @@ let zhajiout = []; let deviceaction = []; -// let passerwebwork = new Worker("../../cbtc/static/workertest/passsimulation/passer.js"); -let stationwebwork = new Worker("../../cbtc/static/workertest/passsimulation/station.js"); +// let passerwebwork = new Worker("/cbtc/static/workertest/passsimulation/passer.js"); +let stationwebwork = new Worker("/cbtc/static/workertest/passsimulation/station.js"); let stationzon = new ZoneManager(); //老版本临时ai控制 @@ -297,7 +297,7 @@ export function Jl3dpassflow(dom,skinCode,routegroup,viewMap) { // load a resource loader.load( // resource URL - '../../cbtc/static/jl3d/path/path.obj', + '/cbtc/static/jl3d/path/path.obj', // called when resource is loaded function ( object ) { diff --git a/src/jlmap3d/jl3dsimulation/connect/Jlmap3dSubscribeNew.js b/src/jlmap3d/jl3dsimulation/connect/Jlmap3dSubscribeNew.js index 32cc7ed4f..ed06590b2 100644 --- a/src/jlmap3d/jl3dsimulation/connect/Jlmap3dSubscribeNew.js +++ b/src/jlmap3d/jl3dsimulation/connect/Jlmap3dSubscribeNew.js @@ -68,7 +68,7 @@ export function Jlmap3dSubscribeNew(jlmap3d,routegroup,jsonwebwork) { for(let i=0,leni=event.data.body.length;i { let mapnetdata = data.data; @@ -111,14 +111,14 @@ export function JLmap3d(dom, data,skinCode,storemod,routegroup,project) { let assetsdata = JSON.parse(netdata.data.sections); if(assetsdata.link){ scope.datatype = "old"; - scope.jsonwebwork = new Worker("../../cbtc/static/workertest/jsonworker.js"); + scope.jsonwebwork = new Worker("/cbtc/static/workertest/jsonworker.js"); scope.Subscribe = new Jlmap3dSubscribe(scope,routegroup,scope.jsonwebwork); scope.Subscribe.socketon(scope.Subscribe.topic); SimulationLoad(mapnetdata,scope,netdata.data,mapdata,camera,controls,scenesimulation,storemod); }else{ scope.datatype = "new"; - scope.jsonwebworknew = new Worker("../../cbtc/static/workertest/jsonworkernew.js"); + scope.jsonwebworknew = new Worker("/cbtc/static/workertest/jsonworkernew.js"); scope.Subscribe = new Jlmap3dSubscribeNew(scope,routegroup,scope.jsonwebworknew); scope.Subscribe.socketon(scope.Subscribe.topic); SimulationLoadNew(mapnetdata,scope,netdata.data,mapdata,camera,controls,scenesimulation,storemod); diff --git a/src/jlmap3d/main/loaders/AssetLoader.js b/src/jlmap3d/main/loaders/AssetLoader.js index f0c921d89..e1ee80de0 100644 --- a/src/jlmap3d/main/loaders/AssetLoader.js +++ b/src/jlmap3d/main/loaders/AssetLoader.js @@ -11,7 +11,7 @@ let defaultsignal = { deviceType:"signal", type:"low", picUrl:"", - url:"../../cbtc/static/MODEL/signal/d3d.FBX" + url:"/cbtc/static/MODEL/signal/d3d.FBX" } let defaulttrain = { @@ -20,7 +20,7 @@ let defaulttrain = { deviceType:"train", type:"num4", picUrl:"", - url:"../../cbtc/static/MODEL/train/train.FBX" + url:"/cbtc/static/MODEL/train/train.FBX" } @@ -30,15 +30,15 @@ let defaultstation = { deviceType:"stand", type:"num4", picUrl:"", - url:"../../cbtc/static/MODEL/station/fuzhou.FBX" -}//"../../cbtc/static/MODEL/station/fuzhou/fuzhou.FBX" + url:"/cbtc/static/MODEL/station/fuzhou.FBX" +}//"/cbtc/static/MODEL/station/fuzhou/fuzhou.FBX" let waicestation = { id:"10", name:"外侧车站", deviceType:"standwaice", type:"num4", picUrl:"", - url:"../../cbtc/static/MODEL/station/waicestation.FBX" + url:"/cbtc/static/MODEL/station/waicestation.FBX" } let station3 = { id:"10000", @@ -46,12 +46,12 @@ let station3 = { deviceType:"station3", type:"num4", picUrl:"", - url:"../../cbtc/static/MODEL/station/station3.FBX" + url:"/cbtc/static/MODEL/station/station3.FBX" } -//"../../cbtc/static/MODEL/station/fuzhou/fuzhou.FBX" +//"/cbtc/static/MODEL/station/fuzhou/fuzhou.FBX" //https://test.joylink.club/oss/station/fuzhou/fuzhou.FBX //https://joylink.club/oss/station/fuzhou/fuzhou.FBX -//../../cbtc/static/MODEL/station/zhantai715(2).FBX +///cbtc/static/MODEL/station/zhantai715(2).FBX let defaultswitch = { id:"11", @@ -59,7 +59,7 @@ let defaultswitch = { deviceType:"switch", type:"fuzhou", picUrl:"", - url:"../../cbtc/static/MODEL/daocha/daocha.FBX" + url:"/cbtc/static/MODEL/daocha/daocha.FBX" } @@ -69,7 +69,7 @@ let defaultsuidao = { deviceType:"suidao", type:"suidao", picUrl:"", - url:"../../cbtc/static/MODEL/suidao/suidao.FBX" + url:"/cbtc/static/MODEL/suidao/suidao.FBX" } let hebsuidao = { @@ -78,7 +78,7 @@ let hebsuidao = { deviceType:"hebsuidao", type:"hebsuidao", picUrl:"", - url:"../../cbtc/static/MODEL/suidao/hebsuidao.FBX" + url:"/cbtc/static/MODEL/suidao/hebsuidao.FBX" } let nbsuidao = { id:"1010", @@ -86,7 +86,7 @@ let nbsuidao = { deviceType:"nbsuidao", type:"nbsuidao", picUrl:"", - url:"../../cbtc/static/MODEL/suidao/nbsuidao.FBX" + url:"/cbtc/static/MODEL/suidao/nbsuidao.FBX" } let defaultbackground = { id:"16", @@ -94,12 +94,12 @@ let defaultbackground = { deviceType:"background", type:"background", picUrl:"", - url:"../../cbtc/static/MODEL/suidao/background.FBX" + url:"/cbtc/static/MODEL/suidao/background.FBX" } -//"../../cbtc/static/MODEL/suidao/suidao.FBX" +//"/cbtc/static/MODEL/suidao/suidao.FBX" //https://test.joylink.club/oss/suidao/suidao.FBX //https://joylink.club/oss/suidao/suidao.FBX -//../../cbtc/static/MODEL/suidao/suidao.FBX +///cbtc/static/MODEL/suidao/suidao.FBX let defaultautorail = { id:"100", @@ -107,7 +107,7 @@ let defaultautorail = { deviceType:"autorail", type:"autorail", picUrl:"", - url:"../../cbtc/static/MODEL/auto/rail.FBX" + url:"/cbtc/static/MODEL/auto/rail.FBX" } let autoswitch = { @@ -116,7 +116,7 @@ let autoswitch = { deviceType:"autoswitch", type:"fuzhou", picUrl:"", - url:"../../cbtc/static/MODEL/auto/autoswitch.FBX" + url:"/cbtc/static/MODEL/auto/autoswitch.FBX" } let autoswitch1 = { @@ -125,7 +125,7 @@ let autoswitch1 = { deviceType:"autoswitch1", type:"fuzhou", picUrl:"", - url:"../../cbtc/static/MODEL/auto/switch1.FBX" + url:"/cbtc/static/MODEL/auto/switch1.FBX" } let autoswitch2 = { @@ -134,7 +134,7 @@ let autoswitch2 = { deviceType:"autoswitch2", type:"fuzhou", picUrl:"", - url:"../../cbtc/static/MODEL/auto/switch2.FBX" + url:"/cbtc/static/MODEL/auto/switch2.FBX" } export function AssetLoader(){ diff --git a/src/jlmap3d/main/loaders/FBXLoader1.js b/src/jlmap3d/main/loaders/FBXLoader1.js deleted file mode 100644 index 1f976e091..000000000 --- a/src/jlmap3d/main/loaders/FBXLoader1.js +++ /dev/null @@ -1,4149 +0,0 @@ - -/** - * @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 ) { - - if(materialNode.Opacity.value<1 && materialNode.Opacity.value>0.9){ - parameters.side = THREE.DoubleSide; - parameters.transparent = true; - // parameters.alphaTest = 0.7; - parameters.opacity = 1; - - }else{ - parameters.opacity = parseFloat( materialNode.Opacity.value ); - } - - } - - if ( materialNode.Opacity ) { - if(materialNode.Opacity.value<1 && materialNode.Opacity.value>0.9){ - parameters.side = THREE.DoubleSide; - parameters.transparent = true; - // parameters.alphaTest = 0.7; - parameters.opacity = 1; - }else{ - parameters.opacity = parseFloat( materialNode.Opacity.value ); - } - } - - if ( parameters.opacity < 1.0 ) { - - parameters.transparent = true; - // parameters.alphaTest = 0.1; - } - - 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; - parameters.alphaTest = 0.1; - 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 deleted file mode 100644 index d1b5d9db1..000000000 --- a/src/jlmap3d/main/loaders/FBXLoader2.js +++ /dev/null @@ -1,3861 +0,0 @@ -/** - * @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 route 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 route 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 route 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 route 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 route 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 route 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/Materialload.js b/src/jlmap3d/main/loaders/Materialload.js index cb065aa30..2f23697ed 100644 --- a/src/jlmap3d/main/loaders/Materialload.js +++ b/src/jlmap3d/main/loaders/Materialload.js @@ -2,13 +2,13 @@ import { BASE_ASSET_API } from '@/api/jlmap3d/assets3d.js'; export function Materialload(jlmap3dedit,standTextureData){ if(jlmap3dedit.materiallist){ - settexture(jlmap3dedit.materiallist,"red",'../../cbtc/static/material/signal/1.jpg'); + settexture(jlmap3dedit.materiallist,"red",'/cbtc/static/material/signal/1.jpg'); - settexture(jlmap3dedit.materiallist,"yellow",'../../cbtc/static/material/signal/2.jpg'); + settexture(jlmap3dedit.materiallist,"yellow",'/cbtc/static/material/signal/2.jpg'); - settexture( jlmap3dedit.materiallist,"green",'../../cbtc/static/material/signal/3.jpg'); + settexture( jlmap3dedit.materiallist,"green",'/cbtc/static/material/signal/3.jpg'); - settexture( jlmap3dedit.materiallist,"black",'../../cbtc/static/material/signal/5.jpg'); + settexture( jlmap3dedit.materiallist,"black",'/cbtc/static/material/signal/5.jpg'); } if(standTextureData.urls.length > 1){ @@ -20,98 +20,98 @@ export function Materialload(jlmap3dedit,standTextureData){ // if(assettype){ // if(assettype.stationtexture == "xian3"){ // if(jlmap3dedit.stationtexture){ - // setstationtexture(jlmap3dedit.stationtexture,"stationlist",'../../cbtc/static/texture/xian3/xian3list.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"pingbimen",'../../cbtc/static/texture/xian3/pingbimen.png'); - // setstationtexture(jlmap3dedit.stationtexture,"Station18000",'../../cbtc/static/texture/xian3/Station18000.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station61238",'../../cbtc/static/texture/xian3/Station61238.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station41790",'../../cbtc/static/texture/xian3/Station41790.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station67945",'../../cbtc/static/texture/xian3/Station67945.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station85598",'../../cbtc/static/texture/xian3/Station85598.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station4324",'../../cbtc/static/texture/xian3/Station4324.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station55755",'../../cbtc/static/texture/xian3/Station55755.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station53597",'../../cbtc/static/texture/xian3/Station53597.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station68029",'../../cbtc/static/texture/xian3/Station68029.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station17596",'../../cbtc/static/texture/xian3/Station17596.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station66742",'../../cbtc/static/texture/xian3/Station66742.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station90701",'../../cbtc/static/texture/xian3/Station90701.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station90652",'../../cbtc/static/texture/xian3/Station90652.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station54200",'../../cbtc/static/texture/xian3/Station54200.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station19253",'../../cbtc/static/texture/xian3/Station19253.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station37821",'../../cbtc/static/texture/xian3/Station37821.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station47557",'../../cbtc/static/texture/xian3/Station47557.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station78551",'../../cbtc/static/texture/xian3/Station78551.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station64474",'../../cbtc/static/texture/xian3/Station64474.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station21203",'../../cbtc/static/texture/xian3/Station21203.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station34499",'../../cbtc/static/texture/xian3/Station34499.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station571",'../../cbtc/static/texture/xian3/Station571.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station99903",'../../cbtc/static/texture/xian3/Station99903.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station43447",'../../cbtc/static/texture/xian3/Station43447.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station67917",'../../cbtc/static/texture/xian3/Station67917.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station60649",'../../cbtc/static/texture/xian3/Station60649.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"stationlist",'/cbtc/static/texture/xian3/xian3list.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"pingbimen",'/cbtc/static/texture/xian3/pingbimen.png'); + // setstationtexture(jlmap3dedit.stationtexture,"Station18000",'/cbtc/static/texture/xian3/Station18000.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station61238",'/cbtc/static/texture/xian3/Station61238.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station41790",'/cbtc/static/texture/xian3/Station41790.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station67945",'/cbtc/static/texture/xian3/Station67945.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station85598",'/cbtc/static/texture/xian3/Station85598.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station4324",'/cbtc/static/texture/xian3/Station4324.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station55755",'/cbtc/static/texture/xian3/Station55755.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station53597",'/cbtc/static/texture/xian3/Station53597.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station68029",'/cbtc/static/texture/xian3/Station68029.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station17596",'/cbtc/static/texture/xian3/Station17596.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station66742",'/cbtc/static/texture/xian3/Station66742.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station90701",'/cbtc/static/texture/xian3/Station90701.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station90652",'/cbtc/static/texture/xian3/Station90652.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station54200",'/cbtc/static/texture/xian3/Station54200.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station19253",'/cbtc/static/texture/xian3/Station19253.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station37821",'/cbtc/static/texture/xian3/Station37821.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station47557",'/cbtc/static/texture/xian3/Station47557.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station78551",'/cbtc/static/texture/xian3/Station78551.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station64474",'/cbtc/static/texture/xian3/Station64474.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station21203",'/cbtc/static/texture/xian3/Station21203.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station34499",'/cbtc/static/texture/xian3/Station34499.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station571",'/cbtc/static/texture/xian3/Station571.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station99903",'/cbtc/static/texture/xian3/Station99903.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station43447",'/cbtc/static/texture/xian3/Station43447.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station67917",'/cbtc/static/texture/xian3/Station67917.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station60649",'/cbtc/static/texture/xian3/Station60649.jpg'); // } // } // if(assettype.stationtexture == "haerbin1"){ // if(jlmap3dedit.stationtexture){ - // setstationtexture(jlmap3dedit.stationtexture,"stationlist",'../../cbtc/static/texture/heb/haerbinlist.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"pingbimen",'../../cbtc/static/texture/heb/pingbimen.png'); - // setstationtexture(jlmap3dedit.stationtexture,"Station5361",'../../cbtc/static/texture/heb/Station5361.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station11094",'../../cbtc/static/texture/heb/Station11094.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station11136",'../../cbtc/static/texture/heb/Station11136.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station17293",'../../cbtc/static/texture/heb/Station17293.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station18398",'../../cbtc/static/texture/heb/Station18398.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station22163",'../../cbtc/static/texture/heb/Station22163.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station25464",'../../cbtc/static/texture/heb/Station25464.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station28090",'../../cbtc/static/texture/heb/Station28090.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station41999",'../../cbtc/static/texture/heb/Station41999.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station44338",'../../cbtc/static/texture/heb/Station44338.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station46464",'../../cbtc/static/texture/heb/Station46464.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station50565",'../../cbtc/static/texture/heb/Station50565.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station60068",'../../cbtc/static/texture/heb/Station60068.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station62429",'../../cbtc/static/texture/heb/Station62429.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station64444",'../../cbtc/static/texture/heb/Station64444.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station71700",'../../cbtc/static/texture/heb/Station71700.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station72132",'../../cbtc/static/texture/heb/Station72132.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station78164",'../../cbtc/static/texture/heb/Station78164.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station79537",'../../cbtc/static/texture/heb/Station79537.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station82618",'../../cbtc/static/texture/heb/Station82618.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station85520",'../../cbtc/static/texture/heb/Station85520.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station89483",'../../cbtc/static/texture/heb/Station89483.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station96090",'../../cbtc/static/texture/heb/Station96090.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"stationlist",'/cbtc/static/texture/heb/haerbinlist.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"pingbimen",'/cbtc/static/texture/heb/pingbimen.png'); + // setstationtexture(jlmap3dedit.stationtexture,"Station5361",'/cbtc/static/texture/heb/Station5361.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station11094",'/cbtc/static/texture/heb/Station11094.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station11136",'/cbtc/static/texture/heb/Station11136.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station17293",'/cbtc/static/texture/heb/Station17293.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station18398",'/cbtc/static/texture/heb/Station18398.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station22163",'/cbtc/static/texture/heb/Station22163.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station25464",'/cbtc/static/texture/heb/Station25464.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station28090",'/cbtc/static/texture/heb/Station28090.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station41999",'/cbtc/static/texture/heb/Station41999.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station44338",'/cbtc/static/texture/heb/Station44338.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station46464",'/cbtc/static/texture/heb/Station46464.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station50565",'/cbtc/static/texture/heb/Station50565.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station60068",'/cbtc/static/texture/heb/Station60068.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station62429",'/cbtc/static/texture/heb/Station62429.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station64444",'/cbtc/static/texture/heb/Station64444.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station71700",'/cbtc/static/texture/heb/Station71700.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station72132",'/cbtc/static/texture/heb/Station72132.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station78164",'/cbtc/static/texture/heb/Station78164.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station79537",'/cbtc/static/texture/heb/Station79537.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station82618",'/cbtc/static/texture/heb/Station82618.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station85520",'/cbtc/static/texture/heb/Station85520.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station89483",'/cbtc/static/texture/heb/Station89483.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station96090",'/cbtc/static/texture/heb/Station96090.jpg'); // } // } // if(assettype.stationtexture == "nb1"){ // if(jlmap3dedit.stationtexture){ - // setstationtexture(jlmap3dedit.stationtexture,"stationlist",'../../cbtc/static/texture/nb1/nb1list.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"pingbimen",'../../cbtc/static/texture/nb1/pingbimen.png'); - // setstationtexture(jlmap3dedit.stationtexture,"Station1533",'../../cbtc/static/texture/nb1/Station1533.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station11231",'../../cbtc/static/texture/nb1/Station11231.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station12421",'../../cbtc/static/texture/nb1/Station12421.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station16356",'../../cbtc/static/texture/nb1/Station16356.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station18678",'../../cbtc/static/texture/nb1/Station18678.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station19817",'../../cbtc/static/texture/nb1/Station19817.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station23567",'../../cbtc/static/texture/nb1/Station23567.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station24903",'../../cbtc/static/texture/nb1/Station24903.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station26416",'../../cbtc/static/texture/nb1/Station26416.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station27207",'../../cbtc/static/texture/nb1/Station27207.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station28032",'../../cbtc/static/texture/nb1/Station28032.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station28958",'../../cbtc/static/texture/nb1/Station28958.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station30520",'../../cbtc/static/texture/nb1/Station30520.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station32955",'../../cbtc/static/texture/nb1/Station32955.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station38286",'../../cbtc/static/texture/nb1/Station38286.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station38674",'../../cbtc/static/texture/nb1/Station38674.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station44173",'../../cbtc/static/texture/nb1/Station44173.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station63100",'../../cbtc/static/texture/nb1/Station63100.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station69378",'../../cbtc/static/texture/nb1/Station69378.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station72310",'../../cbtc/static/texture/nb1/Station72310.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station72764",'../../cbtc/static/texture/nb1/Station72764.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station75414",'../../cbtc/static/texture/nb1/Station75414.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station75494",'../../cbtc/static/texture/nb1/Station75494.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station75703",'../../cbtc/static/texture/nb1/Station75703.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station78392",'../../cbtc/static/texture/nb1/Station78392.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station81700",'../../cbtc/static/texture/nb1/Station81700.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station84098",'../../cbtc/static/texture/nb1/Station84098.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station86112",'../../cbtc/static/texture/nb1/Station86112.jpg'); - // setstationtexture(jlmap3dedit.stationtexture,"Station99584",'../../cbtc/static/texture/nb1/Station99584.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"stationlist",'/cbtc/static/texture/nb1/nb1list.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"pingbimen",'/cbtc/static/texture/nb1/pingbimen.png'); + // setstationtexture(jlmap3dedit.stationtexture,"Station1533",'/cbtc/static/texture/nb1/Station1533.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station11231",'/cbtc/static/texture/nb1/Station11231.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station12421",'/cbtc/static/texture/nb1/Station12421.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station16356",'/cbtc/static/texture/nb1/Station16356.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station18678",'/cbtc/static/texture/nb1/Station18678.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station19817",'/cbtc/static/texture/nb1/Station19817.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station23567",'/cbtc/static/texture/nb1/Station23567.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station24903",'/cbtc/static/texture/nb1/Station24903.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station26416",'/cbtc/static/texture/nb1/Station26416.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station27207",'/cbtc/static/texture/nb1/Station27207.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station28032",'/cbtc/static/texture/nb1/Station28032.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station28958",'/cbtc/static/texture/nb1/Station28958.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station30520",'/cbtc/static/texture/nb1/Station30520.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station32955",'/cbtc/static/texture/nb1/Station32955.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station38286",'/cbtc/static/texture/nb1/Station38286.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station38674",'/cbtc/static/texture/nb1/Station38674.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station44173",'/cbtc/static/texture/nb1/Station44173.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station63100",'/cbtc/static/texture/nb1/Station63100.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station69378",'/cbtc/static/texture/nb1/Station69378.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station72310",'/cbtc/static/texture/nb1/Station72310.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station72764",'/cbtc/static/texture/nb1/Station72764.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station75414",'/cbtc/static/texture/nb1/Station75414.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station75494",'/cbtc/static/texture/nb1/Station75494.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station75703",'/cbtc/static/texture/nb1/Station75703.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station78392",'/cbtc/static/texture/nb1/Station78392.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station81700",'/cbtc/static/texture/nb1/Station81700.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station84098",'/cbtc/static/texture/nb1/Station84098.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station86112",'/cbtc/static/texture/nb1/Station86112.jpg'); + // setstationtexture(jlmap3dedit.stationtexture,"Station99584",'/cbtc/static/texture/nb1/Station99584.jpg'); // } // } // } diff --git a/src/jlmap3d/main/loaders/SimulationLoadNew.js b/src/jlmap3d/main/loaders/SimulationLoadNew.js index d4fd3a759..bc8798895 100644 --- a/src/jlmap3d/main/loaders/SimulationLoadNew.js +++ b/src/jlmap3d/main/loaders/SimulationLoadNew.js @@ -84,7 +84,7 @@ export function SimulationLoadNew(data,scope,netdata,mapdata,camera,controls,sce } } - assetloader.setModelListOver(JSON.parse(netdata.assets).sceneAssetList); + assetloader.setModelListOver(JSON.parse(netdata.assets).sceneAssetList,3); // assetloader.setmodellistnew(netdata.assets); diff --git a/src/jlmap3d/main/model/StationStandList.js b/src/jlmap3d/main/model/StationStandList.js index 03bd2a46f..d966fcf90 100644 --- a/src/jlmap3d/main/model/StationStandList.js +++ b/src/jlmap3d/main/model/StationStandList.js @@ -226,7 +226,7 @@ export function StationStandList() { //canvas文字贴图方法 //PS:待提炼 增强功能 var stationcanvas = new Image(); -stationcanvas.src = "../../cbtc/static/texture/msgt.png"; +stationcanvas.src = "/cbtc/static/texture/msgt.png"; function getTextCanvas(text){ let canvas = document.getElementById('canvastexture'); diff --git a/src/jlmap3d/main/model/TrainList.js b/src/jlmap3d/main/model/TrainList.js index d39cf7e5f..9f470f2a8 100644 --- a/src/jlmap3d/main/model/TrainList.js +++ b/src/jlmap3d/main/model/TrainList.js @@ -213,7 +213,7 @@ let onProgress = function ( xhr ) { let onError = function () { }; var beauty = new Image(); -beauty.src = "../../cbtc/static/texture/guide.png"; +beauty.src = "/cbtc/static/texture/guide.png"; //canvas文字贴图方法 //PS:待提炼 增强功能 function getTextCanvas(text){ diff --git a/src/jlmap3d/main/newmodel/RailListN.js b/src/jlmap3d/main/newmodel/RailListN.js index 5354d8bbe..ef15167a9 100644 --- a/src/jlmap3d/main/newmodel/RailListN.js +++ b/src/jlmap3d/main/newmodel/RailListN.js @@ -147,9 +147,9 @@ export function RailListN(){ for(let i=0;i{ rightpoints.push(item); @@ -157,7 +157,8 @@ export function RailListN(){ ); rightpoints.reverse(); scope.sectionrail[section[i].code].lineright = new THREE.CatmullRomCurve3(rightpoints); - scope.sectionrail[section[i].code].lineright.curveType = "centripetal"; + scope.sectionrail[section[i].code].lineright.curveType = "chordal"; + scope.sectionrail[section[i].code].lineright.tension = 0.15; scope.sectionrail[section[i].code].lineright.name = section[i].code; if(section[i].standTrack == true){ scope.sectionrail[section[i].code].standTrack = true; @@ -240,37 +241,37 @@ export function RailListN(){ scope.sectionrail[sectioncode].lineright.closed = false; scope.sectionrail[sectioncode].lineright.name = sectioncode; - // for(let n=0,nm=scope.sectionrail[sectioncode].lineleft.points.length;n=3){ // console.log(newsection); let height = Math.random()/1000; - var closedSpline = new THREE.CatmullRomCurve3( [ - new THREE.Vector3(newsection.railpoint[0].x,newsection.railpoint[0].y,newsection.railpoint[0].z), - new THREE.Vector3(newsection.railpoint[1].x,newsection.railpoint[1].y,newsection.railpoint[1].z+0.0001), - new THREE.Vector3(newsection.railpoint[2].x,newsection.railpoint[2].y,newsection.railpoint[2].z) - ] ); + var closedSpline; + if(Math.abs(newsection.railpoint[0].y-newsection.railpoint[2].y)>0.01){ + console.log(newsection.railpoint); + closedSpline = new THREE.CatmullRomCurve3( [ + new THREE.Vector3(newsection.railpoint[0].x,newsection.railpoint[0].y,newsection.railpoint[0].z), + new THREE.Vector3(newsection.railpoint[1].x,newsection.railpoint[1].y,newsection.railpoint[1].z+0.001), + new THREE.Vector3(newsection.railpoint[2].x,newsection.railpoint[2].y,newsection.railpoint[2].z) + ] ); + }else{ + closedSpline = new THREE.CatmullRomCurve3( [ + new THREE.Vector3(newsection.railpoint[0].x,newsection.railpoint[0].y,newsection.railpoint[0].z), + new THREE.Vector3(newsection.railpoint[1].x,newsection.railpoint[0].y,newsection.railpoint[1].z+0.001), + new THREE.Vector3(newsection.railpoint[2].x,newsection.railpoint[0].y,newsection.railpoint[2].z) + ] ); + } - closedSpline.type = 'catmullrom'; + + + // closedSpline.type = 'catmullrom'; + closedSpline.curveType = "chordal"; + closedSpline.tension = 0.15; closedSpline.closed = false; var extrudeSettings = { - steps : 5, + steps : 6, curveSegments : 1, bevelSegments : 1, bevelEnabled : false, @@ -117,7 +131,9 @@ this.loadpromise = function(sectionList,sectiondata,rails,scene,assetloader){ var shape = new THREE.Shape(); len = closedSpline.getLength(); - if(newsection.railpoint[0].y != newsection.railpoint[2].y){ + + + if(Math.abs(newsection.railpoint[0].y - newsection.railpoint[2].y)>0.001){ shape.moveTo( -2, 0 ); shape.lineTo( 2, 0 ); }else{ @@ -133,16 +149,16 @@ this.loadpromise = function(sectionList,sectiondata,rails,scene,assetloader){ if(i%2 != 0){ mesh.geometry.attributes.uv.array[ui] = 0; mesh.geometry.attributes.uv.array[ui+1] = 1; - mesh.geometry.attributes.uv.array[ui+2] = len/5; + mesh.geometry.attributes.uv.array[ui+2] = len/6; mesh.geometry.attributes.uv.array[ui+3] = 1; - mesh.geometry.attributes.uv.array[ui+4] = len/5; + mesh.geometry.attributes.uv.array[ui+4] = len/6; mesh.geometry.attributes.uv.array[ui+5] = 0; }else{ mesh.geometry.attributes.uv.array[ui] = 0; mesh.geometry.attributes.uv.array[ui+1] = 0; mesh.geometry.attributes.uv.array[ui+2] = 0; mesh.geometry.attributes.uv.array[ui+3] = 1; - mesh.geometry.attributes.uv.array[ui+4] = len/5; + mesh.geometry.attributes.uv.array[ui+4] = len/6; mesh.geometry.attributes.uv.array[ui+5] = 0; } } @@ -158,7 +174,9 @@ this.loadpromise = function(sectionList,sectiondata,rails,scene,assetloader){ new THREE.Vector3(newsection.railpoint[1].x,height,newsection.railpoint[0].z), ] ); - closedSpline.type = 'catmullrom'; + // closedSpline.type = 'catmullrom'; + closedSpline.curveType = "chordal"; + closedSpline.tension = 0.15; closedSpline.closed = false; // Set up settings for later extrusion var extrudeSettings = { diff --git a/src/jlmap3d/main/newmodel/StationStandListN.js b/src/jlmap3d/main/newmodel/StationStandListN.js index 9a1bca090..961189bbb 100644 --- a/src/jlmap3d/main/newmodel/StationStandListN.js +++ b/src/jlmap3d/main/newmodel/StationStandListN.js @@ -401,7 +401,7 @@ export function StationStandListN() { //canvas文字贴图方法 //PS:待提炼 增强功能 var stationcanvas = new Image(); -stationcanvas.src = "../../cbtc/static/texture/msgtnew.png"; +stationcanvas.src = "/cbtc/static/texture/msgtnew.png"; function getTextCanvas(text){ let canvas = document.getElementById('canvastexture'); diff --git a/src/jlmap3d/main/newmodel/TrainListN.js b/src/jlmap3d/main/newmodel/TrainListN.js index 562098fa5..e45179a6e 100644 --- a/src/jlmap3d/main/newmodel/TrainListN.js +++ b/src/jlmap3d/main/newmodel/TrainListN.js @@ -77,11 +77,11 @@ export function TrainListN() { // // fclip = new THREE.AnimationClip("four",2,ntracks2); - ntracks2 = assetloader.modellist[n].animations.slice(6,13); + ntracks2 = assetloader.modellist[n].animations.slice(6,14); fclip = new THREE.AnimationClip("four",2,ntracks2); - ntracks1 = assetloader.modellist[n].animations.slice(0,5); + ntracks1 = assetloader.modellist[n].animations.slice(0,6); tclip = new THREE.AnimationClip("three",2,ntracks1); @@ -218,11 +218,11 @@ export function TrainListN() { // // fclip = new THREE.AnimationClip("four",2,ntracks2); - ntracks2 = assetloader.modellist[n].animations.slice(6,13); + ntracks2 = assetloader.modellist[n].animations.slice(6,14); fclip = new THREE.AnimationClip("four",2,ntracks2); - ntracks1 = assetloader.modellist[n].animations.slice(0,5); + ntracks1 = assetloader.modellist[n].animations.slice(0,6); tclip = new THREE.AnimationClip("three",2,ntracks1); n = 0; @@ -281,33 +281,33 @@ export function TrainListN() { } }else{ for(let n=0,lenn = newmesh.children[j].children.length;n Date: Sat, 10 Oct 2020 18:39:56 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E8=B5=84=E6=BA=90=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=97=8B=E8=BD=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/iscs_picture/drum.png | Bin 0 -> 5490 bytes src/assets/iscs_picture/exhaustFan.png | Bin 0 -> 9305 bytes src/assets/iscs_picture/fm-blue.png | Bin 0 -> 3624 bytes src/assets/iscs_picture/fm-gray.png | Bin 0 -> 3605 bytes src/assets/iscs_picture/fm-green.png | Bin 0 -> 3347 bytes src/assets/iscs_picture/gufengji-gray-l.png | Bin 0 -> 6718 bytes src/assets/iscs_picture/gufengji-gray-r.png | Bin 0 -> 6717 bytes src/assets/iscs_picture/gufengji-red-l.png | Bin 0 -> 6001 bytes src/assets/iscs_picture/gufengji-red-r.png | Bin 0 -> 6249 bytes src/assets/iscs_picture/hand.png | Bin 0 -> 3196 bytes src/assets/iscs_picture/kongtiao.png | Bin 0 -> 4193 bytes src/assets/iscs_picture/mul-kongtiao.png | Bin 0 -> 16534 bytes src/assets/iscs_picture/setting.png | Bin 0 -> 3450 bytes src/assets/iscs_picture/textBgBluePoint.png | Bin 0 -> 3440 bytes src/assets/iscs_picture/ventilationFan.png | Bin 0 -> 7745 bytes src/iscs/shape/picture.js | 42 +++++++++++++++--- .../iscs/iscsDraw/icscComponents/picture.vue | 34 +++++++++++--- src/views/iscs/iscsDraw/index.vue | 9 ---- 18 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 src/assets/iscs_picture/drum.png create mode 100644 src/assets/iscs_picture/exhaustFan.png create mode 100644 src/assets/iscs_picture/fm-blue.png create mode 100644 src/assets/iscs_picture/fm-gray.png create mode 100644 src/assets/iscs_picture/fm-green.png create mode 100644 src/assets/iscs_picture/gufengji-gray-l.png create mode 100644 src/assets/iscs_picture/gufengji-gray-r.png create mode 100644 src/assets/iscs_picture/gufengji-red-l.png create mode 100644 src/assets/iscs_picture/gufengji-red-r.png create mode 100644 src/assets/iscs_picture/hand.png create mode 100644 src/assets/iscs_picture/kongtiao.png create mode 100644 src/assets/iscs_picture/mul-kongtiao.png create mode 100644 src/assets/iscs_picture/setting.png create mode 100644 src/assets/iscs_picture/textBgBluePoint.png create mode 100644 src/assets/iscs_picture/ventilationFan.png diff --git a/src/assets/iscs_picture/drum.png b/src/assets/iscs_picture/drum.png new file mode 100644 index 0000000000000000000000000000000000000000..a6eed61fe66816d2b771eff1b5a0440f21aaa157 GIT binary patch literal 5490 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 literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/exhaustFan.png b/src/assets/iscs_picture/exhaustFan.png new file mode 100644 index 0000000000000000000000000000000000000000..019396a64958c273c0fe1603920d71e865cf5a58 GIT binary patch literal 9305 zcmch72{@E(+y7)8TgFyG%#4IG%#3A*?8cfUM0Um)jAdrbSd)EEsZb(&NF-FUClrpaivy62dLGse0ctOBe60DwbZ z4`W9AwxeA#KqlHJ(Yg#p`(h#MSyKUkgU|M_1J_R;6r|D231_W*t&9wna3pVOtP9B* zFCFMjrf~xR%4&gREY1_}3wFl45r`_#rTQi)nBbxUJtuDjHzI4{-3fZE5L9FIe9QjQ5vBjE2pR^1xCUVXc;(K1|bVWC@9Inm5@mAA1^4)8pXv`$qb|O zhb`Jj1?ukWOIDJR2?z*~4v>{5QQTw@ii(OdaHI?p38Qhqs6j+uY#@wC75!5K22aIN z2xMOZi3r};h;=6U`Kmx^p8g8Kn`~tCTQQOP2T(M~WCF2d8H6-k#@l;8uAkggUu}Py z@E>mcBRBPI5E(CHhNqJJC^)>fKc46-`j@yF{;w$h8{#zf-@-1q--gM46tADryWnK- zUU+Ys4Jyr5#BV>z?j&Cl)tyBCE%GnMeoz05?@#Z139kRfx&4`cVZ{d${tfH?%)hYG zGUlJ1_!HiJ^RzWk!jN!&`;q8l{>&;D5{}@a^xIh_gn~R)9uLRE1C3RoBXpYkRY092w49c`d?%VB7qhv&A|S$ zD{0d{Eb$cTPpfbwlBVY;{b$tXw1z{APC;H?R{jtAPdBVE1lp?qlKBi9s0pq?|39mok}O(D z9{DePw;o{n4n*@jidtc@e-r+k_Gp_X|-f)*nyH%U?vo zKjftX9x0EzlHr@V)D^mpL?(Z)}Pznu(dntzgL z$Bq5l-{&0K;P*)hPo$Lvg?0{FntXT+01&vRkI^_Am^qLcT!FX9noygs+EAPuA652^ zJ;LT*Of(#L7WCFP(TSk1aXgGaQh+B*puH$aQpc^RDAx4jlPlE4753{gtXA)j2zXo9?euOFw^8-L4#T zn6D1Kvb0ob8x0C9J-!<7jJf0>gZd7)CV-+IWxDe2>r4m(17i{WI6dQJ_=)V&hs}&2 zIt*ZHIx&**ZZ2iT0R%oEIlp#hqI`O-ug!DU@C@rSwv`=!CDn2vcaE6&?pv&x^ggbc zx>gB0I*=dF5Nk3 zJKHjJd?@d@%ZmdqE<2%x%+lE<#m3WI%&;dDuCY$dY!4yXtctuAM1HUX3QHyub(s>6 zjms$3hdLezKb&ykiGzmnNS&HSKAoP-%-wk1Pi)bk++zxQCq^U+f!aBvhvrN%p({^+ z+?Sa;CbfQ}r^S%DA$$@X{t{X8fFV!a;KrfzU;v7Pp+K1Xp69pWK_0y_o@oq^V+t|u zMNg4x+0`obXLH)3?hhcTfO~`2Qa*1*Xhiq0&p*1%@aU8LwYW@vt~1fSazbA+o@!?d zGUVO5afn}sdgYGKF?+S8_?GyOZ`SVBc>Az?I@&sQRhcxCrT#{+v>8!9-|^8-_u%3N zp?WlvyEWsX*f?83k3=E=S;jX0$ipuH53=QOR~n9v0`V|JcERq%pXAy6lC$I<<18^pJP3e)35q%!}(~Q zISLUp4r+Kpw20XdzL4vUob`wKjA~v{kAQv8<#>>S(|+nf#t>7`ajD7q@u%)rwb|Ip z^tC-_iP#YKgau`<`Jdn0)8^51x}+gV(@-W~T3RYmNbcHco7nR=GSCM`NOh3A@~#(2 zozA3QgJ`K6=r>K=DJdxqZO|2E)HuP*D#)PuJc0?I(=diDH3zNU zGx{(DGMmwUYTbRr+zk^MSA0uue5O&CRT~%q(rg{@VWwtM8RmLy%x~Oi=r3B2(tBIL z{aJ(#5v1zF%QGi>myh(Z+~Xjn3$@X(fu3`(9-aJA=QGqf!!b8Aq`M?&1j$OQ3~MKu zjHj99RTWY0JDhg8WLf|G)hpKgu{N8I3W|$$+Xxw`ncLjQ%~(Uw1X{6bk{@;QT3AnM z(*V3X2`ntks+MFHCv>>T6#?MK=-y=7o~+Ne3=QH7PtH3d+QQ_KWX526%bd&N*4Id7 zk_I12p@VucA3UUJfdH4YT-`EW5I;8!!Hi-*bKLb1D*8;-ek2dcSieRjqS4@;_{q)z z_{*u1H`g>Sw`_;=L_*O_cnyXA{CiSL=8sP37RUOgs=qz)Dy8&m;Jexv))p4>@3QYp z78aMVmWlVgCl!d*b8EUUys|iFKk#n5ou~;cNY#@>v`uO~lC|-SZN=g?2D>dpHC`Ti zVk0ISD+7}bj}6%AoeSy?9&fE*onwKJUu8d^F%fQI><~+{)9h%}0Y=DIMzo8B3CXC~ zRzFEgFTnttFQ(5dclRpH*k8h@GO>F#&VX&}wp-?_~%ISPt*ePR!R+8|p#)%L68 zD1?OU#cWwevVj@#ClGENvf+9Odr9VV`z~3MD z;prAqV))LtoUOKdTkS)A@J9IZg@xFoKc*AfHS~6!MwvIqcS&@SR~&<3?FWx~fDWhw zBedcU9&jov%ep->d^e%Isz`#hAeFJG&1YB^KC^!L)B*@Gl|G+m_Pj}o{RJl0KV+F+LM3a8h1`SzYFMgp?BbU% zdTk1rDVqS{_E~iGk&{7}kiHB0z;fBn4zR<4J!QQY3oJnK2v(kKl0rWI7#wgQNwt{I zU(HS?6@xmFEX@lGQU;Qby+_-cqnIlPlkrC^EKGSBvy9_P4(3CR>(jH%;VRg@;RZ2w=`w@TJ-N9~mQ7*WQ>_6*^eE^KCTf z+pwGGC6SQCwQjYX!a^}x1xG1GgI;%bR{G6bW?xv5ezYNpbTY7_@g$0_*jcJwtmw80`RT3PZCk< zBBQ<5Nfs7f;|=MxwY9NtSq8-t(sOd=XzhfDSWivbXj}^MJ?z{xyd`$%(BW&ucej!--oukgTE~&I{t_R(M>e087~f3b1tdueds#1wY!Sc z1k%P=_8v7{8qCjr>xj93Rj~{mkq+#V6lhr=YUs7`|Q@ z?=rd8axRdt64Z88hzxX`^t?7SCBI7Mo*8;t9vPCAJ}tj2u6{rN3j$akY1_;`c;>Sh zu>Iwjzzfzut`@dTyig5~;ir^IhuCFSvnP>~mSA>pre^J7^(LliJ~_Ix;Z8pJ07a*# zC!I!W)U_5cFJQKhD?EqpCtTLc%)0MaZ?8ZaSMHS=jvO8yo~=@$==}k;dFUvVangJqu8hD=~T!Y%taegw6OPEQalJPGq?k*nVE3vz8c9^3y zF1zaBJjw*MB%|Gv=JV_Y%st1z0yx7rWqyKb$;ZYBuaB+Zt&RCxd=2$JolD4gl?#bi zZ5~bK4p+JlDSS3v(GvB>CoDR*hx>>S)T)4|*U2D`UF^hpbN;EPX@T=EeT}7Yyc75D z4U(7`0!wa2pYdzRH}22!iH(FasHrD%Qxc>MT+od74w$AkZrGjwR7e9p)~R)>f<^pf zp+G==|0g&K)y{a>uBZ5l(Jh5a>YVZ5;GjVM`yf@W3}dDG(Jz#h=Z8_aE2QzwrRou2 zmis$mbtOJNNcFH|4>-l=%~heeIvlt7GvF|TPO*-@HXdgeE5N7jZU!@u1PZrdZh{1g zwOhL64fN_1qt*9I@vY4$=uZD9xuImNp-}dE05ZeiE()aJLrM-WIG$W*ZEr8u@u2$s zdz2?#p;o|3dr=GduEqSuIZWc z5((H%O-NL2)kUc0acJ_}P%=Ec;|Yxi)lG@&?lFgZyrYrk?1Ki=Pv8wJC!*axUgj5Z3cGu{85*fhBqvhuyDAv9_GMw$lt ze%(}et*4?x)8~hd?<01$ao+@LzO#kB+zF7T4t%>6=rr-M_QATRpObtQ`FJIlRl@N-yMxxnl)$BQQyJ}4ZifD5XrQpfR556*o z%Q~LRrX6!8<1dmxAI>Hgkk0KMa)5KMYA{2`^K3doj%$ZxJ1M^n;M|Yl))g z-CbRjg}0SV+CnPlADOBNqKIA~edt02Ju^tNB4pd2wjpI$bxgI!309@N>7{n7#BF3> z7DORYD3r!~)G?%9+x9v`+6abIDq6jM>^^Ku!pl09%N{-F#LgiHbd_ql8M+Nxk}}mEl+9(VCh)?H-~_-qb>(RU zJwQm25>1;>P#k4A3=x=f;Y^-cE1mT# zjN(o(Ih91<;ry6++Zn?`UZw6zZLV5F&k6-&O}z{JT8Az@cP#K~7%2&wRyX=~%$^vlp`$-M`E`kD#7I>P=!!MF^I7qK;p%5*J! z2Us+8$7d!lJ&Pj%21_PlmQR71?m@trYzHl~7{-pDZDJ1ZMyY>;LfoIqY_`t%4t{xP~y&5?lohKqg8@g1pV9EgdX#Q>Dx)i=}c-r3Fcte;nz zot;I8{y@)V1qf5ARDPV5t?lih`Kd-yjY^}a_v*sJ0>SUwo)M2r^p^c8E^T%bVWmM0 z!*$E@;_Low#@Qw_V@*CUoNk_wNaV6Q^>e>qE5$zt&+E;%v?sTt}$<%v8129afo)n6bIC7PAAef z_8g$Hmm(sXx~H?9x2$b!JiIiK*@V`Tl8XYl{twPK3aioj3he>p zIp4V+`*KeRA;Kg5xK(j2t|n|QJ&ILVh&JEgN|(7kkXj<)kdU!P|1+(*+&EdD9K6&2g)r=2hhfM67T%bx@Ab0 z+;Xccrp;4`k;HGLaOC!gnNF6JxU+CPLJkXikR+4x-~{yBK~REydo$<4aDP1MPCsG% zux9wk*g@PkF`wzcH_}1HpK5uXEex*VkI>H2xb`G@ivw|-BCb9@l?f@S_~A`4eX~{N zwwn4eX-?J6$#RP-Fig>5mRIrKz`)s-_KSrPSKZl9@Sm_Vl%;b%z$G${W4c zb0t0G@R<-;cbbBzDy!45(Y93WKZ7FaLFCC^300Nz1IBZqIOjGnep5n5O2o8 z)D@I_3AT7G>#Mv5Z!80I)bYT9f@M1^i0c==>(?>*87=+oE_uTZnBq9$^Ti4LNfY!L zhM}v5*wobJaq7ZbMBUgT=6I-3TzOpnyx(SxNW~L>d`Q10h$DhSq7Zju>`Kks0j27d zZ%Cd1YrT7=^r{e@y;L)_C)dn(<*vHkU>``;2jHElWuJCU;IiLD<%#h(;&&4Y4u-DZ z7Fo&T3;`DM(yE$+ct{g2D;rgHgVy<1*ZWOO)Z5u)8sJ4u9xZ&@y@B@{9XP||L_+yZ z`yrO7OS4yQm>+aw{jQa-LzE8&X~~%{n_bhik^$KNc@^ArM$-&mgMX zQscqWHIsA&sS6`jrTV}-ltkLniC$4gwxMocg+0|(UFCYR9uSt8n!NbMaps0C5yDe7 zvL?wu$#BmDA@pxeiv%o!@4raa1LX&m1pqYk{V{=J+$ zu{fswTwHZ~&gjFSZuP|L5avZDk>TMRCAtwy?5#C*hnhN_|9g#+H91_ z4=|{;Qc^2ytrI1yqSCoFm&5tAug|PCFDTpGf^Up?I9yja$R(di43f^Ek(lpJXWu-+ zpCe-_nwfe>7ij}E)o>o73mVjjfh``6`CX3ZXQ?5XuCYo_r5d#`o*e6{Ve70 zA8#8e?7TWz?lr5N6et|B-rLr7j9Gj@%!~(NLAspQFLo*El3#o*oN#AQUL!lMu-5i! zD+F&n-CY;XbG_{)|25gKD0!2kQAo7iA+@jDQ4^Op_U2Ybwt&J%PDm>)c{OYZ1?@6G z7>fKfOnKQKD%?Wtm?!O)__Q z(GH!<3~L!U=hzumujV>b-zX*=x;YhFF=O0Qqj9Rl+QU1WeN~yAPWP|LFwUc6bbYdE> zo@OM(thX3lQ*>vx796`d`C>l7wkdBa`B=+2hoBD_vSd*F9W9m_Z2womhuNF+?S>)A z&n`KNbhjRi({e2;`fRmJIHDYz+t_{NeQ%2!F=lV~>F#VPQr!8$*_fB*tvWedJA2+{ zM>4Mv9y;DB?A|jA@=;RAIa;! zViH;69ngjnkAneKtE^v7@-%L`>0x z<)y=WT2kk3C|a2;-|osk;-k`jd>-O)<*udV4^hF+312~&XI8J>Mkcpv9ry8FUEY=3 j3_Sf0iJL{Mdtrcw*JT01!Vw(%fBwRNj0qt*w$7O9VOYFpWA(MqLNd{(gq z_x--d{q8q6<)-xHApv0l5{YC;YKkEP{Ei0WIsZR^YxxTi1>omtHYJ;vNP-TE!>1}L z=s6(Eq_WI{*_ehAj3XMiG8Qu0YK3DbLK9pVAaIZZ4!In5n#Wu^*u#r~dr>TdArC|-)WPwh zLC9=0L3)NGA$7DyN+2pV6oW=18l?(F$3qG^qLsT7^T)qrJQB)>Z$P@}GKuGygTEJaWnjhQ8U?6#dqga7r zXh>wlEsRsp!N5~*2oBb0>?fxA-b8_v$y_)qL!#v}heM3ZgXV?AVxZjX#v{>uRw+x$ zGDx0ras-)JOwz*G2kIH*gD8H3I)L{pTZw+dtdp~QqPG$<(oQ;n4Ia3P^!v#cFapCD zFl@idL&o~2J@NHI490L1SMV6W1!;9Lr_pLk9MLjy`p)r2AF{%oMI%YhQ6w@ zG6ZGC`klpan_6wNAh^_Gjfs(}Eh?+jf@@K!Mx`N06jh>Hxvh^amF5MUCP3kS^5P_btGn|pJk~&y9P@byS zn>fZs*}()1iRs2tMg19+RVdKGDy>E}P@G26`Jx<=YvsLilepqw4{{WdN(&^%a$rA- zMX!T_UmnIx+`-yO-b8XH%0=2eE_f!)Vzt_xUNowaA-xLOkC8tRPopUiDli}}JC+D8 z(@Bo^Sd}XjfX5^EM4bs54v0>pRx8!La*rEk0|i#ytK*H+qvM&&9x&&P6)YvD=SW;2 z4PXG%a)n%qh~F$kiK!7xDUXtCF*!(!2nDI8Y^DDVRbdDK#XJNB`Lf~y{v@#220itf zLh%B_mG(BOOtPr=&Q3v|Hi6-UH={gWOj_YyA@zv5L=}iN#zGQOtwljd)k>98id*Ek z)P`HsR;3cf6$GeDFKI~uDK6=NuzwYc7sfL-p#GfjsMWL5x zP_&gP;m7sS69qV(PlES=43<4E9)h8ZNlthW6N_^=C_S+9I@rcB4#->kud-x5Brv^C z;!b!hrIqBU0ZAH2*l!K`ebi6P{%`9F|Bvj;#P^lAuw=ikIq$QAV2m7TEt% zU!p$fx&2-E$^Fi2lleWpQmh3}_er1xu_t-E(<64%MC}AmBJ5OuK|V<|FmaEx;QlO((!8PKt)>fZ+rN&`bokzM22y7*BWr%o+s z*u~5bAL;BNY!Hjp9(y0_Tz^M5%h}Ru4lODha&+Sbdb6ktlOHr zE;s0+vFFAXX~c(PH<|Nls;d9iuygI8Wux42UB4{fjobdZEBtc%!Z9Do+Gc%*mQ{b- zo;P7tXlJ8u=lLIh4s`F{5j7z&@{9bf+uPb|Pbm%eu9EUC|$Qd?1XPl8U8Q-putDt{8og) zL)!=F!|~3p4@M7osd%3qm65gv1 zR2k>*yYTJ^LSDe8%askM@1Z$eclBv(WkbuZxUYORL-k+GtIRlHyM3;8pZ}G`Ig_(D z7QKc%A3u~h)hvY*CViB={@p2@W_O9(zi|=2^3Tq|)!+Gy+m3hc{Wo&}o;-*RWr@w*@73Zf!q& z>~@QJeEp%bKfN@med>YSErb@`Sk|()v$^6{&K1qG3-->h9kDs*lUu&swR1)mS-$Zd zy2RCq4kNWa$nv9RIgmOCfH zspUSwo0nzUS0*6Cz-2fzZkL`IUcg90-?r5Y#*^~`KV#J z`|F-9UFo`A>6hnQ(KLMfRXn39FQe`F$<39;y1CtmeSxOCcID+2*=^Ms$7;&YpLr(p z$m+tF=G3D7mo7!VG^NJ8?lyh-Y+UHM*p}Dl&So7w7`%VgnrOb`?$O4to8t05-EnL7 wQ;IX!X9n0_nWDlDqWUK6<8_0his+u$(nww5>5GH*ivQ(O6VnYJB+Op)AFgUVf&c&j literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/fm-gray.png b/src/assets/iscs_picture/fm-gray.png new file mode 100644 index 0000000000000000000000000000000000000000..6b2fc9027897957bd06a3143657a628824080b5f GIT binary patch literal 3605 zcmd5p#efRF*2(0C3?!K_GZ+x+gHk=L z+M~8=U8T5oU3I(GRm(w(^|5x*b7Xbf?fRmG~S+|O^vo@eAaeE>gT^^n+F)1XuBzzair~;87LZL~K zEGk5DGqVsg%K=2E(kp39t3wQg3e#(}1ThxD)tC`g8&OQ7#PpGJ@@Z5omycouL7-|J#c?G>DEU&4K$R#xe0-+_3*c#v@d^y< zK}3m^jV%^T3TUa{1-CadGa%T*cLWNC3@xF&D5g@QZnx-{gyw~mg^;Le<0&b{-ho)iyJH1X;V0kC& z4X`|?EU+y3*J&Nx+ZV$-1JMZ-7{^Pz5Lcd)2}+okK*f~+6z&0OVd-MglXOdGR@qsa zv6BH~NrM5{F`G`KRO6UUX$M-MBxob9H0p5;q0wpqP8dWRMQQ0CUZ6ZQ5T!wiih+{s zw2@HbMx&AfHV0(YI+Qj&p;J;utzJ(MHsCN)o&3`{1{Mb8>TXqPiq4#@#qnf4k(#VC zU`9-5Hkv0IwQ3C48h5-jd4nJ)S?i?It%lR|6MYZXWrc?$64i*!tFgl0(e zEHMjVU*H%z-6H_b%Ry~oAwmy2Ala1L>jHch;Ifz!;F7{3@#cyZ=WQ$&6eJ12fUj*guC$|bWrL!QHy49QeS@TEzdJm&5ZDzRMCJvFO5m8?U<0($ zXv1lxPNUT*DVv&7Iw+gYuF((_P8&Pv!4x_QW<3i5Cv^Gbr8o-iI}E!? z;ow*|;xC2*FUUs(wqqui#!E%o0mpO;Qg?v?ZOBg({mSSAogL^Lz@T&(H8%LrVoHL* z4N73p+ih9`4)FgJ{a5<)LKezbvAXQ8vUVz6T z`QOxw$PW*dz%ldchVIwN{*=Q&%x$SeaD|r4HnMF&&+SKbvz6yB zm-+HftS~*E)h9Hg`hj=f{{0WCTU#3M{Hk7)*zYuH+uFPOcfzyWC3IP91O1D#+TZU+ zhpux(B#nRgXmxAEmroO;ORs!oXkKb>vKbyXI_Z>CtFQ!PQ~2h&p(BQ|F3~w67ZQ-Eh#W=^^&<>N7#a)V&ASHtJ8_Fm6m7KKIY3PVX-}H{jO1 zJwn+ROhq{vah0t&)fLA9*8f@)*t6)_P$_;m3)#p|58!q+#CrJXi3U7&Pe6 zV7IVbkNmrbDn2ac?TMkGy{#wZAEkP#cCK9)S1>t#6q6Z=jhT<`iW>P@^OoiYEOls2 zV`jk)c=>ZU-m#9HQhA{1K*fj_)A_g^JvMLNJa^8#>e9KDo9dQ*5v!7qj<~bz_$SqF zOXZ_Eb$Rm+&dl7L^2dX|bGuY&K{G}NKmK-5!LWW6@$-*;@?OP8ZC-n%>}tb_vZ(Q& Qi+|kGQ>I!zoVcLkKLh9dr~m)} literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/fm-green.png b/src/assets/iscs_picture/fm-green.png new file mode 100644 index 0000000000000000000000000000000000000000..33096dd35d36717e1ed663d1159dcf6e757790b9 GIT binary patch literal 3347 zcmcgv2~-nj9uJ3zs4TMDh1KdX>Uv-@N3O}NQ6PaPY7_xictUkDnUIkrGtLYKJPIPK z;MS|2eT%NULOr(XYGqZx7OQLRq1EEkhi5(3dR6VS9z55&-y|VmtIxjfdz+V;%=iEP z*YEfJzyExx&K&bn#;8_+UFyvQ}ii~io z91zO2WkM!SU?8nZr=&5B7SbD3m`<%R7)C%iiV+A(AedT-=|~Mq;y4uiz#y97aFSW( zj9@GOH?*q5lvGta4lig|Go?exmw!5WZejxLswSk3KK<=|-6 zL57kgwR)$XaNx9(aMA`P<BaS2_sPj!_hCr)a$0euBV)jX}9PXO)qzHKl5BJXL4NNY&~w z0@IoZQyQT`F5S1+X9GqBp`I zFTZIv<>B3on8^s4YzgD`r{JH+m8;cV9MCrC5G-hr_Y!%Bc#@X|P(c8B*~xTp`vW70 z{-`L913!MbA9XfpH~^hat5s`*a(^1RW)`e^z$SpxZ{wfK>oga@3YL;I2@EAMW^jSf zD2^&I`J02ONi9aI(PWe$Q9z5V3aDnCr9V~GkeGqQbkC^*z8sW9y-HbbgZ_H8u%g5X zrNKs(&CCcsxmn2HCP<17Fe*|r83!B`vM;DhwN}d*bhujS)avv~tzJVbDVowLaa?bw z2#u3wP@>)5=VIg~?F9QrSOT#{&MEmQfk`a{JF2}I`)fq*3XsVpH(vuZbTaF8a6WNZ zyL;M2c?%iv9zbB^Wq4@LJChNl4oo~HP@wd{${S&)z?v+d=^6ps4yuixr9pr`4RAW2vrRZR7f-$1V8F*7fTPT4N`|RLa#j zeolGSxwh_>1B)uZ-cuf3bLrZ%gW9gOk?ycj&0(Js<9Ed;)J(hU^%c*!-imN=`pzvU zTqmbKICZw*!NaOXH5Y&Cb?kV{_D#~o+|euAF4PaXf4lySC*sok`<{IN$6poHi2M1^ zznROVS{Y?0>V?!UT$^Ecln`*Zc+SRmbhxbn(UR&EQA3A&@?Stjo85xSn zait4CW;S$<+%S)P+%LI2tQ&vxwe+u}6QJbFZ(R;Q&d2!fzEwSZZQ@Rr_k}C3e782P z&mr{aI&tty#rDdtQgrsW9i@vJ@4u~WI(_e3{nRh2lJ>f6Wxeig9}&K&G5cP_zdl&J zZBWy(>Y;VB&5*Tn^YJD3u6*X$o^fG+bMFgtQe&Pi)pZsA9e)1sZ|3FKw%WysjmOGV zN2ZKDGkEOq5qD?To!xk2_JaA1dCR zmwfu-h33y6*A;Jmmz;ij-O7}dwB~I~*UaCtF)JqNB%B`upW{CsRjDv2j3b{K3eH-P zlhd;=JsQ&Ws>c4!50B1NBYuz&2|fAr!T6WVUx`jR<(K4#?a|EeGNe#xP2NjE3`?M+6!@|tJfKND=@ z^QV@r%kRCr?qdIlXZRSXVL?@SblQeo87m8 z^K|(#5ivC-c%%&7ptfNuIwo;NTDNXpvyMdW&hS=mUj9zf^sxhrcE4A+w)c)TIngcG z#be#SO&GXj!YA+5w_xi~wR+63?BAs|-QHU<_~C&5@3bsCf4<<3+jI13e&xQhh%Ng3 z=*)gZ)WW9+Bg~Zp?u?#x>)|@GKCjE4SM)hCwqRM**36hYLy*C*$5#w|^T7V~6>BsF bPX_FXEbaMGOU*L*Z;Lg3ym@=tw2J=%>qxM` literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/gufengji-gray-l.png b/src/assets/iscs_picture/gufengji-gray-l.png new file mode 100644 index 0000000000000000000000000000000000000000..8d329885b59b5a6a6e908b9882b848615da9bc59 GIT binary patch literal 6718 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 W5>E=$Fnywbcr49qDOXJ04*d%}BEA0r literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/gufengji-gray-r.png b/src/assets/iscs_picture/gufengji-gray-r.png new file mode 100644 index 0000000000000000000000000000000000000000..4c67325f057e69079c40b8ac606a32374af905ce GIT binary patch literal 6717 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 aUnIK*>thjq?X}{M0j`eg9V+d&#s3S>YXFu2 literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/hand.png b/src/assets/iscs_picture/hand.png new file mode 100644 index 0000000000000000000000000000000000000000..ed55e939d0b8388634d88f90c65f6712177077c7 GIT binary patch literal 3196 zcmcgv4Nw%<9X~0fLWMyS6(gj}B7vyO?%wY0-7OqncUUk$D4>GlOtQE4?pV3IU3T}l zgAi@~2#TYLQ*Ba1g6Rb7FqV?~H7TJItIdq20gRetqG-`Z%`_&dsbZ39-|oSIDUO+R zrZ=~H`~L6$`Fp?j-*=nK%YGUc`$(+8V2CR&a#X3Fs;brM(h`;zeVLp~bb`!~PlDP8Lw0UR;`lnCB2M5D{5JISo+BtC zxNPVOQz=m@*+H#P)Fgw-rm`x&sgAe0(A*p(JH$c_)$mjJdY{-V{ep&deiE$Ls zRn$5gny&>!s!PieyC?(1l*wQ`Nt+O}HIrm0+G<^l7zxsX6BeALFp^7$=hN!awH39n0UD2{F{!pv z)hGeH0w`iY=0RZt@T;lg<`rPPitk|#)g#6(J`z|8$X>mA7mouk@WB`gOqGn}DbvA;|U7mtR!J_)3n*bxW>E{`xTY*^FZ?k{bvf$ z6T{FvW#OHen{qRlnW3E+M=~77nHa|6CONm&N{`t;DGP98aNdboEh#kF^J$|opRq2< zHYOM{WEeuR-TwDVD9!*XX7GhLhuB|xSgR;OE`&A%IGTe_E z>1`;?OE<0Le3BO^aGko1K36kgE?g^IO4cp|P6ZBl z!O(<}z)0;|MN+JZWGP}1VPOdfi>3;p7Tk^htxB^L!%~)eR3Tn2PURk?thGU%uSJ5Q zigM#m1R#0%Eso&DV-#tPuPzF{nH~ z=AN(R{2l<`12~G`uO43XHvm~3uSw!$4pI-UybX2Bq7Mmkf3*ZWh$@bbw4HEYmo6X+ z6F{2iFcJ;%uy;9?X-hB!LRqILN?lj46%CThYKzM}+As-}~2;8JGbcG2g1L#>7!Q+PZ z7&+&_LFA+a{BT>y@Eml44fYufQT@e^{3^>Z@)ujEnb=5x6~1h zq|_{!(=)3-ulx7E*pPSmt*Dq7k>4Cw;vDGOn3Rx`WXWSjjB78n?w+>3H|-#MwKFI1 zb=&ItpyTGZH|FBV)c99(FRgzv=kmpiKVOmC`^m`2OX88+QSJK;i9H`)xbWLnrM~{{ z!NHj=EiL=hzy3MltIaKIHY0S>v~ISnF6kNRYH~A`P`1|AadzpywpBlUsI_PRuWW}t z#yUKEgyBoi=M;7a&x>~w&Bvm9_wW35BKn%46B%eqJ02vOt{i^9a%EjrOJZtTz!BZM z@W9!klbiPpTuXGUu|B(X(}3*-WzTbO|LtdO86A1Vsuz*i4=d+Nvy-G{ff?x+k5*6L z=19+Os1GjKbfo!qY6#uXx12IO<2-OSbn{#4t@z}J^P=CKvF!Nyn3RjxFSpg-+TQ)+ zAH5L&@|j28pN$p_#^+VcOZs}aD{soVln#2+tuG6%m(|5BOOCmf5S2Z&Z@O_``Rgg@ zf@`N&PI>eBfvii9?LOFW_{EB$FHR9pZ8-DsqTl31_03B<_DyYi_TsBjAW(SHUSoRa z{2N2${=uXFUN?Iv-Qg+8r^{oDO6VC)&z2k7+S`9Qe6{z`d-U&a-}q!#!kkU#JK|1N zr$6Ut&)Qye%2bv1(X9F}*xbCmJ5Qc`;>2fX2J+@)9she=$HwmZk@>>5sDhRM51gjS8jxrKpQyu+Zn%zR4JTNjzMuF6~cfd%1bbv`vo> zJ^_T7R~DUa-A<*ar|+HHeC3sM=~X)~Zc916tL4%42mbl#i09Lry9#4>RP6nudGVdx z)SHj>&B`~I-Q8k{o>!L9^T8eSyuPFB7CyueyFSZpzMI_mr$@1xBxTUpdE$eo)&$J; zQ+Lm?W@XtsIQOcouZG@uIAP}WyKnXznzrsZ{?IDw;PMlz-~B`FYyK-=&i=>nPj0MV V+c~_qUHgAwC@w5>bQL_k?mvLpVr>8b literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/kongtiao.png b/src/assets/iscs_picture/kongtiao.png new file mode 100644 index 0000000000000000000000000000000000000000..f27f1f31d6a6700a6988675c9ada44f39832b78f GIT binary patch literal 4193 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 aKhSO*EkqfT`uzFV0gAF}GM}Z41O6X>mOZ2Z literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/setting.png b/src/assets/iscs_picture/setting.png new file mode 100644 index 0000000000000000000000000000000000000000..800342dd6fc5f22aa092a002695c46f3ce1c0347 GIT binary patch literal 3450 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 literal 0 HcmV?d00001 diff --git a/src/assets/iscs_picture/ventilationFan.png b/src/assets/iscs_picture/ventilationFan.png new file mode 100644 index 0000000000000000000000000000000000000000..159854dca06e0887fb2c98960335c1072192d0a3 GIT binary patch literal 7745 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 M8t2sW)vn(BFD=w=Jpcdz literal 0 HcmV?d00001 diff --git a/src/iscs/shape/picture.js b/src/iscs/shape/picture.js index dcb297d0c..a3953db31 100644 --- a/src/iscs/shape/picture.js +++ b/src/iscs/shape/picture.js @@ -6,10 +6,24 @@ import psdSystem from '@/assets/iscs_picture/iscs-psd.png'; import psdRight from '@/assets/iscs_picture/iscs-psd-right.png'; import fireBlue from '@/assets/iscs_picture/iscs-fire-blue.png'; import fireRed from '@/assets/iscs_picture/iscs-fire-red.png'; -import envPersonDoor from '@/assets/iscs_picture/renfangmen.png' - - +import envPersonDoor from '@/assets/iscs_picture/renfangmen.png'; +import hand from '@/assets/iscs_picture/hand.png'; +import fmBlue from '@/assets/iscs_picture/fm-blue.png'; +import fmGray from '@/assets/iscs_picture/fm-gray.png'; +import fmGreen from '@/assets/iscs_picture/fm-green.png'; +import airCond from '@/assets/iscs_picture/kongtiao.png'; +import airCondMul from '@/assets/iscs_picture/mul-kongtiao.png'; +import setting from '@/assets/iscs_picture/setting.png'; +import blowerRedLeft from '@/assets/iscs_picture/gufengji-red-l.png'; +import blowerGrayLeft from '@/assets/iscs_picture/gufengji-gray-l.png'; +import blowerRedRight from '@/assets/iscs_picture/gufengji-red-r.png'; +import blowerGrayRight from '@/assets/iscs_picture/gufengji-gray-r.png'; import iscsAPF from '@/assets/iscs_picture/iscs_APF.png'; +import drum from '@/assets/iscs_picture/drum.png'; +import exhaustFan from '@/assets/iscs_picture/exhaustFan.png' +import waterCooler from '@/assets/iscs_picture/ZSDF533.png'; +import textBgBluePoint from '@/assets/iscs_picture/textBgBluePoint.png'; +import ventilationFan from '@/assets/iscs_picture/ventilationFan.png' const pictureObj = { 'psdLeft': psdLeft, @@ -18,7 +32,23 @@ const pictureObj = { 'APF': iscsAPF, 'envPersonDoor': envPersonDoor, 's': fireBlue, - 'a': fireRed + 'a': fireRed, + hand, + fmBlue, + fmGray, + fmGreen, + airCond, + airCondMul, + setting, + blowerRedLeft, + blowerGrayLeft, + blowerRedRight, + blowerGrayRight, + drum, + exhaustFan, + waterCooler, + textBgBluePoint, + ventilationFan }; export default class Picture extends Group { constructor(device) { @@ -39,7 +69,9 @@ export default class Picture extends Group { }); this.imageButton = new Image({ zlevel: model.zlevel, - z: model.z, + z: model.z, + origin: [model.width/2, model.height/2], + rotation: (model.rotation||0)*Math.PI/180, style: { x: 0, y: 0, diff --git a/src/views/iscs/iscsDraw/icscComponents/picture.vue b/src/views/iscs/iscsDraw/icscComponents/picture.vue index 1436e47de..6e82f8cbb 100644 --- a/src/views/iscs/iscsDraw/icscComponents/picture.vue +++ b/src/views/iscs/iscsDraw/icscComponents/picture.vue @@ -9,6 +9,9 @@ + + + @@ -47,7 +50,8 @@ export default { type: '', size: 10, width: 50, - height: 50, + height: 50, + rotation: 0, x: 10, y: 10 }, @@ -59,6 +63,22 @@ export default { { name: '蓝色FIRE', value: 's'}, { name: '红色FIRE', value: 'a'}, { name: 'APF', value: 'APF'}, + { name: '手动阀', value: 'hand'}, + { name: '蓝色-阀门', value: 'fmBlue'}, + { name: '灰色-阀门', value: 'fmGray'}, + { name: '绿色-阀门', value: 'fmGreen'}, + { name: '空调', value: 'airCond'}, + { name: '多联空调', value: 'airCondMul'}, + { name: '管件', value: 'setting'}, + { name: '汽包', value: 'drum' }, + { name: '红色-左侧-鼓风机', value: 'blowerRedLeft'}, + { name: '灰色-左侧-鼓风机', value: 'blowerGrayLeft'}, + { name: '红色-右侧-鼓风机', value: 'blowerRedRight'}, + { name: '灰色-右侧-鼓风机', value: 'blowerGrayRight'}, + { name: '排风机', value: 'exhaustFan' }, + { name: '冷水机', value: 'waterCooler'}, + { name: '顶部排风机', value: 'ventilationFan'}, + { name: '文字蓝色背景', value: 'textBgBluePoint'} ], rules: { @@ -85,7 +105,8 @@ export default { this.isUpdate = true; this.form.code = model.code; this.form.type = model.type; - this.form.size = model.size; + this.form.size = model.size; + this.form.rotation = model.rotation; this.form.width = model.width; this.form.height = model.height; this.form.x = model.point.x; @@ -106,7 +127,8 @@ export default { code: this.isUpdate ? this.form.code : getUID('IscsPicture', this.iscs.iscsPictureList), _type: 'IscsPicture', type: this.form.type, - size: this.form.size, + size: this.form.size, + rotation: this.form.rotation, width: this.form.width, height: this.form.height }; @@ -126,7 +148,8 @@ export default { code: this.form.code, _type: 'IscsPicture', type: this.form.type, - size: this.form.size + size: this.form.size, + rotation: this.form.rotation, }; this.$emit('deleteDataModel', rectModel); }, @@ -137,7 +160,8 @@ export default { this.form = { code: '', type: '', - size: 10, + size: 10, + rotation: 0, width: 50, height: 50, x: 10, diff --git a/src/views/iscs/iscsDraw/index.vue b/src/views/iscs/iscsDraw/index.vue index ec8df6585..9abb07a62 100644 --- a/src/views/iscs/iscsDraw/index.vue +++ b/src/views/iscs/iscsDraw/index.vue @@ -114,15 +114,6 @@ export default { }).catch(() => { this.$message.error('ISCS数据保存异常!'); }); - // { - // "graphData": "string", - // "id": 0, - // "lineCode": "string", - // "stationCode": "string", - // "system": "string", - // "totalSystem": "string", - // "userInterface": "string" - // } } } };