Commit 5a0a99db authored by Nico Mack's avatar Nico Mack

Fixed a bug in Point.magnitude method

Added possibility to disable tethering
Added Force reactive and generator interface and manager
parent 27e69f9f
......@@ -560,7 +560,7 @@ public class Point extends Float implements Comparable<Point>, KdComparator<Poin
* @return
*/
public double magnitude() {
return (Math.sqrt(this.x * this.x) + (this.y * this.y));
return Math.sqrt((this.x * this.x) + (this.y * this.y));
}
/** {@inheritDoc} */
......
......@@ -71,7 +71,7 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
private static final double UPPER_BOUND = +2;
private static final double MIN_DISTANCE = 0.1;
private static final double COALESCE_RATIO = 1d / 50;
private static final double COALESCE_RATIO = 1d / 100;
private static final long TIMESTEP = 40; // 40ms => 25 fps
private static final Logger LOGGER = LoggerFactory.getLogger(ForceDirectedGraph.class.getSimpleName());
......@@ -181,14 +181,15 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
if (!outer.equals(inner)) {
Vector2D offset = outer.getPosition().subtract(inner.getPosition());
double distance = offset.magnitude() + MIN_DISTANCE;
distance = distance * distance * 0.5;
double charge = outer.getMass() * inner.getMass() * this.repulsion;
// double charge = this.repulsion;
Vector2D direction = offset.normalize();
Vector2D directedRepulsion = direction.multiplyBy(charge);
if (!outer.isImmobile()) {
outer.applyForce(directedRepulsion.divideBy(distance * distance * 0.5));
outer.applyForce(directedRepulsion.divideBy(distance));
if (!inner.isImmobile()) {
inner.applyForce(directedRepulsion.divideBy(distance * distance * -0.5));
inner.applyForce(directedRepulsion.divideBy(-distance));
}
}
}
......@@ -203,13 +204,13 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
for (Spring spring : graphSprings) {
Vector2D offset = spring.getSecondAttachment().getPosition().subtract(spring.getFirstAttachment().getPosition());
double displacement = Math.log(spring.getLength() / offset.magnitude());
double displacement = spring.getLength() - offset.magnitude();
Vector2D direction = offset.normalize();
double exertedForce = spring.getSpringConstant() * displacement;
double exertedForce = spring.getSpringConstant() * displacement * 0.5;
if (!spring.getFirstAttachment().isImmobile()) {
spring.getFirstAttachment().applyForce(direction.multiplyBy(exertedForce * -0.5));
spring.getFirstAttachment().applyForce(direction.multiplyBy(-exertedForce));
if (!spring.getSecondAttachment().isImmobile()) {
spring.getSecondAttachment().applyForce(direction.multiplyBy(exertedForce * 0.5));
spring.getSecondAttachment().applyForce(direction.multiplyBy(exertedForce));
}
}
}
......@@ -318,6 +319,18 @@ public class ForceDirectedGraph implements GraphListener, Runnable {
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @return the underlying graph holding the nodes and edges represented by this force directed
* graphs bodies and springs.
*
*/
// ---------------------------------------------------------------------------
public Graph getGraph() {
return this.graph;
}
// ---------------------------------------------------------------------------
/**
* @return
......
......@@ -115,6 +115,20 @@ public abstract class TetherableWidget extends PointingWidget implements Tethera
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Enables or disables tethering.
*
* @param enableIt
* determines whether to turn on or off tethering. Specify <code>true</code> to enable
* tethering, <code>false</code> to disable it.
*/
// ---------------------------------------------------------------------------
public void enableTethering(boolean enableIt) {
this.tetherManager.enableTethering(enableIt);
}
// ---------------------------------------------------------------------------
/**
* {@inheritDoc}
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP 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, version 3 of the
* License.
*
* TULIP 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 TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.widget.force;
import lu.list.itis.dkd.tui.utility.Point;
/**
* @author nico.mack@list.lu
* @since 2.5
* @version 1.0.0
*/
public interface ForceGenerator {
public void addPotentialSubject(ForceReactive potential);
public void removePotentialSubject(ForceReactive potential);
public boolean isRepelling(ForceReactive potential);
public double getRange();
public double getMagnitude();
public Point getOrigin();
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP 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, version 3 of the
* License.
*
* TULIP 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 TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.widget.force;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
import lu.list.itis.dkd.tui.utility.Vector2D;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Class managing imparting of force on force reactive objects. The manager maintains a list of
* potential force reactive candidates and provides functionality to determine from object positions
* when and how much force is to be applied.
*
* @author Nico Mack [nico.mack@list.lu]
* @since 2.5
* @version 2.5.0
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class ForceManager {
private ForceGenerator generator;
private List<ForceReactive> potentialSubjects;
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final Logger logger = LoggerFactory.getLogger(ForceManager.class.getSimpleName());
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param generator
*/
// ---------------------------------------------------------------------------
public ForceManager(ForceGenerator generator) {
this.generator = generator;
this.potentialSubjects = new ArrayList<>();
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
private Vector2D impart(Point distance, double magnitude, double range, boolean isRepelling) {
Vector2D force;
double effective = (isRepelling) ? -magnitude : magnitude;
double radius = distance.magnitude();
radius = (range - radius) / range;
Vector2D direction = new Vector2D(distance.x, distance.y).normalize();
double exertedForce = effective * (radius * radius);
force = direction.multiplyBy(exertedForce);
return force;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Adds the specified force reactive object as a potential subject to the force exerted by the the
* generator managed by this instance.
*
* @param potential
* specifies the potential force reactive object to be added.
*/
// ---------------------------------------------------------------------------
public void addPotentialSubject(ForceReactive potential) {
Preconditions.checkNotNull(potential, "Potential ForceReactive Subject MUST not be null!"); //$NON-NLS-1$
if (!this.potentialSubjects.contains(potential)) {
this.potentialSubjects.add(potential);
} else {
logger.warn("Attempt to add an already known ForceReactive object as a potential subject!"); //$NON-NLS-1$
}
}
// ---------------------------------------------------------------------------
/**
* Removes the specified force reactive object from the list of potential subjects.
*
* @param potential
* specifies the potential force reactive object to be removed.
*/
// ---------------------------------------------------------------------------
public void removePotentialSubject(ForceReactive potential) {
Preconditions.checkNotNull(potential, "Potential ForceReactive Subject MUST not be null!"); //$NON-NLS-1$
if (this.potentialSubjects.contains(potential)) {
this.potentialSubjects.remove(potential);
} else {
logger.warn("Attempt to remove an unknown ForceReactive object from list of potential subject!"); //$NON-NLS-1$
}
}
// ---------------------------------------------------------------------------
/**
*
* @param newPosition
* specifies the origin of this object.
* @return
*/
// ---------------------------------------------------------------------------
public void move(Point newPosition) {
double range = this.generator.getRange();
double magnitude = this.generator.getMagnitude();
Point origin = newPosition.toCoordinates(ScreenCoordinates.class);
for (ForceReactive subject : this.potentialSubjects) {
if (subject.getPosition() == null)
continue;
Point target = subject.getPosition().clone().toCoordinates(ScreenCoordinates.class);
Point distance = origin.clone().subtract(target, false);
if (distance.magnitude() < range) {
subject.applyForce(this.impart(distance, magnitude, range, this.generator.isRepelling(subject)));
}
}
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of class *
// ***************************************************************************
// ---------------------------------------------------------------------------
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP 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, version 3 of the
* License.
*
* TULIP 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 TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.widget.force;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.Vector2D;
/**
* @author nico.mack@list.lu
* @since 2.5
* @version 1.0.0
*/
public interface ForceReactive {
public void applyForce(Vector2D force);
public Point getPosition();
}
......@@ -62,6 +62,8 @@ public class TetherManager {
private List<String> providers;
private List<String> receivers;
private boolean tetheringEnabled;
// ***************************************************************************
// * Constants *
// ***************************************************************************
......@@ -85,6 +87,7 @@ public class TetherManager {
this.potentialTethers = new HashMap<>();
this.tetherable = tetherable;
this.exclusive = exclusive;
this.tetheringEnabled = true;
this.tetherListeners = new TetherEventSource();
}
......@@ -102,8 +105,8 @@ public class TetherManager {
// ---------------------------------------------------------------------------
/**
* Calculates the intersection of a line given by points a and b with a circle at position
* origin with a given radius.
* Calculates the intersection of a line given by points a and b with a circle at position origin
* with a given radius.
*
* @param a
* First point of intersecting line
......@@ -161,8 +164,8 @@ public class TetherManager {
// ---------------------------------------------------------------------------
/**
* Iterates over all currently active tethers and determines the one which is closest in terms
* of location and returns the distance expressed in {@link ScreenCoordinates}.
* Iterates over all currently active tethers and determines the one which is closest in terms of
* location and returns the distance expressed in {@link ScreenCoordinates}.
*
* @return the shortest distance, or Double.MAX_VALUE if no tether is currently setup.
*/
......@@ -248,6 +251,32 @@ public class TetherManager {
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* Allows checking whether tethering is currently enabled or not.
*
* @return <code>true</code> if tethering is enabled, <code>false</code> otherwise
*/
// ---------------------------------------------------------------------------
public boolean isTetheringEnabled() {
return tetheringEnabled;
}
// ---------------------------------------------------------------------------
/**
* Enables or disables tethering.
*
* @param enableIt
* determines whether to turn on or off tethering. Specify <code>true</code> to enable
* tethering, <code>false</code> to disable it.
*/
// ---------------------------------------------------------------------------
public void enableTethering(boolean enableIt) {
this.tetheringEnabled = enableIt;
}
// ---------------------------------------------------------------------------
/**
* Allows checking whether this tether manager operates in exclusive mode, i.e. allows only one
......@@ -329,9 +358,10 @@ public class TetherManager {
public boolean isPotentialTether() {
boolean potentialTether = false;
potentialTether |= ((this.providers != null) && (!this.providers.isEmpty()));
potentialTether |= ((this.receivers != null) && (!this.receivers.isEmpty()));
if (this.tetheringEnabled) {
potentialTether |= ((this.providers != null) && (!this.providers.isEmpty()));
potentialTether |= ((this.receivers != null) && (!this.receivers.isEmpty()));
}
return potentialTether;
}
......@@ -432,15 +462,15 @@ public class TetherManager {
* Adds the specified tetherable object as a potential tether.
*
* @param potential
* specifies the potential tetherable object to be added as a potential tether,
* independently whether its a provider or a receiver.
* specifies the potential tetherable object to be added as a potential tether, independently
* whether its a provider or a receiver.
* @param tether
* specifies the tether object to be used in case tethering occurs.
* @param propagate
* specifies whether or not a call to addPotentialTether should be performed on the
* potential tether so that both objects are mutually aware of each other. The caller in
* general specifies <code>true</code>, however, the manager will always specify
* <code>false</code> when propagating in order to avoid a potential call loop.
* specifies whether or not a call to addPotentialTether should be performed on the potential
* tether so that both objects are mutually aware of each other. The caller in general
* specifies <code>true</code>, however, the manager will always specify <code>false</code>
* when propagating in order to avoid a potential call loop.
*/
// ---------------------------------------------------------------------------
......@@ -460,8 +490,8 @@ public class TetherManager {
// ---------------------------------------------------------------------------
/**
* Setups tethering between this object and the specified candidate. It is important that the
* specified candidate is known as a potential tether ( see #addPotentialTether) before calling
* this method.
* specified candidate is known as a potential tether ( see #addPotentialTether) before calling this
* method.
*
* @param candidate
* specifies the already known potential tether to tether with.
......@@ -471,6 +501,10 @@ public class TetherManager {
public void tetherWith(Tetherable candidate) {
Preconditions.checkNotNull(candidate, "Other Tetherable MUST not be null!"); //$NON-NLS-1$
if (!this.tetheringEnabled) {
return;
}
if (this.potentialTethers.containsKey(candidate)) {
if (!this.currentlyTethered.contains(candidate)) {
if (this.exclusive) {
......@@ -590,9 +624,9 @@ public class TetherManager {
// ---------------------------------------------------------------------------
/**
* Moves the origin of this tetherable object and establishes new tethers if tethering
* conditions are met, or tears down already established tethers if conditions no longer are
* met. Please note that draggable property is not yet honored.
* Moves the origin of this tetherable object and establishes new tethers if tethering conditions
* are met, or tears down already established tethers if conditions no longer are met. Please note
* that draggable property is not yet honored.
*
* @param newPosition
* specifies the origin of this object.
......
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