Skip to content

Commit

Permalink
Merge 382025d into 61d576d
Browse files Browse the repository at this point in the history
  • Loading branch information
nhartland committed Jun 24, 2022
2 parents 61d576d + 382025d commit 0be9267
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 23 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/documentation.yaml
Expand Up @@ -7,8 +7,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: leafo/gh-actions-lua@v5
- uses: leafo/gh-actions-luarocks@v2
- uses: leafo/gh-actions-lua@v9
- uses: leafo/gh-actions-luarocks@v4
- name: Install ldoc
run: luarocks install ldoc
- name: Generate documentation
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/tests.yaml
Expand Up @@ -13,24 +13,24 @@ jobs:
lua-version: ['5.1', '5.2', '5.3', 'luajit']
steps:
- uses: actions/checkout@v1
- uses: leafo/gh-actions-lua@v5
- uses: leafo/gh-actions-lua@v9
with:
luaVersion: ${{ matrix.lua-version }}
- uses: leafo/gh-actions-luarocks@v2
- uses: leafo/gh-actions-luarocks@v4
- name: Run tests
run: luarocks test
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: leafo/gh-actions-lua@v5
- uses: leafo/gh-actions-lua@v9
with:
luaVersion: "luajit"
- uses: leafo/gh-actions-luarocks@v2
- uses: leafo/gh-actions-luarocks@v4
- name: Install rocks
run: |
luarocks install luacov
luarocks install luaunit
luarocks install luaunit 3.3
luarocks install luacov-reporter-lcov
- name: Run tests
run: |
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Expand Up @@ -85,3 +85,20 @@ Initial release
- Improved error messages on some subpattern methods
- Slightly improved example gallery generation
- Changed to using LuaRocks as test runner

# 0.6 (WIP)

## Features
- A raycasting tool for determining 'visible' areas of a pattern
from a source cell.

## Bugfix
- Fixed GitHub actions workflows by bumping `gh-action-lua` and
`gh-action-luarocks` versions.
- Fixed luaunit at v3.3

## Misc
- Adjust `pattern.sum` so that it can also take a single table of patterns as
an argument (pattern.sum({a,b,c}) instead of just pattern.sum(a,b,c)).
- Relaxed the assertions on the nature of distance measures in Mitchell
sampling / Poisson disc sampling.
1 change: 1 addition & 0 deletions config.ld
Expand Up @@ -16,4 +16,5 @@ file = {
'./forma/subpattern.lua',
'./forma/automata.lua',
'./forma/neighbourhood.lua'
'./forma/raycasting.lua'
}
16 changes: 16 additions & 0 deletions examples/raycasting.lua
@@ -0,0 +1,16 @@
-- Raycasting
-- This generates a messy random blocking pattern, selects a random point
-- within it, and casts rays from that point to identify a 'visible' area.

local subpattern = require('forma.subpattern')
local primitives = require('forma.primitives')
local raycasting = require("forma.raycasting")

-- Generate a domain and a messy 'blocking' pattern
local domain = primitives.square(80, 20)
local blocks = subpattern.random(domain, 100)
domain = domain - blocks

-- Cast rays in all direction from a random point in the domain
local traced = raycasting.cast_360(domain:rcell(), domain, 10)
subpattern.print_patterns(domain,{blocks, traced}, {'#', '+'})
1 change: 1 addition & 0 deletions forma/init.lua
Expand Up @@ -8,4 +8,5 @@ primitives = require('forma.primitives')
subpattern = require('forma.subpattern')
automata = require('forma.automata')
neighbourhood = require('forma.neighbourhood')
raycasting = require('forma.raycasting')

8 changes: 7 additions & 1 deletion forma/pattern.lua
Expand Up @@ -692,10 +692,16 @@ function pattern.intersection(...)
end

--- Generate a pattern consisting of the sum of existing patterns
-- @param ... patterns for summation
-- @param ... patterns for summation, can be either a table ({a,b}) or a list of arguments (a,b)
-- @return A pattern consisting of the sum of the input patterns
function pattern.sum(...)
local patterns = {...}
-- Handle a single, table argument of patterns ({a,b,c}) rather than (a,b,c)
if #patterns == 1 then
if type(patterns[1]) == 'table' then
patterns = patterns[1]
end
end
assert(#patterns > 1, "pattern.sum requires at least two patterns as arguments")
local total = pattern.clone(patterns[1])
for i=2, #patterns, 1 do
Expand Down
108 changes: 108 additions & 0 deletions forma/raycasting.lua
@@ -0,0 +1,108 @@
--- Ray tracing algorithms
-- Algorithms for identifying visible segments of a pattern from a single cell
-- This can be used for 'field of view' applications
-- Sources:
-- http:--www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html
-- http://www.roguebasin.com/index.php?title=LOS_using_strict_definition

local ray = {}

local cell = require('forma.cell')
local pattern = require('forma.pattern')

--- Casts a ray from a start to an end cell.
-- Returns {true/false} if the cast is successful/blocked.
-- Adapted from: http://www.roguebasin.com/index.php?title=LOS_using_strict_definition
-- @param v0 starting cell of ray
-- @param v1 end cell of ray
-- @param the domain in which we are casting
-- @return true or false depending on whether the ray was successfully cast
function ray.cast(v0, v1, domain)
assert(getmetatable(v0) == cell, "ray.cast requires a cell as the first argument")
assert(getmetatable(v1) == cell, "ray.cast requires a cell as the second argument")
assert(getmetatable(domain) == pattern, "ray.cast requires a pattern as the third argument")
-- Start or end cell was already blocked
if domain:has_cell(v0.x, v0.y) == false or domain:has_cell(v1.x, v1.y) == false then
return false
end
-- Initialise line walk
local dv = v1 - v0
local sx = (v0.x < v1.x) and 1 or -1
local sy = (v0.y < v1.y) and 1 or -1
-- Rasterise step by step
local nx = v0:clone()
local denom = cell.euclidean(v1, v0)
while (nx.x ~= v1.x or nx.y ~= v1.y) do
-- Ray is blocked
if domain:has_cell(nx.x, nx.y) == false then
return false
-- Ray is not blocked, calculate next step
elseif(math.abs(dv.y * (nx.x - v0.x + sx) - dv.x * (nx.y - v0.y)) / denom < 0.5) then
nx.x = nx.x + sx
elseif(math.abs(dv.y * (nx.x - v0.x) - dv.x * (nx.y - v0.y + sy)) / denom < 0.5) then
nx.y = nx.y + sy
else
nx.x = nx.x + sx
nx.y = nx.y + sy
end
end
-- Successfully traced a ray
return true
end

--- Casts rays from a start cell across an octant.
-- @param v0 starting cell of ray
-- @param the domain in which we are casting
-- @param the octant identifier (integer between 1 and 8)
-- @param radius the maximum length of the ray
-- @return the pattern illuminated by the ray casting
function ray.cast_octant(v0, domain, oct, ray_length)
assert(getmetatable(v0) == cell, "ray.cast_octant requires a cell as the first argument")
assert(getmetatable(domain) == pattern, "ray.cast_octant requires a pattern as the second argument")
assert(type(oct) == 'number', "ray.cast_octant requires a number as the third argument")
assert(type(ray_length) == 'number', "ray.cast_octant requires a number as the fourth argument")
local function transformOctant(r, c)
if oct == 1 then return r, -c end
if oct == 2 then return r, c end
if oct == 3 then return c, r end
if oct == 4 then return -c, r end
if oct == 5 then return -r, c end
if oct == 6 then return -r, -c end
if oct == 7 then return -c, -r end
if oct == 8 then return c, -r end
end
local lit_pattern = pattern.new()
for row=1,ray_length,1 do
for col=0,row,1 do
local tcol,trow = transformOctant(row,col)
local v1 = v0:clone() + cell.new(tcol, -trow)
if cell.euclidean2(v0, v1) < ray_length*ray_length then
ray_status = ray.cast(v0,v1,domain)
-- Successful ray casting, add to the illuminated pattern
if ray_status == true then
lit_pattern:insert(v1.x, v1.y)
end
end
end
end
return lit_pattern
end

--- Casts rays from a starting cell in all directions
-- @param v0 starting cell of ray
-- @param the domain in which we are casting
-- @param the maximum length of the ray
-- @return the pattern illuminated by the ray casting
function ray.cast_360(v, domain, ray_length)
assert(getmetatable(v) == cell, "ray.cast_360 requires a cell as the first argument")
assert(getmetatable(domain) == pattern, "ray.cast_360 requires a pattern as the second argument")
assert(type(ray_length) == 'number', "ray.cast_360 requires a number as the third argument")
local lit_pattern = pattern.new():insert(v.x, v.y)
for ioct=1,8,1 do
local np = ray.cast_octant(v, domain, ioct, ray_length)
lit_pattern = lit_pattern + np
end
return lit_pattern
end

return ray
6 changes: 2 additions & 4 deletions forma/subpattern.lua
Expand Up @@ -85,8 +85,7 @@ end
-- @return a Poisson-disc sample of `domain`
function subpattern.poisson_disc(ip, distance, radius, rng)
assert(getmetatable(ip) == pattern, "subpattern.poisson_disc requires a pattern as the first argument")
assert(distance(cell.new(5,5), cell.new(5,5)) == 0,
"subpattern.poisson_disc requires a distance measure as the second argument")
assert(type(distance) == 'function', "subpattern.poisson_disc requires a distance measure as an argument")
assert(type(radius) == "number", "subpattern.poisson_disc requires a number as the target radius")
if rng == nil then rng = math.random end
local sample = pattern.new()
Expand Down Expand Up @@ -118,8 +117,7 @@ function subpattern.mitchell_sample(ip, distance, n, k, rng)
"subpattern.mitchell_sample requires a pattern as the first argument")
assert(ip:size() >= n,
"subpattern.mitchell_sample requires a pattern with at least as many points as in the requested sample")
assert(distance(cell.new(5,5), cell.new(5,5)) == 0,
"subpattern.mitchell_sample requires a distance measure as the second argument")
assert(type(distance) == 'function', "subpattern.mitchell_sample requires a distance measure as an argument")
assert(type(n) == "number", "subpattern.mitchell_sample requires a target number of samples")
assert(type(k) == "number", "subpattern.mitchell_sample requires a target number of candidate tries")
if rng == nil then rng = math.random end
Expand Down
14 changes: 3 additions & 11 deletions rockspec/forma-scm-1.rockspec
Expand Up @@ -25,17 +25,9 @@ dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
forma = "forma/init.lua",
["forma.automata"] = "forma/automata.lua",
["forma.cell"] = "forma/cell.lua",
["forma.neighbourhood"] = "forma/neighbourhood.lua",
["forma.pattern"] = "forma/pattern.lua",
["forma.primitives"] = "forma/primitives.lua",
["forma.subpattern"] = "forma/subpattern.lua",
},
type = "none",
copy_directories = {
"forma",
"tests"
}
}
Expand All @@ -45,5 +37,5 @@ test = {
flags = {"-v"}
}
test_dependencies = {
"luaunit >=3.3"
"luaunit ==3.3"
}
2 changes: 2 additions & 0 deletions tests/pattern.lua
Expand Up @@ -79,11 +79,13 @@ function testPattern:testSum()
{0,0,0,0,0}})
local tp12 = primitives.square(5)
local sum = pattern.sum(tp1, tp2)
local sum_v2 = pattern.sum({tp1, tp2})
lu.assertEquals(tp1+tp2, tp12)
lu.assertEquals(tp1+tp2, sum)
lu.assertEquals(tp12, sum)
lu.assertNotEquals(tp1, sum)
lu.assertNotEquals(tp2, sum)
lu.assertEquals(sum, sum_v2)
end

