Created
January 27, 2015 22:41
-
-
Save JavadocMD/9be3b99b702c28e8002a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.javadocmd.drawing; | |
import com.badlogic.gdx.ApplicationAdapter; | |
import com.badlogic.gdx.Gdx; | |
import com.badlogic.gdx.Input; | |
import com.badlogic.gdx.InputAdapter; | |
import com.badlogic.gdx.files.FileHandle; | |
import com.badlogic.gdx.graphics.Color; | |
import com.badlogic.gdx.graphics.GL20; | |
import com.badlogic.gdx.graphics.Pixmap; | |
import com.badlogic.gdx.graphics.Pixmap.Blending; | |
import com.badlogic.gdx.graphics.Pixmap.Format; | |
import com.badlogic.gdx.graphics.Texture; | |
import com.badlogic.gdx.graphics.g2d.SpriteBatch; | |
import com.badlogic.gdx.graphics.glutils.ShaderProgram; | |
import com.badlogic.gdx.math.Vector2; | |
import com.badlogic.gdx.utils.Disposable; | |
/** | |
* A very simple game where an image is initially hidden (transparent). The | |
* image is revealed by 'scribbling' over the window with the mouse. | |
* | |
* Demonstrates 1) using a Pixmap to generate a texture dynamically, and 2) | |
* using a texture as a mask via a custom shader. | |
* | |
* @author Tyler Coles | |
*/ | |
public class DrawingGame extends ApplicationAdapter { | |
private SpriteBatch batch; | |
private ShaderProgram shader; | |
private Texture outline, color; | |
private FileHandle vertexShader, fragmentShader; | |
private DrawablePixmap drawable; | |
@Override | |
public void create() { | |
/* Some regular textures to draw on the scene. */ | |
outline = new Texture("smiley-outline.png"); | |
color = new Texture("smiley-color.png"); | |
/* I like to keep my shader programs as text files in the assets | |
* directory rather than dealing with horrid Java string formatting. */ | |
vertexShader = Gdx.files.internal("vertex.glsl"); | |
fragmentShader = Gdx.files.internal("fragment.glsl"); | |
/* Bonus: you can set `pedantic = false` while tinkering with your | |
* shaders. This will stop it from crashing if you have unused variables | |
* and so on. */ | |
// ShaderProgram.pedantic = false; | |
/* Construct our shader program. Spit out a log and quit if the shaders | |
* fail to compile. */ | |
shader = new ShaderProgram(vertexShader, fragmentShader); | |
if (!shader.isCompiled()) { | |
Gdx.app.log("Shader", shader.getLog()); | |
Gdx.app.exit(); | |
} | |
/* Construct a simple SpriteBatch using our shader program. */ | |
batch = new SpriteBatch(); | |
batch.setShader(shader); | |
/* Tell our shader that u_texture will be in the TEXTURE0 spot and | |
* u_mask will be in the TEXTURE1 spot. We can set these now since | |
* they'll never change; we don't have to send them every render frame. */ | |
shader.begin(); | |
shader.setUniformi("u_texture", 0); | |
shader.setUniformi("u_mask", 1); | |
shader.end(); | |
/* Pixmap blending can result result in some funky looking lines when | |
* drawing. You may need to disable it. */ | |
Pixmap.setBlending(Blending.None); | |
/* Construct our DrawablePixmap (custom class, defined below) with a | |
* Pixmap that is the dimensions of our screen. Alpha format is chosen | |
* because we are just using it as a mask and don't care about RGB color | |
* information. This will require less memory. */ | |
drawable = new DrawablePixmap(new Pixmap(Gdx.graphics.getWidth(), | |
Gdx.graphics.getHeight(), Format.Alpha), 1); | |
Gdx.input.setInputProcessor(new DrawingInput()); | |
} | |
@Override | |
public void render() { | |
Gdx.gl.glClearColor(1, 1, 1, 1); | |
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); | |
/* Update the mask texture (only if necessary). */ | |
drawable.update(); | |
/* Color and outline are drawn separately here, but only to demonstrate | |
* this technique supports multiple images in a batch. */ | |
batch.begin(); | |
batch.draw(color, 0, 0); | |
batch.draw(outline, 0, 0); | |
batch.end(); | |
} | |
@Override | |
public void dispose() { | |
drawable.dispose(); | |
outline.dispose(); | |
color.dispose(); | |
batch.dispose(); | |
} | |
/** | |
* Nested (static) class to provide a nice abstraction over Pixmap, exposing | |
* only the draw calls we want and handling some of the logic for smoothed | |
* (linear interpolated, aka 'lerped') drawing. This will become the 'owner' | |
* of the underlying pixmap, so it will need to be disposed. | |
*/ | |
private static class DrawablePixmap implements Disposable { | |
private final int brushSize = 10; | |
private final Color clearColor = new Color(0, 0, 0, 0); | |
private final Color drawColor = new Color(1, 1, 1, 1); | |
private Pixmap pixmap; | |
private Texture texture; | |
private boolean dirty; | |
public DrawablePixmap(Pixmap pixmap, int textureBinding) { | |
this.pixmap = pixmap; | |
pixmap.setColor(drawColor); | |
/* Create a texture which we'll update from the pixmap. */ | |
this.texture = new Texture(pixmap); | |
this.dirty = false; | |
/* Bind the mask texture to TEXTURE<N> (TEXTURE1 for our purposes), | |
* which also sets the currently active texture unit. */ | |
this.texture.bind(textureBinding); | |
/* However SpriteBatch will auto-bind to the current active texture, | |
* so we must now reset it to TEXTURE0 or else our mask will be | |
* overwritten. */ | |
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0); | |
} | |
/** Write the pixmap onto the texture if the pixmap has changed. */ | |
public void update() { | |
if (dirty) { | |
texture.draw(pixmap, 0, 0); | |
dirty = false; | |
} | |
} | |
public void clear() { | |
pixmap.setColor(clearColor); | |
pixmap.fill(); | |
pixmap.setColor(drawColor); | |
dirty = true; | |
} | |
private void drawDot(Vector2 spot) { | |
pixmap.fillCircle((int) spot.x, (int) spot.y, brushSize); | |
} | |
public void draw(Vector2 spot) { | |
drawDot(spot); | |
dirty = true; | |
} | |
public void drawLerped(Vector2 from, Vector2 to) { | |
float dist = to.dst(from); | |
/* Calc an alpha step to put one dot roughly every 1/8 of the brush | |
* radius. 1/8 is arbitrary, but the results are fairly nice. */ | |
float alphaStep = brushSize / (8f * dist); | |
for (float a = 0; a < 1f; a += alphaStep) { | |
Vector2 lerped = from.lerp(to, a); | |
drawDot(lerped); | |
} | |
drawDot(to); | |
dirty = true; | |
} | |
@Override | |
public void dispose() { | |
texture.dispose(); | |
pixmap.dispose(); | |
} | |
} | |
/** | |
* Inner (non-static) class to handle mouse and keyboard events. Mostly we | |
* want to pass on appropriate draw calls to our DrawablePixmap and this | |
* means keeping track of some state (last coordinates drawn and whether or | |
* not the left mouse button is pressed) to handle smooth drawing while | |
* dragging the mouse. | |
*/ | |
private class DrawingInput extends InputAdapter { | |
private Vector2 last = null; | |
private boolean leftDown = false; | |
@Override | |
public boolean touchDown(int screenX, int screenY, int pointer, | |
int button) { | |
if (button == Input.Buttons.LEFT) { | |
Vector2 curr = new Vector2(screenX, screenY); | |
drawable.draw(curr); | |
last = curr; | |
leftDown = true; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
@Override | |
public boolean touchDragged(int screenX, int screenY, int pointer) { | |
if (leftDown) { | |
Vector2 curr = new Vector2(screenX, screenY); | |
drawable.drawLerped(last, curr); | |
last = curr; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
@Override | |
public boolean touchUp(int screenX, int screenY, int pointer, int button) { | |
if (button == Input.Buttons.LEFT) { | |
drawable.draw(new Vector2(screenX, screenY)); | |
last = null; | |
leftDown = false; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
@Override | |
public boolean keyDown(int keycode) { | |
switch (keycode) { | |
case Input.Keys.ESCAPE: | |
case Input.Keys.F5: | |
return true; | |
default: | |
return false; | |
} | |
} | |
@Override | |
public boolean keyUp(int keycode) { | |
switch (keycode) { | |
case Input.Keys.ESCAPE: | |
Gdx.app.exit(); | |
return true; | |
case Input.Keys.F5: | |
drawable.clear(); | |
return true; | |
default: | |
return false; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment