/******************************************************************************* * Copyright 2011 See AUTHORS file. * * 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.badlogic.gdx.math; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * Encapsulates a catmull rom spline with n control points, n >= 4. For more information on this type of spline see * http://www.mvps.org/directx/articles/catmull/. * * @author badlogicgames@gmail.com */ public class CatmullRomSpline implements Serializable { private static final long serialVersionUID = -3290464799289771451L; private List<Vector3> controlPoints = new ArrayList<Vector3>(); Vector3 T1 = new Vector3(); Vector3 T2 = new Vector3(); /** * Adds a new control point * * @param point * the point */ public void add(Vector3 point) { controlPoints.add(point); } /** @return all control points */ public List<Vector3> getControlPoints() { return controlPoints; } /** * Returns a path, between every two control points numPoints are generated and the control points themselves are * added too. The first and the last controlpoint are omitted. if there's less than 4 controlpoints an empty path is * returned. * * @param numPoints * number of points returned for a segment * @return the path */ public List<Vector3> getPath(int numPoints) { ArrayList<Vector3> points = new ArrayList<Vector3>(); if (controlPoints.size() < 4) return points; Vector3 T1 = new Vector3(); Vector3 T2 = new Vector3(); for (int i = 1; i <= controlPoints.size() - 3; i++) { points.add(controlPoints.get(i)); float increment = 1.0f / (numPoints + 1); float t = increment; T1.set(controlPoints.get(i + 1)).sub(controlPoints.get(i - 1)).mul(0.5f); T2.set(controlPoints.get(i + 2)).sub(controlPoints.get(i)).mul(0.5f); for (int j = 0; j < numPoints; j++) { float h1 = 2 * t * t * t - 3 * t * t + 1; // calculate basis // function 1 float h2 = -2 * t * t * t + 3 * t * t; // calculate basis // function 2 float h3 = t * t * t - 2 * t * t + t; // calculate basis // function 3 float h4 = t * t * t - t * t; // calculate basis function 4 Vector3 point = new Vector3(controlPoints.get(i)).mul(h1); point.add(controlPoints.get(i + 1).tmp().mul(h2)); point.add(T1.tmp().mul(h3)); point.add(T2.tmp().mul(h4)); points.add(point); t += increment; } } if (controlPoints.size() >= 4) points.add(controlPoints.get(controlPoints.size() - 2)); return points; } /** * Returns a path, between every two control points numPoints are generated and the control points themselves are * added too. The first and the last controlpoint are omitted. if there's less than 4 controlpoints an empty path is * returned. * * @param points * the array of Vector3 instances to store the path in * @param numPoints * number of points returned for a segment */ public void getPath(Vector3[] points, int numPoints) { int idx = 0; if (controlPoints.size() < 4) return; for (int i = 1; i <= controlPoints.size() - 3; i++) { points[idx++].set(controlPoints.get(i)); float increment = 1.0f / (numPoints + 1); float t = increment; T1.set(controlPoints.get(i + 1)).sub(controlPoints.get(i - 1)).mul(0.5f); T2.set(controlPoints.get(i + 2)).sub(controlPoints.get(i)).mul(0.5f); for (int j = 0; j < numPoints; j++) { float h1 = 2 * t * t * t - 3 * t * t + 1; // calculate basis // function 1 float h2 = -2 * t * t * t + 3 * t * t; // calculate basis // function 2 float h3 = t * t * t - 2 * t * t + t; // calculate basis // function 3 float h4 = t * t * t - t * t; // calculate basis function 4 Vector3 point = points[idx++].set(controlPoints.get(i)).mul(h1); point.add(controlPoints.get(i + 1).tmp().mul(h2)); point.add(T1.tmp().mul(h3)); point.add(T2.tmp().mul(h4)); t += increment; } } points[idx].set(controlPoints.get(controlPoints.size() - 2)); } /** * Returns all tangents for the points in a path. Same semantics as getPath. * * @param numPoints * number of points returned for a segment * @return the tangents of the points in the path */ public List<Vector3> getTangents(int numPoints) { ArrayList<Vector3> tangents = new ArrayList<Vector3>(); if (controlPoints.size() < 4) return tangents; Vector3 T1 = new Vector3(); Vector3 T2 = new Vector3(); for (int i = 1; i <= controlPoints.size() - 3; i++) { float increment = 1.0f / (numPoints + 1); float t = increment; T1.set(controlPoints.get(i + 1)).sub(controlPoints.get(i - 1)).mul(0.5f); T2.set(controlPoints.get(i + 2)).sub(controlPoints.get(i)).mul(0.5f); tangents.add(new Vector3(T1).nor()); for (int j = 0; j < numPoints; j++) { float h1 = 6 * t * t - 6 * t; // calculate basis function 1 float h2 = -6 * t * t + 6 * t; // calculate basis function 2 float h3 = 3 * t * t - 4 * t + 1; // calculate basis function 3 float h4 = 3 * t * t - 2 * t; // calculate basis function 4 Vector3 point = new Vector3(controlPoints.get(i)).mul(h1); point.add(controlPoints.get(i + 1).tmp().mul(h2)); point.add(T1.tmp().mul(h3)); point.add(T2.tmp().mul(h4)); tangents.add(point.nor()); t += increment; } } if (controlPoints.size() >= 4) tangents.add(T1.set(controlPoints.get(controlPoints.size() - 1)) .sub(controlPoints.get(controlPoints.size() - 3)).mul(0.5f).cpy().nor()); return tangents; } /** * Returns all tangent's normals in 2D space for the points in a path. The controlpoints have to lie in the x/y * plane for this to work. Same semantics as getPath. * * @param numPoints * number of points returned for a segment * @return the tangents of the points in the path */ public List<Vector3> getTangentNormals2D(int numPoints) { ArrayList<Vector3> tangents = new ArrayList<Vector3>(); if (controlPoints.size() < 4) return tangents; Vector3 T1 = new Vector3(); Vector3 T2 = new Vector3(); for (int i = 1; i <= controlPoints.size() - 3; i++) { float increment = 1.0f / (numPoints + 1); float t = increment; T1.set(controlPoints.get(i + 1)).sub(controlPoints.get(i - 1)).mul(0.5f); T2.set(controlPoints.get(i + 2)).sub(controlPoints.get(i)).mul(0.5f); Vector3 normal = new Vector3(T1).nor(); float x = normal.x; normal.x = normal.y; normal.y = -x; tangents.add(normal); for (int j = 0; j < numPoints; j++) { float h1 = 6 * t * t - 6 * t; // calculate basis function 1 float h2 = -6 * t * t + 6 * t; // calculate basis function 2 float h3 = 3 * t * t - 4 * t + 1; // calculate basis function 3 float h4 = 3 * t * t - 2 * t; // calculate basis function 4 Vector3 point = new Vector3(controlPoints.get(i)).mul(h1); point.add(controlPoints.get(i + 1).tmp().mul(h2)); point.add(T1.tmp().mul(h3)); point.add(T2.tmp().mul(h4)); point.nor(); x = point.x; point.x = point.y; point.y = -x; tangents.add(point); t += increment; } } return tangents; } /** * Returns the tangent's normals using the tangent and provided up vector doing a cross product. * * @param numPoints * number of points per segment * @param up * up vector * @return a list of tangent normals */ public List<Vector3> getTangentNormals(int numPoints, Vector3 up) { List<Vector3> tangents = getTangents(numPoints); ArrayList<Vector3> normals = new ArrayList<Vector3>(); for (Vector3 tangent : tangents) normals.add(new Vector3(tangent).crs(up).nor()); return normals; } public List<Vector3> getTangentNormals(int numPoints, List<Vector3> up) { List<Vector3> tangents = getTangents(numPoints); ArrayList<Vector3> normals = new ArrayList<Vector3>(); int i = 0; for (Vector3 tangent : tangents) normals.add(new Vector3(tangent).crs(up.get(i++)).nor()); return normals; } }