com.replaymod.replaystudio.pathing.interpolation.CatmullRomSplineInterpolator.java Source code

Java tutorial

Introduction

Here is the source code for com.replaymod.replaystudio.pathing.interpolation.CatmullRomSplineInterpolator.java

Source

/*
 * This file is part of ReplayStudio, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2016 johni0702 <https://github.com/johni0702>
 * Copyright (c) contributors
 *
 * 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 com.replaymod.replaystudio.pathing.interpolation;

import com.google.common.collect.Iterables;
import com.replaymod.replaystudio.pathing.interpolation.PolynomialSplineInterpolator.Polynomial;
import com.replaymod.replaystudio.pathing.path.Keyframe;
import com.replaymod.replaystudio.pathing.path.PathSegment;
import com.replaymod.replaystudio.pathing.property.Property;
import com.replaymod.replaystudio.pathing.property.PropertyPart;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.*;

@RequiredArgsConstructor
public class CatmullRomSplineInterpolator extends AbstractInterpolator {

    @Getter
    private final double alpha;

    private Map<PropertyPart<?>, Polynomial[]> cubicPolynomials = new HashMap<>();
    private Map<Property<?>, Set<Keyframe>> framesToProperty = new HashMap<>();

    private void addToMap(Property property, Keyframe keyframe) {
        Set<Keyframe> set = framesToProperty.get(property);
        if (set == null) {
            framesToProperty.put(property, set = new LinkedHashSet<>());
        }
        set.add(keyframe);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Map<PropertyPart, InterpolationParameters> bakeInterpolation(
            Map<PropertyPart, InterpolationParameters> parameters) {
        framesToProperty.clear();
        for (PathSegment segment : getSegments()) {
            for (Property property : getKeyframeProperties()) {
                if (segment.getStartKeyframe().getValue(property).isPresent()) {
                    addToMap(property, segment.getStartKeyframe());
                }
                if (segment.getEndKeyframe().getValue(property).isPresent()) {
                    addToMap(property, segment.getEndKeyframe());
                }
            }
        }

        calcPolynomials();

        Map<PropertyPart, InterpolationParameters> lastParameters = new HashMap<>();

        for (Property<?> property : getKeyframeProperties()) {
            for (PropertyPart<?> part : property.getParts()) {
                Polynomial[] polynomials = cubicPolynomials.get(part);
                Polynomial last = polynomials[polynomials.length - 1];

                double value = last.eval(1);
                double velocity = last.derivative().eval(1);
                double acceleration = last.derivative().derivative().eval(1);

                lastParameters.put(part, new InterpolationParameters(value, velocity, acceleration));
            }
        }

        return lastParameters;
    }

    // http://steve.hollasch.net/cgindex/curves/catmull-rom.html
    protected void calcPolynomials() {
        for (Map.Entry<Property<?>, Set<Keyframe>> e : framesToProperty.entrySet()) {
            Property<?> property = e.getKey();
            Set<Keyframe> keyframes = e.getValue();

            for (PropertyPart<?> part : property.getParts()) {
                if (!part.isInterpolatable())
                    continue;

                Polynomial[] polynomials = new Polynomial[keyframes.size() - 1];

                for (int i = 0; i < keyframes.size() - 1; i++) {
                    Keyframe k0, k1, k2, k3;

                    k1 = Iterables.get(keyframes, i);
                    k2 = Iterables.get(keyframes, i + 1);

                    if (i > 0) {
                        k0 = Iterables.get(keyframes, i - 1);
                    } else {
                        k0 = k1;
                    }

                    if (i < keyframes.size() - 2) {
                        k3 = Iterables.get(keyframes, i + 2);
                    } else {
                        k3 = k2;
                    }

                    double p0 = getValueAsDouble(k0, part);
                    double p1 = getValueAsDouble(k1, part);
                    double p2 = getValueAsDouble(k2, part);
                    double p3 = getValueAsDouble(k3, part);

                    double t0 = alpha * (p2 - p0);
                    double t1 = alpha * (p3 - p1);

                    double[] c = new double[] { 2 * p1 - 2 * p2 + t0 + t1, -3 * p1 + 3 * p2 - 2 * t0 - t1, t0, p1 };

                    polynomials[i] = new Polynomial(c);
                }

                cubicPolynomials.put(part, polynomials);
            }
        }
    }

    // Helper method because generics cannot be defined on blocks
    private <T> double getValueAsDouble(Keyframe keyframe, PropertyPart<T> part) {
        return part.toDouble(keyframe.getValue(part.getProperty()).get());
    }

    @Override
    public <T> Optional<T> getValue(Property<T> property, long time) {
        Set<Keyframe> kfSet = framesToProperty.get(property);
        if (kfSet == null) {
            return Optional.empty();
        }

        T valueBefore = null;
        long timeBefore = -1, timeAfter = -1;
        int index = 0;
        int i = 0;
        for (Keyframe keyframe : kfSet) {
            if (keyframe.getTime() == time) {
                return keyframe.getValue(property);
            } else if (keyframe.getTime() < time) {
                index = i;
                timeBefore = keyframe.getTime();
                valueBefore = keyframe.getValue(property).get();
            } else if (keyframe.getTime() > time) {
                timeAfter = keyframe.getTime();
                break;
            }
            i++;
        }

        if (timeBefore == -1 || timeAfter == -1) {
            return Optional.empty();
        }

        double fraction = (time - timeBefore) / (double) (timeAfter - timeBefore);

        T interpolated = valueBefore;

        for (PropertyPart<T> part : property.getParts()) {
            if (!part.isInterpolatable())
                continue;

            Polynomial[] polynomials = cubicPolynomials.get(part);
            interpolated = part.fromDouble(interpolated, polynomials[index].eval(fraction));
        }
        return Optional.of(interpolated);
    }

}