All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robokind.api.animation.PathInterpolator Maven / Gradle / Ivy

/*
 * Copyright 2011 Hanson Robokind LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.robokind.api.animation;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.robokind.api.interpolation.Interpolator;
import org.robokind.api.interpolation.InterpolatorDirectory;
import org.robokind.api.interpolation.InterpolatorFactory;
import org.robokind.api.common.utils.Utils;
import org.robokind.api.common.config.VersionProperty;

/**
 * Wraps an Interpolator to provide certain guarantees for animating.
 * The PathInterpolator ensures that all Control points are ordered by
 * X-values.  It also ensures that the X-values of interpolated points are
 * non-decreasing.
 * The underlying Interpolator type can be changed using either the Class or
 * full class name of the desired interpolator.
 *
 * @author Matthew Stevenson 
 */
public class PathInterpolator implements Interpolator {
    InterpolatorFactory myFactory;
    private Interpolator myInterpolator;
    /**
     * A sorted List of the points' x-values.
     */
    protected List myXVals;
    private List myInterpolatedPoints;

    PathInterpolator(){}
    /**
     * Creates an empty AnimationInterpolater with an Interpolator from the
     * given InterpolatorFactory.
     *
     * @param factory the given InterpolatorFactor
     */
    public PathInterpolator(InterpolatorFactory factory){
        myFactory = factory != null ? factory : InterpolatorDirectory.instance().getDefaultFactory();
        myInterpolator = myFactory.getValue();
        if(myInterpolator instanceof PathInterpolator){
            throw new IllegalArgumentException("Unable to set interpolator to PathInterpolator");
        }
        myXVals = new ArrayList();
        myInterpolatedPoints = new ArrayList();
    }
    /**
     * Changes underlying interpolator to the given type.
     *
     * @param factory a factory for creating the underlying interpolator
     */
    public void setInterpolatorFactory(InterpolatorFactory factory){
        if(factory == null){
            return;
        }
        myFactory = factory;
        Interpolator i = myFactory.getValue();
        i.addPoints(myInterpolator.getControlPoints());
        myInterpolator = i;
        interpolate();
    }
    /**
     * Returns the factory for the underlying Interpolator.
     *
     * @return the factory for the underlying Interpolator
     */
    public InterpolatorFactory getInterpolatorFactory(){
        return myFactory;
    }

    /**
     * Adds a point with the given coordinates.  Ensures points are sorted by
     * X-value.
     *
     * @param x time in milliseconds
     * @param y servo position (0 <= y <= 1)
     * @return the Point2D that was added
     */
    @Override
    public Point2D addPoint(double x, double y) {
        y = Math.max(0.0, Math.min(y, 1.0));
        int i = Collections.binarySearch(myXVals, x);
        i = i<0 ? -(i+1) :  i;
        myXVals.add(i, x);
        Point2D p = myInterpolator.insertPoint(i, x, y);
        return p;
    }

    /**
     * Adds given points. Ensures points are sorted by X-value.
     *
     * @param points points to add
     */
    @Override
    public void addPoints(List points) {
        for(Point2D p : points){
            addPoint(p.getX(), p.getY());
        }
    }

    /**
     * Updates the point at index i. Ensures points are sorted by X-value.
     * Removes the point at i, then adds a new point with given values.
     *
     * @param i index of point to update
     * @param x new x value
     * @param y new y value
     * @return point with new values
     */
    @Override
    public Point2D setPoint(int i, double x, double y) {
        myInterpolator.removePoint(i);
        myXVals.remove(i);
        return addPoint(x, y);
    }

    /**
     * Removes given point.
     *
     * @param p point to remove
     */
    @Override
    public void removePoint(Point2D p) {
        removePoint(myXVals.indexOf(p.getX()));
    }

    /**
     * Remove point at given index.
     *
     * @param i index of point to remove
     * @return removed point
     */
    @Override
    public Point2D removePoint(int i) {
        myXVals.remove(i);
        return myInterpolator.removePoint(i);
    }

    /**
     * Returns the control points for the interpolator.
     *
     * @return list of points which define the interpolation
     */
    @Override
    public List getControlPoints() {
        return myInterpolator.getControlPoints();
    }

    /**
     * Returns the positions interpolated from the control points.
     *
     * @return list of interpolated points
     */
    @Override
    public List getInterpolatedPoints(){
        interpolate();
        return myInterpolatedPoints;
    }

    /**
     * Iterates through the underlying interpolated points
     * and ensures that the time is always increasing.
     * Some IInterpolators, such as the CSpline, are not functions of time
     * i.e. f(x), and as such may have multiple positions for a given time.
     *
     * @return true if interpolation has changed since the interpolated points
     * have last been accessed, otherwise returns false
     */
    protected boolean interpolate(){
        if(!myInterpolator.interpolationChanged()){
            return false;
        }
        List points = myInterpolator.getInterpolatedPoints();
        myInterpolatedPoints = new ArrayList();
        if(points.isEmpty()){
            return true;
        }
        Iterator pIt = points.iterator();
        Point2D prev = pIt.next();
        myInterpolatedPoints.add(prev);
        while(pIt.hasNext()){
            Point2D p = pIt.next();
            if(p.getX() <= prev.getX()){
                continue;
            }
            myInterpolatedPoints.add(new Point2D.Double(p.getX(), Math.max(0.0, Math.min(p.getY(), 1.0))));
            prev = p;
        }
        return true;
    }
    /**
     * Returns if the control points have been modified and needs to be
     * interpolated.
     *
     * @return true if interpolation is needed, otherwise false
     */
    @Override
    public boolean interpolationChanged(){
        return myInterpolator.interpolationChanged();
    }

    /**
     * Checks if values of another interpolator overlap this one.
     *
     * @param b interpolator to check
     * @return true if overlap otherwise false
     */
    public boolean overlaps(PathInterpolator b){
        if(myXVals.isEmpty() || b.myXVals.isEmpty()){
            return false;
        }
        double sA = myXVals.get(0);
        double eA = myXVals.get(myXVals.size()-1);
        double sB = b.myXVals.get(0);
        double eB = b.myXVals.get(b.myXVals.size()-1);
        return (sA >= sB && sA <= eB) || (sB >= sA && sB <= eA);
    }

    /**
     * Adds a point with the given coordinates.  Ensures points are sorted by
     * X-value.
     * @param i ignored
     * @param x time in milliseconds
     * @param y servo position (0 <= y <= 1)
     * @return Point2D inserted
     */
    @Override
    public Point2D insertPoint(int i, double x, double y) {
        return addPoint(x, y);
    }
    /**
     * Adds points with the given coordinates.  Ensures points are sorted by
     * X-value.
     * @param i ignored
     * @param points List of Points to add
     */
    @Override
    public void addPoints(int i, List points) {
        addPoints(points);
    }
    /**
     * Removes all control points from the MotionPath.
     */
    @Override
    public void clear() {
        myInterpolator.clear();
        myXVals.clear();
        myInterpolatedPoints.clear();
    }

    /**
     * WARNING! This method bypasses the sorting!  You must ensure all points
     * are sorted after using this method.
     * @param i
     * @param x
     * @param y
     */
    protected void moveControlPoint(int i, double x, double y){
        if(i<0 || i>= myXVals.size()){
            return;
        }
        x = Math.max(x, 0);
        y = Utils.bound(y, 0.0, 1.0);
        myXVals.set(i, x);
        myInterpolator.setPoint(i, x, y);
    }

    /**
     * Returns the version of the underlying Interpolator.
     * @return the version of the underlying Interpolator
     */
    @Override
    public VersionProperty getInterpolatorVersion(){
        return myInterpolator.getInterpolatorVersion();
    }
    
    @Override
    public boolean touchesControlPoints(){
        return myInterpolator.touchesControlPoints();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final PathInterpolator other = (PathInterpolator) obj;
        if (this.myInterpolator != other.myInterpolator && (this.myInterpolator == null || !this.myInterpolator.equals(other.myInterpolator))) {
            return false;
        }
        if (this.myXVals != other.myXVals && (this.myXVals == null || !this.myXVals.equals(other.myXVals))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + (this.myInterpolator != null ? this.myInterpolator.hashCode() : 0);
        hash = 79 * hash + (this.myXVals != null ? this.myXVals.hashCode() : 0);
        return hash;
    }
    
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy