Skip to content
Snippets Groups Projects
Commit b4e022fd authored by DarkGod's avatar DarkGod
Browse files

new WaveFunctionCollapse map generator

*****requires recompiling******
parent 6bb271be
No related branches found
No related tags found
No related merge requests found
Showing
with 2170 additions and 5 deletions
......@@ -37,7 +37,7 @@ project "TEngine"
if _OPTIONS.steam then
files { "../steamworks/luasteam.c", }
end
links { "physfs", "lua".._OPTIONS.lua, "fov", "luasocket", "luaprofiler", "lpeg", "tcodimport", "lxp", "expatstatic", "luamd5", "luazlib", "luabitop", "te4-bzip" }
links { "physfs", "lua".._OPTIONS.lua, "fov", "luasocket", "luaprofiler", "lpeg", "tcodimport", "lxp", "expatstatic", "luamd5", "luazlib", "luabitop", "te4-bzip", "te4-wfc" }
defines { "_DEFAULT_VIDEOMODE_FLAGS_='SDL_HWSURFACE|SDL_DOUBLEBUF'" }
defines { [[TENGINE_HOME_PATH='".t-engine"']], "TE4CORE_VERSION="..TE4CORE_VERSION }
buildoptions { "-O3" }
......@@ -48,7 +48,7 @@ project "TEngine"
if _OPTIONS.relpath == "32" then defines{"TE4_RELPATH32"} end
if _OPTIONS.relpath == "64" then defines{"TE4_RELPATH64"} end
links { "m" }
links { "m", "stdc++" }
if _OPTIONS.no_rwops_size then defines{"NO_RWOPS_SIZE"} end
......@@ -177,6 +177,7 @@ if _OPTIONS.lua == "default" then
kind "StaticLib"
language "C"
targetname "lua"
buildoptions{ "-O2" }
files { "../src/lua/*.c", }
elseif _OPTIONS.lua == "jit2" then
......@@ -358,6 +359,7 @@ elseif _OPTIONS.lua == "jit2" then
language "C"
targetname "lua"
links { "buildvm" }
buildoptions { "-O2", "-fomit-frame-pointer" }
files { "../src/luajit2/src/*.c", "../src/luajit2/src/*.s", "../src/luajit2/src/lj_vm.s", "../src/luajit2/src/lj_bcdef.h", "../src/luajit2/src/lj_ffdef.h", "../src/luajit2/src/lj_ffdef.h", "../src/luajit2/src/lj_libdef.h", "../src/luajit2/src/lj_recdef.h", "../src/luajit2/src/lj_folddef.h" }
excludes { "../src/luajit2/src/buildvm*.c", "../src/luajit2/src/luajit.c", "../src/luajit2/src/ljamalg.c" }
......@@ -422,6 +424,7 @@ project "luasocket"
kind "StaticLib"
language "C"
targetname "luasocket"
buildoptions { "-O2" }
configuration "not windows"
files {
......@@ -460,6 +463,7 @@ project "fov"
kind "StaticLib"
language "C"
targetname "fov"
buildoptions { "-O2" }
files { "../src/fov/*.c", }
......@@ -467,6 +471,7 @@ project "lpeg"
kind "StaticLib"
language "C"
targetname "lpeg"
buildoptions { "-O2" }
files { "../src/lpeg/*.c", }
......@@ -474,6 +479,7 @@ project "luaprofiler"
kind "StaticLib"
language "C"
targetname "luaprofiler"
buildoptions { "-O2" }
files { "../src/luaprofiler/*.c", }
......@@ -481,6 +487,7 @@ project "tcodimport"
kind "StaticLib"
language "C"
targetname "tcodimport"
buildoptions { "-O2" }
files { "../src/libtcod_import/*.c", }
......@@ -489,6 +496,7 @@ project "expatstatic"
language "C"
targetname "expatstatic"
defines{ "HAVE_MEMMOVE" }
buildoptions { "-O2" }
files { "../src/expat/*.c", }
......@@ -496,6 +504,7 @@ project "lxp"
kind "StaticLib"
language "C"
targetname "lxp"
buildoptions { "-O2" }
files { "../src/lxp/*.c", }
......@@ -503,6 +512,7 @@ project "luamd5"
kind "StaticLib"
language "C"
targetname "luamd5"
buildoptions { "-O2" }
files { "../src/luamd5/*.c", }
......@@ -510,6 +520,7 @@ project "luazlib"
kind "StaticLib"
language "C"
targetname "luazlib"
buildoptions { "-O2" }
files { "../src/lzlib/*.c", }
......@@ -517,6 +528,7 @@ project "luabitop"
kind "StaticLib"
language "C"
targetname "luabitop"
buildoptions { "-O2" }
files { "../src/luabitop/*.c", }
......@@ -524,9 +536,18 @@ project "te4-bzip"
kind "StaticLib"
language "C"
targetname "te4-bzip"
buildoptions { "-O2" }
files { "../src/bzip2/*.c", }
project "te4-wfc"
kind "StaticLib"
language "C++"
targetname "te4-wfc"
buildoptions { "-O3" }
files { "../src/wfc/*.cpp", }
if _OPTIONS['web-awesomium'] and not _OPTIONS.wincross then
project "te4-web"
kind "SharedLib"
......
-- TE4 - T-Engine 4
-- Copyright (C) 2009 - 2017 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.class"
local lom = require "lxp.lom"
local mime = require "mime"
--- Generate map-like data from samples using the WaveFunctionCollapse algorithm (in C++)
-- @classmod engine.WaveFunctionCollapse
module(..., package.seeall, class.make)
--- Run the algorithm
-- It will produce internal results which this class can then manipulate
-- If async is true in the parameters the generator will run in an asynchronous thread and you must call :waitCompute()
-- before using the results, this allows you to run multiple WFCs for later merging wihtout taking more time (if the user have enoguh CPUs obviously)
function _M:init(t)
assert(t.mode == "overlapping", "bad WaveFunctionCollapse mode")
assert(t.size, "WaveFunctionCollapse has no size")
self.data_w = t.size[1]
self.data_h = t.size[2]
if t.mode == "overlapping" then
if type(t.sample) == "string" then
t.sample = self:tmxLoad(t.sample)
end
self:run(t)
end
end
--- Used internally to load a sample from a tmx file
function _M:tmxLoad(file)
local f = fs.open(file, "r") local data = f:read(10485760) f:close()
local map = lom.parse(data)
local mapprops = {}
if map:findOne("properties") then mapprops = map:findOne("properties"):findAllAttrs("property", "name", "value") end
self.props = mapprops
local w, h = tonumber(map.attr.width), tonumber(map.attr.height)
if mapprops.map_data then
local params = self:loadLuaInEnv(g, nil, "return "..mapprops.map_data)
table.merge(self.data, params, true)
end
local gids = {}
for _, tileset in ipairs(map:findAll("tileset")) do
local firstgid = tonumber(tileset.attr.firstgid)
local has_tile = tileset:findOne("tile")
for _, tile in ipairs(tileset:findAll("tile")) do
local tileprops = {}
if tile:findOne("properties") then tileprops = tile:findOne("properties"):findAllAttrs("property", "name", "value") end
local gid = tonumber(tile.attr.id)
gids[firstgid + gid] = {tid=tileprops.id or ' '}
end
end
local data = {}
for y = 1, h do
data[y] = {}
for x = 1, w do data[y][x] = ' ' end
end
local function populate(x, y, gid)
if not gids[gid] then return end
local g = gids[gid]
data[y][x] = g.tid
end
for _, layer in ipairs(map:findAll("layer")) do
local mapdata = layer:findOne("data")
if mapdata.attr.encoding == "base64" then
local b64 = mime.unb64(mapdata[1]:trim())
local data
if mapdata.attr.compression == "zlib" then data = zlib.decompress(b64)
elseif not mapdata.attr.compression then data = b64
else error("tmx map compression unsupported: "..mapdata.attr.compression)
end
local gid, i = nil, 1
local x, y = 1, 1
while i <= #data do
gid, i = struct.unpack("<I4", data, i)
populate(x, y, gid)
x = x + 1
if x > w then x = 1 y = y + 1 end
end
elseif mapdata.attr.encoding == "csv" then
local data = mapdata[1]:gsub("[^,0-9]", ""):split(",")
local x, y = 1, 1
for i, gid in ipairs(data) do
gid = tonumber(gid)
populate(x, y, gid)
x = x + 1
if x > w then x = 1 y = y + 1 end
end
elseif not mapdata.attr.encoding then
local data = mapdata:findAll("tile")
local x, y = 1, 1
for i, tile in ipairs(data) do
local gid = tonumber(tile.attr.gid)
populate(x, y, gid)
x = x + 1
if x > w then x = 1 y = y + 1 end
end
end
end
for y = 1, h do data[y] = table.concat(data[y]) end
return data
end
--- Used internally to parse the results
function _M:parseResult(data)
if not data then return end
self.data = {}
for y = 1, #data do
local x = 1
self.data[y] = {}
for c in data[y]:gmatch('.') do
self.data[y][x] = c
x = x + 1
end
end
end
--- Called by the constructor to actaully start doing stuff
function _M:run(t)
print("[WaveFunctionCollapse] running with parameter table:")
table.print(t)
if not t.async then
local data = core.generator.wfc.overlapping(t.sample, t.size[1], t.size[2], t.n, t.symmetry, t.periodic_out, t.periodic_in, t.has_foundation)
self:parseResult(data)
return false
else
self.async_data = core.generator.wfc.asyncOverlapping(t.sample, t.size[1], t.size[2], t.n, t.symmetry, t.periodic_out, t.periodic_in, t.has_foundation)
return true
end
end
--- Do we have results, or did we fail?
function _M:hasResult()
return self.data and true or false
end
--- Wait for computation to finish if in async mode, if not it just returns immediately
function _M:waitCompute()
if not self.async_data then return end
local data = self.async_data:wait()
self.async_data = nil
self:parseResult(data)
return self:hasResult()
end
--- Wait for multiple WaveFunctionCollapse at once
-- Static
function _M:waitAll(...)
local all_have_data = true
for _, wfcasync in ipairs{...} do
wfcasync:waitCompute()
if not wfcasync:hasResult() then all_have_data = false end -- We cant break, we need to wait all the threads to not leave them dangling in the wind
end
return all_have_data
end
--- Return a list of groups of tiles representing each of the connected areas
function _M:findFloodfillGroups(wall)
if type(wall) == "table" then local first = wall[1] wall = table.reverse(wall) wall.__first = first
else wall = {[wall] = true, __first=wall} end
local fills = {}
local opens = {}
local list = {}
for i = 1, self.data_w do
opens[i] = {}
for j = 1, self.data_h do
if not wall[self.data[j][i]] then
opens[i][j] = #list+1
list[#list+1] = {x=i, y=j}
end
end
end
local nbg = 0
local function floodFill(x, y)
nbg=nbg+1
local q = {{x=x,y=y}}
local closed = {}
while #q > 0 do
local n = table.remove(q, 1)
if opens[n.x] and opens[n.x][n.y] then
-- self.data[n.y][n.x] = string.char(string.byte('0') + nbg) -- Debug to visualize floodfill groups
closed[#closed+1] = n
list[opens[n.x][n.y]] = nil
opens[n.x][n.y] = nil
q[#q+1] = {x=n.x-1, y=n.y}
q[#q+1] = {x=n.x, y=n.y+1}
q[#q+1] = {x=n.x+1, y=n.y}
q[#q+1] = {x=n.x, y=n.y-1}
q[#q+1] = {x=n.x+1, y=n.y-1}
q[#q+1] = {x=n.x+1, y=n.y+1}
q[#q+1] = {x=n.x-1, y=n.y-1}
q[#q+1] = {x=n.x-1, y=n.y+1}
end
end
return closed
end
-- Process all open spaces
local groups = {}
while next(list) do
local i, l = next(list)
local closed = floodFill(l.x, l.y)
groups[#groups+1] = {id=id, list=closed}
print("[WaveFunctionCollapse] Floodfill group", i, #closed)
end
return groups, wall
end
--- Find groups by floodfill and apply a custom function over them
-- It gives the groups in order of bigger to smaller
function _M:applyOnFloodfillGroups(wall, fct)
if not self.data then return end
local groups = self:findFloodfillGroups(wall)
table.sort(groups, function(a,b) return #a.list > #b.list end)
for _, group in ipairs(groups) do
fct(self.data_w, self.data_h, self.data, group)
end
end
--- Given a list of groups, eliminate them all
function _M:eliminateGroups(wall, groups)
print("[WaveFunctionCollapse] Eleminating groups", #groups)
for i = 1, #groups do
print("[WaveFunctionCollapse] Eleminating group "..i.." of", #groups[i].list)
for j = 1, #groups[i].list do
local jn = groups[i].list[j]
self.data[jn.y][jn.x] = wall
end
end
end
--- Simply destroy all connected groups except the biggest one
function _M:eliminateByFloodfill(wall)
local groups, wall = self:findFloodfillGroups(wall)
-- If nothing exists, regen
if #groups == 0 then
print("[WaveFunctionCollapse] Floodfill found nothing")
return 0
end
-- Sort to find the biggest group
table.sort(groups, function(a,b) return #a.list < #b.list end)
local g = table.remove(groups)
if g and #g.list > 0 then
print("[WaveFunctionCollapse] Ok floodfill with main group size", #g.list)
self:eliminateGroups(wall.__first, groups)
return #g.list
else
print("[WaveFunctionCollapse] Floodfill left nothing")
return 0
end
end
--- Get the results
-- @param is_array if true returns a table[][] of characters, if false a table[] of string lines
function _M:getResult(is_array)
if is_array then return self.data end
if not self.data then return nil end
local data = {}
for y = 1, self.data_h do data[y] = table.concat(self.data[y]) end
return data
end
--- Debug function to print the result to the log
function _M:printResult()
print("-------------")
print("------------- WaveFunctionCollapse result")
print("-----------[[")
for _, line in ipairs(self:getResult()) do
print(line)
end
print("]]-----------")
print("-------------")
print("-------------")
end
--- Merge and other WaveFunctionCollapse's data
function _M:merge(wfc, empty_char, char_order)
char_order = table.reverse(char_order or {})
empty_char = empty_char or ' '
if not wfc.data then return end
for i = 1, math.min(self.data_w, wfc.data_w) do
for j = 1, math.min(self.data_h, wfc.data_h) do
local c = wfc.data[j][i]
if c ~= empty_char then
local sc = self.data[j][i]
local sc_o = char_order[sc] or 0
local c_o = char_order[c] or 0
if c_o >= sc_o then
self.data[j][i] = wfc.data[j][i]
end
end
end
end
end
-- TE4 - T-Engine 4
-- Copyright (C) 2009 - 2017 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.class"
local Map = require "engine.Map"
local RoomsLoader = require "engine.generator.map.RoomsLoader"
require "engine.Generator"
--- @classmod engine.generator.map.FromCustom
module(..., package.seeall, class.inherit(engine.Generator, RoomsLoader))
function _M:init(zone, map, level, data)
engine.Generator.init(self, zone, map, level)
self.data = data
self.grid_list = zone.grid_list
self.spots = {}
self.mapsize = {self.map.w, self.map.h}
RoomsLoader.init(self, data)
end
--- Resolve a filename, from /data/ /data-addon/ or subfodler of zonedir
function _M:getFile(file, folder)
folder = folder or "maps"
if file:prefix("/") then return file end
-- Found in the zone itself ?
if file:find("^!") then return self.zone:getBaseName().."/"..folder.."/"..file:sub(2) end
local _, _, addon, rfile = file:find("^([^+]+)%+(.+)$")
if addon and rfile then
return "/data-"..addon.."/"..folder.."/"..rfile
end
return "/data/"..folder.."/"..file
end
function _M:regenerate()
self.force_regen = true
end
function _M:custom(lev, old_lev)
if self.data.custom then return self.data.custom(self, lev, old_lev) end
error("Generator FromCustom called without customization!")
end
function _M:generate(lev, old_lev)
print("Generating FromCustom")
self.force_regen = false
local data = self:custom(lev, old_lev)
if self.force_regen then return self:generate(lev, old_lev) end
for i = 0, self.map.w - 1 do
for j = 0, self.map.h - 1 do
self.map(i, j, Map.TERRAIN, self:resolve(data[j+1][i+1] or '#'))
end
end
return self:makeStairsInside(lev, old_lev, self.spots)
end
--- Create the stairs inside the level
function _M:makeStairsInside(lev, old_lev, spots)
-- Put down stairs
local dx, dy
if lev < self.zone.max_level or self.data.force_last_stair then
while true do
dx, dy = rng.range(1, self.map.w - 1), rng.range(1, self.map.h - 1)
if not self.map:checkEntity(dx, dy, Map.TERRAIN, "block_move") and not self.map.room_map[dx][dy].special then
self.map(dx, dy, Map.TERRAIN, self:resolve(">"))
self.map.room_map[dx][dy].special = "exit"
break
end
end
end
-- Put up stairs
local ux, uy
while true do
ux, uy = rng.range(1, self.map.w - 1), rng.range(1, self.map.h - 1)
if not self.map:checkEntity(ux, uy, Map.TERRAIN, "block_move") and not self.map.room_map[ux][uy].special then
self.map(ux, uy, Map.TERRAIN, self:resolve("<"))
self.map.room_map[ux][uy].special = "exit"
break
end
end
return ux, uy, dx, dy, spots
end
--[=[
local st = core.game.getTime()
local WaveFunctionCollapse = require "engine.WaveFunctionCollapse"
-- ADD OVERLAYING OF MULTIPLE WFC
local wfcwater = WaveFunctionCollapse.new{
mode="overlapping", async=true,
sample="/data/wfctest2.tmx",
size={50, 50},
n=3, symmetry=8, periodic_out=true, periodic_in=true, has_foundation=false
}
print("===done in", core.game.getTime()-st, "ms")
local wfc = WaveFunctionCollapse.new{
mode="overlapping", async=true,
sample="/data/wfctest.tmx",
size={50, 50},
n=3, symmetry=8, periodic_out=true, periodic_in=true, has_foundation=false
}
WaveFunctionCollapse:waitAll(wfc, wfcwater)
wfc:merge(wfcwater, ' ', {'.', '+', '#', '=', 'T'})
wfc:eliminateByFloodfill{'#', 'T'}
-- local lastspot, tp_id = nil, 0
-- local leftovergroups = {}
-- wfc:applyOnFloodfillGroups('#', function(w, h, data, group)
-- if #group.list < 30 or tp_id >= 10 then leftovergroups[#leftovergroups+1] = group return end
-- local grouplist = table.clone(group.list)
-- local spotfrom = rng.tableRemove(grouplist)
-- if lastspot then
-- local spotto = rng.tableRemove(grouplist)
-- data[spotto.y][spotto.x] = tostring(tp_id)
-- data[lastspot.y][lastspot.x] = tostring(tp_id)
-- tp_id = tp_id + 1
-- end
-- lastspot = spotfrom
-- end)
-- print("===eliminating leftover groups", #leftovergroups)
-- wfc:eliminateGroups('#', leftovergroups)
for _, line in ipairs(wfc:getResult()) do print(line) end
print("===done in", core.game.getTime()-st, "ms")
os.crash()
--]=]
-- ToME - Tales of Middle-Earth
-- Copyright (C) 2009 - 2017 Nicolas Casalini
--
......
......@@ -19,3 +19,4 @@
load("/data/general/grids/basic.lua")
load("/data/general/grids/forest.lua")
load("/data/general/grids/water.lua")
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" renderorder="right-down" width="16" height="16" tilewidth="32" tileheight="32" nextobjectid="31">
<properties>
<property name="status_all" value="{no_teleport=true}"/>
</properties>
<tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90">
<image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
<tile id="0">
<properties>
<property name="id" value="#"/>
</properties>
</tile>
<tile id="3">
<properties>
<property name="id" value="+"/>
</properties>
</tile>
<tile id="51">
<properties>
<property name="id" value="."/>
</properties>
</tile>
<tile id="54">
<properties>
<property name="id" value="*"/>
</properties>
</tile>
</tileset>
<tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144">
<image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
</tileset>
<tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36">
<image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
</tileset>
<layer name="Terrain" width="16" height="16">
<data encoding="base64" compression="zlib">
eJxjZGBgYARiFiiNjLGJYRM3QcPYxHCJk6KfWDNZaGQ/LrWEwoucsCInLMjRPxTkSU1/g818fPK40iq94hcA4S4l9A==
</data>
</layer>
</map>
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" renderorder="right-down" width="16" height="16" tilewidth="32" tileheight="32" nextobjectid="31">
<properties>
<property name="status_all" value="{no_teleport=true}"/>
</properties>
<tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90">
<image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
<tile id="0">
<properties>
<property name="id" value="#"/>
</properties>
</tile>
<tile id="3">
<properties>
<property name="id" value="+"/>
</properties>
</tile>
<tile id="51">
<properties>
<property name="id" value="."/>
</properties>
</tile>
<tile id="54">
<properties>
<property name="id" value="*"/>
</properties>
</tile>
</tileset>
<tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144">
<image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
<tile id="22">
<properties>
<property name="id" value=";"/>
</properties>
</tile>
<tile id="36">
<properties>
<property name="id" value="T"/>
</properties>
</tile>
<tile id="52">
<properties>
<property name="id" value="="/>
</properties>
</tile>
</tileset>
<tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36">
<image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
</tileset>
<layer name="Terrain" width="16" height="16">
<data encoding="base64" compression="zlib">
eJxjYBgFwwEUomFy9NZDMTb9+MxE1otPPzb3odtdj0dNPxQjy2PTj+4GZL3IZuByPyn6kf1Eqf5CBvz+JxSG+NIALnEAH5QrWQ==
</data>
</layer>
</map>
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" renderorder="right-down" width="16" height="16" tilewidth="32" tileheight="32" nextobjectid="31">
<properties>
<property name="status_all" value="{no_teleport=true}"/>
</properties>
<tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90">
<image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
<tile id="0">
<properties>
<property name="id" value="#"/>
</properties>
</tile>
<tile id="3">
<properties>
<property name="id" value="+"/>
</properties>
</tile>
<tile id="27">
<properties>
<property name="id" value="#"/>
</properties>
</tile>
<tile id="51">
<properties>
<property name="id" value="."/>
</properties>
</tile>
<tile id="54">
<properties>
<property name="id" value="*"/>
</properties>
</tile>
</tileset>
<tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144">
<image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
</tileset>
<tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36">
<image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
</tileset>
<layer name="Terrain" width="16" height="16">
<data encoding="base64" compression="zlib">
eJxjYKAOMMGCSdUrg4SJNQObXlLMQNbLiISRzSDGfpg+FiQ2sfZToh/ZDErigFZgNF0M7nQBAEC8Dvc=
</data>
</layer>
</map>
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" renderorder="right-down" width="16" height="16" tilewidth="32" tileheight="32" nextobjectid="31">
<properties>
<property name="status_all" value="{no_teleport=true}"/>
</properties>
<tileset firstgid="1" name="dg_dungeon32" tilewidth="32" tileheight="32" tilecount="90">
<image source="../../../../../../../tiled-maps/gfx/dg_dungeon32.gif" width="288" height="320"/>
<tile id="0">
<properties>
<property name="id" value="#"/>
</properties>
</tile>
<tile id="3">
<properties>
<property name="id" value="+"/>
</properties>
</tile>
<tile id="51">
<properties>
<property name="id" value="."/>
</properties>
</tile>
<tile id="54">
<properties>
<property name="id" value="*"/>
</properties>
</tile>
</tileset>
<tileset firstgid="91" name="tome-terrains" tilewidth="32" tileheight="32" tilecount="144">
<image source="../../../../../../../tiled-maps/gfx/tome-terrains.png" width="256" height="576"/>
</tileset>
<tileset firstgid="235" name="numbers" tilewidth="32" tileheight="32" tilecount="36">
<image source="../../../../../../../tiled-maps/gfx/numbers.png" width="192" height="192"/>
</tileset>
<layer name="Terrain" width="16" height="16">
<data encoding="base64" compression="zlib">
eJxjZGBgYMSBTfDIoatDxoTEidVPrFvw6UeXI0YNvfXj8yc5+onBtLafmHRDaboj1e/DXT85GABHnxWG
</data>
</layer>
</map>
......@@ -25,7 +25,7 @@ return {
decay = {300, 800},
actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
width = 50, height = 50,
all_remembered = true,
-- all_remembered = true,
all_lited = true,
no_level_connectivity = true,
......@@ -34,6 +34,39 @@ return {
generator = {
map = {
-- [[
class = "engine.generator.map.FromCustom",
['<'] = "UP", ['>'] = "DOWN",
['.'] = "FLOOR", ['+'] = "DOOR", ['#'] = "WALL",
[';'] = "GRASS", ['T'] = "TREE",
['='] = "DEEP_WATER",
custom = function(self, lev, old_lev)
local WaveFunctionCollapse = require "engine.WaveFunctionCollapse"
-- Water & trees layer
local wfcwater = WaveFunctionCollapse.new{
mode="overlapping", async=true,
sample=self:getFile("!wfctest2.tmx", "samples"),
size=self.mapsize,
n=3, symmetry=8, periodic_out=true, periodic_in=true, has_foundation=false
}
-- Base buildings
local wfc = WaveFunctionCollapse.new{
mode="overlapping", async=true,
sample=self:getFile("!wfctest4.tmx", "samples"),
size=self.mapsize,
n=3, symmetry=8, periodic_out=false, periodic_in=false, has_foundation=false
}
-- Wait for all generators to finish
if not WaveFunctionCollapse:waitAll(wfc, wfcwater) then return self:regenerate() end
-- Merge water in
wfc:merge(wfcwater, ' ', {'.', '+', '#', '=', ';', 'T'})
-- Elimitate the rest
if wfc:eliminateByFloodfill{'#', 'T'} < 800 then return self:regenerate() end
wfc:printResult()
return wfc:getResult(true)
end,
--]]
--[[
class = "engine.generator.map.Hexacle",
up = "FLOOR",
......@@ -75,7 +108,7 @@ return {
down = "JUNGLE_GRASS_DOWN6",
door = "JUNGLE_GRASS",
--]]
-- [[
--[[
class = "engine.generator.map.Roomer",
nb_rooms = 10,
edge_entrances = {4,6},
......@@ -106,7 +139,7 @@ return {
]]
},
post_process = function(level) -- testing level generation failure
if level.level >=3 then level.force_recreate = true end
-- if level.level >=3 then level.force_recreate = true end
end,
--[[
......
......@@ -36,6 +36,7 @@
#include "physfs.h"
#include "physfsrwops.h"
#include "core_lua.h"
#include "wfc/lua_wfc_external.h"
#include "getself.h"
#include "music.h"
#include "serial.h"
......@@ -1168,6 +1169,7 @@ void boot_lua(int state, bool rebooting, int argc, char *argv[])
luaopen_zlib(L);
luaopen_bit(L);
luaopen_wait(L);
luaopen_wfc(L);
physfs_reset_dir_allowed(L);
......
#pragma once
#include <vector>
template<typename T>
struct Array2D
{
public:
Array2D() : _width(0), _height(0) {}
Array2D(size_t w, size_t h, T value = {})
: _width(w), _height(h), _data(w * h, value) {}
const size_t index(size_t x, size_t y) const
{
// DCHECK_LT_F(x, _width);
// DCHECK_LT_F(y, _height);
return y * _width + x;
}
inline T& mut_ref(size_t x, size_t y) { return _data[index(x, y)]; }
inline const T& ref(size_t x, size_t y) const { return _data[index(x, y)]; }
inline T get(size_t x, size_t y) const { return _data[index(x, y)]; }
inline void set(size_t x, size_t y, const T& value) { _data[index(x, y)] = value; }
size_t width() const { return _width; }
size_t height() const { return _height; }
const T* data() const { return _data.data(); }
private:
size_t _width, _height;
std::vector<T> _data;
};
template<typename T>
struct Array3D
{
public:
Array3D() : _width(0), _height(0), _depth(0) {}
Array3D(size_t w, size_t h, size_t d, T value = {})
: _width(w), _height(h), _depth(d), _data(w * h * d, value) {}
const size_t index(size_t x, size_t y, size_t z) const
{
// DCHECK_LT_F(x, _width);
// DCHECK_LT_F(y, _height);
// DCHECK_LT_F(z, _depth);
// return z * _width * _height + y * _width + x;
return x * _height * _depth + y * _depth + z; // better cache hit ratio in our use case
}
inline T& mut_ref(size_t x, size_t y, size_t z) { return _data[index(x, y, z)]; }
inline const T& ref(size_t x, size_t y, size_t z) const { return _data[index(x, y, z)]; }
inline T get(size_t x, size_t y, size_t z) const { return _data[index(x, y, z)]; }
inline void set(size_t x, size_t y, size_t z, const T& value) { _data[index(x, y, z)] = value; }
inline size_t size() const { return _data.size(); }
private:
size_t _width, _height, _depth;
std::vector<T> _data;
};
// By Emil Ernerfeldt 2014-2016
// LICENSE:
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
// HISTORY:
// Created 2014 for Ghostel.
/* Crash-course:
for (const auto ix : irange(end)) { CHECK_F(0 <= ix && ix < end); }
for (const auto ix : irange(begin, end)) { CHECK_F(begin <= ix && ix < end); }
for (const auto ix : indices(some_vector)) { CHECK_F(0 <= ix && ix < some_vector.size(); }
for (const char ch : emilib::cstr_range("hello world!"))
for (auto& value : it_range(begin, end)) { std::cout << value; }
*/
#pragma once
// #include "loguru.hpp"
#include <iterator>
#include <type_traits>
namespace emilib {
template<typename Integer>
class Range
{
public:
static_assert(std::is_integral<Integer>::value, "Range should only be used for integers.");
Range() : _begin(0), _end(0) { }
Range(Integer begin, Integer end) : _begin(begin), _end(end)
{
// DCHECK_LE_F(begin, end);
}
struct iterator : public std::iterator<std::input_iterator_tag, Integer>
{
Integer value;
explicit iterator(Integer v) : value(v) {}
Integer operator*() const { return value; }
iterator& operator++()
{
++value;
return *this;
}
friend bool operator!=(const iterator& a, const iterator& b)
{
return a.value != b.value;
}
};
Integer operator[](size_t ix) const { return _begin + ix; }
iterator begin() const { return iterator{ _begin }; }
iterator end() const { return iterator{ _end }; }
Integer size() const { return _end - _begin; }
Integer front() const { return _begin; }
Integer back() const { return _end - 1; }
private:
Integer _begin, _end;
};
// for (const auto i : irange(end)) { CHECK_F(0 <= i && i < end); }
template<typename Integer>
Range<Integer> irange(Integer end)
{
return {0, end};
}
// for (const auto i : irange(begin, end)) { CHECK_F(begin <= i && i < end); }
template<typename Integer>
Range<Integer> irange(Integer begin, Integer end)
{
return {begin, end};
}
// for (const auto i : irange_inclusive(first, last)) { CHECK_F(first <= i && i <= last); }
template<typename Integer>
Range<Integer> irange_inclusive(Integer first, Integer last)
{
return {first, last + 1};
}
// for (const auto i : indices(some_vector)) { CHECK_F(0 <= i && i < some_vector.size(); }
template<typename Integer = size_t, typename Container>
Range<Integer> indices(const Container& container)
{
return {0, static_cast<Integer>(container.size())};
}
// -----------------------------------------------------------------------
template<typename Integer, typename Visitor>
void repeat(Integer count, const Visitor& visitor)
{
CHECK_GE_F(count, static_cast<Integer>(0));
while (count != 0) {
visitor();
count -= 1;
}
}
template<typename Chr>
class CStrRange
{
Chr* _begin;
public:
CStrRange(const Chr* c) : _begin(c) { }
struct iterator
{
Chr* _ptr;
Chr operator*() const { return *_ptr; }
iterator& operator++()
{
++_ptr;
return *this;
}
friend bool operator!=(const iterator& a, const iterator& b)
{
return *a._ptr != *b._ptr;
}
};
iterator begin() const
{
return { _begin };
}
iterator end() const
{
static Chr zero = 0;
return { &zero };
}
};
// for (const char ch : emilib::cstr_range("hello world!"))
template<typename Chr>
inline CStrRange<Chr> cstr_range(Chr* str) { return CStrRange<Chr>(str); }
} // namespace emilib
/*
TE4 - T-Engine 4
Copyright (C) 2009 - 2017 Nicolas Casalini
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Nicolas Casalini "DarkGod"
darkgod@te4.org
*/
extern "C" {
#include "lauxlib.h"
#include "lualib.h"
#include "auxiliar.h"
#include "lua_wfc_external.h"
}
#include "stdlib.h"
#include "string.h"
#include "lua_wfc.hpp"
static WFCOverlapping *parse_config_overlapping(lua_State *L) {
WFCOverlapping *config = new WFCOverlapping();
config->output.w = luaL_checknumber(L, 2);
config->output.h = luaL_checknumber(L, 3);
config->n = luaL_checknumber(L, 4);
config->symmetry = luaL_checknumber(L, 5);
config->periodic_out = lua_toboolean(L, 6);
config->periodic_in = lua_toboolean(L, 7);
config->has_foundation = lua_toboolean(L, 8);
// Iterate the sample lines
config->sample_w = 9999;
config->sample_h = lua_objlen(L, 1);
config->sample = (unsigned char**)malloc(config->sample_h * sizeof(unsigned char *));
for (int y = 0; y < config->sample_h; y++) {
lua_rawgeti(L, 1, y + 1);
size_t len;
unsigned char *line = (unsigned char*)strdup(luaL_checklstring(L, -1, &len));
config->sample[y] = line;
if (len < config->sample_w) config->sample_w = len;
// printf("==sample line %d: '%s' len(%d)\n", y, line, len);
lua_pop(L, 1);
}
// Generate output data
config->output.data = (unsigned char**)malloc(config->output.h * sizeof(unsigned char *));
for (int y = 0; y < config->output.h; y++) {
config->output.data[y] = (unsigned char*)calloc(config->output.w, sizeof(unsigned char));
}
return config;
}
static void free_config_overlapping(WFCOverlapping *config) {
for (int y = 0; y < config->sample_h; y++) free(config->sample[y]);
for (int y = 0; y < config->output.h; y++) free(config->output.data[y]);
free(config->sample);
free(config->output.data);
delete config;
}
static void generate_table_from_output(lua_State *L, WFCOutput *output) {
lua_newtable(L);
// printf("===========RESULT\n");
for (int y = 0; y < output->h; y++) {
// for (int x = 0; x < output->w; x++) {
// printf("%c", output->data[y][x]);
// }
// printf("\n");
lua_pushlstring(L, (const char*)output->data[y], output->w);
lua_rawseti(L, -2, y + 1);
}
// printf("===========\n");
}
static int lua_wfc_overlapping(lua_State *L) {
WFCOverlapping *config = parse_config_overlapping(L);
if (wfc_generate_overlapping(config)) {
generate_table_from_output(L, &config->output);
} else {
lua_pushnil(L);
}
// Cleanup
free_config_overlapping(config);
return 1;
}
static int thread_generate_overlapping(void *ptr) {
WFCOverlapping *config = (WFCOverlapping*)ptr;
if (wfc_generate_overlapping(config)) {
return 1;
} else {
return 0;
}
}
static int lua_wfc_overlapping_async(lua_State *L) {
WFCOverlapping *config = parse_config_overlapping(L);
SDL_Thread *thread = SDL_CreateThread(thread_generate_overlapping, "particles", config);
WFCAsync *async = (WFCAsync*)lua_newuserdata(L, sizeof(WFCAsync));
auxiliar_setclass(L, "wfc{async}", -1);
async->mode = WFCAsyncMode::OVERLAPPING;
async->overlapping_config = config;
async->thread = thread;
return 1;
}
static int lua_wfc_wait_all_async(lua_State *L) {
return 0;
}
static int lua_wfc_wait_async(lua_State *L) {
WFCAsync *async = (WFCAsync*)auxiliar_checkclass(L, "wfc{async}", 1);
int ret;
SDL_WaitThread(async->thread, &ret);
WFCOutput *output;
if (async->mode == WFCAsyncMode::OVERLAPPING) output = &async->overlapping_config->output;
if (ret == 1) {
generate_table_from_output(L, output);
} else {
lua_pushnil(L);
}
// Cleanup
if (async->mode == WFCAsyncMode::OVERLAPPING) free_config_overlapping(async->overlapping_config);
return 1;
}
static const struct luaL_Reg async_reg[] =
{
{"wait", lua_wfc_wait_async},
{NULL, NULL},
};
static const struct luaL_Reg wfclib[] =
{
{"asyncOverlapping", lua_wfc_overlapping_async},
{"asyncWaitAll", lua_wfc_wait_all_async},
{"overlapping", lua_wfc_overlapping},
{NULL, NULL},
};
int luaopen_wfc(lua_State *L) {
auxiliar_newclass(L, "wfc{async}", async_reg);
luaL_openlib(L, "core.generator.wfc", wfclib, 0);
lua_settop(L, 0);
return 1;
}
/*
TE4 - T-Engine 4
Copyright (C) 2009 - 2017 Nicolas Casalini
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Nicolas Casalini "DarkGod"
darkgod@te4.org
*/
#ifndef _WFC_LUA_H_
#define _WFC_LUA_H_
#include "tSDL.h"
struct WFCOutput {
int w, h;
unsigned char **data;
};
struct WFCOverlapping {
int n;
int symmetry;
bool periodic_out;
bool periodic_in;
bool has_foundation;
int sample_w, sample_h;
unsigned char **sample;
WFCOutput output;
};
enum class WFCAsyncMode { OVERLAPPING, TILED };
struct WFCAsync {
WFCAsyncMode mode = WFCAsyncMode::OVERLAPPING;
WFCOverlapping *overlapping_config = nullptr;
SDL_Thread *thread;
};
// In wfc.cpp
extern bool wfc_generate_overlapping(WFCOverlapping *config);
#endif
/*
TE4 - T-Engine 4
Copyright (C) 2009 - 2017 Nicolas Casalini
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Nicolas Casalini "DarkGod"
darkgod@te4.org
*/
#ifndef _WFC_LUA_EXTERNAL_H_
#define _WFC_LUA_EXTERNAL_H_
#include "lua.h"
extern int luaopen_wfc(lua_State *L);
#endif
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment