UNPKG

dreemgl

Version:

DreemGL is an open-source multi-screen prototyping framework for mediated environments, with a visual editor and shader styling for webGL and DALi runtimes written in JavaScript. As a toolkit for gpu-accelerated multiscreen development, DreemGL includes

1,213 lines (1,021 loc) 33.9 kB
/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others. Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ // Parts copyright 2012 Google, Inc. All Rights Reserved. (APACHE 2.0 license) define.class('$system/platform/$platform/shader$platform', function(require, exports, baseclass){ //internal // the font this.font = require('$resources/fonts/opensans_regular_ascii.glf') // initial pixel and vertex shaders this.position = "glyphy_mesh()" this.color = "glyphy_pixel()" //this.fgcolor = vec4("blue") //this.boldness = 0 //this.outline = false this.pixel_contrast = 1.4 this.pixel_gamma_adjust = vec3(1.2) this.subpixel_off = 1.0115 this.subpixel_distance = 3. // forward ref of things we need on view this.view = { _fgcolor:vec4(), _bgcolor:vec4(), _totalmatrix:mat4(), _viewmatrix:mat4(), _polygonoffset:0.0, _outlinecolor:vec4(), _outlinethickness:0.0, _boldness: 0.0, _outline: false, _opacity:1.0, textstyle: function(style, tag){ return style }, //textstyle: function(fgcolor, pos, tag){return fgcolor;}, //textpositionfn:function(pos, tag){return pos;}, screen:{ device:{ frame:{ size:vec2() } } } } // lets define a custom struct and subclass the array this.textgeom = define.struct({ pos:vec4, // x y z w unicode in w tex:vec2, tag:vec4, }).extend(function(exports, self){ this.align = "left" this.start_x = 0 this.start_y = 0 this.text_x = 0 this.text_y = 0 this.shift = vec2(0) this.add_x = 0 this.add_y = 0 this.fontsize = 10 this.linespacing = 1.0 this.italic_ness = 0 // defines the line this.cursorspacing = 1.3 this.cursor_sink = 0.32 this.scaling = 0 this.distance = 0 this.debug = false //this.bgcolor = vec3('black') //this.fgcolor = vec4('white') this.__defineGetter__('line_height', function(){ return this.fontsize * this.linespacing }) this.__defineGetter__('min_y', function(){ return this.fontsize * this.linespacing }) this.__defineGetter__('block_y', function(){ return this.add_y - this.line_height + this.cursor_sink * this.fontsize }) this.__defineGetter__('bound_w', function(){ return this.text_w }) this.__defineGetter__('bound_h', function(){ return this.text_h + this.cursor_sink * this.fontsize }) this.clear = function(){ this.text_w = 0 this.text_h = 0 this.add_x = this.start_x this.add_y = this.start_y //=== null? this.min_y:0 } this.measurestring = function(string){ var res = {w:0, h:0}; if (!string || string.length == 0) return res; var length = string.length // alright lets convert some text babeh! for(var i = 0; i < length; i++){ var unicode = string.struct? string.array[i * 4]: string.charCodeAt(i) var info = this.font.glyphs[unicode] if(!info) info = this.font.glyphs[32] res.w += info.advance * this.fontsize } return res; } this.addWithinWidth = function(string, maxwidth, indent, m1, m2, m3){ if (!indent) indent = 0; var words = string.split(' '); var lines = [] var widths = [] var currentline = [] var currentw = this.add_x; for(var i = 0;i < words.length; i++) { var s = this.measurestring("_" + words[i]); if (currentw > 0) { if (currentw + s.w > maxwidth) { lines.push(currentline); widths.push(currentw); currentw = indent; currentline = []; } } currentw+= s.w; currentline.push(words[i]); } if (currentline.length > 0) { lines.push(currentline); widths.push(currentw); } if (this.align === "left") { for (var i = 0;i<lines.length;i++) { var line = lines[i]; for (var j = 0;j<line.length;j++) this.add(line[j] + ((j<line.length-1)?' ':'') , m1, m2, m3); if (i < lines.length -1) { this.add_y += this.fontsize * this.cursorspacing this.add_x = indent; } } } else if (this.align === "right") { for (var i = 0;i<lines.length;i++) { this.add_x = maxwidth - widths[i]; var line = lines[i]; for (var j = 0;j<line.length;j++) this.add(line[j] + ' ', m1, m2, m3); this.add_y += this.fontsize * this.cursorspacing } } else if (this.align === "center") { for (var i = 0;i<lines.length;i++) { this.add_x = maxwidth/2 - widths[i]/2; var line = lines[i]; for (var j = 0;j<line.length;j++) this.add(line[j] + ' ', m1, m2, m3); this.add_y += this.fontsize * this.cursorspacing } } else if (this.align === "justify") { for (var i = 0;i<lines.length;i++) { this.add_x = 0; var line = lines[i]; var spacer = 0; if (line.length > 1) spacer = (maxwidth - widths[i])/ (line.length-1) for (var j = 0;j<line.length;j++) { this.add(line[j]+' ', m1, m2, m3); this.add_x += spacer; } this.add_y += this.fontsize * this.cursorspacing } } } self.pushQuad = function(){ this.clean = false var slots = this.slots if(arguments.length !== slots * 4) throw new Error('Please use individual components to set a quad for '+slots) var off = this.length * slots this.length += 6 if(this.length >= this.allocated){ this.ensureSize(this.length) } // ok so lets just write it out var out = this.array for(var i = 0; i < slots; i++){ // iterate the components out[off + i ] = arguments[i] out[off + i + 1*slots] = out[off + i + 4*slots] = arguments[i + 1*slots] out[off + i + 2*slots] = out[off + i + 3*slots] = arguments[i + 2*slots] out[off + i + 5*slots] = arguments[i + 3*slots] } } // what do we need to know? this.addGlyph = function(info, unicode, m1, m2, m3) { var fontsize = this.fontsize var x1 = this.add_x + fontsize * info.min_x var x2 = this.add_x + fontsize * info.max_x var y1 = this.add_y - fontsize * info.min_y var y2 = this.add_y - fontsize * info.max_y var advance = info.advance var italic = this.italic_ness * info.height * fontsize var cz = this.add_z ? this.add_z:0; var dx = 0 this.clean = false var slots = this.slots //if(arguments.length !== slots * 4) throw new Error('Please use individual components to set a quad for '+slots) var o = this.length * slots this.length += 6 if(this.length >= this.allocated){ this.ensureSize(this.length) } // m1 is the formatting layout if(m1 < 0){ var format = m1 * -1 //var m1info = unicode === 10?this.font.glyphs[9]:info //var indent = parseInt(m1/65536) var mode = Math.floor(format/256)%256 var padding = format%256 // mode is 1, space left // mode is 2, space right // mode is 3, space left/right // mode is 4, scale indent if(mode){ var padskip = padding * info.advance * fontsize if(mode&1) x1 += padskip, x2 += padskip, dx += padskip if(mode&2) dx += padskip if(mode == 4){ x2 += padskip, dx = (padding - 1)* info.advance * fontsize } } } else if(unicode === 10){ x2 = 0 advance = 0 } var a = this.array if(this.font.baked){ // INLINED for optimization. a[o + 2] = a[o + 12] = a[o + 32] = a[o + 22] = a[o + 52] = a[o + 42] = cz a[o + 3] = a[o + 13] = a[o + 33] = a[o + 23] = a[o + 53] = a[o + 43] = fontsize a[o + 6] = a[o + 16] = a[o + 36] = a[o + 26] = a[o + 56] = a[o + 46] = unicode a[o + 7] = a[o + 17] = a[o + 37] = a[o + 27] = a[o + 57] = a[o + 47] = m1 a[o + 8] = a[o + 18] = a[o + 38] = a[o + 28] = a[o + 58] = a[o + 48] = m2 a[o + 9] = a[o + 19] = a[o + 39] = a[o + 29] = a[o + 59] = a[o + 49] = m3 // top left a[o + 0] = x1 a[o + 1] = y1 a[o + 4] = info.tmin_x a[o + 5] = info.tmin_y // top right a[o + 10] = a[o + 30] = x2 a[o + 11] = a[o + 31] = y1 a[o + 14] = a[o + 34] = info.tmax_x a[o + 15] = a[o + 35] = info.tmin_y // bottom left a[o + 20] = a[o + 50] = x1+italic a[o + 21] = a[o + 51] = y2 a[o + 24] = a[o + 54] = info.tmin_x a[o + 25] = a[o + 55] = info.tmax_y // bottom right a[o + 40] = x2 + italic a[o + 41] = y2 a[o + 44] = info.tmax_x a[o + 45] = info.tmax_y /* this.pushQuad( x1, y1, cz, fontsize, info.tmin_x, info.tmin_y, unicode, m1, m2, m3, x2, y1, cz, fontsize, info.tmax_x, info.tmin_y, unicode, m1, m2, m3, x1 + italic, y2, cz, fontsize, info.tmin_x, info.tmax_y, unicode, m1, m2, m3, x2 + italic, y2, cz, fontsize, info.tmax_x, info.tmax_y, unicode, m1, m2, m3 )*/ } else { var gx = ((info.atlas_x<<6) | info.nominal_w)<<1 var gy = ((info.atlas_y<<6) | info.nominal_h)<<1 // INLINED for optimization. text is used a lot a[o + 2] = a[o + 12] = a[o + 32] = a[o + 22] = a[o + 52] = a[o + 42] = cz a[o + 3] = a[o + 13] = a[o + 33] = a[o + 23] = a[o + 53] = a[o + 43] = fontsize a[o + 6] = a[o + 16] = a[o + 36] = a[o + 26] = a[o + 56] = a[o + 46] = unicode a[o + 7] = a[o + 17] = a[o + 37] = a[o + 27] = a[o + 57] = a[o + 47] = m1 a[o + 8] = a[o + 18] = a[o + 38] = a[o + 28] = a[o + 58] = a[o + 48] = m2 a[o + 9] = a[o + 19] = a[o + 39] = a[o + 29] = a[o + 59] = a[o + 49] = m3 // top left a[o + 0] = x1 a[o + 1] = y1 a[o + 4] = gx a[o + 5] = gy // top right a[o + 10] = a[o + 30] = x2 a[o + 11] = a[o + 31] = y1 a[o + 14] = a[o + 34] = gx|1 a[o + 15] = a[o + 35] = gy // bottom left a[o + 20] = a[o + 50] = x1+italic a[o + 21] = a[o + 51] = y2 a[o + 24] = a[o + 54] = gx a[o + 25] = a[o + 55] = gy|1 // bottom right a[o + 40] = x2 + italic a[o + 41] = y2 a[o + 44] = gx|1 a[o + 45] = gy|1 /* this.pushQuad( x1, y1, cz, fontsize, gx, gy, unicode, m1, m2, m3, x2, y1, cz, fontsize, gx|1, gy, unicode, m1, m2, m3, x1 + italic, y2, cz,fontsize, gx, gy|1, unicode, m1, m2, m3, x2 + italic, y2, cz, fontsize, gx|1, gy|1, unicode, m1, m2, m3 )*/ } this.add_x += advance * fontsize + dx if(this.add_x > this.text_w) this.text_w = this.add_x } this.computeBounds = function(with_cursor){ var text_w = 0 var text_h = 0 var length = this.lengthQuad() for(var i = 0; i < length; i++){ var o = i * 6 * 10 var x1 = this.array[o] var y1 = this.array[o + 1] var fontsize = this.array[o + 3] var unicode = this.array[o + 6] var info = this.font.glyphs[unicode] if(!info) info = this.font.glyphs[32] var add_x = x1 - fontsize * info.min_x + (unicode === 10 ? 0 : info.advance * fontsize) var add_y = y1 + fontsize * info.min_x //+ this.fontsize * this.linespacing if(add_x > text_w) text_w = add_x if(add_y > text_h) text_h = add_y } if(with_cursor){ var info = this.font.glyphs[32] if (info) { text_w += info.advance * this.fontsize if(!text_h) text_h = this.fontsize * this.linespacing } } this.text_w = text_w this.text_h = text_h } // lets add some strings this.add = function(string, im1, im2, im3){ var length = string.length this.ensureSize(this.length + length) var m1 = im1, m2 = im2, m3 = im3 // alright lets convert some text babeh! var array if(string.struct) array = string.array var glyphs = this.font.glyphs for(var i = string.start || 0; i < length; i++){ var unicode if(array){ unicode = array[i * 4] m1 = array[i*4+1] m2 = array[i*4+2] m3 = array[i*4+3] } else{ unicode = string.charCodeAt(i) } var info = glyphs[unicode] if(!info) info = glyphs[32], unicode = 32 // lets add some vertices if(unicode == 10){ // newline this.add_x = this.start_x this.add_y += this.fontsize * this.cursorspacing } this.addGlyph(info, unicode, m1, m2, m3) //if(!(m1<0) && unicode == 10){ // newline // this.add_x = this.start_x // this.add_y += this.fontsize * this.linespacing //} } if(this.add_y > this.text_h) this.text_h = this.add_y } // lets add some strings this.addAtPos = function(string, pos, m1, m2, m3){ var length = string.length this.add_x = pos[0]; this.add_y = pos[1]; this.add_z = pos[2]; // alright lets convert some text babeh! for(var i = 0; i < length; i++){ var unicode = string.struct? string.array[i * 4]: string.charCodeAt(i) var info = this.font.glyphs[unicode] if(!info) info = this.font.glyphs[32] // lets add some vertices this.addGlyph(info, unicode, m1, m2, m3) if(unicode == 10){ // newline this.add_x = this.start_x this.add_y += this.fontsize * this.cursorspacing } } if(this.add_y > this.text_h) this.text_h = this.add_y } this.__defineGetter__('char_count', function(){ return this.lengthQuad() }) // get the character coordinates this.charCoords = function(off){ if(off >= this.lengthQuad()){ return { x:this.add_x, y:this.add_y, //- this.line_height +this.fontsize * this.cursor_sink, w:0, h:this.line_height } } var info = this.font.glyphs[this.charCodeAt(off)] if(!info) info = this.font.glyphs[32] if(isNaN(off))debugger var coords = { x: this.array[off * 6 * 10 + 0] - this.fontsize * info.min_x, y: this.array[off * 6 * 10 + 1] + this.fontsize * info.min_y, w: info.advance * this.fontsize, h: this.line_height } return coords } this.char_tl_x = function(off){ return this.array[off * 6 * 10 + 0] } this.char_tr_x = function(off){ return this.array[off * 6 * 10 + 0 + 10] } this.tagAt = function(off, tagid){ return this.array[off * 6 * 10 + 6 + (tagid||0)] } this.offsetFromTag = function(tagid, tag, start){ var off = start || 0, len = this.length while(Math.abs(this.array[off * 6 * 10 + 7 + tagid] - tag) > 0.001){ if(off >= len) return -1 off ++ } return off } this.offsetFromPos = function(x, y){ var height = this.line_height //console.log(x, y) //if(y < 0) return -2 var array = this.array var fontsize = this.fontsize var line_height = this.line_height var glyphs = this.font.glyphs var sink = fontsize * this.cursor_sink for(var len = this.lengthQuad(), o = len - 1; o >= 0; o--){ var char_code = parseInt(array[o * 6*10 + 6]) var info = glyphs[char_code] var y2 = array[o * 6 * 10 + 1] + fontsize * info.min_y + sink var y1 = y2 - line_height if(y>=y1 && y<=y2){ var tl_x = this.array[o * 6 * 10 + 0] var tr_x = this.array[o * 6 * 10 + 0 + 10] var hx = (tl_x+tr_x)/2 // lets debug paint these 2 if(this.debug_mesh){ this.debug_mesh.length = 0 this.debug_mesh.add(tl_x, y1, 3, 3, 0.) this.debug_mesh.add(tr_x, y2, 3, 3, 1.) this.debug_mesh.add(x, y, 3, 3, 2.) } if(this.charCodeAt(o-1) == 10 && x< tl_x){ return o } if(x >= tl_x && x <= hx){ return o } if(o == 0 && x < tl_x){ return -1 // to the left } if(o == len - 1 && x > tr_x){ //if(char_code ==10) return o return -4 // to the right of self } if(x > hx){ //if(char_code == 10) return o return o + 1 } } if(y>y2){ //console.log(y, y2, -3) return -3 // below self } } return -2 // above self } this.cursorRect = function(off){ var coords= this.charCoords(off) // do a little bit of alignment fixery var m1 = this.tagAt(off,1) if(m1<0){ // lets check the alignment mode var format = m1 * -1 //var indent = parseInt(m1/65536) var mode = Math.floor(format/256)%256 if(this.charCodeAt(off) === 10){ coords = this.charCoords(off-1) if(this.charCodeAt(off-1) !== 10){ coords.x = coords.x + coords.w } else{ // add padd coords.x = coords.x + coords.w var padding = format%256 var info = this.font.glyphs[10] var fontsize = this.array[off * 6 * 10 + 3] coords.x += (padding-1) * info.advance * fontsize } } else if(mode&1){ var coords1 = this.charCoords(off - 1) coords.x = coords1.x + coords1.w } } coords.y -= coords.h - this.fontsize * this.cursor_sink return coords } this.charCodeAt = function(off){ return this.array[off * 6 * 10 + 6] } this.charAt = function(off){ return String.fromCharCode(this.charCodeAt(off)) } this.serializeText = function(start, end){ var str = '' for(var i = start; i < end; i++){ str += String.fromCharCode(this.charCodeAt(i)) } return str } this.serializeTags = function(start, end){ // lets serialize the tags array var out = vec4.array(end - start) var a = this.array var o = out.array for(var i = start, x = 0; i < end; x +=4, i++){ o[x] = a[i * 6 * 10 + 6] o[x+1] = a[i * 6 * 10 + 7] o[x+2] = a[i * 6 * 10 + 8] o[x+3] = a[i * 6 * 10 + 9] } out.length = x>>2 return out } this.setLength = function(len){ // if we are inserting at a newline, we need to grab the previous var off = len if(this.charCodeAt(len) === 10) off -=1 var m1 = this.array[off * 6 * 10 + 7] // if its a padded character we need to compute the new add_x differently if(m1<0 && (-m1)&65535){ var format = m1 * -1 //var indent = parseInt(m1/65536) var mode = Math.floor(format/256)%256 var rect = this.charCoords(off) this.length = len * 6 this.add_x = rect.x //+ rect.w this.add_y = rect.y // rip off padding if(mode&1){ var padding = format%256 var info = this.font.glyphs[this.array[off * 6 * 10 + 6]] var fontsize = this.array[off * 6 * 10 + 3] this.add_x -= padding * info.advance * fontsize//- rect.w } else if(mode&2){ console.log(2) var padding = format%256 var info = this.font.glyphs[this.array[off * 6 * 10 + 6]] var fontsize = this.array[off * 6 * 10 + 3] this.add_x += padding * info.advance * fontsize//- rect.w } else if(mode === 4){ console.log(4) var padding = format%256 var info = this.font.glyphs[this.array[off * 6 * 10 + 6]] var fontsize = this.array[off * 6 * 10 + 3] this.add_x += padding * info.advance * fontsize - rect.w } return } var rect = this.charCoords(off) this.length = len * 6 this.add_x = rect.x this.add_y = rect.y if(off !== len)this.add_x += rect.w } this.insertText = function(off, text){ // ok lets pull in the 'rest' as string //var str = this.serializeText(off, this.lengthQuad()) var tags = this.serializeTags(off, this.lengthQuad()) // lets set the length and start adding var rect = this.charCoords(off) this.setLength(off) this.add(text) this.add(tags) this.computeBounds(true) return text.length } this.removeText = function(off, end){ var tags = this.serializeTags(end, this.lengthQuad()) this.setLength(off) this.add(tags) // recompute the bounds this.computeBounds(true) return tags.length } }) // for type information this.mesh = this.textgeom.array() // this thing makes a new text array buffer this.newText = function(length){ var buf = this.textgeom.array((length||0)*6) buf.font = this.font buf.clear() return buf } this.subpixel = false this.glyphy_mesh_sdf = function(){ return glyphy_compute_position() } this.glyphy_mesh_atlas = function(){ glyph = glyph_vertex_transcode(mesh.tex) return glyphy_compute_position() } // glyphy shader library this.GLYPHY_INFINITY = '1e9' this.GLYPHY_EPSILON = '1e-5' this.GLYPHY_MAX_NUM_ENDPOINTS = '32' //this.paint = function(p, m, pixelscale){ // if(abs(mesh.tag.x-32.)<0.01 || abs(mesh.tag.x-10.)<0.01) discard // return vec4(-1.) //} this.moddist = function(pos, dist){ return dist } this.glyphy_arc_t = define.struct({ p0:vec2, p1:vec2, d:float }, 'glyphy_arc_t') this.glyphy_arc_endpoint_t = define.struct({ /* Second arc endpoint */ p:vec2, /* Infinity if this endpoint does not form an arc with the previous * endpoint. Ie. a "move_to". Test with glyphy_isinf(). * Arc depth otherwise. */ d:float }, 'glyphy_arc_endpoint_t') this.glyphy_arc_list_t = define.struct({ /* Number of endpoints in the list. * Will be zero if we're far away inside or outside, in which case side is set. * Will be -1 if this arc-list encodes a single line, in which case line_* are set. */ num_endpoints:int, /* If num_endpoints is zero, this specifies whether we are inside(-1) * or outside(+1). Otherwise we're unsure(0). */ side:int, /* Offset to the arc-endpoints from the beginning of the glyph blob */ offset:int, /* A single line is all we care about. It's right here. */ line_angle:float, line_distance:float /* From nominal glyph center */ }, 'glyphy_arc_list_t') this.glyphy_isinf = function(v){ return abs(v) >= GLYPHY_INFINITY * .5 } this.glyphy_iszero = function(v){ return abs(v) <= GLYPHY_EPSILON * 2. } this.glyphy_ortho = function(v){ return vec2(-v.y, v.x) } this.glyphy_float_to_byte = function(v){ return int(v *(256. - GLYPHY_EPSILON)) } this.glyphy_vec4_to_bytes = function(v){ return ivec4(v *(256. - GLYPHY_EPSILON)) } this.glyphy_float_to_two_nimbles = function(v){ var f = glyphy_float_to_byte(v) return ivec2(f / 16, int(mod(float(f), 16.))) } /* returns tan(2 * atan(d)) */ this.glyphy_tan2atan = function( d){ var a = (2. * d) var b = (1. - d * d) return a/b } this.glyphy_arc_endpoint_decode = function(v, nominal_size){ var p =(vec2(glyphy_float_to_two_nimbles(v.a)) + v.gb) / 16. var d = v.r if(d == 0.) d = GLYPHY_INFINITY else d = float(glyphy_float_to_byte(d) - 128) * .5 / 127. return glyphy_arc_endpoint_t(p * vec2(nominal_size), d) } this.glyphy_arc_center = function(a){ return mix(a.p0, a.p1, .5) + glyphy_ortho(a.p1 - a.p0) /(2. * glyphy_tan2atan(a.d)) } this.glyphy_arc_wedge_contains = function(a, p){ var d2 = glyphy_tan2atan(a.d) return dot(p - a.p0,(a.p1 - a.p0) * mat2(1, d2, -d2, 1)) >= 0. && dot(p - a.p1,(a.p1 - a.p0) * mat2(1, -d2, d2, 1)) <= 0. } this.glyphy_arc_wedge_signed_dist_shallow = function(a, p){ var v = normalize(a.p1 - a.p0) var line_d = dot(p - a.p0, glyphy_ortho(v))// * .1abs on sin(time.sec+p.x) if(a.d == 0.) return line_d var d0 = dot((p - a.p0), v) if(d0 < 0.) return sign(line_d) * distance(p, a.p0) var d1 = dot((a.p1 - p), v) if(d1 < 0.) return sign(line_d) * distance(p, a.p1) var d2 = d0 * d1 var r = 2. * a.d * d2 r = r / d2 if(r * line_d > 0.) return sign(line_d) * min(abs(line_d + r), min(distance(p, a.p0), distance(p, a.p1))) return line_d + r } this.glyphy_arc_wedge_signed_dist = function(a, p){ if(abs(a.d) <= .03) return glyphy_arc_wedge_signed_dist_shallow(a, p) var c = glyphy_arc_center(a) return sign(a.d) * (distance(a.p0, c) - distance(p, c)) } this.glyphy_arc_extended_dist = function(a, p){ /* Note: this doesn't handle points inside the wedge. */ var m = mix(a.p0, a.p1, .5) var d2 = glyphy_tan2atan(a.d) if(dot(p - m, a.p1 - m) < 0.) return dot(p - a.p0, normalize((a.p1 - a.p0) * mat2(+d2, -1, +1, +d2))) else return dot(p - a.p1, normalize((a.p1 - a.p0) * mat2(-d2, -1, +1, -d2))) } this.glyphy_arc_list_offset = function(p, nominal_size){ var cell = ivec2(clamp(floor(p), vec2(0.,0.), vec2(nominal_size - 1))) return cell.y * nominal_size.x + cell.x } this.glyphy_arc_list_decode = function(v, nominal_size){ var l = glyphy_arc_list_t() var iv = glyphy_vec4_to_bytes(v) l.side = 0 /* unsure */ if(iv.r == 0) { /* arc-list encoded */ l.offset = (iv.g * 256) + iv.b l.num_endpoints = iv.a if(l.num_endpoints == 255) { l.num_endpoints = 0 l.side = -1 } else if(l.num_endpoints == 0){ l.side = 1 } } else { /* single line encoded */ l.num_endpoints = -1 l.line_distance = float(((iv.r - 128) * 256 + iv.g) - 0x4000) / float(0x1FFF) * max(float(nominal_size.x), float(nominal_size.y)) l.line_angle = float(-((iv.b * 256 + iv.a) - 0x8000)) / float(0x7FFF) * 3.14159265358979 } return l } this.glyphy_antialias = function(d){ return smoothstep(-.75, +.75, d) } this.glyphy_arc_list = function(p, nominal_size, _atlas_pos){ var cell_offset = glyphy_arc_list_offset(p, nominal_size) var arc_list_data = glyphy_atlas_lookup(cell_offset, _atlas_pos) return glyphy_arc_list_decode(arc_list_data, nominal_size) } this.glyphy_sdf = function(p, nominal_size, _atlas_pos){ var arc_list = glyphy_arc_list(p, nominal_size, _atlas_pos) /* Short-circuits */ if(arc_list.num_endpoints == 0) { /* far-away cell */ return GLYPHY_INFINITY * float(arc_list.side) } if(arc_list.num_endpoints == -1) { /* single-line */ var angle = arc_list.line_angle //+ 90.*time var n = vec2(cos(angle), sin(angle)) return dot(p -(vec2(nominal_size) * .5), n) - arc_list.line_distance } var side = float(arc_list.side) var min_dist = GLYPHY_INFINITY var closest_arc = glyphy_arc_t() var endpoint = glyphy_arc_endpoint_t() var endpoint_prev = glyphy_arc_endpoint_decode(glyphy_atlas_lookup(arc_list.offset, _atlas_pos), nominal_size) for(var i = 1; i < GLYPHY_MAX_NUM_ENDPOINTS; i++){ if(i >= arc_list.num_endpoints) { break } endpoint = glyphy_arc_endpoint_decode(glyphy_atlas_lookup(arc_list.offset + i, _atlas_pos), nominal_size) var a = glyphy_arc_t(endpoint_prev.p, endpoint.p, endpoint.d) a.p0 = endpoint_prev.p; a.p1 = endpoint.p; a.d = endpoint.d; endpoint_prev = endpoint if(!glyphy_isinf(a.d)){ if(glyphy_arc_wedge_contains(a, p)) { var sdist = glyphy_arc_wedge_signed_dist(a, p) var udist = abs(sdist) * (1. - GLYPHY_EPSILON) if(udist <= min_dist) { min_dist = udist side = sdist <= 0. ? -1. : +1. } } else { var udist = min(distance(p, a.p0), distance(p, a.p1)) if(udist < min_dist) { min_dist = udist side = 0. /* unsure */ closest_arc = a } else if(side == 0. && udist == min_dist) { /* If this new distance is the same as the current minimum, * compare extended distances. Take the sign from the arc * with larger extended distance. */ var old_ext_dist = glyphy_arc_extended_dist(closest_arc, p) var new_ext_dist = glyphy_arc_extended_dist(a, p) var ext_dist = abs(new_ext_dist) <= abs(old_ext_dist) ? old_ext_dist : new_ext_dist //#ifdef GLYPHY_SDF_PSEUDO_DISTANCE /* For emboldening and stuff: */ min_dist = abs(ext_dist) //#endif side = sign(ext_dist) } } } } if(side == 0.) { // Technically speaking this should not happen, but it does. So try to fix it. var ext_dist = glyphy_arc_extended_dist(closest_arc, p) side = sign(ext_dist) } return min_dist * side } this.glyphy_point_dist = function(p, nominal_size, _atlas_pos){ var arc_list = glyphy_arc_list(p, nominal_size, _atlas_pos) var side = float(arc_list.side) var min_dist = GLYPHY_INFINITY if(arc_list.num_endpoints == 0){ return min_dist } var endpoint = glyphy_arc_endpoint_t() var endpoint_prev = glyphy_arc_endpoint_decode(glyphy_atlas.lookup(arc_list.offset, _atlas_pos), nominal_size) for(var i = 1; i < GLYPHY_MAX_NUM_ENDPOINTS; i++) { if(i >= arc_list.num_endpoints) { break } endpoint = glyphy_arc_endpoint_decode(glyphy_atlas.lookup(arc_list.offset + i, _atlas_pos), nominal_size) if(glyphy_isinf(endpoint.d)) continue min_dist = min(min_dist, distance(p, endpoint.p)) } return min_dist } this.glyph_vertex_transcode = function(v){ var g = ivec2(v) var corner = ivec2(mod (v, 2.)) g /= 2 var nominal_size = ivec2(mod (vec2(g), 64.)) return vec4(corner * nominal_size, g * 4) } this.glyphy_sdf_encode = function(value){ var enc = .75-.25 * value return vec4(enc,enc,enc,1.) } this.glyphy_sdf_decode = function(value){ return ((.75-value.r)*4.) } this.glyphy_sdf_generate = function(){ var glyph = glyph_vertex_transcode(glyphy_coords) var nominal_size = (ivec2(mod(glyph.zw, 256.)) + 2) / 4 var atlas_pos = ivec2(glyph.zw) / 256 var p = glyph.xy return glyphy_sdf_encode(glyphy_sdf(p, nominal_size, atlas_pos)) } this.glyphy_sdf_lookup = function(pos){ return texture2D(mesh.font.texture, pos, { MIN_FILTER: 'LINEAR', MAG_FILTER: 'LINEAR', WRAP_S: 'CLAMP_TO_EDGE', WRAP_T: 'CLAMP_TO_EDGE' }) } this.glyphy_atlas_lookup = function(offset, _atlas_pos){ var pos = (vec2(_atlas_pos.xy * mesh.font.item_geom + ivec2(mod(float(offset), mesh.font.item_geom_f.x), offset / mesh.font.item_geom.x)) + vec2(.5, .5)) / mesh.font.tex_geom_f return texture2D(mesh.font.texture, pos, { MIN_FILTER: 'NEAREST', MAG_FILTER: 'NEAREST', WRAP_S: 'CLAMP_TO_EDGE', WRAP_T: 'CLAMP_TO_EDGE' }) } // draw using atlas this.time = 0 this.atExtend = function(){ this.mesh.font = this.font baseclass.atExtend.call(this) } this.font_style_t = define.struct({ pos:vec3, fgcolor: vec4, outlinecolor: vec4, boldness: float, outlinethickness: float, outline: bool, visible: bool }, "font_style_t") // default text style this.style = function(style, tag){ return style } this.glyphy_compute_position = function(){ // we want to compute a measure of scale relative to the actual pixels var matrix = view.totalmatrix * view.viewmatrix var s = font_style_t( vec3(mesh.pos.x + mesh.shift.x, mesh.pos.y + mesh.shift.y, mesh.pos.z), view.fgcolor, view.outlinecolor, view.boldness, view.outlinethickness, view.outline, (abs(mesh.tag.x - 10.)<0.001 || abs(mesh.tag.x - 32.)<0.001)?false:true ) s = view.textstyle(s, mesh.tag) // plug it into varyings stylefgcolor = s.fgcolor styleoutlinecolor = s.outlinecolor stylepack = vec3(s.boldness, s.outlinethickness, s.outline ? 1.0 : 0.0) // hide it if(!s.visible) return vec4(0.) var pos1 = vec4(s.pos, 1.) * matrix pos1.w += view.polygonoffset; return pos1 } // draw using SDF texture this.glyphy_sdf_draw = function(){ var pos = mesh.tex var m = length(vec2(length(dFdx(mesh.tex)), length(dFdy(mesh.tex))))*0.25 var dist = glyphy_sdf_decode( glyphy_sdf_lookup(pos)) * 0.0012 dist -= stylepack.x / 300. dist = dist / m * pixel_contrast //dist = moddist(pos, dist) // TODO(aki): verify that this is correct if(dist >= 1. + stylepack.y){ discard } var alpha = glyphy_antialias(-dist) if(stylepack.z>0.){ var dist2 = abs(dist) - (stylepack.y) + 4. var alpha2 = glyphy_antialias(-dist2) var rgb = mix(stylefgcolor.rgb, styleoutlinecolor.rgb, alpha2); return vec4(rgb, alpha * stylefgcolor.a * view.opacity) } return vec4(stylefgcolor.rgb, alpha * stylefgcolor.a * view.opacity) //if(mesh.gamma_adjust.r != 1.){ // alpha = pow(alpha, 1. / mesh.gamma_adjust.r) //} //return vec4(col.rgb, pow(glyphy_antialias(-dist), mesh.gamma_adjust.x)) } this.glyphy_sdf_draw_subpixel_aa = function(){ var pos = mesh.tex var m = length(vec2(length(dFdx(pos)), length(dFdy(pos))))*SQRT_1_2 //var m = pixelscale*.5//0.005 // screenspace length mesh.scaling = 500. * m var sub_delta = vec2((m / subpixel_distance)*0.1,0) var v1 = glyphy_sdf_decode(glyphy_sdf_lookup(pos - sub_delta*2.)) var v2 = glyphy_sdf_decode(glyphy_sdf_lookup(pos - sub_delta)) var v3 = glyphy_sdf_decode(glyphy_sdf_lookup(pos)) var v4 = glyphy_sdf_decode(glyphy_sdf_lookup(pos + sub_delta)) var v5 = glyphy_sdf_decode(glyphy_sdf_lookup(pos + sub_delta*2.)) var dist = vec3( v1+v2+v3, v2+v3+v4, v3+v4+v5 ) * 0.001 dist -= stylepack.x / 300. dist = dist / m * pixel_contrast if(stylepack.z>0.){ dist = abs(dist) - (stylepack.y) } // TODO(aki): verify that this is correct if(dist.y > 1. + stylepack.y){ discard } var alpha = glyphy_antialias(-dist) alpha = pow(alpha, pixel_gamma_adjust) //var max_alpha = max(max(alpha.r,alpha.g),alpha.b) //if(max_alpha >0.5) max_alpha = 1. //return vec4(alpha.b<0.?'yellow'.rgb:'blue'.rgb, 1) return vec4(mix(view.bgcolor.rgb, stylefgcolor.rgb, alpha.rgb), view.opacity)//max_alpha) } this.glyphy_atlas_draw = function(){ //'trace' var nominal_size = (ivec2(mod(glyph.zw, 256.)) + 2) / 4 var atlas_pos = ivec2(glyph.zw) / 256 var pos = glyph.xy /* isotropic antialiasing */ var m = length(vec2(length(dFdx(pos)), length(dFdy(pos))))*SQRT_1_2//*0.1 var dist = glyphy_sdf(glyph.xy, nominal_size, atlas_pos) //+ noise.noise3d(vec3(glyph.x, glyph.y, time))*0.6 dist -= stylepack.x //debug(mesh.distance) dist = dist / m * pixel_contrast var dist2 = dist; if(stylepack.z>0.){ dist2 = abs(dist) - (stylepack.y) } // TODO(aki): verify that this is correct if(dist > 1. + stylepack.y){ discard } var alpha = glyphy_antialias(-dist) var alpha2 = glyphy_antialias(-dist2) //if(mesh.gamma_adjust.r != 1.){ // alpha = pow(alpha, 1. / mesh.gamma_adjust.r) //} var rgb = mix(stylefgcolor.rgb, styleoutlinecolor.rgb, alpha2-alpha); return vec4(this.textpixel(rgb, pos, dist), max(alpha, alpha2) * stylefgcolor.a * view.opacity) } this.glyphy_mesh = this.glyphy_mesh_sdf this.glyphy_pixel = this.glyphy_sdf_draw this.textpixel = function(col, pos, dist) { return col; } })