// $Id$ int frames=0; float theta, phi; Font3D f = new Font3D(); Stanza3D stanza1, stanza2; void setup() { size(800, 400); this.stanza1 = new Stanza3D(new String[] { "SWOOPS OF LETTERS","OVER LETTERS,","INDECIPHERABLE"}); this.stanza2 = new Stanza3D(new String[] { "INDECIPHERABLE","AS ANYTHING","WRITTEN BY DESIRE" }); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getClickCount()==2) { theta = 0; phi = 0; }}}); Thread.currentThread().setPriority(1); stroke(220,200,195); framerate(20); smooth(); } void loop() { background(80); translate(400,265,0); rotateY(-.8f); push(); rotateX(-phi+(float)(Math.sin(frames/10f)*.1)); rotateY(theta+(float)(noise(frames++/10f)*.2)-.1); stanza1.render(); rotateY((float)Math.PI/2f); translate(0,30,0); stanza2.render(); pop(); } void mouseDragged() { theta += .01 * (float) (mouseX - pmouseX); phi += .01 * (float) (mouseY - pmouseY); } // Constants ------------------------------------- static final char[] PUNCT = {',','.',':','!','?'}; static final float PUNCT_WIDTH = -50f; static final float SPACE_WIDTH = -80f; static final float KERNING = 115f; static final float LEADING = 60f; static final float FONT_SZ = .3f; static final boolean SPIN = true; class Stanza3D { Text3D[] text; float fontSize; Stanza3D(String[] lines) { this(lines, FONT_SZ); } Stanza3D(String[] lines, float fontSz) { this.fontSize = fontSz; text = new Text3D[lines.length]; for (int i = 0; i < lines.length; i++) text[i] = new Text3D(lines[i],i); } void render() { for (int i = 0; i < text.length; i++) { text[i].setFontSize(fontSize); rotateY((float)-i*.15f); text[i].render(); } } } class Text3D { int lineNum = 0; float fontSize = 1; private float kwidth = 0; java.util.List letters = new ArrayList(); Text3D(String text) { this(text, 0); } Text3D(String text, int lineNum) { this.lineNum = lineNum; for (int i = 0; i < text.length(); i++) letters.add(f.getGlyph(text.charAt(i))); this.kwidth = letters.size() * KERNING; } void render() { push(); translate(0, (lineNum-2.5)*LEADING, 0); scale(fontSize*.8, fontSize, fontSize); for (int i = 0; i < letters.size(); i++) { Glyph3D g = (Glyph3D)letters.get(i); if (g.isPunct()) translate(PUNCT_WIDTH, 0, 0); else if (g.isSpace()) { translate(SPACE_WIDTH, 0, 0); continue; } push(); translate((kwidth/-2f)+1, -.5, 0); translate((i*KERNING), 0, 0); if (SPIN) rotateY((float)(frames*.1f)); g.render(); pop(); } pop(); } void setFontSize(float sz) { fontSize = sz; } void setLineNumber(int ln) { lineNum = ln; } } // end class Glyph3D { char letter; int partCount; float[][] data; void render() { for (int i = 0; i < data.length; i++) for (int j = 0; j < data[i].length; j+=2) { float sz = data[i][j+1] - data[i][j]; float avg = (data[i][j+1] + data[i][j])/2.; float trans = (avg-.5) * 100; // constant=100 push(); translate(trans, i*15, 0); scale(sz, .1, .2); box(100); pop(); } } Glyph3D(char c, float[][] dIn) { letter = c; data = new float[dIn.length-1][]; for (int j = 1; j < dIn.length; j++) { float[] dTmp = dIn[j]; data[j-1] = new float[dTmp.length]; for (int k = 0; k < dTmp.length; k++) { data[j-1][k] = dTmp[k]; if (k%2==0) partCount++; } } } public String toString() { String s = "[ "+Character.toString(letter)+": "; for (int j = 0; j < data.length; j++) { s += "{"; for (int k = 0; k < data[j].length; k++) s += data[j][k]+","; s += "}"; } return s + " ]\n"; } boolean isPunct() { for (int i = 0; i < PUNCT.length; i++) if (letter == PUNCT[i]) return true; return false; } boolean isSpace() { return (letter == ' '); } float[][] getShapeData() { return data; } char getLetter() { return letter; } } // end class Font3D { java.util.List letters = new ArrayList(); float fontData[][][] = { {{'A'}, {.4,.6},{.3,.7},{.2,.4,.6,.8},{.1,.9},{0,.2,.8,1}}, {{'B'}, {0,.8},{0,.2,.8,1},{0,.8},{0,.2,.8,1},{0,.8}}, {{'C'}, {.2,.8},{0,.2,.8,1},{0,.2},{0,.2,.8,1},{.2,.8}}, {{'D'}, {0,.8},{0,.2,.8,1},{0,.2,.8,1},{0,.2,.8,1},{0,.8}}, {{'E'}, {0,1},{0,.2,},{0,.8,},{0,.2,},{0,1}}, {{'F'}, {0,1},{0,.2,},{0,.8,},{0,.2,},{0,.2}}, {{'G'}, {.2,1},{0,.2,},{0,.2,.6,1},{0,.2,.8,1},{.2,.8}}, {{'H'}, {0,.2,.8,1},{0,.2,.8,1},{0,1},{0,.2,.8,1},{0,.2,.8,1}}, {{'I'}, {.2,.8},{.4,.6},{.4,.6},{.4,.6},{.2,.8}}, {{'J'}, {.8,1},{.8,1},{.8,1},{0,.2,.8,1},{.2,.8}}, {{'K'}, {0,.2,.8,1},{0,.2,.6,.8},{0,.6},{0,.2,.6,.8},{0,.2,.8,1}}, {{'L'}, {0,.2},{0,.2,},{0,.2,},{0,.2,},{0,1}}, {{'M'}, {0,.2,.8,1},{0,.4,.6,1},{0,.2,.4,.6,.8,1},{0,.2,.8,1},{0,.2,.8,1}}, {{'N'}, {0,.2,.8,1},{0,.4,.8,1},{0,.2,.4,.6,.8,1},{0,.2,.6,1},{0,.2,.8,1}}, {{'O'}, {.2,.8},{0,.2,.8,1},{0,.2,.8,1},{0,.2,.8,1},{.2,.8}}, {{'P'}, {0,.8},{0,.2,.8,1},{0,.8},{0,.2},{0,.2}}, {{'Q'}, {.2,.8},{0,.2,.8,1},{0,.2,.8,1},{0,.2,.6,.8},{.2,.6,.8,1}}, {{'R'}, {0,.8},{0,.2,.8,1},{0,.8},{0,.2,.6,.8},{0,.2,.8,1}}, {{'S'}, {.2,.9},{0,.2},{.2,.7},{.7,.9},{.2,.7}}, {{'T'}, {0,1},{.4,.6},{.4,.6},{.4,.6},{.4,.6}}, {{'U'}, {0,.2,.8,1},{0,.2,.8,1},{0,.2,.8,1},{0,.2,.8,1},{.2,.8}}, {{'V'}, {0,.2,.8,1},{.1,.3,.7,.9},{.2,.4,.6,.8},{.3,.7},{.4,.6}}, {{'W'}, {0,.2,.8,1},{0,.2,.8,1},{0,.2,.4,.6,.8,1},{0,.4,.6,1},{0,.2,.8,1}}, {{'X'}, {0,.2,.8,1},{.2,.4,.6,.8},{.4,.6},{.2,.4,.6,.8},{0,.2,.8,1}}, {{'Y'}, {0,.2,.8,1},{.2,.4,.6,.8},{.4,.6},{.4,.6},{.4,.6}}, {{'Z'}, {0,1},{.6,.8},{.4,.6},{.2,.4},{0,1}}, {{','}, {},{},{},{.5,.7},{.4,.6}}, {{'.'}, {},{},{},{},{.4,.6}}, {{' '}, {},{},{},{},{}}, {{':'}, {},{.4,.6},{},{.4,.6},{}}, {{','}, {},{},{},{.5,.7},{.4,.6}}, {{'"'}, {.25,.4,.6,.75},{.25,.4,.6,.75},{},{},{}}, {{'\''}, {.45,.6},{.45,.6},{},{},{}}, {{'!'}, {.4,.6},{.4,.6},{.4,.6},{},{.4,.6}}, {{'?'}, {.2,.8},{.8,1},{.45,.8},{},{.4,.6}}, }; Font3D() { addGlyphs(fontData); } void addGlyphs(float[][][] data) { for (int i = 0; i < data.length; i++) { char c = (char)data[i][0][0]; float[][] d = data[i]; letters.add(new Glyph3D(c, d)); } } Glyph3D getGlyph(char c) { for (int i = 0; i < letters.size(); i++) { Glyph3D l = (Glyph3D)letters.get(i); if (l.letter == c) return l; } throw new RuntimeException("ERROR: no letter: "+c); } }// end code