-- Test insert methods
Expand Down
36 changes: 36 additions & 0 deletions tests/raycasting.lua
@@ -0,0 +1,36 @@
--- Tests of basic forma primitives
local lu = require('luaunit')
local pattern = require("forma.pattern")
local primitives = require("forma.primitives")
local subpattern = require("forma.subpattern")
local raycasting = require("forma.raycasting")

testRaycasting = {}

-- Test single ray ------------------------------------------------
function testRaycasting:testRay()
-- Test here is simmilar to line primitives
-- Draw a bunch of lines, check their properties
local domain = primitives.square(100)
for _=1, 100, 1 do
local success = raycasting.cast( domain:rcell(), domain:rcell(), domain )
-- Must succeed
lu.assertTrue(success)
end
end

-- Test 360 raycasting ------------------------------------------------
function testRaycasting:test360()
local domain = primitives.square(100)
for _=1, 100, 1 do
local start = domain:rcell()
local traced = raycasting.cast_360( start, domain, 5 )
-- Must have start cells
lu.assertTrue(traced:has_cell(start.x, start.y))
-- Most be contained within the domain
lu.assertEquals(pattern.intersection(domain, traced), traced)
-- Must consist of one contiguous area
local floodfill = subpattern.floodfill(traced, start)
lu.assertEquals(floodfill, traced)
end
end
1 change: 1 addition & 0 deletions tests/run.lua
Expand Up @@ -5,6 +5,7 @@ require('tests.primitives')
require('tests.neighbourhood')
require('tests.subpattern')
require('tests.automata')
require('tests.raycasting')

math.randomseed(0)

Expand Down

0 comments on commit 0be9267

Please sign in to comment.