From c8decda29746e079f995ceab30f852eb6943d2ba Mon Sep 17 00:00:00 2001 From: LeshaInc Date: Thu, 4 Jun 2020 18:55:10 +0300 Subject: [PATCH] Add relays --- lib/ocelot-brain | 2 +- sprites/nodes/Relay.png | Bin 0 -> 595 bytes .../resources/ocelot/desktop/spritesheet.png | Bin 55274 -> 55895 bytes .../resources/ocelot/desktop/spritesheet.txt | 35 +-- .../ocelot/desktop/geometry/Rect2D.scala | 4 + .../ocelot/desktop/graphics/Graphics.scala | 6 +- src/main/scala/ocelot/desktop/node/Node.scala | 90 ++++++- .../scala/ocelot/desktop/node/NodePort.scala | 18 ++ .../ocelot/desktop/node/NodeRegistry.scala | 7 +- .../node/{ => nodes}/ComputerNode.scala | 6 +- .../node/{ => nodes}/ComputerWindow.scala | 2 +- .../ocelot/desktop/node/nodes/RelayNode.scala | 27 ++ .../desktop/node/{ => nodes}/ScreenNode.scala | 3 +- .../node/{ => nodes}/ScreenWindow.scala | 2 +- .../desktop/ui/widget/WorkspaceView.scala | 235 ++++++++++++------ .../scala/ocelot/desktop/util/DrawUtils.scala | 2 +- .../scala/ocelot/desktop/util/TierColor.scala | 2 +- 17 files changed, 334 insertions(+), 107 deletions(-) create mode 100644 sprites/nodes/Relay.png create mode 100644 src/main/scala/ocelot/desktop/node/NodePort.scala rename src/main/scala/ocelot/desktop/node/{ => nodes}/ComputerNode.scala (92%) rename src/main/scala/ocelot/desktop/node/{ => nodes}/ComputerWindow.scala (97%) create mode 100644 src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala rename src/main/scala/ocelot/desktop/node/{ => nodes}/ScreenNode.scala (93%) rename src/main/scala/ocelot/desktop/node/{ => nodes}/ScreenWindow.scala (99%) diff --git a/lib/ocelot-brain b/lib/ocelot-brain index 69c4076..2ea8158 160000 --- a/lib/ocelot-brain +++ b/lib/ocelot-brain @@ -1 +1 @@ -Subproject commit 69c4076ae6dd199c403bbfb564c5e54d5065fbcc +Subproject commit 2ea81581884e6f766dc8477af54eb645ab63a5a7 diff --git a/sprites/nodes/Relay.png b/sprites/nodes/Relay.png new file mode 100644 index 0000000000000000000000000000000000000000..0afdbe3d989bdc15fed9dd48d5f3fdaccf775cf4 GIT binary patch literal 595 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 F{V!N^#Wer` diff --git a/src/main/resources/ocelot/desktop/spritesheet.txt b/src/main/resources/ocelot/desktop/spritesheet.txt index db3ea9d..b1577b2 100644 --- a/src/main/resources/ocelot/desktop/spritesheet.txt +++ b/src/main/resources/ocelot/desktop/spritesheet.txt @@ -1,6 +1,6 @@ BackgroundPattern 0 0 304 304 -Empty 337 197 1 1 -ShadowBorder 305 197 1 24 +Empty 337 204 1 1 +ShadowBorder 305 204 1 24 ShadowCorner 424 0 24 24 buttons/PowerOff 441 25 18 18 buttons/PowerOn 460 25 18 18 @@ -78,18 +78,19 @@ nodes/ComputerActivityOverlay 356 180 16 16 nodes/ComputerErrorOverlay 373 180 16 16 nodes/ComputerOnOverlay 390 180 16 16 nodes/NewNode 407 180 16 16 -nodes/Screen 424 180 16 16 -nodes/ScreenOnOverlay 441 180 16 16 -screen/BorderB 310 197 2 8 -screen/BorderT 307 197 2 10 -screen/CornerBL 476 180 8 8 -screen/CornerBR 485 180 8 8 -screen/CornerTL 458 180 8 10 -screen/CornerTR 467 180 8 10 -window/BorderDark 333 197 1 4 -window/BorderLight 335 197 1 4 -window/CloseButton 494 180 7 6 -window/CornerBL 313 197 4 4 -window/CornerBR 318 197 4 4 -window/CornerTL 323 197 4 4 -window/CornerTR 328 197 4 4 +nodes/Relay 424 180 16 16 +nodes/Screen 441 180 16 16 +nodes/ScreenOnOverlay 458 180 16 16 +screen/BorderB 310 204 2 8 +screen/BorderT 307 204 2 10 +screen/CornerBL 493 180 8 8 +screen/CornerBR 502 180 8 8 +screen/CornerTL 475 180 8 10 +screen/CornerTR 484 180 8 10 +window/BorderDark 333 204 1 4 +window/BorderLight 335 204 1 4 +window/CloseButton 305 197 7 6 +window/CornerBL 313 204 4 4 +window/CornerBR 318 204 4 4 +window/CornerTL 323 204 4 4 +window/CornerTR 328 204 4 4 diff --git a/src/main/scala/ocelot/desktop/geometry/Rect2D.scala b/src/main/scala/ocelot/desktop/geometry/Rect2D.scala index 499f08d..6798496 100644 --- a/src/main/scala/ocelot/desktop/geometry/Rect2D.scala +++ b/src/main/scala/ocelot/desktop/geometry/Rect2D.scala @@ -88,4 +88,8 @@ case class Rect2D(x: Float, y: Float, w: Float, h: Float) { def manhattanDistanceTo(that: Rect2D): Float = { ((center - that.center).abs - (extent + that.extent)).max(Vector2D(0, 0)).manhattanLength } + + def mapX(f: Float => Float): Rect2D = copy(x = f(x)) + + def mapY(f: Float => Float): Rect2D = copy(y = f(y)) } diff --git a/src/main/scala/ocelot/desktop/graphics/Graphics.scala b/src/main/scala/ocelot/desktop/graphics/Graphics.scala index 8e433a6..dbb80d3 100644 --- a/src/main/scala/ocelot/desktop/graphics/Graphics.scala +++ b/src/main/scala/ocelot/desktop/graphics/Graphics.scala @@ -1,7 +1,7 @@ package ocelot.desktop.graphics import ocelot.desktop.color.{Color, RGBAColorNorm} -import ocelot.desktop.geometry.{Size2D, Transform2D, Vector2D} +import ocelot.desktop.geometry.{Rect2D, Size2D, Transform2D, Vector2D} import ocelot.desktop.graphics.mesh.{Mesh, MeshInstance} import ocelot.desktop.graphics.render.InstanceRenderer import ocelot.desktop.util.{Font, Logging, Spritesheet} @@ -227,6 +227,10 @@ class Graphics extends Logging { _rect(x, y, width, height) } + def rect(r: Rect2D, color: Color): Unit = { + rect(r.x, r.y, r.w, r.h, color) + } + def rect(x: Float, y: Float, width: Float, height: Float, color: Color = RGBAColorNorm(1f, 1f, 1f)): Unit = { sprite("Empty", x, y, width, height, color) } diff --git a/src/main/scala/ocelot/desktop/node/Node.scala b/src/main/scala/ocelot/desktop/node/Node.scala index 96adcb2..4a5d781 100644 --- a/src/main/scala/ocelot/desktop/node/Node.scala +++ b/src/main/scala/ocelot/desktop/node/Node.scala @@ -1,18 +1,21 @@ package ocelot.desktop.node import ocelot.desktop.color.{Color, RGBAColor} -import ocelot.desktop.geometry.{Size2D, Vector2D} +import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.graphics.Graphics -import ocelot.desktop.ui.event.{ClickEvent, DragEvent, HoverEvent, MouseEvent} import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler} import ocelot.desktop.ui.event.sources.KeyEvents -import ocelot.desktop.ui.widget.{Widget, WorkspaceView} +import ocelot.desktop.ui.event.{ClickEvent, DragEvent, HoverEvent, MouseEvent} import ocelot.desktop.ui.widget.window.Window +import ocelot.desktop.ui.widget.{Widget, WorkspaceView} import ocelot.desktop.util.DrawUtils import ocelot.desktop.util.animation.ColorAnimation import org.apache.commons.lang3.StringUtils import org.lwjgl.input.Keyboard import totoro.ocelot.brain.entity.traits.Environment +import totoro.ocelot.brain.network + +import scala.collection.mutable.ArrayBuffer trait Node extends Widget with DragHandler with ClickHandler with HoverHandler { var workspaceView: WorkspaceView = _ @@ -25,6 +28,8 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler { private var isMoving = false private var grabPoint: Vector2D = Vector2D(0, 0) + protected val _connections: ArrayBuffer[(NodePort, Node, NodePort)] = ArrayBuffer[(NodePort, Node, NodePort)]() + size = minimumSize override def receiveMouseEvents = true @@ -37,6 +42,9 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler { if (!KeyEvents.isDown(Keyboard.KEY_LSHIFT)) { grabPoint = pos - position startMoving() + } else { + val port = portsBounds.flatMap(p => p._2.map(a => (p._1, a))).minBy(p => (p._2.center - pos).lengthSquared)._1 + workspaceView.newConnection = Some((this, port, pos)) } case DragEvent(DragEvent.State.Drag, MouseEvent.Button.Left, pos) => @@ -50,9 +58,8 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler { workspaceView.resolveCollision(this) if (workspaceView.collides(this) || (position - desiredPos).lengthSquared > 50 * 50) position = oldPos - // UiHandler.cursor = Cursor.Hand } else { - workspaceView.newConnection = Some((this, pos)) + workspaceView.newConnection = Some((this, workspaceView.newConnection.get._2, pos)) } case DragEvent(DragEvent.State.Stop, MouseEvent.Button.Left, _) => @@ -78,6 +85,69 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler { def iconColor: Color = RGBAColor(255, 255, 255) + def ports: Array[NodePort] = Array(NodePort()) + + def getNodeByPort(port: NodePort): network.Node = environment.node + + def connections: Iterator[(NodePort, Node, NodePort)] = _connections.iterator + + def connect(portA: NodePort, node: Node, portB: NodePort): Unit = { + this._connections.append((portA, node, portB)) + node._connections.append((portB, this, portA)) + this.onConnectionAdded(portA, node, portB) + node.onConnectionAdded(portB, this, portA) + } + + def disconnect(portA: NodePort, node: Node, portB: NodePort): Unit = { + this._connections -= ((portA, node, portB)) + node._connections -= ((portB, this, portA)) + this.onConnectionRemoved(portA, node, portB) + node.onConnectionRemoved(portB, this, portA) + } + + def isConnected(portA: NodePort, node: Node, portB: NodePort): Boolean = { + _connections.contains((portA, node, portB)) + } + + def onConnectionAdded(portA: NodePort, node: Node, portB: NodePort): Unit = { + getNodeByPort(portA).connect(node.getNodeByPort(portB)) + } + + def onConnectionRemoved(portA: NodePort, node: Node, portB: NodePort): Unit = { + getNodeByPort(portA).disconnect(node.getNodeByPort(portB)) + } + + def portsBounds: Iterator[(NodePort, Array[Rect2D])] = { + val length = -4 + val thickness = 4 + val stride = thickness + 4 + val hsize = Size2D(length, thickness) + val vsize = Size2D(thickness, length) + + val centers = bounds.edgeCenters + val ports = this.ports + val numPorts = ports.length + + ports.sorted.iterator.zipWithIndex.map { case (port, portIdx) => + val top = Rect2D(centers(0) + Vector2D(-thickness / 2, -length), vsize) + val right = Rect2D(centers(1) + Vector2D(0, -thickness / 2), hsize) + val bottom = Rect2D(centers(2) + Vector2D(-thickness / 2, 0), vsize) + val left = Rect2D(centers(3) + Vector2D(-length, -thickness / 2), hsize) + val centersBounds = Array[Rect2D](top, right, bottom, left) + + val portBounds = (0 until 4).map(side => { + val offset = thickness - numPorts * stride / 2 + portIdx * stride + val rect = centersBounds(side) + side match { + case 0 | 2 => rect.mapX(_ + offset) + case 1 | 3 => rect.mapY(_ + offset) + } + }) + + (port, portBounds.toArray) + } + } + // noinspection VarCouldBeVal var label: Option[String] = None @@ -98,7 +168,7 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler { highlight.goto(NoHighlight) } - protected def getShortLabel: String = + def getShortLabel: String = StringUtils.substring(label.orElse(Option(environment.node.address)).getOrElse("unknown"), 0, 8) override def draw(g: Graphics): Unit = { @@ -115,5 +185,13 @@ trait Node extends Widget with DragHandler with ClickHandler with HoverHandler { g.setNormalFont() } + def drawPorts(g: Graphics): Unit = { + for ((port, rects) <- portsBounds) { + val color = port.getColor + for (rect <- rects) + g.rect(rect, color) + } + } + lazy val window: Option[Window] = None } diff --git a/src/main/scala/ocelot/desktop/node/NodePort.scala b/src/main/scala/ocelot/desktop/node/NodePort.scala new file mode 100644 index 0000000..5e49671 --- /dev/null +++ b/src/main/scala/ocelot/desktop/node/NodePort.scala @@ -0,0 +1,18 @@ +package ocelot.desktop.node + +import ocelot.desktop.color.{Color, IntColor, RGBAColor} +import totoro.ocelot.brain.util.Direction + +case class NodePort(direction: Option[Direction.Value] = None) extends Ordered[NodePort] { + def getColor: Color = direction match { + case Some(Direction.Down) => IntColor(0x8382d8) + case Some(Direction.Up) => IntColor(0x75bdc1) + case Some(Direction.North) => IntColor(0xc8ca5f) + case Some(Direction.East) => IntColor(0xdb7d75) + case Some(Direction.West) => IntColor(0x7ec95f) + case Some(Direction.South) => IntColor(0x990da3) + case None => IntColor(0x9b9b9b) + } + + override def compare(that: NodePort): Int = this.direction.compare(that.direction) +} diff --git a/src/main/scala/ocelot/desktop/node/NodeRegistry.scala b/src/main/scala/ocelot/desktop/node/NodeRegistry.scala index f64f010..a82e557 100644 --- a/src/main/scala/ocelot/desktop/node/NodeRegistry.scala +++ b/src/main/scala/ocelot/desktop/node/NodeRegistry.scala @@ -1,6 +1,7 @@ package ocelot.desktop.node -import totoro.ocelot.brain.entity.{Case, Screen} +import ocelot.desktop.node.nodes.{ComputerNode, RelayNode, ScreenNode} +import totoro.ocelot.brain.entity.{Case, Relay, Screen} import scala.collection.mutable @@ -11,6 +12,10 @@ object NodeRegistry { types += t } + register(NodeType("Relay", "nodes/Relay", -1, () => { + new RelayNode(new Relay) + })) + for (tier <- 0 to 2) { register(NodeType("Screen" + tier, "nodes/Screen", tier, () => { new ScreenNode(new Screen(tier)) diff --git a/src/main/scala/ocelot/desktop/node/ComputerNode.scala b/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala similarity index 92% rename from src/main/scala/ocelot/desktop/node/ComputerNode.scala rename to src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala index 949c8cb..9e8e541 100644 --- a/src/main/scala/ocelot/desktop/node/ComputerNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala @@ -1,11 +1,12 @@ -package ocelot.desktop.node +package ocelot.desktop.node.nodes import ocelot.desktop.OcelotDesktop import ocelot.desktop.color.Color import ocelot.desktop.graphics.Graphics +import ocelot.desktop.node.Node import ocelot.desktop.util.TierColor import totoro.ocelot.brain.entity.traits.Computer -import totoro.ocelot.brain.entity.{CPU, Case, EEPROM, GraphicsCard, HDDManaged, InternetCard, Memory} +import totoro.ocelot.brain.entity.{CPU, Case, EEPROM, GraphicsCard, HDDManaged, InternetCard, Memory, NetworkCard} import totoro.ocelot.brain.loot.Loot import totoro.ocelot.brain.util.Tier @@ -17,6 +18,7 @@ class ComputerNode(val computer: Case) extends Node { computer.add(new InternetCard) computer.add(new Memory(Tier.Six)) computer.add(new Memory(Tier.Six)) + computer.add(new NetworkCard) computer.add(new HDDManaged(java.util.UUID.randomUUID().toString, Tier.Three, "hello")) private val eeprom = Loot.OpenOsEEPROM.create() eeprom.asInstanceOf[EEPROM].readonly = false diff --git a/src/main/scala/ocelot/desktop/node/ComputerWindow.scala b/src/main/scala/ocelot/desktop/node/nodes/ComputerWindow.scala similarity index 97% rename from src/main/scala/ocelot/desktop/node/ComputerWindow.scala rename to src/main/scala/ocelot/desktop/node/nodes/ComputerWindow.scala index f7313ee..7e211f7 100644 --- a/src/main/scala/ocelot/desktop/node/ComputerWindow.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ComputerWindow.scala @@ -1,4 +1,4 @@ -package ocelot.desktop.node +package ocelot.desktop.node.nodes import ocelot.desktop.geometry.Size2D import ocelot.desktop.graphics.Graphics diff --git a/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala b/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala new file mode 100644 index 0000000..80246db --- /dev/null +++ b/src/main/scala/ocelot/desktop/node/nodes/RelayNode.scala @@ -0,0 +1,27 @@ +package ocelot.desktop.node.nodes + +import ocelot.desktop.OcelotDesktop +import ocelot.desktop.node.{Node, NodePort} +import totoro.ocelot.brain.entity.Relay +import totoro.ocelot.brain.network +import totoro.ocelot.brain.util.Direction + +class RelayNode(relay: Relay) extends Node { + OcelotDesktop.workspace.add(relay) + + override def environment: Relay = relay + + override val icon: String = "nodes/Relay" + + override def ports: Array[NodePort] = Array( + NodePort(Some(Direction.North)), + NodePort(Some(Direction.South)), + NodePort(Some(Direction.East)), + NodePort(Some(Direction.West)), + NodePort(Some(Direction.Up)), + NodePort(Some(Direction.Down))) + + override def getShortLabel: String = "" + + override def getNodeByPort(port: NodePort): network.Node = relay.sidedNode(port.direction.get) +} diff --git a/src/main/scala/ocelot/desktop/node/ScreenNode.scala b/src/main/scala/ocelot/desktop/node/nodes/ScreenNode.scala similarity index 93% rename from src/main/scala/ocelot/desktop/node/ScreenNode.scala rename to src/main/scala/ocelot/desktop/node/nodes/ScreenNode.scala index 96dd489..1f6f00f 100644 --- a/src/main/scala/ocelot/desktop/node/ScreenNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ScreenNode.scala @@ -1,8 +1,9 @@ -package ocelot.desktop.node +package ocelot.desktop.node.nodes import ocelot.desktop.OcelotDesktop import ocelot.desktop.color.Color import ocelot.desktop.graphics.Graphics +import ocelot.desktop.node.Node import ocelot.desktop.util.TierColor import totoro.ocelot.brain.entity.traits.Environment import totoro.ocelot.brain.entity.{Keyboard, Screen} diff --git a/src/main/scala/ocelot/desktop/node/ScreenWindow.scala b/src/main/scala/ocelot/desktop/node/nodes/ScreenWindow.scala similarity index 99% rename from src/main/scala/ocelot/desktop/node/ScreenWindow.scala rename to src/main/scala/ocelot/desktop/node/nodes/ScreenWindow.scala index 6379deb..81f822f 100644 --- a/src/main/scala/ocelot/desktop/node/ScreenWindow.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ScreenWindow.scala @@ -1,4 +1,4 @@ -package ocelot.desktop.node +package ocelot.desktop.node.nodes import ocelot.desktop.color.{IntColor, RGBAColor, RGBAColorNorm} import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} diff --git a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala index 9ae8848..9d262b0 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala @@ -3,7 +3,8 @@ package ocelot.desktop.ui.widget import ocelot.desktop.color.{Color, RGBAColor, RGBAColorNorm} import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.graphics.Graphics -import ocelot.desktop.node.{ComputerNode, Node, ScreenNode} +import ocelot.desktop.node.nodes.{ComputerNode, ScreenNode} +import ocelot.desktop.node.{Node, NodePort} import ocelot.desktop.ui.event._ import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler} import ocelot.desktop.ui.layout.{CopyLayout, Layout} @@ -23,10 +24,11 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover val nodeSelector = new NodeSelector var cameraOffset: Vector2D = Vector2D(0, 0) - var newConnection: Option[(Node, Vector2D)] = None + var newConnection: Option[(Node, NodePort, Vector2D)] = None private var newNodePos = Vector2D(0, 0) private val gridAlpha = new ValueAnimation(0, 1f) + private val portsAlpha = new ValueAnimation(0, 7f) override protected val layout: Layout = new CopyLayout(this) @@ -45,21 +47,22 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover } def buildNewConnection(): Unit = { - val (start, endPoint) = newConnection.get - val end = nodes.find(_.bounds.contains(endPoint)) - if (end.isDefined && end.get != start) { - if (end.get.environment.node.neighbors.exists(_ == start.environment.node)) - end.get.environment.disconnect(start.environment) - else - end.get.environment.connect(start.environment) + val (node, port, _) = newConnection.get + newConnectionTarget match { + case Some((target, targetPort)) => + if (node.isConnected(port, target, targetPort)) + node.disconnect(port, target, targetPort) + else + node.connect(port, target, targetPort) + case None => } newConnection = None } def createDefaultWorkspace(): Unit = { - addNode(new ComputerNode(new Case(Tier.Six))) + addNode(new ComputerNode(new Case(Tier.Four))) addNode(new ScreenNode(new Screen(Tier.Two)), Vector2D(200, 100)) - nodes(0).environment.connect(nodes(1).environment) + nodes(0).connect(NodePort(), nodes(1), NodePort()) } override def receiveMouseEvents = true @@ -144,40 +147,39 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover !nodes.exists(_.bounds.collides(clearance)) } - private def findSuitableConnections(a: Rect2D, b: Rect2D, checkCollision: Boolean, - forceParallel: Boolean, clampMin: Boolean): Iterator[Array[Vector2D]] = { - var pairs = - for (aSide <- a.edgeCenters.iterator; bSide <- b.edgeCenters.iterator) - yield (aSide, bSide) + private def findSuitableConnections(a: Array[(Vector2D, Vector2D)], + b: Array[(Vector2D, Vector2D)], + checkCollision: Boolean, + forceParallel: Boolean, + clampMin: Boolean): Array[Array[Vector2D]] = { + val product = for (x <- a; y <- b) yield (x, y) + var iter = product.map { case ((aSide, aCenter), (bSide, bCenter)) => + val (aLen, bLen) = connectorLen(aSide, aCenter, bSide, bCenter, clampMin) + ((aSide, aCenter, aLen), (bSide, bCenter, bLen)) + } if (checkCollision) - pairs = pairs.filter(pair => { - val (aSide, bSide) = pair - val (aLen, bLen) = connectorLen(a.center, aSide, b.center, bSide, clampMin) - checkConnectorCollision(aSide, a.center, aLen) && - checkConnectorCollision(bSide, b.center, bLen) - }) + iter = iter.filter { case ((aSide, aCenter, aLen), (bSide, bCenter, bLen)) => + checkConnectorCollision(aSide, aCenter, aLen) && + checkConnectorCollision(bSide, bCenter, bLen) + } - var paths = pairs.map(p => { - val (aLen, bLen) = connectorLen(a.center, p._1, b.center, p._2, clampMin) - val aEnd = p._1 + (p._1 - a.center).normalizeAxisAligned * aLen - val bEnd = p._2 + (p._2 - b.center).normalizeAxisAligned * bLen - Array(p._1, aEnd, bEnd, p._2) - }).filter(DrawUtils.isValidPolyline) + var paths = iter.map { case ((aSide, aCenter, aLen), (bSide, bCenter, bLen)) => + val aEnd = aSide + (aSide - aCenter).normalizeAxisAligned * aLen + val bEnd = bSide + (bSide - bCenter).normalizeAxisAligned * bLen + Array(aSide, aEnd, bEnd, bSide) + }.filter(DrawUtils.isValidPolyline) if (forceParallel) - paths = paths.filter(p => { - val Array(aStart, aEnd, bEnd, bStart) = p - val aDir = aEnd - aStart - val bDir = bEnd - bStart - aDir.dot(bDir) != 0f - }) + paths = paths.filter { case Array(aStart, aEnd, bEnd, bStart) => + (aEnd - aStart).dot(bEnd - bStart) != 0f + } paths } - private def connectorLen(aCenter: Vector2D, aSide: Vector2D, - bCenter: Vector2D, bSide: Vector2D, + private def connectorLen(aSide: Vector2D, aCenter: Vector2D, + bSide: Vector2D, bCenter: Vector2D, clampMin: Boolean): (Float, Float) = { val aDir = aSide - aCenter val bDir = bSide - bCenter @@ -191,32 +193,111 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover (aLen.min(40f), bLen.min(40f)) } - def drawConnection(g: Graphics, a: Rect2D, b: Rect2D, - clampMin: Boolean = true, - checkCollision: Boolean = true, - forceParallel: Boolean = false, - drawBorder: Boolean = true, - thickness: Float = 4, col: Color = RGBAColor(150, 150, 150)): Unit = { - if (a.collides(b)) return + private def portDirections(node: Node, port: NodePort): Array[(Vector2D, Vector2D)] = { + val rect = node.bounds + val sides = node.portsBounds.find(_._1 == port).get._2 + .map(rects => rects.edgeCenters.maxBy(c => (c - rect.center).lengthSquared)) - val paths = findSuitableConnections(a, b, checkCollision, forceParallel, clampMin) + sides.zipWithIndex.map { case (side, i) => + val center = if (i % 2 == 0) rect.center.copy(x = side.x) else rect.center.copy(y = side.y) + (side, center) + } + } + + private def drawConnection(g: Graphics, aNode: Node, aPort: NodePort, bNode: Node, bPort: NodePort, + color: Color = RGBAColor(150, 150, 150)): Unit = { + val (aRect, bRect) = (aNode.bounds, bNode.bounds) + if (aRect.collides(bRect)) return + if (aRect.x < bRect.x) { + drawConnection(g, bNode, bPort, aNode, aPort, color) + return + } + + val paths = findSuitableConnections(portDirections(aNode, aPort), + portDirections(bNode, bPort), checkCollision = true, forceParallel = false, clampMin = true) if (paths.isEmpty) { - val canGiveUp = checkCollision || clampMin - if (canGiveUp) { - if (drawBorder) - g.line(a.center, b.center, thickness + 4, RGBAColorNorm(0.1f, 0.1f, 0.1f)) - g.line(a.center, b.center, thickness, col) - } + // give up + g.line(aRect.center, bRect.center, 8, RGBAColorNorm(0.1f, 0.1f, 0.1f)) + g.line(aRect.center, bRect.center, 4, color) } else { - val path = paths.minBy(p => { - val Array(_, aEnd, bEnd, _) = p - (aEnd - bEnd).lengthSquared - }) + val path = paths.minBy { case Array(_, aEnd, bEnd, _) => (aEnd - bEnd).lengthSquared } + DrawUtils.polyline(g, path, 8, RGBAColorNorm(0.1f, 0.1f, 0.1f)) + DrawUtils.polyline(g, path, 4, color) - if (drawBorder) - DrawUtils.polyline(g, path, thickness + 4, RGBAColorNorm(0.1f, 0.1f, 0.1f)) - DrawUtils.polyline(g, path, thickness, col) + val aDir = (path(1) - path(0)).normalizeAxisAligned * 8 + g.line(path(0), path(0) + aDir, 4, aPort.getColor) + val bDir = (path(2) - path(3)).normalizeAxisAligned * 8 + g.line(path(3), path(3) + bDir, 4, bPort.getColor) + } + } + + private def drawNewConnectionNoTarget(g: Graphics, node: Node, port: NodePort, endpoint: Vector2D): Unit = { + val color = RGBAColorNorm(0.3f, 0.5f, 0.4f) + val rect = node.bounds + + val filtered = portDirections(node, port) + .map { case (side, center) => + val (len, _) = connectorLen(side, side, endpoint, endpoint, clampMin = true) + (center, side, len) + } + .filter { case (center, side, len) => checkConnectorCollision(side, center, len) } + .map { case (center, side, len) => + val end = side + (side - center).normalizeAxisAligned * len + (side, end, (end - endpoint).lengthSquared) + } + .filter { case (side, end, _) => DrawUtils.isValidPolyline(Array(side, end, endpoint)) } + + if (filtered.isEmpty) + g.line(rect.center, endpoint, 4, color) + else { + val (side, end, _) = filtered.minBy(_._3) + DrawUtils.polyline(g, Array(side, end, endpoint), 4, color) + val dir = (end - side).normalizeAxisAligned * 8 + g.line(side, side + dir, 4, port.getColor) + } + } + + private def newConnectionTarget: Option[(Node, NodePort)] = { + val (node, _, endpoint) = newConnection.get + val validTargets = nodes.iterator.filter(n => n != node && n.bounds.inflate(20).contains(endpoint)) + + if (validTargets.nonEmpty) { + val target = validTargets.minBy(n => (n.bounds.center - endpoint).lengthSquared) + val targetPort = target.portsBounds.flatMap(p => p._2.map(a => (p._1, a))) + .minBy(p => (p._2.center - endpoint).lengthSquared)._1 + Some((target, targetPort)) + } else + None + } + + private def drawNewConnection(g: Graphics): Unit = { + val (node, port, endpoint) = newConnection.get + if (node.bounds.contains(endpoint)) return + + val color = RGBAColorNorm(0.3f, 0.5f, 0.4f) + + newConnectionTarget match { + case Some((target, targetPort)) => + drawConnection(g, node, port, target, targetPort, color = color) + case None => + drawNewConnectionNoTarget(g, node, port, endpoint) + } + } + + private def drawSelectorConnection(g: Graphics, aRect: Rect2D, bRect: Rect2D, + thickness: Float = 4, + color: Color = RGBAColor(150, 150, 150)): Unit = { + if (aRect.collides(bRect)) return + val (a, b) = if (aRect.x > bRect.x) (aRect, bRect) else (bRect, aRect) + + val paths = findSuitableConnections(a.edgeCenters.map((_, a.center)), + b.edgeCenters.map((_, b.center)), + checkCollision = false, forceParallel = true, clampMin = false) + + if (paths.nonEmpty) { + val path = paths.minBy { case Array(_, aEnd, bEnd, _) => (aEnd - bEnd).lengthSquared } + DrawUtils.polyline(g, path, thickness, color) } } @@ -263,37 +344,43 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover gridAlpha.goto(0f) } - val drawn = mutable.HashSet[(Node, Node)]() + val drawn = mutable.HashSet[(Node, NodePort, Node, NodePort)]() - for (node <- nodes) { - for (neighbour <- node.environment.node.neighbors) - for (other <- nodes.find(_.environment.node == neighbour)) { - if (!drawn.contains((node, other)) && !drawn.contains((other, node))) { - val gray = ((node.environment.node.address + other.environment.node.address).hashCode % 25 + 133).toShort - val color = RGBAColor(gray, gray, gray) - drawn.add((node, other)) - drawConnection(g, node.bounds, other.bounds, col = color) - } + for (nodeA <- nodes) { + nodeA.connections.foreach { case (portA, nodeB, portB) => + if (!drawn.contains((nodeA, portA, nodeB, portB)) && !drawn.contains((nodeB, portB, nodeA, portA))) { + val gray = ((nodeA.getShortLabel + nodeB.getShortLabel).hashCode % 25 + 133).toShort + val color = RGBAColor(gray, gray, gray) + drawn += ((nodeA, portA, nodeB, portB)) + drawConnection(g, nodeA, portA, nodeB, portB, color = color) } + } } - for ((start, endpoint) <- newConnection) { - drawConnection(g, start.bounds, Rect2D(endpoint - Vector2D(32, 32), Size2D(64, 64)), - col = RGBAColorNorm(0.3f, 0.5f, 0.4f)) + if (newConnection.isDefined) { + drawNewConnection(g) } nodes.foreach(_.draw(g)) nodes.foreach(_.drawLabel(g)) - val col = nodeSelector.ringColor + portsAlpha.update() + portsAlpha.goto(if (newConnection.isDefined) 1 else 0) + + g.save() + g.alphaMultiplier *= portsAlpha.value + nodes.foreach(_.drawPorts(g)) + g.restore() + + val color = nodeSelector.ringColor if (nodeSelector.isShown) { g.sprite("nodes/NewNode", newNodePos.x + cameraOffset.x, - newNodePos.y + cameraOffset.y, 64, 64, col) + newNodePos.y + cameraOffset.y, 64, 64, color) } if (nodeSelector.isShown) - drawConnection(g, Rect2D(newNodePos.x + cameraOffset.x, newNodePos.y + cameraOffset.y, 64, 64), - nodeSelector.bounds, col = col, checkCollision = false, drawBorder = false, forceParallel = true, clampMin = false) + drawSelectorConnection(g, Rect2D(newNodePos.x + cameraOffset.x, newNodePos.y + cameraOffset.y, 64, 64), + nodeSelector.bounds, color = color) drawChildren(g) } diff --git a/src/main/scala/ocelot/desktop/util/DrawUtils.scala b/src/main/scala/ocelot/desktop/util/DrawUtils.scala index e1d7d61..b3c3faf 100644 --- a/src/main/scala/ocelot/desktop/util/DrawUtils.scala +++ b/src/main/scala/ocelot/desktop/util/DrawUtils.scala @@ -43,7 +43,7 @@ object DrawUtils { def polyline(g: Graphics, points: Array[Vector2D], thickness: Float = 4, color: Color = RGBAColor(255, 255, 255)): Unit = { if (points.length < 2) return - if (!isValidPolyline(points)) return +// if (!isValidPolyline(points)) return var start = points(0) for ((end, i) <- points.iterator.zipWithIndex.drop(1)) { if (end != points.last) { diff --git a/src/main/scala/ocelot/desktop/util/TierColor.scala b/src/main/scala/ocelot/desktop/util/TierColor.scala index fe06316..da26eb4 100644 --- a/src/main/scala/ocelot/desktop/util/TierColor.scala +++ b/src/main/scala/ocelot/desktop/util/TierColor.scala @@ -10,5 +10,5 @@ object TierColor { val Tiers: Array[Color] = Array(Tier0, Tier1, Tier2, Tier3) - def get(tier: Int): Color = Tiers(tier.min(3)) + def get(tier: Int): Color = if (tier >= 0 && tier <= 3) Tiers(tier.min(3)) else IntColor(0xFFFFFF) }