aboutsummaryrefslogtreecommitdiffstats
path: root/src/javax/media/j3d/Text3DRetained.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/javax/media/j3d/Text3DRetained.java')
-rw-r--r--src/javax/media/j3d/Text3DRetained.java999
1 files changed, 999 insertions, 0 deletions
diff --git a/src/javax/media/j3d/Text3DRetained.java b/src/javax/media/j3d/Text3DRetained.java
new file mode 100644
index 0000000..ed21941
--- /dev/null
+++ b/src/javax/media/j3d/Text3DRetained.java
@@ -0,0 +1,999 @@
+/*
+ * Copyright 1998-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+package javax.media.j3d;
+
+import java.awt.font.GlyphVector;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+
+import javax.vecmath.Point3d;
+import javax.vecmath.Point3f;
+import javax.vecmath.Vector3f;
+
+/**
+ * Implements Text3D class.
+ */
+class Text3DRetained extends GeometryRetained {
+ /**
+ * Packaged scope variables needed for implementation
+ */
+ Font3D font3D = null;
+ String string = null;
+ Point3f position = new Point3f(0.0f, 0.0f, 0.0f);
+ int alignment = Text3D.ALIGN_FIRST, path = Text3D.PATH_RIGHT;
+ float charSpacing = 0.0f;
+ int numChars = 0;
+ static final int targetThreads = (J3dThread.UPDATE_TRANSFORM |
+ J3dThread.UPDATE_GEOMETRY |
+ J3dThread.UPDATE_RENDER);
+ /**
+ * The temporary transforms for this Text3D
+ */
+ Transform3D[] charTransforms = new Transform3D[0];
+
+ /**
+ * A cached list of geometry arrays for the current settings
+ */
+ GeometryArrayRetained[] geometryList = new GeometryArrayRetained[0];
+ GlyphVector[] glyphVecs = new GlyphVector[0];
+
+ /**
+ * Bounding box data for this text string.
+ */
+ Point3d lower = new Point3d();
+ Point3d upper = new Point3d();
+
+
+ /**
+ * An Array list used for messages
+ */
+ ArrayList newGeometryAtomList = new ArrayList();
+ ArrayList oldGeometryAtomList = new ArrayList();
+
+
+ /**
+ * temporary model view matrix for immediate mode only
+ */
+ Transform3D vpcToEc;
+ Transform3D drawTransform;
+
+
+ Text3DRetained(){
+ this.geoType = GEO_TYPE_TEXT3D;
+ }
+
+
+ @Override
+ synchronized void computeBoundingBox() {
+ Point3d l = new Point3d();
+ Point3d u = new Point3d();
+ Vector3f location = new Vector3f(this.position);
+ int i, k=0, numTotal=0;
+ double width = 0, height = 0;
+ Rectangle2D bounds;
+
+ //Reset bounds data
+ l.set(location);
+ u.set(location);
+
+ if (numChars != 0) {
+ // Set loop counters based on path type
+ if (path == Text3D.PATH_RIGHT || path == Text3D.PATH_UP) {
+ k = 0;
+ numTotal = numChars + 1;
+ } else if (path == Text3D.PATH_LEFT || path == Text3D.PATH_DOWN) {
+ k = 1;
+ numTotal = numChars;
+ // Reset bounds to bounding box if first character
+ bounds = glyphVecs[0].getVisualBounds();
+ u.x += bounds.getWidth();
+ u.y += bounds.getHeight();
+ }
+
+ for (i=1; i<numTotal; i++, k++) {
+ width = glyphVecs[k].getLogicalBounds().getWidth();
+ bounds = glyphVecs[k].getVisualBounds();
+ // 'switch' could be outside loop with little hacking,
+ width += charSpacing;
+ height = bounds.getHeight();
+
+ switch (this.path) {
+ case Text3D.PATH_RIGHT:
+ u.x += (width);
+ if (u.y < (height + location.y)) {
+ u.y = location.y + height;
+ }
+ break;
+ case Text3D.PATH_LEFT:
+ l.x -= (width);
+ if (u.y < ( height + location.y)) {
+ u.y = location.y + height;
+ }
+ break;
+ case Text3D.PATH_UP:
+ u.y += height;
+ if (u.x < (bounds.getWidth() + location.x)) {
+ u.x = location.x + bounds.getWidth();
+ }
+ break;
+ case Text3D.PATH_DOWN:
+ l.y -= height;
+ if (u.x < (bounds.getWidth() + location.x)) {
+ u.x = location.x + bounds.getWidth();
+ }
+ break;
+ }
+ }
+
+ // Handle string alignment. ALIGN_FIRST is handled by default
+ if (alignment != Text3D.ALIGN_FIRST) {
+ double cx = (u.x - l.x);
+ double cy = (u.y - l.y);
+
+ if (alignment == Text3D.ALIGN_CENTER) {
+ cx *= .5;
+ cy *= .5;
+ }
+ switch (path) {
+ case Text3D.PATH_RIGHT:
+ l.x -= cx;
+ u.x -= cx;
+ break;
+ case Text3D.PATH_LEFT:
+ l.x += cx;
+ u.x += cx;
+ break;
+ case Text3D.PATH_UP:
+ l.y -= cy;
+ u.y -= cy;
+ break;
+ case Text3D.PATH_DOWN:
+ l.y += cy;
+ u.y += cy;
+ break;
+
+ }
+ }
+ }
+
+ l.z = 0.0f;
+ if ((font3D == null) || (font3D.fontExtrusion == null)) {
+ u.z = l.z;
+ } else {
+ u.z = l.z + font3D.fontExtrusion.length;
+ }
+ }
+
+ @Override
+ void update() {}
+
+
+ /**
+ * Returns the Font3D objects used by this Text3D NodeComponent object.
+ *
+ * @return the Font3D object of this Text3D node - null if no Font3D
+ * has been associated with this node.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ */
+ final Font3D getFont3D() {
+ return this.font3D;
+ }
+
+ /**
+ * Sets the Font3D object used by this Text3D NodeComponent object.
+ *
+ * @param font3d the Font3D object to associate with this Text3D node.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ */
+ final void setFont3D(Font3D font3d) {
+ geomLock.getLock();
+ this.font3D = font3d;
+ updateCharacterData();
+ geomLock.unLock();
+ sendDataChangedMessage();
+ }
+
+ /**
+ * Copies the character string used in the construction of the
+ * Text3D node into the supplied parameter.
+ *
+ * @return a copy of the String object in this Text3D node.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ */
+ final String getString() {
+ return this.string;
+ }
+
+ /**
+ * Copies the character string from the supplied parameter into Tex3D
+ * node.
+ *
+ * @param string the String object to recieve the Text3D node's string.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ */
+ final void setString(String string) {
+ geomLock.getLock();
+ this.string = string;
+ if (string == null) {
+ numChars = 0;
+ } else {
+ numChars = string.length();
+ }
+ updateCharacterData();
+ geomLock.unLock();
+ sendDataChangedMessage();
+ }
+
+ /**
+ * Copies the node's <code>position</code> field into the supplied
+ * parameter. The <code>position</code> is used to determine the
+ * initial placement of the Text3D string. The position, combined with
+ * the path and alignment control how the text is displayed.
+ *
+ * @param position the point to position the text.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ *
+ * @see #getAlignment
+ * @see #getPath
+ */
+ final void getPosition(Point3f position) {
+ position.set(this.position);
+ }
+
+ /**
+ * Sets the node's <code>position</code> field to the supplied
+ * parameter. The <code>position</code> is used to determine the
+ * initial placement of the Text3D string. The position, combined with
+ * the path and alignment control how the text is displayed.
+ *
+ * @param position the point to position the text.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ *
+ * @see #getAlignment
+ * @see #getPath
+ */
+ final void setPosition(Point3f position) {
+ geomLock.getLock();
+ this.position.set(position);
+ updateTransformData();
+ geomLock.unLock();
+ sendTransformChangedMessage();
+ }
+
+ /**
+ * Retrieves the text alignment policy for this Text3D NodeComponent
+ * object. The <code>alignment</code> is used to specify how
+ * glyphs in the string are placed in relation to the
+ * <code>position</code> field. Valid values for this field
+ * are:
+ * <UL>
+ * <LI> ALIGN_CENTER - the center of the string is placed on the
+ * <code>position</code> point.
+ * <LI> ALIGN_FIRST - the first character of the string is placed on
+ * the <code>position</code> point.
+ * <LI> ALIGN_LAST - the last character of the string is placed on the
+ * <code>position</code> point.
+ * </UL>
+ * The default value of this field is <code>ALIGN_FIRST</code>.
+ *
+ * @return the current alingment policy for this node.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ *
+ * @see #getPosition
+ */
+ final int getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Sets the text alignment policy for this Text3D NodeComponent
+ * object. The <code>alignment</code> is used to specify how
+ * glyphs in the string are placed in relation to the
+ * <code>position</code> field. Valid values for this field
+ * are:
+ * <UL>
+ * <LI> ALIGN_CENTER - the center of the string is placed on the
+ * <code>position</code> point.
+ * <LI> ALIGN_FIRST - the first character of the string is placed on
+ * the <code>position</code> point.
+ * <LI> ALIGN_LAST - the last character of the string is placed on the
+ * <code>position</code> point.
+ * </UL>
+ * The default value of this field is <code>ALIGN_FIRST</code>.
+ *
+ * @return the current alingment policy for this node.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ *
+ * @see #getPosition
+ */
+ final void setAlignment(int alignment) {
+ geomLock.getLock();
+ this.alignment = alignment;
+ updateTransformData();
+ geomLock.unLock();
+ sendTransformChangedMessage();
+ }
+
+ /**
+ * Retrieves the node's <code>path</code> field. This field
+ * is used to specify how succeeding
+ * glyphs in the string are placed in relation to the previous glyph.
+ * Valid values for this field are:
+ * <UL>
+ * <LI> PATH_LEFT: - succeeding glyphs are placed to the left of the
+ * current glyph.
+ * <LI> PATH_RIGHT: - succeeding glyphs are placed to the right of the
+ * current glyph.
+ * <LI> PATH_UP: - succeeding glyphs are placed above the current glyph.
+ * <LI> PATH_DOWN: - succeeding glyphs are placed below the current glyph.
+ * </UL>
+ * The default value of this field is <code>PATH_RIGHT</code>.
+ *
+ * @return the current alingment policy for this node.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ */
+ final int getPath() {
+ return this.path;
+ }
+
+ /**
+ * Sets the node's <code>path</code> field. This field
+ * is used to specify how succeeding
+ * glyphs in the string are placed in relation to the previous glyph.
+ * Valid values for this field are:
+ * <UL>
+ * <LI> PATH_LEFT - succeeding glyphs are placed to the left of the
+ * current glyph.
+ * <LI> PATH_RIGHT - succeeding glyphs are placed to the right of the
+ * current glyph.
+ * <LI> PATH_UP - succeeding glyphs are placed above the current glyph.
+ * <LI> PATH_DOWN - succeeding glyphs are placed below the current glyph.
+ * </UL>
+ * The default value of this field is <code>PATH_RIGHT</code>.
+ *
+ * @param path the value to set the path to.
+ *
+ * @return the current alingment policy for this node.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ */
+ final void setPath(int path) {
+ this.path = path;
+ updateTransformData();
+ sendTransformChangedMessage();
+ }
+
+ /**
+ * Retrieves the 3D bounding box that encloses this Text3D object.
+ *
+ * @param bounds the object to copy the bounding information to.
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ *
+ * @see BoundingBox
+ */
+ final void getBoundingBox(BoundingBox bounds) {
+ synchronized (this) {
+ bounds.setLower(lower);
+ bounds.setUpper(upper);
+ }
+ }
+
+ /**
+ * Retrieves the character spacing used to construct the Text3D string.
+ * This spacing is in addition to the regular spacing between glyphs as
+ * defined in the Font object. 1.0 in this space is measured as the
+ * width of the largest glyph in the 2D Font. The default value is
+ * 0.0.
+ *
+ * @return the current character spacing value
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ */
+ final float getCharacterSpacing() {
+ return charSpacing;
+ }
+
+ /**
+ * Sets the character spacing used hwne constructing the Text3D string.
+ * This spacing is in addition to the regular spacing between glyphs as
+ * defined in the Font object. 1.0 in this space is measured as the
+ * width of the largest glyph in the 2D Font. The default value is
+ * 0.0.
+ *
+ * @param characterSpacing the new character spacing value
+ *
+ * @exception CapabilityNotSetException if appropriate capability is
+ * not set and this object is part of live or compiled scene graph
+ */
+ final void setCharacterSpacing(float characterSpacing) {
+ geomLock.getLock();
+ this.charSpacing = characterSpacing;
+ updateTransformData();
+ geomLock.unLock();
+ sendTransformChangedMessage();
+ }
+
+
+ final void sendDataChangedMessage() {
+ J3dMessage[] m;
+ int i, j, k, kk, numMessages;
+ int gSize;
+ ArrayList gaList;
+ GeometryAtom[] newGeometryAtoms;
+ ArrayList tiArrList = new ArrayList();
+ ArrayList newCtArrArrList = new ArrayList();
+
+ synchronized(liveStateLock) {
+ if (source.isLive()) {
+ synchronized (universeList) {
+ numMessages = universeList.size();
+ m = new J3dMessage[numMessages];
+ for (i=0; i<numMessages; i++) {
+ m[i] = new J3dMessage();
+ m[i].type = J3dMessage.TEXT3D_DATA_CHANGED;
+ m[i].threads = targetThreads;
+ ArrayList<Shape3DRetained> shapeList = userLists.get(i);
+ newGeometryAtomList.clear();
+ oldGeometryAtomList.clear();
+
+ for (j=0; j<shapeList.size(); j++) {
+ Shape3DRetained s = shapeList.get(j);
+ if (s.boundsAutoCompute) {
+ // update combine bounds of mirrorShape3Ds. So we need to
+ // use its bounds and not localBounds.
+ // bounds is actually a reference to
+ // mirrorShape3D.source.localBounds.
+ // XXXX : Should only need to update distinct localBounds.
+ s.getCombineBounds((BoundingBox)s.bounds);
+ }
+
+ gSize = s.geometryList.size();
+
+ GeometryAtom oldGA = Shape3DRetained.getGeomAtom(s);
+ GeometryAtom newGA = new GeometryAtom();
+
+ int geometryCnt = 0;
+ for(k = 0; k<gSize; k++) {
+ GeometryRetained geomRetained = s.geometryList.get(k);
+ if(geomRetained != null) {
+ Text3DRetained tempT3d = (Text3DRetained)geomRetained;
+ geometryCnt += tempT3d.numChars;
+ }
+ else {
+ // Slightly wasteful, but not quite worth to optimize yet.
+ geometryCnt++;
+ }
+ }
+
+ newGA.geometryArray = new GeometryRetained[geometryCnt];
+ newGA.lastLocalTransformArray = new Transform3D[geometryCnt];
+ // Reset geometryCnt;
+ geometryCnt = 0;
+
+ newGA.locale = s.locale;
+ newGA.visible = s.visible;
+ newGA.source = s;
+ int gaCnt=0;
+ GeometryRetained geometry = null;
+ for(; gaCnt<gSize; gaCnt++) {
+ geometry = s.geometryList.get(gaCnt);
+ if(geometry != null) {
+ newGA.geoType = geometry.geoType;
+ newGA.alphaEditable = s.isAlphaEditable(geometry);
+ break;
+ }
+ }
+
+ for(; gaCnt<gSize; gaCnt++) {
+ geometry = s.geometryList.get(gaCnt);
+ if(geometry == null) {
+ newGA.geometryArray[gaCnt] = null;
+ }
+ else {
+ Text3DRetained t = (Text3DRetained)geometry;
+ GeometryRetained geo;
+ for (k=0; k<t.numChars; k++, geometryCnt++) {
+ geo = t.geometryList[k];
+ if (geo != null) {
+ newGA.geometryArray[geometryCnt] = geo;
+ newGA.lastLocalTransformArray[geometryCnt] =
+ t.charTransforms[k];
+
+ } else {
+ newGA.geometryArray[geometryCnt] = null;
+ newGA.lastLocalTransformArray[geometryCnt] = null;
+ }
+
+ }
+
+ }
+ }
+
+ oldGeometryAtomList.add(oldGA);
+ newGeometryAtomList.add(newGA);
+ Shape3DRetained.setGeomAtom(s, newGA);
+ }
+
+ Object[] oldGAArray = oldGeometryAtomList.toArray();
+ Object[] newGAArray = newGeometryAtomList.toArray();
+ ArrayList uniqueList = getUniqueSource(shapeList);
+ int numSrc = uniqueList.size();
+ int numMS3D;
+ Shape3DRetained ms, src;
+
+ for (j=0; j<numSrc; j++) {
+ CachedTargets[] newCtArr = null;
+ src = (Shape3DRetained)uniqueList.get(j);
+ numMS3D = src.mirrorShape3D.size();
+
+ TargetsInterface ti = ((GroupRetained)src.
+ parent).getClosestTargetsInterface(
+ TargetsInterface.TRANSFORM_TARGETS);
+
+ if (ti != null) {
+ CachedTargets ct;
+ newCtArr = new CachedTargets[numMS3D];
+
+ for (k=0; k<numMS3D; k++) {
+ ms = src.mirrorShape3D.get(k);
+
+ GeometryAtom ga =
+ Shape3DRetained.getGeomAtom(ms);
+ for(kk=0; kk<newGAArray.length; kk++) {
+ if(ga == newGAArray[kk]) {
+ break;
+ }
+ }
+
+ if(kk==newGAArray.length) {
+ System.err.println("Text3DRetained : Problem !!! Can't find matching geomAtom");
+ }
+
+ ct = ti.getCachedTargets(TargetsInterface.
+ TRANSFORM_TARGETS, k, -1);
+ if (ct != null) {
+ newCtArr[k] = new CachedTargets();
+ newCtArr[k].copy(ct);
+ newCtArr[k].replace((NnuId)oldGAArray[kk],
+ (NnuId)newGAArray[kk],
+ Targets.GEO_TARGETS);
+ } else {
+ newCtArr[k] = null;
+ }
+
+ }
+
+ ti.resetCachedTargets(
+ TargetsInterface.TRANSFORM_TARGETS, newCtArr, -1);
+
+ tiArrList.add(ti);
+ newCtArrArrList.add(newCtArr);
+
+ }
+
+ }
+
+ m[i].args[0] = oldGAArray;
+ m[i].args[1] = newGAArray;
+ m[i].universe = universeList.get(i);
+
+ if(tiArrList.size() > 0) {
+ m[i].args[2] = tiArrList.toArray();
+ m[i].args[3] = newCtArrArrList.toArray();
+ }
+
+ tiArrList.clear();
+ newCtArrArrList.clear();
+
+ }
+ VirtualUniverse.mc.processMessage(m);
+ }
+
+ }
+ }
+ }
+
+
+ final void sendTransformChangedMessage() {
+ J3dMessage[] m;
+ int i, j, numMessages, sCnt;
+ ArrayList gaList = new ArrayList();
+ GeometryRetained geomR;
+ synchronized(liveStateLock) {
+ if (source.isLive()) {
+ synchronized (universeList) {
+ numMessages = universeList.size();
+ m = new J3dMessage[numMessages];
+ for (i=0; i<numMessages; i++) {
+ m[i] = new J3dMessage();
+ m[i].type = J3dMessage.TEXT3D_TRANSFORM_CHANGED;
+ m[i].threads = targetThreads;
+ ArrayList<Shape3DRetained> shapeList = userLists.get(i);
+ // gaList = new GeometryAtom[shapeList.size() * numChars];
+ for (j = 0; j < shapeList.size(); j++) {
+ Shape3DRetained s = shapeList.get(j);
+
+ // Find the right geometry.
+ for(sCnt=0; sCnt<s.geometryList.size(); sCnt++) {
+ geomR = s.geometryList.get(sCnt);
+ if(geomR == this) {
+ break;
+ }
+ }
+
+ if(sCnt < s.geometryList.size())
+ gaList.add(Shape3DRetained.getGeomAtom(s));
+
+ }
+ m[i].args[0] = gaList.toArray();
+ m[i].args[1] = charTransforms;
+ m[i].universe = universeList.get(i);
+ }
+ VirtualUniverse.mc.processMessage(m);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update internal reprsentation of tranform matrices and geometry.
+ * This method will be called whenever string or font3D change.
+ */
+ final void updateCharacterData() {
+ char c[] = new char[1];
+
+ if (geometryList.length != numChars) {
+ geometryList = new GeometryArrayRetained[numChars];
+ glyphVecs = new GlyphVector[numChars];
+ }
+
+ if (font3D != null) {
+ for (int i=0; i<numChars; i++) {
+ c[0] = string.charAt(i);
+ glyphVecs[i] = font3D.font.createGlyphVector(font3D.frc, c);
+ geometryList[i] = font3D.triangulateGlyphs(glyphVecs[i], c[0]);
+ }
+ }
+
+ updateTransformData();
+ }
+
+ /**
+ * Update per character transform based on Text3D location,
+ * per character size and path.
+ *
+ * WARNING: Caller of this method must make sure SceneGraph is live,
+ * else exceptions may be thrown.
+ */
+ final void updateTransformData(){
+ int i, k=0, numTotal=0;
+ double width = 0, height = 0;
+ Vector3f location = new Vector3f(this.position);
+ Rectangle2D bounds;
+
+ //Reset bounds data
+ lower.set(location);
+ upper.set(location);
+
+ charTransforms = new Transform3D[numChars];
+ for (i=0; i<numChars; i++) {
+ charTransforms[i] = new Transform3D();
+ }
+
+ if (numChars != 0) {
+ charTransforms[0].set(location);
+
+ // Set loop counters based on path type
+ if (path == Text3D.PATH_RIGHT || path == Text3D.PATH_UP) {
+ k = 0;
+ numTotal = numChars + 1;
+ } else if (path == Text3D.PATH_LEFT || path == Text3D.PATH_DOWN) {
+ k = 1;
+ numTotal = numChars;
+ // Reset bounds to bounding box if first character
+ bounds = glyphVecs[0].getVisualBounds();
+ upper.x += bounds.getWidth();
+ upper.y += bounds.getHeight();
+ }
+
+ for (i=1; i<numTotal; i++, k++) {
+ width = glyphVecs[k].getLogicalBounds().getWidth();
+ bounds = glyphVecs[k].getVisualBounds();
+ // 'switch' could be outside loop with little hacking,
+ width += charSpacing;
+ height = bounds.getHeight();
+
+ switch (this.path) {
+ case Text3D.PATH_RIGHT:
+ location.x += width;
+ upper.x += (width);
+ if (upper.y < (height + location.y)) {
+ upper.y = location.y + height;
+ }
+ break;
+ case Text3D.PATH_LEFT:
+ location.x -= width;
+ lower.x -= (width);
+ if (upper.y < ( height + location.y)) {
+ upper.y = location.y + height;
+ }
+ break;
+ case Text3D.PATH_UP:
+ location.y += height;
+ upper.y += height;
+ if (upper.x < (bounds.getWidth() + location.x)) {
+ upper.x = location.x + bounds.getWidth();
+ }
+ break;
+ case Text3D.PATH_DOWN:
+ location.y -= height;
+ lower.y -= height;
+ if (upper.x < (bounds.getWidth() + location.x)) {
+ upper.x = location.x + bounds.getWidth();
+ }
+ break;
+ }
+ if (i < numChars) {
+ charTransforms[i].set(location);
+ }
+ }
+
+ // Handle string alignment. ALIGN_FIRST is handled by default
+ if (alignment != Text3D.ALIGN_FIRST) {
+ double cx = (upper.x - lower.x);
+ double cy = (upper.y - lower.y);
+
+ if (alignment == Text3D.ALIGN_CENTER) {
+ cx *= .5;
+ cy *= .5;
+ }
+ switch (path) {
+ case Text3D.PATH_RIGHT:
+ for (i=0;i < numChars;i++) {
+ charTransforms[i].mat[3] -= cx;
+ }
+ lower.x -= cx;
+ upper.x -= cx;
+ break;
+ case Text3D.PATH_LEFT:
+ for (i=0;i < numChars;i++) {
+ charTransforms[i].mat[3] += cx;
+ }
+ lower.x += cx;
+ upper.x += cx;
+ break;
+
+ case Text3D.PATH_UP:
+ for (i=0;i < numChars;i++) {
+ charTransforms[i].mat[7] -=cy;
+ }
+ lower.y -= cy;
+ upper.y -= cy;
+ break;
+ case Text3D.PATH_DOWN:
+ for (i=0;i < numChars;i++) {
+ charTransforms[i].mat[7] +=cy;
+ }
+ lower.y += cy;
+ upper.y += cy;
+ break;
+
+ }
+ }
+ }
+
+ lower.z = 0.0f;
+ if ((font3D == null) || (font3D.fontExtrusion == null)) {
+ upper.z = lower.z;
+ } else {
+ upper.z = lower.z + font3D.fontExtrusion.length;
+ }
+
+ // update geoBounds
+ getBoundingBox(geoBounds);
+ }
+
+
+ /**
+ * This method is called when the SceneGraph becomes live. All characters
+ * used by this.string are tesselated in this method, to avoid wait during
+ * traversal and rendering.
+ */
+ @Override
+ void setLive(boolean inBackgroundGroup, int refCount) {
+ // Tesselate all character data and update character transforms
+ updateCharacterData();
+ super.doSetLive(inBackgroundGroup, refCount);
+ super.markAsLive();
+ }
+
+ // TODO -- Need to rethink. Might have to consider charTransform[] in returns pickInfo.
+ @Override
+ boolean intersect(PickShape pickShape, PickInfo pickInfo, int flags, Point3d iPnt,
+ GeometryRetained geom, int geomIndex) {
+ Transform3D tempT3D = new Transform3D();
+ GeometryArrayRetained geo = null;
+ int sIndex = -1;
+ PickShape newPS;
+ double minDist = Double.MAX_VALUE;
+ double distance =0.0;
+ Point3d closestIPnt = new Point3d();
+
+ for (int i=0; i < numChars; i++) {
+ geo= geometryList[i];
+ if (geo != null) {
+ tempT3D.invert(charTransforms[i]);
+ newPS = pickShape.transform(tempT3D);
+ if (geo.intersect(newPS, pickInfo, flags, iPnt, geom, geomIndex)) {
+ if (flags == 0) {
+ return true;
+ }
+ distance = newPS.distance(iPnt);
+ if (distance < minDist) {
+ sIndex = i;
+ minDist = distance;
+ closestIPnt.set(iPnt);
+ }
+ }
+ }
+ }
+
+ if (sIndex >= 0) {
+ // We need to transform iPnt to the vworld to compute the actual distance.
+ // In this method we'll transform iPnt by its char. offset. Shape3D will
+ // do the localToVworld transform.
+ iPnt.set(closestIPnt);
+ charTransforms[sIndex].transform(iPnt);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ boolean intersect(Point3d[] pnts) {
+ Transform3D tempT3D = new Transform3D();
+ GeometryArrayRetained ga;
+ boolean isIntersect = false;
+ Point3d transPnts[] = new Point3d[pnts.length];
+ for (int j=pnts.length-1; j >= 0; j--) {
+ transPnts[j] = new Point3d();
+ }
+
+ for (int i=numChars-1; i >= 0; i--) {
+ ga = geometryList[i];
+ if ( ga != null) {
+ tempT3D.invert(charTransforms[i]);
+ for (int j=pnts.length-1; j >= 0; j--) {
+ tempT3D.transform(pnts[j], transPnts[j]);
+ }
+ if (ga.intersect(transPnts)) {
+ isIntersect = true;
+ break;
+ }
+ }
+ }
+ return isIntersect;
+ }
+
+
+ @Override
+ boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) {
+ GeometryArrayRetained ga;
+
+ for (int i=numChars-1; i >=0; i--) {
+ ga = geometryList[i];
+ if ((ga != null) && ga.intersect(thisToOtherVworld, geom)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ boolean intersect(Bounds targetBound) {
+ GeometryArrayRetained ga;
+
+ for (int i=numChars-1; i >=0; i--) {
+ ga = geometryList[i];
+ if ((ga != null) && ga.intersect(targetBound)) {
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ void setModelViewMatrix(Transform3D vpcToEc, Transform3D drawTransform) {
+ this.vpcToEc = vpcToEc;
+ this.drawTransform = drawTransform;
+ }
+
+
+ @Override
+ void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale,
+ boolean updateAlpha, float alpha,
+ int screen,
+ boolean ignoreVertexColors) {
+
+ Transform3D trans = new Transform3D();
+
+ for (int i = 0; i < geometryList.length; i++) {
+ trans.set(drawTransform);
+ trans.mul(charTransforms[i]);
+ cv.setModelViewMatrix(cv.ctx, vpcToEc.mat, trans);
+ geometryList[i].execute(cv, ra, isNonUniformScale, updateAlpha, alpha,
+ screen, ignoreVertexColors);
+ }
+ }
+
+ @Override
+ int getClassType() {
+ return TEXT3D_TYPE;
+ }
+
+
+ ArrayList getUniqueSource(ArrayList shapeList) {
+ ArrayList uniqueList = new ArrayList();
+ int size = shapeList.size();
+ Object src;
+ int i, index;
+
+ for (i=0; i<size; i++) {
+ src = ((Shape3DRetained)shapeList.get(i)).sourceNode;
+ index = uniqueList.indexOf(src);
+ if (index == -1) {
+ uniqueList.add(src);
+ }
+ }
+ return uniqueList;
+ }
+}
+