averbraeck / opentrafficsim

Open Source Multi-Level Traffic Simulator
BSD 3-Clause "New" or "Revised" License
28 stars 8 forks source link

Use actual bounds to click on items #99

Closed WJSchakel closed 6 months ago

WJSchakel commented 6 months ago

The method Renderable2d.contains(...) uses BoundsUtil.projectBounds(...). This has the following line:

Bounds2d b = new Bounds2d(bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(), bounds.getMaxY());

These box bounds are subsequently used to check whether the clicked coordinate is within it. This makes precise clicking on items quite a hassle.

WJSchakel commented 6 months ago

Transform2d is used to perform the transform. It can only accept a Bounds2d to transform, not more generalized bounds. Although it can transform a single Point2d or an Iterator<Point2d>.

To utilize this, we introduce interface TransformableBounds<B extends TransformableBounds<B>> which defines a B transform(final Transform2d transformation); method. Bounds of OTS objects can implement this interface to return transformed bounds using the iterator approach. For example in BoundingPolygon (which wraps a Polygon2d):

    public BoundingPolygon transform(final Transform2d transformation)
    {
        return new BoundingPolygon(new Polygon2d(transformation.transform(this.polygon.getPoints())));
    }
WJSchakel commented 6 months ago

Finally, we introduce abstract class OtsRenderable<L extends Locatable> extends Renderable2d<L> and overwrite the contains method. It is highly similar to the super implementation in Renderable2d, but note the instanceof TransformableBounds<?> and how the transform is subsequently used.

    public boolean contains(final Point2d pointWorldCoordinates, final Bounds2d extent)
    {
        try
        {
            if (getSource().getBounds() instanceof TransformableBounds<?> && getSource().getLocation() instanceof Oriented)
            {
                Point<?> center = getSource().getLocation();
                TransformableBounds<?> bounds = (TransformableBounds<?>) getSource().getBounds();

                Point2d c = new Point2d(center.getX(), center.getY());
                Oriented<?> o = (Oriented<?>) center;
                Transform2d transformation = new Transform2d();
                transformation.translate(c);
                transformation.rotation(o.getDirZ());
                return bounds.transform(transformation).contains(pointWorldCoordinates);
            }
            else
            {
                return super.contains(pointWorldCoordinates, extent);
            }
        }
        catch (RemoteException ex)
        {
            CategoryLogger.always().warn(ex, "contains");
            return false;
        }
    }
WJSchakel commented 6 months ago

All OTS classes that extended Renderable2d now extend OtsRenderable. It is suggested that DSOL should incorporate similar changes such that more generic bounds can be used.

WJSchakel commented 6 months ago

A much cheaper method is of course to translate the world coordinate, rather than an entire shape:

    public boolean contains(final Point2d pointWorldCoordinates, final Bounds2d extent)
    {
        try
        {
            if (getSource().getLocation() instanceof Oriented)
            {
                Point<?> center = getSource().getLocation();
                Oriented<?> o = (Oriented<?>) center;
                Transform2d transformation = new Transform2d();
                transformation.translate(-center.getX(), -center.getY());
                transformation.rotation(-o.getDirZ());
                Point2d pointObjectCoordinates = transformation.transform(pointWorldCoordinates);
                return ((Bounds<?, Point2d, ?>) getSource().getBounds()).contains(pointObjectCoordinates);
            }
            return super.contains(pointWorldCoordinates, extent);
        }
        catch (RemoteException ex)
        {
            CategoryLogger.always().warn(ex, "contains");
            return false;
        }
    }

This then applies to all types of bounds, rendering TransformableBounds useless.

WJSchakel commented 6 months ago

Tried to get OTS to be consistent with:

This is not yet possible. DSOL does not consider bounds like this. The following was done:

WJSchakel commented 6 months ago

In both animation and the editor, elements are now clickable by their actual bounds. For point and line elements, this entails an additional area of 2m wide.