Commit c95a0f34 authored by Nico Mack's avatar Nico Mack

De-jitter of touch functionality

parent 8121574b
......@@ -25,6 +25,10 @@ public class ColorFactory {
// * Constants *
// ***************************************************************************
private static final double THREE_PI_HALF = (3 * Math.PI) / 2;
private static final double TWO_PI_THIRD = (2 * Math.PI) / 3;
private static final double FOUR_PI_THIRD = (4 * Math.PI) / 3;
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
......@@ -63,18 +67,18 @@ public class ColorFactory {
* @param bluePhase
* specifies the phase for the blue channel
* @param center
* allows controlling the gamut of the generated colors. Specifying a higher value for
* center will produce lighter colors, specifying a lower value will produce darker
* shades. Value should be in the range 0 < center < 255
* allows controlling the gamut of the generated colors. Specifying a higher value for center
* will produce lighter colors, specifying a lower value will produce darker shades. Value
* should be in the range 0 < center < 255
* @param spread
* second parameter affecting the gamut of the generated colors. Pastel colors required a
* smaller spread, vivid colours a higher spread. Value should be in the range 0 <=
* spread < center
* smaller spread, vivid colours a higher spread. Value should be in the range 0 <= spread <
* center
*
* @return a vector containing the generated colors
*/
public static List<Color> makeColorGradient(int number, float redFreq, float greenFreq, float blueFreq,
float redPhase, float greenPhase, float bluePhase, int center, int spread) {
public static List<Color> makeColorGradient(int number, double redFreq, double greenFreq, double blueFreq,
double redPhase, double greenPhase, double bluePhase, int center, int spread) {
Preconditions.checkArgument((center > 0) && (center < 255), "Center should be in the range 0 < center < 255"); //$NON-NLS-1$
......@@ -102,9 +106,9 @@ public class ColorFactory {
* @return a vector containing the generated colors
*/
public static List<Color> makeRainbowColours(int number) {
float frequency = (float) (Math.PI / number);
double frequency = (THREE_PI_HALF / number);
return makeColorGradient(number, frequency, frequency, frequency, 0f, 2f, 4f, 128, 127);
return makeColorGradient(number, frequency, frequency, frequency, 0d, TWO_PI_THIRD, FOUR_PI_THIRD, 128, 127);
}
// ---------------------------------------------------------------------------
......
......@@ -33,6 +33,7 @@ public class Body {
private volatile Vector2D position;
private volatile Vector2D velocity;
private volatile Vector2D acceleration;
private volatile boolean immobile;
// ---------------------------------------------------------------------------
// ***************************************************************************
......@@ -50,6 +51,7 @@ public class Body {
this.position = position;
this.velocity = new Vector2D();
this.acceleration = new Vector2D();
this.immobile = false;
}
// ---------------------------------------------------------------------------
......@@ -166,6 +168,26 @@ public class Body {
this.acceleration = acceleration;
}
// ---------------------------------------------------------------------------
/**
* @param immobilizeIt
*/
// ---------------------------------------------------------------------------
public boolean isImmobile() {
return this.immobile;
}
// ---------------------------------------------------------------------------
/**
* @param immobilizeIt
*/
// ---------------------------------------------------------------------------
public void setImmobile(boolean immobilizeIt) {
this.immobile = immobilizeIt;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class
......
......@@ -136,8 +136,12 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
double distance = offset.magnitude() + MIN_DISTANCE;
Vector2D direction = offset.normalize();
Vector2D directedRepulsion = direction.multiplyBy(this.repulsion);
outer.applyForce(directedRepulsion.divideBy(distance * distance * 0.5));
inner.applyForce(directedRepulsion.divideBy(distance * distance * -0.5));
if (!outer.isImmobile()) {
outer.applyForce(directedRepulsion.divideBy(distance * distance * 0.5));
if (!inner.isImmobile()) {
inner.applyForce(directedRepulsion.divideBy(distance * distance * -0.5));
}
}
}
}
}
......@@ -153,9 +157,12 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
double displacement = spring.getLength() - offset.magnitude();
Vector2D direction = offset.normalize();
double exertedForce = spring.getSpringConstant() * displacement;
spring.getFirstAttachment().applyForce(direction.multiplyBy(exertedForce * -0.5));
spring.getSecondAttachment().applyForce(direction.multiplyBy(exertedForce * 0.5));
if (!spring.getFirstAttachment().isImmobile()) {
spring.getFirstAttachment().applyForce(direction.multiplyBy(exertedForce * -0.5));
if (!spring.getSecondAttachment().isImmobile()) {
spring.getSecondAttachment().applyForce(direction.multiplyBy(exertedForce * 0.5));
}
}
}
}
......@@ -164,6 +171,8 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
private void coalesce(List<Body> graphBodies) {
for (Body body : graphBodies) {
if (body.isImmobile())
continue;
Vector2D direction = body.getPosition().multiplyBy(-1);
body.applyForce(direction.multiplyBy(this.repulsion * COALESCE_RATIO));
}
......@@ -173,6 +182,8 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
private void updateVelocities(List<Body> graphBodies, double timestep) {
for (Body body : graphBodies) {
if (body.isImmobile())
continue;
Vector2D velocity = body.getVelocity();
Vector2D deltaV = body.getAcceleration().multiplyBy(timestep);
velocity = velocity.add(deltaV).multiplyBy(damping);
......@@ -188,6 +199,8 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
private void updatePositions(List<Body> graphBodies, double timestep) {
for (Body body : graphBodies) {
if (body.isImmobile())
continue;
Vector2D position = body.getPosition();
position = position.add(body.getVelocity().multiplyBy(timestep));
body.setPosition(position);
......@@ -351,6 +364,8 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
Thread.currentThread().interrupt();
}
} while (!this.inEquilibrum());
this.mobilizeAllBodies();
running = false;
}
......@@ -386,4 +401,13 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
// ---------------------------------------------------------------------------
public void mobilizeAllBodies() {
List<Body> graphBodies = this.getGraphBodies();
for (Body body : graphBodies) {
body.setImmobile(false);
}
}
}
......@@ -28,6 +28,7 @@ import lu.list.itis.dkd.tui.widget.builder.BaseStatefulBuilder;
import lu.list.itis.dkd.tui.widget.corona.FadingCorona;
import lu.list.itis.dkd.tui.widget.state.NuiState;
import lu.list.itis.dkd.tui.widget.state.StateManager;
import lu.list.itis.dkd.tui.widget.touch.TouchEvent;
import com.google.common.base.Preconditions;
......@@ -52,7 +53,8 @@ import java.util.stream.Stream;
@NonNullByDefault
public class StatefulWidget extends BaseWidget {
/** The coalesced NuiState instances per handle. */
protected ConcurrentHashMap<Integer, StateManager> states;
protected ConcurrentHashMap<Integer, StateManager> objectStates;
protected ConcurrentHashMap<Integer, StateManager> cursorStates;
// ***************************************************************************
// * Constants
......@@ -73,8 +75,9 @@ public class StatefulWidget extends BaseWidget {
public StatefulWidget(BaseStatefulBuilder<?> builder) {
super(builder);
Preconditions.checkState(builder.states != null);
states = builder.states;
Preconditions.checkState(builder.objectStates != null);
objectStates = builder.objectStates;
cursorStates = builder.cursorStates;
}
// ---------------------------------------------------------------------------
......@@ -89,10 +92,10 @@ public class StatefulWidget extends BaseWidget {
public StatefulWidget(StatefulWidget original) {
super(original);
states = new ConcurrentHashMap<>();
for (Entry<Integer, StateManager> entry : original.states.entrySet()) {
objectStates = new ConcurrentHashMap<>();
for (Entry<Integer, StateManager> entry : original.objectStates.entrySet()) {
boolean isPersistent = entry.getValue().isPersistent();
states.putIfAbsent(entry.getKey(), new StateManager(isPersistent));
objectStates.putIfAbsent(entry.getKey(), new StateManager(isPersistent));
}
}
......@@ -118,14 +121,14 @@ public class StatefulWidget extends BaseWidget {
// ---------------------------------------------------------------------------
public boolean isPersistent(Integer objectId) {
StateManager manager = states.get(objectId);
StateManager manager = objectStates.get(objectId);
return manager.isPersistent();
}
// ---------------------------------------------------------------------------
/**
* Method invoked when a handle associated with the widget was moved. The method issues a call
* to the super method before handling state updates.
* Method invoked when a handle associated with the widget was moved. The method issues a call to
* the super method before handling state updates.
*
* @param tangibleObject
* The {@link TangibleObject} that was triggering the move.
......@@ -136,7 +139,7 @@ public class StatefulWidget extends BaseWidget {
@Override
public void actionMove(TangibleObject tangibleObject) {
StateManager manager = states.get(tangibleObject.getObjectId());
StateManager manager = objectStates.get(tangibleObject.getObjectId());
boolean wasStill = manager.isStill();
boolean wasMoving = manager.isMoving() || manager.isRotating();
......@@ -166,8 +169,8 @@ public class StatefulWidget extends BaseWidget {
// ---------------------------------------------------------------------------
/**
* Method invoked when the tangible is detected on the table surface for the first time. The
* method issues a call to the super method before handling state updates.
* Method invoked when the tangible is detected on the table surface for the first time. The method
* issues a call to the super method before handling state updates.
*
* @param tangibleObject
* The {@link TangibleObject} that was triggering the drop action.
......@@ -178,14 +181,14 @@ public class StatefulWidget extends BaseWidget {
@Override
public void actionDrop(TangibleObject tangibleObject) {
super.actionDrop(tangibleObject);
states.get(tangibleObject.getObjectId()).drop(new Point(tangibleObject.getX(), tangibleObject.getY(), tangibleObject.getAngle()));
objectStates.get(tangibleObject.getObjectId()).drop(new Point(tangibleObject.getX(), tangibleObject.getY(), tangibleObject.getAngle()));
TangibleLogger.logWidget(tangibleObject.getObjectId(), getPosition(tangibleObject.getObjectId()), TangibleLogger.DROPPED);
}
// ---------------------------------------------------------------------------
/**
* Method invoked when the cursor is removed from the table surface. The method issues a call to
* the super method before handling state updates.
* Method invoked when the cursor is removed from the table surface. The method issues a call to the
* super method before handling state updates.
*
* @param tangibleObject
* The {@link TangibleObject} that was triggering the drop action.
......@@ -196,7 +199,7 @@ public class StatefulWidget extends BaseWidget {
@Override
public void actionLift(TangibleObject tangibleObject) {
TangibleLogger.logWidget(tangibleObject.getObjectId(), getPosition(tangibleObject.getObjectId()), TangibleLogger.LIFTED);
states.get(tangibleObject.getObjectId()).lift(new Point(tangibleObject.getX(), tangibleObject.getY(), tangibleObject.getAngle()));
objectStates.get(tangibleObject.getObjectId()).lift(new Point(tangibleObject.getX(), tangibleObject.getY(), tangibleObject.getAngle()));
super.actionLift(tangibleObject);
}
......@@ -209,11 +212,57 @@ public class StatefulWidget extends BaseWidget {
// ---------------------------------------------------------------------------
public Collection<StateManager> getManagers() {
Preconditions.checkState(states.values() != null);
return states.values();
Preconditions.checkState(objectStates.values() != null);
return objectStates.values();
}
// ---------------------------------------------------------------------------
/** {@inheritDoc} */
// ---------------------------------------------------------------------------
@Override
public boolean touched(TouchEvent event) {
boolean touched = false;
touched = super.touched(event);
if (touched) {
cursorStates.putIfAbsent(event.getObjectId(), new StateManager(false));
cursorStates.get(event.getObjectId()).drop(event.getPosition());
}
return touched;
}
// ---------------------------------------------------------------------------
/** {@inheritDoc} */
// ---------------------------------------------------------------------------
@Override
public boolean dragged(TouchEvent event) {
boolean dragged = false;
if (cursorStates.containsKey(event.getObjectId())) {
StateManager manager = cursorStates.get(event.getObjectId());
manager.move(event.getPosition());
if (manager.isMoving()) {
dragged = super.dragged(event);
}
}
return dragged;
}
// ---------------------------------------------------------------------------
/** {@inheritDoc} */
// ---------------------------------------------------------------------------
@Override
public boolean released(TouchEvent event) {
boolean released = false;
released = super.released(event);
if (released) {
cursorStates.remove(event.getObjectId());
}
return released;
}
@Override
public StatefulWidget clone() {
......
......@@ -302,7 +302,7 @@ public abstract class TetherableWidget extends PointingWidget implements Tethera
public void actionMove(TangibleObject tangibleObject) {
super.actionMove(tangibleObject);
StateManager manager = states.get(tangibleObject.getObjectId());
StateManager manager = objectStates.get(tangibleObject.getObjectId());
if (manager.isMoving() || manager.isRotating()) {
Point position = getPosition(tangibleObject.getObjectId());
position.toCoordinates(ScreenCoordinates.class);
......
......@@ -47,7 +47,8 @@ import java.util.concurrent.ConcurrentHashMap;
@NonNullByDefault
public abstract class BaseStatefulBuilder<B extends BaseStatefulBuilder<B>> extends BaseBuilder<B> {
/** The coalesced NuiState instances per handle! */
public ConcurrentHashMap<Integer, StateManager> states;
public ConcurrentHashMap<Integer, StateManager> objectStates;
public ConcurrentHashMap<Integer, StateManager> cursorStates;
/** */
public Boolean isPersistent;
......@@ -55,16 +56,17 @@ public abstract class BaseStatefulBuilder<B extends BaseStatefulBuilder<B>> exte
protected BaseStatefulBuilder() {
super();
states = new ConcurrentHashMap<>();
objectStates = new ConcurrentHashMap<>();
cursorStates = new ConcurrentHashMap<>();
}
/**
* Constructor initializing all fields from an {@link Element} containing as child elements all
* the information on fields to initialize.
* Constructor initializing all fields from an {@link Element} containing as child elements all the
* information on fields to initialize.
*
* @param rootElement
* The element harbouring, on child nodes, the necessary information to initialize all
* fields of the builder.
* The element harbouring, on child nodes, the necessary information to initialize all fields
* of the builder.
* @throws BuildException
* Thrown when any of the fields fail to populate due to an error in reading information
* from the XML file.
......@@ -94,9 +96,10 @@ public abstract class BaseStatefulBuilder<B extends BaseStatefulBuilder<B>> exte
* @throws BuildException
*/
private void buildFromBootstrap(@Nullable Element rootElement, BootstrapContext context, BootstrapCallback callback) throws BuildException {
states = new ConcurrentHashMap<>();
objectStates = new ConcurrentHashMap<>();
cursorStates = new ConcurrentHashMap<>();
isPersistent = BootstrappingUtils.getContentAsBoolean(rootElement, Externalization.PERSISTENT_NODE, BootstrappingUtils.OPTIONAL, false, context);
positions.keySet().forEach(handleId -> states.putIfAbsent(handleId, new StateManager(isPersistent)));
positions.keySet().forEach(handleId -> objectStates.putIfAbsent(handleId, new StateManager(isPersistent)));
}
/**
......@@ -110,14 +113,13 @@ public abstract class BaseStatefulBuilder<B extends BaseStatefulBuilder<B>> exte
}
/**
* {@inheritDoc} The method will initialise the state map with a new {@link StateManager}
* instance.
* {@inheritDoc} The method will initialise the state map with a new {@link StateManager} instance.
*/
@SuppressWarnings("unchecked")
@Override
public B withHandle(int handleID, Point position) {
super.withHandle(handleID, position);
states.put(handleID, new StateManager(isPersistent));
objectStates.put(handleID, new StateManager(isPersistent));
return (B) this;
}
......
......@@ -51,7 +51,7 @@ public class StatefulWidgetTest {
assertTrue(widget.coronas.containsKey(1));
assertFalse(widget.coronas.containsKey(2));
assertTrue(widget.getWidgetIds().contains(1));
assertTrue(widget.states.containsKey(1));
assertNotNull(widget.states);
assertTrue(widget.objectStates.containsKey(1));
assertNotNull(widget.objectStates);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment