package barneshut import java.awt._ import java.awt.event._ import javax.swing._ import javax.swing.event._ class SimulationCanvas(val model: SimulationModel) extends JComponent { val MAX_RES = 3000 val pixels = new Array[Int](MAX_RES * MAX_RES) override def paintComponent(gcan: Graphics) = { super.paintComponent(gcan) val width = getWidth val height = getHeight val img = new image.BufferedImage(width, height, image.BufferedImage.TYPE_INT_ARGB) // clear canvas pixels for (x <- 0 until MAX_RES; y <- 0 until MAX_RES) pixels(y * width + x) = 0 // count number of bodies in each pixel for (b <- model.bodies) { val px = ((b.x - model.screen.minX) / model.screen.width * width).toInt val py = ((b.y - model.screen.minY) / model.screen.height * height).toInt if (px >= 0 && px < width && py >= 0 && py < height) pixels(py * width + px) += 1 } // set image intensity depending on the number of bodies in the pixel for (y <- 0 until height; x <- 0 until width) { val count = pixels(y * width + x) val intensity = if (count > 0) math.min(255, 70 + count * 50) else 0 val color = (255 << 24) | (intensity << 16) | (intensity << 8) | intensity img.setRGB(x, y, color) } // for debugging purposes, if the number of bodies is small, output their locations val g = img.getGraphics.asInstanceOf[Graphics2D] g.setColor(Color.GRAY) if (model.bodies.length < 350) for (b <- model.bodies) { def round(x: Float) = (x * 100).toInt / 100.0f val px = ((b.x - model.screen.minX) / model.screen.width * width).toInt val py = ((b.y - model.screen.minY) / model.screen.height * height).toInt if (px >= 0 && px < width && py >= 0 && py < height) { g.drawString(s"${round(b.x)}, ${round(b.y)}", px, py) } } // render quad if necessary if (model.shouldRenderQuad) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) val green = new Color(0, 225, 80, 150) val red = new Color(200, 0, 0, 150) g.setColor(green) def drawQuad(depth: Int, quad: Quad): Unit = { def drawRect(fx: Float, fy: Float, fsz: Float, q: Quad, fill: Boolean = false): Unit = { val x = ((fx - model.screen.minX) / model.screen.width * width).toInt val y = ((fy - model.screen.minY) / model.screen.height * height).toInt val w = ((fx + fsz - model.screen.minX) / model.screen.width * width).toInt - x val h = ((fy + fsz - model.screen.minY) / model.screen.height * height).toInt - y g.drawRect(x, y, w, h) if (fill) g.fillRect(x, y, w, h) if (depth <= 5) g.drawString("#:" + q.total, x + w / 2, y + h / 2) } quad match { case Fork(nw, ne, sw, se) => val cx = quad.centerX val cy = quad.centerY val sz = quad.size drawRect(cx - sz / 2, cy - sz / 2, sz / 2, nw) drawRect(cx - sz / 2, cy, sz / 2, sw) drawRect(cx, cy - sz / 2, sz / 2, ne) drawRect(cx, cy, sz / 2, se) drawQuad(depth + 1, nw) drawQuad(depth + 1, ne) drawQuad(depth + 1, sw) drawQuad(depth + 1, se) case Empty(_, _, _) | Leaf(_, _, _, _) => // done } } drawQuad(0, model.quad) } gcan.drawImage(img, 0, 0, null) } // zoom on mouse rotation addMouseWheelListener(new MouseAdapter { override def mouseWheelMoved(e: MouseWheelEvent): Unit = { val rot = e.getWheelRotation val cx = model.screen.centerX val cy = model.screen.centerY val w = model.screen.width val h = model.screen.height val factor = { if (rot > 0) 0.52f else if (rot < 0) 0.48f else 0.5f } model.screen.minX = cx - w * factor model.screen.minY = cy - h * factor model.screen.maxX = cx + w * factor model.screen.maxY = cy + h * factor repaint() } }) // reset the last known mouse drag position on mouse press var xlast = Int.MinValue var ylast = Int.MinValue addMouseListener(new MouseAdapter { override def mousePressed(e: MouseEvent): Unit = { xlast = Int.MinValue ylast = Int.MinValue } }) // update the last known mouse drag position on mouse drag, // update the boundaries of the visible area addMouseMotionListener(new MouseMotionAdapter { override def mouseDragged(e: MouseEvent): Unit = { val xcurr = e.getX val ycurr = e.getY if (xlast != Int.MinValue) { val xd = xcurr - xlast val yd = ycurr - ylast val w = model.screen.width val h = model.screen.height val cx = model.screen.centerX - xd * w / 1000 val cy = model.screen.centerY - yd * h / 1000 model.screen.minX = cx - w / 2 model.screen.minY = cy - h / 2 model.screen.maxX = cx + w / 2 model.screen.maxY = cy + h / 2 println(model.screen) } xlast = xcurr ylast = ycurr repaint() } }) }