Forum: Ruby Midpoint Displacement (#197)

33117162fff8a9cf50544a604f60c045?d=identicon&s=25 Daniel X Moore (yahivin)
on 2009-03-20 16:34
(Received via mailing list)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz:

1.  Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have elapsed from the time this message was
sent.

2.  Support Ruby Quiz by submitting ideas and responses
as often as you can! Visit: <http://rubyquiz.strd6.com>

3.  Enjoy!

Suggestion:  A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby Talk follow the discussion.  Please reply to
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Midpoint Displacement (#197)

Haileo Rubyists,

The [midpoint displacement algorithm][1] is used to generate random
terrain in one dimension. The idea behind the algorithm is to start
with a line segment and adjust the middle by a random amount. This is
repeated for each new segment, with a decreasing random threshold. In
the end you will end up with an interesting looking ridge line.

This week's quiz is to develop a Ruby implementation of the algorithm.
The details of the implementation are up to you, but the output should
be an array containing the heights. This can be displayed visually for
extra credit. Golfers are welcome, but easily explainable solutions
are cool too.

[1]: http://gameprogrammer.com/fractal.html#midpoint
0df4a6c75caf1bd9b01d2dcbfb085ee4?d=identicon&s=25 Sandro Paganotti (Guest)
on 2009-03-22 20:00
(Received via mailing list)
Attachment: ishot-97.png (10 KB)
Ok, 48 hours has been passed by so here's my solution:

=== BEGIN
require 'enumerator'

segments = [0.0,0.0]; midpoints = []
max_randomness = 40
times = 5
app = {:width=>600, :height=>200}
boundary = 10

while times > 0  do
  segments.each_with_index do |b,i|
    next if i == 0; a = segments[i-1]
    midpoints << ((a + b / 2.0) + (rand(max_randomness) -
(max_randomness /
2.0)))
  end
  segments = segments.zip(midpoints).flatten.compact
  max_randomness -= 5; times -=1;
end

segment_width = (app[:width] - boundary*2) / segments.size.to_f


Shoes.app (:title=>'Dreaming mountains', :resizable=>false,
:width=>app[:width], :height=>app[:height]) do
  stroke white
  strokewidth 2
  background black

  segments.each_with_index do |b,i|
    next if i == 0; a = segments[i-1]
    line (f = (segment_width*(i-1) + boundary)), a + app[:height]/2.0,
(f +
segment_width), b + app[:height]/2.0
  end
end
=== END

I used Shoes to display the result of the algorithm, It was my first
attempt
with this
framework, so if someone can help me to improve the style pleas suggest
:D

Sandro

(attached a screenshot of the program's output)
26a81e5badb9e002ab9ed3542036e584?d=identicon&s=25 Michael Libby (Guest)
on 2009-03-24 17:24
(Received via mailing list)
On Fri, Mar 20, 2009 at 10:30 AM, Daniel Moore <yahivin@gmail.com>
wrote:
> ## Midpoint Displacement (#197)

Here's my version. Starts with a flat line and displaces the
midpoint(s) iteratively. Iteration method takes a block so that
calling code can decide what to do with the information. In this
implementation I dump iteration info to the console and create some
pictures.

The bulk of the code is specific to RMagick and does per frame GIFs as
an animated one of the whole sequence.

Sample image at: http://www.above-average-software.com/img/line_anim.gif

#!/usr/bin/ruby -w

require 'RMagick'

def get_displaced_midpoint(left, right, range)
 mid = (left + right) / 2.0
 adj = (rand * range) * (0 == rand(2) ? -1 : 1)
 return mid + adj
end

def displace_line(line, range)
 offset = 1
 0.upto(line.length - 2) do |x|
   line.insert(x + offset, get_displaced_midpoint(line[x + offset -
1], line[x + offset], range))
   offset += 1
 end
 return line
end

def draw_line(iter, line, range, width, height, anim_list)
 drawing = Magick::Draw.new
 x_factor = width / (line.count - 1)
 0.upto(line.count - 2) do |x|
   drawing.line x_factor * x, (height/2) - (height * line[x] / 2),
x_factor * (x + 1), (height / 2) - (height * line[x + 1] / 2)
 end
 drawing.stroke("transparent")
 drawing.fill("black")
 drawing.text(4, 16, "Iteration #{iter}, Range +/- #{range}")

 img = Magick::Image.new(width, height,
Magick::HatchFill.new('white', 'lightcyan2'))
 drawing.draw(img)
 img.write("line_img_#{'%03d' % iter}.gif")
 anim_list << img.copy
end

def print_line(iter, line, range)
 puts "iteration #{iter} (range=#{range}): #{line.inspect}"
end

def perform_iterations(line, iterations, range, roughness)
 1.upto(iterations) do |i|
   line = displace_line(line, range)
   yield(i, line, range)
   range *= 2 ** (0 - roughness)
 end
end

anim = Magick::ImageList.new

# TODO: parse command line for options
width = 512
height = 512
roughness = 1.0

# set iterations to be enough to get "full resolution" for image but no
higher
iterations = (Math.log(width) / Math.log(2)).to_i

perform_iterations([0.0, 0.0], iterations, 0.5, roughness){|iter, line,
range|
 draw_line(iter, line, range, width, height, anim)
 print_line(iter, line, range)
}

anim.delay = 100
anim.cur_image.delay = 300
anim.iterations = 0
anim.write("line_anim.gif")
09348009e57e24e10bbc08d925bf69ca?d=identicon&s=25 Matthias Reitinger (reima)
on 2009-03-24 23:07
Di Mo wrote:
> ## Midpoint Displacement (#197)

My solution utilizes Builder to print a SVG representation to stdout.
Note that the enum_for(:each_cons, 2) construct could be replaced by a
simple each_cons(2) when running Ruby 1.8.7 or 1.9.x.

  #!/usr/bin/env ruby

  require 'rubygems'
  require 'builder'
  require 'enumerator'

  # Options
  max_offset = 0.5
  roughness = 0.5
  iterations = 12
  width = height = 100
  style = "fill: black; stroke: none"

  # Midpoint displacement
  heights = [0.0, 0.0]
  iterations.times do
    heights = heights.enum_for(:each_cons, 2).map do |left, right|
      mid = (left + right) / 2
      mid += (rand * 2 - 1) * max_offset
      [left, mid]
    end.flatten << heights[-1]
    max_offset *= roughness
  end

  # SVG output
  builder = Builder::XmlMarkup.new(:target => STDOUT)
  builder.instruct!
  builder.svg(:version => "1.1", :width => width, :height => height) do
|svg|
    xs = (0...heights.size).map {|x| x.to_f / (heights.size - 1) *
width}
    ys = heights.map {|y| (1 - y) * height * 0.5}
    points = [0, height] + xs.zip(ys) + [width, height]
    svg.polyline(:style => style, :points => points.join(" "))
  end

-Matthias
33117162fff8a9cf50544a604f60c045?d=identicon&s=25 Daniel X Moore (yahivin)
on 2009-03-30 00:06
(Received via mailing list)
This week's quiz received several interesting solutions. Summary also
available at http://rubyquiz.strd6.com/quizzes/197/#summary

Sandro's solution uses the [Shoes][1] graphical toolkit. I hadn't
played around with Shoes before, but it is basically awesome. It is
very simple to get going and well worth exploring.

Here is the entire display logic for Sandro's App:

    Shoes.app(:title=>'Dreaming mountains', :resizable=>false,
:width=>app[:width], :height=>app[:height]) do
      stroke white
      strokewidth 2
      background black

      segments.each_with_index do |b,i|
        next if i == 0; a = segments[i-1]
        line (f = (segment_width*(i-1) + boundary)), a +
app[:height]/2.0, (f + segment_width), b + app[:height]/2.0
      end
    end

After setting up a few application parameters and creating the height
values it is super easy to display the segments, and makes a cool
looking screenshot:

![Dreaming Mountains][2]

Michael Libby provided a solution using RMagick that generates an
animated gif of the whole process as well as each step. This clearly
illustrates the algorithm in action; it's neat to see how it
progresses at each step.

![Midpoint Displacement Animation][3]

Michael uses some clever techniques to pack a thorough solution into a
small amount of code. The `perform_iterations` method accepts a block
which it uses to pass the current state of the simulation back, to
make it easy to capture the output on every step. Another cool
technique is using the width of the image to determine how many
iterations to run, with the idea being that once we get to sub-pixel
midpoints it is ok to stop.

    # set iterations to be enough to get "full resolution" for image
but no higher
    iterations = (Math.log(width) / Math.log(2)).to_i

Matthias's solution has some useful techniques as well, such as using
of the `:each_cons` enumerator. This yields consecutive elements in
the array, for example:

    >> [1, 2, 3].enum_for(:each_cons, 2).to_a
    => [[1, 2], [2, 3]]

This makes mapping inserting the midpoints a little easier, observe:

    # Midpoint displacement
    heights = [0.0, 0.0]
    iterations.times do
      heights = heights.enum_for(:each_cons, 2).map do |left, right|
        mid = (left + right) / 2
        mid += (rand * 2 - 1) * max_offset
        [left, mid]
      end.flatten << heights[-1]
      max_offset *= roughness
    end

The last height is appended to the end because it would otherwise be
lost and replaced by a midpoint.

Matthias' solutions output is in SVG format using `builder` to create
the xml.

![Matthias Mountains][4]

There are useful bits that can be learned from each solution. So
please check them all out on the mailing list. Additionally the output
strategies of Shoe's, RMagick, and SVG demonstrate what great options
we have in Ruby.

Thank you Sandro, Michael, and Matthias for your solutions this week!
Keep 'em coming!

[1]: http://shoooes.net/about/
[2]: http://rubyquiz.strd6.com/quizzes/197/dreaming_mountains.png
[3]: http://rubyquiz.strd6.com/quizzes/197/line_anim.gif
[4]: http://rubyquiz.strd6.com/quizzes/197/matthias_mountains.png

-- Daniel
http://rubyquiz.strd6.com
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.