Skip to main content

Problems selecting Shapes

3 replies [Last post]
asantiago
Offline
Joined: 2006-11-09
Points: 0

Hi all,
I have write some time ago about this problem but never had time to make an example.
Here I attach a peace of code that creates an scene with a Shape node.
I have attached a MouseMotionListener so every time the mouse moves it prints the "object" under the mouse position. The idea is to see when the mouse is over or out the shape.

As you would see, the border of the shape seems to have no effect, that is, the mouse is over but any object is printed as selected.

What I need is to draw shapes (polygons or polyline without fill them) and know when I'm over it.

I would appreciate any help.
Thanks in advice.

-------------------------------------------------------------------------------------------

package sgtest;

import com.sun.scenario.scenegraph.JSGPanel;
import com.sun.scenario.scenegraph.SGGroup;
import com.sun.scenario.scenegraph.SGShape;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.GeneralPath;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

/**
* SceneGraph test selecting shapes.
*
* @author Antonio Santiago
*/
public class SceneTest extends JSGPanel
{

public SceneTest()
{
final SGGroup root = new SGGroup();
setScene(root);
root.add(new CustomPolygon());

addMouseMotionListener(new MouseMotionAdapter()
{

@Override
public void mouseMoved(MouseEvent e)
{
System.out.println("Node: " + root.pick(e.getPoint()));
}
});
}

private class CustomPolygon extends SGShape
{

public CustomPolygon()
{
GeneralPath gp = new GeneralPath(GeneralPath.WIND_NON_ZERO);
gp = new GeneralPath();
gp.moveTo(50, 50);
gp.lineTo(400, 50);
gp.lineTo(380, 300);
gp.lineTo(50, 300);

// Uncomment to draw the line in inverse direction and avoid to
// form a polygon.
// gp.lineTo(380, 300);
// gp.lineTo(400, 50);
// gp.moveTo(50, 50);

setShape(gp);
setMode(SGShape.Mode.STROKE_FILL);
setFillPaint(new Color(0.7f, 0.8f, 1f, 0.7f));
setDrawPaint(new Color(0.5f, 0.0f, 0.3f, 0.9f));
setDrawStroke(new BasicStroke(5));
}
}

public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{

public void run()
{
JFrame frame = new JFrame("Scene Graph Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(500, 400);

SceneTest st = new SceneTest();
frame.getRootPane().setLayout(new BorderLayout());
frame.getRootPane().add(st, BorderLayout.CENTER);

frame.setVisible(true);
}
});
}
}

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
william13163
Offline
Joined: 2008-03-19
Points: 0

The approach you are using is rather heavy handed or upside down from what you want to do. It is much easier to have a class do the work directly as in the following:

First you would have some class inherit from SGMouseListener. This is a class that represents some node in your application/tree.

[code]
public abstract class BaseContainer implements SGMouseListener
{
...
public void mouseEntered(MouseEvent e, SGNode node)
{
// do something here, like highlight.
}

public void mouseExited(MouseEvent e, SGNode node)
{
// do something here, like un-highlight.
}
}
[/code]

Then when your code is building nodes you would subscribe your class as a listener to that node. In my example I have my class listening to a translate node which is the parent of the actual visible node in the viewport. Anyway, that is the general idea.

[code]
public SGNode build()
{
_parentNode = SGTransform.Translate.createTranslation(0, 0, _nodeGroup);

// Have this container listen to translate events.
_parentNode.addMouseListener(this);

return _parentNode;
}
[/code]

This is a snippet:
[code]
public abstract class BaseContainer implements SGMouseListener
{
...
public SGNode build()
{
_parentNode = SGTransform.Translate.createTranslation(0, 0, _nodeGroup);

// Have this Map container listen to translate events.
_parentNode.addMouseListener(this);

return _parentNode;
}
public void mouseEntered(MouseEvent e, SGNode node)
{
// do something here, like highlight.
}

public void mouseExited(MouseEvent e, SGNode node)
{
// do something here, like un-highlight.
}
}
[/code]

The general idea is to have classes do the work not an external class probe around using pick. Pick is still useful under other scenarios, but more control can be placed in classes. Note: when your mouseEnter and Exit event callbacks are triggered you will get one or more node "firings", one for each node under the cursor. So, you will need to filter accordingly. One way is to set the ID of your nodes and then filter on that, example:

[code]
public void setName(String name)
{
_parentNode.setID(name);
}
[/code]

You will then need to write code that can handle depth picking too. If you have several nodes on top of each other you will need to react to each mouse event appropriately.

Hope this gets you going. Just remember that you can have classes listening for events too. I kind of think of it as being passive verse active. Passive is where the classes listen and Active is where your code actively probes.

Cheers.

asantiago
Offline
Joined: 2006-11-09
Points: 0

Thanks for your reply William.
I modify the example program to work using SGMouseListener. Really the node CustomPolygon is listening for itself.

As you could see the problem is the "precision", when the mouse is over the border line (the STROKE) the entered method not occurs. It seem it is only recognized when the mouse is in the FILL region.

What I want is to draw a polyline, a polygon without filling, and allow to know when the mouse is over the lines.

