/** * Part of the SurfaceMapper library: http://surfacemapper.sourceforge.net/ * Copyright (c) 2011-12 Ixagon AB * * This source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This code is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * A copy of the GNU General Public License is available on the World * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also * obtain it by writing to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package ixagon.surface.mapper; import java.awt.Event; import java.awt.Rectangle; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import jto.processing.sketch.mapper.Sketch; import processing.core.PApplet; import processing.core.PFont; import processing.core.PGraphics; import processing.core.PImage; import processing.core.PVector; import processing.data.XML; import processing.event.KeyEvent; import processing.event.MouseEvent; public class SurfaceMapper { final static public int MODE_RENDER = 0; final static public int MODE_CALIBRATE = 1; final static public int CMD = 157; public final String VERSION = "1"; public int MODE = MODE_CALIBRATE; protected ArrayList<SuperSurface> surfaces; protected ArrayList<SuperSurface> selectedSurfaces; protected boolean snap = true; protected PVector prevMouse = new PVector(); protected boolean ctrlDown; protected boolean altDown; protected boolean grouping; protected Rectangle selectionTool; protected PVector startPos; protected boolean isDragging; protected boolean disableSelectionTool; private PApplet parent; private boolean allowUserInput; private int snapDistance = 30; private int selectionDistance = 15; private int selectionMouseColor; private int numAddedSurfaces = 0; private PImage backgroundTexture; private boolean usingBackground = false; private int[] ccolor; private int width; private int height; private PFont idFont; private boolean debug = true; private boolean shaking; private int shakeStrength; private int shakeSpeed; private float shakeAngle; private float shakeZ; private boolean enableSelectionMouse; private Object eventHandlerObject; private Method eventHandlerMethod; private String eventHandlerMethodName; private List<Sketch> sketchList = new ArrayList<Sketch>(); /** * Create instance of IxKeystone * * @param parent * @param width * @param height */ public SurfaceMapper(PApplet parent, int width, int height) { this.parent = parent; this.enableMouseEvents(); this.parent.registerMethod("keyEvent", this); this.width = width; this.height = height; this.ccolor = new int[0]; this.idFont = parent.createFont("Verdana", 80); this.setSelectionMouseColor(0xFFCCCCCC); surfaces = new ArrayList<SuperSurface>(); selectedSurfaces = new ArrayList<SuperSurface>(); allowUserInput = true; enableSelectionMouse = true; } public static <T> void removeDuplicates(ArrayList<T> list) { int size = list.size(); int out = 0; { final Set<T> encountered = new HashSet<T>(); for (int in = 0; in < size; in++) { final T t = list.get(in); final boolean first = encountered.add(t); if (first) { list.set(out++, t); } } } while (out < size) { list.remove(--size); } } public void addEventHandler(Object object, String methodName) { eventHandlerObject = object; eventHandlerMethodName = methodName; try { eventHandlerMethod = object.getClass().getMethod(methodName, new Class[] { int.class }); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } /** * Add a surface to selected surfaces * * @param cps */ public void addSelectedSurface(SuperSurface cps) { selectedSurfaces.add(cps); } /** * Add a surface to surfaceMapper * * @param superSurface * @return */ public SuperSurface addSurface(SuperSurface superSurface) { if (ccolor.length > 0) superSurface.setColor(ccolor[superSurface.getId() % ccolor.length]); superSurface.setModeCalibrate(); surfaces.add(superSurface); if (superSurface.getId() >= numAddedSurfaces) numAddedSurfaces = superSurface.getId() + 1; return surfaces.get(surfaces.size() - 1); } /** * Places the surface last in the surfaces array, i.e. on top. * * @param index */ public void bringSurfaceToFront(int index) { SuperSurface s = surfaces.get(index); surfaces.remove(index); surfaces.add(s); } /** * Clears the arraylist of selected surfaces. */ public void clearSelectedSurfaces() { selectedSurfaces.clear(); } /** * Remove all surfaces */ public void clearSurfaces() { selectedSurfaces.clear(); surfaces.clear(); } public void bringFrontSurface() { } public void bringBackSurface() { } /** * Creates a Bezier surface with perspective transform. Res is the amount of subdivisioning. Returns the surface after it has been created. * * @param res * @return */ public SuperSurface createBezierSurface(int res) { SuperSurface s = new BezierSurface(parent, this, parent.mouseX, parent.mouseY, res, getNextIndex()); if (ccolor.length > 0) s.setColor(ccolor[numAddedSurfaces % ccolor.length]); s.setModeCalibrate(); surfaces.add(s); numAddedSurfaces++; return s; } /** * Creates a Bezier surface at X/Y with perspective transform. Res is the amount of subdivisioning. Returns the surface after it has been created. * * @param res * @param x * @param y * @return */ public SuperSurface createBezierSurface(int res, int x, int y) { SuperSurface s = new BezierSurface(parent, this, x, y, res, getNextIndex()); if (ccolor.length > 0) s.setColor(ccolor[numAddedSurfaces % ccolor.length]); s.setModeCalibrate(); surfaces.add(s); numAddedSurfaces++; return s; } /** * Creates a Quad surface with perspective transform. Res is the amount of subdivisioning. Returns the surface after it has been created. * * @param res * @return */ public SuperSurface createQuadSurface(int res) { SuperSurface s = new QuadSurface(parent, this, parent.mouseX, parent.mouseY, res, getNextIndex()); if (ccolor.length > 0) s.setColor(ccolor[numAddedSurfaces % ccolor.length]); s.setModeCalibrate(); surfaces.add(s); numAddedSurfaces++; return s; } /** * Creates a Quad surface at X/Y with perspective transform. Res is the amount of subdivisioning. Returns the surface after it has been created. * * @param res * @param x * @param y * @return */ public SuperSurface createQuadSurface(int res, int x, int y) { SuperSurface s = new QuadSurface(parent, this, x, y, res, getNextIndex()); if (ccolor.length > 0) s.setColor(ccolor[numAddedSurfaces % ccolor.length]); s.setModeCalibrate(); surfaces.add(s); numAddedSurfaces++; return s; } /** * Unregisters Mouse Event listener for the SurfaceMapper */ public void disableMouseEvents() { this.parent.unregisterMethod("mouseEvent", this); } /** * Registers Mouse Event listener for the SurfaceMapper */ public void enableMouseEvents() { this.parent.registerMethod("mouseEvent", this); } /** * Check if coordinates is inside any of the bezier surfaces. * * @param mX * @param mY * @return */ public boolean findActiveBezierSurface(float mX, float mY) { for (SuperSurface surface : surfaces) { if (surface.isInside(mX, mY) && surface.getSurfaceType() == SuperSurface.BEZIER) { return true; } } return false; } /** * Check if coordinates is inside any of the quad surfaces. * * @param mX * @param mY * @return */ public boolean findActiveQuadSurface(float mX, float mY) { for (SuperSurface surface : surfaces) { if (surface.isInside(mX, mY) && surface.getSurfaceType() == SuperSurface.QUAD) { return true; } } return false; } /** * Check if coordinates is inside any of the surfaces. * * @param mX * @param mY * @return */ public boolean findActiveSurface(float mX, float mY) { for (SuperSurface surface : surfaces) { if (surface.isInside(mX, mY)) { return true; } } return false; } /** * Fire event to object */ public void fireEvent(int id) { if (eventHandlerMethod != null) { try { eventHandlerMethod.invoke(eventHandlerObject, id); } catch (Exception e) { e.printStackTrace(); } } } /** * Returns the array of colors used in calibration mode for coloring the surfaces. * * @return */ public int[] getColor() { return ccolor; } /** * See if debug mode is on. * * @return */ public boolean getDebug() { return debug; } /** * Is the selection tool disabled? * * @return */ public boolean getDisableSelectionTool() { return disableSelectionTool; } /** * Get font for drawing text * * @return */ public PFont getIdFont() { return idFont; } /** * Check which mode is enabled (render or calibrate) * * @return */ public int getMode() { return this.MODE; } private int getNextIndex() { if (surfaces.isEmpty()) { return 0; } int index = 0; for (SuperSurface superSurface : surfaces) { if (superSurface.getId() > index) { index = superSurface.getId(); } } return index + 1; } public int getNumAddedSurfaces() { return numAddedSurfaces; } /** * Get previous mouse position * * @return */ public PVector getPrevMouse() { return prevMouse; } /** * Get the selected surfaces * * @return */ public ArrayList<SuperSurface> getSelectedSurfaces() { return selectedSurfaces; } /** * Get current max distance for an object to be selected * * @return */ public int getSelectionDistance() { return selectionDistance; } public int getSelectionMouseColor() { return selectionMouseColor; } /** * Returns the rectangle used for selecting surfaces * * @return */ public Rectangle getSelectionTool() { return selectionTool; } public List<Sketch> getSketchList() { return sketchList; } /** * See if snap mode is used * * @return */ public boolean getSnap() { return snap; } /** * See the snap distance * * @return */ public int getSnapDistance() { return snapDistance; } /** * Get surface by Id. * * @param id * @return */ public SuperSurface getSurfaceById(int id) { for (SuperSurface superSurface : surfaces) { if (superSurface.getId() == id) { return superSurface; } } return null; } /** * Get all surfaces * * @return surfaces */ public ArrayList<SuperSurface> getSurfaces() { return surfaces; } /** * Check if any user event is allowed * * @return */ public boolean isAllowUserInput() { return allowUserInput; } /** * Is ALT pressed? * * @return */ public boolean isAltDown() { return altDown; } /** * Is CTRL pressed? * * @return */ public boolean isCtrlDown() { return ctrlDown; } /** * @return */ public boolean isDragging() { return isDragging; } public boolean isEnableSelectionMouse() { return enableSelectionMouse; } /** * Check if multiple surfaces are being manipulated * * @return */ public boolean isGrouping() { return grouping; } /** * Boolean used to know if the background image should be rendered in calibration mode. * * @return */ public boolean isUsingBackground() { return usingBackground; } /** * KeyEvent method * * @param k */ public void keyEvent(KeyEvent k) { if (MODE == MODE_RENDER) return; // ignore everything unless we're in calibration mode switch (k.getAction()) { case KeyEvent.RELEASE: switch (k.getKeyCode()) { case KeyEvent.CTRL: case CMD: ctrlDown = false; break; case KeyEvent.ALT: altDown = false; break; } break; case KeyEvent.PRESS: switch (k.getKeyCode()) { case '1': if (selectedSurfaces.size() == 1) selectedSurfaces.get(0).setSelectedCorner(0); break; case '2': if (selectedSurfaces.size() == 1) selectedSurfaces.get(0).setSelectedCorner(1); break; case '3': if (selectedSurfaces.size() == 1) selectedSurfaces.get(0).setSelectedCorner(2); break; case '4': if (selectedSurfaces.size() == 1) selectedSurfaces.get(0).setSelectedCorner(3); case '9': if (selectedSurfaces.size() == 1) selectedSurfaces.get(0).setId(0); break; case 38: if (!altDown && !ctrlDown) { for (SuperSurface ss : selectedSurfaces) { movePoint(ss, 0, -1); } } // ALT is Offset! if (altDown && !ctrlDown) { for (SuperSurface ss : selectedSurfaces) { ss.setTextureWindow(ss.getTextureWindow()[0].x, ss.getTextureWindow()[0].y + 0.05f, ss.getTextureWindow()[1].x, ss.getTextureWindow()[1].y); } } // CTRL is Size! if (!altDown && ctrlDown) { for (SuperSurface ss : selectedSurfaces) { ss.setTextureWindow(ss.getTextureWindow()[0].x, ss.getTextureWindow()[0].y, ss.getTextureWindow()[1].x, ss.getTextureWindow()[1].y + 0.05f); } } break; case 40: if (!altDown && !ctrlDown) { for (SuperSurface ss : selectedSurfaces) { movePoint(ss, 0, 1); } } // ALT is Offset! if (altDown && !ctrlDown) { for (SuperSurface ss : selectedSurfaces) { ss.setTextureWindow(ss.getTextureWindow()[0].x, ss.getTextureWindow()[0].y - 0.05f, ss.getTextureWindow()[1].x, ss.getTextureWindow()[1].y); } } // CTRL is Size! if (!altDown && ctrlDown) { for (SuperSurface ss : selectedSurfaces) { ss.setTextureWindow(ss.getTextureWindow()[0].x, ss.getTextureWindow()[0].y, ss.getTextureWindow()[1].x, ss.getTextureWindow()[1].y - 0.05f); } } break; case 37: if (!altDown && !ctrlDown) { for (SuperSurface ss : selectedSurfaces) { movePoint(ss, -1, 0); } } // ALT is Offset! if (altDown && !ctrlDown) { for (SuperSurface ss : selectedSurfaces) { ss.setTextureWindow(ss.getTextureWindow()[0].x + 0.05f, ss.getTextureWindow()[0].y, ss.getTextureWindow()[1].x, ss.getTextureWindow()[1].y); } } // CTRL is Size! if (!altDown && ctrlDown) { for (SuperSurface ss : selectedSurfaces) { ss.setTextureWindow(ss.getTextureWindow()[0].x, ss.getTextureWindow()[0].y, ss.getTextureWindow()[1].x + 0.05f, ss.getTextureWindow()[1].y); } } break; case 39: if (!altDown && !ctrlDown) { for (SuperSurface ss : selectedSurfaces) { movePoint(ss, 1, 0); } } // ALT is Offset! if (altDown && !ctrlDown) { for (SuperSurface ss : selectedSurfaces) { ss.setTextureWindow(ss.getTextureWindow()[0].x - 0.05f, ss.getTextureWindow()[0].y, ss.getTextureWindow()[1].x, ss.getTextureWindow()[1].y); } } // CTRL is Size! if (!altDown && ctrlDown) { for (SuperSurface ss : selectedSurfaces) { ss.setTextureWindow(ss.getTextureWindow()[0].x, ss.getTextureWindow()[0].y, ss.getTextureWindow()[1].x - 0.05f, ss.getTextureWindow()[1].y); } } break; /* * case KeyEvent.VK_O: for (SuperSurface ss : selectedSurfaces) { ss.increaseResolution(); } break; * * case KeyEvent.VK_P: for (SuperSurface ss : selectedSurfaces) { ss.decreaseResolution(); } break; * * case KeyEvent.VK_U: for (SuperSurface ss : selectedSurfaces) { ss.increaseHorizontalForce(); } break; * * case KeyEvent.VK_I: for (SuperSurface ss : selectedSurfaces) { ss.decreaseHorizontalForce(); } break; * * case KeyEvent.VK_J: for (SuperSurface ss : selectedSurfaces) { ss.increaseVerticalForce(); } break; * * case KeyEvent.VK_K: for (SuperSurface ss : selectedSurfaces) { ss.decreaseVerticalForce(); } break; * * case KeyEvent.VK_T: for (SuperSurface ss : selectedSurfaces) { ss.toggleLocked(); } break; * */ //SUPR key. Todo //case 147: // removeSelectedSurfaces(); // break; case KeyEvent.CTRL: case CMD: ctrlDown = true; grouping = true; break; case KeyEvent.ALT: altDown = true; break; default: break; } } } /** * MouseEvent method. * * @param e */ public void ksMouseEvent(MouseEvent e) { if (this.MODE == SurfaceMapper.MODE_RENDER) return; int mX = e.getX(); int mY = e.getY(); switch (e.getAction()) { case MouseEvent.PRESS: selectSurfaces(mX, mY); break; case MouseEvent.DRAG: if (this.MODE == SurfaceMapper.MODE_CALIBRATE) { float deltaX = mX - prevMouse.x; float deltaY = mY - prevMouse.y; // Right mouse button drags very slowly. if (e.getButton() == PApplet.RIGHT) { deltaX *= 0.1; deltaY *= 0.1; } boolean[] movingPolys = new boolean[surfaces.size()]; int iteration = 0; for (SuperSurface ss : selectedSurfaces) { movingPolys[iteration] = false; // Don't allow editing of surface if it's locked! if (!ss.isLocked()) { if (ss.getSelectedBezierControl() != -1) { ss.setBezierPoint(ss.getSelectedBezierControl(), ss.getBezierPoint(ss.getSelectedBezierControl()).x + deltaX, ss.getBezierPoint(ss.getSelectedBezierControl()).y + deltaY); } else if (ss.getActivePoint() != -1) { // special case. // index 2000 is the center point so move all four // corners. if (ss.getActivePoint() == 2000) { // If multiple surfaces are selected, ALT need // to be pressed in order to move them. if ((grouping && altDown) || selectedSurfaces.size() == 1) { boolean cornerMovementAllowed = true; for (int i = 0; i < 4; i++) { Point3D[] cTemp = new Point3D[4]; for (int j = 0; j < 4; j++) { cTemp[j] = new Point3D(ss.getCornerPoint(j).x, ss.getCornerPoint(j).y); } cTemp[i].x = ss.getCornerPoint(i).x + deltaX; cTemp[i].y = ss.getCornerPoint(i).y + deltaY; for (int j = 0; j < cTemp.length; j++) { if (QuadSurface.CCW(cTemp[(j + 2) % cTemp.length], cTemp[(j + 1) % cTemp.length], cTemp[j % cTemp.length]) >= 0) { cornerMovementAllowed = false; } } } if (cornerMovementAllowed) { for (int i = 0; i < 4; i++) { ss.setCornerPoint(i, ss.getCornerPoint(i).x + deltaX, ss.getCornerPoint(i).y + deltaY); if (SuperSurface.BEZIER == ss.getSurfaceType()) { ss.setBezierPoint(i, ss.getBezierPoint(i).x + deltaX, ss.getBezierPoint(i).y + deltaY); ss.setBezierPoint( i + 4, ss.getBezierPoint(i + 4).x + deltaX, ss.getBezierPoint(i + 4).y + deltaY); } } } movingPolys[iteration] = true; } } else { // Move a corner point. int index = ss.getActivePoint(); ss.setCornerPoint(index, ss.getCornerPoint(ss.getActivePoint()).x + deltaX, ss.getCornerPoint(ss.getActivePoint()).y + deltaY); if (SuperSurface.BEZIER == ss.getSurfaceType()) { index = index * 2; ss.setBezierPoint(index, ss.getBezierPoint(index).x + deltaX, ss.getBezierPoint(index).y + deltaY); index = index + 1; ss.setBezierPoint(index, ss.getBezierPoint(index).x + deltaX, ss.getBezierPoint(index).y + deltaY); } movingPolys[iteration] = true; } } } iteration++; } for (int i = 0; i < movingPolys.length; i++) { if (movingPolys[i]) { disableSelectionTool = true; break; } } if (altDown) disableSelectionTool = true; if (!disableSelectionTool && startPos != null) { selectionTool = new Rectangle((int) startPos.x, (int) startPos.y, (int) (mX - startPos.x), (int) (mY - startPos.y)); PVector sToolPos = new PVector(selectionTool.x, selectionTool.y); if (selectionTool.x < selectionTool.x - selectionTool.width) { sToolPos.set(sToolPos.x + selectionTool.width, sToolPos.y, 0); } if (selectionTool.y < selectionTool.y - selectionTool.height) { sToolPos.set(sToolPos.x, sToolPos.y + selectionTool.height, 0); } for (SuperSurface cps : surfaces) { java.awt.Polygon p = cps.getPolygon(); if (p.intersects(sToolPos.x, sToolPos.y, Math.abs(selectionTool.width), Math.abs(selectionTool.height))) { cps.setSelected(true); selectedSurfaces.add(cps); removeDuplicates(selectedSurfaces); grouping = true; } else { if (!ctrlDown) { cps.setSelected(false); selectedSurfaces.remove(cps); } } } } isDragging = true; } break; case MouseEvent.RELEASE: if (this.MODE == SurfaceMapper.MODE_CALIBRATE) { if (snap) { for (SuperSurface ss : selectedSurfaces) { if (ss.getActivePoint() != 2000 && ss.getActivePoint() != -1) { int closestIndex = -1; int cornerIndex = -1; float closestDist = this.getSnapDistance() + 1; for (int j = 0; j < surfaces.size(); j++) { if (surfaces.get(j).getId() != ss.getId()) { for (int i = 0; i < surfaces.get(j).getCornerPoints().length; i++) { float dist = PApplet.dist(ss.getCornerPoint(ss.getActivePoint()).x, ss.getCornerPoint(ss.getActivePoint()).y, surfaces.get(j).getCornerPoint(i).x, surfaces.get(j).getCornerPoint(i).y); if (dist < this.getSnapDistance()) { if (dist < closestDist) { closestDist = dist; closestIndex = j; cornerIndex = i; } } } } } if (closestDist > -1 && closestDist < this.getSnapDistance()) { ss.setCornerPoint(ss.getActivePoint(), surfaces.get(closestIndex).getCornerPoint(cornerIndex).x, surfaces.get(closestIndex).getCornerPoint(cornerIndex).y); } } } int selection = 0; for (SuperSurface cps : surfaces) { cps.setActivePoint(-1); if (cps.getActiveCornerPointIndex(mX, mY) != -1) selection++; } if (isDragging) selection++; if (selection == 0) { for (SuperSurface ss : selectedSurfaces) { ss.setSelected(false); } grouping = false; selectedSurfaces.clear(); } } } for (SuperSurface ss : selectedSurfaces) { fireEvent(ss.getId()); } startPos = new PVector(0, 0); selectionTool = null; disableSelectionTool = false; isDragging = false; break; } prevMouse = new PVector(mX, mY, 0); } /** * Load projection map from file * * @param file */ public void load(File file) { if (this.MODE == SurfaceMapper.MODE_CALIBRATE) { if (file.exists()) { this.setGrouping(false); selectedSurfaces.clear(); surfaces.clear(); try { XML root = new XML(file); for (int i = 0; i < root.getChildCount(); i++) { if (root.getChild(i).getName().equals("surface")) { SuperSurface loaded = null; if (SuperSurface.BEZIER == root.getChild(i).getInt("type")) { loaded = new BezierSurface(parent, this, root.getChild(i), root.getChild(i).getInt("id"), root.getChild(i).getString("name")); } else { loaded = new QuadSurface(parent, this, root.getChild(i), root.getChild(i).getInt("id"), root.getChild(i).getString("name")); } if (ccolor.length > 0) loaded.setColor(ccolor[numAddedSurfaces % ccolor.length]); loaded.setModeCalibrate(); final String sketch = root.getChild(i).getString("sketch"); if (null != sketch && sketch.trim().length() > 0) { loadSketch(loaded, sketch); } surfaces.add(loaded); numAddedSurfaces++; } } if (this.getDebug()) PApplet.println("Projection layout loaded from " + file.getName() + ". " + surfaces.size() + " surfaces were loaded!"); } catch (Exception e) { PApplet.println("Error loading configuration!!!"); e.printStackTrace(); } } else { if (this.getDebug()) PApplet.println("ERROR loading XML! No projection layout exists!"); } } } private void loadSketch(final SuperSurface loaded, final String sketchName) { Optional<Sketch> sketch = sketchList.stream().filter(s -> sketchName.equals(s.getName())).findFirst(); if (sketch.isPresent()) { loaded.setSketch(sketch.get()); } } /** * MouseEvent method. Forwards the MouseEvent to ksMouseEvent if user input is allowed * * @param e */ public void mouseEvent(MouseEvent e) { if (allowUserInput) { ksMouseEvent(e); } } /** * Move a point of a surface * * @param ss * @param x * @param y */ public void movePoint(SuperSurface ss, int x, int y) { int index = ss.getSelectedCorner(); ss.setCornerPoint(index, ss.getCornerPoint(index).x + x, ss.getCornerPoint(index).y + y); if (SuperSurface.BEZIER == ss.getSurfaceType()) { index = index * 2; ss.setBezierPoint(index, ss.getBezierPoint(index).x + x, ss.getBezierPoint(index).y + y); index = index + 1; ss.setBezierPoint(index, ss.getBezierPoint(index).x + x, ss.getBezierPoint(index).y + y); } } /** * Delete the selected surfaces */ public void removeSelectedSurfaces() { for (SuperSurface ss : selectedSurfaces) { for (int i = surfaces.size() - 1; i >= 0; i--) { if (ss.getId() == surfaces.get(i).getId()) { if (ss.isLocked()) return; if (this.getDebug()) PApplet.println("SurfaceMapper --> DELETED SURFACE with ID: #" + ss.getId()); surfaces.remove(i); } } } this.setGrouping(false); selectedSurfaces.clear(); if (surfaces.size() == 0) numAddedSurfaces = 0; } /** * Render method used when calibrating. Shouldn't be used for final rendering. * * @param glos */ public void render(PGraphics glos) { // glos.beginDraw(); // glos.endDraw(); if (MODE == MODE_CALIBRATE) { parent.cursor(); glos.beginDraw(); if (this.isUsingBackground()) { glos.image(backgroundTexture, 0, 0, width, height); } glos.fill(0, 40); glos.noStroke(); glos.rect(-2, -2, width + 4, height + 4); glos.stroke(255, 255, 255, 40); glos.strokeWeight(1); /* * float gridRes = 32.0f; * * float step = (float) (width / gridRes); * * for (float i = 1; i < width; i += step) { glos.line(i, 0, i, parent.height); } * * step = (float) (height / gridRes); * * for (float i = 1; i < width; i += step) { glos.line(0, i, parent.width, i); } */ glos.stroke(255); glos.strokeWeight(2); glos.line(1, 1, width - 1, 1); glos.line(width - 1, 1, width - 1, height - 1); glos.line(1, height - 1, width - 1, height - 1); glos.line(1, 1, 1, height - 1); glos.endDraw(); for (int i = 0; i < surfaces.size(); i++) { surfaces.get(i).render(glos); } // Draw circles for SelectionDistance or SnapDistance (snap if CMD // is down) glos.beginDraw(); if (enableSelectionMouse) { if (!ctrlDown) { glos.ellipseMode(PApplet.CENTER); glos.fill(this.getSelectionMouseColor(), 100); glos.noStroke(); glos.ellipse(parent.mouseX, parent.mouseY, this.getSelectionDistance() * 2, this.getSelectionDistance() * 2); } else { glos.ellipseMode(PApplet.CENTER); glos.fill(255, 0, 0, 100); glos.noStroke(); glos.ellipse(parent.mouseX, parent.mouseY, this.getSnapDistance() * 2, this.getSnapDistance() * 2); } } if (selectionTool != null && !disableSelectionTool) { glos.stroke(255, 100); glos.strokeWeight(1); glos.fill(0, 200, 255, 50); glos.rect(selectionTool.x, selectionTool.y, selectionTool.width, selectionTool.height); glos.noStroke(); } glos.endDraw(); } else { parent.noCursor(); } } /** * Puts all projection mapping data in the XMLElement * * @param root */ public void save(XML root) { root.setName("ProjectionMap"); // create XML elements for each surface containing the resolution // and control point data for (SuperSurface s : surfaces) { XML surf = new XML("surface"); surf.setInt("type", s.getSurfaceType()); surf.setInt("id", s.getId()); surf.setString("name", s.getSurfaceName()); surf.setInt("res", s.getRes()); surf.setString("lock", String.valueOf(s.isLocked())); surf.setInt("horizontalForce", s.getHorizontalForce()); surf.setInt("verticalForce", s.getVerticalForce()); surf.setString("sketch", s.getSketch().getName()); for (int i = 0; i < s.getCornerPoints().length; i++) { XML cp = new XML("cornerpoint"); cp.setInt("i", i); cp.setFloat("x", s.getCornerPoint(i).x); cp.setFloat("y", s.getCornerPoint(i).y); surf.addChild(cp); } if (s.getSurfaceType() == SuperSurface.BEZIER) { for (int i = 0; i < 8; i++) { XML bp = new XML("bezierpoint"); bp.setInt("i", i); bp.setFloat("x", s.getBezierPoint(i).x); bp.setFloat("y", s.getBezierPoint(i).y); surf.addChild(bp); } } root.addChild(surf); } } /** * Save all projection mapping data to file * * @param file */ public void save(File file) { if (this.MODE == SurfaceMapper.MODE_CALIBRATE) { XML root = new XML("root"); this.save(root); try { root.save(file); } catch (Exception e) { PApplet.println(e.getStackTrace()); } } } public void selectSurfaces(int mX, int mY) { if (this.MODE == SurfaceMapper.MODE_CALIBRATE) { startPos = new PVector(mX, mY); for (int i = surfaces.size() - 1; i >= 0; i--) { SuperSurface cps = surfaces.get(i); cps.setActivePoint(cps.getActiveCornerPointIndex(mX, mY)); cps.setSelectedBezierControl(cps.getActiveBezierPointIndex(mX, mY)); if (cps.getActivePoint() >= 0 || cps.getSelectedBezierControl() >= 0) { if (grouping && !ctrlDown) { if (!cps.isSelected()) { for (SuperSurface ss : selectedSurfaces) { ss.setSelected(false); } grouping = false; selectedSurfaces.clear(); } } disableSelectionTool = true; if (ctrlDown && grouping) { boolean actionTaken = false; if (cps.isSelected()) { cps.setSelected(false); for (int j = selectedSurfaces.size() - 1; j >= 0; j--) { if (cps.getId() == selectedSurfaces.get(j).getId()) selectedSurfaces.remove(j); } actionTaken = true; } if (!cps.isSelected() && !actionTaken) { cps.setSelected(true); selectedSurfaces.add(cps); removeDuplicates(selectedSurfaces); } } else { if (grouping == false) { for (SuperSurface ss : selectedSurfaces) { ss.setSelected(false); } selectedSurfaces.clear(); cps.setSelected(true); selectedSurfaces.add(cps); } } // no need to loop through all surfaces unless multiple // surfaces has been selected if (!grouping) break; } } if (grouping) { int moveClick = 0; for (SuperSurface ss : selectedSurfaces) { if (ss.getActivePoint() == 2000) moveClick++; } // PApplet.println(moveClick); if (moveClick > 0) { for (SuperSurface ss : selectedSurfaces) { ss.setActivePoint(2000); // PApplet.println(ss.getActivePoint()); } } } } } /** * Set if any user event is allowed * * @param allowUserInput */ public void setAllowUserInput(boolean allowUserInput) { this.allowUserInput = allowUserInput; } /** * Optionally set a background image in calibration mode. * * @param tex */ public void setBackground(PImage tex) { this.backgroundTexture = tex; this.setUsingBackground(true); } /** * Set the array of colors used in calibration mode for coloring the surfaces. * * @param ccolor */ public void setColor(int[] ccolor) { this.ccolor = ccolor; } /** * Set CTRL pressed * * @param pressed */ public void setCtrlDown(boolean pressed) { ctrlDown = pressed; } /** * Manually set debug mode. (debug mode will print more to console) * * @param debug */ public void setDebug(boolean debug) { this.debug = debug; } /** * Enable/disable selection tool * * @param disableSelectionTool */ public void setDisableSelectionTool(boolean disableSelectionTool) { this.disableSelectionTool = disableSelectionTool; } public void setEnableSelectionMouse(boolean enableSelectionMouse) { this.enableSelectionMouse = enableSelectionMouse; } /** * Set if multiple surfaces are being manipulated * * @param grouping */ public void setGrouping(boolean grouping) { this.grouping = grouping; } /** * @param isDragging */ public void setIsDragging(boolean isDragging) { this.isDragging = isDragging; } /** * Set mode to calibrate */ public void setModeCalibrate() { this.MODE = SurfaceMapper.MODE_CALIBRATE; for (SuperSurface s : surfaces) { s.setModeCalibrate(); } } /** * Set mode to render */ public void setModeRender() { this.MODE = SurfaceMapper.MODE_RENDER; for (SuperSurface s : surfaces) { s.setModeRender(); } } public void setNumAddedSurfaces(int numAddedSurfaces) { this.numAddedSurfaces = numAddedSurfaces; } /** * Set previous mouse position * * @param x * @param y */ public void setPrevMouse(float x, float y) { prevMouse = new PVector(x, y); } /** * Select the surface. Deselects all previously selected surfaces. * * @param cps */ public void setSelectedSurface(SuperSurface cps) { for (SuperSurface ss : selectedSurfaces) { ss.setSelected(false); } selectedSurfaces.clear(); cps.setSelected(true); selectedSurfaces.add(cps); } /** * Set the max distance for an object to be selected * * @param selectionDistance */ public void setSelectionDistance(int selectionDistance) { this.selectionDistance = selectionDistance; } public void setSelectionMouseColor(int selectionMouseColor) { this.selectionMouseColor = selectionMouseColor; } /** * Set the selection tool * * @param r */ public void setSelectionTool(Rectangle r) { selectionTool = r; } /** * Set the selection tool * * @param x * @param y * @param width * @param height */ public void setSelectionTool(int x, int y, int width, int height) { selectionTool = new Rectangle(x, y, width, height); } /** * Shake all surfaces with max Z-displacement strength, vibration-speed speed, and shake decline fallOfSpeed. (min 0, max 1000 (1000 = un-ending shaking)) * * @param strength * @param speed * @param fallOfSpeed */ public void setShakeAll(int strength, int speed, int fallOfSpeed) { for (SuperSurface ss : surfaces) { ss.setShake(strength, speed, fallOfSpeed); } } public void setSketchList(List<Sketch> sketchList) { this.sketchList = sketchList; } /** * Manually set corner snap mode * * @param snap */ public void setSnap(boolean snap) { this.snap = snap; } /** * Set corner snap distance * * @param snapDistance */ public void setSnapDistance(int snapDistance) { this.snapDistance = snapDistance; } /** * Set if background image should rendered in calibration mode * * @param val */ public void setUsingBackground(boolean val) { usingBackground = val; } /** * Update shaking for all surfaces */ public void shake() { for (SuperSurface ss : surfaces) { ss.shake(); } } /** * Toggle the mode */ public void toggleCalibration() { if (MODE == MODE_RENDER) MODE = MODE_CALIBRATE; else MODE = MODE_RENDER; for (SuperSurface s : surfaces) { if (MODE == MODE_CALIBRATE) s.setModeCalibrate(); else s.setModeRender(); } } /** * Toggle if corner snapping is used */ public void toggleSnap() { snap = !snap; } public String version() { return VERSION; } }