Package org.samcrow.vehicle

Source Code of org.samcrow.vehicle.Vehicle

package org.samcrow.vehicle;

import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.TimerTask;

import org.lekan.graphics.SGLine;
import org.lekan.graphics.SGObject;
import org.lekan.graphics.SGText;
import org.samcrow.environment.Obstacle;
import org.samcrow.sensor.MultiRangefinder;
import org.samcrow.sensor.Sensor;
import org.samcrow.sensor.SensorData;
import org.samcrow.sensor.SensorEventListener;
import org.samcrow.sensor.SingleRangefinder;
import org.samcrow.util.DrawQueue;

/**
* Base class for a vehicle.
*
* @author scamper
*
*/
public abstract class Vehicle extends TimerTask implements SensorEventListener {
  // constants
  /** The number of miliseconds to wait between loops */
  public static final long DELAY_TIME = 10;

  /**
   * The rate of acceleration, in pixels per second per second, that the
   * vehicle can accelerate at when at full throttle
   */
  private static final double MAX_ACCELERATION = 4.0;

  /**
   * The rate of acceleration, in pixels per second per second, that the
   * vehicle can brake at when at full braking power. This must be positive.
   */
  private static final double MAX_BRAKING = 8.0;

  /**
   * The rate of acceleration, in pixels per second per second forwards
   * (negative = backwards), that acts upon the vehicle to slow it down
   * because of friction.
   */
  /* package */static final double DYNAMIC_FRICTION = -2.0;

  /**
   * The rate of acceleratoin, in pixels per second per second forwards
   * (negative = backwards), that acts upon the vehicle to slow it down
   * because of friction when the vehicle is at 90 degrees steering, either
   * direction. It decreases linearly to zero degrees steering.
   */
  /* package */static final double STEERING_FRICTION = -4.0;

  // private instance variables
  /**
   * Current total acceleration, positive being forward, in pixels per second
   * per second.
   */
  private double acceleration = 0.0;

  /** If the vehicle is in reverse mode */
  private boolean isReversed = false;

  private int width = 30;
  private int length = 60;

  // X and Y coordinates of the Vehicle origin
  private double x;
  private double y;

  private Point2D.Double lastLocation;

  /** rotation, clockwise from above, from north */
  private double heading;
  /**
   * Current speed, as measured at the non-steering wheels, in pixels per
   * second
   */
  private double speed;

  /**
   * The rear, non-steering, wheels of the vehicle. Currently only two of
   * these are supported.
   */
  private Wheel[] rearWheels;
  /**
   * The front, steering, wheels of the vehicle. Currently only two of these
   * are supported.
   */
  private SteerableWheel[] frontWheels;

  /** Physics task that manages this vehicle */
  private VehiclePhysicsTask physics;

  // drawing stuff
  /** A reference to the main queue to draw to */
  private DrawQueue queue;

  /** sensors */
  private Collection<Sensor> sensors;

  /** Obstacles */
  protected Collection<Obstacle> obstacles;

  /* package */Date lastFrameDate;

  /**
   * Construct a new Vehicle with its origin at a specified location in the
   * environment
   *
   * @param inX
   *            the X location
   * @param inY
   *            the Y location
   * @param inQueue
   *            The DrawQueue to draw to
   */
  public Vehicle(int inX, int inY, DrawQueue inQueue,
      Collection<Obstacle> inObstacles) {
    x = inX;
    y = inY;
    queue = inQueue;
    obstacles = inObstacles;

    sensors = new HashSet<Sensor>();

    rearWheels = new Wheel[2];
    frontWheels = new SteerableWheel[2];
    System.out.println("Setting up vehicle task.");
    setup();

    physics = new VehiclePhysicsTask(this, obstacles);

    obstacles = inObstacles;

    lastFrameDate = new Date();

  }

  // threading methods

  /*
   * (non-Javadoc)
   *
   * @see java.lang.Runnable#run()
   */
  @Override
  public final void run() {
    // System.out.println("Vehicle task looping");
    lastLocation = new Point2D.Double(x, y);
    for (Sensor sensor : sensors) {
      sensor.run();// check sensors
    }
    runLoop();
    physics.run();
    draw();
    Date currentDate = new Date();
    long renderTime = (currentDate.getTime() - lastFrameDate.getTime());
    double renderTimeSeconds = renderTime / 1000d;
    double fps = 1 / renderTimeSeconds;
    queue.addTransient(new SGText(Math.round(fps) + " FPS", 5, 15));
    queue.redrawAndClear();
    // System.out.println("Current vehicle location ("+x+", "+y+")");
    // System.out.println("Vehicle task sleeping");
    lastFrameDate = new Date();
  }

