PointsList.java
package com.github.celldynamics.quimp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.celldynamics.quimp.geom.ExtendedVector2d;
/**
* Represents node of bidirectional list of points in Cartesian coordinates.
*
* <p>This abstract class contains basic properties of points and provides method for moving across
* the
* list. Points in list are numbered from <b>1</b> and list can be looped. There is one special node
* called <b>head</b> that indicates beginning of the list (and its end if the list is looped).
* PointList is assumed to be infinite long and may not be looped.
*
* @author p.baniukiewicz
*
* @param <T> Type of point, currently can be Node or Vert
*/
public abstract class PointsList<T extends PointsList<T>> {
/**
* The Constant LOGGER.
*/
static final Logger LOGGER = LoggerFactory.getLogger(PointsList.class.getName());
/**
* Previous point in list, null if no other point.
*/
protected transient T prev;
/**
* Next point in list, null if no other point.
*/
protected transient T next;
/**
* x,y co-ordinates of the point.
*/
protected ExtendedVector2d point;
/**
* Normal vector. Calculated by
* {@link com.github.celldynamics.quimp.PointsList#updateNormale(boolean)} and implicitly by
* {@link com.github.celldynamics.quimp.Shape#updateNormals(boolean)} from Shape during
* serialization and deserialization and changing the shape of Shape
*/
protected ExtendedVector2d normal;
/**
* tangent vector. Calculated by com.github.celldynamics.quimp.PointsList.calcTan(). Implicitly
* during calculating normals (see normal)
*/
protected ExtendedVector2d tan;
/**
* Indicate if this point is head.
*/
protected boolean head = false;
/**
* The clockwise. access clockwise if true.
*/
private static boolean clockwise = true;
/**
* ID number of point, unique across list. Given during adding point to list, controlled by
* Shape
*/
protected int tracknumber = 1;
/**
* Normalized position on list.
*
* <p>0 - beginning , 1 - end of the list according to Shape perimeter. Set by
* com.github.celldynamics.quimp.Shape.setPositions() and called before and after serialise and on
* Shape writing.
*/
protected double position = -1;
/**
* flag which is set when the velocity is below the critical velocity.
*/
private boolean frozen = false;
/**
* Default constructor, assumes that first point is created on list with ID = 1.
*/
public PointsList() {
point = new ExtendedVector2d();
normal = new ExtendedVector2d();
tan = new ExtendedVector2d();
}
/**
* Create point with given ID. New point is not linked to any other yet.
*
* <p>Caller should care about correct numbering of points
*
* @param t ID of point
*/
public PointsList(int t) {
this();
setTrackNum(t);
}
/**
* Copy constructor. Make copy of properties of passed point.
*
* <p>Previous or next points are not copied
*
* @param src Source Point
*/
public PointsList(final PointsList<?> src) {
this.point = new ExtendedVector2d(src.point);
this.normal = new ExtendedVector2d(src.normal);
this.tan = new ExtendedVector2d(src.tan);
this.head = src.head;
this.tracknumber = src.tracknumber;
this.position = src.position;
this.frozen = src.frozen;
}
/**
* Creates point with given ID and coordinates. New point is not linked to any other yet.
*
* <p>Caller should care about correct numbering of points
*
* @param xx x coordinate of point
* @param yy y coordinate of point
* @param t ID of point
*/
public PointsList(double xx, double yy, int t) {
this(t);
point = new ExtendedVector2d(xx, yy);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (frozen ? 1231 : 1237);
result = prime * result + (head ? 1231 : 1237);
result = prime * result + ((normal == null) ? 0 : normal.hashCode());
result = prime * result + ((point == null) ? 0 : point.hashCode());
long temp;
temp = Double.doubleToLongBits(position);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((tan == null) ? 0 : tan.hashCode());
result = prime * result + tracknumber;
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof PointsList)) {
return false;
}
PointsList<?> other = (PointsList<?>) obj;
if (frozen != other.frozen) {
return false;
}
if (head != other.head) {
return false;
}
if (normal == null) {
if (other.normal != null) {
return false;
}
} else if (!normal.equals(other.normal)) {
return false;
}
if (point == null) {
if (other.point != null) {
return false;
}
} else if (!point.equals(other.point)) {
return false;
}
if (Double.doubleToLongBits(position) != Double.doubleToLongBits(other.position)) {
return false;
}
if (tan == null) {
if (other.tan != null) {
return false;
}
} else if (!tan.equals(other.tan)) {
return false;
}
if (tracknumber != other.tracknumber) {
return false;
}
return true;
}
/**
* point getter.
*
* @return X space co-ordinate
*/
public double getX() {
return point.getX();
}
/**
* point getter.
*
* @return Y space co-ordinate
*/
public double getY() {
return point.getY();
}
/**
* Set X space co-ordinate.
*
* @param x coordinate
*/
public void setX(double x) {
point.setX(x);
}
/**
* Set Y space co-ordinate.
*
* @param y coordinate
*/
public void setY(double y) {
point.setY(y);
}
/**
* Sets the clockwise.
*
* @param b the new clockwise
*/
public static void setClockwise(boolean b) {
PointsList.clockwise = b;
}
/**
* Gets the point.
*
* @return the point
*/
public ExtendedVector2d getPoint() {
return point;
}
/**
* Gets the track num.
*
* @return the track num
*/
public int getTrackNum() {
return tracknumber;
}
/**
* Gets the normal.
*
* @return the normal
*/
public ExtendedVector2d getNormal() {
return normal;
}
/**
* Gets the tangent.
*
* @return the tangent
*/
public ExtendedVector2d getTangent() {
return tan;
}
/**
* Get normalised position of node.
*
* @return the position
*/
public double getPosition() {
return position;
}
/**
* Set normalised position of node.
*
* @param position the position to set
*/
public void setPosition(double position) {
this.position = position;
}
/**
* Checks if is head.
*
* @return true, if is head
*/
public boolean isHead() {
return head;
}
/**
* Sets the normal.
*
* @param x the x
* @param y the y
*/
public void setNormal(double x, double y) {
normal.setX(x);
normal.setY(y);
}
/**
* Sets the track num.
*
* @param b the new track num
*/
public void setTrackNum(int b) {
tracknumber = b;
}
/**
* Set head marker to current node.
*
* <p><b>Warning</b>
*
* <p>Only one Node in Snake can be head
*
* @param t true if current node is head, false otherwise
* @see com.github.celldynamics.quimp.Snake#setHead(int)
* @see com.github.celldynamics.quimp.Snake
*/
public void setHead(boolean t) {
head = t;
}
/**
* Get previous node in chain (next if not clockwise).
*
* @return next or previous Node from list
*/
public T getPrev() {
if (clockwise) {
return prev;
} else {
return next;
}
}
/**
* Get next node in chain (previous if not clockwise).
*
* @return previous or next Node from list
*/
public T getNext() {
if (clockwise) {
return next;
} else {
return prev;
}
}
/**
* Adds previous (or next if not clockwise) Node to list.
*
* @param n Node to add
*/
public void setPrev(T n) {
if (clockwise) {
prev = n;
} else {
next = n;
}
}
/**
* Adds next (or previous if not clockwise) Node to list.
*
* @param n Node to add
*/
public void setNext(T n) {
if (clockwise) {
next = n;
} else {
prev = n;
}
}
/**
* Updates the normal (must point inwards).
*
* @param inner inner
*/
public void updateNormale(boolean inner) {
boolean c = clockwise;
clockwise = true; // just in case
tan = calcTan(); // tangent
if (!inner) { // switch around if expanding snake
normal.setX(-tan.getY());
normal.setY(tan.getX());
} else {
normal.setX(tan.getY());
normal.setY(-tan.getX());
}
clockwise = c;
}
/**
* Calculate tangent at current point (i.e. unit vector between neighbours).
*
* <p>Calculate a unit vector towards neighbouring nodes and then a unit vector between their
* ends.
* direction important for normale calculation. Always calculate tan as if clockwise.
*
* @return Tangent at point
*/
private ExtendedVector2d calcTan() {
ExtendedVector2d unitVecLeft = ExtendedVector2d.unitVector(point, prev.getPoint());
ExtendedVector2d pointLeft = new ExtendedVector2d();
pointLeft.setX(getX());
pointLeft.setY(getY());
pointLeft.addVec(unitVecLeft);
ExtendedVector2d unitVecRight = ExtendedVector2d.unitVector(point, next.getPoint());
ExtendedVector2d pointRight = new ExtendedVector2d();
pointRight.setX(getX());
pointRight.setY(getY());
pointRight.addVec(unitVecRight);
return ExtendedVector2d.unitVector(pointLeft, pointRight);
}
/**
* Set direction of list.
*/
public static void randDirection() {
if (Math.random() < 0.5) {
clockwise = true;
} else {
clockwise = false;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "PointsList [point=" + point + ", normal=" + normal + ", tan=" + tan + ", head=" + head
+ ", tracknumber=" + tracknumber + ", position=" + position + ", frozen=" + frozen
+ "]";
}
/**
* Freeze Point.
*/
public void freeze() {
frozen = true;
}
/**
* Unfreeze Point.
*/
public void unfreeze() {
frozen = false;
}
/**
* Getter to frozen field.
*
* @return frozen
*/
public boolean isFrozen() {
return frozen;
}
/**
* Evaluate local curvature of T related to previous, this and next T.
*
* @return Local curvature for this node in degrees
*/
public double getCurvatureLocal() {
ExtendedVector2d edge1 = ExtendedVector2d.vecP2P(this.getPoint(), this.getPrev().getPoint());
ExtendedVector2d edge2 = ExtendedVector2d.vecP2P(this.getPoint(), this.getNext().getPoint());
double angle = ExtendedVector2d.angle(edge1, edge2) * (180 / Math.PI);
if (angle > 360 || angle < -360) {
LOGGER.warn("Warning-angle out of range (Vert l:320)");
}
if (angle < 0) {
angle = 360 + angle;
}
double curvatureLocal = 0;
if (angle == 180) {
curvatureLocal = 0;
} else if (angle < 180) {
curvatureLocal = -1 * (1 - (angle / 180));
} else {
curvatureLocal = (angle - 180) / 180;
}
return curvatureLocal;
}
}