All Downloads are FREE. Search and download functionalities are using the official Maven repository.

doodle.image.examples.DiffusionLimitedAggregation.scala Maven / Gradle / Ivy

There is a newer version: 0.27.0
Show newest version
/*
 * Copyright 2015 Creative Scala
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package doodle
package image
package examples

import cats.syntax.all.*
import doodle.core.*
import doodle.random.*
import doodle.syntax.all.*

object DiffusionLimitedAggregation {
  def brownianMotion(start: Point, drift: Vec): Random[Point] =
    jitter(start) map { pt =>
      pt + drift
    }

  def jitter(point: Point): Random[Point] = {
    val noise = Random.normal(0, 10.0)

    (noise, noise) mapN { (dx, dy) =>
      Point.cartesian(point.x + dx, point.y + dy)
    }
  }

  val start = Point.zero

  val seed: Random[(Point, Vec)] = Random.double.map { t =>
    val angle = t.turns
    val start = Point.polar(200, angle)
    val drift = Vec.polar(3, (angle + 0.5.turns).normalize)

    (start, drift)
  }

  val stuckStart =
    Random.always(
      List(
        Point.cartesian(0, 0),
        Point.cartesian(0, 50),
        Point.cartesian(0, -50)
      )
    )

  val stickRadius = 5.0

  def distance(pt1: Point, pt2: Point): Double =
    (pt1 - pt2).length

  def isStuck(point: Point, stuck: List[Point]): Boolean =
    stuck.exists(pt => distance(point, pt) < stickRadius)

  def walk(maxSteps: Int, stuck: List[Point]): Random[Option[Point]] = {
    def iter(
        step: Int,
        point: Random[Point],
        drift: Vec
    ): Random[Option[Point]] =
      step match {
        case 0 => Random.always(None)
        case _ =>
          point.flatMap { pt =>
            if isStuck(pt, stuck) then Random.always(Some(pt))
            else iter(step - 1, brownianMotion(pt, drift), drift)
          }
      }

    seed flatMap { case (start, drift) =>
      iter(maxSteps, Random.always(start), drift)
    }
  }

  def dla(
      nParticles: Int,
      maxSteps: Int,
      stuck: Random[List[Point]]
  ): Random[List[Point]] =
    nParticles match {
      case 0 => stuck
      case _ =>
        val nowStuck =
          stuck flatMap { s =>
            walk(maxSteps, s) map { opt =>
              opt match {
                case Some(pt) => pt :: s
                case None     => s
              }
            }
          }
        dla(nParticles - 1, maxSteps, nowStuck)
    }

  val smoke: Random[Image] = {
    val alpha = Random.normal(0.5, 0.1)
    val hue = Random.double.map { h =>
      (h * 0.1 + 0.6).turns
    }
    val saturation = Random.double.map(s => (s * 0.8))
    val lightness = Random.normal(0.4, 0.1)
    val color =
      (hue, saturation, lightness, alpha) mapN { (h, s, l, a) =>
        Color.hsla(h, s, l, a)
      }
    val c = Random.normal(1, 1) map (r => Image.circle(Math.abs(r)))

    (c, color).mapN { (circle, line) =>
      circle.strokeColor(line).noFill
    }
  }

  def makeImage(nParticles: Int, maxSteps: Int): Random[Image] =
    dla(nParticles, maxSteps, stuckStart) flatMap { pts =>
      pts.foldLeft(Random.always(Image.empty)) { (accum, pt) =>
        accum.flatMap { imgs =>
          smoke.map { img =>
            (img at pt.toVec) on imgs
          }
        }
      }
    }

  val image: Random[Image] = makeImage(1000, 1000)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy