/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: J3DUtils.java
* Written by Gilda Garreton, Sun Microsystems.
*
* Copyright (c) 2003 Sun Microsystems and Static Free Software
*
* Electric(tm) is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.plugins.j3d.utils;
import com.sun.electric.database.geometry.DBMath;
import com.sun.electric.database.text.Pref;
import com.sun.electric.plugins.j3d.View3DWindow;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.dialogs.OpenFile;
import com.sun.j3d.utils.behaviors.interpolators.KBKeyFrame;
import com.sun.j3d.utils.behaviors.interpolators.TCBKeyFrame;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.picking.PickTool;
import com.sun.j3d.utils.universe.SimpleUniverse;
import java.awt.Color;
import java.awt.GraphicsConfiguration;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.FileReader;
import java.io.LineNumberReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.StringTokenizer;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.GeometryUpdater;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Light;
import javax.media.j3d.Node;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Quat4f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector4f;
/**
* Utility class for 3D module
* @author Gilda Garreton
* @version 0.1
*/
public final class J3DUtils
{
/** standard colors to be used by materials **/ public static final Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
// /** standard colors to be used by materials **/ public static final Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
/** standard colors to be used by materials **/ public static final Color3f plastic = new Color3f(0.89f, 0.89f, 0.89f);
/** Ambiental light color **/ private static Color3fObservable ambientalColor;
/** Directional light color **/ private static Color3fObservable directionalColor;
/** Background color **/ private static Color3fObservable backgroundColor;
/** Directional vectors **/ private static Vector3fObservable[] lights = new Vector3fObservable[2]; // = new Vector3f(-1.0f, -1.0f, -1.0f);
public static final BoundingSphere infiniteBounds = new BoundingSphere(new Point3d(), Double.MAX_VALUE);
private static Pref cache3DOrigZoom = Pref.makeDoublePref("3DOrigZoom3D", User.getUserTool().prefs, 1);
private static Pref cache3DRot = Pref.makeStringPref("3DRotation", User.getUserTool().prefs, "(0 0 0)");
private static Pref cache3DFactor = Pref.makeDoublePref("3DScaleZ", User.getUserTool().prefs, 1.0);
private static Pref cache3DAntialiasing = Pref.makeBooleanPref("3DAntialiasing", User.getUserTool().prefs, false);
private static Pref cache3DPerspective = Pref.makeBooleanPref("3DPerspective", User.getUserTool().prefs, true);
private static Pref cache3DCellBnd = Pref.makeBooleanPref("3DCellBnd", User.getUserTool().prefs, true);
private static Pref cache3DAxes = Pref.makeBooleanPref("3DAxes", User.getUserTool().prefs, false);
private static Pref cache3DMaxNumber = Pref.makeIntPref("3DMaxNumNodes", User.getUserTool().prefs, 1000);
private static Pref cache3DAlpha = Pref.makeIntPref("3DAlpha", User.getUserTool().prefs, 20000);
private static Pref cache3DLightDirs = Pref.makeStringPref("3DLightDirs", User.getUserTool().prefs, "(-1 1 -1)(1 -1 -1)");
public static J3DAlpha jAlpha = null;
// For reading data files (capacitance)
private static final int VALUES_PER_LINE = 11;
private static double[] lastValidValues = new double[VALUES_PER_LINE];
/**
* Method to return the first light direction.
* The default is the X axis.
* @return the company name to use in schematic frames.
*/
public static String get3DLightDirs() { return cache3DLightDirs.getString(); }
/**
* Method to set the first light direction.
* It is stored as string
* @param c the company name to use in schematic frames.
*/
public static void set3DLightDirs(String c)
{
cache3DLightDirs.setString(c);
setDirections(null);
}
/**
* Method to return the first light direction, by default.
* @return the company name to use in schematic frames, by default.
*/
public static String getFactory3DLightDirs() { return cache3DLightDirs.getStringFactoryValue(); }
/**
* Method to get the color of the axes on the 3D display.
* The default values are "red", "blue" and "green"..
* @return the color of the axes on the 3D display.
*/
public static int[] get3DColorAxes()
{
return new int[] {
User.getColor(User.ColorPrefType.AXIS_X_3D),
User.getColor(User.ColorPrefType.AXIS_Y_3D),
User.getColor(User.ColorPrefType.AXIS_Z_3D)
};
}
/**
* Method to get maximum number of nodes to consider a scene graph.
* The default is "1000".
* @return maximim number of nodes.
*/
public static int get3DMaxNumNodes() { return cache3DMaxNumber.getInt(); }
/**
* Method to set maximum number of nodes to display in 3D view.
* @param num maximim number of nodes.
*/
public static void set3DMaxNumNodes(int num) { cache3DMaxNumber.setInt(num); }
/**
* Method to get maximum number of nodes to consider a scene graph, by default.
* @return maximim number of nodes, by default.
*/
public static int getFactory3DMaxNumNodes() { return cache3DMaxNumber.getIntFactoryValue(); }
/**
* Method to tell whether to draw 3D axes or not.
* The default is "true".
* @return true to draw 3D axes.
*/
public static boolean is3DAxesOn() { return cache3DAxes.getBoolean(); }
/**
* Method to set whether to draw 3D axes or not.
* @param on true to draw 3D axes.
*/
public static void set3DAxesOn(boolean on) { cache3DAxes.setBoolean(on); }
/**
* Method to tell whether to draw 3D axes or not, by default.
* @return true to draw 3D axes, by default.
*/
public static boolean isFactory3DAxesOn() { return cache3DAxes.getBooleanFactoryValue(); }
/**
* Method to tell whether to draw bounding box for the cells.
* The default is "true".
* @return true to draw bounding box for the cells.
*/
public static boolean is3DCellBndOn() { return cache3DCellBnd.getBoolean(); }
/**
* Method to set whether to draw bounding box for the cells.
* @param on true to draw bounding box for the cells.
*/
public static void set3DCellBndOn(boolean on) { cache3DCellBnd.setBoolean(on); }
/**
* Method to tell whether to draw bounding box for the cells, by default.
* @return true to draw bounding box for the cells, by default.
*/
public static boolean isFactory3DCellBndOn() { return cache3DCellBnd.getBooleanFactoryValue(); }
/**
* Method to tell whether to draw 3D views with perspective.
* The default is "true".
* @return true to draw 3D views with perspective.
*/
public static boolean is3DPerspective() { return cache3DPerspective.getBoolean(); }
/**
* Method to set whether to draw 3D views with perspective.
* @param on true to draw 3D views with perspective.
*/
public static void set3DPerspective(boolean on) { cache3DPerspective.setBoolean(on); }
/**
* Method to tell whether to draw 3D views with perspective, by default.
* @return true to draw 3D views with perspective, by default.
*/
public static boolean isFactory3DPerspective() { return cache3DPerspective.getBooleanFactoryValue(); }
/**
* Method to tell whether to use antialiasing in 3D view.
* The default is "false" due to performance.
* @return true to draw 3D views with perspective.
*/
public static boolean is3DAntialiasing() { return cache3DAntialiasing.getBoolean(); }
/**
* Method to set whether to draw 3D views with perspective.
* @param on true to draw 3D views with perspective.
*/
public static void set3DAntialiasing(boolean on) { cache3DAntialiasing.setBoolean(on); }
/**
* Method to tell whether to use antialiasing in 3D view, by default.
* @return true to draw 3D views with perspective, by default.
*/
public static boolean isFactory3DAntialiasing() { return cache3DAntialiasing.getBooleanFactoryValue(); }
/**
* Method to get original zoom factor for the view
* The default is 1
* @return original zoom factor
*/
public static double get3DOrigZoom() { return cache3DOrigZoom.getDouble(); }
/**
* Method to set default zoom factor
* @param value zoom factor
*/
public static void set3DOrigZoom(double value) { cache3DOrigZoom.setDouble(value); }
/**
* Method to get original zoom factor for the view, by default.
* @return original zoom factor, by default.
*/
public static double getFactory3DOrigZoom() { return cache3DOrigZoom.getDoubleFactoryValue(); }
/**
* Method to get default rotation for the view along X, Y and Z
* The default is (0 0 0) and values are in radiant
* @return rotation along X, y and Z axes.
*/
public static String get3DRotation() { return cache3DRot.getString(); }
/**
* Method to set default rotation angles along X, Y and Z. Values are in radiant
* @param value angles on X, Y and Z
*/
public static void set3DRotation(String value) { cache3DRot.setString(value); }
/**
* Method to get factory default rotation for the view along X, Y and Z
* @return factory default rotation along X, y and Z axes.
*/
public static String getFactory3DRotation() { return cache3DRot.getStringFactoryValue(); }
/**
* Method to get current scale factor for Z values.
* The default is 1.0
* @return scale factor along Z.
*/
public static double get3DFactor() { return cache3DFactor.getDouble(); }
/**
* Method to set 3D scale factor
* @param value 3D scale factor to set.
*/
public static void set3DFactor(double value) { cache3DFactor.setDouble(value); }
/**
* Method to get current scale factor for Z values, by default.
* @return scale factor along Z, by default.
*/
public static double getFactory3DFactor() { return cache3DFactor.getDoubleFactoryValue(); }
/**
* Method to get current alpha speed for 3D demos
* The default is 1000
* @return alpha speed.
*/
public static int get3DAlpha() { return cache3DAlpha.getInt(); }
/**
* Method to set 3D alpha speed
* @param value 3D alpha to set.
*/
public static void set3DAlpha(int value)
{
cache3DAlpha.setInt(value);
setAlpha(value);
}
/**
* Method to get current alpha speed for 3D demos, by default.
* @return alpha speed, by default.
*/
public static int getFactory3DAlpha() { return cache3DAlpha.getIntFactoryValue(); }
/**
* Method to generate knots for interpolator from a file
* @param view3D
* @return list with knot points. Null if operation was cancelled
*/
public static List<J3DUtils.ThreeDDemoKnot> readDemoDataFromFile(View3DWindow view3D)
{
String fileName = OpenFile.chooseInputFile(FileType.TEXT, null);
if (fileName == null) return null; // Cancel
String[] possibleValues = { "Accept All", "OK", "Skip", "Cancel" };
List<J3DUtils.ThreeDDemoKnot> knotList = null;
try {
LineNumberReader lineReader = new LineNumberReader(new FileReader(fileName));
int response = -1;
for(;;)
{
// get keyword from file
String line = lineReader.readLine();
if (line == null) break;
// responce 0 -> Accept All
if (response != 0)
{
response = Job.getUserInterface().askForChoice("Applying following data " + line, "Action",
possibleValues, possibleValues[0]);
if (response == 2) continue; // skip
else if (response == 3) break; // cancel option
}
String[] stringValues = parseValues(line, 0);
double[] values = convertValues(stringValues);
if (knotList == null) knotList = new ArrayList<J3DUtils.ThreeDDemoKnot>();
knotList.add(view3D.moveAndRotate(values));
}
} catch (Exception e)
{
e.printStackTrace();
}
return knotList;
}
public static double[] convertValues(String[] stringValues)
{
double[] values = new double[stringValues.length];
for (int i = 0; i < stringValues.length; i++)
{
try
{
values[i] = Double.parseDouble(stringValues[i]);
}
catch (Exception e) // invalid number in line
{
values[i] = lastValidValues[i];
}
lastValidValues[i] = values[i];
if (2 < i && i < 6 )
values[i] = convertToRadiant(values[i]); // original value is in degrees
}
return values;
}
/**
* To parse capacitance data from line
* Format: posX posY posZ rotX rotY rotZ rotPosX rotPosY rotPosZ capacitance radius error
* @param line
* @param lineNumner
*/
public static String[] parseValues(String line, int lineNumner)
{
int count = 0;
String[] strings = new String[VALUES_PER_LINE]; // 12 is the max value including errors
StringTokenizer parse = new StringTokenizer(line, " ", false);
while (parse.hasMoreTokens() && count < VALUES_PER_LINE)
{
strings[count++] = parse.nextToken();
}
if (count < 9 || count > 13)
{
System.out.println("Error reading capacitance file in line " + lineNumner);
}
return strings;
}
/********************************************************************************************************
* Observer-Observable pattern for Vector3f
*******************************************************************************************************/
/**
* Class using view-model paradigm to update Vector3d-type of variables like directions
*/
private static class Vector3fObservable extends Observable
{
private Vector3f vector;
public Vector3fObservable(Vector3f vec)
{
this.vector = vec;
}
public void setValue(Vector3f vec)
{
this.vector = vec;
setChanged();
notifyObservers(vector);
clearChanged();
}
public Vector3f getValue() {return vector;}
}
/**
* Observer class for directional light
*/
private static class DirectionalLightObserver extends DirectionalLight implements Observer
{
public DirectionalLightObserver(Color3f color3f, Vector3f vector3f)
{
super(color3f, vector3f);
}
public void update(Observable o, Object arg)
{
if (arg != null)
{
if (arg instanceof Vector3f) // Change direction
{
// change the direction
setDirection((Vector3f)arg);
return;
}
if (arg instanceof Color3f) // Change direction
{
// change the direction
setColor((Color3f)arg);
return;
}
}
}
}
/********************************************************************************************************
* Observer-Observable pattern for Color3f
*******************************************************************************************************/
private static class Color3fObservable extends Observable
{
private Color3f color;
public Color3fObservable(Color3f color)
{
this.color = color;
}
public void setValue(Color3f color)
{
this.color = color;
setChanged();
notifyObservers(color);
clearChanged();
}
public Color3f getValue() {return color;}
}
private static class AmbientLightObserver extends AmbientLight implements Observer
{
public AmbientLightObserver(Color3f color3f)
{
super(color3f);
}
public void update(Observable o, Object arg)
{
if (arg != null && arg instanceof Color3f)
{
// change the color
setColor((Color3f)arg);
}
}
}
private static class BackgroundObserver extends Background implements Observer
{
public BackgroundObserver(Color3f color3f)
{
super(color3f);
setCapability(Background.ALLOW_COLOR_WRITE);
setCapability(Background.ALLOW_COLOR_READ);
}
public void update(Observable o, Object arg)
{
if (arg != null && arg instanceof Color3f)
{
// change the color
setColor((Color3f)arg);
}
}
}
public static Vector3f[] transformIntoVectors(String dir)
{
float[][] values = new float[2][3];
StringTokenizer parse = new StringTokenizer(dir, "()", false);
int pair = 0;
while (parse.hasMoreTokens() && pair < 2)
{
String vector = parse.nextToken();
StringTokenizer parseDir = new StringTokenizer(vector, " )", false);
int count = 0;
while (parseDir.hasMoreTokens() && count < 3)
{
String value = parseDir.nextToken();
values[pair][count++] = Float.parseFloat(value);
}
pair++;
}
Vector3f[] vectors = new Vector3f[2];
for (int i = 0; i < 2; i++)
{
if (!(values[i][0] == 0 && values[i][1] == 0 && values[i][2] == 0))
vectors[i]= new Vector3f(values[i]);
}
return (vectors);
}
/**
* Method to set direction in directional light
* @param initValue
*/
public static void setDirections(Object initValue)
{
Vector3f[] dirs = transformIntoVectors(get3DLightDirs());
for (int i = 0; i < dirs.length; i++)
{
if (lights[i] == null)
lights[i] = new Vector3fObservable(dirs[i]);
else if (initValue == null)
lights[i].setValue(dirs[i]); // it will notify observers
}
}
/**
* Method to set ambiental color
* @param initValue null if value has to be redone from user data
*/
public static void setAmbientalColor(Object initValue)
{
Color3f userColor = new Color3f(new Color(User.getColor(User.ColorPrefType.AMBIENT_3D)));
if (ambientalColor == null)
ambientalColor = new Color3fObservable(userColor);
else if (initValue == null)
ambientalColor.setValue(userColor);
}
/**
* Method to set directional color
* @param initValue null if value has to be redone from user data
*/
public static void setDirectionalColor(Object initValue)
{
Color3f userColor = new Color3f(new Color(User.getColor(User.ColorPrefType.DIRECTIONAL_LIGHT_3D)));
if (directionalColor == null)
directionalColor = new Color3fObservable(userColor);
else if (initValue == null)
directionalColor.setValue(userColor);
}
/**
* Method to set background color
* @param initValue null if value has to be redone from user data
*/
public static void setBackgroundColor(Object initValue)
{
Color3f userColor = new Color3f(new Color(User.getColor(User.ColorPrefType.BACKGROUND)));
if (backgroundColor == null)
backgroundColor = new Color3fObservable(userColor);
else if (initValue == null)
backgroundColor.setValue(userColor);
}
/** Create the background node based on given background color
* @param scene
*/
public static void createBackground(BranchGroup scene)
{
setBackgroundColor(scene);
BackgroundObserver bg = new BackgroundObserver(backgroundColor.getValue());
backgroundColor.addObserver(bg);
bg.setApplicationBounds(infiniteBounds);
scene.addChild(bg);
}
/**
* Create alpha according to given interval time in miliseconds
* @param speed
*/
public static void setAlpha(int speed)
{
if (jAlpha == null)
jAlpha = new J3DAlpha(speed, true, 0.5f);
else
jAlpha.setAlphaSpeed(speed);
}
/**
* Create the lights (directional and ambiental) for the given scene graph
* based on User's data
* @param scene
*/
public static void createLights(BranchGroup scene)
{
// Checking if light colors are available
setDirectionalColor(scene);
setAmbientalColor(scene);
setDirections(scene);
AmbientLightObserver ambientalLight = new AmbientLightObserver(ambientalColor.getValue());
ambientalLight.setInfluencingBounds(infiniteBounds);
ambientalLight.setCapability(AmbientLight.ALLOW_COLOR_READ);
ambientalLight.setCapability(AmbientLight.ALLOW_COLOR_WRITE);
// adding observer
ambientalColor.addObserver(ambientalLight);
// Add ambiental light to the env.
scene.addChild(ambientalLight);
for (int i = 0; i < lights.length; i++)
{
if (lights[i] == null || lights[i].getValue() == null ||
(lights[i].getValue().x == 0 &&
lights[i].getValue().y == 0 &&
lights[i].getValue().z == 0)) continue; // invalid light
DirectionalLightObserver directionalLight = new DirectionalLightObserver(directionalColor.getValue(),
lights[i].getValue());
directionalLight.setInfluencingBounds(infiniteBounds);
// Allow to turn off light while the scene graph is live
directionalLight.setCapability(Light.ALLOW_STATE_WRITE);
directionalLight.setCapability(DirectionalLight.ALLOW_DIRECTION_READ);
directionalLight.setCapability(DirectionalLight.ALLOW_DIRECTION_WRITE);
directionalLight.setCapability(DirectionalLight.ALLOW_COLOR_READ);
directionalLight.setCapability(DirectionalLight.ALLOW_COLOR_WRITE);
// adding observers
lights[i].addObserver(directionalLight);
directionalColor.addObserver(directionalLight);
// Add lights to the env.
scene.addChild(directionalLight);
}
}
public static void setViewPoint(SimpleUniverse u, Canvas3D canvas, BranchGroup scene, Rectangle2D cellBnd)
{
BoundingSphere sceneBnd = (BoundingSphere)scene.getBounds();
double radius = sceneBnd.getRadius();
View view = u.getViewer().getView();
// Too expensive at this point
if (canvas.getSceneAntialiasingAvailable() && is3DAntialiasing())
view.setSceneAntialiasingEnable(true);
// Setting the projection policy
view.setProjectionPolicy(is3DPerspective()? View.PERSPECTIVE_PROJECTION : View.PARALLEL_PROJECTION);
if (!is3DPerspective()) view.setCompatibilityModeEnable(true);
// Setting transparency sorting
//view.setTransparencySortingPolicy(View.TRANSPARENCY_SORT_GEOMETRY);
//view.setDepthBufferFreezeTransparent(false); // set to true only for transparent layers
Point3d c1 = new Point3d();
sceneBnd.getCenter(c1);
Vector3d vCenter = new Vector3d(c1);
double vDist = 1.4 * radius / Math.tan(view.getFieldOfView()/2.0);
Point3d c2 = new Point3d();
sceneBnd.getCenter(c2);
c2.z += vDist;
//if (User.is3DPerspective())
vCenter.z += vDist;
Transform3D vTrans = new Transform3D();
vTrans.set(vCenter);
view.setBackClipDistance((vDist+radius)*2.0);
view.setFrontClipDistance((vDist+radius)/200.0);
view.setBackClipPolicy(View.VIRTUAL_EYE);
view.setFrontClipPolicy(View.VIRTUAL_EYE);
if (is3DPerspective())
{
u.getViewingPlatform().getViewPlatformTransform().setTransform(vTrans);
}
else
{
Transform3D proj = new Transform3D();
proj.ortho(cellBnd.getMinX(), cellBnd.getMinX(), cellBnd.getMinY(), cellBnd.getMaxY(), (vDist+radius)/200.0, (vDist+radius)*2.0);
view.setVpcToEc(proj);
//viewingPlatform.getViewPlatformTransform().setTransform(lookAt);
}
}
// public static double covertToDegrees(double radiant)
// {
// return ((180*radiant)/Math.PI);
// }
public static double convertToRadiant(double degrees)
{
return ((Math.PI*degrees)/180);
}
/**
* Utility class to modify live/compiled scene graph
*/
private static class JGeometryUpdater implements GeometryUpdater
{
float z1, z2, origZ1, origZ2;
public JGeometryUpdater(float origZ1, float origZ2, float z1, float z2)
{
this.z1 = z1;
this.z2 = z2;
this.origZ1 = origZ1;
this.origZ2 = origZ2;
}
public void updateData(Geometry geometry)
{
if (!(geometry instanceof GeometryArray)) return;
GeometryArray ga = (GeometryArray)geometry;
float[] vals = ga.getCoordRefFloat();
for (int i = 0; i < vals.length/3; i++)
{
if (DBMath.areEquals(vals[i*3+2], origZ1))
vals[i*3+2] = z1;
else if (DBMath.areEquals(vals[i*3+2], origZ2))
vals[i*3+2] = z2;
}
ga.setCoordRefFloat(vals);
}
}
/**
* Method to reset z values of shapes created with addPolyhedron
* @param shape
* @param origZ1
* @param origZ2
* @param z1
* @param z2
* @return true if values were valid
*/
public static boolean updateZValues(Shape3D shape, float origZ1, float origZ2, float z1, float z2)
{
if (DBMath.areEquals(z1, z2)) return false; // nothing to do. Eg. 0 as value
GeometryArray ga = (GeometryArray)shape.getGeometry();
ga.updateData(new JGeometryUpdater(origZ1, origZ2, z1, z2));
return true;
}
/**
* Method to add a cylindrical shaped element
* @param points
* @param distance
* @param thickness
* @param ap
* @param objTrans
* @return
*/
public static Node addCylinder(Point2D[] points, double distance, double thickness,
Appearance ap, TransformGroup objTrans)
{
double cX = points[0].getX();
double cY = points[0].getY();
double radius = points[0].distance(points[1]);
Cylinder cylinder = new Cylinder((float)radius, (float)thickness, ap);
Vector3d bottomCenter = new Vector3d(cX,cY,distance);
Transform3D t = new Transform3D();
t.rotX(Math.PI/2);
t.setTranslation(bottomCenter);
t.setScale(1);
TransformGroup grp = new TransformGroup(t);
grp.addChild(cylinder); // adding Primitive to TransformaGroup so location can be modified.
grp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
grp.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
grp.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
grp.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
grp.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
grp.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
grp.setCapability(TransformGroup.ALLOW_LOCAL_TO_VWORLD_READ);
grp.setCapability(TransformGroup.ALLOW_PICKABLE_READ);
grp.setCapability(TransformGroup.ALLOW_BOUNDS_READ);
Shape3D[] shapes = new Shape3D[3];
shapes[0] = cylinder.getShape(Cylinder.BODY);
shapes[1] = cylinder.getShape(Cylinder.TOP);
shapes[2] = cylinder.getShape(Cylinder.BOTTOM);
for (int i = 0; i < 3; i++)
{
shapes[i].setCapability(Primitive.ENABLE_PICK_REPORTING);
shapes[i].setCapability(Primitive.ENABLE_GEOMETRY_PICKING);
shapes[i].setCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ);
shapes[i].setCapability(Primitive.ALLOW_PICKABLE_READ);
shapes[i].setCapability(Shape3D.ALLOW_APPEARANCE_READ);
shapes[i].setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
shapes[i].setCapability(Primitive.ALLOW_BOUNDS_READ);
shapes[i].setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
PickTool.setCapabilities(shapes[i], PickTool.INTERSECT_COORD);
}
objTrans.addChild(grp);
return cylinder;
}
/**
* Method to add a polyhedron to the transformation group
* @param objTrans
*/
public static Shape3D addPolyhedron(Rectangle2D bounds, double distance, double thickness,
Appearance ap, TransformGroup objTrans)
{
GeometryInfo gi = new GeometryInfo(GeometryInfo.QUAD_ARRAY);
double height = thickness + distance;
Point3d[] pts = new Point3d[8];
pts[0] = new Point3d(bounds.getMinX(), bounds.getMinY(), distance);
pts[1] = new Point3d(bounds.getMinX(), bounds.getMaxY(), distance);
pts[2] = new Point3d(bounds.getMaxX(), bounds.getMaxY(), distance);
pts[3] = new Point3d(bounds.getMaxX(), bounds.getMinY(), distance);
pts[4] = new Point3d(bounds.getMinX(), bounds.getMinY(), height);
pts[5] = new Point3d(bounds.getMinX(), bounds.getMaxY(), height);
pts[6] = new Point3d(bounds.getMaxX(), bounds.getMaxY(), height);
pts[7] = new Point3d(bounds.getMaxX(), bounds.getMinY(), height);
int[] indices = {0, 1, 2, 3, /* bottom z */
0, 4, 5, 1, /* back y */
0, 3, 7, 4, /* back x */
1, 5, 6, 2, /* front x */
2, 6, 7, 3, /* front y */
4, 7, 6, 5}; /* top z */
gi.setCoordinates(pts);
gi.setCoordinateIndices(indices);
NormalGenerator ng = new NormalGenerator();
ng.generateNormals(gi);
GeometryArray c = gi.getGeometryArray(true, false, false);
c.setCapability(GeometryArray.ALLOW_INTERSECT);
//c.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
c.setCapability(GeometryArray.ALLOW_COORDINATE_WRITE);
c.setCapability(GeometryArray.BY_REFERENCE);
c.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
c.setCapability(GeometryArray.ALLOW_REF_DATA_WRITE);
Shape3D box = new Shape3D(c, ap);
box.setCapability(Shape3D.ENABLE_PICK_REPORTING);
box.setCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ);
box.setCapability(Shape3D.ALLOW_PICKABLE_READ);
box.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
box.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
box.setCapability(Shape3D.ALLOW_BOUNDS_READ);
box.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
PickTool.setCapabilities(box, PickTool.INTERSECT_FULL);
objTrans.addChild(box);
return(box);
}
/**
* Simple method to generate polyhedra
* @param pts
* @param listLen
* @param ap
* @return
*/
public static Shape3D addShape3D(Point3d[] pts, int listLen, Appearance ap,
TransformGroup objTrans)
{
int numFaces = listLen + 2; // contour + top + bottom
int[] indices = new int[listLen*6];
int[] stripCounts = new int[numFaces];
int[] contourCount = new int[numFaces];
Arrays.fill(contourCount, 1);
Arrays.fill(stripCounts, 4);
stripCounts[0] = listLen; // top
stripCounts[numFaces-1] = listLen; // bottom
int count = 0;
// Top face
for (int i = 0; i < listLen; i++)
indices[count++] = i;
// Contour
for (int i = 0; i < listLen; i++)
{
indices[count++] = i;
indices[count++] = i + listLen;
indices[count++] = (i+1)%listLen + listLen;
indices[count++] = (i+1)%listLen;
}
// Bottom face
for (int i = 0; i < listLen; i++)
indices[count++] = (listLen-i)%listLen + listLen;
GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
gi.setCoordinates(pts);
gi.setCoordinateIndices(indices);
gi.setStripCounts(stripCounts);
gi.setContourCounts(contourCount);
NormalGenerator ng = new NormalGenerator();
ng.setCreaseAngle ((float) Math.toRadians(30));
ng.generateNormals(gi);
GeometryArray c = gi.getGeometryArray();
c.setCapability(GeometryArray.ALLOW_INTERSECT);
Shape3D box = new Shape3D(c, ap);
box.setCapability(Shape3D.ENABLE_PICK_REPORTING);
box.setCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ);
box.setCapability(Shape3D.ALLOW_PICKABLE_READ);
box.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
box.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
box.setCapability(Shape3D.ALLOW_BOUNDS_READ);
PickTool.setCapabilities(box, PickTool.INTERSECT_FULL);
objTrans.addChild(box);
return (box);
}
/**
*/
public static Shape3D addPolyhedron(PathIterator pIt, double distance, double thickness,
Appearance ap, TransformGroup objTrans)
{
double height = thickness + distance;
double [] coords = new double[6];
java.util.List<Point3d> topList = new ArrayList<Point3d>();
java.util.List<Point3d> bottomList = new ArrayList<Point3d>();
java.util.List<Shape3D> shapes = new ArrayList<Shape3D>();
while (!pIt.isDone())
{
int type = pIt.currentSegment(coords);
if (type == PathIterator.SEG_CLOSE)
{
int listLen = topList.size();
Point3d [] pts = new Point3d[listLen*2];
correctNormals(topList, bottomList);
System.arraycopy(topList.toArray(), 0, pts, 0, listLen);
System.arraycopy(bottomList.toArray(), 0, pts, listLen, listLen);
int numFaces = listLen + 2; // contour + top + bottom
int[] indices = new int[listLen*6];
int[] stripCounts = new int[numFaces];
int[] contourCount = new int[numFaces];
Arrays.fill(contourCount, 1);
Arrays.fill(stripCounts, 4);
stripCounts[0] = listLen; // top
stripCounts[numFaces-1] = listLen; // bottom
int count = 0;
// Top face
for (int i = 0; i < listLen; i++)
indices[count++] = i;
// Contour
for (int i = 0; i < listLen; i++)
{
indices[count++] = i;
indices[count++] = i + listLen;
indices[count++] = (i+1)%listLen + listLen;
indices[count++] = (i+1)%listLen;
}
// Bottom face
for (int i = 0; i < listLen; i++)
indices[count++] = (listLen-i)%listLen + listLen;
GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
gi.setCoordinates(pts);
gi.setCoordinateIndices(indices);
gi.setStripCounts(stripCounts);
gi.setContourCounts(contourCount);
NormalGenerator ng = new NormalGenerator();
ng.setCreaseAngle ((float) Math.toRadians(30));
ng.generateNormals(gi);
GeometryArray c = gi.getGeometryArray();
c.setCapability(GeometryArray.ALLOW_INTERSECT);
Shape3D box = new Shape3D(c, ap);
box.setCapability(Shape3D.ENABLE_PICK_REPORTING);
box.setCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ);
box.setCapability(Shape3D.ALLOW_PICKABLE_READ);
box.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
box.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
box.setCapability(Shape3D.ALLOW_BOUNDS_READ);
PickTool.setCapabilities(box, PickTool.INTERSECT_FULL);
objTrans.addChild(box);
shapes.add(box);
topList.clear();
bottomList.clear();
} else if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO)
{
Point3d pt = new Point3d(coords[0], coords[1], distance);
topList.add(pt);
pt = new Point3d(coords[0], coords[1], height);
bottomList.add(pt);
}
pIt.next();
}
if (shapes.size()>1) System.out.println("Error: case not handled");
return shapes.get(0);
}
/**
* Method to correct points sequence to obtain valid normals
* @param topList
* @param bottomList
*/
public static void correctNormals(List<Point3d> topList, List<Point3d> bottomList)
{
// Determining normal direction
Point3d p0 = topList.get(0);
Point3d p1 = new Point3d(topList.get(1));
p1.sub(p0);
Point3d pn = new Point3d(topList.get(topList.size()-1));
pn.sub(p0);
Vector3d aux = new Vector3d();
aux.cross(new Vector3d(p1), new Vector3d(pn));
// the other layer
Point3d b0 = new Point3d(bottomList.get(0));
b0.sub(p0);
// Now the dot product
double dot = aux.dot(new Vector3d(b0));
if (dot > 0) // Invert sequence of points otherwise the normals will be wrong
{
Collections.reverse(topList);
Collections.reverse(bottomList);
}
}
/*******************************************************************************************************
* DEMO SECTION
*******************************************************************************************************/
/**
* This class is @serial
*/
public static class ThreeDDemoKnot implements Serializable
{
private static final long serialVersionUID = -7059885190094183408L;
float xValue;
float yValue;
float zValue;
Vector3f translation;
float scale;
float heading; // Sets the camera's heading. This automatically modifies the target's position.
float pitch; // Sets the camera's pitch in degrees. This automatically modifies the target's position.
float bank; // Sets the camera's bank in degrees. The angle is relative to the horizon.
double rotZ;
double rotY;
double rotX;
Quat4f rotation;
// public Shape3D shape;
public ThreeDDemoKnot(double xValue, double yValue, double zValue, double scale,
double heading, double pitch, double bank,
double rotX, double rotY, double rotZ)
{
this.xValue = (float)xValue;
this.yValue = (float)yValue;
this.zValue = (float)zValue;
this.scale = (float)scale;
this.heading = (float)heading;
this.pitch = (float)pitch;
this.bank = (float)bank;
this.rotZ = rotZ;
this.rotX = rotX;
this.rotY = rotY;
}
public ThreeDDemoKnot(double scale, Vector3f trans, Quat4f rot, Shape3D shape)
{
this.scale = (float)scale;
this.translation = trans;
this.rotation = rot;
// this.shape = shape;
}
}
/**
* Method to generate each individual frame key for the interporlation
* based on Poly information
* @param ratio
* @param knot
* @return
*/
public static KBKeyFrame getNextKBKeyFrame(float ratio, ThreeDDemoKnot knot)
{
// Prepare spline keyframe data
Vector3f pos = new Vector3f (knot.xValue+100, knot.yValue+100, knot.zValue);
Point3f point = new Point3f (pos); // position
Point3f scale = new Point3f(knot.scale, knot.scale, knot.scale); // uniform scale3D
KBKeyFrame key = new KBKeyFrame(ratio, 0, point, knot.heading, knot.pitch, knot.bank, scale, 0.0f, 0.0f, 1.0f);
return key;
}
public static TCBKeyFrame getNextTCBKeyFrame(float ratio, ThreeDDemoKnot knot)
{
// Prepare spline keyframe data
Vector3f pos = knot.translation;
// Initial translatio is not given as vector
if (pos == null)
pos = new Vector3f (knot.xValue, knot.yValue, knot.zValue);
Point3f point = new Point3f (pos); // position
Quat4f quat = knot.rotation;
// Initial rotation not given as Quat4f
if (quat == null)
quat = createQuaternionFromEuler(knot.rotX, knot.rotY, knot.rotZ);
Point3f scale = new Point3f(knot.scale, knot.scale, knot.scale); // uniform scale3D
TCBKeyFrame key = new TCBKeyFrame(ratio, 0, point, quat, scale, 0.0f, 0.0f, 1.0f);
return key;
}
/**
* Convert an angular rotation about an axis to a Quaternion.
* From Selman's book
* @param axis
* @param angle
* @return
*/
private static Quat4f createQuaternionFromAxisAndAngle( Vector3d axis, double angle )
{
double sin_a = Math.sin( angle / 2 );
double cos_a = Math.cos( angle / 2 );
// use a vector so we can call normalize
Vector4f q = new Vector4f( );
q.x = (float) (axis.x * sin_a);
q.y = (float) (axis.y * sin_a);
q.z = (float) (axis.z * sin_a);
q.w = (float) cos_a;
// It is necessary to normalise the quaternion
// in case any values are very close to zero.
q.normalize( );
// convert to a Quat4f and return
return new Quat4f( q );
}
/**
* Convert three rotations about the Euler axes to a Quaternion.
* From Selman's book
* @param angleX
* @param angleY
* @param angleZ
* @return
*/
public static Quat4f createQuaternionFromEuler( double angleX, double angleY, double angleZ )
{
// simply call createQuaternionFromAxisAndAngle
// for each axis and multiply the results
Quat4f qx = createQuaternionFromAxisAndAngle(J3DAxis.axisX, angleX);
Quat4f qy = createQuaternionFromAxisAndAngle(J3DAxis.axisY, angleY);
Quat4f qz = createQuaternionFromAxisAndAngle(J3DAxis.axisZ, angleZ);
// qx = qx * qy
qx.mul( qy );
// qx = qx * qz
qx.mul( qz );
return qx;
}
/*******************************************************************************************************
* Color Tab SECTION
*******************************************************************************************************/
public static void get3DColorsInTab(Map<User.ColorPrefType, String> nameTypeSpecialMap)
{
// 3D Stuff
nameTypeSpecialMap.put(User.ColorPrefType.INSTANCE_3D, "Special: 3D CELL INSTANCES");
nameTypeSpecialMap.put(User.ColorPrefType.HIGHLIGHT_3D, "Special: 3D HIGHLIGHTED INSTANCES");
nameTypeSpecialMap.put(User.ColorPrefType.AMBIENT_3D, "Special: 3D AMBIENT LIGHT");
nameTypeSpecialMap.put(User.ColorPrefType.DIRECTIONAL_LIGHT_3D, "Special: 3D DIRECTIONAL LIGHT");
nameTypeSpecialMap.put(User.ColorPrefType.AXIS_X_3D, "Special: 3D AXIS X");
nameTypeSpecialMap.put(User.ColorPrefType.AXIS_Y_3D, "Special: 3D AXIS Y");
nameTypeSpecialMap.put(User.ColorPrefType.AXIS_Z_3D, "Special: 3D AXIS Z");
}
/*******************************************************************************************************
* IMAGE SECTION
*******************************************************************************************************/
/**
* Class to create offscreen from canvas 3D
*/
public static class OffScreenCanvas3D extends Canvas3D {
public OffScreenCanvas3D(GraphicsConfiguration graphicsConfiguration,
boolean offScreen) {
super(graphicsConfiguration, offScreen);
}
BufferedImage doRender(int width, int height)
{
BufferedImage bImage =
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
ImageComponent2D buffer =
new ImageComponent2D(ImageComponent.FORMAT_RGBA, bImage);
setOffScreenBuffer(buffer);
renderOffScreenBuffer();
waitForOffScreenRendering();
bImage = getOffScreenBuffer().getImage();
return bImage;
}
public void postSwap() {
// No-op since we always wait for off-screen rendering to complete
}
}
}