diff --git a/assets/fonts/arial.fnt b/assets/fonts/arial.fnt new file mode 100644 index 0000000..bfdb14e --- /dev/null +++ b/assets/fonts/arial.fnt @@ -0,0 +1,198 @@ +info face="Arial" size=82 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=3,3,3,3 spacing=-2,-2 +common lineHeight=100 base=75 scaleW=512 scaleH=512 pages=1 packed=0 +page id=0 file="arial.png" +chars count=97 +char id=0 x=344 y=346 width=48 height=58 xoffset=7 yoffset=20 xadvance=66 page=0 chnl=0 +char id=10 x=0 y=0 width=0 height=0 xoffset=-3 yoffset=0 xadvance=4 page=0 chnl=0 +char id=32 x=0 y=0 width=0 height=0 xoffset=-3 yoffset=0 xadvance=27 page=0 chnl=0 +char id=33 x=118 y=346 width=13 height=65 xoffset=6 yoffset=13 xadvance=29 page=0 chnl=0 +char id=34 x=250 y=462 width=28 height=27 xoffset=0 yoffset=13 xadvance=33 page=0 chnl=0 +char id=35 x=191 y=346 width=49 height=65 xoffset=-2 yoffset=13 xadvance=50 page=0 chnl=0 +char id=36 x=269 y=0 width=44 height=76 xoffset=0 yoffset=10 xadvance=50 page=0 chnl=0 +char id=37 x=158 y=83 width=69 height=67 xoffset=2 yoffset=12 xadvance=77 page=0 chnl=0 +char id=38 x=227 y=83 width=55 height=67 xoffset=1 yoffset=12 xadvance=59 page=0 chnl=0 +char id=39 x=489 y=411 width=13 height=27 xoffset=1 yoffset=13 xadvance=20 page=0 chnl=0 +char id=40 x=177 y=0 width=25 height=81 xoffset=2 yoffset=13 xadvance=31 page=0 chnl=0 +char id=41 x=202 y=0 width=25 height=81 xoffset=0 yoffset=13 xadvance=31 page=0 chnl=0 +char id=42 x=216 y=462 width=34 height=31 xoffset=0 yoffset=13 xadvance=36 page=0 chnl=0 +char id=43 x=88 y=462 width=45 height=45 xoffset=1 yoffset=24 xadvance=52 page=0 chnl=0 +char id=44 x=278 y=462 width=13 height=26 xoffset=4 yoffset=65 xadvance=27 page=0 chnl=0 +char id=45 x=371 y=462 width=28 height=13 xoffset=0 yoffset=47 xadvance=31 page=0 chnl=0 +char id=46 x=358 y=462 width=13 height=13 xoffset=4 yoffset=65 xadvance=27 page=0 chnl=0 +char id=47 x=131 y=346 width=30 height=65 xoffset=-3 yoffset=13 xadvance=27 page=0 chnl=0 +char id=48 x=309 y=150 width=44 height=66 xoffset=1 yoffset=13 xadvance=50 page=0 chnl=0 +char id=49 x=0 y=346 width=28 height=65 xoffset=6 yoffset=13 xadvance=50 page=0 chnl=0 +char id=50 x=28 y=346 width=46 height=65 xoffset=-2 yoffset=13 xadvance=50 page=0 chnl=0 +char id=51 x=131 y=150 width=44 height=66 xoffset=1 yoffset=13 xadvance=50 page=0 chnl=0 +char id=52 x=240 y=346 width=47 height=64 xoffset=-2 yoffset=14 xadvance=50 page=0 chnl=0 +char id=53 x=74 y=346 width=44 height=65 xoffset=1 yoffset=14 xadvance=50 page=0 chnl=0 +char id=54 x=175 y=150 width=46 height=66 xoffset=-1 yoffset=13 xadvance=50 page=0 chnl=0 +char id=55 x=287 y=346 width=44 height=64 xoffset=1 yoffset=14 xadvance=50 page=0 chnl=0 +char id=56 x=221 y=150 width=44 height=66 xoffset=1 yoffset=13 xadvance=50 page=0 chnl=0 +char id=57 x=265 y=150 width=44 height=66 xoffset=1 yoffset=13 xadvance=50 page=0 chnl=0 +char id=58 x=476 y=411 width=13 height=49 xoffset=4 yoffset=29 xadvance=27 page=0 chnl=0 +char id=59 x=331 y=346 width=13 height=62 xoffset=4 yoffset=29 xadvance=27 page=0 chnl=0 +char id=60 x=0 y=462 width=44 height=45 xoffset=2 yoffset=24 xadvance=52 page=0 chnl=0 +char id=61 x=171 y=462 width=45 height=31 xoffset=1 yoffset=30 xadvance=52 page=0 chnl=0 +char id=62 x=44 y=462 width=44 height=45 xoffset=2 yoffset=24 xadvance=52 page=0 chnl=0 +char id=63 x=353 y=150 width=44 height=66 xoffset=1 yoffset=12 xadvance=50 page=0 chnl=0 +char id=64 x=70 y=0 width=82 height=83 xoffset=1 yoffset=12 xadvance=87 page=0 chnl=0 +char id=65 x=397 y=150 width=63 height=65 xoffset=-5 yoffset=13 xadvance=59 page=0 chnl=0 +char id=66 x=460 y=150 width=50 height=65 xoffset=4 yoffset=13 xadvance=59 page=0 chnl=0 +char id=67 x=375 y=0 width=57 height=67 xoffset=1 yoffset=12 xadvance=63 page=0 chnl=0 +char id=68 x=0 y=216 width=54 height=65 xoffset=4 yoffset=13 xadvance=63 page=0 chnl=0 +char id=69 x=54 y=216 width=50 height=65 xoffset=4 yoffset=13 xadvance=59 page=0 chnl=0 +char id=70 x=104 y=216 width=45 height=65 xoffset=4 yoffset=13 xadvance=54 page=0 chnl=0 +char id=71 x=432 y=0 width=61 height=67 xoffset=1 yoffset=12 xadvance=68 page=0 chnl=0 +char id=72 x=149 y=216 width=51 height=65 xoffset=4 yoffset=13 xadvance=63 page=0 chnl=0 +char id=73 x=489 y=83 width=14 height=65 xoffset=4 yoffset=13 xadvance=27 page=0 chnl=0 +char id=74 x=282 y=83 width=38 height=66 xoffset=-1 yoffset=13 xadvance=45 page=0 chnl=0 +char id=75 x=200 y=216 width=55 height=65 xoffset=4 yoffset=13 xadvance=59 page=0 chnl=0 +char id=76 x=255 y=216 width=42 height=65 xoffset=4 yoffset=13 xadvance=50 page=0 chnl=0 +char id=77 x=297 y=216 width=63 height=65 xoffset=2 yoffset=13 xadvance=71 page=0 chnl=0 +char id=78 x=360 y=216 width=51 height=65 xoffset=4 yoffset=13 xadvance=63 page=0 chnl=0 +char id=79 x=0 y=83 width=62 height=67 xoffset=1 yoffset=12 xadvance=68 page=0 chnl=0 +char id=80 x=411 y=216 width=50 height=65 xoffset=4 yoffset=13 xadvance=59 page=0 chnl=0 +char id=81 x=313 y=0 width=62 height=71 xoffset=1 yoffset=12 xadvance=68 page=0 chnl=0 +char id=82 x=0 y=281 width=56 height=65 xoffset=4 yoffset=13 xadvance=63 page=0 chnl=0 +char id=83 x=62 y=83 width=53 height=67 xoffset=1 yoffset=12 xadvance=59 page=0 chnl=0 +char id=84 x=56 y=281 width=52 height=65 xoffset=-1 yoffset=13 xadvance=54 page=0 chnl=0 +char id=85 x=320 y=83 width=51 height=66 xoffset=4 yoffset=13 xadvance=63 page=0 chnl=0 +char id=86 x=108 y=281 width=63 height=65 xoffset=-4 yoffset=13 xadvance=59 page=0 chnl=0 +char id=87 x=171 y=281 width=85 height=65 xoffset=-2 yoffset=13 xadvance=85 page=0 chnl=0 +char id=88 x=256 y=281 width=59 height=65 xoffset=-3 yoffset=13 xadvance=58 page=0 chnl=0 +char id=89 x=315 y=281 width=60 height=65 xoffset=-3 yoffset=13 xadvance=58 page=0 chnl=0 +char id=90 x=375 y=281 width=52 height=65 xoffset=-1 yoffset=13 xadvance=54 page=0 chnl=0 +char id=91 x=227 y=0 width=21 height=81 xoffset=2 yoffset=13 xadvance=27 page=0 chnl=0 +char id=92 x=161 y=346 width=30 height=65 xoffset=-3 yoffset=13 xadvance=27 page=0 chnl=0 +char id=93 x=248 y=0 width=21 height=81 xoffset=0 yoffset=13 xadvance=27 page=0 chnl=0 +char id=94 x=133 y=462 width=38 height=38 xoffset=-1 yoffset=12 xadvance=40 page=0 chnl=0 +char id=95 x=399 y=462 width=55 height=13 xoffset=-4 yoffset=81 xadvance=50 page=0 chnl=0 +char id=96 x=338 y=462 width=20 height=17 xoffset=0 yoffset=13 xadvance=31 page=0 chnl=0 +char id=97 x=392 y=346 width=46 height=51 xoffset=0 yoffset=28 xadvance=50 page=0 chnl=0 +char id=98 x=371 y=83 width=43 height=66 xoffset=2 yoffset=13 xadvance=50 page=0 chnl=0 +char id=99 x=438 y=346 width=43 height=51 xoffset=0 yoffset=28 xadvance=45 page=0 chnl=0 +char id=100 x=414 y=83 width=43 height=66 xoffset=0 yoffset=13 xadvance=50 page=0 chnl=0 +char id=101 x=0 y=411 width=46 height=51 xoffset=0 yoffset=28 xadvance=50 page=0 chnl=0 +char id=102 x=457 y=83 width=32 height=66 xoffset=-1 yoffset=12 xadvance=28 page=0 chnl=0 +char id=103 x=115 y=83 width=43 height=67 xoffset=0 yoffset=28 xadvance=49 page=0 chnl=0 +char id=104 x=461 y=216 width=40 height=65 xoffset=3 yoffset=13 xadvance=50 page=0 chnl=0 +char id=105 x=493 y=0 width=13 height=65 xoffset=3 yoffset=13 xadvance=22 page=0 chnl=0 +char id=106 x=152 y=0 width=25 height=82 xoffset=-7 yoffset=13 xadvance=21 page=0 chnl=0 +char id=107 x=427 y=281 width=41 height=65 xoffset=2 yoffset=13 xadvance=45 page=0 chnl=0 +char id=108 x=468 y=281 width=13 height=65 xoffset=3 yoffset=13 xadvance=22 page=0 chnl=0 +char id=109 x=133 y=411 width=63 height=50 xoffset=2 yoffset=28 xadvance=71 page=0 chnl=0 +char id=110 x=196 y=411 width=40 height=50 xoffset=3 yoffset=28 xadvance=50 page=0 chnl=0 +char id=111 x=46 y=411 width=46 height=51 xoffset=0 yoffset=28 xadvance=50 page=0 chnl=0 +char id=112 x=0 y=150 width=43 height=66 xoffset=2 yoffset=28 xadvance=50 page=0 chnl=0 +char id=113 x=43 y=150 width=43 height=66 xoffset=0 yoffset=28 xadvance=49 page=0 chnl=0 +char id=114 x=481 y=346 width=29 height=50 xoffset=3 yoffset=28 xadvance=31 page=0 chnl=0 +char id=115 x=92 y=411 width=41 height=51 xoffset=0 yoffset=28 xadvance=45 page=0 chnl=0 +char id=116 x=481 y=281 width=26 height=65 xoffset=-1 yoffset=14 xadvance=27 page=0 chnl=0 +char id=117 x=236 y=411 width=40 height=50 xoffset=3 yoffset=29 xadvance=50 page=0 chnl=0 +char id=118 x=276 y=411 width=45 height=49 xoffset=-2 yoffset=29 xadvance=45 page=0 chnl=0 +char id=119 x=321 y=411 width=66 height=49 xoffset=-4 yoffset=29 xadvance=61 page=0 chnl=0 +char id=120 x=387 y=411 width=46 height=49 xoffset=-2 yoffset=29 xadvance=45 page=0 chnl=0 +char id=121 x=86 y=150 width=45 height=66 xoffset=-2 yoffset=29 xadvance=43 page=0 chnl=0 +char id=122 x=433 y=411 width=43 height=49 xoffset=-2 yoffset=29 xadvance=44 page=0 chnl=0 +char id=123 x=0 y=0 width=29 height=83 xoffset=-1 yoffset=12 xadvance=31 page=0 chnl=0 +char id=124 x=58 y=0 width=12 height=83 xoffset=4 yoffset=13 xadvance=24 page=0 chnl=0 +char id=125 x=29 y=0 width=29 height=83 xoffset=-1 yoffset=12 xadvance=31 page=0 chnl=0 +char id=126 x=291 y=462 width=47 height=21 xoffset=0 yoffset=37 xadvance=52 page=0 chnl=0 +kernings count=96 +kerning first=121 second=46 amount=-6 +kerning first=84 second=45 amount=-5 +kerning first=86 second=44 amount=-8 +kerning first=114 second=46 amount=-5 +kerning first=114 second=44 amount=-5 +kerning first=49 second=49 amount=-6 +kerning first=89 second=65 amount=-6 +kerning first=84 second=79 amount=-1 +kerning first=65 second=84 amount=-6 +kerning first=76 second=86 amount=-6 +kerning first=65 second=87 amount=-3 +kerning first=76 second=89 amount=-6 +kerning first=84 second=99 amount=-9 +kerning first=86 second=101 amount=-5 +kerning first=102 second=102 amount=-1 +kerning first=89 second=105 amount=-3 +kerning first=86 second=58 amount=-3 +kerning first=86 second=111 amount=-5 +kerning first=89 second=112 amount=-6 +kerning first=89 second=113 amount=-8 +kerning first=86 second=114 amount=-3 +kerning first=89 second=117 amount=-5 +kerning first=65 second=118 amount=-1 +kerning first=84 second=119 amount=-5 +kerning first=76 second=121 amount=-3 +kerning first=82 second=84 amount=-1 +kerning first=89 second=58 amount=-5 +kerning first=87 second=65 amount=-3 +kerning first=87 second=97 amount=-3 +kerning first=87 second=59 amount=-1 +kerning first=82 second=87 amount=-1 +kerning first=118 second=46 amount=-6 +kerning first=65 second=89 amount=-6 +kerning first=65 second=86 amount=-6 +kerning first=80 second=44 amount=-11 +kerning first=86 second=46 amount=-8 +kerning first=84 second=105 amount=-3 +kerning first=84 second=97 amount=-9 +kerning first=84 second=114 amount=-3 +kerning first=80 second=65 amount=-6 +kerning first=84 second=58 amount=-9 +kerning first=86 second=97 amount=-6 +kerning first=76 second=84 amount=-6 +kerning first=89 second=59 amount=-5 +kerning first=70 second=44 amount=-9 +kerning first=80 second=46 amount=-11 +kerning first=89 second=101 amount=-8 +kerning first=65 second=119 amount=-1 +kerning first=87 second=121 amount=-1 +kerning first=76 second=87 amount=-6 +kerning first=86 second=45 amount=-5 +kerning first=32 second=89 amount=-1 +kerning first=84 second=117 amount=-3 +kerning first=89 second=118 amount=-5 +kerning first=65 second=32 amount=-5 +kerning first=86 second=65 amount=-6 +kerning first=84 second=111 amount=-9 +kerning first=89 second=45 amount=-8 +kerning first=65 second=121 amount=-1 +kerning first=87 second=58 amount=-1 +kerning first=82 second=89 amount=-1 +kerning first=89 second=44 amount=-11 +kerning first=32 second=84 amount=-1 +kerning first=87 second=111 amount=-1 +kerning first=84 second=59 amount=-9 +kerning first=84 second=101 amount=-9 +kerning first=84 second=32 amount=-1 +kerning first=86 second=59 amount=-3 +kerning first=89 second=46 amount=-11 +kerning first=87 second=101 amount=-1 +kerning first=32 second=65 amount=-5 +kerning first=84 second=44 amount=-9 +kerning first=70 second=65 amount=-5 +kerning first=86 second=117 amount=-3 +kerning first=84 second=115 amount=-9 +kerning first=84 second=65 amount=-6 +kerning first=89 second=32 amount=-1 +kerning first=87 second=44 amount=-5 +kerning first=89 second=111 amount=-8 +kerning first=89 second=97 amount=-6 +kerning first=119 second=46 amount=-5 +kerning first=87 second=46 amount=-5 +kerning first=82 second=86 amount=-1 +kerning first=121 second=44 amount=-6 +kerning first=84 second=46 amount=-9 +kerning first=80 second=32 amount=-1 +kerning first=87 second=114 amount=-1 +kerning first=119 second=44 amount=-5 +kerning first=76 second=32 amount=-3 +kerning first=84 second=121 amount=-5 +kerning first=86 second=121 amount=-3 +kerning first=70 second=46 amount=-9 +kerning first=87 second=45 amount=-1 +kerning first=118 second=44 amount=-6 +kerning first=87 second=117 amount=-1 +kerning first=86 second=105 amount=-1 diff --git a/assets/fonts/arial.png b/assets/fonts/arial.png new file mode 100644 index 0000000..8ae9e05 Binary files /dev/null and b/assets/fonts/arial.png differ diff --git a/assets/shaders/font.fs b/assets/shaders/font.fs new file mode 100644 index 0000000..a740b1b --- /dev/null +++ b/assets/shaders/font.fs @@ -0,0 +1,10 @@ +precision mediump float; + +varying vec2 uv; + +uniform vec3 uColor; +uniform sampler2D texture0; + +void main() { + gl_FragColor = vec4(uColor, texture2D(texture0, uv).a); +} diff --git a/assets/shaders/font.vs b/assets/shaders/font.vs new file mode 100644 index 0000000..4396498 --- /dev/null +++ b/assets/shaders/font.vs @@ -0,0 +1,12 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aTexCoords; + +varying vec2 uv; +uniform mat4 uTransformation; + +void main() { + gl_Position = uTransformation * vec4(aVertexPosition, 0.0, 1.0); + uv = aTexCoords; +} diff --git a/src/engine/gui/font.js b/src/engine/gui/font.js new file mode 100644 index 0000000..00a2609 --- /dev/null +++ b/src/engine/gui/font.js @@ -0,0 +1,425 @@ +import Resource from '../resource' +import Screen from '../screen' +import { Mesh } from '../mesh' +import { Texture } from '../mesh/material' +import { Node2D } from './' + +import { mat4 } from 'gl-matrix' + +const aspectRatio = Screen.width / Screen.height +const PAD_TOP = 0 +const PAD_LEFT = 1 +const PAD_BOTTOM = 2 +const PAD_RIGHT = 3 +const DESIRED_PADDING = 3 + +const SPLITTER = ' ' +const NUMBER_SEPARATOR = ',' + +const LINE_HEIGHT = 0.03 +const SPACE_ASCII = 32 +const NL_ASCII = 10 + +class Character { + constructor (id, xTexCoord, yTexCoord, xTexSize, yTexSize, + xOffset, yOffset, sizeX, sizeY, xAdvance) { + this.id = id + this.xTexCoord = xTexCoord + this.yTexCoord = yTexCoord + this.xTexSize = xTexSize + this.yTexSize = yTexSize + this.xOffset = xOffset + this.yOffset = yOffset + this.sizeX = sizeX + this.sizeY = sizeY + this.xMaxTexCoord = xTexSize + xTexCoord + this.yMaxTexCoord = yTexSize + yTexCoord + this.xAdvance = xAdvance + } +} + +class Word { + constructor (fontSize) { + this.fontSize = fontSize + this.characters = [] + this.width = 0 + } + + addCharacter (char) { + this.characters.push(char) + this.width += char.xAdvance * this.fontSize + } +} + +class Line { + constructor (spaceWidth, fontSize, maxLength) { + this.spaceSize = spaceWidth * fontSize + this.maxLength = maxLength + this.words = [] + this.lineLength = 0 + } + + attemptToAddWord (word) { + let additionalLength = word.width + additionalLength += this.words.length ? this.spaceSize : 0 + if (this.lineLength + additionalLength <= this.maxLength) { + this.words.push(word) + this.lineLength += additionalLength + return true + } + return false + } +} + +class FontFile { + constructor (name) { + this.name = name + this.values = {} + this.metadata = {} + } + + getValue (vals, key) { + return parseInt(vals[key]) + } + + getValues (vals, key) { + let nums = vals[key].split(NUMBER_SEPARATOR) + let result = [] + for (let i in nums) { + result.push(parseInt(nums[i])) + } + return result + } + + readValues (data) { + let lines = data.split('\n') + for (let i in lines) { + let line = lines[i] + let lineSplit = line.split(SPLITTER) + let lineValues = {} + + for (let j in lineSplit) { + let valuePairs = lineSplit[j].split('=') + if (valuePairs.length === 2) { + let key = valuePairs[0] + let val = valuePairs[1] + lineValues[key] = val + } + } + + if (lineSplit[0] === 'info') { + this.padding = this.getValues(lineValues, 'padding') + this.paddingWidth = this.padding[PAD_LEFT] + this.padding[PAD_RIGHT] + this.paddingHeight = this.padding[PAD_TOP] + this.padding[PAD_BOTTOM] + } else if (lineSplit[0] === 'common') { + let lineHeightPixels = this.getValue(lineValues, 'lineHeight') - this.paddingHeight + this.vertPerPixelSize = LINE_HEIGHT / lineHeightPixels + this.horizPixelSize = this.vertPerPixelSize / aspectRatio + } else if (lineSplit[0] === 'char') { + let c = this.loadCharacter(lineValues, this.getValue(this.values, 'scaleW')) + if (c) this.metadata[c.id] = c + continue + } else if (lineSplit[0] === 'kernings' || lineSplit[0] === 'kerning' || lineSplit[0] === 'chars') { + continue + } + for (let j in lineValues) { + this.values[j] = lineValues[j] + } + } + } + + loadCharacter (values, imageSize) { + let id = this.getValue(values, 'id') + if (id === SPACE_ASCII) { + this.spaceWidth = (this.getValue(values, 'xadvance') - this.paddingWidth) * this.horizPixelSize + return null + } + let xTex = (this.getValue(values, 'x') + (this.padding[PAD_LEFT] - DESIRED_PADDING)) / imageSize + let yTex = (this.getValue(values, 'y') + (this.padding[PAD_TOP] - DESIRED_PADDING)) / imageSize + let width = this.getValue(values, 'width') - (this.paddingWidth - (2 * DESIRED_PADDING)) + let height = this.getValue(values, 'height') - ((this.paddingHeight) - (2 * DESIRED_PADDING)) + let quadWidth = width * this.horizPixelSize + let quadHeight = height * this.vertPerPixelSize + let xTexSize = width / imageSize + let yTexSize = height / imageSize + let xOff = (this.getValue(values, 'xoffset') + this.padding[PAD_LEFT] - DESIRED_PADDING) * this.horizPixelSize + let yOff = (this.getValue(values, 'yoffset') + (this.padding[PAD_TOP] - DESIRED_PADDING)) * this.vertPerPixelSize + let xAdvance = (this.getValue(values, 'xadvance') - this.paddingWidth) * this.horizPixelSize + return new Character(id, xTex, yTex, xTexSize, yTexSize, xOff, yOff, quadWidth, quadHeight, xAdvance) + } + + getCharacter (ascii) { + return this.metadata[ascii] + } + + static async fromFile (fontName) { + let load = await Resource.GET('/assets/fonts/' + fontName + '.fnt') + let file = new FontFile(fontName) + file.readValues(load) + console.log(file) + return file + } +} + +class Font { + constructor (name, metadata) { + this.name = name + this.metadata = metadata + this.texture = null + } + + static async fromFile (name) { + let meta = await FontFile.fromFile(name) + return new Font(name, meta) + } + + async loadTextures (gl) { + this.texture = await Texture.createTexture2D(gl, await Resource.loadImage('/assets/fonts/' + this.name + '.png'), false, gl.LINEAR) + } + + createTextMesh (gl, text) { + let lines = this.createStructure(text) + let data = this.createQuadVertices(text, lines) + return Mesh.constructFromVerticesUVs(gl, data.vertices, data.textureCoords) + } + + createStructure (text) { + let chars = text.asCharacters + let lines = [] + let currentLine = new Line(this.metadata.spaceWidth, text.fontSize, text.lineLength) + let currentWord = new Word(text.fontSize) + for (let c in chars) { + let ascii = parseInt(chars[c]) + if (ascii === SPACE_ASCII) { + let added = currentLine.attemptToAddWord(currentWord) + if (!added) { + lines.push(currentLine) + currentLine = new Line(this.metadata.spaceWidth, text.fontSize, text.lineLength) + currentLine.attemptToAddWord(currentWord) + } + currentWord = new Word(text.fontSize) + continue + } + + if (ascii === NL_ASCII) { + let added = currentLine.attemptToAddWord(currentWord) + if (!added) { + lines.push(currentLine) + currentLine = new Line(this.metadata.spaceWidth, text.fontSize, text.lineLength) + currentLine.attemptToAddWord(currentWord) + } + lines.push(currentLine) + currentLine = new Line(this.metadata.spaceWidth, text.fontSize, text.lineLength) + currentWord = new Word(text.fontSize) + continue + } + + let character = this.metadata.getCharacter(ascii) + currentWord.addCharacter(character) + } + this.completeStructure(lines, currentLine, currentWord, text) + return lines + } + + completeStructure (lines, currentLine, currentWord, text) { + let added = currentLine.attemptToAddWord(currentWord) + if (!added) { + lines.push(currentLine) + currentLine = new Line(this.metadata.spaceWidth, text.fontSize, text.lineLength) + currentLine.attemptToAddWord(currentWord) + } + lines.push(currentLine) + return lines + } + + createQuadVertices (text, lines) { + text.lines = lines.length + let cursorX = 0 + let cursorY = 0 + let vertices = [] + let textureCoords = [] + for (let i in lines) { + let line = lines[i] + if (text.centered) { + cursorX = (line.maxLength - line.lineLength) / 2 + } + for (let j in line.words) { + let word = line.words[j] + for (let k in word.characters) { + let letter = word.characters[k] + this.addVerticesForCharacter(cursorX, cursorY, letter, text.fontSize, vertices) + this.addTexCoords(textureCoords, letter.xTexCoord, letter.yTexCoord, letter.xMaxTexCoord, letter.yMaxTexCoord) + cursorX += letter.xAdvance * text.fontSize + } + cursorX += this.metadata.spaceWidth * text.fontSize + } + cursorX = 0 + cursorY += LINE_HEIGHT * text.fontSize + } + return { vertices, textureCoords } + } + + addVerticesForCharacter (cursorX, cursorY, character, fontSize, vertices) { + let x = cursorX + (character.xOffset * fontSize) + let y = cursorY + (character.yOffset * fontSize) + let maxX = x + (character.sizeX * fontSize) + let maxY = y + (character.sizeY * fontSize) + let properX = (2 * x) - 1 + let properY = (-2 * y) + 1 + let properMaxX = (2 * maxX) - 1 + let properMaxY = (-2 * maxY) + 1 + this.addVertices(vertices, properX, properY, properMaxX, properMaxY) + } + + addVertices (vertices, x, y, maxX, maxY) { + vertices.push(x) + vertices.push(y) + vertices.push(x) + vertices.push(maxY) + vertices.push(maxX) + vertices.push(maxY) + vertices.push(maxX) + vertices.push(maxY) + vertices.push(maxX) + vertices.push(y) + vertices.push(x) + vertices.push(y) + } + + addTexCoords (texCoords, x, y, maxX, maxY) { + texCoords.push(x) + texCoords.push(y) + texCoords.push(x) + texCoords.push(maxY) + texCoords.push(maxX) + texCoords.push(maxY) + texCoords.push(maxX) + texCoords.push(maxY) + texCoords.push(maxX) + texCoords.push(y) + texCoords.push(x) + texCoords.push(y) + } +} + +class GUIText extends Node2D { + constructor (text, font, fontSize, pos, size, centered = false) { + super(pos, size) + this.text = text + this.fontSize = fontSize + this.font = font + this.centered = centered + this.color = [0.0, 0.0, 0.0] + } + + updateTransform () { + let matrix = mat4.create() + mat4.translate(matrix, matrix, [this.pos[0], this.pos[2], 0.0]) + if (this.rotation !== 0.0) { + mat4.rotate(matrix, matrix, this.rotation * Math.PI / 180, [0.0, 0.0, 1.0]) + } + + // Add parent's transform to this + if (this.parent) { + mat4.mul(matrix, this.parent.transform, matrix) + } + + // Set the matrix + this.transform = matrix + + // Update children's transforms + for (let i in this.children) { + let child = this.children[i] + if (!(child instanceof Node2D)) continue + child.updateTransform() + } + } + + createMesh (gl) { + this.mesh = this.font.createTextMesh(gl, this) + } + + get lineLength () { + return this.size[2] + } + + get asCharacters () { + let chars = [] + for (let i = 0; i < this.text.length; i++) { + chars.push(this.text.charCodeAt(i)) + } + return chars + } + + draw (gl, shader) { + const transformLocation = shader.getUniformLocation(gl, 'uTransformation') + const colorLocation = shader.getUniformLocation(gl, 'uColor') + gl.uniformMatrix4fv(transformLocation, false, this.transform) + gl.uniform3fv(colorLocation, this.color) + if (!this.mesh) this.createMesh(gl) + this.mesh.prepare(gl, shader) + this.mesh.draw(gl, shader) + this.mesh.postdraw(gl, shader) + } +} + +class FontRenderer { + discoverTextNodes (nodes) { + let textNodes = [] + for (let i in nodes) { + let node = nodes[i] + if (!(node instanceof GUIText)) { + if (node.children) { + textNodes.concat(this.discoverTextNodes(node.children)) + } + continue + } + textNodes.push(node) + } + return textNodes + } + + draw (gl, nodes) { + let fontPairs = {} + let textNodes = this.discoverTextNodes(nodes) + for (let i in textNodes) { + let node = textNodes[i] + if (!this.fonts) { + this.fonts = {} + } + + if (!this.fonts[node.font.name]) { + this.fonts[node.font.name] = node.font + } + + if (fontPairs[node.font.name]) { + fontPairs[node.font.name].push(node) + } else { + fontPairs[node.font.name] = [node] + } + } + + this.shader.use(gl) + gl.enable(gl.BLEND) + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) + gl.disable(gl.DEPTH_TEST) + for (let i in fontPairs) { + let texts = fontPairs[i] + let font = this.fonts[i] + gl.activeTexture(gl.TEXTURE0) + gl.bindTexture(font.texture.type, font.texture.id) + for (let j in texts) { + let text = texts[j] + text.draw(gl, this.shader) + } + } + gl.enable(gl.DEPTH_TEST) + gl.disable(gl.BLEND) + } + + async initialize (game) { + this.shader = await game.shaders.createShaderFromFiles(game.gl, 'font', false) + } +} + +export { FontRenderer, GUIText, Font } diff --git a/src/engine/gui/index.js b/src/engine/gui/index.js index 0134834..cc9a0a1 100644 --- a/src/engine/gui/index.js +++ b/src/engine/gui/index.js @@ -81,7 +81,7 @@ class Node2D { // Scaling this.gscale = [1.0, 1.0] - this.size = size || new Dim4(0.0, 0.0, 0.0, 0.0) + this.size = size || new Dim4(1.0, 0.0, 1.0, 0.0) // Rotation in degrees this.rotation = rotation || 0.0 @@ -214,4 +214,4 @@ class GUIRenderer { } } -export { Dim4, GUIRenderer, GUIImage } +export { Node2D, Dim4, GUIRenderer, GUIImage } diff --git a/src/engine/index.js b/src/engine/index.js index 539aefa..8bc09f5 100644 --- a/src/engine/index.js +++ b/src/engine/index.js @@ -7,7 +7,7 @@ let gl class Engine { constructor () { - this.screen = new Screen() + this.screen = Screen this.input = new Input(this.screen.gl.canvas) this.shaders = new ShaderManager() this.running = false diff --git a/src/engine/mesh/index.js b/src/engine/mesh/index.js index aa3d5f8..c8ef6f4 100644 --- a/src/engine/mesh/index.js +++ b/src/engine/mesh/index.js @@ -50,6 +50,20 @@ class Mesh { return mesh } + static constructFromVerticesUVs (gl, vertices, uvs, dimensions = 2) { + let pos = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(vertices)) + let mesh = new Mesh() + mesh.uv = uvs + mesh.posBuffer = pos + mesh.vertices = vertices + mesh.vertexCount = vertices.length / dimensions + mesh.vertexLayout = dimensions + mesh.uvs = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(uvs)) + gl.bindBuffer(gl.ARRAY_BUFFER, null) + + return mesh + } + bindBuffers (gl, shader) { this._bufferCount = 1 diff --git a/src/engine/resource.js b/src/engine/resource.js index 9eaf349..e7262af 100644 --- a/src/engine/resource.js +++ b/src/engine/resource.js @@ -53,7 +53,7 @@ function smartGET (data) { } function loadImage (url) { - url = '/assets/textures/' + url + if (url.indexOf('/') !== 0) url = '/assets/textures/' + url // Ensure we don't load a texture multiple times if (imgCache[url]) return imgCache[url] diff --git a/src/engine/screen.js b/src/engine/screen.js index 8ff22e3..1941be2 100644 --- a/src/engine/screen.js +++ b/src/engine/screen.js @@ -30,6 +30,14 @@ class Screen { this._el.width = window.innerWidth this._el.height = window.innerHeight } + + get width () { + return this._el.width + } + + get height () { + return this._el.height + } } -export default Screen +export default new Screen() diff --git a/src/index.js b/src/index.js index 3e2b2a5..b54b90b 100644 --- a/src/index.js +++ b/src/index.js @@ -9,10 +9,12 @@ import { Skybox } from './engine/components/skybox' import { SimplexHeightMap } from './engine/components/terrain/heightmap' import { Material, Texture } from './engine/mesh/material' import { GUIRenderer, GUIImage, Dim4 } from './engine/gui' +import { FontRenderer, GUIText, Font } from './engine/gui/font' let game = new Engine() let env = new Environment() let gui = new GUIRenderer() +let fnt = new FontRenderer() async function pipeline () { let entity = await loadMesh(game.gl, 'test') @@ -22,12 +24,19 @@ async function pipeline () { entity.setRotation([0.0, 0.0, -90.0]) + let arialFont = await Font.fromFile('arial') + await arialFont.loadTextures(game.gl) + // Initialize GUI await gui.initialize(game) + await fnt.initialize(game) + let itms = [ new GUIImage(await Texture.createTexture2D(game.gl, await Resource.loadImage('noisy.png'), false, game.gl.LINEAR), - new Dim4(-0.9, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0)) + new Dim4(-0.9, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0)), + new GUIText('this is example text!\nmulti line!', arialFont, 2, new Dim4(0.1, 0.0, -0.2, 0.0), new Dim4(1.0, 0.0, 0.3, 0.0), true) ] + itms[1].color = [1.0, 0.0, 0.2] // Create a height map based on OpenSimplex noise let hmap = new SimplexHeightMap(1, 1, 256, 50) @@ -106,6 +115,7 @@ async function pipeline () { game.addRenderFunction(function (gl) { gui.draw(gl, itms) + fnt.draw(gl, itms) }) game.startGameLoop()