In diesem Kapitel überlegen wir uns, wie einfach interaktive Elemente funktionieren. Wir verwenden absichtlich keine vorhandenen Libraries und Pakete, die Ihnen Schaltflächen und Controller "schenken", sondern bauen alles in liebevoller Handarbeit selbst, damit wir später, wenn wir Multitouch hinzunehmen, die volle Kontrolle haben.

In diesem Kapitel beschäftigen wir uns auch mit der Frage, wie Sie einen Touchpoint, den Sie im world space auffangen, in einen transformierten Raum bringen, um dort Kollisionserkennung durchzuführen.

1.1 Rollover / Kollisionserkennung

Das einfachste interaktive Element ist eine Form, die "aktiv" wird, sobald der Mauszeiger auf ihr liegt, und "inaktiv", sobald der Mauszeiger wieder fort ist.

Dazu müssen wir zunächst wissen, wie wir feststellen, dass "der Mauszeiger auf einem Objekt liegt". Das nennt man auch Kollisionerkennung und ist in Spielen von hoher Bedeutung.

1.1.1 Rechteck

Kollisionserkennung zwischen einem Punkt (Mauszeiger) und eine Rechteck, das parallel zu den Achsen des Koordinatensystems liegt, ist sehr einfach. Wenn der Punkt (x, y) ist und das rechteck über rx, ry, rwidth, rheight definiert ist, dann liegt eine Kollision vor, wenn die folgenden zwei Bedingungen gelten

rx <= x <= rx + rwidth
ry <= y <= ry + rheight

Im Code sieht das so aus (statt x, y finden Sie mouseX, mouseY):

// interface_1
// Kollision mit Rechteck (Selektion per Rollover)

int rx = 50;
int ry = 50;
int rwidth = 150; // Breite
int rheight = 200; // Höhe

void setup() {
  size(300, 300);
  noStroke();
}

void draw() {

  // setze Füllfarbe ...
  if (rx <= mouseX && mouseX <= rx + rwidth &&
    ry <= mouseY && mouseY <= ry + rheight) {
    // ... wenn Mauszeiger im Rechteck => rot
    fill(255, 0, 0);
  }
  else {
    // ... sonst: weiß
    fill(255);
  }
  rect(rx, ry, rwidth, rheight);
}

Interaktives Feld: (gehen Sie mit der Maus über das Rechteck!)

1.1.2 Kreis

Kollisionserkennung zwischen einem Punkt und einem Kreis ist sogar noch einfacher. Dazu vergleicht man die Distanz des Punkts (x, y) mit dem Mittelpunkt (cx, cy) des Kreises. Ist die Distanz geringer als der Radius, so ist der Punkt im Kreis:

dist(x, y, cx, cy) < radius

Die Funktion dist() berechnet den Abstand zweier Punkte. Im folgenden Code nehmen wieder mouseX, mouseY den Platz von x, y ein und der Radius errechnet sich aus diameter/2.

// interface_2
// Kollision mit Kreis (Selektion per Rollover)

int cx = 170;
int cy = 160;
int diameter = 180; // Durchmesser

void setup() {
  size(300, 300);
  noStroke();
}

void draw() {
  // setze Füllfarbe ...
  if (dist(mouseX, mouseY, cx, cy) < diameter/2) {
    // ... wenn Mauszeiger im Rechteck => rot
    fill(255, 0, 0);
  }
  else {
    // ... sonst: weiß
    fill(255);
  }
  ellipse(cx, cy, diameter, diameter);
}

Interaktives Feld: (gehen Sie mit der Maus über den Kreis!)

1.2 Interaktive Objekte

Jetzt möchten wir die interaktiven Formen als Klassen definieren, damit wir mehrere Formen erzeugen und nutzen können. Eine solche Form könnte so aussehen:

class InteractiveRect implements InteractiveThing {

  int rx = 0;
  int ry = 0;
  int rwidth;
  int rheight;
  boolean selected = false;

  InteractiveRect(int x, int y, int w, int h) {
    rx = x;
    ry = y;
    rwidth = w;
    rheight = h;
  }

  void draw() {
    if (selected)
      fill(255, 0, 0);
    else
      fill(255);
    rect(rx, ry, rwidth, rheight);
  }

  void update(int inputX, int inputY) {
    selected = (rx <= inputX && inputX <= rx + rwidth &&
      ry <= inputY && inputY <= ry + height);
  }
}

Der Code hat zwei Methoden. Eine zum Zeichnen (draw) und eine zum Prüfen, ob die Form vom Mauszeiger getroffen wurde. Dann wird der Zustand der Variable selected geändert, die wiederum die Färbung bestimmt.

Ferner gehorcht die Klasse dem Interface InteractiveThing , damit wir später auch Kreise hinzunehmen können.

interface InteractiveThing {
  void draw();
  void update(int ix, int iy);
}

Für Kreise können wir dann definieren:

class InteractiveCircle implements InteractiveThing {

  int cx = 0;
  int cy = 0;
  int diameter;
  boolean selected = false;

  InteractiveCircle(int x, int y, int d) {
    cx = x;
    cy = y;
    diameter = d;
  }

  void draw() {
    if (selected)
      fill(255, 0, 0);
    else
      fill(255);
    ellipse(cx, cy, diameter, diameter);
  }

  void update(int inputX, int inputY) {
    selected = dist(inputX, inputY, cx, cy) < diameter/2;
  }
}

Jetzt können wir eine Liste von interaktiven Objekten anlegen. Dazu müssen wir die Klasse ArrayList und das Interface List aus der Java-Standardbibliothek (Paket java.util) importieren.

import java.util.*;

List things =
   new ArrayList();

void setup() {
  size(300, 300);
  noStroke();
  things.add(new InteractiveRect(50, 80, 100, 120));
  things.add(new InteractiveRect(180, 50, 70, 50));
  things.add(new InteractiveCircle(150, 150, 100));
}

void draw() {
  // Alle Objekte zeichnen und mit Inputpunkten updaten
  for (InteractiveThing thing: things) {
    thing.update(mouseX, mouseY);
    thing.draw();
  }
}

1.2.1 Selektion

Jetzt möchten wir Schalten, d.h. wir merken uns, ob eine Form angeklickt wurde und setzen den Zustand auf "selektiert" (beim nächsten Mal dann auf "deselektiert" etc.).

Bei den Formen ändern wir das Verhalten von update(). Hier wird der Schalter selected , sofern die Maus auf dem Objekt ist, umgedreht (durch Negation). Hier bei InteractiveRect :

void update(int inputX, int inputY) {
    // Umschalten, falls auf Objekt
    if (rx <= inputX && inputX <= rx + rwidth &&
      ry <= inputY && inputY <= ry + height)
      selected = !selected;
}

Hier bei InteractiveCircle:

void update(int inputX, int inputY) {

    // Umschalten, falls auf Objekt
    if (dist(inputX, inputY, cx, cy) < diameter/2)
      selected = !selected;
}

Im Hauptprogramm rufen wir update() nur dann auf, wenn der Mausknopf gedrückt wurde.

void draw() {
  background(200);

  // Alle objekte zeichnen
  for (InteractiveThing thing: things) {
    thing.draw();
  }
}

void mousePressed() {
  // Erst updaten, wenn Mausknopf gedrückt
  for (InteractiveThing thing: things) {
    thing.update(mouseX, mouseY);
  }
}

Interessant ist dies, wenn wir z.B. alle selektierten Formen gleichzeitig bewegen wollen. Im Hauptprogramm würde man das so machen:

void keyPressed() {
  for (InteractiveThing thing: things) {
    if (thing.isSelected()) {
      if (keyCode == LEFT) {
        thing.move(-2, 0);
      }
      else if (keyCode == RIGHT) {
        thing.move(2, 0);
      }
      else if (keyCode == UP) {
        thing.move(0, -2);
      }
      else if (keyCode == DOWN) {
        thing.move(0, 2);
      }
    }
  }
}

Jetzt fehlt noch Code bei den interaktiven Klassen. Zunächst fehlen die Methoden isSelected() und move(). Die müssen ins Interface:

interface InteractiveThing {
  void draw();
  void update(int ix, int iy);
  boolean isSelected();
  void move(int dx, int dy);
}

Bei beiden Klassen sehen die entsprechenden Methoden so aus:

boolean isSelected() {
   return selected;
}

void move(int dx, int dy) {
   rx += dx;
   ry += dy;
}

1.3 Verschieben

Die nächste Herausforderung ist es, ein Objekt zu verschieben, also zu "draggen". Dazu können Sie die bisherigen Klassen nutzen sowie die Systemvariablen, die die Mausposition im vorigen Frame enthalten: pmouseX und pmouseY . Versuchen Sie, dies selbst zu implementieren.

1.4 Verschieben mit translate

Das "draggen" eines Objekts führen wir jetzt mit der geometrischen Transformation translate() durch. Lesen Sie im Processing-Skript nach, wie translate() funktioniert.

Hauptprogramm:

// interface_6
// Objekte verschieben mit Drag
// jetzt mit translate()

import java.util.*;

List things =
   new ArrayList();

void setup() {
  size(300, 300);
  noStroke();
  things.add(new InteractiveRect(50, 80, 100, 120));
  things.add(new InteractiveRect(180, 50, 70, 50));
}

void draw() {
  background(200);

  // Alle objekte zeichnen
  for (InteractiveThing thing: things) {
    thing.draw();
  }
}

void mouseDragged() {
  // Erst updaten, wenn Maus gedragt
  for (InteractiveThing thing: things) {
    thing.update(pmouseX, pmouseY, mouseX, mouseY);
  }
}
                        

Unsere Klasse erledigt das Zeichnen in ihrer draw() Methode. Das Rechteck wird in seinem object space bei (0, 0) gezeichnet und erst dann mit translate() an den entsprechenden Ort geschoben.

Beachten Sie, dass Sie pushMatrix/popMatrix verwenden müssen, damit Objekte, die später gezeichnet werden, nicht von den Transformationen der früher gezeichneten Objekte beeinflusst werden.

interface InteractiveThing {
  void draw();
  void update(int pix, int piy, int ix, int iy);
}

class InteractiveRect implements InteractiveThing {

  int rx = 50;
  int ry = 50;
  int rwidth = 150; // Breite
  int rheight = 200; // Höhe
  boolean selected = false;

  InteractiveRect(int x, int y, int w, int h) {
    rx = x;
    ry = y;
    rwidth = w;
    rheight = h;
  }

  void draw() {
    pushMatrix();
    if (selected)
      fill(255, 0, 0);
    else
      fill(255);

    translate(rx, ry);

    rect(0, 0, rwidth, rheight);
    popMatrix();
  }

  void update(int pinputX, int pinputY,
              int inputX, int inputY) {
    // Umschalten, falls auf Objekt
    if (rx <= inputX && inputX <= rx + rwidth &&
      ry <= inputY && inputY <= ry + rheight) {
      rx += inputX - pinputX;
      ry += inputY - pinputY;
    }
  }
}

1.4.1 Exkurs: Umrechnen zwischen world space und object space

Nehmen wir an, wir wollen ein Quadrat im Objektraum um den Nullpunkt herum zeichnen:

Der world space ist unser Processing-Grafikfenster:

Wenn wir das Quadrat z.B. mit translate() bewegen, sieht das so aus:

Wie berechnet Processing dies? Ganz einfach: es verwendet die Transformationsmatrix, die sich nach dem translate() ergibt, um für jeden Punkt im Objektraum (Quadrat) den entsprechenden Punkt in der Welt (Processing-Fenster) zu errechnen.

Anders gesagt: die aktuelle Matrix M berechnet, wie ich einen Punkt im object space (xobj) umrechne in den _world space, wo der Punkt x_w dann ja gezeichnet wird:

[1] M x_obj = x_w

Jetzt nehmen wir an, wir bekommen einen Touchpunkt xt. Die Koordinaten liegen i.d.R. im _world space:

Um eine Kollisionsberechnung mit einem Objekt anzustellen, wäre es aber viel komfortabler, wir würden die Position im object space kennen, insbesondere weil ja auch Rotationen und Skalierungen vorkommen könnten! Diese bekommen wir aber durch obige Gleichung, multipliziert mit der Inversen von M:

[2] x_obj = M_inv x_w

Konkret heißt das: jedes Objekt sollte die Matrix seines lokalen Koordinatensystems speichern und invertieren. Am besten sollte die inverse Matrix immer nur dann aktualisiert werden, wenn sich die Matrix ändern, da Matrizeninversion nicht performant ist.

Bei einem Touchevent, verwendet man obige Gleichung 2 und setzt dort in x_w die Koordinaten des Touches ein. Das Ergebnis ist die Position des Touches im Objektraum.

Eine Kollisionserkennung ist dann meist trivial.

1.5 Translation (Drag) und Rotation (Taste)

Wenn wir zu dem Code von 1.5 Rotation hinzufügen, haben wir ein Problem: Die Kollisionserkennung (ist der Mauszeiger auf dem Objekt?) ist nicht mehr so einfach. Wir sehen uns zunächst den "falschen" Code an.

Wieder zuerst das Hauptprogramm:

// interface_7
// Objekte verschieben mit Drag
// jetzt mit translate() und rotate()
// PROBLEM: Kollisionsberechung stimmt nicht mehr!

import java.util.*;

List things =
   new ArrayList();

void setup() {
  size(300, 300);
  noStroke();
  things.add(new InteractiveRect(50, 80, 100, 120));
  things.add(new InteractiveRect(180, 50, 70, 50));
}

void draw() {
  background(200);

  // Alle objekte zeichnen
  for (InteractiveThing thing: things) {
    thing.draw();
  }
}

void mousePressed() {
  for (InteractiveThing thing: things) {
    thing.clicked(mouseX, mouseY);
  }
}

void mouseDragged() {
  // Erst updaten, wenn Maus gedragt
  for (InteractiveThing thing: things) {
    thing.update(pmouseX, pmouseY, mouseX, mouseY);
  }
}

void keyPressed() {
  for (InteractiveThing thing: things) {
    if (thing.isSelected()) {
      if (keyCode == LEFT) {
        thing.incRotation(-.1);
      }
      else if (keyCode == RIGHT) {
        thing.incRotation(.1);
      }
    }
  }
}

Hier das Interface und die Klasse. Wir brauchen ein paar Methoden mehr, um die Rotation zu steuern. Beachten Sie, dass in update() und clicked() die Kollisionsberechnung ganz normal im world space arbeitet und davon ausgeht, dass das Rechteck gar nicht gedreht ist!

interface InteractiveThing {
  void draw();
  void update(int pix, int piy, int ix, int iy);
  void clicked(int x, int y);
  void incRotation(float angle);
  boolean isSelected();
}

class InteractiveRect implements InteractiveThing {

  int rx = 50;
  int ry = 50;
  int rwidth = 150; // Breite
  int rheight = 200; // Höhe
  float angle = 0;
  boolean selected = false;

  InteractiveRect(int x, int y, int w, int h) {
    rx = x;
    ry = y;
    rwidth = w;
    rheight = h;
  }

  void draw() {
    pushMatrix();
    if (selected)
      fill(255, 0, 0);
    else
      fill(255);

    translate(rx, ry);

    // rotate around middle
    translate(rwidth/2, rheight/2);
    rotate(angle);
    translate(-rwidth/2, -rheight/2);

    rect(0, 0, rwidth, rheight);
    popMatrix();
  }

  void clicked(int inputX, int inputY) {
    if (rx <= inputX && inputX <= rx + rwidth &&
      ry <= inputY && inputY <= ry + rheight) {
      selected = !selected;
    }
  }

  void update(int pinputX, int pinputY,
              int inputX, int inputY) {
    // Umschalten, falls auf Objekt
    if (rx <= inputX && inputX <= rx + rwidth &&
      ry <= inputY && inputY <= ry + rheight) {
      rx += inputX - pinputX;
      ry += inputY - pinputY;
    }
  }

  boolean isSelected() {
    return selected;
  }

  void incRotation(float d) {
    angle += d;
  }
}

Man beachte die Technik, um um den Mittelpunkt des Rechecks zu rotieren: Zunächst wird das Koordinatensystem zum Mittelpunkt geschoben, dann wird rotiert, dann wird das Koordinatensystem wieder zurückgeschoben, damit die spätere "eigentliche" Translation nicht verfälscht wird.

translate(rwidth/2, rheight/2);
rotate(angle);
translate(-rwidth/2, -rheight/2);

Korrigierte Version

Das Problem ist, dass unsere Kollisionsberechnung oben von einem nicht-rotierten Rechteck ausgeht. Um dies zubeheben, nutzt man aus, dass man die aktuelle Transformation von Processing als Matrix abrufen kann. Von dieser Matrix nimmt man die inverse Matrix, um den Mauszeiger-Punkt in den object space des Rechtecks zu überführen. Im object space können wir dann mit unserer altbekannten Methode prüfen, ob der überführte Mauszeiger-Punkt im Rechteck liegt.

Hier ist die neue Klasse. Die inverse Matrix wird in matrix gespeichert und nur dann neu berechnet, wenn eine Transformation stattgefunden hat.

class InteractiveRect implements InteractiveThing {

  int rx = 50;
  int ry = 50;
  int rwidth = 150; // Breite
  int rheight = 200; // Höhe
  float angle = 0;
  boolean selected = false;
  boolean matrixChanged = true; // matrix must be recomputed
  PMatrix matrix = null; // inverted transformation matrix

  InteractiveRect(int x, int y, int w, int h) {
    rx = x;
    ry = y;
    rwidth = w;
    rheight = h;
  }

  void draw() {
    pushMatrix();
    if (selected)
      fill(255, 0, 0);
    else
      fill(255);

    // translate to target
    translate(rx, ry);

    // rotate around middle
    translate(rwidth/2, rheight/2);
    rotate(angle);
    translate(-rwidth/2, -rheight/2);

    // draw object
    rect(0, 0, rwidth, rheight);

    // recompute matrix
    if (matrixChanged) {
      matrix = getMatrix(); // current transform
      matrix.invert(); // inverted matrix
      matrixChanged = false; // no recomputation next time
    }

    popMatrix();
  }

  void clicked(int inputX, int inputY) {
    if (isInside(inputX, inputY)) {
      selected = !selected;
    }
  }

  // drag object
  void update(int pinputX, int pinputY,
              int inputX, int inputY) {

    // only if mouse on object
    if (isInside(inputX, inputY)) {
      rx += inputX - pinputX;
      ry += inputY - pinputY;
      matrixChanged = true; // recompute inverted matrix
    }
  }

  // rotate object by increment
  void incRotation(float d) {
    angle += d;
    matrixChanged = true; // recompute inverted matrix
  }

  // checks if point is inside object
  boolean isInside(int inputX, int inputY) {

    // only if matrix has been computed
    if (matrix != null) {

      // compute position in object space
      PVector pos = new PVector();

      // multiply matrix with input point
      pos = matrix.mult(new PVector(inputX, inputY), pos);

      return (0 <= inputX && inputX <= rwidth &&
      0 <= inputY && inputY <= rheight);
    }
    else
      return false;
  }

  boolean isSelected() {
    return selected;
  }
}

1.5.1 Rotation (Drag)

Jetzt möchten wir eine Rotation mit einer Drag-Geste durchführen. Dazu müssen wir einen Drehwinkel berechnen. Dieser Drehwinkel betrachtet den Mauspunkt und den vorigen Mauspunkt und berechnet den Winkel relativ zum Zentrum des Objekts.

Im Hauptprogramm verzichten wir auf Selektion, müssen also die Objekte nur zeichnen und updaten:

import java.util.*;

List things = new ArrayList();

void setup() {
  size(300, 300);
  noStroke();
  things.add(new InteractiveRect(50, 80, 100, 120));
  things.add(new InteractiveRect(180, 50, 70, 50));
}

void draw() {
  background(200);

  // Alle objekte zeichnen
  for (InteractiveThing thing: things) {
    thing.draw();
  }
}

void mouseDragged() {
  // Erst updaten, wenn Maus gedragt
  for (InteractiveThing thing: things) {
    thing.update(pmouseX, pmouseY, mouseX, mouseY);
  }
}

Die interaktiven Objekte berechnen immer das Zentrum, um welches ja dann gedreht wird. Da wir in beide Richtungen drehen wollen, können wir die Methode angleBetween nicht verwenden.

interface InteractiveThing {
  void draw();
  void update(int pix, int piy, int ix, int iy);
  void incRotation(float angle);
}

class InteractiveRect implements InteractiveThing {

  int rx;
  int ry;
  int rwidth; // Breite
  int rheight; // Höhe
  int cx; // center x
  int cy; // center y
  float angle = 0;
  boolean matrixChanged = true; // matrix must be recomputed
  PMatrix matrix = null; // inverted transformation matrix

  InteractiveRect(int x, int y, int w, int h) {
    rx = x;
    ry = y;
    rwidth = w;
    rheight = h;
    cx = rx + rwidth/2;
    cy = ry + rheight/2;
  }

  void draw() {
    pushMatrix();

    fill(255);

    // translate to target
    translate(rx, ry);

    // rotate around middle
    translate(rwidth/2, rheight/2);
    rotate(angle);
    translate(-rwidth/2, -rheight/2);

    // draw object
    rect(0, 0, rwidth, rheight);

    // recompute matrix
    if (matrixChanged) {
      matrix = getMatrix(); // current transform
      matrix.invert(); // inverted matrix
      matrixChanged = false; // no recomputation next time
    }

    popMatrix();
  }

  // drag object
  void update(int pinputX, int pinputY,
              int inputX, int inputY) {

    // only if mouse on object
    if (isInside(inputX, inputY)) {

      // this does not work because angleBetween results
      // in positive angles ALWAYS
      /*
      PVector v1 = new PVector(pinputX - cx, pinputY - cy);
      PVector v2 = new PVector(inputX - cx, inputY - cy);
      float alpha = PVector.angleBetween(v1, v2);
      println(alpha);
      */

      // use this instead
      float alpha1 = atan(float(inputX-cx) / (inputY-cy));
      float alpha2 = atan(float(pinputX-cx) / (pinputY-cy));
      float alpha = alpha2 - alpha1;

      incRotation(alpha);
    }
  }

  // rotate object by increment
  void incRotation(float d) {
    angle += d;
    matrixChanged = true; // recompute inverted matrix
  }

  // checks if point is inside object
  boolean isInside(int inputX, int inputY) {

    // only if matrix has been computed
    if (matrix != null) {

      // compute position in object space
      PVector pos = new PVector();

      // multiply matrix with input point
      pos = matrix.mult(new PVector(inputX, inputY), pos);

      return (rx <= inputX && inputX <= rx + rwidth &&
      ry <= inputY && inputY <= ry + rheight);
    }
    else
      return false;
  }
}

1.5.2 Translation und Rotation (Zonen)

Um Translation und Rotation zu kombinieren und beides mit Maus-Drag zu steuern, kann man Zonen einführen. Im Zentrum würde man Translation ansiedeln, am Rand des Objekts eher Rotation, da sich am Rand präziser rotieren lässt (gern mal im obigen Code ausprobieren nahe des Zentrums zu rotieren, wo eine kleine Änderung eine starke Rotation auslöst).

Zur Verdeutlichung zeichnen wir die Translationszone ins Objekt:

In der Klasse kommt hinzu der Durchmesser des Translationskreises (Hälfte von Breite oder Höhe, was kleiner ist).

interface InteractiveThing {
  void draw();
  void update(int pix, int piy, int ix, int iy);
  void incRotation(float angle);
}

class InteractiveRect implements InteractiveThing {

  int rx;
  int ry;
  int rwidth; // Breite
  int rheight; // Höhe
  int cx; // center x
  int cy; // center y
  float centerCircleDia;
  float angle = 0;
  boolean matrixChanged = true; // matrix must be recomputed
  PMatrix matrix = null; // inverted transformation matrix

  InteractiveRect(int x, int y, int w, int h) {
    rx = x;
    ry = y;
    rwidth = w;
    rheight = h;
    cx = rx + rwidth/2;
    cy = ry + rheight/2;
    centerCircleDia = min(rwidth, rheight)/2;
  }

  void draw() {
    pushMatrix();

    fill(255);

    // translate to target
    translate(rx, ry);

    // rotate around middle
    translate(rwidth/2, rheight/2);
    rotate(angle);
    translate(-rwidth/2, -rheight/2);

    // draw object
    rect(0, 0, rwidth, rheight);

    // draw circle
    stroke(0);
    ellipse(rwidth/2, rheight/2, centerCircleDia,
            centerCircleDia);
    noStroke();

    // recompute matrix
    if (matrixChanged) {
      matrix = getMatrix(); // current transform
      matrix.invert(); // inverted matrix
      matrixChanged = false; // no recomputation next time
    }

    popMatrix();
  }

  // drag object
  void update(int pinputX, int pinputY,
              int inputX, int inputY) {

    // only if mouse on object
    if (isInside(inputX, inputY)) {

      // compute distance from center
      float dist = dist(cx, cy, pinputX, pinputY);
      if (dist < centerCircleDia/2) {
        // do translation near center
        move(inputX-pinputX, inputY-pinputY);
      }
      else {
        // do rotation on periphery
        float alpha1 = atan(float(inputX-cx) / (inputY-cy));
        float alpha2 = atan(float(pinputX-cx) / (pinputY-cy));
        float alpha = alpha2 - alpha1;
        incRotation(alpha);
      }
    }
  }

  // rotate object by increment
  void incRotation(float d) {
    angle += d;
    matrixChanged = true; // recompute inverted matrix
  }

  void move(int dx, int dy) {
    rx += dx;
    ry += dy;
    cx = rx + rwidth/2;
    cy = ry + rheight/2;
    matrixChanged = true; // recompute inverted matrix
  }

  // checks if point is inside object
  boolean isInside(int inputX, int inputY) {

    // only if matrix has been computed
    if (matrix != null) {

      // compute position in object space
      PVector pos = new PVector();

      // multiply matrix with input point
      pos = matrix.mult(new PVector(inputX, inputY), pos);

      return (0 <= inputX && inputX <= rwidth &&
      0 <= inputY && inputY <= rheight);
    }
    else
      return false;
  }
}