jax-fractals 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in heightmap.gemspec
4
+ gemspec
@@ -0,0 +1,124 @@
1
+ # jax-fractals
2
+
3
+ Fractal textures for [Jax](http://blog.jaxgl.com/what-is-jax)!
4
+
5
+ [<img src="https://raw.github.com/sinisterchipmunk/jax-fractals/master/screenshots/hm.png" width="128" height="128">](https://github.com/sinisterchipmunk/jax-fractals/blob/master/screenshots/hm.png)
6
+ [<img src="https://raw.github.com/sinisterchipmunk/jax-fractals/master/screenshots/1.png" width="128" height="128">](https://github.com/sinisterchipmunk/jax-fractals/blob/master/screenshots/1.png)
7
+ [<img src="https://raw.github.com/sinisterchipmunk/jax-fractals/master/screenshots/3.png" width="128" height="128">](https://github.com/sinisterchipmunk/jax-fractals/blob/master/screenshots/3.png)
8
+ [<img src="https://raw.github.com/sinisterchipmunk/jax-fractals/master/screenshots/4.png" width="128" height="128">](https://github.com/sinisterchipmunk/jax-fractals/blob/master/screenshots/4.png)
9
+
10
+ ## Why?
11
+
12
+ Because I can! Also, fractals are oh-so-useful for terrain generation, cloud simulation, lava, and various other natural-looking effects.
13
+
14
+ ## Features
15
+
16
+ * Adds a command-line generator for creating static fractal images
17
+ * Customizable options include random seed, image width, image height, and smoothness factor
18
+ * Defaults to a grayscale image, but can generate fractals using any color for low and high values
19
+ * Fractals which have power-of-two dimensions can be cleanly tiled
20
+ * Fractals can be generated with an alpha channel, which is helpful for rendering clouds
21
+ * Can generate "islands" -- that is, there are no high points on the borders
22
+ * Adds a Heightmap model to Jax for processing fractal images into terrain
23
+ * Vertical scale can be set independently from width and depth scale
24
+ * Quickly calculates normals for the height map, for efficient lighting
25
+ * Returns interpolated height values for fractional coordinates
26
+ * Height maps whose width and depth are powers of two can be cleanly tiled for endless landscapes
27
+ * For Rails projects, adds a controller for generating fractal images on-the-fly and caching them as appropriate
28
+
29
+ ## Dependencies
30
+
31
+ This gem requires Jax and RMagick 2.
32
+
33
+ ## Installation
34
+
35
+ Add the following to your Gemfile:
36
+
37
+ gem 'jax-fractals'
38
+
39
+ Or, for the latest development version, the following:
40
+
41
+ gem 'jax-fractals', :git => "http://github.com/sinisterchipmunk/jax-fractals"
42
+
43
+ Then type:
44
+
45
+ bundle install
46
+
47
+ ## Usage
48
+
49
+ ### The Generator
50
+
51
+ The quickest way to get a fractal image into your app is to generate it from the command line:
52
+
53
+ rails generate fractal name-of-fractal
54
+
55
+ By default, it will have pixel dimensions 128x128, and will be generated with a smoothness factor of 2. You can customize all of these options:
56
+
57
+ rails generate fractal name-of-fractal --width=256 --height=256 --smoothness=1.25
58
+
59
+ A different fractal image will be generated each time the generator is run unless a random seed is specified:
60
+
61
+ rails generate fractal name-of-fractal --seed=100
62
+
63
+ You can also change the colors for the low and high values, which default to black and white, respectively. The following example will replace black with red, and white with blue:
64
+
65
+ rails generate fractal name-of-fractal --low-color=ff0000 --high-color=0000ff
66
+
67
+ Sometimes you need the fractal to include an alpha (transparency) channel, so that the lower values are more transparent and the higher ones are more opaque. Simple!
68
+
69
+ rails generate fractal name-of-fractal --alpha
70
+
71
+ Occasionally, you'll want to generate a fractal that is guaranteed to have intensity values equal to 0 along its borders. This is useful if you're generating a single cloud in the sky (as opposed to a tileable texture) or an island in the sea. To do this with the generator:
72
+
73
+ rails generate fractal name-of-fractal --island
74
+
75
+
76
+ ### The Controller
77
+
78
+ To mount the fractal controller, add the following to your routes.rb file:
79
+
80
+ mount Fractal::Engine => "/fractals"
81
+
82
+ Restart the Rails server, and you can generate a fractal by visiting its URL. There is a single required parameter, its ID, which is used as a random seed. You can also pass width, height, smoothness and color parameters.
83
+
84
+ Experiment with the following examples:
85
+
86
+ * [http://localhost:3000/fractals/1](http://localhost:3000/fractals/1)
87
+ * [http://localhost:3000/fractals/2](http://localhost:3000/fractals/2)
88
+ * [http://localhost:3000/fractals/2?island=1](http://localhost:3000/fractals/2?island=1)
89
+ * [http://localhost:3000/fractals/2?low_color=ff0000&high_color=0000ff](http://localhost:3000/fractals/2?low_color=ff0000&high_color=0000ff)
90
+ * [http://localhost:3000/fractals/2?low_color=ff0000&high_color=0000ff&alpha=true](http://localhost:3000/fractals/2?low_color=ff0000&high_color=0000ff&alpha=true)
91
+ * [http://localhost:3000/fractals/1?width=256&height=256](http://localhost:3000/fractals/1?width=256&height=256)
92
+ * [http://localhost:3000/fractals/1?smoothness=1.25](http://localhost:3000/fractals/1?smoothness=1.25)
93
+ * [http://localhost:3000/fractals/1?width=256&height=256&smoothness=1.25](http://localhost:3000/fractals/1?width=256&height=256&smoothness=1.25)
94
+
95
+ Once generated, fractals will be stored in the Rails cache, so that they only need to be generated one time. After generation, the cached copy will be returned instead.
96
+
97
+
98
+ ### The Heightmap
99
+
100
+ Height maps will work with any image, not just fractals, but they play so nicely together that I couldn't resist adding the Heightmap model to this library.
101
+
102
+ The easiest way to create a height map is to create a resource file. In your Rails or Jax project, create the file `app/assets/jax/resources/heightmaps/test.resource` and add the following information to it:
103
+
104
+ path: "/fractals/5"
105
+ xz_scale: 0.75
106
+ y_scale: 8.0
107
+
108
+ This will load the fractal dynamically from the Fractals controller (note: you have to change the path to reference a static image if you're not using Rails), scale its width and depth by 3/4, and then scale its height by 8 to produce a hilly (but not too mountainous!) terrain.
109
+
110
+ If you want to texture it (and who wouldn't?), create a material like you'd create any other Jax material:
111
+
112
+ $ jax g material ground texture
113
+
114
+ Then, set it in the resource file:
115
+
116
+ material: "ground"
117
+
118
+ #### Actually Using It
119
+
120
+ To add the "test" heightmap to the world, add it like you would any other Jax model instance. Do this in your Jax controller:
121
+
122
+ @world.addObject Heightmap.find "test"
123
+
124
+ Done!
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,160 @@
1
+ # Height map can be created in one of two ways. The most common is by referencing
2
+ # a height image by its path:
3
+ #
4
+ # hm = new HeightMap path: "/path/to/image.png"
5
+ #
6
+ # When loaded form an image, the height value for any given vertex is calculated
7
+ # with the following formula, clamping the value between 0 and 1:
8
+ #
9
+ # (red * 65536 + green * 256 + blue) / 16777215
10
+ #
11
+ # The height is then scaled by @y_scale, if given. Alpha values are ignored.
12
+ # You can ignore this formula entirely if the image is an 8bpp grayscale.
13
+ #
14
+ # You can also set the @xz_scale to control the horizontal dimensions of the
15
+ # height map.
16
+ #
17
+ #
18
+ # The second method of instantiation is to specify height values explicitly
19
+ # along with a width and depth. You can use nested arrays for this, for
20
+ # organization, but a single flat array is also supported:
21
+ #
22
+ # hm = new HeightMap
23
+ # width: 4
24
+ # depth: 2
25
+ # heights: [
26
+ # [ 0, 64, 128, 255 ],
27
+ # [ 255, 64, 128, 0 ]
28
+ # ]
29
+ #
30
+ # hm = new HeightMap
31
+ # width: 4
32
+ # depth: 2
33
+ # heights: [
34
+ # 0, 64, 128, 255,
35
+ # 255, 64, 128, 0
36
+ # ]
37
+ #
38
+ # Note that each height value must be a number between 0 and 255, and each
39
+ # height value corresponds to a separate vertex (so, no worrying about RGBA).
40
+ # The height values will be automatically scaled to a value between 0 and 1,
41
+ # and then scaled again according to @y_scale, as with loading from an image.
42
+ #
43
+ RGB_SCALE = 1.0 / 16777215
44
+ Jax.getGlobal()['Heightmap'] = Jax.Model.create
45
+ # Returns the height map value at the given X or Z offset. Values may be
46
+ # fractional. If the values are negative or if they exceed the width or
47
+ # depth of this height map, they will be wrapped. If they are fractional,
48
+ # then a suitable height value between the floor and ceiling of the values
49
+ # will be calculated.
50
+ #
51
+ # Examples:
52
+ # hm = new Heightmap(width:2,depth:2,heights:[[0,1],[2,0]])
53
+ # hm.height( 0 , 0 ) #=> 0
54
+ # hm.height( 1 , 0 ) #=> 1
55
+ # hm.height( 0 , 1 ) #=> 2
56
+ # hm.height(-1 , 0 ) #=> 1
57
+ # hm.height( 3 , 0 ) #=> 1
58
+ # hm.height( 0.5, 0 ) #=> 0.5
59
+ # hm.height( 0 , 0.75) #=> 1.5
60
+ #
61
+ height: (x, z) ->
62
+ if @heights == undefined then return 0 # heightmap hasn't loaded yet
63
+
64
+ x %= @width
65
+ z %= @depth
66
+ x = @width + x if x < 0
67
+ z = @depth + z if z < 0
68
+
69
+ [fracX, fracZ] = [x % 1, z % 1]
70
+ x = Math.floor x if fracX
71
+ z = Math.floor z if fracZ
72
+
73
+ p = (x+z*@width)*4
74
+ # read RGB, ignoring alpha
75
+ y = (@heights[p] * 65536 + @heights[p+1] * 256 + @heights[p+2]) * RGB_SCALE * @y_scale
76
+ return y unless fracX || fracZ
77
+
78
+ [h, h2, h3, h4] = [y, @height(x+1, z)-y, @height(x, z+1)-y, @height(x+1,z+1)-y]
79
+ y = h2 * fracX + h3 * fracZ
80
+ if fracX && fracZ
81
+ y += (h4 + h2 - h3) * 0.5 * fracX
82
+ y += (h4 + h3 - h2) * 0.5 * fracZ
83
+ y /= 3
84
+ h + y
85
+
86
+ after_initialize: ->
87
+ if @path
88
+ @img = new Image
89
+ @img.onload = =>
90
+ @loaded = true
91
+ c = document.createElement 'canvas'
92
+ [c.width, c.height] = [@img.width, @img.height]
93
+ c2d = c.getContext '2d'
94
+ c2d.drawImage @img, 0, 0
95
+ img = c2d.getImageData 0, 0, @img.width, @img.height
96
+ @width = img.width
97
+ @depth = img.height
98
+ @heights = img.data
99
+ @mesh.rebuild()
100
+ @img.src = @path
101
+ else if @width && @depth && @heights
102
+ heights = @heights
103
+ @heights = []
104
+ # modify data for RGBA compatibility
105
+ #
106
+ # this is a tradeoff -- slightly more memory for
107
+ # much faster image loading, since images will
108
+ # be much bigger and much more common. Note the only
109
+ # alternative is to iterate through each image pixel
110
+ # and convert it to a straight value, much like below,
111
+ # but way slower since images are bigger and more common.
112
+ for h in heights
113
+ if h instanceof Array
114
+ @heights.push v, v, v, v for v in h
115
+ else
116
+ @heights.push h, h, h, h
117
+ @loaded = true
118
+ else
119
+ throw new Error "Heightmap requires a path!"
120
+
121
+ @mesh = new Jax.Mesh
122
+ default_material: @material
123
+ draw_mode: GL_TRIANGLE_STRIP
124
+ init: (vertices, colors, texcoords, normals, indices) =>
125
+ if @loaded
126
+ # since we know some details about the map, this is much
127
+ # faster than the default Jax normal calcs, which must find
128
+ # and then average all adjacent face normals for any given
129
+ # vertex.
130
+ [r, l, t, b, h, v, n] = [vec3.create(),vec3.create(),vec3.create(),
131
+ vec3.create(),vec3.create(),vec3.create(),
132
+ vec3.create()]
133
+ calculateNormal = (x,z) =>
134
+ [r[0],r[1],r[2]] = [(x+1)*@xz_scale, @height(x+1,z), z*@xz_scale]
135
+ [l[0],l[1],l[2]] = [(x-1)*@xz_scale, @height(x-1,z), z*@xz_scale]
136
+ [t[0],t[1],t[2]] = [x*@xz_scale, @height(x,z+1), (z+1)*@xz_scale]
137
+ [b[0],b[1],b[2]] = [x*@xz_scale, @height(x,z-1), (z-1)*@xz_scale]
138
+
139
+ vec3.normalize vec3.subtract r, l, h
140
+ vec3.normalize vec3.subtract t, b, v
141
+ vec3.normalize vec3.cross v, h, n
142
+ normals.push n[0], n[1], n[2]
143
+
144
+ for x in [0..(@width-2)] by 2
145
+ index_base = vertices.length / 3
146
+ for z in [0..@depth]
147
+ indices.push index_base+z*3+1, index_base+z*3
148
+ vertices.push x *@xz_scale, @height(x , z), z*@xz_scale
149
+ vertices.push (x+1)*@xz_scale, @height(x+1, z), z*@xz_scale
150
+ vertices.push (x+2)*@xz_scale, @height(x+2, z), z*@xz_scale
151
+ texcoords.push x / @width, z / @depth
152
+ texcoords.push (x+1) / @width, z / @depth
153
+ texcoords.push (x+2) / @width, z / @depth
154
+ calculateNormal x, z
155
+ calculateNormal x+1, z
156
+ calculateNormal x+2, z
157
+
158
+ # reverse direction of z every other x, so triangle strip renders properly
159
+ for z in [(@depth)..0]
160
+ indices.push index_base+z*3+1, index_base+z*3+2
@@ -0,0 +1,4 @@
1
+ # default attribute values
2
+ # (these will apply to all heightmaps)
3
+ xz_scale: 1
4
+ y_scale: 1
@@ -0,0 +1,95 @@
1
+ require_dependency 'fractal'
2
+
3
+ class Fractal::FractalsController < ApplicationController
4
+ before_filter :set_default_parameters
5
+ before_filter :validate_dimensions
6
+
7
+ # Fields:
8
+ #
9
+ # id - the random seed for this fractal. Required.
10
+ # width - the width of this fractal. Default: 128
11
+ # height - the height of this fractal. Default: 128
12
+ # smoothness - the smoothness of this fractal. Lower values produce more jagged / turbulent
13
+ # results, higher values produce smoother results. Default: 2
14
+ # high_color - the hex color code to use for high intensity values. Default: "ffffff"
15
+ # low_color - the hex color code to use for low intensity values. Default: "000000"
16
+ # alpha - if true, an alpha channel will be added. Lower intensity values will be more
17
+ # transparent. Default: false
18
+ # island - if true, an "island" fractal will be generated, such that its borders are
19
+ # guaranteed to have intensity values equal to 0. Default: false
20
+ #
21
+ def show
22
+ cache_key = File.join(params[:id], params[:width].to_s, params[:height].to_s,
23
+ params[:smoothness].to_s, params[:alpha].to_s,
24
+ params[:high_color].to_s, params[:low_color].to_s,
25
+ params[:island].to_s)
26
+
27
+ unless data = Rails.cache.read(cache_key)
28
+ # The proc ensures that the image leaves scope prior to garbage collection,
29
+ # thus ensuring that it will actually be collected. This is all to prevent
30
+ # a memory leak, detailed here:
31
+ # http://rubyforge.org/forum/forum.php?thread_id=1374&forum_id=1618
32
+ proc {
33
+ image = generate_image
34
+ data = image.to_blob { self.format = 'PNG' }
35
+ image.destroy!
36
+ }.call
37
+ GC.start
38
+
39
+ Rails.cache.write cache_key, data
40
+ end
41
+
42
+ send_data data, :filename => "#{params[:id]}.png", :type => "image/png",
43
+ :disposition => 'inline'
44
+ end
45
+
46
+ protected
47
+ def generate_image
48
+ Fractal::Generator.image(params.merge(:seed => params[:id].to_i))
49
+ end
50
+
51
+ def set_default_parameters
52
+ params[:width] = params[:width].blank? ? default_dimensions[:width] : params[:width].to_i
53
+ params[:height] = params[:height].blank? ? default_dimensions[:height] : params[:height].to_i
54
+ params[:smoothness] = params[:smoothness].blank? ? 2 : params[:smoothness].to_f
55
+ params[:smoothness] = 2 if params[:smoothness] <= 0
56
+ params[:alpha] = false unless params.key?(:alpha)
57
+ params[:island] = false unless params.key?(:island)
58
+ end
59
+
60
+ def default_dimensions
61
+ { :width => 128, :height => 128 }
62
+ end
63
+
64
+ def validate_dimensions
65
+ if (requested_width = params[:width]) > Fractal.max_size
66
+ params[:width] = Fractal.max_size
67
+ log = true
68
+ end
69
+ if (requested_height = params[:height]) > Fractal.max_size
70
+ params[:height] = Fractal.max_size
71
+ log = true
72
+ end
73
+ if log
74
+ logger.warn <<-end_warning
75
+ Requested dimensions #{requested_width}x#{requested_height} exceed maximum size
76
+ #{Fractal.max_size}x#{Fractal.max_size}. If you need a larger fractal,
77
+ create a file called config/initializers/fractals.rb and set
78
+
79
+ Fractal.max_size = [some acceptable number]
80
+
81
+ Or, set
82
+
83
+ Fractal.max_size = nil
84
+
85
+ ...to remove limits altogether (not recommended, as large fractals
86
+ can take a long time to generate!)
87
+
88
+ The safest way to use very large fractals is just to preprocess them:
89
+
90
+ rails g fractal
91
+
92
+ end_warning
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fractal/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "jax-fractals"
7
+ s.version = Fractal::VERSION
8
+ s.authors = ["Colin MacKenzie IV"]
9
+ s.email = ["sinisterchipmunk@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Adds a fractal generator, and corresponding controller for Jax projects}
12
+ s.description = %q{Adds a fractal generator, and corresponding controller for Jax projects. Also adds a Heightmap model for Jax.}
13
+
14
+ s.rubyforge_project = "jax-fractals"
15
+
16
+ s.add_dependency 'jax', '~> 2.0.6'
17
+ s.add_dependency 'rmagick', '~> 2.13.1'
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
@@ -0,0 +1,23 @@
1
+ class Fixnum
2
+ def pot?
3
+ (self & (self - 1)) == 0
4
+ end
5
+ end
6
+
7
+ module Math
8
+ module_function
9
+
10
+ def max(a, b)
11
+ a > b ? a : b
12
+ end
13
+
14
+ def min(a, b)
15
+ a > b ? b : a
16
+ end
17
+
18
+ def pot(x)
19
+ pot = 1
20
+ pot *= 2 until pot >= x
21
+ pot
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ begin
2
+ require 'rmagick'
3
+ rescue LoadError
4
+ raise "RMagick not found! Make sure `gem 'rmagick', '~> 2.13.1'` is in your Gemfile"
5
+ end
6
+
7
+ require File.expand_path('core_ext/math', File.dirname(__FILE__))
8
+
9
+ module Fractal
10
+ require File.expand_path("fractal/engine", File.dirname(__FILE__))
11
+
12
+ autoload :Map, File.expand_path("fractal/map", File.dirname(__FILE__))
13
+ autoload :Generator, File.expand_path("fractal/generator", File.dirname(__FILE__))
14
+ autoload :Version, File.expand_path("fractal/version", File.dirname(__FILE__))
15
+ autoload :IslandGenerator, File.expand_path('fractal/island_generator', File.dirname(__FILE__))
16
+
17
+ class << self
18
+ attr_accessor :max_size
19
+ end
20
+
21
+ self.max_size = 1024
22
+ end
@@ -0,0 +1,10 @@
1
+ require 'rails'
2
+
3
+ class Fractal::Engine < Rails::Engine
4
+ engine_name "fractals"
5
+ isolate_namespace Fractal
6
+
7
+ routes do
8
+ match "/:id", :controller => "fractals", :action => "show"
9
+ end
10
+ end
@@ -0,0 +1,146 @@
1
+ class Fractal::Generator
2
+ class << self
3
+ def image(options = {})
4
+ options.reverse_merge! default_image_options
5
+ klass = options[:island] ? Fractal::IslandGenerator : self
6
+
7
+ fractal = klass.new options
8
+
9
+ if options[:alpha]
10
+ image = Magick::Image.new(fractal.width, fractal.height) { self.background_color = 'transparent' }
11
+ image.import_pixels 0, 0, fractal.width, fractal.height, 'IA', fractal.bytes.collect { |b| [b,b] }.flatten.pack('C*')
12
+ else
13
+ image = Magick::Image.new(fractal.width, fractal.height)
14
+ image.import_pixels 0, 0, fractal.width, fractal.height, 'I', fractal.bytes.pack('C*')
15
+ end
16
+
17
+ options[:low_color] = '000000' if options[:low_color].blank?
18
+ options[:high_color] = 'ffffff' if options[:high_color].blank?
19
+ image = image.level_colors("##{options[:low_color]}", "##{options[:high_color]}", true)
20
+ image
21
+ end
22
+
23
+ def default_image_options
24
+ { :alpha => false, :island => false, :high_color => 'ffffff', :low_color => '000000' }
25
+ end
26
+ end
27
+
28
+ INITIAL_RANGE = 400
29
+ include Math
30
+ attr_reader :map, :random, :smoothness, :seed
31
+
32
+ def width; map.width; end
33
+ def height; map.height; end
34
+ def bytes; map.bytes; end
35
+
36
+ def initialize(options = {})
37
+ options.reverse_merge! default_options
38
+ width, height = options[:width], options[:height]
39
+ @map = Fractal::Map.new(pot(max(width, height)) + 1)
40
+ @random = options[:seed] ? Random.new(options[:seed]) : Random.new
41
+ @seed = @random.seed
42
+ @smoothness = options[:smoothness] || 2
43
+ generate
44
+ @map.truncate(width, height)
45
+ end
46
+
47
+ def to_s
48
+ "".tap do |result|
49
+ for x in 0...width
50
+ for y in 0...height
51
+ result.concat map[x, y].to_s[0..6].rjust(7)
52
+ result.concat " "
53
+ end
54
+ result.concat "\n"
55
+ end
56
+ end
57
+ end
58
+
59
+ protected
60
+ def default_options
61
+ {
62
+ :width => 128,
63
+ :height => 128,
64
+ :smoothness => 2
65
+ }
66
+ end
67
+
68
+ # Seed initial values, then return [step, range]
69
+ def sow_seeds
70
+ map[ 0, 0] ||= 128
71
+ map[ 0, height-1] ||= 128
72
+ map[width-1, 0] ||= 128
73
+ map[width-1, height-1] ||= 128
74
+
75
+ [width - 1, INITIAL_RANGE]
76
+ end
77
+
78
+ def compute(x, y, points, range)
79
+ c = map[x, y] || 0
80
+ 4.times do |i|
81
+ if points[i][0] < 0 then points[i][0] += (width - 1)
82
+ elsif points[i][0] > width then points[i][0] -= (width - 1)
83
+ elsif points[i][1] < 0 then points[i][1] += (height - 1)
84
+ elsif points[i][1] > height then points[i][1] -= (height - 1)
85
+ end
86
+ c += map[points[i][0], points[i][1]] * 0.25
87
+ end
88
+
89
+ c += random.rand() * range - range / 2.0
90
+ if c < 0 then c = 0
91
+ elsif c > 255 then c = 255
92
+ end
93
+
94
+ c = c.to_i
95
+ map[x, y] = c
96
+ if x == 0 then map[width-1, y] = c
97
+ elsif x == width-1 then map[0, y] = c
98
+ elsif y == 0 then map[x, height-1] = c
99
+ elsif y == height-1 then map[x, 0] = c
100
+ end
101
+ end
102
+
103
+ private
104
+ def half(step)
105
+ step >> 1
106
+ end
107
+
108
+ def diamond(step, range)
109
+ halfstep = half step
110
+ (0...(width-1)).step step do |x|
111
+ (0...(height-1)).step step do |y|
112
+ sx = x + halfstep
113
+ sy = y + halfstep
114
+ points = [ [x, y], [x+step, y], [x, y+step], [x+step, y+step] ]
115
+ compute sx, sy, points, range
116
+ end
117
+ end
118
+ end
119
+
120
+ def square(step, range)
121
+ halfstep = half step
122
+ (0...(width-1)).step step do |x|
123
+ (0...(height-1)).step step do |y|
124
+ x1 = x + halfstep
125
+ y1 = y
126
+ x2 = x
127
+ y2 = y + halfstep
128
+ points1 = [ [x1 - halfstep, y1], [x1, y1 - halfstep], [x1 + halfstep, y1], [x1, y1 + halfstep] ]
129
+ points2 = [ [x2 - halfstep, y2], [x2, y2 - halfstep], [x2 + halfstep, y2], [x2, y2 + halfstep] ]
130
+ compute x1, y1, points1, range
131
+ compute x2, y2, points2, range
132
+ end
133
+ end
134
+ end
135
+
136
+ def generate
137
+ step, range = *sow_seeds
138
+
139
+ while step > 1
140
+ diamond step, range
141
+ square step, range
142
+ range /= smoothness
143
+ step >>= 1
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,32 @@
1
+ # Generates fractals which are guaranteed to have border pixels set to 0
2
+ class Fractal::IslandGenerator < Fractal::Generator
3
+ protected
4
+ def sow_seeds
5
+ step, range = *super
6
+ map[ 0, 0] = 0
7
+ map[ 0, height-1] = 0
8
+ map[width-1, 0] = 0
9
+ map[width-1, height-1] = 0
10
+
11
+ # we can multiply range by 2 to maintain brightness
12
+ # because we are effectively cutting area in half by
13
+ # setting borders to 0 -- we must do this if we want
14
+ # max brightness == 255, instead of 128
15
+ [step, range*2]
16
+ end
17
+
18
+ def compute(x, y, points, range)
19
+ # set borders to 0, but center to 128
20
+ if x == (width-1)/2 && y == (width-1/2)
21
+ map[x, y] = 128
22
+ elsif x == 0 || x == width-1
23
+ map[0, y] = 0
24
+ map[width-1, y] = 0
25
+ elsif y == 0 || y == height-1
26
+ map[x, 0] = 0
27
+ map[x, height-1] = 0
28
+ else
29
+ super
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ class Fractal::Map < Array
2
+ attr_accessor :width, :height
3
+ alias :row :[]
4
+
5
+ class Line < Array
6
+ def initialize(count)
7
+ super count, nil
8
+ end
9
+
10
+ def [](a)
11
+ if a.kind_of?(Numeric)
12
+ raise "Out of bounds: #{a} / #{length}" if a < 0 || a >= length
13
+ end
14
+ super
15
+ end
16
+
17
+ def []=(a, *)
18
+ raise "Out of bounds: #{a} / #{length}" if a < 0 || a >= length
19
+ super
20
+ end
21
+ end
22
+
23
+ def initialize(size)
24
+ @width = @height = size
25
+ super(size) { Fractal::Map::Line.new(size) }
26
+ end
27
+
28
+ def truncate(width, height)
29
+ pop while length > height
30
+ collect! { |line| line[0...width] }
31
+ @width, @height = width, height
32
+ end
33
+
34
+ def [](x, y)
35
+ raise "Out of bounds: #{y} / #{@height}" if y < 0 || y >= @height
36
+ super(y)[x]
37
+ end
38
+
39
+ def []=(x, y, value)
40
+ row(y)[x] = value
41
+ end
42
+
43
+ # Encodes the map as a grayscale bitmap, with a color depth of 8 bits per pixel.
44
+ #
45
+ # Returns the bitmap as an array of bytes.
46
+ def bytes
47
+ flatten.pack("C*").bytes.to_a
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ module Fractal
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
+ STRING = [MAJOR, MINOR, TINY].join('.')
7
+ end
8
+
9
+ VERSION = Version::STRING
10
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generates a fractal image in public/images/fractals.
3
+
4
+ Example:
5
+ rails generate fractal clouds
6
+
7
+ This will create:
8
+ public/images/fractals/clouds
@@ -0,0 +1,18 @@
1
+ require 'fractal'
2
+
3
+ class FractalGenerator < Rails::Generators::NamedBase
4
+ class_option :seed, :default => nil, :desc => "Random seed for the fractal", :type => :numeric
5
+ class_option :width, :default => 128, :desc => "Width of the fractal image in pixels", :type => :numeric
6
+ class_option :height, :default => 128, :desc => "Height of the fractal image in pixels", :type => :numeric
7
+ class_option :smoothness, :default => 2, :desc => "Smoothness factor (higher is smoother)", :type => :numeric
8
+ class_option :dest, :default => "app/assets", :desc => "Where to place generated fractal", :type => :string
9
+ class_option :high_color, :default => 'ffffff', :desc => "Color to use for high intensity values", :type => :string
10
+ class_option :low_color, :default => '000000', :desc => "Color to use for low intensity values", :type => :string
11
+ class_option :alpha, :default => false, :desc => "Whether to save transparency data", :type => :boolean
12
+ class_option :island, :default => false, :desc => "Whether the borders should be guaranteed to have 0 intensity", :type => :boolean
13
+
14
+ def generate_fractal
15
+ create_file File.join(options[:dest], 'images/fractals', "#{name}.png"),
16
+ Fractal::Generator.image(options).to_blob { self.format = 'PNG' }
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path('fractal', File.dirname(__FILE__))
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,67 @@
1
+ describe "Heightmap", ->
2
+ model = null
3
+
4
+ describe "while still loading", ->
5
+ beforeEach ->
6
+ model = new Heightmap(path: "/fractals/1")
7
+
8
+ it "should return height 0", ->
9
+ expect(model.heights(5, 5)).toEqual 0
10
+
11
+ describe "loaded from a path", ->
12
+ beforeEach ->
13
+ model = new Heightmap(path: "/fractals/1")
14
+
15
+ it "should load the model", ->
16
+ waitsFor -> model.loaded
17
+
18
+ describe "with RGB, not grayscale", ->
19
+ beforeEach ->
20
+ model = new Heightmap(width: 1, depth: 1, heights: [[1]], y_scale: 16777215)
21
+ # HACK no good way to do this, but really it's not meant to be done
22
+ model.heights[0] = 1 # R
23
+ model.heights[1] = 2 # G
24
+ model.heights[2] = 3 # B
25
+ model.heights[3] = 4 # A
26
+
27
+ it "should sample RGB and ignore A", ->
28
+ expected_height = 1 * 65536 + 2 * 256 + 3 # RGB, ignoring A
29
+ expect(model.height(0, 0)).toEqual expected_height
30
+
31
+ describe "defined in-line", ->
32
+ beforeEach ->
33
+ model = new Heightmap(width: 4, depth: 2, heights: [ [ 0, 4, 0, 0 ], [2, 3, 0, 0] ], y_scale: 255)
34
+
35
+ it "should return the expected height data", ->
36
+ expect(model.height(0, 0)).toEqual 0
37
+ expect(model.height(1, 0)).toEqual 4
38
+ expect(model.height(2, 0)).toEqual 0
39
+ expect(model.height(3, 0)).toEqual 0
40
+ expect(model.height(0, 1)).toEqual 2
41
+ expect(model.height(1, 1)).toEqual 3
42
+ expect(model.height(2, 1)).toEqual 0
43
+ expect(model.height(3, 1)).toEqual 0
44
+
45
+ it "should wrap height indices out of bounds", ->
46
+ # X
47
+ expect(model.height(-3, 0)).toEqual 4
48
+ expect(model.height(-7, 0)).toEqual 4
49
+ expect(model.height( 5, 0)).toEqual 4
50
+ expect(model.height( 9, 0)).toEqual 4
51
+ # Z
52
+ expect(model.height(0, -1)).toEqual 2
53
+ expect(model.height(0, -3)).toEqual 2
54
+ expect(model.height(0, 2)).toEqual 0
55
+ expect(model.height(0, 3)).toEqual 2
56
+ expect(model.height(0, 4)).toEqual 0
57
+
58
+ it "should compensate for fractional X,Z components", ->
59
+ expect(model.height(0.5, 0)).toEqual 2
60
+ expect(model.height(0, 0.5)).toEqual 1
61
+ expect(model.height(0.5, 0.5)).toEqual 1.5
62
+
63
+ # Proximity -- should return approximately the same as x+1/z+1
64
+ expect(model.height(0.99999999, 0)).toBeGreaterThan 4-Math.EPSILON
65
+ expect(model.height(0, 0.99999999)).toBeGreaterThan 2-Math.EPSILON
66
+ expect(model.height(0.99999999, 0.99999999)).toBeGreaterThan 3-Math.EPSILON
67
+
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fractal::Generator do
4
+ it "should truncate map by default" do
5
+ subject = Fractal::Generator.new(:width => 10, :height => 10)
6
+ subject.width.should == 10
7
+ subject.height.should == 10
8
+ subject.map.length.should == 10
9
+ subject.map.row(0).length.should == 10
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Fractal images" do
4
+ def subject(options = {})
5
+ Fractal::Generator.image(options.reverse_merge(:seed => 1)).to_blob { self.format = 'PNG' }
6
+ end
7
+
8
+ it "should generate default image" do
9
+ subject.should == fractal('1')
10
+ end
11
+
12
+ it "should generate with alpha" do
13
+ subject(:alpha => true).should == fractal('1?alpha=1')
14
+ end
15
+
16
+ it "should truncate size" do
17
+ subject(:width => 126, :height => 130).should == fractal('1?width=126&height=130')
18
+ end
19
+
20
+ it "should generate an island" do
21
+ subject(:island => true).should == fractal('1?island=1')
22
+ end
23
+
24
+ it "should generate more jagged fractals" do
25
+ subject(:smoothness => 1.2).should == fractal('1?smoothness=1.2')
26
+ end
27
+
28
+ it "should set high color" do
29
+ subject(:high_color => 'ff0000').should == fractal('1?high_color=ff0000')
30
+ end
31
+
32
+ it "should set low color" do
33
+ subject(:low_color => '0000ff').should == fractal('1?low_color=0000ff')
34
+ end
35
+
36
+ it "should set high and low color" do
37
+ subject(:high_color => 'ff0000', :low_color => '0000ff').should == fractal('1?low_color=0000ff&high_color=ff0000')
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fractal::Map do
4
+ subject { Fractal::Map.new(32) }
5
+
6
+ it "should have size 32x32" do
7
+ subject.width.should == 32
8
+ subject.height.should == 32
9
+ end
10
+
11
+ it "should have 32x32 elements" do
12
+ subject.flatten.length.should == 32*32
13
+ end
14
+
15
+ it "should restrict bounds to 32x32" do
16
+ proc { subject[32, 0] }.should raise_error
17
+ proc { subject[32, 0] = 1 }.should raise_error
18
+ proc { subject[0, 32] }.should raise_error
19
+ proc { subject[0, 32] = 1 }.should raise_error
20
+ end
21
+
22
+ it "should not allow assignment to first dimension" do
23
+ proc { subject[0] = [] }.should raise_error
24
+ end
25
+
26
+ it "should truncate size" do
27
+ subject.truncate 10, 15
28
+ subject.width.should == 10
29
+ subject.height.should == 15
30
+ subject.length.should == 15
31
+ subject.row(0).length.should == 10
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
2
+ require 'jax-fractals'
3
+
4
+ module Fixtures
5
+ def fractal(name)
6
+ File.read(File.expand_path(File.join("fixtures/fractals", name), File.dirname(__FILE__))).force_encoding('BINARY')
7
+ end
8
+ end
9
+
10
+ RSpec.configure do |c|
11
+ c.include Fixtures
12
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jax-fractals
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Colin MacKenzie IV
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-01-08 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: jax
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.6
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: rmagick
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 2.13.1
35
+ type: :runtime
36
+ version_requirements: *id002
37
+ description: Adds a fractal generator, and corresponding controller for Jax projects. Also adds a Heightmap model for Jax.
38
+ email:
39
+ - sinisterchipmunk@gmail.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ files:
47
+ - .gitignore
48
+ - Gemfile
49
+ - README.md
50
+ - Rakefile
51
+ - app/assets/jax/models/heightmap.js.coffee
52
+ - app/assets/jax/resources/heightmaps/default.resource
53
+ - app/controllers/fractal/fractals_controller.rb
54
+ - jax-fractals.gemspec
55
+ - lib/core_ext/math.rb
56
+ - lib/fractal.rb
57
+ - lib/fractal/engine.rb
58
+ - lib/fractal/generator.rb
59
+ - lib/fractal/island_generator.rb
60
+ - lib/fractal/map.rb
61
+ - lib/fractal/version.rb
62
+ - lib/generators/fractal/USAGE
63
+ - lib/generators/fractal/fractal_generator.rb
64
+ - lib/jax-fractals.rb
65
+ - screenshots/1.png
66
+ - screenshots/3.png
67
+ - screenshots/4.png
68
+ - screenshots/hm.png
69
+ - spec/fixtures/fractals/1
70
+ - spec/fixtures/fractals/1?alpha=1
71
+ - spec/fixtures/fractals/1?high_color=ff0000
72
+ - spec/fixtures/fractals/1?island=1
73
+ - spec/fixtures/fractals/1?low_color=0000ff
74
+ - spec/fixtures/fractals/1?low_color=0000ff&high_color=ff0000
75
+ - spec/fixtures/fractals/1?smoothness=1.2
76
+ - spec/fixtures/fractals/1?width=126&height=130
77
+ - spec/javascripts/jax/models/heightmap_spec.js.coffee
78
+ - spec/lib/fractal/generator_spec.rb
79
+ - spec/lib/fractal/images_spec.rb
80
+ - spec/lib/fractal/map_spec.rb
81
+ - spec/spec_helper.rb
82
+ homepage: ""
83
+ licenses: []
84
+
85
+ post_install_message:
86
+ rdoc_options: []
87
+
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: "0"
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: "0"
102
+ requirements: []
103
+
104
+ rubyforge_project: jax-fractals
105
+ rubygems_version: 1.8.10
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Adds a fractal generator, and corresponding controller for Jax projects
109
+ test_files:
110
+ - spec/fixtures/fractals/1
111
+ - spec/fixtures/fractals/1?alpha=1
112
+ - spec/fixtures/fractals/1?high_color=ff0000
113
+ - spec/fixtures/fractals/1?island=1
114
+ - spec/fixtures/fractals/1?low_color=0000ff
115
+ - spec/fixtures/fractals/1?low_color=0000ff&high_color=ff0000
116
+ - spec/fixtures/fractals/1?smoothness=1.2
117
+ - spec/fixtures/fractals/1?width=126&height=130
118
+ - spec/javascripts/jax/models/heightmap_spec.js.coffee
119
+ - spec/lib/fractal/generator_spec.rb
120
+ - spec/lib/fractal/images_spec.rb
121
+ - spec/lib/fractal/map_spec.rb
122
+ - spec/spec_helper.rb