Skip to main content

Picking question !!!

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

Hi,

I have created a SGNode subclass which draws apolygon.
The problem is the getBounds() method must return a rectangle and then when I want to know over which object the mouse is over (pick) the SG returns me I'm over the polygon when I'm out.

Is there a way to know exactly over which object I'm in SG?

Thanks in advice.

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

What I did was to copy code from the [i]SGShape[/i] class and create a class that I could then control. The [i]SGShape[/i] class, of course, was designed for shapes, and I needed to render my own dynamically changing polygon (a grid in my case.) I also needed the scenegraph to properly recognize my node just like it does for SGShapes.

I created a class called [b]SGAbstractVisual[/b] the has an inheritance tree as follows:
[pre]
SGNode
SGLeaf
[b]SGAbstractVisual[/b]
[b]SGGrid[/b]
[/pre]

[b]SGAbstractVisual[/b] provides some of the features of the [i]SGShape[/i]'s bounding rectangle code with some of the overrides from [i]SGLeaf[/i]. [b]SGGrid[/b] is my node that renders a polygon that looks like a grid. It is the [b]SGAbstractVisual[/b] that provides the proper code to return a correct bounding rectangle to the scenegraph during its picking and culling passes.

This is a portion of my [b]SGGrid[/b] class and how it interacts with the scenegraph:

[code]
public class SGFiniteGridLeaf extends SGAbstractVisual
{

/*
* This rectangle is the local-space bounds of the grid.
* When zoomed in or out the leaf uses a view-space bounds based
* on any parent transforms. This rectangle is always fixed in size unless
* it is changed by the resizer icon.
* The resizer icon only adjusts the rectangle. The scenegraph needs a transformed
* rectangle for rendering and picking/selecting which it should get from the getBounds()
* methods.
*/
private Rectangle2D _rectangle = new Rectangle2D.Double();
...

/*
* This renders the grid based on a bounding rectangle that has
* been transformed by all its parent transforms.
*/
public void paint(Graphics2D g)
{
// BEGIN State management
AffineTransform gt = g.getTransform();

Color c = g.getColor();
Stroke s = g.getStroke();

...
Rectangle2D r = getBounds();

// render grid contained by the rectangle. lots-o-code here...

// END State management
g.setColor(c);
g.setStroke(s);
g.setTransform(gt);
}

public Rectangle2D getBounds(AffineTransform transform)
{
Rectangle2D r = getBounds(transform, _rectangle);
return r;
}
... // more methods that do stuff relating to the grid
}
[/code]

The [i]_rectangle[/i] is the bounds for your polygon and what is used as the "basis" for the SGAbstractVisual.getBounds(...) method. The scenegraph will call your node's getBounds(AffineTransform...) method when it needs a rectangle that is properly transformed. Your node, however, may need the [i]_rectangle[/i] or it may need the local-space version by calling getBounds() with no parameters. It just depends on what you need.

The key is that the scenegraph needs a rectangle that can be transformed by an incoming AffineTransform that the scenegraph passes in. For example, if you have a parent Scale node, then the AffineTransform passed in will have some sort of scaling present. That parent Scale node could be controlling your view when zooming in and out, which means that in view-space the rectangle get bigger or smaller, and it is in view-space where picking and culling occurs.

However, your polygon render logic does most of its work in local-space and that is why you would use getBounds() with no transformation supplied.

Here is the [b]SGAbstractVisual[/b] class you can inherit from--it is straight out of the SGShape class minus any Shape specific stuff, plus any code to satisfy SGLeaf):

[code]
public abstract class SGAbstractVisual extends SGLeaf
{
private static final Stroke defaultStroke = new BasicStroke(1f);

Paint drawPaint = Color.BLACK;
Paint fillPaint = Color.WHITE;
Stroke drawStroke = defaultStroke;

public abstract SGAbstractVisual getVisual();

public final Paint getDrawPaint()
{
return drawPaint;
}

public void setDrawPaint(Paint drawPaint)
{
if (drawPaint == null)
{
throw new IllegalArgumentException("null drawPaint");
}
this.drawPaint = drawPaint;
repaint(false);
}

public final Paint getFillPaint()
{
return fillPaint;
}

public void setFillPaint(Paint fillPaint)
{
if (fillPaint == null)
{
throw new IllegalArgumentException("null fillPaint");
}
this.fillPaint = fillPaint;
repaint(false);
}

public final Stroke getDrawStroke()
{
return drawStroke;
}

public void setDrawStroke(Stroke drawStroke)
{
if (drawStroke == null)
{
throw new IllegalArgumentException("null drawStroke");
}
this.drawStroke = drawStroke;
repaint(true);
}

private static final int AT_IDENT = 0;
private static final int AT_TRANS = 1;
private static final int AT_GENERAL = 2;

protected int classify(AffineTransform at)
{
if (at == null)
return AT_IDENT;
switch (at.getType())
{
case AffineTransform.TYPE_IDENTITY:
return AT_IDENT;
case AffineTransform.TYPE_TRANSLATION:
return AT_TRANS;
default:
return AT_GENERAL;
}
}

public Rectangle2D getBounds(AffineTransform at, Rectangle2D rect)
{
Rectangle2D r = getBounds(at, classify(at), 0.0f, 0.0f, rect);
return r;
}

public Rectangle2D getExpandedBounds(AffineTransform at, Rectangle2D rect,
double byX, double byY)
{
Rectangle2D r = getBounds(at, classify(at), 0.0f, 0.0f, rect);
r.setFrame(r.getX() - byX, r.getY() - byY, r.getWidth() + 2 * byX, r
.getHeight()
+ 2 * byY);
return r;
}

private Rectangle2D getBounds(AffineTransform at, int atclass, float upad,
float dpad, RectangularShape r)
{
float x1 = (float) r.getWidth();
float y1 = (float) r.getHeight();
if (x1 < 0 || y1 < 0)
{
return new Rectangle2D.Float(0, 0, -1, -1);
}
float x0 = (float) r.getX();
float y0 = (float) r.getY();
if (atclass <= AT_TRANS)
{
x1 += x0;
y1 += y0;
if (atclass == AT_TRANS)
{
float tx = (float) at.getTranslateX();
float ty = (float) at.getTranslateY();
x0 += tx;
y0 += ty;
x1 += tx;
y1 += ty;
}
// TODO - only pad by upad or dpad, depending on transform
dpad += upad;
} else
{
float m00 = (float) at.getScaleX();
float m01 = (float) at.getShearX();
float m10 = (float) at.getShearY();
float m11 = (float) at.getScaleY();
// Computed translated translation components
float m02 = x0 * m00 + y0 * m01 + (float) at.getTranslateX();
float m12 = x0 * m10 + y0 * m11 + (float) at.getTranslateY();
// Scale non-translation components by w/h
m00 *= x1;
m01 *= y1;
m10 *= x1;
m11 *= y1;
x0 = (float) ((Math.min(Math.min(0, m00), Math.min(m01, m00 + m01))) + m02);
y0 = (float) ((Math.min(Math.min(0, m10), Math.min(m11, m10 + m11))) + m12);
x1 = (float) ((Math.max(Math.max(0, m00), Math.max(m01, m00 + m01))) + m02);
y1 = (float) ((Math.max(Math.max(0, m10), Math.max(m11, m10 + m11))) + m12);
}
x0 -= dpad;
y0 -= dpad;
x1 += dpad;
y1 += dpad;
return new Rectangle2D.Float(x0, y0, x1 - x0, y1 - y0);
}

}
[/code]

The key thing to get from all of this is that the scenegraph needs a properly transformed bounding rectangle (in view-space) in order to correctly determine picking and culling. The [b]SGAbstractVisual[/b] does that. It would have been cool if the [i]SGShape[/i] class was abstracted out another level to allow for custom rendering nodes. ;-) Who knows, the final release may have such a refactor...Or I have completely over-used the scenegraph. :-)

This is how it all fit together in the scenegraph:
[pre]
Root
camera transform.translate node
camera transform.scale node
map transform.translate node
grid
[/pre]

It is the camera's scale node that is important for picking and culling and is what the [b]SGAbstractVisual[/b] class provides to the scenegraph.

mortennobel
Offline
Joined: 2004-11-03
Points: 0

I suggest that you use SGShape (or a subclass of it) if that is possible.

But to answer your question, you need to implement the method contains(Point2D point). The javadoc for this method is:

[i]Returns true if the given point (specified in the local/untransformed coordinate space of this node) is contained within the visual bounds of this node. Note that this method does not take visibility into account, the test is based on the node's geometry only.[/i]

If you want a list of nodes related to a point, you can use the method List pick(Point2D p) on any SGNode. Javadoc for this method is:
[i]Returns a list of the visible nodes that overlap the specified point in the same order they'd be considered for event dispatching, topmost leaf first. The point is specified in local coordinates.[/i]

- Morten