読者です 読者をやめる 読者になる 読者になる

Scalaで波のシュミレーション

プログラミング

scalaで波のシュミレーションを行った。

import scala.swing._
import scala.swing.event._
import java.awt.image.BufferedImage
import java.awt.{Color,Graphics,Graphics2D,BasicStroke}
import java.awt.geom._

class Field(val step:Int){
  val s=0.1f
  val u0=Array.ofDim[Float](step,step)
  val u1=Array.ofDim[Float](step,step)
  val u2=Array.ofDim[Float](step,step)
  val queue=new scala.collection.mutable.Queue[(Int,Int)]
  val maxAmplitude=500.0f
  for (i<- 0 until step) for (j <- 0 until step) queue.enqueue((i,j))
  def apply(x:Int,y:Int)=u0(x)(y)
  def init={
    for (i <- 0 until step){
      for (j <- 0 until step){
        u0(i)(j)=0
        u1(i)(j)=0
        u2(i)(j)=0
      }
    }
  }
  def newWaveSource(x:Int,y:Int,amplitude:Float=250.0f)= u0(x)(y)=amplitude
  def calculate={
    
    for (i <- 0 until step){
      for (j <- 0 until step){
        u2(i)(j)=u1(i)(j)
        u1(i)(j)=u0(i)(j)
      }
    }
    for (i <- 0 until step){
      u0(0)(i)=0
      u0(step-1)(i)=0
      u0(i)(0)=0
      u0(i)(step-1)=0
    }
    for (i <- 1 until step-1){
      for ( j <- 1 until step-1){
        val w=2*u1(i)(j)-u2(i)(j)+s*s*(u1(i-1)(j)-2*u1(i)(j)+u1(i+1)(j)+u1(i)(j-1)-2*u1(i)(j)+u1(i)(j+1))
        if(w!=u0(i)(j))queue.enqueue((i,j))
        u0(i)(j)=w
      }
    }
  }
}
class Canvas(val field:Field) extends Component{
  preferredSize = new Dimension(320,320)
  val imgBuf=new BufferedImage(320,320,BufferedImage.TYPE_INT_RGB)
  listenTo(mouse.clicks)
  reactions+={
    case MouseClicked(_,p,_,_,_)=>mouseClick(p.x,p.y)
  }
  private def mouseClick(cx:Int,cy:Int){
    val (squreSide,cx0,cy0,wid)=squareGeometry
    val (fx,fy)=((cx-cx0)/wid,(cy-cy0)/wid)
    List((0,0),(1,1),(1,0),(0,1)).foreach({case(px,py)=>
    field.newWaveSource(fx+px,fy+py)
    })
    
  }
  private def squareGeometry:(Int,Int,Int,Int)={
    val d = size
    val squareSide=d.height min d.width
    val x0 = (d.width-squareSide)/2
    val y0 = (d.height-squareSide)/2
    (squareSide,x0,y0,squareSide/field.step)
  }
  private def amplitudeWaveToColor(amp:Float,max:Float):Color={
    var p=amp/max
   
    if(p<0){
      if(p < -1)p = -1
      new Color((255*(-p)).toInt,(255*(1+p)).toInt,0)
      
    }else{
      if (p>1)p=1
      new Color(0,(255*p).toInt,(255*(1-p)).toInt)
     
    }
  }
  
  override def paintComponent(g:Graphics2D){
    val gb=imgBuf.getGraphics
    g.setRenderingHint(
        java.awt.RenderingHints.KEY_ANTIALIASING,
        java.awt.RenderingHints.VALUE_ANTIALIAS_ON)
    val (squareSide,x0,y0,wid)=squareGeometry
    while(!field.queue.isEmpty){
      val (x,y)=field.queue.dequeue()    
      gb.setColor(amplitudeWaveToColor(field(x,y),field.maxAmplitude))
      gb.fillRect(x0+x*wid,y0+y*wid,wid,wid)
    }
    g.drawImage(imgBuf,0,0,null)
  }
}
class Wave(val field :Field,val updateInterval:Int) extends MainFrame{
  private def restrictHeight(s:Component){
    s.maximumSize = new Dimension(Short.MaxValue,s.preferredSize.height)
  }
  title="Water surface"
  resizable=false
  val canvas=new Canvas(field)
  contents=canvas
  Timer(updateInterval){
    field.calculate
    canvas.repaint
  }
}
object Timer{
  def apply(interval:Int,repeats:Boolean=true)(op: => Unit){
    val timeOut=new javax.swing.AbstractAction(){
      def actionPerformed(e:java.awt.event.ActionEvent)=op
    }
    val t=new javax.swing.Timer(interval,timeOut)
    t.setRepeats(repeats)
    t.start()
  }
}
object Main{
  def main(args:Array[String]){
    val field=new Field(320)
    val ui=new Wave(field,100)
    ui.visible=true
  }
 }

値を更新するごとに全面を書き換えるのは無駄な気がしたので値が変わったもののみを書き換えることにした。これはQueueとBufferedImageを用いることで実装した。また、波の振幅を色を用いて表現する上手い式が思いつかなかったのでやっつけで作った。(amplitudeWaveToColor)

以下のサイトを参考にした。
Processingでシミュレーション~波動方程式 - Qiita

java - How to draw an image over another image? - Stack Overflow

Scala Documentation -- GUI Programming