Created
August 5, 2020 17:22
-
-
Save belyaev-mikhail/f789f1bc494abd7abe1a481e05c3e0c7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ru.spbstu.draw | |
import ru.spbstu.wheels.aStarSearch | |
import java.awt.* | |
import java.awt.event.ActionEvent | |
import java.awt.event.MouseAdapter | |
import java.awt.event.MouseEvent | |
import java.awt.event.MouseWheelEvent | |
import java.awt.geom.* | |
import javax.swing.* | |
import kotlin.math.abs | |
import kotlin.math.floor | |
fun Action(body: (ActionEvent) -> Unit) = object: AbstractAction() { | |
override fun actionPerformed(e: ActionEvent) { | |
return body(e) | |
} | |
} | |
operator fun Point2D.component1() = x | |
operator fun Point2D.component2() = y | |
fun Point2D(x: Double, y: Double): Point2D = Point2D.Double(x, y) | |
fun Point2D(x: Int, y: Int): Point2D = Point2D.Double(x.toDouble(), y.toDouble()) | |
fun Point2D.copy(x: Double = this.x, y: Double = this.y) = Point2D(x, y) | |
operator fun Point2D.plus(rhv: Point2D) = Point2D(x + rhv.x, y + rhv.y) | |
operator fun Point2D.plusAssign(rhv: Point2D) = setLocation(x + rhv.x, y + rhv.y) | |
infix fun Point2D.distance(that: Point2D) = this.distance(that) | |
infix fun Point2D.manhattanDistance(that: Point2D) = maxOf(abs(x - that.x), abs(y - that.y)) | |
infix fun Point2D.chebyshevDistance(that: Point2D) = abs(x - that.x) + abs(y - that.y) | |
val Rectangle2D.center get() = Point2D(centerX, centerY) | |
fun Rectangle2D(x: Double, y: Double, w: Double, h: Double = w): Rectangle2D = | |
Rectangle2D.Double(x, y, w, h) | |
fun Rectangle2D(p: Point2D, w: Double, h: Double = w): Rectangle2D = | |
Rectangle2D.Double(p.x, p.y, w, h) | |
fun Rectangle2D(p1: Point2D, p2: Point2D): Rectangle2D = | |
Rectangle2D.Double(p1.x, p2.y, 0.0, 0.0).apply { add(p2) } | |
fun Rectangle2D.copy(x: Double = this.x, | |
y: Double = this.y, | |
w: Double = this.width, | |
h: Double = this.height) = Rectangle2D(x, y, w, h) | |
operator fun Rectangle2D.plusAssign(point: Point2D) = add(point) | |
operator fun Rectangle2D.plusAssign(rectangle2D: Rectangle2D) = add(rectangle2D) | |
fun Ellipse2D(x: Double, y: Double, w: Double, h: Double): Ellipse2D = | |
Ellipse2D.Double(x, y, w, h) | |
fun Ellipse2D(center: Point2D, w: Double, h: Double = w): Ellipse2D = | |
Ellipse2D.Double(center.x - w / 2, center.y - h / 2, w, h) | |
fun Line2D(p1: Point2D, p2: Point2D): Line2D = Line2D.Double(p1, p2) | |
operator fun Line2D.component1() = p1 | |
operator fun Line2D.component2() = p2 | |
inline fun dumbCanvas(width: Int = 800, height: Int = 600, crossinline draw: Graphics2D.() -> Unit) = object : TransformablePanel() { | |
override val transform: AffineTransform = AffineTransform() | |
override fun getPreferredSize(): Dimension = Dimension(width, height) | |
override fun paintComponent(g: Graphics) { | |
super.paintComponent(g) | |
g as Graphics2D | |
val oldTransform = g.transform | |
g.transform(transform) | |
g.stroke = BasicStroke((1.0 / maxOf(transform.scaleX, transform.scaleY)).toFloat()) | |
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) | |
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY) | |
g.draw() | |
g.transform = oldTransform | |
} | |
} | |
fun dumbFrame(panel: JPanel, title: String = "") = JFrame(title).apply { | |
add(panel) | |
pack() | |
isVisible = true | |
} | |
inline fun dumbFrame(title: String = "", width: Int = 800, height: Int = 600, crossinline draw: Graphics2D.() -> Unit) = | |
dumbFrame(title = title, panel = dumbCanvas(width, height, draw)) | |
abstract class TransformablePanel: JPanel() { | |
abstract val transform: AffineTransform | |
fun scale(scaleX: Double, scaleY: Double = scaleX) { | |
SwingUtilities.invokeLater { | |
transform.scale(scaleX, scaleY) | |
repaint() | |
} | |
} | |
fun translate(tx: Double, ty: Double) { | |
SwingUtilities.invokeLater { | |
transform.translate(tx, ty) | |
repaint() | |
} | |
} | |
fun zoomTo(point: Point2D, scaleX: Double, scaleY: Double = scaleX) { | |
SwingUtilities.invokeLater { | |
transform.apply { | |
translate(point.x, point.y) | |
scale(scaleX, scaleY) | |
translate(-point.x, -point.y) | |
} | |
repaint() | |
} | |
} | |
fun invokeRepaint() = SwingUtilities.invokeLater { repaint() } | |
val canvasMousePosition: Point2D? get() = this.mousePosition?.let { transform.inverseTransform(it) } | |
val MouseEvent.canvasPoint: Point2D get() = transform.inverseTransform(point, Point2D.Double()) | |
} | |
inline fun Graphics2D.withPaint(p: Paint, body: Graphics2D.() -> Unit) { | |
val oldPaint = paint | |
paint = p | |
body() | |
paint = oldPaint | |
} | |
inline fun Graphics2D.withFont(f: Font, body: Graphics2D.() -> Unit) { | |
val oldFont = font | |
font = f | |
body() | |
font = oldFont | |
} | |
inline fun Graphics2D.withStroke(s: Stroke, body: Graphics2D.() -> Unit) { | |
val oldStroke = stroke | |
stroke = s | |
body() | |
stroke = oldStroke | |
} | |
inline fun Graphics2D.absolute(body: Graphics2D.() -> Unit) { | |
val oldTransform = transform | |
transform = AffineTransform() | |
body() | |
transform = oldTransform | |
} | |
inline fun TransformablePanel.onMouseClick(crossinline body: TransformablePanel.(e: MouseEvent) -> Unit) { | |
addMouseListener(object: MouseAdapter() { | |
override fun mouseClicked(e: MouseEvent) { | |
body(e) | |
} | |
}) | |
} | |
inline fun TransformablePanel.onMouseWheel(crossinline body: TransformablePanel.(e: MouseWheelEvent) -> Unit) { | |
addMouseWheelListener { e -> body(e) } | |
} | |
fun sameButton(start: MouseEvent, e: MouseEvent) = | |
start.modifiersEx == e.modifiersEx | |
inline fun TransformablePanel.onMousePan( | |
crossinline filter: TransformablePanel.(e: MouseEvent) -> Boolean = { true }, | |
crossinline destructor: TransformablePanel.(start: MouseEvent, prev: MouseEvent) -> Unit = { _, _ -> }, | |
crossinline body: TransformablePanel.(start: MouseEvent, prev: MouseEvent, e: MouseEvent) -> Unit) { | |
val panner = object: MouseAdapter() { | |
private var start: MouseEvent? = null | |
private var prev: MouseEvent? = null | |
override fun mousePressed(e: MouseEvent) { | |
if(filter(e)) { | |
start = e | |
prev = e | |
} | |
} | |
override fun mouseReleased(e: MouseEvent) { | |
val start = start | |
val prev = prev | |
if(start != null && prev != null && e.button == start.button) { | |
body(start, prev, e) | |
destructor(start, e) | |
this.start = null | |
this.prev = null | |
} | |
} | |
override fun mouseDragged(e: MouseEvent) { | |
val start = start | |
val prev = prev | |
if(start != null && prev != null && sameButton(start, e)) { | |
body(start, prev, e) | |
this.prev = e | |
} | |
} | |
} | |
addMouseListener(panner) | |
addMouseMotionListener(panner) | |
} | |
inline fun JComponent.onKey(stroke: KeyStroke, crossinline body: () -> Unit) { | |
val skey = "$stroke".replace(" ", "+") | |
inputMap.put(stroke, skey) | |
actionMap.put(skey, Action { body() }) | |
} | |
inline fun JComponent.onKey(stroke: String, crossinline body: () -> Unit) { | |
val skey = stroke.replace(" ", "+") | |
inputMap.put(KeyStroke.getKeyStroke(stroke), skey) | |
actionMap.put(skey, Action { body() }) | |
} | |
operator fun AffineTransform.invoke(p: Point2D) = transform(p, Point2D.Double()) | |
fun AffineTransform.inverseTransform(p: Point2D) = inverseTransform(p, Point2D.Double()) | |
enum class CellState { | |
Free, NotFree, Start, Finish; | |
val invert: CellState | |
get() = when(this) { | |
Free -> NotFree | |
NotFree -> Free | |
Start -> Free | |
Finish -> Free | |
} | |
} | |
data class Cell(var state: CellState, val x: Int, val y: Int) | |
interface Drawable { | |
fun drawOn(graphics2D: Graphics2D) | |
object Nil: Drawable { | |
override fun drawOn(graphics2D: Graphics2D){} | |
} | |
data class Shape(val inner: java.awt.Shape): Drawable { | |
override fun drawOn(graphics2D: Graphics2D) { | |
graphics2D.fill(inner) | |
} | |
} | |
class Multi(vararg val inner: Drawable): Drawable { | |
constructor(vararg shapes: java.awt.Shape): this(*shapes.map { Shape(it) }.toTypedArray<Drawable>()) | |
override fun drawOn(graphics2D: Graphics2D) { | |
for(e in inner) e.drawOn(graphics2D) | |
} | |
} | |
} | |
fun Graphics2D.draw(drawable: Drawable) = drawable.drawOn(this) | |
fun main() { | |
val field = List(18) { x -> List(18) { y -> Cell(CellState.Free, x, y) } } | |
fun getCell(pt: Point2D) = field.getOrNull(floor(pt.x).toInt())?.getOrNull(floor(pt.y).toInt()) | |
val overlays: MutableMap<String, Drawable> = mutableMapOf() | |
val canvas = dumbCanvas { | |
withPaint(Color.BLUE.darker()) { | |
for((state, x, y) in field.flatten()) { | |
val rec = Rectangle2D.Double(x.toDouble(), y.toDouble(), 1.0, 1.0) | |
val paint = when(state) { | |
CellState.NotFree -> Color.DARK_GRAY | |
CellState.Start -> Color.BLUE.brighter() | |
CellState.Finish -> Color.RED | |
CellState.Free -> null | |
} | |
if(paint != null) withPaint(paint) { | |
fill(rec) | |
} | |
draw(rec) | |
} | |
} | |
withPaint(Color(255, 175, 175, 100)) { | |
for((_, overlay) in overlays) if(overlay != null) draw(overlay) | |
} | |
absolute { | |
withFont(Font.decode("Fira-Mono-Bold-20")) { | |
drawString("Hello", 20.0f, 20.0f) | |
} | |
} | |
} | |
canvas.translate(20.0, 20.0) | |
canvas.scale(30.0) | |
canvas.onMouseClick { e -> | |
val pt = e.canvasPoint | |
getCell(pt)?.apply { | |
state = state.invert | |
} | |
canvas.invokeRepaint() | |
} | |
canvas.onKey("S") { | |
val pt = canvas.canvasMousePosition ?: return@onKey | |
getCell(pt)?.apply { | |
field.flatten().filter { it.state == CellState.Start }.forEach { it.state = CellState.Free } | |
state = CellState.Start | |
} | |
canvas.invokeRepaint() | |
} | |
canvas.onKey("F") { | |
val pt = canvas.canvasMousePosition ?: return@onKey | |
getCell(pt)?.apply { | |
field.flatten().filter { it.state == CellState.Finish }.forEach { it.state = CellState.Free } | |
state = CellState.Finish | |
} | |
canvas.invokeRepaint() | |
} | |
canvas.onKey("DOWN") { | |
val ty = 20.0 / canvas.transform.scaleY | |
canvas.translate(0.0, ty) | |
} | |
canvas.onKey("UP") { | |
val ty = -20.0 / canvas.transform.scaleY | |
canvas.translate(0.0, ty) | |
} | |
canvas.onKey("LEFT") { | |
val tx = -20.0 / canvas.transform.scaleX | |
canvas.translate(tx, 0.0) | |
} | |
canvas.onKey("RIGHT") { | |
val tx = 20.0 / canvas.transform.scaleX | |
canvas.translate(tx, 0.0) | |
} | |
canvas.onKey(KeyStroke.getKeyStroke('+', 0)) { | |
canvas.scale(1.1) | |
} | |
canvas.onKey(KeyStroke.getKeyStroke('-', 0)) { | |
canvas.scale(0.9) | |
} | |
canvas.onKey("SPACE") { | |
val start = field.flatten().firstOrNull { it.state == CellState.Start } ?: return@onKey | |
val end = field.flatten().firstOrNull { it.state == CellState.Finish } ?: return@onKey | |
val spt = Point2D(start.x, start.y) | |
val ept = Point2D(end.x, end.y) | |
var astarPath by overlays | |
val epath = aStarSearch( | |
spt, | |
heur = { it.distance(ept) * 2 }, | |
goal = { it == ept }, | |
neighbours = { el -> | |
val neigh = sequenceOf( | |
Point2D(0, 1), | |
Point2D(1, 0), | |
Point2D(0, -1), | |
Point2D(-1, 0), | |
Point2D(-2, -2) | |
) | |
neigh.map { el + it }.filter { | |
getCell(it)?.state != CellState.NotFree && it.x.toInt() in 0..17 && it.y.toInt() in 0..17 | |
} | |
} | |
) ?: run { astarPath = Drawable.Nil; return@onKey } | |
astarPath = Drawable.Multi(shapes = *epath.map { Rectangle2D(it, 1.0, 1.0) }.toTypedArray()) | |
canvas.invokeRepaint() | |
} | |
canvas.onMouseWheel { e -> | |
val rot = e.preciseWheelRotation | |
val point = e.canvasPoint | |
val scale = 1.0 - rot * 0.1 | |
canvas.zoomTo(point, scale) | |
} | |
canvas.onMousePan(filter = { SwingUtilities.isRightMouseButton(it) }) { _, prev, e -> | |
val st = prev.canvasPoint | |
val end = e.canvasPoint | |
canvas.translate(end.x - st.x, end.y - st.y) | |
} | |
var panline by overlays | |
canvas.onMousePan( | |
destructor = { _, _ -> panline = Drawable.Nil }, | |
filter = { SwingUtilities.isLeftMouseButton(it) }) { start, _, e -> | |
val st = start.canvasPoint | |
val end = e.canvasPoint | |
val line = Line2D.Double(st, end) | |
panline = Drawable.Shape(BasicStroke(0.2f).createStrokedShape(line)) | |
val nstate = getCell(st)?.state?.takeIf { it == CellState.NotFree } ?: CellState.Free | |
for(cell in field.flatten()) { | |
val (_, x, y) = cell | |
val rec = Rectangle2D.Double(x.toDouble(), y.toDouble(), 1.0, 1.0) | |
if(line.intersects(rec)) cell.state = nstate | |
} | |
canvas.invokeRepaint() | |
} | |
dumbFrame(canvas).defaultCloseOperation = JFrame.EXIT_ON_CLOSE | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment