Wege System erweitert
This commit is contained in:
@@ -1048,6 +1048,16 @@ public class SharedInput {
|
||||
public final ConcurrentLinkedQueue<PathNetworkClick> pathNetworkClickQueue =
|
||||
new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* JFX → JME: Drag-Ereignis im Wegnetz-Modus (Knoten verschieben).
|
||||
* phase: START=0, MOVE=1, END=2.
|
||||
*/
|
||||
public record PathNetworkDrag(float screenX, float screenY, int phase) {
|
||||
public static final int START = 0, MOVE = 1, END = 2;
|
||||
}
|
||||
public final ConcurrentLinkedQueue<PathNetworkDrag> pathNetworkDragQueue =
|
||||
new ConcurrentLinkedQueue<>();
|
||||
|
||||
/** JME → JFX: Wegnetz wurde verändert, UI-Refresh nötig. */
|
||||
public volatile boolean pathNetworkChanged = false;
|
||||
|
||||
|
||||
@@ -34,25 +34,22 @@ import java.util.List;
|
||||
*
|
||||
* <p>Interaktionsmodell:
|
||||
* <ul>
|
||||
* <li>Linksklick auf leeres Terrain → neuen Knoten platzieren</li>
|
||||
* <li>Linksklick auf Knoten → Knoten auswählen; ist bereits einer gewählt,
|
||||
* wird eine Kante zwischen beiden erzeugt</li>
|
||||
* <li>Linksklick auf Terrain mit gewähltem Knoten → neuen Knoten platzieren
|
||||
* UND mit dem gewählten Knoten verbinden</li>
|
||||
* <li>Rechtsklick auf Knoten → Knoten und alle seine Kanten löschen</li>
|
||||
* <li>Rechtsklick auf Terrain → Auswahl aufheben</li>
|
||||
* <li>LMK auf leeres Terrain → neuen Knoten platzieren</li>
|
||||
* <li>LMK auf Kante → Kante splitten, neuen Knoten einfügen</li>
|
||||
* <li>LMK auf Knoten → auswählen; ist bereits einer gewählt, Kante erzeugen</li>
|
||||
* <li>LMK Drag auf Knoten → Knoten auf neuer Terrain-Position ablegen</li>
|
||||
* <li>RMK auf Knoten → Knoten und seine Kanten löschen</li>
|
||||
* <li>RMK auf Terrain → Auswahl aufheben</li>
|
||||
* </ul>
|
||||
*
|
||||
* Änderungen werden sofort in die Datei geschrieben.
|
||||
*/
|
||||
public class PathNetworkEditorState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PathNetworkEditorState.class);
|
||||
|
||||
/** Radius in Welt-Einheiten, innerhalb dessen ein Klick einen Knoten trifft. */
|
||||
private static final float NODE_HIT_RADIUS = 3f;
|
||||
private static final float NODE_SPHERE_RADIUS = 0.6f;
|
||||
private static final float EDGE_RADIUS = 0.15f;
|
||||
private static final float EDGE_HIT_THRESHOLD = 3f;
|
||||
|
||||
private final SharedInput input;
|
||||
private Camera cam;
|
||||
@@ -62,8 +59,8 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
|
||||
private final PathNetwork network = new PathNetwork();
|
||||
private PathNode selectedNode = null;
|
||||
private PathNode draggedNode = null;
|
||||
|
||||
// Szene-Graph-Knoten für Visualisierung
|
||||
private final Node netRoot = new Node("pathnet_root");
|
||||
private final Node nodeRoot = new Node("pathnet_nodes");
|
||||
private final Node edgeRoot = new Node("pathnet_edges");
|
||||
@@ -83,9 +80,9 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
assets = app.getAssetManager();
|
||||
rootNode = app.getRootNode();
|
||||
|
||||
matDefault = mat(new ColorRGBA(1f, 0.85f, 0f, 1f)); // gelb
|
||||
matSelected = mat(new ColorRGBA(1f, 0.4f, 0f, 1f)); // orange
|
||||
matEdge = mat(new ColorRGBA(0.3f, 0.9f, 0.3f, 1f)); // grün
|
||||
matDefault = mat(new ColorRGBA(1f, 0.85f, 0f, 1f));
|
||||
matSelected = mat(new ColorRGBA(1f, 0.4f, 0f, 1f));
|
||||
matEdge = mat(new ColorRGBA(0.3f, 0.9f, 0.3f, 1f));
|
||||
|
||||
netRoot.attachChild(nodeRoot);
|
||||
netRoot.attachChild(edgeRoot);
|
||||
@@ -111,6 +108,7 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
protected void onDisable() {
|
||||
netRoot.removeFromParent();
|
||||
selectedNode = null;
|
||||
draggedNode = null;
|
||||
}
|
||||
|
||||
public void setTerrain(TerrainQuad t) { this.terrain = t; }
|
||||
@@ -123,21 +121,31 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
while ((click = input.pathNetworkClickQueue.poll()) != null) {
|
||||
handleClick(click);
|
||||
}
|
||||
|
||||
// MOVE-Events kollabieren: nur den aktuellsten pro Frame verarbeiten,
|
||||
// START/END sofort durchlassen.
|
||||
SharedInput.PathNetworkDrag pendingMove = null;
|
||||
SharedInput.PathNetworkDrag drag;
|
||||
while ((drag = input.pathNetworkDragQueue.poll()) != null) {
|
||||
if (drag.phase() == SharedInput.PathNetworkDrag.MOVE) {
|
||||
pendingMove = drag;
|
||||
} else {
|
||||
if (pendingMove != null) { handleDrag(pendingMove); pendingMove = null; }
|
||||
handleDrag(drag);
|
||||
}
|
||||
}
|
||||
if (pendingMove != null) { handleDrag(pendingMove); }
|
||||
|
||||
if (input.deletePathNodeRequested) {
|
||||
input.deletePathNodeRequested = false;
|
||||
deleteSelected();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Klick ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private void handleClick(SharedInput.PathNetworkClick click) {
|
||||
float jmeX = click.screenX() * (float) input.viewportScaleX;
|
||||
float jmeY = cam.getHeight() - click.screenY() * (float) input.viewportScaleY;
|
||||
|
||||
Vector3f near = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 0f);
|
||||
Vector3f far = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 1f);
|
||||
Ray ray = new Ray(near, far.subtract(near).normalizeLocal());
|
||||
|
||||
// Knoten in der Nähe des Strahls suchen
|
||||
Ray ray = screenToRay(click.screenX(), click.screenY());
|
||||
PathNode hitNode = findNodeNearRay(ray);
|
||||
|
||||
if (click.rightClick()) {
|
||||
@@ -149,23 +157,26 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
|
||||
private void handleLeftClick(Ray ray, PathNode hitNode) {
|
||||
if (hitNode != null) {
|
||||
// Klick auf Knoten
|
||||
if (selectedNode == null) {
|
||||
select(hitNode);
|
||||
} else if (selectedNode == hitNode) {
|
||||
select(null); // Abwählen
|
||||
select(null);
|
||||
} else {
|
||||
network.addEdge(selectedNode.getUuid(), hitNode.getUuid());
|
||||
select(hitNode);
|
||||
saveAndNotify();
|
||||
rebuildVisuals();
|
||||
}
|
||||
} else {
|
||||
// Klick auf Terrain → neuer Knoten
|
||||
return;
|
||||
}
|
||||
|
||||
WorldPoint pt = terrainHit(ray);
|
||||
if (pt == null) return;
|
||||
PathNode newNode = new PathNode(pt);
|
||||
network.addNode(newNode);
|
||||
|
||||
// Kante in der Nähe? → splitten und neuen Knoten einfügen
|
||||
PathEdge edgeHit = findEdgeNearPoint(pt);
|
||||
PathNode newNode = (edgeHit != null) ? insertNodeOnEdge(edgeHit, pt) : newNodeAt(pt);
|
||||
|
||||
if (selectedNode != null) {
|
||||
network.addEdge(selectedNode.getUuid(), newNode.getUuid());
|
||||
}
|
||||
@@ -173,11 +184,10 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
saveAndNotify();
|
||||
rebuildVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRightClick(PathNode hitNode) {
|
||||
if (hitNode != null) {
|
||||
if (selectedNode == hitNode) selectedNode = null;
|
||||
if (selectedNode == hitNode) { selectedNode = null; }
|
||||
network.removeNode(hitNode.getUuid());
|
||||
saveAndNotify();
|
||||
rebuildVisuals();
|
||||
@@ -186,6 +196,83 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Drag (Knoten verschieben) ──────────────────────────────────────────────
|
||||
|
||||
private void handleDrag(SharedInput.PathNetworkDrag drag) {
|
||||
Ray ray = screenToRay(drag.screenX(), drag.screenY());
|
||||
|
||||
switch (drag.phase()) {
|
||||
case SharedInput.PathNetworkDrag.START -> {
|
||||
PathNode node = findNodeNearRay(ray);
|
||||
if (node != null) {
|
||||
draggedNode = node;
|
||||
select(node);
|
||||
}
|
||||
}
|
||||
case SharedInput.PathNetworkDrag.MOVE -> {
|
||||
if (draggedNode != null) {
|
||||
WorldPoint pt = terrainHit(ray);
|
||||
if (pt != null) {
|
||||
draggedNode.setPosition(pt);
|
||||
moveNodeVisual(draggedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
case SharedInput.PathNetworkDrag.END -> {
|
||||
if (draggedNode != null) {
|
||||
WorldPoint pt = terrainHit(ray);
|
||||
if (pt != null) { draggedNode.setPosition(pt); }
|
||||
saveAndNotify();
|
||||
rebuildVisuals();
|
||||
draggedNode = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Kanten-Insertion ───────────────────────────────────────────────────────
|
||||
|
||||
private PathEdge findEdgeNearPoint(WorldPoint pt) {
|
||||
PathEdge best = null;
|
||||
float bestDist = EDGE_HIT_THRESHOLD;
|
||||
for (PathEdge e : network.getEdges()) {
|
||||
PathNode a = network.nodeById(e.getNodeUuidA());
|
||||
PathNode b = network.nodeById(e.getNodeUuidB());
|
||||
if (a == null || b == null) continue;
|
||||
float d = distPointToSegmentXZ(pt, a.getPosition(), b.getPosition());
|
||||
if (d < bestDist) { bestDist = d; best = e; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private float distPointToSegmentXZ(WorldPoint p, WorldPoint a, WorldPoint b) {
|
||||
float dx = b.x - a.x, dz = b.z - a.z;
|
||||
float len2 = dx * dx + dz * dz;
|
||||
if (len2 < 0.001f) {
|
||||
float ex = p.x - a.x, ez = p.z - a.z;
|
||||
return (float) Math.sqrt(ex * ex + ez * ez);
|
||||
}
|
||||
float t = Math.max(0f, Math.min(1f, ((p.x - a.x) * dx + (p.z - a.z) * dz) / len2));
|
||||
float cx = a.x + t * dx, cz = a.z + t * dz;
|
||||
float ex = p.x - cx, ez = p.z - cz;
|
||||
return (float) Math.sqrt(ex * ex + ez * ez);
|
||||
}
|
||||
|
||||
private PathNode insertNodeOnEdge(PathEdge edge, WorldPoint pos) {
|
||||
PathNode newNode = new PathNode(pos);
|
||||
network.addNode(newNode);
|
||||
network.removeEdge(edge.getNodeUuidA(), edge.getNodeUuidB());
|
||||
network.addEdge(edge.getNodeUuidA(), newNode.getUuid());
|
||||
network.addEdge(newNode.getUuid(), edge.getNodeUuidB());
|
||||
return newNode;
|
||||
}
|
||||
|
||||
private PathNode newNodeAt(WorldPoint pos) {
|
||||
PathNode n = new PathNode(pos);
|
||||
network.addNode(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
// ── Visualisierung ─────────────────────────────────────────────────────────
|
||||
|
||||
private void rebuildVisuals() {
|
||||
@@ -193,8 +280,7 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
edgeRoot.detachAllChildren();
|
||||
|
||||
for (PathNode n : network.getNodes()) {
|
||||
Geometry g = new Geometry("node_" + n.getUuid(),
|
||||
new Sphere(8, 8, NODE_SPHERE_RADIUS));
|
||||
Geometry g = new Geometry("node_" + n.getUuid(), new Sphere(8, 8, NODE_SPHERE_RADIUS));
|
||||
g.setMaterial(n == selectedNode ? matSelected : matDefault);
|
||||
g.setLocalTranslation(n.getPosition().x, n.getPosition().y + NODE_SPHERE_RADIUS, n.getPosition().z);
|
||||
g.setUserData("nodeUuid", n.getUuid());
|
||||
@@ -210,6 +296,27 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
|
||||
/** Effizienter Update: nur Sphere-Position + betroffene Kanten neu bauen. */
|
||||
private void moveNodeVisual(PathNode node) {
|
||||
Geometry sphere = (Geometry) nodeRoot.getChild("node_" + node.getUuid());
|
||||
if (sphere != null) {
|
||||
sphere.setLocalTranslation(
|
||||
node.getPosition().x, node.getPosition().y + NODE_SPHERE_RADIUS, node.getPosition().z);
|
||||
}
|
||||
String uuid = node.getUuid();
|
||||
for (PathEdge e : network.getEdges()) {
|
||||
if (!e.involves(uuid)) continue;
|
||||
PathNode a = network.nodeById(e.getNodeUuidA());
|
||||
PathNode b = network.nodeById(e.getNodeUuidB());
|
||||
if (a == null || b == null) continue;
|
||||
String edgeName = "edge_" + a.getUuid() + "_" + b.getUuid();
|
||||
Spatial old = edgeRoot.getChild(edgeName);
|
||||
if (old != null) { old.removeFromParent(); }
|
||||
Geometry g = buildEdgeGeometry(a, b);
|
||||
if (g != null) { edgeRoot.attachChild(g); }
|
||||
}
|
||||
}
|
||||
|
||||
private Geometry buildEdgeGeometry(PathNode a, PathNode b) {
|
||||
float ax = a.getPosition().x, ay = a.getPosition().y, az = a.getPosition().z;
|
||||
float bx = b.getPosition().x, by = b.getPosition().y, bz = b.getPosition().z;
|
||||
@@ -225,26 +332,30 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
g.setMaterial(matEdge);
|
||||
g.setLocalTranslation(mid);
|
||||
|
||||
// Zylinder liegt entlang lokaler Y-Achse.
|
||||
// Rotationsmatrix: Spalten = wohin lokale Achsen X, Y, Z in Weltkoordinaten zeigen.
|
||||
// Lokale Y → Richtung des Weges (nx, 0, nz) im XZ-Boden.
|
||||
// Lokale Z → Welt-Y (oben), damit Zylinder nicht rollt.
|
||||
// Lokale X = lokale Y × lokale Z = (nz, 0, -nx).
|
||||
Matrix3f rot = new Matrix3f();
|
||||
rot.setColumn(0, new Vector3f( nz, 0f, -nx)); // lokales X
|
||||
rot.setColumn(1, new Vector3f( nx, 0f, nz)); // lokales Y → dir
|
||||
rot.setColumn(2, new Vector3f( 0f, 1f, 0f)); // lokales Z → Welt-oben
|
||||
rot.setColumn(0, new Vector3f( nz, 0f, -nx));
|
||||
rot.setColumn(1, new Vector3f( nx, 0f, nz));
|
||||
rot.setColumn(2, new Vector3f( 0f, 1f, 0f));
|
||||
g.setLocalRotation(new Quaternion().fromRotationMatrix(rot));
|
||||
return g;
|
||||
}
|
||||
|
||||
private void updateNodeMaterial(PathNode node) {
|
||||
Geometry g = (Geometry) nodeRoot.getChild("node_" + node.getUuid());
|
||||
if (g != null) g.setMaterial(node == selectedNode ? matSelected : matDefault);
|
||||
if (g != null) { g.setMaterial(node == selectedNode ? matSelected : matDefault); }
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ──────────────────────────────────────────────────────────
|
||||
|
||||
private Ray screenToRay(float screenX, float screenY) {
|
||||
float jmeX = screenX * (float) input.viewportScaleX;
|
||||
float jmeY = cam.getHeight() - screenY * (float) input.viewportScaleY;
|
||||
Vector2f sc = new Vector2f(jmeX, jmeY);
|
||||
Vector3f near = cam.getWorldCoordinates(sc, 0f);
|
||||
Vector3f far = cam.getWorldCoordinates(sc, 1f);
|
||||
return new Ray(near, far.subtract(near).normalizeLocal());
|
||||
}
|
||||
|
||||
private PathNode findNodeNearRay(Ray ray) {
|
||||
PathNode best = null;
|
||||
float bestDist = NODE_HIT_RADIUS;
|
||||
@@ -273,8 +384,8 @@ public class PathNetworkEditorState extends BaseAppState {
|
||||
private void select(PathNode node) {
|
||||
PathNode prev = selectedNode;
|
||||
selectedNode = node;
|
||||
if (prev != null) updateNodeMaterial(prev);
|
||||
if (node != null) updateNodeMaterial(node);
|
||||
if (prev != null) { updateNodeMaterial(prev); }
|
||||
if (node != null) { updateNodeMaterial(node); }
|
||||
}
|
||||
|
||||
private void saveAndNotify() {
|
||||
|
||||
@@ -24,6 +24,12 @@ public class ViewportController {
|
||||
private int editPressAction;
|
||||
private javafx.animation.Timeline editTimer;
|
||||
|
||||
// Wegnetz-Drag-Tracking
|
||||
private float pathNetPressX, pathNetPressY;
|
||||
private boolean pathNetPressPending;
|
||||
private boolean pathNetDragging;
|
||||
private boolean pathNetRightPress;
|
||||
|
||||
public ViewportController(SharedInput input, WritableImage jfxImage, Runnable scheduleAutoSave) {
|
||||
this.input = input;
|
||||
this.initialImage = jfxImage;
|
||||
@@ -140,12 +146,12 @@ public class ViewportController {
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_PATH_NETWORK) {
|
||||
if (e.getButton() == MouseButton.PRIMARY && !bothDown) {
|
||||
input.pathNetworkClickQueue.offer(
|
||||
new SharedInput.PathNetworkClick((float)e.getX(), (float)e.getY(), false));
|
||||
} else if (e.getButton() == MouseButton.SECONDARY && !bothDown) {
|
||||
input.pathNetworkClickQueue.offer(
|
||||
new SharedInput.PathNetworkClick((float)e.getX(), (float)e.getY(), true));
|
||||
if (!bothDown && (e.getButton() == MouseButton.PRIMARY || e.getButton() == MouseButton.SECONDARY)) {
|
||||
pathNetPressX = (float) e.getX();
|
||||
pathNetPressY = (float) e.getY();
|
||||
pathNetPressPending = true;
|
||||
pathNetDragging = false;
|
||||
pathNetRightPress = e.getButton() == MouseButton.SECONDARY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -225,6 +231,25 @@ public class ViewportController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_PATH_NETWORK
|
||||
&& e.isPrimaryButtonDown() && !pathNetRightPress) {
|
||||
if (pathNetPressPending) {
|
||||
float ddx = (float)(e.getX() - pathNetPressX);
|
||||
float ddy = (float)(e.getY() - pathNetPressY);
|
||||
if (ddx * ddx + ddy * ddy > 16f) {
|
||||
input.pathNetworkDragQueue.offer(new SharedInput.PathNetworkDrag(
|
||||
pathNetPressX, pathNetPressY, SharedInput.PathNetworkDrag.START));
|
||||
pathNetDragging = true;
|
||||
pathNetPressPending = false;
|
||||
}
|
||||
}
|
||||
if (pathNetDragging) {
|
||||
input.pathNetworkDragQueue.offer(new SharedInput.PathNetworkDrag(
|
||||
(float) e.getX(), (float) e.getY(), SharedInput.PathNetworkDrag.MOVE));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Play-Tool EDIT: Drag-Events an JME3 weiterleiten
|
||||
if (input.activeLayer == SharedInput.LAYER_PLAY_TOOL
|
||||
&& input.playToolMode == SharedInput.PlayToolMode.EDIT
|
||||
@@ -245,6 +270,17 @@ public class ViewportController {
|
||||
viewport.setOnMouseReleased(e -> {
|
||||
objDragging = false;
|
||||
if (!isObjectMode()) { stopEditTimer(); }
|
||||
if (input.activeLayer == SharedInput.LAYER_PATH_NETWORK) {
|
||||
if (pathNetDragging) {
|
||||
input.pathNetworkDragQueue.offer(new SharedInput.PathNetworkDrag(
|
||||
(float) e.getX(), (float) e.getY(), SharedInput.PathNetworkDrag.END));
|
||||
pathNetDragging = false;
|
||||
} else if (pathNetPressPending) {
|
||||
input.pathNetworkClickQueue.offer(new SharedInput.PathNetworkClick(
|
||||
pathNetPressX, pathNetPressY, pathNetRightPress));
|
||||
pathNetPressPending = false;
|
||||
}
|
||||
}
|
||||
if (input.activeLayer == SharedInput.LAYER_WATER) {
|
||||
input.waterDragEnd = true;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ public enum AnimationAction {
|
||||
TALK2,
|
||||
DISAPPOINT,
|
||||
SECRET,
|
||||
TALK_SITTING;
|
||||
TALK_SITTING,
|
||||
SWORD_ATTACK_1;
|
||||
|
||||
/** Lesbare Bezeichnung für UI-Anzeige, via TextRegistry aufgelöst. */
|
||||
public String displayName() {
|
||||
@@ -60,6 +61,7 @@ public enum AnimationAction {
|
||||
case DISAPPOINT -> TextRegistry.resolve(null, key, "Enttäuscht");
|
||||
case SECRET -> TextRegistry.resolve(null, key, "Geheimnis");
|
||||
case TALK_SITTING -> TextRegistry.resolve(null, key, "Reden (Sitzend)");
|
||||
case SWORD_ATTACK_1 -> TextRegistry.resolve(null, key, "Angriff 1(Schwert)");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,27 @@ NODE d886a17b-4ad0-4437-9dbf-6c0df951742f - 252.24966 6.40171 -892.78693
|
||||
NODE caa8813b-d828-4db8-ae77-9cf369309618 - 254.10876 2.00000 -852.84991
|
||||
NODE ff5bb246-2c95-4e76-b592-5881c51dab51 - 254.28955 2.00035 -826.95752
|
||||
NODE 28a87ffd-a39b-4347-a79f-fe5ead071b59 - 291.11404 2.00004 -817.18658
|
||||
NODE 7e3c1080-d37e-4fc9-b489-c0784f34fac1 - 306.78833 2.00000 -777.88641
|
||||
NODE 7e3c1080-d37e-4fc9-b489-c0784f34fac1 - 306.97855 2.00000 -784.28113
|
||||
NODE c29223fa-67a0-4df0-8055-91381ec8c83f - 317.70370 2.00000 -782.45502
|
||||
NODE 43677d9c-a19b-45f2-8eb3-d3368251d0b9 - 349.32700 2.00000 -774.56525
|
||||
NODE aeb4a96f-b1d7-404a-a654-e98f74e5c856 - 299.91858 2.00001 -803.26099
|
||||
NODE 64a2b232-6180-4f6d-9984-82820ac79053 - 298.20966 2.00000 -787.21967
|
||||
NODE 0bcd1776-f65a-4d2f-9f2f-b7cff6db8dc6 - 288.40808 2.00000 -778.93536
|
||||
NODE 2b4e0e77-30a4-4314-87ad-0d3582feffd0 - 280.04413 2.00000 -766.30432
|
||||
NODE 4f3a890b-a973-41fc-9798-d897209b12d0 - 295.18164 2.00000 -735.89783
|
||||
EDGE c7fe03f3-736b-4d81-accb-27dd6649e000 fa3be430-7714-49ed-9a81-4e4fb45ba564
|
||||
EDGE fa3be430-7714-49ed-9a81-4e4fb45ba564 5618a5ba-d0a5-4897-ab4d-a4a2932edb18
|
||||
EDGE f8f01e21-3963-4530-a5d6-bfdd0cb77233 d886a17b-4ad0-4437-9dbf-6c0df951742f
|
||||
EDGE caa8813b-d828-4db8-ae77-9cf369309618 ff5bb246-2c95-4e76-b592-5881c51dab51
|
||||
EDGE ff5bb246-2c95-4e76-b592-5881c51dab51 28a87ffd-a39b-4347-a79f-fe5ead071b59
|
||||
EDGE 28a87ffd-a39b-4347-a79f-fe5ead071b59 7e3c1080-d37e-4fc9-b489-c0784f34fac1
|
||||
EDGE 7e3c1080-d37e-4fc9-b489-c0784f34fac1 c29223fa-67a0-4df0-8055-91381ec8c83f
|
||||
EDGE c29223fa-67a0-4df0-8055-91381ec8c83f 43677d9c-a19b-45f2-8eb3-d3368251d0b9
|
||||
EDGE 5618a5ba-d0a5-4897-ab4d-a4a2932edb18 f8f01e21-3963-4530-a5d6-bfdd0cb77233
|
||||
EDGE 5618a5ba-d0a5-4897-ab4d-a4a2932edb18 caa8813b-d828-4db8-ae77-9cf369309618
|
||||
EDGE f8f01e21-3963-4530-a5d6-bfdd0cb77233 caa8813b-d828-4db8-ae77-9cf369309618
|
||||
EDGE 28a87ffd-a39b-4347-a79f-fe5ead071b59 aeb4a96f-b1d7-404a-a654-e98f74e5c856
|
||||
EDGE aeb4a96f-b1d7-404a-a654-e98f74e5c856 64a2b232-6180-4f6d-9984-82820ac79053
|
||||
EDGE 64a2b232-6180-4f6d-9984-82820ac79053 7e3c1080-d37e-4fc9-b489-c0784f34fac1
|
||||
EDGE 64a2b232-6180-4f6d-9984-82820ac79053 0bcd1776-f65a-4d2f-9f2f-b7cff6db8dc6
|
||||
EDGE 0bcd1776-f65a-4d2f-9f2f-b7cff6db8dc6 2b4e0e77-30a4-4314-87ad-0d3582feffd0
|
||||
EDGE 2b4e0e77-30a4-4314-87ad-0d3582feffd0 4f3a890b-a973-41fc-9798-d897209b12d0
|
||||
|
||||
Reference in New Issue
Block a user