Updates, various.
- Naka has been split to a separate file for better readability. - Naka::Noise2D added. Basically Perlin noise, but it adds a few APIs to sum stacks of octaves and such. Generation from seed is still missing even though it’s critical. - An example map is generated using several Naka::Noise2D. - Some assets added, and used to display an example map. Objects that are higher than one tile are displayed, but widths can’t overflow at the moment. - Maps are saved to disk. This is still minimal, however, and loading them from save files is not implemented at the moment. - Some Naka::Event were added at some point, although they’re still very much works in progress, and probably incomplete. - Maps are displayed in a very crude way. Transitions between terrain types are unsupported, for example.
BIN
assets/cactus.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
assets/dirt.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
assets/flowers.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
assets/grass.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/haunted.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/rocks.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/sand.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/stone.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
assets/tiles.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/tree.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/tree_pine.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/water.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
377
src/main.cr
@ -1,167 +1,211 @@
|
|||||||
|
|
||||||
require "sdl"
|
require "json"
|
||||||
require "sdl/image"
|
|
||||||
|
|
||||||
## Helper code comes here
|
require "./naka.cr"
|
||||||
|
|
||||||
class Naka
|
class Map
|
||||||
def self.init
|
@save_directory : String
|
||||||
SDL.init SDL::Init::VIDEO
|
getter altitude_maps : Naka::Noise2D
|
||||||
SDL::IMG.init SDL::IMG::Init::PNG
|
getter humidity_maps : Naka::Noise2D
|
||||||
|
getter evil_maps : Naka::Noise2D
|
||||||
|
getter flowers_map : Naka::Noise2D
|
||||||
|
def initialize(@save_directory)
|
||||||
|
Dir.mkdir_p @save_directory
|
||||||
|
|
||||||
at_exit { SDL.quit }
|
@altitude_maps = Naka::Noise2D.new.tap do |a|
|
||||||
end
|
a.add_octave(32, 1)
|
||||||
|
a.add_octave(16, 0.5)
|
||||||
|
a.add_octave(8, 0.25)
|
||||||
|
a.add_octave(4, 0.125)
|
||||||
|
a.add_octave(2, 1/16) # wtf, too smol
|
||||||
end
|
end
|
||||||
|
|
||||||
class Naka::Renderer < SDL::Renderer
|
@humidity_maps = Naka::Noise2D.new.tap do |a|
|
||||||
|
a.add_octave(64, 1)
|
||||||
|
a.add_octave(32, 0.5)
|
||||||
|
a.add_octave(16, 0.25)
|
||||||
|
a.add_octave(8, 0.20)
|
||||||
|
a.add_octave(4, 0.15)
|
||||||
|
a.add_octave(2, 0.10)
|
||||||
end
|
end
|
||||||
|
|
||||||
class Naka::Window
|
@evil_maps = Naka::Noise2D.new.tap do |a|
|
||||||
alias Flags = LibSDL::WindowFlags
|
a.add_octave(48, 0.6)
|
||||||
alias Position = LibSDL::WindowPosition
|
a.add_octave(32, 0.6)
|
||||||
|
a.add_octave(16, 0.3)
|
||||||
getter renderer
|
|
||||||
|
|
||||||
def initialize(title, width, height,
|
|
||||||
x : Position = Position::UNDEFINED,
|
|
||||||
y : Position = Position::UNDEFINED,
|
|
||||||
flags : Flags = Flags::SHOWN)
|
|
||||||
|
|
||||||
@window = SDL::Window.new(title, width, height, x, y, flags)
|
|
||||||
@renderer = Naka::Renderer.new @window
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def newImage(file_path) : SDL::Texture
|
@flowers_map = Naka::Noise2D.new(1, 1)
|
||||||
SDL::IMG.load file_path, @renderer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# FIXME: We’ll probably want options for scaling, rotations, and so on.
|
class Object
|
||||||
def draw(texture : SDL::Texture, x : Int32, y : Int32)
|
getter type : String
|
||||||
@renderer.copy texture,
|
def initialize(@type)
|
||||||
dstrect: SDL::Rect[x, y, texture.width, texture.height]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class Naka::Timer
|
def to_json(builder)
|
||||||
getter start
|
builder.object do
|
||||||
getter last_update
|
builder.field "type", @type
|
||||||
|
|
||||||
def initialize
|
|
||||||
@start = Time.now
|
|
||||||
@last_update = @start
|
|
||||||
end
|
|
||||||
|
|
||||||
def step
|
|
||||||
now = Time.now
|
|
||||||
|
|
||||||
dt = now.epoch_ms - @last_update.epoch_ms
|
|
||||||
|
|
||||||
@last_update = now
|
|
||||||
|
|
||||||
dt
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Naka::Event
|
|
||||||
class Quit
|
|
||||||
end
|
|
||||||
|
|
||||||
class Draw
|
|
||||||
getter window : Naka::Window
|
|
||||||
|
|
||||||
def initialize(@window)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Update
|
|
||||||
getter dt : Int64
|
|
||||||
|
|
||||||
def initialize(@dt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# FIXME: Split in KeyUp and KeyDown.
|
|
||||||
class KeyUp
|
|
||||||
getter key : LibSDL::Keycode
|
|
||||||
getter scan_code : LibSDL::Scancode
|
|
||||||
|
|
||||||
def initialize(@key, @scan_code)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
class KeyDown
|
|
||||||
getter key : LibSDL::Keycode
|
|
||||||
getter scan_code : LibSDL::Scancode
|
|
||||||
getter repeat : Bool
|
|
||||||
|
|
||||||
def initialize(@key, @scan_code, @repeat)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# FIXME: THIS MAIN LOOP IS **NOT** READY FOR PRODUCTION
|
|
||||||
# Update events are still missing, and both Draw and Update should
|
|
||||||
# be time-based.
|
|
||||||
# Why having a `window` parameter instead of making a Window#loop method?
|
|
||||||
# Because we may want to have a `windows` parameter in the future, although
|
|
||||||
# unlikely. But still possible.
|
|
||||||
def self.loop(window, &block)
|
|
||||||
max_fps = 60
|
|
||||||
max_dt = 1000. / 60
|
|
||||||
|
|
||||||
timer = Naka::Timer.new
|
|
||||||
|
|
||||||
renderer = window.renderer
|
|
||||||
|
|
||||||
::loop do
|
|
||||||
exit_requested = false
|
|
||||||
|
|
||||||
while event = SDL::Event.poll
|
|
||||||
r_value = yield case event
|
|
||||||
when SDL::Event::Quit
|
|
||||||
Naka::Event::Quit.new
|
|
||||||
when SDL::Event::Keyboard
|
|
||||||
keysym = event.keysym
|
|
||||||
if event.type == LibSDL::EventType::KEYUP
|
|
||||||
Naka::Event::KeyUp.new keysym.sym, keysym.scancode
|
|
||||||
elsif event.type == LibSDL::EventType::KEYDOWN
|
|
||||||
Naka::Event::KeyDown.new keysym.sym, keysym.scancode, event.repeat != 0
|
|
||||||
end
|
|
||||||
else # FIXME: Most event types may need proper Naka:: classes.
|
|
||||||
event
|
|
||||||
end
|
|
||||||
|
|
||||||
if r_value == Naka::Event::Quit
|
|
||||||
exit_requested = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
break if exit_requested
|
|
||||||
|
|
||||||
dt = timer.step
|
|
||||||
|
|
||||||
r_value = yield Naka::Event::Update.new dt
|
|
||||||
|
|
||||||
break if r_value == Naka::Event::Quit
|
|
||||||
|
|
||||||
renderer.draw_color = SDL::Color[255, 255, 255, 255]
|
|
||||||
renderer.clear
|
|
||||||
|
|
||||||
yield Naka::Event::Draw.new window
|
|
||||||
|
|
||||||
renderer.present
|
|
||||||
|
|
||||||
sleep 0.001
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
## Actual application code comes here
|
class Tile < Array(Object)
|
||||||
|
def initialize(element)
|
||||||
|
initialize
|
||||||
|
|
||||||
|
self << element
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
CHUNK_SIZE = 32
|
||||||
|
class Chunk
|
||||||
|
property tiles : Array(Array(Tile))
|
||||||
|
getter x : Int32
|
||||||
|
getter y : Int32
|
||||||
|
|
||||||
|
def initialize(@x, @y, map)
|
||||||
|
p "Creating Map::Chunk"
|
||||||
|
|
||||||
|
@tiles = Array.new(CHUNK_SIZE) do |x|
|
||||||
|
x += @x * CHUNK_SIZE
|
||||||
|
|
||||||
|
Array.new(CHUNK_SIZE) do |y|
|
||||||
|
y += @y * CHUNK_SIZE
|
||||||
|
|
||||||
|
Tile.new(Object.new "stone").tap do |i|
|
||||||
|
altitude = map.altitude_maps.get(x, y) + 0.5
|
||||||
|
|
||||||
|
humidity = map.humidity_maps.get(x, y) + 0.5
|
||||||
|
|
||||||
|
evil = map.evil_maps.get(x, y)
|
||||||
|
|
||||||
|
if altitude < 0.25
|
||||||
|
i << Object.new "sand"
|
||||||
|
|
||||||
|
if altitude < 0.175
|
||||||
|
i << Object.new "water"
|
||||||
|
else
|
||||||
|
if humidity < 0.33
|
||||||
|
i << Object.new "sand"
|
||||||
|
else
|
||||||
|
i << Object.new "dirt"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elsif altitude < 0.75
|
||||||
|
i << Object.new "dirt"
|
||||||
|
|
||||||
|
if evil > 0.5
|
||||||
|
i << Object.new "haunted"
|
||||||
|
else
|
||||||
|
i << Object.new "grass"
|
||||||
|
end
|
||||||
|
|
||||||
|
if altitude > 0.35 && altitude < 0.65
|
||||||
|
if humidity > 0.4
|
||||||
|
if humidity > 0.8
|
||||||
|
i << Object.new "tree"
|
||||||
|
elsif humidity < 0.6
|
||||||
|
i << Object.new "pine"
|
||||||
|
else
|
||||||
|
if Random.rand(2) == 0
|
||||||
|
i << Object.new "tree"
|
||||||
|
else
|
||||||
|
i << Object.new "pine"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if Random.rand(10) == 0
|
||||||
|
i << Object.new "flowers"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elsif altitude > 0.85
|
||||||
|
i << Object.new "rocks"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_json
|
||||||
|
{
|
||||||
|
:x => @x,
|
||||||
|
:y => @y,
|
||||||
|
:tiles => @tiles
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(x, y)
|
||||||
|
@tiles[x][y]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@chunks = Hash(Int32, Hash(Int32, Chunk)).new
|
||||||
|
|
||||||
|
def get_chunk(x, y)
|
||||||
|
chunks_list = @chunks[x]?
|
||||||
|
if chunks_list.nil?
|
||||||
|
chunks_list = Hash(Int32, Chunk).new
|
||||||
|
@chunks[x] = chunks_list
|
||||||
|
end
|
||||||
|
|
||||||
|
chunk = chunks_list[y]?
|
||||||
|
if chunk.nil?
|
||||||
|
chunk = Chunk.new x, y, self
|
||||||
|
chunks_list[y] = chunk
|
||||||
|
|
||||||
|
save chunk
|
||||||
|
end
|
||||||
|
|
||||||
|
chunk
|
||||||
|
end
|
||||||
|
|
||||||
|
def save(chunk : Chunk)
|
||||||
|
File.write "#{@save_directory}/chunk-#{chunk.x}-#{chunk.y}.json", chunk.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(x, y)
|
||||||
|
get_chunk(x / CHUNK_SIZE, y / CHUNK_SIZE).get(x % CHUNK_SIZE, y % CHUNK_SIZE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ImagesLoader
|
||||||
|
getter grass : SDL::Texture
|
||||||
|
getter stone : SDL::Texture
|
||||||
|
getter dirt : SDL::Texture
|
||||||
|
getter tree : SDL::Texture
|
||||||
|
getter pine : SDL::Texture
|
||||||
|
getter flowers : SDL::Texture
|
||||||
|
getter sand : SDL::Texture
|
||||||
|
getter water : SDL::Texture
|
||||||
|
getter haunted : SDL::Texture
|
||||||
|
getter rocks : SDL::Texture
|
||||||
|
|
||||||
|
def initialize(window)
|
||||||
|
@grass = window.newImage "assets/grass.png"
|
||||||
|
@stone = window.newImage "assets/stone.png"
|
||||||
|
@dirt = window.newImage "assets/dirt.png"
|
||||||
|
@tree = window.newImage "assets/tree.png"
|
||||||
|
@flowers = window.newImage "assets/flowers.png"
|
||||||
|
@sand = window.newImage "assets/sand.png"
|
||||||
|
@water = window.newImage "assets/water.png"
|
||||||
|
@haunted = window.newImage "assets/haunted.png"
|
||||||
|
@pine = window.newImage "assets/tree_pine.png"
|
||||||
|
@rocks = window.newImage "assets/rocks.png"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Naka.init
|
Naka.init
|
||||||
|
|
||||||
window = Naka::Window.new "Test", 640, 480
|
window = Naka::Window.new "Test", 16*128, 16*128, flags: LibSDL::WindowFlags::RESIZABLE | LibSDL::WindowFlags::SHOWN | LibSDL::WindowFlags::OPENGL
|
||||||
|
|
||||||
naka = window.newImage "naka.png"
|
images = ImagesLoader.new window
|
||||||
|
|
||||||
|
connection_font = window.newFont "Connection.otf", 30.5
|
||||||
|
window.set_font connection_font
|
||||||
|
|
||||||
|
map = Map.new "map-test-01"
|
||||||
|
|
||||||
Naka::Event.loop window do |event|
|
Naka::Event.loop window do |event|
|
||||||
case event
|
case event
|
||||||
@ -169,7 +213,54 @@ Naka::Event.loop window do |event|
|
|||||||
next Naka::Event::Quit
|
next Naka::Event::Quit
|
||||||
when Naka::Event::Update
|
when Naka::Event::Update
|
||||||
when Naka::Event::Draw
|
when Naka::Event::Draw
|
||||||
event.window.draw naka, 20, 20
|
zoom_level = 1.0
|
||||||
|
|
||||||
|
128.times do |x|
|
||||||
|
128.times do |y|
|
||||||
|
tile = map.get x, y
|
||||||
|
|
||||||
|
# FIXME:
|
||||||
|
# - Please don’t draw more than one layer of ground…
|
||||||
|
# - Transitions. (eg. between dirt and grass)
|
||||||
|
tile.each do |object|
|
||||||
|
image = case object.type
|
||||||
|
when "grass"
|
||||||
|
images.grass
|
||||||
|
when "dirt"
|
||||||
|
images.dirt
|
||||||
|
when "stone"
|
||||||
|
images.stone
|
||||||
|
when "tree"
|
||||||
|
images.tree
|
||||||
|
when "pine"
|
||||||
|
images.pine
|
||||||
|
when "flowers"
|
||||||
|
images.flowers
|
||||||
|
when "water"
|
||||||
|
images.water
|
||||||
|
when "haunted"
|
||||||
|
images.haunted
|
||||||
|
when "sand"
|
||||||
|
images.sand
|
||||||
|
when "rocks"
|
||||||
|
images.rocks
|
||||||
|
else
|
||||||
|
images.stone
|
||||||
|
end
|
||||||
|
|
||||||
|
window.draw(
|
||||||
|
image,
|
||||||
|
x: (x * 16 * zoom_level).to_i,
|
||||||
|
y: (y * 16 * zoom_level - (image.height - 16) * zoom_level).to_i,
|
||||||
|
scale_x: zoom_level,
|
||||||
|
scale_y: zoom_level
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Urgh. This shit eats CPU. Fonts need goddamn fixing.
|
||||||
|
#window.print "The quick brown fox jumps over the lazy dog.", 50, 50
|
||||||
when Naka::Event::KeyUp, Naka::Event::KeyDown
|
when Naka::Event::KeyUp, Naka::Event::KeyDown
|
||||||
pp! event
|
pp! event
|
||||||
end
|
end
|
||||||
|
203
src/naka.cr
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
|
||||||
|
require "sdl"
|
||||||
|
require "sdl/image"
|
||||||
|
require "sdl/ttf"
|
||||||
|
|
||||||
|
class Naka
|
||||||
|
end
|
||||||
|
|
||||||
|
require "./noise.cr"
|
||||||
|
|
||||||
|
## Helper code comes here
|
||||||
|
|
||||||
|
class Naka
|
||||||
|
def self.init
|
||||||
|
SDL.init SDL::Init::VIDEO
|
||||||
|
SDL::IMG.init SDL::IMG::Init::PNG
|
||||||
|
SDL::TTF.init
|
||||||
|
|
||||||
|
at_exit {
|
||||||
|
SDL.quit
|
||||||
|
SDL::TTF.quit
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Naka::Renderer < SDL::Renderer
|
||||||
|
end
|
||||||
|
|
||||||
|
class Naka::Window
|
||||||
|
alias Flags = LibSDL::WindowFlags
|
||||||
|
alias Position = LibSDL::WindowPosition
|
||||||
|
|
||||||
|
getter renderer
|
||||||
|
|
||||||
|
@current_font : SDL::TTF::Font? = nil
|
||||||
|
|
||||||
|
def initialize(title, width, height,
|
||||||
|
x : Position = Position::UNDEFINED,
|
||||||
|
y : Position = Position::UNDEFINED,
|
||||||
|
flags : Flags = Flags::SHOWN)
|
||||||
|
|
||||||
|
@window = SDL::Window.new(title, width, height, x, y, flags)
|
||||||
|
@renderer = Naka::Renderer.new @window
|
||||||
|
|
||||||
|
@renderer.draw_blend_mode = SDL::BlendMode::BLEND
|
||||||
|
end
|
||||||
|
|
||||||
|
def newImage(file_path) : SDL::Texture
|
||||||
|
SDL::IMG.load(file_path, @renderer).tap do |texture|
|
||||||
|
texture.blend_mode = SDL::BlendMode::BLEND
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def newFont(file_path, font_size = 12) : SDL::TTF::Font
|
||||||
|
SDL::TTF::Font.new file_path, font_size
|
||||||
|
end
|
||||||
|
|
||||||
|
# FIXME: We’ll probably want options for scaling, rotations, and so on.
|
||||||
|
def draw(texture : SDL::Texture, x : Int32, y : Int32, scale_x = 1, scale_y = 1)
|
||||||
|
@renderer.copy texture,
|
||||||
|
dstrect: SDL::Rect[x, y, (texture.width * scale_x).to_i, (texture.height * scale_y).to_i]
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw(surface : SDL::Surface, x : Int32, y : Int32)
|
||||||
|
@renderer.copy surface,
|
||||||
|
dstrect: SDL::Rect[x, y, surface.width, surface.height]
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_font(@current_font)
|
||||||
|
end
|
||||||
|
|
||||||
|
# FIXME:
|
||||||
|
# - This only prints properly “solid” text. Shaded text (depixelated)
|
||||||
|
# would need some additional OpenGL manipulations.
|
||||||
|
# - This is very inefficient resources-wise. If we are willing to drop
|
||||||
|
# ligatures support (is it even supported in SDL::TTF?), we should
|
||||||
|
# build a textures atlas of all glyphs and use that to render text.
|
||||||
|
# - Alternatively, it may be worth it to let users generate a texture of
|
||||||
|
# some text and draw it, manually.
|
||||||
|
def print(text, x, y)
|
||||||
|
font = @current_font
|
||||||
|
|
||||||
|
return if font.nil?
|
||||||
|
|
||||||
|
surface = font.render_solid(text, SDL::Color[0, 0, 0, 255], @renderer.draw_color)
|
||||||
|
|
||||||
|
draw surface, x, y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Naka::Timer
|
||||||
|
getter start
|
||||||
|
getter last_update
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@start = Time.now
|
||||||
|
@last_update = @start
|
||||||
|
end
|
||||||
|
|
||||||
|
def step
|
||||||
|
now = Time.now
|
||||||
|
|
||||||
|
dt = now.epoch_ms - @last_update.epoch_ms
|
||||||
|
|
||||||
|
@last_update = now
|
||||||
|
|
||||||
|
dt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Naka::Event
|
||||||
|
class Quit
|
||||||
|
end
|
||||||
|
|
||||||
|
class Draw
|
||||||
|
getter window : Naka::Window
|
||||||
|
|
||||||
|
def initialize(@window)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Update
|
||||||
|
getter dt : Int64
|
||||||
|
|
||||||
|
def initialize(@dt)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# FIXME: Split in KeyUp and KeyDown.
|
||||||
|
class KeyUp
|
||||||
|
getter key : LibSDL::Keycode
|
||||||
|
getter scan_code : LibSDL::Scancode
|
||||||
|
|
||||||
|
def initialize(@key, @scan_code)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class KeyDown
|
||||||
|
getter key : LibSDL::Keycode
|
||||||
|
getter scan_code : LibSDL::Scancode
|
||||||
|
getter repeat : Bool
|
||||||
|
|
||||||
|
def initialize(@key, @scan_code, @repeat)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# FIXME: THIS MAIN LOOP IS **NOT** READY FOR PRODUCTION
|
||||||
|
# Update events are still missing, and both Draw and Update should
|
||||||
|
# be time-based.
|
||||||
|
# Why having a `window` parameter instead of making a Window#loop method?
|
||||||
|
# Because we may want to have a `windows` parameter in the future, although
|
||||||
|
# unlikely. But still possible.
|
||||||
|
def self.loop(window, &block)
|
||||||
|
max_fps = 60
|
||||||
|
max_dt = 1000. / 60
|
||||||
|
|
||||||
|
timer = Naka::Timer.new
|
||||||
|
|
||||||
|
renderer = window.renderer
|
||||||
|
|
||||||
|
::loop do
|
||||||
|
exit_requested = false
|
||||||
|
|
||||||
|
while event = SDL::Event.poll
|
||||||
|
r_value = yield case event
|
||||||
|
when SDL::Event::Quit
|
||||||
|
Naka::Event::Quit.new
|
||||||
|
when SDL::Event::Keyboard
|
||||||
|
keysym = event.keysym
|
||||||
|
if event.type == LibSDL::EventType::KEYUP
|
||||||
|
Naka::Event::KeyUp.new keysym.sym, keysym.scancode
|
||||||
|
elsif event.type == LibSDL::EventType::KEYDOWN
|
||||||
|
Naka::Event::KeyDown.new keysym.sym, keysym.scancode, event.repeat != 0
|
||||||
|
end
|
||||||
|
else # FIXME: Most event types may need proper Naka:: classes.
|
||||||
|
event
|
||||||
|
end
|
||||||
|
|
||||||
|
if r_value == Naka::Event::Quit
|
||||||
|
exit_requested = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
break if exit_requested
|
||||||
|
|
||||||
|
dt = timer.step
|
||||||
|
|
||||||
|
r_value = yield Naka::Event::Update.new dt
|
||||||
|
|
||||||
|
break if r_value == Naka::Event::Quit
|
||||||
|
|
||||||
|
renderer.draw_color = SDL::Color[255, 255, 255, 255]
|
||||||
|
renderer.clear
|
||||||
|
|
||||||
|
yield Naka::Event::Draw.new window
|
||||||
|
|
||||||
|
renderer.present
|
||||||
|
|
||||||
|
sleep 0.001
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
105
src/noise.cr
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
|
||||||
|
# FIXME: We may want to generate noise of other numbers of dimensions.
|
||||||
|
class Naka::Noise2D
|
||||||
|
class Octave
|
||||||
|
record Vec2, x : Float64, y : Float64
|
||||||
|
|
||||||
|
private def lerp(a, b, v)
|
||||||
|
a * (1.0 - v) + b * v
|
||||||
|
end
|
||||||
|
|
||||||
|
private def smooth(v)
|
||||||
|
v * v * (3.0 - 2.0 * v)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def gradient(orig, grad, p)
|
||||||
|
sp = Vec2.new(p.x - orig.x, p.y - orig.y)
|
||||||
|
grad.x * sp.x + grad.y * sp.y
|
||||||
|
end
|
||||||
|
|
||||||
|
private def random_gradient
|
||||||
|
v = rand * Math::PI * 2.0
|
||||||
|
Vec2.new(Math.cos(v), Math.sin(v))
|
||||||
|
end
|
||||||
|
|
||||||
|
@frequency : Float64 | Int32
|
||||||
|
@amplitude : Float64 | Int32
|
||||||
|
|
||||||
|
def amplitude
|
||||||
|
@amplitude.to_f
|
||||||
|
end
|
||||||
|
def frequency
|
||||||
|
@frequency.to_f
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(@frequency, @amplitude)
|
||||||
|
@rgradients = StaticArray(Vec2, 256).new { random_gradient }
|
||||||
|
@permutations = StaticArray(Int32, 256).new { |i| i }
|
||||||
|
@permutations.shuffle!
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_gradient(x, y)
|
||||||
|
idx = @permutations[x & 255] + @permutations[y & 255]
|
||||||
|
@rgradients[idx & 255]
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_gradients(x, y)
|
||||||
|
x0f = x.floor
|
||||||
|
y0f = y.floor
|
||||||
|
x0 = x0f.to_i
|
||||||
|
y0 = y0f.to_i
|
||||||
|
x1 = x0 + 1
|
||||||
|
y1 = y0 + 1
|
||||||
|
|
||||||
|
{
|
||||||
|
{
|
||||||
|
get_gradient(x0, y0),
|
||||||
|
get_gradient(x1, y0),
|
||||||
|
get_gradient(x0, y1),
|
||||||
|
get_gradient(x1, y1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vec2.new(x0f + 0.0, y0f + 0.0),
|
||||||
|
Vec2.new(x0f + 1.0, y0f + 0.0),
|
||||||
|
Vec2.new(x0f + 0.0, y0f + 1.0),
|
||||||
|
Vec2.new(x0f + 1.0, y0f + 1.0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(x, y)
|
||||||
|
x = x.to_f / @frequency
|
||||||
|
y = y.to_f / @frequency
|
||||||
|
|
||||||
|
p = Vec2.new(x, y)
|
||||||
|
gradients, origins = get_gradients(x, y)
|
||||||
|
v0 = gradient(origins[0], gradients[0], p)
|
||||||
|
v1 = gradient(origins[1], gradients[1], p)
|
||||||
|
v2 = gradient(origins[2], gradients[2], p)
|
||||||
|
v3 = gradient(origins[3], gradients[3], p)
|
||||||
|
fx = smooth(x - origins[0].x)
|
||||||
|
vx0 = lerp(v0, v1, fx)
|
||||||
|
vx1 = lerp(v2, v3, fx)
|
||||||
|
fy = smooth(y - origins[0].y)
|
||||||
|
|
||||||
|
lerp(vx0, vx1, fy) * @amplitude.to_f
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@storage = Array(Octave).new
|
||||||
|
def initialize
|
||||||
|
end
|
||||||
|
def initialize(a, f)
|
||||||
|
add_octave a, f
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_octave(a, f)
|
||||||
|
@storage << Octave.new a, f
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(x, y)
|
||||||
|
@storage.reduce 0 { |a, e| a + e.get(x, y) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|