/**
* Squidy is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Squidy 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Squidy. If not, see <http://www.gnu.org/licenses/>.
*
* 2006-2009 Human-Computer Interaction Group, University of Konstanz.
* <http://hci.uni-konstanz.de>
*
* Please contact info@squidy-lib.de or visit our website http://squidy-lib.de for
* further information.
*/
/**
*
*/
package org.squidy.nodes;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import org.squidy.manager.ProcessException;
import org.squidy.manager.controls.CheckBox;
import org.squidy.manager.controls.Slider;
import org.squidy.manager.controls.TextField;
import org.squidy.manager.data.DataConstant;
import org.squidy.manager.data.IData;
import org.squidy.manager.data.IDataContainer;
import org.squidy.manager.data.Processor;
import org.squidy.manager.data.Property;
import org.squidy.manager.data.impl.DataPosition2D;
import org.squidy.manager.model.AbstractNode;
/**
* <code>Synchronize</code>.
*
* <pre>
* Date: Okt 12, 2009
* Time: 15:32:29 PM
* </pre>
*
* @author Nicolas Hirrle <a
* href="mailto:nihirrle@htwg-konstanz.de">nihirrle@htwg-konstanz.de</a>
* Human-Computer Interaction Group University of Konstanz
* @author Roman Rädle, <a href="mailto:Roman.Raedle@uni-konstanz.de">Roman.
* Raedle@uni-konstanz.de</a>, University of Konstanz
*
* @version $Id: Synchronize.java 772 2011-09-16 15:39:44Z raedle $
* @since 1.0.0
*/
@XmlType(name = "Synchronize")
@Processor(
name = "Synchronize",
icon = "/org/squidy/nodes/image/48x48/synchronize.png",
types = { Processor.Type.FILTER },
tags = { "synchronize", "TUIO", "object", "position", "touch", "hittest" }
)
public class Synchronize extends AbstractNode {
// ################################################################################
// BEGIN OF PROPERTIES
// ################################################################################
@XmlAttribute(name = "periodic-messages")
@Property(
name = "Periodic messages",
description = "Sends a periodic update every second just to indicate that the tracker is still available and to correct eventually lost packets in between"
)
@CheckBox
private boolean periodicMessages = false;
public synchronized boolean isPeriodicMessages() {
return periodicMessages;
}
public synchronized void setPeriodicMessages(boolean periodicMessages) {
this.periodicMessages = periodicMessages;
if (isProcessing() && periodicMessages) {
startPeriodicMessages();
}
}
// ################################################################################
@XmlAttribute(name = "touch-synchronization-active")
@Property(
name = "Touch synchronization active",
description = "Indicates whether touch synchronization is active.",
group = "Touch Synchronization"
)
@CheckBox
private boolean touchSynchronizationActive = true;
public boolean isTouchSynchronizationActive() {
return touchSynchronizationActive;
}
public void setTouchSynchronizationActive(boolean touchSynchronizationActive) {
this.touchSynchronizationActive = touchSynchronizationActive;
}
@XmlAttribute(name = "x-resolution")
@Property(
name = "X-Resolution",
description = "X-Resolution (in pixel) of the screen",
group = "Touch Synchronization",
suffix = "pixels"
)
@TextField
private int resolutionX = 1920;
public int getResolutionX() {
return resolutionX;
}
public void setResolutionX(int resolutionX) {
this.resolutionX = resolutionX;
}
@XmlAttribute(name = "y-resolution")
@Property(
name = "Y-Resolution",
description = "Y-Resolution (in pixel) of the screen",
group = "Touch Synchronization",
suffix = "pixels"
)
@TextField
private int resolutionY = 1200;
public int getResolutionY() {
return resolutionY;
}
public void setResolutionY(int resolutionY) {
this.resolutionY = resolutionY;
}
@XmlAttribute(name = "radius")
@Property(
name = "Token radius",
description = "Radius (in Pixel) where Fingertouchevents aren't displayed",
group = "Touch Synchronization",
suffix = "pixels"
)
@Slider(
type = Integer.class,
minimumValue = 0,
maximumValue = 200,
showLabels = true,
showTicks = true,
majorTicks = 50,
minorTicks = 50,
snapToTicks = false
)
private int radius = 17;
/**
* @return the myProperty
*/
public final int getRadius() {
return radius;
}
/**
* @param myProperty
* the myProperty to set
*/
public final void setRadius(int radius) {
this.radius = radius;
}
// ################################################################################
// END OF PROPERTIES
// ################################################################################
private Thread activePeriodicThread = null;
private boolean needPeriodicMessage = false;
/*
* (non-Javadoc)
*
* @see org.squidy.manager.ReflectionProcessable#onStart()
*/
@Override
public void onStart() throws ProcessException {
if (isPeriodicMessages()) {
startPeriodicMessages();
}
}
/*
* (non-Javadoc)
*
* @see org.squidy.manager.ReflectionProcessable#onStop()
*/
@Override
public void onStop() throws ProcessException {
activePeriodicThread = null;
}
/**
*
*/
private void startPeriodicMessages() {
if (activePeriodicThread == null) {
activePeriodicThread = new Thread(new Runnable() {
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
while (isPeriodicMessages()) {
needPeriodicMessage = true;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
stop();
}
if (isPeriodicMessages() && needPeriodicMessage) {
if (objectsAlive.size() > 0) {
publish(objectsAlive.values());
}
else {
DataPosition2D dataPosition2D = new DataPosition2D(ReacTIVision.class, 0, 0);
dataPosition2D.setAttribute(TUIO.OBJECT_STATE, "periodic");
publish(dataPosition2D);
}
}
}
activePeriodicThread = null;
}
});
activePeriodicThread.start();
}
}
// ################################################################################
// BEGIN OF PROCESS
// ################################################################################
private int frameId = 0;
/**
* @return
*/
private synchronized int getSessionId() {
if (frameId > Integer.MAX_VALUE) {
frameId = 0;
}
return ++frameId;
}
private final Map<Integer, DataPosition2D> objectsAlive = new HashMap<Integer, DataPosition2D>();
private final Map<Integer, DataPosition2D> fingersAlive = new HashMap<Integer, DataPosition2D>();
public IData process(DataPosition2D dataPosition2D) {
needPeriodicMessage = false;
if (dataPosition2D.hasAttribute(TUIO.ORIGIN_ADDRESS) &&
"/tuio/2Dobj".equals(dataPosition2D.getAttribute(TUIO.ORIGIN_ADDRESS))) {
if (dataPosition2D.hasAttribute(TUIO.OBJECT_STATE)) {
for (DataPosition2D objectAlive : objectsAlive.values()) {
objectAlive.setAttribute(TUIO.OBJECT_STATE, "refresh");
}
String objectState = (String) dataPosition2D.getAttribute(TUIO.OBJECT_STATE);
Integer fiducialId = (Integer) dataPosition2D.getAttribute(TUIO.FIDUCIAL_ID);
if (objectState.equals("add")) {
dataPosition2D.setAttribute(DataConstant.SESSION_ID, getSessionId());
}
else if (objectsAlive.containsKey(fiducialId)) {
int sessionId = (Integer) objectsAlive.get(fiducialId).getAttribute(DataConstant.SESSION_ID);
dataPosition2D.setAttribute(DataConstant.SESSION_ID, sessionId);
}
objectsAlive.put(fiducialId, dataPosition2D);
publish(objectsAlive.values());
if (objectState.equals("remove")) {
if (objectsAlive.containsKey(fiducialId)) {
objectsAlive.remove(fiducialId);
}
}
// System.out.println("SIZE OF OBJECTS2: " + objectsAlive.size());
}
else {
throw new IllegalStateException("Object state was not set on data object.");
}
return null;
}
if (dataPosition2D.hasAttribute(TUIO.ORIGIN_ADDRESS) &&
"/tuio/2Dcur".equals(dataPosition2D.getAttribute(TUIO.ORIGIN_ADDRESS))){
}
return dataPosition2D;
}
/**
* Within the postProcess method it will be checked whether any data position 2D that is not an object hits current
* alive objects area. If a position hits an objects area the position will be removed otherwise it will be retained.
*
* @see org.squidy.manager.model.AbstractNode#postProcess(org.squidy.manager.data.IDataContainer)
*/
@Override
public IDataContainer postProcess(IDataContainer dataContainer) {
// Do not process if touch synchronization is not activated.
if (!touchSynchronizationActive || objectsAlive.isEmpty()) {
return dataContainer;
}
List<IData> collectionToReturn = new ArrayList<IData>();
boolean hasHit = false;
for (IData data : dataContainer.getData()) {
if (data instanceof DataPosition2D) {
if (!"/tuio/2Dobj".equals(data.getAttribute(TUIO.ORIGIN_ADDRESS))) {
for (DataPosition2D object : objectsAlive.values()) {
if (positionHitsObjectArea((DataPosition2D) data, object)) {
hasHit = true;
break;
}
}
if (!hasHit) {
collectionToReturn.add(data);
}
}
else {
continue;
}
}
else {
collectionToReturn.add(data);
}
}
// Add all objects that are alive otherwise objects get lost.
collectionToReturn.addAll(objectsAlive.values());
dataContainer.setData(collectionToReturn.toArray(new IData[collectionToReturn.size()]));
return dataContainer;
}
/**
* Checks whether a position hits the objects area or not. If the position is within an objects are
* it will return true otherwise false.
*
* @param position The position that gets tested against an objects area.
* @param object The object that indicates the object area and the forbidden positions.
* @return Returns true if the position hits the objects area otherwise false.
*/
private boolean positionHitsObjectArea(DataPosition2D position, DataPosition2D object) {
double x = object.getX() * resolutionX;
double y = object.getY() * resolutionY;
Ellipse2D circle = new Ellipse2D.Double(x - radius, y - radius, 2 * radius, 2 * radius);
double positionX = position.getX() * resolutionX;
double positionY = position.getY() * resolutionY;
return circle.contains(positionX, positionY);
}
// ################################################################################
// END OF PROCESS
// ################################################################################
}