package info.u250.c2d.tests.mesh;
import info.u250.c2d.engine.Engine;
import info.u250.c2d.engine.EngineDrive;
import info.u250.c2d.engine.Scene;
import info.u250.c2d.engine.resources.AliasResourceManager;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.glutils.ImmediateModeRenderer20;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
public class FingerSwipeTest extends Engine {
@Override
protected EngineDrive onSetupEngineDrive() {
return new EngineX();
}
@Override
public void dispose () {
super.dispose();
}
private class EngineX implements EngineDrive{
@Override
public void onResourcesRegister(AliasResourceManager<String> reg) {
reg.texture("RES", "data/gradient.png");
}
@Override
public void dispose() {}
@Override
public EngineOptions onSetupEngine() {
final EngineOptions opt = new EngineOptions(new String[]{"data/gradient.png"},800,480);
opt.useGL20 = true;
return opt;
}
@Override
public void onLoadedResourcesCompleted() {
final SwipeHandler swipe = new SwipeHandler(10);;
final Texture tex = Engine.resource("RES");
final ShapeRenderer shapes = Engine.getShapeRenderer();
final SwipeTriStrip tris = new SwipeTriStrip();
//minimum distance between two points
swipe.minDistance = 10;
//minimum distance between first and second point
swipe.initialDistance = 10;
//we will use a texture for the smooth edge, and also for stroke effects
tex.setFilter(TextureFilter.Linear, TextureFilter.Linear);
Engine.setMainScene(new Scene() {
@Override
public void render(float delta) {
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
tex.bind();
tris.endcap = 5f;
//the thickness of the line
tris.thickness = 30f;
//generate the triangle strip from our path
tris.update(swipe.path());
//the vertex color for tinting, i.e. for opacity
tris.color = Color.WHITE;
//render the triangles to the screen
tris.draw(Engine.getDefaultCamera());
drawDebug();
}
//optional debug drawing..
public void drawDebug() {
Array<Vector2> input = swipe.input();
//draw the raw input
shapes.begin(ShapeType.Line);
shapes.setColor(Color.GRAY);
for (int i=0; i<input.size-1; i++) {
Vector2 p = input.get(i);
Vector2 p2 = input.get(i+1);
shapes.line(p.x, p.y, p2.x, p2.y);
}
shapes.end();
//draw the smoothed and simplified path
shapes.begin(ShapeType.Line);
shapes.setColor(Color.RED);
Array<Vector2> out = swipe.path();
for (int i=0; i<out.size-1; i++) {
Vector2 p = out.get(i);
Vector2 p2 = out.get(i+1);
shapes.line(p.x, p.y, p2.x, p2.y);
}
shapes.end();
//render our perpendiculars
shapes.begin(ShapeType.Line);
Vector2 perp = new Vector2();
for (int i=1; i<input.size-1; i++) {
Vector2 p = input.get(i);
Vector2 p2 = input.get(i+1);
shapes.setColor(Color.LIGHT_GRAY);
perp.set(p).sub(p2).nor();
perp.set(perp.y, -perp.x);
perp.scl(10f);
shapes.line(p.x, p.y, p.x+perp.x, p.y+perp.y);
perp.scl(-1f);
shapes.setColor(Color.BLUE);
shapes.line(p.x, p.y, p.x+perp.x, p.y+perp.y);
}
shapes.end();
}
@Override
public InputProcessor getInputProcessor() {
return swipe;
}
@Override
public void update(float delta) {
}
@Override
public void hide() {
}
@Override
public void show() {
}
});
}
}
public static class FixedList<T> extends Array<T> {
/**
* Safely creates a list that is backed by an array which
* can be directly accessed.
*
* @param capacity the fixed-size capacity of this list
* @param type the class type of the elements in this list
*/
public FixedList(int capacity, Class<T> type) {
super(false, capacity, type);
}
/**
* Inserts the item into index zero, shifting all items to the right,
* but without increasing the list's size past its array capacity.
* @param t the element to insert
*/
public void insert(T t) {
T[] items = this.items;
// increase size if we have a new point
size = Math.min(size + 1, items.length);
// shift elements right
for (int i = size - 1; i > 0; i--) {
items[i] = items[i - 1];
}
// insert new item at first index
items[0] = t;
}
}
public static class ResolverRadialChaikin{
private Array<Vector2> tmp = new Array<Vector2>(Vector2.class);
public static int iterations = 2;
public static float simplifyTolerance = 35f;
public void resolve(Array<Vector2> input, Array<Vector2> output) {
output.clear();
if (input.size<=2) { //simple copy
output.addAll(input);
return;
}
//simplify with squared tolerance
if (simplifyTolerance>0 && input.size>3) {
simplify(input, simplifyTolerance * simplifyTolerance, tmp);
input = tmp;
}
//perform smooth operations
if (iterations<=0) { //no smooth, just copy input to output
output.addAll(input);
} else if (iterations==1) { //1 iteration, smooth to output
smooth(input, output);
} else { //multiple iterations.. ping-pong between arrays
int iters = iterations;
//subsequent iterations
do {
smooth(input, output);
tmp.clear();
tmp.addAll(output);
Array<Vector2> old = output;
input = tmp;
output = old;
} while (--iters > 0);
}
}
public static void smooth(Array<Vector2> input, Array<Vector2> output) {
//expected size
output.clear();
output.ensureCapacity(input.size*2);
//first element
output.add(input.get(0));
//average elements
for (int i=0; i<input.size-1; i++) {
Vector2 p0 = input.get(i);
Vector2 p1 = input.get(i+1);
Vector2 Q = new Vector2(0.75f * p0.x + 0.25f * p1.x, 0.75f * p0.y + 0.25f * p1.y);
Vector2 R = new Vector2(0.25f * p0.x + 0.75f * p1.x, 0.25f * p0.y + 0.75f * p1.y);
output.add(Q);
output.add(R);
}
//last element
output.add(input.get(input.size-1));
}
//simple distance-based simplification
//adapted from simplify.js
public static void simplify(Array<Vector2> points, float sqTolerance, Array<Vector2> out) {
int len = points.size;
Vector2 point = new Vector2();
Vector2 prevPoint = points.get(0);
out.clear();
out.add(prevPoint);
for (int i = 1; i < len; i++) {
point = points.get(i);
if (distSq(point, prevPoint) > sqTolerance) {
out.add(point);
prevPoint = point;
}
}
if (!prevPoint.equals(point)) {
out.add(point);
}
}
public static float distSq(Vector2 p1, Vector2 p2) {
float dx = p1.x - p2.x, dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
}
public static class SwipeHandler extends InputAdapter {
private FixedList<Vector2> inputPoints;
/** The pointer associated with this swipe event. */
private int inputPointer = 0;
/** The minimum distance between the first and second point in a drawn line. */
public int initialDistance = 10;
/** The minimum distance between two points in a drawn line (starting at the second point). */
public int minDistance = 20;
private Vector2 lastPoint = new Vector2();
private boolean isDrawing = false;
private ResolverRadialChaikin simplifier = new ResolverRadialChaikin();
private Array<Vector2> simplified;
public SwipeHandler(int maxInputPoints) {
this.inputPoints = new FixedList<Vector2>(maxInputPoints, Vector2.class);
simplified = new Array<Vector2>(true, maxInputPoints, Vector2.class);
resolve(); //copy initial empty list
}
/**
* Returns the fixed list of input points (not simplified).
* @return the list of input points
*/
public Array<Vector2> input() {
return this.inputPoints;
}
/**
* Returns the simplified list of points representing this swipe.
* @return
*/
public Array<Vector2> path() {
return simplified;
}
/**
* If the points are dirty, the line is simplified.
*/
public void resolve() {
simplifier.resolve(inputPoints, simplified);
}
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
if (pointer!=inputPointer)
return false;
isDrawing = true;
//clear points
inputPoints.clear();
//starting point
lastPoint = new Vector2(screenX, Gdx.graphics.getHeight()-screenY);
inputPoints.insert(lastPoint);
resolve();
return true;
}
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
//on release, the line is simplified
resolve();
isDrawing = false;
return false;
}
public boolean touchDragged(int screenX, int screenY, int pointer) {
if (pointer!=inputPointer)
return false;
isDrawing = true;
Vector2 v = new Vector2(screenX, Gdx.graphics.getHeight()-screenY);
//calc length
float dx = v.x - lastPoint.x;
float dy = v.y - lastPoint.y;
float len = (float)Math.sqrt(dx*dx + dy*dy);
//TODO: use minDistanceSq
//if we are under required distance
if (len < minDistance && (inputPoints.size>1 || len<initialDistance))
return false;
//add new point
inputPoints.insert(v);
lastPoint = v;
//simplify our new line
resolve();
return true;
}
public boolean isDrawing() {
return isDrawing;
}
public void setDrawing(boolean isDrawing) {
this.isDrawing = isDrawing;
}
}
public static class SwipeTriStrip {
Array<Vector2> texcoord = new Array<Vector2>();
Array<Vector2> tristrip = new Array<Vector2>();
int batchSize;
Vector2 perp = new Vector2();
public float thickness = 30f;
public float endcap = 8.5f;
public Color color = new Color(Color.WHITE);
ImmediateModeRenderer20 gl20;
public SwipeTriStrip() {
gl20 = new ImmediateModeRenderer20(false, true, 1);
}
public void draw(Camera cam) {
if (tristrip.size<=0)
return;
gl20.begin(cam.combined, GL20.GL_TRIANGLE_STRIP);
for (int i=0; i<tristrip.size; i++) {
if (i==batchSize) {
gl20.end();
gl20.begin(cam.combined, GL20.GL_TRIANGLE_STRIP);
}
Vector2 point = tristrip.get(i);
Vector2 tc = texcoord.get(i);
gl20.color(color.r, color.g, color.b, color.a);
gl20.texCoord(tc.x, 0f);
gl20.vertex(point.x, point.y, 0f);
}
gl20.end();
}
private int generate(Array<Vector2> input, int mult) {
int c = tristrip.size;
if (endcap<=0) {
tristrip.add(input.get(0));
} else {
Vector2 p = input.get(0);
Vector2 p2 = input.get(1);
perp.set(p).sub(p2).scl(endcap);
tristrip.add(new Vector2(p.x+perp.x, p.y+perp.y));
}
texcoord.add(new Vector2(0f, 0f));
for (int i=1; i<input.size-1; i++) {
Vector2 p = input.get(i);
Vector2 p2 = input.get(i+1);
//get direction and normalize it
perp.set(p).sub(p2).nor();
//get perpendicular
perp.set(-perp.y, perp.x);
float thick = thickness * (1f-((i)/(float)(input.size)));
//move outward by thickness
perp.scl(thick/2f);
//decide on which side we are using
perp.scl(mult);
//add the tip of perpendicular
tristrip.add(new Vector2(p.x+perp.x, p.y+perp.y));
//0.0 -> end, transparent
texcoord.add(new Vector2(0f, 0f));
//add the center point
tristrip.add(new Vector2(p.x, p.y));
//1.0 -> center, opaque
texcoord.add(new Vector2(1f, 0f));
}
//final point
if (endcap<=0) {
tristrip.add(input.get(input.size-1));
} else {
Vector2 p = input.get(input.size-2);
Vector2 p2 = input.get(input.size-1);
perp.set(p2).sub(p).scl(endcap);
tristrip.add(new Vector2(p2.x+perp.x, p2.y+perp.y));
}
//end cap is transparent
texcoord.add(new Vector2(0f, 0f));
return tristrip.size-c;
}
public void update(Array<Vector2> input) {
tristrip.clear();
texcoord.clear();
if (input.size<2)
return;
batchSize = generate(input, 1);
generate(input, -1);
}
}
}