001/** 002 * J3DGenScreenNote -- A Java3D node that represents a GenScreenNote in a J3D scene graph. 003 * 004 * Copyright (C) 2014-2023, 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 geomss.app.GeomSSCanvas3D; 021import geomss.geom.GenScreenNote; 022import geomss.geom.GeomPoint; 023import jahuwaldt.j3d.BGFGImage; 024import java.awt.*; 025import java.awt.image.BufferedImage; 026import static java.util.Objects.requireNonNull; 027import javax.measure.unit.SI; 028import org.jogamp.java3d.*; 029import org.jogamp.vecmath.*; 030 031/** 032 * A Java 3D node that represents a GenScreenNote in a Java 3D scene graph. 033 * 034 * <p> Modified by: Joseph A. Huwaldt </p> 035 * 036 * @author Joseph A. Huwaldt, Date: February 7, 2014 037 * @version June 4, 2023 038 */ 039public class J3DGenScreenNote extends J3DGeomGroup<GenScreenNote> { 040 041 // The switch for main or mirrored geometry. 042 private Switch _symmSG; 043 044 // The overlay used to render the text to the screen. 045 private final MyOverlay _overlay; 046 047 /** 048 * Construct a J3DGenScreenNote using the specified GenScreenNote as a reference. 049 * 050 * @param canvas The canvas that the note is rendered into. 051 * @param geometry The GeomSS geometry to be turned into a Java3D node. 052 */ 053 public J3DGenScreenNote(GeomSSCanvas3D canvas, GenScreenNote geometry) { 054 super(requireNonNull(canvas), requireNonNull(geometry)); 055 056 // Create a overlay that renders the text of the note on the screen. 057 GeomPoint location = geometry.getLocation().to(SI.METER); // Convert all geometry to meters. 058 int dims = location.getPhyDimension(); 059 double x = location.getValue(0); 060 double y = (dims > 1 ? location.getValue(1) : 0); 061 double z = (dims > 2 ? location.getValue(2) : 0); 062 Point3d location3d = new Point3d(x, y, z); 063 _overlay = new MyOverlay(location3d, geometry); 064 065 // Add the overlay to the canvas. 066 getCanvas3D().addOverlay(_overlay); 067 } 068 069 /** 070 * Set the display of a mirrored copy of this geometry. This is called from 071 * "setDisplayed()" to turn on and off the display of mirrored geometry without 072 * changing the "mirrored state" of the object. This call does not affect the output 073 * of "isMirrored()". 074 * 075 * @param mirrored Flag indicating if the mirrored geometry should be displayed or 076 * not. 077 * @see #setMirrored(boolean) 078 * @see #isMirrored() 079 */ 080 @Override 081 protected void internalSetMirrored(boolean mirrored) { 082 if (mirrored) 083 _symmSG.setWhichChild(Switch.CHILD_ALL); 084 else 085 _symmSG.setWhichChild(0); 086 } 087 088 /** 089 * Create a new Java 3D <code>Group</code> that contains the geometry contained in 090 * this object. This method is called from <code>createSceneGraph</code>. 091 * 092 * @return New Java 3D Group that contains the geometry in this object. 093 * @see #createSceneGraph 094 */ 095 @Override 096 protected Group createGeometry() { 097 J3DRenderingPrefs drawPrefs = getRenderingPrefs(); 098 099 // Create a point array with a single point in it. 100 GenScreenNote thisNote = this.getGeomElement(); 101 PointArray pointA = new PointArray(1, PointArray.COORDINATES | PointArray.COLOR_4); 102 GeomPoint location = thisNote.getLocation().to(SI.METER); // Convert all geometry to meters. 103 int dims = location.getPhyDimension(); 104 double x = location.getValue(0); 105 double y = (dims > 1 ? location.getValue(1) : 0); 106 double z = (dims > 2 ? location.getValue(2) : 0); 107 Point3d location3d = new Point3d(x, y, z); 108 pointA.setCoordinate(0, location3d); 109 //pointA.setColor(0, drawPrefs.getPointColorJ3D()); 110 111 // Set the color to be transparent. Makes the point invisible, but still pickable. 112 pointA.setColor(0, new Color4f(0f, 0f, 0f, 0f)); 113 114 // Create a 3D shape from the point array 115 Shape3D pointShape = new GeomShape3D(thisNote, pointA); 116 Appearance pApp = new Appearance(); 117 pointShape.setAppearance(pApp); 118 pApp.setPointAttributes(new PointAttributes(drawPrefs.getPointSize(), true)); 119 120 // Add the basic unmirrored geometry to the switch. 121 _symmSG = new Switch(); 122 _symmSG.setCapability(Switch.ALLOW_SWITCH_READ); 123 _symmSG.setCapability(Switch.ALLOW_SWITCH_WRITE); 124 _symmSG.addChild(pointShape); 125 126 // Clone the basic geometry to make the mirrored geometry. 127 Node mirrored = pointShape.cloneTree(); 128 129 // Create a mirror across the XZ plane of symmetry transform. 130 Transform3D symmT = new Transform3D(); 131 symmT.setScale(new Vector3d(1, -1, 1)); 132 TransformGroup symmTG = new TransformGroup(symmT); 133 134 // Add the mirrored geometry to the symmetry transform group. 135 symmTG.addChild(mirrored); 136 137 // Add the mirrored geometry to the switch. 138 _symmSG.addChild(symmTG); 139 140 // By default, display only the main geometry (not the mirrored). 141 _symmSG.setWhichChild(0); 142 143 return _symmSG; 144 } 145 146 /** 147 * Creates a new instance of the node. This routine is called by 148 * <code>cloneTree</code> to duplicate the current node. 149 * 150 * @param forceDuplicate when set to <code>true</code>, causes the 151 * <code>duplicateOnCloneTree</code> flag to be ignored. When 152 * <code>false</code>, the value of each node's 153 * <code>duplicateOnCloneTree</code> variable determines whether 154 * NodeComponent data is duplicated or copied. 155 * @return A new instance of this Java3D node. 156 */ 157 @Override 158 public Node cloneNode(boolean forceDuplicate) { 159 J3DGenScreenNote node = new J3DGenScreenNote(getCanvas3D(), this.getGeomElement()); 160 node.duplicateNode(this, forceDuplicate); 161 return node; 162 } 163 164 /** 165 * Detaches this BranchGroup from its parent. This implementation also removes the 166 * note overlay from the canvas it was rendered into. 167 */ 168 @Override 169 public void detach() { 170 super.detach(); 171 172 // Remove our overlay from the rendering canvas. 173 getCanvas3D().removeOverlay(_overlay); 174 175 // Force J3DGeomGroupFactory to create a new J3DGenScreenNote the next time 176 // the note is drawn, by removing it from the notes meta-data. 177 getGeomElement().removeUserData(USERDATA_KEY); 178 } 179 180 /** 181 * An overlay image that contains the rendered text for the screen note. 182 */ 183 private final class MyOverlay implements BGFGImage { 184 185 private final Point3d modelPoint; 186 private final String noteString; 187 private final Font noteFont; 188 189 // The ascent and overall height of the font in pixels. 190 private int ascent, height, width; 191 192 // The X,Y location of the model point when projected onto the screen. 193 private int px, py; 194 195 // Stored for efficiency to prevent wasteful object creation. 196 private BufferedImage bufImg; 197 private final Rectangle imgBounds = new Rectangle(); 198 private final Rectangle canvasBounds = new Rectangle(); 199 200 // Used as optimizations to get3DTo2DPoint() to prevent wastefull object creation. 201 private final Transform3D T_VL = new Transform3D(); // Vworld wrt Local 202 private final Transform3D T_IV = new Transform3D(); // ImagePlate wrt Vworld 203 private final Point3d _p3d = new Point3d(); // A temporary point3d object. 204 205 public MyOverlay(Point3d point, GenScreenNote note) { 206 modelPoint = point; 207 noteString = note.getNote(); 208 noteFont = note.getFont(); 209 } 210 211 /** 212 * Returns the overlay used to show a drag rectangle. 213 */ 214 @Override 215 public synchronized BufferedImage getImage() { 216 GeomSSCanvas3D canvas = getCanvas3D(); 217 if (bufImg == null) { 218 // The buffered image doesn't exist, so create it. 219 220 // Measure the note string. 221 FontMetrics fm = canvas.getFontMetrics(noteFont); 222 ascent = fm.getAscent(); 223 height = fm.getHeight(); 224 width = fm.stringWidth(noteString); 225 226 // TYPE_INT_ARGB is required to get transparency. However, the buffer only gets 227 // rendered as a square with the given width no matter the height specified. 228 // So, this is using the max of width & height to work around this problem 229 // which must be a bug in Java3D(?). 230 int max = Math.max(width, height); 231 bufImg = new BufferedImage(max, max, BufferedImage.TYPE_INT_ARGB); 232 Graphics2D g2d = bufImg.createGraphics(); 233 234 // Clear the buffer to transparent. 235 clearSurface(g2d, max, max); 236 237 // Render the text into the buffered image. 238 Color textColor = SystemColor.textText; 239 g2d.setPaint(textColor); 240 g2d.setFont(noteFont); 241 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 242 RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); 243 g2d.drawString(noteString, 0, ascent); 244 //g2d.drawLine(0,ascent,0,ascent); 245 } 246 247 // Convert the model space 3D point to a screen pixel location. 248 Point2d p2d = get3DTo2DPoint(modelPoint); 249 px = (int)Math.round(p2d.getX()); 250 py = (int)Math.round(p2d.getY()); 251 252 // Is this inside the displayed area of the canvas? 253 imgBounds.x = px; 254 imgBounds.y = py; 255 imgBounds.width = width; 256 imgBounds.height = height; 257 canvas.getBounds(canvasBounds); 258 canvasBounds.x = 0; 259 canvasBounds.y = 0; 260 if (canvasBounds.intersects(imgBounds)) { 261 // Return the buffered image. 262 return bufImg; 263 } 264 265 // If the point is outside the canvas, then return nothing. 266 return null; 267 } 268 269 /** 270 * Returns the X coordinate of the upper left corner of the image. 271 */ 272 @Override 273 public int getImageX() { 274 return px; 275 } 276 277 /** 278 * Return the Y coordinate of the upper left corner of the drag rectangle. 279 */ 280 @Override 281 public int getImageY() { 282 return py - ascent; 283 } 284 285 /** 286 * Clears the buffered image to be 100% transparent. 287 */ 288 private void clearSurface(Graphics2D drawg2d, int width, int height) { 289 drawg2d.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f)); 290 drawg2d.fillRect(0, 0, width + 1, height + 1); 291 drawg2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); 292 } 293 294 /** 295 * Converts a 3D point in model space to it's projected 2D screen coordinate 296 * location. 297 * 298 * @param point3d The 3D model space point to be projected. 299 * @return the 2D projected pixel location on the screen of the 3D point 300 * projection. 301 */ 302 private Point2d get3DTo2DPoint(Point3d point3d) { 303 _p3d.set(point3d); // Must defensively copy the input point. 304 305 // Get the required transforms. 306 _symmSG.getChild(0).getLocalToVworld(T_VL); 307 GeomSSCanvas3D canvas = getCanvas3D(); 308 canvas.getVworldToImagePlate(T_IV); 309 310 // Convert from local coordinates to image plate coordinates. 311 // T_IL = T_IV * T_VL = ImagePlate wrt Local 312 T_IV.mul(T_VL); 313 T_IV.transform(_p3d); 314 315 // Convert from image plate to AWT pixel locations. 316 Point2d point2d = new Point2d(); 317 canvas.getPixelLocationFromImagePlate(_p3d, point2d); 318 319 return point2d; 320 } 321 } 322}