From 67f2b1c3064a2c1b77d814ca9ec3ac0507b734be Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Tue, 7 May 2013 01:13:18 +0200 Subject: [PATCH] spine-js rendering with Turbulenz. --- spine-js/data/goblins.atlas | 285 + spine-js/data/goblins.json | 499 ++ spine-js/data/goblins.png | Bin 0 -> 148167 bytes spine-js/data/spineboy.atlas | 166 + spine-js/{index.html => data/spineboy.json} | 20 +- spine-js/data/spineboy.png | Bin 0 -> 66315 bytes spine-js/spine.js | 316 +- spine-js/turbulenz/SpriteBatch.js | 44 + spine-js/turbulenz/draw2d.js | 2380 +++++++ spine-js/turbulenz/graphicsdevice.js | 6208 +++++++++++++++++++ spine-js/turbulenz/index.html | 153 + spine-js/turbulenz/turbulenzengine.js | 843 +++ 12 files changed, 10859 insertions(+), 55 deletions(-) create mode 100644 spine-js/data/goblins.atlas create mode 100644 spine-js/data/goblins.json create mode 100644 spine-js/data/goblins.png create mode 100644 spine-js/data/spineboy.atlas rename spine-js/{index.html => data/spineboy.json} (98%) create mode 100644 spine-js/data/spineboy.png create mode 100644 spine-js/turbulenz/SpriteBatch.js create mode 100644 spine-js/turbulenz/draw2d.js create mode 100644 spine-js/turbulenz/graphicsdevice.js create mode 100644 spine-js/turbulenz/index.html create mode 100644 spine-js/turbulenz/turbulenzengine.js 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 0000000000000000000000000000000000000000..863b294675c0447e37be1d498e88390eb1440538 GIT binary patch literal 148167 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 - - - 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 0000000000000000000000000000000000000000..b8b493dfd171fea37d29596ef6a67725d8a2f36f GIT binary patch literal 66315 zcmV)#K##wPP)nw>WaP5YXSsFZ<9<hd-bjOl0O`Fa5PA(oYG@*ey{?K178C+l zuwz{zdH-|1%wPx!q=O6dJmXQ3&Uf8)*B<|~kHeeuZ#~vrH2Tx6 zrDjK)OQlD*7i*3;mzZK-*53g9<SbnyxUv0m5)8TqgZydxg_%F z_L7Wa+e-?_mXRGGJKVCpwv zWpn;8CW>ok_!SI=X)TGq7XrJWh@L4lsB4($?M^>c9MJ`E8jLlAoi?*E^7cnL) zIqHF^sHneRyG^dU?&DpB_Z;0;>cBug)?D%;0rx9141)bmc9d*y%l6_bA_||AyNVyY zb_E6y^i3r<(zTRp$@f+5wLEjdwP-FqdHT7Unb!#5Dt!>xjmb;T)q3>b1_|&zGCGcz z=w^*RzO&??*C5=t$FXg>cOKndT1=$+V^7cf>E~;)y(GCjTqgVXpZ)}7B*t2##zqw< zM1*`C6B2-^Kwm@!`5`*kAJMdpq`zb6IgtX=5@IkrIsVMd=q>9Qv)eldNi!vqK zLms~!_zZe2z%OPY-tO)D(UyP@FMHKew!t_?52^GvjZ9|Xppdz16w*k0r99DIDT~u6 zWJwNk=`4*>mO}4k*vqB!92D|w+Rm|8$Z|DuSqbe^&d==?Qf~*jT<&0Fb6>x1l;+}~ zZf*n}2rs81xcK*PD|z~}t@SrugL|iVzq!P;Ki7cNAcX;Sczx+?c$!-vTBAw7@;!cH z%=onUn7Z?TPu~4f!0&9~eqQi#vV*JAwvg?AnE;OOC>wu~8=-XwynkN_qV>G+U|UJR zHQ4|0_;!EwoBhteG?(JX=WDRFC>GAfmYD1Bo_VDv_9mpo#cX5g9vR?sPS*Dp_#Fwr z+ffY{g^eFue~wM1Qr%0yUQQscaZt*93Dh|b3h8Fr9whsP3KoDdqn|1B_^_# zJw7M)&c*U1;rj~yFkeq$o^}d2NhKC+eK|IbTyEtcmn|gFe?LGV@2MShwo^LyhV! z?Xm$q$4AM&c)A*2>?%e{s1w3es^^9d`0>#*Nanw%i|+@@+jo}!5&qt-{;rO2kx5!O zN!OEOv$M0igMh#FN&x;j0Vq%0D`V)%Sy7d0!C7Id(mk5B9R)PR_ltup;fj?u$ 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 literal 0 HcmV?d00001 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;