├── sdftrace.jpg ├── README.md ├── py ├── framework.py └── sdf.py ├── js ├── libs │ ├── stats.min.js │ ├── Detector.js │ └── dat.gui.min.js ├── renderer.js ├── fastliner.js └── sdf.js └── index.html /sdftrace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-westreicher/sdftrace/HEAD/sdftrace.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sdftrace 2 | A signed distance field tracer in the browser. 3 | 4 | [Try it!](http://david-westreicher.github.io/sdftrace/) 5 | 6 | ![screenshot](/sdftrace.jpg?raw=true "screenshot") 7 | -------------------------------------------------------------------------------- /py/framework.py: -------------------------------------------------------------------------------- 1 | import pygtk 2 | import gtk, gobject,cairo 3 | 4 | class Screen(gtk.DrawingArea): 5 | 6 | # Draw in response to an expose-event 7 | __gsignals__ = { "expose-event": "override" } 8 | 9 | # Handle the expose-event by drawing 10 | def do_expose_event(self, event): 11 | 12 | # Create the cairo context 13 | cr = self.window.cairo_create() 14 | 15 | # Restrict Cairo to the exposed area; avoid extra work 16 | cr.rectangle(event.area.x, event.area.y, 17 | event.area.width, event.area.height) 18 | cr.clip() 19 | 20 | self.draw(cr, *self.window.get_size()) 21 | 22 | def draw(self, cr, width, height): 23 | # Fill the background with gray 24 | cr.set_source_rgb(0.5, 0.5, 0.5) 25 | cr.rectangle(0, 0, width, height) 26 | cr.fill() 27 | 28 | # GTK mumbo-jumbo to show the widget in a window and quit when it's closed 29 | def run(Widget,width,height): 30 | window = gtk.Window() 31 | window.resize(width,height) 32 | window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_SPLASHSCREEN) 33 | window.connect("delete-event", gtk.main_quit) 34 | window.connect("key-press-event", gtk.main_quit) 35 | widget = Widget() 36 | widget.show() 37 | window.add(widget) 38 | window.present() 39 | widget.add_events(gtk.gdk.POINTER_MOTION_MASK) 40 | gtk.main() 41 | 42 | if __name__ == "__main__": 43 | run(Screen,100,100) 44 | -------------------------------------------------------------------------------- /js/libs/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function f(a,e,b){a=document.createElement(a);a.id=e;a.style.cssText=b;return a}function l(a,e,b){var c=f("div",a,"padding:0 0 3px 3px;text-align:left;background:"+b),d=f("div",a+"Text","font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px;color:"+e);d.innerHTML=a.toUpperCase();c.appendChild(d);a=f("div",a+"Graph","width:74px;height:30px;background:"+e);c.appendChild(a);for(e=0;74>e;e++)a.appendChild(f("span","","width:1px;height:30px;float:left;opacity:0.9;background:"+ 3 | b));return c}function m(a){for(var b=c.children,d=0;dr+1E3&&(d=Math.round(1E3* 5 | t/(a-r)),u=Math.min(u,d),v=Math.max(v,d),A.textContent=d+" FPS ("+u+"-"+v+")",p(B,d/100),r=a,t=0,void 0!==h)){var b=performance.memory.usedJSHeapSize,c=performance.memory.jsHeapSizeLimit;h=Math.round(9.54E-7*b);y=Math.min(y,h);z=Math.max(z,h);E.textContent=h+" MB ("+y+"-"+z+")";p(F,b/c)}return a},update:function(){k=this.end()}}};"object"===typeof module&&(module.exports=Stats); 6 | -------------------------------------------------------------------------------- /js/libs/Detector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | */ 5 | 6 | var Detector = { 7 | 8 | canvas: !! window.CanvasRenderingContext2D, 9 | webgl: ( function () { 10 | 11 | try { 12 | 13 | var canvas = document.createElement( 'canvas' ); return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); 14 | 15 | } catch ( e ) { 16 | 17 | return false; 18 | 19 | } 20 | 21 | } )(), 22 | workers: !! window.Worker, 23 | fileapi: window.File && window.FileReader && window.FileList && window.Blob, 24 | 25 | getWebGLErrorMessage: function () { 26 | 27 | var element = document.createElement( 'div' ); 28 | element.id = 'webgl-error-message'; 29 | element.style.fontFamily = 'monospace'; 30 | element.style.fontSize = '13px'; 31 | element.style.fontWeight = 'normal'; 32 | element.style.textAlign = 'center'; 33 | element.style.background = '#fff'; 34 | element.style.color = '#000'; 35 | element.style.padding = '1.5em'; 36 | element.style.width = '400px'; 37 | element.style.margin = '5em auto 0'; 38 | 39 | if ( ! this.webgl ) { 40 | 41 | element.innerHTML = window.WebGLRenderingContext ? [ 42 | 'Your graphics card does not seem to support WebGL.
', 43 | 'Find out how to get it here.' 44 | ].join( '\n' ) : [ 45 | 'Your browser does not seem to support WebGL.
', 46 | 'Find out how to get it here.' 47 | ].join( '\n' ); 48 | 49 | } 50 | 51 | return element; 52 | 53 | }, 54 | 55 | addGetWebGLMessage: function ( parameters ) { 56 | 57 | var parent, id, element; 58 | 59 | parameters = parameters || {}; 60 | 61 | parent = parameters.parent !== undefined ? parameters.parent : document.body; 62 | id = parameters.id !== undefined ? parameters.id : 'oldie'; 63 | 64 | element = Detector.getWebGLErrorMessage(); 65 | element.id = id; 66 | 67 | parent.appendChild( element ); 68 | 69 | } 70 | 71 | }; 72 | 73 | // browserify support 74 | if ( typeof module === 'object' ) { 75 | 76 | module.exports = Detector; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /js/renderer.js: -------------------------------------------------------------------------------- 1 | if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); 2 | var stats, perfInfo; 3 | var scene, cam, renderer; 4 | var mouseX = 0, mouseY = 0; 5 | var fastliner; 6 | var sdfuniforms; 7 | var sdfquad,lightAccumBuffer; 8 | 9 | function init() { 10 | renderer = new THREE.WebGLRenderer({preserveDrawingBuffer:true}); 11 | renderer.setPixelRatio( window.devicePixelRatio ); 12 | renderer.setSize( window.innerWidth, window.innerHeight ); 13 | renderer.autoClearColor = false; 14 | renderer.setClearColor(0x000000,0.0); 15 | 16 | cam = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, -10000, 10000 ); 17 | 18 | fastliner = new FastLiner(renderer, size); 19 | lightmapUs = { 20 | tex: { type: "t", value: fastliner.getTex() }, 21 | exposure: { type: "f", value: 1.0 }, 22 | } 23 | lightAccumBuffer = new THREE.Mesh( 24 | new THREE.PlaneBufferGeometry(size,size), 25 | new THREE.ShaderMaterial({ 26 | uniforms: lightmapUs, 27 | vertexShader: document.getElementById( 'vertexShader' ).textContent, 28 | fragmentShader: document.getElementById( 'lightmapExposure' ).textContent, 29 | transparent:true, 30 | depthWrite: false, 31 | depthTest: false, 32 | })); 33 | sdfuniforms = { 34 | tex: { type: "t", value: null }, 35 | size: { type: "f", value: size }, 36 | } 37 | var sdfmaterial = new THREE.ShaderMaterial( { 38 | uniforms: sdfuniforms, 39 | vertexShader: document.getElementById( 'vertexShader' ).textContent, 40 | fragmentShader: document.getElementById( 'fragmentDrawSDF' ).textContent, 41 | depthWrite: false 42 | } ); 43 | sdfquad = new THREE.Mesh( 44 | new THREE.PlaneBufferGeometry(size,size), 45 | sdfmaterial); 46 | 47 | scene = new THREE.Scene(); 48 | scene.add(lightAccumBuffer); 49 | scene.add(sdfquad); 50 | 51 | 52 | var container = document.getElementById( 'container' ); 53 | container.appendChild( renderer.domElement ); 54 | 55 | perfInfo = document.getElementById( 'perf' ); 56 | stats = new Stats(); 57 | stats.domElement.style.position = 'absolute'; 58 | stats.domElement.style.top = '0px'; 59 | container.appendChild( stats.domElement ); 60 | 61 | container.addEventListener( 'mousemove', onDocumentMouseMove, false ); 62 | container.addEventListener( 'mousedown', onDocumentMouseDown, false ); 63 | container.addEventListener( 'mouseup', onDocumentMouseUp, false ); 64 | window.addEventListener( 'resize', onResize, false ); 65 | } 66 | 67 | 68 | function onResize(event){ 69 | renderer.setSize(window.innerWidth, window.innerHeight); 70 | cam.left = -window.innerWidth/2; 71 | cam.right = window.innerWidth/2; 72 | cam.top = window.innerHeight/2; 73 | cam.bottom = -window.innerHeight/2; 74 | cam.updateProjectionMatrix(); 75 | } 76 | 77 | var mouseDown = false; 78 | function onDocumentMouseDown( event ) { 79 | mouseDown = true; 80 | fastliner.reset(mouseX,mouseY); 81 | } 82 | function onDocumentMouseUp( event ) { 83 | mouseDown = false; 84 | } 85 | function onDocumentMouseMove( event ) { 86 | mouseX = ( event.clientX - window.innerWidth / 2 ); 87 | mouseY = ( event.clientY - window.innerHeight / 2 ); 88 | if(mouseDown){ 89 | fastliner.reset(mouseX,mouseY); 90 | } 91 | } 92 | 93 | function animate() { 94 | requestAnimationFrame( animate ); 95 | render(); 96 | stats.update(); 97 | } 98 | 99 | var size = 800; 100 | var exposure = 0.1; 101 | var sdf = new SDF(size); 102 | var isinit = true; 103 | var useBounds = true; 104 | init(); 105 | animate(); 106 | 107 | function showSDF(istrue){ 108 | if(istrue){ 109 | lightAccumBuffer.visible = false; 110 | sdfquad.visible = true; 111 | }else{ 112 | lightAccumBuffer.visible = true; 113 | sdfquad.visible = false; 114 | } 115 | } 116 | var test = 0; 117 | function render() { 118 | renderer.clear(); 119 | if(isinit){ 120 | sdf.clear(renderer); 121 | for(var i=0;i<20;i++){ 122 | var subtractive = Math.random()>0.5; 123 | if(Math.random()>0.5) 124 | sdf.drawCircle(renderer, 125 | Math.random()*size, 126 | Math.random()*size, 127 | (Math.random()+1)*size/10.0, 128 | subtractive); 129 | else 130 | sdf.drawRect(renderer, 131 | Math.random()*size, 132 | Math.random()*size, 133 | Math.random()*size/10.0, 134 | Math.random()*size/10.0, 135 | Math.random()*20, 136 | subtractive); 137 | } 138 | if(useBounds){ 139 | sdf.drawRect(renderer,0,0,size,1); 140 | sdf.drawRect(renderer,0,size,size,1); 141 | sdf.drawRect(renderer,0,0,1,size); 142 | sdf.drawRect(renderer,size,0,1,size); 143 | sdfuniforms.tex.value = sdf.getTex(); 144 | } 145 | isinit = false; 146 | } 147 | fastliner.update(sdf.getTex()); 148 | lightmapUs.exposure.value = fastliner.bounces*exposure*size/fastliner.getSampleNum(); 149 | renderer.render(scene,cam); 150 | } 151 | 152 | -------------------------------------------------------------------------------- /js/fastliner.js: -------------------------------------------------------------------------------- 1 | function createFullScreenScene(shader){ 2 | var plane = new THREE.PlaneBufferGeometry(2,2); 3 | var quad = new THREE.Mesh( plane, shader ); 4 | var tmpScene = new THREE.Scene(); 5 | tmpScene.add( quad ); 6 | return tmpScene; 7 | } 8 | 9 | var FastLiner = function(renderer,size){ 10 | var self = this; 11 | self.bounces = 10; 12 | var datasize = 64; 13 | var rtCam = new THREE.OrthographicCamera( size / - 2, size / 2, size / 2, size / - 2, -10000, 10000 ); 14 | var renderTarget = new THREE.WebGLRenderTarget(size,size, { 15 | depthBuffer: false, 16 | stencilBuffer: false, 17 | minFilter: THREE.NearestFilter, 18 | magFilter: THREE.NearestFilter, 19 | format: THREE.RGBAFormat, 20 | type:THREE.FloatType 21 | }); 22 | var initTexData = new Float32Array(datasize*datasize*4); 23 | var initTex = new THREE.DataTexture(initTexData, datasize, datasize, THREE.RGBAFormat, THREE.FloatType); 24 | var randData = new Float32Array(datasize*datasize*4); 25 | var randTex = new THREE.DataTexture(randData, datasize, datasize, THREE.RGBAFormat, THREE.FloatType); 26 | //TODO use randomness for diffusnes 27 | var posTex1 = new THREE.WebGLRenderTarget(datasize, datasize, { 28 | depthBuffer: false, 29 | stencilBuffer: false, 30 | minFilter: THREE.NearestFilter, 31 | magFilter: THREE.NearestFilter, 32 | format: THREE.RGBAFormat, 33 | type:THREE.FloatType 34 | }); 35 | var posTex2 = new THREE.WebGLRenderTarget(datasize, datasize, { 36 | depthBuffer: false, 37 | stencilBuffer: false, 38 | minFilter: THREE.NearestFilter, 39 | magFilter: THREE.NearestFilter, 40 | format: THREE.RGBAFormat, 41 | type:THREE.FloatType 42 | }); 43 | var geometry = new THREE.Geometry(); 44 | var vertindex = 0; 45 | for(var x=0;x0.0){\n'+ 37 | ' if(olddist<0.0){\n'+ 38 | ' if(dist<0.0)\n'+ 39 | ' newdist = max(dist,olddist);\n'+ 40 | ' else\n'+ 41 | ' newdist = max(-dist,olddist);\n'+ 42 | ' }else{\n'+ 43 | ' if(dist<0.0)\n'+ 44 | ' newdist = max(dist,-olddist);\n'+ 45 | ' else\n'+ 46 | ' newdist = min(dist,olddist);\n'+ 47 | ' }\n'+ 48 | ' }else{\n'+ 49 | ' if(olddist<0.0 || dist<0.0)\n'+ 50 | ' newdist = max(-dist,olddist);\n'+ 51 | ' }\n'+ 52 | ' gl_FragColor = vec4(newdist,0.0,0.0,1.0);\n'+ 53 | '}\n'; 54 | 55 | var SDF = function(size){ 56 | var self = this; 57 | self.pingpong = new PingPong(); 58 | self.dummycam = new THREE.OrthographicCamera(0,0); 59 | self.drawCirclePass = makeDrawCirclePass(); 60 | self.drawRectPass = makeDrawRectPass(); 61 | self.resetPass = makeResetPass(); 62 | 63 | function createFullScreenScene(shader){ 64 | var plane = new THREE.PlaneBufferGeometry(2,2); 65 | var quad = new THREE.Mesh( plane, shader ); 66 | var tmpScene = new THREE.Scene(); 67 | tmpScene.add( quad ); 68 | return tmpScene; 69 | } 70 | 71 | function makeDrawCirclePass(){ 72 | self.drawCircleUs = { 73 | pingpong: { type: "t", value: self.pingpong.current() }, 74 | sign: {type:"f", value: 1.0}, 75 | circle: {type:"v3", value: new THREE.Vector3(size/2,size/2,size/2)} 76 | } 77 | var shader = new THREE.ShaderMaterial( { 78 | uniforms: self.drawCircleUs, 79 | vertexShader: document.getElementById( 'vertexFullscreen' ).textContent, 80 | fragmentShader: 81 | 'uniform vec3 circle;\n'+ 82 | 'float getdist(){\n'+ 83 | ' return length(gl_FragCoord.xy-circle.xy)-circle.z;\n'+ 84 | '}\n'+ 85 | sdfDrawObjectShader, 86 | depthWrite: false 87 | } ); 88 | return createFullScreenScene(shader); 89 | } 90 | 91 | function makeDrawRectPass(){ 92 | self.drawRectUs = { 93 | pingpong: { type: "t", value: self.pingpong.current() }, 94 | sign: {type:"f", value: 1.0}, 95 | rect: {type:"v4", value: new THREE.Vector4(size/2,size/2,size/2,size/2)}, 96 | border: {type:"f", value: 0.0} 97 | } 98 | var shader = new THREE.ShaderMaterial( { 99 | uniforms: self.drawRectUs, 100 | vertexShader: document.getElementById( 'vertexFullscreen' ).textContent, 101 | fragmentShader: 102 | 'uniform vec4 rect;\n'+ 103 | 'uniform float border;\n'+ 104 | 'float getdist(){\n'+ 105 | ' vec2 dist = abs(gl_FragCoord.xy-rect.xy)-rect.zw;\n'+ 106 | ' if(dist.x<0.0 && dist.y<0.0)\n'+ 107 | ' return max(dist.x,dist.y)-border;\n'+ 108 | ' return length(max(dist,vec2(0.0)))-border;\n'+ 109 | '}\n'+ 110 | sdfDrawObjectShader, 111 | depthWrite: false 112 | } ); 113 | return createFullScreenScene(shader); 114 | } 115 | 116 | function makeResetPass(){ 117 | var shader = new THREE.ShaderMaterial({ 118 | uniforms: { 119 | val: {type:"f", value: size*size} 120 | }, 121 | vertexShader: document.getElementById( 'vertexFullscreen' ).textContent, 122 | fragmentShader: document.getElementById( 'fragmentResetSDF' ).textContent, 123 | depthWrite: false 124 | }); 125 | return createFullScreenScene(shader); 126 | } 127 | 128 | self.clear = function(renderer){ 129 | renderer.render( self.resetPass, self.dummycam, self.pingpong.current()); 130 | } 131 | 132 | self.drawCircle = function(renderer,x,y,size,subtractive){ 133 | self.drawCircleUs.sign.value = subtractive?1.0:-1.0; 134 | self.drawCircleUs.circle.value.set(x,y,size); 135 | self.drawCircleUs.pingpong.value = self.pingpong.current(); 136 | renderer.render( self.drawCirclePass, self.dummycam, self.pingpong.next()); 137 | } 138 | 139 | self.drawRect = function(renderer,x,y,width,height,border,subtractive){ 140 | self.drawRectUs.sign.value = subtractive?-1.0:1.0; 141 | self.drawRectUs.rect.value.set(x,y,width,height); 142 | self.drawRectUs.pingpong.value = self.pingpong.current(); 143 | if(!border) 144 | self.drawRectUs.border.value = 0.0; 145 | else 146 | self.drawRectUs.border.value = border; 147 | renderer.render( self.drawRectPass, self.dummycam, self.pingpong.next()); 148 | } 149 | 150 | self.getTex = function(){ 151 | return self.pingpong.current(); 152 | } 153 | } 154 | 155 | -------------------------------------------------------------------------------- /py/sdf.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random as rand 3 | import numpy as np 4 | 5 | size = 400 6 | sdf = [[size**2]*size for _ in range(size)] 7 | 8 | def printsdf(sdf): 9 | for row in sdf: 10 | for el in row: 11 | print(str("%.2f" % el).center(5)), 12 | print('\n'), 13 | print('\n'), 14 | 15 | class Circle(): 16 | def __init__(self, c): 17 | self.circle = c 18 | def dist(self,x,y): 19 | xdist = x-self.circle[0] 20 | ydist = y-self.circle[1] 21 | return math.sqrt(xdist*xdist+ydist*ydist)-self.circle[2] 22 | def draw(self,cr): 23 | circle = self.circle 24 | cr.arc(circle[0],circle[1],circle[2],0,2*math.pi) 25 | 26 | class Rect(): 27 | def __init__(self, rect): 28 | self.rect = rect 29 | def dist(self,x,y): 30 | xdist = abs(x-self.rect[0])-self.rect[2] 31 | ydist = abs(y-self.rect[1])-self.rect[3] 32 | if xdist<0 and ydist<0: 33 | dist = max(xdist,ydist) 34 | else: 35 | xdist = max(xdist,0) 36 | ydist = max(ydist,0) 37 | dist = math.sqrt(xdist*xdist+ydist*ydist) 38 | return dist 39 | def draw(self,cr): 40 | rect = self.rect 41 | cr.rectangle(rect[0]-rect[2],rect[1]-rect[3],rect[2]*2,rect[3]*2) 42 | 43 | def fillobject(sdf,obj): 44 | for x in range(size): 45 | for y in range(size): 46 | dist = obj.dist(x,y) 47 | if sdf[x][y]<0: 48 | if dist<0: 49 | sdf[x][y] = max(dist,sdf[x][y]) 50 | if dist>0: 51 | sdf[x][y] = max(-dist,sdf[x][y]) 52 | else: 53 | if dist<0: 54 | sdf[x][y] = max(dist,-sdf[x][y]) 55 | else: 56 | sdf[x][y] = min(dist,sdf[x][y]) 57 | 58 | def fillobjects(sdf,objs): 59 | for obj in objs: 60 | fillobject(sdf,obj) 61 | 62 | objs = [] 63 | for _ in range(3): 64 | data = np.concatenate((np.random.rand(2)*size,np.random.rand(2)*20+20)) 65 | objs.append(Rect(data)) 66 | for _ in range(3): 67 | data = np.concatenate((np.random.rand(2)*size,np.random.rand(1)*20+20)) 68 | objs.append(Circle(data)) 69 | fillobjects(sdf,objs) 70 | 71 | def raymarchstep(pos,direction): 72 | res = [] 73 | while _: 74 | if not isinside(pos): break 75 | if sdf[int(pos[0])][int(pos[1])]<0: break 76 | res.append(pos) 77 | pos = pos + direction 78 | return res 79 | 80 | def isinside(pos): 81 | if pos[0]<0 or pos[1]<0 or pos[0]>=size or pos[1]>=size: 82 | return False 83 | return True 84 | 85 | def raymarch(pos,direction): 86 | res = [] 87 | while _: 88 | if not isinside(pos): 89 | res.append(pos) 90 | return res 91 | dist = sdf[int(pos[0])][int(pos[1])] 92 | res.append(pos) 93 | pos = pos + direction*dist 94 | if dist<0.5: return res 95 | 96 | def norm(vec): 97 | return vec/np.linalg.norm(vec) 98 | 99 | def normal(pos): 100 | locpos = [int(pos[0]),int(pos[1])] 101 | if locpos[0]<=0 or locpos[0]>=size-1 or locpos[1]<=0 or locpos[1]>=size-1: 102 | normal = np.array([1,0]) 103 | else: 104 | dx = (sdf[locpos[0]+1][locpos[1]]-sdf[locpos[0]-1][locpos[1]])/2 105 | dy = (sdf[locpos[0]][locpos[1]+1]-sdf[locpos[0]][locpos[1]-1])/2 106 | normal = norm(np.array([dx,dy])) 107 | return normal 108 | 109 | def reflect(pos,direction): 110 | n = normal(pos) 111 | r = direction - 2*(np.dot(direction,n))*n 112 | return r 113 | 114 | 115 | import framework 116 | import cairo 117 | import gtk 118 | 119 | class SDFRenderer(framework.Screen): 120 | 121 | def mousemove(self,w,event): 122 | loc = [event.x,event.y] 123 | print(sdf[int(loc[0])][int(loc[1])]) 124 | print(normal(loc)) 125 | pass 126 | 127 | def draw(self, cr, width, height): 128 | self.connect('motion-notify-event',self.mousemove) 129 | rgb = np.zeros((size,size,4)) 130 | for x in range(size): 131 | for y in range(size): 132 | dist = 4.0*sdf[y][x]/size 133 | rgb[x,y,0] = dist if dist>=0 else 0 134 | rgb[x,y,1] = -dist if dist<0 else 0 135 | for x in range(size): 136 | for y in range(size): 137 | rgb[x,y,0] = max(0,min(1,rgb[x,y,0])) 138 | rgb[x,y,1] = max(0,min(1,rgb[x,y,1])) 139 | rgb[x,y,2] = max(0,min(1,rgb[x,y,2])) 140 | 141 | arr = rgb*255 142 | arr = arr.astype(np.uint8) 143 | surface = cairo.ImageSurface.create_for_data(arr, cairo.FORMAT_RGB24, size, size) 144 | cr.set_source_surface(surface) 145 | cr.paint() 146 | cr.set_source_rgb(1,1,1) 147 | for obj in objs: 148 | obj.draw(cr) 149 | cr.stroke() 150 | for _ in range(100): 151 | isfirst = 0 152 | pos = np.random.rand(2)*size 153 | pos = [size/2,size/2] 154 | direction = norm(np.random.rand(2)-0.5) 155 | marchpos = raymarch(pos,direction) 156 | last = None 157 | for pos in marchpos: 158 | if isfirst>0: 159 | col = float(isfirst)/len(marchpos) 160 | if isfirst%2==1: 161 | col = 1-col 162 | cr.set_source_rgb(col,1-col,0) 163 | cr.move_to(last[0],last[1]) 164 | cr.line_to(pos[0],pos[1]) 165 | cr.stroke() 166 | isfirst += 1 167 | last = pos 168 | 169 | class LightRenderer(framework.Screen): 170 | def draw(self, cr, width, height): 171 | rgb = np.zeros((size,size,4)) 172 | arr = rgb*255 173 | arr = arr.astype(np.uint8) 174 | surface = cairo.ImageSurface.create_for_data(arr, cairo.FORMAT_RGB24, size, size) 175 | cr.set_source_surface(surface) 176 | cr.paint() 177 | cr.set_source_rgb(1,1,1) 178 | for obj in objs: 179 | obj.draw(cr) 180 | cr.stroke() 181 | cr.set_source_rgba(1,1,1,0.01) 182 | cr.set_line_width(1) 183 | for _ in range(40000): 184 | isfirst = 0 185 | pos = np.random.rand(2)*size 186 | pos = [size/2,size/2] 187 | angle = rand.random()*math.pi*2 188 | direction = np.array([math.cos(angle),math.sin(angle)]) 189 | for bounces in range(10): 190 | marchpos = raymarch(pos,direction) 191 | cr.move_to(marchpos[0][0],marchpos[0][1]) 192 | cr.line_to(marchpos[-1][0],marchpos[-1][1]) 193 | cr.stroke() 194 | if isinside(marchpos[-1]): 195 | direction = reflect(marchpos[-1],direction) 196 | pos = marchpos[-1]+direction 197 | else: 198 | break 199 | 200 | 201 | framework.run(SDFRenderer,size,size) 202 | framework.run(LightRenderer,size,size) 203 | #printsdf(sdf) 204 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SDF-trace 5 | 6 | 7 | 35 | 36 | 37 | 38 |
39 |
40 | SDF-trace 41 |
42 | click to move lightsource 43 |
44 |
0 rays/sec
45 | 46 | 47 | 48 | 49 | 50 | 58 | 65 | 79 | 87 | 103 | 112 | 147 | 156 | 166 | 167 | 168 | 169 | 170 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /js/libs/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}(); 14 | dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}}, 15 | each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;bthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common); 29 | dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input, 30 | "blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b= 31 | this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); 32 | dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background= 33 | document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a= 34 | (this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 35 | dat.controllers.FunctionController=function(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object); 36 | this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 37 | dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 39 | dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); 40 | dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 41 | return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!= 42 | a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& 43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1; 44 | var b=1\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n', 71 | ".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", 72 | dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e= 73 | function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype, 74 | f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, 75 | dat.controllers.ColorController=function(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; 76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window, 77 | "mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1 78 | b&&(b=0);1e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); 79 | this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); 80 | this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style, 81 | {position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%", 82 | background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window, 83 | "mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1; 84 | c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255<