View Javadoc
1   package com.github.celldynamics.quimp;
2   
3   import org.slf4j.Logger;
4   import org.slf4j.LoggerFactory;
5   
6   import com.github.celldynamics.quimp.geom.ExtendedVector2d;
7   
8   /**
9    * Represents node of bidirectional list of points in Cartesian coordinates.
10   * 
11   * <p>This abstract class contains basic properties of points and provides method for moving across
12   * the
13   * list. Points in list are numbered from <b>1</b> and list can be looped. There is one special node
14   * called <b>head</b> that indicates beginning of the list (and its end if the list is looped).
15   * PointList is assumed to be infinite long and may not be looped.
16   * 
17   * @author p.baniukiewicz
18   *
19   * @param <T> Type of point, currently can be Node or Vert
20   */
21  public abstract class PointsList<T extends PointsList<T>> {
22  
23    /**
24     * The Constant LOGGER.
25     */
26    static final Logger LOGGER = LoggerFactory.getLogger(PointsList.class.getName());
27  
28    /**
29     * Previous point in list, null if no other point.
30     */
31    protected transient T prev;
32  
33    /**
34     * Next point in list, null if no other point.
35     */
36    protected transient T next;
37    /**
38     * x,y co-ordinates of the point.
39     */
40    protected ExtendedVector2d point;
41    /**
42     * Normal vector. Calculated by
43     * {@link com.github.celldynamics.quimp.PointsList#updateNormale(boolean)} and implicitly by
44     * {@link com.github.celldynamics.quimp.Shape#updateNormals(boolean)} from Shape during
45     * serialization and deserialization and changing the shape of Shape
46     */
47    protected ExtendedVector2d normal;
48    /**
49     * tangent vector. Calculated by com.github.celldynamics.quimp.PointsList.calcTan(). Implicitly
50     * during calculating normals (see normal)
51     */
52    protected ExtendedVector2d tan;
53  
54    /**
55     * Indicate if this point is head.
56     */
57    protected boolean head = false;
58  
59    /**
60     * The clockwise. access clockwise if true.
61     */
62    private static boolean clockwise = true;
63    /**
64     * ID number of point, unique across list. Given during adding point to list, controlled by
65     * Shape
66     */
67    protected int tracknumber = 1;
68    /**
69     * Normalized position on list.
70     * 
71     * <p>0 - beginning , 1 - end of the list according to Shape perimeter. Set by
72     * com.github.celldynamics.quimp.Shape.setPositions() and called before and after serialise and on
73     * Shape writing.
74     */
75    protected double position = -1;
76    /**
77     * flag which is set when the velocity is below the critical velocity.
78     */
79    private boolean frozen = false;
80  
81    /**
82     * Default constructor, assumes that first point is created on list with ID = 1.
83     */
84    public PointsList() {
85      point = new ExtendedVector2d();
86      normal = new ExtendedVector2d();
87      tan = new ExtendedVector2d();
88    }
89  
90    /**
91     * Create point with given ID. New point is not linked to any other yet.
92     * 
93     * <p>Caller should care about correct numbering of points
94     * 
95     * @param t ID of point
96     */
97    public PointsList(int t) {
98      this();
99      setTrackNum(t);
100   }
101 
102   /**
103    * Copy constructor. Make copy of properties of passed point.
104    * 
105    * <p>Previous or next points are not copied
106    * 
107    * @param src Source Point
108    */
109   public PointsList(final PointsList<?> src) {
110     this.point = new ExtendedVector2d(src.point);
111     this.normal = new ExtendedVector2d(src.normal);
112     this.tan = new ExtendedVector2d(src.tan);
113     this.head = src.head;
114     this.tracknumber = src.tracknumber;
115     this.position = src.position;
116     this.frozen = src.frozen;
117   }
118 
119   /**
120    * Creates point with given ID and coordinates. New point is not linked to any other yet.
121    * 
122    * <p>Caller should care about correct numbering of points
123    * 
124    * @param xx x coordinate of point
125    * @param yy y coordinate of point
126    * @param t ID of point
127    */
128   public PointsList(double xx, double yy, int t) {
129     this(t);
130     point = new ExtendedVector2d(xx, yy);
131   }
132 
133   /*
134    * (non-Javadoc)
135    * 
136    * @see java.lang.Object#hashCode()
137    */
138   @Override
139   public int hashCode() {
140     final int prime = 31;
141     int result = 1;
142     result = prime * result + (frozen ? 1231 : 1237);
143     result = prime * result + (head ? 1231 : 1237);
144     result = prime * result + ((normal == null) ? 0 : normal.hashCode());
145     result = prime * result + ((point == null) ? 0 : point.hashCode());
146     long temp;
147     temp = Double.doubleToLongBits(position);
148     result = prime * result + (int) (temp ^ (temp >>> 32));
149     result = prime * result + ((tan == null) ? 0 : tan.hashCode());
150     result = prime * result + tracknumber;
151     return result;
152   }
153 
154   /*
155    * (non-Javadoc)
156    * 
157    * @see java.lang.Object#equals(java.lang.Object)
158    */
159   @Override
160   public boolean equals(Object obj) {
161     if (this == obj) {
162       return true;
163     }
164     if (obj == null) {
165       return false;
166     }
167     if (!(obj instanceof PointsList)) {
168       return false;
169     }
170     PointsList<?> other = (PointsList<?>) obj;
171     if (frozen != other.frozen) {
172       return false;
173     }
174     if (head != other.head) {
175       return false;
176     }
177     if (normal == null) {
178       if (other.normal != null) {
179         return false;
180       }
181     } else if (!normal.equals(other.normal)) {
182       return false;
183     }
184     if (point == null) {
185       if (other.point != null) {
186         return false;
187       }
188     } else if (!point.equals(other.point)) {
189       return false;
190     }
191     if (Double.doubleToLongBits(position) != Double.doubleToLongBits(other.position)) {
192       return false;
193     }
194     if (tan == null) {
195       if (other.tan != null) {
196         return false;
197       }
198     } else if (!tan.equals(other.tan)) {
199       return false;
200     }
201     if (tracknumber != other.tracknumber) {
202       return false;
203     }
204     return true;
205   }
206 
207   /**
208    * point getter.
209    * 
210    * @return X space co-ordinate
211    */
212   public double getX() {
213     return point.getX();
214   }
215 
216   /**
217    * point getter.
218    * 
219    * @return Y space co-ordinate
220    */
221   public double getY() {
222     return point.getY();
223   }
224 
225   /**
226    * Set X space co-ordinate.
227    * 
228    * @param x coordinate
229    */
230   public void setX(double x) {
231     point.setX(x);
232   }
233 
234   /**
235    * Set Y space co-ordinate.
236    * 
237    * @param y coordinate
238    */
239   public void setY(double y) {
240     point.setY(y);
241   }
242 
243   /**
244    * Sets the clockwise.
245    *
246    * @param b the new clockwise
247    */
248   public static void setClockwise(boolean b) {
249     PointsList.clockwise = b;
250   }
251 
252   /**
253    * Gets the point.
254    *
255    * @return the point
256    */
257   public ExtendedVector2d getPoint() {
258     return point;
259   }
260 
261   /**
262    * Gets the track num.
263    *
264    * @return the track num
265    */
266   public int getTrackNum() {
267     return tracknumber;
268   }
269 
270   /**
271    * Gets the normal.
272    *
273    * @return the normal
274    */
275   public ExtendedVector2d getNormal() {
276     return normal;
277   }
278 
279   /**
280    * Gets the tangent.
281    *
282    * @return the tangent
283    */
284   public ExtendedVector2d getTangent() {
285     return tan;
286   }
287 
288   /**
289    * Get normalised position of node.
290    * 
291    * @return the position
292    */
293   public double getPosition() {
294     return position;
295   }
296 
297   /**
298    * Set normalised position of node.
299    * 
300    * @param position the position to set
301    */
302   public void setPosition(double position) {
303     this.position = position;
304   }
305 
306   /**
307    * Checks if is head.
308    *
309    * @return true, if is head
310    */
311   public boolean isHead() {
312     return head;
313   }
314 
315   /**
316    * Sets the normal.
317    *
318    * @param x the x
319    * @param y the y
320    */
321   public void setNormal(double x, double y) {
322     normal.setX(x);
323     normal.setY(y);
324   }
325 
326   /**
327    * Sets the track num.
328    *
329    * @param b the new track num
330    */
331   public void setTrackNum(int b) {
332     tracknumber = b;
333   }
334 
335   /**
336    * Set head marker to current node.
337    * 
338    * <p><b>Warning</b>
339    * 
340    * <p>Only one Node in Snake can be head
341    * 
342    * @param t true if current node is head, false otherwise
343    * @see com.github.celldynamics.quimp.Snake#setHead(int)
344    * @see com.github.celldynamics.quimp.Snake
345    */
346   public void setHead(boolean t) {
347     head = t;
348   }
349 
350   /**
351    * Get previous node in chain (next if not clockwise).
352    * 
353    * @return next or previous Node from list
354    */
355   public T getPrev() {
356     if (clockwise) {
357       return prev;
358     } else {
359       return next;
360     }
361   }
362 
363   /**
364    * Get next node in chain (previous if not clockwise).
365    * 
366    * @return previous or next Node from list
367    */
368   public T getNext() {
369     if (clockwise) {
370       return next;
371     } else {
372       return prev;
373     }
374   }
375 
376   /**
377    * Adds previous (or next if not clockwise) Node to list.
378    * 
379    * @param n Node to add
380    */
381   public void setPrev(T n) {
382     if (clockwise) {
383       prev = n;
384     } else {
385       next = n;
386     }
387   }
388 
389   /**
390    * Adds next (or previous if not clockwise) Node to list.
391    * 
392    * @param n Node to add
393    */
394   public void setNext(T n) {
395     if (clockwise) {
396       next = n;
397     } else {
398       prev = n;
399     }
400   }
401 
402   /**
403    * Updates the normal (must point inwards).
404    * 
405    * @param inner inner
406    */
407   public void updateNormale(boolean inner) {
408     boolean c = clockwise;
409     clockwise = true; // just in case
410     tan = calcTan(); // tangent
411 
412     if (!inner) { // switch around if expanding snake
413       normal.setX(-tan.getY());
414       normal.setY(tan.getX());
415     } else {
416       normal.setX(tan.getY());
417       normal.setY(-tan.getX());
418     }
419     clockwise = c;
420 
421   }
422 
423   /**
424    * Calculate tangent at current point (i.e. unit vector between neighbours).
425    *
426    * <p>Calculate a unit vector towards neighbouring nodes and then a unit vector between their
427    * ends.
428    * direction important for normale calculation. Always calculate tan as if clockwise.
429    *
430    * @return Tangent at point
431    */
432   private ExtendedVector2d calcTan() {
433 
434     ExtendedVector2d unitVecLeft = ExtendedVector2d.unitVector(point, prev.getPoint());
435     ExtendedVector2dedVector2d.html#ExtendedVector2d">ExtendedVector2d pointLeft = new ExtendedVector2d();
436     pointLeft.setX(getX());
437     pointLeft.setY(getY());
438     pointLeft.addVec(unitVecLeft);
439 
440     ExtendedVector2d unitVecRight = ExtendedVector2d.unitVector(point, next.getPoint());
441     ExtendedVector2ddVector2d.html#ExtendedVector2d">ExtendedVector2d pointRight = new ExtendedVector2d();
442     pointRight.setX(getX());
443     pointRight.setY(getY());
444     pointRight.addVec(unitVecRight);
445 
446     return ExtendedVector2d.unitVector(pointLeft, pointRight);
447   }
448 
449   /**
450    * Set direction of list.
451    */
452   public static void randDirection() {
453     if (Math.random() < 0.5) {
454       clockwise = true;
455     } else {
456       clockwise = false;
457     }
458   }
459 
460   /*
461    * (non-Javadoc)
462    * 
463    * @see java.lang.Object#toString()
464    */
465   @Override
466   public String toString() {
467     return "PointsList [point=" + point + ", normal=" + normal + ", tan=" + tan + ", head=" + head
468             + ", tracknumber=" + tracknumber + ", position=" + position + ", frozen=" + frozen
469             + "]";
470   }
471 
472   /**
473    * Freeze Point.
474    */
475   public void freeze() {
476     frozen = true;
477   }
478 
479   /**
480    * Unfreeze Point.
481    */
482   public void unfreeze() {
483     frozen = false;
484   }
485 
486   /**
487    * Getter to frozen field.
488    * 
489    * @return frozen
490    */
491   public boolean isFrozen() {
492     return frozen;
493   }
494 
495   /**
496    * Evaluate local curvature of T related to previous, this and next T.
497    * 
498    * @return Local curvature for this node in degrees
499    */
500   public double getCurvatureLocal() {
501     ExtendedVector2d edge1 = ExtendedVector2d.vecP2P(this.getPoint(), this.getPrev().getPoint());
502     ExtendedVector2d edge2 = ExtendedVector2d.vecP2P(this.getPoint(), this.getNext().getPoint());
503 
504     double angle = ExtendedVector2d.angle(edge1, edge2) * (180 / Math.PI);
505 
506     if (angle > 360 || angle < -360) {
507       LOGGER.warn("Warning-angle out of range (Vert l:320)");
508     }
509 
510     if (angle < 0) {
511       angle = 360 + angle;
512     }
513 
514     double curvatureLocal = 0;
515     if (angle == 180) {
516       curvatureLocal = 0;
517     } else if (angle < 180) {
518       curvatureLocal = -1 * (1 - (angle / 180));
519     } else {
520       curvatureLocal = (angle - 180) / 180;
521     }
522     return curvatureLocal;
523   }
524 
525 }