Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* The MIT License
*
* Copyright 2016 Benny Lutati.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package bgu.dcr.az.vdev.util;
import java.util.ArrayList;
import com.sun.javafx.geom.Path2D;
import com.sun.javafx.geom.PathIterator;
import com.sun.javafx.geom.transform.BaseTransform;
/**
* Helper class to support opaque geometric path generated for PathMotion
* Transition.
*
* Not part of the public API.
*/
public class AnimationPathHelper {
private final static int SMOOTH_ZONE = 10;
private ArrayList segments = new ArrayList();
private double totalLength = 0;
/**
* Creates an internal flatten representation of path for efficient query
* of position data along the path.
* path - path to be snap shot and converted.
* flatness - the maximum allowable distance between the control points and
* the flattened curve.
* return value - true is creation is successful otherwise false.
*
* Implementation detail:
* The constructor is required to create an internal representation of
* the given path with the flatness specified. The internal representation
* needs to keep (a) the flatten path, (b) the normalized length of each
* segment in the path, and (c) the normal of each point of the segment.
*/
public AnimationPathHelper(Path2D path,
BaseTransform transform,
double flatness)
{
Segment moveToSeg = Segment.getZeroSegment();
Segment lastSeg = Segment.getZeroSegment();
float[] coords = new float[6];
for (PathIterator i = path.getPathIterator(transform, (float)flatness); !i.isDone(); i.next()) {
Segment newSeg = null;
int segType = i.currentSegment(coords);
double x = coords[0];
double y = coords[1];
switch (segType) {
case PathIterator.SEG_MOVETO:
moveToSeg = Segment.newMoveTo(x, y, lastSeg.accumLength);
newSeg = moveToSeg;
break;
case PathIterator.SEG_CLOSE:
newSeg = Segment.newClosePath(lastSeg, moveToSeg);
if (newSeg == null) {
// make the last segment to close the path
lastSeg.convertToClosePath(moveToSeg);
}
break;
case PathIterator.SEG_LINETO:
newSeg = Segment.newLineTo(lastSeg, x, y);
break;
}
if (newSeg != null) {
segments.add(newSeg);
lastSeg = newSeg;
}
}
totalLength = lastSeg.accumLength;
}
/**
* Return the position data along this path at the given alpha value.
* alpha - a value within [0, 1] that maps to the begin to end of this
* path.
* requiredNormal - specifys normal information is needed.
* result - returns the computed position if it is not null.
* return value - result if it is not null otherwise create a new Position2D
* object with the computed position information.
*
* Implementation detail:
* The method can do a binary search of the connected segments, possibly
* an array, key on the normalized length of each segment.
*/
public Position2D getPosition2D(double alpha, boolean requiredNormal,
Position2D result) {
double part = totalLength * Math.min(1, Math.max(0, alpha));
int segIdx = findSegment(0, segments.size() - 1, part);
Segment seg = segments.get(segIdx);
double lengthBefore = seg.accumLength - seg.length;
double partLength = part - lengthBefore;
if (result == null) {
result = new Position2D();
}
double ratio = partLength / seg.length;
Segment prevSeg = seg.prevSeg;
result.x = prevSeg.toX + (seg.toX - prevSeg.toX) * ratio;
result.y = prevSeg.toY + (seg.toY - prevSeg.toY) * ratio;
result.rotateAngle = seg.rotateAngle;
// provide smooth rotation on segment bounds
double z = Math.min(SMOOTH_ZONE, seg.length / 2);
if (partLength < z && !prevSeg.isMoveTo) {
//interpolate rotation to previous segment
result.rotateAngle = interpolate(
prevSeg.rotateAngle, seg.rotateAngle,
partLength / z / 2 + 0.5F);
} else {
double dist = seg.length - partLength;
Segment nextSeg = seg.nextSeg;
if (dist < z && nextSeg != null) {
//interpolate rotation to next segment
if (!nextSeg.isMoveTo) {
result.rotateAngle = interpolate(
seg.rotateAngle, nextSeg.rotateAngle,
(z - dist) / z / 2);
}
}
}
return result;
}
/** Interpolates angle according to rate,
* with correct 0->360 and 360->0 transitions
*/
private static double interpolate(double fromAngle, double toAngle, double ratio) {
double delta = toAngle - fromAngle;
if (Math.abs(delta) > 180) {
toAngle += delta > 0 ? -360 : 360;
}
return normalize(fromAngle + ratio * (toAngle - fromAngle));
}
/** Converts angle to range 0-360
*/
private static double normalize(double angle) {
while (angle > 360) {
angle -= 360;
}
while (angle < 0) {
angle += 360;
}
return angle;
}
/**
* Returns the index of the first segment having accumulated length
* from the path beginning, greater than {@code length}
*/
private int findSegment(int begin, int end, double length) {
// check for search termination
if (begin == end) {
// find last non-moveTo segment for given length
return segments.get(begin).isMoveTo && begin > 0
? findSegment(begin - 1, begin - 1, length)
: begin;
}
// otherwise continue binary search
int middle = begin + (end - begin) / 2;
return segments.get(middle).accumLength > length
? findSegment(begin, middle, length)
: findSegment(middle + 1, end, length);
}
private static class Segment {
private static Segment zeroSegment;
boolean isMoveTo;
double length;
// total length from the path's beginning to the end of this segment
double accumLength;
// end point of this segment
double toX;
double toY;
// segment's rotation angle in degrees
double rotateAngle;
Segment prevSeg;
Segment nextSeg;
private Segment(boolean isMoveTo, double toX, double toY,
double length, double lengthBefore, double rotateAngle) {
this.isMoveTo = isMoveTo;
this.toX = toX;
this.toY = toY;
this.length = length;
this.accumLength = lengthBefore + length;
this.rotateAngle = rotateAngle;
}
public static Segment getZeroSegment() {
if (zeroSegment == null) {
zeroSegment = new Segment(true, 0, 0, 0, 0, 0);
}
return zeroSegment;
}
public static Segment newMoveTo(double toX, double toY,
double accumLength) {
return new Segment(true, toX, toY, 0, accumLength, 0);
}
public static Segment newLineTo(Segment fromSeg, double toX, double toY) {
double deltaX = toX - fromSeg.toX;
double deltaY = toY - fromSeg.toY;
double length = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
if ((length >= 1) || fromSeg.isMoveTo) { // filtering out flattening noise
double sign = Math.signum(deltaY == 0 ? deltaX : deltaY);
double angle = (sign * Math.acos(deltaX / length));
angle = normalize(angle / Math.PI * 180);
Segment newSeg = new Segment(false, toX, toY,
length, fromSeg.accumLength, angle);
fromSeg.nextSeg = newSeg;
newSeg.prevSeg = fromSeg;
return newSeg;
}
return null;
}
public static Segment newClosePath(Segment fromSeg, Segment moveToSeg) {
Segment newSeg = newLineTo(fromSeg, moveToSeg.toX, moveToSeg.toY);
if (newSeg != null) {
newSeg.convertToClosePath(moveToSeg);
}
return newSeg;
}
public void convertToClosePath(Segment moveToSeg) {
Segment firstLineToSeg = moveToSeg.nextSeg;
nextSeg = firstLineToSeg;
firstLineToSeg.prevSeg = this;
}
}
}