Browse Source

fix: Merge branch 'master' of http://192.168.0.115:3000/4dkankan/4dkankan_bim

# Conflicts:
#	src/pages/Bim.vue
#	src/pages/LaserBim.vue
xzw 2 years ago
parent
commit
c28ae36353

File diff suppressed because it is too large
+ 1 - 1
public/js/10.js


File diff suppressed because it is too large
+ 1 - 1
public/js/3.js


File diff suppressed because it is too large
+ 1 - 1
public/js/4.js


File diff suppressed because it is too large
+ 1 - 1
public/js/5.js


File diff suppressed because it is too large
+ 1 - 1
public/js/6.js


File diff suppressed because it is too large
+ 1 - 1
public/js/8.js


File diff suppressed because it is too large
+ 3 - 3
public/js/chunk-vendors.js


File diff suppressed because it is too large
+ 1 - 1
public/js/smart.js


+ 3 - 2
src/pages/Bim.vue

@@ -32,11 +32,12 @@ const successCallback = viewMetaData => {
         viewAdded = true
         // 渲染3D模型
         viewer3D.render()
+        window.loader.resolve(viewer3D)
     }) 
-    viewer3D.addEventListener(Glodon.Bimface.Viewer.Viewer3DEvent.MouseClicked, function (objectdata) {
+    /*viewer3D.addEventListener(Glodon.Bimface.Viewer.Viewer3DEvent.MouseClicked, function (objectdata) {
      // 调用viewerDrawing对象的Method,可以继续扩展功能
         alert('objectId : ' + JSON.stringify(objectdata.objectId) + '\n' + 'worldPosition : ' + JSON.stringify(objectdata.worldPosition))
-    })
+    })*/
 }
 
 // 加载失败回调函数

+ 81 - 80
src/pages/LaserBim.vue

@@ -8,8 +8,8 @@
                 <iframe ref="sourceFrame" :src="`smart-laser.html?m=${source.num}&dev`" frameborder="0" @load="onLoadSource"></iframe>
                 <div class="tools">
                     <div class="item-mode">
-                        <div class="iconfont icon-show_roaming_normal" @click="onModeChange('panorama')"></div>
-                        <div class="iconfont icon-show_more" @click="onModeChange('cloud')"></div>
+                        <div class="iconfont icon-show_roaming_normal" :class="{ active: mode == 0 }" @click="onModeChange(0)"></div>
+                        <div class="iconfont icon-show_more" :class="{ active: mode == 1 }" @click="onModeChange(1)"></div>
                     </div>
                     <div class="item-date" v-if="target">
                         <span class="prev" @click="onPrevDate('source')"><i class="iconfont icon-show_back"></i></span>
@@ -45,15 +45,16 @@
 import Datepicker from 'vuejs3-datepicker'
 import { ref, onActivated, onDeactivated, reactive, onMounted, computed } from 'vue'
 import ConvertViews from '@/utils/ConvertViews'
-let sourceWindow = null
-let targetWindow = null
-let sourceApp = null
+ 
+let sourceApp = null, sourceSceneName
 let targetApp = null
+
 const views = new ConvertViews()
 const datepickName = ref(null)
 const datepickValue = ref(null)
 const sourceFrame = ref(null)
 const targetFrame = ref(null)
+const mode = ref(0)
 const source = ref(null)
 const target = ref(null)
 const project = ref(null)
@@ -62,7 +63,7 @@ const scenes = reactive([
     { pid: '001', num: 'SS-t-bFMqa1dqUU', date: '2022-09-01' },
     { pid: '001', num: 'SS-t-xp9BDKfzhR', date: '2022-09-03' },
     { pid: '001', num: 'SS-t-lc5OWhZPaC', date: '2022-09-05' },
-    { pid: '001', num: 'SS-t-Y6gLRFwxE5', date: '2022-09-10' }
+    { pid: '001', num: 'SS-t-Y6gLRFwxE5', date: '2022-09-10' },
 ])
 const highlighted = computed(() => {
     let dates = []
@@ -70,56 +71,54 @@ const highlighted = computed(() => {
         dates = scenes.map(item => item.date.toDate())
     }
     return {
-        dates: dates
+        dates: dates,
     }
 })
 
-const onLoadSource = () => {
-    sourceWindow = sourceFrame.value.contentWindow
-    
-    
-    // sourceApp.Scene.on('loaded', () => {
-    //     sourceApp.Camera.setCompassDisplay(false)
-    //     sourceApp.Connect.sync.start()
-    //     sourceApp.VRScreenSYNC = false
+let getView = ()=>{
+    let camera = sourceApp.viewer.mainViewport.camera
+    return {
+        position : camera.position,
+        quaternion : camera.quaternion, 
+        fov : camera.fov
+    }
+} 
 
-    //     console.log('sourceApp.Scene loaded')
-    //     views.KanKan = sourceFrame.value.contentWindow.KanKan
-    //     views.bind({ sourceApp })
-    //     targetApp && targetApp.Connect.sync.sync()
-    // })
-    // sourceApp.Connect.sync.on('data', data => {
-    //     if (targetApp) {
-    //         views.applyDiff(targetApp, data)
-    //         targetApp.Connect.sync.receive(data)
-    //     }
-    // })
+const initConvertView = () => {
+    if(sourceApp && targetApp/* && this.sourceSceneName != sourceApp.Potree.settings.number*/){//存在且和当前不同
+        views.init(sourceApp, targetApp , sourceApp.viewer.inputHandler.domElement, 'laser', getView(), 
+                    sourceApp.viewer.images360.panos.slice(0,2).map(e=>e.position))
+    }
 }
-const onLoadTarget = () => {
 
-    targetApp = targetFrame.value.contentWindow.viewer
-    sourceApp = sourceWindow.viewer
-     
-    let getView = ()=>{
-        let camera = sourceApp.mainViewport.camera
-        return {
-            position : camera.position,
-            quaternion : camera.quaternion, 
-            fov : camera.fov
+const onLoadSource = () => {
+    sourceApp = sourceFrame.value.contentWindow
+    sourceApp.loaded.then(sdk => {
+        if (mode.value != 0) {
+            sdk.scene.changeMode(mode.value)
         }
         
-    } 
-    views.init(targetApp, sourceApp.renderArea, 'laser', getView())
-    
-    
-    sourceApp.addEventListener('camera_changed', e => { 
-        targetApp.receive( getView() )
+        window.viewer1 = sourceApp.viewer 
+        initConvertView()
+        sourceSceneName = sourceApp.Potree.settings.number
+        sourceApp.viewer.addEventListener('camera_changed', e => { 
+            targetApp && views.receive( getView() )
+        })  
+        
+    }) 
+}
+const onLoadTarget = () => { 
+ 
+    targetApp = targetFrame.value.contentWindow
+    targetApp.loader.then(viewer=>{
+        //console.log(viewer.getViewer().camera)
+        window.viewer2 = targetApp.viewer
+        initConvertView()
     })
-    
-    
-   
-    
-    
+ 
+ 
+ 
+ 
     // targetApp.Scene.on('loaded', () => {
     //     targetApp.Camera.setCompassDisplay(false)
     //     targetApp.Connect.sync.start()
@@ -136,9 +135,10 @@ const onLoadTarget = () => {
     //     }
     // })
 }
-const onModeChange = mode => {
+const onModeChange = targetMode => {
     if (sourceApp) {
-        
+        sourceApp.loaded.then(sdk => sdk.scene.changeMode(targetMode))
+        mode.value = targetMode
     }
 }
 const onPickDate = name => {
@@ -188,6 +188,7 @@ const onCompare = () => {
     if (target.value) {
         target.value = null
         targetApp = null
+        views.clear()
         return
     }
     let index = scenes.findIndex(item => item.num == source.value.num)
@@ -201,9 +202,6 @@ const onCompare = () => {
 }
 
 const onPrevDate = name => {
-    if (!scene) {
-        scene = source
-    }
     let scene = null
     if (!name || name == 'source') {
         scene = source
@@ -218,20 +216,20 @@ const onPrevDate = name => {
         index = scenes.length - 1
     }
 
-    if (name) {
-        if (name == 'source') {
-            if (scenes[index].num == target.value.num) {
-                index--
-            }
-        } else {
-            if (scenes[index].num == source.value.num) {
-                index--
-            }
-        }
-    }
-    if (index == -1) {
-        index = scenes.length - 1
-    }
+    // if (name) {
+    //     if (name == 'source') {
+    //         if (scenes[index].num == target.value.num) {
+    //             index--
+    //         }
+    //     } else {
+    //         if (scenes[index].num == source.value.num) {
+    //             index--
+    //         }
+    //     }
+    // }
+    // if (index == -1) {
+    //     index = scenes.length - 1
+    // }
     scene.value = scenes[index]
 }
 const onNextDate = name => {
@@ -248,20 +246,20 @@ const onNextDate = name => {
     if (++index > scenes.length - 1) {
         index = 0
     }
-    if (name) {
-        if (name == 'source') {
-            if (scenes[index].num == target.value.num) {
-                index++
-            }
-        } else {
-            if (scenes[index].num == source.value.num) {
-                index++
-            }
-        }
-    }
-    if (index > scenes.length - 1) {
-        index = 0
-    }
+    // if (name) {
+    //     if (name == 'source') {
+    //         if (scenes[index].num == target.value.num) {
+    //             index++
+    //         }
+    //     } else {
+    //         if (scenes[index].num == source.value.num) {
+    //             index++
+    //         }
+    //     }
+    // }
+    // if (index > scenes.length - 1) {
+    //     index = 0
+    // }
     scene.value = scenes[index]
 }
 
@@ -354,6 +352,9 @@ main {
                     margin-left: 20px;
                     margin-right: 20px;
                     cursor: pointer;
+                    &.active {
+                        color: #00c8af;
+                    }
                 }
                 i {
                     font-size: 18px;

+ 2 - 0
src/pages/bim.js

@@ -1,4 +1,6 @@
 import { createApp } from 'vue'
 import App from './Bim.vue'
+import Deferred from '@/utils/Deferred'
+window.loader = Deferred()
 const app = (window.__app = createApp(App))
 app.mount('#app')

+ 168 - 117
src/utils/ConvertViews.js

@@ -1,111 +1,113 @@
-
+import * as THREE from "../../public/static/lib/three.js/build/three.module.js";
 import math from './math.js'
 
 export default class ConvertViews {
     constructor() {
         this.sourceApp = null
         this.targetApp = null
-        this.player1 = null
-        this.player2 = null
-        this.KanKan = null
+        
+       
     }
 
-    /* bind(options = { sourceApp: null, targetApp: null }) {
-        if (options.sourceApp) {
-            this.sourceApp = options.sourceApp
-            this.player1 = this.sourceApp.core.get('Player')
-            //this.player1.model.addEventListener('gotPanos', this.init.bind(this))
-        } else if (options.targetApp) {
-            this.targetApp = options.targetApp
-            this.player2 = this.targetApp.core.get('Player')
-            //this.player2.model.addEventListener('gotPanos', this.init.bind(this))
-        }
-        if (this.sourceApp && this.targetApp) {
-            // 两个实例都加载完了,可以进行特性处理
-            this.init()
-            this.applyDiff(options.sourceApp || options.targetApp)
-        }
-    } */
-
-    init(viewer, dom2, sceneType, data) {
+     
+    clear(){
+        this.loaded = false;
+        this.targetApp = this.sourceApp = null;
+    }
+    
+    init(sourceApp, targetApp, dom2, sceneType, cameraData, sourcePano) {
         //if (!this.player1.model.panos.list.length || !this.player2.model.panos.list.length) return
+        
+        if(this.loaded || !targetApp ) return 
+        
+        this.sourceApp = sourceApp
+        this.targetApp = targetApp
+        let viewer = this.viewer = targetApp.viewer 
+        
         this.needConvertAxis = sceneType == '4dkk'
         this.lastCamStatus = viewer.getCameraStatus()
+        this.computeAveDiffLon(sourcePano)
         
-        
-        viewer.setNavigationMode(Glodon.Bimface.Viewer.NavigationMode3D.Walk)
+        viewer.setNavigationMode(targetApp.Glodon.Bimface.Viewer.NavigationMode3D.Walk)
         viewer.setFlySpeedRate(5)
         viewer.getViewer().setTransitionAnimationState(false) //setCameraStatus瞬间变化相机
         
-        viewer.addEventListener('Rendered', (e)=>{
-            let info = viewer.getCameraStatus()
-             
+        /* viewer.addEventListener('Rendered', (e)=>{
+            let info = viewer.getCameraStatus() 
             let poseChanged = !math.closeTo(this.lastCamStatus.position, info.position)
                 || !math.closeTo(this.lastCamStatus.target, info.target)
                 || !math.closeTo(this.lastCamStatus.fov, info.fov)
             
-            if(poseChanged){
-                
+            if(poseChanged){ 
                 this.send(info)
             }
             
-            this.lastCamStatus = info
+            this.lastCamStatus = info 
             
-            
-            
-           /*   aspect: 0.7879440258342304
-                coordinateSystem: "world"
-                far: 11485.989363357028
-                fov: 45
-                name: "persp"
-                near: 1.1852000000072447
-                position: {x: -1130.0432094639486, y: -6058.569138159733, z: 2265.9284566100446}
-                target: {x: 310.3968263091223, y: -66.0595010237127, z: 1477.7045866099475}
-                up: {x: 0, y: 1.534753124827774e-13, z: 1}
-                version: 1
-                zoom: 0 */
-        })
+        }) */
         
         
-        viewer.addEventListener( Glodon.Bimface.Viewer.Viewer3DEvent.ViewAdded,
+        /* viewer.addEventListener(targetApp.Glodon.Bimface.Viewer.Viewer3DEvent.ViewAdded,
             ()=>{
                 this.loaded = true
                 if(this.firstData){
                     this.receive(this.firstData)
                 }
             }
-        )
+        ) */
+        this.loaded = true
+        this.receive(cameraData)
         
-        this.receive(data)
         
         
         
-        
-        {
+        {//传递到另一边的dom
+            let getEvent = (type, e)=>{
+                return new MouseEvent(type, {
+                    bubbles: false,//?
+                    cancelable: true,
+                    view: this.sourceApp,
+                    clientX: e.clientX, clientY: e.clientY, 
+                    button: e.button, buttons: e.buttons,  which: e.which,
+                    altKey: e.altKey, ctrlKey: e.ctrlKey, shiftKey:e.shiftKey, metaKey: e.metaKey, 
+                    detail:e.detail, 
+                    //target :  dom2  
+                });
+                
+            }
+            
+            
             this.lockCamera(true)
             let dom1 = viewer.getDomElement() 
-            dom1.addEventListener('mousedown',(e)=>{//传递到另一边的dom
-                dom2.dispatchEvent(e) //试试
+            dom1.addEventListener('mousedown',(e)=>{
+                let event = getEvent('mousedown', e)
+                dom2.dispatchEvent(event)  
             })
-            dom1.addEventListener('mousemove',(e)=>{//传递到另一边的dom
-                dom2.dispatchEvent(e) //试试
+            dom1.addEventListener('mousemove',(e)=>{
+                let event = getEvent('mousemove', e)
+                dom2.dispatchEvent(event)  
             })
-            dom1.addEventListener('mouseup',(e)=>{//传递到另一边的dom
-                dom2.dispatchEvent(e) //试试
+            dom1.addEventListener('mouseup',(e)=>{
+                let event = getEvent('mouseup', e)
+                sourceApp.dispatchEvent(event)   //mouseup 在laser中加在window上的
             })
-            dom1.addEventListener('mousewheel',(e)=>{//传递到另一边的dom
-                dom2.dispatchEvent(e) //试试
+            dom1.addEventListener('mousewheel',(e)=>{
+                let event = getEvent('mousewheel', e)
+                event.wheelDelta = e.wheelDelta  //wheelDelta没法在getEvent参数中赋值
+                dom2.dispatchEvent(event)  
             })
             let stop = (e)=>{
-                var event = document.createEvent('MouseEvents');
+                /* var event = document.createEvent('MouseEvents');
                 event.initMouseEvent('mouseup', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
-                dom2.dispatchEvent(event)  
+                dom2.dispatchEvent(event)   */
+                let event = getEvent('mouseup', e)
+                dom2.dispatchEvent(event)   
             }
             dom1.addEventListener('mouseout',stop)
             dom1.addEventListener('mouseover',stop)
              
-        }
-        this.computeAveDiffLon()
+        } 
+        
         
     }
 
@@ -123,25 +125,23 @@ export default class ConvertViews {
             return //正在操作当前,不接收 
         } */
         
-        let position, target
-        let currStatus = viewer.getCameraStatus()
+        let position = new THREE.Vector3, target = new THREE.Vector3
+        let currStatus = this.viewer.getCameraStatus()
         if(data.position){
             position = new THREE.Vector3().copy(data.position)
-        }else{
-            position.copy(currStatus.position)
-        }
-        if(this.needConvertAxis){
-            position = math.convertVector.YupToZup(position)
-        }
+        } 
+        
         
         
         if(!data.target){
             if(data.quaternion){ 
-                if(this.needConvertAxis){
+                /* if(this.needConvertAxis){
                     data.quaternion = math.convertQuaternion.YupToZup(data.quaternion)
-                }
+                } */
                 let dir = new THREE.Vector3(0, 0, -1).applyQuaternion(data.quaternion)
-                dir.applyQuaternion(this.diffQuaternion)/////!
+                //dir.applyQuaternion(this.diffQuaternion)/////!
+                 
+                
                 target.copy(position).add(dir) 
             }
         }else{
@@ -151,92 +151,123 @@ export default class ConvertViews {
                 target.copy(data.target)
             } 
         }
+        
+        if(this.needConvertAxis){
+            position = math.convertVector.YupToZup(position)
+            target = math.convertVector.YupToZup(target)
+        }
+
+        position.applyMatrix4(this.convertMatrix)
+        target.applyMatrix4(this.convertMatrix)
+
 
         let msg = {
             position,
             target,
-            up: new THREE.Vector3(0,0,1)
-        }//前三个缺一不可
+            up: new THREE.Vector3(0,0,1),
+            //前三个缺一不可
+            
+            fov : data.fov
+            
+        }
 
-        viewer.setCameraStatus(msg)    
+        this.viewer.setCameraStatus(msg)    
 
         
 
 
         //fov, near,  far
-        
+        /*   aspect: 0.7879440258342304
+            coordinateSystem: "world"
+            far: 11485.989363357028
+            fov: 45
+            name: "persp"
+            near: 1.1852000000072447
+            position: {x: -1130.0432094639486, y: -6058.569138159733, z: 2265.9284566100446}
+            target: {x: 310.3968263091223, y: -66.0595010237127, z: 1477.7045866099475}
+            up: {x: 0, y: 1.534753124827774e-13, z: 1}
+            version: 1
+            zoom: 0 */
 
 
     }
 
 
-    send(info){ 
-        let camera = viewer.getViewer().camera
+    /* send(info){ 
+        let camera = this.viewer.getViewer().camera
         
         let data = { 
             position : info.position,
             quaternion : camera.quaternion,
             target : info.target,
-        }
-        
-        
-    }
-
-
-    lockCamera(locked){
-        this.locked = locked 
-        this.updateCtrlEnable() 
-    }
-
-    setPanoMode(state){
-        this.isPanoMode = state
-        this.updateCtrlEnable()
-    }
-
-    updateCtrlEnable(){
-        viewer.camera3D.enableRotate(this.locked ? false : true)
-        viewer.enableShortcutKey((this.locked || this.isPanoMode) ? false : true) //键盘移动
-    }
+        }  
+    } */
 
 
 
-    computeAveDiffLon() {
+    computeAveDiffLon(sourcePano) {
         //获取两个场景的lon偏差值
         //需要点的个数>1, 且两个场景点一一对应,位置接近且顺序一致
         //pick两个点来计算
         let diffLonAve = 0,
             diffLons = []
 
-        let panoPos1 = [//4dkk
-            
-        ]
-        let panoPos2 = [{
-            x: -0.8948667986001945,
-            y: -0.7200655233964686,
-            z: 1.742798893355817
-        },{
-            x: -0.8675960783595227,
-            y: 2.3758592370806317,
-            z: 1.7428102653224462
-        }]
+        /* let panoPos1 = [//4dkk   SS-t-lc5OWhZPaC 
+            new THREE.Vector3( 2.1985836955069153,  -0.7253820937020852,  -0.01348725),
+            new THREE.Vector3( 4.07288387528266,  1.8350265362839944, 0.04772775)
+        ] */
+        
+        let panoPos1 = sourcePano
         
+        let panoPos2 = [
+            new THREE.Vector3( -5.313605730801787,  -4.889868407960505,  1.717447893355817),
+            new THREE.Vector3( -5.337403524084278,  -2.5012228235167737, 1.7608838933558175) 
+        ]
+         
         let length = panoPos1.length
         
         if(this.needConvertAxis){
             panoPos1 = panoPos1.map(e=>math.convertVector.YupToZup(e))
         }
 
-        //挑选连续的两个点为向量来计算,如有123个漫游点,则选取12 23 31作为向量
 
-        let index = 0
+        var vec1 = new THREE.Vector3().subVectors(panoPos1[0], panoPos1[1]) //旧的向量
+        var vec2 = new THREE.Vector3().subVectors(panoPos2[0], panoPos2[1])//新的向量
+        /* var angle = vec1.angleTo(vec2) 
+        if(vec1.clone().cross(vec2).z < 0)angle *= -1 //这里不确定是<0还是>0 */
+        var angle = math.getAngle(vec1, vec2, 'z')
+
+
+        //var scale = vec2.length()/vec1.length() 
+        //var scaleMatrix = new THREE.Matrix4().makeScale(scale,scale,scale)   //默认为1, 但由于坐标暂时是自己采集的,所以结果会是第一个点附近比较正确,越远偏差越大
+        var matrix = new THREE.Matrix4().setPosition(panoPos1[0].clone().negate())//先以点0为基准平移到000
+        //matrix.premultiply(scaleMatrix)//再缩放
+        var rotateMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle );
+        matrix.premultiply(rotateMatrix)//和旋转
+        var moveBackMatrix = new THREE.Matrix4().setPosition(panoPos2[0])
+        matrix.premultiply(moveBackMatrix)//再移动到realPosition的点0处
+
+
+
+        this.convertMatrix = /* matrix.invert()  */ matrix
+    
+        var pos = panoPos1.map(e=>{ 
+            return e.clone().applyMatrix4(matrix)
+        })
+        console.log(pos)
+
+
+        //挑选连续的两个点为向量来计算,如有123个漫游点,则选取12 23 31作为向量
+            
+        /* let index = 0
         while (index < length) {
             let pos11 = new THREE.Vector3().copy(panoPos1[index])
             let pos12 = new THREE.Vector3().copy(panoPos1[(index + 1) % length])
             let pos21 = new THREE.Vector3().copy(panoPos2[index])
             let pos22 = new THREE.Vector3().copy(panoPos2[(index + 1) % length])
-            let vec1 = new THREE.Vector3().subVectors(pos11, pos12).setY(0)
-            let vec2 = new THREE.Vector3().subVectors(pos21, pos22).setY(0)
-            let diffLon = this.KanKan.Utils.math.getAngle(vec1, vec2, 'z')
+            let vec1 = new THREE.Vector3().subVectors(pos11, pos12).setZ(0)
+            let vec2 = new THREE.Vector3().subVectors(pos21, pos22).setZ(0)
+            let diffLon = math.getAngle(vec1, vec2, 'z')
             diffLons.push(diffLon)
             diffLonAve += diffLon
             index++
@@ -247,10 +278,30 @@ export default class ConvertViews {
         console.log('diffLonAve', diffLonAve)
         
         this.diffQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0),  diffLonAve)
-        this.diffQuaternionInvert = this.diffQuaternion.clone().invert()
+        this.diffQuaternionInvert = this.diffQuaternion.clone().invert() */
+
+    }
+
+
+    lockCamera(locked){
+        this.locked = locked 
+        this.updateCtrlEnable() 
+    }
+
+    setPanoMode(state){
+        this.isPanoMode = state
+        this.updateCtrlEnable()
+    }
 
+    updateCtrlEnable(){
+        this.viewer.camera3D.enableRotate(this.locked ? false : true)
+        this.viewer.enableShortcutKey((this.locked || this.isPanoMode) ? false : true) //键盘移动
     }
 
+
+
+
+
     /* applyDiff(app) {
         //sourcePlayer -> targetPlayer
         if (!this.player1 || !this.player2 || this.targetApp.config.num == this.sourceApp.config.num) return //场景码相同的话返回

+ 374 - 0
src/utils/Deferred.js

@@ -0,0 +1,374 @@
+function isArray(arr) {
+    return Object.prototype.toString.call(arr) === '[object Array]'
+}
+
+function foreach(arr, handler) {
+    if (isArray(arr)) {
+        for (var i = 0; i < arr.length; i++) {
+            handler(arr[i])
+        }
+    } else handler(arr)
+}
+
+function D(fn) {
+    var status = 'pending',
+        doneFuncs = [],
+        failFuncs = [],
+        progressFuncs = [],
+        resultArgs = null,
+        promise = {
+            done: function () {
+                for (var i = 0; i < arguments.length; i++) {
+                    // skip any undefined or null arguments
+                    if (!arguments[i]) {
+                        continue
+                    }
+
+                    if (isArray(arguments[i])) {
+                        var arr = arguments[i]
+                        for (var j = 0; j < arr.length; j++) {
+                            // immediately call the function if the deferred has been resolved
+                            if (status === 'resolved') {
+                                arr[j].apply(this, resultArgs)
+                            }
+
+                            doneFuncs.push(arr[j])
+                        }
+                    } else {
+                        // immediately call the function if the deferred has been resolved
+                        if (status === 'resolved') {
+                            arguments[i].apply(this, resultArgs)
+                        }
+
+                        doneFuncs.push(arguments[i])
+                    }
+                }
+
+                return this
+            },
+
+            fail: function () {
+                for (var i = 0; i < arguments.length; i++) {
+                    // skip any undefined or null arguments
+                    if (!arguments[i]) {
+                        continue
+                    }
+
+                    if (isArray(arguments[i])) {
+                        var arr = arguments[i]
+                        for (var j = 0; j < arr.length; j++) {
+                            // immediately call the function if the deferred has been resolved
+                            if (status === 'rejected') {
+                                arr[j].apply(this, resultArgs)
+                            }
+
+                            failFuncs.push(arr[j])
+                        }
+                    } else {
+                        // immediately call the function if the deferred has been resolved
+                        if (status === 'rejected') {
+                            arguments[i].apply(this, resultArgs)
+                        }
+
+                        failFuncs.push(arguments[i])
+                    }
+                }
+
+                return this
+            },
+
+            always: function () {
+                return this.done.apply(this, arguments).fail.apply(this, arguments)
+            },
+
+            progress: function () {
+                for (var i = 0; i < arguments.length; i++) {
+                    // skip any undefined or null arguments
+                    if (!arguments[i]) {
+                        continue
+                    }
+
+                    if (isArray(arguments[i])) {
+                        var arr = arguments[i]
+                        for (var j = 0; j < arr.length; j++) {
+                            // immediately call the function if the deferred has been resolved
+                            if (status === 'pending') {
+                                progressFuncs.push(arr[j])
+                            }
+                        }
+                    } else {
+                        // immediately call the function if the deferred has been resolved
+                        if (status === 'pending') {
+                            progressFuncs.push(arguments[i])
+                        }
+                    }
+                }
+
+                return this
+            },
+
+            then: function (done, fail, progress) {
+                /*
+                // fail callbacks
+                if (arguments.length > 1 && arguments[1]) {
+                    this.fail(arguments[1])
+                }
+
+                // done callbacks
+                if (arguments.length > 0 && arguments[0]) {
+                    this.done(arguments[0])
+                }
+
+                // notify callbacks
+                if (arguments.length > 2 && arguments[2]) {
+                    this.progress(arguments[2])
+                }
+
+                return this
+                */
+
+                return D(function (def) {
+                    foreach(done, function (func) {
+                        // filter function
+                        if (typeof func === 'function') {
+                            deferred.done(function () {
+                                var returnval = func.apply(this, arguments)
+                                // if a new deferred/promise is returned, its state is passed to the current deferred/promise
+                                if (returnval && typeof returnval === 'function') {
+                                    returnval.promise().then(def.resolve, def.reject, def.notify)
+                                } else {
+                                    // if new return val is passed, it is passed to the piped done
+                                    def.resolve(returnval)
+                                }
+                            })
+                        } else {
+                            deferred.done(def.resolve)
+                        }
+                    })
+
+                    foreach(fail, function (func) {
+                        if (typeof func === 'function') {
+                            deferred.fail(function () {
+                                var returnval = func.apply(this, arguments)
+
+                                if (returnval && typeof returnval === 'function') {
+                                    returnval.promise().then(def.resolve, def.reject, def.notify)
+                                } else {
+                                    def.reject(returnval)
+                                }
+                            })
+                        } else {
+                            deferred.fail(def.reject)
+                        }
+                    })
+                }).promise()
+            },
+            catch: function () {
+                for (var i = 0; i < arguments.length; i++) {
+                    // skip any undefined or null arguments
+                    if (!arguments[i]) {
+                        continue
+                    }
+
+                    if (isArray(arguments[i])) {
+                        var arr = arguments[i]
+                        for (var j = 0; j < arr.length; j++) {
+                            // immediately call the function if the deferred has been resolved
+                            if (status === 'rejected') {
+                                arr[j].apply(this, resultArgs)
+                            }
+
+                            failFuncs.push(arr[j])
+                        }
+                    } else {
+                        // immediately call the function if the deferred has been resolved
+                        if (status === 'rejected') {
+                            arguments[i].apply(this, resultArgs)
+                        }
+
+                        failFuncs.push(arguments[i])
+                    }
+                }
+
+                return this
+            },
+
+            promise: function (obj) {
+                if (obj == null) {
+                    return promise
+                } else {
+                    for (var i in promise) {
+                        obj[i] = promise[i]
+                    }
+                    return obj
+                }
+            },
+
+            state: function () {
+                return status
+            },
+
+            debug: function () {
+                console.log('[debug]', doneFuncs, failFuncs, status)
+            },
+
+            isRejected: function () {
+                return status === 'rejected'
+            },
+
+            isResolved: function () {
+                return status === 'resolved'
+            },
+
+            pipe: function (done, fail, progress) {
+                return D(function (def) {
+                    foreach(done, function (func) {
+                        // filter function
+                        if (typeof func === 'function') {
+                            deferred.done(function () {
+                                var returnval = func.apply(this, arguments)
+                                // if a new deferred/promise is returned, its state is passed to the current deferred/promise
+                                if (returnval && typeof returnval === 'function') {
+                                    returnval.promise().then(def.resolve, def.reject, def.notify)
+                                } else {
+                                    // if new return val is passed, it is passed to the piped done
+                                    def.resolve(returnval)
+                                }
+                            })
+                        } else {
+                            deferred.done(def.resolve)
+                        }
+                    })
+
+                    foreach(fail, function (func) {
+                        if (typeof func === 'function') {
+                            deferred.fail(function () {
+                                var returnval = func.apply(this, arguments)
+
+                                if (returnval && typeof returnval === 'function') {
+                                    returnval.promise().then(def.resolve, def.reject, def.notify)
+                                } else {
+                                    def.reject(returnval)
+                                }
+                            })
+                        } else {
+                            deferred.fail(def.reject)
+                        }
+                    })
+                }).promise()
+            },
+        },
+        deferred = {
+            resolveWith: function (context) {
+                if (status === 'pending') {
+                    status = 'resolved'
+                    var args = (resultArgs = arguments.length > 1 ? arguments[1] : [])
+                    for (var i = 0; i < doneFuncs.length; i++) {
+                        doneFuncs[i].apply(context, args)
+                    }
+                }
+                return this
+            },
+
+            rejectWith: function (context) {
+                if (status === 'pending') {
+                    status = 'rejected'
+                    var args = (resultArgs = arguments.length > 1 ? arguments[1] : [])
+                    for (var i = 0; i < failFuncs.length; i++) {
+                        failFuncs[i].apply(context, args)
+                    }
+                }
+                return this
+            },
+
+            notifyWith: function (context) {
+                if (status === 'pending') {
+                    var args = (resultArgs = arguments.length > 1 ? arguments[1] : [])
+                    for (var i = 0; i < progressFuncs.length; i++) {
+                        progressFuncs[i].apply(context, args)
+                    }
+                }
+                return this
+            },
+
+            resolve: function () {
+                return this.resolveWith(this, arguments)
+            },
+
+            reject: function () {
+                return this.rejectWith(this, arguments)
+            },
+
+            notify: function () {
+                return this.notifyWith(this, arguments)
+            },
+        }
+
+    var obj = promise.promise(deferred)
+
+    if (fn) {
+        fn.apply(obj, [obj])
+    }
+
+    return obj
+}
+
+D.when = function () {
+    if (arguments.length < 2) {
+        var obj = arguments.length ? arguments[0] : undefined
+        if (obj && typeof obj.isResolved === 'function' && typeof obj.isRejected === 'function') {
+            return obj.promise()
+        } else {
+            return D().resolve(obj).promise()
+        }
+    } else {
+        return (function (args) {
+            var df = D(),
+                size = args.length,
+                done = 0,
+                rp = new Array(size) // resolve params: params of each resolve, we need to track down them to be able to pass them in the correct order if the master needs to be resolved
+
+            for (var i = 0; i < args.length; i++) {
+                ;(function (j) {
+                    var obj = null
+
+                    if (args[j].done) {
+                        args[j]
+                            .done(function () {
+                                rp[j] = arguments.length < 2 ? arguments[0] : arguments
+                                if (++done == size) {
+                                    df.resolve.apply(df, rp)
+                                }
+                            })
+                            .fail(function () {
+                                df.reject(arguments)
+                            })
+                    } else {
+                        obj = args[j]
+                        args[j] = new Deferred()
+
+                        args[j]
+                            .done(function () {
+                                rp[j] = arguments.length < 2 ? arguments[0] : arguments
+                                if (++done == size) {
+                                    df.resolve.apply(df, rp)
+                                }
+                            })
+                            .fail(function () {
+                                df.reject(arguments)
+                            })
+                            .resolve(obj)
+                    }
+                })(i)
+            }
+
+            return df.promise()
+        })(arguments)
+    }
+}
+
+export const Defer = D
+
+export default function () {
+    return new D()
+}