001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.demos.touchtable;
031
032import java.io.IOException;
033import java.io.PrintWriter;
034import java.util.ArrayList;
035import java.util.Scanner;
036
037import org.openimaj.image.MBFImage;
038import org.openimaj.image.colour.RGBColour;
039import org.openimaj.image.processing.transform.PiecewiseMeshWarp;
040import org.openimaj.math.geometry.point.Point2d;
041import org.openimaj.math.geometry.point.Point2dImpl;
042import org.openimaj.math.geometry.shape.Rectangle;
043import org.openimaj.math.geometry.shape.Shape;
044import org.openimaj.math.geometry.shape.Triangle;
045import org.openimaj.util.pair.Pair;
046
047public class TriangleCameraConfig implements CameraConfig {
048
049        private int gridWidth;
050        private int gridHeight;
051        private Rectangle visibleArea;
052        private ArrayList<Point2d> touchArray;
053        private ArrayList<Point2d> screenArray;
054        private ArrayList<Pair<Shape>> trianglePairs;
055        private PiecewiseMeshWarp<Float[], MBFImage> nlp;
056
057        public TriangleCameraConfig(ArrayList<Point2d> touchArray, int gridx,int gridy, Rectangle visibleArea) {
058                this.gridWidth = gridx;
059                this.gridHeight = gridy;
060                this.visibleArea = visibleArea;
061                this.touchArray = touchArray;
062                createScreenArray();
063                createTriangles();
064                createNonLinearWarp();
065        }
066
067        private void createScreenArray() {
068                this.screenArray = new ArrayList<Point2d>();
069                int yInc = (int) (this.visibleArea.height/gridHeight);
070                int xInc = (int) (this.visibleArea.width/gridWidth);
071                for(int row = 0; row < gridHeight+1; row++){
072                        for(int col = 0; col < gridWidth + 1; col++){
073                                this.screenArray.add(new Point2dImpl(col * xInc, row * yInc));
074                        }
075                }
076        }
077
078        public TriangleCameraConfig() {
079                
080        }
081
082        private void createTriangles() {
083                this.trianglePairs = new ArrayList<Pair<Shape>>();
084                for (int row = 0; row < gridHeight; row++) {
085                        for(int col = 0; col < gridWidth; col++){
086                                Point2d p1 = this.touchArray.get(col + row * (gridWidth+1));
087                                Point2d p2 = this.touchArray.get((col+1) + row * (gridWidth+1));
088                                Point2d p3a = this.touchArray.get(col + (row+1) * (gridWidth+1));
089                                Point2d p3b = this.touchArray.get((col+1) + (row+1) * (gridWidth+1));
090                                
091                                Point2d p1Screen = this.screenArray.get(col + row * (gridWidth+1));
092                                Point2d p2Screen = this.screenArray.get((col+1) + row * (gridWidth+1));
093                                Point2d p3aScreen = this.screenArray.get(col + (row+1) * (gridWidth+1));
094                                Point2d p3bScreen = this.screenArray.get((col+1) + (row+1) * (gridWidth+1));
095                                
096                                Triangle ct1 = new Triangle(p1,p2,p3a);
097                                Triangle ct2 = new Triangle(p2,p3a,p3b);
098                                
099                                Triangle st1 = new Triangle(p1Screen,p2Screen,p3aScreen);
100                                Triangle st2 = new Triangle(p2Screen,p3aScreen,p3bScreen);
101                                
102                                this.trianglePairs.add(new Pair<Shape>(st1,ct1));
103                                this.trianglePairs.add(new Pair<Shape>(st2,ct2));
104                        }
105                }
106                
107        }
108
109        @Override
110        public Touch transformTouch(Touch point) {
111                int matching = this.nlp.getMatchingShapeIndex(point.calculateCentroid());
112                if(matching == -1) return null;
113                float ptx = point.getX();
114                float pty = point.getY();
115                Point2d[] cameraTriangle = ((Triangle)(this.trianglePairs.get(matching).secondObject())).vertices;
116                Point2d[] screenTriangle = ((Triangle)(this.trianglePairs.get(matching).firstObject())).vertices;
117                Point2dImpl A = (Point2dImpl) cameraTriangle[0]; // Place camera vector triangle points
118        Point2dImpl B = (Point2dImpl) cameraTriangle[1]; // into some local vectors
119        Point2dImpl C = (Point2dImpl) cameraTriangle[2];
120        float total_area = (A.x - B.x) * (A.y - C.y) - (A.y - B.y) * (A.x - C.x); // Calculate the total area of the triangle
121        // pt,B,C
122        float area_A = (ptx - B.x) * (pty - C.y) - (pty - B.y) * (ptx - C.x); // and find the area enclosed by the
123
124        // A,pt,C
125        float area_B = (A.x - ptx) * (A.y - C.y) - (A.y - pty) * (A.x - C.x); // three camera vector triangle points
126
127        float bary_A = area_A / total_area;                                                                                             // so we can find three fractions of the total area
128        float bary_B = area_B / total_area;
129        float bary_C = 1.0f - bary_A - bary_B;  // bary_A + bary_B + bary_C = 1
130
131        Point2dImpl sA = (Point2dImpl) screenTriangle[0]; // Place screen vector triangle points
132        Point2dImpl sB = (Point2dImpl) screenTriangle[1]; // into some local vectors
133        Point2dImpl sC = (Point2dImpl) screenTriangle[2];
134
135
136        float transformedPosx = (sA.x*bary_A) + (sB.x*bary_B) + (sC.x*bary_C);
137        float transformedPosy = (sA.y*bary_A) + (sB.y*bary_B) + (sC.y*bary_C);
138        
139        return new Touch(transformedPosx,transformedPosy,point.getRadius(), point.touchID, point.motionVector);
140        }
141
142        @Override
143        public String asciiHeader() {
144                return "triangleconfig";
145        }
146
147        @Override
148        public void readASCII(Scanner in) throws IOException {
149                this.gridWidth = in.nextInt();
150                this.gridHeight = in.nextInt();
151                this.visibleArea = new Rectangle(in.nextFloat(),in.nextFloat(),in.nextFloat(),in.nextFloat());
152                this.touchArray = new ArrayList<Point2d>();
153                while(in.hasNext()){
154                        this.touchArray.add(
155                                        new Point2dImpl(in.nextFloat(),in.nextFloat())
156                        );
157                }
158                this.createScreenArray();
159                this.createTriangles();
160                this.createNonLinearWarp();
161                
162        }
163
164        private void createNonLinearWarp() {
165                this.nlp = new PiecewiseMeshWarp<Float[], MBFImage>(this.trianglePairs);                
166        }
167
168        @Override
169        public void writeASCII(PrintWriter out) throws IOException {
170                out.format("%d %d\n",this.gridWidth,this.gridHeight);
171                out.format("%f %f %f %f\n",this.visibleArea.x,this.visibleArea.y,this.visibleArea.width,this.visibleArea.height);
172                for (Point2d t : this.touchArray) {
173                        out.format("%f %f\n",
174                                        t.getX(),t.getY()
175                        );
176                }
177        }
178
179        public void drawTriangles(MBFImage image) {
180                for (Pair<Shape> t : this.trianglePairs) {
181                        image.drawShape(t.firstObject(), RGBColour.RED);
182                }
183        }
184
185}