package com.axe.path; import com.axe.core.Attribute; import com.axe.io.InputModel; import com.axe.io.OutputModel; import com.axe.math.Scalari; 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; public KramerPath() { } public KramerPath( int depth, boolean loops, Attribute ... points ) { this( depth, loops, DEFAULT_LOOSENESS, points ); } public KramerPath( int depth, boolean loops, float looseness, Attribute ... points ) { this.depth = depth; this.loops = loops; this.looseness = looseness; 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; // 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 (int k = 0; k < depth; k++) { // 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 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 ); return subject.get(); } 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" ); } }