  /**
   * Reset the vehicle to its default location and heading
   */
  public final void reset(){
    x = 1024 / 2;
    y = 768 / 2;
    heading = 0;
  }
 
  /**
   * Add objects to the draw queue and redraw it
   */
  private final void draw() {
    // System.out.println("Drawing.");
    List<SGObject> objects = new LinkedList<SGObject>();
    // path marker
//     queue.addPersistent(new SGLine(lastLocation.x, lastLocation.y, x,
//     y));

    Shape result = getBoundingRectangle();
    PathIterator iterator = result.getPathIterator(null);
    Point2D.Double lastPoint = null;
    while (!iterator.isDone()) {// iterate through all points
      double[] coords = new double[8];
      int type = iterator.currentSegment(coords);

      if (type == PathIterator.SEG_MOVETO) {
        lastPoint = new Point2D.Double(coords[0], coords[1]);
      } else if (type == PathIterator.SEG_LINETO) {
        assert (lastPoint != null);// this must be after a point of type
        // 0
        SGLine line = new SGLine(lastPoint.x, lastPoint.y, coords[0],
            coords[1]);
        objects.add(line);
        lastPoint = new Point2D.Double(coords[0], coords[1]);
        // System.out.println("Adding line "+line);
      }

      iterator.next();
    }

    for (Sensor sensor : sensors) {
      objects.addAll(sensor.getDrawObjects());
    }

    objects.addAll(rearWheels[0].getDrawObjects());
    objects.addAll(rearWheels[1].getDrawObjects());
    objects.addAll(frontWheels[0].getDrawObjects());
    objects.addAll(frontWheels[1].getDrawObjects());

    queue.addTransient(objects);
  }

  /**
   * Get the Shape, in the form of a Rectanlge, with all transformations
   * applied, that represents the rectangle that can represent the exterior of
   * this vehicle
   *
   * @return the shape
   */
  /* package */Shape getBoundingRectangle() {
    final int PADDING = 7;// padding on each side for clearing wheels
    // get the lines that form the rectangle representing this vehicle
    final double width = Math.abs(frontWheels[0].getX()
        - frontWheels[0].getX())
        + 2 * PADDING;
    Rectangle2D.Double rect = new Rectangle2D.Double(x - width - PADDING
        / 2d, y + frontWheels[0].getY() - PADDING, width + 3 * PADDING,
        Math.abs(frontWheels[0].getY())
            + Math.abs(rearWheels[0].getY()) + PADDING * 3);
    AffineTransform transform = new AffineTransform();
    transform.translate(x, y);
    transform.rotate(Math.toRadians(heading));
    transform.translate(-x, -y);
    Shape result = transform.createTransformedShape(rect);
    return result;
  }

  // methods for vehicles to override

  /**
   * This is called once at the start of the simulation. Vehicles should
   * override this method to set their attributes.<br />
   * Here are some things that every Vehicle should do:
   * <ul>
   * <li>Define wheels using addWheel(int inX, int inY) and
   * addSteerableWheel(int inX, int inY)</li>
   * <li>Define sensors</li>
   * </ul>
   */
  protected void setup() {
    System.err.println(toString() + ": setup() not overriden.");
  }

  /**
   * This is called periodically when the simulation. Vehicles should override
   * this method.<br />
   * The physics engine will not calculate physics for the vehicle until after
   * the first time this method executes.
   */
  protected void runLoop() {
    System.err.println(toString() + ": runLoop() not overriden.");
  }

  /**
   * This is called when new sensor data is received from a sensor. Vehicles
   * should override this class to receive data from their sensors.
   *
   * @param data
   *            the data from the sensor
   */
  @Override
  public void sensorDataReceived(SensorData data) {
    System.err.println("A Vehicle (" + toString()
        + ") has not overriden the sensorDataReceived method!");
  }

