diff --git a/src/main/scala/ocelot/desktop/geometry/Rect2D.scala b/src/main/scala/ocelot/desktop/geometry/Rect2D.scala index 7dc1537..b4d6f74 100644 --- a/src/main/scala/ocelot/desktop/geometry/Rect2D.scala +++ b/src/main/scala/ocelot/desktop/geometry/Rect2D.scala @@ -15,6 +15,8 @@ case class Rect2D(var x: Float, var y: Float, var w: Float, var h: Float) { def size: Size2D = Size2D(w, h) + def extent: Vector2D = Vector2D(w / 2f, h / 2f) + def collides(b: Rect2D): Boolean = { val a = this a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y @@ -30,7 +32,7 @@ case class Rect2D(var x: Float, var y: Float, var w: Float, var h: Float) { var minDist = math.abs(point.x - min.x) var boundsPoint = Vector2D(min.x, point.y) - if (math.abs(max.x - point.x) < minDist) { + if (math.abs(max.x - point.x) < minDist) { minDist = Math.abs(max.x - point.x) boundsPoint = Vector2D(max.x, point.y) } @@ -47,4 +49,20 @@ case class Rect2D(var x: Float, var y: Float, var w: Float, var h: Float) { boundsPoint } + + def inflate(addition: Float): Rect2D = Rect2D(x - addition, y - addition, w + addition * 2, h + addition * 2) + + def center: Vector2D = { + min + (size * 0.5f).toVector + } + + def centerPoints: Array[Vector2D] = Array( + Vector2D(x + w / 2f, y), + Vector2D(x + w, y + h / 2f), + Vector2D(x + w / 2f, y + h), + Vector2D(x, y + h / 2f)) + + def distanceTo(that: Rect2D): Float = { + ((center - that.center).abs - (extent + that.extent)).max(Vector2D(0, 0)).length + } } diff --git a/src/main/scala/ocelot/desktop/geometry/Size2D.scala b/src/main/scala/ocelot/desktop/geometry/Size2D.scala index f50760f..8e225fb 100644 --- a/src/main/scala/ocelot/desktop/geometry/Size2D.scala +++ b/src/main/scala/ocelot/desktop/geometry/Size2D.scala @@ -26,5 +26,7 @@ case class Size2D(var width: Float, var height: Float) { def +(that: Size2D): Size2D = Size2D(width + that.width, height + that.height) + def *(mul: Float): Size2D = Size2D(width * mul, height * mul) + override def toString: String = f"Size2D [ $width%8.2f x $height%8.2f ]" } \ No newline at end of file diff --git a/src/main/scala/ocelot/desktop/geometry/Vector2D.scala b/src/main/scala/ocelot/desktop/geometry/Vector2D.scala index ddbf704..9ac85da 100644 --- a/src/main/scala/ocelot/desktop/geometry/Vector2D.scala +++ b/src/main/scala/ocelot/desktop/geometry/Vector2D.scala @@ -37,7 +37,7 @@ case class Vector2D(var x: Float, var y: Float) { def *(that: Vector2D): Float = x * that.x + y * that.y - def length: Float = math.sqrt(lengthSquared).asInstanceOf + def length: Float = math.sqrt(lengthSquared).toFloat def lengthSquared: Float = this.x * this.x + this.y * this.y @@ -51,9 +51,9 @@ case class Vector2D(var x: Float, var y: Float) { if (l > 0) this /= this.length } - def rounded(): Vector2D = Vector2D(x.round, y.round) + def rounded: Vector2D = Vector2D(x.round, y.round) - def angle: Float = math.atan2(this.y, this.x).asInstanceOf + def angle: Float = math.atan2(this.y, this.x).toFloat def angle(that: Vector2D): Float = angle - that.angle @@ -64,9 +64,13 @@ case class Vector2D(var x: Float, var y: Float) { def rotate(angle: Float): Unit = { val (s, c) = (math.sin(angle), math.cos(angle)) - x = (c * x - s * y).asInstanceOf - y = (s * x + c * y).asInstanceOf + x = (c * x - s * y).toFloat + y = (s * x + c * y).toFloat } + def abs: Vector2D = Vector2D(x.abs, y.abs) + + def max(that: Vector2D): Vector2D = Vector2D(x.max(that.x), y.max(that.y)) + override def toString: String = s"Vector2D [$x, $y]" } diff --git a/src/main/scala/ocelot/desktop/graphics/Graphics.scala b/src/main/scala/ocelot/desktop/graphics/Graphics.scala index dbbeba1..b6c7aad 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.Transform2D +import ocelot.desktop.geometry.{Transform2D, Vector2D} import ocelot.desktop.graphics.mesh.{Mesh, MeshInstance} import ocelot.desktop.graphics.render.InstanceRenderer import ocelot.desktop.util.{Font, Spritesheet} @@ -140,6 +140,16 @@ class Graphics extends Logging { transform(Transform2D.translate(x, y)) } + def rotate(angle: Float): Unit = { + transform(Transform2D.rotate(angle)) + } + + def origin: Vector2D = stack.head.origin + + def origin_=(origin: Vector2D): Unit = { + stack.head.origin = origin + } + def text(x: Float, y: Float, text: String): Unit = { var ox = x val fontSize = fontSizeMultiplier * _font.fontSize @@ -194,20 +204,43 @@ class Graphics extends Logging { def rect(x: Float, y: Float, width: Float, height: Float, rotation: Float = 0f): Unit = { val uvTransform = Transform2D.translate(spriteRect.x, spriteRect.y) >> - Transform2D.scale(spriteRect.w, spriteRect.h) + Transform2D.scale(spriteRect.w, spriteRect.h - 0.3f / 512) + + val origin = stack.head.origin val transform = stack.head.transform >> Transform2D.translate(x, y) >> - Transform2D.scale(width, height) >> - Transform2D.translate(0.5f, 0.5f) >> - Transform2D.rotate(rotation * 0.0174532925f) >> - Transform2D.translate(-0.5f, -0.5f) + Transform2D.translate(-origin.x, -origin.y) >> + Transform2D.rotate(rotation) >> + Transform2D.translate(origin.x, origin.y) >> + Transform2D.scale(width, height) rectRenderer.schedule(MeshInstance(stack.head.foreground, z, transform, uvTransform)) z += 1 } + def line(x1: Float, y1: Float, x2: Float, y2: Float, thickness: Float): Unit = { + save() + + origin = Vector2D(0.0, 0.5) + sprite = "Empty" + + val dy = x2 - x1 + val dx = y2 - y1 + val length = math.sqrt(dx * dx + dy * dy).toFloat + val inclination = math.atan2(dy, dx).toFloat + translate(x1, y1) + rotate(-inclination + (math.Pi * 0.5).toFloat) + rect(-thickness * 0.25f, -thickness * 0.5f, length + thickness * 0.25f, thickness) + + restore() + } + + def line(start: Vector2D, end: Vector2D, thickness: Float = 4): Unit = { + line(start.x, start.y, end.x, end.y, thickness) + } + def flush(): Unit = { Spritesheet.texture.bind() rectRenderer.flush() diff --git a/src/main/scala/ocelot/desktop/graphics/GraphicsState.scala b/src/main/scala/ocelot/desktop/graphics/GraphicsState.scala index 4238b24..21e7f3c 100644 --- a/src/main/scala/ocelot/desktop/graphics/GraphicsState.scala +++ b/src/main/scala/ocelot/desktop/graphics/GraphicsState.scala @@ -1,11 +1,12 @@ package ocelot.desktop.graphics import ocelot.desktop.color.RGBAColorNorm -import ocelot.desktop.geometry.Transform2D +import ocelot.desktop.geometry.{Transform2D, Vector2D} case class GraphicsState(var foreground: RGBAColorNorm = RGBAColorNorm(0f, 0f, 0f), var background: RGBAColorNorm = RGBAColorNorm(1f, 1f, 1f), var fontSizeMultiplier: Float = 1f, var sprite: String = "Empty", var scissor: Option[(Int, Int, Int, Int)] = None, - var transform: Transform2D = Transform2D.identity) + var transform: Transform2D = Transform2D.identity, + var origin: Vector2D = Vector2D(0, 0)) diff --git a/src/main/scala/ocelot/desktop/ui/UiHandler.scala b/src/main/scala/ocelot/desktop/ui/UiHandler.scala index c0703e1..57f9657 100644 --- a/src/main/scala/ocelot/desktop/ui/UiHandler.scala +++ b/src/main/scala/ocelot/desktop/ui/UiHandler.scala @@ -56,6 +56,7 @@ object UiHandler extends Logging { GLFW.glfwDefaultWindowHints() GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE) GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE) + GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 16) windowSize = root.size.copy() diff --git a/src/main/scala/ocelot/desktop/ui/layout/LinearLayout.scala b/src/main/scala/ocelot/desktop/ui/layout/LinearLayout.scala index 1f2ccec..d068789 100644 --- a/src/main/scala/ocelot/desktop/ui/layout/LinearLayout.scala +++ b/src/main/scala/ocelot/desktop/ui/layout/LinearLayout.scala @@ -140,7 +140,7 @@ class LinearLayout(widget: Widget, child.position.y = widget.position.y + widget.size.height / 2 - child.size.height / 2 } - child.position = child.position.rounded() + child.position = child.position.rounded } } } diff --git a/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala index 6c81187..ae63b9c 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala @@ -1,6 +1,5 @@ package ocelot.desktop.ui.widget -import ocelot.desktop.color.RGBAColor import ocelot.desktop.ui.layout.LinearLayout import totoro.ocelot.brain.entity.Screen @@ -10,7 +9,7 @@ class RootWidget(screen: Screen) extends Widget { val screenWidget = new ScreenWidget(screen) children :+= new WorkspaceView -// children :+= new ScrollView(new WorkspaceView) -// -// children :+= new ScrollView(new CenterBox(screenWidget, background = RGBAColor(30, 30, 30))) + // children :+= new ScrollView(new WorkspaceView) + // + // children :+= new ScrollView(new CenterBox(screenWidget, background = RGBAColor(30, 30, 30))) } diff --git a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala index 4f7344c..959b0af 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala @@ -1,5 +1,6 @@ package ocelot.desktop.ui.widget +import ocelot.desktop.color.RGBAColor import ocelot.desktop.geometry.{Size2D, Vector2D} import ocelot.desktop.graphics.Graphics import ocelot.desktop.ui.UiHandler @@ -85,11 +86,33 @@ class WorkspaceView extends Widget { node.position -= penetrationVector } + private def drawConnection(g: Graphics, a: Node, b: Node): Unit = { + g.foreground = RGBAColor(150, 150, 150) + + val aBounds = a.bounds + val bBounds = b.bounds + val dist = aBounds.distanceTo(bBounds) / 2f + + val addition = if (dist < 100) { + 40.56077 + (2.219244 - 40.56077) / math.pow(1 + math.pow(dist / 22949.13, 2.598411), 14930690) + } else 40 + + val aRect = aBounds.inflate(addition.toFloat) + val bRect = bBounds.inflate(addition.toFloat) + + val pairs = for (a <- aRect.centerPoints; b <- bRect.centerPoints) yield (a, b) + val pair = pairs.minBy(pair => (pair._1 - pair._2).length.toInt) + val (start, end) = pair + g.line(start, end) + g.line(start, a.bounds.center) + g.line(end, b.bounds.center) + } + override def draw(g: Graphics): Unit = { g.save() - val backgroundOffsetX = if (cameraOffset.x > 0) 304f - cameraOffset.x % 304f else -cameraOffset.x % 304f - val backgroundOffsetY = if (cameraOffset.y > 0) 304f - cameraOffset.y % 304f else -cameraOffset.y % 304f + val backgroundOffsetX = if (cameraOffset.x > 0) 304 - cameraOffset.x.toInt % 304 else -cameraOffset.x.toInt % 304 + val backgroundOffsetY = if (cameraOffset.y > 0) 304 - cameraOffset.y.toInt % 304 else -cameraOffset.y.toInt % 304 val numRepeatsX = math.ceil((size.width + backgroundOffsetX) / 304f).asInstanceOf[Int] val numRepeatsY = math.ceil((size.height + backgroundOffsetY) / 304f).asInstanceOf[Int] @@ -97,13 +120,14 @@ class WorkspaceView extends Widget { for (x <- 0 to numRepeatsX) { for (y <- 0 to numRepeatsY) { - g.sprite("BackgroundPattern", x * 304f, y * 304f, 304f, 304f) + g.sprite("BackgroundPattern", x * 304, y * 304, 304, 304) } } g.restore() g.translate(cameraOffset.x + position.x, cameraOffset.y + position.y) + drawConnection(g, nodes(0), nodes(1)) nodes.foreach(_.draw(g)) } } diff --git a/src/main/scala/ocelot/desktop/util/Easing.scala b/src/main/scala/ocelot/desktop/util/Easing.scala index b0ef26e..92ab817 100644 --- a/src/main/scala/ocelot/desktop/util/Easing.scala +++ b/src/main/scala/ocelot/desktop/util/Easing.scala @@ -3,7 +3,7 @@ package ocelot.desktop.util object Easing { def linear(t: Float): Float = t - def easeInQuad(t: Float): Float = t + def easeInQuad(t: Float): Float = t * t def easeOutQuad(t: Float): Float = t * (2 - t)