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