  /**
   * This is called as soon as a collision between a vehicle and an obstacle
   * is detected.<br />
   * Vehicles should override this to handle crashes.<br />
   * Immediately before this is called, the vehicle's brake, throttle, and
   * speed are set to zero.
   *
   * @param target
   *            the Obstacle that the Vehicle crashed into
   */
  public void crashed(Obstacle target) {

  }

  // Methods that vehicles should not override. These should all be protected
  // final.

  /**
   * Add a rear (non-steering) wheel at a specified location. A Vehicle must
   * add exactly two rear wheels. Any attempt to add a subsequent rear wheel
   * after two have been added will be ignored.
   *
   * @param inX
   *            the X-axis location, relative to the car origin
   * @param inY
   *            the Y-axis location, relative to the car origin
   */
  protected final void addRearWheel(double inX, double inY) {
    inY = -inY;// Positive Y in the global coordinate system means down.
    // Positive Y hear means up. Reverse it.
    if (rearWheels[0] == null) {
      rearWheels[0] = new Wheel(inX, inY, this);// add to slot 0
    } else if (rearWheels[1] == null) {
      rearWheels[1] = new Wheel(inX, inY, this);// add to slot 1
    }
    // else: both are set
    // do nothing
  }

  /**
   * Add a front (steering) wheel at a specified location. A Vehicle must add
   * exactly two front wheels. Any attempt to add a subsequent front wheel
   * after two have been added will be ignored.
   *
   * @param inX
   *            the X-axis location, relative to the car origin
   * @param inY
   *            the Y-axis location, relative to the car origin
   */
  protected final void addFrontWheel(double inX, double inY) {
    inY = -inY;// Positive Y in the global coordinate system means down.
    // Positive Y hear means up. Reverse it.
    if (frontWheels[0] == null) {
      frontWheels[0] = new SteerableWheel(inX, inY, this);// add to slot 0
    } else if (frontWheels[1] == null) {
      frontWheels[1] = new SteerableWheel(inX, inY, this);// add to slot 1
    }
    // else: both are set
    // do nothing
  }

  /**
   * Set the power being applied to move the car forward. If any braking is
   * occuring, all braking will stop.
   *
   * @param throttle
   *            The ratio from 0 to 1 of throttle to apply
   */
  protected final void setThrottle(double throttle) {
    acceleration = throttle * MAX_ACCELERATION;
    if (isReversed) {
      acceleration = -acceleration;
    }
  }

  /**
   * Set the degree of braking being applied to stop the car. If any power is
   * being applied to move the vehicle forward, all forward power application
   * will stop.
   *
   * @param brake
   *            The ratio from 0 to 1 of brake to apply
   */
  protected final void setBrake(double brake) {
    acceleration = -brake * MAX_BRAKING;
    if (isReversed) {
      acceleration = -acceleration;
    }
  }

  /**
   * Set the steering angle
   *
   * @param inAngle
   *            The desired angle in degrees to steer at, where zero is
   *            straight ahead and positive is right.
   */
  protected final void setSteeringAngle(double inAngle) {
    if (frontWheels[0] != null && rearWheels[1] != null) {
      for (int i = 0; i < frontWheels.length; i++) {
        if (inAngle <= 90 && inAngle >= -90) {
          frontWheels[i].setAngle(inAngle);
        }
      }
    }
  }

  /**
   * Add a sensor to the vehicle. This sensor does not need to have its
   * obstacles already set. If they are, the will be overwritten.
   *
   * @param sensor
   *            the Sensor to add
   */
  protected final void addSensor(Sensor sensor) {
    sensor.localY = -sensor.localY;
    // sensor.localX = -sensor.localX;

    if (sensor instanceof SingleRangefinder
        || sensor instanceof MultiRangefinder) {
      sensor.localRotation += 180;
      if (sensor.localRotation > 359) {
        sensor.localRotation -= 360;
      }
      if (sensor instanceof SingleRangefinder) {
        sensor.localX = -sensor.localX;
      }
    }
    sensor.addEventListener(this);
    sensors.add(sensor);
  }

  // package getters and setters. Used by the VehiclePhysicsTask.

  /**
   * Get the current absolute X-axis position of the Vehicle.
   *
   * @return the current absolute X-axis position of the Vehicle
   */
  public double getX() {
    return x;
  }

  /**
   * Get the current absolute Y-axis position of the Vehicle.
   *
   * @return the current absolute Y-axis position of the Vehicle
   */
  public double getY() {
    return y;
  }

