package org.codecranachan.asteroidpush.base.workshop.actor;
import java.util.HashSet;
import java.util.Set;
import org.codecranachan.asteroidpush.base.simulation.RigidBody;
import org.codecranachan.asteroidpush.base.simulation.RigidBodyFactory;
import org.codecranachan.asteroidpush.utils.NewtonianState;
import org.jgrapht.alg.ConnectivityInspector;
import org.jgrapht.event.GraphEdgeChangeEvent;
import org.jgrapht.event.GraphListener;
import org.jgrapht.event.GraphVertexChangeEvent;
public class BodyAssociationManager implements
GraphListener<BodyVertex, BodyEdge> {
private BodyGraph graph;
public BodyAssociationManager(BodyGraph graph) {
this.graph = graph;
}
/**
* This will create bodies for connected sets of BodyVertices which do not
* have any Bodies attached.
*
* @param initialState
* The world coordinates of where to spawn the bodies.
* @param factory
* The RigidBodyFactory to use for spawning new bodies.
*/
public void spawnMissingBodies(NewtonianState initialState,
RigidBodyFactory factory) {
ConnectivityInspector<BodyVertex, BodyEdge> inspector = new ConnectivityInspector<BodyVertex, BodyEdge>(
graph);
for (Set<BodyVertex> set : inspector.connectedSets()) {
RigidBody assignedBody = findAssignedBody(set);
if (assignedBody == null) {
RigidBody newBody = factory.createDynamicBody(initialState);
Set<RigidBody> replacedBodies = replaceBodiesInSet(newBody, set);
assert replacedBodies.size() == 0;
}
}
}
private RigidBody findAssignedBody(Set<BodyVertex> set) {
boolean bodyLocked = false;
RigidBody foundBody = null;
for (BodyVertex vertex : set) {
if (bodyLocked) {
assert vertex.getBody() == foundBody;
} else {
bodyLocked = true;
foundBody = vertex.getBody();
}
}
return foundBody;
}
public void vertexAdded(GraphVertexChangeEvent<BodyVertex> event) {
}
public void vertexRemoved(GraphVertexChangeEvent<BodyVertex> event) {
}
public void edgeAdded(GraphEdgeChangeEvent<BodyVertex, BodyEdge> event) {
if (event.getType() != GraphEdgeChangeEvent.BEFORE_EDGE_ADDED) {
return;
}
BodyVertex first = event.getEdgeSource();
BodyVertex second = event.getEdgeSource();
// Possible cases:
// - Neither has a body attached
// - Both have the same body attached
// - Only one has a body attached
// - Both have a different body attached
if (first.getBody() == second.getBody()) {
// Both vertices are attached to the same body (or both are null),
// nothing needs to be done
return;
} else {
// Vertices are attached to different bodies or one of both is null,
// merge bodies and attach all nodes to new body.
RigidBody merged = mergeBodies(first.getBody(), second.getBody());
assert merged != null;
Set<RigidBody> replacedBodies = new HashSet<RigidBody>();
if (first.getBody() != merged) {
replacedBodies.addAll(replaceBodiesOnConnected(first, merged));
}
if (second.getBody() != merged) {
replacedBodies.addAll(replaceBodiesOnConnected(second, merged));
}
// If everything works as intended, there should only ever be a single
// body that is being replaced.
assert replacedBodies.size() <= 1;
for (RigidBody deletee : replacedBodies) {
deletee.destroy();
}
}
}
private RigidBody mergeBodies(RigidBody first, RigidBody second) {
if (first == null) {
return second;
} else if (second == null) {
return first;
} else {
return first;
}
}
private Set<RigidBody> replaceBodiesOnConnected(BodyVertex vertex,
RigidBody newBody) {
ConnectivityInspector<BodyVertex, BodyEdge> inspector = new ConnectivityInspector<BodyVertex, BodyEdge>(
graph);
Set<RigidBody> replacedBodies = replaceBodiesInSet(newBody,
inspector
.connectedSetOf(vertex));
return replacedBodies;
}
private Set<RigidBody> replaceBodiesInSet(RigidBody newBody,
Set<BodyVertex> connectedSet) {
Set<RigidBody> replacedBodies = new HashSet<RigidBody>();
for (BodyVertex connected : connectedSet) {
RigidBody oldBody = connected.getBody();
// Track replaced bodies
if (oldBody != null) {
assert connected.getBody() != newBody;
replacedBodies.add(oldBody);
}
// Detach behaviors from old body and attach them to the new body
for (Plug changedPlug : connected.getPlugs()) {
if (oldBody != null) {
changedPlug.notifyDetach(oldBody);
}
changedPlug.notifyAttach(newBody);
}
connected.setBody(newBody);
}
return replacedBodies;
}
public void edgeRemoved(GraphEdgeChangeEvent<BodyVertex, BodyEdge> event) {
if (event.getType() != GraphEdgeChangeEvent.EDGE_REMOVED) {
return;
}
BodyVertex first = event.getEdgeSource();
BodyVertex second = event.getEdgeTarget();
// Possible cases:
// - both vertices are still in the same connecting set
// - they are not connected any more...
// - and the separating vertices have a body attached
// - and the separating vertices do not have a body attached
ConnectivityInspector<BodyVertex, BodyEdge> inspector = new ConnectivityInspector<BodyVertex, BodyEdge>(
graph);
if (inspector.connectedSetOf(first).contains(second)) {
// both vertices are still in the same connected set,
// nothing needs to be done
return;
} else {
// the vertices have been disconnected, shallow clone the attached body
// and replace all body references in the connected set of one of the
// vertices.
assert first.getBody() == second.getBody();
if (first.getBody() != null) {
RigidBody clone = first.getBody().shallowClone();
replaceBodiesOnConnected(second, clone);
}
}
}
}