public class SceneTest extends JSGPanel
{

public SceneTest()
{
final SGGroup root = new SGGroup();
setScene(root);
root.add(new CustomPolygon());

// addMouseMotionListener(new MouseMotionAdapter()
// {
//
// @Override
// public void mouseMoved(MouseEvent e)
// {
// System.out.println("Node: " + root.pick(e.getPoint()));
// }
// });
}

private class CustomPolygon extends SGShape implements SGMouseListener
{

public CustomPolygon()
{
setMode(SGShape.Mode.STROKE);
setFillPaint(new Color(0.7f, 0.8f, 1f, 0.7f));
setDrawPaint(new Color(0.5f, 0.0f, 0.3f, 0.9f));
setDrawStroke(new BasicStroke(6));

addMouseListener(this);

GeneralPath gp = new GeneralPath(GeneralPath.WIND_NON_ZERO);
gp = new GeneralPath();
gp.moveTo(50, 50);
gp.lineTo(400, 50);
gp.lineTo(380, 300);
gp.lineTo(50, 300);

// Uncomment to draw the line in inverse direction and avoid to
// form a polygon.
// gp.lineTo(380, 300);
// gp.lineTo(400, 50);
// gp.moveTo(50, 50);

setShape(gp);

}

public void mouseClicked(MouseEvent arg0, SGNode arg1)
{
}

public void mousePressed(MouseEvent arg0, SGNode arg1)
{
}

public void mouseReleased(MouseEvent arg0, SGNode arg1)
{
}

public void mouseEntered(MouseEvent arg0, SGNode arg1)
{
System.out.println("Entered");
}

public void mouseExited(MouseEvent arg0, SGNode arg1)
{
System.out.println("Exited");
}

public void mouseDragged(MouseEvent arg0, SGNode arg1)
{
}

public void mouseMoved(MouseEvent arg0, SGNode arg1)
{
}

public void mouseWheelMoved(MouseWheelEvent arg0, SGNode arg1)
{
}
}

public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{

public void run()
{
JFrame frame = new JFrame("Scene Graph Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(500, 400);

SceneTest st = new SceneTest();
frame.getRootPane().setLayout(new BorderLayout());
frame.getRootPane().add(st, BorderLayout.CENTER);

frame.setVisible(true);
}
});
}
}

Message was edited by: asantiago

william13163
Offline
Joined: 2008-03-19
Points: 0

Hi asantiago,

I am doing something similar with polygons too. However, I use a slightly different approach. I maintain a separate list of the segments that make up the lines of the polygon. I never really used the scenegraph in the manor you are using.

I use the standard Line2D to determine where the mouse is relative to the line segment. I my case I use a collection and a custom SGLeaf type node. I don't actually use the SGShape class.

I have a class that represent a segment of the polygon, such that given a cursor location and a list of knots/corners on the polygon, I can tell if the cursor is near or on a segment of the polygon. I can then do something accordingly like highlight or drag it around. For knots you would use the Rectangle.contains(point2d) method against the cursor.

Here is the relevant portion of my Segment class that shows the idea.

[code]
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.LinkedList;

public class Segment
{
private static final double DISTANCE = 3.0;
private Line2D _segment = new Line2D.Double();
private Point2D _midPoint = new Point2D.Double();

private boolean _onSegment = false;

// These are references into the model.
private Knot _kStart = null;
private Knot _kEnd = null;

public boolean determineSegment(Point2D cursor, LinkedList knots)
{
boolean onSegment = false;
if (knots.size() > 1)
{
_kStart = knots.get(0);
for (Knot k1 : knots.subList(1, knots.size()))
{
_kEnd = k1;
_segment.setLine(_kStart.getPoint(), _kEnd.getPoint());
if (_segment.ptSegDist(cursor) < DISTANCE)
{
onSegment = true;
break;
}
_kStart = _kEnd;
}
}
return onSegment;
}

/**
*
* @param activeState
* true = segment is activated.
*/
public void markSegmentActive(boolean activeState)
{
_kStart.makeAsPartOfSegment(activeState);
_kEnd.makeAsPartOfSegment(activeState);
}

public void setSegment(Knot k1, Knot k2)
{
_kStart = k1;
_kEnd = k2;
}

public void reset()
{
_onSegment = false;
}

public boolean isSegmentOn()
{
return _onSegment;
}

public void setSegmentOn(boolean b)
{
_onSegment = b;
}

public Knot getSegStart()
{
return _kStart;
}

public Knot getSegEnd()
{
return _kEnd;
}

public Point2D getMidPoint()
{
_midPoint.setLocation((_kStart.X() + _kEnd.X()) / 2.0,
(_kStart.Y() + _kEnd.Y()) / 2.0);

return _midPoint;
}

}
[/code]
Note: the DISTANCE variable is how "sensitive" you want the detection to be.

The Knot is a simple class representing a knot on the polygon.
[code]
public class Knot() {}
[/code]

Incidentally, the knots collection allows me to modify the polygon dynamically without having to recreate a shape each time.

Basically, the code iterates through the polygon checking the distance, from the mouse, to each segment in the polygon. The rest of the code is specific to what I am doing, and unfortunately I can't show all of it.

So far, I am only dealing with linear segments, it doesn't work with curves...yet.

Your knots collection is a direct reflection of the Shape and the Shape can be built using the knots collection. Although, I am not doing that. As mentioned I have created a custom SGLeaf that renders itself based on the knots collection, so in effect I have created my own Shape object. Shapes are non-mutable, so once created one you have to discard it and create another one if the shape changes; that was something I didn't want so I created an SGLeaf to render the collection of knots.

In closing, I don't use the enter/exit approach against an SGShape in this case. I use the bounding rectangle of my polygon for enter/exit and then scan the polygon once inside using the mouseMove event.

Hope that gives you an idea.