  /**
   * Get the absolute heading of the vehicle
   *
   * @return The absolute heading of the vehicle, in degrees clockwise from
   *         above, from north
   */
  public double getHeading() {// this is public because Sensors need to access
    // it.
    if (heading > 359) {
      heading -= 359;
    }
    return heading;
  }

  /**
   * Get the current speed of the vehicle
   *
   * @return The speed in pixels per second
   */
  /* package */double getSpeed() {
    return speed;
  }

  /**
   * Get the average Y location of the Vehicle's rear wheels
   *
   * @return the average Y-axis location of the rear wheels relative to the
   *         Vehicle origin
   */
  /* package */int getRearWheelY() {
    if (rearWheels[0] != null && rearWheels[1] != null) {
      return (int) Math.round((rearWheels[0].getY() + rearWheels[1]
          .getY()) / 2);
    }
    return 0;
  }

  /**
   * Get the average Y location of the Vehicle's front wheels
   *
   * @return the average Y-axis location of the front wheels relative to the
   *         Vehicle origin
   */
  /* package */int getFrontWheelY() {
    if (frontWheels[0] != null && frontWheels[1] != null) {
      return (int) Math.round((frontWheels[0].getY() + frontWheels[1]
          .getY()) / 2);
    }
    return 0;
  }

  /**
   * Get the acceleration currently being applied to the car
   *
   * @return the current total acceleration in pixels per second per second
   */
  /* package */double getAcceleration() {
    return acceleration;
  }

  /**
   * Get the current angle of the wheels that steer.
   *
   * @return The angle in degrees, with positive being right and zero being
   *         straight ahead, that the wheels are turned
   */
  /* package */double getSteeringAngle() {
    if (frontWheels[0] != null && frontWheels[1] != null) {
      return (frontWheels[0].getAngle() + frontWheels[1].getAngle()) / 2;
    }
    return 0;
  }

  /**
   * Determine if the vehicle is in reverse
   *
   * @return if the vehicle is in reverse
   */
  /* package */boolean isReversed() {
    return isReversed;
  }

  /**
   * Set the reverse status of the vehicle
   *
   * @param inIsReversed
   */
  /* package */void setIsReversed(boolean inIsReversed) {
    speed = 0;
    if (!isReversed && inIsReversed || isReversed && !inIsReversed) {
      // invert the acceleration if the reversal state is changed
      acceleration = -acceleration;
    }
    isReversed = inIsReversed;
  }

  /**
   * Set the vehicle's absolute location
   *
   * @param inX
   *            The X-axis location
   * @param inY
   *            The Y-axis location
   */
  /* package */void setLocation(double inX, double inY) {
    x = inX;
    y = inY;
  }

  /**
   * Set the absolute heading of the Vehicle
   *
   * @param inHeading
   *            The desired heading
   */
  /* package */void setHeading(double inHeading) {
    heading = inHeading;
  }

  /**
   * Set the Vehicle's absolute location and heading
   *
   * @param inX
   *            The X-axis location
   * @param inY
   *            The Y-axis location
   * @param inHeading
   *            The heading
   */
  /* package */void setLocationAndHeading(double inX, double inY,
      double inHeading) {
    x = inX;
    y = inY;
    heading = inHeading;
  }

  /**
   * Set the speed of the Vehicle
   *
   * @param inSpeed
   *            The speed in pixels per second
   */
  /* package */void setSpeed(double inSpeed) {
    speed = inSpeed;
  }

  /**
   * Set the acceleration of the Vehicle
   *
   * @param inAcceleration
   *            The acceleration in pixels per second per second, positive
   *            being forward
   */
  /* package */void setAcceleration(double inAcceleration) {
    acceleration = inAcceleration;
  }

  // private methods

  /**
   * Determine if this Vehicle has two front wheels and two rear wheels. If
   * the wheels are invalid, also prints a message to <code>System.err</code>.
   *
   * @return true if it does, false if it doesn't.
   */
  private boolean validateWheels() {
    boolean isValid = ((rearWheels[0] != null && rearWheels[1] != null) && (frontWheels[0] != null && frontWheels[1] != null));
    if (!isValid) {
      System.err
          .println("This Vehicle has not specified the correct wheels.");
    }
    return isValid;
  }
}
TOP

Related Classes of org.samcrow.vehicle.Vehicle

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.