diff --git a/spine-js/data/goblins.atlas b/spine-js/data/goblins.atlas new file mode 100644 index 000000000..271742263 --- /dev/null +++ b/spine-js/data/goblins.atlas @@ -0,0 +1,285 @@ + +goblins.png +format: RGBA8888 +filter: Linear,Linear +repeat: none +spear + rotate: false + xy: 2, 142 + size: 22, 368 + orig: 22, 368 + offset: 0, 0 + index: -1 +goblingirl/head + rotate: false + xy: 26, 429 + size: 103, 81 + orig: 103, 81 + offset: 0, 0 + index: -1 +goblin/head + rotate: false + xy: 26, 361 + size: 103, 66 + orig: 103, 66 + offset: 0, 0 + index: -1 +goblin/torso + rotate: false + xy: 131, 414 + size: 68, 96 + orig: 68, 96 + offset: 0, 0 + index: -1 +goblingirl/torso + rotate: false + xy: 26, 263 + size: 68, 96 + orig: 68, 96 + offset: 0, 0 + index: -1 +dagger + rotate: false + xy: 26, 153 + size: 26, 108 + orig: 26, 108 + offset: 0, 0 + index: -1 +goblin/right-lower-leg + rotate: false + xy: 201, 434 + size: 36, 76 + orig: 36, 76 + offset: 0, 0 + index: -1 +goblingirl/right-lower-leg + rotate: false + xy: 54, 185 + size: 36, 76 + orig: 36, 76 + offset: 0, 0 + index: -1 +goblin/left-upper-leg + rotate: false + xy: 96, 286 + size: 33, 73 + orig: 33, 73 + offset: 0, 0 + index: -1 +goblin/pelvis + rotate: false + xy: 131, 369 + size: 62, 43 + orig: 62, 43 + offset: 0, 0 + index: -1 +goblingirl/pelvis + rotate: false + xy: 131, 324 + size: 62, 43 + orig: 62, 43 + offset: 0, 0 + index: -1 +goblin/right-foot + rotate: false + xy: 131, 289 + size: 63, 33 + orig: 63, 33 + offset: 0, 0 + index: -1 +goblin/left-lower-leg + rotate: false + xy: 2, 70 + size: 33, 70 + orig: 33, 70 + offset: 0, 0 + index: -1 +goblin/right-upper-leg + rotate: false + xy: 2, 5 + size: 34, 63 + orig: 34, 63 + offset: 0, 0 + index: -1 +goblingirl/left-lower-leg + rotate: false + xy: 195, 342 + size: 33, 70 + orig: 33, 70 + offset: 0, 0 + index: -1 +goblingirl/left-upper-leg + rotate: false + xy: 37, 81 + size: 33, 70 + orig: 33, 70 + offset: 0, 0 + index: -1 +goblingirl/right-upper-leg + rotate: false + xy: 38, 16 + size: 34, 63 + orig: 34, 63 + offset: 0, 0 + index: -1 +goblin/eyes-closed + rotate: false + xy: 38, 2 + size: 34, 12 + orig: 34, 12 + offset: 0, 0 + index: -1 +goblin/undies + rotate: false + xy: 54, 154 + size: 36, 29 + orig: 36, 29 + offset: 0, 0 + index: -1 +goblin/right-arm + rotate: false + xy: 72, 102 + size: 23, 50 + orig: 23, 50 + offset: 0, 0 + index: -1 +goblin/left-foot + rotate: false + xy: 131, 256 + size: 65, 31 + orig: 65, 31 + offset: 0, 0 + index: -1 +goblingirl/right-arm + rotate: false + xy: 196, 290 + size: 28, 50 + orig: 28, 50 + offset: 0, 0 + index: -1 +goblingirl/left-shoulder + rotate: false + xy: 226, 294 + size: 28, 46 + orig: 28, 46 + offset: 0, 0 + index: -1 +goblin/left-arm + rotate: false + xy: 198, 253 + size: 37, 35 + orig: 37, 35 + offset: 0, 0 + index: -1 +goblingirl/left-foot + rotate: false + xy: 92, 223 + size: 65, 31 + orig: 65, 31 + offset: 0, 0 + index: -1 +goblingirl/right-foot + rotate: false + xy: 92, 188 + size: 63, 33 + orig: 63, 33 + offset: 0, 0 + index: -1 +goblin/undie-straps + rotate: false + xy: 92, 167 + size: 55, 19 + orig: 55, 19 + offset: 0, 0 + index: -1 +goblingirl/left-arm + rotate: false + xy: 159, 219 + size: 37, 35 + orig: 37, 35 + offset: 0, 0 + index: -1 +goblin/right-shoulder + rotate: false + xy: 97, 120 + size: 39, 45 + orig: 39, 45 + offset: 0, 0 + index: -1 +goblingirl/right-shoulder + rotate: false + xy: 198, 206 + size: 39, 45 + orig: 39, 45 + offset: 0, 0 + index: -1 +goblin/left-hand + rotate: false + xy: 157, 176 + size: 36, 41 + orig: 36, 41 + offset: 0, 0 + index: -1 +goblin/neck + rotate: false + xy: 195, 163 + size: 36, 41 + orig: 36, 41 + offset: 0, 0 + index: -1 +goblingirl/undie-straps + rotate: false + xy: 97, 99 + size: 55, 19 + orig: 55, 19 + offset: 0, 0 + index: -1 +goblingirl/neck + rotate: false + xy: 138, 120 + size: 35, 41 + orig: 35, 41 + offset: 0, 0 + index: -1 +goblingirl/left-hand + rotate: false + xy: 175, 121 + size: 35, 40 + orig: 35, 40 + offset: 0, 0 + index: -1 +goblin/left-shoulder + rotate: false + xy: 212, 117 + size: 29, 44 + orig: 29, 44 + offset: 0, 0 + index: -1 +goblingirl/eyes-closed + rotate: false + xy: 154, 97 + size: 37, 21 + orig: 37, 21 + offset: 0, 0 + index: -1 +goblin/right-hand + rotate: false + xy: 193, 78 + size: 36, 37 + orig: 36, 37 + offset: 0, 0 + index: -1 +goblingirl/right-hand + rotate: false + xy: 74, 39 + size: 36, 37 + orig: 36, 37 + offset: 0, 0 + index: -1 +goblingirl/undies + rotate: false + xy: 74, 8 + size: 36, 29 + orig: 36, 29 + offset: 0, 0 + index: -1 diff --git a/spine-js/data/goblins.json b/spine-js/data/goblins.json new file mode 100644 index 000000000..f1dcc96a0 --- /dev/null +++ b/spine-js/data/goblins.json @@ -0,0 +1,499 @@ +{ +"bones": [ + { "name": "root" }, + { "name": "hip", "parent": "root", "x": 0.64, "y": 114.41 }, + { "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 }, + { "name": "left lower leg", "parent": "left upper leg", "length": 49.89, "x": 56.34, "y": 0.98, "rotation": -16.65 }, + { "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 58.94, "y": -7.61, "rotation": 102.43 }, + { "name": "right upper leg", "parent": "hip", "length": 42.45, "x": -20.07, "y": -6.83, "rotation": -97.49 }, + { "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 42.99, "y": -0.61, "rotation": -14.34 }, + { "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 }, + { "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 93.92 }, + { "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -1.51 }, + { "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59, "rotation": -13.92 }, + { "name": "right shoulder", "parent": "torso", "length": 37.24, "x": 76.02, "y": 18.14, "rotation": 133.88 }, + { "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 37.6, "y": 0.31, "rotation": 36.32 }, + { "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 }, + { "name": "left shoulder", "parent": "torso", "length": 35.43, "x": 74.04, "y": -20.38, "rotation": -156.96 }, + { "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 37.85, "y": -2.34, "rotation": 28.16 }, + { "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 }, + { "name": "pelvis", "parent": "hip", "x": 1.41, "y": -6.57 } +], +"slots": [ + { "name": "left shoulder", "bone": "left shoulder", "attachment": "left shoulder" }, + { "name": "left arm", "bone": "left arm", "attachment": "left arm" }, + { "name": "left hand item", "bone": "left hand", "attachment": "spear" }, + { "name": "left hand", "bone": "left hand", "attachment": "left hand" }, + { "name": "left foot", "bone": "left foot", "attachment": "left foot" }, + { "name": "left lower leg", "bone": "left lower leg", "attachment": "left lower leg" }, + { "name": "left upper leg", "bone": "left upper leg", "attachment": "left upper leg" }, + { "name": "neck", "bone": "neck", "attachment": "neck" }, + { "name": "torso", "bone": "torso", "attachment": "torso" }, + { "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" }, + { "name": "right foot", "bone": "right foot", "attachment": "right foot" }, + { "name": "right lower leg", "bone": "right lower leg", "attachment": "right lower leg" }, + { "name": "undie straps", "bone": "pelvis", "attachment": "undie straps" }, + { "name": "undies", "bone": "pelvis", "attachment": "undies" }, + { "name": "right upper leg", "bone": "right upper leg", "attachment": "right upper leg" }, + { "name": "head", "bone": "head", "attachment": "head" }, + { "name": "eyes", "bone": "head" }, + { "name": "right shoulder", "bone": "right shoulder", "attachment": "right shoulder" }, + { "name": "right arm", "bone": "right arm", "attachment": "right arm" }, + { "name": "right hand item", "bone": "right hand", "attachment": "dagger" }, + { "name": "right hand", "bone": "right hand", "attachment": "right hand" } +], +"skins": { + "default": { + "left hand item": { + "dagger": { "x": 7.88, "y": -23.45, "rotation": 10.47, "width": 26, "height": 108 }, + "spear": { "x": -4.55, "y": 39.2, "rotation": 13.04, "width": 22, "height": 368 } + }, + "right hand item": { + "dagger": { "x": 6.51, "y": -24.15, "rotation": -8.06, "width": 26, "height": 108 } + } + }, + "goblin": { + "neck": { + "neck": { "name": "goblin/neck", "x": 10.1, "y": 0.42, "rotation": -93.69, "width": 36, "height": 41 } + }, + "undies": { + "undies": { "name": "goblin/undies", "x": 6.3, "y": 0.12, "rotation": 0.91, "width": 36, "height": 29 } + }, + "right hand": { + "right hand": { "name": "goblin/right-hand", "x": 7.88, "y": 2.78, "rotation": 91.96, "width": 36, "height": 37 } + }, + "right arm": { + "right arm": { "name": "goblin/right-arm", "x": 16.44, "y": -1.04, "rotation": 94.32, "width": 23, "height": 50 } + }, + "head": { + "head": { "name": "goblin/head", "x": 25.73, "y": 2.33, "rotation": -92.29, "width": 103, "height": 66 } + }, + "left shoulder": { + "left shoulder": { "name": "goblin/left-shoulder", "x": 15.56, "y": -2.26, "rotation": 62.01, "width": 29, "height": 44 } + }, + "left arm": { + "left arm": { + "name": "goblin/left-arm", + "x": 16.7, + "y": -1.69, + "scaleX": 1.057, + "scaleY": 1.057, + "rotation": 33.84, + "width": 37, + "height": 35 + } + }, + "left hand": { + "left hand": { + "name": "goblin/left-hand", + "x": 3.47, + "y": 3.41, + "scaleX": 0.892, + "scaleY": 0.892, + "rotation": 31.14, + "width": 36, + "height": 41 + } + }, + "right lower leg": { + "right lower leg": { "name": "goblin/right-lower-leg", "x": 25.68, "y": -3.15, "rotation": 111.83, "width": 36, "height": 76 } + }, + "right upper leg": { + "right upper leg": { "name": "goblin/right-upper-leg", "x": 20.35, "y": 1.47, "rotation": 97.49, "width": 34, "height": 63 } + }, + "pelvis": { + "pelvis": { "name": "goblin/pelvis", "x": -5.61, "y": 0.76, "width": 62, "height": 43 } + }, + "left lower leg": { + "left lower leg": { "name": "goblin/left-lower-leg", "x": 23.58, "y": -2.06, "rotation": 105.75, "width": 33, "height": 70 } + }, + "left upper leg": { + "left upper leg": { "name": "goblin/left-upper-leg", "x": 29.68, "y": -3.87, "rotation": 89.09, "width": 33, "height": 73 } + }, + "torso": { + "torso": { "name": "goblin/torso", "x": 38.09, "y": -3.87, "rotation": -94.95, "width": 68, "height": 96 } + }, + "right shoulder": { + "right shoulder": { "name": "goblin/right-shoulder", "x": 15.68, "y": -1.03, "rotation": 130.65, "width": 39, "height": 45 } + }, + "right foot": { + "right foot": { "name": "goblin/right-foot", "x": 23.56, "y": 9.8, "rotation": 1.52, "width": 63, "height": 33 } + }, + "left foot": { + "left foot": { "name": "goblin/left-foot", "x": 24.85, "y": 8.74, "rotation": 3.32, "width": 65, "height": 31 } + }, + "undie straps": { + "undie straps": { "name": "goblin/undie-straps", "x": -3.87, "y": 13.1, "scaleX": 1.089, "width": 55, "height": 19 } + }, + "eyes": { + "eyes closed": { "name": "goblin/eyes-closed", "x": 32.21, "y": -21.27, "rotation": -88.92, "width": 34, "height": 12 } + } + }, + "goblingirl": { + "left upper leg": { + "left upper leg": { "name": "goblingirl/left-upper-leg", "x": 30.21, "y": -2.95, "rotation": 89.09, "width": 33, "height": 70 } + }, + "left lower leg": { + "left lower leg": { "name": "goblingirl/left-lower-leg", "x": 25.02, "y": -0.6, "rotation": 105.75, "width": 33, "height": 70 } + }, + "left foot": { + "left foot": { "name": "goblingirl/left-foot", "x": 25.17, "y": 7.92, "rotation": 3.32, "width": 65, "height": 31 } + }, + "right upper leg": { + "right upper leg": { "name": "goblingirl/right-upper-leg", "x": 19.69, "y": 2.13, "rotation": 97.49, "width": 34, "height": 63 } + }, + "right lower leg": { + "right lower leg": { "name": "goblingirl/right-lower-leg", "x": 26.15, "y": -3.27, "rotation": 111.83, "width": 36, "height": 76 } + }, + "right foot": { + "right foot": { "name": "goblingirl/right-foot", "x": 23.46, "y": 9.66, "rotation": 1.52, "width": 63, "height": 33 } + }, + "torso": { + "torso": { "name": "goblingirl/torso", "x": 36.28, "y": -5.14, "rotation": -95.74, "width": 68, "height": 96 } + }, + "left shoulder": { + "left shoulder": { "name": "goblingirl/left-shoulder", "x": 19.8, "y": -0.42, "rotation": 61.21, "width": 28, "height": 46 } + }, + "left arm": { + "left arm": { "name": "goblingirl/left-arm", "x": 19.64, "y": -2.42, "rotation": 33.05, "width": 37, "height": 35 } + }, + "left hand": { + "left hand": { + "name": "goblingirl/left-hand", + "x": 4.34, + "y": 2.39, + "scaleX": 0.896, + "scaleY": 0.896, + "rotation": 30.34, + "width": 35, + "height": 40 + } + }, + "neck": { + "neck": { "name": "goblingirl/neck", "x": 6.16, "y": -3.14, "rotation": -98.86, "width": 35, "height": 41 } + }, + "head": { + "head": { "name": "goblingirl/head", "x": 27.71, "y": -4.32, "rotation": -85.58, "width": 103, "height": 81 } + }, + "right shoulder": { + "right shoulder": { "name": "goblingirl/right-shoulder", "x": 14.46, "y": 0.45, "rotation": 129.85, "width": 39, "height": 45 } + }, + "right arm": { + "right arm": { "name": "goblingirl/right-arm", "x": 16.85, "y": -0.66, "rotation": 93.52, "width": 28, "height": 50 } + }, + "right hand": { + "right hand": { "name": "goblingirl/right-hand", "x": 7.21, "y": 3.43, "rotation": 91.16, "width": 36, "height": 37 } + }, + "pelvis": { + "pelvis": { "name": "goblingirl/pelvis", "x": -3.87, "y": 3.18, "width": 62, "height": 43 } + }, + "undie straps": { + "undie straps": { "name": "goblingirl/undie-straps", "x": -1.51, "y": 14.18, "width": 55, "height": 19 } + }, + "undies": { + "undies": { "name": "goblingirl/undies", "x": 5.4, "y": 1.7, "width": 36, "height": 29 } + }, + "eyes": { + "eyes closed": { "name": "goblingirl/eyes-closed", "x": 28, "y": -25.54, "rotation": -87.04, "width": 37, "height": 21 } + } + } +}, +"animations": { + "walk": { + "bones": { + "left upper leg": { + "rotate": [ + { "time": 0, "angle": -26.55 }, + { "time": 0.1333, "angle": -8.78 }, + { "time": 0.2333, "angle": 9.51 }, + { "time": 0.3666, "angle": 30.74 }, + { "time": 0.5, "angle": 25.33 }, + { "time": 0.6333, "angle": 26.11 }, + { "time": 0.7333, "angle": -7.7 }, + { "time": 0.8666, "angle": -21.19 }, + { "time": 1, "angle": -26.55 } + ], + "translate": [ + { "time": 0, "x": -1.32, "y": 1.7 }, + { "time": 0.3666, "x": -0.06, "y": 2.42 }, + { "time": 1, "x": -1.32, "y": 1.7 } + ] + }, + "right upper leg": { + "rotate": [ + { "time": 0, "angle": 42.45 }, + { "time": 0.1333, "angle": 52.1 }, + { "time": 0.2333, "angle": 8.53 }, + { "time": 0.5, "angle": -16.93 }, + { "time": 0.6333, "angle": 1.89 }, + { + "time": 0.7333, + "angle": 28.06, + "curve": [ 0.462, 0.11, 1, 1 ] + }, + { + "time": 0.8666, + "angle": 58.68, + "curve": [ 0.5, 0.02, 1, 1 ] + }, + { "time": 1, "angle": 42.45 } + ], + "translate": [ + { "time": 0, "x": 6.23, "y": 0 }, + { "time": 0.2333, "x": 2.14, "y": 2.4 }, + { "time": 0.5, "x": 2.44, "y": 4.8 }, + { "time": 1, "x": 6.23, "y": 0 } + ] + }, + "left lower leg": { + "rotate": [ + { "time": 0, "angle": -22.98 }, + { "time": 0.1333, "angle": -63.5 }, + { "time": 0.2333, "angle": -73.76 }, + { "time": 0.5, "angle": 5.11 }, + { "time": 0.6333, "angle": -28.29 }, + { "time": 0.7333, "angle": 4.08 }, + { "time": 0.8666, "angle": 3.53 }, + { "time": 1, "angle": -22.98 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0 }, + { "time": 0.2333, "x": 2.55, "y": -0.47 }, + { "time": 0.5, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1, "x": 0, "y": 0 } + ] + }, + "left foot": { + "rotate": [ + { "time": 0, "angle": -3.69 }, + { "time": 0.1333, "angle": -10.42 }, + { "time": 0.2333, "angle": -5.01 }, + { "time": 0.3666, "angle": 3.87 }, + { "time": 0.5, "angle": -3.87 }, + { "time": 0.6333, "angle": 2.78 }, + { "time": 0.7333, "angle": 1.68 }, + { "time": 0.8666, "angle": -8.54 }, + { "time": 1, "angle": -3.69 } + ] + }, + "right shoulder": { + "rotate": [ + { + "time": 0, + "angle": 5.29, + "curve": [ 0.264, 0, 0.75, 1 ] + }, + { "time": 0.6333, "angle": 6.65 }, + { "time": 1, "angle": 5.29 } + ] + }, + "right arm": { + "rotate": [ + { + "time": 0, + "angle": -4.02, + "curve": [ 0.267, 0, 0.804, 0.99 ] + }, + { + "time": 0.6333, + "angle": 19.78, + "curve": [ 0.307, 0, 0.787, 0.99 ] + }, + { "time": 1, "angle": -4.02 } + ] + }, + "right hand": { + "rotate": [ + { "time": 0, "angle": 8.98 }, + { "time": 0.6333, "angle": 0.51 }, + { "time": 1, "angle": 8.98 } + ] + }, + "left shoulder": { + "rotate": [ + { + "time": 0, + "angle": 6.25, + "curve": [ 0.339, 0, 0.683, 1 ] + }, + { + "time": 0.5, + "angle": -11.78, + "curve": [ 0.281, 0, 0.686, 0.99 ] + }, + { "time": 1, "angle": 6.25 } + ], + "translate": [ + { "time": 0, "x": 1.15, "y": 0.23 } + ] + }, + "left hand": { + "rotate": [ + { + "time": 0, + "angle": -21.23, + "curve": [ 0.295, 0, 0.755, 0.98 ] + }, + { + "time": 0.5, + "angle": -27.28, + "curve": [ 0.241, 0, 0.75, 0.97 ] + }, + { "time": 1, "angle": -21.23 } + ] + }, + "left arm": { + "rotate": [ + { + "time": 0, + "angle": 28.37, + "curve": [ 0.339, 0, 0.683, 1 ] + }, + { + "time": 0.5, + "angle": 60.09, + "curve": [ 0.281, 0, 0.686, 0.99 ] + }, + { "time": 1, "angle": 28.37 } + ] + }, + "torso": { + "rotate": [ + { "time": 0, "angle": -10.28 }, + { + "time": 0.1333, + "angle": -15.38, + "curve": [ 0.545, 0, 0.818, 1 ] + }, + { + "time": 0.3666, + "angle": -9.78, + "curve": [ 0.58, 0.17, 0.669, 0.99 ] + }, + { + "time": 0.6333, + "angle": -15.75, + "curve": [ 0.235, 0.01, 0.795, 1 ] + }, + { + "time": 0.8666, + "angle": -7.06, + "curve": [ 0.209, 0, 0.816, 0.98 ] + }, + { "time": 1, "angle": -10.28 } + ], + "translate": [ + { "time": 0, "x": -1.29, "y": 1.68 } + ] + }, + "right foot": { + "rotate": [ + { "time": 0, "angle": -5.25 }, + { "time": 0.2333, "angle": -1.91 }, + { "time": 0.3666, "angle": -6.45 }, + { "time": 0.5, "angle": -5.39 }, + { "time": 0.7333, "angle": -11.68 }, + { "time": 0.8666, "angle": 0.46 }, + { "time": 1, "angle": -5.25 } + ] + }, + "right lower leg": { + "rotate": [ + { + "time": 0, + "angle": -3.39, + "curve": [ 0.316, 0.01, 0.741, 0.98 ] + }, + { + "time": 0.1333, + "angle": -45.53, + "curve": [ 0.229, 0, 0.738, 0.97 ] + }, + { "time": 0.2333, "angle": -4.83 }, + { "time": 0.5, "angle": -19.53 }, + { "time": 0.6333, "angle": -64.8 }, + { + "time": 0.7333, + "angle": -82.56, + "curve": [ 0.557, 0.18, 1, 1 ] + }, + { "time": 1, "angle": -3.39 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5, "x": 0, "y": 0 }, + { "time": 0.6333, "x": 2.18, "y": 0.21 }, + { "time": 1, "x": 0, "y": 0 } + ] + }, + "hip": { + "rotate": [ + { "time": 0, "angle": 0, "curve": "stepped" }, + { "time": 1, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": -4.16 }, + { + "time": 0.1333, + "x": 0, + "y": -7.05, + "curve": [ 0.359, 0.47, 0.646, 0.74 ] + }, + { "time": 0.3666, "x": 0, "y": 6.78 }, + { "time": 0.5, "x": 0, "y": -6.13 }, + { + "time": 0.6333, + "x": 0, + "y": -7.05, + "curve": [ 0.359, 0.47, 0.646, 0.74 ] + }, + { "time": 0.8666, "x": 0, "y": 6.78 }, + { "time": 1, "x": 0, "y": -4.16 } + ] + }, + "neck": { + "rotate": [ + { "time": 0, "angle": 3.6 }, + { "time": 0.1333, "angle": 17.49 }, + { "time": 0.2333, "angle": 6.1 }, + { "time": 0.3666, "angle": 3.45 }, + { "time": 0.5, "angle": 5.17 }, + { "time": 0.6333, "angle": 18.36 }, + { "time": 0.7333, "angle": 6.09 }, + { "time": 0.8666, "angle": 2.28 }, + { "time": 1, "angle": 3.6 } + ] + }, + "head": { + "rotate": [ + { + "time": 0, + "angle": 3.6, + "curve": [ 0, 0, 0.704, 1.17 ] + }, + { "time": 0.1333, "angle": -0.2 }, + { "time": 0.2333, "angle": 6.1 }, + { "time": 0.3666, "angle": 3.45 }, + { + "time": 0.5, + "angle": 5.17, + "curve": [ 0, 0, 0.704, 1.61 ] + }, + { "time": 0.6666, "angle": 1.1 }, + { "time": 0.7333, "angle": 6.09 }, + { "time": 0.8666, "angle": 2.28 }, + { "time": 1, "angle": 3.6 } + ] + } + }, + "slots": { + "eyes": { + "attachment": [ + { "time": 0.7, "name": "eyes closed" }, + { "time": 0.8, "name": null } + ] + } + } + } +} +} \ No newline at end of file diff --git a/spine-js/data/goblins.png b/spine-js/data/goblins.png new file mode 100644 index 000000000..863b29467 Binary files /dev/null and b/spine-js/data/goblins.png differ diff --git a/spine-js/data/spineboy.atlas b/spine-js/data/spineboy.atlas new file mode 100644 index 000000000..1f482c0a1 --- /dev/null +++ b/spine-js/data/spineboy.atlas @@ -0,0 +1,166 @@ + +spineboy.png +format: RGBA8888 +filter: Linear,Linear +repeat: none +head + rotate: false + xy: 1, 122 + size: 121, 132 + orig: 121, 132 + offset: 0, 0 + index: -1 +torso + rotate: false + xy: 1, 28 + size: 68, 92 + orig: 68, 92 + offset: 0, 0 + index: -1 +left-pant-bottom + rotate: false + xy: 1, 4 + size: 44, 22 + orig: 44, 22 + offset: 0, 0 + index: -1 +right-pant-bottom + rotate: false + xy: 47, 8 + size: 46, 18 + orig: 46, 18 + offset: 0, 0 + index: -1 +right-upper-leg + rotate: false + xy: 71, 50 + size: 44, 70 + orig: 44, 70 + offset: 0, 0 + index: -1 +pelvis + rotate: false + xy: 95, 1 + size: 63, 47 + orig: 63, 47 + offset: 0, 0 + index: -1 +left-upper-leg + rotate: false + xy: 117, 53 + size: 33, 67 + orig: 33, 67 + offset: 0, 0 + index: -1 +right-foot + rotate: false + xy: 160, 224 + size: 67, 30 + orig: 67, 30 + offset: 0, 0 + index: -1 +left-shoulder + rotate: false + xy: 124, 201 + size: 34, 53 + orig: 34, 53 + offset: 0, 0 + index: -1 +left-ankle + rotate: false + xy: 229, 222 + size: 25, 32 + orig: 25, 32 + offset: 0, 0 + index: -1 +left-foot + rotate: false + xy: 160, 192 + size: 65, 30 + orig: 65, 30 + offset: 0, 0 + index: -1 +neck + rotate: false + xy: 124, 171 + size: 34, 28 + orig: 34, 28 + offset: 0, 0 + index: -1 +right-arm + rotate: false + xy: 124, 124 + size: 21, 45 + orig: 21, 45 + offset: 0, 0 + index: -1 +right-ankle + rotate: false + xy: 227, 190 + size: 25, 30 + orig: 25, 30 + offset: 0, 0 + index: -1 +left-hand + rotate: false + xy: 147, 131 + size: 35, 38 + orig: 35, 38 + offset: 0, 0 + index: -1 +left-arm + rotate: false + xy: 184, 161 + size: 35, 29 + orig: 35, 29 + offset: 0, 0 + index: -1 +eyes-closed + rotate: false + xy: 221, 161 + size: 34, 27 + orig: 34, 27 + offset: 0, 0 + index: -1 +right-lower-leg + rotate: false + xy: 152, 65 + size: 51, 64 + orig: 51, 64 + offset: 0, 0 + index: -1 +right-foot-idle + rotate: false + xy: 184, 131 + size: 53, 28 + orig: 53, 28 + offset: 0, 0 + index: -1 +left-lower-leg + rotate: false + xy: 205, 65 + size: 49, 64 + orig: 49, 64 + offset: 0, 0 + index: -1 +right-shoulder + rotate: false + xy: 160, 12 + size: 52, 51 + orig: 52, 51 + offset: 0, 0 + index: -1 +eyes + rotate: false + xy: 214, 36 + size: 34, 27 + orig: 34, 27 + offset: 0, 0 + index: -1 +right-hand + rotate: false + xy: 214, 2 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 diff --git a/spine-js/index.html b/spine-js/data/spineboy.json similarity index 98% rename from spine-js/index.html rename to spine-js/data/spineboy.json index 14332ecd7..57b0f6cbe 100644 --- a/spine-js/index.html +++ b/spine-js/data/spineboy.json @@ -1,16 +1,4 @@ - - - - spine-js - - - - - - - - \ No newline at end of file +} \ No newline at end of file diff --git a/spine-js/data/spineboy.png b/spine-js/data/spineboy.png new file mode 100644 index 000000000..b8b493dfd Binary files /dev/null and b/spine-js/data/spineboy.png differ diff --git a/spine-js/spine.js b/spine-js/spine.js index d67d222b2..4d5f2b684 100644 --- a/spine-js/spine.js +++ b/spine-js/spine.js @@ -24,6 +24,7 @@ spine.SlotData.prototype = { spine.Bone = function (boneData, parent) { this.data = boneData; this.parent = parent; + this.setToSetupPose(); }; spine.Bone.yDown = false; spine.Bone.prototype = { @@ -49,8 +50,9 @@ spine.Bone.prototype = { this.worldScaleY = this.scaleY; this.worldRotation = this.rotation; } - var cos = worldRotation * Math.PI / 180; - var sin = worldRotation * Math.PI / 180; + var radians = this.worldRotation * Math.PI / 180; + var cos = Math.cos(radians); + var sin = Math.sin(radians); this.m00 = cos * this.worldScaleX; this.m10 = sin * this.worldScaleX; this.m01 = -sin * this.worldScaleY; @@ -82,6 +84,7 @@ spine.Slot = function (slotData, skeleton, bone) { this.data = slotData; this.skeleton = skeleton; this.bone = bone; + this.setToSetupPose(); }; spine.Slot.prototype = { r: 1, g: 1, b: 1, a: 1, @@ -104,10 +107,10 @@ spine.Slot.prototype = { this.b = data.b; this.a = data.a; - var slots = this.skeleton.slots; - for (var i = 0, n = slots.length; i < n; i++) { - if (slots[i] == this) { - this.setAttachment(!data.attachmentName ? null : this.skeleton.getAttachment(i, data.attachmentName)); + var slotDatas = this.skeleton.data.slots; + for (var i = 0, n = slotDatas.length; i < n; i++) { + if (slotDatas[i] == data) { + this.setAttachment(!data.attachmentName ? null : this.skeleton.getAttachmentBySlotIndex(i, data.attachmentName)); break; } } @@ -131,7 +134,7 @@ spine.Skin.prototype = { var slotIndex = parseInt(key.substring(0, colon)); var name = key.substring(colon + 1); var slot = skeleton.slots[slotIndex]; - if (slot.attachment.name == name) { + if (slot.attachment && slot.attachment.name == name) { var attachment = this.getAttachment(slotIndex, name); if (attachment) slot.setAttachment(attachment); } @@ -147,11 +150,13 @@ spine.Animation = function (name, timelines, duration) { spine.Animation.prototype = { apply: function (skeleton, time, loop) { if (loop && this.duration != 0) time %= this.duration; + var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) timelines[i].apply(skeleton, time, 1); }, mix: function (skeleton, time, loop, alpha) { if (loop && this.duration != 0) time %= this.duration; + var timelines = this.timelines; for (var i = 0, n = timelines.length; i < n; i++) timelines[i].apply(skeleton, time, alpha); } @@ -217,7 +222,7 @@ spine.Curves.prototype = { var curveIndex = frameIndex * 6; var curves = this.curves; var dfx = curves[curveIndex]; - if (dfx == 0/*LINEAR*/) return percent; + if (!dfx/*LINEAR*/) return percent; if (dfx == -1/*STEPPED*/) return 0; var dfy = curves[curveIndex + 1]; var ddfx = curves[curveIndex + 2]; @@ -467,7 +472,7 @@ spine.AttachmentTimeline.prototype = { frameIndex = spine.binarySearch(frames, time, 1) - 1; var attachmentName = this.attachmentNames[frameIndex]; - skeleton.slots[this.slotIndex].setAttachment(!attachmentName ? null : skeleton.getAttachment(this.slotIndex, attachmentName)); + skeleton.slots[this.slotIndex].setAttachment(!attachmentName ? null : skeleton.getAttachmentBySlotIndex(this.slotIndex, attachmentName)); } }; @@ -530,8 +535,8 @@ spine.Skeleton = function (skeletonData) { this.bones = []; for (var i = 0, n = skeletonData.bones.length; i < n; i++) { var boneData = skeletonData.bones[i]; - var parent = !boneData.parent ? null : bones[skeletonData.bones.indexOf(boneData.parent)]; - bones.push(new spine.Bone(boneData, parent)); + var parent = !boneData.parent ? null : this.bones[skeletonData.bones.indexOf(boneData.parent)]; + this.bones.push(new spine.Bone(boneData, parent)); } this.slots = []; @@ -540,8 +545,8 @@ spine.Skeleton = function (skeletonData) { var slotData = skeletonData.slots[i]; var bone = this.bones[skeletonData.bones.indexOf(slotData.boneData)]; var slot = new spine.Slot(slotData, this, bone); - slots.push(slot); - drawOrder.push(slot); + this.slots.push(slot); + this.drawOrder.push(slot); } }; spine.Skeleton.prototype = { @@ -559,8 +564,8 @@ spine.Skeleton.prototype = { }, /** Sets the bones and slots to their setup pose values. */ setToSetupPose: function () { - setBonesToSetupPose(); - setSlotsToSetupPose(); + this.setBonesToSetupPose(); + this.setSlotsToSetupPose(); }, setBonesToSetupPose: function () { var bones = this.bones; @@ -574,7 +579,7 @@ spine.Skeleton.prototype = { }, /** @return May return null. */ getRootBone: function () { - return bones.length == 0 ? null : bones[0]; + return this.bones.length == 0 ? null : this.bones[0]; }, /** @return May be null. */ findBone: function (boneName) { @@ -613,12 +618,12 @@ spine.Skeleton.prototype = { * from the new skin are attached if the corresponding attachment from the old skin was attached. * @param newSkin May be null. */ setSkin: function (newSkin) { - if (this.skin && newSkin) newSkin._attachAll(this, skin); + if (this.skin && newSkin) newSkin._attachAll(this, this.skin); this.skin = newSkin; }, /** @return May be null. */ getAttachmentBySlotName: function (slotName, attachmentName) { - return this.getAttachment(this.data.findSlotIndex(slotName), attachmentName); + return this.getAttachmentBySlotIndex(this.data.findSlotIndex(slotName), attachmentName); }, /** @return May be null. */ getAttachmentBySlotIndex: function (slotIndex, attachmentName) { @@ -671,6 +676,7 @@ spine.RegionAttachment.prototype = { regionWidth: 0, regionHeight: 0, regionOriginalWidth: 0, regionOriginalHeight: 0, setUVs: function (u, v, u2, v2, rotate) { + var uvs = this.uvs; if (rotate) { uvs[2/*X2*/] = u; uvs[3/*Y2*/] = v2; @@ -719,21 +725,20 @@ spine.RegionAttachment.prototype = { offset[6/*X4*/] = localX2Cos - localYSin; offset[7/*Y4*/] = localYCos + localX2Sin; }, - updateVertices: function (bone, vertices) { + computeVertices: function (bone, vertices) { var x = bone.worldX; var y = bone.worldY; var m00 = bone.m00; var m01 = bone.m01; var m10 = bone.m10; var m11 = bone.m11; - var vertices = this.vertices; var offset = this.offset; vertices[0/*X1*/] = offset[0/*X1*/] * m00 + offset[1/*Y1*/] * m01 + x; vertices[1/*Y1*/] = offset[0/*X1*/] * m10 + offset[1/*Y1*/] * m11 + y; vertices[2/*X2*/] = offset[2/*X2*/] * m00 + offset[3/*Y2*/] * m01 + x; vertices[3/*Y2*/] = offset[2/*X2*/] * m10 + offset[3/*Y2*/] * m11 + y; - vertices[4/*X3*/] = offset[4/*X3*/] * m00 + offset[4/*X3*/] * m01 + x; - vertices[4/*X3*/] = offset[4/*X3*/] * m10 + offset[4/*X3*/] * m11 + y; + vertices[4/*X3*/] = offset[4/*X3*/] * m00 + offset[5/*X3*/] * m01 + x; + vertices[5/*X3*/] = offset[4/*X3*/] * m10 + offset[5/*X3*/] * m11 + y; vertices[6/*X4*/] = offset[6/*X4*/] * m00 + offset[7/*Y4*/] * m01 + x; vertices[7/*Y4*/] = offset[6/*X4*/] * m10 + offset[7/*Y4*/] * m11 + y; } @@ -752,10 +757,10 @@ spine.AnimationStateData.prototype = { this.setMix(from, to, duration); }, setMix: function (from, to, duration) { - animationToMixTime[from.name + ":" + to.name] = duration; + this.animationToMixTime[from.name + ":" + to.name] = duration; }, getMix: function (from, to) { - var time = animationToMixTime[from.name + ":" + to.name]; + var time = this.animationToMixTime[from.name + ":" + to.name]; return time ? time : 0; } }; @@ -791,12 +796,12 @@ spine.AnimationState.prototype = { if (this.previous) { this.previous.apply(skeleton, this.previousTime, this.previousLoop); var alpha = this.mixTime / this.mixDuration; - if (this.alpha >= 1) { - this.alpha = 1; + if (alpha >= 1) { + alpha = 1; this.previous = null; } this.current.mix(skeleton, this.currentTime, this.currentLoop, alpha); - } else + } else this.current.apply(skeleton, this.currentTime, this.currentLoop); }, clearAnimation: function () { @@ -844,10 +849,10 @@ spine.AnimationState.prototype = { entry.animation = animation; entry.loop = loop; - if (delay <= 0) { + if (!delay || delay <= 0) { var previousAnimation = this.queue.length == 0 ? this.current : this.queue[this.queue.length - 1].animation; if (previousAnimation != null) - delay = previousAnimation.duration - this.data.getMix(previousAnimation, animation) + delay; + delay = previousAnimation.duration - this.data.getMix(previousAnimation, animation) + (delay || 0); else delay = 0; } @@ -942,7 +947,7 @@ spine.SkeletonJson.prototype = { name = map["name"] || name; var type = spine.AttachmentType[map["type"] || "region"]; - var attachment = this.attachmentLoader(skin, type, name); + var attachment = this.attachmentLoader.newAttachment(skin, type, name); if (type == spine.AttachmentType.region) { attachment.x = (map["x"] || 0) * this.scale; @@ -983,7 +988,7 @@ spine.SkeletonJson.prototype = { frameIndex++; } timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.frameCount * 2 - 2]); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]); } else if (timelineName == "translate" || timelineName == "scale") { var timeline; @@ -1006,7 +1011,7 @@ spine.SkeletonJson.prototype = { frameIndex++; } timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.frameCount * 3 - 3]); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]); } else throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"; @@ -1019,9 +1024,9 @@ spine.SkeletonJson.prototype = { var slotMap = slots[slotName]; var slotIndex = skeletonData.findSlotIndex(slotName); - for (var timelineName in boneMap) { - if (!boneMap.hasOwnProperty(timelineName)) continue; - var values = boneMap[timelineName2]; + for (var timelineName in slotMap) { + if (!slotMap.hasOwnProperty(timelineName)) continue; + var values = slotMap[timelineName]; if (timelineName == "color") { var timeline = new spine.ColorTimeline(values.length); timeline.slotIndex = slotIndex; @@ -1039,7 +1044,7 @@ spine.SkeletonJson.prototype = { frameIndex++; } timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.frameCount * 5 - 5]); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 5 - 5]); } else if (timelineName == "attachment") { var timeline = new spine.AttachmentTimeline(values.length); @@ -1051,7 +1056,7 @@ spine.SkeletonJson.prototype = { timeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]); } timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.frameCount - 1]); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); } else throw "Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"; @@ -1073,3 +1078,240 @@ spine.SkeletonJson.toColor = function (hexString, colorIndex) { if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString; return parseInt(hexString.substring(colorIndex * 2, 2), 16) / 255; }; + +spine.Atlas = function (atlasText, textureLoader) { + this.textureLoader = textureLoader; + this.pages = []; + this.regions = []; + + var reader = new spine.AtlasReader(atlasText); + var tuple = []; + tuple.length = 4; + var page = null; + while (true) { + var line = reader.readLine(); + if (line == null) break; + line = reader.trim(line); + if (line.length == 0) + page = null; + else if (!page) { + page = new spine.AtlasPage(); + page.name = line; + + page.format = spine.Atlas.Format[reader.readValue()]; + + reader.readTuple(tuple); + page.minFilter = spine.Atlas.TextureFilter[tuple[0]]; + page.magFilter = spine.Atlas.TextureFilter[tuple[1]]; + + var direction = reader.readValue(); + page.uWrap = spine.Atlas.TextureWrap.clampToEdge; + page.vWrap = spine.Atlas.TextureWrap.clampToEdge; + if (direction == "x") + page.uWrap = spine.Atlas.TextureWrap.repeat; + else if (direction == "y") + page.vWrap = spine.Atlas.TextureWrap.repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = spine.Atlas.TextureWrap.repeat; + + textureLoader.load(page, line); + + this.pages.push(page); + + } else { + var region = new spine.AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = reader.readValue() == "true"; + + reader.readTuple(tuple); + var x = parseInt(tuple[0]); + var y = parseInt(tuple[1]); + + reader.readTuple(tuple); + var width = parseInt(tuple[0]); + var height = parseInt(tuple[1]); + + region.u = x / page.width; + region.v = y / page.height; + if (region.rotate) { + region.u2 = (x + height) / page.width; + region.v2 = (y + width) / page.height; + } else { + region.u2 = (x + width) / page.width; + region.v2 = (y + height) / page.height; + } + region.x = x; + region.y = y; + region.width = Math.abs(width); + region.height = Math.abs(height); + + if (reader.readTuple(tuple) == 4) { // split is optional + region.splits = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])]; + + if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits + region.pads = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])]; + + reader.readTuple(tuple); + } + } + + region.originalWidth = parseInt(tuple[0]); + region.originalHeight = parseInt(tuple[1]); + + reader.readTuple(tuple); + region.offsetX = parseInt(tuple[0]); + region.offsetY = parseInt(tuple[1]); + + region.index = parseInt(reader.readValue()); + + this.regions.push(region); + } + } +}; +spine.Atlas.prototype = { + findRegion: function (name) { + var regions = this.regions; + for (var i = 0, n = regions.length; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + }, + dispose: function () { + var pages = this.pages; + for (var i = 0, n = pages.length; i < n; i++) + this.textureLoader.unload(pages[i].rendererObject); + }, + updateUVs: function (page) { + var regions = this.regions; + for (var i = 0, n = regions.length; i < n; i++) { + var region = regions[i]; + if (region.page != page) continue; + region.u = region.x / page.width; + region.v = region.y / page.height; + if (region.rotate) { + region.u2 = (region.x + region.height) / page.width; + region.v2 = (region.y + region.width) / page.height; + } else { + region.u2 = (region.x + region.width) / page.width; + region.v2 = (region.y + region.height) / page.height; + } + } + } +}; + +spine.Atlas.Format = { + alpha: 0, + intensity: 1, + luminanceAlpha: 2, + rgb565: 3, + rgba4444: 4, + rgb888: 5, + rgba8888: 6 +}; + +spine.Atlas.TextureFilter = { + nearest: 0, + linear: 1, + mipMap: 2, + mipMapNearestNearest: 3, + mipMapLinearNearest: 4, + mipMapNearestLinear: 5, + mipMapLinearLinear: 6 +}; + +spine.Atlas.TextureWrap = { + mirroredRepeat: 0, + clampToEdge: 1, + repeat: 2 +}; + +spine.AtlasPage = function () {}; +spine.AtlasPage.prototype = { + name: null, + format: null, + minFilter: null, + magFilter: null, + uWrap: null, + vWrap: null, + rendererObject: null, + width: 0, + height: 0 +}; + +spine.AtlasRegion = function () {}; +spine.AtlasRegion.prototype = { + page: null, + name: null, + x: 0, y: 0, + width: 0, height: 0, + u: 0, v: 0, u2: 0, v2: 0, + offsetX: 0, offsetY: 0, + originalWidth: 0, originalHeight: 0, + index: 0, + rotate: false, + splits: null, + pads: null, +}; + +spine.AtlasReader = function (text) { + this.lines = text.split(/\r\n|\r|\n/); +}; +spine.AtlasReader.prototype = { + index: 0, + trim: function (value) { + return value.replace(/^\s+|\s+$/g, ""); + }, + readLine: function () { + if (this.index >= this.lines.length) return null; + return this.lines[this.index++]; + }, + readValue: function () { + var line = this.readLine(); + var colon = line.indexOf(":"); + if (colon == -1) throw "Invalid line: " + line; + return this.trim(line.substring(colon + 1)); + }, + /** Returns the number of tuple values read (2 or 4). */ + readTuple: function (tuple) { + var line = this.readLine(); + var colon = line.indexOf(":"); + if (colon == -1) throw "Invalid line: " + line; + var i = 0, lastMatch= colon + 1; + for (; i < 3; i++) { + var comma = line.indexOf(",", lastMatch); + if (comma == -1) { + if (i == 0) throw "Invalid line: " + line; + break; + } + tuple[i] = this.trim(line.substr(lastMatch, comma - lastMatch)); + lastMatch = comma + 1; + } + tuple[i] = this.trim(line.substring(lastMatch)); + return i + 1; + } +} + +spine.AtlasAttachmentLoader = function (atlas) { + this.atlas = atlas; +} +spine.AtlasAttachmentLoader.prototype = { + newAttachment: function (skin, type, name) { + switch (type) { + case spine.AttachmentType.region: + var region = this.atlas.findRegion(name); + if (!region) throw "Region not found in atlas: " + name + " (" + type + ")"; + var attachment = new spine.RegionAttachment(name); + attachment.rendererObject = region; + attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } + throw "Unknown attachment type: " + type; + } +} diff --git a/spine-js/turbulenz/SpriteBatch.js b/spine-js/turbulenz/SpriteBatch.js new file mode 100644 index 000000000..a52e54884 --- /dev/null +++ b/spine-js/turbulenz/SpriteBatch.js @@ -0,0 +1,44 @@ + +function SpriteBatch (draw2D) { + this.draw2D = draw2D; + this.buffer = []; +} +SpriteBatch.prototype = { + count: 0, + texture: null, + begin: function (blendMode, sortMode) { + this.draw2D.begin(blendMode, sortMode); + }, + add: function (texture, x1, y1, x2, y2, x3, y3, x4, y4, r, g, b, a, u1, v1, u2, v2) { + if (this.texture && this.texture != texture) this.flush(); + this.texture = texture; + var index = this.count++ * 16; + var buffer = this.buffer; + buffer[index++] = x1; + buffer[index++] = y1; + buffer[index++] = x2; + buffer[index++] = y2; + buffer[index++] = x3; + buffer[index++] = y3; + buffer[index++] = x4; + buffer[index++] = y4; + buffer[index++] = r; + buffer[index++] = g; + buffer[index++] = b; + buffer[index++] = a; + buffer[index++] = u1; + buffer[index++] = v1; + buffer[index++] = u2; + buffer[index] = v2; + }, + flush: function () { + if (!this.texture) return; + this.draw2D.drawRaw(this.texture, this.buffer, this.count, 0); + this.texture = null; + this.count = 0; + }, + end: function () { + this.flush(); + this.draw2D.end(); + } +}; diff --git a/spine-js/turbulenz/draw2d.js b/spine-js/turbulenz/draw2d.js new file mode 100644 index 000000000..77a09c6ac --- /dev/null +++ b/spine-js/turbulenz/draw2d.js @@ -0,0 +1,2380 @@ +// Copyright (c) 2012 Turbulenz Limited + +/*global +Draw2D: false +Float32Array: false +*/ + +// +// Draw2DGroup. Wraps vertex buffer data with pairings of indices and textures +// representing subsets of buffer relating to a set of equal-texture quads. +// +// [ sprite1 sprite2 sprite3 sprite4 sprite5 ] +// \---------------/ \------/ \--------------/ +// texture 1 texture 2 texture 3 +// 12 indices 6 indices 12 indices +// +function Draw2DGroup() {} +Draw2DGroup.create = function draw2DGroupFn() +{ + var group = new Draw2DGroup(); + + // pairs of index counts + associated texture for subset of group. + group.indices = []; + group.textures = []; + group.numSets = 0; + + // vertex buffer for group. + group.vertexBufferData = new Draw2D.prototype.floatArray(1024); + group.numVertices = 0; + + return group; +}; + +function Draw2DSprite() {} +Draw2DSprite.prototype = { + + version : 1, + + // + // Assumption is that user will not be performing these actions frequently. + // To that end, we provide a function which performs the ssary side effects + // on call, to prevent an overhead for lazy evaluation. + // + getTextureRectangle : function getTextureRectangleFn(dst) + { + if (dst === undefined) + { + dst = new Draw2D.prototype.floatArray(4); + } + var data = this.data; + var texture = this._texture; + if (texture) + { + dst[0] = data[12] * texture.width; + dst[1] = data[13] * texture.height; + dst[2] = data[14] * texture.width; + dst[3] = data[15] * texture.height; + } + else + { + dst[0] = data[12]; + dst[1] = data[13]; + dst[2] = data[14]; + dst[3] = data[15]; + } + return dst; + }, + setTextureRectangle : function setTextureRectangleFn(uvRect) + { + var data = this.data; + var texture = this._texture; + if (texture) + { + var iwidth = 1 / texture.width; + var iheight = 1 / texture.height; + data[12] = uvRect[0] * iwidth; + data[13] = uvRect[1] * iheight; + data[14] = uvRect[2] * iwidth; + data[15] = uvRect[3] * iheight; + } + else + { + data[12] = uvRect[0]; + data[13] = uvRect[1]; + data[14] = uvRect[2]; + data[15] = uvRect[3]; + } + }, + + getColor : function getColorFn(dst) + { + if (dst === undefined) + { + dst = new Draw2D.prototype.floatArray(4); + } + var data = this.data; + dst[0] = data[8]; + dst[1] = data[9]; + dst[2] = data[10]; + dst[3] = data[11]; + return dst; + }, + setColor : function setColorFn(color) + { + var data = this.data; + data[8] = color[0]; + data[9] = color[1]; + data[10] = color[2]; + data[11] = color[3]; + }, + + getTexture : function getTextureFn() + { + return this._texture; + }, + setTexture : function setTextureFn(texture) + { + if (this._texture !== texture) + { + var su = (this._texture ? this._texture.width : 1.0) / (texture ? texture.width : 1.0); + var sv = (this._texture ? this._texture.height : 1.0) / (texture ? texture.height : 1.0); + this._texture = texture || null; + + // re-normalise texture coordinates. + var data = this.data; + data[12] *= su; + data[13] *= sv; + data[14] *= su; + data[15] *= sv; + } + }, + + getWidth : function getWidthFn() + { + return this.data[17] * 2; + }, + setWidth : function setWidthFn(width) + { + width *= 0.5; + var data = this.data; + if (data[17] !== width) + { + data[17] = width; + this._invalidate(); + } + }, + + getHeight : function getHeightFn() + { + return this.data[18] * 2; + }, + setHeight : function setHeightFn(height) + { + height *= 0.5; + var data = this.data; + if (data[18] !== height) + { + data[18] = height; + this._invalidate(); + } + }, + + getScale : function getScaleFn(dst) + { + if (dst === undefined) + { + dst = new Draw2D.prototype.floatArray(2); + } + var data = this.data; + dst[0] = data[19]; + dst[1] = data[20]; + return dst; + }, + setScale : function setScaleFn(scale) + { + var scaleX = scale[0]; + var scaleY = scale[1]; + var data = this.data; + if (data[19] !== scaleX || data[20] !== scaleY) + { + data[19] = scaleX; + data[20] = scaleY; + this._invalidate(); + } + }, + + getShear : function getShearFn(dst) + { + if (dst === undefined) + { + dst = new Draw2D.prototype.floatArray(2); + } + var data = this.data; + dst[0] = data[21]; + dst[1] = data[22]; + return dst; + }, + setShear : function setShearFn(shear) + { + var shearX = shear[0]; + var shearY = shear[1]; + var data = this.data; + if (data[21] !== shearX || data[22] !== shearY) + { + data[21] = shearX; + data[22] = shearY; + this._invalidate(); + } + }, + + getOrigin : function getOriginFn(dst) + { + if (dst === undefined) + { + dst = new Draw2D.prototype.floatArray(2); + } + var data = this.data; + dst[0] = data[23]; + dst[1] = data[24]; + return dst; + }, + setOrigin : function setOriginFn(origin) + { + var originX = origin[0]; + var originY = origin[1]; + var data = this.data; + if (data[23] !== originX || data[24] !== originY) + { + data[23] = originX; + data[24] = originY; + this._invalidate(); + } + }, + + // Method for internal use only. + // + // Recompute locally defined vectors. + _invalidate : function invalidateFn() + { + var data = this.data; + // [ T1 T2 ] = [ scaleX 0 ] [ 1 shearX ] + // [ T3 T4 ] [ 0 scaleY ] [ shearY 1 ] + var T1 = data[19]; + var T2 = data[19] * data[21]; + var T3 = data[20] * data[22]; + var T4 = data[20]; + + // Recompute locally defined position of true center of sprite. + var x = data[17] - data[23]; // x = width/2 - originX + var y = data[18] - data[24]; // y = height/2 - originY + var cx = data[25] = (T1 * x + T2 * y); // (cx) = T (x) + var cy = data[26] = (T3 * x + T4 * y); // (cy) (y) + + // Recompute locally defined position of top-left vertex relative to center of sprite. + x = -data[17]; // x = -width/2 + y = -data[18]; // y = -height/2 + var ux = data[27] = (T1 * x + T2 * y); // (ux) = T (x) + var uy = data[28] = (T3 * x + T4 * y); // (uy) (y) + + // Recompute locally defined position of top-right vertex relative to center of sprite. + x = -x; // x = width / 2 + var vx = data[29] = (T1 * x + T2 * y); // (vx) = T (x) + var vy = data[30] = (T3 * x + T4 * y); // (vy) (y) + + // Rotate vectors to screen space so that in the case that rotation is not performed + // These vectors are still valid. + var rotation = data[16] = this.rotation; + var cos = Math.cos(rotation); + var sin = Math.sin(rotation); + + data[31] = ((cos * cx) - (sin * cy)); + data[32] = ((sin * cx) + (cos * cy)); + data[33] = ((cos * ux) - (sin * uy)); + data[34] = ((sin * ux) + (cos * uy)); + data[35] = ((cos * vx) - (sin * vy)); + data[36] = ((sin * vx) + (cos * vy)); + + // Compute suitable epsilon to consider rotations equals. + // We do this by finding the vertex furthest from defined center of rotation. + // And using its distance to compute what rotation constitutes a 'visible' rotation. + // + // Positions of vertices relative to origin are given by: + // v1 = c + u, v2 = c + v, v3 = c - v, v4 = c - u. + // |v1|^2 = |c|^2 + |u|^2 + 2c.u + // |v4|^2 = |c|^2 + |u|^2 - 2c.u + // |v2|^2 = |c|^2 + |v|^2 + 2c.v + // |v3|^2 = |c|^2 + |v|^2 - 2c.v + // + // Compute r1 = |u|^2 + abs(2c.u) + // Compute r2 = |v|^2 + abs(2c.v) + // + // Finally max(|vi|^2) = |c|^2 + max(r1, r2) + // + var dot = 2 * ((cx * ux) + (cy * uy)); + if (dot < 0) + { + dot = -dot; + } + var r1 = (ux * ux) + (uy * uy) + dot; + + dot = 2 * ((cx * vx) + (cy * vy)); + if (dot < 0) + { + dot = -dot; + } + var r2 = (vx * vx) + (vy * vy) + dot; + + if (r2 > r1) + { + r1 = r2; + } + + r1 += ((cx * cx) + (cy * cy)); + // r1 is the squared distance to furthest vertex. + // + // We permit a half pixel movement to be considered a 'true' movement. + // Squared rotation required to impart this movement on furthest vertex is + data[37] = (0.25 / r1); // squared epsilon + }, + + // Method for internal use only. + // + // Recompute draw2d coordinate space vertices and vectors. + _update : function _updateFn(angleScaleFactor) + { + var data = this.data; + var x, y, u, v; + + // Check if rotation has been modified + x = this.rotation; + y = x - data[16]; // y = rotation - previousRotation + if ((y * y) > (data[37] * angleScaleFactor)) // if |y| > epsilon + { + data[16] = x; //previousRotation = rotation + u = Math.cos(x); + v = Math.sin(x); + + // rotate locally defined vectors. + x = data[25]; + y = data[26]; + data[31] = (u * x - v * y); // (px) = [cos -sin] (cx) + data[32] = (v * x + u * y); // (py) = [sin cos] (cy) + + x = data[27]; + y = data[28]; + data[33] = (u * x - v * y); // (x1) = [cos -sin] (ux) + data[34] = (v * x + u * y); // (y1) = [sin cos] (uy) + + x = data[29]; + y = data[30]; + data[35] = (u * x - v * y); // (x2) = [cos -sin] (vx) + data[36] = (v * x + u * y); // (y2) = [sin cos] (vy) + } + + // Compute center of this sprite in screen space. + u = this.x + data[31]; // u = centerX = positionX + px + v = this.y + data[32]; // v = centerY = positionY + py + + // Compute vertex positions in screen space. + x = data[33]; + y = data[34]; + data[0] = u + x; // v1x = centerX + x1 + data[1] = v + y; // v1y = centerY + y1 + data[6] = u - x; // v4x = centerX - x1 + data[7] = v - y; // v4y = centerY - y1 + + x = data[35]; + y = data[36]; + data[2] = u + x; // v2x = centerX + x2 + data[3] = v + y; // v2y = centerY + y2 + data[4] = u - x; // v3x = centerX - x2 + data[5] = v - y; // v3y = centerY - y2 + } +}; + +Draw2DSprite.create = function draw2DSpriteCreateFn(params) +{ + if ((params.width === undefined || params.height === undefined) && !params.texture) + { + return null; + } + + // data: + // --- + // First 16 values reserved for Draw2DSpriteData. + // includes colour and texture coordinates. + // + // 16 : old_rotation (for lazy evaluation) + // 17,18 : width/2, height/2 (changed by user via function) + // 19,20 : scaleX, scaleY (changed by user via function) + // 21,22 : shearX, shearY (changed by user via function) + // 23,24 : originX, originY (changed by user via function) + // 25,26 : cx, cy // locally defined position of true center of sprite relative to origin + // (dependant on scale/shear/center/dimension) + // 27,28 : u1, v1 // locally defined position of top-left vertex relative to center of sprite. + // (dependant on scale/shear/dimension) + // 29,30 : u2, v2 // locally defined position of top-right vertex relative to center of sprite. + // (dependant on scale/shear/dimension) + // 31,32 : px, py // relative defined position of true center of sprite relative to origin + // (dependant on rotation and cx,cy) + // 33,34 : x1, y1 // relative defined position of top-left vertex relative to center of sprite. + // (dependant on rotation and u1,v1) + // 35,36 : x2, y2 // relative defined position of top-right vertex relative to center of sprite. + // (dependant on rotation and u2,v2) + // 37 : Squared epsilon to consider rotations equal based on dimensions. + var s = new Draw2DSprite(); + var data = s.data = new Draw2D.prototype.floatArray(38); + + // texture (not optional) + var texture = s._texture = params.texture || null; + + // position (optional, default 0,0) + s.x = (params.x || 0.0); + s.y = (params.y || 0.0); + + // rotation (optional, default 0) + s.rotation = data[16] = (params.rotation || 0.0); + + // colour (optional, default [1,1,1,1]) + var color = params.color; + data[8] = (color ? color[0] : 1.0); + data[9] = (color ? color[1] : 1.0); + data[10] = (color ? color[2] : 1.0); + data[11] = (color ? color[3] : 1.0); + + // uvRect (optional, default texture rectangle) + var uvRect = params.textureRectangle; + var iwidth = (texture ? 1 / texture.width : 1); + var iheight = (texture ? 1 / texture.height : 1); + data[12] = (uvRect ? (uvRect[0] * iwidth) : 0.0); + data[13] = (uvRect ? (uvRect[1] * iheight) : 0.0); + data[14] = (uvRect ? (uvRect[2] * iwidth) : 1.0); + data[15] = (uvRect ? (uvRect[3] * iheight) : 1.0); + + // dimensions / 2 (default texture dimensions) + data[17] = ((params.width !== undefined) ? params.width : texture.width) * 0.5; + data[18] = ((params.height !== undefined) ? params.height : texture.height) * 0.5; + + // scale (default [1,1]) + var scale = params.scale; + data[19] = (scale ? scale[0] : 1.0); + data[20] = (scale ? scale[1] : 1.0); + + // shear (default [0,0]) + var shear = params.shear; + data[21] = (shear ? shear[0] : 0.0); + data[22] = (shear ? shear[1] : 0.0); + + // origin (default dimensions / 2) + var origin = params.origin; + data[23] = (origin ? origin[0] : data[17]); + data[24] = (origin ? origin[1] : data[18]); + + s._invalidate(); + return s; +}; + +// +// Used in rectangle draw routines to compute data to be pushed into vertex buffers. +// +function Draw2DSpriteData() {} +Draw2DSpriteData.setFromRotatedRectangle = function setFromRotatedRectangleFn(sprite, texture, rect, uvrect, color, rotation, origin) +{ + var x1 = rect[0]; + var y1 = rect[1]; + var x2 = rect[2]; + var y2 = rect[3]; + + if (!rotation) + { + sprite[0] = x1; + sprite[1] = y1; + sprite[2] = x2; + sprite[3] = y1; + sprite[4] = x1; + sprite[5] = y2; + sprite[6] = x2; + sprite[7] = y2; + } + else + { + var cx, cy; + if (origin) + { + cx = x1 + origin[0]; + cy = y1 + origin[1]; + } + else + { + cx = 0.5 * (x1 + x2); + cy = 0.5 * (y1 + y2); + } + + var dx = x1 - cx; + var dy = y1 - cy; + + var cos = Math.cos(rotation); + var sin = Math.sin(rotation); + var w = (x2 - x1); + var h = (y2 - y1); + + sprite[0] = x1 = cx + (cos * dx - sin * dy); + sprite[1] = y1 = cy + (sin * dx + cos * dy); + sprite[2] = x1 + (cos * w); + sprite[3] = y1 + (sin * w); + sprite[4] = x1 - (sin * h); + sprite[5] = y1 + (cos * h); + sprite[6] = x1 + (cos * w - sin * h); + sprite[7] = y1 + (sin * w + cos * h); + } + + if (color) + { + sprite[8] = color[0]; + sprite[9] = color[1]; + sprite[10] = color[2]; + sprite[11] = color[3]; + } + else + { + sprite[8] = sprite[9] = sprite[10] = sprite[11] = 1.0; + } + + if (uvrect && texture) + { + var iwidth = 1 / texture.width; + var iheight = 1 / texture.height; + sprite[12] = uvrect[0] * iwidth; + sprite[13] = uvrect[1] * iheight; + sprite[14] = uvrect[2] * iwidth; + sprite[15] = uvrect[3] * iheight; + } + else + { + sprite[12] = sprite[13] = 0; + sprite[14] = sprite[15] = 1; + } +}; + +Draw2DSpriteData.create = function draw2DSpriteFn() +{ + // x1 y1 x2 y2 x3 y3 x4 y4 - vertices [0,8) + // cr cg cb ca u1 v1 u2 v2 - normalized color + texture [8,16) + return new Draw2D.prototype.floatArray(16); +}; + +function Draw2D() {} + +Draw2D.prototype = { + + version : 7, + + forceUpdate : false, + clearBackBuffer : false, + + // supported sort modes. + sort : { + deferred : 'deferred', + immediate : 'immediate', + texture : 'texture' + }, + + // supported scale modes. + scale : { + scale : 'scale', + none : 'none' + }, + + drawStates: { + uninit: 0, + ready : 1, + draw : 2 + }, + + defaultClearColor: [0, 0, 0, 1], + + clear: function clearFn(clearColor) + { + if (this.state !== this.drawStates.ready) + { + return false; + } + + var gd = this.graphicsDevice; + if (this.currentRenderTarget) + { + if (!gd.beginRenderTarget(this.currentRenderTarget.renderTarget)) + { + return false; + } + + gd.clear(clearColor || this.defaultClearColor); + gd.endRenderTarget(); + } + else + { + gd.clear(clearColor || this.defaultClearColor); + } + + return true; + }, + + clearBatch: function clearFn() + { + for (var name in this.texLists) + { + if (this.texLists.hasOwnProperty(name)) + { + delete this.texLists[name]; + } + } + this.currentTextureGroup = undefined; + this.numGroups = 0; + }, + + bufferSprite : function bufferSpriteFn(buffer, sprite, index) + { + sprite._update(0); + /*jshint bitwise: false*/ + index <<= 4; + /*jshint bitwise: true*/ + + var data = sprite.data; + buffer[index] = data[0]; + buffer[index + 1] = data[1]; + buffer[index + 2] = data[2]; + buffer[index + 3] = data[3]; + buffer[index + 4] = data[4]; + buffer[index + 5] = data[5]; + buffer[index + 6] = data[6]; + buffer[index + 7] = data[7]; + buffer[index + 8] = data[8]; + buffer[index + 9] = data[9]; + buffer[index + 10] = data[10]; + buffer[index + 11] = data[11]; + buffer[index + 12] = data[12]; + buffer[index + 13] = data[13]; + buffer[index + 14] = data[14]; + buffer[index + 15] = data[15]; + }, + + update: function updateFn() + { + var graphicsDevice = this.graphicsDevice; + var width = this.width; + var height = this.height; + + var graphicsDeviceWidth = graphicsDevice.width; + var graphicsDeviceHeight = graphicsDevice.height; + + if (width !== graphicsDeviceWidth || height !== graphicsDeviceHeight || this.forceUpdate) + { + var viewWidth, viewHeight, viewX, viewY; + var viewportRectangle = this.viewportRectangle; + + if (viewportRectangle) + { + viewX = viewportRectangle[0]; + viewY = viewportRectangle[1]; + viewWidth = viewportRectangle[2] - viewX; + viewHeight = viewportRectangle[3] - viewY; + } + else + { + viewX = 0; + viewY = 0; + viewWidth = graphicsDeviceWidth; + viewHeight = graphicsDeviceHeight; + } + + if ((viewWidth === graphicsDeviceWidth) && (viewHeight === graphicsDeviceHeight)) + { + this.clearBackBuffer = false; + } + else + { + this.clearBackBuffer = true; + } + + var target = this.currentRenderTarget; + + if (this.scaleMode === 'scale') + { + var viewAspectRatio = viewWidth / viewHeight; + var graphicsDeviceAspectRatio = graphicsDeviceWidth / graphicsDeviceHeight; + var calcViewWidth, calcViewHeight, diffWidth, diffHeight, halfDiffWidth, halfDiffHeight; + + if (graphicsDeviceAspectRatio > viewAspectRatio) + { + calcViewWidth = Math.ceil((graphicsDeviceHeight / viewHeight) * viewWidth); + diffWidth = graphicsDeviceWidth - calcViewWidth; + halfDiffWidth = Math.floor(diffWidth * 0.5); + + this.scissorX = halfDiffWidth; + this.scissorY = 0; + this.scissorWidth = calcViewWidth; + this.scissorHeight = graphicsDeviceHeight; + + this.viewScaleX = viewWidth / calcViewWidth; + this.viewScaleY = viewHeight / graphicsDeviceHeight; + + if (!target) + { + this.clipOffsetX = (halfDiffWidth / graphicsDeviceWidth * 2.0) - 1.0; + this.clipOffsetY = 1; + this.clipScaleX = (calcViewWidth / graphicsDeviceWidth * 2.0) / viewWidth; + this.clipScaleY = -2.0 / viewHeight; + } + } + else + { + calcViewHeight = Math.ceil((graphicsDeviceWidth / viewWidth) * viewHeight); + diffHeight = graphicsDeviceHeight - calcViewHeight; + halfDiffHeight = Math.floor(diffHeight * 0.5); + + this.scissorX = 0; + this.scissorY = halfDiffHeight; + this.scissorWidth = graphicsDeviceWidth; + this.scissorHeight = calcViewHeight; + + this.viewScaleX = viewWidth / graphicsDeviceWidth; + this.viewScaleY = viewHeight / calcViewHeight; + + if (!target) + { + this.clipOffsetX = -1.0; + this.clipOffsetY = 1 - ((halfDiffHeight / graphicsDeviceHeight) * 2.0); + this.clipScaleX = 2.0 / viewWidth; + this.clipScaleY = ((calcViewHeight / graphicsDeviceHeight) * -2.0) / viewHeight; + } + } + } + else + { + this.viewScaleX = 1; + this.viewScaleY = 1; + + if (!target) + { + this.clipOffsetX = -1.0; + this.clipOffsetY = 1.0; + this.clipScaleX = 2.0 / graphicsDeviceWidth; + this.clipScaleY = -2.0 / graphicsDeviceHeight; + } + + this.scissorX = 0; + this.scissorY = (graphicsDeviceHeight - viewHeight); + this.scissorWidth = viewWidth; + this.scissorHeight = viewHeight; + } + + this.spriteAngleFactor = Math.min(this.viewScaleX, this.viewScaleY); + this.spriteAngleFactor *= this.spriteAngleFactor; + + this.width = graphicsDeviceWidth; + this.height = graphicsDeviceHeight; + + var i = 0; + var renderTargets = this.renderTargetStructs; + var limit = renderTargets.length; + for (i = 0; i < limit; i += 1) + { + this.validateTarget(renderTargets[i], this.scissorWidth, this.scissorHeight); + } + + if (target) + { + this.clipOffsetX = -1.0; + this.clipOffsetY = -1.0; + this.clipScaleX = 2.0 * target.actualWidth / target.texture.width / viewWidth; + this.clipScaleY = 2.0 * target.actualHeight / target.texture.height / viewHeight; + } + + // Deal with viewports that are not started at (0,0) + this.clipOffsetX -= viewX * this.clipScaleX; + this.clipOffsetY -= viewY * this.clipScaleY; + + var clipSpace = this.techniqueParameters.clipSpace; + clipSpace[0] = this.clipScaleX; + clipSpace[1] = this.clipScaleY; + clipSpace[2] = this.clipOffsetX; + clipSpace[3] = this.clipOffsetY; + + this.updateRenderTargetVbo(this.scissorX, this.scissorY, this.scissorWidth, this.scissorHeight); + this.forceUpdate = false; + } + }, + + getViewport: function getViewportFn(dst) + { + if (!dst) + { + dst = new Draw2D.prototype.floatArray(4); + } + var viewport = this.viewportRectangle; + if (viewport) + { + dst[0] = viewport[0]; + dst[1] = viewport[1]; + dst[2] = viewport[2]; + dst[3] = viewport[3]; + } + else + { + dst[0] = dst[1] = 0; + dst[2] = this.graphicsDevice.width; + dst[3] = this.graphicsDevice.height; + } + return dst; + }, + getScreenSpaceViewport: function screenSpaceViewportFn(dst) + { + if (!dst) + { + dst = new Draw2D.prototype.floatArray(4); + } + // ensure mapping is correct. + this.update(); + + dst[0] = this.scissorX; + dst[1] = this.height - (this.scissorY + this.scissorHeight); + dst[2] = dst[0] + this.scissorWidth; + dst[3] = dst[1] + this.scissorHeight; + return dst; + }, + + viewportMap: function viewportMapFn(screenX, screenY, dst) + { + if (!dst) + { + dst = new Draw2D.prototype.floatArray(2); + } + // ensure mapping is correct. + this.update(); + + // webgl coordinates have flipped y. + var scissorY = (this.height - this.scissorHeight - this.scissorY); + + dst[0] = (screenX - this.scissorX) * this.viewScaleX; + dst[1] = (screenY - scissorY) * this.viewScaleY; + + var viewport = this.viewportRectangle; + if (viewport) + { + dst[0] += viewport[0]; + dst[1] += viewport[1]; + } + + return dst; + }, + viewportUnmap: function screenMapFn(x, y, dst) + { + if (!dst) + { + dst = new Draw2D.prototype.floatArray(2); + } + // ensure mapping is correct. + this.update(); + + var viewport = this.viewportRectangle; + if (viewport) + { + x -= viewport[0]; + y -= viewport[1]; + } + + // webgl coordinates have flipped y. + var scissorY = (this.height - this.scissorHeight - this.scissorY); + + dst[0] = (x / this.viewScaleX) + this.scissorX; + dst[1] = (y / this.viewScaleY) + scissorY; + return dst; + }, + + viewportClamp: function viewportClampFn(point) + { + if (point) + { + var x = point[0]; + var y = point[1]; + + var minX, minY, maxX, maxY; + var viewport = this.viewportRectangle; + if (viewport) + { + minX = viewport[0]; + minY = viewport[1]; + maxX = viewport[2]; + maxY = viewport[3]; + } + else + { + minX = 0; + minY = 0; + maxX = this.graphicsDevice.width; + maxY = this.graphicsDevice.height; + } + + if (x < minX) + { + x = minX; + } + else if (x > maxX) + { + x = maxX; + } + + if (y < minY) + { + y = minY; + } + else if (y > maxY) + { + y = maxY; + } + + point[0] = x; + point[1] = y; + } + + return point; + }, + + configure: function configureFn(params) + { + if (this.state !== this.drawStates.ready) + { + return false; + } + + var viewportRectangle = ("viewportRectangle" in params) ? params.viewportRectangle : this.viewportRectangle; + + var scaleMode = params.scaleMode; + if (scaleMode !== undefined) + { + // check scaleMode is supported. + if (!(scaleMode in this.scale)) + { + return false; + } + if (scaleMode === 'scale' && !viewportRectangle) + { + return false; + } + this.scaleMode = scaleMode; + } + + this.viewportRectangle = viewportRectangle; + + this.forceUpdate = true; + this.update(); + + return true; + }, + + destroy: function destroyFn() + { + this.texLists = null; + this.state = this.drawStates.uninit; + + delete this.graphicsDevice; + + if (this.vertexBuffer) + { + this.vertexBuffer.destroy(); + } + if (this.indexBuffer) + { + this.indexBuffer.destroy(); + } + + this.copyVertexBuffer.destroy(); + + var renderTargets = this.renderTargetStructs; + while (renderTargets.length > 0) + { + var target = renderTargets.pop(); + target.texture.destroy(); + target.renderTarget.destroy(); + delete target.texture; + delete target.renderTarget; + } + }, + + begin: function beginFn(blendMode, sortMode) + { + // Check sort mode is well defined (or undefined signifying default) + if (sortMode && !(sortMode in this.sort)) + { + return false; + } + + // Check blend mode is well defined (or undefined signifying default) + if (blendMode && !(blendMode in this.blend)) + { + return false; + } + + //if there are render states left in the stack + //and begin has been called without an end + //draw previous data with current render state + var firstTime = !this.sortMode; + if (this.dispatch()) + { + this.clearBatch(); + } + + if (firstTime) + { + if (this.state !== this.drawStates.ready) + { + return false; + } + + // Check the buffers are correct before we render + this.update(); + + if (!this.currentRenderTarget) + { + this.graphicsDevice.setScissor(this.scissorX, this.scissorY, this.scissorWidth, this.scissorHeight); + } + } + + this.state = this.drawStates.draw; + + sortMode = (sortMode) ? sortMode : (firstTime ? 'deferred' : this.sortMode); + blendMode = (blendMode) ? blendMode : (firstTime ? 'opaque' : this.blendMode); + + + if (!firstTime) + { + this.sortModeStack.push(this.sortMode); + this.blendModeStack.push(this.blendMode); + } + this.sortMode = sortMode; + this.blendMode = blendMode; + + this.prepareSortMode(sortMode); + this.graphicsDevice.setTechnique(this.blendModeTechniques[blendMode]); + + return true; + }, + + /////////////////////////////////////////////////////////////////////////////////////////////// + + // append sprite data to group buffer. + _bufferSprite : function bufferSpriteFn(group, sprite) + { + var vertexData = group.vertexBufferData; + var vertexBuffer = this.vertexBuffer; + + var index = group.numVertices * vertexBuffer.stride; + var total = index + (4 * vertexBuffer.stride); + if (total >= vertexData.length) + { + // allocate new vertex buffer data array. + var size = this.bufferSizeAlgorithm(total, this.cpuStride); + var newData = new Draw2D.prototype.floatArray(size); + + // copy data from existing buffer. + var i; + for (i = 0; i < index; i += 1) + { + newData[i] = vertexData[i]; + } + + group.vertexBufferData = vertexData = newData; + } + + var c1 = sprite[8]; + var c2 = sprite[9]; + var c3 = sprite[10]; + var c4 = sprite[11]; + var u1 = sprite[12]; + var v1 = sprite[13]; + var u2 = sprite[14]; + var v2 = sprite[15]; + + vertexData[index] = sprite[0]; + vertexData[index + 1] = sprite[1]; + vertexData[index + 2] = c1; + vertexData[index + 3] = c2; + vertexData[index + 4] = c3; + vertexData[index + 5] = c4; + vertexData[index + 6] = u1; + vertexData[index + 7] = v1; + + vertexData[index + 8] = sprite[2]; + vertexData[index + 9] = sprite[3]; + vertexData[index + 10] = c1; + vertexData[index + 11] = c2; + vertexData[index + 12] = c3; + vertexData[index + 13] = c4; + vertexData[index + 14] = u2; + vertexData[index + 15] = v1; + + vertexData[index + 16] = sprite[4]; + vertexData[index + 17] = sprite[5]; + vertexData[index + 18] = c1; + vertexData[index + 19] = c2; + vertexData[index + 20] = c3; + vertexData[index + 21] = c4; + vertexData[index + 22] = u1; + vertexData[index + 23] = v2; + + vertexData[index + 24] = sprite[6]; + vertexData[index + 25] = sprite[7]; + vertexData[index + 26] = c1; + vertexData[index + 27] = c2; + vertexData[index + 28] = c3; + vertexData[index + 29] = c4; + vertexData[index + 30] = u2; + vertexData[index + 31] = v2; + + group.numVertices += 4; + + // increment number of indices in present subset. + group.indices[group.numSets - 1] += 6; + }, + + bufferMultiSprite : function bufferMultiSprite(group, buffer, count, offset) + { + var vertexData = group.vertexBufferData; + var vertexBuffer = this.vertexBuffer; + + var numSprites = (count === undefined) ? Math.floor(buffer.length / 16) : count; + count = numSprites * 16; + + offset = (offset !== undefined ? offset : 0) * 16; + + var i; + var index = (group.numVertices * vertexBuffer.stride); + var total = index + (numSprites * 4 * vertexBuffer.stride); + if (total >= vertexData.length) + { + // allocate new vertex buffer data array. + var size = this.bufferSizeAlgorithm(total, this.cpuStride); + var newData = new Draw2D.prototype.floatArray(size); + + // copy data from existing buffer. + for (i = 0; i < index; i += 1) + { + newData[i] = vertexData[i]; + } + + group.vertexBufferData = vertexData = newData; + } + + var limit = offset + count; + for (i = offset; i < limit; i += 16) + { + var c1 = buffer[i + 8]; + var c2 = buffer[i + 9]; + var c3 = buffer[i + 10]; + var c4 = buffer[i + 11]; + var u1 = buffer[i + 12]; + var v1 = buffer[i + 13]; + var u2 = buffer[i + 14]; + var v2 = buffer[i + 15]; + + vertexData[index] = buffer[i]; + vertexData[index + 1] = buffer[i + 1]; + vertexData[index + 2] = c1; + vertexData[index + 3] = c2; + vertexData[index + 4] = c3; + vertexData[index + 5] = c4; + vertexData[index + 6] = u1; + vertexData[index + 7] = v1; + + vertexData[index + 8] = buffer[i + 2]; + vertexData[index + 9] = buffer[i + 3]; + vertexData[index + 10] = c1; + vertexData[index + 11] = c2; + vertexData[index + 12] = c3; + vertexData[index + 13] = c4; + vertexData[index + 14] = u2; + vertexData[index + 15] = v1; + + vertexData[index + 16] = buffer[i + 4]; + vertexData[index + 17] = buffer[i + 5]; + vertexData[index + 18] = c1; + vertexData[index + 19] = c2; + vertexData[index + 20] = c3; + vertexData[index + 21] = c4; + vertexData[index + 22] = u1; + vertexData[index + 23] = v2; + + vertexData[index + 24] = buffer[i + 6]; + vertexData[index + 25] = buffer[i + 7]; + vertexData[index + 26] = c1; + vertexData[index + 27] = c2; + vertexData[index + 28] = c3; + vertexData[index + 29] = c4; + vertexData[index + 30] = u2; + vertexData[index + 31] = v2; + + index += 32; + } + + group.numVertices += (numSprites * 4); + // increment number of indices in present subset. + group.indices[group.numSets - 1] += (numSprites * 6); + }, + + /////////////////////////////////////////////////////////////////////////////////////////////// + + indexData : function indexDataFn(count) + { + var indexData = new Draw2D.prototype.uint16Array(count); + var i; + var vertexIndex = 0; + for (i = 0; i < count; i += 6) + { + indexData[i] = vertexIndex; + indexData[i + 1] = vertexIndex + 1; + indexData[i + 2] = vertexIndex + 2; + indexData[i + 3] = vertexIndex + 1; + indexData[i + 4] = vertexIndex + 2; + indexData[i + 5] = vertexIndex + 3; + vertexIndex += 4; + } + return indexData; + }, + + // upload group buffer to graphics device vertexBuffer. + uploadBuffer : function uploadBufferFn(group, count, offset) + { + var vertexBuffer = this.vertexBuffer; + var vertexBufferParameters = this.vertexBufferParameters; + var graphicsDevice = this.graphicsDevice; + var vertexData = group.vertexBufferData; + + var performanceData = this.performanceData; + + // Resize buffers. + if (count > vertexBufferParameters.numVertices) + { + var newSize = this.bufferSizeAlgorithm(count, this.gpuStride); + if (newSize > this.maxVertices) + { + newSize = this.maxVertices; + } + + vertexBufferParameters.numVertices = newSize; + this.vertexBuffer.destroy(); + this.vertexBuffer = vertexBuffer = graphicsDevice.createVertexBuffer(vertexBufferParameters); + + // 32 bytes per vertex. + // 2 bytes per index, 1.5 indices per vertex. + performanceData.gpuMemoryUsage = newSize * 35; // 32 + (1.5 * 2) + + newSize *= 1.5; + + // Set indices. + var indexBufferParameters = this.indexBufferParameters; + indexBufferParameters.data = this.indexData(newSize); + indexBufferParameters.numIndices = newSize; + this.indexBuffer.destroy(); + this.indexBuffer = graphicsDevice.createIndexBuffer(indexBufferParameters); + graphicsDevice.setIndexBuffer(this.indexBuffer); + } + + performanceData.dataTransfers += 1; + + // Upload data. + if (offset === 0) + { + vertexBuffer.setData(vertexData, 0, count); + } + else + { + var stride = vertexBuffer.stride; + vertexBuffer.setData(vertexData.subarray(offset * stride, (offset + count) * stride), 0, count); + } + }, + + /////////////////////////////////////////////////////////////////////////////////////////////// + + drawRawImmediate : function drawRawImmediateFn(texture, multiSprite, count, offset) + { + var group = this.drawGroups[0]; + group.textures[0] = texture || this.defaultTexture; + group.indices[0] = 0; + group.numSets = 1; + this.numGroups = 1; + + this.bufferMultiSprite(group, multiSprite, count, offset); + + // Draw render group immediately. + this.dispatch(); + }, + + drawSpriteImmediate : function drawSpriteImmediateFn(sprite) + { + var group = this.drawGroups[0]; + group.textures[0] = sprite._texture || this.defaultTexture; + group.indices[0] = 0; + group.numSets = 1; + this.numGroups = 1; + + sprite._update(this.spriteAngleFactor); + this._bufferSprite(group, sprite.data); + + // Draw render group immediately. + this.dispatch(); + }, + + drawImmediate : function drawImmediateFn(params) + { + var texture = params.texture || this.defaultTexture; + var destRect = params.destinationRectangle; + var srcRect = params.sourceRectangle; + var color = params.color; + var rotation = params.rotation; + + var group = this.drawGroups[0]; + group.textures[0] = texture; + group.indices[0] = 0; + group.numSets = 1; + this.numGroups = 1; + + var drawSprite = this.drawSprite; + Draw2DSpriteData.setFromRotatedRectangle(drawSprite, texture, destRect, srcRect, color, rotation, params.origin); + this._bufferSprite(group, drawSprite); + + // Draw render group immediately. + this.dispatch(); + }, + + /////////////////////////////////////////////////////////////////////////////////////////////// + + drawRawDeferred : function drawRawDeferredFn(texture, multiSprite, count, offset) + { + texture = texture || this.defaultTexture; + var group = this.drawGroups[0]; + this.numGroups = 1; + // If present group draw list uses a different texture + // We must start a new draw list. + var numSets = group.numSets; + if (numSets === 0 || group.textures[numSets - 1] !== texture) + { + group.textures[numSets] = texture; + group.indices[numSets] = 0; + group.numSets += 1; + } + + this.bufferMultiSprite(group, multiSprite, count, offset); + }, + + drawSpriteDeferred : function drawSpriteDeferredFn(sprite) + { + var texture = sprite._texture || this.defaultTexture; + + var group = this.drawGroups[0]; + this.numGroups = 1; + // If present group draw list uses a different texture + // We must start a new draw list. + var numSets = group.numSets; + if (numSets === 0 || group.textures[numSets - 1] !== texture) + { + group.textures[numSets] = texture; + group.indices[numSets] = 0; + group.numSets += 1; + } + + sprite._update(this.spriteAngleFactor); + this._bufferSprite(group, sprite.data); + }, + + drawDeferred : function drawDeferredFn(params) + { + var texture = params.texture || this.defaultTexture; + + var group = this.drawGroups[0]; + this.numGroups = 1; + // If present group draw list uses a different texture + // We must start a new draw list. + var numSets = group.numSets; + if (numSets === 0 || group.textures[numSets - 1] !== texture) + { + group.textures[numSets] = texture; + group.indices[numSets] = 0; + group.numSets += 1; + } + + var destRect = params.destinationRectangle; + var srcRect = params.sourceRectangle; + var color = params.color; + var rotation = params.rotation; + + var drawSprite = this.drawSprite; + Draw2DSpriteData.setFromRotatedRectangle(drawSprite, texture, destRect, srcRect, color, rotation, params.origin); + + this._bufferSprite(group, drawSprite); + }, + + /////////////////////////////////////////////////////////////////////////////////////////////// + + drawRawTextured : function drawRawTexturedFn(texture, multiSprite, count, offset) + { + texture = texture || this.defaultTexture; + var group; + // If last call to drawTextured used the same texture, then we need not look up render group. + if (this.currentTextureGroup !== undefined && this.currentTextureGroup.textures[0] === texture) + { + group = this.currentTextureGroup; + } + else + { + // Look up render group in texLists. + var name = texture.name; + var texLists = this.texLists; + group = texLists[name]; + if (!group) + { + // Create new render group. + group = this.drawGroups[this.numGroups]; + if (!group) + { + group = Draw2DGroup.create(); + } + this.drawGroups[this.numGroups] = texLists[name] = group; + group.textures[0] = texture; + group.indices[0] = 0; + group.numSets = 1; + this.numGroups += 1; + } + this.currentTextureGroup = group; + } + + this.bufferMultiSprite(group, multiSprite, count, offset); + }, + + drawSpriteTextured : function drawSpriteTexturedFn(sprite) + { + var texture = sprite._texture || this.defaultTexture; + + var group; + // If last call to drawTextured used the same texture, then we need not look up render group. + if (this.currentTextureGroup !== undefined && this.currentTextureGroup.textures[0] === texture) + { + group = this.currentTextureGroup; + } + else + { + // Look up render group in texLists. + var name = texture.name; + var texLists = this.texLists; + group = texLists[name]; + if (!group) + { + // Create new render group. + group = this.drawGroups[this.numGroups]; + if (!group) + { + group = Draw2DGroup.create(); + } + this.drawGroups[this.numGroups] = texLists[name] = group; + group.textures[0] = texture; + group.indices[0] = 0; + group.numSets = 1; + this.numGroups += 1; + } + this.currentTextureGroup = group; + } + + sprite._update(this.spriteAngleFactor); + this._bufferSprite(group, sprite.data); + }, + + drawTextured : function drawTexturedFn(params) + { + var texture = params.texture || this.defaultTexture; + + var group; + // If last call to drawTextured used the same texture, then we need not look up render group. + if (this.currentTextureGroup !== undefined && this.currentTextureGroup.textures[0] === texture) + { + group = this.currentTextureGroup; + } + else + { + // Look up render group in texLists. + var name = texture.name; + var texLists = this.texLists; + group = texLists[name]; + if (!group) + { + // Create new render group. + group = this.drawGroups[this.numGroups]; + if (!group) + { + group = Draw2DGroup.create(); + } + this.drawGroups[this.numGroups] = texLists[name] = group; + group.textures[0] = texture; + group.indices[0] = 0; + group.numSets = 1; + this.numGroups += 1; + } + this.currentTextureGroup = group; + } + + var destRect = params.destinationRectangle; + var srcRect = params.sourceRectangle; + var color = params.color; + var rotation = params.rotation; + + var drawSprite = this.drawSprite; + Draw2DSpriteData.setFromRotatedRectangle(drawSprite, texture, destRect, srcRect, color, rotation, params.origin); + + this._bufferSprite(group, drawSprite); + }, + + /////////////////////////////////////////////////////////////////////////////////////////////// + + prepareSortMode : function refreshSortModeFn(sortMode) + { + if (sortMode === 'deferred') + { + this.draw = this.drawDeferred; + this.drawSprite = this.drawSpriteDeferred; + this.drawRaw = this.drawRawDeferred; + } + else if (sortMode === 'immediate') + { + this.draw = this.drawImmediate; + this.drawSprite = this.drawSpriteImmediate; + this.drawRaw = this.drawRawImmediate; + } + else + { + this.draw = this.drawTextured; + this.drawSprite = this.drawSpriteTextured; + this.drawRaw = this.drawRawTextured; + } + }, + + /////////////////////////////////////////////////////////////////////////////////////////////// + + end: function endFn() + { + if (this.state !== this.drawStates.draw) + { + return false; + } + + //dispatch objects to the graphics card + if (this.dispatch()) + { + this.clearBatch(); + } + + if (this.blendModeStack.length !== 0) + { + this.blendMode = this.blendModeStack.pop(); + this.sortMode = this.sortModeStack.pop(); + this.prepareSortMode(this.sortMode); + this.graphicsDevice.setTechnique(this.blendModeTechniques[this.blendMode]); + } + else + { + this.blendMode = undefined; + this.sortMode = undefined; + this.state = this.drawStates.ready; + } + + return true; + }, + + dispatch: function dispatchFn() + { + // Nothing to dispatch. + var numGroups = this.numGroups; + if (numGroups === 0) + { + return false; + } + + var graphicsDevice = this.graphicsDevice; + var techniqueParameters = this.techniqueParameters; + graphicsDevice.setIndexBuffer(this.indexBuffer); + + var drawGroups = this.drawGroups; + var renderTargetUsed = false; + if (this.currentRenderTarget) + { + renderTargetUsed = graphicsDevice.beginRenderTarget(this.currentRenderTarget.renderTarget); + } + + var performanceData = this.performanceData; + + var i; + for (i = 0; i < numGroups; i += 1) + { + var group = drawGroups[i]; + + var textures = group.textures; + var indices = group.indices; + var setIndex = 0; + + var vindex = 0; + var vlimit = group.numVertices; + while (vindex < vlimit) + { + // number of vertices remaining. + var vcount = vlimit - vindex; + if (vcount > this.maxVertices) + { + vcount = this.maxVertices; + } + + // Upload group vertex sub-buffer to graphics device. + this.uploadBuffer(group, vcount, vindex); + graphicsDevice.setStream(this.vertexBuffer, this.semantics); + + // sprite uses 4 vertices, and 6 indices + // so for 'vcount' number of vertices, we have vcount * 1.5 indices + var ilimit = vcount * 1.5; + var iindex = 0; + while (iindex < ilimit) { + techniqueParameters.texture = textures[setIndex]; + + // number of indices remaining to render. + var icount = ilimit - iindex; + if (icount >= indices[setIndex]) + { + // finish rendering sub list. + icount = indices[setIndex]; + setIndex += 1; + } + else + { + // sub list still has remaining indices to render. + indices[setIndex] -= icount; + } + + var batchSize = icount / 6; + if (performanceData.batchCount === 0) + { + performanceData.minBatchSize = batchSize; + performanceData.maxBatchSize = batchSize; + performanceData.avgBatchSize = batchSize; + performanceData.batchCount = 1; + } + else + { + if (batchSize < performanceData.minBatchSize) + { + performanceData.minBatchSize = batchSize; + } + if (batchSize > performanceData.maxBatchSize) + { + performanceData.maxBatchSize = batchSize; + } + performanceData.avgBatchSize *= performanceData.batchCount; + performanceData.avgBatchSize += batchSize; + performanceData.batchCount += 1; + performanceData.avgBatchSize /= performanceData.batchCount; + } + + graphicsDevice.setTechniqueParameters(techniqueParameters); + graphicsDevice.drawIndexed(graphicsDevice.PRIMITIVE_TRIANGLES, icount, iindex); + + iindex += icount; + } + + vindex += vcount; + } + + group.numSets = 0; + group.numVertices = 0; + } + + if (this.currentRenderTarget && renderTargetUsed) + { + graphicsDevice.endRenderTarget(); + } + + return true; + }, + + bufferSizeAlgorithm : function bufferSizeAlgorithmFn(target, stride) + { + // scale factor of 2 is asymtopically optimal in terms of number of resizes + // performed and copies performed, but we want to try and conserve memory + // and so choose a less optimal 1.25 so that buffer will never be too much + // larger than necessary. + var factor = 1.25; + + // We size buffer to the next power of the factor which is >= target + var logf = Math.ceil(Math.log(target) / Math.log(factor)); + var size = Math.floor(Math.pow(factor, logf)); + + // Additionally ensure that we always take a multiple of of the stride + // to avoid wasted bytes that could never be used. + return (stride * Math.ceil(size / stride)); + }, + + updateRenderTargetVbo : function updateRenderTargetVboFn(viewX, viewY, viewWidth, viewHeight) + { + var graphicsDevice = this.graphicsDevice; + var halfGraphicsDeviceWidth = 0.5 * graphicsDevice.width; + var halfGraphicsDeviceHeight = 0.5 * graphicsDevice.height; + + // + // Update the VBO for the presentRenderTarget + // + var vertexBuffer = this.copyVertexBuffer; + + var left = (viewX - halfGraphicsDeviceWidth) / halfGraphicsDeviceWidth; + var right = (viewX + viewWidth - halfGraphicsDeviceWidth) / halfGraphicsDeviceWidth; + var topv = (viewY - halfGraphicsDeviceHeight) / halfGraphicsDeviceHeight; + var bottom = (viewY + viewHeight - halfGraphicsDeviceHeight) / halfGraphicsDeviceHeight; + + var vertexData = this.vertexBufferData; + vertexData[0] = left; + vertexData[1] = bottom; + vertexData[2] = 0.0; + vertexData[3] = 1.0; + + vertexData[4] = left; + vertexData[5] = topv; + vertexData[6] = 0.0; + vertexData[7] = 0.0; + + vertexData[8] = right; + vertexData[9] = bottom; + vertexData[10] = 1.0; + vertexData[11] = 1.0; + + vertexData[12] = right; + vertexData[13] = topv; + vertexData[14] = 1.0; + vertexData[15] = 0.0; + + vertexBuffer.setData(vertexData, 0, 4); + }, + + // always overallocate. + /*jshint bitwise: false*/ + makePow2 : function makePow2Fn(dim) + { + var index = Math.log(dim) / Math.log(2); + return (1 << Math.ceil(index)); + }, + /*jshint bitwise: true*/ + + createRenderTarget : function createRenderTargetFn(params) + { + var gd = this.graphicsDevice; + var renderTargets = this.renderTargetStructs; + var index = renderTargets.length; + + var name = (params && params.name) ? params.name : ("RenderTarget#" + index); + var backBuffer = (params && params.backBuffer !== undefined) ? params.backBuffer : true; + var matchScreen = (params.width === undefined || params.height === undefined); + + var texParams = this.renderTargetTextureParameters; + texParams.name = name; + + var width = (matchScreen) ? gd.width : params.width; + var height = (matchScreen) ? gd.height : params.height; + texParams.width = this.makePow2(width); + texParams.height = this.makePow2(height); + + var texture = gd.createTexture(texParams); + var targetParams = this.renderTargetParams; + targetParams.colorTexture0 = texture; + var renderTarget = gd.createRenderTarget(targetParams); + + renderTargets.push({ + managed : matchScreen, + renderTarget : renderTarget, + texture : texture, + backBuffer : backBuffer, + actualWidth : (backBuffer ? width : texture.width), + actualHeight : (backBuffer ? height : texture.height) + }); + + return index; + }, + + validateTarget : function validateTargetFn(target, viewWidth, viewHeight) + { + if (target.managed) + { + var tex = target.texture; + if (target.backBuffer) + { + target.actualWidth = viewWidth; + target.actualHeight = viewHeight; + } + viewWidth = this.makePow2(viewWidth); + viewHeight = this.makePow2(viewHeight); + if (!target.backBuffer) + { + target.actualWidth = viewWidth; + target.actualHeight = viewHeight; + } + if (tex.width !== viewWidth || tex.height !== viewHeight) + { + var texParams = this.renderTargetTextureParameters; + var targetParams = this.renderTargetParams; + + texParams.name = tex.name; + texParams.width = viewWidth; + texParams.height = viewHeight; + + tex.destroy(); + target.renderTarget.destroy(); + + var graphicsDevice = this.graphicsDevice; + target.texture = graphicsDevice.createTexture(texParams); + targetParams.colorTexture0 = target.texture; + target.renderTarget = graphicsDevice.createRenderTarget(targetParams); + } + } + }, + + setBackBuffer : function setBackBufferFn() + { + if (this.state !== this.drawStates.ready) + { + return false; + } + + this.currentRenderTarget = null; + this.forceUpdate = true; + + return true; + }, + + getRenderTargetTexture : function getRenderTargetTextureFn(renderTargetIndex) + { + var renderTargets = this.renderTargetStructs; + if (renderTargetIndex < 0 || renderTargetIndex >= renderTargets.length) + { + return null; + } + + return renderTargets[renderTargetIndex].texture; + }, + + getRenderTarget : function getRenderTargetFn(renderTargetIndex) + { + var renderTargets = this.renderTargetStructs; + if (renderTargetIndex < 0 || renderTargetIndex >= renderTargets.length) + { + return null; + } + + return renderTargets[renderTargetIndex].renderTarget; + }, + + setRenderTarget : function setRenderTargetFn(renderTargetIndex) + { + var renderTargets = this.renderTargetStructs; + if (renderTargetIndex < 0 || renderTargetIndex >= renderTargets.length) + { + return false; + } + + if (this.state !== this.drawStates.ready) + { + return false; + } + + this.currentRenderTarget = renderTargets[renderTargetIndex]; + this.forceUpdate = true; + + return true; + }, + + copyRenderTarget: function copyRenderTargetFn(renderTargetIndex) + { + if (this.state !== this.drawStates.ready) + { + return false; + } + + var renderTargets = this.renderTargetStructs; + if (renderTargetIndex < 0 || renderTargetIndex >= renderTargets.length) + { + return false; + } + + // Check the buffers are correct before we render. + this.update(); + + if (!this.currentRenderTarget) + { + this.graphicsDevice.setScissor(this.scissorX, this.scissorY, this.scissorWidth, this.scissorHeight); + } + + var graphicsDevice = this.graphicsDevice; + var target = renderTargets[renderTargetIndex]; + var tex = target.texture; + + var technique = this.copyTechnique; + var params = this.copyTechniqueParameters; + var copyUVScale = params.copyUVScale; + copyUVScale[0] = target.actualWidth / tex.width; + copyUVScale[1] = target.actualHeight / tex.height; + params.copyFlip = (!this.currentRenderTarget ? -1.0 : 1.0); + params.inputTexture0 = tex; + + var renderTargetUsed = false; + var currentTarget = this.currentRenderTarget; + var vbo = this.copyVertexBuffer; + if (currentTarget) + { + renderTargetUsed = graphicsDevice.beginRenderTarget(currentTarget.renderTarget); + } + + graphicsDevice.setTechnique(technique); + graphicsDevice.setTechniqueParameters(params); + + graphicsDevice.setStream(vbo, this.quadSemantics); + graphicsDevice.draw(this.quadPrimitive, 4, 0); + + if (currentTarget && renderTargetUsed) + { + graphicsDevice.endRenderTarget(); + } + + return true; + }, + + resetPerformanceData : function resetPerformanceDataFn() + { + var data = this.performanceData; + data.minBatchSize = data.maxBatchSize = data.avgBatchSize = undefined; + data.batchCount = 0; + data.dataTransfers = 0; + } +}; + +// Constructor function +// +// params : { +// graphicsDevice : gd, +// blendModes : { // optional +// name : Technique, +// **repeated** +// } +// } +Draw2D.create = function draw2DCreateFn(params) +{ + var o = new Draw2D(); + var gd = o.graphicsDevice = params.graphicsDevice; + + // Current sort and blend mode. + o.sortMode = undefined; + o.blendMode = undefined; + // Disjoint stack of modes for nested begins. + o.sortModeStack = []; + o.blendModeStack = []; + + // Set of render groups to be dispatched. + o.drawGroups = [Draw2DGroup.create()]; + o.numGroups = 0; + + // Set of render groups for texture sort mode. + // dictionary on texture name. + o.texLists = []; + // Cached reference to last retrieved group to accelerate + // texture sort mode draw calls. + o.texGroup = undefined; + + // Sprite data instance used for rectangle draw calls. + o.drawSprite = Draw2DSpriteData.create(); + + // Solid fill texture for draw calls that do not specify a texture. + o.defaultTexture = gd.createTexture({ + name : "DefaultDraw2DTexture", + width : 1, + height : 1, + depth : 1, + format : "L8", + cubemap : false, + mipmaps : true, + renderable : false, + dynamic : false, + data : [0xff] + }); + + // Draw call methods. + // These are set based on current sort mode. + o.draw = undefined; + o.drawSprite = undefined; + o.drawRaw = undefined; + + // Load embedded default shader and techniques + /*jshint white: false*/ + var shader = gd.createShader( +{ + "version": 1, + "name": "draw2D.cgfx", + "samplers": + { + "texture": + { + "MinFilter": 9985, + "MagFilter": 9729, + "WrapS": 33071, + "WrapT": 33071 + }, + "inputTexture0": + { + "MinFilter": 9728, + "MagFilter": 9729, + "WrapS": 33071, + "WrapT": 33071 + } + }, + "parameters": + { + "clipSpace": + { + "type": "float", + "columns": 4 + }, + "copyUVScale": + { + "type": "float", + "columns": 2 + }, + "copyFlip": + { + "type": "float" + }, + "texture": + { + "type": "sampler2D" + }, + "inputTexture0": + { + "type": "sampler2D" + } + }, + "techniques": + { + "opaque": + [ + { + "parameters": ["clipSpace","texture"], + "semantics": ["POSITION","COLOR","TEXCOORD0"], + "states": + { + "DepthTestEnable": false, + "DepthMask": false, + "CullFaceEnable": false, + "BlendEnable": false + }, + "programs": ["vp_draw2D","fp_draw2D"] + } + ], + "alpha": + [ + { + "parameters": ["clipSpace","texture"], + "semantics": ["POSITION","COLOR","TEXCOORD0"], + "states": + { + "DepthTestEnable": false, + "DepthMask": false, + "CullFaceEnable": false, + "BlendEnable": true, + "BlendFunc": [770,771] + }, + "programs": ["vp_draw2D","fp_draw2D"] + } + ], + "additive": + [ + { + "parameters": ["clipSpace","texture"], + "semantics": ["POSITION","COLOR","TEXCOORD0"], + "states": + { + "DepthTestEnable": false, + "DepthMask": false, + "CullFaceEnable": false, + "BlendEnable": true, + "BlendFunc": [770,1] + }, + "programs": ["vp_draw2D","fp_draw2D"] + } + ], + "copy": + [ + { + "parameters": ["copyUVScale","copyFlip","inputTexture0"], + "semantics": ["POSITION","TEXCOORD0"], + "states": + { + "DepthTestEnable": false, + "DepthMask": false, + "CullFaceEnable": false, + "BlendEnable": false + }, + "programs": ["vp_copy","fp_copy"] + } + ] + }, + "programs": + { + "fp_copy": + { + "type": "fragment", + "code": "#ifdef GL_ES\n#define TZ_LOWP lowp\nprecision mediump float;\nprecision mediump int;\n#else\n#define TZ_LOWP\n#endif\nvarying vec4 tz_TexCoord[8];\nvec4 _ret_0;uniform sampler2D inputTexture0;void main()\n{_ret_0=texture2D(inputTexture0,tz_TexCoord[0].xy);gl_FragColor=_ret_0;}" + }, + "vp_copy": + { + "type": "vertex", + "code": "#ifdef GL_ES\n#define TZ_LOWP lowp\nprecision mediump float;\nprecision mediump int;\n#else\n#define TZ_LOWP\n#endif\nvarying vec4 tz_TexCoord[8];attribute vec4 ATTR8;attribute vec4 ATTR0;\nvec4 _OutPosition1;vec2 _OutUV1;uniform vec2 copyUVScale;uniform float copyFlip;void main()\n{_OutPosition1.x=ATTR0.x;_OutPosition1.y=ATTR0.y*copyFlip;_OutPosition1.zw=ATTR0.zw;_OutUV1=ATTR8.xy*copyUVScale;tz_TexCoord[0].xy=_OutUV1;gl_Position=_OutPosition1;}" + }, + "fp_draw2D": + { + "type": "fragment", + "code": "#ifdef GL_ES\n#define TZ_LOWP lowp\nprecision mediump float;\nprecision mediump int;\n#else\n#define TZ_LOWP\n#endif\nvarying TZ_LOWP vec4 tz_Color;varying vec4 tz_TexCoord[8];\nvec4 _ret_0;vec4 _TMP0;uniform sampler2D texture;void main()\n{_TMP0=texture2D(texture,tz_TexCoord[0].xy);_ret_0=tz_Color*_TMP0;gl_FragColor=_ret_0;}" + }, + "vp_draw2D": + { + "type": "vertex", + "code": "#ifdef GL_ES\n#define TZ_LOWP lowp\nprecision mediump float;\nprecision mediump int;\n#else\n#define TZ_LOWP\n#endif\nvarying TZ_LOWP vec4 tz_Color;varying vec4 tz_TexCoord[8];attribute vec4 ATTR8;attribute vec4 ATTR3;attribute vec4 ATTR0;\nvec4 _OUTPosition1;vec4 _OUTColor1;vec2 _OUTTexCoord01;uniform vec4 clipSpace;void main()\n{vec2 _position;_position=ATTR0.xy*clipSpace.xy+clipSpace.zw;_OUTPosition1.x=_position.x;_OUTPosition1.y=_position.y;_OUTPosition1.z=0.0;_OUTPosition1.w=1.0;_OUTColor1=ATTR3;_OUTTexCoord01=ATTR8.xy;tz_TexCoord[0].xy=ATTR8.xy;tz_Color=ATTR3;gl_Position=_OUTPosition1;}" + } + } +} + ); + /*jshint white: true*/ + + // supported blend modes. + o.blend = { + additive : 'additive', + alpha : 'alpha', + opaque : 'opaque' + }, + + // Mapping from blend mode name to Technique object. + o.blendModeTechniques = {}; + o.blendModeTechniques.additive = shader.getTechnique("additive"); + o.blendModeTechniques.alpha = shader.getTechnique("alpha"); + o.blendModeTechniques.opaque = shader.getTechnique("opaque"); + + // Append techniques and supported blend modes with user supplied techniques. + if (params.blendModes) + { + for (var name in params.blendModes) + { + if (params.blendModes.hasOwnProperty(name)) + { + o.blend[name] = name; + o.blendModeTechniques[name] = params.blendModes[name]; + } + } + } + + // Blending techniques. + o.techniqueParameters = gd.createTechniqueParameters({ + clipSpace: new Draw2D.prototype.floatArray(4), + texture: null + }); + + // Current render target + o.currentRenderTarget = null; + o.renderTargetStructs = []; + + o.state = o.drawStates.ready; + + o.scaleMode = 'none'; + o.blendMode = 'opaque'; + + // View port, back buffer and managed render target values. + o.width = 0; + o.height = 0; + + o.scissorX = 0; + o.scissorY = 0; + o.scissorWidth = o.graphicsDevice.width; + o.scissorHeight = o.graphicsDevice.height; + + o.clipOffsetX = -1.0; + o.clipOffsetY = 1; + o.clipScaleX = 2.0 / o.graphicsDevice.width; + o.clipScaleY = -2.0 / o.graphicsDevice.height; + + o.viewScaleX = 1; + o.viewScaleY = 1; + + // GPU Memory. + // ----------- + + var initial = (params.initialGpuMemory ? params.initialGpuMemory : 0); + if (initial < 140) + { + // 140 = minimum that can be used to draw a single sprite. + initial = 140; + } + if (initial > 2293760) + { + // 2293760 = maximum that can ever be used in 16bit indices. + initial = 2293760; + } + + o.performanceData = { + gpuMemoryUsage : initial, + minBatchSize : 0, + maxBatchSize : 0, + avgBatchSize : 0, + batchCount : 0, + dataTransfers : 0 + }; + + o.maxGpuMemory = (params.maxGpuMemory ? params.maxGpuMemory : 2293760); + if (o.maxGpuMemory < initial) + { + o.maxGpuMemory = initial; + } + + var initialVertices = Math.floor(initial / 140) * 4; + o.maxVertices = Math.floor(o.maxGpuMemory / 140) * 4; + if (o.maxVertices > 65536) + { + o.maxVertices = 65536; + } + + // number of bytes used per-sprite on cpu vertex buffers. + o.cpuStride = 64; + + // vertex buffer is in terms of number of vertices. + // so we have a stride of 4 rather than 128. + o.gpuStride = 4; + + // Index and vertex buffer setup. + o.vertexBufferParameters = { + numVertices: initialVertices, + attributes: [gd.VERTEXFORMAT_FLOAT2, gd.VERTEXFORMAT_FLOAT4, gd.VERTEXFORMAT_FLOAT2], + 'transient': true + }; + o.vertexBuffer = gd.createVertexBuffer(o.vertexBufferParameters); + + o.semantics = gd.createSemantics([gd.SEMANTIC_POSITION, gd.SEMANTIC_COLOR, gd.SEMANTIC_TEXCOORD0]); + o.indexBufferParameters = { + numIndices: (initialVertices * 1.5), + format: gd.INDEXFORMAT_USHORT, + dynamic: false, + data : o.indexData((initialVertices * 1.5)) + }; + o.indexBuffer = gd.createIndexBuffer(o.indexBufferParameters); + + // Render Target API + // ----------------- + + // Objects and values used in render target management. + o.renderTargetIndex = 0; + o.renderTargetCount = 0; + + o.renderTargetTextureParameters = { + name : '', + width : 0, + height : 0, + depth : 1, + format : "R8G8B8A8", + cubemap : false, + mipmaps : true, + renderable : true, + dynamic : true + }; + + o.renderTargetParams = { + colorTexture0 : null + }; + + // Render Target copying. + // ---------------------- + + // Copy technique for copyRenderTarget + o.copyTechnique = shader.getTechnique("copy"); + o.copyTechniqueParameters = gd.createTechniqueParameters({ + inputTexture0 : null, + copyFlip : 1, + copyUVScale : new Draw2D.prototype.floatArray([1, 1]) + }); + + // Objects used in copyRenderTarget method. + o.quadSemantics = gd.createSemantics([gd.SEMANTIC_POSITION, gd.SEMANTIC_TEXCOORD0]); + o.quadPrimitive = gd.PRIMITIVE_TRIANGLE_STRIP; + + o.copyVertexBufferParams = { + numVertices: 4, + attributes: [gd.VERTEXFORMAT_FLOAT2, gd.VERTEXFORMAT_FLOAT2], + 'transient': true + }; + o.copyVertexBuffer = gd.createVertexBuffer(o.copyVertexBufferParams); + + // updateRenderTargetVBO + // --------------------- + + /*jshint white: false*/ + o.vertexBufferData = new Draw2D.prototype.floatArray([-1.0, -1.0, 0.0, 0.0, + 1.0, -1.0, 1.0, 0.0, + -1.0, 1.0, 0.0, 1.0, + 1.0, 1.0, 1.0, 1.0]); + /*jshint white: true*/ + + return o; +}; + +// Detect correct typed arrays +(function () { + Draw2D.prototype.uint16Array = function (arg) { + if (arguments.length === 0) + { + return []; + } + + var i, ret; + if (typeof arg === "number") + { + ret = new Array(arg); + } + else + { + ret = []; + for (i = 0; i < arg.length; i += 1) + { + ret[i] = arg[i]; + } + } + return ret; + }; + + var testArray; + var textDescriptor; + + if (typeof Uint16Array !== "undefined") + { + testArray = new Uint16Array(4); + textDescriptor = Object.prototype.toString.call(testArray); + if (textDescriptor === '[object Uint16Array]') + { + Draw2D.prototype.uint16Array = Uint16Array; + } + } + + Draw2D.prototype.floatArray = function (arg) { + if (arguments.length === 0) + { + return []; + } + + var i, ret; + if (typeof arg === "number") + { + ret = new Array(arg); + } + else + { + ret = []; + for (i = 0; i < arg.length; i += 1) + { + ret[i] = arg[i]; + } + } + return ret; + }; + + if (typeof Float32Array !== "undefined") + { + testArray = new Float32Array(4); + textDescriptor = Object.prototype.toString.call(testArray); + if (textDescriptor === '[object Float32Array]') + { + Draw2D.prototype.floatArray = Float32Array; + Draw2D.prototype.defaultClearColor = new Float32Array(Draw2D.prototype.defaultClearColor); + } + } +}()); diff --git a/spine-js/turbulenz/graphicsdevice.js b/spine-js/turbulenz/graphicsdevice.js new file mode 100644 index 000000000..43d8470ae --- /dev/null +++ b/spine-js/turbulenz/graphicsdevice.js @@ -0,0 +1,6208 @@ +// Copyright (c) 2011-2012 Turbulenz Limited +/*global TurbulenzEngine*/ +/*global TGALoader*/ +/*global DDSLoader*/ +/*global TARLoader*/ +/*global Int8Array*/ +/*global Int16Array*/ +/*global Int32Array*/ +/*global Uint8Array*/ +/*global Uint8ClampedArray*/ +/*global Uint16Array*/ +/*global Uint32Array*/ +/*global Float32Array*/ +/*global ArrayBuffer*/ +/*global DataView*/ +/*global window*/ +"use strict"; + +// +// WebGLTexture +// +function WebGLTexture() {} +WebGLTexture.prototype = +{ + version : 1, + + setData : function textureSetDataFn(data) + { + var gd = this.gd; + var target = this.target; + gd.bindTexture(target, this.glTexture); + this.updateData(data); + gd.bindTexture(target, null); + }, + + // Internal + createGLTexture : function createGLTextureFn(data) + { + var gd = this.gd; + var gl = gd.gl; + + var target; + if (this.cubemap) + { + target = gl.TEXTURE_CUBE_MAP; + } + else if (this.depth > 1) + { + //target = gl.TEXTURE_3D; + // 3D textures are not supported yet + return false; + } + else + { + target = gl.TEXTURE_2D; + } + this.target = target; + + var gltex = gl.createTexture(); + this.glTexture = gltex; + + gd.bindTexture(target, gltex); + + gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + + if (this.mipmaps || 1 < this.numDataLevels) + { + gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + } + + this.updateData(data); + + gd.bindTexture(target, null); + + return true; + }, + + updateData : function updateDataFn(data) + { + var gd = this.gd; + var gl = gd.gl; + + function log2(a) + { + return Math.floor(Math.log(a) / Math.log(2)); + } + + var generateMipMaps = this.mipmaps && (this.numDataLevels !== (1 + Math.max(log2(this.width), log2(this.height)))); + var format = this.format; + var internalFormat, gltype, srcStep, bufferData = null; + var compressedTexturesExtension; + + if (format === gd.PIXELFORMAT_A8) + { + internalFormat = gl.ALPHA; + gltype = gl.UNSIGNED_BYTE; + srcStep = 1; + if (data && !data.src) + { + if (data instanceof Uint8Array) + { + bufferData = data; + } + else + { + bufferData = new Uint8Array(data); + } + } + } + else if (format === gd.PIXELFORMAT_L8) + { + internalFormat = gl.LUMINANCE; + gltype = gl.UNSIGNED_BYTE; + srcStep = 1; + if (data && !data.src) + { + if (data instanceof Uint8Array) + { + bufferData = data; + } + else + { + bufferData = new Uint8Array(data); + } + } + } + else if (format === gd.PIXELFORMAT_L8A8) + { + internalFormat = gl.LUMINANCE_ALPHA; + gltype = gl.UNSIGNED_BYTE; + srcStep = 2; + if (data && !data.src) + { + if (data instanceof Uint8Array) + { + bufferData = data; + } + else + { + bufferData = new Uint8Array(data); + } + } + } + else if (format === gd.PIXELFORMAT_R5G5B5A1) + { + internalFormat = gl.RGBA; + gltype = gl.UNSIGNED_SHORT_5_5_5_1; + srcStep = 1; + if (data && !data.src) + { + if (data instanceof Uint16Array) + { + bufferData = data; + } + else + { + bufferData = new Uint16Array(data); + } + } + } + else if (format === gd.PIXELFORMAT_R5G6B5) + { + internalFormat = gl.RGB; + gltype = gl.UNSIGNED_SHORT_5_6_5; + srcStep = 1; + if (data && !data.src) + { + if (data instanceof Uint16Array) + { + bufferData = data; + } + else + { + bufferData = new Uint16Array(data); + } + } + } + else if (format === gd.PIXELFORMAT_R8G8B8A8) + { + internalFormat = gl.RGBA; + gltype = gl.UNSIGNED_BYTE; + srcStep = 4; + if (data && !data.src) + { + if (data instanceof Uint8Array) + { + // Some browsers consider Uint8ClampedArray to be + // an instance of Uint8Array (which is correct as + // per the spec), yet won't accept a + // Uint8ClampedArray as pixel data for a + // gl.UNSIGNED_BYTE Texture. If we have a + // Uint8ClampedArray then we can just reuse the + // underlying data. + + if (typeof Uint8ClampedArray !== "undefined" && + data instanceof Uint8ClampedArray) + { + bufferData = new Uint8Array(data.buffer); + } + else + { + bufferData = data; + } + } + else + { + bufferData = new Uint8Array(data); + } + } + } + else if (format === gd.PIXELFORMAT_R8G8B8) + { + internalFormat = gl.RGB; + gltype = gl.UNSIGNED_BYTE; + srcStep = 3; + if (data && !data.src) + { + if (data instanceof Uint8Array) + { + // See comment above about Uint8ClampedArray + + if (typeof Uint8ClampedArray !== "undefined" && + data instanceof Uint8ClampedArray) + { + bufferData = new Uint8Array(data.buffer); + } + else + { + bufferData = data; + } + } + else + { + bufferData = new Uint8Array(data); + } + } + } + else if (format === gd.PIXELFORMAT_D24S8) + { + //internalFormat = gl.DEPTH24_STENCIL8_EXT; + //gltype = gl.UNSIGNED_INT_24_8_EXT; + //internalFormat = gl.DEPTH_COMPONENT; + internalFormat = gl.DEPTH_STENCIL; + gltype = gl.UNSIGNED_INT; + srcStep = 1; + if (data && !data.src) + { + bufferData = new Uint32Array(data); + } + } + else if (format === gd.PIXELFORMAT_DXT1 || + format === gd.PIXELFORMAT_DXT3 || + format === gd.PIXELFORMAT_DXT5) + { + compressedTexturesExtension = gd.compressedTexturesExtension; + if (compressedTexturesExtension) + { + if (format === gd.PIXELFORMAT_DXT1) + { + internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + srcStep = 8; + } + else if (format === gd.PIXELFORMAT_DXT3) + { + internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + srcStep = 16; + } + else //if (format === gd.PIXELFORMAT_DXT5) + { + internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT5_EXT; + srcStep = 16; + } + + if (internalFormat === undefined) + { + return; // Unsupported format + } + + if (data && !data.src) + { + if (data instanceof Uint8Array) + { + bufferData = data; + } + else + { + bufferData = new Uint8Array(data); + } + } + } + else + { + return; // Unsupported format + } + } + else + { + return; //unknown/unsupported format + } + + var numLevels = (data && 0 < this.numDataLevels ? this.numDataLevels : 1); + var w = this.width, h = this.height, offset = 0, target, n, levelSize, levelData; + if (this.cubemap) + { + target = gl.TEXTURE_CUBE_MAP; + gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + for (var f = 0; f < 6; f += 1) + { + var faceTarget = (gl.TEXTURE_CUBE_MAP_POSITIVE_X + f); + for (n = 0; n < numLevels; n += 1) + { + if (compressedTexturesExtension) + { + levelSize = (Math.floor((w + 3) / 4) * Math.floor((h + 3) / 4) * srcStep); + if (bufferData) + { + if (numLevels === 1) + { + levelData = bufferData; + } + else + { + levelData = bufferData.subarray(offset, (offset + levelSize)); + } + } + else + { + levelData = new Uint8Array(levelSize); + } + if (gd.WEBGL_compressed_texture_s3tc) + { + gl.compressedTexImage2D(faceTarget, n, internalFormat, w, h, 0, + levelData); + } + else + { + compressedTexturesExtension.compressedTexImage2D(faceTarget, n, internalFormat, w, h, 0, + levelData); + } + } + else + { + levelSize = (w * h * srcStep); + if (bufferData) + { + if (numLevels === 1) + { + levelData = bufferData; + } + else + { + levelData = bufferData.subarray(offset, (offset + levelSize)); + } + gl.texImage2D(faceTarget, n, internalFormat, w, h, 0, internalFormat, gltype, levelData); + } + else if (data) + { + gl.texImage2D(faceTarget, n, internalFormat, internalFormat, gltype, data); + } + else + { + gl.texImage2D(faceTarget, n, internalFormat, w, h, 0, internalFormat, gltype, + new Uint8Array(levelSize)); + } + } + offset += levelSize; + w = (w > 1 ? Math.floor(w / 2) : 1); + h = (h > 1 ? Math.floor(h / 2) : 1); + } + w = this.width; + h = this.height; + } + } + else + { + target = gl.TEXTURE_2D; + gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + for (n = 0; n < numLevels; n += 1) + { + if (compressedTexturesExtension) + { + levelSize = (Math.floor((w + 3) / 4) * Math.floor((h + 3) / 4) * srcStep); + if (bufferData) + { + if (numLevels === 1) + { + levelData = bufferData; + } + else + { + levelData = bufferData.subarray(offset, (offset + levelSize)); + } + } + else + { + levelData = new Uint8Array(levelSize); + } + if (gd.WEBGL_compressed_texture_s3tc) + { + gl.compressedTexImage2D(target, n, internalFormat, w, h, 0, levelData); + } + else + { + compressedTexturesExtension.compressedTexImage2D(target, n, internalFormat, w, h, 0, levelData); + } + } + else + { + levelSize = (w * h * srcStep); + if (bufferData) + { + if (numLevels === 1) + { + levelData = bufferData; + } + else + { + levelData = bufferData.subarray(offset, (offset + levelSize)); + } + gl.texImage2D(target, n, internalFormat, w, h, 0, internalFormat, gltype, levelData); + } + else if (data) + { + gl.texImage2D(target, n, internalFormat, internalFormat, gltype, data); + } + else + { + gl.texImage2D(target, n, internalFormat, w, h, 0, internalFormat, gltype, + new Uint8Array(levelSize)); + } + } + offset += levelSize; + w = (w > 1 ? Math.floor(w / 2) : 1); + h = (h > 1 ? Math.floor(h / 2) : 1); + } + } + + if (generateMipMaps) + { + gl.generateMipmap(target); + } + }, + + updateMipmaps : function updateMipmapsFn(face) + { + if (this.mipmaps) + { + if (this.depth > 1) + { + TurbulenzEngine.callOnError( + "3D texture mipmap generation unsupported"); + return; + } + + if (this.cubemap && face !== 5) + { + return; + } + + var gd = this.gd; + var gl = gd.gl; + + var target = this.target; + gd.bindTexture(target, this.glTexture); + gl.generateMipmap(target); + gd.bindTexture(target, null); + } + }, + + destroy : function textureDestroyFn() + { + var gd = this.gd; + if (gd) + { + var glTexture = this.glTexture; + if (glTexture) + { + var gl = gd.gl; + if (gl) + { + gd.unbindTexture(glTexture); + gl.deleteTexture(glTexture); + } + delete this.glTexture; + } + + delete this.sampler; + delete this.gd; + } + }, + + typedArrayIsValid : function textureTypedArrayIsValidFn(typedArray) + { + var gd = this.gd; + var format = this.format; + + if (gd) + { + if ((format === gd.PIXELFORMAT_A8) || + (format === gd.PIXELFORMAT_L8) || + (format === gd.PIXELFORMAT_S8)) + { + return ((typedArray instanceof Uint8Array) || + (typeof Uint8ClampedArray !== "undefined" && + typedArray instanceof Uint8ClampedArray)) && + (typedArray.length === + this.width * this.height * this.depth); + } + if (format === gd.PIXELFORMAT_L8A8) + { + return ((typedArray instanceof Uint8Array) || + (typeof Uint8ClampedArray !== "undefined" && + typedArray instanceof Uint8ClampedArray)) && + (typedArray.length === + 2 * this.width * this.height * this.depth); + } + if (format === gd.PIXELFORMAT_R8G8B8) + { + return ((typedArray instanceof Uint8Array) || + (typeof Uint8ClampedArray !== "undefined" && + typedArray instanceof Uint8ClampedArray)) && + (typedArray.length === + 3 * this.width * this.height * this.depth); + } + if (format === gd.PIXELFORMAT_R8G8B8A8) + { + return ((typedArray instanceof Uint8Array) || + (typeof Uint8ClampedArray !== "undefined" && + typedArray instanceof Uint8ClampedArray)) && + (typedArray.length === + 4 * this.width * this.height * this.depth); + } + if ((format === gd.PIXELFORMAT_R5G5B5A1) || + (format === gd.PIXELFORMAT_R5G6B5)) + { + return (typedArray instanceof Uint16Array) && + (typedArray.length === + this.width * this.height * this.depth); + } + } + return false; + } +}; + +// Constructor function +WebGLTexture.create = function webGLTextureCreateFn(gd, params) +{ + var tex = new WebGLTexture(); + tex.gd = gd; + tex.mipmaps = params.mipmaps; + tex.dynamic = params.dynamic; + tex.renderable = params.renderable; + tex.numDataLevels = 0; + + var src = params.src; + if (src) + { + tex.name = params.name || src; + var extension; + var data = params.data; + if (data) + { + // do not trust file extensions if we got data... + if (data[0] === 137 && + data[1] === 80 && + data[2] === 78 && + data[3] === 71) + { + extension = '.png'; + } + else if (data[0] === 255 && + data[1] === 216 && + data[2] === 255 && + (data[3] === 224 || data[3] === 225)) + { + extension = '.jpg'; + } + else if (data[0] === 68 && + data[1] === 68 && + data[2] === 83 && + data[3] === 32) + { + extension = '.dds'; + } + else + { + extension = src.slice(-4); + } + } + else + { + extension = src.slice(-4); + } + + // DDS and TGA textures require out own image loaders + if (extension === '.dds' || + extension === '.tga') + { + if (extension === '.tga' && typeof TGALoader !== 'undefined') + { + var tgaParams = { + gd: gd, + onload : function tgaLoadedFn(data, width, height, format, status) + { + tex.width = width; + tex.height = height; + tex.depth = 1; + tex.format = format; + tex.cubemap = false; + var result = tex.createGLTexture(data); + if (params.onload) + { + params.onload(result ? tex : null, status); + } + }, + onerror : function tgaFailedFn() + { + tex.failed = true; + if (params.onload) + { + params.onload(null); + } + } + }; + if (data) + { + tgaParams.data = data; + } + else + { + tgaParams.src = src; + } + TGALoader.create(tgaParams); + return tex; + } + else if (extension === '.dds' && typeof DDSLoader !== 'undefined') + { + var ddsParams = { + gd: gd, + onload : function ddsLoadedFn(data, width, height, format, numLevels, cubemap, depth, status) + { + tex.width = width; + tex.height = height; + tex.format = format; + tex.cubemap = cubemap; + tex.depth = depth; + tex.numDataLevels = numLevels; + var result = tex.createGLTexture(data); + if (params.onload) + { + params.onload(result ? tex : null, status); + } + }, + onerror : function ddsFailedFn() + { + tex.failed = true; + if (params.onload) + { + params.onload(null); + } + } + }; + if (data) + { + ddsParams.data = data; + } + else + { + ddsParams.src = src; + } + DDSLoader.create(ddsParams); + return tex; + } + else + { + TurbulenzEngine.callOnError( + 'Missing image loader required for ' + src); + + tex = webGLTextureCreateFn(gd, { + name : (params.name || src), + width : 2, + height : 2, + depth : 1, + format : 'R8G8B8A8', + cubemap : false, + mipmaps : params.mipmaps, + dynamic : params.dynamic, + renderable : params.renderable, + data : [255, 20, 147, 255, + 255, 0, 0, 255, + 255, 255, 255, 255, + 255, 20, 147, 255] + }); + + if (params.onload) + { + if (TurbulenzEngine) + { + TurbulenzEngine.setTimeout(function () { + params.onload(tex, 200); + }, 0); + } + else + { + window.setTimeout(function () { + params.onload(tex, 200); + }, 0); + } + } + return tex; + } + } + + var img = new Image(); + img.onload = function imageLoadedFn() + { + tex.width = img.width; + tex.height = img.height; + tex.depth = 1; + tex.format = gd.PIXELFORMAT_R8G8B8A8; + tex.cubemap = false; + var result = tex.createGLTexture(img); + if (params.onload) + { + params.onload(result ? tex : null, 200); + } + }; + img.onerror = function imageFailedFn() + { + tex.failed = true; + if (params.onload) + { + params.onload(null); + } + }; + if (data) + { + if (extension === '.jpg' || extension === '.jpeg') + { + src = 'data:image/jpeg;base64,' + TurbulenzEngine.base64Encode(data); + } + else if (extension === '.png') + { + src = 'data:image/png;base64,' + TurbulenzEngine.base64Encode(data); + } + } + else + { + img.crossOrigin = 'anonymous'; + } + img.src = src; + } + else + { + // Invalid src values like "" fall through to here + if ("" === src && params.onload) + { + // Assume the caller intended to pass in a valid url. + return null; + } + + var format = params.format; + if (typeof format === 'string') + { + format = gd['PIXELFORMAT_' + format]; + } + + tex.width = params.width; + tex.height = params.height; + tex.depth = params.depth; + tex.format = format; + tex.cubemap = params.cubemap; + tex.name = params.name; + + var result = tex.createGLTexture(params.data); + if (!result) + { + tex = null; + } + + if (params.onload) + { + params.onload(tex, 200); + } + } + + return tex; +}; + + +// +// WebGLRenderBuffer +// +function WebGLRenderBuffer() {} +WebGLRenderBuffer.prototype = +{ + version : 1, + + destroy : function renderBufferDestroyFn() + { + var gd = this.gd; + if (gd) + { + var glBuffer = this.glBuffer; + if (glBuffer) + { + var gl = gd.gl; + if (gl) + { + gl.deleteRenderbuffer(glBuffer); + } + delete this.glBuffer; + } + + delete this.gd; + } + } +}; + +// Constructor function +WebGLRenderBuffer.create = function webGLRenderBufferFn(gd, params) +{ + var renderBuffer = new WebGLRenderBuffer(); + + var width = params.width; + var height = params.height; + var format = params.format; + if (typeof format === 'string') + { + format = gd['PIXELFORMAT_' + format]; + } + + if (format !== gd.PIXELFORMAT_D24S8) + { + return null; + } + + var gl = gd.gl; + + var glBuffer = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, glBuffer); + + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height); + + renderBuffer.width = gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH); + renderBuffer.height = gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_HEIGHT); + + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + + if (renderBuffer.width < width || + renderBuffer.height < height) + { + gl.deleteRenderbuffer(glBuffer); + return null; + } + + renderBuffer.gd = gd; + renderBuffer.format = format; + renderBuffer.glBuffer = glBuffer; + + return renderBuffer; +}; + + +// +// WebGLRenderTarget +// +function WebGLRenderTarget() {} +WebGLRenderTarget.prototype = +{ + version : 1, + + // Shared because there can only be one active at a time + oldViewportBox : [], + oldScissorBox : [], + + copyBox : function copyBoxFn(dst, src) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + }, + + bind : function bindFn() + { + var gd = this.gd; + var gl = gd.gl; + + gd.unbindTexture(this.colorTexture0.glTexture); + if (this.depthTexture) + { + gd.unbindTexture(this.depthTexture.glTexture); + } + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.glObject); + + var state = gd.state; + this.copyBox(this.oldViewportBox, state.viewportBox); + this.copyBox(this.oldScissorBox, state.scissorBox); + gd.setViewport(0, 0, this.width, this.height); + gd.setScissor(0, 0, this.width, this.height); + + return true; + }, + + unbind : function unbindFn() + { + var gd = this.gd; + var gl = gd.gl; + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + gd.setViewport.apply(gd, this.oldViewportBox); + gd.setScissor.apply(gd, this.oldScissorBox); + + this.colorTexture0.updateMipmaps(this.face); + if (this.depthTexture) + { + this.depthTexture.updateMipmaps(this.face); + } + }, + + destroy : function renderTargetDestroyFn() + { + var gd = this.gd; + if (gd) + { + var glObject = this.glObject; + if (glObject) + { + var gl = gd.gl; + if (gl) + { + gl.deleteFramebuffer(glObject); + } + delete this.glObject; + } + + delete this.colorTexture0; + delete this.colorTexture1; + delete this.colorTexture2; + delete this.colorTexture3; + delete this.depthBuffer; + delete this.depthTexture; + delete this.gd; + } + } +}; + +// Constructor function +WebGLRenderTarget.create = function webGLRenderTargetFn(gd, params) +{ + var renderTarget = new WebGLRenderTarget(); + + var colorTexture0 = params.colorTexture0; + var colorTexture1 = (colorTexture0 ? (params.colorTexture1 || null) : null); + var colorTexture2 = (colorTexture1 ? (params.colorTexture2 || null) : null); + var colorTexture3 = (colorTexture2 ? (params.colorTexture3 || null) : null); + var depthBuffer = params.depthBuffer || null; + var depthTexture = params.depthTexture || null; + var face = params.face; + + var maxSupported = gd.maxSupported("RENDERTARGET_COLOR_TEXTURES"); + if (colorTexture1 && maxSupported < 2) + { + return null; + } + if (colorTexture2 && maxSupported < 3) + { + return null; + } + if (colorTexture3 && maxSupported < 4) + { + return null; + } + + var gl = gd.gl; + + var glObject = gl.createFramebuffer(); + + gl.bindFramebuffer(gl.FRAMEBUFFER, glObject); + + var width, height; + if (colorTexture0) + { + width = colorTexture0.width; + height = colorTexture0.height; + + var glTexture = colorTexture0.glTexture; + if (glTexture === undefined) + { + TurbulenzEngine.callOnError("Color texture is not a Texture"); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.deleteFramebuffer(glObject); + return null; + } + + var colorAttachment0 = gl.COLOR_ATTACHMENT0; + if (colorTexture0.cubemap) + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, colorAttachment0, (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0); + } + else + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, colorAttachment0, gl.TEXTURE_2D, glTexture, 0); + } + + if (colorTexture1) + { + glTexture = colorTexture1.glTexture; + if (colorTexture1.cubemap) + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 1), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0); + } + else + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 1), gl.TEXTURE_2D, glTexture, 0); + } + + if (colorTexture2) + { + glTexture = colorTexture2.glTexture; + if (colorTexture1.cubemap) + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 2), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0); + } + else + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 2), gl.TEXTURE_2D, glTexture, 0); + } + + if (colorTexture3) + { + glTexture = colorTexture3.glTexture; + if (colorTexture1.cubemap) + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 3), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0); + } + else + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 3), gl.TEXTURE_2D, glTexture, 0); + } + } + } + } + } + else if (depthTexture) + { + width = depthTexture.width; + height = depthTexture.height; + } + else if (depthBuffer) + { + width = depthBuffer.width; + height = depthBuffer.height; + } + else + { + TurbulenzEngine.callOnError( + "No RenderBuffers or Textures specified for this RenderTarget"); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.deleteFramebuffer(glObject); + return null; + } + + if (depthTexture) + { + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, + gl.TEXTURE_2D, depthTexture.glTexture, 0); + } + else if (depthBuffer) + { + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, + gl.RENDERBUFFER, depthBuffer.glBuffer); + } + + var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + if (status !== gl.FRAMEBUFFER_COMPLETE) + { + gl.deleteFramebuffer(glObject); + return null; + } + + renderTarget.gd = gd; + renderTarget.glObject = glObject; + renderTarget.colorTexture0 = colorTexture0; + renderTarget.colorTexture1 = colorTexture1; + renderTarget.colorTexture2 = colorTexture2; + renderTarget.colorTexture3 = colorTexture3; + renderTarget.depthBuffer = depthBuffer; + renderTarget.depthTexture = depthTexture; + renderTarget.width = width; + renderTarget.height = height; + renderTarget.face = face; + + return renderTarget; +}; + + +// +// WebGLIndexBuffer +// +function WebGLIndexBuffer() {} +WebGLIndexBuffer.prototype = +{ + version : 1, + + map : function indexBufferMapFn(offset, numIndices) + { + if (offset === undefined) + { + offset = 0; + } + if (numIndices === undefined) + { + numIndices = this.numIndices; + } + + var gd = this.gd; + var gl = gd.gl; + + var format = this.format; + var data; + if (format === gl.UNSIGNED_BYTE) + { + data = new Uint8Array(numIndices); + } + else if (format === gl.UNSIGNED_SHORT) + { + data = new Uint16Array(numIndices); + } + else //if (format === gl.UNSIGNED_INT) + { + data = new Uint32Array(numIndices); + } + + var numValues = 0; + var writer = function indexBufferWriterFn() + { + var numArguments = arguments.length; + for (var n = 0; n < numArguments; n += 1) + { + data[numValues] = arguments[n]; + numValues += 1; + } + }; + writer.data = data; + writer.offset = offset; + writer.getNumWrittenIndices = function getNumWrittenIndicesFn() + { + return numValues; + }; + writer.write = writer; + return writer; + }, + + unmap : function indexBufferUnmapFn(writer) + { + if (writer) + { + var gd = this.gd; + var gl = gd.gl; + + var data = writer.data; + delete writer.data; + + var offset = writer.offset; + + delete writer.write; + + var numIndices = writer.getNumWrittenIndices(); + if (!numIndices) + { + return; + } + + if (numIndices < data.length) + { + data = data.subarray(0, numIndices); + } + + gd.setIndexBuffer(this); + + if (numIndices < this.numIndices) + { + gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, offset, data); + } + else + { + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, this.usage); + } + } + }, + + setData : function indexBufferSetDataFn(data, offset, numIndices) + { + if (offset === undefined) + { + offset = 0; + } + if (numIndices === undefined) + { + numIndices = this.numIndices; + } + + var gd = this.gd; + var gl = gd.gl; + + var bufferData; + var format = this.format; + if (format === gl.UNSIGNED_BYTE) + { + if (data instanceof Uint8Array) + { + bufferData = data; + } + else + { + bufferData = new Uint8Array(data); + } + } + else if (format === gl.UNSIGNED_SHORT) + { + if (data instanceof Uint16Array) + { + bufferData = data; + } + else + { + bufferData = new Uint16Array(data); + } + offset *= 2; + } + else if (format === gl.UNSIGNED_INT) + { + if (data instanceof Uint32Array) + { + bufferData = data; + } + else + { + bufferData = new Uint32Array(data); + } + offset *= 4; + } + data = undefined; + + if (numIndices < bufferData.length) + { + bufferData = bufferData.subarray(0, numIndices); + } + + gd.setIndexBuffer(this); + + if (numIndices < this.numIndices) + { + gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, offset, bufferData); + } + else + { + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, bufferData, this.usage); + } + }, + + destroy : function indexBufferDestroyFn() + { + var gd = this.gd; + if (gd) + { + var glBuffer = this.glBuffer; + if (glBuffer) + { + var gl = gd.gl; + if (gl) + { + gd.unsetIndexBuffer(this); + gl.deleteBuffer(glBuffer); + } + delete this.glBuffer; + } + + delete this.gd; + } + } +}; + +// Constructor function +WebGLIndexBuffer.create = function webGLIndexBufferCreateFn(gd, params) +{ + var gl = gd.gl; + + var ib = new WebGLIndexBuffer(); + ib.gd = gd; + + var numIndices = params.numIndices; + ib.numIndices = numIndices; + + var format = params.format; + if (typeof format === "string") + { + format = gd['INDEXFORMAT_' + format]; + } + ib.format = format; + + var stride; + if (format === gl.UNSIGNED_BYTE) + { + stride = 1; + } + else if (format === gl.UNSIGNED_SHORT) + { + stride = 2; + } + else //if (format === gl.UNSIGNED_INT) + { + stride = 4; + } + ib.stride = stride; + + /*jshint sub: true*/ + // Avoid dot notation lookup to prevent Google Closure complaining about transient being a keyword + ib['transient'] = (params['transient'] || false); + ib.dynamic = (params.dynamic || ib['transient']); + ib.usage = (ib['transient'] ? gl.STREAM_DRAW : (ib.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW)); + /*jshint sub: false*/ + + ib.glBuffer = gl.createBuffer(); + + if (params.data) + { + ib.setData(params.data, 0, numIndices); + } + else + { + gd.setIndexBuffer(ib); + + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, (numIndices * stride), ib.usage); + } + + return ib; +}; + + +// +// WebGLSemantics +// +function WebGLSemantics() {} +WebGLSemantics.prototype = +{ + version : 1 +}; + +// Constructor function +WebGLSemantics.create = function webGLSemanticsCreateFn(gd, attributes) +{ + var semantics = new WebGLSemantics(); + + var numAttributes = attributes.length; + semantics.length = numAttributes; + for (var i = 0; i < numAttributes; i += 1) + { + var attribute = attributes[i]; + if (typeof attribute === "string") + { + semantics[i] = gd['SEMANTIC_' + attribute]; + } + else + { + semantics[i] = attribute; + } + } + + return semantics; +}; + + +// +// WebGLVertexBuffer +// +function WebGLVertexBuffer() {} +WebGLVertexBuffer.prototype = +{ + version : 1, + + map : function vertexBufferMapFn(offset, numVertices) + { + if (offset === undefined) + { + offset = 0; + } + if (numVertices === undefined) + { + numVertices = this.numVertices; + } + + var gd = this.gd; + var gl = gd.gl; + + var numValuesPerVertex = this.stride; + var attributes = this.attributes; + var numAttributes = attributes.length; + + var data, writer; + var numValues = 0; + + if (this.hasSingleFormat) + { + var maxNumValues = (numVertices * numValuesPerVertex); + var format = attributes[0].format; + + if (format === gl.BYTE) + { + data = new Int8Array(maxNumValues); + } + else if (format === gl.UNSIGNED_BYTE) + { + data = new Uint8Array(maxNumValues); + } + else if (format === gl.SHORT) + { + data = new Int16Array(maxNumValues); + } + else if (format === gl.UNSIGNED_SHORT) + { + data = new Uint16Array(maxNumValues); + } + else if (format === gl.INT) + { + data = new Int32Array(maxNumValues); + } + else if (format === gl.UNSIGNED_INT) + { + data = new Uint32Array(maxNumValues); + } + else if (format === gl.FLOAT) + { + data = new Float32Array(maxNumValues); + } + + writer = function vertexBufferWriterSingleFn() + { + var numArguments = arguments.length; + var currentArgument = 0; + for (var a = 0; a < numAttributes; a += 1) + { + var attribute = attributes[a]; + var numComponents = attribute.numComponents; + var currentComponent = 0, j; + do + { + if (currentArgument < numArguments) + { + var value = arguments[currentArgument]; + currentArgument += 1; + if (typeof value === "number") + { + if (attribute.normalized) + { + value *= attribute.normalizationScale; + } + data[numValues] = value; + numValues += 1; + currentComponent += 1; + } + else if (currentComponent === 0) + { + var numSubArguments = value.length; + if (numSubArguments > numComponents) + { + numSubArguments = numComponents; + } + if (attribute.normalized) + { + var scale = attribute.normalizationScale; + for (j = 0; j < numSubArguments; j += 1) + { + data[numValues] = (value[j] * scale); + numValues += 1; + currentComponent += 1; + } + } + else + { + for (j = 0; j < numSubArguments; j += 1) + { + data[numValues] = value[j]; + numValues += 1; + currentComponent += 1; + } + } + while (currentComponent < numComponents) + { + // No need to clear to zeros + numValues += 1; + currentComponent += 1; + } + break; + } + else + { + TurbulenzEngine.callOnError( + 'Missing values for attribute ' + a); + return null; + } + } + else + { + // No need to clear to zeros + numValues += 1; + currentComponent += 1; + } + } + while (currentComponent < numComponents); + } + }; + } + else + { + var destOffset = 0; + var bufferSize = (numVertices * this.strideInBytes); + + data = new ArrayBuffer(bufferSize); + + if (typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) + { + var dataView = new DataView(data); + + writer = function vertexBufferWriterDataViewFn() + { + var numArguments = arguments.length; + var currentArgument = 0; + for (var a = 0; a < numAttributes; a += 1) + { + var attribute = attributes[a]; + var numComponents = attribute.numComponents; + var setter = attribute.typedSetter; + var componentStride = attribute.componentStride; + var currentComponent = 0, j; + do + { + if (currentArgument < numArguments) + { + var value = arguments[currentArgument]; + currentArgument += 1; + if (typeof value === "number") + { + if (attribute.normalized) + { + value *= attribute.normalizationScale; + } + setter.call(dataView, destOffset, value, true); + destOffset += componentStride; + currentComponent += 1; + numValues += 1; + } + else if (currentComponent === 0) + { + var numSubArguments = value.length; + if (numSubArguments > numComponents) + { + numSubArguments = numComponents; + } + if (attribute.normalized) + { + var scale = attribute.normalizationScale; + for (j = 0; j < numSubArguments; j += 1) + { + setter.call(dataView, destOffset, (value[j] * scale), true); + destOffset += componentStride; + currentComponent += 1; + numValues += 1; + } + } + else + { + for (j = 0; j < numSubArguments; j += 1) + { + setter.call(dataView, destOffset, value[j], true); + destOffset += componentStride; + currentComponent += 1; + numValues += 1; + } + } + while (currentComponent < numComponents) + { + // No need to clear to zeros + numValues += 1; + currentComponent += 1; + } + break; + } + else + { + TurbulenzEngine.callOnError( + 'Missing values for attribute ' + a); + return null; + } + } + else + { + // No need to clear to zeros + numValues += 1; + currentComponent += 1; + } + } + while (currentComponent < numComponents); + } + }; + } + else + { + writer = function vertexBufferWriterMultiFn() + { + var numArguments = arguments.length; + var currentArgument = 0; + var dest; + for (var a = 0; a < numAttributes; a += 1) + { + var attribute = attributes[a]; + var numComponents = attribute.numComponents; + dest = new attribute.typedArray(data, destOffset, numComponents); + destOffset += attribute.stride; + + var currentComponent = 0, j; + do + { + if (currentArgument < numArguments) + { + var value = arguments[currentArgument]; + currentArgument += 1; + if (typeof value === "number") + { + if (attribute.normalized) + { + value *= attribute.normalizationScale; + } + dest[currentComponent] = value; + currentComponent += 1; + numValues += 1; + } + else if (currentComponent === 0) + { + var numSubArguments = value.length; + if (numSubArguments > numComponents) + { + numSubArguments = numComponents; + } + if (attribute.normalized) + { + var scale = attribute.normalizationScale; + for (j = 0; j < numSubArguments; j += 1) + { + dest[currentComponent] = (value[j] * scale); + currentComponent += 1; + numValues += 1; + } + } + else + { + for (j = 0; j < numSubArguments; j += 1) + { + dest[currentComponent] = value[j]; + currentComponent += 1; + numValues += 1; + } + } + while (currentComponent < numComponents) + { + // No need to clear to zeros + currentComponent += 1; + numValues += 1; + } + break; + } + else + { + TurbulenzEngine.callOnError( + 'Missing values for attribute ' + a); + return null; + } + } + else + { + // No need to clear to zeros + currentComponent += 1; + numValues += 1; + } + } + while (currentComponent < numComponents); + } + }; + } + } + + writer.data = data; + writer.offset = offset; + writer.getNumWrittenVertices = function getNumWrittenVerticesFn() + { + return Math.floor(numValues / numValuesPerVertex); + }; + writer.getNumWrittenValues = function getNumWrittenValuesFn() + { + return numValues; + }; + writer.write = writer; + return writer; + }, + + unmap : function vertexBufferUnmapFn(writer) + { + if (writer) + { + var data = writer.data; + delete writer.data; + + delete writer.write; + + var numVertices = writer.getNumWrittenVertices(); + if (!numVertices) + { + return; + } + + var offset = writer.offset; + + var stride = this.strideInBytes; + + if (this.hasSingleFormat) + { + var numValues = writer.getNumWrittenValues(); + if (numValues < data.length) + { + data = data.subarray(0, numValues); + } + } + else + { + var numBytes = (numVertices * stride); + if (numBytes < data.byteLength) + { + data = data.slice(0, numBytes); + } + } + + var gd = this.gd; + var gl = gd.gl; + + gd.bindVertexBuffer(this.glBuffer); + + if (numVertices < this.numVertices) + { + gl.bufferSubData(gl.ARRAY_BUFFER, (offset * stride), data); + } + else + { + gl.bufferData(gl.ARRAY_BUFFER, data, this.usage); + } + } + }, + + setData : function vertexBufferSetDataFn(data, offset, numVertices) + { + if (offset === undefined) + { + offset = 0; + } + if (numVertices === undefined) + { + numVertices = this.numVertices; + } + + var gd = this.gd; + var gl = gd.gl; + var strideInBytes = this.strideInBytes; + + // Fast path for ArrayBuffer data + + if (data.constructor === ArrayBuffer) + { + gd.bindVertexBuffer(this.glBuffer); + + if (numVertices < this.numVertices) + { + gl.bufferSubData(gl.ARRAY_BUFFER, (offset * strideInBytes), data); + } + else + { + gl.bufferData(gl.ARRAY_BUFFER, data, this.usage); + } + return; + } + + var attributes = this.attributes; + var numAttributes = this.numAttributes; + var attribute, format, bufferData, TypedArrayConstructor; + + if (this.hasSingleFormat) + { + attribute = attributes[0]; + format = attribute.format; + + if (format === gl.BYTE) + { + if (!(data instanceof Int8Array)) + { + TypedArrayConstructor = Int8Array; + } + } + else if (format === gl.UNSIGNED_BYTE) + { + if (!(data instanceof Uint8Array)) + { + TypedArrayConstructor = Uint8Array; + } + } + else if (format === gl.SHORT) + { + if (!(data instanceof Int16Array)) + { + TypedArrayConstructor = Int16Array; + } + } + else if (format === gl.UNSIGNED_SHORT) + { + if (!(data instanceof Uint16Array)) + { + TypedArrayConstructor = Uint16Array; + } + } + else if (format === gl.INT) + { + if (!(data instanceof Int32Array)) + { + TypedArrayConstructor = Int32Array; + } + } + else if (format === gl.UNSIGNED_INT) + { + if (!(data instanceof Uint32Array)) + { + TypedArrayConstructor = Uint32Array; + } + } + else if (format === gl.FLOAT) + { + if (!(data instanceof Float32Array)) + { + TypedArrayConstructor = Float32Array; + } + } + + var numValuesPerVertex = this.stride; + var numValues = (numVertices * numValuesPerVertex); + + if (TypedArrayConstructor) + { + // Data has to be put into a Typed Array and + // potentially normalized. + + if (attribute.normalized) + { + data = this.scaleValues(data, attribute.normalizationScale, numValues); + } + bufferData = new TypedArrayConstructor(data); + if (numValues < bufferData.length) + { + bufferData = bufferData.subarray(0, numValues); + } + } + else + { + bufferData = data; + } + + if (numValues < data.length) + { + bufferData = bufferData.subarray(0, numValues); + } + } + else + { + var bufferSize = (numVertices * strideInBytes); + + bufferData = new ArrayBuffer(bufferSize); + + var srcOffset = 0, destOffset = 0, v, c, a, numComponents, componentStride, scale; + if (typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) + { + var dataView = new DataView(bufferData); + + for (v = 0; v < numVertices; v += 1) + { + for (a = 0; a < numAttributes; a += 1) + { + attribute = attributes[a]; + numComponents = attribute.numComponents; + componentStride = attribute.componentStride; + var setter = attribute.typedSetter; + if (attribute.normalized) + { + scale = attribute.normalizationScale; + for (c = 0; c < numComponents; c += 1) + { + setter.call(dataView, destOffset, (data[srcOffset] * scale), true); + destOffset += componentStride; + srcOffset += 1; + } + } + else + { + for (c = 0; c < numComponents; c += 1) + { + setter.call(dataView, destOffset, data[srcOffset], true); + destOffset += componentStride; + srcOffset += 1; + } + } + } + } + } + else + { + for (v = 0; v < numVertices; v += 1) + { + for (a = 0; a < numAttributes; a += 1) + { + attribute = attributes[a]; + numComponents = attribute.numComponents; + var dest = new attribute.typedArray(bufferData, destOffset, numComponents); + destOffset += attribute.stride; + if (attribute.normalized) + { + scale = attribute.normalizationScale; + for (c = 0; c < numComponents; c += 1) + { + dest[c] = (data[srcOffset] * scale); + srcOffset += 1; + } + } + else + { + for (c = 0; c < numComponents; c += 1) + { + dest[c] = data[srcOffset]; + srcOffset += 1; + } + } + } + } + } + } + data = undefined; + + gd.bindVertexBuffer(this.glBuffer); + + if (numVertices < this.numVertices) + { + gl.bufferSubData(gl.ARRAY_BUFFER, (offset * strideInBytes), bufferData); + } + else + { + gl.bufferData(gl.ARRAY_BUFFER, bufferData, this.usage); + } + }, + + // Internal + scaleValues : function scaleValuesFn(values, scale, numValues) + { + if (numValues === undefined) + { + numValues = values.length; + } + var scaledValues = new values.constructor(numValues); + for (var n = 0; n < numValues; n += 1) + { + scaledValues[n] = (values[n] * scale); + } + return scaledValues; + }, + + bindAttributes : function bindAttributesFn(numAttributes, attributes, offset) + { + var gd = this.gd; + var gl = gd.gl; + var vertexAttributes = this.attributes; + var stride = this.strideInBytes; + var attributeMask = 0; + /*jshint bitwise: false*/ + for (var n = 0; n < numAttributes; n += 1) + { + var vertexAttribute = vertexAttributes[n]; + var attribute = attributes[n]; + + attributeMask |= (1 << attribute); + + gl.vertexAttribPointer(attribute, + vertexAttribute.numComponents, + vertexAttribute.format, + vertexAttribute.normalized, + stride, + offset); + + offset += vertexAttribute.stride; + } + /*jshint bitwise: true*/ + return attributeMask; + }, + + setAttributes : function setAttributesFn(attributes) + { + var gd = this.gd; + + var numAttributes = attributes.length; + this.numAttributes = numAttributes; + + this.attributes = []; + var stride = 0, numValuesPerVertex = 0, hasSingleFormat = true; + + for (var i = 0; i < numAttributes; i += 1) + { + var format = attributes[i]; + if (typeof format === "string") + { + format = gd['VERTEXFORMAT_' + format]; + } + this.attributes[i] = format; + stride += format.stride; + numValuesPerVertex += format.numComponents; + + if (hasSingleFormat && i) + { + if (format.format !== this.attributes[i - 1].format) + { + hasSingleFormat = false; + } + } + } + this.strideInBytes = stride; + this.stride = numValuesPerVertex; + this.hasSingleFormat = hasSingleFormat; + + return stride; + }, + + resize : function resizeFn(size) + { + if (size !== (this.strideInBytes * this.numVertices)) + { + var gd = this.gd; + var gl = gd.gl; + + gd.bindVertexBuffer(this.glBuffer); + + var bufferType = gl.ARRAY_BUFFER; + gl.bufferData(bufferType, size, this.usage); + + var bufferSize = gl.getBufferParameter(bufferType, gl.BUFFER_SIZE); + this.numVertices = Math.floor(bufferSize / this.strideInBytes); + } + }, + + destroy : function vertexBufferDestroyFn() + { + var gd = this.gd; + if (gd) + { + var glBuffer = this.glBuffer; + if (glBuffer) + { + var gl = gd.gl; + if (gl) + { + gd.unbindVertexBuffer(glBuffer); + gl.deleteBuffer(glBuffer); + } + delete this.glBuffer; + } + + delete this.gd; + } + } +}; + +// Constructor function +WebGLVertexBuffer.create = function webGLVertexBufferCreateFn(gd, params) +{ + var gl = gd.gl; + + var vb = new WebGLVertexBuffer(); + vb.gd = gd; + + var numVertices = params.numVertices; + vb.numVertices = numVertices; + + var strideInBytes = vb.setAttributes(params.attributes); + + /*jshint sub: true*/ + // Avoid dot notation lookup to prevent Google Closure complaining about transient being a keyword + vb['transient'] = (params['transient'] || false); + vb.dynamic = (params.dynamic || vb['transient']); + vb.usage = (vb['transient'] ? gl.STREAM_DRAW : (vb.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW)); + /*jshint sub: false*/ + vb.glBuffer = gl.createBuffer(); + + var bufferSize = (numVertices * strideInBytes); + + if (params.data) + { + vb.setData(params.data, 0, numVertices); + } + else + { + gd.bindVertexBuffer(vb.glBuffer); + + gl.bufferData(gl.ARRAY_BUFFER, bufferSize, vb.usage); + } + + return vb; +}; + + +// +// WebGLPass +// +function WebGLPass() {} +WebGLPass.prototype = +{ + version : 1, + + updateParametersData : function updateParametersDataFn(gd) + { + var gl = gd.gl; + + this.dirty = false; + + // Set parameters + var hasProperty = Object.prototype.hasOwnProperty; + var parameters = this.parameters; + for (var p in parameters) + { + if (hasProperty.call(parameters, p)) + { + var parameter = parameters[p]; + if (parameter.dirty) + { + parameter.dirty = 0; + + var paramInfo = parameter.info; + var location = parameter.location; + if (paramInfo && + null !== location) + { + var parameterValues = paramInfo.values; + var sampler = parameter.sampler; + if (sampler) + { + gd.setTexture(parameter.textureUnit, parameterValues, sampler); + } + else if (1 < paramInfo.numValues) + { + parameter.setter.call(gl, location, parameterValues); + } + else //if (1 === paramInfo.numValues) + { + parameter.setter.call(gl, location, parameterValues[0]); + } + } + } + } + } + }, + + initializeParameters : function passInitializeParametersFn(gd) + { + var gl = gd.gl; + + var glProgram = this.glProgram; + + gd.setProgram(glProgram); + + var passParameters = this.parameters; + for (var p in passParameters) + { + if (passParameters.hasOwnProperty(p)) + { + var parameter = passParameters[p]; + + var paramInfo = parameter.info; + if (paramInfo) + { + var location = gl.getUniformLocation(glProgram, p); + if (null !== location) + { + parameter.location = location; + + if (parameter.sampler) + { + gl.uniform1i(location, parameter.textureUnit); + } + else + { + if (1 < paramInfo.numValues) + { + parameter.setter.call(gl, location, paramInfo.values); + } + else //if (1 === paramInfo.numValues) + { + parameter.setter.call(gl, location, paramInfo.values[0]); + } + } + } + } + } + } + }, + + destroy : function passDestroyFn() + { + delete this.glProgram; + delete this.semanticsMask; + delete this.parameters; + + var states = this.states; + if (states) + { + states.length = 0; + delete this.states; + } + } +}; + +// Constructor function +WebGLPass.create = function webGLPassCreateFn(gd, shader, params) +{ + var gl = gd.gl; + + var pass = new WebGLPass(); + + pass.name = (params.name || null); + + var programs = shader.programs; + var parameters = shader.parameters; + + var parameterNames = params.parameters; + var programNames = params.programs; + var semanticNames = params.semantics; + var states = params.states; + + var compoundProgramName = programNames.join(':'); + var linkedProgram = shader.linkedPrograms[compoundProgramName]; + var glProgram, semanticsMask, p, s; + if (linkedProgram === undefined) + { + // Create GL program + glProgram = gl.createProgram(); + + var numPrograms = programNames.length; + for (p = 0; p < numPrograms; p += 1) + { + var glShader = programs[programNames[p]]; + if (glShader) + { + gl.attachShader(glProgram, glShader); + } + } + + /*jshint bitwise: false*/ + var numSemantics = semanticNames.length; + semanticsMask = 0; + for (s = 0; s < numSemantics; s += 1) + { + var semanticName = semanticNames[s]; + var attribute = gd['SEMANTIC_' + semanticName]; + if (attribute !== undefined) + { + semanticsMask |= (1 << attribute); + gl.bindAttribLocation(glProgram, attribute, ("ATTR" + attribute)); + } + } + /*jshint bitwise: true*/ + + gl.linkProgram(glProgram); + + shader.linkedPrograms[compoundProgramName] = { + glProgram : glProgram, + semanticsMask : semanticsMask + }; + } + else + { + //console.log('Reused program ' + compoundProgramName); + glProgram = linkedProgram.glProgram; + semanticsMask = linkedProgram.semanticsMask; + } + + pass.glProgram = glProgram; + pass.semanticsMask = semanticsMask; + + // Set parameters + var numTextureUnits = 0; + var passParameters = {}; + pass.parameters = passParameters; + var numParameters = parameterNames ? parameterNames.length : 0; + for (p = 0; p < numParameters; p += 1) + { + var parameterName = parameterNames[p]; + + var parameter = {}; + passParameters[parameterName] = parameter; + + var paramInfo = parameters[parameterName]; + parameter.info = paramInfo; + if (paramInfo) + { + parameter.location = null; + if (paramInfo.sampler) + { + parameter.sampler = paramInfo.sampler; + parameter.textureUnit = numTextureUnits; + numTextureUnits += 1; + } + else + { + parameter.sampler = undefined; + parameter.textureUnit = undefined; + } + parameter.setter = paramInfo.setter; + } + } + pass.numTextureUnits = numTextureUnits; + pass.numParameters = numParameters; + + function equalRenderStates(defaultValues, values) + { + var numDefaultValues = defaultValues.length; + var n; + for (n = 0; n < numDefaultValues; n += 1) + { + if (defaultValues[n] !== values[n]) + { + return false; + } + } + return true; + } + + var hasProperty = Object.prototype.hasOwnProperty; + var stateHandlers = gd.stateHandlers; + var passStates = []; + var passStatesSet = {}; + pass.states = passStates; + pass.statesSet = passStatesSet; + for (s in states) + { + if (hasProperty.call(states, s)) + { + var stateHandler = stateHandlers[s]; + if (stateHandler) + { + var values = stateHandler.parse(states[s]); + if (values !== null) + { + if (equalRenderStates(stateHandler.defaultValues, values)) + { + continue; + } + passStates.push({ + name: s, + set: stateHandler.set, + reset: stateHandler.reset, + values: values + }); + passStatesSet[s] = true; + } + else + { + TurbulenzEngine.callOnError('Unknown value for state ' + + s + ': ' + states[s]); + } + } + } + } + + return pass; +}; + + +// +// WebGLTechnique +// +function WebGLTechnique() {} +WebGLTechnique.prototype = +{ + version : 1, + + getPass : function getPassFn(id) + { + var passes = this.passes; + var numPasses = passes.length; + if (typeof id === "string") + { + for (var n = 0; n < numPasses; n += 1) + { + var pass = passes[n]; + if (pass.name === id) + { + return pass; + } + } + } + else + { + /*jshint bitwise: false*/ + id = (id | 0); + /*jshint bitwise: true*/ + if (id < numPasses) + { + return passes[id]; + } + } + return null; + }, + + activate : function activateFn(gd) + { + this.device = gd; + + if (!this.initialized) + { + this.shader.initialize(gd); + this.initialize(gd); + } + }, + + deactivate : function deactivateFn() + { + this.device = null; + }, + + checkProperties : function checkPropertiesFn(gd) + { + // Check for parameters set directly into the technique... + var fakeTechniqueParameters = {}, p; + for (p in this) + { + if (p !== 'version' && + p !== 'name' && + p !== 'passes' && + p !== 'numPasses' && + p !== 'device' && + p !== 'numParameters') + { + fakeTechniqueParameters[p] = this[p]; + } + } + + if (fakeTechniqueParameters) + { + var passes = this.passes; + if (passes.length === 1) + { + gd.setParametersImmediate(gd, passes, fakeTechniqueParameters); + } + else + { + gd.setParametersDeferred(gd, passes, fakeTechniqueParameters); + } + + var hasProperty = Object.prototype.hasOwnProperty; + for (p in fakeTechniqueParameters) + { + if (hasProperty.call(fakeTechniqueParameters, p)) + { + delete this[p]; + } + } + } + }, + + initialize : function techniqueInitializeFn(gd) + { + if (this.initialized) + { + return; + } + + var passes = this.passes; + if (passes) + { + var numPasses = passes.length; + var n; + for (n = 0; n < numPasses; n += 1) + { + passes[n].initializeParameters(gd); + } + } + + if (Object.defineProperty) + { + this.initializeParametersSetters(gd); + } + + this.initialized = true; + }, + + initializeParametersSetters : function initializeParametersSettersFn(gd) + { + var gl = gd.gl; + + function make_sampler_setter(pass, parameter) { + return function (parameterValues) { + if (this.device) + { + gd.setTexture(parameter.textureUnit, parameterValues, parameter.info.sampler); + } + else + { + pass.dirty = true; + parameter.dirty = 1; + parameter.info.values = parameterValues; + } + }; + } + + function make_float_uniform_setter(pass, parameter) { + + var paramInfo = parameter.info; + var location = parameter.location; + + function setDeferredParameter(parameterValues) + { + if (typeof parameterValues !== 'number') + { + var values = paramInfo.values; + var numValues = Math.min(paramInfo.numValues, parameterValues.length); + for (var v = 0; v < numValues; v += 1) + { + values[v] = parameterValues[v]; + } + parameter.dirty = Math.max(numValues, (parameter.dirty || 0)); + } + else + { + paramInfo.values[0] = parameterValues; + parameter.dirty = (parameter.dirty || 1); + } + pass.dirty = true; + } + + switch (paramInfo.columns) + { + case 1: + if (1 === paramInfo.numValues) + { + return function (parameterValues) + { + if (this.device) + { + gl.uniform1f(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + } + return function (parameterValues) + { + if (this.device) + { + gl.uniform1fv(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + case 2: + return function (parameterValues) + { + if (this.device) + { + gl.uniform2fv(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + case 3: + return function (parameterValues) + { + if (this.device) + { + gl.uniform3fv(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + case 4: + return function (parameterValues) + { + if (this.device) + { + gl.uniform4fv(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + default: + return null; + } + } + + function make_int_uniform_setter(pass, parameter) { + var paramInfo = parameter.info; + var location = parameter.location; + + function setDeferredParameter(parameterValues) + { + if (typeof parameterValues !== 'number') + { + var values = paramInfo.values; + var numValues = Math.min(paramInfo.numValues, parameterValues.length); + for (var v = 0; v < numValues; v += 1) + { + values[v] = parameterValues[v]; + } + parameter.dirty = Math.max(numValues, (parameter.dirty || 0)); + } + else + { + paramInfo.values[0] = parameterValues; + parameter.dirty = (parameter.dirty || 1); + } + pass.dirty = true; + } + + switch (paramInfo.columns) + { + case 1: + if (1 === paramInfo.numValues) + { + return function (parameterValues) + { + if (this.device) + { + gl.uniform1i(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + } + return function (parameterValues) + { + if (this.device) + { + gl.uniform1iv(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + case 2: + return function (parameterValues) + { + if (this.device) + { + gl.uniform2iv(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + case 3: + return function (parameterValues) + { + if (this.device) + { + gl.uniform3iv(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + case 4: + return function (parameterValues) + { + if (this.device) + { + gl.uniform4iv(location, parameterValues); + } + else + { + setDeferredParameter(parameterValues); + } + }; + default: + return null; + } + } + + var passes = this.passes; + var numPasses = passes.length; + var pass, parameters, p, parameter, paramInfo, setter; + if (numPasses === 1) + { + pass = passes[0]; + parameters = pass.parameters; + for (p in parameters) + { + if (parameters.hasOwnProperty(p)) + { + parameter = parameters[p]; + paramInfo = parameter.info; + if (paramInfo) + { + if (undefined !== parameter.location) + { + if (parameter.sampler) + { + setter = make_sampler_setter(pass, parameter); + } + else + { + if (paramInfo.type === 'float') + { + setter = make_float_uniform_setter(pass, parameter); + } + else + { + setter = make_int_uniform_setter(pass, parameter); + } + } + + Object.defineProperty(this, p, { + set : setter, + enumerable : false, + configurable : false + }); + } + } + } + } + + this.checkProperties = function () + { + }; + } + else + { + Object.defineProperty(this, 'device', { + writable : true, + enumerable : false, + configurable : false + }); + + Object.defineProperty(this, 'version', { + writable : false, + enumerable : false, + configurable : false + }); + + Object.defineProperty(this, 'name', { + writable : false, + enumerable : false, + configurable : false + }); + + Object.defineProperty(this, 'passes', { + writable : false, + enumerable : false, + configurable : false + }); + + Object.defineProperty(this, 'numParameters', { + writable : false, + enumerable : false, + configurable : false + }); + } + }, + + destroy : function techniqueDestroyFn() + { + var passes = this.passes; + if (passes) + { + var numPasses = passes.length; + var n; + + for (n = 0; n < numPasses; n += 1) + { + passes[n].destroy(); + } + + passes.length = 0; + + delete this.passes; + } + + delete this.device; + } +}; + +// Constructor function +WebGLTechnique.create = function webGLTechniqueCreateFn(gd, shader, name, passes) +{ + var technique = new WebGLTechnique(); + + technique.initialized = false; + technique.shader = shader; + technique.name = name; + + var numPasses = passes.length, n; + var numParameters = 0; + technique.passes = []; + technique.numPasses = numPasses; + for (n = 0; n < numPasses; n += 1) + { + var passParams = passes[n]; + if (passParams.parameters) + { + numParameters += passParams.parameters.length; + } + technique.passes[n] = WebGLPass.create(gd, shader, passParams); + } + + technique.numParameters = numParameters; + + technique.device = null; + + + return technique; +}; + +// +// WebGLShader +// +function WebGLShader() {} +WebGLShader.prototype = +{ + version : 1, + + getTechnique : function getTechniqueFn(name) + { + if (typeof name === "string") + { + return this.techniques[name]; + } + else + { + var techniques = this.techniques; + for (var t in techniques) + { + if (techniques.hasOwnProperty(t)) + { + if (name === 0) + { + return techniques[t]; + } + else + { + name -= 1; + } + } + } + return null; + } + }, + + getParameter : function getParameterFn(name) + { + if (typeof name === "string") + { + return this.parameters[name]; + } + else + { + /*jshint bitwise: false*/ + name = (name | 0); + /*jshint bitwise: true*/ + var parameters = this.parameters; + for (var p in parameters) + { + if (parameters.hasOwnProperty(p)) + { + if (name === 0) + { + return parameters[p]; + } + else + { + name -= 1; + } + } + } + return null; + } + }, + + initialize : function shaderInitializeFn(gd) + { + if (this.initialized) + { + return; + } + + var gl = gd.gl; + var p; + + // Check copmpiled programs as late as possible + var shaderPrograms = this.programs; + for (p in shaderPrograms) + { + if (shaderPrograms.hasOwnProperty(p)) + { + var compiledProgram = shaderPrograms[p]; + var compiled = gl.getShaderParameter(compiledProgram, gl.COMPILE_STATUS); + if (!compiled) + { + var compilerInfo = gl.getShaderInfoLog(compiledProgram); + TurbulenzEngine.callOnError( + 'Program "' + p + '" failed to compile: ' + compilerInfo); + } + } + } + + // Check linked programs as late as possible + var linkedPrograms = this.linkedPrograms; + for (p in linkedPrograms) + { + if (linkedPrograms.hasOwnProperty(p)) + { + var linkedProgram = linkedPrograms[p]; + var glProgram = linkedProgram.glProgram; + if (glProgram) + { + var linked = gl.getProgramParameter(glProgram, gl.LINK_STATUS); + if (!linked) + { + var linkerInfo = gl.getProgramInfoLog(glProgram); + TurbulenzEngine.callOnError( + 'Program "' + p + '" failed to link: ' + linkerInfo); + } + } + } + } + + this.initialized = true; + }, + + destroy : function shaderDestroyFn() + { + var gd = this.gd; + if (gd) + { + var gl = gd.gl; + var p; + + var techniques = this.techniques; + if (techniques) + { + for (p in techniques) + { + if (techniques.hasOwnProperty(p)) + { + techniques[p].destroy(); + } + } + delete this.techniques; + } + + var linkedPrograms = this.linkedPrograms; + if (linkedPrograms) + { + if (gl) + { + for (p in linkedPrograms) + { + if (linkedPrograms.hasOwnProperty(p)) + { + var linkedProgram = linkedPrograms[p]; + var glProgram = linkedProgram.glProgram; + if (glProgram) + { + gl.deleteProgram(glProgram); + delete linkedProgram.glProgram; + } + } + } + } + delete this.linkedPrograms; + } + + var programs = this.programs; + if (programs) + { + if (gl) + { + for (p in programs) + { + if (programs.hasOwnProperty(p)) + { + gl.deleteShader(programs[p]); + } + } + } + delete this.programs; + } + + delete this.samplers; + delete this.parameters; + delete this.gd; + } + } +}; + +// Constructor function +WebGLShader.create = function webGLShaderCreateFn(gd, params) +{ + var gl = gd.gl; + + var shader = new WebGLShader(); + + shader.initialized = false; + + var techniques = params.techniques; + var parameters = params.parameters; + var programs = params.programs; + var samplers = params.samplers; + var p; + + shader.gd = gd; + shader.name = params.name; + + // Compile programs as early as possible + var shaderPrograms = {}; + shader.programs = shaderPrograms; + for (p in programs) + { + if (programs.hasOwnProperty(p)) + { + var program = programs[p]; + + var glShaderType; + if (program.type === 'fragment') + { + glShaderType = gl.FRAGMENT_SHADER; + } + else if (program.type === 'vertex') + { + glShaderType = gl.VERTEX_SHADER; + } + var glShader = gl.createShader(glShaderType); + + gl.shaderSource(glShader, program.code); + + gl.compileShader(glShader); + + shaderPrograms[p] = glShader; + } + } + + var linkedPrograms = {}; + shader.linkedPrograms = linkedPrograms; + + // Samplers + var defaultSampler = gd.DEFAULT_SAMPLER; + var maxAnisotropy = gd.maxAnisotropy; + + shader.samplers = {}; + var sampler; + for (p in samplers) + { + if (samplers.hasOwnProperty(p)) + { + sampler = samplers[p]; + + var samplerMaxAnisotropy = sampler.MaxAnisotropy; + if (samplerMaxAnisotropy) + { + if (samplerMaxAnisotropy > maxAnisotropy) + { + samplerMaxAnisotropy = maxAnisotropy; + } + } + else + { + samplerMaxAnisotropy = defaultSampler.maxAnisotropy; + } + + sampler = { + minFilter : (sampler.MinFilter || defaultSampler.minFilter), + magFilter : (sampler.MagFilter || defaultSampler.magFilter), + wrapS : (sampler.WrapS || defaultSampler.wrapS), + wrapT : (sampler.WrapT || defaultSampler.wrapT), + wrapR : (sampler.WrapR || defaultSampler.wrapR), + maxAnisotropy : samplerMaxAnisotropy + }; + if (sampler.wrapS === 0x2900) + { + sampler.wrapS = gl.CLAMP_TO_EDGE; + } + if (sampler.wrapT === 0x2900) + { + sampler.wrapT = gl.CLAMP_TO_EDGE; + } + if (sampler.wrapR === 0x2900) + { + sampler.wrapR = gl.CLAMP_TO_EDGE; + } + shader.samplers[p] = gd.createSampler(sampler); + } + } + + // Parameters + var numParameters = 0; + shader.parameters = {}; + for (p in parameters) + { + if (parameters.hasOwnProperty(p)) + { + var parameter = parameters[p]; + if (!parameter.columns) + { + parameter.columns = 1; + } + if (!parameter.rows) + { + parameter.rows = 1; + } + parameter.numValues = (parameter.columns * parameter.rows); + var parameterType = parameter.type; + if (parameterType === "float" || + parameterType === "int" || + parameterType === "bool") + { + var parameterValues = parameter.values; + if (parameterValues) + { + if (parameterType === "float") + { + parameter.values = new Float32Array(parameterValues); + } + else + { + parameter.values = new Int32Array(parameterValues); + } + } + else + { + if (parameterType === "float") + { + parameter.values = new Float32Array(parameter.numValues); + } + else + { + parameter.values = new Int32Array(parameter.numValues); + } + } + + if (parameterType === 'float') + { + switch (parameter.columns) + { + case 1: + if (1 === parameter.numValues) + { + parameter.setter = gl.uniform1f; + } + else + { + parameter.setter = gl.uniform1fv; + } + break; + case 2: + parameter.setter = gl.uniform2fv; + break; + case 3: + parameter.setter = gl.uniform3fv; + break; + case 4: + parameter.setter = gl.uniform4fv; + break; + default: + break; + } + } + else + { + switch (parameter.columns) + { + case 1: + if (1 === parameter.numValues) + { + parameter.setter = gl.uniform1i; + } + else + { + parameter.setter = gl.uniform1iv; + } + break; + case 2: + parameter.setter = gl.uniform2iv; + break; + case 3: + parameter.setter = gl.uniform3iv; + break; + case 4: + parameter.setter = gl.uniform4iv; + break; + default: + break; + } + } + } + else // Sampler + { + sampler = shader.samplers[p]; + if (!sampler) + { + sampler = defaultSampler; + shader.samplers[p] = defaultSampler; + } + parameter.sampler = sampler; + parameter.values = null; + } + + parameter.name = p; + + shader.parameters[p] = parameter; + numParameters += 1; + } + } + shader.numParameters = numParameters; + + // Techniques and passes + var shaderTechniques = {}; + var numTechniques = 0; + shader.techniques = shaderTechniques; + for (p in techniques) + { + if (techniques.hasOwnProperty(p)) + { + shaderTechniques[p] = WebGLTechnique.create(gd, shader, p, techniques[p]); + numTechniques += 1; + } + } + shader.numTechniques = numTechniques; + + return shader; +}; + +// +// WebGLTechniqueParameters +// +function WebGLTechniqueParameters() {} + +// Constructor function +WebGLTechniqueParameters.create = function WebGLTechniqueParametersFn(params) +{ + var techniqueParameters = new WebGLTechniqueParameters(); + + if (params) + { + for (var p in params) + { + if (params.hasOwnProperty(p)) + { + techniqueParameters[p] = params[p]; + } + } + } + + return techniqueParameters; +}; + +// +// WebGLTechniqueParameterBuffer +// +function techniqueParameterBufferSetData(data, offset, numValues) +{ + for (var n = 0, o = offset; n < numValues; n += 1, o += 1) + { + this[o] = data[n]; + } + return o; +} + +function techniqueParameterBufferCreate(params) +{ + if (Float32Array.prototype.map === undefined) + { + Float32Array.prototype.map = function techniqueParameterBufferMap(offset, numFloats) { + if (offset === undefined) + { + offset = 0; + } + var buffer = this; + if (numFloats === undefined) + { + numFloats = this.length; + } + function techniqueParameterBufferWriter() + { + var numArguments = arguments.length; + for (var a = 0; a < numArguments; a += 1) + { + var value = arguments[a]; + if (typeof value === 'number') + { + buffer[offset] = value; + offset += 1; + } + else + { + offset = techniqueParameterBufferSetData.call(buffer, value, offset, value.length); + } + } + } + return techniqueParameterBufferWriter; + }; + + Float32Array.prototype.unmap = function techniqueParameterBufferUnmap(/* writer */) { + }; + } + + return new Float32Array(params.numFloats); +} + + +// +// WebGLDrawParameters +// +function WebGLDrawParameters() +{ + // Streams, TechniqueParameters and Instances are stored as indexed properties + this.endStreams = 0; + this.endTechniqueParameters = (16 * 3); + this.endInstances = ((16 * 3) + 8); + this.firstIndex = 0; + this.count = 0; + this.sortKey = 0; + this.technique = null; + this.indexBuffer = null; + this.primitive = -1; + this.userData = null; + + // Initialize for 1 Stream, 2 TechniqueParameters and 8 Instances + this[0] = undefined; + this[1] = undefined; + this[2] = undefined; + + this[(16 * 3) + 0] = undefined; + this[(16 * 3) + 1] = undefined; + + this[((16 * 3) + 8) + 0] = undefined; + this[((16 * 3) + 8) + 1] = undefined; + this[((16 * 3) + 8) + 2] = undefined; + this[((16 * 3) + 8) + 3] = undefined; + this[((16 * 3) + 8) + 4] = undefined; + this[((16 * 3) + 8) + 5] = undefined; + this[((16 * 3) + 8) + 6] = undefined; + this[((16 * 3) + 8) + 7] = undefined; +} + +WebGLDrawParameters.prototype = +{ + version : 1, + + setTechniqueParameters : function setTechniqueParametersFn(indx, techniqueParameters) + { + if (indx < 8) + { + indx += (16 * 3); + + this[indx] = techniqueParameters; + + var endTechniqueParameters = this.endTechniqueParameters; + if (techniqueParameters) + { + if (endTechniqueParameters <= indx) + { + this.endTechniqueParameters = (indx + 1); + } + } + else + { + while ((16 * 3) < endTechniqueParameters && + !this[endTechniqueParameters - 1]) + { + endTechniqueParameters -= 1; + } + this.endTechniqueParameters = endTechniqueParameters; + } + } + }, + + setVertexBuffer : function setVertexBufferFn(indx, vertexBuffer) + { + if (indx < 16) + { + indx *= 3; + + this[indx] = vertexBuffer; + + var endStreams = this.endStreams; + if (vertexBuffer) + { + if (endStreams <= indx) + { + this.endStreams = (indx + 3); + } + } + else + { + while (0 < endStreams && + !this[endStreams - 3]) + { + endStreams -= 3; + } + this.endStreams = endStreams; + } + } + }, + + setSemantics : function setSemanticsFn(indx, semantics) + { + if (indx < 16) + { + this[(indx * 3) + 1] = semantics; + } + }, + + setOffset : function setOffsetFn(indx, offset) + { + if (indx < 16) + { + this[(indx * 3) + 2] = offset; + } + }, + + getTechniqueParameters : function getTechniqueParametersFn(indx) + { + if (indx < 8) + { + return this[indx + (16 * 3)]; + } + else + { + return undefined; + } + }, + + getVertexBuffer : function getVertexBufferFn(indx) + { + if (indx < 16) + { + return this[(indx * 3) + 0]; + } + else + { + return undefined; + } + }, + + getSemantics : function getSemanticsFn(indx) + { + if (indx < 16) + { + return this[(indx * 3) + 1]; + } + else + { + return undefined; + } + }, + + getOffset : function getOffsetFn(indx) + { + if (indx < 16) + { + return this[(indx * 3) + 2]; + } + else + { + return undefined; + } + }, + + addInstance : function drawParametersAddInstanceFn(instanceParameters) + { + if (instanceParameters) + { + var endInstances = this.endInstances; + this.endInstances = (endInstances + 1); + this[endInstances] = instanceParameters; + } + }, + + removeInstances : function drawParametersRemoveInstancesFn() + { + this.endInstances = ((16 * 3) + 8); + }, + + getNumInstances : function drawParametersGetNumInstancesFn() + { + return (this.endInstances - ((16 * 3) + 8)); + } +}; + +// Constructor function +WebGLDrawParameters.create = function webGLDrawParametersFn(/* params */) +{ + return new WebGLDrawParameters(); +}; + + +// +// WebGLGraphicsDevice +// +function WebGLGraphicsDevice() {} +WebGLGraphicsDevice.prototype = +{ + version : 1, + + SEMANTIC_POSITION: 0, + SEMANTIC_POSITION0: 0, + SEMANTIC_BLENDWEIGHT: 1, + SEMANTIC_BLENDWEIGHT0: 1, + SEMANTIC_NORMAL: 2, + SEMANTIC_NORMAL0: 2, + SEMANTIC_COLOR: 3, + SEMANTIC_COLOR0: 3, + SEMANTIC_COLOR1: 4, + SEMANTIC_SPECULAR: 4, + SEMANTIC_FOGCOORD: 5, + SEMANTIC_TESSFACTOR: 5, + SEMANTIC_PSIZE0: 6, + SEMANTIC_BLENDINDICES: 7, + SEMANTIC_BLENDINDICES0: 7, + SEMANTIC_TEXCOORD: 8, + SEMANTIC_TEXCOORD0: 8, + SEMANTIC_TEXCOORD1: 9, + SEMANTIC_TEXCOORD2: 10, + SEMANTIC_TEXCOORD3: 11, + SEMANTIC_TEXCOORD4: 12, + SEMANTIC_TEXCOORD5: 13, + SEMANTIC_TEXCOORD6: 14, + SEMANTIC_TEXCOORD7: 15, + SEMANTIC_TANGENT: 14, + SEMANTIC_TANGENT0: 14, + SEMANTIC_BINORMAL0: 15, + SEMANTIC_BINORMAL: 15, + SEMANTIC_PSIZE: 6, + SEMANTIC_ATTR0: 0, + SEMANTIC_ATTR1: 1, + SEMANTIC_ATTR2: 2, + SEMANTIC_ATTR3: 3, + SEMANTIC_ATTR4: 4, + SEMANTIC_ATTR5: 5, + SEMANTIC_ATTR6: 6, + SEMANTIC_ATTR7: 7, + SEMANTIC_ATTR8: 8, + SEMANTIC_ATTR9: 9, + SEMANTIC_ATTR10: 10, + SEMANTIC_ATTR11: 11, + SEMANTIC_ATTR12: 12, + SEMANTIC_ATTR13: 13, + SEMANTIC_ATTR14: 14, + SEMANTIC_ATTR15: 15, + + PIXELFORMAT_A8: 0, + PIXELFORMAT_L8: 1, + PIXELFORMAT_L8A8: 2, + PIXELFORMAT_R5G5B5A1: 3, + PIXELFORMAT_R5G6B5: 4, + PIXELFORMAT_R8G8B8A8: 5, + PIXELFORMAT_R8G8B8: 6, + PIXELFORMAT_D24S8: 7, + PIXELFORMAT_DXT1: 8, + PIXELFORMAT_DXT3: 9, + PIXELFORMAT_DXT5: 10, + + drawIndexed : function drawIndexedFn(primitive, numIndices, first) + { + var gl = this.gl; + var indexBuffer = this.activeIndexBuffer; + + var offset; + if (first) + { + offset = (first * indexBuffer.stride); + } + else + { + offset = 0; + } + + var format = indexBuffer.format; + + var attributeMask = this.attributeMask; + + var activeTechnique = this.activeTechnique; + var passes = activeTechnique.passes; + var numPasses = passes.length; + var mask; + + activeTechnique.checkProperties(this); + + /*jshint bitwise: false*/ + if (1 === numPasses) + { + mask = (passes[0].semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + this.enableClientState(mask); + } + + gl.drawElements(primitive, numIndices, format, offset); + } + else + { + for (var p = 0; p < numPasses; p += 1) + { + var pass = passes[p]; + + mask = (pass.semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + this.enableClientState(mask); + } + + this.setPass(pass); + + gl.drawElements(primitive, numIndices, format, offset); + } + } + /*jshint bitwise: true*/ + }, + + draw : function drawFn(primitive, numVertices, first) + { + var gl = this.gl; + + var attributeMask = this.attributeMask; + + var activeTechnique = this.activeTechnique; + var passes = activeTechnique.passes; + var numPasses = passes.length; + var mask; + + activeTechnique.checkProperties(this); + + /*jshint bitwise: false*/ + if (1 === numPasses) + { + mask = (passes[0].semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + this.enableClientState(mask); + } + + gl.drawArrays(primitive, first, numVertices); + } + else + { + for (var p = 0; p < numPasses; p += 1) + { + var pass = passes[p]; + + mask = (pass.semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + this.enableClientState(mask); + } + + this.setPass(pass); + + gl.drawArrays(primitive, first, numVertices); + } + } + /*jshint bitwise: true*/ + }, + + setTechniqueParameters : function setTechniqueParametersFn() + { + var activeTechnique = this.activeTechnique; + var passes = activeTechnique.passes; + var setParameters = (1 === passes.length ? this.setParametersImmediate : this.setParametersDeferred); + var numTechniqueParameters = arguments.length; + for (var t = 0; t < numTechniqueParameters; t += 1) + { + setParameters(this, passes, arguments[t]); + } + }, + + //Internal + + setParametersImmediate : function setParametersImmediateFn(gd, passes, techniqueParameters) + { + var gl = gd.gl; + + var parameters = passes[0].parameters; + /*jshint forin: true*/ + for (var p in techniqueParameters) + { + var parameter = parameters[p]; + if (parameter !== undefined) + { + var sampler = parameter.sampler; + var parameterValues = techniqueParameters[p]; + if (parameterValues !== undefined) + { + if (sampler !== undefined) + { + gd.setTexture(parameter.textureUnit, parameterValues, sampler); + } + else + { + parameter.setter.call(gl, parameter.location, parameterValues); + } + } + else + { + delete techniqueParameters[p]; + if (sampler) + { + gd.setTexture(parameter.textureUnit); + } + } + } + } + /*jshint forin: false*/ + }, + + // ONLY USE FOR SINGLE PASS TECHNIQUES ON DRAWARRAY + setParametersCaching : function setParametersCachingFn(gd, passes, techniqueParameters) + { + var gl = gd.gl; + + var parameters = passes[0].parameters; + /*jshint forin: true*/ + for (var p in techniqueParameters) + { + var parameter = parameters[p]; + if (parameter !== undefined) + { + var parameterValues = techniqueParameters[p]; + if (parameter.value !== parameterValues) + { + parameter.value = parameterValues; + + var sampler = parameter.sampler; + if (parameterValues !== undefined) + { + if (sampler !== undefined) + { + gd.setTexture(parameter.textureUnit, parameterValues, sampler); + } + else + { + parameter.setter.call(gl, parameter.location, parameterValues); + } + } + else + { + delete techniqueParameters[p]; + if (sampler) + { + gd.setTexture(parameter.textureUnit); + } + } + } + } + } + /*jshint forin: false*/ + }, + + setParametersDeferred : function setParametersDeferredFn(gd, passes, techniqueParameters) + { + var numPasses = passes.length; + var min = Math.min; + var max = Math.max; + for (var n = 0; n < numPasses; n += 1) + { + var pass = passes[n]; + var parameters = pass.parameters; + pass.dirty = true; + + /*jshint forin: true*/ + for (var p in techniqueParameters) + { + var parameter = parameters[p]; + if (parameter) + { + var paramInfo = parameter.info; + var parameterValues = techniqueParameters[p]; + if (parameterValues !== undefined) + { + if (parameter.sampler) + { + paramInfo.values = parameterValues; + parameter.dirty = 1; + } + else if (typeof parameterValues !== 'number') + { + var values = paramInfo.values; + var numValues = min(paramInfo.numValues, parameterValues.length); + for (var v = 0; v < numValues; v += 1) + { + values[v] = parameterValues[v]; + } + parameter.dirty = max(numValues, (parameter.dirty || 0)); + } + else + { + paramInfo.values[0] = parameterValues; + parameter.dirty = (parameter.dirty || 1); + } + } + else + { + delete techniqueParameters[p]; + } + } + } + /*jshint forin: false*/ + } + }, + + setTechnique : function setTechniqueFn(technique) + { + var activeTechnique = this.activeTechnique; + if (activeTechnique !== technique) + { + if (activeTechnique) + { + activeTechnique.deactivate(); + } + + this.activeTechnique = technique; + + technique.activate(this); + + var passes = technique.passes; + if (1 === passes.length) + { + this.setPass(passes[0]); + } + } + }, + + // ONLY USE FOR SINGLE PASS TECHNIQUES ON DRAWARRAY + setTechniqueCaching : function setTechniqueCachingFn(technique) + { + var pass = technique.passes[0]; + + var activeTechnique = this.activeTechnique; + if (activeTechnique !== technique) + { + if (activeTechnique) + { + activeTechnique.deactivate(); + } + + this.activeTechnique = technique; + + technique.activate(this); + + this.setPass(pass); + } + + var parameters = pass.parameters; + for (var p in parameters) + { + if (parameters.hasOwnProperty(p)) + { + parameters[p].value = null; + } + } + }, + + setStream : function setStreamFn(vertexBuffer, semantics, offset) + { + if (offset) + { + offset *= vertexBuffer.strideInBytes; + } + else + { + offset = 0; + } + + this.bindVertexBuffer(vertexBuffer.glBuffer); + + var attributes = semantics; + var numAttributes = attributes.length; + if (numAttributes > vertexBuffer.numAttributes) + { + numAttributes = vertexBuffer.numAttributes; + } + + /*jshint bitwise: false*/ + this.attributeMask |= vertexBuffer.bindAttributes(numAttributes, attributes, offset); + /*jshint bitwise: true*/ + }, + + setIndexBuffer : function setIndexBufferFn(indexBuffer) + { + if (this.activeIndexBuffer !== indexBuffer) + { + this.activeIndexBuffer = indexBuffer; + var glBuffer; + if (indexBuffer) + { + glBuffer = indexBuffer.glBuffer; + } + else + { + glBuffer = null; + } + var gl = this.gl; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffer); + } + }, + + drawArray : function drawArrayFn(drawParametersArray, globalTechniqueParametersArray, sortMode) + { + var gl = this.gl; + var ELEMENT_ARRAY_BUFFER = gl.ELEMENT_ARRAY_BUFFER; + + var setParametersCaching = this.setParametersCaching; + var setParametersDeferred = this.setParametersDeferred; + + var setStream = this.setStream; + var enableClientState = this.enableClientState; + + var numGlobalTechniqueParameters = globalTechniqueParametersArray.length; + + var numDrawParameters = drawParametersArray.length; + if (numDrawParameters > 1 && sortMode) + { + if (sortMode > 0) + { + drawParametersArray.sort(function drawArraySortPositive(a, b) { + return (b.sortKey - a.sortKey); + }); + } + else if (sortMode < 0) + { + drawParametersArray.sort(function drawArraySortNegative(a, b) { + return (a.sortKey - b.sortKey); + }); + } + } + + + var activeIndexBuffer = this.activeIndexBuffer; + var setParameters = null; + var lastTechnique = null; + var lastEndStreams = -1; + var lastDrawParameters = null; + var techniqueParameters = null; + var v = 0; + var streamsMatch = false; + var vertexBuffer = null; + var offset = 0; + var passes = null; + var p = null; + var pass = null; + var format = 0; + var numPasses = 0; + var mask = 0; + var attributeMask = 0; + var t = 0; + + for (var n = 0; n < numDrawParameters; n += 1) + { + var drawParameters = drawParametersArray[n]; + var technique = drawParameters.technique; + var endTechniqueParameters = drawParameters.endTechniqueParameters; + var endStreams = drawParameters.endStreams; + var endInstances = drawParameters.endInstances; + var indexBuffer = drawParameters.indexBuffer; + var primitive = drawParameters.primitive; + var count = drawParameters.count; + var firstIndex = drawParameters.firstIndex; + + if (lastTechnique !== technique) + { + lastTechnique = technique; + + passes = technique.passes; + numPasses = passes.length; + if (1 === numPasses) + { + this.setTechniqueCaching(technique); + setParameters = setParametersCaching; + } + else + { + this.setTechnique(technique); + setParameters = setParametersDeferred; + } + + technique.checkProperties(this); + + for (t = 0; t < numGlobalTechniqueParameters; t += 1) + { + setParameters(this, passes, globalTechniqueParametersArray[t]); + } + } + + for (t = (16 * 3); t < endTechniqueParameters; t += 1) + { + techniqueParameters = drawParameters[t]; + if (techniqueParameters) + { + setParameters(this, passes, techniqueParameters); + } + } + + streamsMatch = (lastEndStreams === endStreams); + for (v = 0; streamsMatch && v < endStreams; v += 3) + { + streamsMatch = (lastDrawParameters[v] === drawParameters[v] && + lastDrawParameters[v + 1] === drawParameters[v + 1] && + lastDrawParameters[v + 2] === drawParameters[v + 2]); + } + + if (!streamsMatch) + { + lastEndStreams = endStreams; + lastDrawParameters = drawParameters; + + for (v = 0; v < endStreams; v += 3) + { + vertexBuffer = drawParameters[v]; + if (vertexBuffer) + { + setStream.call(this, vertexBuffer, drawParameters[v + 1], drawParameters[v + 2]); + } + } + + attributeMask = this.attributeMask; + } + + /*jshint bitwise: false*/ + if (indexBuffer) + { + if (activeIndexBuffer !== indexBuffer) + { + activeIndexBuffer = indexBuffer; + gl.bindBuffer(ELEMENT_ARRAY_BUFFER, indexBuffer.glBuffer); + } + + offset = firstIndex; + if (offset) + { + offset *= indexBuffer.stride; + } + + format = indexBuffer.format; + + if (1 === numPasses) + { + mask = (passes[0].semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + enableClientState.call(this, mask); + } + + t = ((16 * 3) + 8); + if (t < endInstances) + { + do + { + setParameters(this, passes, drawParameters[t]); + + gl.drawElements(primitive, count, format, offset); + + t += 1; + } + while (t < endInstances); + } + else + { + gl.drawElements(primitive, count, format, offset); + } + } + else + { + t = ((16 * 3) + 8); + if (t < endInstances) + { + do + { + setParameters(this, passes, drawParameters[t]); + + for (p = 0; p < numPasses; p += 1) + { + pass = passes[p]; + + mask = (pass.semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + enableClientState.call(this, mask); + } + + this.setPass(pass); + + gl.drawElements(primitive, count, format, offset); + } + + t += 1; + } + while (t < endInstances); + } + else + { + for (p = 0; p < numPasses; p += 1) + { + pass = passes[p]; + + mask = (pass.semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + enableClientState.call(this, mask); + } + + this.setPass(pass); + + gl.drawElements(primitive, count, format, offset); + } + } + } + } + else + { + if (1 === numPasses) + { + mask = (passes[0].semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + enableClientState.call(this, mask); + } + + t = ((16 * 3) + 8); + if (t < endInstances) + { + do + { + setParameters(this, passes, drawParameters[t]); + + gl.drawArrays(primitive, firstIndex, count); + + t += 1; + } + while (t < endInstances); + } + else + { + gl.drawArrays(primitive, firstIndex, count); + } + } + else + { + t = ((16 * 3) + 8); + if (t < endInstances) + { + do + { + setParameters(this, passes, drawParameters[t]); + + for (p = 0; p < numPasses; p += 1) + { + pass = passes[p]; + + mask = (pass.semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + enableClientState.call(this, mask); + } + + this.setPass(pass); + + gl.drawArrays(primitive, firstIndex, count); + } + + t += 1; + } + while (t < endInstances); + } + else + { + for (p = 0; p < numPasses; p += 1) + { + pass = passes[p]; + + mask = (pass.semanticsMask & attributeMask); + if (mask !== this.clientStateMask) + { + enableClientState.call(this, mask); + } + + this.setPass(pass); + + gl.drawArrays(primitive, firstIndex, count); + } + } + } + } + /*jshint bitwise: true*/ + } + + this.activeIndexBuffer = activeIndexBuffer; + }, + + beginDraw : function beginDrawFn(primitive, numVertices, formats, semantics) + { + this.immediatePrimitive = primitive; + if (numVertices) + { + var n; + var immediateSemantics = this.immediateSemantics; + var attributes = semantics; + var numAttributes = attributes.length; + immediateSemantics.length = numAttributes; + for (n = 0; n < numAttributes; n += 1) + { + var attribute = attributes[n]; + if (typeof attribute === "string") + { + attribute = this['SEMANTIC_' + attribute]; + } + immediateSemantics[n] = attribute; + } + + var immediateVertexBuffer = this.immediateVertexBuffer; + + var oldStride = immediateVertexBuffer.strideInBytes; + var oldSize = (oldStride * immediateVertexBuffer.numVertices); + + var stride = immediateVertexBuffer.setAttributes(formats); + if (stride !== oldStride) + { + immediateVertexBuffer.numVertices = Math.floor(oldSize / stride); + } + + var size = (stride * numVertices); + if (size > oldSize) + { + immediateVertexBuffer.resize(size); + } + + return immediateVertexBuffer.map(0, numVertices); + } + return null; + }, + + endDraw : function endDrawFn(writer) + { + var immediateVertexBuffer = this.immediateVertexBuffer; + + var numVerticesWritten = writer.getNumWrittenVertices(); + + immediateVertexBuffer.unmap(writer); + + if (numVerticesWritten) + { + var gl = this.gl; + + var stride = immediateVertexBuffer.strideInBytes; + var offset = 0; + + /*jshint bitwise: false*/ + var vertexAttributes = immediateVertexBuffer.attributes; + + var semantics = this.immediateSemantics; + var numSemantics = semantics.length; + var deltaAttributeMask = 0; + for (var n = 0; n < numSemantics; n += 1) + { + var vertexAttribute = vertexAttributes[n]; + + var attribute = semantics[n]; + + deltaAttributeMask |= (1 << attribute); + + gl.vertexAttribPointer(attribute, + vertexAttribute.numComponents, + vertexAttribute.format, + vertexAttribute.normalized, + stride, + offset); + + offset += vertexAttribute.stride; + } + this.attributeMask |= deltaAttributeMask; + /*jshint bitwise: true*/ + + this.draw(this.immediatePrimitive, numVerticesWritten, 0); + } + }, + + setViewport : function setViewportFn(x, y, w, h) + { + var currentBox = this.state.viewportBox; + if (currentBox[0] !== x || + currentBox[1] !== y || + currentBox[2] !== w || + currentBox[3] !== h) + { + currentBox[0] = x; + currentBox[1] = y; + currentBox[2] = w; + currentBox[3] = h; + this.gl.viewport(x, y, w, h); + } + }, + + setScissor : function setScissorFn(x, y, w, h) + { + var currentBox = this.state.scissorBox; + if (currentBox[0] !== x || + currentBox[1] !== y || + currentBox[2] !== w || + currentBox[3] !== h) + { + currentBox[0] = x; + currentBox[1] = y; + currentBox[2] = w; + currentBox[3] = h; + this.gl.scissor(x, y, w, h); + } + }, + + clear : function clearFn(color, depth, stencil) + { + var gl = this.gl; + var state = this.state; + + var clearMask = 0; + + if (color) + { + clearMask += gl.COLOR_BUFFER_BIT; + + var currentColor = state.clearColor; + var color0 = color[0]; + var color1 = color[1]; + var color2 = color[2]; + var color3 = color[3]; + if (currentColor[0] !== color0 || + currentColor[1] !== color1 || + currentColor[2] !== color2 || + currentColor[3] !== color3) + { + currentColor[0] = color0; + currentColor[1] = color1; + currentColor[2] = color2; + currentColor[3] = color3; + gl.clearColor(color0, color1, color2, color3); + } + } + + if (depth !== undefined) + { + clearMask += gl.DEPTH_BUFFER_BIT; + + if (state.clearDepth !== depth) + { + state.clearDepth = depth; + gl.clearDepth(depth); + } + + if (stencil !== undefined) + { + clearMask += gl.STENCIL_BUFFER_BIT; + + if (state.clearStencil !== stencil) + { + state.clearStencil = stencil; + gl.clearStencil(stencil); + } + } + } + + if (clearMask) + { + var colorMask = state.colorMask; + var colorMaskEnabled = (colorMask[0] || colorMask[1] || colorMask[2] || colorMask[3]); + var depthMask = state.depthMask; + var program = state.program; + + if (color) + { + if (!colorMaskEnabled) + { + // This is posibly a mistake, enable it for this call + gl.colorMask(true, true, true, true); + } + } + + if (depth !== undefined) + { + if (!depthMask) + { + // This is posibly a mistake, enable it for this call + gl.depthMask(true); + } + } + + if (program) + { + gl.useProgram(null); // Work around for Mac crash bug. + } + + gl.clear(clearMask); + + if (color) + { + if (!colorMaskEnabled) + { + gl.colorMask(false, false, false, false); + } + } + + if (depth !== undefined) + { + if (!depthMask) + { + gl.depthMask(false); + } + } + + if (program) + { + gl.useProgram(program); + } + } + }, + + beginFrame : function beginFrameFn() + { + var gl = this.gl; + + this.attributeMask = 0; + + /*jshint bitwise: false*/ + var clientStateMask = this.clientStateMask; + var n; + if (clientStateMask) + { + for (n = 0; n < 16; n += 1) + { + if (clientStateMask & (1 << n)) + { + gl.disableVertexAttribArray(n); + } + } + this.clientStateMask = 0; + } + /*jshint bitwise: true*/ + + this.resetStates(); + + this.setScissor(0, 0, this.width, this.height); + this.setViewport(0, 0, this.width, this.height); + + return true; + }, + + beginRenderTarget : function beginRenderTargetFn(renderTarget) + { + this.activeRenderTarget = renderTarget; + return renderTarget.bind(); + }, + + endRenderTarget : function endRenderTargetFn() + { + this.activeRenderTarget.unbind(); + this.activeRenderTarget = null; + }, + + beginOcclusionQuery : function beginOcclusionQueryFn() + { + return false; + }, + + endOcclusionQuery : function endOcclusionQueryFn() + { + }, + + endFrame : function endFrameFn() + { + var gl = this.gl; + //gl.flush(); + + if (this.activeTechnique) + { + this.activeTechnique.deactivate(); + this.activeTechnique = null; + } + + if (this.activeIndexBuffer) + { + this.setIndexBuffer(null); + } + + var state = this.state; + if (state.program) + { + state.program = null; + gl.useProgram(null); + } + + this.numFrames += 1; + var currentFrameTime = TurbulenzEngine.getTime(); + var diffTime = (currentFrameTime - this.previousFrameTime); + if (diffTime >= 1000.0) + { + this.fps = (this.numFrames / (diffTime * 0.001)); + this.numFrames = 0; + this.previousFrameTime = currentFrameTime; + } + + var canvas = gl.canvas; + var width = (gl.drawingBufferWidth || canvas.width); + var height = (gl.drawingBufferHeight || canvas.height); + if (this.width !== width || + this.height !== height) + { + this.width = width; + this.height = height; + this.setViewport(0, 0, width, height); + this.setScissor(0, 0, width, height); + } + + this.checkFullScreen(); + }, + + createTechniqueParameters : function createTechniqueParametersFn(params) + { + return WebGLTechniqueParameters.create(params); + }, + + createSemantics : function createSemanticsFn(attributes) + { + return WebGLSemantics.create(this, attributes); + }, + + createVertexBuffer : function createVertexBufferFn(params) + { + return WebGLVertexBuffer.create(this, params); + }, + + createIndexBuffer : function createIndexBufferFn(params) + { + return WebGLIndexBuffer.create(this, params); + }, + + createTexture : function createTextureFn(params) + { + return WebGLTexture.create(this, params); + }, + + createShader : function createShaderFn(params) + { + return WebGLShader.create(this, params); + }, + + createTechniqueParameterBuffer : function createTechniqueParameterBufferFn(params) + { + return techniqueParameterBufferCreate(params); + }, + + createRenderBuffer : function createRenderBufferFn(params) + { + return WebGLRenderBuffer.create(this, params); + }, + + createRenderTarget : function createRenderTargetFn(params) + { + return WebGLRenderTarget.create(this, params); + }, + + createOcclusionQuery : function createOcclusionQueryFn(/* params */) + { + return null; + }, + + createDrawParameters : function createDrawParametersFn(params) + { + return WebGLDrawParameters.create(params); + }, + + isSupported : function isSupportedFn(name) + { + var gl = this.gl; + if ("OCCLUSION_QUERIES" === name) + { + return false; + } + else if ("NPOT_MIPMAPPED_TEXTURES" === name) + { + return false; + } + else if ("TEXTURE_DXT1" === name || + "TEXTURE_DXT3" === name || + "TEXTURE_DXT5" === name) + { + var compressedTexturesExtension = this.compressedTexturesExtension; + if (compressedTexturesExtension) + { + var compressedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS); + if (compressedFormats) + { + var requestedFormat; + if ("TEXTURE_DXT1" === name) + { + requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + } + else if ("TEXTURE_DXT3" === name) + { + requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + } + else //if ("TEXTURE_DXT5" === name) + { + requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + var numCompressedFormats = compressedFormats.length; + for (var n = 0; n < numCompressedFormats; n += 1) + { + if (compressedFormats[n] === requestedFormat) + { + return true; + } + } + } + } + return false; + } + else if ("TEXTURE_ETC1" === name) + { + return false; + } + else if ("INDEXFORMAT_UINT" === name) + { + if (gl.getExtension('OES_element_index_uint')) + { + return true; + } + return false; + } + return undefined; + }, + + maxSupported : function maxSupportedFn(name) + { + var gl = this.gl; + if ("ANISOTROPY" === name) + { + return this.maxAnisotropy; + } + else if ("TEXTURE_SIZE" === name) + { + return gl.getParameter(gl.MAX_TEXTURE_SIZE); + } + else if ("CUBEMAP_TEXTURE_SIZE" === name) + { + return gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); + } + else if ("3D_TEXTURE_SIZE" === name) + { + return 0; + } + else if ("RENDERTARGET_COLOR_TEXTURES" === name) + { + return 1; + } + else if ("RENDERBUFFER_SIZE" === name) + { + return gl.getParameter(gl.MAX_RENDERBUFFER_SIZE); + } + return 0; + }, + + loadTexturesArchive : function loadTexturesArchiveFn(params) + { + var src = params.src; + if (typeof TARLoader !== 'undefined') + { + TARLoader.create({ + gd: this, + src : src, + mipmaps : params.mipmaps, + ontextureload : function tarTextureLoadedFn(texture) + { + params.ontextureload(texture); + }, + onload : function tarLoadedFn(success, status) + { + if (params.onload) + { + params.onload(true, status); + } + }, + onerror : function tarFailedFn() + { + if (params.onload) + { + params.onload(false, status); + } + } + }); + return true; + } + else + { + TurbulenzEngine.callOnError( + 'Missing archive loader required for ' + src); + return false; + } + }, + + getScreenshot : function getScreenshotFn(compress, x, y, width, height) + { + var gl = this.gl; + var canvas = gl.canvas; + + if (compress) + { + return canvas.toDataURL('image/jpeg'); + } + else + { + if (x === undefined) + { + x = 0; + } + + if (y === undefined) + { + y = 0; + } + + var target = this.activeRenderTarget; + if (!target) + { + target = canvas; + } + + if (width === undefined) + { + width = target.width; + } + + if (height === undefined) + { + height = target.height; + } + + var pixels = new Uint8Array(4 * width * height); + + gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); + + return pixels; + } + }, + + // private + checkFullScreen : function checkFullScreenFn() + { + var fullscreen = this.fullscreen; + if (this.oldFullscreen !== fullscreen) + { + this.oldFullscreen = fullscreen; + + this.requestFullScreen(fullscreen); + } + }, + + requestFullScreen : function requestFullScreenFn(fullscreen) + { + if (fullscreen) + { + var canvas = this.gl.canvas; + if (canvas.webkitRequestFullScreenWithKeys) + { + canvas.webkitRequestFullScreenWithKeys(); + } + else if (canvas.requestFullScreenWithKeys) + { + canvas.requestFullScreenWithKeys(); + } + else if (canvas.webkitRequestFullScreen) + { + canvas.webkitRequestFullScreen(canvas.ALLOW_KEYBOARD_INPUT); + } + else if (canvas.mozRequestFullScreen) + { + canvas.mozRequestFullScreen(); + } + else if (canvas.requestFullScreen) + { + canvas.requestFullScreen(); + } + else if (canvas.requestFullscreen) + { + canvas.requestFullscreen(); + } + } + else + { + if (document.webkitCancelFullScreen) + { + document.webkitCancelFullScreen(); + } + else if (document.cancelFullScreen) + { + document.cancelFullScreen(); + } + else if (document.exitFullscreen) + { + document.exitFullscreen(); + } + } + }, + + createSampler : function createSamplerFn(sampler) + { + var samplerKey = sampler.minFilter.toString() + + ':' + sampler.magFilter.toString() + + ':' + sampler.wrapS.toString() + + ':' + sampler.wrapT.toString() + + ':' + sampler.wrapR.toString() + + ':' + sampler.maxAnisotropy.toString(); + + var cachedSamplers = this.cachedSamplers; + var cachedSampler = cachedSamplers[samplerKey]; + if (!cachedSampler) + { + cachedSamplers[samplerKey] = sampler; + return sampler; + } + return cachedSampler; + }, + + unsetIndexBuffer : function unsetIndexBufferFn(indexBuffer) + { + if (this.activeIndexBuffer === indexBuffer) + { + this.activeIndexBuffer = null; + var gl = this.gl; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + } + }, + + bindVertexBuffer : function bindVertexBufferFn(buffer) + { + if (this.bindedVertexBuffer !== buffer) + { + this.bindedVertexBuffer = buffer; + var gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + } + }, + + unbindVertexBuffer : function unbindVertexBufferFn(buffer) + { + if (this.bindedVertexBuffer === buffer) + { + this.bindedVertexBuffer = null; + var gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + }, + + bindTextureUnit : function bindTextureUnitFn(unit, target, texture) + { + var state = this.state; + var gl = this.gl; + + if (state.activeTextureUnit !== unit) + { + state.activeTextureUnit = unit; + gl.activeTexture(gl.TEXTURE0 + unit); + } + gl.bindTexture(target, texture); + }, + + bindTexture : function bindTextureFn(target, texture) + { + var state = this.state; + var gl = this.gl; + + var dummyUnit = (state.maxTextureUnit - 1); + if (state.activeTextureUnit !== dummyUnit) + { + state.activeTextureUnit = dummyUnit; + gl.activeTexture(gl.TEXTURE0 + dummyUnit); + } + gl.bindTexture(target, texture); + }, + + unbindTexture : function unbindTextureFn(texture) + { + var state = this.state; + var lastMaxTextureUnit = state.lastMaxTextureUnit; + var textureUnits = state.textureUnits; + for (var u = 0; u < lastMaxTextureUnit; u += 1) + { + var textureUnit = textureUnits[u]; + if (textureUnit.texture === texture) + { + textureUnit.texture = null; + this.bindTextureUnit(u, textureUnit.target, null); + } + } + }, + + setSampler : function setSamplerFn(sampler, target) + { + if (sampler) + { + var gl = this.gl; + + gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, sampler.minFilter); + gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, sampler.magFilter); + gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS); + gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT); + /* + if (sSupports3DTextures) + { + gl.texParameteri(target, gl.TEXTURE_WRAP_R, sampler.wrapR); + } + */ + if (this.TEXTURE_MAX_ANISOTROPY_EXT) + { + gl.texParameteri(target, this.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maxAnisotropy); + } + } + }, + + setPass : function setPassFn(pass) + { + var gl = this.gl; + var state = this.state; + + // Set renderstates + var renderStatesSet = pass.statesSet; + var renderStates = pass.states; + var numRenderStates = renderStates.length; + var r, renderState; + for (r = 0; r < numRenderStates; r += 1) + { + renderState = renderStates[r]; + renderState.set.apply(renderState, renderState.values); + } + + // Reset previous renderstates + var renderStatesToReset = state.renderStatesToReset; + var numRenderStatesToReset = renderStatesToReset.length; + for (r = 0; r < numRenderStatesToReset; r += 1) + { + renderState = renderStatesToReset[r]; + if (!(renderState.name in renderStatesSet)) + { + renderState.reset(); + } + } + + // Copy set renderstates to be reset later + renderStatesToReset.length = numRenderStates; + for (r = 0; r < numRenderStates; r += 1) + { + renderStatesToReset[r] = renderStates[r]; + } + + // Reset texture units + var lastMaxTextureUnit = state.lastMaxTextureUnit; + var textureUnits = state.textureUnits; + var currentMaxTextureUnit = pass.numTextureUnits; + if (currentMaxTextureUnit < lastMaxTextureUnit) + { + var u = currentMaxTextureUnit; + do + { + var textureUnit = textureUnits[u]; + if (textureUnit.texture) + { + textureUnit.texture = null; + this.bindTextureUnit(u, textureUnit.target, null); + } + u += 1; + } + while (u < lastMaxTextureUnit); + } + state.lastMaxTextureUnit = currentMaxTextureUnit; + + var program = pass.glProgram; + if (state.program !== program) + { + state.program = program; + gl.useProgram(program); + } + + if (pass.dirty) + { + pass.updateParametersData(this); + } + }, + + enableClientState : function enableClientStateFn(mask) + { + var gl = this.gl; + + var oldMask = this.clientStateMask; + this.clientStateMask = mask; + + /*jshint bitwise: false*/ + var disableMask = (oldMask & (~mask)); + var enableMask = ((~oldMask) & mask); + var n; + + if (disableMask) + { + if ((disableMask & 0xff) === 0) + { + disableMask >>= 8; + n = 8; + } + else + { + n = 0; + } + do + { + if (0 !== (0x01 & disableMask)) + { + gl.disableVertexAttribArray(n); + } + n += 1; + disableMask >>= 1; + } + while (disableMask); + } + + if (enableMask) + { + if ((enableMask & 0xff) === 0) + { + enableMask >>= 8; + n = 8; + } + else + { + n = 0; + } + do + { + if (0 !== (0x01 & enableMask)) + { + gl.enableVertexAttribArray(n); + } + n += 1; + enableMask >>= 1; + } + while (enableMask); + } + /*jshint bitwise: true*/ + }, + + setTexture : function setTextureFn(textureUnitIndex, texture, sampler) + { + var state = this.state; + var gl = this.gl; + + var textureUnit = state.textureUnits[textureUnitIndex]; + var oldgltarget = textureUnit.target; + var oldglobject = textureUnit.texture; + + if (texture) + { + var gltarget = texture.target; + var globject = texture.glTexture; + if (oldglobject !== globject || + oldgltarget !== gltarget) + { + textureUnit.target = gltarget; + textureUnit.texture = globject; + + if (state.activeTextureUnit !== textureUnitIndex) + { + state.activeTextureUnit = textureUnitIndex; + gl.activeTexture(gl.TEXTURE0 + textureUnitIndex); + } + + if (oldgltarget !== gltarget && + oldglobject) + { + gl.bindTexture(oldgltarget, null); + } + + gl.bindTexture(gltarget, globject); + + if (texture.sampler !== sampler) + { + texture.sampler = sampler; + + this.setSampler(sampler, gltarget); + } + } + } + else + { + if (oldgltarget && + oldglobject) + { + textureUnit.target = 0; + textureUnit.texture = null; + + if (state.activeTextureUnit !== textureUnitIndex) + { + state.activeTextureUnit = textureUnitIndex; + gl.activeTexture(gl.TEXTURE0 + textureUnitIndex); + } + + gl.bindTexture(oldgltarget, null); + } + } + }, + + setProgram : function setProgramFn(program) + { + var state = this.state; + if (state.program !== program) + { + state.program = program; + this.gl.useProgram(program); + } + }, + + syncState : function syncStateFn() + { + var state = this.state; + var gl = this.gl; + + if (state.depthTestEnable) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } + + gl.depthFunc(state.depthFunc); + + gl.depthMask(state.depthMask); + + if (state.blendEnable) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } + + gl.blendFunc(state.blendSrc, state.blendDst); + + if (state.cullFaceEnable) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } + + gl.cullFace(state.cullFace); + + gl.frontFace(state.frontFace); + + var colorMask = state.colorMask; + gl.colorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]); + + if (state.stencilTestEnable) + { + gl.enable(gl.STENCIL_TEST); + } + else + { + gl.disable(gl.STENCIL_TEST); + } + + gl.stencilFunc(state.stencilFunc, state.stencilRef, state.stencilMask); + + gl.stencilOp(state.stencilFail, state.stencilZFail, state.stencilZPass); + + if (state.polygonOffsetFillEnable) + { + gl.enable(gl.POLYGON_OFFSET_FILL); + } + else + { + gl.disable(gl.POLYGON_OFFSET_FILL); + } + + gl.polygonOffset(state.polygonOffsetFactor, state.polygonOffsetUnits); + + gl.lineWidth(state.lineWidth); + + gl.activeTexture(gl.TEXTURE0 + state.activeTextureUnit); + + var currentBox = this.state.viewportBox; + gl.viewport(currentBox[0], currentBox[1], currentBox[2], currentBox[3]); + + currentBox = this.state.scissorBox; + gl.scissor(currentBox[0], currentBox[1], currentBox[2], currentBox[3]); + + var currentColor = state.clearColor; + gl.clearColor(currentColor[0], currentColor[1], currentColor[2], currentColor[3]); + + gl.clearDepth(state.clearDepth); + + gl.clearStencil(state.clearStencil); + }, + + resetStates : function resetStatesFn() + { + var state = this.state; + + var lastMaxTextureUnit = state.lastMaxTextureUnit; + var textureUnits = state.textureUnits; + for (var u = 0; u < lastMaxTextureUnit; u += 1) + { + var textureUnit = textureUnits[u]; + if (textureUnit.texture) + { + this.bindTextureUnit(u, textureUnit.target, null); + textureUnit.texture = null; + textureUnit.target = 0; + } + } + }, + + destroy : function graphicsDeviceDestroyFn() + { + delete this.activeTechnique; + delete this.activeIndexBuffer; + delete this.bindedVertexBuffer; + + if (this.immediateVertexBuffer) + { + this.immediateVertexBuffer.destroy(); + delete this.immediateVertexBuffer; + } + + delete this.gl; + } +}; + +// Constructor function +WebGLGraphicsDevice.create = function webGLGraphicsDeviceCreateFn(canvas, params) +{ + function getAvailableContext(canvas, params, contextList) + { + if (canvas.getContext) + { + var canvasParams = { + alpha: false, + stencil: true, + antialias: false + }; + + var multisample = params.multisample; + if (multisample !== undefined && 1 < multisample) + { + canvasParams.antialias = true; + } + + var numContexts = contextList.length, i; + for (i = 0; i < numContexts; i += 1) + { + try + { + var context = canvas.getContext(contextList[i], canvasParams); + if (context) + { + return context; + } + } + catch (ex) + { + } + } + } + return null; + } + + // TODO: Test if we can also use "webkit-3d" and "moz-webgl" + var gl = getAvailableContext(canvas, params, ['webgl', 'experimental-webgl']); + if (!gl) + { + return null; + } + + var width = (gl.drawingBufferWidth || canvas.width); + var height = (gl.drawingBufferHeight || canvas.height); + + gl.enable(gl.SCISSOR_TEST); + gl.depthRange(0.0, 1.0); + gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); + //gl.hint(gl.GENERATE_MIPMAP_HINT, gl.NICEST); + + var gd = new WebGLGraphicsDevice(); + gd.gl = gl; + gd.width = width; + gd.height = height; + + var extensions = gl.getSupportedExtensions(); + if (extensions) + { + extensions = extensions.join(' '); + } + else + { + extensions = ''; + } + gd.extensions = extensions; + gd.shadingLanguageVersion = gl.getParameter(gl.SHADING_LANGUAGE_VERSION); + gd.rendererVersion = gl.getParameter(gl.VERSION); + gd.renderer = gl.getParameter(gl.RENDERER); + gd.vendor = gl.getParameter(gl.VENDOR); + + if (extensions.indexOf('WEBGL_compressed_texture_s3tc') !== -1) + { + gd.WEBGL_compressed_texture_s3tc = true; + if (extensions.indexOf('WEBKIT_WEBGL_compressed_texture_s3tc') !== -1) + { + gd.compressedTexturesExtension = gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc'); + } + else if (extensions.indexOf('MOZ_WEBGL_compressed_texture_s3tc') !== -1) + { + gd.compressedTexturesExtension = gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc'); + } + else + { + gd.compressedTexturesExtension = gl.getExtension('WEBGL_compressed_texture_s3tc'); + } + } + else if (extensions.indexOf('WEBKIT_WEBGL_compressed_textures') !== -1) + { + gd.compressedTexturesExtension = gl.getExtension('WEBKIT_WEBGL_compressed_textures'); + } + + var anisotropyExtension; + if (extensions.indexOf('EXT_texture_filter_anisotropic') !== -1) + { + if (extensions.indexOf('MOZ_EXT_texture_filter_anisotropic') !== -1) + { + anisotropyExtension = gl.getExtension('MOZ_EXT_texture_filter_anisotropic'); + } + else if (extensions.indexOf('WEBKIT_EXT_texture_filter_anisotropic') !== -1) + { + anisotropyExtension = gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic'); + } + else + { + anisotropyExtension = gl.getExtension('EXT_texture_filter_anisotropic'); + } + } + if (anisotropyExtension) + { + gd.TEXTURE_MAX_ANISOTROPY_EXT = anisotropyExtension.TEXTURE_MAX_ANISOTROPY_EXT; + gd.maxAnisotropy = gl.getParameter(anisotropyExtension.MAX_TEXTURE_MAX_ANISOTROPY_EXT); + } + else + { + gd.maxAnisotropy = 1; + } + + gd.PRIMITIVE_POINTS = gl.POINTS; + gd.PRIMITIVE_LINES = gl.LINES; + gd.PRIMITIVE_LINE_LOOP = gl.LINE_LOOP; + gd.PRIMITIVE_LINE_STRIP = gl.LINE_STRIP; + gd.PRIMITIVE_TRIANGLES = gl.TRIANGLES; + gd.PRIMITIVE_TRIANGLE_STRIP = gl.TRIANGLE_STRIP; + gd.PRIMITIVE_TRIANGLE_FAN = gl.TRIANGLE_FAN; + + gd.INDEXFORMAT_UBYTE = gl.UNSIGNED_BYTE; + gd.INDEXFORMAT_USHORT = gl.UNSIGNED_SHORT; + gd.INDEXFORMAT_UINT = gl.UNSIGNED_INT; + + function getNormalizationScale(format) + { + if (format === gl.BYTE) + { + return 0x7f; + } + else if (format === gl.UNSIGNED_BYTE) + { + return 0xff; + } + else if (format === gl.SHORT) + { + return 0x7fff; + } + else if (format === gl.UNSIGNED_SHORT) + { + return 0xffff; + } + else if (format === gl.INT) + { + return 0x7fffffff; + } + else if (format === gl.UNSIGNED_INT) + { + return 0xffffffff; + } + else //if (format === gl.FLOAT) + { + return 1; + } + } + + function makeVertexformat(n, c, s, f, name) + { + var attributeFormat = { + numComponents: c, + stride: s, + componentStride: (s / c), + format: f, + name: name + }; + if (n) + { + attributeFormat.normalized = true; + attributeFormat.normalizationScale = getNormalizationScale(f); + } + else + { + attributeFormat.normalized = false; + attributeFormat.normalizationScale = 1; + } + + if (typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) + { + if (f === gl.BYTE) + { + attributeFormat.typedSetter = DataView.prototype.setInt8; + } + else if (f === gl.UNSIGNED_BYTE) + { + attributeFormat.typedSetter = DataView.prototype.setUint8; + } + else if (f === gl.SHORT) + { + attributeFormat.typedSetter = DataView.prototype.setInt16; + } + else if (f === gl.UNSIGNED_SHORT) + { + attributeFormat.typedSetter = DataView.prototype.setUint16; + } + else if (f === gl.INT) + { + attributeFormat.typedSetter = DataView.prototype.setInt32; + } + else if (f === gl.UNSIGNED_INT) + { + attributeFormat.typedSetter = DataView.prototype.setUint32; + } + else //if (f === gl.FLOAT) + { + attributeFormat.typedSetter = DataView.prototype.setFloat32; + } + } + else + { + if (f === gl.BYTE) + { + attributeFormat.typedArray = Int8Array; + } + else if (f === gl.UNSIGNED_BYTE) + { + attributeFormat.typedArray = Uint8Array; + } + else if (f === gl.SHORT) + { + attributeFormat.typedArray = Int16Array; + } + else if (f === gl.UNSIGNED_SHORT) + { + attributeFormat.typedArray = Uint16Array; + } + else if (f === gl.INT) + { + attributeFormat.typedArray = Int32Array; + } + else if (f === gl.UNSIGNED_INT) + { + attributeFormat.typedArray = Uint32Array; + } + else //if (f === gl.FLOAT) + { + attributeFormat.typedArray = Float32Array; + } + } + return attributeFormat; + } + + gd.VERTEXFORMAT_BYTE4 = makeVertexformat(0, 4, 4, gl.BYTE, 'BYTE4'); + gd.VERTEXFORMAT_BYTE4N = makeVertexformat(1, 4, 4, gl.BYTE, 'BYTE4N'); + gd.VERTEXFORMAT_UBYTE4 = makeVertexformat(0, 4, 4, gl.UNSIGNED_BYTE, 'UBYTE4'); + gd.VERTEXFORMAT_UBYTE4N = makeVertexformat(1, 4, 4, gl.UNSIGNED_BYTE, 'UBYTE4N'); + gd.VERTEXFORMAT_SHORT2 = makeVertexformat(0, 2, 4, gl.SHORT, 'SHORT2'); + gd.VERTEXFORMAT_SHORT2N = makeVertexformat(1, 2, 4, gl.SHORT, 'SHORT2N'); + gd.VERTEXFORMAT_SHORT4 = makeVertexformat(0, 4, 8, gl.SHORT, 'SHORT4'); + gd.VERTEXFORMAT_SHORT4N = makeVertexformat(1, 4, 8, gl.SHORT, 'SHORT4N'); + gd.VERTEXFORMAT_USHORT2 = makeVertexformat(0, 2, 4, gl.UNSIGNED_SHORT, 'USHORT2'); + gd.VERTEXFORMAT_USHORT2N = makeVertexformat(1, 2, 4, gl.UNSIGNED_SHORT, 'USHORT2N'); + gd.VERTEXFORMAT_USHORT4 = makeVertexformat(0, 4, 8, gl.UNSIGNED_SHORT, 'USHORT4'); + gd.VERTEXFORMAT_USHORT4N = makeVertexformat(1, 4, 8, gl.UNSIGNED_SHORT, 'USHORT4N'); + gd.VERTEXFORMAT_FLOAT1 = makeVertexformat(0, 1, 4, gl.FLOAT, 'FLOAT1'); + gd.VERTEXFORMAT_FLOAT2 = makeVertexformat(0, 2, 8, gl.FLOAT, 'FLOAT2'); + gd.VERTEXFORMAT_FLOAT3 = makeVertexformat(0, 3, 12, gl.FLOAT, 'FLOAT3'); + gd.VERTEXFORMAT_FLOAT4 = makeVertexformat(0, 4, 16, gl.FLOAT, 'FLOAT4'); + + gd.DEFAULT_SAMPLER = { + minFilter : gl.LINEAR_MIPMAP_LINEAR, + magFilter : gl.LINEAR, + wrapS : gl.REPEAT, + wrapT : gl.REPEAT, + wrapR : gl.REPEAT, + maxAnisotropy : 1 + }; + + gd.cachedSamplers = {}; + + var maxTextureUnit = 1; + var maxUnit = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + if (maxTextureUnit < maxUnit) + { + maxTextureUnit = maxUnit; + } + maxUnit = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); + if (maxTextureUnit < maxUnit) + { + maxTextureUnit = maxUnit; + } + + var textureUnits = []; + textureUnits.length = maxTextureUnit; + for (var t = 0; t < maxTextureUnit; t += 1) + { + textureUnits[t] = {}; + } + + var defaultDepthFunc = gl.LEQUAL; + var defaultBlendFuncSrc = gl.SRC_ALPHA; + var defaultBlendFuncDst = gl.ONE_MINUS_SRC_ALPHA; + var defaultCullFace = gl.BACK; + var defaultFrontFace = gl.CCW; + var defaultStencilFunc = gl.ALWAYS; + var defaultStencilOp = gl.KEEP; + + var currentState = { + depthTestEnable : true, + blendEnable : false, + cullFaceEnable : true, + stencilTestEnable : false, + polygonOffsetFillEnable : false, + depthMask : true, + depthFunc : defaultDepthFunc, + blendSrc : defaultBlendFuncSrc, + blendDst : defaultBlendFuncDst, + cullFace : defaultCullFace, + frontFace : defaultFrontFace, + colorMask : [true, true, true, true], + stencilFunc : defaultStencilFunc, + stencilRef : 0, + stencilMask : 0xffffffff, + stencilFail : defaultStencilOp, + stencilZFail : defaultStencilOp, + stencilZPass : defaultStencilOp, + polygonOffsetFactor : 0, + polygonOffsetUnits : 0, + lineWidth : 1, + + renderStatesToReset : [], + + viewportBox : [0, 0, width, height], + scissorBox : [0, 0, width, height], + + clearColor : [0, 0, 0, 1], + clearDepth : 1.0, + clearStencil : 0, + + activeTextureUnit : 0, + maxTextureUnit : maxTextureUnit, + lastMaxTextureUnit: 0, + textureUnits : textureUnits, + + program : null + }; + gd.state = currentState; + + // State handlers + function setDepthTestEnable(enable) + { + if (currentState.depthTestEnable !== enable) + { + currentState.depthTestEnable = enable; + if (enable) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } + } + } + + function setDepthFunc(func) + { + if (currentState.depthFunc !== func) + { + currentState.depthFunc = func; + gl.depthFunc(func); + } + } + + function setDepthMask(enable) + { + if (currentState.depthMask !== enable) + { + currentState.depthMask = enable; + gl.depthMask(enable); + } + } + + function setBlendEnable(enable) + { + if (currentState.blendEnable !== enable) + { + currentState.blendEnable = enable; + if (enable) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } + } + } + + function setBlendFunc(src, dst) + { + if (currentState.blendSrc !== src || currentState.blendDst !== dst) + { + currentState.blendSrc = src; + currentState.blendDst = dst; + gl.blendFunc(src, dst); + } + } + + function setCullFaceEnable(enable) + { + if (currentState.cullFaceEnable !== enable) + { + currentState.cullFaceEnable = enable; + if (enable) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } + } + } + + function setCullFace(face) + { + if (currentState.cullFace !== face) + { + currentState.cullFace = face; + gl.cullFace(face); + } + } + + function setFrontFace(face) + { + if (currentState.frontFace !== face) + { + currentState.frontFace = face; + gl.frontFace(face); + } + } + + function setColorMask(mask0, mask1, mask2, mask3) + { + var colorMask = currentState.colorMask; + if (colorMask[0] !== mask0 || + colorMask[1] !== mask1 || + colorMask[2] !== mask2 || + colorMask[3] !== mask3) + { + colorMask[0] = mask0; + colorMask[1] = mask1; + colorMask[2] = mask2; + colorMask[3] = mask3; + gl.colorMask(mask0, mask1, mask2, mask3); + } + } + + function setStencilTestEnable(enable) + { + if (currentState.stencilTestEnable !== enable) + { + currentState.stencilTestEnable = enable; + if (enable) + { + gl.enable(gl.STENCIL_TEST); + } + else + { + gl.disable(gl.STENCIL_TEST); + } + } + } + + function setStencilFunc(stencilFunc, stencilRef, stencilMask) + { + if (currentState.stencilFunc !== stencilFunc || + currentState.stencilRef !== stencilRef || + currentState.stencilMask !== stencilMask) + { + currentState.stencilFunc = stencilFunc; + currentState.stencilRef = stencilRef; + currentState.stencilMask = stencilMask; + gl.stencilFunc(stencilFunc, stencilRef, stencilMask); + } + } + + function setStencilOp(stencilFail, stencilZfail, stencilZpass) + { + if (currentState.stencilFail !== stencilFail || + currentState.stencilZFail !== stencilZfail || + currentState.stencilZPass !== stencilZpass) + { + currentState.stencilFail = stencilFail; + currentState.stencilZFail = stencilZfail; + currentState.stencilZPass = stencilZpass; + gl.stencilOp(stencilFail, stencilZfail, stencilZpass); + } + } + + function setPolygonOffsetFillEnable(enable) + { + if (currentState.polygonOffsetFillEnable !== enable) + { + currentState.polygonOffsetFillEnable = enable; + if (enable) + { + gl.enable(gl.POLYGON_OFFSET_FILL); + } + else + { + gl.disable(gl.POLYGON_OFFSET_FILL); + } + } + } + + function setPolygonOffset(factor, units) + { + if (currentState.polygonOffsetFactor !== factor || + currentState.polygonOffsetUnits !== units) + { + currentState.polygonOffsetFactor = factor; + currentState.polygonOffsetUnits = units; + gl.polygonOffset(factor, units); + } + } + + function setLineWidth(lineWidth) + { + if (currentState.lineWidth !== lineWidth) + { + currentState.lineWidth = lineWidth; + gl.lineWidth(lineWidth); + } + } + + function resetDepthTestEnable() + { + //setDepthTestEnable(true); + if (!currentState.depthTestEnable) + { + currentState.depthTestEnable = true; + gl.enable(gl.DEPTH_TEST); + } + } + + function resetDepthFunc() + { + //setDepthFunc(defaultDepthFunc); + var func = defaultDepthFunc; + if (currentState.depthFunc !== func) + { + currentState.depthFunc = func; + gl.depthFunc(func); + } + } + + function resetDepthMask() + { + //setDepthMask(true); + if (!currentState.depthMask) + { + currentState.depthMask = true; + gl.depthMask(true); + } + } + + function resetBlendEnable() + { + //setBlendEnable(false); + if (currentState.blendEnable) + { + currentState.blendEnable = false; + gl.disable(gl.BLEND); + } + } + + function resetBlendFunc() + { + //setBlendFunc(defaultBlendFuncSrc, defaultBlendFuncDst); + var src = defaultBlendFuncSrc; + var dst = defaultBlendFuncDst; + if (currentState.blendSrc !== src || currentState.blendDst !== dst) + { + currentState.blendSrc = src; + currentState.blendDst = dst; + gl.blendFunc(src, dst); + } + } + + function resetCullFaceEnable() + { + //setCullFaceEnable(true); + if (!currentState.cullFaceEnable) + { + currentState.cullFaceEnable = true; + gl.enable(gl.CULL_FACE); + } + } + + function resetCullFace() + { + //setCullFace(defaultCullFace); + var face = defaultCullFace; + if (currentState.cullFace !== face) + { + currentState.cullFace = face; + gl.cullFace(face); + } + } + + function resetFrontFace() + { + //setFrontFace(defaultFrontFace); + var face = defaultFrontFace; + if (currentState.frontFace !== face) + { + currentState.frontFace = face; + gl.frontFace(face); + } + } + + function resetColorMask() + { + //setColorMask(true, true, true, true); + var colorMask = currentState.colorMask; + if (colorMask[0] !== true || + colorMask[1] !== true || + colorMask[2] !== true || + colorMask[3] !== true) + { + colorMask[0] = true; + colorMask[1] = true; + colorMask[2] = true; + colorMask[3] = true; + gl.colorMask(true, true, true, true); + } + } + + function resetStencilTestEnable() + { + //setStencilTestEnable(false); + if (currentState.stencilTestEnable) + { + currentState.stencilTestEnable = false; + gl.disable(gl.STENCIL_TEST); + } + } + + function resetStencilFunc() + { + //setStencilFunc(defaultStencilFunc, 0, 0xffffffff); + var stencilFunc = defaultStencilFunc; + if (currentState.stencilFunc !== stencilFunc || + currentState.stencilRef !== 0 || + currentState.stencilMask !== 0xffffffff) + { + currentState.stencilFunc = stencilFunc; + currentState.stencilRef = 0; + currentState.stencilMask = 0xffffffff; + gl.stencilFunc(stencilFunc, 0, 0xffffffff); + } + } + + function resetStencilOp() + { + //setStencilOp(defaultStencilOp, defaultStencilOp, defaultStencilOp); + var stencilOp = defaultStencilOp; + if (currentState.stencilFail !== stencilOp || + currentState.stencilZFail !== stencilOp || + currentState.stencilZPass !== stencilOp) + { + currentState.stencilFail = stencilOp; + currentState.stencilZFail = stencilOp; + currentState.stencilZPass = stencilOp; + gl.stencilOp(stencilOp, stencilOp, stencilOp); + } + } + + function resetPolygonOffsetFillEnable() + { + //setPolygonOffsetFillEnable(false); + if (currentState.polygonOffsetFillEnable) + { + currentState.polygonOffsetFillEnable = false; + gl.disable(gl.POLYGON_OFFSET_FILL); + } + } + + function resetPolygonOffset() + { + //setPolygonOffset(0, 0); + if (currentState.polygonOffsetFactor !== 0 || + currentState.polygonOffsetUnits !== 0) + { + currentState.polygonOffsetFactor = 0; + currentState.polygonOffsetUnits = 0; + gl.polygonOffset(0, 0); + } + } + + function resetLineWidth() + { + //setLineWidth(1); + if (currentState.lineWidth !== 1) + { + currentState.lineWidth = 1; + gl.lineWidth(1); + } + } + + function parseBoolean(state) + { + if (typeof state === 'number') + { + return (state ? true : false); + } + if (typeof state !== 'boolean') + { + // TODO + return null; + } + return [state]; + } + + function parseEnum(state) + { + if (typeof state !== 'number') + { + // TODO + return null; + } + return [state]; + } + + function parseEnum2(state) + { + if (typeof state === 'object') + { + var value0 = state[0], value1 = state[1]; + if (typeof value0 !== 'number') + { + // TODO + return null; + } + if (typeof value1 !== 'number') + { + // TODO + return null; + } + return [value0, value1]; + } + return null; + } + + function parseEnum3(state) + { + if (typeof state === 'object') + { + var value0 = state[0], value1 = state[1], value2 = state[2]; + if (typeof value0 !== 'number') + { + // TODO + return null; + } + if (typeof value1 !== 'number') + { + // TODO + return null; + } + if (typeof value2 !== 'number') + { + // TODO + return null; + } + return [value0, value1, value2]; + } + return null; + } + + function parseFloat(state) + { + if (typeof state !== 'number') + { + // TODO + return null; + } + return [state]; + } + + function parseFloat2(state) + { + if (typeof state === 'object') + { + var value0 = state[0], value1 = state[1]; + if (typeof value0 !== 'number') + { + // TODO + return null; + } + if (typeof value1 !== 'number') + { + // TODO + return null; + } + return [value0, value1]; + } + return null; + } + + function parseColorMask(state) + { + if (typeof state === 'object') + { + var value0 = state[0], value1 = state[1], value2 = state[2], value3 = state[3]; + if (typeof value0 !== 'number') + { + // TODO + return null; + } + if (typeof value1 !== 'number') + { + // TODO + return null; + } + if (typeof value2 !== 'number') + { + // TODO + return null; + } + if (typeof value3 !== 'number') + { + // TODO + return null; + } + return [value0, value1, value2, value3]; + } + return null; + } + + var stateHandlers = {}; + function addStateHandler(name, sf, rf, pf, dv) + { + stateHandlers[name] = { + set: sf, + reset: rf, + parse: pf, + defaultValues: dv + }; + } + addStateHandler("DepthTestEnable", setDepthTestEnable, resetDepthTestEnable, parseBoolean, [true]); + addStateHandler("DepthFunc", setDepthFunc, resetDepthFunc, parseEnum, [defaultDepthFunc]); + addStateHandler("DepthMask", setDepthMask, resetDepthMask, parseBoolean, [true]); + addStateHandler("BlendEnable", setBlendEnable, resetBlendEnable, parseBoolean, [false]); + addStateHandler("BlendFunc", setBlendFunc, resetBlendFunc, parseEnum2, [defaultBlendFuncSrc, defaultBlendFuncDst]); + addStateHandler("CullFaceEnable", setCullFaceEnable, resetCullFaceEnable, parseBoolean, [true]); + addStateHandler("CullFace", setCullFace, resetCullFace, parseEnum, [defaultCullFace]); + addStateHandler("FrontFace", setFrontFace, resetFrontFace, parseEnum, [defaultFrontFace]); + addStateHandler("ColorMask", setColorMask, resetColorMask, parseColorMask, [true, true, true, true]); + addStateHandler("StencilTestEnable", setStencilTestEnable, resetStencilTestEnable, parseBoolean, [false]); + addStateHandler("StencilFunc", setStencilFunc, resetStencilFunc, parseEnum3, [defaultStencilFunc, 0, 0xffffffff]); + addStateHandler("StencilOp", setStencilOp, resetStencilOp, parseEnum3, [defaultStencilOp, defaultStencilOp, defaultStencilOp]); + addStateHandler("PolygonOffsetFillEnable", setPolygonOffsetFillEnable, resetPolygonOffsetFillEnable, parseBoolean, [false]); + addStateHandler("PolygonOffset", setPolygonOffset, resetPolygonOffset, parseFloat2, [0, 0]); + addStateHandler("LineWidth", setLineWidth, resetLineWidth, parseFloat, [1]); + gd.stateHandlers = stateHandlers; + + gd.syncState(); + + gd.videoRam = 0; + gd.desktopWidth = window.screen.width; + gd.desktopHeight = window.screen.height; + + if (Object.defineProperty) + { + Object.defineProperty(gd, "fullscreen", { + get : function getFullscreenFn() { + return (document.fullscreenEnabled || + document.mozFullScreen || + document.webkitIsFullScreen || + false); + }, + set : function setFullscreenFn(newFullscreen) { + gd.requestFullScreen(newFullscreen); + }, + enumerable : true, + configurable : false + }); + + gd.checkFullScreen = function dummyCheckFullScreenFn() + { + }; + } + else + { + gd.fullscreen = false; + gd.oldFullscreen = false; + } + + gd.clientStateMask = 0; + gd.attributeMask = 0; + gd.activeTechnique = null; + gd.activeIndexBuffer = null; + gd.bindedVertexBuffer = 0; + gd.activeRenderTarget = null; + + gd.immediateVertexBuffer = gd.createVertexBuffer({ + numVertices: (256 * 1024 / 16), + attributes: ['FLOAT4'], + dynamic: true, + 'transient': true + }); + gd.immediatePrimitive = -1; + gd.immediateSemantics = []; + + gd.fps = 0; + gd.numFrames = 0; + gd.previousFrameTime = TurbulenzEngine.getTime(); + + return gd; +}; diff --git a/spine-js/turbulenz/index.html b/spine-js/turbulenz/index.html new file mode 100644 index 000000000..90c6cfa9c --- /dev/null +++ b/spine-js/turbulenz/index.html @@ -0,0 +1,153 @@ + + + +spine-js + + + + + + + + + +
+
+
+ + +      Click above to change the animation (Spineboy) or skin (Goblins). + + + + + \ No newline at end of file diff --git a/spine-js/turbulenz/turbulenzengine.js b/spine-js/turbulenz/turbulenzengine.js new file mode 100644 index 000000000..0c75d3d59 --- /dev/null +++ b/spine-js/turbulenz/turbulenzengine.js @@ -0,0 +1,843 @@ +// Copyright (c) 2011-2012 Turbulenz Limited +/*global VMath*/ +/*global WebGLGraphicsDevice*/ +/*global WebGLInputDevice*/ +/*global WebGLSoundDevice*/ +/*global WebGLPhysicsDevice*/ +/*global WebGLNetworkDevice*/ +/*global Float32Array*/ +/*global console*/ +/*global window*/ +"use strict"; + +// +// WebGLTurbulenzEngine +// +function WebGLTurbulenzEngine() {} +WebGLTurbulenzEngine.prototype = { + + version : '0.24.0.0', + + setInterval: function (f, t) + { + var that = this; + return window.setInterval(function () { + that.updateTime(); + f(); + }, t); + }, + + clearInterval: function (i) + { + return window.clearInterval(i); + }, + + createGraphicsDevice: function (params) + { + if (this.graphicsDevice) + { + this.callOnError('GraphicsDevice already created'); + return null; + } + else + { + var graphicsDevice = WebGLGraphicsDevice.create(this.canvas, params); + this.graphicsDevice = graphicsDevice; + return graphicsDevice; + } + }, + + createPhysicsDevice: function (params) + { + if (this.physicsDevice) + { + this.callOnError('PhysicsDevice already created'); + return null; + } + else + { + var physicsDevice; + var plugin = this.getPluginObject(); + if (plugin) + { + physicsDevice = plugin.createPhysicsDevice(params); + } + else + { + physicsDevice = WebGLPhysicsDevice.create(params); + } + this.physicsDevice = physicsDevice; + return physicsDevice; + } + }, + + createSoundDevice: function (params) + { + if (this.soundDevice) + { + this.callOnError('SoundDevice already created'); + return null; + } + else + { + var soundDevice; + var plugin = this.getPluginObject(); + if (plugin) + { + soundDevice = plugin.createSoundDevice(params); + } + else + { + soundDevice = WebGLSoundDevice.create(params); + } + this.soundDevice = soundDevice; + return soundDevice; + } + }, + + createInputDevice: function (params) + { + if (this.inputDevice) + { + this.callOnError('InputDevice already created'); + return null; + } + else + { + var inputDevice = WebGLInputDevice.create(this.canvas, params); + this.inputDevice = inputDevice; + return inputDevice; + } + }, + + createNetworkDevice: function (params) + { + if (this.networkDevice) + { + throw 'NetworkDevice already created'; + } + else + { + var networkDevice = WebGLNetworkDevice.create(params); + this.networkDevice = networkDevice; + return networkDevice; + } + }, + + createMathDevice: function (/* params */) + { + // Check if the browser supports using apply with Float32Array + try + { + var testVector = new Float32Array([1, 2, 3]); + + VMath.v3Build.apply(VMath, testVector); + + // Clamp FLOAT_MAX + testVector[0] = VMath.FLOAT_MAX; + VMath.FLOAT_MAX = testVector[0]; + } + catch (e) + { + } + + return VMath; + }, + + createNativeMathDevice: function (/* params */) + { + return VMath; + }, + + getGraphicsDevice: function () + { + var graphicsDevice = this.graphicsDevice; + if (graphicsDevice === null) + { + this.callOnError("GraphicsDevice not created yet."); + } + return graphicsDevice; + }, + + getPhysicsDevice: function () + { + return this.physicsDevice; + }, + + getSoundDevice: function () + { + return this.soundDevice; + }, + + getInputDevice: function () + { + return this.inputDevice; + }, + + getNetworkDevice: function () + { + return this.networkDevice; + }, + + getMathDevice: function () + { + return VMath; + }, + + getNativeMathDevice: function () + { + return VMath; + }, + + flush: function () + { + + }, + + run: function () + { + + }, + + encrypt: function (msg) + { + return msg; + }, + + decrypt: function (msg) + { + return msg; + }, + + generateSignature: function (/* msg */) + { + return null; + }, + + verifySignature: function (/* msg, sig */) + { + return true; + }, + + onerror: function (msg) + { + console.error(msg); + }, + + onwarning: function (msg) + { + console.warn(msg); + }, + + getSystemInfo: function () + { + return this.systemInfo; + }, + + request: function (url, callback) + { + var that = this; + + var xhr; + if (window.XMLHttpRequest) + { + xhr = new window.XMLHttpRequest(); + } + else if (window.ActiveXObject) + { + xhr = new window.ActiveXObject("Microsoft.XMLHTTP"); + } + else + { + that.callOnError("No XMLHTTPRequest object could be created"); + return; + } + + function httpRequestCallback() + { + if (xhr.readyState === 4) /* 4 == complete */ + { + if (!that.isUnloading()) + { + var xhrResponseText = xhr.responseText; + var xhrStatus = xhr.status; + + if ("" === xhrResponseText) + { + xhrResponseText = null; + } + + if (null === xhr.getResponseHeader("Content-Type") && + "" === xhr.getAllResponseHeaders()) + { + // Sometimes the browser sets status to 200 OK + // when the connection is closed before the + // message is sent (weird!). In order to address + // this we fail any completely empty responses. + // Hopefully, nobody will get a valid response + // with no headers and no body! + // Except that for cross domain requests getAllResponseHeaders ALWAYS returns an empty string + // even for valid responses... + callback(null, 0); + return; + } + + // Fix for loading from file + if (xhrStatus === 0 && xhrResponseText && window.location.protocol === "file:") + { + xhrStatus = 200; + } + + // Invoke the callback + if (xhrStatus !== 0) + { + // Under these conditions, we return a null + // response text. + + if (404 === xhrStatus) + { + xhrResponseText = null; + } + + callback(xhrResponseText, xhrStatus); + } + else + { + // Checking xhr.statusText when xhr.status is + // 0 causes a silent error + + callback(xhrResponseText, 0); + } + } + + // break circular reference + xhr.onreadystatechange = null; + xhr = null; + callback = null; + } + } + + xhr.open('GET', url, true); + if (callback) + { + xhr.onreadystatechange = httpRequestCallback; + } + xhr.send(); + }, + + // Internals + destroy : function () + { + if (this.networkDevice) + { + delete this.networkDevice; + } + if (this.inputDevice) + { + this.inputDevice.destroy(); + delete this.inputDevice; + } + if (this.physicsDevice) + { + delete this.physicsDevice; + } + if (this.soundDevice) + { + if (this.soundDevice.destroy) + { + this.soundDevice.destroy(); + } + delete this.soundDevice; + } + if (this.graphicsDevice) + { + this.graphicsDevice.destroy(); + delete this.graphicsDevice; + } + if (this.canvas) + { + delete this.canvas; + } + if (this.resizeCanvas) + { + window.removeEventListener('resize', this.resizeCanvas, false); + } + }, + + getPluginObject : function () + { + if (!this.plugin && + this.pluginId) + { + this.plugin = document.getElementById(this.pluginId); + } + return this.plugin; + }, + + unload : function () + { + if (!this.unloading) + { + this.unloading = true; + if (this.onunload) + { + this.onunload(); + } + if (this.destroy) + { + this.destroy(); + } + } + }, + + isUnloading : function () + { + return this.unloading; + }, + + enableProfiling : function () + { + }, + + startProfiling : function () + { + if (console && console.profile && console.profileEnd) + { + console.profile("turbulenz"); + } + }, + + stopProfiling : function () + { + // Chrome and Safari return an object. IE and Firefox print to the console/profile tab. + var result; + if (console && console.profile && console.profileEnd) + { + console.profileEnd("turbulenz"); + if (console.profiles) + { + result = console.profiles[console.profiles.length - 1]; + } + } + + return result; + }, + + callOnError : function (msg) + { + var onerror = this.onerror; + if (onerror) + { + onerror(msg); + } + } +}; + +// Constructor function +WebGLTurbulenzEngine.create = function webGLTurbulenzEngineFn(params) +{ + var tz = new WebGLTurbulenzEngine(); + + var canvas = params.canvas; + var fillParent = params.fillParent; + + // To expose unload (the whole interaction needs a re-design) + window.TurbulenzEngineCanvas = tz; + + tz.pluginId = params.pluginId; + tz.plugin = null; + + // time property + var getTime = Date.now; + var performance = window.performance; + if (performance) + { + // It seems high resolution "now" requires a proper "this" + if (performance.now) + { + getTime = function getTimeFn() + { + return performance.now(); + }; + } + else if (performance.webkitNow) + { + getTime = function getTimeFn() + { + return performance.webkitNow(); + }; + } + } + + // To be used by the GraphicsDevice for accurate fps calculations + tz.getTime = getTime; + + var baseTime = getTime(); // all in milliseconds (our "time" property is in seconds) + + // Safari 6.0 has broken object property defines. + var canUseDefineProperty = true; + var navStr = navigator.userAgent; + var navVersionIdx = navStr.indexOf("Version/6.0"); + if (-1 !== navVersionIdx) + { + if (-1 !== navStr.substring(navVersionIdx).indexOf("Safari/")) + { + canUseDefineProperty = false; + } + } + + if (canUseDefineProperty && Object.defineProperty) + { + Object.defineProperty(tz, "time", { + get : function () { + return ((getTime() - baseTime) * 0.001); + }, + set : function (newValue) { + if (typeof newValue === 'number') + { + // baseTime is in milliseconds, newValue is in seconds + baseTime = (getTime() - (newValue * 1000)); + } + else + { + tz.callOnError("Must set 'time' attribute to a number"); + } + }, + enumerable : false, + configurable : false + }); + + tz.updateTime = function () + { + }; + } + else + { + tz.updateTime = function () + { + this.time = ((getTime() - baseTime) * 0.001); + }; + } + + // fast zero timeouts + if (window.postMessage) + { + var zeroTimeoutMessageName = "0-timeout-message"; + var timeouts = []; + var timeId = 0; + + var setZeroTimeout = function setZeroTimeoutFn(fn) + { + timeId += 1; + var timeout = { + id : timeId, + fn : fn + }; + timeouts.push(timeout); + window.postMessage(zeroTimeoutMessageName, "*"); + return timeout; + }; + + var clearZeroTimeout = function clearZeroTimeoutFn(timeout) + { + var id = timeout; + var numTimeouts = timeouts.length; + for (var n = 0; n < numTimeouts; n += 1) + { + if (timeouts[n].id === id) + { + timeouts.splice(n, 1); + return; + } + } + }; + + var handleZeroTimeoutMessages = function handleZeroTimeoutMessagesFn(event) + { + if (event.source === window && + event.data === zeroTimeoutMessageName) + { + event.stopPropagation(); + + if (timeouts.length && !tz.isUnloading()) + { + var timeout = timeouts.shift(); + var fn = timeout.fn; + fn(); + } + } + }; + window.addEventListener("message", handleZeroTimeoutMessages, true); + + tz.setTimeout = function (f, t) + { + if (t < 1) + { + return setZeroTimeout(f); + } + else + { + var that = this; + return window.setTimeout(function () { + that.updateTime(); + if (!that.isUnloading()) + { + f(); + } + }, t); + } + }; + + tz.clearTimeout = function (i) + { + if (typeof i === 'object') + { + return clearZeroTimeout(i); + } + else + { + return window.clearTimeout(i); + } + }; + } + else + { + tz.setTimeout = function (f, t) + { + var that = this; + return window.setTimeout(function () { + that.updateTime(); + if (!that.isUnloading()) + { + f(); + } + }, t); + }; + + tz.clearTimeout = function (i) + { + return window.clearTimeout(i); + }; + } + + var requestAnimationFrame = (window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + window.mozRequestAnimationFrame); + if (requestAnimationFrame) + { + tz.setInterval = function (f, t) + { + var that = this; + if (Math.abs(t - (1000 / 60)) <= 1) + { + var interval = { + enabled: true + }; + var wrap1 = function wrap1() + { + if (interval.enabled) + { + that.updateTime(); + if (!that.isUnloading()) + { + f(); + } + requestAnimationFrame(wrap1, that.canvas); + } + }; + requestAnimationFrame(wrap1, that.canvas); + return interval; + } + else + { + var wrap2 = function wrap2() + { + that.updateTime(); + if (!that.isUnloading()) + { + f(); + } + }; + return window.setInterval(wrap2, t); + } + }; + + tz.clearInterval = function (i) + { + if (typeof i === 'object') + { + i.enabled = false; + } + else + { + window.clearInterval(i); + } + }; + } + + tz.canvas = canvas; + tz.networkDevice = null; + tz.inputDevice = null; + tz.physicsDevice = null; + tz.soundDevice = null; + tz.graphicsDevice = null; + + if (fillParent) + { + // Resize canvas to fill parent + tz.resizeCanvas = function () + { + canvas.width = canvas.parentNode.clientWidth; + canvas.height = canvas.parentNode.clientHeight; + }; + + tz.resizeCanvas(); + + window.addEventListener('resize', tz.resizeCanvas, false); + } + + var previousOnBeforeUnload = window.onbeforeunload; + window.onbeforeunload = function () + { + tz.unload(); + + if (previousOnBeforeUnload) + { + previousOnBeforeUnload.call(this); + } + }; + + tz.time = 0; + + // System info + var systemInfo = { + architecture: '', + cpuDescription: '', + cpuVendor: '', + numPhysicalCores: 1, + numLogicalCores: 1, + ramInMegabytes: 0, + frequencyInMegaHZ: 0, + osVersionMajor: 0, + osVersionMinor: 0, + osVersionBuild: 0, + osName: navigator.platform, + userLocale: (navigator.language || navigator.userLanguage).replace('-', '_') + }; + var userAgent = navigator.userAgent; + var osIndex = userAgent.indexOf('Windows'); + if (osIndex !== -1) + { + systemInfo.osName = 'Windows'; + if (navigator.platform === 'Win64') + { + systemInfo.architecture = 'x86_64'; + } + else if (navigator.platform === 'Win32') + { + systemInfo.architecture = 'x86'; + } + osIndex += 7; + if (userAgent.slice(osIndex, (osIndex + 4)) === ' NT ') + { + osIndex += 4; + systemInfo.osVersionMajor = parseInt(userAgent.slice(osIndex, (osIndex + 1)), 10); + systemInfo.osVersionMinor = parseInt(userAgent.slice((osIndex + 2), (osIndex + 4)), 10); + } + } + else + { + osIndex = userAgent.indexOf('Mac OS X'); + if (osIndex !== -1) + { + systemInfo.osName = 'Darwin'; + if (navigator.platform.indexOf('Intel') !== -1) + { + systemInfo.architecture = 'x86'; + } + osIndex += 9; + systemInfo.osVersionMajor = parseInt(userAgent.slice(osIndex, (osIndex + 2)), 10); + systemInfo.osVersionMinor = parseInt(userAgent.slice((osIndex + 3), (osIndex + 4)), 10); + systemInfo.osVersionBuild = (parseInt(userAgent.slice((osIndex + 5), (osIndex + 6)), 10) || 0); + } + else + { + osIndex = userAgent.indexOf('Linux'); + if (osIndex !== -1) + { + systemInfo.osName = 'Linux'; + if (navigator.platform.indexOf('64') !== -1) + { + systemInfo.architecture = 'x86_64'; + } + else if (navigator.platform.indexOf('x86') !== -1) + { + systemInfo.architecture = 'x86'; + } + } + } + } + tz.systemInfo = systemInfo; + + var b64ConversionTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''); + + tz.base64Encode = function base64EncodeFn(bytes) + { + var output = ""; + var numBytes = bytes.length; + var valueToChar = b64ConversionTable; + var n, chr1, chr2, chr3, enc1, enc2, enc3, enc4; + + /*jshint bitwise: false*/ + n = 0; + while (n < numBytes) + { + chr1 = bytes[n]; + n += 1; + + enc1 = (chr1 >> 2); + + if (n < numBytes) + { + chr2 = bytes[n]; + n += 1; + + if (n < numBytes) + { + chr3 = bytes[n]; + n += 1; + + enc2 = (((chr1 & 3) << 4) | (chr2 >> 4)); + enc3 = (((chr2 & 15) << 2) | (chr3 >> 6)); + enc4 = (chr3 & 63); + } + else + { + enc2 = (((chr1 & 3) << 4) | (chr2 >> 4)); + enc3 = ((chr2 & 15) << 2); + enc4 = 64; + } + } + else + { + enc2 = ((chr1 & 3) << 4); + enc3 = 64; + enc4 = 64; + } + + output += valueToChar[enc1]; + output += valueToChar[enc2]; + output += valueToChar[enc3]; + output += valueToChar[enc4]; + } + /*jshint bitwise: true*/ + + return output; + }; + + return tz; +}; + +window.WebGLTurbulenzEngine = WebGLTurbulenzEngine;