/** * @author: Jan B. Markowski * @version 1.00 2008/7/28 (Started 2008/7/20) * @(#)wiiBoardJ.java * * This very simple application uses a wiimote and an IR source to create a whiteboard control of a monitor. * When an IR source is detected, the event may be configured to be perceived as a mouse left click, or a mouse move. * * The wiimote has an ~45 degree viewing angle. Make sure that the wiimote is pointing at the monitor * in the range from 50cm to 500cm with the entire monitor in view. * * Running the program: * 1) Download the WiiRemoteJ library, and the JSR-82 Java bluetooth library (Bluecove) * WiiremoteJ lib: http://www.world-of-cha0s.hostrocket.com/WiiRemoteJ/ * Bluecove lib : http://bluecove.sourceforge.net/ * 2) Append the libraries to the Java classpath when running this program * (e.g. java -cp "C:\Projects\bluecove-2.0.2.jar;C:\Projects\WiiRemoteJ.jar" wiiBoardJ) * 3) MAKE SURE Wiimote is propped right side up (i.e. so A button is pointing up. * 4) Run this program, follow the calibration instructions. */ import wiiremotej.*; import wiiremotej.event.*; import java.awt.*; import java.awt.event.*; //import javax.swing.*; // Import this package if you're testing the graphics. public class wiiBoardJ extends WiiRemoteAdapter { // Graphics //private static JFrame graphFrame; // Uncomment these lines if you're testing the graphics. //private static JPanel graph; private WiiRemote remote; private static Robot mouseRobot; // Used to control the mouse. private static double[] IRraw; // Raw IR input coordinates private static int[] IRscreen; // Screen-sized IR coordinates private static int[] prevIRscr; // Previous screen-sized IR input coordinates. private static int chg; // Minimum pixel change to allow new mouse click. private static double[] ssc; // Skewed screen coordinates (ssc) private static Dimension screenSize; // Screen size. private static int calibrationCounter; // Count the calibration iteration. private static boolean init; // Program initializing? (true/false) private static boolean mouseIsPressed; // Is left-clicked? private static int nullCounter; // Increments each time IR is not detected. private static int nullLimit; // Wiimote 100 Hz data rate. If nullLimit = 100, // then 1 s must elapse without light to produce // a mouse release event. // Translation Parameters. private static double transX; private static double transY; private static double shearX; private static double shearY; private static double slope12; private static double slope23; private static double inter12; private static double inter23; private static double scaleX; private static double scaleY; public static void main(String[] args) { WiiRemoteJ.setConsoleLoggingErrors(); // Output error messages to console. init = true; chg = 2; // Set this value high for more coarse mouse click movements. nullCounter = 0; nullLimit = 100; mouseIsPressed = false; IRraw = new double[2]; prevIRscr = new int[2]; IRscreen = new int[2]; ssc = new double[8]; screenSize = Toolkit.getDefaultToolkit().getScreenSize(); calibrationCounter = 0; System.out.println("Screen size: " + screenSize); System.out.println("Finding Wiimote... Press buttons (1) and (2) on the wiimote."); try { // This is the graphics pane. Uncomment this block of code for a visual test of the IR input field. // (Note: You will have to uncomment the "graph.repaint() // *****" line. /*graphFrame = new JFrame(); graphFrame.setTitle("IR input field"); graphFrame.setSize(screenSize.width, screenSize.height); graphFrame.setResizable(false); graph = new JPanel() { boolean paintInit = true; public void paintComponent(Graphics graphics) { if (paintInit) { graphics.clearRect(0, 0, screenSize.width-1, screenSize.height-1); graphics.setColor(Color.BLACK); graphics.fillRect(0, 0, screenSize.width-1, screenSize.height-1); paintInit = false; } graphics.setColor(Color.RED); if (calibrationCounter >= 1) graphics.drawString("Calibration 1 set.", 40, 40); if (calibrationCounter >= 2) graphics.drawString("Calibration 2 set.", 40, 60); if (calibrationCounter >= 3) graphics.drawString("Calibration 3 set.", 40, 80); if (calibrationCounter >= 4) graphics.drawString("Calibration 4 set.", 40, 100); graphics.fillOval(IRscreen[0], screenSize.height-IRscreen[1], 5, 5); } }; graphFrame.add(graph); //Uncomment the following line to view a graphic display for drawing. graphFrame.setVisible(true);*/ mouseRobot = new Robot(); WiiRemote remote = WiiRemoteJ.findRemote(); // Add a wiimote listener to this class. remote.addWiiRemoteListener(new wiiBoardJ(remote)); remote.setIRSensorEnabled(true, WRIREvent.BASIC); remote.setLEDIlluminated(0, true); remote.setLEDIlluminated(3, true); System.out.println("\nProceed with calibration..."); System.out.println("1) Point IR source at TOP-LEFT corner of screen and emit IR. \n(Try until calibration 1 is set)."); } catch (Exception e) { System.out.println("Unable to create wii remote variable"); } } public wiiBoardJ (WiiRemote remote) { this.remote = remote; } // This method is called if the Wiimote is unpowered or disconnected somehow. public void disconnected() { System.out.println("The Wiimote has been disconnected."); System.exit(0); } public void IRInputReceived(WRIREvent evt) { for (int i = 0; i < evt.getIRLights().length; i++) { IRLight light = evt.getIRLights()[i]; if (light != null) { /* get IR calibration: * This is called upon initiation of the program. It obtains information * about the location of an IR source at four points (screen corners), and returns * the value in an array of integers [top-left][top-right][bottom-right][bottom-left]. */ if (init) { //Proceed with calibration... if (calibrationCounter == 0) { ssc[0] = light.getX(); ssc[1] = light.getY(); calibrationCounter++; System.out.println("Calibration 1 set: ("+ssc[0]+","+ssc[1]+")\n"); System.out.println("2) Point IR source at TOP-RIGHT corner of screen and emit IR."); // Use the following 3 lines to see the skewed coordinates on screen. //IRscreen[0] = (int) Math.round(ssc[0]*1000); //IRscreen[1] = (int) Math.round(ssc[1]*1000); //graph.repaint(); } else if ((calibrationCounter == 1)&&(light.getX()>(ssc[0]+0.2))) { ssc[2] = light.getX(); ssc[3] = light.getY(); calibrationCounter++; System.out.println("Calibration 2 set: ("+ssc[2]+","+ssc[3]+")\n"); System.out.println("3) Point IR source at BOTTOM-RIGHT corner of screen and emit IR."); //IRscreen[0] = (int) Math.round(ssc[2]*1000); //IRscreen[1] = (int) Math.round(ssc[3]*1000); //graph.repaint(); } else if ((calibrationCounter == 2)&&(light.getY()<(ssc[3]-0.2))) { ssc[4] = light.getX(); ssc[5] = light.getY(); calibrationCounter++; System.out.println("Calibration 3 set: ("+ssc[4]+","+ssc[5]+")\n"); System.out.println("4) Point IR source at BOTTOM-LEFT corner of screen and emit IR."); //IRscreen[0] = (int) Math.round(ssc[4]*1000); //IRscreen[1] = (int) Math.round(ssc[5]*1000); //graph.repaint(); } else if ((calibrationCounter == 3)&&(light.getX()<(ssc[4]-0.2))) { ssc[6] = light.getX(); ssc[7] = light.getY(); calibrationCounter++; System.out.println("Calibration 4 set: ("+ssc[6]+","+ssc[7]+")"); //IRscreen[0] = (int) Math.round(ssc[6]*1000); //IRscreen[1] = (int) Math.round(ssc[7]*1000); //graph.repaint(); System.out.println("Calibration done.\n"); System.out.println("Wii-Whiteboard ready. \n(Turn OFF wiimote to finish)"); getTranslationValues(); init = false; } } else { //System.out.println("X:"+light.getX()+" Y:"+light.getY()); IRraw[0] = light.getX(); IRraw[1] = light.getY(); // Uncomment the following 3 lines if you would like to see the location of the raw data. //IRscreen[0] = (int) Math.round(IRraw[0]*1000); //IRscreen[1] = (int) Math.round(IRraw[1]*1000); //graph.repaint(); prevIRscr = IRscreen; IRscreen = convertCoords(IRraw); // Move the mouse to the location of the IR beam. mouseRobot.mouseMove(IRscreen[0], screenSize.height-IRscreen[1]); if ((nullCounter > nullLimit)&&(!mouseIsPressed)) { nullCounter = 0; mouseRobot.mousePress(InputEvent.BUTTON1_MASK); // left-click. mouseIsPressed = true; } if (mouseIsPressed) nullCounter = 0; // This comment must be uncomment if you are using the Graphics Frame. //graph.repaint();// ***** } } else { if (!init) nullCounter++; if ((nullCounter > nullLimit)&&(mouseIsPressed)) { mouseIsPressed = false; mouseRobot.mouseRelease(InputEvent.BUTTON1_MASK); } } } } /* getTranslationValues(): * This is the most important method in that it converts the skewed calibration coordinates * that are seen by the wiimote into a perfect rectangle in exact proportion to the screen * resolution. The values of the "Translation variables" are stored and used continuously * to convert the relative location of the sensed IR beam. */ public void getTranslationValues() { double[] tempssc = ssc; transX = ssc[6]; transY = ssc[7]; // Translate coordinates so that 4th coordinate is (0,0). for (int i = 0; i < ssc.length; i+=2) { tempssc[i] -= transX; tempssc[i+1] -= transY; } // Shear along the y-axis. shearY = tempssc[5]/tempssc[4]; // DeltaY/DeltaX for (int i=0; i < tempssc.length; i+=2) tempssc[i+1] -= tempssc[i]*shearY; // Shear along the x-axis. shearX = tempssc[0]/tempssc[1]; // DeltaX/DeltaY for (int i=0; i < tempssc.length; i+=2) tempssc[i] -= tempssc[i+1]*shearX; // Align the top part of box by slope 1st to 2nd calib. coord. slope12 = (1-tempssc[3]/tempssc[1])/tempssc[2]; inter12 = tempssc[3]/tempssc[1]; tempssc[1] *= (slope12*tempssc[0] + inter12); // Align the right part of box by slope 2nd to 3rd calib. coord. slope23 = (1-tempssc[2]/tempssc[4])/tempssc[3]; inter23 = tempssc[2]/tempssc[4]; tempssc[4] *= (slope23*tempssc[5] + inter23); // Scale X and Y. scaleX = screenSize.width/tempssc[2]; scaleY = screenSize.height/tempssc[3]; for (int i=0; i < tempssc.length; i+=2) { tempssc[i] *= scaleX; tempssc[i+1] *= scaleY; } //showCoordinates(tempssc); } /* showCoordinates(): * This method is for debugging purposes only. Usually if you're not sure of what is being stored, * you may want to output its value. */ public void showCoordinates(double[] temp) { System.out.println("Copy 1 set: ("+temp[0]+","+temp[1]+")"); System.out.println("Copy 2 set: ("+temp[2]+","+temp[3]+")"); System.out.println("Copy 3 set: ("+temp[4]+","+temp[5]+")"); System.out.println("Copy 4 set: ("+temp[6]+","+temp[7]+")"); } /* convertCoords(): * This method converts the skewed screen coordinates of the input IR source * to the screen coordinates of the monitor. */ public int[] convertCoords(double[] rawCoords) { int[] realCoords = new int[2]; //System.out.println("Raw Data : "+rawCoords[0]+" "+rawCoords[1]); rawCoords[0] -= transX;// Move raw coordinates relative to the 4th coordinates. rawCoords[1] -= transY; //System.out.println("Translted: "+rawCoords[0]+" "+rawCoords[1]); rawCoords[1] -= rawCoords[0]*shearY;// Shear the coordinate relative to the 3rd and 4th coordinates forming the x-axis; rawCoords[0] -= rawCoords[1]*shearX;// Shear the coordinate relative to the 1st and 4th coordinates forming the y-axis; //System.out.println("Sheared : "+rawCoords[0]+" "+rawCoords[1]); rawCoords[1] *= (slope12*rawCoords[0] + inter12);// Size the coordinate relative to the 1st and 2nd coordinate slope. rawCoords[0] *= (slope23*rawCoords[1] + inter23);// Size the coordinate relative to the 2nd and 3rd coordinate slope. //System.out.println("Skewed : "+rawCoords[0]+" "+rawCoords[1]); rawCoords[0] *= scaleX;// Scale the coordinate to the screen resolution by scaling factor scaleX and scaleY. rawCoords[1] *= scaleY; realCoords[0] = (int)Math.round(rawCoords[0]); realCoords[1] = (int)Math.round(rawCoords[1]); //System.out.println("Converted: "+realCoords[0]+" "+realCoords[1]); return realCoords; } }// End class.