0001: /*
0002: * $RCSfile: Text3DRetained.java,v $
0003: *
0004: * Copyright 1998-2008 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0006: *
0007: * This code is free software; you can redistribute it and/or modify it
0008: * under the terms of the GNU General Public License version 2 only, as
0009: * published by the Free Software Foundation. Sun designates this
0010: * particular file as subject to the "Classpath" exception as provided
0011: * by Sun in the LICENSE file that accompanied this code.
0012: *
0013: * This code is distributed in the hope that it will be useful, but WITHOUT
0014: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0015: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0016: * version 2 for more details (a copy is included in the LICENSE file that
0017: * accompanied this code).
0018: *
0019: * You should have received a copy of the GNU General Public License version
0020: * 2 along with this work; if not, write to the Free Software Foundation,
0021: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0022: *
0023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0024: * CA 95054 USA or visit www.sun.com if you need additional information or
0025: * have any questions.
0026: *
0027: * $Revision: 1.9 $
0028: * $Date: 2008/02/28 20:17:31 $
0029: * $State: Exp $
0030: */
0031:
0032: package javax.media.j3d;
0033:
0034: import javax.vecmath.*;
0035: import java.awt.font.*;
0036: import java.awt.*;
0037: import java.awt.geom.Rectangle2D;
0038: import java.util.ArrayList;
0039:
0040: /**
0041: * Implements Text3D class.
0042: */
0043: class Text3DRetained extends GeometryRetained {
0044: /**
0045: * Packaged scope variables needed for implementation
0046: */
0047: Font3D font3D = null;
0048: String string = null;
0049: Point3f position = new Point3f(0.0f, 0.0f, 0.0f);
0050: int alignment = Text3D.ALIGN_FIRST, path = Text3D.PATH_RIGHT;
0051: float charSpacing = 0.0f;
0052: int numChars = 0;
0053: static final int targetThreads = (J3dThread.UPDATE_TRANSFORM
0054: | J3dThread.UPDATE_GEOMETRY | J3dThread.UPDATE_RENDER);
0055: /**
0056: * The temporary transforms for this Text3D
0057: */
0058: Transform3D[] charTransforms = new Transform3D[0];
0059:
0060: /**
0061: * A cached list of geometry arrays for the current settings
0062: */
0063: GeometryArrayRetained[] geometryList = new GeometryArrayRetained[0];
0064: GlyphVector[] glyphVecs = new GlyphVector[0];
0065:
0066: /**
0067: * Bounding box data for this text string.
0068: */
0069: Point3d lower = new Point3d();
0070: Point3d upper = new Point3d();
0071:
0072: /**
0073: * An Array list used for messages
0074: */
0075: ArrayList newGeometryAtomList = new ArrayList();
0076: ArrayList oldGeometryAtomList = new ArrayList();
0077:
0078: /**
0079: * temporary model view matrix for immediate mode only
0080: */
0081: Transform3D vpcToEc;
0082: Transform3D drawTransform;
0083:
0084: Text3DRetained() {
0085: this .geoType = GEO_TYPE_TEXT3D;
0086: }
0087:
0088: synchronized void computeBoundingBox() {
0089: Point3d l = new Point3d();
0090: Point3d u = new Point3d();
0091: Vector3f location = new Vector3f(this .position);
0092: int i, k = 0, numTotal = 0;
0093: double width = 0, height = 0;
0094: Rectangle2D bounds;
0095:
0096: //Reset bounds data
0097: l.set(location);
0098: u.set(location);
0099:
0100: if (numChars != 0) {
0101: // Set loop counters based on path type
0102: if (path == Text3D.PATH_RIGHT || path == Text3D.PATH_UP) {
0103: k = 0;
0104: numTotal = numChars + 1;
0105: } else if (path == Text3D.PATH_LEFT
0106: || path == Text3D.PATH_DOWN) {
0107: k = 1;
0108: numTotal = numChars;
0109: // Reset bounds to bounding box if first character
0110: bounds = glyphVecs[0].getVisualBounds();
0111: u.x += bounds.getWidth();
0112: u.y += bounds.getHeight();
0113: }
0114:
0115: for (i = 1; i < numTotal; i++, k++) {
0116: width = glyphVecs[k].getLogicalBounds().getWidth();
0117: bounds = glyphVecs[k].getVisualBounds();
0118: // 'switch' could be outside loop with little hacking,
0119: width += charSpacing;
0120: height = bounds.getHeight();
0121:
0122: switch (this .path) {
0123: case Text3D.PATH_RIGHT:
0124: u.x += (width);
0125: if (u.y < (height + location.y)) {
0126: u.y = location.y + height;
0127: }
0128: break;
0129: case Text3D.PATH_LEFT:
0130: l.x -= (width);
0131: if (u.y < (height + location.y)) {
0132: u.y = location.y + height;
0133: }
0134: break;
0135: case Text3D.PATH_UP:
0136: u.y += height;
0137: if (u.x < (bounds.getWidth() + location.x)) {
0138: u.x = location.x + bounds.getWidth();
0139: }
0140: break;
0141: case Text3D.PATH_DOWN:
0142: l.y -= height;
0143: if (u.x < (bounds.getWidth() + location.x)) {
0144: u.x = location.x + bounds.getWidth();
0145: }
0146: break;
0147: }
0148: }
0149:
0150: // Handle string alignment. ALIGN_FIRST is handled by default
0151: if (alignment != Text3D.ALIGN_FIRST) {
0152: double cx = (u.x - l.x);
0153: double cy = (u.y - l.y);
0154:
0155: if (alignment == Text3D.ALIGN_CENTER) {
0156: cx *= .5;
0157: cy *= .5;
0158: }
0159: switch (path) {
0160: case Text3D.PATH_RIGHT:
0161: l.x -= cx;
0162: u.x -= cx;
0163: break;
0164: case Text3D.PATH_LEFT:
0165: l.x += cx;
0166: u.x += cx;
0167: break;
0168: case Text3D.PATH_UP:
0169: l.y -= cy;
0170: u.y -= cy;
0171: break;
0172: case Text3D.PATH_DOWN:
0173: l.y += cy;
0174: u.y += cy;
0175: break;
0176:
0177: }
0178: }
0179: }
0180:
0181: l.z = 0.0f;
0182: if ((font3D == null) || (font3D.fontExtrusion == null)) {
0183: u.z = l.z;
0184: } else {
0185: u.z = l.z + font3D.fontExtrusion.length;
0186: }
0187: }
0188:
0189: void update() {
0190: }
0191:
0192: /**
0193: * Returns the Font3D objects used by this Text3D NodeComponent object.
0194: *
0195: * @return the Font3D object of this Text3D node - null if no Font3D
0196: * has been associated with this node.
0197: *
0198: * @exception CapabilityNotSetException if appropriate capability is
0199: * not set and this object is part of live or compiled scene graph
0200: */
0201: final Font3D getFont3D() {
0202: return this .font3D;
0203: }
0204:
0205: /**
0206: * Sets the Font3D object used by this Text3D NodeComponent object.
0207: *
0208: * @param font3d the Font3D object to associate with this Text3D node.
0209: *
0210: * @exception CapabilityNotSetException if appropriate capability is
0211: * not set and this object is part of live or compiled scene graph
0212: */
0213: final void setFont3D(Font3D font3d) {
0214: geomLock.getLock();
0215: this .font3D = font3d;
0216: updateCharacterData();
0217: geomLock.unLock();
0218: sendDataChangedMessage();
0219: }
0220:
0221: /**
0222: * Copies the character string used in the construction of the
0223: * Text3D node into the supplied parameter.
0224: *
0225: * @return a copy of the String object in this Text3D node.
0226: *
0227: * @exception CapabilityNotSetException if appropriate capability is
0228: * not set and this object is part of live or compiled scene graph
0229: */
0230: final String getString() {
0231: return this .string;
0232: }
0233:
0234: /**
0235: * Copies the character string from the supplied parameter into Tex3D
0236: * node.
0237: *
0238: * @param string the String object to recieve the Text3D node's string.
0239: *
0240: * @exception CapabilityNotSetException if appropriate capability is
0241: * not set and this object is part of live or compiled scene graph
0242: */
0243: final void setString(String string) {
0244: geomLock.getLock();
0245: this .string = string;
0246: if (string == null) {
0247: numChars = 0;
0248: } else {
0249: numChars = string.length();
0250: }
0251: updateCharacterData();
0252: geomLock.unLock();
0253: sendDataChangedMessage();
0254: }
0255:
0256: /**
0257: * Copies the node's <code>position</code> field into the supplied
0258: * parameter. The <code>position</code> is used to determine the
0259: * initial placement of the Text3D string. The position, combined with
0260: * the path and alignment control how the text is displayed.
0261: *
0262: * @param position the point to position the text.
0263: *
0264: * @exception CapabilityNotSetException if appropriate capability is
0265: * not set and this object is part of live or compiled scene graph
0266: *
0267: * @see #getAlignment
0268: * @see #getPath
0269: */
0270: final void getPosition(Point3f position) {
0271: position.set(this .position);
0272: }
0273:
0274: /**
0275: * Sets the node's <code>position</code> field to the supplied
0276: * parameter. The <code>position</code> is used to determine the
0277: * initial placement of the Text3D string. The position, combined with
0278: * the path and alignment control how the text is displayed.
0279: *
0280: * @param position the point to position the text.
0281: *
0282: * @exception CapabilityNotSetException if appropriate capability is
0283: * not set and this object is part of live or compiled scene graph
0284: *
0285: * @see #getAlignment
0286: * @see #getPath
0287: */
0288: final void setPosition(Point3f position) {
0289: geomLock.getLock();
0290: this .position.set(position);
0291: updateTransformData();
0292: geomLock.unLock();
0293: sendTransformChangedMessage();
0294: }
0295:
0296: /**
0297: * Retrieves the text alignment policy for this Text3D NodeComponent
0298: * object. The <code>alignment</code> is used to specify how
0299: * glyphs in the string are placed in relation to the
0300: * <code>position</code> field. Valid values for this field
0301: * are:
0302: * <UL>
0303: * <LI> ALIGN_CENTER - the center of the string is placed on the
0304: * <code>position</code> point.
0305: * <LI> ALIGN_FIRST - the first character of the string is placed on
0306: * the <code>position</code> point.
0307: * <LI> ALIGN_LAST - the last character of the string is placed on the
0308: * <code>position</code> point.
0309: * </UL>
0310: * The default value of this field is <code>ALIGN_FIRST</code>.
0311: *
0312: * @return the current alingment policy for this node.
0313: *
0314: * @exception CapabilityNotSetException if appropriate capability is
0315: * not set and this object is part of live or compiled scene graph
0316: *
0317: * @see #getPosition
0318: */
0319: final int getAlignment() {
0320: return alignment;
0321: }
0322:
0323: /**
0324: * Sets the text alignment policy for this Text3D NodeComponent
0325: * object. The <code>alignment</code> is used to specify how
0326: * glyphs in the string are placed in relation to the
0327: * <code>position</code> field. Valid values for this field
0328: * are:
0329: * <UL>
0330: * <LI> ALIGN_CENTER - the center of the string is placed on the
0331: * <code>position</code> point.
0332: * <LI> ALIGN_FIRST - the first character of the string is placed on
0333: * the <code>position</code> point.
0334: * <LI> ALIGN_LAST - the last character of the string is placed on the
0335: * <code>position</code> point.
0336: * </UL>
0337: * The default value of this field is <code>ALIGN_FIRST</code>.
0338: *
0339: * @return the current alingment policy for this node.
0340: *
0341: * @exception CapabilityNotSetException if appropriate capability is
0342: * not set and this object is part of live or compiled scene graph
0343: *
0344: * @see #getPosition
0345: */
0346: final void setAlignment(int alignment) {
0347: geomLock.getLock();
0348: this .alignment = alignment;
0349: updateTransformData();
0350: geomLock.unLock();
0351: sendTransformChangedMessage();
0352: }
0353:
0354: /**
0355: * Retrieves the node's <code>path</code> field. This field
0356: * is used to specify how succeeding
0357: * glyphs in the string are placed in relation to the previous glyph.
0358: * Valid values for this field are:
0359: * <UL>
0360: * <LI> PATH_LEFT: - succeeding glyphs are placed to the left of the
0361: * current glyph.
0362: * <LI> PATH_RIGHT: - succeeding glyphs are placed to the right of the
0363: * current glyph.
0364: * <LI> PATH_UP: - succeeding glyphs are placed above the current glyph.
0365: * <LI> PATH_DOWN: - succeeding glyphs are placed below the current glyph.
0366: * </UL>
0367: * The default value of this field is <code>PATH_RIGHT</code>.
0368: *
0369: * @return the current alingment policy for this node.
0370: *
0371: * @exception CapabilityNotSetException if appropriate capability is
0372: * not set and this object is part of live or compiled scene graph
0373: */
0374: final int getPath() {
0375: return this .path;
0376: }
0377:
0378: /**
0379: * Sets the node's <code>path</code> field. This field
0380: * is used to specify how succeeding
0381: * glyphs in the string are placed in relation to the previous glyph.
0382: * Valid values for this field are:
0383: * <UL>
0384: * <LI> PATH_LEFT - succeeding glyphs are placed to the left of the
0385: * current glyph.
0386: * <LI> PATH_RIGHT - succeeding glyphs are placed to the right of the
0387: * current glyph.
0388: * <LI> PATH_UP - succeeding glyphs are placed above the current glyph.
0389: * <LI> PATH_DOWN - succeeding glyphs are placed below the current glyph.
0390: * </UL>
0391: * The default value of this field is <code>PATH_RIGHT</code>.
0392: *
0393: * @param path the value to set the path to.
0394: *
0395: * @return the current alingment policy for this node.
0396: *
0397: * @exception CapabilityNotSetException if appropriate capability is
0398: * not set and this object is part of live or compiled scene graph
0399: */
0400: final void setPath(int path) {
0401: this .path = path;
0402: updateTransformData();
0403: sendTransformChangedMessage();
0404: }
0405:
0406: /**
0407: * Retrieves the 3D bounding box that encloses this Text3D object.
0408: *
0409: * @param bounds the object to copy the bounding information to.
0410: *
0411: * @exception CapabilityNotSetException if appropriate capability is
0412: * not set and this object is part of live or compiled scene graph
0413: *
0414: * @see BoundingBox
0415: */
0416: final void getBoundingBox(BoundingBox bounds) {
0417: synchronized (this ) {
0418: bounds.setLower(lower);
0419: bounds.setUpper(upper);
0420: }
0421: }
0422:
0423: /**
0424: * Retrieves the character spacing used to construct the Text3D string.
0425: * This spacing is in addition to the regular spacing between glyphs as
0426: * defined in the Font object. 1.0 in this space is measured as the
0427: * width of the largest glyph in the 2D Font. The default value is
0428: * 0.0.
0429: *
0430: * @return the current character spacing value
0431: *
0432: * @exception CapabilityNotSetException if appropriate capability is
0433: * not set and this object is part of live or compiled scene graph
0434: */
0435: final float getCharacterSpacing() {
0436: return charSpacing;
0437: }
0438:
0439: /**
0440: * Sets the character spacing used hwne constructing the Text3D string.
0441: * This spacing is in addition to the regular spacing between glyphs as
0442: * defined in the Font object. 1.0 in this space is measured as the
0443: * width of the largest glyph in the 2D Font. The default value is
0444: * 0.0.
0445: *
0446: * @param characterSpacing the new character spacing value
0447: *
0448: * @exception CapabilityNotSetException if appropriate capability is
0449: * not set and this object is part of live or compiled scene graph
0450: */
0451: final void setCharacterSpacing(float characterSpacing) {
0452: geomLock.getLock();
0453: this .charSpacing = characterSpacing;
0454: updateTransformData();
0455: geomLock.unLock();
0456: sendTransformChangedMessage();
0457: }
0458:
0459: final void sendDataChangedMessage() {
0460: J3dMessage[] m;
0461: int i, j, k, kk, numMessages;
0462: int gSize;
0463: ArrayList shapeList, gaList;
0464: Shape3DRetained s;
0465: GeometryAtom[] newGeometryAtoms;
0466: ArrayList tiArrList = new ArrayList();
0467: ArrayList newCtArrArrList = new ArrayList();
0468:
0469: synchronized (liveStateLock) {
0470: if (source.isLive()) {
0471: synchronized (universeList) {
0472: numMessages = universeList.size();
0473: m = new J3dMessage[numMessages];
0474: for (i = 0; i < numMessages; i++) {
0475: m[i] = new J3dMessage();
0476: m[i].type = J3dMessage.TEXT3D_DATA_CHANGED;
0477: m[i].threads = targetThreads;
0478: shapeList = (ArrayList) userLists.get(i);
0479: newGeometryAtomList.clear();
0480: oldGeometryAtomList.clear();
0481:
0482: for (j = 0; j < shapeList.size(); j++) {
0483: s = (Shape3DRetained) shapeList.get(j);
0484: if (s.boundsAutoCompute) {
0485: // update combine bounds of mirrorShape3Ds. So we need to
0486: // use its bounds and not localBounds.
0487: // bounds is actually a reference to
0488: // mirrorShape3D.source.localBounds.
0489: // XXXX : Should only need to update distinct localBounds.
0490: s
0491: .getCombineBounds((BoundingBox) s.bounds);
0492: }
0493:
0494: gSize = s.geometryList.size();
0495:
0496: GeometryAtom oldGA = Shape3DRetained
0497: .getGeomAtom(s);
0498: GeometryAtom newGA = new GeometryAtom();
0499:
0500: int geometryCnt = 0;
0501: for (k = 0; k < gSize; k++) {
0502: GeometryRetained geomRetained = (GeometryRetained) s.geometryList
0503: .get(k);
0504: if (geomRetained != null) {
0505: Text3DRetained tempT3d = (Text3DRetained) geomRetained;
0506: geometryCnt += tempT3d.numChars;
0507: } else {
0508: // Slightly wasteful, but not quite worth to optimize yet.
0509: geometryCnt++;
0510: }
0511: }
0512:
0513: newGA.geometryArray = new GeometryRetained[geometryCnt];
0514: newGA.lastLocalTransformArray = new Transform3D[geometryCnt];
0515: // Reset geometryCnt;
0516: geometryCnt = 0;
0517:
0518: newGA.locale = s.locale;
0519: newGA.visible = s.visible;
0520: newGA.source = s;
0521: int gaCnt = 0;
0522: GeometryRetained geometry = null;
0523: for (; gaCnt < gSize; gaCnt++) {
0524: geometry = (GeometryRetained) s.geometryList
0525: .get(gaCnt);
0526: if (geometry != null) {
0527: newGA.geoType = geometry.geoType;
0528: newGA.alphaEditable = s
0529: .isAlphaEditable(geometry);
0530: break;
0531: }
0532: }
0533:
0534: for (; gaCnt < gSize; gaCnt++) {
0535: geometry = (GeometryRetained) s.geometryList
0536: .get(gaCnt);
0537: if (geometry == null) {
0538: newGA.geometryArray[gaCnt] = null;
0539: } else {
0540: Text3DRetained t = (Text3DRetained) geometry;
0541: GeometryRetained geo;
0542: for (k = 0; k < t.numChars; k++, geometryCnt++) {
0543: geo = t.geometryList[k];
0544: if (geo != null) {
0545: newGA.geometryArray[geometryCnt] = geo;
0546: newGA.lastLocalTransformArray[geometryCnt] = t.charTransforms[k];
0547:
0548: } else {
0549: newGA.geometryArray[geometryCnt] = null;
0550: newGA.lastLocalTransformArray[geometryCnt] = null;
0551: }
0552:
0553: }
0554:
0555: }
0556: }
0557:
0558: oldGeometryAtomList.add(oldGA);
0559: newGeometryAtomList.add(newGA);
0560: Shape3DRetained.setGeomAtom(s, newGA);
0561: }
0562:
0563: Object[] oldGAArray = oldGeometryAtomList
0564: .toArray();
0565: Object[] newGAArray = newGeometryAtomList
0566: .toArray();
0567: ArrayList uniqueList = getUniqueSource(shapeList);
0568: int numSrc = uniqueList.size();
0569: int numMS3D;
0570: Shape3DRetained ms, src;
0571:
0572: for (j = 0; j < numSrc; j++) {
0573: CachedTargets[] newCtArr = null;
0574: src = (Shape3DRetained) uniqueList.get(j);
0575: numMS3D = src.mirrorShape3D.size();
0576:
0577: TargetsInterface ti = ((GroupRetained) src.parent)
0578: .getClosestTargetsInterface(TargetsInterface.TRANSFORM_TARGETS);
0579:
0580: if (ti != null) {
0581: CachedTargets ct;
0582: newCtArr = new CachedTargets[numMS3D];
0583:
0584: for (k = 0; k < numMS3D; k++) {
0585: ms = (Shape3DRetained) src.mirrorShape3D
0586: .get(k);
0587:
0588: GeometryAtom ga = Shape3DRetained
0589: .getGeomAtom(ms);
0590: for (kk = 0; kk < newGAArray.length; kk++) {
0591: if (ga == newGAArray[kk]) {
0592: break;
0593: }
0594: }
0595:
0596: if (kk == newGAArray.length) {
0597: System.err
0598: .println("Text3DRetained : Problem !!! Can't find matching geomAtom");
0599: }
0600:
0601: ct = ti
0602: .getCachedTargets(
0603: TargetsInterface.TRANSFORM_TARGETS,
0604: k, -1);
0605: if (ct != null) {
0606: newCtArr[k] = new CachedTargets();
0607: newCtArr[k].copy(ct);
0608: newCtArr[k].replace(
0609: (NnuId) oldGAArray[kk],
0610: (NnuId) newGAArray[kk],
0611: Targets.GEO_TARGETS);
0612: } else {
0613: newCtArr[k] = null;
0614: }
0615:
0616: }
0617:
0618: ti
0619: .resetCachedTargets(
0620: TargetsInterface.TRANSFORM_TARGETS,
0621: newCtArr, -1);
0622:
0623: tiArrList.add(ti);
0624: newCtArrArrList.add(newCtArr);
0625:
0626: }
0627:
0628: }
0629:
0630: m[i].args[0] = oldGAArray;
0631: m[i].args[1] = newGAArray;
0632: m[i].universe = (VirtualUniverse) universeList
0633: .get(i);
0634:
0635: if (tiArrList.size() > 0) {
0636: m[i].args[2] = tiArrList.toArray();
0637: m[i].args[3] = newCtArrArrList.toArray();
0638: }
0639:
0640: tiArrList.clear();
0641: newCtArrArrList.clear();
0642:
0643: }
0644: VirtualUniverse.mc.processMessage(m);
0645: }
0646:
0647: }
0648: }
0649: }
0650:
0651: final void sendTransformChangedMessage() {
0652: J3dMessage[] m;
0653: int i, j, numMessages, sCnt;
0654: ArrayList shapeList;
0655: ArrayList gaList = new ArrayList();
0656: Shape3DRetained s;
0657: GeometryRetained geomR;
0658: synchronized (liveStateLock) {
0659: if (source.isLive()) {
0660: synchronized (universeList) {
0661: numMessages = universeList.size();
0662: m = new J3dMessage[numMessages];
0663: for (i = 0; i < numMessages; i++) {
0664: m[i] = new J3dMessage();
0665: m[i].type = J3dMessage.TEXT3D_TRANSFORM_CHANGED;
0666: m[i].threads = targetThreads;
0667: shapeList = (ArrayList) userLists.get(i);
0668: // gaList = new GeometryAtom[shapeList.size() * numChars];
0669: for (j = 0; j < shapeList.size(); j++) {
0670: s = (Shape3DRetained) shapeList.get(j);
0671:
0672: // Find the right geometry.
0673: for (sCnt = 0; sCnt < s.geometryList.size(); sCnt++) {
0674: geomR = (GeometryRetained) s.geometryList
0675: .get(sCnt);
0676: if (geomR == this ) {
0677: break;
0678: }
0679: }
0680:
0681: if (sCnt < s.geometryList.size())
0682: gaList.add(Shape3DRetained
0683: .getGeomAtom(s));
0684:
0685: }
0686: m[i].args[0] = gaList.toArray();
0687: m[i].args[1] = charTransforms;
0688: m[i].universe = (VirtualUniverse) universeList
0689: .get(i);
0690: }
0691: VirtualUniverse.mc.processMessage(m);
0692: }
0693: }
0694: }
0695: }
0696:
0697: /**
0698: * Update internal reprsentation of tranform matrices and geometry.
0699: * This method will be called whenever string or font3D change.
0700: */
0701: final void updateCharacterData() {
0702: char c[] = new char[1];
0703:
0704: if (geometryList.length != numChars) {
0705: geometryList = new GeometryArrayRetained[numChars];
0706: glyphVecs = new GlyphVector[numChars];
0707: }
0708:
0709: if (font3D != null) {
0710: for (int i = 0; i < numChars; i++) {
0711: c[0] = string.charAt(i);
0712: glyphVecs[i] = font3D.font.createGlyphVector(
0713: font3D.frc, c);
0714: geometryList[i] = font3D.triangulateGlyphs(
0715: glyphVecs[i], c[0]);
0716: }
0717: }
0718:
0719: updateTransformData();
0720: }
0721:
0722: /**
0723: * Update per character transform based on Text3D location,
0724: * per character size and path.
0725: *
0726: * WARNING: Caller of this method must make sure SceneGraph is live,
0727: * else exceptions may be thrown.
0728: */
0729: final void updateTransformData() {
0730: int i, k = 0, numTotal = 0;
0731: double width = 0, height = 0;
0732: Vector3f location = new Vector3f(this .position);
0733: Rectangle2D bounds;
0734:
0735: //Reset bounds data
0736: lower.set(location);
0737: upper.set(location);
0738:
0739: charTransforms = new Transform3D[numChars];
0740: for (i = 0; i < numChars; i++) {
0741: charTransforms[i] = new Transform3D();
0742: }
0743:
0744: if (numChars != 0) {
0745: charTransforms[0].set(location);
0746:
0747: // Set loop counters based on path type
0748: if (path == Text3D.PATH_RIGHT || path == Text3D.PATH_UP) {
0749: k = 0;
0750: numTotal = numChars + 1;
0751: } else if (path == Text3D.PATH_LEFT
0752: || path == Text3D.PATH_DOWN) {
0753: k = 1;
0754: numTotal = numChars;
0755: // Reset bounds to bounding box if first character
0756: bounds = glyphVecs[0].getVisualBounds();
0757: upper.x += bounds.getWidth();
0758: upper.y += bounds.getHeight();
0759: }
0760:
0761: for (i = 1; i < numTotal; i++, k++) {
0762: width = glyphVecs[k].getLogicalBounds().getWidth();
0763: bounds = glyphVecs[k].getVisualBounds();
0764: // 'switch' could be outside loop with little hacking,
0765: width += charSpacing;
0766: height = bounds.getHeight();
0767:
0768: switch (this .path) {
0769: case Text3D.PATH_RIGHT:
0770: location.x += width;
0771: upper.x += (width);
0772: if (upper.y < (height + location.y)) {
0773: upper.y = location.y + height;
0774: }
0775: break;
0776: case Text3D.PATH_LEFT:
0777: location.x -= width;
0778: lower.x -= (width);
0779: if (upper.y < (height + location.y)) {
0780: upper.y = location.y + height;
0781: }
0782: break;
0783: case Text3D.PATH_UP:
0784: location.y += height;
0785: upper.y += height;
0786: if (upper.x < (bounds.getWidth() + location.x)) {
0787: upper.x = location.x + bounds.getWidth();
0788: }
0789: break;
0790: case Text3D.PATH_DOWN:
0791: location.y -= height;
0792: lower.y -= height;
0793: if (upper.x < (bounds.getWidth() + location.x)) {
0794: upper.x = location.x + bounds.getWidth();
0795: }
0796: break;
0797: }
0798: if (i < numChars) {
0799: charTransforms[i].set(location);
0800: }
0801: }
0802:
0803: // Handle string alignment. ALIGN_FIRST is handled by default
0804: if (alignment != Text3D.ALIGN_FIRST) {
0805: double cx = (upper.x - lower.x);
0806: double cy = (upper.y - lower.y);
0807:
0808: if (alignment == Text3D.ALIGN_CENTER) {
0809: cx *= .5;
0810: cy *= .5;
0811: }
0812: switch (path) {
0813: case Text3D.PATH_RIGHT:
0814: for (i = 0; i < numChars; i++) {
0815: charTransforms[i].mat[3] -= cx;
0816: }
0817: lower.x -= cx;
0818: upper.x -= cx;
0819: break;
0820: case Text3D.PATH_LEFT:
0821: for (i = 0; i < numChars; i++) {
0822: charTransforms[i].mat[3] += cx;
0823: }
0824: lower.x += cx;
0825: upper.x += cx;
0826: break;
0827:
0828: case Text3D.PATH_UP:
0829: for (i = 0; i < numChars; i++) {
0830: charTransforms[i].mat[7] -= cy;
0831: }
0832: lower.y -= cy;
0833: upper.y -= cy;
0834: break;
0835: case Text3D.PATH_DOWN:
0836: for (i = 0; i < numChars; i++) {
0837: charTransforms[i].mat[7] += cy;
0838: }
0839: lower.y += cy;
0840: upper.y += cy;
0841: break;
0842:
0843: }
0844: }
0845: }
0846:
0847: lower.z = 0.0f;
0848: if ((font3D == null) || (font3D.fontExtrusion == null)) {
0849: upper.z = lower.z;
0850: } else {
0851: upper.z = lower.z + font3D.fontExtrusion.length;
0852: }
0853:
0854: // update geoBounds
0855: getBoundingBox(geoBounds);
0856: }
0857:
0858: /**
0859: * This method is called when the SceneGraph becomes live. All characters
0860: * used by this.string are tesselated in this method, to avoid wait during
0861: * traversal and rendering.
0862: */
0863: void setLive(boolean inBackgroundGroup, int refCount) {
0864: // Tesselate all character data and update character transforms
0865: updateCharacterData();
0866: super .doSetLive(inBackgroundGroup, refCount);
0867: super .markAsLive();
0868: }
0869:
0870: // TODO -- Need to rethink. Might have to consider charTransform[] in returns pickInfo.
0871: boolean intersect(PickShape pickShape, PickInfo pickInfo,
0872: int flags, Point3d iPnt, GeometryRetained geom,
0873: int geomIndex) {
0874: Transform3D tempT3D = new Transform3D();
0875: GeometryArrayRetained geo = null;
0876: int sIndex = -1;
0877: PickShape newPS;
0878: double minDist = Double.MAX_VALUE;
0879: double distance = 0.0;
0880: Point3d closestIPnt = new Point3d();
0881:
0882: for (int i = 0; i < numChars; i++) {
0883: geo = geometryList[i];
0884: if (geo != null) {
0885: tempT3D.invert(charTransforms[i]);
0886: newPS = pickShape.transform(tempT3D);
0887: if (geo.intersect(newPS, pickInfo, flags, iPnt, geom,
0888: geomIndex)) {
0889: if (flags == 0) {
0890: return true;
0891: }
0892: distance = newPS.distance(iPnt);
0893: if (distance < minDist) {
0894: sIndex = i;
0895: minDist = distance;
0896: closestIPnt.set(iPnt);
0897: }
0898: }
0899: }
0900: }
0901:
0902: if (sIndex >= 0) {
0903: // We need to transform iPnt to the vworld to compute the actual distance.
0904: // In this method we'll transform iPnt by its char. offset. Shape3D will
0905: // do the localToVworld transform.
0906: iPnt.set(closestIPnt);
0907: charTransforms[sIndex].transform(iPnt);
0908: return true;
0909: }
0910: return false;
0911: }
0912:
0913: boolean intersect(Point3d[] pnts) {
0914: Transform3D tempT3D = new Transform3D();
0915: GeometryArrayRetained ga;
0916: boolean isIntersect = false;
0917: Point3d transPnts[] = new Point3d[pnts.length];
0918: for (int j = pnts.length - 1; j >= 0; j--) {
0919: transPnts[j] = new Point3d();
0920: }
0921:
0922: for (int i = numChars - 1; i >= 0; i--) {
0923: ga = geometryList[i];
0924: if (ga != null) {
0925: tempT3D.invert(charTransforms[i]);
0926: for (int j = pnts.length - 1; j >= 0; j--) {
0927: tempT3D.transform(pnts[j], transPnts[j]);
0928: }
0929: if (ga.intersect(transPnts)) {
0930: isIntersect = true;
0931: break;
0932: }
0933: }
0934: }
0935: return isIntersect;
0936: }
0937:
0938: boolean intersect(Transform3D this ToOtherVworld,
0939: GeometryRetained geom) {
0940: GeometryArrayRetained ga;
0941:
0942: for (int i = numChars - 1; i >= 0; i--) {
0943: ga = geometryList[i];
0944: if ((ga != null) && ga.intersect(this ToOtherVworld, geom)) {
0945: return true;
0946: }
0947: }
0948:
0949: return false;
0950: }
0951:
0952: boolean intersect(Bounds targetBound) {
0953: GeometryArrayRetained ga;
0954:
0955: for (int i = numChars - 1; i >= 0; i--) {
0956: ga = geometryList[i];
0957: if ((ga != null) && ga.intersect(targetBound)) {
0958: return true;
0959: }
0960: }
0961:
0962: return false;
0963:
0964: }
0965:
0966: void setModelViewMatrix(Transform3D vpcToEc,
0967: Transform3D drawTransform) {
0968: this .vpcToEc = vpcToEc;
0969: this .drawTransform = drawTransform;
0970: }
0971:
0972: void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale,
0973: boolean updateAlpha, float alpha, int screen,
0974: boolean ignoreVertexColors) {
0975:
0976: Transform3D trans = new Transform3D();
0977:
0978: for (int i = 0; i < geometryList.length; i++) {
0979: trans.set(drawTransform);
0980: trans.mul(charTransforms[i]);
0981: cv.setModelViewMatrix(cv.ctx, vpcToEc.mat, trans);
0982: geometryList[i].execute(cv, ra, isNonUniformScale,
0983: updateAlpha, alpha, screen, ignoreVertexColors);
0984: }
0985: }
0986:
0987: int getClassType() {
0988: return TEXT3D_TYPE;
0989: }
0990:
0991: ArrayList getUniqueSource(ArrayList shapeList) {
0992: ArrayList uniqueList = new ArrayList();
0993: int size = shapeList.size();
0994: Object src;
0995: int i, index;
0996:
0997: for (i = 0; i < size; i++) {
0998: src = ((Shape3DRetained) shapeList.get(i)).sourceNode;
0999: index = uniqueList.indexOf(src);
1000: if (index == -1) {
1001: uniqueList.add(src);
1002: }
1003: }
1004: return uniqueList;
1005: }
1006: }
|