/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2015 JaamSim Software Inc.
*
* 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 com.jaamsim.render;
import java.util.ArrayList;
public class AnimCurve {
// A holder for collada curve information
public static class ColCurve {
public int numComponents;
public double[] in;
public double[][] out;
public double[][] inTan;
public double[][] outTan;
public String[] interp;
}
public int numComponents;
public double[] times;
public double[][] values;
// Cache the last value as this may be requested several times in a row
private double[] lastVal;
private double lastTime;
// Animation curves represent up to 4 values at a given time
private static class CurveVal {
public double time;
public double[] vals;
}
private static CurveVal solveBezier(double s, double[] p0, double[] p1, double[] c0, double[] c1, int numComponents) {
double oneMinS = 1 - s;
double coeffP0 = oneMinS*oneMinS*oneMinS;
double coeffC0 = 3*s*oneMinS*oneMinS;
double coeffC1 = 3*s*s*oneMinS;
double coeffP1 = s*s*s;
CurveVal ret = new CurveVal();
ret.time = (coeffP0*p0[0] + coeffC0*c0[0] + coeffC1*c1[0] + coeffP1*p1[0]);
ret.vals = new double[numComponents];
for (int compInd = 0; compInd < numComponents; ++compInd) {
double v = coeffP0*p0[compInd+1] +
coeffC0*c0[compInd+1] +
coeffC1*c1[compInd+1] +
coeffP1*p1[compInd+1];
ret.vals[compInd] = v;
}
return ret;
}
// Add bezier keys to allow for smooth interpolation on the parameter range (s0, s1)
// This does not insert the end points
public static void interpBezier(double s0, double s1, double[] p0, double[] p1, double[] c0, double[] c1, int numComponents, ArrayList<Double> ts, ArrayList<double[]> vs) {
if ((s1-s0)<= 1/16.0) {
return; // Do not divide a curve into more than 16 parts
}
double halfS = (s0 + s1)/2.0;
CurveVal startVal = solveBezier(s0, p0, p1, c0, c1, numComponents);
CurveVal endVal = solveBezier(s1, p0, p1, c0, c1, numComponents);
// Determine if this midpoint has value
boolean recurse = false;
for (double samp = 0.25; samp < 1; samp+=0.25) {
double sampS = s0 + samp*(s1-s0);
CurveVal sampVal = solveBezier(sampS, p0, p1, c0, c1, numComponents);
for (int i = 0; i < numComponents; ++i) {
double slope = (endVal.vals[i] - startVal.vals[i])/(endVal.time - startVal.time);
double sampSlope = (sampVal.vals[i] - startVal.vals[i])/(sampVal.time - startVal.time);
if (slope == 0) {
if (sampSlope != 0)
recurse = true;
continue;
}
double scaledSlope = sampSlope/slope;
if (Math.abs(scaledSlope-1) > 0.05) {
recurse = true;
}
}
}
if (!recurse) {
return;
}
// Otherwise recurse
interpBezier(s0, halfS, p0, p1, c0, c1, numComponents, ts, vs);
CurveVal midVal = solveBezier(halfS, p0, p1, c0, c1, numComponents);
ts.add(midVal.time);
vs.add(midVal.vals);
interpBezier(halfS, s1, p0, p1, c0, c1, numComponents, ts, vs);
}
public static AnimCurve buildFromColCurve(ColCurve colData) {
ArrayList<Double> ts = new ArrayList<>();
ArrayList<double[]> vs = new ArrayList<>();
ts.add(colData.in[0]);
vs.add(colData.out[0]);
for (int i = 0; i < colData.in.length-1; ++i) {
String interp = colData.interp[i];
if (interp.equals("LINEAR")) {
ts.add(colData.in[i+1]);
vs.add(colData.out[i+1]);
continue;
}
if (interp.equals("BEZIER")) {
if (colData.inTan == null) {
throw new RenderException("Missing IN_TANGENT component in collada bezier animation");
}
if (colData.outTan == null) {
throw new RenderException("Missing OUT_TANGENT component in collada bezier animation");
}
double[] p0 = new double[colData.numComponents+1];
double[] p1 = new double[colData.numComponents+1];
// Due to collada weirdness we need to reassemble the basic bezier position vectors
p0[0] = colData.in[i ];
p1[0] = colData.in[i+1];
for (int compInd = 0; compInd < colData.numComponents; ++compInd) {
p0[compInd+1] = colData.out[i ][compInd];
p1[compInd+1] = colData.out[i+1][compInd];
}
double[] c0 = colData.outTan[i];
double[] c1 = colData.inTan[i+1];
interpBezier(0, 1, p0, p1, c0, c1, colData.numComponents, ts, vs);
// Add the end point
CurveVal val = solveBezier(1, p0, p1, c0, c1, colData.numComponents);
ts.add(val.time);
vs.add(val.vals);
continue;
}
// Currently unsupported interpolation
throw new RenderException(String.format("Unknown animation interpolation:%s", colData.interp[i]));
}
AnimCurve ret = new AnimCurve();
ret.numComponents = colData.numComponents;
ret.times = new double[ts.size()];
ret.values = new double[vs.size()][];
assert(ts.size() == vs.size());
for (int i = 0; i < ts.size(); ++i) {
ret.times[i] = ts.get(i);
ret.values[i] = vs.get(i);
}
return ret;
}
private AnimCurve() {
}
public double[] getValueForTime(double time) {
if (lastVal != null && lastTime == time) {
return lastVal;
}
// Check if we are past the ends
if (time <= times[0]) {
return values[0];
}
if (time >= times[times.length-1]) {
return values[values.length -1];
}
// Basic binary search for appropriate segment
int start = 0;
int end = times.length;
while ((end - start) > 1) {
int test = (start + end)/2;
double samp = times[test];
if (samp == time) // perfect match
return values[test];
if (samp < time) {
start = test;
} else {
end = test;
}
}
assert(end - start == 1);
// Linearly interpolate on the segment
double t0 = times[start];
double t1 = times[end];
assert(time >= t0);
assert(time <= t1);
double[] v0 = values[start];
double[] v1 = values[end];
double scale = (time - t0)/(t1-t0);
double[] ret = new double[v0.length];
for (int i = 0; i < ret.length; ++i) {
ret[i] = v0[i]*(1-scale) + v1[i]*scale;
}
lastTime = time;
lastVal = ret;
return ret;
}
}