Created
May 24, 2011 11:46
-
-
Save zz85/988576 to your computer and use it in GitHub Desktop.
THREE.FontText
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author mr.doob / http://mrdoob.com/ | |
* @author philogb / http://blog.thejit.org/ | |
* @author zz85 / http://www.lab4games.net/zz85/blog | |
*/ | |
THREE.Vector2 = function ( x, y ) { | |
this.set( | |
x || 0, | |
y || 0 | |
); | |
}; | |
THREE.Vector2.prototype = { | |
set : function ( x, y ) { | |
this.x = x; | |
this.y = y; | |
return this; | |
}, | |
copy : function ( v ) { | |
this.set( | |
v.x, | |
v.y | |
); | |
return this; | |
}, | |
addSelf : function ( v ) { | |
this.set( | |
this.x + v.x, | |
this.y + v.y | |
); | |
return this; | |
}, | |
add : function ( v1, v2 ) { | |
this.set( | |
v1.x + v2.x, | |
v1.y + v2.y | |
); | |
return this; | |
}, | |
subSelf : function ( v ) { | |
this.set( | |
this.x - v.x, | |
this.y - v.y | |
); | |
return this; | |
}, | |
sub : function ( v1, v2 ) { | |
this.set( | |
v1.x - v2.x, | |
v1.y - v2.y | |
); | |
return this; | |
}, | |
multiplyScalar : function ( s ) { | |
this.set( | |
this.x * s, | |
this.y * s | |
); | |
return this; | |
}, | |
negate : function() { | |
this.set( | |
- this.x, | |
- this.y | |
); | |
return this; | |
}, | |
unit : function () { | |
this.multiplyScalar( 1 / this.length() ); | |
return this; | |
}, | |
length : function () { | |
return Math.sqrt( this.lengthSq() ); | |
}, | |
lengthSq : function () { | |
return this.x * this.x + this.y * this.y; | |
}, | |
distanceTo : function ( v ) { | |
return Math.sqrt( this.distanceToSquared( v ) ); | |
}, | |
distanceToSquared : function ( v ) { | |
var dx = this.x - v.x, dy = this.y - v.y; | |
return dx * dx + dy * dy; | |
}, | |
clone : function () { | |
return new THREE.Vector2( this.x, this.y ); | |
}, | |
equals : function(v) { | |
return ( (v.x == this.x) && (v.y == this.y) ); | |
} | |
}; | |
/** | |
* @author zz85 / http://www.lab4games.net/zz85/blog | |
* THREE.FontText.js | |
* Techniques obtained from typeface.js, canvastext | |
* Triangulation ** with holes http://www.sakri.net/blog/2009/06/12/an-approach-to-triangulating-polygons-with-holes/ | |
*/ | |
/* | |
* An experiment to use typeface fonts in Three.js | |
* for creating 2d and 3d text | |
* Adapted from typeface.js and canvastext projects | |
*/ | |
THREE.Text = function (text, size, height, curveSegments, font) { | |
if (!size) size = 100; | |
if (!height) height = 50; | |
if (!curveSegments) curveSegments = 4; | |
if (!font) font = ""; | |
THREE.Geometry.call( this ); | |
var data = ThreeFont.drawText(text); | |
vertices = data.points; | |
faces = data.faces; | |
contour = data.contour; | |
console.log("data", data); | |
var scope = this; | |
//console.log(vertices, faces); | |
var vlen = 0; | |
for (var i in vertices) { | |
var vert = vertices[i]; | |
v(vert.x, vert.y, 0); | |
vlen++; | |
} | |
for (var i in vertices) { | |
var vert = vertices[i]; | |
v(vert.x, vert.y, height); | |
} | |
for (var i in faces) { | |
var face = faces[i]; | |
f3(face[0], face[1], face[2]); | |
} | |
for (var i in faces) { | |
var face = faces[i]; | |
f3(face[0]+vlen, face[1]+vlen, face[2]+vlen); | |
// f3(face[2]+vlen, face[1]+vlen, face[0]+vlen); | |
} | |
var i = contour.length; | |
var lastV; | |
var j; | |
while (--i > 0) { | |
if (!lastV) { | |
lastV = contour[i]; | |
} else if (lastV.equals(contour[i])) { | |
lastV = null; | |
continue; | |
} | |
var j,k; | |
for (j=0; j<vertices.length; j++) { | |
if (vertices[j].equals(contour[i])) { | |
break; | |
} | |
} | |
for (k=0; k<vertices.length; k++) { | |
if (vertices[k].equals(contour[i-1])) break; | |
} | |
console.log("add new face 4 ", j,k); | |
//f4(j+vlen, k+vlen,k,j); | |
f4(j,k, k+vlen, j+vlen); | |
} | |
/* for (var i in faces) { | |
var face = faces[i]; | |
f3(face.a, face.b, face.c); | |
console.log(face); | |
} | |
*/ | |
// UVs to be added | |
this.computeCentroids(); | |
this.computeFaceNormals(); | |
// this.computeVertexNormals(); | |
function v( x, y, z ) { | |
scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) ); | |
} | |
function f3( a, b, c) { | |
scope.faces.push( new THREE.Face3( a, b, c) ); | |
} | |
function f4( a, b, c, d) { | |
scope.faces.push( new THREE.Face4( a, b, c, d) ); | |
} | |
console.log(this); | |
}; | |
THREE.Text.prototype = new THREE.Geometry(); | |
THREE.Text.prototype.constructor = THREE.Text; | |
var ThreeFont = {}; | |
ThreeFont.faces = {}; | |
// Just for now. face[weight][style] | |
ThreeFont.face = "helvetiker"; | |
ThreeFont.weight = "normal"; | |
ThreeFont.style = "normal"; | |
ThreeFont.size = 150; | |
ThreeFont.getFace = function() { | |
return this.faces[this.face][this.weight][this.style]; | |
} | |
ThreeFont.loadFace = function(data) { | |
var family = data.familyName.toLowerCase(); | |
this.faces[family] = this.faces[family] || {}; | |
if (data.strokeFont) { | |
this.faces[family].normal = this.faces[family].normal || {}; | |
this.faces[family].normal.normal = data; | |
this.faces[family].normal.italic = data; | |
this.faces[family].bold = this.faces[family].normal || {}; | |
this.faces[family].bold.normal = data; | |
this.faces[family].bold.italic = data; | |
} | |
else { | |
this.faces[family][data.cssFontWeight] = this.faces[family][data.cssFontWeight] || {}; | |
this.faces[family][data.cssFontWeight][data.cssFontStyle] = data; | |
} | |
//console.log(ThreeFont, data); | |
return data; | |
}; | |
ThreeFont.extractPoints = function(points) { | |
// try to split shapes and holes. | |
var all = [], point, shape; | |
// Use a quick hashmap for locating duplicates | |
for (var p in points) { | |
point = points[p]; | |
all.push(point.x +","+ point.y); | |
} | |
if (points.length <3) { | |
console.log("not valid polygon"); | |
return; | |
} | |
var firstPt = all[0]; | |
var endPt = all.slice(1).indexOf(firstPt); | |
var n = points.slice(1).indexOf(points[0]); | |
console.log(endPt + "=="+ n); | |
if (endPt < all.length) { | |
endPt ++; | |
shape = points.slice(0, endPt); | |
//console.log("first and end", firstPt, endPt, shape.length, all.length); | |
} | |
holes = []; | |
while (endPt < all.length) { | |
firstIndex = endPt+1; | |
firstPt = all[firstIndex]; | |
endPt = all.slice(firstIndex+1).indexOf(firstPt) + firstIndex; | |
console.log(firstIndex, endPt, "slicing first and last"); | |
if (endPt <= firstIndex ) break; | |
var contours = points.slice(firstIndex, endPt+1); | |
if (THREE.Triangulate.area(contours)<0) { | |
holes.push(contours); | |
} else { | |
//shape = shape.concat(contours); | |
} | |
endPt++; | |
console.log("holes--> ", contours.length); | |
console.log("CCW", THREE.Triangulate.area(contours)); | |
} | |
console.log("holes", holes); | |
// we could optimize here | |
// Find closest points between holes | |
//http://en.wikipedia.org/wiki/Proximity_problems | |
//http://en.wikipedia.org/wiki/Closest_pair_of_points | |
// http://stackoverflow.com/questions/1602164/shortest-distance-between-points-algorithm | |
var verts = []; | |
for (var h in holes) { | |
var hole = holes[h]; | |
var shortest = Number.POSITIVE_INFINITY; | |
var holeIndex, pointIndex; | |
for (var h2 in hole) { | |
var pts1 = hole[h2]; | |
for (var p in shape) { | |
var pts2 = shape[p]; | |
d = pts1.distanceTo(pts2); | |
if (d<shortest) { | |
shortest = d; | |
holeIndex = h2; | |
pointIndex = p; | |
} | |
} | |
} | |
console.log("point ind, hole ind", pointIndex, holeIndex); | |
console.log("shape.length, tmpShape",shape.length, hole.length); | |
prevShapeVert = (pointIndex-1)>=0 ? pointIndex-1:shape.length-1 ; | |
nextShapeVert = (pointIndex+1)<shape.length ? pointIndex + 1 : 0; | |
prevHoleVert = (holeIndex-1)>=0 ? holeIndex-1:hole.length-1; | |
nextHoleVert = (holeIndex+1)<hole.length ? holeIndex + 1 : 0 ; | |
var tmpShape1 = shape.slice(0, pointIndex); | |
var tmpShape2 = shape.slice(pointIndex); | |
var tmpHole1 = hole.slice(holeIndex); | |
var tmpHole2 = hole.slice(0,holeIndex); | |
verts.push(shape[prevShapeVert]); | |
verts.push(shape[pointIndex]); | |
verts.push(hole[holeIndex]); | |
verts.push(hole[holeIndex]); | |
verts.push(hole[prevHoleVert]); | |
verts.push(shape[pointIndex]); | |
shape = tmpShape1.concat(tmpHole1).concat(tmpHole2).concat(tmpShape2); | |
} | |
points = []; | |
for (var s in shape) { | |
points.push(shape[s]); | |
} | |
var triangles = THREE.Triangulate(points); | |
var triangulatedVertices = THREE.Triangulate(points,true); | |
// Now we push the "cutted" vertics back to the triangulated indics. | |
var j; | |
for (var v=0; v<verts.length/3; v++) { | |
var face = []; | |
for (var k=0;k<3;k++) { | |
for (j=0; j<points.length; j++) { | |
if (points[j].equals(verts[v+k])) { | |
face.push(j); | |
break; | |
} | |
} | |
} | |
triangulatedVertices.push(face); | |
} | |
console.log("triangles", triangles.length); | |
//console.log(verts, triangles); | |
//console.log(JSON.stringify(points)); //pts2 | |
//console.log(JSON.stringify(verts)); //pts2 | |
//console.log(JSON.stringify(triangulatedVertices)); | |
return { | |
points: points, | |
faces: triangulatedVertices | |
}; | |
} | |
ThreeFont.drawText = function(text) { | |
pts = []; | |
// RenderText // (text); | |
var face = this.getFace(), | |
scale = (this.size / face.resolution), | |
offset = 0, i, | |
chars = String(text).split(''), | |
length = chars.length; | |
for (i = 0; i < length; i++) { | |
offset += this.extractGlyphPoints(chars[i], face, scale, offset); | |
} | |
console.log("Length pts", pts.length); | |
var extract = this.extractPoints(pts); | |
extract.contour = pts; | |
return extract; | |
}; | |
// Quad Bezier http://en.wikipedia.org/wiki/B%C3%A9zier_curve | |
function b2p0(t, p) { | |
return (1-t)*(1-t) * p; | |
} | |
function b2p1(t, p) { | |
return 2*(1-t) * t * p; | |
} | |
function b2p2(t, p) { | |
return t * t * p; | |
} | |
function b2(t, p0, p1, p2) { | |
return b2p0(t, p0) + b2p1(t, p1) + b2p2(t, p2); | |
} | |
ThreeFont.extractGlyphPoints = function(c, face, scale, offset){ | |
var i, cpx, cpy, outline, action, length, | |
glyph = face.glyphs[c] || face.glyphs[ctxt.options.fallbackCharacter]; | |
if (!glyph) return; | |
if (glyph.o) { | |
outline = glyph._cachedOutline || (glyph._cachedOutline = glyph.o.split(' ')); | |
length = outline.length; | |
for (i = 0; i < length; ) { | |
action = outline[i++]; | |
switch(action) { | |
case 'm': | |
// Move To | |
x = outline[i++]*scale+offset, y = outline[i++]*-scale; | |
pts.push(new THREE.Vector2(x,y)); | |
break; | |
case 'l': | |
// Line To | |
x = outline[i++]*scale+offset, y = outline[i++]*-scale; | |
pts.push(new THREE.Vector2(x,y)); | |
break; | |
case 'q': | |
//quadraticCurveTo | |
cpx = outline[i++]*scale+offset; | |
cpy = outline[i++]*-scale; | |
cpx1 = outline[i++]*scale+offset; | |
cpy1 = outline[i++]*-scale; | |
var laste = pts.pop(); | |
if (laste) { | |
cpx0 = laste.x; | |
cpy0 = laste.y; | |
pts.push(laste); | |
for (var i2 = 1, divisions = 10;i2<=divisions;i2++) { | |
var t = i2/divisions; | |
var tx = b2(t, cpx0, cpx1, cpx); | |
var ty = b2(t, cpy0, cpy1, cpy); | |
pts.push(new THREE.Vector2(tx, ty)); | |
} | |
} | |
break; | |
case 'b': | |
cpx = outline[i++]*scale+offset; | |
cpy = outline[i++]*-scale; | |
pts.push(new THREE.Vector2(cpx,cpy)); | |
//this.ctx.bezierCurveTo(outline[i++]*scale+offset, outline[i++]*-scale, outline[i++]*scale+offset, outline[i++]*-scale, cpx, cpy); | |
break; | |
} | |
} | |
} | |
return glyph.ha*scale; | |
}; | |
/** | |
This code is a quick port of code written in C++ which was submitted to | |
flipcode.com by John W. Ratcliff // July 22, 2000 | |
See original code and more information here: | |
http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml | |
ported to actionscript by Zevan Rosser | |
www.actionsnippet.com | |
ported to javascript by Joshua Koo | |
*/ | |
(function(THREE) { | |
var EPSILON =0.0000000001; | |
// takes in an contour array and returns | |
var process = function(contour, indics) { | |
var result = []; | |
var n = contour.length; | |
if ( n < 3 ) return null | |
var verts = [], vertIndics = []; | |
/* we want a counter-clockwise polygon in verts */ | |
var v; | |
if ( 0.0 < area(contour)) { | |
for (v=0; v<n; v++) verts[v] = v; | |
} else { | |
for (v=0; v<n; v++) verts[v] = (n-1)-v; | |
} | |
var nv = n; | |
/* remove nv-2 vertsertices, creating 1 triangle every time */ | |
var count = 2*nv; /* error detection */ | |
var m; | |
for(m=0, v=nv-1; nv>2; ) | |
{ | |
/* if we loop, it is probably a non-simple polygon */ | |
if (0 >= (count--)){ | |
//** Triangulate: ERROR - probable bad polygon! | |
console.log("Warning, unable to triangulate polygon!"); | |
return null; | |
} | |
/* three consecutive vertices in current polygon, <u,v,w> */ | |
var u = v; if (nv <= u) u = 0; /* previous */ | |
v = u+1; if (nv <= v) v = 0; /* new v */ | |
var w = v+1; if (nv <= w) w = 0; /* next */ | |
if ( snip(contour,u,v,w,nv,verts)){ | |
var a,b,c,s,t; | |
/* true names of the vertices */ | |
a = verts[u]; b = verts[v]; c = verts[w]; | |
/* output Triangle */ | |
result.push( contour[a] ); | |
result.push( contour[b] ); | |
result.push( contour[c] ); | |
vertIndics.push([verts[u], verts[v],verts[w]]); | |
//vertIndics.push(THREE.Face3(verts[u], verts[v],verts[w])); | |
m++; | |
/* remove v from remaining polygon */ | |
for(s=v,t=v+1;t<nv;s++,t++) { | |
verts[s] = verts[t]; | |
} | |
nv--; | |
/* resest error detection counter */ | |
count = 2 * nv; | |
} | |
} | |
if (indics) return vertIndics; | |
return result; | |
}; | |
// calculate area of the contour polygon | |
var area = function (contour){ | |
var n = contour.length; | |
var a = 0.0; | |
for(var p=n-1, q=0; q<n; p=q++){ | |
a += contour[p].x * contour[q].y - contour[q].x * contour[p].y; | |
} | |
return a * 0.5; | |
}; | |
// see if p is inside triangle abc | |
var insideTriangle = function(ax, ay, | |
bx, by, | |
cx, cy, | |
px, py){ | |
var aX, aY, bX, bY; | |
var cX, cY, apx, apy; | |
var bpx, bpy, cpx, cpy; | |
var cCROSSap, bCROSScp, aCROSSbp; | |
aX = cx - bx; aY = cy - by; | |
bX = ax - cx; bY = ay - cy; | |
cX = bx - ax; cY = by - ay; | |
apx= px -ax; apy= py - ay; | |
bpx= px - bx; bpy= py - by; | |
cpx= px - cx; cpy= py - cy; | |
aCROSSbp = aX*bpy - aY*bpx; | |
cCROSSap = cX*apy - cY*apx; | |
bCROSScp = bX*cpy - bY*cpx; | |
return ((aCROSSbp >= 0.0) && (bCROSScp >= 0.0) && (cCROSSap >= 0.0)); | |
}; | |
var snip = function (contour, u, v, w, n, verts) { | |
var p; | |
var ax, ay, bx, by; | |
var cx, cy, px, py; | |
ax = contour[verts[u]].x; | |
ay = contour[verts[u]].y; | |
bx = contour[verts[v]].x; | |
by = contour[verts[v]].y; | |
cx = contour[verts[w]].x; | |
cy = contour[verts[w]].y; | |
if ( EPSILON > (((bx-ax)*(cy-ay)) - ((by-ay)*(cx-ax))) ) return false; | |
for (p=0;p<n;p++){ | |
if( (p == u) || (p == v) || (p == w) ) continue; | |
px = contour[verts[p]].x | |
py = contour[verts[p]].y | |
if (insideTriangle(ax,ay,bx,by,cx,cy,px,py)) return false; | |
} | |
return true; | |
}; | |
THREE.Triangulate = process; | |
THREE.Triangulate.area = area; | |
return THREE; | |
})(THREE); | |
// To use the typeface.js face files, hook their API | |
window._typeface_js = {faces: ThreeFont.faces, loadFace: ThreeFont.loadFace}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment