├── .gitignore ├── LICENSE ├── README ├── bin └── obj_to_bin.rb ├── bundle.sh ├── demos ├── bin_loader.html ├── bouncing_cube.html ├── chemdoodle.jpg ├── complex_hello_world.html ├── glge.jpg ├── learningwebgl.jpg ├── obj_loader.html ├── shadertoy.jpg ├── spidergl.jpg ├── stereo.html ├── tar_bin_loader.html ├── tar_loader.html ├── tram.bin.tar ├── tram.jpg ├── tram.obj ├── tram.obj.bin ├── tram.obj.tar ├── tram.png ├── walas.binm ├── walas.obj ├── webgl_esitys.html └── webgl_presentation.html ├── minify.sh ├── slides ├── editor.html ├── slides.css └── slides.js └── src ├── bin_loader.js ├── gl_util.js ├── magi.js ├── matrix.js ├── obj_loader.js ├── scene_util.js ├── scenegraph.js ├── tar.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .* 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Scene graph library for WebGL 2 | 3 | Copyright (C) 2009 Ilmari Heikkinen 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Sketching a small WebGL scene graph library 2 | 3 | Goals: [- planned, + implemented, x started] 4 | Put together quick presentation graphics 5 | x slides/ -- semi-complete HTML slideshow app 6 | 7 | Write documentation 8 | + matrix.js changes -- glMatrix.js fork 9 | x gl_util.js -- WebGL utils, DOM utils, Colors, Curves, Klass 10 | - scenegraph.js -- Basic scene graph 11 | - scene_util.js -- More advanced stuff, default shaders 12 | - tar.js -- Tar archive loader 13 | - obj_loader.js -- Quite incomplete OBJ model format loader 14 | - bin_loader.js -- Custom binary model format loader 15 | 16 | Make simple things simple 17 | Drawing text 18 | + Draw text using the 2D canvas 19 | + Billboard text (buggy) 20 | - Extruded text (hmm?) 21 | Drawing images 22 | + Load image to textured quad 23 | Applying post-processing filters 24 | + Displays with FBO render targets 25 | + Material system 26 | - Easy way to chain filters 27 | Animation 28 | + Move along Bezier curves & Catmull-Rom splines 29 | x Animation vocabulary 30 | - Transformation tweens 31 | User inputs 32 | + Default camera navigation 33 | - Event handlers 34 | Loading and displaying simple textured models 35 | x Load archives with model and textures using XMLHttpRequest 36 | - Multiple lights 37 | Drawing presentation geometry 38 | + Quads, mesh quads 39 | + Fullscreen quads 40 | x Spheres 41 | x Cubes, mesh cubes 42 | x Ring segments, capped rings 43 | - Capped cylinders 44 | - Ribbons (along Bezier paths) 45 | - Dice 46 | - Layouted arrays of objects (grids, circles, ...) 47 | 48 | Not-so-complex stuff 49 | x Rewrite scene graph so that it can handle multiple passes easier. 50 | x Have a structure that's somewhat like this: 51 | - Display == render target + scene 52 | - to draw, bind FBO and render scene 53 | - write a custom draw method to do fancy stuff like envmaps or shadows 54 | x Camera == perspective & lookat matrices, used by display 55 | - Light == per-scene list of light attribs, for use in shaders 56 | x Scene == graph of nodes 57 | + Node == matrix + model + material + subgraph 58 | + Model == geometry 59 | + Material == shader + uniforms 60 | 61 | Complex stuff 62 | - Shadow maps 63 | - Depth-peeling 64 | - Envmaps 65 | -------------------------------------------------------------------------------- /bin/obj_to_bin.rb: -------------------------------------------------------------------------------- 1 | lines = File.readlines(ARGV[0]).map{|l|l.strip} 2 | verts = lines.grep(/^v /).map{|v| v[2..-1].strip.split(/\s+/).map{|i| i.to_f}} 3 | normals = lines.grep(/^vn /).map{|v| v[3..-1].strip.split(/\s+/).map{|i| i.to_f}} 4 | texcoords = lines.grep(/^vt /).map{|v| v[3..-1].strip.split(/\s+/).map{|i| i.to_f}} 5 | faces = lines.grep(/^f /).map{|f| f[2..-1].strip.split(/\s+/).map{|v| v.split("/").map{|i|i.strip.to_i-1}}} 6 | 7 | larges = faces.find_all{|f| f.length > 4} 8 | quads = faces.find_all{|f| f.length == 4} 9 | tris = faces.find_all{|f| f.length == 3} 10 | 11 | larges.each{|l| 12 | (2..l.size-1).each{|i| 13 | tris.push([l[0], l[i-1], l[i]]) 14 | } 15 | } 16 | 17 | flat_verts = verts.flatten 18 | xmax = verts.map{|v| v[0]}.max 19 | xmin = verts.map{|v| v[0]}.min 20 | ymax = verts.map{|v| v[1]}.max 21 | ymin = verts.map{|v| v[1]}.min 22 | zmax = verts.map{|v| v[2]}.max 23 | zmin = verts.map{|v| v[2]}.min 24 | xscale = xmax - xmin 25 | yscale = ymax - ymin 26 | zscale = zmax - zmin 27 | 28 | normalized_verts = verts.map{|x,y,z| 29 | [(x - xmin) / xscale, (y - ymin) / yscale, (z - zmin) / zscale] 30 | }.flatten 31 | fixed_verts = normalized_verts.map{|v| (v * 65535).to_i } 32 | fixed_texcoords = texcoords.flatten.map{|v| ([1,[0,v].max].min * 65535).to_i } 33 | fixed_normals = normals.flatten.map{|v| (0.5 * ([1,[-1,v].max].min + 1.0) * 65535).to_i } 34 | 35 | File.open(ARGV[0]+".bin", "w"){|s| 36 | # write vert counts 37 | s << [verts.size, texcoords.size, normals.size, quads.size, tris.size].pack("N*") 38 | 39 | # write scale factors and translate vector 40 | if [1].pack("N") == [1].pack("I") # big-endian machine 41 | s << [xscale, yscale, zscale, xmin, ymin, zmin].pack("f*") 42 | else # little-endian machine 43 | s << [xscale, yscale, zscale, xmin, ymin, zmin].map{|f| [f].pack("f").reverse }.join 44 | end 45 | 46 | # write verts 47 | s << fixed_verts.pack("n*") 48 | s << quads.map{|q| q.map{|v| v[0] } }.flatten.pack("n*") 49 | s << tris.map{|q| q.map{|v| v[0] } }.flatten.pack("n*") 50 | 51 | if texcoords.size > 0 52 | # write texcoords 53 | s << fixed_texcoords.pack("n*") 54 | s << quads.map{|q| q.map{|v| v[1] } }.flatten.pack("n*") 55 | s << tris.map{|q| q.map{|v| v[1] } }.flatten.pack("n*") 56 | if normals.size > 0 57 | # write normals 58 | s << fixed_normals.pack("n*") 59 | s << quads.map{|q| q.map{|v| v[2] } }.flatten.pack("n*") 60 | s << tris.map{|q| q.map{|v| v[2] } }.flatten.pack("n*") 61 | end 62 | end 63 | } -------------------------------------------------------------------------------- /bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat src/{matrix,util,gl_util,scenegraph,scene_util,tar,obj_loader,bin_loader}.js > src/magi.js && 4 | echo "Created src/magi.js"; 5 | 6 | cat slides/slides.js > slides/slides.base.min.js && 7 | echo "Created slides/slides.base.js"; 8 | 9 | -------------------------------------------------------------------------------- /demos/bin_loader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 63 | 69 | 70 | 71 |
72 | 73 |

74 |
75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /demos/bouncing_cube.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CUBE 4 | 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 | 23 |

24 |   
25 | 
26 | 


--------------------------------------------------------------------------------
/demos/chemdoodle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/chemdoodle.jpg


--------------------------------------------------------------------------------
/demos/complex_hello_world.html:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 |   
  4 |     Hello, world!
  5 |     
 30 |     
 57 |     
 81 |     
 95 |     
 96 |     
 97 |     
 98 |     
 99 |     
100 |     
347 | 	
348 | 	
349 |     
350 |     

351 | 	
352 | 
353 | 


--------------------------------------------------------------------------------
/demos/glge.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/glge.jpg


--------------------------------------------------------------------------------
/demos/learningwebgl.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/learningwebgl.jpg


--------------------------------------------------------------------------------
/demos/obj_loader.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     Demo
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
44 |                 
49 |   
50 |   
51 |     
52 | 53 |

54 |
55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /demos/shadertoy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/shadertoy.jpg -------------------------------------------------------------------------------- /demos/spidergl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/spidergl.jpg -------------------------------------------------------------------------------- /demos/stereo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 51 | 57 | 58 | 59 |
60 | 61 |

62 |
63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /demos/tar_bin_loader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 56 | 61 | 62 | 63 |
64 | 65 |

66 |
67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /demos/tar_loader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 57 | 62 | 63 | 64 |
65 | 66 |

67 |
68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /demos/tram.bin.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/tram.bin.tar -------------------------------------------------------------------------------- /demos/tram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/tram.jpg -------------------------------------------------------------------------------- /demos/tram.obj.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/tram.obj.bin -------------------------------------------------------------------------------- /demos/tram.obj.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/tram.obj.tar -------------------------------------------------------------------------------- /demos/tram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/tram.png -------------------------------------------------------------------------------- /demos/walas.binm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kig/magi/ad5eb7e9e5a35b2c979975f19f2f30cbd14e5723/demos/walas.binm -------------------------------------------------------------------------------- /demos/webgl_esitys.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGL Presentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 |
22 |
23 |

WebGL

24 |

OpenGL ES 2.0 + JavaScript

25 |

<CANVAS>

26 |
27 |
28 |

Kuinka sen saan?

29 |

Firefox 4 beta tai Minefield

30 |

WebKit nightly OS X 10.6:lla

31 |

Chromium

32 |

khronos.org/webgl/wiki/Getting_a_WebGL_Implementation

33 |
34 |
35 |

Tukeeko IE9?

36 |

Ei mitään tietoa.

37 |

Chrome Frame?

38 |
39 |
40 |

HTML:ssä

41 |

Tavan <CANVAS>

42 |

Toimii kuten kuva

43 |

CSS, toDataURL(), jne.

44 |
45 |
46 |

OpenGL ES 2.0

47 |

OpenGL kännyköille.

48 |

Toimii myös tietokoneella.

49 |

Kiihdytettyä piirtämistä.

50 |
51 |
52 |

1.1 vs. 2.0

53 |

Fiksattu vs. ohjelmoitava.

54 |
55 |
56 |

Ohjelmoitava?

57 |

Shaders, baby, yeah!

58 |

Julmetusti laskentatehoa.

59 |
60 |
61 |

Ihan tosi?

62 |

1000x nopeampi kuin JavaScript

63 |

(shaderista riippuen)

64 |
65 |
66 |

Jaa?

67 | 68 |

80 000 kolmiota

69 |
70 |
71 |

Mitä sillä tekee?

72 |

Kustomia 2D-piirtoa.

73 |

Kustomia 3D-piirtoa.

74 |
75 |
76 |

Kustomia?

77 |

Kaikki vaatii shaderin.

78 |

Ei oikoteitä.

79 |
80 |
81 |

Piirtoputki

82 |

Vertex shader

83 |

84 |

Fragment shader

85 |

86 |

Piirtopinta

87 |
88 |
89 |

Vertex shader

90 |
    91 |

    Laskee

    92 |
  • Mihin piirretään
  • 93 |
  • Muuttujia fragment shaderille
  • 94 |
95 |
96 |
97 |

Vertex shader

98 |
    99 |

    Parametreinä

    100 |
  • Bufferit (pistetaulukot)
  • 101 |
  • Tekstuurit (pintakuviot)
  • 102 |
  • Uniformit (kutsuparametrit)
  • 103 |
104 |
105 |
106 |

Fragment shader

107 |
    108 |

    Laskee

    109 |
  • Pikselin värin
  • 110 |
111 |
112 |
113 |

Fragment shader

114 |
    115 |

    Parametreinä

    116 |
  • Muuttujat vertex shaderilta
  • 117 |
  • Tekstuurit
  • 118 |
  • Uniformit
  • 119 |
120 |
121 |
122 |

Piirtopinta

123 |

<CANVAS>

124 |

tai

125 |

Framebuffer object (FBO)

126 |
127 |
128 |

FBO

129 |

Näytönohjaimen muistissa

130 |

Tekstuuriin piirtämiseen

131 | 132 |
133 |
134 |

Tekstuurit

