Skip to content

Instantly share code, notes, and snippets.

@trentbrooks
Created October 30, 2012 07:10
Show Gist options
  • Save trentbrooks/3978737 to your computer and use it in GitHub Desktop.
Save trentbrooks/3978737 to your computer and use it in GitHub Desktop.
ProcessingJS Multitouch Demo
<html>
<head>
<title>Multitouch ProcessingJS Test</title>
<meta content='True' name='HandheldFriendly' />
<meta content='width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;' name='viewport' />
<meta name="viewport" content="width=device-width" />
<script type="text/javascript" src="http://trentbrooks.com/files/MultitouchHitTestDemo/processing-1.4.1.js"></script>
<script type="text/processing" data-processing-target="targetcanvas">
/* @pjs crisp="true";
font="http://trentbrooks.com/files/MultitouchHitTestDemo/data/AmaticSC.ttf";
preload="http://trentbrooks.com/files/MultitouchHitTestDemo/data/sky.jpg,http://trentbrooks.com/files/MultitouchHitTestDemo/data/rathausFrog.jpg,http://trentbrooks.com/files/MultitouchHitTestDemo/data/img.jpg,http://trentbrooks.com/files/MultitouchHitTestDemo/data/sky.jpg,http://trentbrooks.com/files/MultitouchHitTestDemo/data/jelly.jpg";
*/
//--------------------------------------------------------------
/*
ProcessingJS Multitouch example by Trent Brooks.
I couldn't find much info on the TouchEvents api related to ProcessingJS, so I made this demo
which uses native browser touch events. The examples that I did find, were not using the
correct lists (changedTouches) for what I needed.
- Plenty of todos...
*/
//--------------------------------------------------------------
HashMap<int, TouchCursor> touchCursors = new HashMap<int, TouchCursor>();
int touchPosX = 0; // debug
int touchPosY = 0; // debug
int touchId = -1; // debug
String touchStatus = "NONE"; // debug
boolean bringTouchedObjectsToFront = true;
// display list
ArrayList<InteractiveObject> touchableObjects = new ArrayList<InteractiveObject>();
InteractiveObject bg;
void setup()
{
size(screenWidth, screenHeight);
frameRate(60);
background(180);
smooth();
PFont myfont = createFont("http://trentbrooks.com/files/MultitouchHitTestDemo/data/AmaticSC.ttf");//,24);// loadFont("data/AmaticSC.ttf"); // custom font
textFont(myfont, 24);
// make some movable things
InteractiveObject obj1 = new InteractiveObject(550,100);//int(width * .5), int(height * .5));
obj1.loadImg("http://trentbrooks.com/files/MultitouchHitTestDemo/data/jelly.jpg");
touchableObjects.add(obj1);
InteractiveObject obj2 = new InteractiveObject(400,250);
obj2.loadImg("http://trentbrooks.com/files/MultitouchHitTestDemo/data/sky.jpg");
touchableObjects.add(obj2);
InteractiveObject obj3 = new InteractiveObject(450,350);
obj3.loadImg("http://trentbrooks.com/files/MultitouchHitTestDemo/data/rathausFrog.jpg");
touchableObjects.add(obj3);
// blank object
InteractiveObject obj4 = new InteractiveObject(325,175);
touchableObjects.add(obj4);
// sub layers
//InteractiveObject obj3SubLayer = new InteractiveObject(0,0);
//obj3SubLayer.loadImg("data/sky.jpg");
//obj3.addLayer(obj3SubLayer);
// lets not add this one to the touchableObjects list and update manually
bg = new InteractiveObject(0,0);
bg.loadImg("http://trentbrooks.com/files/MultitouchHitTestDemo/data/img.jpg");
}
void draw()
{
background(180);
fill(255);
// draw interactive bg
bg.draw();
// draw all interactive objects
for (int k = 0; k < touchableObjects.size(); k++) {
InteractiveObject io = (InteractiveObject) touchableObjects.get(k);
//io.rotation += .01;
io.draw();
}
// draw touches (for debug)
int prevKey = -1; // note keys could be anything- on ios devices they are long numbers, in chrome win7 they start at 0
for(int key : touchCursors.keySet()) {
TouchCursor tc = (TouchCursor) touchCursors.get(key);
tc.debugDraw();
if(prevKey != -1) {
TouchCursor prevTc = (TouchCursor) touchCursors.get(prevKey);
line(prevTc.x, prevTc.y, tc.x, tc.y);
}
prevKey = key;
}
// show info about the last touch + object
fill(255);
text("Multitouch Hit Test demo" +
"\nFrame rate: " + int(frameRate) +
"\nTouch Status: " + touchStatus +
"\nTotal touches: " + touchCursors.size() +
"\nCurrent touch id: " + touchId +
"\nCurrent touch position: " + touchPosX + ", " + touchPosY +
"\nTotal objects: " + touchableObjects.size(), 30, 40);
}
//--------------------------------------------------------------
/*
Touch events.
- Lists in TouchEvent: changedTouches, targetTouches, touches
- For description see: http://www.sitepen.com/blog/2011/12/07/touching-and-gesturing-on-iphone-android-and-more/
*/
void touchStart(TouchEvent touchEvent) {
touchStatus = "start";
for (int i = 0; i < touchEvent.changedTouches.length; i++) {
// info we need from the event
touchPosX = touchEvent.changedTouches[i].clientX;
touchPosY = touchEvent.changedTouches[i].clientY;
touchId = touchEvent.changedTouches[i].identifier;
// create a new TouchCursor for every touch and colour them for debuging: yellow 1st touch, blue 2nd touch, pink 3rd touch, and random 4th +
// could store the event itself, but this is a clearer for debugging and can be simulated by mouse
TouchCursor tc = new TouchCursor(touchPosX, touchPosY, touchId);
int totalTouchesAdded = touchCursors.size();
tc.clr = (totalTouchesAdded == 0) ? color(255, 255, 0) : (totalTouchesAdded == 1) ? color(0, 255, 255) : (totalTouchesAdded == 2) ? color(255, 0, 255) : color(random(255), random(255),random(255));
touchCursors.put(touchId, tc);
// update interactive objects with the TouchCursor
// all in reverse order, so top items get touches first
// the touched item can move to the front of the display list
int touchedObjectIndex = -1;
for (int k = touchableObjects.size() - 1; k >= 0; k--) {
InteractiveObject io = (InteractiveObject) touchableObjects.get(k);
if(io.onTouchedDown(tc)) {
touchedObjectIndex = k;
}
}
// update bg seperately
bg.onTouchedDown(tc);
// move object to front
if(bringTouchedObjectsToFront) {
if(touchedObjectIndex != -1) {
InteractiveObject frontObj = (InteractiveObject) touchableObjects.get(touchedObjectIndex);
touchableObjects.remove(touchedObjectIndex);
touchableObjects.add(frontObj);
}
}
}
}
void touchMove(TouchEvent touchEvent) {
touchStatus = "move";
for (int i = 0; i < touchEvent.changedTouches.length; i++) {
touchPosX = touchEvent.changedTouches[i].clientX;
touchPosY = touchEvent.changedTouches[i].clientY;
touchId = touchEvent.changedTouches[i].identifier;
TouchCursor tc = (TouchCursor) touchCursors.get(touchId);
if(tc != null) {
tc.update(touchPosX, touchPosY);
// update ALL interactive objects with active TouchCursor
for (int k = touchableObjects.size() - 1; k >= 0; k--) {
InteractiveObject io = (InteractiveObject) touchableObjects.get(k);
io.onTouchedMove(tc);
}
// update bg seperately
bg.onTouchedMove(tc);
}
}
}
void touchEnd(TouchEvent touchEvent) {
touchStatus = "end";
for (int i = 0; i < touchEvent.changedTouches.length; i++) {
touchId = touchEvent.changedTouches[i].identifier;
TouchCursor tc = (TouchCursor) touchCursors.get(touchId);
if(tc != null) {
// update ALL interactive objects with active TouchCursor
for (int k = touchableObjects.size() - 1; k >= 0; k--) {
InteractiveObject io = (InteractiveObject) touchableObjects.get(k);
io.onTouchedUp(tc);
}
// update bg seperately
bg.onTouchedUp(tc);
// delete current touch cursor from the list
touchCursors.remove(touchId);
}
}
}
void touchCancel(TouchEvent touchEvent) {
touchEnd(touchEvent);
}
//--------------------------------------------------------------
/* Simulating touch events.
- for debugging only, id for the fake touch cursor is always 0.
- delete this code soon
*/
void mousePressed() {
//simulate touch start
touchStatus = "mouse start";
touchPosX = mouseX;
touchPosY = mouseY;
touchId = 0;
TouchCursor tc = new TouchCursor(touchPosX, touchPosY, touchId);
touchCursors.put(touchId, tc);
int touchedObjectIndex = -1;
for (int k = touchableObjects.size() - 1; k >= 0; k--) {
InteractiveObject io = (InteractiveObject) touchableObjects.get(k);
if(io.onTouchedDown(tc)) {
touchedObjectIndex = k;
}
}
bg.onTouchedDown(tc);
// move to front
if(bringTouchedObjectsToFront) {
if(touchedObjectIndex != -1) {
InteractiveObject frontObj = (InteractiveObject) touchableObjects.get(touchedObjectIndex);
touchableObjects.remove(touchedObjectIndex);
touchableObjects.add(frontObj);
}
}
}
void mouseDragged() {
//simulate touch moved
touchStatus = "mouse move";
touchPosX = mouseX;
touchPosY = mouseY;
touchId = 0;
TouchCursor tc = (TouchCursor) touchCursors.get(touchId);
if(tc != null) {
tc.update(touchPosX, touchPosY);
// update ALL interactive objects with active TouchCursor
for (int k = touchableObjects.size() - 1; k >= 0; k--) {
InteractiveObject io = (InteractiveObject) touchableObjects.get(k);
io.onTouchedMove(tc);
}
bg.onTouchedMove(tc);
}
}
void mouseReleased() {
//simulate touch end
touchStatus = "mouse end";
touchId = 0;
TouchCursor tc = (TouchCursor) touchCursors.get(touchId);
if(tc != null) {
// update ALL interactive objects with active TouchCursor
for (int k = touchableObjects.size() - 1; k >= 0; k--) {
InteractiveObject io = (InteractiveObject) touchableObjects.get(k);
io.onTouchedUp(tc);
}
bg.onTouchedUp(tc);
// delete current touch cursor from the list
touchCursors.remove(touchId);
}
}
//--------------------------------------------------------------
/*
TouchCursor class to store the important info from TouchEvents.
*/
//--------------------------------------------------------------
class TouchCursor {
int x = 0;
int y = 0;
int id = -1;
boolean busy; // we don't want one cursor to control multiple interactive objects
color clr = color(255); // default white
TouchCursor(int tx, int ty, int identifier) {
x = tx;
y = ty;
id = identifier;
}
void update(int tx, int ty) {
x = tx;
y = ty;
}
void debugDraw() {
fill(clr);
ellipse(x, y, 60, 60); // draw a 60px circle
fill(0);
text("id: " + id, x - 15, y); // display the cursor id
}
}
//--------------------------------------------------------------
/*
InteractiveObjects: Simple display object.
- requires a TouchCursor for hit testing
- automatic tweening
- can add other InteractiveObjects to itself (display lists)
Todo:
- make scalable rotatable
- test adding layers, add TouchCursors to sub layers
- ability to toggle the moving/dragging functionality so you can use as normal gui element eg. button
*/
//--------------------------------------------------------------
class InteractiveObject {
// positional/size/rotation
PVector position = new PVector();
PVector offset = new PVector();
PVector currentPosition = new PVector();
float width = 100, height = 100;
float currentWidth = 100, currentHeight = 100;
float rotation = 0;
float currentRotation = 0;
// display
PImage img;
boolean imgLoaded = false;
float tweenSpeed = 0.25;
color clr = color(255);
// display list- add other InteractiveObjects to this
ArrayList layers = new ArrayList();
InteractiveObject parentLayer = null;
// 2 cursors can touch the object for scale/rotate
int[] touchIds = {-1,-1};
boolean isPressed = false;
InteractiveObject(float posX, float posY) {
setPosition(posX, posY);
setOffset(posX, posY);
setCurrentPosition(posX, posY);
}
void loadImg(String path) {
img = loadImage(path);
width = currentWidth = img.width;
height = currentHeight = img.height;
imgLoaded = true;
}
void addLayer(InteractiveObject layer) {
layers.add(layer);
layer.parentLayer = this; // does this work?
layer.setPosition(-currentWidth * .5, -currentHeight * .5); // need it to draw from top left
//layer.width = layer.parentLayer.width;
}
void updateTransform() {
// ease to position, scale, rotation
currentPosition.x += (position.x - currentPosition.x) * tweenSpeed;
currentPosition.y += (position.y - currentPosition.y) * tweenSpeed;
currentWidth += (width - currentWidth) * tweenSpeed;
currentHeight += (height - currentHeight) * tweenSpeed;
currentHeight += (height - currentHeight) * tweenSpeed;
currentRotation += (rotation - currentRotation) * tweenSpeed;
}
void draw() {
updateTransform();
pushMatrix();
translate(currentPosition.x + (currentWidth * .5),currentPosition.y + (currentHeight * .5));
rotate(currentRotation);
fill(clr);
if(imgLoaded) {
image(img, -currentWidth * .5, -currentHeight * .5, currentWidth, currentHeight);
} else {
rect(-currentWidth * .5, -currentHeight * .5, currentWidth, currentHeight);
}
// update/draw all sublayers
for(int i = 0; i < layers.size(); i++) {
InteractiveObject io = (InteractiveObject) layers.get(i);
io.draw();
}
popMatrix();
}
void setPosition(float dx, float dy) {
position.x = dx;
position.y = dy;
}
void setCurrentPosition(float dx, float dy) {
currentPosition.x = dx;
currentPosition.y = dy;
}
void setOffset(float dx, float dy) {
offset.x = dx;
offset.y = dy;
}
int getObjectTouchCursorIndex(int touchId) {
for(int i = 0; i < touchIds.length; i++) {
if(touchIds[i] == touchId) return i;
}
return -1;
}
boolean onTouchedDown(TouchCursor tc) {
if(tc.busy) return false;
if (hitTest(tc.x, tc.y)) {
isPressed = true;
if(touchIds[0] == -1) {
touchIds[0] = tc.id; // first touch
} else {
touchIds[1] = tc.id; // any other touches (note- only uses 2)
}
setOffset(tc.x - position.x, tc.y - position.y);
tc.busy = true;
return true;
}
return false;
}
boolean onTouchedMove(TouchCursor tc) {
if (isPressed) {
int objectTouchCursorIndex = getObjectTouchCursorIndex(tc.id);
if(objectTouchCursorIndex >= 0) {
setPosition(tc.x - offset.x, tc.y - offset.y);
return true;
}
}
return false;
}
boolean onTouchedUp(TouchCursor tc) {
if (isPressed ) {
int objectTouchCursorIndex = getObjectTouchCursorIndex(tc.id);
if(objectTouchCursorIndex >= 0) {
touchIds[objectTouchCursorIndex] = -1;
if(touchIds[0] == -1 && touchIds[1] == -1) {
isPressed = false;
}
}
return true;
}
return false;
}
boolean hitTest(float tx, float ty) {
return tx >= currentPosition.x && tx <= currentPosition.x + currentWidth && ty >= currentPosition.y && ty <= currentPosition.y + currentHeight;
}
}
</script>
</head>
<body style="margin:0px;padding:0px;">
<pre></pre>
<canvas id="targetcanvas" style="margin:0px;padding:0px;">
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment