/*
* Copyright (c) 2009, Hamish Morgan. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the University of Sussex nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package piccat.diamondtouch;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.HierarchyBoundsListener;
import java.awt.event.HierarchyEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
/**
* Class that holds a mapping from table-space to screen space. Used to
* convert points in touch events to and from table space.
*
* Some thoughts....
*
* Could use and AffineTransform but would require solving a rather nasty
* equasion. The table-space can be defined by translation, rotation,
* scale and skew so and AffineTransform can be used. The user will
* callibrate the table by clicking on a number of points on the screen.
* Each point p will be rendered at a given x,y, and will correspond to
* a touch point p' at (x',y'). The transform T can be user for moving
* between these two spaces.
*
* P' = pT and p = pT' (where T' represents the inverse of T)
*
* So...
*
* [ x'] [ m00 m01 m02 ] [ x ] [ m00x + m01y + m02 ]
* [ y'] = [ m10 m11 m12 ] [ y ] = [ m10x + m11y + m12 ]
* [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
*
* We need to resolve the values of m in T. I think this should work as a
* simultanious equasion (though I could be wrong.)
*
* hmm... need internet
*
* =====================================
*
* Actually it's not an affine transform, because paralel lines may not
* remain paralel. This is because the project can (and generally will)
* not be directly above the table.
*
*
* ========================================
*
* Am now thinking that there are several levels of transform that form a
* pipeline. There a number of different vector spaces to be considered:
* World space - The positions of the entities in the world model
* Screen space - The position of things on the screen
* Touch space - The position of touchs on the table
* Component space - The position of things relative to the rendering area.
* Projection space - Where things are actually projected on the table.
*
*
*
* @author Hamish Morgan
*/
public class DTTransform {
private final AffineTransform at;
private Dimension table;
private Dimension screen;
private Rectangle canvas;
private boolean clean;
public DTTransform() {
at = new AffineTransform();
table = new Dimension();
screen = new Dimension();
canvas = new Rectangle();
clean = true;
}
private void regenerateTransform() {
at.setToIdentity();
if (!canvas.isEmpty())
at.translate(-canvas.x, -canvas.y);
if (screen.width != 0 && screen.height != 0 && table.height != 0 &&
table.width != 0) {
at.scale((double) screen.width / (double) table.width,
(double) screen.height / (double) table.height);
}
clean = true;
}
public void transform(Point2D p) {
if (!clean) regenerateTransform();
at.transform(p, p);
}
public int transformX(int x) {
// Assumes y = 0
final Point2D p = new Point2D.Double(x, 0);
transform(p);
return (int) p.getX();
}
public int transformY(int y) {
// Assumes y = 0
final Point2D p = new Point2D.Double(0, y);
transform(p);
return (int) p.getY();
}
/**
* The effective verticle resolution of the DiamonTouch table. This does
* not correspont to the sensor desity, but rather fidelity with which
* touch events can be distinquished,
*
* @return the verticle resolution
*/
public int getTableHeight() {
return table.height;
}
/**
* The effective horizontal resolution of the DiamonTouch table. This does
* not correspont to the sensor desity, but rather fidelity with which
* touch events can be distinquished,
*
* @return the horizontal resolution
*/
public int getTableWidth() {
return table.width;
}
public int getScreenHeight() {
return screen.height;
}
public int getScreenWidth() {
return screen.width;
}
public int getCanvasX() {
return canvas.x;
}
public int getCanvasY() {
return canvas.y;
}
public int getCanvasWidth() {
return canvas.width;
}
public int getCanvasHeight() {
return canvas.height;
}
public void setScreenHeight(int height) throws IllegalArgumentException {
if (height <= 0) throw new IllegalArgumentException(
"Height argument should be a possitive integer, found: " +
height);
if (screen.height != height) {
screen.height = height;
clean = false;
}
}
public void setScreenWidth(int width) throws IllegalArgumentException {
if (width <= 0) throw new IllegalArgumentException(
"Width argument should be a possitive integer, found: " +
width);
if (screen.width != width) {
screen.width = width;
clean = false;
}
}
public void setScreenSize(int width, int height) throws
IllegalArgumentException {
setScreenWidth(width);
setScreenHeight(height);
}
public void setScreenSize(Dimension dim) throws IllegalArgumentException,
NullPointerException {
setScreenSize(dim.width, dim.height);
}
public void setTableHeight(int height) throws IllegalArgumentException {
if (height <= 0) {
throw new IllegalArgumentException(
"Height should be a possitive integer, found: " +
height);
}
if (table.height != height) {
table.height = height;
clean = false;
}
}
public void setTableWidth(int width) throws IllegalArgumentException {
if (width <= 0)
throw new IllegalArgumentException(
"Width should be a possitive integer, found: " +
width);
if (table.width != width) {
table.width = width;
clean = false;
}
}
public void setTableSize(int width, int height) throws
IllegalArgumentException {
setTableWidth(width);
setTableHeight(height);
}
public void setTableSize(Dimension dim) throws IllegalArgumentException,
NullPointerException {
setTableSize(dim.width, dim.height);
}
public void setCanvasX(int x) {
if (canvas.x != x) {
canvas.x = x;
clean = false;
}
}
public void setCanvasY(int y) {
if (canvas.y != y) {
canvas.y = y;
clean = false;
}
}
public void setCanvasWidth(int width) {
if (canvas.width != width) {
canvas.width = width;
clean = false;
}
}
public void setCanvasHeight(int height) {
if (canvas.height != height) {
canvas.height = height;
clean = false;
}
}
public void setCanvasPosition(int x, int y) {
setCanvasX(x);
setCanvasY(y);
}
public void setCanvasPosition(Point p) {
setCanvasPosition(p.x, p.y);
}
public void setCanvasSize(int width, int height) {
setCanvasWidth(width);
setCanvasHeight(height);
}
public void setCanvasSize(Dimension dim) {
setCanvasSize(dim.width, dim.height);
}
public void setCanvasBounds(int x, int y, int w, int h) {
setCanvasPosition(x, y);
setCanvasSize(w, h);
}
public void setCanvasBounds(Rectangle bounds) {
setCanvasBounds(bounds.x, bounds.y, bounds.width, bounds.height);
}
private void updateBounds(Component c) {
setScreenSize(c.getToolkit().getScreenSize());
if(c.isShowing())
setCanvasPosition(c.getLocationOnScreen());
setCanvasSize(c.getSize());
}
public void watchComponent(Component c) {
c.addHierarchyBoundsListener(new BoundsListener());
updateBounds(c);
}
private class BoundsListener implements HierarchyBoundsListener {
public void ancestorMoved(HierarchyEvent e) {
updateBounds(e.getComponent());
}
public void ancestorResized(HierarchyEvent e) {
updateBounds(e.getComponent());
}
}
}
|