135 |
    136 |

    WebGL osaa ladata:

    137 |
  • <CANVAS>
  • 138 |
  • <IMG>
  • 139 |
  • <VIDEO>
  • 140 |
  • Plus pikselitaulukot.
  • 141 |
142 |
143 |
144 |

CANVAS

145 |

Kaikki tekstit tässä.

146 |

(Parempi olisi käyttää CSS 3D:tä, mut.)

147 | 148 |
149 |
150 |

IMG

151 | 152 |
153 |
154 |

VIDEO

155 | 159 |
160 |
161 |

Jos kiinnostaa

162 |

learningwebgl.com

163 | 164 |
165 |
166 |

Eiks toi oo vähän...

167 |

...kankeeta? Joo.

168 |

Tavalliseen piirtoon turhan järeä.

169 |

Kirjastot apuun!

170 |
171 |
172 |

Kirjastoja

173 |

Aakkosjärjestyksessä:

174 |

C3DL

175 |

CopperLight

176 |

CubicVR

177 |

GLGE

178 |

O3D

179 |

SpiderGL

180 |

X3DOM

181 |

XB PointStream

182 |
183 |
184 |

GLGE

185 | 186 |

glge.org

187 |
188 |
189 |

SpiderGL

190 | 191 |

spidergl.org

192 |
193 |
194 |

Pelejä

195 |

Googlen Quake 2 -portti

196 |

O3D biljardi

197 |

Muutama Q3 kartankatselin

198 |
199 |
200 |

Hyötysoftaa?

201 |

3D-mallien katselimia

202 |

ChemDoodle-molekyylivisualisoija

203 |

ShaderToy

204 |

WebGL Street View

205 |
206 |
207 |

ChemDoodle

208 |

web.chemdoodle.com

209 | 210 |
211 |
212 |

ShaderToy

213 |

www.iquilezles.org/apps/shadertoy/

214 | 215 |
216 |
217 |

Status

218 |

WebGL 1.0 kohta.

219 |

Chrome, Safari, Firefox, Opera(?)

220 |

Shaderien validointi ANGLElla.

221 |

Softarendaus OSMESAlla.

222 |
223 |
224 |

ANGLE?

225 |

Almost Native Graphics Layer Engine

226 |

angleproject.googlecode.com

227 |

OpenGL ES 2.0 -tulkki

228 |
229 |
230 |

Tulkki?

231 |

Kääntää shaderit natiiveiksi.

232 |

GLES 2.0 -> OpenGL 2.1

233 |

GLES 2.0 -> Direct3D 9

234 |

(jossain vaiheessa)

235 |
236 |
237 |

OSMESA

238 |

Off-Screen MESA GL

239 |

Piirtää CPU:lla.

240 |

Erittäin hidas.

241 |

Bugejakin löytyy.

242 |
243 |
244 |

Yhteenveto

245 |

Nopeaa kustomipiirtoa.

246 |

Melko hyvin tuettu.

247 |

Web-yhteensopiva.

248 |

Vaatii ohjelmointia.

249 |
250 |
251 |

Kiitos!

252 |

Ilmari Heikkinen

253 |

ilmari.heikkinen@gmail.com

254 |
255 |
256 |

Tiedostokoot

257 |
    258 |
  • 30kB esityssofta + slaidit
  • 259 |
  • 85kB minifioity kirjasto
  • 260 |
  • (yhteensä 30kB gzipattuna)
  • 261 |
  • 250kB kuvia
  • 262 |
  • 2MB video
  • 263 |
264 |
265 |
266 |

Xiittejä

267 |

App.editSlide()

268 |

App.toggleAllSlidesVisible()

269 |

App.toggleApp()

270 |

App.wavyText = false

271 |

App.cycleColors = true

272 |

txt = App.scene.getNodesByKlass(Magi.MeshText)[0]

273 |

txt.alignedNode.material.floats.pitch = 2

274 |

txt = App.scene.getNodesByKlass(Magi.MeshText)[2] 275 |

App.addDotShader(txt)

276 |

txt.alignedNode.material.floats.pitch = 2

277 |
278 |
279 |

Loppu.

280 |

Klikkaa takaisin alkuun.

281 |
282 |
283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /demos/webgl_presentation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGL Presentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

WebGL

19 |

20 |

Press space for next slide.

21 |

Press backspace for previous slide.

22 |
23 |
24 |

WebGL

25 |

OpenGL ES 2.0 + JavaScript

26 |

<CANVAS>

27 |
28 |
29 |

How do I get it?

30 |

Firefox 4 beta or Minefield

31 |

WebKit nightly on OS X 10.6

32 |

Chromium

33 |

khronos.org/webgl/wiki/Getting_a_WebGL_Implementation

34 |
35 |
36 |

IE9 support?

37 |

No idea whatsoever.

38 |

Chrome Frame?

39 |
40 |
41 |

In HTML

42 |

The usual <CANVAS>

43 |

Works like an image

44 |

CSS, toDataURL(), etc.

45 |
46 |
47 |

OpenGL ES 2.0

48 |

OpenGL for mobile phones.

49 |

Also works on computers.

50 |

Accelerated drawing.

51 |
52 |
53 |

1.1 vs. 2.0

54 |

Fixed vs. programmable.

55 |
56 |
57 |

Programmable?

58 |

Shaders, baby, yeah!

59 |

Massive performance.

60 |
61 |
62 |

No kidding?

63 |

1000x faster than JavaScript

64 |

(depending on shader)

65 |
66 | 71 |
72 |

What's it good for?

73 |

Custom 2D drawing.

74 |

Custom 3D drawing.

75 |
76 |
77 |

Custom?

78 |

Everything requires shaders.

79 |

No shortcuts.

80 |
81 |
82 |

Drawing pipeline

83 |

Vertex shader

84 |

85 |

Fragment shader

86 |

87 |

Render target

88 |
89 |
90 |

Vertex shader

91 |
    92 |

    Computes

    93 |
  • Where to draw
  • 94 |
  • Varyings for the fragment shader
  • 95 |
96 |
97 |
98 |

Vertex shader

99 |
    100 |

    Parameters

    101 |
  • Buffers (vertex arrays)
  • 102 |
  • Textures (images)
  • 103 |
  • Uniforms (call parameters)
  • 104 |
105 |
106 |
107 |

Fragment shader

108 |
    109 |

    Computes

    110 |
  • Color of the pixel
  • 111 |
112 |
113 |
114 |

Fragment shader

115 |
    116 |

    Parameters

    117 |
  • Varyings from the vertex shader
  • 118 |
  • Textures
  • 119 |
  • Uniforms
  • 120 |
121 |
122 |
123 |

Render target

124 |

<CANVAS>

125 |

or

126 |

Framebuffer object (FBO)

127 |
128 |
129 |

FBO

130 |

In GPU memory

131 |

For rendering to textures

132 | 133 |
134 |
135 |

Textures

136 |
    137 |

    WebGL can load:

    138 |
  • <CANVAS>
  • 139 |
  • <IMG>
  • 140 |
  • <VIDEO>
  • 141 |
  • And pixel arrays.
  • 142 |
143 |
144 |
145 |

CANVAS

146 |

All the text here.

147 |

(CSS 3D would be nicer, but eh.)

148 | 149 |
150 |
151 |

IMG

152 | 153 |
154 |
155 |

VIDEO

156 | 160 |
161 |
162 |

Further reading

163 |

learningwebgl.com

164 | 165 |
166 |
167 |

Isn't that a bit...

168 |

...cumbersome? Yes.

169 |

Overkill for simple drawing.

170 |

Libraries to the rescue!

171 |
172 |
173 |

Libraries

174 |

Alphabetically:

175 |

C3DL

176 |

CopperLight

177 |

CubicVR

178 |

GLGE

179 |

O3D

180 |

SpiderGL

181 |

X3DOM

182 |

XB PointStream

183 |
184 |
185 |

GLGE

186 | 187 |

glge.org

188 |
189 |
190 |

SpiderGL

191 | 192 |

spidergl.org

193 |
194 |
195 |

Games

196 |

Google's Quake 2 -port

197 |

O3D pool game

198 |

Some Q3 map viewers

199 |
200 |
201 |

Applications?

202 |

3D model viewers

203 |

ChemDoodle molecule visualizer

204 |

ShaderToy

205 |

WebGL Street View

206 |
207 |
208 |

ChemDoodle

209 |

web.chemdoodle.com

210 | 211 |
212 |
213 |

ShaderToy

214 |

www.iquilezles.org/apps/shadertoy/

215 | 216 |
217 |
218 |

Status

219 |

WebGL 1.0 soon.

220 |

Chrome, Safari, Firefox, Opera(?)

221 |

Shader validation with ANGLE.

222 |

CPU rendering with OSMESA.

223 |
224 |
225 |

ANGLE?

226 |

Almost Native Graphics Layer Engine

227 |

angleproject.googlecode.com

228 |

OpenGL ES 2.0 interpreter

229 |
230 |
231 |

Interpreter?

232 |

Converts WebGL shaders to native.

233 |

GLES 2.0 -> OpenGL 2.1

234 |

GLES 2.0 -> DirectX 9

235 |
236 |
237 |

OSMESA

238 |

Off-Screen MESA GL

239 |

Draws on the CPU.

240 |

Very slow.

241 |
242 |
243 |

Conclusion

244 |

Fast custom drawing.

245 |

Quite well supported.

246 |

Web-compatible.

247 |

Requires programming.

248 |
249 |
250 |

Thank You!

251 |

Ilmari Heikkinen

252 |

ilmari.heikkinen@gmail.com

253 |
254 |
255 |

Cheats

256 |

App.editSlide()

257 |

App.toggleAllSlidesVisible()

258 |

App.toggleApp()

259 |

App.wavyText = false

260 |

App.cycleColors = true

261 |

txt = App.scene.getNodesByKlass(Magi.MeshText)[0]

262 |

txt.alignedNode.material.floats.pitch = 2

263 |

txt = App.scene.getNodesByKlass(Magi.MeshText)[2] 264 |

App.addDotShader(txt)

265 |

txt.alignedNode.material.floats.pitch = 2

266 |
267 |
268 |

The End

269 |

Click to return to start.

270 |
271 |
272 | 275 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /minify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat src/{matrix,util,gl_util,scenegraph,scene_util,tar,obj_loader,bin_loader}.js | yui-compressor --type js > src/magi.js && 4 | echo "Created src/magi.js"; 5 | 6 | cat slides/slides.js | yui-compressor --type js > slides/slides.base.min.js && 7 | echo "Created slides/slides.base.min.js"; 8 | 9 | -------------------------------------------------------------------------------- /slides/editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Presentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Instructions

19 |

Click the button in the top-right corner

20 |

Edit the text boxes on the right

21 |

Ctrl-Enter creates a new slide

22 |

Pressing delete or backspace in an empty slide deletes it

23 |

When you're done, close the editor and save this page

24 |
25 |
26 |

Markup

27 |

Format: Command Content

28 |

E.g. H3 Some text here

29 |
30 |
31 |

Commands

32 |
    33 |
  • H1 Big title
  • 34 |
  • H2 Normal title
  • 35 |
  • H3 Small title
  • 36 |
  • P Even smaller text
  • 37 |
  • * Bullet point list
  • 38 |
  • # Numbered list
  • 39 |
  • IMG image_url.jpg
  • 40 |
  • VIDEO video.mp4 alternative.webm
  • 41 |
42 |
43 |
44 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /slides/slides.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | background: #333333; 5 | font-family: Arial; 6 | } 7 | #slides { 8 | text-align: center; 9 | } 10 | canvas { 11 | box-shadow: 0px 0px 20px rgba(0,0,0,1); 12 | -webkit-box-shadow: 0px 0px 20px rgba(0,0,0,1); 13 | -moz-box-shadow: 0px 0px 20px rgba(0,0,0,1); 14 | } 15 | #slides img, #slides video, #slides canvas, #slides audio { 16 | display: block; 17 | margin-left: auto; 18 | margin-right: auto; 19 | } 20 | textarea { 21 | position: relative; 22 | font-family: sans-serif; 23 | white-space: pre; 24 | width: 474px; 25 | min-height: 40px; 26 | height: auto; 27 | overflow: visible; 28 | resize: none; 29 | background-color: rgba(255,255,255,0.8); 30 | border: 0px; 31 | margin: 0px; 32 | margin-right: 10px; 33 | margin-top: 8px; 34 | padding: 4px; 35 | padding-left: 8px; 36 | line-height: 1.5em; 37 | text-align: left 38 | } 39 | textarea:focus { 40 | background: white; 41 | } 42 | h1 { 43 | font-family: Century Gothic,URW Gothic L,Tahoma,Arial; 44 | font-size: 100px; 45 | } 46 | h2 { 47 | font-family: Arial; 48 | margin-bottom: 0.3em; 49 | } 50 | h3 { 51 | font-family: Arial; 52 | margin-top: 0em; 53 | margin-bottom: 0.2em; 54 | } 55 | img { 56 | max-height: 50%; 57 | } 58 | p { 59 | margin-top: 0em; 60 | margin-bottom: 0.3em; 61 | } 62 | #editorToggle { 63 | position: fixed; 64 | top: 8px; 65 | right: 0px; 66 | margin-right: 10px; 67 | z-index: 1000000; 68 | } 69 | #slides div { 70 | margin: 40px; 71 | padding: 0; 72 | margin-bottom: 10%; 73 | height: 80%; 74 | border: 1px solid #888; 75 | box-shadow: 0px 2px 6px rgba(0,0,0,0.75); 76 | -webkit-box-shadow: 0px 2px 6px rgba(0,0,0,0.75); 77 | -moz-box-shadow: 0px 2px 6px rgba(0,0,0,0.75); 78 | padding-top: 20px; 79 | padding-bottom: 60px; 80 | border-top-left-radius: 20px; 81 | border-bottom-right-radius: 20px; 82 | background: -webkit-gradient( 83 | linear, 84 | left bottom, 85 | left top, 86 | color-stop(0.25, rgb(255,255,255)), 87 | color-stop(0.69, rgb(235,235,235)) 88 | ); 89 | background: -moz-linear-gradient( 90 | center bottom, 91 | rgb(255,255,255) 25%, 92 | rgb(245,245,245) 69% 93 | ); 94 | font-size: 200%; 95 | } 96 | #slides li { 97 | list-style: none; 98 | } 99 | .editor { 100 | position: absolute; 101 | top: 20px; 102 | right: 40px; 103 | background-color: rgba(255,255,255,0.2); 104 | padding: 10px; 105 | } 106 | .editor input { 107 | border: 0px; 108 | width: 400px; 109 | font-size: 20px; 110 | padding-left: 2px; 111 | padding-right: 2px; 112 | margin-bottom: 4px; 113 | background-color: rgba(255,255,255,0.6); 114 | } 115 | .editor button { 116 | margin: 0px; 117 | margin-top: 10px; 118 | } 119 | canvas:focus { 120 | outline: none; 121 | } 122 | -------------------------------------------------------------------------------- /slides/slides.js: -------------------------------------------------------------------------------- 1 | Slides = Klass({ 2 | currentSlide : 0, 3 | 4 | h1FontSize : 100, 5 | h2FontSize : 72, 6 | h3FontSize : 60, 7 | listFontSize : 48, 8 | pFontSize : 48, 9 | 10 | h1Color : 'white', 11 | h2Color : 'white', 12 | h3Color : 'white', 13 | listColor : 'white', 14 | pColor : 'white', 15 | 16 | h1Font : 'Century Gothic,URW Gothic L,Tahoma,Arial', 17 | h2Font : 'Arial', 18 | h3Font : 'Arial', 19 | listFont : 'Arial', 20 | pFont : 'Arial', 21 | preFont : 'Droid Mono,Monaco,Courier New', 22 | 23 | listBullet : "\u2605", // filled star 24 | 25 | pulse : 0, 26 | 27 | editorWidth: 520, 28 | 29 | wavyText : true, 30 | cycleColors : false, 31 | 32 | webGLCanvasID : 'webgl-slides-canvas', 33 | editorID : 'slides-editor', 34 | 35 | usingWebGL : false, 36 | usingHTML : false, 37 | 38 | initialize : function(elem) { 39 | var et = byId('editorToggle'); 40 | if (et) et.parentNode.removeChild(et); 41 | et = BUTTON("Edit", {id: 'editorToggle'}); 42 | et.onclick = function() { 43 | self.toggleEditMode(); 44 | }; 45 | this.editorToggleButton = et 46 | document.body.insertBefore(this.editorToggleButton, elem); 47 | var ed = byId(this.editorID); 48 | if (ed) { 49 | ed.parentNode.removeChild(ed); 50 | } 51 | if (document.location.hash) { 52 | this.currentSlide = parseInt(document.location.hash.slice(1)); 53 | } else { 54 | this.currentSlide = 0; 55 | } 56 | var self = this; 57 | window.addEventListener('hashchange', function() { 58 | var idx = parseInt(document.location.hash.slice(1)); 59 | if (!isNaN(idx) && idx != this.currentSlide) 60 | self.setSlide(idx); 61 | }, false); 62 | if (this.isWebGLSupported()) { // render using WebGL 63 | this.initializeWebGL(elem); 64 | this.usingWebGL = true; 65 | } else if (this.isSVGSupported()) { // SVG fallback 66 | } else { // JS + CSS fallback 67 | window.onkeydown = this.makeKeydownHandler(); 68 | this.rendererGoTo = this.htmlGoTo; 69 | this.usingHTML = true; 70 | } 71 | this.setSlideElement(elem); 72 | }, 73 | 74 | htmlGoTo : function() { 75 | var divs = this.slideElement.getElementsByTagName('div'); 76 | window.scrollTo(0, divs[this.currentSlide].offsetTop-40); 77 | }, 78 | 79 | isWebGLSupported : function() { 80 | return (Magi.findGLContextId(E.canvas(1,1)) != null); 81 | }, 82 | 83 | isSVGSupported : function() { 84 | return false; 85 | }, 86 | 87 | makeKeydownHandler : function() { 88 | var self = this; 89 | var skipTags = ['BUTTON', 'INPUT', 'TEXTAREA']; 90 | return function(ev) { 91 | var t = ev.target.tagName; 92 | if (skipTags.indexOf(t) != -1) 93 | return; 94 | if (Key.match(ev, Key.SPACE)) { 95 | if (ev.shiftKey) 96 | self.previousSlide(); 97 | else 98 | self.nextSlide(); 99 | ev.preventDefault(); 100 | } else if (Key.match(ev, Key.BACKSPACE)) { 101 | if (ev.shiftKey) 102 | self.nextSlide(); 103 | else 104 | self.previousSlide(); 105 | ev.preventDefault(); 106 | } 107 | }; 108 | }, 109 | 110 | initializeWebGL : function(elem) { 111 | document.body.style.overflow = 'hidden'; 112 | var oldCanvas = byId(this.webGLCanvasID); 113 | if (oldCanvas) oldCanvas.parentNode.removeChild(oldCanvas); 114 | this.canvas = E.canvas(window.innerWidth, window.innerHeight); 115 | this.canvas.setAttribute('id', this.webGLCanvasID); 116 | this.canvas.id = this.webGLCanvasID; 117 | this.canvas.setAttribute('tabindex', '-1'); 118 | this.canvas.style.position = 'absolute'; 119 | this.canvas.style.left = this.canvas.style.top = '0px'; 120 | document.body.insertBefore(this.canvas, elem); 121 | 122 | this.realDisplay = new Magi.Scene(this.canvas); 123 | this.previousFrameFBO = new Magi.FBO(this.realDisplay.gl, this.canvas.width*2, this.canvas.height*2); 124 | this.previousFrameFBO.use(); 125 | this.fbo = new Magi.FBO(this.realDisplay.gl, this.canvas.width*2, this.canvas.height*2); 126 | this.fbo.use(); 127 | var showFBO = new Magi.FilterQuad(); 128 | var self = this; 129 | showFBO.addFrameListener(function(t,dt){ 130 | this.material.textures.Texture0 = self.fbo.texture; 131 | self.display.fbo = self.fbo; 132 | self.fbo.setSize(self.canvas.width*2, self.canvas.height*2); 133 | self.display.draw(t,dt); 134 | var f = self.previousFrameFBO; 135 | self.previousFrameFBO = self.fbo; 136 | self.fbo = f; 137 | }); 138 | this.realDisplay.scene.appendChild(showFBO); 139 | 140 | this.display = new Magi.Scene(this.fbo); 141 | this.scene = this.display.scene; 142 | var s = this.display; 143 | s.bg = [0.3, 0.7, 0.9, 1]; 144 | s.camera.targetFov = 40; 145 | s.useDefaultCameraControls(this.canvas); 146 | s.yRot.setX(0.02).setY(1.0).setAngle(-0.1); 147 | s.xRot.setAngle(-0.05); 148 | vec3.set([2.5,-3,7.5], s.camera.position); 149 | vec3.set([0.5,-2,0], s.camera.lookAt); 150 | s.scene.setScale(1/200); 151 | 152 | var grad = new Magi.FilterQuad(this.shaders.bgFrag); 153 | grad.material.floats.aspect = 4/3; 154 | s.scene.appendChild(grad); 155 | 156 | byId('slides').style.display = 'none'; 157 | 158 | 159 | s.scene.addFrameListener(function(t,dt) { 160 | if (self.cycleColors) { 161 | var rgb = Magi.Colors.hsv2rgb((t/10)%360, 0.9, 0.7); 162 | vec3.set(rgb, s.bg); 163 | } 164 | }); 165 | 166 | window.addEventListener('resize', function() { 167 | self.canvas.width = window.innerWidth - (self.editing ? self.editorWidth : 0); 168 | self.canvas.height = window.innerHeight; 169 | self.display.changed = true; 170 | }, false); 171 | 172 | var downX=0, downY=0, cancelled=true; 173 | this.canvas.onmousedown = function(ev) { 174 | downX = ev.clientX; 175 | downY = ev.clientY; 176 | this.focus(); 177 | cancelled = false; 178 | }; 179 | this.canvas.onmousemove = function(ev) { 180 | if (!cancelled) { 181 | var x = ev.clientX; 182 | var y = ev.clientY; 183 | var dx = x - downX; 184 | var dy = y - downY; 185 | var d = Math.sqrt(dx*dx + dy*dy); 186 | if (d > 5) cancelled = true; 187 | } 188 | }; 189 | this.canvas.onclick = function(ev) { 190 | if (!cancelled) { 191 | if (ev.shiftKey) 192 | self.previousSlide(); 193 | else 194 | self.nextSlide(); 195 | s.changed = true; 196 | } 197 | cancelled = true; 198 | ev.preventDefault(); 199 | }; 200 | this.canvas.onkeydown = this.makeKeydownHandler(); 201 | this.canvas.focus(); 202 | 203 | this.targetPos = vec3.create(); 204 | this.top = new Magi.Node(); 205 | this.scene.appendChild(this.top); 206 | this.cube = this.makeCube(); 207 | this.top.appendChild(this.cube); 208 | this.rendererGoTo = this.webGLGoTo; 209 | var d = vec3.create(); 210 | this.scene.addFrameListener(function(t,dt) { 211 | vec3.sub(self.targetPos, self.top.position, d); 212 | if (vec3.lengthSquare(d) > 1) { 213 | var f = 1-Math.pow(0.75, dt/33); 214 | vec3.scale(d, f); 215 | vec3.add(self.top.position, d); 216 | } else { 217 | self.visibleSlides(self.currentSlide, self.currentSlide); 218 | } 219 | vec3.negate(self.targetPos, d); 220 | vec3.sub(d, self.cube.position); 221 | var dist = vec3.length(d); 222 | if (dist > 1) { 223 | var f = 1-Math.pow(0.85, dt/33); 224 | vec3.scale(d, f); 225 | vec3.add(self.cube.position, d); 226 | self.cube.outerCube.setScale(Math.max(0.3, Math.min(0.8, 100.0/dist))); 227 | self.cube.outerCube.rotationSpeed = Math.min(50, 1+dist/10); 228 | if (dist < 80 && dist > 50 && self.pulse <= 0) { 229 | self.pulse = 330; 230 | } 231 | self.cube.counter = 1000; 232 | } else { 233 | self.cube.outerCube.setScale(0.8); 234 | self.cube.outerCube.rotationSpeed = Math.sqrt(self.cube.counter / 1000); 235 | self.cube.counter -= dt; 236 | if (self.cube.counter < 10) 237 | self.cube.counter = 10; 238 | } 239 | if (self.pulse>0) { 240 | self.cube.innerCube.setScale(0.8-(self.pulse/33)*0.02); 241 | self.pulse-=dt; 242 | } else { 243 | self.cube.innerCube.setScale(0.8); 244 | } 245 | }); 246 | }, 247 | 248 | webGLGoTo : function(before, current) { 249 | vec3.negate(this.slides.childNodes[current].position, this.targetPos); 250 | 251 | this.visibleSlides(before, current); 252 | }, 253 | 254 | toggleAllSlidesVisible : function() { 255 | this.allSlidesVisible = !this.allSlidesVisible; 256 | this.visibleSlides(this.currentSlide, this.currentSlide); 257 | }, 258 | 259 | visibleSlides : function(first, last) { 260 | if (!this.usingWebGL) return; 261 | var min = Math.min(first, last); 262 | var max = Math.max(first, last); 263 | var cc = this.slides.childNodes; 264 | if (min == 0 && max == cc.length-1) { 265 | min = cc.length-1; 266 | max = 0; 267 | } 268 | for (var i=0; i max)) { 270 | cc[i].display = false; 271 | this.deleteTextures(cc[i]); 272 | } else { 273 | cc[i].display = true; 274 | } 275 | } 276 | }, 277 | 278 | deleteTextures : function(node) { 279 | node.forEach(function(c) { 280 | if (c.material) { 281 | for (var i in c.material.textures) { 282 | if (c.material.textures[i]) 283 | c.material.textures[i].clear(); 284 | } 285 | } 286 | }); 287 | }, 288 | 289 | textualize : function(e, level, prefix) { 290 | var strs = []; 291 | if (level == null) level = 0; 292 | switch (e.tagName) { 293 | case 'IMG': 294 | //case 'MODEL': 295 | strs.push(e.tagName + " " + e.getAttribute('src')); 296 | break; 297 | case 'VIDEO': 298 | var src = e.getAttribute('src'); 299 | var srcs = toArray(e.getElementsByTagName('source')).map(function(c){ 300 | return c.getAttribute('src'); 301 | }); 302 | var srcstr = (src ? src + " " : "") + srcs.join(" "); 303 | strs.push(e.tagName + " " + srcstr); 304 | break; 305 | case 'LI': 306 | var indent = ""; 307 | for (var i=1; i '+ e.textContent); 320 | break; 321 | case 'H1': 322 | case 'H2': 323 | case 'H3': 324 | case 'P': 325 | strs.push(e.tagName + " " + e.textContent); 326 | break; 327 | } 328 | var cc = e.childNodes; 329 | for (var i=0; i 0) 332 | strs.push(txt); 333 | } 334 | return strs.join("\n") 335 | }, 336 | 337 | parseMarkup : function(str) { 338 | var lines = str.split("\n"); 339 | var top = DIV(); 340 | var lists = []; 341 | for (var i=0; i 0 && l.tagName != 'UL') { 367 | lists.pop(); 368 | l = lists[lists.length-1]; 369 | } 370 | if (lists.length == 0) { 371 | l = UL(); 372 | lists.push(l); 373 | top.appendChild(l) 374 | } 375 | l.appendChild(LI(T(content))); 376 | endList = false; 377 | break; 378 | case '#': 379 | var l = lists[lists.length-1]; 380 | while (lists.length > 0 && l.tagName != 'OL') { 381 | lists.pop(); 382 | l = lists[lists.length-1]; 383 | } 384 | if (lists.length == 0) { 385 | l = OL(); 386 | lists.push(l); 387 | top.appendChild(l) 388 | } 389 | l.appendChild(LI(T(content))); 390 | endList = false; 391 | break; 392 | case '>': 393 | top.appendChild(E('PRE', T(content))); 394 | break; 395 | case 'H1': 396 | case 'H2': 397 | case 'H3': 398 | case 'P': 399 | top.appendChild(E(cmd, T(content))); 400 | break; 401 | default: 402 | var oneWord = !(/\s/).test(line); 403 | if (oneWord && (/\.(jpe?g|png|gif)$/i).test(line)) { 404 | top.appendChild(IMG({src: line})); 405 | } else if (oneWord && (/\.(mp4|webm|og[gvm])$/i).test(line)) { 406 | top.appendChild(VIDEO({src: line, controls: 'on'})); 407 | } else if (oneWord && (/\.(mp3|oga)$/i).test(line)) { 408 | top.appendChild(AUDIO({src: line, controls: 'on'})); 409 | } else { 410 | top.appendChild(E(i == 0 ? 'H1' : 'H2', T(line))); 411 | } 412 | } 413 | if (endList && lists.length > 0) { 414 | lists.splice(0); 415 | } 416 | } 417 | return top; 418 | }, 419 | 420 | toggleEditMode : function() { 421 | this.editing = !this.editing; 422 | if (this.editing) { 423 | if (!this.editor) { 424 | // createNewSlide 425 | // gotoCurrentSlide 426 | // edit slideshow style (BG, fonts, transitions) 427 | // save slideshow html 428 | // deleteSlide 429 | // change slide style (BG, fonts, transitions) 430 | // change slide order 431 | var self = this; 432 | this.editorButtons = DIV( 433 | BUTTON("New slide", { 434 | onclick : function(ev) { 435 | self.createNewSlide(); 436 | ev.preventDefault(); 437 | } 438 | }), 439 | // BUTTON("Edit global style"), 440 | BUTTON("Party on!", { 441 | onclick : function(ev) { 442 | self.cycleColors = !self.cycleColors; 443 | ev.preventDefault(); 444 | } 445 | }), 446 | BUTTON("Toggle all slides visible", {style: {marginRight: '2px'}, 447 | onclick : function(ev) { 448 | self.toggleAllSlidesVisible(); 449 | ev.preventDefault(); 450 | } 451 | }), 452 | {style: { 453 | position: 'absolute', 454 | right: '0px', 455 | bottom: '0px', 456 | width: '510px', 457 | textAlign: 'right' 458 | }} 459 | ); 460 | this.editorSlides = DIV( 461 | {style: { 462 | position: 'absolute', 463 | right: '0px', 464 | top: '0px', 465 | bottom: '32px', 466 | width: '510px', 467 | overflow: 'auto' 468 | }} 469 | ); 470 | toArray(this.slideElement.childNodes).forEach(function(c) { 471 | if (!c.tagName) return; 472 | var ta = TEXTAREA(T(self.textualize(c)), { 473 | onkeyup : self.textareaKeyUpHandler(c), 474 | onfocus : self.textareaFocusHandler(), 475 | }); 476 | ta.addEventListener('DOMNodeInserted', function() { 477 | var s = this; 478 | setTimeout(function() { 479 | s.style.height = s.scrollHeight+'px'; 480 | }, 10); 481 | }, false); 482 | self.editorSlides.appendChild(DIV(ta)); 483 | }); 484 | this.editor = DIV(this.editorSlides, this.editorButtons, 485 | { 486 | id: this.editorID, 487 | style: { 488 | position: 'fixed', 489 | right: '0px', 490 | top: '0px', 491 | bottom: '8px' 492 | } 493 | } 494 | ); 495 | } 496 | document.body.insertBefore(this.editor, this.slideElement); 497 | if (this.usingWebGL) { 498 | this.display.camera.targetFov *= 1.0/0.6; 499 | this.canvas.width -= this.editorWidth; 500 | this.display.changed = true; 501 | } 502 | this.editorToggleButton.style.right = this.editorWidth + 'px'; 503 | } else { 504 | this.editor.parentNode.removeChild(this.editor); 505 | if (this.usingWebGL) { 506 | this.display.camera.targetFov *= 0.6; 507 | this.canvas.width += this.editorWidth; 508 | this.display.changed = true; 509 | } 510 | this.editorToggleButton.style.right = '0px'; 511 | } 512 | }, 513 | 514 | saveSlideshow : function() { 515 | App.toggleApp(); 516 | if (this.canvas) 517 | document.body.removeChild(this.canvas); 518 | document.body.removeChild(this.editorToggleButton); 519 | if (this.editor) 520 | document.body.removeChild(this.editor); 521 | var html = document.documentElement.innerHTML; 522 | document.body.insertBefore(this.editorToggleButton,this.slideElement); 523 | if (this.canvas) 524 | document.body.insertBefore(this.canvas,this.slideElement); 525 | document.body.insertBefore(this.editor,this.slideElement); 526 | App.toggleApp(); 527 | return html; 528 | }, 529 | 530 | textareaKeyUpHandler : function(e) { 531 | var self = this; 532 | return (function(ev) { 533 | var content = this.value; 534 | if (this.previousContent == "" && Key.match(ev, [Key.BACKSPACE, Key.DELETE])) { 535 | var a = toArray(this.parentNode.parentNode.childNodes); 536 | self.deleteSlide(a.indexOf(this.parentNode)); 537 | } else if (ev.ctrlKey && Key.match(ev, Key.ENTER)) { 538 | self.createNewSlide(); 539 | ev.preventDefault(); 540 | } else if (this.previousContent != content) { 541 | this.style.height = this.scrollHeight+'px'; 542 | this.previousContent = content; 543 | var dom = self.parseMarkup(content); 544 | while (e.firstChild) { 545 | e.removeChild(e.firstChild); 546 | } 547 | while (dom.firstChild) { 548 | var d = dom.firstChild; 549 | dom.removeChild(d); 550 | e.appendChild(d); 551 | } 552 | if (self.usingWebGL) { 553 | var slide = self.parseSlide(e); 554 | var a = toArray(this.parentNode.parentNode.childNodes); 555 | var idx = a.indexOf(this.parentNode); 556 | self.replaceSlide(idx, slide); 557 | } 558 | } 559 | }); 560 | }, 561 | 562 | textareaFocusHandler : function() { 563 | var self = this; 564 | return (function(ev) { 565 | this.style.height = this.scrollHeight+'px'; 566 | var a = toArray(this.parentNode.parentNode.childNodes); 567 | self.setSlide(a.indexOf(this.parentNode)); 568 | }); 569 | }, 570 | 571 | toggleApp : function() { 572 | if (this.disabled) { 573 | this.canvas.style.display = 'block'; 574 | this.slideElement.style.display = 'none'; 575 | this.disabled = false; 576 | this.display.paused = false; 577 | } else { 578 | this.canvas.style.display = 'none'; 579 | this.slideElement.style.display = 'block'; 580 | this.disabled = true; 581 | this.display.paused = true; 582 | } 583 | }, 584 | 585 | setSlideElement : function(elem) { 586 | if (this.slides && this.scene) { 587 | this.scene.removeChild(this.slides); 588 | this.slideElement.style.display = 'block'; 589 | } 590 | this.slideElement = elem; 591 | this.slideCount = this.slideElement.getElementsByTagName('div').length; 592 | if (this.scene) { 593 | this.slideElement.style.display = 'none'; 594 | this.slides = this.parseSlides(elem); 595 | this.top.appendChild(this.slides); 596 | } 597 | this.setSlide(this.currentSlide); 598 | }, 599 | 600 | setSlide : function(index) { 601 | if (index < 0) index = this.slideCount+(index%this.slideCount); 602 | var before = this.currentSlide 603 | this.currentSlide = index % this.slideCount; 604 | this.rendererGoTo(before, this.currentSlide); 605 | if (this.editing) { 606 | var c = this.editorSlides.childNodes[this.currentSlide]; 607 | c.firstChild.focus(); 608 | if (this.editorSlides.scrollTop > c.offsetTop + (c.offsetHeight-50) || 609 | this.editorSlides.scrollTop + this.editorSlides.offsetHeight < c.offsetTop+50) { 610 | this.editorSlides.scrollTop = c.offsetTop - 50; 611 | } 612 | } 613 | document.location.replace("#"+this.currentSlide); 614 | }, 615 | 616 | getCurrentTitle : function() { 617 | return this.slides.childNodes[this.currentSlide].title; 618 | }, 619 | 620 | nextSlide : function() { 621 | this.setSlide(this.currentSlide+1); 622 | }, 623 | 624 | previousSlide : function() { 625 | this.setSlide(this.currentSlide-1); 626 | }, 627 | 628 | updateText : function(node, text) { 629 | if (node.text != text) { 630 | node.setText(text); 631 | node.alignedNode.material.floats.xScale = node.width; 632 | node.alignedNode.material.floats.yScale = node.height; 633 | } 634 | }, 635 | 636 | closeEditor : function() { 637 | if (this.editor) { 638 | this.editor.innerHTML = ""; 639 | document.body.removeChild(this.editor); 640 | } 641 | }, 642 | 643 | parseSlides : function(elem) { 644 | var cn = toArray(elem.childNodes).filter(function(s){ return s.tagName; }); 645 | var top = new Magi.Node(); 646 | var self = this; 647 | var x = 0; 648 | var cf = cn.length*400; 649 | var r = cf / 2*Math.PI; 650 | cn.forEach(function(s) { 651 | var d = x/cn.length; 652 | var slide = self.parseSlide(s, null, x); 653 | slide.element = s; 654 | slide.position[1] = Math.sin(2*Math.PI*x/10)*2500; 655 | slide.position[0] = -x*10000; 656 | slide.position[2] = Math.cos(2*Math.PI*x/10)*2500; 657 | x++; 658 | top.appendChild(slide); 659 | }); 660 | return top; 661 | }, 662 | 663 | repositionSlides : function() { 664 | if (this.usingWebGL) { 665 | var cc = this.slides.childNodes; 666 | for (var i=0; i 0) || this.currentSlide+1 == this.slideCount) 724 | this.currentSlide--; 725 | this.slideCount--; 726 | if (this.editorSlides) { 727 | var c = this.editorSlides.childNodes[index]; 728 | c.parentNode.removeChild(c); 729 | } 730 | if (this.usingWebGL) { 731 | var ps = this.slides.childNodes[index]; 732 | this.deleteTextures(ps); 733 | this.slides.childNodes.splice(index,1); 734 | ps.element.parentNode.removeChild(ps.element); 735 | } 736 | this.repositionSlides(); 737 | }, 738 | 739 | replaceSlide : function(index, slide) { 740 | var ps = this.slides.childNodes[index]; 741 | if (!ps) { 742 | this.slides.appendChild(slide); 743 | this.repositionSlides(); 744 | } else { 745 | this.deleteTextures(ps); 746 | var oldChildren = ps.childNodes.splice(0); 747 | var cc = slide.childNodes; 748 | while (cc.length > 0) { 749 | ps.appendChild(cc.shift()); 750 | } 751 | } 752 | }, 753 | 754 | parseSlide : function(elem, acc, idx) { 755 | var cn = toArray(elem.childNodes); 756 | acc = acc || {top: new Magi.Node(), left: 0, xOffset: 0, offset: 0, counter: 0}; 757 | var self = this; 758 | var origOffset = acc.offset; 759 | cn.forEach(function(e) { 760 | var node; 761 | var xOffset = acc.xOffset; 762 | var yOffset = acc.offset; 763 | var left = acc.left; 764 | switch (e.tagName) { 765 | case "H1": 766 | sz = self.h1FontSize; 767 | if (!acc.top.title) 768 | acc.top.title = e.textContent; 769 | node = self.makeH1(e.textContent); 770 | acc.offset += sz*3.5; 771 | break; 772 | case "H2": 773 | sz = self.h2FontSize; 774 | node = self.makeH2(e.textContent); 775 | if (acc.left < 0) { 776 | node.setAlign(node.leftAlign); 777 | left -= 100; 778 | } 779 | acc.offset += sz*1.5; 780 | break; 781 | case "H3": 782 | sz = self.h3FontSize; 783 | node = self.makeH3(e.textContent); 784 | acc.offset += sz*1.3; 785 | break; 786 | case "P": 787 | sz = self.pFontSize; 788 | node = new Magi.MeshText(e.textContent, self.pFontSize, self.pColor, self.pFont); 789 | node.setVAlign(node.topAlign); 790 | self.addWaveShader(node); 791 | acc.offset += sz*1.3; 792 | break; 793 | case "PRE": 794 | sz = self.pFontSize; 795 | node = new Magi.MeshText(e.textContent, self.pFontSize, self.pColor, self.preFont); 796 | node.setAlign(node.leftAlign, node.topAlign); 797 | acc.offset += self.listFontSize*1.25; 798 | left = -450; 799 | break; 800 | case "UL": 801 | case "OL": 802 | var lt = acc.listType; 803 | acc.listType = e.tagName; 804 | acc.counter = 1; 805 | acc.xOffset += 32; 806 | var l = acc.left; 807 | acc.left = -300; 808 | self.parseSlide(e, acc, idx); 809 | acc.left = l; 810 | acc.xOffset -= 32; 811 | acc.listType = lt; 812 | break; 813 | case "LI": 814 | var prefix = acc.listType == "OL" ? (acc.counter++)+"." : self.listBullet; 815 | node = new Magi.MeshText(prefix + " " + e.textContent, self.listFontSize, self.listColor, self.listFont); 816 | node.setAlign(node.leftAlign, node.topAlign); 817 | self.addWaveShader(node); 818 | acc.offset += self.listFontSize*1.25; 819 | break; 820 | case "IMG": 821 | node = new Magi.MeshImage(e); 822 | self.addWaveShader(node, 1.0); 823 | node.setVAlign(node.topAlign); 824 | if (!Object.isImageLoaded(e)) { 825 | e.addEventListener('load', (function(node,top,h) { 826 | return function() { 827 | var idx = top.childNodes.indexOf(node); 828 | for (var i=idx+1; i> 7)); 68 | var exp = (((b1 << 1) & 0xff) | (b2 >> 7)) - 127; 69 | var sig = ((b2 & 0x7f) << 16) | (b3 << 8) | b4; 70 | if (sig == 0 && exp == -127) 71 | return 0.0; 72 | return sign * (1 + sig * Math.pow(2, -23)) * Math.pow(2, exp); 73 | }, 74 | readUInt32 : function(data, offset) { 75 | return ((data.charCodeAt(offset) & 0xFF) << 24) + 76 | ((data.charCodeAt(offset+1) & 0xFF) << 16) + 77 | ((data.charCodeAt(offset+2) & 0xFF) << 8) + 78 | (data.charCodeAt(offset+3) & 0xFF); 79 | }, 80 | readUInt16 : function(data, offset) { 81 | return ((data.charCodeAt(offset) & 0xFF) << 8) + 82 | (data.charCodeAt(offset+1) & 0xFF); 83 | }, 84 | readNormalizedFixedPoint16 : function(data, offset) { 85 | return 2.0 * (this.readUInt16(data, offset) / 65535.0 - 0.5); 86 | }, 87 | readNormalizedUFixedPoint16 : function(data, offset) { 88 | return this.readUInt16(data, offset) / 65535.0; 89 | }, 90 | 91 | readVerts : function(data, i, raw_vertices, vertCount) { 92 | for (var l=i+vertCount*3*2; i 0) { 155 | i = this.readTexVerts(data, i, raw_texcoords, texCount); 156 | i = this.readTris(data, i, tex_tris, quadCount, triCount); 157 | } 158 | if (normalCount > 0) { 159 | i = this.readVerts(data, i, raw_normals, normalCount); 160 | i = this.readTris(data, i, nor_tris, quadCount, triCount); 161 | } 162 | 163 | this.vertices = this.lookup_faces(raw_vertices, geo_tris, 3); 164 | if (tex_tris.length > 0 && !this.noTexCoords) 165 | this.texcoords = this.lookup_faces(raw_texcoords, tex_tris, 2); 166 | if (nor_tris.length > 0 && !this.overrideNormals) 167 | this.normals = this.lookup_faces(raw_normals, nor_tris, 3); 168 | else 169 | this.normals = this.calculate_normals(this.vertices, geo_tris, this.flatNormals); 170 | 171 | this.boundingBox = this.calculateBoundingBox(raw_vertices); 172 | this.parseTime = new Date() - t; 173 | }, 174 | 175 | calculateBoundingBox : function(raw_vertices) { 176 | var bbox = {min:[0,0,0], max:[0,0,0]}; 177 | for (var i=0; i bbox.max[0]) bbox.max[0] = x; 183 | if (y < bbox.min[1]) bbox.min[1] = y; 184 | else if (y > bbox.max[1]) bbox.max[1] = y; 185 | if (z < bbox.min[2]) bbox.min[2] = z; 186 | else if (z > bbox.max[2]) bbox.max[2] = z; 187 | } 188 | bbox.width = bbox.max[0] - bbox.min[0]; 189 | bbox.height = bbox.max[1] - bbox.min[1]; 190 | bbox.depth = bbox.max[2] - bbox.min[2]; 191 | bbox.diameter = Math.max(bbox.width, bbox.height, bbox.depth); 192 | return bbox; 193 | }, 194 | 195 | lookup_faces : function(verts, faces, sz) { 196 | var v = []; 197 | for (var i=0; i 3) { 94 | faces.push(faces[0]); 95 | faces.push(v); 96 | } 97 | v = vals[j]; 98 | faces.push(v); 99 | } 100 | for (var j=0; j 1) 105 | tex_faces.push(parseInt(a[1]) - 1); 106 | if (a.length > 2) 107 | nor_faces.push(parseInt(a[2]) - 1); 108 | } 109 | break; 110 | } 111 | } 112 | this.vertices = this.lookup_faces(raw_vertices, geo_faces, 3); 113 | if (tex_faces.length > 0) 114 | this.texcoords = this.lookup_faces(raw_texcoords, tex_faces, 2); 115 | if (nor_faces.length > 0 && !this.overrideNormals) 116 | this.normals = this.lookup_faces(raw_normals, nor_faces, 3); 117 | else 118 | this.normals = this.calculate_normals(this.vertices); 119 | var bbox = {min:[0,0,0], max:[0,0,0]}; 120 | for (var i=0; i bbox.max[0]) bbox.max[0] = x; 126 | if (y < bbox.min[1]) bbox.min[1] = y; 127 | else if (y > bbox.max[1]) bbox.max[1] = y; 128 | if (z < bbox.min[2]) bbox.min[2] = z; 129 | else if (z > bbox.max[2]) bbox.max[2] = z; 130 | } 131 | bbox.width = bbox.max[0] - bbox.min[0]; 132 | bbox.height = bbox.max[1] - bbox.min[1]; 133 | bbox.depth = bbox.max[2] - bbox.min[2]; 134 | bbox.diameter = Math.max(bbox.width, bbox.height, bbox.depth); 135 | this.boundingBox = bbox; 136 | this.parseTime = new Date() - t; 137 | }, 138 | 139 | lookup_faces : function(verts, faces, sz) { 140 | var v = []; 141 | for (var i=0; i 0) 170 | this.drawEffects(this.fbo, this.preEffects, this.fbo.texture,t,dt); 171 | 172 | var f = this.canvas ? this.supersample : 1; 173 | this.camera.draw(this.gl, this.width*f, this.height*f, this.root,t,dt); 174 | 175 | 176 | if (this.canvas) { 177 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); 178 | this.gl.depthMask(true); 179 | this.gl.clearColor(0,0,0,0); 180 | this.gl.clear(this.clearBits); 181 | Magi.throwError(this.gl, "clear"); 182 | } 183 | 184 | this.drawEffects(this.canvas||this.fbo, this.postEffects, this.fbo.texture,t,dt); 185 | 186 | 187 | this.drawTime = new Date() - t; 188 | 189 | this.updateFps(this.frameTimes, real_dt); 190 | if (!this.firstFrameDoneTime) this.firstFrameDoneTime = new Date(); 191 | this.changed = false; 192 | Magi.throwError(this.gl, "Scene draw loop"); 193 | if (this.showStats) { 194 | var stats = E.byId('stats'); 195 | if (stats) { 196 | Magi.Stats.print(stats); 197 | Magi.Stats.reset(); 198 | } 199 | } 200 | }, 201 | 202 | // applies effects (magi nodes) to tex and draws the result to target (fbo or canvas) 203 | // does not modify tex (unless it's the texture of target fbo) 204 | drawEffects : function(target, effects, tex,t,dt) { 205 | if (effects.length == 0 && tex == target.texture) 206 | return; 207 | var fbo = this.postFBO1; 208 | var postFBO = this.postFBO2; 209 | fbo.resize(target.width, target.height); 210 | postFBO.resize(target.width, target.height); 211 | for (var i=0; i 1000) 242 | frames.splice(200); 243 | var start = Math.max(0,frames.length-200); 244 | for (var i=start; i 0) ? (1/1.25) : 1.25; 269 | if (ev.shiftKey) { 270 | s.camera.targetFov *= ds; 271 | } else { 272 | s.camera.multiplyDistance(ds); 273 | } 274 | s.changed = true; 275 | ev.preventDefault(); 276 | }; 277 | s.camera.addFrameListener(function() { 278 | if (Math.abs(this.targetFov - this.fov) > 0.01) { 279 | s.changed = true; 280 | } 281 | }); 282 | cv.addEventListener('DOMMouseScroll', wheelHandler, false); 283 | cv.addEventListener('mousewheel', wheelHandler, false); 284 | 285 | cv.addEventListener('mousedown', function(ev){ 286 | s.dragging = true; 287 | s.sx = ev.clientX; 288 | s.sy = ev.clientY; 289 | ev.preventDefault(); 290 | }, false); 291 | window.addEventListener('mousemove', function(ev) { 292 | if (s.dragging) { 293 | var dx = ev.clientX - s.sx, dy = ev.clientY - s.sy; 294 | s.sx = ev.clientX, s.sy = ev.clientY; 295 | if (s.mouse.left) { 296 | xRot.rotation.angle += dx / 200; 297 | yRot.rotation.angle += dy / 200; 298 | } else if (s.mouse.middle) { 299 | yRot.position[0] += dx * 0.01 * (s.camera.fov / 45); 300 | yRot.position[1] -= dy * 0.01 * (s.camera.fov / 45); 301 | } 302 | 303 | var xa = xRot.rotation.angle; 304 | var ya = yRot.rotation.angle; 305 | var r = vec3.distance(s.camera.lookAt, s.camera.position); 306 | var v = vec3.scale(vec3.normalize(vec3.create(Math.cos(xa), Math.sin(ya), Math.sin(xa))), r); 307 | vec3.add(v, s.camera.lookAt, s.camera.position); 308 | 309 | ev.preventDefault(); 310 | s.changed = true; 311 | } 312 | }, false); 313 | window.addEventListener('mouseup', function(ev) { 314 | if (s.dragging) { 315 | s.dragging = false; 316 | ev.preventDefault(); 317 | } 318 | }, false); 319 | var xa = xRot.rotation.angle; 320 | var ya = yRot.rotation.angle; 321 | var r = vec3.distance(s.camera.lookAt, s.camera.position); 322 | var v = vec3.scale(vec3.normalize(vec3.create(Math.cos(xa), Math.sin(ya), Math.sin(xa))), r); 323 | vec3.add(v, s.camera.lookAt, s.camera.position); 324 | s.changed = true; 325 | } 326 | }); 327 | 328 | 329 | 330 | Magi.UberShader = Klass({ 331 | initialize : function(verts, frags) { 332 | this.verts = verts; 333 | this.frags = frags; 334 | this.shaderCache = []; 335 | }, 336 | 337 | build : function(keys, defines) { 338 | var defs = []; 339 | for (var i in defines) { 340 | defs.push("#define " + i + " " + defines[i]); 341 | } 342 | defs.sort(); 343 | var sk = keys.join("¤")+"¤"+defs.join("¤"); 344 | if (!this.shaderCache[sk]) { 345 | var vertVals = []; 346 | var fragVals = []; 347 | for (var i=0; i 1.0 || v.t < 0.0 || v.t > 1.0) c = vec4(0.0);"+ 728 | " gl_FragColor = c*c.a;"+ 729 | "}" 730 | )}, 731 | 732 | make : function(gl, fragmentShader) { 733 | var shader = new Magi.Filter(null, this.vert, fragmentShader||this.frag); 734 | return this.setupMaterial(shader); 735 | }, 736 | 737 | get : function(gl) { 738 | if (!this.cached) 739 | this.cached = this.make(gl); 740 | return this.cached.copy(); 741 | }, 742 | 743 | setupMaterial : function(shader) { 744 | var m = new Magi.Material(shader); 745 | m.textures.Texture0 = null; 746 | return m; 747 | } 748 | }; 749 | 750 | Magi.FlipFilterMaterial = Object.clone(Magi.FilterMaterial); 751 | Magi.FlipFilterMaterial.vert = {type: 'VERTEX_SHADER', text: ( 752 | Magi.ShaderLib.defaultTransform+ 753 | "void main()"+ 754 | "{"+ 755 | " defaultTransform();"+ 756 | "}" 757 | )}; 758 | 759 | Magi.FilterQuadMaterial = Object.clone(Magi.FilterMaterial); 760 | Magi.FilterQuadMaterial.vert = {type: 'VERTEX_SHADER', text: ( 761 | Magi.ShaderLib.defaultTransform+ 762 | "void main()"+ 763 | "{"+ 764 | " vec4 v = vec4(Vertex, 1.0);"+ 765 | " texCoord0 = texCoord();"+ 766 | " gl_Position = v;"+ 767 | "}" 768 | )}; 769 | 770 | Magi.FlipFilterQuadMaterial = Object.clone(Magi.FilterMaterial); 771 | Magi.FlipFilterQuadMaterial.vert = {type: 'VERTEX_SHADER', text: ( 772 | Magi.ShaderLib.defaultTransform+ 773 | "void main()"+ 774 | "{"+ 775 | " vec4 v = vec4(Vertex, 1.0);"+ 776 | " texCoord0 = flipTexCoord();"+ 777 | " gl_Position = v;"+ 778 | "}" 779 | )}; 780 | 781 | Magi.IdFilterMaterial = Object.clone(Magi.FilterQuadMaterial); 782 | Magi.IdFilterMaterial.frag = {type: 'FRAGMENT_SHADER', text: ( 783 | "precision highp float;"+ 784 | "uniform sampler2D Texture0;"+ 785 | "varying vec2 texCoord0;"+ 786 | "void main()"+ 787 | "{"+ 788 | " vec4 c = texture2D(Texture0, texCoord0);"+ 789 | " gl_FragColor = c;"+ 790 | "}" 791 | )}; 792 | 793 | Magi.RadialGlowMaterial = Object.clone(Magi.FilterQuadMaterial); 794 | Magi.RadialGlowMaterial.frag = {type:'FRAGMENT_SHADER', text: ( 795 | "precision highp float;"+ 796 | "uniform sampler2D Texture0;"+ 797 | "varying vec2 texCoord0;"+ 798 | "uniform vec2 center;"+ 799 | "uniform float radius;"+ 800 | "uniform float currentFactor;"+ 801 | "uniform float intensity;"+ 802 | "uniform float falloff;"+ 803 | "void main()"+ 804 | "{"+ 805 | " float samples = 8.0;"+ 806 | " float len = length(center - texCoord0);"+ 807 | " float rs = len*radius/samples;"+ 808 | " vec2 dir = rs * normalize(center - texCoord0);"+ 809 | " vec4 c = currentFactor * texture2D(Texture0, texCoord0);"+ 810 | " float d = intensity;"+ 811 | " float count = 0.0;"+ 812 | " float ran = 1.0 + 0.01*sin(texCoord0.t*123.489);"+ 813 | " for (float r=1.0; r <= 8.0; r++) {"+ 814 | " vec2 tc = texCoord0 + (r*dir) * ran;"+ 815 | " vec4 pc = texture2D(Texture0, tc + rs);"+ 816 | " c += pc*d;"+ 817 | " count += d;"+ 818 | " d *= falloff;"+ 819 | " }"+ 820 | " c /= count;"+ 821 | " c.a = 1.0;"+ 822 | " gl_FragColor = c;"+ 823 | "}" 824 | )}; 825 | Magi.RadialGlowMaterial.setupMaterial = function(shader) { 826 | var m = new Magi.Material(shader); 827 | m.textures.Texture0 = null; 828 | m.floats.center = vec2.create(0.5, -0.2); 829 | m.floats.radius = 0.04; 830 | m.floats.intensity = 1.0; 831 | m.floats.falloff = 0.87; 832 | m.floats.currentFactor = 0.0; 833 | return m; 834 | } 835 | Magi.FlipRadialGlowMaterial = Object.clone(Magi.RadialGlowMaterial); 836 | Magi.FlipRadialGlowMaterial.vert = Magi.FlipFilterQuadMaterial.vert; 837 | 838 | 839 | Magi.ChromaticAberrationMaterial = Object.clone(Magi.FilterQuadMaterial); 840 | Magi.ChromaticAberrationMaterial.frag = {type:'FRAGMENT_SHADER', text: ( 841 | "precision highp float;"+ 842 | "uniform sampler2D Texture0;"+ 843 | "varying vec2 texCoord0;"+ 844 | "uniform vec2 center;"+ 845 | "uniform float radius;"+ 846 | "void main()"+ 847 | "{"+ 848 | " vec2 shift = radius * (center - texCoord0);"+ 849 | " vec4 r = texture2D(Texture0, texCoord0+shift);"+ 850 | " vec4 g = texture2D(Texture0, texCoord0);"+ 851 | " vec4 b = texture2D(Texture0, texCoord0-shift);"+ 852 | " gl_FragColor = vec4(r.r, g.g, b.b, g.a);"+ 853 | "}" 854 | )}; 855 | Magi.ChromaticAberrationMaterial.setupMaterial = function(shader) { 856 | var m = new Magi.Material(shader); 857 | m.textures.Texture0 = null; 858 | m.floats.center = vec2.create(0.5, 0.5); 859 | m.floats.radius = 0.01; 860 | return m; 861 | } 862 | Magi.FlipChromaticAberrationMaterial = Object.clone(Magi.ChromaticAberrationMaterial); 863 | Magi.FlipChromaticAberrationMaterial.vert = Magi.FlipFilterQuadMaterial.vert; 864 | 865 | 866 | Magi.GrayscaleMaterial = Object.clone(Magi.FilterQuadMaterial); 867 | Magi.GrayscaleMaterial.frag = {type:'FRAGMENT_SHADER', text: ( 868 | "precision highp float;"+ 869 | "uniform sampler2D Texture0;"+ 870 | "varying vec2 texCoord0;"+ 871 | "void main()"+ 872 | "{"+ 873 | " vec4 c = texture2D(Texture0, texCoord0);"+ 874 | " c.r = c.g = c.b = 0.2126*c.r + 0.7152*c.g + 0.0722*c.b;"+ 875 | " gl_FragColor = c;"+ 876 | "}" 877 | )}; 878 | Magi.GrayscaleMaterial.setupMaterial = function(shader) { 879 | var m = new Magi.Material(shader); 880 | m.textures.Texture0 = null; 881 | return m; 882 | } 883 | Magi.FlipGrayscaleMaterial = Object.clone(Magi.GrayscaleMaterial); 884 | Magi.FlipGrayscaleMaterial.vert = Magi.FlipFilterQuadMaterial.vert; 885 | 886 | 887 | Magi.ThresholdMaterial = Object.clone(Magi.FilterQuadMaterial); 888 | Magi.ThresholdMaterial.frag = {type:'FRAGMENT_SHADER', text: ( 889 | "precision highp float;"+ 890 | "uniform sampler2D Texture0;"+ 891 | "varying vec2 texCoord0;"+ 892 | "uniform float threshold;"+ 893 | "uniform vec4 highColor;"+ 894 | "uniform vec4 lowColor;"+ 895 | "void main()"+ 896 | "{"+ 897 | " vec4 c = texture2D(Texture0, texCoord0);"+ 898 | " if (0.2126*c.r + 0.7152*c.g + 0.0722*c.b > threshold) {"+ 899 | " gl_FragColor = highColor;"+ 900 | " } else {"+ 901 | " gl_FragColor = lowColor;"+ 902 | " }"+ 903 | "}" 904 | )}; 905 | Magi.ThresholdMaterial.setupMaterial = function(shader) { 906 | var m = new Magi.Material(shader); 907 | m.textures.Texture0 = null; 908 | m.floats.lowColor = vec4.create(0.0, 0.0, 0.0, 1.0); 909 | m.floats.highColor = vec4.create(1.0, 1.0, 1.0, 1.0); 910 | m.floats.threshold = 0.5; 911 | return m; 912 | } 913 | Magi.FlipThresholdMaterial = Object.clone(Magi.ThresholdMaterial); 914 | Magi.FlipThresholdMaterial.vert = Magi.FlipFilterQuadMaterial.vert; 915 | 916 | 917 | Magi.Convolution3x3Material = Object.clone(Magi.FilterQuadMaterial); 918 | Magi.Convolution3x3Material.frag = {type:'FRAGMENT_SHADER', text: ( 919 | "precision highp float;"+ 920 | "uniform sampler2D Texture0;"+ 921 | "varying vec2 texCoord0;"+ 922 | "uniform vec2 PixelSize;"+ 923 | "uniform mat3 Kernel;"+ 924 | "void main()"+ 925 | "{"+ 926 | " vec4 c = vec4(0.0);"+ 927 | " c += Kernel[0][0] * texture2D(Texture0, texCoord0+vec2(-PixelSize.x, -PixelSize.y));"+ 928 | " c += Kernel[0][1] * texture2D(Texture0, texCoord0+vec2(-PixelSize.x, 0.0));"+ 929 | " c += Kernel[0][2] * texture2D(Texture0, texCoord0+vec2(-PixelSize.x, PixelSize.y));"+ 930 | " c += Kernel[1][0] * texture2D(Texture0, texCoord0+vec2( 0.0, -PixelSize.y));"+ 931 | " c += Kernel[1][1] * texture2D(Texture0, texCoord0+vec2( 0.0, 0.0));"+ 932 | " c += Kernel[1][2] * texture2D(Texture0, texCoord0+vec2( 0.0, PixelSize.y));"+ 933 | " c += Kernel[2][0] * texture2D(Texture0, texCoord0+vec2( PixelSize.x, -PixelSize.y));"+ 934 | " c += Kernel[2][1] * texture2D(Texture0, texCoord0+vec2( PixelSize.x, 0.0));"+ 935 | " c += Kernel[2][2] * texture2D(Texture0, texCoord0+vec2( PixelSize.x, PixelSize.y));"+ 936 | " c.a = 1.0;"+ 937 | " gl_FragColor = c;"+ 938 | "}" 939 | )}; 940 | Magi.Convolution3x3Material.setupMaterial = function(shader) { 941 | var m = new Magi.Material(shader); 942 | m.textures.Texture0 = null; 943 | m.floats.PixelSize = vec2.create(1/500, 1/500); 944 | m.floats.Kernel = mat3.identity(); 945 | return m; 946 | } 947 | Magi.FlipConvolution3x3Material = Object.clone(Magi.Convolution3x3Material); 948 | Magi.FlipConvolution3x3Material.vert = Magi.FlipFilterQuadMaterial.vert; 949 | 950 | 951 | Magi.CubeArrayMaterial = Object.clone(Magi.FilterMaterial); 952 | Magi.CubeArrayMaterial.vert = {type: 'VERTEX_SHADER', text: ( 953 | Magi.ShaderLib.defaultTransform+ 954 | "uniform float width;"+ 955 | "uniform float height;"+ 956 | "varying vec2 texCoord0;"+ 957 | "float grid(float c, float sz)"+ 958 | "{"+ 959 | " return (0.5+floor(c*sz))/sz;"+ 960 | "}"+ 961 | "void main()"+ 962 | "{"+ 963 | " texCoord0 = vec2(grid(TexCoord.s, width), grid(1.0-TexCoord.t, height));"+ 964 | " if (texture2D(Texture0, texCoord0).a == 0.0) {"+ 965 | " gl_Position = vec4(-3.0, -3.0, -3.0, 1.0);"+ 966 | " } else {"+ 967 | " gl_Position = transform();"+ 968 | " }"+ 969 | "}" 970 | )}; 971 | 972 | 973 | Magi.ColorQuadMaterial = Object.clone(Magi.FilterMaterial); 974 | Magi.ColorQuadMaterial.vert = {type: 'VERTEX_SHADER', text: ( 975 | Magi.ShaderLib.defaultTransform+ 976 | "void main()"+ 977 | "{"+ 978 | " vec4 v = vec4(Vertex, 1.0);"+ 979 | " gl_Position = v;"+ 980 | "}" 981 | )}; 982 | Magi.ColorQuadMaterial.frag = {type: 'FRAGMENT_SHADER', text: ( 983 | "precision highp float;"+ 984 | "uniform vec4 Color;"+ 985 | "void main()"+ 986 | "{"+ 987 | " gl_FragColor = Color;"+ 988 | "}" 989 | )}; 990 | 991 | Magi.ColorMaterial = Object.clone(Magi.FilterMaterial); 992 | Magi.ColorMaterial.vert = {type: 'VERTEX_SHADER', text: ( 993 | Magi.ShaderLib.defaultTransform+ 994 | "void main()"+ 995 | "{"+ 996 | " gl_Position = transform();"+ 997 | "}" 998 | )}; 999 | Magi.ColorMaterial.frag = {type: 'FRAGMENT_SHADER', text: ( 1000 | "precision highp float;"+ 1001 | "uniform vec4 Color;"+ 1002 | "void main()"+ 1003 | "{"+ 1004 | " gl_FragColor = Color;"+ 1005 | "}" 1006 | )}; 1007 | 1008 | Magi.DefaultMaterial = { 1009 | vert : {type: 'VERTEX_SHADER', text: ( 1010 | "precision highp float;"+ 1011 | "attribute vec3 Vertex;"+ 1012 | "attribute vec3 Normal;"+ 1013 | "attribute vec2 TexCoord;"+ 1014 | "uniform mat4 PMatrix;"+ 1015 | "uniform mat4 MVMatrix;"+ 1016 | "uniform mat4 LightMatrix;"+ 1017 | "uniform mat3 NMatrix;"+ 1018 | "uniform float LightConstantAtt;"+ 1019 | "uniform float LightLinearAtt;"+ 1020 | "uniform float LightQuadraticAtt;"+ 1021 | "uniform vec4 LightPos;"+ 1022 | "varying vec3 normal, lightDir, eyeVec;"+ 1023 | "varying vec2 texCoord0;"+ 1024 | "varying float attenuation;"+ 1025 | "void main()"+ 1026 | "{"+ 1027 | " vec3 lightVector;"+ 1028 | " vec4 v = vec4(Vertex, 1.0);"+ 1029 | " texCoord0 = vec2(TexCoord.s, 1.0-TexCoord.t);"+ 1030 | " normal = normalize(NMatrix * Normal);"+ 1031 | " vec4 worldPos = MVMatrix * v;"+ 1032 | " vec4 lightWorldPos = LightMatrix * LightPos;"+ 1033 | " lightVector = vec3(lightWorldPos - worldPos);"+ 1034 | " lightDir = normalize(lightVector);"+ 1035 | " float dist = length(lightVector);"+ 1036 | " eyeVec = -vec3(worldPos);"+ 1037 | " attenuation = 1.0 / (1.0 + LightConstantAtt + LightLinearAtt*dist + LightQuadraticAtt * dist*dist);"+ 1038 | " gl_Position = PMatrix * worldPos;"+ 1039 | "}" 1040 | )}, 1041 | 1042 | frag : {type: 'FRAGMENT_SHADER', text: ( 1043 | "precision highp float;"+ 1044 | "uniform vec4 LightDiffuse;"+ 1045 | "uniform vec4 LightSpecular;"+ 1046 | "uniform vec4 LightAmbient;"+ 1047 | "uniform vec4 MaterialSpecular;"+ 1048 | "uniform vec4 MaterialDiffuse;"+ 1049 | "uniform vec4 MaterialAmbient;"+ 1050 | "uniform vec4 MaterialEmit;"+ 1051 | "uniform vec4 GlobalAmbient;"+ 1052 | "uniform float MaterialShininess;"+ 1053 | "uniform sampler2D DiffTex, SpecTex, EmitTex;"+ 1054 | "varying vec3 normal, lightDir, eyeVec;"+ 1055 | "varying vec2 texCoord0;"+ 1056 | "varying float attenuation;"+ 1057 | "void main()"+ 1058 | "{"+ 1059 | " vec4 color = GlobalAmbient * LightAmbient * MaterialAmbient;"+ 1060 | " vec4 matDiff = MaterialDiffuse + texture2D(DiffTex, texCoord0);"+ 1061 | " matDiff.a = 1.0 - (1.0-MaterialDiffuse.a) * (1.0-texture2D(DiffTex, texCoord0).a);"+ 1062 | " vec4 matSpec = MaterialSpecular + texture2D(SpecTex, texCoord0);"+ 1063 | " matSpec.a = 1.0 - (1.0-MaterialSpecular.a) * (1.0-texture2D(SpecTex, texCoord0).a);"+ 1064 | " vec4 diffuse = LightDiffuse * matDiff;"+ 1065 | " float lambertTerm = dot(normal, lightDir);"+ 1066 | " vec4 lcolor = diffuse * lambertTerm * attenuation;"+ 1067 | " vec3 E = normalize(eyeVec);"+ 1068 | " vec3 R = reflect(-lightDir, normal);"+ 1069 | " float specular = pow( max(dot(R, E), 0.0), MaterialShininess );"+ 1070 | " lcolor += matSpec * LightSpecular * specular * attenuation;"+ 1071 | " if (lambertTerm > 0.0) color += lcolor * lambertTerm;"+ 1072 | " else color += diffuse * attenuation * MaterialAmbient.a * -lambertTerm;"+ 1073 | " color += MaterialEmit + texture2D(EmitTex, texCoord0);" + 1074 | " color *= matDiff.a;"+ 1075 | " color.a = matDiff.a;"+ 1076 | " gl_FragColor = color;"+ 1077 | "}" 1078 | )}, 1079 | 1080 | get : function(gl) { 1081 | if (!this.cached) { 1082 | var shader = new Magi.Shader(null, this.vert, this.frag); 1083 | this.cached = this.setupMaterial(shader); 1084 | } 1085 | var c = this.cached.copy(); 1086 | c.floats.LightMatrix = this.lightMatrix; 1087 | return c; 1088 | }, 1089 | 1090 | lightMatrix : mat4.identity(), 1091 | 1092 | setupMaterial : function(shader) { 1093 | var m = new Magi.Material(shader); 1094 | m.textures.DiffTex = m.textures.SpecTex = m.textures.EmitTex = null; 1095 | m.floats.MaterialSpecular = vec4.create([1, 1, 1, 0]); 1096 | m.floats.MaterialDiffuse = vec4.create([0.5, 0.5, 0.5, 1]); 1097 | m.floats.MaterialAmbient = vec4.create([1, 1, 1, 0.3]); 1098 | m.floats.MaterialEmit = vec4.create([0, 0, 0, 0]); 1099 | m.floats.MaterialShininess = 1.5; 1100 | m.floats.LightMatrix = this.lightMatrix; 1101 | 1102 | m.floats.LightPos = vec4.create([1, 1, 1, 1.0]); 1103 | m.floats.GlobalAmbient = vec4.create([1, 1, 1, 1]); 1104 | m.floats.LightSpecular = vec4.create([0.8, 0.8, 0.95, 1]); 1105 | m.floats.LightDiffuse = vec4.create([0.7, 0.6, 0.9, 1]); 1106 | m.floats.LightAmbient = vec4.create([0.1, 0.10, 0.2, 1]); 1107 | m.floats.LightConstantAtt = 0.0; 1108 | m.floats.LightLinearAtt = 0.0; 1109 | m.floats.LightQuadraticAtt = 0.0; 1110 | return m; 1111 | } 1112 | 1113 | } 1114 | 1115 | 1116 | Magi.MultiMaterial = { 1117 | frag : { 1118 | type: Magi.DefaultMaterial.frag.type, 1119 | text: Magi.DefaultMaterial.frag.text.replace(/uniform (\S+ Material)/g, 'varying $1') 1120 | }, 1121 | vert : {type: 'VERTEX_SHADER', text: ( 1122 | "#define MAX_MATERIALS 4\n"+ 1123 | "precision highp float;"+ 1124 | "precision highp int;"+ 1125 | "struct material {"+ 1126 | " vec4 diffuse; vec4 specular; vec4 ambient; vec4 emit; float shininess;"+ 1127 | "};"+ 1128 | "attribute vec3 Vertex;"+ 1129 | "attribute vec3 Normal;"+ 1130 | "attribute vec2 TexCoord;"+ 1131 | "attribute float MaterialIndex;"+ 1132 | "uniform mat4 PMatrix;"+ 1133 | "uniform mat4 MVMatrix;"+ 1134 | "uniform mat4 LightMatrix;"+ 1135 | "uniform mat3 NMatrix;"+ 1136 | "uniform float LightConstantAtt;"+ 1137 | "uniform float LightLinearAtt;"+ 1138 | "uniform float LightQuadraticAtt;"+ 1139 | "uniform vec4 LightPos;"+ 1140 | "uniform material Material0;"+ 1141 | "uniform material Material1;"+ 1142 | "uniform material Material2;"+ 1143 | "uniform material Material3;"+ 1144 | "varying vec3 normal, lightDir, eyeVec;"+ 1145 | "varying vec2 texCoord0;"+ 1146 | "varying float attenuation;"+ 1147 | "varying vec4 MaterialDiffuse;"+ 1148 | "varying vec4 MaterialSpecular;"+ 1149 | "varying vec4 MaterialAmbient;"+ 1150 | "varying vec4 MaterialEmit;"+ 1151 | "varying float MaterialShininess;"+ 1152 | "void main()"+ 1153 | "{"+ 1154 | " vec3 lightVector;"+ 1155 | " vec4 v = vec4(Vertex, 1.0);"+ 1156 | " texCoord0 = vec2(TexCoord.s, 1.0-TexCoord.t);"+ 1157 | " normal = normalize(NMatrix * Normal);"+ 1158 | " vec4 worldPos = MVMatrix * v;"+ 1159 | " vec4 lightWorldPos = LightMatrix * LightPos;"+ 1160 | " lightVector = vec3(lightWorldPos - worldPos);"+ 1161 | " lightDir = normalize(lightVector);"+ 1162 | " float dist = length(lightVector);"+ 1163 | " eyeVec = normalize(-vec3(worldPos));"+ 1164 | " attenuation = 1.0 / (1.0 + LightConstantAtt + LightLinearAtt*dist + LightQuadraticAtt * dist*dist);"+ 1165 | " gl_Position = PMatrix * worldPos;"+ 1166 | " float midx = MaterialIndex;"+ 1167 | " material mat = Material3;"+ 1168 | " if (midx == 0.0) mat = Material0;"+ 1169 | " if (midx == 1.0) mat = Material1;"+ 1170 | " if (midx == 2.0) mat = Material2;"+ 1171 | " MaterialDiffuse = mat.diffuse;"+ 1172 | " MaterialSpecular = mat.specular;"+ 1173 | " MaterialAmbient = mat.ambient;"+ 1174 | " MaterialEmit = mat.emit;"+ 1175 | " MaterialShininess = mat.shininess;"+ 1176 | "}" 1177 | )}, 1178 | 1179 | get : function(gl) { 1180 | if (!this.cached) { 1181 | var shader = new Magi.Shader(null, this.vert, this.frag); 1182 | this.cached = this.setupMaterial(shader); 1183 | } 1184 | var c = this.cached.copy(); 1185 | c.floats.LightMatrix = this.lightMatrix; 1186 | return c; 1187 | }, 1188 | 1189 | lightMatrix : mat4.identity(), 1190 | 1191 | setupMaterial : function(shader) { 1192 | var m = new Magi.Material(shader); 1193 | m.textures.DiffTex = m.textures.SpecTex = m.textures.EmitTex = null; 1194 | 1195 | m.floats.LightMatrix = this.lightMatrix; 1196 | 1197 | m.floats.LightPos = vec4.create([1, 1, 1, 1.0]); 1198 | m.floats.GlobalAmbient = vec4.create([1, 1, 1, 1]); 1199 | m.floats.LightSpecular = vec4.create([1, 1, 1, 1]); 1200 | m.floats.LightDiffuse = vec4.create([1, 1, 1, 1]); 1201 | m.floats.LightAmbient = vec4.create([0.1, 0.1, 0.1, 1]); 1202 | m.floats.LightConstantAtt = 0.0; 1203 | m.floats.LightLinearAtt = 0.0; 1204 | m.floats.LightQuadraticAtt = 0.0; 1205 | return m; 1206 | } 1207 | 1208 | } 1209 | -------------------------------------------------------------------------------- /src/scenegraph.js: -------------------------------------------------------------------------------- 1 | Magi.Motion = { 2 | makeBounce : function() { 3 | this.addFrameListener(function(t, dt) { 4 | var y = 2*Math.abs(Math.sin(t / 500)); 5 | this.position[1] = y; 6 | }); 7 | return this; 8 | }, 9 | 10 | makeRotate : function(speed) { 11 | speed = speed || 0.2; 12 | this.addFrameListener(function(t,dt) { 13 | this.rotation.angle = (Math.PI*2*t / (1000/speed)) % (Math.PI*2); 14 | }); 15 | return this; 16 | } 17 | }; 18 | 19 | Magi.Node = Klass(Magi.Motion, { 20 | model : null, 21 | position : null, 22 | rotation : null, 23 | scaling : null, 24 | polygonOffset : null, 25 | scaleAfterRotate : false, 26 | depthMask : null, 27 | depthTest : null, 28 | display : true, 29 | transparent : false, 30 | id : null, 31 | parentNode : null, 32 | 33 | initialize : function(model) { 34 | this.model = model; 35 | this.absolutePosition = vec3.create(); 36 | this.renderPasses = {normal: true}; 37 | this.material = new Magi.Material(); 38 | this.matrix = mat4.identity(); 39 | this.normalMatrix = mat3.identity(); 40 | this.rotation = {angle : 0, axis : vec3.create([0,1,0])}; 41 | this.position = vec3.create([0, 0, 0]); 42 | this.scaling = vec3.create([1, 1, 1]); 43 | this.frameListeners = []; 44 | this.childNodes = []; 45 | this.afterTransformListeners = []; 46 | }, 47 | 48 | getNodeById : function(name) { 49 | var found = null; 50 | try { 51 | this.filterNodes(function(n){ if (n.id == name) { found=n; throw(null); } }); 52 | } catch(e) { 53 | return found; 54 | } 55 | }, 56 | 57 | getNodesById : function(name) { 58 | return this.filterNodes(function(n){ return (n.id == name); }); 59 | }, 60 | 61 | getNodesByKlass : function(klass) { 62 | return this.filterNodes(function(n){ return (n instanceof klass); }); 63 | }, 64 | 65 | getNodesByMethod : function(name) { 66 | return this.filterNodes(function(n){ return n[name]; }); 67 | }, 68 | 69 | getNodesByKeyValue : function(key, value) { 70 | return this.filterNodes(function(n){ return n[key] == value; }); 71 | }, 72 | 73 | filterNodes : function(f) { 74 | var nodes = []; 75 | this.forEach(function(n) { 76 | if (f(n)) nodes.push(n); 77 | }); 78 | return nodes; 79 | }, 80 | 81 | forEach : function(f) { 82 | f.call(this,this); 83 | this.childNodes.forEach(function(cn){ 84 | cn.forEach(f); 85 | }); 86 | }, 87 | 88 | setX : function(x) { 89 | this.position[0] = x; 90 | return this; 91 | }, 92 | 93 | setY : function(x) { 94 | this.position[1] = x; 95 | return this; 96 | }, 97 | 98 | setZ : function(x) { 99 | this.position[2] = x; 100 | return this; 101 | }, 102 | 103 | setPosition : function(x,y,z) { 104 | if (x.length != null) { 105 | vec3.set(x, this.position); 106 | } else { 107 | if (y == null) { 108 | vec3.set3(x, this.position) 109 | } else { 110 | this.position[0] = x; 111 | this.position[1] = y; 112 | if (z != null) 113 | this.position[2] = z; 114 | } 115 | } 116 | return this; 117 | }, 118 | 119 | setScale : function(x,y,z) { 120 | if (x.length != null) { 121 | vec3.set(x, this.scaling); 122 | } else { 123 | if (y == null) { 124 | vec3.set3(x, this.scaling) 125 | } else { 126 | this.scaling[0] = x; 127 | this.scaling[1] = y; 128 | if (z != null) 129 | this.scaling[2] = z; 130 | } 131 | } 132 | return this; 133 | }, 134 | 135 | setAngle : function(a) { 136 | this.rotation.angle = a; 137 | return this; 138 | }, 139 | 140 | setAxis : function(x,y,z) { 141 | if (x.length != null) { 142 | vec3.set(x, this.rotation.axis); 143 | } else { 144 | if (y == null) { 145 | vec3.set3(x, this.rotation.axis) 146 | } else { 147 | this.rotation.axis[0] = x; 148 | this.rotation.axis[1] = y; 149 | if (z != null) 150 | this.rotation.axis[2] = z; 151 | } 152 | } 153 | return this; 154 | }, 155 | 156 | draw : function(gl, state, perspectiveMatrix) { 157 | if (!this.model || !this.display) return; 158 | if (this.material) { 159 | this.material.apply(gl, state, perspectiveMatrix, this.matrix, this.normalMatrix); 160 | } 161 | if (this.model.gl == null) this.model.gl = gl; 162 | var psrc = state.blendFuncSrc; 163 | var pdst = state.blendFuncDst; 164 | var dm = state.depthMask; 165 | var dt = state.depthTest; 166 | var poly = state.polygonOffset; 167 | var bl = state.blend; 168 | var cl = state.cullFace; 169 | if (this.polygonOffset) { 170 | gl.polygonOffset(this.polygonOffset.factor, this.polygonOffset.units); 171 | } 172 | if (this.depthMask != null && this.depthMask != state.depthMask) { 173 | gl.depthMask(this.depthMask); 174 | } 175 | if (this.depthTest != null && this.depthTest != state.depthTest) { 176 | if (this.depthTest) 177 | gl.enable(gl.DEPTH_TEST); 178 | else 179 | gl.disable(gl.DEPTH_TEST); 180 | } 181 | if (this.blendFuncSrc && this.blendFuncDst) { 182 | gl.blendFunc(gl[this.blendFuncSrc], gl[this.blendFuncDst]); 183 | } 184 | if (this.blend != null && this.blend != state.blend) { 185 | if (this.blend) gl.enable(gl.BLEND); 186 | else gl.disable(gl.BLEND); 187 | } 188 | if (this.cullFace != null && this.cullFace != state.cullFace) { 189 | gl.enable(gl.CULL_FACE); 190 | gl.cullFace(gl[this.cullFace]); 191 | } 192 | 193 | if (this.model.attributes) { 194 | if (!this.model.attributeIdxs) { 195 | this.model.attributeIdxs = []; 196 | } 197 | for (var i=0; i= offset + 512) { 127 | var header = this.fileArray.length == 0 ? null : this.fileArray[this.fileArray.length-1]; 128 | if (header && header.data == null) { 129 | if (offset + header.length <= totalSize) { 130 | header.data = this.chunkSubstring(chunks, offset, offset+header.length); 131 | header.toDataURL = this.__toDataURL; 132 | offset += 512 * Math.ceil(header.length / 512); 133 | if (this.onstream) 134 | this.onstream(header, this.gzip); 135 | } else { // not loaded yet 136 | break; 137 | } 138 | } else { 139 | var s = this.chunkSubstring(chunks, offset, offset+512); 140 | var header = this.parseTarHeader(s, 0); 141 | if (header.length > 0 || header.filename != '') { 142 | this.fileArray.push(header); 143 | this.files[header.filename] = header; 144 | offset += 512; 145 | header.offset = offset; 146 | } else { // empty header, stop processing 147 | offset = totalSize; 148 | } 149 | } 150 | } 151 | this.parseTime += new Date() - t; 152 | return offset; 153 | }, 154 | 155 | parseTarHeader : function(text, offset) { 156 | var i = offset || 0; 157 | var h = {}; 158 | h.filename = this.parseTarField(text, i, i+=100); 159 | h.mode = this.parseTarNumber(text, i, i+=8); 160 | h.uid = this.parseTarNumber(text, i, i+=8); 161 | h.gid = this.parseTarNumber(text, i, i+=8); 162 | h.length = this.parseTarNumber(text, i, i+=12); 163 | h.lastModified = this.parseTarNumber(text, i, i+=12); 164 | h.checkSum = this.parseTarField(text, i, i+=8); 165 | h.fileType = this.parseTarField(text, i, i+=1); 166 | h.linkName = this.parseTarField(text, i, i+=100); 167 | h.ustar = this.parseTarField(text, i, i+=6); 168 | h.ustarVersion = this.parseTarField(text, i, i+=2); 169 | h.userName = this.parseTarField(text, i, i+=32); 170 | h.groupName = this.parseTarField(text, i, i+=32); 171 | h.deviceMajor = this.parseTarField(text, i, i+=8); 172 | h.deviceMinor = this.parseTarField(text, i, i+=8); 173 | h.filenamePrefix = this.parseTarField(text, i, i+=155); 174 | return h; 175 | }, 176 | 177 | parseTarField : function(text, start, end) { 178 | return text.substring(start, end).split("\0", 1)[0]; 179 | }, 180 | 181 | parseTarNumber : function(text, start, end) { 182 | var s = text.substring(start, end); 183 | // if (s.charCodeAt(0) & 0x80 == 1) { 184 | // GNU tar 8-byte binary big-endian number 185 | // } else { 186 | return parseInt('0'+s.replace(/[^\d]+/g, '')); 187 | // } 188 | }, 189 | 190 | // extract substring from an array of strings 191 | chunkSubstring : function (chunks, start, end) { 192 | var soff=0, eoff=0, i=0, j=0; 193 | for (i=0; i start) 195 | break; 196 | soff += chunks[i].length; 197 | } 198 | var strs = []; 199 | eoff = soff; 200 | for (j=i; j end) 203 | break; 204 | eoff += chunks[j].length; 205 | } 206 | var s = strs.join(''); 207 | return s.substring(start-soff, start-soff+(end-start)); 208 | }, 209 | 210 | __toDataURL : function() { 211 | if (this.data.substring(0,40).match(/^data:[^\/]+\/[^,]+,/)) { 212 | return this.data; 213 | } else if (Magi.Tar.prototype.cleanHighByte(this.data.substring(0,10)).match(/\377\330\377\340..JFIF/)) { 214 | return 'data:image/jpeg;base64,'+btoa(Magi.Tar.prototype.cleanHighByte(this.data)); 215 | } else if (Magi.Tar.prototype.cleanHighByte(this.data.substring(0,6)) == "\211PNG\r\n") { 216 | return 'data:image/png;base64,'+btoa(Magi.Tar.prototype.cleanHighByte(this.data)); 217 | } else if (Magi.Tar.prototype.cleanHighByte(this.data.substring(0,6)).match(/GIF8[79]a/)) { 218 | return 'data:image/gif;base64,'+btoa(Magi.Tar.prototype.cleanHighByte(this.data)); 219 | } else { 220 | throw("toDataURL: I don't know how to handle " + this.filename); 221 | } 222 | } 223 | } 224 | 225 | 226 | --------------------------------------------------------------------------------