/*
* Copyright (c) 2003-2009 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package automenta.spacenet.space.geom.text3d;
import automenta.spacenet.space.geom.text3d.math.ClosedPolygon;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.state.BlendState;
import com.ardor3d.renderer.state.MaterialState;
import com.ardor3d.renderer.state.ZBufferState;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.util.scenegraph.CompileOptions;
import java.awt.Font;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.PathIterator;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.logging.Logger;
/**
* This class represents a font ready to be used for 3D.
*
* Known bugs:
*
* - When glyphs are constructed from other glyphs, the shape returned by
* gv.getGlyphOutline(0); has them all cluddered up. This might be a bug in the
* VM, and I have no time to fix it, that is why the loading of each glyph has a
* try-catch-all statement around it.
*
* @author emanuel
*/
public class Font3D {
private static final Logger logger = Logger.getLogger(Font3D.class.getName());
private static Hashtable<String, Font3D> loadedFonts = new Hashtable<String, Font3D>();
// This Node is only used for rendering
Node renderNode = new Node();
// The glyphs created from the font.
//Glyph3D glyph3Ds[] = new Glyph3D[256];
private Map<Character, Glyph3D> glyphs3Ds = new HashMap();
// Settings
Font font;
private double flatness;
private boolean drawSides;
private boolean drawFront;
private boolean drawBack;
private static BlendState general_alphastate = null;
private static MaterialState general_diffuse_material = null;
boolean has_alpha_blending = false;
boolean has_diffuse_material = false;
private final CompileOptions options;
protected Glyph3D updateGlyph(char g) {
try {
// if(g != 'H') // TEST
// continue;
// logger.info("Glyph: "+g+":"+(char)g);
// GlyphVector gv = font.createGlyphVector(new
// FontRenderContext(null, true, true), new char[] { (char)g });
GlyphVector gv = font.layoutGlyphVector(new FontRenderContext(
null, true, true), new char[]{(char) g}, 0, 1, 0);
gv.performDefaultLayout();
ClosedPolygon closedPolygon = null;
Glyph3D fontGlyph = new Glyph3D((char) g);
// Get the shape
Shape s = gv.getGlyphOutline(0);
// GlyphMetrics metrics = gv.getGlyphMetrics(0);
PathIterator pi = new FlatteningPathIterator(s.getPathIterator(new AffineTransform()), flatness);
// logger.info("\n\n\n\nWIND IS BLOWING:
// "+(pi.getWindingRule() == PathIterator.WIND_EVEN_ODD ?
// "WIND_EVEN_ODD" : "WIND_NON_ZERO"));
float[] coords = new float[6];
while (!pi.isDone()) {
int seg = pi.currentSegment(coords);
switch (seg) {
case PathIterator.SEG_MOVETO:
closedPolygon = new ClosedPolygon();
closedPolygon.addPoint(new Vector3(coords[0],
-coords[1], 0));
break;
case PathIterator.SEG_LINETO:
closedPolygon.addPoint(new Vector3(coords[0],
-coords[1], 0));
break;
case PathIterator.SEG_CLOSE:
closedPolygon.close();
fontGlyph.addPolygon(closedPolygon);
closedPolygon = null;
break;
default:
throw new IllegalArgumentException(
"unknown segment type " + seg);
}
pi.next();
}
// If we added something then we have a valid glyph !
fontGlyph.setBounds(gv.getGlyphLogicalBounds(0).getBounds2D());
if (!fontGlyph.isEmpty()) {
// Time to triangulate the surface of the glyph
fontGlyph.triangulate();
// And create the actual geometry.
fontGlyph.generateMesh(drawSides, drawFront, drawBack);
if (fontGlyph.getMesh() != null) {
fontGlyph.setChildIndex(renderNode.getNumberOfChildren());
renderNode.attachChild(fontGlyph.getMesh());
}
//TODO use DisplayList's for rendering optimization - see Ardor3D's DisplayListExample.java
//SceneCompiler.compile(fontGlyph.getMesh(), _canvas.getCanvasRenderer().getRenderer(), options);
//RenderDelegate delegate = fontGlyph.getMesh().getRenderDelegate(ContextManager.getCurrentContext().getGlContextRep());
}
glyphs3Ds.put(g, fontGlyph);
return fontGlyph;
} catch (Exception e) {
//logger.log(Level.INFO, e + " - error in char: (" + g + ":" + (char) g + "), the following is most likely due to glyphs constructed " + "from other glyphs.... that does not work.", e);
}
return null;
}
public Font3D(Font font, double flatness) {
this(font, flatness, true, false, false);
}
// Create the
public Font3D(Font font, double flatness, boolean drawFront, boolean drawSides, boolean drawBack) {
if (font.getSize() != 1) {
font = font.deriveFont(1.0f);
}
// Save for later
this.font = font;
this.flatness = flatness;
this.drawSides = drawSides;
this.drawFront = drawFront;
this.drawBack = drawBack;
options = new CompileOptions();
options.setDisplayList(true);
// Clear our "parent node"
renderNode.detachAllChildren();
updateRange(0, 256);
// Apply a Z-state
ZBufferState zstate = new ZBufferState();
zstate.setFunction(ZBufferState.TestFunction.LessThan);
zstate.setWritable(true);
zstate.setEnabled(true);
renderNode.setRenderState(zstate);
// Finally create display-lists for each mesh
//renderNode.lockMeshes();
}
/**
* This method is used when text wants to render, much like the shared
*
* @return
*/
public Node getRenderNode() {
return renderNode;
}
/**
* Method for creating the text from the font. TODO: react on the flags
* parameter.
*
* @param text
* @param size
* @param flags
* @return
*/
public Text3D createText(String text, int flags) {
Text3D text_obj = new Text3D(this, text);
return text_obj;
}
/**
* This method loads and caches a font, call this before calls to
* {@link #createText(String, int)}.
*
* @param fontname
* @param font
*/
public static void loadFont3D(String fontname, Font font, double flatness,
boolean drawSides, boolean drawFront, boolean drawBack) {
logger.info("FontSize: " + font.getSize());
logger.info("FontSize2D:" + font.getSize2D());
Font3D f = new Font3D(font, flatness, drawFront, drawSides, drawBack);
loadedFonts.put(fontname, f);
}
/**
* Removes a cached Font3D.
*
* @param fontname
*/
public static void unloadFont(String fontname) {
loadedFonts.remove(fontname);
}
/**
* This method will create a peace of 3d text from this font.
*
* @param fontname
* @param text
* @param size
* @return
*/
public static Text3D createText(String fontname, String text, int flags) {
// Find the cached font and create a text instance.
Font3D cachedf = loadedFonts.get(fontname);
return cachedf.createText(text, flags);
}
public Glyph3D getGlyph(char c) {
Glyph3D g = glyphs3Ds.get(c);
if (g == null) {
g = updateGlyph(c);
}
return g;
}
public Font getFont() {
return font;
}
public double getFlatness() {
return flatness;
}
public boolean drawSides() {
return drawSides;
}
public boolean drawFront() {
return drawFront;
}
public boolean drawBack() {
return drawBack;
}
public boolean isMeshLocked() {
//return (renderNode.getLocks() & Spatial.LOCKED_MESH_DATA) != 0;
return false;
}
public void unlockMesh() {
//renderNode.unlockMeshes();
}
public void lockMesh() {
//renderNode.lockMeshes();
}
public void enableBlendState() {
if (has_alpha_blending) {
return;
}
if (general_alphastate == null) {
general_alphastate = new BlendState();
general_alphastate.setBlendEnabled(true);
general_alphastate.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
general_alphastate.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
general_alphastate.setTestEnabled(true);
general_alphastate.setTestFunction(BlendState.TestFunction.Always);
general_alphastate.setEnabled(true);
}
//renderNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
renderNode.setRenderState(general_alphastate);
has_alpha_blending = true;
}
public void enableDiffuseMaterial() {
if (has_diffuse_material) {
return;
}
if (general_diffuse_material == null) {
general_diffuse_material = new MaterialState();
general_diffuse_material.setEnabled(true);
general_diffuse_material.setColorMaterial(MaterialState.ColorMaterial.Diffuse);
}
renderNode.setRenderState(general_diffuse_material);
}
protected void updateRange(int start, int stop) {
// Generate the glyphs
for (int g = start; g < stop; g++) {
//HACK: wrap updateGlyph because it often throws "unnecessary" geometric errors
try {
updateGlyph((char)g);
} catch (Exception e) { }
}
}
}
//public class MVectorFont extends Font3D {
// private static final Logger logger = Logger.getLogger(MVectorFont.class);
//
// private ColorRGBA col;
//
// final private VectorFont vectorFont;
//
// protected boolean drawSides = false;
//
// private final Jme jme;
// protected final static boolean drawFront = true;
// protected final static boolean drawBack = false;
//
// protected static Map<VectorFont, MVectorFont> fonts = new FastMap<VectorFont, MVectorFont>();
//
// static {
// java.util.logging.Logger.getLogger(Font3D.class.getName()).setLevel(Level.OFF);
// java.util.logging.Logger.getLogger(Triangulator.class.getName()).setLevel(Level.OFF);
// java.util.logging.Logger.getLogger(TriangulationVertex.class.getName()).setLevel(Level.OFF);
// java.util.logging.Logger.getLogger("com.jmex.font3d.math.Triangulator$SweepLineComparer").setLevel(Level.OFF);
// java.util.logging.Logger.getLogger("com.jmex.font3d.math.Triangulator$1SweepLineStatus").setLevel(Level.OFF);
// }
//
// protected MVectorFont(Jme jme, final VectorFont font) {
// super(font.getFont(), font.getFlatness(), font.hasSides(), drawFront, drawBack );
//
// logger.info(this + " loaded font: " + font + " -> [ " + getRenderNode() + " ]");
// this.jme = jme;
// this.vectorFont = font;
//
//
// setColor(Jme.asJMEColor( font.getDefaultColor() ) );
// }
//
// public void setColor(final ColorRGBA c) {
// this.col = c;
//
// // ColorRGBA cStart = new ColorRGBA(1,0,0,1);
// // ColorRGBA cStop = new ColorRGBA(0,1,0,0f);
// // Font3DGradient gradient = new Font3DGradient(Vector3f.UNIT_X, cStart, cStop);
// // gradient.applyEffect(this);
//
// if (getVectorFont().getBorderColor()!=null) {
// ColorRGBA innerColor = c;
// ColorRGBA borderColor = Jme.asJMEColor(getVectorFont().getBorderColor());
//
// Font3DBorder fontborder = new Font3DBorder(getBorderWidth(), innerColor, borderColor, this);
// fontborder.applyEffect(this);
// }
//
// //this.enableBlendState();
// //this.enableDiffuseMaterial();
// //
// //
// this.getRenderNode().updateGeometricState(0.0f, true);
// this.getRenderNode().updateRenderState();
//
// }
//
// private float getBorderWidth() {
// return 0.02f;
// }
//
// public VectorFont getVectorFont() {
// return vectorFont;
// }
//
//
// public static MVectorFont getFont(Jme jme, VectorFont font) {
// MVectorFont f;
// f = fonts.get(font);
// if (f == null) {
// f = new MVectorFont(jme, font);
// fonts.put(font, f);
// }
// return f;
// }
//}