/* * Bee foraging simulation. Copyright by Joerg Hoehne. * For suggestions or questions email me at hoehne@thinktel.de */ package de.thinktel.foragingBee.simulation; import java.awt.Color; import java.util.Random; import javax.vecmath.Point3d; import javax.vecmath.Tuple3d; import javax.vecmath.Vector3d; import sim.engine.Steppable; import de.thinktel.foragingBee.masonGlue.ForagingHoneyBeeSimulation; import de.thinktel.foragingBee.masonGlue.IAgentVisualization; import de.thinktel.foragingBee.masonGlue.IIterationAgent; import de.thinktel.utils.Filter; import de.thinktel.utils.Geometric; import de.thinktel.utils.J3dPolar; /** * An abstract class for a moving object. Used by several subclasses that are * locating and moving objects in space. Every agent has an orientation (given * in radians) and a velocity (given as a vector but heading in the same * direction as the orientation). * <p> * Changes: * <ul> * <li>20090901: The agent is no more inherited from any MASON object (but still * supports the {@link Steppable} interface) and does not support directly * visualization. Instead the agent holds a visualization object {@link #visual} * that will be used by the simulation environment for displaying this agent.</li> * <li>20090901: Removed the color attribute because the color is part of the * visualization rather than the moving agent.</li> * <li>20090908: All angular methods are now working with radians.</li> * <li>20090918: Added the flag {@link #is3dMode} and its according methods.</li> * <li>20090920: Changed some methods to behave accordingly to the * {@link #is3dMode} flag.</li> * </ul> * <p> * Copyright 2009 Joerg Hoehne * * @author hoehne (<a href="mailto:hoehne@thinktel.de">Jörg Höhne</a>) * */ public abstract class AbstractMovingAgent implements IIterationAgent, IVisualAgent { /** * Generated serial version id. */ private static final long serialVersionUID = 7815716363922534915L; /** * The random number generator for all agents. */ static public Random r = new Random(); /** * Determine if this agent is running in a 3D simulation environment. Some * methods may behave differently according to this flag. */ private boolean is3dMode = false; /** * The object that is used for visualizing this agent. The simulation * environment is not known so no knowledge of the type is given. */ IAgentVisualization visual; /** * Information to access the scheduler for this agent. */ Object schedulerInformation; /** * The simulation the agent is dealing with. */ private ForagingHoneyBeeSimulation simulation; /** * The current location of the agent. */ private Point3d location = new Point3d(); /** * The current speed vector of the agent. */ private Vector3d velocity = new Vector3d(); /** * This variable contains the heading. The heading itself contains the * orientation (azimuth) that the angle in radians inside the xy-plane and * the elevation that is the angle between the positive x-axis and the * projection of the vector onto the xy-plane. */ private J3dPolar heading = new J3dPolar(); /** * The size of the agent. */ private double size; /** * The radius of the object. */ private double radius; /** * The constructor of the agent. * * @param simulation * The simulation the agent resides in. * @param is3dMode * Determine if the agent is running in a 3d simulation * environment (if it is set to true). * @param location * The current location of the agent. * @param velocity * The current velocity of the agent. * @param size * The size (diameter) of the agent. * @param color * The color of the agent. */ public AbstractMovingAgent(ForagingHoneyBeeSimulation simulation, boolean is3dMode, Point3d location, Vector3d velocity, double size, Color color) { setIs3dMode(is3dMode); /* * Set the values which will not affect any other classes. */ setSize(size); /* * store the simulation */ this.simulation = simulation; /* * set the visualization object for this agent */ this.setVisualizationObject(this.simulation.createVisual(this)); this.getVisualizationObject().setColor(color); /* * The location and velocity may affect the visualization by calling * updateLocation() so the visualization has to be initialized first. */ setLocation(location); setVelocityVector(velocity); } /** * Get the simulation this agent resides in. * * @return The reference to the current simulation. */ public final ForagingHoneyBeeSimulation getSimulation() { return simulation; } /** * Return true if this agent is running in a 3d simulation environment * (basically the agent thinks it is running in such an environment). * * @return True, if the agent is running in a 3d simulation environment. */ public final boolean is3dMode() { return is3dMode; } /** * Set the flag if the agent is running in a 3d simulation environment. * * @param value * True, if the agent is running in a 3d simulation environment; * false otherwise. */ private final void setIs3dMode(boolean value) { is3dMode = value; } /** * Return the sphere radius the agent fills in. * * @return The radius of the sphere. */ public double getSphereRadius() { return radius; } /** * Return the size of the agent. * * @return The size (diameter) of the agent. */ public double getSize() { return this.size; } /** * Set the size of the agent. * * @param size * The size of the agent. */ public void setSize(double size) { this.size = size; this.radius = size / 2; } /** * Get the current heading of the agent. * * @return the heading */ public final J3dPolar getHeading() { return heading; } /** * Set the current heading of the agent. * * @param heading * the heading to set */ public final void setHeading(J3dPolar heading) { this.heading = heading; } /** * Return the reference to the agent's location. * * @return The agent's location reference. */ public Point3d getLocation() { return location; } /** * Set the location of this agent. Setting the location will cause a visual * update by calling {@link #updateLocation()}. If {@link #is3dMode} is * false the z coordinate will be set to 0. * * @param x * The x value on the x-axis. * @param y * The y value on the y-axis. * @param z * The z value on the z-axis. * */ public void setLocation(double x, double y, double z) { if (is3dMode) this.location.set(x, y, z); else { this.location.set(x, y, 0); } updateLocation(); } /** * Set the location of this agent by calling * {@link #setLocation(double, double, double).} * * @param location * The location of this agent. */ public void setLocation(Tuple3d location) { setLocation(location.x, location.y, location.z); } /** * Returns the vector with the velocity. * * @return The reference to the agent's current velocity vector. */ public final Vector3d getVelocityVector() { return velocity; } /** * Set the velocity vector to a speed meaning setting the vector to a given * length. * * @param speed */ public final void setVelocity(double speed) { this.velocity.normalize(); this.velocity.scale(speed); this.heading.set(velocity); } /** * Set the velocity vector. Setting the vector will also cause a change in * the current orientation according to the velocity vector settings. * * @param x * The velocity value for the x-axis. * @param y * The velocity value for the y-axis. * @param z * The velocity value for the z-axis. */ public final void setVelocityVector(double x, double y, double z) { velocity.set(x, y, z); heading.set(velocity); } /** * Copies the field of the input vector in to current velocity vector. * * @param v * The new velocity vector. */ public final void setVelocityVector(Vector3d v) { setVelocityVector(v.x, v.y, v.z); } /** * Compute the direction (including distance) from agent1 to agent2 in * radians. * * @param agent1 * The first agent the orientation is computed from. * @param agent2 * The second agent the orientation is computed to. * @return The direction from agent1 to agent2. */ static final J3dPolar directionTo(IMovingAgent agent1, IMovingAgent agent2) { return J3dPolar.createFrom(agent1.getLocation(), agent2.getLocation()); } /** * Return the distance to an other agent. * * @param agent * The other agent. * @return The distance. */ public final double distance(IMovingAgent agent) { return distance(agent.getLocation()); } /** * Return the distance to an other point. * * @param p * The point the distance is calculated to. * @return The distance. */ public final double distance(Point3d p) { return location.distance(p); } /** * Return the squared distance to an other agent. This method saves some * execution time by not applying the square root so a squared distance is * returned. This method calls {@link #distanceSquared(Point3d)}. * * @param agent * The other agent. * @return The squared distance. */ public final double distanceSquared(IMovingAgent agent) { return distanceSquared(agent.getLocation()); } /** * Return the squared distance to the given point. This method saves some * execution time by not applying the square root so a squared distance is * returned. * * @param p * The point. * @return The squared distance. */ public final double distanceSquared(Point3d p) { return location.distanceSquared(p); } /** * Move the agent in the direction given by the provided vector. If * {@link #is3dMode} is false the z coordinate will be set to 0. * * @param direction * The direction and speed as a vector. */ public final void forward(Vector3d direction) { location.add(direction); if (!is3dMode) location.z = 0.0d; updateLocation(); } /** * Move the agent in the current direction given by {@link #velocity}. */ public final void forward() { forward(velocity); } /** * Return the objects within the distance of this object. Due to the * dimension of an object the sphere of both objects might be included in * the computation. * * @param radius * The distance we are looking for. * @param useMySphere * Use this agents sphere for reducing the distance between this * agent and others * @param useTheirSpheres * Use the others agents spheres to reduce the distance * @param includeMySelf * Shall the agent itself be included in the results? * @return The objects meeting the criteria. */ public IMovingAgent[] getObjectsWithinMyDistance(double radius, boolean useMySphere, boolean useTheirSpheres, boolean includeMySelf) { return getObjectsWithinMyDistance(radius, useMySphere, useTheirSpheres, getSimulation().getMaxSphereRadius(), includeMySelf, null); } /** * This method returns all objects that are within a radius of the this * object. The computation is done in several stages and configurations. * First the distance between objects (agents) is measured from center to * center. The center is a point so it has no dimension.<br> * The parameter radius controls the radius from the center of the current * object (agent) where the center of the other objects (agents) have to lie * within to be returned.<br> * Every agent in the simulation has a sphere given by a radius. This radius * can be used to compute if a center of an other objects lies inside the * sphere. This behaviour is configured by the parameter useMySphere. The * usage of the sphere of the other object (agent) instead of its center is * controlled by useTheirSpheres.<br> * The parameter maxSphere defines the maximum radius of all other agents * used for computing the distance in which one object (agent) have to be in * maximum to be returned.<br> * * @param radius * The radius where to look for other objects (agents). * @param useMySphere * Is the center or sphere of an other object inside by sphere. * @param useTheirSpheres * Use the sphere of the other object instead of the center * point. * @param maxSphere * The maximum sphere of all other objects (agents). * @param includeMySelf * If the returned objects shall include this object (agent). * @param type * If this type is not null only objects (agents) of the defined * type (or subclass) are returned. * @return The objects meeting the criteria. */ public IMovingAgent[] getObjectsWithinMyDistance(double radius, boolean useMySphere, boolean useTheirSpheres, double maxSphere, boolean includeMySelf, Class<?> type) { double distanceCorrection = 0; double correction = 0; if (useMySphere) { distanceCorrection += this.getSphereRadius(); correction = distanceCorrection; } if (useTheirSpheres) distanceCorrection += maxSphere; // get all objects which centers lies within a certain distance Object[] objects = simulation.getObjectsWithinDistance(this, radius + distanceCorrection); if (type != null) objects = Filter.filter(objects, type); IMovingAgent agent; int i, k; for (i = 0, k = 0; i < objects.length; i++) { agent = (IMovingAgent) objects[i]; // continue if the agent itself is included or the agent is not // myself if (includeMySelf || (agent != this)) { // get the distance from center to center double dist = this.distance(agent); // correct the distance according to the spheres dist -= correction; if (useTheirSpheres) dist -= agent.getSphereRadius(); if (dist < radius) { objects[k] = agent; k++; } } } IMovingAgent[] neighbours = new IMovingAgent[k]; System.arraycopy(objects, 0, neighbours, 0, k); return neighbours; } /** * Head to the direction of the given agent. * * @param agent * The agent this agent has to head to. */ public final void headTo(IMovingAgent agent) { headTo(agent.getLocation()); } /** * Head to the direction of the given point. * * @param p * The point this agent has to head to. */ public final void headTo(Tuple3d p) { J3dPolar direction = J3dPolar.createFrom(this.getLocation(), p); turnTo(direction.azimuth, direction.elevation); } /** * Compute if the location of an agent is inside the sphere of this agent. * The outer sphere of the agent is not considered, only the center of the * agent. * * @param agent * The agent whose sphere will be tested. * @return True, if this agent is inside the other agent's sphere. */ public final boolean isInSphere(IMovingAgent agent) { return isInSphere(agent, false); } /** * Compute if the location of an agent if it is inside the sphere of this * agent. The consideration of the other agents sphere is optional. If the * sphere is used for computation false is returned if the sphere of the * agent is (partially) out of this sphere. * * @param agent * The agent whose sphere will be tested. * @param useSphere * If true the sphere of this agent will also used to decide if * this agent's sphere intersects with the other agent's sphere. * @return True, if this agent is inside the other agent's sphere. */ public final boolean isInSphere(IMovingAgent agent, boolean useSphere) { return (distanceToSphere(agent, true) <= this.getSphereRadius()); } /** * Compute the distance of the given agent to the outer sphere perimeter of * this agent. The sphere perimeter of the agent also can be used otherwise * the center. * * @param agent * The other agent. * @param useSphere * If true use the outer perimeter of the other agent. * @return The distance to the other agent's sphere. */ public final double distanceToSphere(IMovingAgent agent, boolean useSphere) { double d = distance(agent); if (useSphere) d += agent.getSphereRadius(); return d; } /** * Turn the agent by the given angle in radians. This method will call * {@link #turnTo(double, double)} so the checking for 2d/3d mode has to be * done in {@link #turnTo(double, double)}. * * @param dAzimuth * The angle in radians the agent will be turned. * @param dElevation * The angle in radians the agent will be raised for elevation. */ public final void turnBy(double dAzimuth, double dElevation) { turnTo(heading.azimuth + dAzimuth, heading.elevation + dElevation); } /** * Turn the agent to the given angle given in radians. * * @param azimuth * The angle (azimuth) in radians the agent will head to. * @param elevation * The angle in radians the agent will elevate from the xy-pane. */ public final void turnTo(double azimuth, double elevation) { if (!is3dMode) elevation = 0.0d; heading.azimuth = Geometric.clampAngleRadians(azimuth); heading.elevation = Geometric.clampAngleRadians(elevation); velocity.set(heading.toCartesian()); } /** * Update the location of the agent using the simulation framework. */ final private void updateLocation() { simulation.updateLocation(this); } /** * Calculate the vector from this agent to the provided one. * * @param agent * The other the computed vector will head to. * @return The vector heading to the other agent. */ public final Vector3d vectorTo(IMovingAgent agent) { Vector3d v = new Vector3d(); v.sub(agent.getLocation(), location); return v; } // ================== IVisualAgent ==================== /** * Return the object that will be used for visualizing the this agent. The * type of the object is not known due to different simulation environments * so an interface {@link IAgentVisualization} is returned. * * @return The visualization object. */ public IAgentVisualization getVisualizationObject() { return visual; } /** * Set the object that will be used for visualizing the this agent. The type * of the object is not known due to different simulation environments so an * interface {@link IAgentVisualization} is used. * * @param visual * The object used for visualization. */ public void setVisualizationObject(IAgentVisualization visual) { this.visual = visual; } // ================== IIterationAgent ==================== /** * Return the information a simulation environment needs to access * information to the scheduler regarding this agent. * * @return The information for the scheduler. */ public Object getSchedulerInformation() { return schedulerInformation; } /** * Set information a simulation environment needs to access information to * the scheduler regarding this agent. * * @param o */ public void setSchedulerInformation(Object o) { schedulerInformation = o; } // ======================================= /** * Method for easy setting of the color of this agent. This method calls the * color setting method of the visualization object. * * @param color * The new color of the agent. */ public final void setColor(Color color) { visual.setColor(color); } }