OtsShape.java

package org.opentrafficsim.base.geometry;

import org.djunits.value.vdouble.scalar.Duration;
import org.djutils.draw.Directed;
import org.djutils.draw.Transform2d;
import org.djutils.draw.bounds.Bounds2d;
import org.djutils.draw.line.PolyLine2d;
import org.djutils.draw.line.Polygon2d;
import org.djutils.draw.point.DirectedPoint2d;
import org.djutils.draw.point.Point2d;

import nl.tudelft.simulation.dsol.animation.Locatable;

/**
 * Locatable that provides absolute and relative contours and bounds, both statically and dynamically.
 * <p>
 * Copyright (c) 2024-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
 * </p>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 */
public interface OtsShape extends Locatable
{

    /** Minimum world margin to click on object line or point objects. */
    double WORLD_MARGIN_LINE = 1.0;

    /** The default intended number of polygon segments to represent continuous shapes. */
    int DEFAULT_POLYGON_SEGMENTS = 128;

    @Override
    DirectedPoint2d getLocation();

    /**
     * Returns the contour of the object in world coordinates.
     * @return the contour of the object in world coordinates
     */
    default Polygon2d getAbsoluteContour()
    {
        Transform2d transform = toAbsoluteTransform(getLocation());
        return new Polygon2d(transform.transform(getRelativeContour().iterator()));
    }

    /**
     * Return the contour of a dynamic object at time 'time' in world coordinates. The default implementation returns the static
     * contour.
     * @param time simulation time for which we want the shape
     * @return the shape of the object at time 'time'
     */
    default Polygon2d getAbsoluteContour(final Duration time)
    {
        return getAbsoluteContour();
    }

    /**
     * Returns the contour of the object in relative coordinates.
     * @return the contour of the object in relative coordinates
     */
    Polygon2d getRelativeContour();

    /**
     * Returns the bounds relative to the location. The default implementation returns the bounds of the contour.
     * @return bounds relative to the location.
     */
    @Override
    default Bounds2d getRelativeBounds()
    {
        return getRelativeContour().getAbsoluteBounds();
    }

    /**
     * Returns whether the point is contained within the shape. The default implementation calculates this based on the contour.
     * @param point point
     * @return whether the point is contained within the shape
     */
    default boolean contains(final Point2d point)
    {
        return signedDistance(point) < 0.0;
    }

    /**
     * Returns whether the point is contained within the shape. The default implementation calculates this based on the contour.
     * @param x x coordinate of the point
     * @param y y coordinate of the point
     * @return whether the point is contained within the shape
     */
    default boolean contains(final double x, final double y)
    {
        return contains(new Point2d(x, y));
    }

    /**
     * Returns the bounds in world coordinates.
     * @return bounds in world coordinates.
     */
    default Bounds2d getAbsoluteBounds()
    {
        return getAbsoluteContour().getAbsoluteBounds();
    }

    @Override
    default double getDirZ()
    {
        return getLocation().dirZ;
    }

    /**
     * Signed distance function. The point must be relative. Negative distances returned are inside the bounds, with the
     * absolute value of the distance towards the edge. The default implementation is based on the polygon representation, which
     * is expensive.
     * @param point point for which distance is returned.
     * @return distance from point to these bounds.
     */
    default double signedDistance(final Point2d point)
    {
        double dist = getRelativeContour().closestPointOnPolyLine(point).distance(point);
        return getRelativeContour().contains(point) ? -dist : dist;
    }

    /**
     * Signed distance function. The point must be relative. Negative distances returned are inside the bounds, with the
     * absolute value of the distance towards the edge. The default implementation is based on the polygon representation, which
     * is expensive.
     * @param x x coordinate of the point
     * @param y y coordinate of the point
     * @return distance from point to these bounds.
     */
    default double signedDistance(final double x, final double y)
    {
        return signedDistance(new Point2d(x, y));
    }

    /*
     * The following methods that take an OtsLocatable as input are defined not as default but as static, as these methods
     * should not be exposed as functions of an OtsLocatable.
     */

    /**
     * Generates a polygon as contour based on bounds and location of the locatable.
     * @param locatable locatable
     * @return contour
     */
    static Polygon2d boundsAsAbsoluteContour(final OtsShape locatable)
    {
        return new Polygon2d(toAbsoluteTransform(locatable.getLocation()).transform(locatable.getRelativeBounds().iterator()));
    }

    /**
     * Transform the line by location, which may also be an {@code OrientedPoint} for rotation.
     * @param line line
     * @param location location, which may also be an {@code OrientedPoint} for rotation
     * @return transformed line
     */
    static PolyLine2d transformLine(final PolyLine2d line, final Point2d location)
    {
        return new PolyLine2d(toRelativeTransform(location).transform(line.iterator()));
    }

    /**
     * Returns a transformation by which absolute coordinates can be translated and rotated to the frame of the possibly
     * oriented location around which bounds are defined.
     * @param location location (can be an {@code Oriented}).
     * @return transformation.
     */
    static Transform2d toRelativeTransform(final Point2d location)
    {
        Transform2d transformation = new Transform2d();
        if (location instanceof Directed dir)
        {
            transformation.rotation(-dir.getDirZ());
        }
        transformation.translate(-location.getX(), -location.getY());
        return transformation;
    }

    /**
     * Returns a transformation by which relative coordinates can be translated and rotated to the frame of the possibly
     * oriented location around which bounds are defined.
     * @param location location (can be an {@code Oriented}).
     * @return transformation.
     */
    static Transform2d toAbsoluteTransform(final Point2d location)
    {
        Transform2d transformation = new Transform2d();
        transformation.translate(location.getX(), location.getY());
        if (location instanceof Directed dir)
        {
            transformation.rotation(dir.getDirZ());
        }
        return transformation;
    }

}