diff --git a/index.js b/index.js index 929a3ee..3c47e19 100755 --- a/index.js +++ b/index.js @@ -7,116 +7,118 @@ var utils = require('./lib/utils') var Base = THREE.BufferGeometry -module.exports = function createTextGeometry (opt) { - return new TextGeometry(opt) +module.exports = function createTextGeometry(opt) { + return new TextGeometry(opt) } -function TextGeometry (opt) { - Base.call(this) +function TextGeometry(opt) { + Base.call(this) - if (typeof opt === 'string') { - opt = { text: opt } - } + if (typeof opt === 'string') { + opt = { text: opt } + } - // use these as default values for any subsequent - // calls to update() - this._opt = Object.assign({}, opt) + // use these as default values for any subsequent + // calls to update() + this._opt = Object.assign({}, opt) - // also do an initial setup... - if (opt) this.update(opt) + // also do an initial setup... + if (opt) this.update(opt) } inherits(TextGeometry, Base) TextGeometry.prototype.update = function (opt) { - if (typeof opt === 'string') { - opt = { text: opt } - } - - // use constructor defaults - opt = Object.assign({}, this._opt, opt) - - if (!opt.font) { - throw new TypeError('must specify a { font } in options') - } - - this.layout = createLayout(opt) - - // get vec2 texcoords - var flipY = opt.flipY !== false - - // the desired BMFont data - var font = opt.font - - // determine texture size from font file - var texWidth = font.common.scaleW - var texHeight = font.common.scaleH - - // get visible glyphs - var glyphs = this.layout.glyphs.filter(function (glyph) { - var bitmap = glyph.data - return bitmap.width * bitmap.height > 0 - }) - - // provide visible glyphs for convenience - this.visibleGlyphs = glyphs - - // get common vertex data - var positions = vertices.positions(glyphs) - var uvs = vertices.uvs(glyphs, texWidth, texHeight, flipY) - var indices = createIndices([], { - clockwise: true, - type: 'uint16', - count: glyphs.length - }) - - // update vertex data - this.setIndex(indices) - this.setAttribute('position', new THREE.BufferAttribute(positions, 2)) - this.setAttribute('uv', new THREE.BufferAttribute(uvs, 2)) - - // update multipage data - if (!opt.multipage && 'page' in this.attributes) { - // disable multipage rendering - this.removeAttribute('page') - } else if (opt.multipage) { - // enable multipage rendering - var pages = vertices.pages(glyphs) - this.setAttribute('page', new THREE.BufferAttribute(pages, 1)) - } + if (typeof opt === 'string') { + opt = { text: opt } + } + + // use constructor defaults + opt = Object.assign({}, this._opt, opt) + + if (!opt.font) { + throw new TypeError('must specify a { font } in options') + } + + this.layout = createLayout(opt) + + // get vec2 texcoords + var flipY = opt.flipY !== false + + // the desired BMFont data + var font = opt.font + + // determine texture size from font file + var texWidth = font.common.scaleW + var texHeight = font.common.scaleH + + // get visible glyphs + var glyphs = this.layout.glyphs.filter(function (glyph) { + var bitmap = glyph.data + return bitmap.width * bitmap.height > 0 + }) + + // provide visible glyphs for convenience + this.visibleGlyphs = glyphs + + // get common vertex data + var positions = vertices.positions(glyphs) + var uvs = vertices.uvs(glyphs, texWidth, texHeight, flipY) + var indices = createIndices([], { + clockwise: true, + type: 'uint16', + count: glyphs.length, + }) + + // update vertex data + this.setIndex(indices) + this.setAttribute('position', new THREE.BufferAttribute(positions, 2)) + this.setAttribute('uv', new THREE.BufferAttribute(uvs, 2)) + + // update multipage data + if (!opt.multipage && 'page' in this.attributes) { + // disable multipage rendering + this.removeAttribute('page') + } else if (opt.multipage) { + // enable multipage rendering + var pages = vertices.pages(glyphs) + this.setAttribute('page', new THREE.BufferAttribute(pages, 1)) + } } TextGeometry.prototype.computeBoundingSphere = function () { - if (this.boundingSphere === null) { - this.boundingSphere = new THREE.Sphere() - } - - var positions = this.attributes.position.array - var itemSize = this.attributes.position.itemSize - if (!positions || !itemSize || positions.length < 2) { - this.boundingSphere.radius = 0 - this.boundingSphere.center.set(0, 0, 0) - return - } - utils.computeSphere(positions, this.boundingSphere) - if (isNaN(this.boundingSphere.radius)) { - console.error('THREE.BufferGeometry.computeBoundingSphere(): ' + - 'Computed radius is NaN. The ' + - '"position" attribute is likely to have NaN values.') - } + if (this.boundingSphere === null) { + this.boundingSphere = new THREE.Sphere() + } + + var positions = this.attributes.position.array + var itemSize = this.attributes.position.itemSize + if (!positions || !itemSize || positions.length < 2) { + this.boundingSphere.radius = 0 + this.boundingSphere.center.set(0, 0, 0) + return + } + utils.computeSphere(positions, this.boundingSphere) + if (isNaN(this.boundingSphere.radius)) { + console.error( + 'THREE.BufferGeometry.computeBoundingSphere(): ' + + 'Computed radius is NaN. The ' + + '"position" attribute is likely to have NaN values.' + ) + } } TextGeometry.prototype.computeBoundingBox = function () { - if (this.boundingBox === null) { - this.boundingBox = new THREE.Box3() - } - - var bbox = this.boundingBox - var positions = this.attributes.position.array - var itemSize = this.attributes.position.itemSize - if (!positions || !itemSize || positions.length < 2) { - bbox.makeEmpty() - return - } - utils.computeBox(positions, bbox) + if (this.boundingBox === null) { + this.boundingBox = new THREE.Box3() + } + + var bbox = this.boundingBox + var positions = this.attributes.position.array + var itemSize = this.attributes.position.itemSize + if (!positions || !itemSize || positions.length < 2) { + bbox.makeEmpty() + return + } + utils.computeBox(positions, bbox) } diff --git a/shaders/basic.js b/shaders/basic.js index 68795bd..705de1c 100644 --- a/shaders/basic.js +++ b/shaders/basic.js @@ -14,36 +14,56 @@ module.exports = function createBasicShader (opt) { delete opt.precision delete opt.opacity - return assign({ - uniforms: { - opacity: { type: 'f', value: opacity }, - map: { type: 't', value: map || new THREE.Texture() }, - color: { type: 'c', value: new THREE.Color(color) } + const webgl2 = document.getElementsByTagName('canvas')[0].getContext('webgl2') + + return assign( + { + uniforms: { + opacity: { value: opacity }, + map: { value: map || new THREE.Texture() }, + color: { value: new THREE.Color(color) } + }, + vertexShader: `${ + webgl2 + ? `#version 300 es + +#define attribute in +#define varying out` + : '' + } +attribute vec2 uv; +attribute vec3 position; +uniform mat4 projectionMatrix; +uniform mat4 modelViewMatrix; + +varying vec2 vUv; + +void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +}`, + fragmentShader: `${ + webgl2 + ? `#version 300 es +#define varying in +out highp vec4 pc_fragColor; + +#define texture2D texture +#define gl_FragColor pc_fragColor` + : '' + } +precision ${precision} float; +uniform float opacity; +uniform vec3 color; +uniform sampler2D map; + +varying vec2 vUv; + +void main() { + gl_FragColor = texture2D(map, vUv) * vec4(color, opacity); + ${alphaTest === 0 ? '' : `if (gl_FragColor.a < ${alphaTest}) discard;`} +}` }, - vertexShader: [ - 'attribute vec2 uv;', - 'attribute vec4 position;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 modelViewMatrix;', - 'varying vec2 vUv;', - 'void main() {', - 'vUv = uv;', - 'gl_Position = projectionMatrix * modelViewMatrix * position;', - '}' - ].join('\n'), - fragmentShader: [ - 'precision ' + precision + ' float;', - 'uniform float opacity;', - 'uniform vec3 color;', - 'uniform sampler2D map;', - 'varying vec2 vUv;', - - 'void main() {', - ' gl_FragColor = texture2D(map, vUv) * vec4(color, opacity);', - alphaTest === 0 - ? '' - : ' if (gl_FragColor.a < ' + alphaTest + ') discard;', - '}' - ].join('\n') - }, opt) + opt + ) } diff --git a/shaders/msdf.js b/shaders/msdf.js index 8973a70..4d6930a 100644 --- a/shaders/msdf.js +++ b/shaders/msdf.js @@ -1,61 +1,84 @@ -var assign = require('object-assign'); +var assign = require('object-assign') module.exports = function createMSDFShader (opt) { - opt = opt || {}; - var opacity = typeof opt.opacity === 'number' ? opt.opacity : 1; - var alphaTest = typeof opt.alphaTest === 'number' ? opt.alphaTest : 0.0001; - var precision = opt.precision || 'highp'; - var color = opt.color; - var map = opt.map; - var negate = typeof opt.negate === 'boolean' ? opt.negate : true; + opt = opt || {} + var opacity = typeof opt.opacity === 'number' ? opt.opacity : 1 + var alphaTest = typeof opt.alphaTest === 'number' ? opt.alphaTest : 0.0001 + var precision = opt.precision || 'highp' + var color = opt.color + var map = opt.map + var negate = typeof opt.negate === 'boolean' ? opt.negate : true // remove to satisfy r73 - delete opt.map; - delete opt.color; - delete opt.precision; - delete opt.opacity; - delete opt.negate; - - return assign({ - uniforms: { - opacity: { type: 'f', value: opacity }, - map: { type: 't', value: map || new THREE.Texture() }, - color: { type: 'c', value: new THREE.Color(color) } + delete opt.map + delete opt.color + delete opt.precision + delete opt.opacity + delete opt.negate + + const webgl2 = document.getElementsByTagName('canvas')[0].getContext('webgl2') + + return assign( + { + uniforms: { + opacity: { value: opacity }, + map: { value: map || new THREE.Texture() }, + color: { value: new THREE.Color(color) } + }, + vertexShader: `${ + webgl2 + ? `#version 300 es + +#define attribute in +#define varying out` + : '' + } +attribute vec2 uv; +attribute vec3 position; +uniform mat4 projectionMatrix; +uniform mat4 modelViewMatrix; + +varying vec2 vUv; + +void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +}`, + fragmentShader: `${ + webgl2 + ? `#version 300 es +#define varying in +out highp vec4 pc_fragColor; + +#define texture2D texture +#define gl_FragColor pc_fragColor` + : '' + } + +#ifdef GL_OES_standard_derivatives + #extension GL_OES_standard_derivatives : enable +#endif + +precision ${precision} float; + +uniform float opacity; +uniform vec3 color; +uniform sampler2D map; + +varying vec2 vUv; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + vec3 samp = ${negate ? '1.0 - ' : ''} texture2D(map, vUv).rgb; + float sigDist = median(samp.r, samp.g, samp.b) - 0.5; + float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0); + gl_FragColor = vec4(color.xyz, alpha * opacity); + ${alphaTest === 0 ? '' : `if (gl_FragColor.a < ${alphaTest}) discard;`} +}` }, - vertexShader: [ - 'attribute vec2 uv;', - 'attribute vec4 position;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 modelViewMatrix;', - 'varying vec2 vUv;', - 'void main() {', - 'vUv = uv;', - 'gl_Position = projectionMatrix * modelViewMatrix * position;', - '}' - ].join('\n'), - fragmentShader: [ - '#ifdef GL_OES_standard_derivatives', - '#extension GL_OES_standard_derivatives : enable', - '#endif', - 'precision ' + precision + ' float;', - 'uniform float opacity;', - 'uniform vec3 color;', - 'uniform sampler2D map;', - 'varying vec2 vUv;', - - 'float median(float r, float g, float b) {', - ' return max(min(r, g), min(max(r, g), b));', - '}', - - 'void main() {', - ' vec3 sample = ' + (negate ? '1.0 - ' : '') + 'texture2D(map, vUv).rgb;', - ' float sigDist = median(sample.r, sample.g, sample.b) - 0.5;', - ' float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0);', - ' gl_FragColor = vec4(color.xyz, alpha * opacity);', - alphaTest === 0 - ? '' - : ' if (gl_FragColor.a < ' + alphaTest + ') discard;', - '}' - ].join('\n') - }, opt); -}; + opt + ) +} diff --git a/shaders/multipage.js b/shaders/multipage.js index a1c2498..c2068e1 100644 --- a/shaders/multipage.js +++ b/shaders/multipage.js @@ -7,28 +7,30 @@ module.exports = function createMultipageShader (opt) { var alphaTest = typeof opt.alphaTest === 'number' ? opt.alphaTest : 0.0001 var textures = opt.textures || [] - textures = Array.isArray(textures) ? textures : [ textures ] + textures = Array.isArray(textures) ? textures : [textures] var baseUniforms = {} - textures.forEach(function (tex, i) { - baseUniforms['texture' + i] = { - type: 't', + textures.forEach((tex, i) => { + baseUniforms[`texture${i}`] = { value: tex } }) - var samplers = textures.map(function (tex, i) { - return 'uniform sampler2D texture' + i + ';' - }).join('\n') + var samplers = textures + .map((tex, i) => { + return `uniform sampler2D texture${i}; + ` + }) + .join('') - var body = textures.map(function (tex, i) { - var cond = i === 0 ? 'if' : 'else if' - return [ - cond + ' (vPage == ' + i + '.0) {', - 'sampleColor = texture2D(texture' + i + ', vUv);', - '}' - ].join('\n') - }).join('\n') + var body = textures + .map((tex, i) => { + var cond = i === 0 ? 'if' : 'else if' + return `${cond} (vPage == ${i}.0) { + sampleColor = texture2D(texture${i}, vUv); + }` + }) + .join('') var color = opt.color @@ -38,8 +40,10 @@ module.exports = function createMultipageShader (opt) { delete opt.precision delete opt.opacity + const webgl2 = document.getElementsByTagName('canvas')[0].getContext('webgl2') + var attributes = { - attributes: { page: { type: 'f', value: 0 } } + attributes: { page: { value: 0 } } } var threeVers = (parseInt(THREE.REVISION, 10) || 0) | 0 @@ -47,40 +51,62 @@ module.exports = function createMultipageShader (opt) { attributes = undefined } - return assign({ - uniforms: assign({}, baseUniforms, { - opacity: { type: 'f', value: opacity }, - color: { type: 'c', value: new THREE.Color(color) } - }), - vertexShader: [ - 'attribute vec4 position;', - 'attribute vec2 uv;', - 'attribute float page;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 modelViewMatrix;', - 'varying vec2 vUv;', - 'varying float vPage;', - 'void main() {', - 'vUv = uv;', - 'vPage = page;', - 'gl_Position = projectionMatrix * modelViewMatrix * position;', - '}' - ].join('\n'), - fragmentShader: [ - 'precision ' + precision + ' float;', - 'uniform float opacity;', - 'uniform vec3 color;', - samplers, - 'varying float vPage;', - 'varying vec2 vUv;', - 'void main() {', - 'vec4 sampleColor = vec4(0.0);', - body, - 'gl_FragColor = sampleColor * vec4(color, opacity);', - alphaTest === 0 - ? '' - : ' if (gl_FragColor.a < ' + alphaTest + ') discard;', - '}' - ].join('\n') - }, attributes, opt) + return assign( + { + uniforms: assign({}, baseUniforms, { + opacity: { value: opacity }, + color: { value: new THREE.Color(color) } + }), + vertexShader: `${ + webgl2 + ? `#version 300 es + +#define attribute in +#define varying out` + : '' + } +attribute vec3 position; +attribute vec2 uv; +attribute float page; + +uniform mat4 projectionMatrix; +uniform mat4 modelViewMatrix; + +varying vec2 vUv; +varying float vPage; + +void main() { + vUv = uv; + vPage = page; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +}`, + fragmentShader: `${ + webgl2 + ? `#version 300 es +#define varying in +out highp vec4 pc_fragColor; + +#define texture2D texture +#define gl_FragColor pc_fragColor` + : '' + } +precision ${precision} float; +uniform float opacity; +uniform vec3 color; + +${samplers} + +varying float vPage; +varying vec2 vUv; + +void main() { + vec4 sampleColor = vec4(0.0); + ${body} + gl_FragColor = sampleColor * vec4(color, opacity); + ${alphaTest === 0 ? '' : `if (gl_FragColor.a < ${alphaTest}) discard;`} +}` + }, + attributes, + opt + ) } diff --git a/shaders/sdf.js b/shaders/sdf.js index f99ce06..eeda8ef 100644 --- a/shaders/sdf.js +++ b/shaders/sdf.js @@ -14,50 +14,70 @@ module.exports = function createSDFShader (opt) { delete opt.precision delete opt.opacity - return assign({ - uniforms: { - opacity: { type: 'f', value: opacity }, - map: { type: 't', value: map || new THREE.Texture() }, - color: { type: 'c', value: new THREE.Color(color) } + const webgl2 = document.getElementsByTagName('canvas')[0].getContext('webgl2') + + return assign( + { + uniforms: { + opacity: { value: opacity }, + map: { value: map || new THREE.Texture() }, + color: { value: new THREE.Color(color) } + }, + vertexShader: `${ + webgl2 + ? `#version 300 es + +#define attribute in +#define varying out` + : '' + } +attribute vec2 uv; +attribute vec3 position; + +uniform mat4 projectionMatrix; +uniform mat4 modelViewMatrix; + +varying vec2 vUv; + +void main() { +vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +}`, + fragmentShader: `${ + webgl2 + ? `#version 300 es +#define varying in +out highp vec4 pc_fragColor; + +#define texture2D texture +#define gl_FragColor pc_fragColor` + : '' + } +#ifdef GL_OES_standard_derivatives + #extension GL_OES_standard_derivatives : enable +#endif +precision ${precision} float; +uniform float opacity; +uniform vec3 color; +uniform sampler2D map; +varying vec2 vUv; + +float aastep(float value) { + #ifdef GL_OES_standard_derivatives + float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; + #else + float afwidth = (1.0 / 32.0) * (1.4142135623730951 / (2.0 * gl_FragCoord.w)); + #endif + return smoothstep(0.5 - afwidth, 0.5 + afwidth, value); +} + +void main() { + vec4 texColor = texture2D(map, vUv); + float alpha = aastep(texColor.a); + gl_FragColor = vec4(color, opacity * alpha); + ${alphaTest === 0 ? '' : `if (gl_FragColor.a < ${alphaTest}) discard;`} +}` }, - vertexShader: [ - 'attribute vec2 uv;', - 'attribute vec4 position;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 modelViewMatrix;', - 'varying vec2 vUv;', - 'void main() {', - 'vUv = uv;', - 'gl_Position = projectionMatrix * modelViewMatrix * position;', - '}' - ].join('\n'), - fragmentShader: [ - '#ifdef GL_OES_standard_derivatives', - '#extension GL_OES_standard_derivatives : enable', - '#endif', - 'precision ' + precision + ' float;', - 'uniform float opacity;', - 'uniform vec3 color;', - 'uniform sampler2D map;', - 'varying vec2 vUv;', - - 'float aastep(float value) {', - ' #ifdef GL_OES_standard_derivatives', - ' float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;', - ' #else', - ' float afwidth = (1.0 / 32.0) * (1.4142135623730951 / (2.0 * gl_FragCoord.w));', - ' #endif', - ' return smoothstep(0.5 - afwidth, 0.5 + afwidth, value);', - '}', - - 'void main() {', - ' vec4 texColor = texture2D(map, vUv);', - ' float alpha = aastep(texColor.a);', - ' gl_FragColor = vec4(color, opacity * alpha);', - alphaTest === 0 - ? '' - : ' if (gl_FragColor.a < ' + alphaTest + ') discard;', - '}' - ].join('\n') - }, opt) + opt + ) } diff --git a/test/load.js b/test/load.js index 2a8e06e..23571f5 100644 --- a/test/load.js +++ b/test/load.js @@ -3,10 +3,10 @@ global.THREE = require('three') // A utility to load a font, then a texture module.exports = function (opt, cb) { - loadFont(opt.font, function (err, font) { - if (err) throw err - THREE.ImageUtils.loadTexture(opt.image, undefined, function (tex) { - cb(font, tex) - }) - }) + loadFont(opt.font, function (err, font) { + if (err) throw err + THREE.ImageUtils.loadTexture(opt.image, undefined, function (tex) { + cb(font, tex) + }) + }) }