package com.axe.path; import com.axe.core.Attribute; import com.axe.io.InputModel; import com.axe.io.OutputModel; import com.axe.math.Numbers; import com.axe.math.Scalari; /** * http://www.reddit.com/r/gamedev/comments/1ei88i/very_fast_2d_interpolation/ * * @author Philip Diffenderfer * * @param */ public class KramerPath implements Path { public static final float DEFAULT_LOOSENESS = 0.0575f; protected Attribute[] points; protected Attribute temp0; protected Attribute temp1; protected int depth; protected float looseness; protected boolean loops; protected float roughness; public KramerPath() { } public KramerPath( int depth, boolean loops, Attribute ... points ) { this( depth, loops, DEFAULT_LOOSENESS, 0.0f, points ); } public KramerPath( int depth, boolean loops, float looseness, Attribute ... points ) { this( depth, loops, looseness, 0.0f, points ); } public KramerPath( float roughness, boolean loops, Attribute ... points ) { this( 0, loops, DEFAULT_LOOSENESS, roughness, points ); } public KramerPath( float roughness, boolean loops, float looseness, Attribute ... points ) { this( 0, loops, looseness, roughness, points ); } protected KramerPath( int depth, boolean loops, float looseness, float roughness, Attribute ... points ) { this.depth = depth; this.loops = loops; this.looseness = looseness; this.roughness = roughness; this.points = points; this.temp0 = points[0].create(); this.temp1 = points[0].create(); } @Override public T set(Attribute subject, float delta) { final int n = points.length; final float a = delta * n; final int i = Scalari.clamp( (int)a, 0, n - 1 ); float d = a - i; if (depth != 0) { getPointWithExactDepth( i, d, subject ); } else { getPointWithRoughness( i, d, subject ); } return subject.get(); } public void getPointWithExactDepth( int i, float d, Attribute subject ) { // v0 and v5 are used to calculate the next v1 or v4, at the next level. T v0 = points[ getActualIndex( i - 2 ) ].get(); T v1 = points[ getActualIndex( i - 1 ) ].get(); T v2 = points[ getActualIndex( i ) ].get(); T v3 = points[ getActualIndex( i + 1 ) ].get(); T v4 = points[ getActualIndex( i + 2 ) ].get(); T v5 = points[ getActualIndex( i + 3 ) ].get(); int k = depth; while (--k >= 0) { // Get mid point T mid = getPoint( v1, v2, v3, v4 ); // If the desired point is closer to v2... if (d < 0.5f) { // shift all surrounding points one-level closer to v2 if (k == 0) { v3 = mid; } else { T newEnd = v1; v5 = v4; v4 = v3; v3 = mid; v1 = getPoint( v0, v1, v2, v3 ); v0 = newEnd; } // adjust d so it's between 0.0 an 1.0 d = d * 2.0f; } // else, the desired point is closer to v3... else { // shift all surrounding points one-level closer to v3 if (k == 0) { v2 = mid; } else { T newEnd = v4; v0 = v1; v1 = v2; v2 = mid; v4 = getPoint( v2, v3, v4, v5 ); v5 = newEnd; } // adjust d so it's between 0.0 an 1.0 d = (d - 0.5f) * 2.0f; } } // subject = (v3 - v2) * d + v2 subject.interpolate( v2, v3, d ); } public void getPointWithRoughness( int i, float d, Attribute subject ) { // v0 and v5 are used to calculate the next v1 or v4, at the next level. T v0 = points[ getActualIndex( i - 2 ) ].get(); T v1 = points[ getActualIndex( i - 1 ) ].get(); T v2 = points[ getActualIndex( i ) ].get(); T v3 = points[ getActualIndex( i + 1 ) ].get(); T v4 = points[ getActualIndex( i + 2 ) ].get(); T v5 = points[ getActualIndex( i + 3 ) ].get(); for (;;) { // Get mid point T mid = getPoint( v1, v2, v3, v4 ); // if distance from mid to (v2->v3) is <= roughness, break // calculate the distance between all three points to form a triangle, // with the distances determine the perimeter then area. Use the // area = 0.5 * b * h formula to calculate height. float a = getDistance( v2, v3 ); float b = getDistance( mid, v2 ); float c = getDistance( mid, v3 ); float p = (a + b + c) * 0.5f; float area = Numbers.sqrt( p * (p - a) * (p - b) * (p - c) ); float height = area * 2.0f / a; if (height <= roughness) { break; } // If the desired point is closer to v2... if (d < 0.5f) { // shift all surrounding points one-level closer to v2 T newEnd = v1; v5 = v4; v4 = v3; v3 = mid; v1 = getPoint( v0, v1, v2, v3 ); v0 = newEnd; // adjust d so it's between 0.0 an 1.0 d = d * 2.0f; } // else, the desired point is closer to v3... else { // shift all surrounding points one-level closer to v3 T newEnd = v4; v0 = v1; v1 = v2; v2 = mid; v4 = getPoint( v2, v3, v4, v5 ); v5 = newEnd; // adjust d so it's between 0.0 an 1.0 d = (d - 0.5f) * 2.0f; } } // subject = (v3 - v2) * d + v2 subject.interpolate( v2, v3, d ); } public float getDistance( T a, T b ) { temp0.set( a ); return temp0.distance( b ); } public T getPoint( T v1, T v2, T v3, T v4 ) { // p = (0.5f + looseness) * (v2 + v3) - looseness * (v1 + v4) temp0.set( v2 ); temp0.add( v3, 1f ); temp0.scale( 0.5f + looseness ); temp1.set( v1 ); temp1.add( v4, 1f ); temp0.add( temp1.get(), -looseness ); return temp0.clone(); } public int getActualIndex( int index ) { final int n = points.length; return ( loops ? (index + n) % n : Scalari.clamp( index, 0, n - 1 ) ); } @Override public int getAttributeCount() { return points.length; } @Override public Attribute getAttribute(int index) { return points[ index ]; } @Override public T get(int index) { return points[ index ].get(); } public Attribute[] points() { return points; } @Override public void read( InputModel input ) { depth = input.readInt( "depth" ); loops = input.readBoolean( "loops" ); looseness = input.readFloat( "looseness" ); points = input.readModelArray( "point", "point-type" ); temp0 = points[0].create(); temp1 = points[0].create(); } @Override public void write( OutputModel output ) { output.write( "depth", depth ); output.write( "loops", loops ); output.write( "looseness", looseness ); output.writeModelArray( "point", points, "point-type" ); } }