001/**
002 * J3DGenModelNote -- A Java3D node that represents a GenModelNote in a J3D scene graph.
003 *
004 * Copyright (C) 2014-2016, Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 */
018package geomss.j3d;
019
020import com.sun.j3d.utils.geometry.Text2D;
021import geomss.app.GeomSSCanvas3D;
022import geomss.geom.GenModelNote;
023import geomss.geom.GeomPoint;
024import geomss.geom.GeomVector;
025import java.awt.Canvas;
026import java.awt.Font;
027import java.awt.FontMetrics;
028import java.awt.SystemColor;
029import static java.util.Objects.requireNonNull;
030import javax.measure.quantity.Dimensionless;
031import javax.measure.unit.SI;
032import javax.media.j3d.*;
033import javax.vecmath.*;
034
035/**
036 * A Java 3D node that represents a GenModelNote in a Java 3D scene graph.
037 *
038 * <p> Modified by: Joseph A. Huwaldt </p>
039 *
040 * @author Joseph A. Huwaldt, Date: February 10, 2014
041 * @version September 9, 2016
042 */
043public class J3DGenModelNote extends J3DGeomGroup<GenModelNote> {
044
045    private static final int X = 0, Y = 1, Z = 2;
046
047    //  The switch for main or mirrored geometry.
048    private Switch _symmSG;
049
050    /**
051     * Construct a J3DGenModelNote using the specified GenModelNote as a reference.
052     *
053     * @param canvas   The canvas that the geometry is being rendered into.
054     * @param geometry The GeomSS geometry to be turned into a Java3D node.
055     */
056    public J3DGenModelNote(GeomSSCanvas3D canvas, GenModelNote geometry) {
057        super(requireNonNull(canvas), requireNonNull(geometry));
058    }
059
060    /**
061     * Set the display of a mirrored copy of this geometry. This is called from
062     * "setDisplayed()" to turn on and off the display of mirrored geometry without
063     * changing the "mirrored state" of the object. This call does not affect the output
064     * of "isMirrored()".
065     *
066     * @param mirrored Flag indicating if the mirrored geometry should be displayed or
067     *                 not.
068     * @see #setMirrored(boolean)
069     * @see #isMirrored()
070     */
071    @Override
072    protected void internalSetMirrored(boolean mirrored) {
073        if (mirrored)
074            _symmSG.setWhichChild(Switch.CHILD_ALL);
075        else
076            _symmSG.setWhichChild(0);
077    }
078
079    /**
080     * Create a new Java 3D <code>Group</code> that contains the geometry contained in
081     * this object. This method is called from <code>createSceneGraph</code>.
082     *
083     * @return New Java 3D Group that contains the geometry in this object.
084     * @see #createSceneGraph
085     */
086    @Override
087    protected Group createGeometry() {
088        //J3DRenderingPrefs drawPrefs = getRenderingPrefs();
089
090        //      Create a Text2D representation of the note.
091        GenModelNote thisNote = this.getGeomElement();
092        Font font = thisNote.getFont();
093        font = font.deriveFont(font.getSize() * 4f);  //  Scale up the font a bit to make it sharper in the rendering.
094        Color3f textColor = new Color3f(SystemColor.textText);
095        Text2D t2d = new Text2D(thisNote.getNote(), textColor, font.getName(), font.getSize(), font.getStyle());
096
097        //  Set the text scale factor to get the box height correct.
098        FontMetrics fm = new Canvas().getFontMetrics(font);
099        int fontAscent = fm.getMaxAscent();
100        int fontDescent = fm.getMaxDescent();
101        int fontHeight = fontAscent + fontDescent;
102
103        // Need to make height a power of 2 because of Java3d texture
104        // size restrictions
105        int pow = 1;
106        for (int i = 1; i < 32; ++i) {
107            pow *= 2;
108            if (fontHeight <= pow)
109                break;
110        }
111        fontHeight = Math.max(fontHeight, pow);
112
113        double modelHeight = thisNote.getHeight().getValue(SI.METER);       //  Convert all geometry to meters.
114        double scale = modelHeight / fontHeight;
115        t2d.setRectangleScaleFactor((float)scale);
116
117        //  Get the location of the note.
118        GeomPoint location = thisNote.getLocation().to(SI.METER);           //  Convert all geometry to meters.
119        int dims = location.getPhyDimension();
120        double x = location.getValue(X);
121        double y = location.getValue(Y);
122        double z = (dims > 2 ? location.getValue(Z) : 0);
123        Vector3d location3d = new Vector3d(x, y, z);
124
125        //  Get the orientation of the note.
126        Matrix3d rotT = new Matrix3d();
127        GeomVector<Dimensionless> xhat = thisNote.getXHat();
128        x = xhat.getValue(X);
129        y = xhat.getValue(Y);
130        z = (dims > 2 ? xhat.getValue(Z) : 0);
131        rotT.setColumn(X, x, y, z);
132        GeomVector<Dimensionless> yhat = thisNote.getYHat();
133        x = yhat.getValue(X);
134        y = yhat.getValue(Y);
135        z = (dims > 2 ? yhat.getValue(Z) : 0);
136        rotT.setColumn(Y, x, y, z);
137        GeomVector<Dimensionless> zhat = thisNote.getNormal();
138        x = zhat.getValue(X);
139        y = zhat.getValue(Y);
140        z = (dims > 2 ? zhat.getValue(Z) : 0);
141        rotT.setColumn(Z, x, y, z);
142
143        //  Offset the text so that the text box origin is at the text string baseline.
144        Vector3d offset = new Vector3d(0, -fontDescent * scale, 0);
145        rotT.transform(offset);
146        location3d.add(offset);
147
148        //      Update the appearance of the Text2D shape.
149        Appearance app = t2d.getAppearance();
150        PolygonAttributes pa = app.getPolygonAttributes();
151        if (pa == null)
152            pa = new PolygonAttributes();
153        pa.setCullFace(PolygonAttributes.CULL_NONE);
154        if (app.getPolygonAttributes() == null)
155            app.setPolygonAttributes(pa);
156
157        //  Create a transform group that contains the Text2D object oriented and
158        //  positioned correctly.
159        Transform3D T = new Transform3D();
160        T.setRotation(rotT);
161        T.setTranslation(location3d);
162        TransformGroup TG = new TransformGroup(T);
163        TG.addChild(t2d);
164
165        //      Add the basic unmirrored geometry to the switch.
166        _symmSG = new Switch();
167        _symmSG.setCapability(Switch.ALLOW_SWITCH_READ);
168        _symmSG.setCapability(Switch.ALLOW_SWITCH_WRITE);
169        _symmSG.addChild(TG);
170
171        //      Clone the basic geometry to make the mirrored geometry.
172        Node mirrored = TG.cloneTree();
173
174        //      Create a mirror across the XZ plane of symmetry transform.
175        Transform3D symmT = new Transform3D();
176        symmT.setScale(new Vector3d(1, -1, 1));
177        TransformGroup symmTG = new TransformGroup(symmT);
178
179        //      Add the mirrored geometry to the symmetry transform group.
180        symmTG.addChild(mirrored);
181
182        //      Add the mirrored geometry to the switch.
183        _symmSG.addChild(symmTG);
184
185        //      By default, display only the main geometry (not the mirrored).
186        _symmSG.setWhichChild(0);
187
188        return _symmSG;
189    }
190
191    /**
192     * Creates a new instance of the node. This routine is called by
193     * <code>cloneTree</code> to duplicate the current node.
194     *
195     * @param forceDuplicate when set to <code>true</code>, causes the
196     *                       <code>duplicateOnCloneTree</code> flag to be ignored. When
197     *                       <code>false</code>, the value of each node's
198     *                       <code>duplicateOnCloneTree</code> variable determines whether
199     *                       NodeComponent data is duplicated or copied.
200     * @return A new instance of this Java3D node.
201     */
202    @Override
203    public Node cloneNode(boolean forceDuplicate) {
204        J3DGenModelNote node = new J3DGenModelNote(this.getCanvas3D(), this.getGeomElement());
205        node.duplicateNode(this, forceDuplicate);
206        return node;
207    }
208
209}