...
 
Commits (18)
......@@ -32,5 +32,7 @@
<classpathentry kind="lib" path="lib/itext/itext-pdfa-5.5.5.jar"/>
<classpathentry kind="lib" path="lib/itext/itext-xtra-5.5.5.jar"/>
<classpathentry kind="lib" path="lib/itext/itextpdf-5.5.5_dgdgitlab.jar"/>
<classpathentry kind="lib" path="lib/batik/batik-svggen-1.8.jar"/>
<classpathentry kind="lib" path="lib/batik/batik-util-1.8.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
......@@ -92,6 +92,7 @@
<copy todir="${build}" description="copy resources to output folder">
<fileset dir="${src}">
<include name="de/jtem/halfedgetools/plugin/image/**"/>
<include name="de/jtem/halfedgetools/plugin/data/visualizer/Raleway-Thin.ttf"/>
<exclude name="**/ImageHook.java"/>
</fileset>
</copy>
......
......@@ -20,8 +20,10 @@ import de.jtem.halfedgetools.adapter.generic.BaryCenter4dAdapter;
import de.jtem.halfedgetools.adapter.generic.BaryCenterAdapter;
import de.jtem.halfedgetools.adapter.generic.EdgeLengthAdapter;
import de.jtem.halfedgetools.adapter.generic.EdgeLengthTexAdapter;
import de.jtem.halfedgetools.adapter.generic.EdgeNormalAdapter;
import de.jtem.halfedgetools.adapter.generic.EdgeVectorAdapter;
import de.jtem.halfedgetools.adapter.generic.FaceAreaAdapter;
import de.jtem.halfedgetools.adapter.generic.IndexLabelAdapter;
import de.jtem.halfedgetools.adapter.generic.NormalAdapter;
import de.jtem.halfedgetools.adapter.generic.Position2dAdapter;
import de.jtem.halfedgetools.adapter.generic.Position3dAdapter;
......@@ -481,6 +483,8 @@ public class AdapterSet extends TreeSet<Adapter<?>> {
aSet.add(new EdgeVectorAdapter());
aSet.add(new EdgeLengthAdapter());
aSet.add(new EdgeLengthTexAdapter());
aSet.add(new IndexLabelAdapter());
aSet.add(new EdgeNormalAdapter());
return aSet;
}
......
package de.jtem.halfedgetools.adapter.generic;
import de.jreality.math.Rn;
import de.jtem.halfedge.Edge;
import de.jtem.halfedge.Face;
import de.jtem.halfedge.Node;
import de.jtem.halfedge.Vertex;
import de.jtem.halfedgetools.adapter.AbstractAdapter;
import de.jtem.halfedgetools.adapter.AdapterSet;
import de.jtem.halfedgetools.adapter.type.EdgeNormal;
import de.jtem.halfedgetools.adapter.type.Normal;
import de.jtem.halfedgetools.adapter.type.VectorField;
import de.jtem.halfedgetools.adapter.type.generic.EdgeVector;
@EdgeNormal
@VectorField
public class EdgeNormalAdapter extends AbstractAdapter<double[]> {
public EdgeNormalAdapter() {
super(double[].class, true, false);
}
public EdgeNormalAdapter(Class<? extends double[]> typeClass, boolean getter, boolean setter) {
super(typeClass, getter, setter);
}
@Override
public double getPriority() {
return -1;
}
@Override
public <
V extends Vertex<V, E, F>,
E extends Edge<V, E, F>,
F extends Face<V, E, F>
> double[] getE(E e, AdapterSet a) {
double[] normal = new double[3];
if (e.getLeftFace() != null) {
normal = a.getD(Normal.class, e.getLeftFace());
return normal;
}
else if (e.getPreviousEdge()!= null && e.getNextEdge() != null) {
double[] edgeVector = a.getD(EdgeVector.class, e);
double[] prevEdgeVector = a.getD(EdgeVector.class, e.getPreviousEdge());
double[] nextEdgeVector = a.getD(EdgeVector.class, e.getNextEdge());
if (e.getPreviousEdge().equals(e.getNextEdge())){
//TODO
}
double[] n1 = Rn.crossProduct(null, edgeVector, Rn.negate(prevEdgeVector,prevEdgeVector));
Rn.add(normal, normal, n1);
if (e.getNextEdge() == null) {
return Rn.normalize(normal, normal);
}
double[] n2 = Rn.crossProduct(null, nextEdgeVector, Rn.negate(edgeVector,edgeVector));
Rn.add(normal, normal, n2);
return Rn.times(normal, 1.0 / 2.0, normal);
}
// else if (e.getRightFace() != null) {
// normal = a.getD(Normal.class, e.getRightFace());
// return normal;
// }
return normal;
}
@Override
public <N extends Node<?, ?, ?>> boolean canAccept(Class<N> nodeClass) {
return true;
}
@Override
public String toString() {
return "EdgeNormals";
}
}
......@@ -9,13 +9,13 @@ import de.jtem.halfedgetools.adapter.AdapterSet;
import de.jtem.halfedgetools.adapter.type.Label;
@Label
public class IndexLabelAdapter extends AbstractAdapter<String> {
public class IndexLabelAdapter extends AbstractAdapter<Integer> {
public IndexLabelAdapter() {
super(String.class, true, false);
super(Integer.class, true, false);
}
public IndexLabelAdapter(Class<? extends String> typeClass, boolean getter, boolean setter) {
public IndexLabelAdapter(Class<? extends Integer> typeClass, boolean getter, boolean setter) {
super(typeClass, getter, setter);
}
......@@ -35,8 +35,8 @@ public class IndexLabelAdapter extends AbstractAdapter<String> {
E extends Edge<V, E, F>,
F extends Face<V, E, F>,
N extends Node<V, E, F>
> String get(N n, AdapterSet a) {
return "" + n.getIndex();
> Integer get(N n, AdapterSet a) {
return n.getIndex();
}
}
package de.jtem.halfedgetools.adapter.type;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EdgeNormal {
}
package de.jtem.halfedgetools.plugin.data.visualizer;
import de.jreality.math.Matrix;
import de.jreality.util.Rectangle3D;
import de.jtem.halfedge.Edge;
import de.jtem.halfedge.Face;
import de.jtem.halfedge.Node;
import de.jtem.halfedge.Vertex;
import de.jtem.halfedgetools.adapter.AdapterSet;
public abstract class AbstractLabelPosition implements LabelPosition {
protected double
marginFactor = 1.0,
offset = 0.0;
/**
* Gives the converted absolute margin factor.
*/
public double getMarginFactor(){
return marginFactor*getDefaultMargin();
}
public void setMarginFactor(double marginFactor){
this.marginFactor = marginFactor;//getDefaultMargin();
}
/**
* Gives the standard default margin fitting best for this LabelPosition class.
*/
public abstract double getDefaultMargin();
public double getOffset(){
return offset;
}
public void setOffset(double offset){
this.offset = offset;
}
@Override
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Node<?, ?, ?> n, AdapterSet as, boolean horizontalFlip, boolean verticalFlip) {
if(Vertex.class.isAssignableFrom(n.getClass())) {
return getTransformationMatrix(labelBoundingBox, (Vertex<?,?,?>)n, as, horizontalFlip, verticalFlip);
} else if(Edge.class.isAssignableFrom(n.getClass())) {
return getTransformationMatrix(labelBoundingBox, (Edge<?,?,?>)n, as, horizontalFlip, verticalFlip);
} else if(Face.class.isAssignableFrom(n.getClass())) {
return getTransformationMatrix(labelBoundingBox, (Face<?,?,?>)n, as, horizontalFlip, verticalFlip);
}
return null;
}
@Override
public abstract Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Vertex<?, ?, ?> v, AdapterSet as, boolean horizontalFlip, boolean verticalFlip);
@Override
public abstract Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Edge<?, ?, ?> e, AdapterSet as, boolean horizontalFlip, boolean verticalFlip);
@Override
public abstract Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Face<?, ?, ?> f, AdapterSet as, boolean horizontalFlip, boolean verticalFlip);
}
/**
*
*/
package de.jtem.halfedgetools.plugin.data.visualizer;
import de.jreality.math.Matrix;
import de.jreality.math.MatrixBuilder;
import de.jreality.math.Rn;
import de.jreality.util.Rectangle3D;
import de.jtem.halfedge.Edge;
import de.jtem.halfedge.Face;
import de.jtem.halfedgetools.adapter.AdapterSet;
import de.jtem.halfedgetools.adapter.type.EdgeNormal;
import de.jtem.halfedgetools.adapter.type.Normal;
import de.jtem.halfedgetools.adapter.type.generic.BaryCenter3d;
import de.jtem.halfedgetools.adapter.type.generic.EdgeVector;
/**
* @author hinzmann
*
*/
public class AlongEdgeLabelPosition extends ZUpLabelPosition {
@Override
public String toString(){
return "along Edge";
}
@Override
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Edge<?, ?, ?> e, AdapterSet as, boolean horizontalFlip, boolean verticalFlip) {
double[] labelbbCenter = labelBoundingBox.getCenter();
double[] edgeVector = as.getD(EdgeVector.class, e); //get edge as vector
// Face<?,?,?> face = e.getLeftFace();
double marginFactor = getMarginFactor();
// if(face == null) {
// face = e.getRightFace();
// }
// double[] faceNormal = (face == null)?new double[]{0,0,1}:as.getD(Normal.class, face);
// double[] faceNormal = (face == null)?as.getD(EdgeNormal.class, e):as.getD(Normal.class, face);
double[] faceNormal = as.getD(EdgeNormal.class, e);
double[][] edgeCoordinateSystem = new double[3][3];
edgeCoordinateSystem[2] = faceNormal; //the faces normal (will become labels z-direction by making a matrix out of these vectors)
edgeCoordinateSystem[1] = Rn.crossProduct(null, faceNormal, edgeVector); //vector orthogonal both to the edge and the faces normal, i.e. the vector orthogonal to the edge and lying in the face (will get labels y-direction)
Rn.normalize(edgeCoordinateSystem[1], edgeCoordinateSystem[1]); //normalize it
edgeCoordinateSystem[0] = edgeVector; //the edge (will become labels x-direction)
Rn.normalize(edgeCoordinateSystem[0], edgeCoordinateSystem[0]); //normalize it
double[] edgeCenter = as.getD(BaryCenter3d.class, e);
// double[] faceCenter = as.getD(BaryCenter3d.class, e.getLeftFace());
// Rn.linearCombination(edgeCenter, 0.95, edgeCenter, 0.05, faceCenter);
Rn.add(edgeCenter, edgeCenter, Rn.times(null, marginFactor, edgeCoordinateSystem[1])); //add a bit margin by "translating" edgeCenter
//for offsetting the label, move the edgeCenter with factor offset in direction of the facenormal
Rn.add(edgeCenter, edgeCenter, Rn.times(null, offset, faceNormal));
double[] anchorPoint = new double[3];
anchorPoint[0] = labelBoundingBox.getCenter()[0]; //take the x-coord of the center as x-coordinate for the anchorpoint
anchorPoint[1] = labelBoundingBox.getMinY(); //and choose minY as y-coordinate
anchorPoint[2] = labelBoundingBox.getMinZ(); //and we always take the min in z-direction, such that a 3D-label-boundingbox would stick out to the geometry's outside
Matrix M = getTransformationMatrix(edgeCoordinateSystem, edgeCenter); //puts together the coord-vectors to a matrix which will be the transformation of x-axis -> edgeCoordinateSystem[0], y-axis -> edgeCoordinateSystem[1], z-axis -> edgeCoordinateSystem[2] and the translation by edgeCenter
M.multiplyOnRight(MatrixBuilder.euclidean().translate(Rn.negate(null, anchorPoint)).getMatrix());
if(horizontalFlip || verticalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().translate(labelbbCenter).getMatrix());
if(horizontalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().rotateY(Math.PI).getMatrix());
}
if(verticalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().rotateX(Math.PI).getMatrix());
}
M.multiplyOnRight(MatrixBuilder.euclidean().translate(Rn.negate(null, labelbbCenter)).getMatrix());
}
return M;
//TEST: the following did not work... but thought it should do the same
// double[] edgeVector = as.getD(EdgeVector.class, e); //get edge as vector
// double[] normedEdgeVector = Rn.normalize(null, edgeVector);
// double[] faceNormal = as.getD(Normal.class, e.getLeftFace());
// double[] orthogToEdge = Rn.crossProduct(null, faceNormal, edgeVector);
// Rn.normalize(orthogToEdge, orthogToEdge);
//
// double[] edgeCenter = as.getD(BaryCenter3d.class, e);
//// double[] faceCenter = as.getD(BaryCenter3d.class, e.getLeftFace());
//// Rn.linearCombination(edgeCenter, 0.95, edgeCenter, 0.05, faceCenter);
// double marginFactor = 0.03;
// Rn.add(edgeCenter, edgeCenter, Rn.times(null, marginFactor, orthogToEdge)); //add a bit margin by "translating" edgeCenter
//
// double[] anchorPoint = new double[3];
// anchorPoint[0] = labelBoundingBox.getCenter()[0]; //take the x-coord of the center as x-coordinate for the anchorpoint
// anchorPoint[1] = labelBoundingBox.getMinY(); //and choose minY as y-coordinate
// anchorPoint[2] = labelBoundingBox.getMinZ(); //and we always take the min in z-direction, such that a 3D-label-boundingbox would stick out to the geometry's outside
//
// Matrix M = MatrixBuilder.euclidean() //generate the trafo-matrix, done/to read from bottom to top
// .translate(edgeCenter) //translate the label to the vertex position
// .rotateFromTo(new double[]{1,0,0}, normedEdgeVector)
// .rotateFromTo(new double[]{0,1,0}, orthogToEdge)
// .rotateFromTo(new double[]{0,0,1}, faceNormal) //new double[]{0, 0,1}, normal) //rotate the updirection to the vertexnormal
// .getMatrix();
// if(labelFlip){
// M.multiplyOnRight(MatrixBuilder.euclidean().rotateY(Math.PI).getMatrix()); //if the label shall be flipped: rotate it 180degree around central axis of its boundingbox (since translated in the origin this is the y-axis)
// }
// M.multiplyOnRight(MatrixBuilder.euclidean().translate(Rn.negate(null, anchorPoint)).getMatrix()); //translate the labels center to the origin
//
// return M;
}
/**
* Gives the standard default margin fitting best for this LabelPosition class.
*/
@Override
public double getDefaultMargin() {
return 0.03;
}
}
package de.jtem.halfedgetools.plugin.data.visualizer;
import de.jreality.math.Matrix;
import de.jreality.util.Rectangle3D;
import de.jtem.halfedge.Edge;
import de.jtem.halfedge.Face;
import de.jtem.halfedge.Node;
import de.jtem.halfedge.Vertex;
import de.jtem.halfedgetools.adapter.AdapterSet;
public interface LabelPosition {
public void setMarginFactor(double marginFactor);
public void setOffset(double offset);
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Node<?,?,?> n, AdapterSet as, boolean horizontalFlip, boolean verticalFlip);
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Vertex<?,?,?> v, AdapterSet as, boolean horizontalFlip, boolean verticalFlip);
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Edge<?, ?, ?> e, AdapterSet as, boolean horizontalFlip, boolean verticalFlip);
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Face<?,?,?> f, AdapterSet as, boolean horizontalFlip, boolean verticalFlip);
}
package de.jtem.halfedgetools.plugin.data.visualizer;
import static de.jreality.shader.CommonAttributes.DIFFUSE_COLOR;
import static de.jreality.shader.CommonAttributes.LINE_SHADER;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import de.jreality.geometry.BoundingBoxUtility;
import de.jreality.geometry.TTFLineSetFactory;
import de.jreality.math.Matrix;
import de.jreality.scene.Appearance;
import de.jreality.scene.IndexedLineSet;
import de.jreality.scene.SceneGraphComponent;
import de.jreality.shader.CommonAttributes;
import de.jreality.ui.ColorChooseJButton;
import de.jreality.ui.ColorChooseJButton.ColorChangedEvent;
import de.jreality.ui.ColorChooseJButton.ColorChangedListener;
import de.jreality.ui.LayoutFactory;
import de.jreality.util.Rectangle3D;
import de.jreality.util.SceneGraphUtility;
import de.jtem.halfedge.Node;
import de.jtem.halfedgetools.adapter.Adapter;
import de.jtem.halfedgetools.adapter.AdapterSet;
import de.jtem.halfedgetools.plugin.HalfedgeInterface;
import de.jtem.halfedgetools.plugin.HalfedgeLayer;
import de.jtem.halfedgetools.plugin.data.AbstractDataVisualization;
import de.jtem.halfedgetools.plugin.data.DataVisualization;
import de.jtem.halfedgetools.plugin.data.DataVisualizer;
import de.jtem.halfedgetools.plugin.data.DataVisualizerPlugin;
import de.jtem.halfedgetools.plugin.image.ImageHook;
import de.jtem.jrworkspace.plugin.Controller;
import de.jtem.jrworkspace.plugin.PluginInfo;
public class LabelVisualizer extends DataVisualizerPlugin {
/**
* @author hinzmann
* A class intended for the generation of individual text-labels on vertices, edges and faces of a Halfedge-Geometry for Halfedge Data such as Index, Edgelength, Gauss Curvature etc.
*/
public class LabelVisualizer extends DataVisualizerPlugin implements ActionListener, ChangeListener, ColorChangedListener {
private class LabelVisualization extends AbstractDataVisualization {
public LabelVisualization(
HalfedgeLayer layer,
Adapter<?> source,
DataVisualizer visualizer,
NodeType type
) {
super(layer, source, visualizer, type);
}
private HalfedgeInterface
hif = null;
private JPanel
optionsPanel = new JPanel(); // the GUI-Panel for individual adjustments of the Labels
private List<LabelPosition>
labelPositions = new LinkedList<>();
private JComboBox<LabelPosition>
labelPositionCombo; //= new JComboBox<>(labelPositions.toArray(new LabelPosition[0]));
private SpinnerNumberModel
decimalsModel = new SpinnerNumberModel(3.0, 0.0, 100.0, 1.0),
fontSizeModel = new SpinnerNumberModel(0.05, 0.0, 100, 0.01),
marginFactorModel = new SpinnerNumberModel(1, -10, 10, 0.1), ////factor moving the Label away from its corresponding edge, here 1 as default value in order to have one GUI-element for different LabelPosition classes, but in computation of the matrixTrafo this will be converted to an absolute value using standard-margin (getDefaultMargin()) of the current LabelPosition-class
offsetModel = new SpinnerNumberModel(0.0, -5, 5, 0.01);
private JSpinner
decimalsSpinner = new JSpinner(decimalsModel),
sizeSpinner = new JSpinner(fontSizeModel),
marginFactorSpinner = new JSpinner(marginFactorModel),
offsetSpinner = new JSpinner(offsetModel);
private JTextField
prefixTextField = new JTextField(); //a textfield allowing to set a prefix to all labels of a visualization
private JCheckBox
horizontalFlipChecker = new JCheckBox("Flip horizontally"), //gives the possibility to flip the labels (if normals are in the "wrong" direction for it), do this by rotating the label-lineset 180degree around its bb-center-axis (z axis resp. earlier y-axis)
verticalFlipChecker = new JCheckBox("Flip vertically");
private ColorChooseJButton
labelColorButton = new ColorChooseJButton(true); //cool color button, true = showing colors to choose in new window
@Override
public void update() {
}
private LabelVisualization
activeVisualization = null; // a field holding the current Visualization, needed for interplay with the UI
private boolean listenersDisabled; //boolean to let the update of the active visualization wait until data exchange with the UI is finished
public LabelVisualizer() {
ZUpLabelPosition zUpLabelPosition = new ZUpLabelPosition();
labelPositions.add(zUpLabelPosition);
AlongEdgeLabelPosition alongEdgeLabelPosition = new AlongEdgeLabelPosition();
labelPositions.add(alongEdgeLabelPosition);
labelPositionCombo = new JComboBox<>(labelPositions.toArray(new LabelPosition[0]));
// labelPositionCombo.addItem(zUpLabelPosition);
optionsPanel.setLayout(new GridBagLayout());
GridBagConstraints lc = LayoutFactory.createLeftConstraint();
GridBagConstraints rc = LayoutFactory.createRightConstraint();
optionsPanel.add(new JLabel("Alignment"), lc);
optionsPanel.add(labelPositionCombo, rc);
labelPositionCombo.addActionListener(this);
optionsPanel.add(new JLabel("Decimals"), lc);
optionsPanel.add(decimalsSpinner, rc);
decimalsSpinner.addChangeListener(this);
optionsPanel.add(new JLabel("Prefix"), lc);
optionsPanel.add(prefixTextField, rc);
prefixTextField.addActionListener(this);
optionsPanel.add(horizontalFlipChecker, lc);
horizontalFlipChecker.addActionListener(this);
optionsPanel.add(verticalFlipChecker, rc);
verticalFlipChecker.addActionListener(this);
optionsPanel.add(new JLabel("Font size"), lc);
// sizeSpinner.setPreferredSize(new Dimension(100, 32));
optionsPanel.add(sizeSpinner, rc);
sizeSpinner.addChangeListener(this);
optionsPanel.add(new JLabel("Margin"), lc);
optionsPanel.add(marginFactorSpinner, rc);
marginFactorSpinner.addChangeListener(this);
optionsPanel.add(new JLabel("Offset"), lc);
optionsPanel.add(offsetSpinner, rc);
offsetSpinner.addChangeListener(this);
// lc.gridwidth = 2;
labelColorButton.setColor(Color.BLACK); //set the initial color to black
optionsPanel.add(new JLabel("Color"), lc);
optionsPanel.add(labelColorButton, rc);
labelColorButton.addColorChangedListener(this);
}
@Override
public boolean canRead(Adapter<?> a, NodeType type) {
return true;
return true; //right now all adapters can be chosen as a source for what the labels show
}
@Override
......@@ -46,12 +161,452 @@ public class LabelVisualizer extends DataVisualizerPlugin {
}
@Override
/**
* Generates a String from given Data with chosen options. If the data is a Double it is rounded to have the chosen decimals.
* @param layer the corresponding layer
* @param type the NodeType the labels shall be created for
* @param source giving the data which shall be represented in a label
* @return the corresponding labels as a visualization
*/
public DataVisualization createVisualization(HalfedgeLayer layer, NodeType type, Adapter<?> source) {
return new LabelVisualization(layer, source, this, type);
LabelVisualization vis = new LabelVisualization(layer,source, this, type);
// copy last values
// vis.decimals = decimalsModel.getNumber().intValue();
// vis.prefix = prefixTextField.getText();
vis.setFontSize(fontSizeModel.getNumber().doubleValue()); //stay with the fontsize previously chosen
vis.setMarginFactor(marginFactorModel.getNumber().doubleValue());
vis.horizontalFlip = horizontalFlipChecker.isSelected(); //if labels were flipped in the previous visualization than go on with doing so
// vis.setColor(getLabelColor());
// vis.update();
return vis;
}
@Override
public void actionPerformed(ActionEvent e) {
updateVisualization();
}
@Override
public void disposeVisualization(DataVisualization vis) {
public void stateChanged(ChangeEvent e) {
updateVisualization();
}
@Override
public void colorChanged(ColorChangedEvent cce) {
setLabelColor(cce.getColor());
}
public Color getLabelColor() {
return labelColorButton.getColor();
}
public void setLabelColor(Color c) {
labelColorButton.setColor(c);
updateLabelColor();
}
private void updateLabelColor() {
//when we are just connecting with the UI we don't want to update but first hand all values of the current visualization to the UI with connectUserInterfaceFor
if(listenersDisabled) {
return;
}
activeVisualization.setColor(getLabelColor());
}
/**
* Hands over the current values chosen in the UI to the active visualization and updates it accordingly.
*/
private void updateVisualization() {
//when we are just connecting with the UI we don't want to update but first hand all values of the current visualization to the UI with connectUserInterfaceFor
if(listenersDisabled) {
return;
}
activeVisualization.setLabelPosition((LabelPosition) labelPositionCombo.getSelectedItem());
activeVisualization.setDecimals(decimalsModel.getNumber().intValue());
activeVisualization.setPrefix(prefixTextField.getText());
activeVisualization.setFlippedHorizontally(horizontalFlipChecker.isSelected());
activeVisualization.setFlippedVertically(verticalFlipChecker.isSelected());
activeVisualization.setFontSize(fontSizeModel.getNumber().doubleValue());
activeVisualization.setMarginFactor(marginFactorModel.getNumber().doubleValue());
activeVisualization.setOffset(offsetModel.getNumber().doubleValue());
activeVisualization.setColor(getLabelColor());
activeVisualization.update();
}
@Override
/**
* Connects the active visualization with the UI by handing over all values to the UI.
*/
public JPanel connectUserInterfaceFor(DataVisualization visualization) {
listenersDisabled = true; //in order to let the update wait until all values are handed to the UI
activeVisualization = (LabelVisualization) visualization;
labelPositionCombo.setSelectedItem(activeVisualization.getLabelPosition());
decimalsModel.setValue(activeVisualization.getDecimals());
prefixTextField.setText(activeVisualization.getPrefix());
horizontalFlipChecker.setSelected(activeVisualization.isFlippedHorizontally());
verticalFlipChecker.setSelected(activeVisualization.isFlippedVertically());
fontSizeModel.setValue(activeVisualization.getFontSize());
marginFactorModel.setValue(activeVisualization.getMarginFactor());
offsetModel.setValue(activeVisualization.getOffset());
labelColorButton.setColor(activeVisualization.getColor());
listenersDisabled = false;
return optionsPanel;
}
@Override
public void install(Controller c) throws Exception {
super.install(c);
hif = c.getPlugin(HalfedgeInterface.class);
}
/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
private class LabelVisualization extends AbstractDataVisualization {
/*---------------------------------------------------------------------------------------------------------------------------------*/
private class LabelFactory {
private NodeType
type = NodeType.Vertex;
public LabelFactory(NodeType type) {
this.type = type;
}
/**
* Generates a Label for a node with given data.
* @param the node the label is created for
* @param data the data which shall be represented in a label
* @return the label which represents the given data
*/
public Label createNodeLabel(Node<?,?,?> node, Object data) {
switch (type) {
case Vertex:
return new VertexLabel(node, data);
case Edge:
return new EdgeLabel(node, data);
case Face:
return new FaceLabel(node, data);
default:
return null;
}
}
/**
* Gives a List of all Nodes of the respective type.
*/
@SuppressWarnings("unchecked")
public List<Node<?,?,?>> getNodes() {
switch (type) {
case Vertex:
return (List<Node<?, ?, ?>>) getLayer().get().getVertices();
case Edge:
return (List<Node<?, ?, ?>>) getLayer().get().getEdges();
case Face:
return (List<Node<?, ?, ?>>) getLayer().get().getFaces();
default:
return null;
}
}
}
/*---------------------------------------------------------------------------------------------------------------------------------*/
private abstract class Label {
private SceneGraphComponent
labelComponent = null;
public Label(Node<?,?,?> node, Object data) {
String label = generateDataString(data);
labelComponent = new SceneGraphComponent(getNodePrefix() + String.format("%0"+getNumDigits()+"d",node.getIndex())); //name the sgc with its prefix, e.g. V, and format s.t. leading zeros are added
IndexedLineSet labelLineSet = ttfLineSetFactory.getIndexLineSet(prefix + label); //with the factory: generate a lineset out of the string
labelComponent.setGeometry(labelLineSet); // and set this lineset as geometry of the SGC
}
/**
* Generates a String from given Data with chosen options. If the data is a Double it is rounded to have the chosen decimals.
* @param data the data which shall be represented in a label
* @return label a String which represents the given data with format as chosen in options
*/
private String generateDataString(Object data) {
NumberFormat n = NumberFormat.getInstance();
n.setMaximumFractionDigits(decimals);
String label = new String();
if(data.getClass() == Double.class){
label = n.format(data); //cuts off the Double to have #decimals after comma and makes it a string
}else{
label = data.toString();
}
return label;
}
/**
* Calculates the boundingbox of the label.
* @return the boundingbox of the label
*/
public Rectangle3D getBoundingBox() {
return BoundingBoxUtility.calculateBoundingBox(labelComponent);
}
/**
* Gives the corresponding SceneGraphComponent of the label.
* @return the SceneGraphComponent which holds the Label as its geometry.
*/
public SceneGraphComponent getLabelComponent() {
return labelComponent;
}
/**
* Gives the nodeprefix used to name the SceneGraphComponent
* @return nodeprefix for naming the sgc
*/
public abstract String getNodePrefix();
/**
* Gives how many digits the number of nodes of a specific type of the geometry has (needed for formatting & ordering their indices consecutively e.g. 01 02 ... 10 instead of 1 10 .. 2)
* @return number of digits the respective number of nodes has
*/
public abstract int getNumDigits();
}
private class VertexLabel extends Label {
public VertexLabel(Node<?, ?, ?> node, Object data) {
super(node, data);
}
@Override
public int getNumDigits() {
return (int) Math.ceil(Math.log10(getLayer().get().numVertices()));
}
@Override
public String getNodePrefix() {
return "V";
}
}
private class EdgeLabel extends Label {
public EdgeLabel(Node<?, ?, ?> node, Object data) {
super(node, data);
}
@Override
public int getNumDigits() {
return (int) Math.ceil(Math.log10(getLayer().get().numEdges()));
}
@Override
public String getNodePrefix() {
return "E";
}
}
private class FaceLabel extends Label {
public FaceLabel(Node<?, ?, ?> node, Object data) {
super(node, data);
}
@Override
public int getNumDigits() {
return (int) Math.ceil(Math.log10(getLayer().get().numFaces()));
}
@Override
public String getNodePrefix() {
return "F";
}
}
/*---------------------------------------------------------------------------------------------------------------------------------*/
private SceneGraphComponent
visualizationRoot = new SceneGraphComponent("Label visualization root"); //the root-component all labels will be attached to as SceneGraphComponents
private LabelPosition
labelPosition = new ZUpLabelPosition(); //determines the alignment of the labels by giving a transformation matrix which transforms the labels accordingly
private TTFLineSetFactory
ttfLineSetFactory = new TTFLineSetFactory(); //a factory which generates text as lineset from a corresponding string
private int
decimals = 3; //the number of decimals that will be shown in the labels
public String
prefix = new String(); //a possible prefix for all labels
private boolean
horizontalFlip, //labels are flipped horizontally if true (to make them readable both from in- and outside of the corresponding geometry)
verticalFlip; //labels are flipped vertically
private Appearance
labelAppearance = null;
private double
fontSize = 0.05, //font size of the label resp. the lineset
marginFactor = 1.0, //factor moving the Label away from its corresponding edge, here 1 as default value but in computation of the matrixTrafo this will be converted to an absolute value using standard-margin (getDefaultMargin()) of the current LabelPosition-class
offset = 0.0;
private Color
color = Color.BLACK;
public LabelVisualization(
HalfedgeLayer layer,
Adapter<?> source,
DataVisualizer visualizer,
NodeType type
) {
super(layer, source, visualizer, type);
visualizationRoot.setName("Label for " + source.toString());
//set initial type and size of labels font
ttfLineSetFactory.setTTFFont(LabelVisualizer.class.getResource("Raleway-Thin.ttf").getPath());
ttfLineSetFactory.setSingleLineFont(false);
ttfLineSetFactory.setSize(fontSize);
labelAppearance = ttfLineSetFactory.getStandardAppearance();
labelAppearance.setAttribute(CommonAttributes.EDGE_DRAW, true);
}
/* (non-Javadoc)
* @see de.jtem.halfedgetools.plugin.data.DataVisualization#update()
*/
@Override
public void update() {
AdapterSet a = hif.getAdapters();
dispose(); // remove the current visualization root and all labels as its children
visualizationRoot.removeAllChildren();
//create and attach a label as follows:
LabelFactory labelFactory = new LabelFactory(getType()); //generates a LabelFactory for the type of node which is provided with data from the adapter-source
for(Node<?,?,?> node : labelFactory.getNodes()) { //go through all these nodes which shall be labelled with the data
Object data = getSource().get(node, a); //getSource() gives the Data-Adapter for which the visualization by labels was chosen, .get(v, a) gets its value for the vertex v - this is what the label shall show
Label label = labelFactory.createNodeLabel(node, data); //creates the label for the node with its data
Matrix M = labelPosition.getTransformationMatrix(label.getBoundingBox(), node, a, horizontalFlip, verticalFlip); //now work out where to place it by getting the needed transformations from the trafoMatrix of labelPosition
SceneGraphComponent labelComponent = label.getLabelComponent();
M.assignTo(labelComponent); //and assign this trafo to the SGC
SceneGraphUtility.addChildNode(visualizationRoot, labelComponent);
}
//set the appearance for the label-lineset
updateAppearanceAttributes();
// add the labels-visualiztion to the layer
getLayer().addTemporaryGeometry(visualizationRoot);
}
/**
* Update the appearance of the visualization.
*/
private void updateAppearanceAttributes() {
labelAppearance.setAttribute(LINE_SHADER + "." + DIFFUSE_COLOR, this.getColor());
visualizationRoot.setAppearance(labelAppearance);
}
@Override
public void dispose() {
getLayer().removeTemporaryGeometry(visualizationRoot);
}
@Override
public void setActive(boolean active) {
visualizationRoot.setVisible(active);
}
@Override
public boolean isActive() {
return visualizationRoot.isVisible();
}
public LabelPosition getLabelPosition() {
return labelPosition;
}
public void setLabelPosition(LabelPosition labelPosition) {
this.labelPosition = labelPosition;
}
public int getDecimals() {
return decimals;
}
public void setDecimals(int decimals) {
this.decimals = decimals;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public boolean isFlippedHorizontally() {
return horizontalFlip;
}
public void setFlippedHorizontally(boolean horizontalFlip) {
this.horizontalFlip = horizontalFlip;
}
public boolean isFlippedVertically() {
return verticalFlip;
}
public void setFlippedVertically(boolean verticalFlip) {
this.verticalFlip = verticalFlip;
}
public double getFontSize() {
return fontSize;
}
public void setFontSize(double fontSize) {
this.fontSize = fontSize;
ttfLineSetFactory.setSize(fontSize);
}
public double getMarginFactor() {
return marginFactor;
}
public void setMarginFactor(double marginFactor) {
this.marginFactor = marginFactor;
labelPosition.setMarginFactor(marginFactor);
}
public double getOffset() {
return offset;
}
public void setOffset(double offset) {
this.offset = offset;
labelPosition.setOffset(offset);
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
updateAppearanceAttributes();
}
}
}
......@@ -29,7 +29,8 @@ import java.util.List;
import static de.jreality.shader.CommonAttributes.*;
public class VectorFieldVisualizer extends DataVisualizerPlugin implements
public class
VectorFieldVisualizer extends DataVisualizerPlugin implements
ActionListener, ChangeListener, SelectionListener {
private SpinnerNumberModel
......
package de.jtem.halfedgetools.plugin.data.visualizer;
import de.jreality.math.Matrix;
import de.jreality.math.MatrixBuilder;
import de.jreality.math.Rn;
import de.jreality.util.Rectangle3D;
import de.jtem.halfedge.Edge;
import de.jtem.halfedge.Face;
import de.jtem.halfedge.Node;
import de.jtem.halfedge.Vertex;
import de.jtem.halfedgetools.adapter.AdapterSet;
import de.jtem.halfedgetools.adapter.type.EdgeNormal;
import de.jtem.halfedgetools.adapter.type.Normal;
import de.jtem.halfedgetools.adapter.type.generic.BaryCenter3d;
import de.jtem.halfedgetools.adapter.type.generic.EdgeVector;
import de.jtem.halfedgetools.adapter.type.generic.Position3d;
public class ZUpLabelPosition extends AbstractLabelPosition {
private double[]
upDirection = new double[]{0,0,1};
public String toString(){
return "z-axis up";
}
@Override
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Vertex<?,?,?> v, AdapterSet as, boolean horizontalFlip, boolean verticalFlip) {
double[] labelbbCenter = labelBoundingBox.getCenter();
double[] center = as.getD(Position3d.class, v);
double[] normal = as.getD(Normal.class, v);
//for offsetting the label, move the center with factor offset in direction of the normal
Rn.add(center, center, Rn.times(null, offset, normal));
Matrix M = MatrixBuilder.euclidean() //generate the trafo-matrix, done/to read from bottom to top
.translate(center) //translate the label to the vertex position
.rotateFromTo(upDirection, normal) //new double[]{0, 0,1}, normal) //rotate the updirection to the vertexnormal
.getMatrix();
if(horizontalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().rotateY(Math.PI).getMatrix()); //if the label shall be flipped horizontally: rotate it 180degree around central axis of its boundingbox (since translated in the origin this is the y-axis)
}
if(verticalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().rotateX(Math.PI).getMatrix()); //if the label shall be flipped vertically: rotate it 180degree around the x-axis (since translated in the origin)
}
M.multiplyOnRight(MatrixBuilder.euclidean().translate(Rn.negate(null, labelbbCenter)).getMatrix()); //translate the labels center to the origin
return M;
}
@Override
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Edge<?, ?, ?> e, AdapterSet as, boolean horizontalFlip, boolean verticalFlip) {
double[] labelbbCenter = labelBoundingBox.getCenter();
double[] edgeVector = as.getD(EdgeVector.class, e); //get edge as vector
double[] edgeCenter = as.getD(BaryCenter3d.class, e);
Face<?,?,?> face = e.getLeftFace();
double marginFactor = getMarginFactor(); //get the absolute marginFactor for this LabelPosition which will be used to translate the label away from the edge to make it fit better
//if the corresponding left face is null (e.g. if the corresp. edge is a boundary edge) then take the right face to place the label
if(face == null) {
face = e.getRightFace();
marginFactor *= -1.0; //in this case negate the factor in order to still place the label on the correct side of the edge
}
double[] faceCenter = null;
double[][] localCoordinateSystem = null;
double[] marginVector = null;
if(face == null) { //if right face is also null, return
localCoordinateSystem = getLocalCoordinateSystem(e,as); //TODO find a solution for the case that the edge has no incident faces!!
marginVector = localCoordinateSystem[2]; //TODO
}else{
localCoordinateSystem = getLocalCoordinateSystem(face,as);
faceCenter = as.getD(BaryCenter3d.class, face);
marginVector = Rn.subtract(null, faceCenter, edgeCenter); //vector from edgeCenter to faceCenter
}
double[] anchorPoint = getAnchorPoint(edgeVector, localCoordinateSystem, labelBoundingBox); //find out which point of the label shall be used as anchorpoint for placing it
// marginVector = Rn.subtract(null, faceCenter, edgeCenter); //vector from edgeCenter to faceCenter
Rn.normalize(marginVector,marginVector);
//for moving the label a bit apart from its edge, move the edgeCenter with factor marginFactor towards faceCenter
Rn.add(edgeCenter, edgeCenter, Rn.times(null, marginFactor, marginVector)); //add a bit margin by "translating" edgeCenter
// Rn.linearCombination(edgeCenter, 0.95, edgeCenter, marginFactor, faceCenter); //add a bit margin by shifting edgeCenter to faceCenter by marginFactor
//for offsetting the label, move the edgeCenter with factor offset in direction of the facenormal
Rn.add(edgeCenter, edgeCenter, Rn.times(null, offset, localCoordinateSystem[2]));
Matrix M = getTransformationMatrix(localCoordinateSystem, edgeCenter);
M.multiplyOnRight(MatrixBuilder.euclidean().translate(Rn.negate(null, anchorPoint)).getMatrix());
if(horizontalFlip || verticalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().translate(labelbbCenter).getMatrix());
if(horizontalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().rotateY(Math.PI).getMatrix());
}
if(verticalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().rotateX(Math.PI).getMatrix());
}
M.multiplyOnRight(MatrixBuilder.euclidean().translate(Rn.negate(null, labelbbCenter)).getMatrix());
}
return M;
}
@Override
public Matrix getTransformationMatrix(Rectangle3D labelBoundingBox, Face<?, ?, ?> f, AdapterSet as, boolean horizontalFlip, boolean verticalFlip) {
double[] labelbbCenter = labelBoundingBox.getCenter();
double[] center = as.getD(BaryCenter3d.class, f);
double[][] localCoordinateSystem = getLocalCoordinateSystem(f,as);
//for offsetting the label, move the center with factor offset in direction of the facenormal
Rn.add(center, center, Rn.times(null, offset, localCoordinateSystem[2]));
Matrix M = getTransformationMatrix(localCoordinateSystem, center);
if(horizontalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().rotateY(Math.PI).getMatrix()); //if the label shall be flipped horizontally: rotate it 180degree around central axis of its boundingbox (since translated in the origin this is the y-axis)
}
if(verticalFlip){
M.multiplyOnRight(MatrixBuilder.euclidean().rotateX(Math.PI).getMatrix()); //if the label shall be flipped vertically: rotate it 180degree around the x-axis (since translated in the origin)
}
M.multiplyOnRight(MatrixBuilder.euclidean().translate(Rn.negate(null, labelbbCenter)).getMatrix());
return M;
}
/**
* Calculates a 3-dim coordinate system as double[][] on the basis of a given face.
* @param node the face for which the coordinate system is calculated
* @param as the adapterset
* @return facesCoordinateSystem where
* facesCoordinateSystem[2] is the normal of the face
* facesCoordinateSystem[1] is the normalized projection of the upDirection onto the normals complement
* facesCoordinateSystem[0] is their crossproduct
*
* If the normal is almost the same as updirection and therefore facesCoordinateSystem[1] almost zero, then {0, 1,0} is projected as alternate upDirection.
*/
protected double[][] getLocalCoordinateSystem(Node<?,?,?> node, AdapterSet as) {
// double[] upDirection = new double[]{0,0,1};
double[][] facesCoordinateSystem = new double[3][3];
facesCoordinateSystem[2] = as.getD(Normal.class, node); //the faces normal
if(facesCoordinateSystem[2] == null) {
facesCoordinateSystem[2] = as.getD(EdgeNormal.class, node);
}
facesCoordinateSystem[1] = Rn.projectOntoComplement(null, upDirection, facesCoordinateSystem[2]); // projection of the upDirection onto the normals complement
if(Rn.euclideanNorm(facesCoordinateSystem[1]) < 1E-6){ //if the normal is almost the same as updirection -> facesCoordinateSystem[1] almost zero
facesCoordinateSystem[1] = Rn.projectOntoComplement(null, new double[]{0, 1,0}, facesCoordinateSystem[2]); //then choose {0, 1,0} as alternate upDirection for projecting
}
Rn.normalize(facesCoordinateSystem[1], facesCoordinateSystem[1]);
facesCoordinateSystem[0] = Rn.crossProduct(null, facesCoordinateSystem[1], facesCoordinateSystem[2]);
return facesCoordinateSystem;
}
/**
* Sets up the trafo-matrix we need for placing the labels.
* @param faceCoordinateSystem giving the desired directions for alignment of the label
* @param basePoint the targetpoint the label will be translated to
* @return the transformation matrix
*/
protected Matrix getTransformationMatrix(double[][] faceCoordinateSystem, double[]basePoint) {
Matrix matrix = new Matrix(
faceCoordinateSystem[0][0], faceCoordinateSystem[1][0], faceCoordinateSystem[2][0], basePoint[0],
faceCoordinateSystem[0][1], faceCoordinateSystem[1][1], faceCoordinateSystem[2][1], basePoint[1],
faceCoordinateSystem[0][2], faceCoordinateSystem[1][2], faceCoordinateSystem[2][2], basePoint[2],
0,0,0,1
);
return matrix;
}
/**
* Computes which corner point of the labels boundingbox should be used as anchorpoint of the label on an edge.
* @param edge the edge the label shall be attached to, given as vector
* @param localCoordinateSystem the coordinate system given by the incident face to its left
* @param bb a rectangle representing the boundingbox of the label
* @return anchorPoint the corner point of the labels boundingbox which suits best as anchorpoint for a label
*/
private double[] getAnchorPoint(double[] edge, double[][] localCoordinateSystem, Rectangle3D bb) {
//Calculate the scalar product of the edge with the x- and y-directions of the local coordinate system, this gives how the edge-vector is directed within this 2D-xy-coord.system.
//E.g. if <e,x-axis> > 0 and <e,y-axis> < 0 the edge is located in the upper left corner. Then the anchorpoint should be the upper right point of the labels bb.
//We figured out: Turning the edge clockwise by 90° always gives the direction of the bb-corner one should pick as anchorpoint,
//this turning is a multiplication by ({0, -1}; {1, 0}), we apply this to our coord.system to directly get out the signs for the anchorpoint: ({0, -1}; {1, 0})*{x,y} = {y, -x}
double signX = Rn.innerProduct(edge, localCoordinateSystem[1]); //therefore <e,x-axis> is interpreted as <e,y-axis>
double signY = -Rn.innerProduct(edge, localCoordinateSystem[0]); // and <e,y-axis> as -<e,x-axis>
double[] anchorPoint = new double[3];
anchorPoint[0] = (signX>0)?bb.getMaxX():bb.getMinX(); //if the edge is on the upper half of the xy-coord.system chose MaxX of bb as x-coordinate for the anchorpoint, otherwise MinX
anchorPoint[1] = (signY>0)?bb.getMaxY():bb.getMinY(); //if the edge is on the right half of the xy-coord.system chose MaxY of bb as y-coordinate for the anchorpoint, otherwise MinY
if(Math.abs(signX) < 1E-6) { //if the edge is aligned with the x-axis
anchorPoint[0] = bb.getCenter()[0]; //the anchorpoint should be in the middle of the upper or lower boundingbox-edge
}
if(Math.abs(signY) < 1E-6) { //if the edge is aligned with the y-axis
anchorPoint[1] = bb.getCenter()[1]; //the anchorpoint should be in the middle of the left- or righthand boundingbox-edge
}
anchorPoint[2] = bb.getMinZ(); //and we always take the min in z-direction, such that a 3D-label-boundingbox would stick out to the geometry's outside
return anchorPoint;
}
/**
* Gives the standard default margin fitting best for this LabelPosition class.
*/
@Override
public double getDefaultMargin() {
return 0.05;
}
}