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