myspace / CommonLua /MapGen /BiomeFiller.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
73 kB
local unit_weight = 4096
local type_tile = const.TypeTileSize
local height_max = const.MaxTerrainHeight
local height_scale = const.TerrainHeightScale
local empty_table = empty_table
local gofPermanent = const.gofPermanent
local gofGenerated = const.gofGenerated
local maxh = height_max - 10*guim
local minh = 10*guim
local gmodes = {"Height", "Type", "Grass", "Objects", "POI"}
local smodes = {"Prefab", "POI"}
local pmodes = {"Marks", "Overlap", "Cover"}
local omodes = {"Marks", "Overlap", "Cover", "Types"}
local imodes = {"Rollover", "Pos", "POI"}
local def_gm = set(table.unpack(gmodes))
local def_pn = set("Marks")
local b_dprint = CreatePrint{ "RM", format = print_format, output = DebugPrint }
local b_print = Platform.developer and CreatePrint{ "RM", format = print_format, color = yellow} or b_dprint
local pct_mul = 100
local pct_100 = 100*pct_mul
local function to_pct(mul, div)
return div == 0 and 0 or MulDivRound(100 * pct_mul, mul, div)
end
local def_bd = "BiomeDistort"
DbgShowPrefab = empty_func
----
DefineClass.BiomeFiller = {
__parents = { "GridOpInput", "DebugOverlayControl" },
properties = {
{ category = "General", id = "SlopeGrid", name = "Slope Grid", editor = "choice", default = "", items = function(self) return GridOpOutputNames(self) end, grid_input = true, optional = true, help = "Required by POI logic." },
{ category = "General", id = "MinHeight", name = "Min Height (m)", editor = "number", default = minh, min = 0, max = height_max, scale = guim, help = "Prefabs below that limit will be smart clamped." },
{ category = "General", id = "MaxHeight", name = "Max Height (m)", editor = "number", default = maxh, min = 0, max = height_max, scale = guim, help = "Prefabs above that limit will be smart clamped." },
{ category = "General", id = "LoadPrefabLoc", name = "Load Prefab Loc", editor = "bool", default = false, help = "Load any previously saved prefab locations found in the map." },
{ category = "General", id = "SavePrefabLoc", name = "Save Prefab Loc", editor = "bool", default = false, help = "Save any prefab locations with persistable tags." },
{ category = "General", id = "UseMeshOverlap",name = "Use Mesh Overlap", editor = "bool", default = true, log = true, help = "Detect prefab overlapping objects by analysing their collision mesh instead only their origin"},
{ category = "General", id = "OptionalChance",name = "Optional Chance (%)", editor = "number", default = 50, slider = true, min = 0, max = 100, log = true, help = "Chance for optional objects to be placed" },
{ category = "General", id = "SteepSlope", name = "Steep Slope", editor = "number", default = 30 * 60,slider = true, min = 0, max = 90*60, log = true, scale = "deg", help = "Slope threshold to start deleting objects marked to be removed on steep slopes" },
{ category = "Debug", id = "GenMode", name = "Gen Mode", editor = "set", default = def_gm, items = gmodes, },
{ category = "Debug", id = "RemFadedObjs", name = "Rem Faded Objs", editor = "bool", default = true, log = true, help = "Remove border objects that wont be seen from the playable zone as they are always faded away"},
{ category = "Debug", id = "StepMode", name = "Step Mode", editor = "set", default = set(), items = smodes },
{ category = "Debug", id = "StepTime", name = "Step Time (ms)", editor = "number", default = 300, help = "Delay in each step during Step debug mode. Set to -1 to trigger a pause.", buttons = {{name = "Toggle Pause", func = "ActionTogglePause"}, {name = "Interrupt", func = "ActionInterrupt"}} },
{ category = "Debug", id = "Overlay", name = "Overlay Mode", editor = "set", default = set(), update_dbg = true, items = omodes, max_items_in_set = 1},
{ category = "Debug", id = "OverlayAlpha", name = "Overlay Alpha (%)", editor = "number", default = 30, slider = true, min = 0, max = 100, dont_save = true },
{ category = "Debug", id = "OverlayEdges", name = "Overlay Edges", editor = "bool", default = true, dont_save = true, update_dbg = true, },
{ category = "Debug", id = "InspectMode", name = "Inspect Mode", editor = "set", default = set(), dont_save = true, update_dbg = true, items = imodes, max_items_in_set = 1},
{ category = "Debug", id = "InspectFilter", name = "Inspect Filter", editor = "set", default = set(), dont_save = true, update_dbg = true, items = function() return PrefabTagsCombo() end, help = "Show only prefabs with the selected tags" },
{ category = "Debug", id = "InspectPattern",name = "Inspect Prefab", editor = "text", default = "", dont_save = true, update_dbg = true, buttons = {{name = "View", func = "ViewInspectedPrefab"}}, help = "Show only prefabs with names matching this pattern" },
{ category = "Debug", id = "SelectedPrefab",name = "Selected Prefab", editor = "text", default = "", dont_save = true, buttons = {{name = "Goto", func = "GotoPrefabAction"}} },
{ category = "Debug", id = "SelectedPoi", name = "Selected Info", editor = "text", default = "", dont_save = true},
{ category = "Debug", id = "SelectedTags", name = "Selected Tags", editor = "text", default = "", dont_save = true},
{ category = "Debug", id = "SelectedMark", name = "Selected Mark", editor = "number", default = 0, dont_save = true},
{ category = "Debug", id = "SelectedBreak", name = "Selected Break", editor = "bool", default = false, dont_save = true},
{ category = "Results", id = "PreviewSet", name = "Preview Name", editor = "set", default = def_pn, items = pmodes, object_update = true, max_items_in_set = 1 },
{ category = "Results", id = "GridPreview", name = "Preview Grid", editor = "grid", default = false, min = 128, max = 512, frame = 1, color = true, invalid_value = 0 },
{ category = "Results", id = "MarkGrid", name = "Prefab Marks", editor = "grid", default = false, no_edit = true },
{ category = "Results", id = "OverlapGrid", name = "Prefab Overlap", editor = "grid", default = false, no_edit = true },
{ category = "Results", id = "CoverGrid", name = "Area Cover", editor = "grid", default = false, no_edit = true },
{ category = "Results", id = "PTypeGrid", name = "PType Marks", editor = "grid", default = false, no_edit = true },
{ category = "Results", id = "PrefabCount", name = "Prefab Count", editor = "number", default = 0, log = true},
{ category = "Results", id = "PrefabVisible", name = "Prefab Visible (%)", editor = "number", default = 0, scale = pct_mul, log = true },
{ category = "Results", id = "OverlapMax", name = "Max Overlap Prefabs", editor = "number", default = 0, log = true },
{ category = "Results", id = "OverlapPct", name = "Area Overlap (%)", editor = "number", default = 0, scale = pct_mul, log = true },
{ category = "Results", id = "AreaUncovered", name = "Area Uncovered (%)", editor = "number", default = 0, scale = pct_mul, log = true },
{ category = "Results", id = "AreaSpill", name = "Area Spill (%)", editor = "number", default = 0, scale = pct_mul, log = true },
{ category = "Results", id = "ObjectCount", name = "Object Count", editor = "number", default = 0, log = true },
{ category = "Results", id = "RemObjects", name = "Removed Objects", editor = "number", default = 0, log = true },
{ category = "Results", id = "RemColls", name = "Removed Collections", editor = "number", default = 0, log = true },
{ category = "Results", id = "PlacedColls", name = "Placed Collections", editor = "number", default = 0, log = true },
{ category = "Results", id = "RasterMem", name = "Raster Mem (MB)", editor = "number", default = 0, scale = 1024*1024 },
{ category = "Results", id = "MixHash", name = "Prefab Types Hash", editor = "number", default = 0, log = true },
{ category = "Results", id = "PrefabHash", name = "Prefab Placed Hash", editor = "number", default = 0, log = true },
{ category = "Results", id = "MarkHash", name = "Marks Hash", editor = "number", default = 0, log = true },
{ category = "Results", id = "FirstRand", name = "First Rand", editor = "number", default = 0, log = true },
{ category = "Results", id = "LastRand", name = "Last Rand", editor = "number", default = 0, log = true },
{ category = "Results", id = "LocateTime", name = "Locate Time", editor = "number", default = 0, scale = "sec" },
{ category = "Results", id = "PoiTime", name = "Locate POI Time", editor = "number", default = 0, scale = "sec" },
{ category = "Results", id = "RasterizeTime", name = "Rasterize Time", editor = "number", default = 0, scale = "sec" },
{ category = "Results", id = "ObjectTime", name = "Object Time", editor = "number", default = 0, scale = "sec" },
{ category = "Results", id = "GenStep", name = "Generate Step", editor = "number", default = 0, scale = "m" },
{ category = "Results", id = "PlacedPrefabs", name = "Placed Prefabs", editor = "text", default = false, lines = 10, text_style = "GedConsole", buttons = {{name = "Sort", func = "ActionSortPrefabs"}} },
{ category = "Results", id = "PrefabTypes", name = "Prefab Types", editor = "text", default = false, lines = 10, text_style = "GedConsole", log = true, buttons = {{name = "Sort", func = "ActionSortPrefabTypes"}} },
{ category = "Results", id = "VisiblePrefabs",name = "Prefab Visibility", editor = "text", default = false, lines = 10, text_style = "GedConsole", log = true, buttons = {{name = "Sort", func = "ActionSortVisible"}} },
{ category = "Results", id = "PlacedObjects", name = "Placed Objects", editor = "text", default = false, lines = 10, text_style = "GedConsole", log = true, buttons = {{name = "Sort", func = "ActionSortObjects"}} },
{ category = "Results", id = "PlacedPOI", name = "Placed POI", editor = "text", default = false, lines = 10, text_style = "GedConsole", log = true, buttons = {{name = "Sort", func = "ActionSortPoi"}} },
{ category = "Results", id = "PrefabList", editor = "prop_table", default = false, no_edit = true },
},
gen_thread = false,
gen_handles = false,
DbgInit = empty_func,
DbgDone = empty_func,
DbgUpdate = empty_func,
DbgOnModified = empty_func,
GridOpType = "Map Biome Fill",
recalc_on_change = false,
}
do
for i, prop in ipairs(BiomeFiller.properties) do
if prop.category == "Results" and not prop.items then
prop.dont_save = true
prop.read_only = true
end
end
end
function BiomeFiller:GetGridModes()
return {
Marks = self.MarkGrid,
Overlap = self.OverlapGrid,
Cover = self.CoverGrid,
Types = self.PTypeGrid,
}
end
function BiomeFiller:CollectTags(tags)
tags.Terrain = true
tags.Objects = true
tags.Pause = true
return GridOp.CollectTags(self, tags)
end
function BiomeFiller:SetGridInput(state, grid)
if not next(GetPrefabTypeList()) then
state.proc:AddLog(self:GetFullName() .. ": No prefab types found!", state)
return
end
PauseInfiniteLoopDetection("BiomeFiller.Generate")
SuspendPassEdits("BiomeFiller.Generate")
SuspendObjModified("BiomeFiller.Generate")
-- allow gen proc restarting (via Ged)
local map_thread = CurrentThread()
local gen_thread = CreateRealTimeThread(function()
self:Generate(state, grid)
Wakeup(map_thread)
end)
self.gen_thread = gen_thread
while self.gen_thread == gen_thread and IsValidThread(gen_thread) and not WaitWakeup(100) do
-- wait for the gen thread to finish
end
if self.gen_thread ~= gen_thread then
return
end
ResumeObjModified("BiomeFiller.Generate")
ResumePassEdits("BiomeFiller.Generate")
ResumeInfiniteLoopDetection("BiomeFiller.Generate")
end
function BiomeFiller:Generate(state, ptype_grid)
local gen_thread = CurrentThread()
if gen_thread ~= self.gen_thread then
return
end
self:DbgInit()
state = state or empty_table
local debug = state.run_mode ~= "GM" and (Platform.editor or Platform.developer)
local dump = state.dump
local gen_mode = self.GenMode
local step
local step_prefab, step_poi
local prefab_stats, prefab_stat_count
local AddPrefabStat = empty_func
local Min, Max = Min, Max
local irOutside = const.irOutside
local group_dist_pct = const.RandomMap.PrefabGroupSimilarDistPct
local map_divs = const.RandomMap.PrefabRasterParallelDiv
local group_attract = const.RandomMap.PrefabGroupSimilarWeight
local max_map_size = const.RandomMap.PrefabMaxMapSize
local raster_cache_memory = const.RandomMap.PrefabRasterCacheMemory
local table_append = table.append
local table_get = table.get
local ipairs, pairs = ipairs, pairs
local BraidRandom = BraidRandom
local LerpRandRange = LerpRandRange
local unpack = table.unpack
local table_keys = table.keys
local MulDivRound = MulDivRound
local GetHeight = terrain.GetHeight
local GetSlopeOrientation = terrain.GetSlopeOrientation
local IsPointInBounds = terrain.IsPointInBounds
local start_seed = state.rand or AsyncRand()
local rand_seed = BraidRandom(start_seed)
local g_print = b_print
if dump then
g_print = function(...)
dump(print_format("\n--", ...))
return b_print(...)
end
end
if debug then
if next(self.StepMode or empty_table) then
if self.StepMode.Prefab then
step_prefab = true
map_divs = 1
end
if self.StepMode.POI then
step_poi = true
end
step = function(fmt, ...)
if self.dbg_interrupt then
return
end
if fmt then
printf(fmt, ...)
end
self:DbgOnModified()
if self.StepTime < 0 then
self.dbg_paused = true
else
Sleep(self.StepTime)
end
if self.dbg_paused then
print("Pause")
while self.dbg_paused do
WaitMsg(self)
end
print("Resume")
end
if gen_thread ~= self.gen_thread then
Halt()
end
end
end
prefab_stats, prefab_stat_count = {}, {}
AddPrefabStat = function(prefab, name, value)
local stat = prefab_stats[prefab] or {}
local count = prefab_stat_count[prefab] or {}
stat[name] = (stat[name] or 0) + (value or 1)
count[name] = (count[name] or 0) + 1
prefab_stats[prefab] = stat
prefab_stat_count[prefab] = count
end
end
local mw, mh = terrain.GetMapSize()
assert(mw == mh)
if mw > max_map_size then
g_print("map larger than", max_map_size)
return
end
local gw, gh = ptype_grid:size()
if gw ~= gh or mw / gw == 0 or mw % gw ~= 0 or (mw / gw) % type_tile ~= 0 then
return "Invalid mix grid size!"
end
local work_step = mw / gw
local work_ratio = work_step / type_tile
self.GenStep = work_step
local function new_grid(packing)
return NewComputeGrid(gw, gh, "u", packing or 16)
end
local function free_grid(grid)
if grid then grid:free() end
end
local function rand_init(name, ...)
rand_seed = xxhash(start_seed, name, ...)
if dump then
dump("\n*** INITRAND %d %s", rand_seed, name)
end
return rand_seed
end
local function trand(tbl, calc_weight)
rand_seed = BraidRandom(rand_seed)
return table.weighted_rand(tbl, calc_weight, rand_seed)
end
local function crand(chance, max_chance)
rand_seed = BraidRandom(rand_seed)
return LerpRandRange(rand_seed, max_chance or 100) < chance
end
local function rand(min, max)
rand_seed = BraidRandom(rand_seed)
return min and LerpRandRange(rand_seed, min, max) or rand_seed
end
local prefab_markers = PrefabMarkers
local exported_prefabs = ExportedPrefabs
local ptype_to_preset = PrefabTypeToPreset
local prefab_list = {}
local add_idx = 0
local prefabs_count = {}
local bx_changes, levels_count, placed_marks
local height_out_of_lims
local mark_grid, ptype_grid_res, overlap_grid
local locate_time, poi_time, raster_time = 0, 0, 0
local prefab_tag_loc, prefabs_to_persist = {}, {}
local point_pack, point_unpack = point_pack, point_unpack
local function FindAndRasterPrefabs()
rand_init("FindAndRasterPrefabs")
local ptype_to_prefabs = PrefabTypeToPrefabs
local poi_type_to_preset = PrefabPoiToPreset
local idx_to_ptype = GetPrefabTypeList()
local ptypes_found, ptype_to_tags, ptype_to_area, ptype_to_idx = {}, {}, {}, {}
for idx, area in pairs(GridLevels(ptype_grid)) do
local ptype = idx_to_ptype[idx]
assert(ptype)
if ptype then
ptypes_found[#ptypes_found + 1] = ptype
ptype_to_area[ptype] = area
ptype_to_idx[ptype] = idx
local tags = ptype_to_preset[ptype].Tags
if next(tags) then
ptype_to_tags[ptype] = tags
end
end
end
local ptype_cmp = PrefabType.Compare
table.sort(ptypes_found, function(a, b)
local pa, pb = ptype_to_preset[a], ptype_to_preset[b]
return ptype_cmp(pa, pb)
end)
mark_grid = new_grid()
ptype_grid_res = new_grid()
local cover_grid
if debug then
overlap_grid = new_grid()
cover_grid = new_grid()
self.MarkGrid = mark_grid
self.OverlapGrid = overlap_grid
self.CoverGrid = cover_grid
self.PTypeGrid = ptype_grid_res
end
local prefab_tags, prefab_to_persist_tags = {}, {}
local persistable_tags = GetPrefabTagsPersistable()
for _, prefab in ipairs(prefab_markers) do
local poi_tags = table_get(poi_type_to_preset, prefab.poi_type, "Tags")
local ptype_tags = ptype_to_tags[prefab.type]
local marker_tags = prefab.tags
if next(ptype_tags) or next(marker_tags) or next(poi_tags) then
local tags = {}
table_append(tags, ptype_tags)
table_append(tags, marker_tags)
table_append(tags, poi_tags)
tags = table_keys(tags, true)
local persist_tags
for _, tag in ipairs(tags) do
if persistable_tags[tag] then
persist_tags = table.create_add(persist_tags, tag)
end
end
prefab_to_persist_tags[prefab] = persist_tags
prefab_tags[prefab] = tags
end
end
local persisted_prefabs, persisted_tag_count
if self.LoadPrefabLoc then
for _, entry in ipairs(mapdata.PersistedPrefabs) do
local name = entry[1]
local prefab = prefab_markers[name]
local persist_tags = prefab and prefab_to_persist_tags[prefab]
if not persist_tags then
g_print("Non persistable prefab loaded:", name)
else
persisted_prefabs = table.create_add(persisted_prefabs, entry)
persisted_tag_count = persisted_tag_count or {}
for _, tag in pairs(persist_tags) do
persisted_tag_count[tag] = (persisted_tag_count[tag] or 0) + 1
end
end
end
end
local function IsPrefabAllowed(prefab)
if (prefab.max_count or -1) == (prefabs_count[prefab] or 0) then
return
end
if persisted_tag_count then
local tags = prefab_tags[prefab]
for _, tag in ipairs(tags) do
local tag_count = persisted_tag_count[tag]
if tag_count and tag_count <= 0 then
return
end
end
end
return true
end
local map_angle = rand(360*60)
local similar_grids = {}
local function MulDivWeight(weight, mul, div, pow)
for i=1,(pow or 0) do
weight = weight * mul / div
end
return weight
end
local _overlap_reduct, _fit_effort -- prefab type props
local _place_x, _place_y, _radius_target, _radius_range -- prefab location props
local radius_getters = PrefabRadiusEstimators()
local get_radius
local repeat_weights = {}
local function prefab_weight(prefab)
local weight = MulDivRound(unit_weight, prefab.weight, 100)
--[[
local sweight = 0
for tag in pairs(prefab.tags) do
local sgrid = similar_grids[tag]
if sgrid then
sweight = sweight + sgrid:get(_place_x, _place_y) - 1
end
end
if sweight > 0 then
weight = weight + MulDivRound(weight, sweight, group_attract)
end
--]]
weight = MulDivWeight(weight, prefab.min_radius, prefab.max_radius, _overlap_reduct) -- prioritize prefabs with better incircle to excircle radius ratio to reduce overlapping
local radius = get_radius(prefab)
local radius_err = abs(_radius_target - radius)
weight = MulDivWeight(weight, _radius_range - radius_err, _radius_range, _fit_effort) -- prioritize prefabs with radius closer to the desired one
local repeat_weight = repeat_weights[prefab]
if repeat_weight then
weight = MulDivRound(weight, repeat_weight, unit_weight)
end
return 1 + weight
end
local function prefab_add(prefab_list, prefab, x, y, radius, mix_grid_idx, ptype, try_persist, skip_raster, angle)
--assert(ptype_grid:get(x, y) == mix_grid_idx)
assert((prefab.max_count or -1) ~= (prefabs_count[prefab] or 0))
local count = (prefabs_count[prefab] or 0) + 1
prefabs_count[prefab] = count
local reduct = prefab.repeat_reduct or 0
if reduct > 0 then
local rstep = reduct / 10
local weight = unit_weight
for i=1,count do
local new_weight = weight * (100 - reduct) / 100
if new_weight == weight then
break
end
weight = new_weight
reduct = reduct - rstep
if reduct <= 0 then
break
end
end
repeat_weights[prefab] = weight
end
local mx, my = x * work_step, y * work_step
local mz = GetHeight(mx, my)
local prefab_pos = point(mx, my, mz)
assert(IsPointInBounds(prefab_pos))
local bbox
if not skip_raster then
local excircle_m = prefab.max_radius * type_tile
bbox = box(mx - excircle_m, my - excircle_m, mx + excircle_m, my + excircle_m)
end
if not angle then
local angle_variation = prefab.angle_variation or 180*60
angle = rand(-angle_variation, angle_variation) - (prefab.angle or 0)
local rotation_mode = prefab.rotation_mode
if rotation_mode == "slope" then
angle = angle + GetSlopeOrientation(prefab_pos, work_step * prefab.min_radius / 2)
elseif rotation_mode == "map" then
angle = angle + map_angle
end
end
local raster = {
pos = prefab_pos,
angle = angle,
place_idx = 0,
place_mask_idx = mix_grid_idx,
}
add_idx = add_idx + 1
prefab_list[#prefab_list + 1] = {prefab, raster, add_idx, ptype, bbox}
local remaining = max_int
local tags = prefab_tags[prefab]
if tags then
local loc = point_pack(x, y, radius)
for _, tag in ipairs(tags) do
local loc_list = prefab_tag_loc[tag]
if not loc_list then
prefab_tag_loc[tag] = { loc }
else
loc_list[#loc_list + 1] = loc
end
local tag_count = persisted_tag_count and persisted_tag_count[tag]
if tag_count then
assert(tag_count > 0)
tag_count = tag_count - 1
persisted_tag_count[tag] = tag_count
remaining = Min(remaining, tag_count)
end
end
if try_persist and prefab_to_persist_tags[prefab] then
local name = prefab_markers[prefab]
prefabs_to_persist[#prefabs_to_persist + 1] = { name, ptype, x, y, angle }
end
end
return count, remaining
end
local skip = debug and {
Height = not gen_mode.Height,
Type = not gen_mode.Type,
Grass = not gen_mode.Grass,
}
local raster_params = {
place_grid = mark_grid,
place_mask = ptype_grid,
place_mask_res = ptype_grid_res,
overlap_grid = overlap_grid,
dither_seed = rand(),
height_min = self.MinHeight,
height_max = self.MaxHeight,
}
local raster_meta = {__index = raster_params}
local prefab_cache = {}
local cache_info = {}
local current_memory = 0
local peak_memory = 0
local tasks_count = map_divs * map_divs
local PREFAB_META, PREFAB_RASTER, PREFAB_IDX, PREFAB_TYPE, PREFAB_BOX = 1, 2, 3, 4, 5
local function FreeCache(prefab)
local cache = prefab_cache[prefab]
if not cache then
assert(false, "Missing cache")
return
end
local data = cache.__index
free_grid(data.height_grid)
free_grid(data.type_grid)
free_grid(data.grass_grid)
free_grid(data.mask_grid)
prefab_cache[prefab] = nil
current_memory = current_memory - (prefab.required_memory or 0)
assert(current_memory >= 0, "Wrong memory estimation")
end
local function LoadCache(prefab)
local cache, ignore_memory_limits
while true do
cache = prefab_cache[prefab]
if cache then
break
elseif cache == false then -- load error
return
end
local required_memory = prefab.required_memory or 0
if ignore_memory_limits or current_memory + required_memory <= raster_cache_memory then
local preload_start_time = GetPreciseTicks()
local data = PrefabPreload(prefab, raster_meta, skip)
if debug then
AddPrefabStat(prefab, "grid_load_time", GetPreciseTicks() - preload_start_time)
end
cache = data and {__index = data} or false
if cache then
current_memory = current_memory + required_memory
peak_memory = Max(peak_memory, current_memory)
end
prefab_cache[prefab] = cache
break
end
local min_prefab
local min_required_memory = max_int
local locked = 0
for i=1,#cache_info do
local prefab_i = cache_info[i]
if prefab_cache[prefab_i] then
local required_memory_i = prefab_i.required_memory or 0
if min_required_memory > required_memory_i then
if cache_info[prefab_i].locks == 0 then
min_prefab = prefab_i
min_required_memory = required_memory_i
else
locked = locked + 1
end
end
end
end
if min_prefab then
FreeCache(min_prefab)
elseif locked > 0 then
WaitMsg("PrefabCacheUnloaded")
else
g_print("Unable to free enough memory for rasterization!")
ignore_memory_limits = true
end
end
if not cache then
return
end
local info = cache_info[prefab]
assert(info, "Load cache error")
if info then
info.locks = info.locks + 1
end
return cache
end
local function UnloadCache(prefab)
local info = cache_info[prefab]
if not info or info.locks <= 0 or info.count <= 0 then
assert(false, "Unload cache error")
return
end
info.locks = info.locks - 1
info.count = info.count - 1
Msg("PrefabCacheUnloaded")
if info.count ~= 0 then
return
end
assert(info.locks == 0, "Invalid lock count")
FreeCache(prefab)
end
local function WaitRaster(prefabs_to_raster)
if #prefabs_to_raster == 0 then
return
end
local start_time_raster = GetPreciseTicks()
-- try to reduce object removal from overlapping:
local function prefab_list_sort(a, b)
local p1, p2 = a[PREFAB_META], b[PREFAB_META]
if p1 ~= p2 then
local ptype1, ptype2 = a[PREFAB_TYPE], b[PREFAB_TYPE]
if ptype1 ~= ptype2 then
local preset1, preset2 = ptype_to_preset[ptype1], ptype_to_preset[ptype2]
if preset1 and preset2 then
return ptype_cmp(preset1, preset2)
end
end
local mul1 = (p1.obj_count or 1) * (p2.total_area or 1)
local mul2 = (p2.obj_count or 1) * (p1.total_area or 1)
if mul1 ~= mul2 then
return mul1 < mul2
end
local mul1 = p1.max_radius * p2.min_radius
local mul2 = p2.max_radius * p1.min_radius
if mul1 ~= mul2 then
return mul1 < mul2
end
end
return a[PREFAB_IDX] < b[PREFAB_IDX]
end
table.sort(prefabs_to_raster, prefab_list_sort)
-- add the new prefabs in to the final list
local place_idx = #prefab_list
for _, info in ipairs(prefabs_to_raster) do
place_idx = place_idx + 1
local raster = info[PREFAB_RASTER]
raster.place_idx = place_idx
prefab_list[place_idx] = info
end
local waiting = {}
local thread_idx = 0
local y0 = 0
for y=1,map_divs do
local y1 = MulDivRound(mh, y, map_divs)
assert(y1 % type_tile == 0)
local x0 = 0
for x=1,map_divs do
local x1 = MulDivRound(mw, x, map_divs)
assert(x1 % type_tile == 0)
local mbox = box(x0, y0, x1, y1)
for i=1,#prefabs_to_raster do
local prefab, raster, add_idx, ptype, bbox = unpack(prefabs_to_raster[i])
if bbox and bbox:Intersect2D(mbox) ~= irOutside then
local info = cache_info[prefab]
if not info then
info = {
count = 1,
locks = 0,
}
cache_info[prefab] = info
cache_info[#cache_info + 1] = prefab
else
info.count = info.count + 1
end
end
end
local thread = CreateRealTimeThread(function()
for i=1,#prefabs_to_raster do
local prefab, raster, add_idx, ptype, bbox = unpack(prefabs_to_raster[i])
local cache = bbox and bbox:Intersect2D(mbox) ~= irOutside and LoadCache(prefab)
if cache then
setmetatable(raster, cache)
local raster_start_time = GetPreciseTicks()
local err, ibox, out_of_lims = AsyncGridSetTerrain(raster, mbox)
if debug then
AddPrefabStat(prefab, "grid_place_time", GetPreciseTicks() - raster_start_time)
end
if err then
g_print("Failed to rasterize prefab", prefab_markers[prefab], err)
elseif ibox then
bx_changes = bx_changes or box()
bx_changes = Extend(bx_changes, ibox)
end
if out_of_lims then
height_out_of_lims = true
end
setmetatable(raster, nil)
UnloadCache(prefab)
if step_prefab then
terrain.InvalidateHeight(ibox)
terrain.InvalidateType(ibox)
DbgClear()
local name = prefab_markers[prefab]
DbgShowPrefab(raster.pos, name, white, raster.place_idx, prefab.min_radius, prefab.max_radius)
step("Terrain %d %s", add_idx, name)
end
end
end
waiting[CurrentThread()] = nil
Wakeup(gen_thread)
end)
thread_idx = thread_idx + 1
waiting[thread] = thread_idx
x0 = x1
end
y0 = y1
end
while next(waiting) do
WaitWakeup(1000)
for thread in pairs(waiting) do
if not IsValidThread(thread) then
waiting[thread] = nil
end
end
end
local inv_bbox = bx_changes:grow(type_tile)
terrain.FixHeightBorder(inv_bbox)
if not step_prefab then
terrain.InvalidateHeight(inv_bbox)
terrain.InvalidateType(inv_bbox)
end
raster_time = raster_time + (GetPreciseTicks() - start_time_raster)
end
local dist_mask
local dist_grid = new_grid()
local dist_prec = 8
local dist_tile = work_ratio * dist_prec
local function WaitFitAndRaster(ptype)
local ptype_preset = ptype_to_preset[ptype]
if dump then
dump("\n----\nPTYPE '%s' %s", ptype, TableToLuaCode(ptype_preset))
end
get_radius = radius_getters[ptype_preset.RadiusEstim]
_overlap_reduct = ptype_preset.OverlapReduct > 0 and 2 ^ (ptype_preset.OverlapReduct - 1) or 0 -- prefab_weight
_fit_effort = ptype_preset.FitEffort > 0 and 2 ^ (ptype_preset.FitEffort - 1) or 0 -- prefab_weight
local prefabs = {}
local respect_bounds = ptype_preset.RespectBounds
local place_radius = ptype_preset.PlaceRadius / type_tile
local radius_min, radius_max = max_int, 0
for _, prefab in ipairs(ptype_to_prefabs[ptype] or empty_table) do
local poi_type = prefab.poi_type or ""
if poi_type == "" and IsPrefabAllowed(prefab) then
local radius = get_radius(prefab)
if radius >= place_radius then
radius_max = Max(radius_max, radius)
radius_min = Min(radius_min, radius)
prefabs[#prefabs + 1] = prefab
end
end
end
if #prefabs == 0 then
return
end
_radius_range = radius_max - radius_min + 1 -- prefab_weight
local ptype_idx = ptype_to_idx[ptype]
local mix_grid_idx = respect_bounds and ptype_idx
local min_fill_ratio = ptype_preset.MinFillRatio -- pct
local max_fill_error = ptype_preset.MaxFillError -- promil
local max_pass = ptype_preset.FitPasses
local placed_count = 0
for pass = 1, max_pass do
local start_time_fit = GetPreciseTicks()
local prefab_list_pass = {}
local area_remaining, all_area = GridMaskMark(ptype_grid, dist_grid, ptype_idx, ptype_grid_res)
if not area_remaining then
return
end
local min_area_remaining = all_area - all_area * min_fill_ratio / 100
if area_remaining <= min_area_remaining + all_area * max_fill_error / 1000 then
return
end
GridDistance(dist_grid, dist_tile, radius_max * dist_prec)
if dump then
local pct_x100 = all_area and MulDivRound(10000, all_area - area_remaining, all_area) or 0
dump("\nPASS %d/%d START %s - %2d prefab(s) | form '%s' | min fill %2d%% | filled area %2d.%02d%%\n", pass, max_pass, ptype, #prefabs, ptype_preset.RadiusEstim, min_fill_ratio, pct_x100/100, pct_x100%100)
end
rand_init("FindAndRasterPrefabs", pass, ptype)
local pass_placed_count = 0
GridRandomEnumMarkDist(dist_grid, rand(), dist_tile, function(x, y, dist, area)
if area <= min_area_remaining then
return
end
_radius_target = Max(dist / dist_prec, radius_min) -- prefab_weight
_place_x, _place_y = x, y -- prefab_weight
local prefab, idx = trand(prefabs, prefab_weight)
if not prefab then
g_print("Failed to find prefab for type", ptype)
return
end
local radius = get_radius(prefab)
local count, remaining = prefab_add(prefab_list_pass, prefab, x, y, radius, mix_grid_idx, ptype)
if (prefab.max_count or -1) == count or remaining <= 0 then
table.remove(prefabs, idx)
end
if dump then
pass_placed_count = pass_placed_count + 1
local weight = prefab_weight(prefab)
local total_weight, min_weight, max_weight = 0
for i=1,#prefabs do
local weight_i = prefab_weight(prefabs[i])
total_weight = total_weight + weight_i
min_weight, max_weight = MinMax(weight_i, min_weight, max_weight)
end
local avg_weight = total_weight / #prefabs
dump("PREFAB %4d | rand 0x%016X | weight %5d (%5d -%5d -%5d) | dist %3d (rad %3d - %3d) | pos (%4d, %4d) | '%s'",
pass_placed_count, rand_seed, weight, min_weight, avg_weight, max_weight,
_radius_target, prefab.min_radius, prefab.max_radius, x, y, prefab_markers[prefab])
end
return radius * dist_prec
end)
if dump then
placed_count = placed_count + pass_placed_count
dump("\nPASS %d/%d END %s PLACED %d TOTAL %d\n", pass, max_pass, ptype, pass_placed_count, placed_count)
end
locate_time = locate_time + (GetPreciseTicks() - start_time_fit)
if #prefab_list_pass == 0 then
return
end
WaitRaster(prefab_list_pass)
end
end
for _, ptype in ipairs(ptypes_found) do
WaitFitAndRaster(ptype)
end
local function WaitPlaceAndRasterPois()
if not gen_mode.POI then
return
end
local start_time_poi = GetPreciseTicks()
local ptype_to_poi_prefabs = {}
local poi_type_to_ptypes = {}
local prefab_to_ptype_map = {}
local prefab_list_poi = {}
local dbg_poi_count
local poi_count, poi_marks, poi_types = {}, {}, {}
local function poi_weight(prefab)
local weight = MulDivRound(unit_weight, prefab.weight, 100)
local count = prefabs_count[prefab] or 0
if count > 0 then
local max_count = prefab.max_count or -1
weight = max_count > 0 and MulDivRound(weight, max_count - count, max_count) or weight
end
return weight
end
local tag_to_tag_limits = GetPrefabTagsLimits()
local slope_grid = self:GetGridInput(self.SlopeGrid)
local slope_mask
local function PlacePois(poi_type, poi_area, ptypes, partial_count, max_count)
local total_count = poi_count[poi_type] or 0
if partial_count == 0 or total_count == max_count then
return
end
local prefabs
local min_radius, max_radius = max_int, 0
for _, ptype in ipairs(ptypes) do
for _, prefab in ipairs(ptype_to_poi_prefabs[ptype]) do
if poi_type == prefab.poi_type
and (not poi_area or poi_area == prefab.poi_area)
and prefab_to_ptype_map[prefab][ptype]
and IsPrefabAllowed(prefab) then
prefabs = prefabs or {}
if not prefabs[prefab] then
prefabs[prefab] = ptype
prefabs[#prefabs + 1] = prefab
local radius = get_radius(prefab)
max_radius = Max(max_radius, radius)
min_radius = Min(min_radius, radius)
end
end
end
end
if not prefabs then
return
end
if dump then
dump("\nPOI '%s' START {%s} - %2d prefab(s), count %d/%d\n", poi_type, table.concat(ptypes, ","), #prefabs, partial_count, max_count)
end
rand_init("PlacePois", poi_type, unpack(ptypes))
local ptype_single
if #ptypes == 1 then
ptype_single = ptypes[1]
local ptype_idx = ptype_to_idx[ptype_single]
if ptype_idx then
GridMask(ptype_grid, dist_grid, ptype_idx)
end
else
local grid_idx_remap = {}
for _, ptype in ipairs(ptypes) do
local ptype_idx = ptype_to_idx[ptype]
if ptype_idx then
grid_idx_remap[ptype_idx] = 1
end
end
GridReplace(ptype_grid, dist_grid, grid_idx_remap, 0)
end
local ptypes_map = table.invert(ptypes)
local poi_preset = poi_types[poi_type]
local fill_radius = poi_preset.FillRadius * dist_prec / type_tile
if fill_radius > 0 then
GridNot(dist_grid)
GridDistance(dist_grid, dist_tile, fill_radius)
GridMask(dist_grid, 0, fill_radius - 1)
GridDistance(dist_grid, dist_tile, fill_radius)
GridMask(dist_grid, fill_radius)
end
local to_mark = {}
local function MarkDist(x, y, radius, min_dist, max_dist)
local pos = point_pack(x, y)
local limits = to_mark[pos]
if not limits then
limits = {}
to_mark[pos] = limits
end
if min_dist and min_dist >= 0 then
limits[1] = Max(limits[1] or min_int, radius + min_dist / type_tile)
end
if max_dist and max_dist < max_int then
limits[2] = Min(limits[2] or max_int, radius + max_dist / type_tile)
end
end
local dist_to_same_m = poi_preset.DistToSame
for _, poi_info in pairs(poi_marks) do
local poi_type_i, x, y, radius = unpack(poi_info)
MarkDist(x, y, radius, poi_type_i == poi_type and dist_to_same_m or 0)
end
for poi_tag in pairs(poi_preset.Tags) do
for tag, limits in pairs(tag_to_tag_limits[poi_tag]) do
local min_dist, max_dist = limits[1], limits[2]
for _, loc in ipairs(prefab_tag_loc[tag]) do
local x, y, radius = point_unpack(loc)
MarkDist(x, y, radius, min_dist, max_dist)
end
end
end
local GridCircleSet = GridCircleSet
local limit_dist
for pos, limits in pairs(to_mark) do
local x, y = point_unpack(pos)
local min_dist, max_dist = limits[1], limits[2]
if min_dist and min_dist >= 0 then
GridCircleSet(dist_grid, 0, x, y, min_dist, 0, work_ratio)
end
if max_dist and max_dist < max_int then
if not limit_dist then
dist_mask = dist_mask or GridDest(dist_grid)
dist_mask:clear()
limit_dist = true
end
GridCircleSet(dist_mask, 1, x, y, max_dist, 0, work_ratio)
end
end
if limit_dist then
GridAnd(dist_grid, dist_mask)
end
local frame = (mapdata.PassBorder - poi_preset.DistToPlayable) / type_tile
if frame > 0 then
if frame >= gw / 2 or frame >= gh / 2 then
g_print("Dist To Playable Area for", poi_type, "leaves no available space for placement!")
end
GridFrame(dist_grid, frame, 0)
end
local slope_min, slope_max = poi_preset.TerrainSlopeMin, poi_preset.TerrainSlopeMax
if slope_grid and (slope_min > 0 or slope_max < 90*60) then
if not slope_mask then
slope_mask = GridDest(dist_grid)
slope_grid = GridMakeSame(slope_grid, slope_mask)
end
GridMask(slope_grid, slope_mask, slope_min, slope_max)
GridAnd(dist_grid, slope_mask)
end
GridDistance(dist_grid, dist_tile, max_radius * dist_prec)
if step_poi then
DbgClear()
local show_grid, poi_grid = GridDest(dist_grid), GridDest(dist_grid)
GridMask(dist_grid, show_grid, 1, max_int)
local colors = { yellow, green, cyan, purple, orange, white, black }
local palette = { [0] = 0, red }
for i, prefab in ipairs(prefabs) do
local radius = get_radius(prefab)
GridMask(dist_grid, poi_grid, radius * dist_prec, max_int)
GridAdd(show_grid, poi_grid)
palette[i + 1] = palette[i + 1] or colors[1 + (i - 1) % #colors]
end
DbgShowTerrainGrid(show_grid, palette)
step("POI %s START {%s}: %2d prefab(s) available", poi_type, table.concat(ptypes, ","), #prefabs)
end
-- todo: make forced mode to ensure the placement of at least one?
local placed_count = 0
local place_model = poi_preset.PlaceModel
local skip_raster = place_model ~= "terrain"
local place_mark = place_model ~= "point"
local dist_to_same = dist_to_same_m / type_tile
local prefab, idx, radius, max_radius, poi_radius
GridRandomFetchMarkDist(dist_grid, rand(), dist_tile, function(x, y, dist)
if idx then
if x < 0 then
table.remove(prefabs, idx)
else
local ptype = ptype_single
if not ptype then
local ptype_idx = ptype_grid:get(x, y)
ptype = idx_to_ptype[ptype_idx]
if not ptype or not ptypes_map[ptype] then
ptype = prefabs[prefab]
end
end
assert(prefab_to_ptype_map[prefab][ptype])
local count, remaining = prefab_add(prefab_list_poi, prefab, x, y, radius, false, ptype, true, skip_raster)
if (prefab.max_count or -1) == count or remaining <= 0 then
table.remove(prefabs, idx)
end
if place_mark then
poi_marks[#poi_marks + 1] = { poi_type, x, y, radius }
end
placed_count = placed_count + 1
total_count = total_count + 1
if debug then
if dump then
dump("POI %4d | rand 0x%016X | pos (%4d, %4d) | '%s'", placed_count, rand_seed, x, y, prefab_markers[prefab])
end
if step_poi then
local mx, my = x * work_step, y * work_step
DbgShowPrefab(point(mx, my), prefab_markers[prefab], cyan, total_count, radius, poi_radius)
end
end
if placed_count == partial_count or total_count == max_count then
return
end
end
end
prefab, idx = trand(prefabs, poi_weight)
if not prefab then
return
end
radius = get_radius(prefab)
poi_radius = radius + dist_to_same
return radius * dist_prec, poi_radius * dist_prec
end)
poi_count[poi_type] = total_count
if debug then
local poi_name = poi_area and (poi_type .. "." .. poi_area) or poi_type
dbg_poi_count = dbg_poi_count or {}
dbg_poi_count[poi_name] = (dbg_poi_count[poi_name] or 0) + placed_count
local types_str = table.concat(ptypes, ",")
if step_poi then
step("POI %s END {%s}: %2d prefab(s) placed", poi_type, types_str, total_count)
end
if dump then
dump("\nPOI '%s' END {%s} PLACED %d TOTAL\n", poi_type, types_str, placed_count, total_count)
end
end
return placed_count
end
for _, prefab in ipairs(prefab_markers) do
local poi_type = prefab.poi_type or ""
if poi_type ~= "" then
local poi_preset = poi_type_to_preset[poi_type]
if not poi_preset then
g_print("No such POI type", poi_type, "selected in", prefab_markers[prefab])
else
local ptype_map
if #poi_preset.CustomTypes > 0 then
ptype_map = table.invert(poi_preset.CustomTypes)
elseif #poi_preset.PrefabTypeGroups > 0 then
local group = table.find_value(poi_preset.PrefabTypeGroups, "id", prefab.poi_area) or empty_table
ptype_map = table.invert(group.types)
else
ptype_map = {[prefab.type] = true}
end
assert(next(ptype_map))
prefab_to_ptype_map[prefab] = ptype_map
local all_ptypes = poi_type_to_ptypes[poi_type]
if not all_ptypes then
poi_type_to_ptypes[poi_type] = ptype_map
else
table.append(all_ptypes, ptype_map)
end
for ptype in pairs(ptype_map) do
if ptype_to_idx[ptype] then
ptype_to_poi_prefabs[ptype] = table.create_add_unique(ptype_to_poi_prefabs[ptype], prefab)
if not poi_types[poi_type] then
poi_types[poi_type] = poi_preset
poi_types[#poi_types + 1] = poi_type
end
end
end
end
end
end
local PoiCmp = PrefabPOI.Compare
table.sort(poi_types, function (poi1, poi2)
local preset1, preset2 = poi_types[poi1], poi_types[poi2]
return PoiCmp(preset1, preset2)
end)
if dump then
dump("\n----\nPersisted prefabs loaded: %d", #(persisted_prefabs or ""))
dump("Persistable tag counters: %s\n", TableToLuaCode(persisted_tag_count))
end
DbgClear()
local persisted_placed = 0
for i, entry in ipairs(persisted_prefabs) do
local name, ptype, x, y, angle = unpack(entry)
local prefab = prefab_markers[name]
local poi_type = prefab.poi_type or ""
local poi_preset = poi_types[poi_type]
if not poi_preset then
g_print("Persisted prefab", name, "has invalid POI type", poi_type)
else
if not prefab_to_ptype_map[prefab][ptype] then
g_print("Persisted prefab", name, "has invalid prefab type", ptype)
end
get_radius = radius_getters[poi_preset.RadiusEstim]
local radius = get_radius(prefab)
local place_model = poi_preset.PlaceModel
local skip_raster = place_model ~= "terrain"
local place_mark = place_model ~= "point"
if place_mark then
poi_marks[#poi_marks + 1] = { poi_type, x, y, radius }
end
local show
for tag in pairs(poi_preset.Tags) do
for other_tag, limits in pairs(tag_to_tag_limits[tag]) do
local min_dist, max_dist = limits[1], limits[2]
for _, loc in ipairs(prefab_tag_loc[other_tag]) do
local xi, yi, radiusi = point_unpack(loc)
local dx, dy = x - xi, y - yi
local d = (sqrt(dx*dx + dy*dy) - radius - radiusi) * type_tile
local is_err
if min_dist and min_dist >= 0 and d < min_dist then
g_print("Persisted prefab", i, name, "is too close to tag", other_tag, "(", d, "/", min_dist, ")")
is_err = true
end
if max_dist and max_dist >= 0 and d > max_dist then
g_print("Persisted prefab", i, name, "is too far from tag", other_tag, "(", d, "/", max_dist, ")")
is_err = true
end
if is_err then
show = true
local pos = point(x, y) * type_tile
local posi = point(xi, yi) * type_tile
local r = radius * type_tile
local ri = radiusi * type_tile
DbgAddCircle(posi, ri, red)
DbgAddSegment(pos, posi, yellow)
if dump then
dump("DbgClear(); DbgAddCircle(%s, %d); DbgAddCircle(%s, %d, red); DbgAddSegment(%s, %s, yellow); ViewPos(%s, %d)\n",
ValueToLuaCode(pos), r,
ValueToLuaCode(posi), ri,
ValueToLuaCode(pos), ValueToLuaCode(posi),
ValueToLuaCode((pos + posi) / 2), pos:Dist2D(posi) + ri + r)
end
end
end
end
end
if show then
local pos = point(x, y) * type_tile
DbgAddText(string.format("[%d] %s", i, name), ValidateZ(pos):AddZ(101*guim))
DbgAddVector(pos, 100*guim)
DbgAddCircle(pos, radius * type_tile)
end
local total_count = poi_count[poi_type] or 0
total_count = total_count + 1
poi_count[poi_type] = total_count
prefab_add(prefab_list_poi, prefab, x, y, radius, false, ptype, true, skip_raster, angle)
persisted_placed = persisted_placed + 1
if debug then
if dump then
dump("PERSIST %3d | pos (%4d, %4d) | angle %6d | '%s'", persisted_placed, x, y, angle, name)
end
if step_poi then
local mx, my = x * work_step, y * work_step
DbgShowPrefab(point(mx, my), name, red, total_count, radius, poi_radius)
end
end
end
end
for _, poi_type in ipairs(poi_types) do
local poi_preset = poi_types[poi_type]
local poi_max_count = -1
if poi_preset.MaxCount ~= -1 then
poi_max_count = rand(Max(poi_preset.MinCount, 0), poi_preset.MaxCount)
end
local orig_max_count = poi_max_count
get_radius = radius_getters[poi_preset.RadiusEstim]
local custom_types = poi_preset.CustomTypes
local type_groups = poi_preset.PrefabTypeGroups
if dump then
dump("\n----\nPOITYPE '%s' %s", poi_type, TableToLuaCode(poi_preset))
end
if #custom_types > 0 then
PlacePois(poi_type, false, custom_types, poi_max_count, orig_max_count)
elseif #type_groups > 0 then
local total_area = 0
if poi_max_count ~= -1 then
for _, group in ipairs(type_groups) do
for _, ptype in ipairs(group.types) do
total_area = total_area + (ptype_to_area[ptype] or 0)
end
end
end
for i, group in ipairs(type_groups) do
local count = poi_max_count
if total_area > 0 and count ~= -1 and i ~= #type_groups then
local group_area = 0
for _, ptype in ipairs(group.types) do
group_area = group_area + (ptype_to_area[ptype] or 0)
end
count = MulDivRound(poi_max_count, group_area, total_area)
total_area = total_area - group_area
end
count = PlacePois(poi_type, group.id, group.types, count, orig_max_count)
if count and poi_max_count ~= -1 then
poi_max_count = poi_max_count - count
end
end
else
local ptype_map = poi_type_to_ptypes[poi_type]
local ptypes = table_keys(ptype_map, true)
local total_area = 0
if poi_max_count ~= -1 then
for _, ptype in ipairs(ptypes) do
total_area = total_area + (ptype_to_area[ptype] or 0)
end
end
for i, ptype in ipairs(ptypes) do
local count = poi_max_count
if total_area > 0 and count ~= -1 and i ~= #ptypes then
local ptype_area = (ptype_to_area[ptype] or 0)
count = MulDivRound(poi_max_count, ptype_area, total_area)
poi_max_count = poi_max_count - count
total_area = total_area - ptype_area
end
count = PlacePois(poi_type, false, { ptype }, count, orig_max_count)
if count and poi_max_count ~= -1 then
poi_max_count = poi_max_count - count
end
end
end
local placed_count = poi_count[poi_type] or 0
if placed_count < poi_preset.MinCount then
g_print("Not all", poi_type, "prefabs are placed:", placed_count, "/", poi_preset.MinCount)
end
end
poi_time = GetPreciseTicks() - start_time_poi
WaitRaster(prefab_list_poi)
if debug then
if step_poi then
DbgShowTerrainGrid(false)
end
local tmp = {}
for name, count in pairs(dbg_poi_count) do
tmp[#tmp + 1] = {name = name, count = count}
end
self.dbg_placed_poi = tmp
end
end
WaitPlaceAndRasterPois()
free_grid(dist_grid)
free_grid(dist_mask)
placed_marks = GridLevels(mark_grid)
if debug then
self.LocateTime = locate_time
self.RasterizeTime = raster_time
self.PoiTime = poi_time
self.PrefabCount = #prefab_list
assert(add_idx == #prefab_list)
local list = {}
local prefab_hash
for i, info in ipairs(prefab_list) do
local prefab, raster = unpack(info)
list[i] = { prefab_markers[prefab], raster.pos }
if debug then
prefab_hash = xxhash(prefab.hash, prefab_hash)
end
end
self.PrefabHash = prefab_hash
self.MixHash = xxhash(ptype_grid)
self.MarkHash = xxhash(mark_grid)
self.PrefabList = list
self.RasterMem = peak_memory
local min, max = GridMinMax(overlap_grid)
local all = GridCount(overlap_grid, 0, max_int)
local overlap = GridCount(overlap_grid, 1, max_int)
self.OverlapMax = max
self.OverlapPct = to_pct(overlap, all)
local prefabs_to_raster = 0
local visible_prefabs = {}
local tmp = {}
for idx, info in ipairs(prefab_list) do
local prefab, raster = unpack(info)
if raster.place_mask_idx then
prefabs_to_raster = prefabs_to_raster + 1
local visible_area = Min(placed_marks[idx] or 0, prefab.total_area) -- not accurate due to prefab rotation
assert(visible_area <= prefab.total_area)
local completely_hidden = visible_area == 0 and 1 or 0
local stat = tmp[prefab] or {}
stat.area = (stat.area or 0) + visible_area
stat.hidden = (stat.hidden or 0) + completely_hidden
stat.count = (stat.count or 0) + 1
tmp[prefab] = stat
end
end
for prefab, stat in pairs(tmp) do
local max_area = prefab.total_area * stat.count
assert(max_area > 0 and stat.area * work_ratio <= max_area)
visible_prefabs[#visible_prefabs + 1] = {
name = prefab_markers[prefab],
visible_area = max_area > 0 and MulDivRound(pct_100, stat.area * work_ratio, max_area) or 0,
fully_hidden = MulDivRound(pct_100, stat.hidden, stat.count),
}
end
self.dbg_visible_prefabs = visible_prefabs
self.PrefabVisible = prefabs_to_raster > 0 and to_pct(table.count(placed_marks), prefabs_to_raster) or 0
local mix_area = gw * gh
local area_spill, area_uncovered = GridGetCover(mark_grid, ptype_grid, cover_grid)
self.AreaUncovered = to_pct(area_uncovered, mix_area)
self.AreaSpill = to_pct(area_spill, mix_area)
local tmp = {}
local w, h = ptype_grid:size()
for ptype, area in pairs(ptype_to_area) do
local stats = {
name = ptype,
area = pct_mul * 100 * area / (w * h),
prefabs = table.count(prefab_list, PREFAB_TYPE, ptype)
}
tmp[#tmp + 1] = stats
end
self.dbg_prefab_types = tmp
end
end
local function PlacePrefabObjects()
if not gen_mode.Objects then
return
end
local start_time_po = GetPreciseTicks()
rand_init("PlacePrefabObjects")
local IsValidPos = CObject.IsValidPos
local GetClassFlags = CObject.GetClassFlags
local SetGameFlags = CObject.SetGameFlags
local GetGameFlags = CObject.GetGameFlags
local SetCollectionIndex = CObject.SetCollectionIndex
local ClearCachedZ = CObject.ClearCachedZ
local GridGetMark = GridGetMark
local unpack = table.unpack
local g_Classes = g_Classes
local IsValid = IsValid
local handle_provider = empty_func
if self.gen_handles then
local first_handle, handle_size = GetHandlesAutoLimits()
local first_handle_pool, handle_pool_size, handle_pool = GetHandlesAutoPoolLimits()
local last_handle = first_handle + handle_size - 1
local last_handle_pool = first_handle_pool + handle_pool_size - handle_pool
local system_handles = 1000
local start_marker_handle = first_handle
local next_handle = first_handle + system_handles + 1
local next_handle_pool = first_handle_pool
local handle_collisions = 0
local handle_to_prefab = {}
local handle_to_object = HandleToObject
local IsKindOf = IsKindOf
handle_provider = function(current_prefab, classname, reserved_handles, backwards)
if not reserved_handles then
local classdef = classname and g_Classes[classname]
if not classdef or not IsKindOf(classdef, "Object") then
return
end
reserved_handles = classdef.reserved_handles
end
local handle
if not reserved_handles or reserved_handles == 0 then
if last_handle <= next_handle then
assert(false, "No more handles!")
return false, "handles"
elseif backwards then
handle = last_handle
last_handle = handle - 1
else
handle = next_handle
next_handle = handle + 1
end
else
if last_handle_pool <= next_handle_pool then
assert(false, "No more handle pools!")
return false, "handles"
elseif backwards then
handle = last_handle_pool
last_handle_pool = handle - handle_pool
else
handle = next_handle_pool
next_handle_pool = handle + handle_pool
end
end
assert(handle > 0)
local existing_obj = handle_to_object[handle]
if IsValid(existing_obj) then
handle_collisions = handle_collisions + 1
if handle_collisions == 100 then
assert(false, "Blank map used for generation isn't empty!")
return false, "map"
end
if backwards then
return false, "collision"
end
if handle_collisions < 100 and classname then
local prev_prefab = handle_to_prefab[handle]
g_print("Duplicated handle", handle, "\nNew object is", classname, "from", prefab_markers[current_prefab], "\nExisting object is", existing_obj.class, "from", prefab_markers[prev_prefab] or "map", "\n")
end
local new_handle, handle_err
if handle - start_marker_handle > system_handles then
while true do
new_handle, handle_err = handle_provider(current_prefab, existing_obj.class, existing_obj.reserved_handles, true)
if handle_err ~= "collision" then
break
end
end
end
if new_handle then
existing_obj.handle = new_handle
handle_to_object[handle] = nil
handle_to_object[new_handle] = existing_obj
else
g_print("Replacing existing object", existing_obj.class, existing_obj.handle, "by", classname)
DoneObject(existing_obj)
end
end
handle_to_prefab[handle] = current_prefab
return handle
end
end
local placed_objects, object_source = {}, {}
local obj_count = 0
local game_flags = gofPermanent | gofGenerated
local function PlacedObject(obj, mark, prefab)
mark = mark or 0
local prev_mark = placed_objects[obj]
assert(not prev_mark or prev_mark == mark)
if prev_mark == mark then return end
placed_objects[obj] = mark or 0
object_source[obj] = prefab
obj_count = obj_count + 1
SetGameFlags(obj, game_flags)
--bp(obj:IsEqualPos2D(1185431, 1650466))
end
--[[
if dump then
local orig_PlacedObject = PlacedObject
PlacedObject = function(obj, ...)
orig_PlacedObject(obj, ...)
local x, y, z = obj:GetVisualPosXYZ()
dump("OBJECT %4d: pos (%7d, %7d, %7d) | angle %7d | '%s'", obj_count, x, y, z, obj:GetAngle(), obj.class)
end
end
--]]
local max_chance = 100 * 1000
local optional_chance = self.OptionalChance * 1000
local steep_slope_cos = cos(self.SteepSlope)
local rem_faded_objs = self.RemFadedObjs
local use_mesh_overlap = self.UseMeshOverlap
local removed_count = 0
local RemoveObject = DoneObject
local SkippedObject = empty_func
if debug then
RemoveObject = function(obj)
DoneObject(obj)
removed_count = removed_count + 1
end
SkippedObject = function()
removed_count = removed_count + 1
end
end
local OVRLP_DEL_NONE, OVRLP_DEL_ALL, OVRLP_DEL_IGNORE, OVRLP_DEL_PARTIAL, OVRLP_DEL_SINGLE = false, 0, 1, 2, 3
local rem_coll_count = 0
local obj_to_coll, coll_to_objs, coll_indice, coll_is_partial, removed_coll = {}, {}, {}, {}, {}
local function RemoveColl(idx, nested_colls)
if removed_coll[idx] then
return
end
removed_coll[idx] = true
if not nested_colls then
return
end
for _, sub_idx in ipairs(nested_colls[idx]) do
RemoveColl(sub_idx, nested_colls)
end
end
local prefab_defs = {}
local class_to_defaults = {}
local last_col_idx, placed_collections = 0, 0
local collections = Collections
local rmfOptionalPlacement = const.rmfOptionalPlacement
local rmfMeshOverlapCheck = const.rmfMeshOverlapCheck
local rmfDeleteOnSteepSlope = const.rmfDeleteOnSteepSlope
local cofComponentRandomMap = const.cofComponentRandomMap
local max_collection_idx = const.GameObjectMaxCollectionIndex
local base_prop_count = const.RandomMap.PrefabBasePropCount
local GetPrefabFileObjs = GetPrefabFileObjs
local AsyncFileToString = async.AsyncFileToString -- avoid yielding
local Unserialize = Unserialize
local GetDefRandomMapFlags = GetDefRandomMapFlags
local GetPrefabObjPos = GetPrefabObjPos
local SetPrefabObjPos = SetPrefabObjPos
local PropObjSetProperty = PropObjSetProperty
local SetRandomMapFlags = CObject.SetRandomMapFlags
local selected_break = self.SelectedBreak and self.SelectedMark or 0
local abort_prefab_placement
local function PlacePrefab(prefab, prefab_pos, prefab_angle, mark, ptype, bbox)
if abort_prefab_placement then
return
end
--assert((prefab.revision or 0) <= revision)
--assert((prefab.version or 1) <= version)
assert(prefab_pos:IsValidZ())
if dump then
local x, y = prefab_pos:xy()
dump("PlacePrefab %4d | pos (%7d, %7d) | angle %7d | '%s' ----------------------", mark, x, y, prefab_angle, prefab_markers[prefab])
end
mark = mark_grid and mark or 0
if selected_break > 0 then
bp(selected_break == mark)
end
local ptype_preset = ptype_to_preset[ptype] or empty_table
local on_obj_overlap = ptype_preset.OnObjOverlap
local ignore_colls = on_obj_overlap == OVRLP_DEL_IGNORE
local ignore_partial_colls = on_obj_overlap == OVRLP_DEL_PARTIAL
local delete_no_colls = on_obj_overlap == OVRLP_DEL_SINGLE
local objs
local nested_colls = prefab.nested_colls
local nested_opt_objs = prefab.nested_opt_objs
local save_collections = prefab.save_collections
local defs = prefab_defs[prefab]
if defs == nil then
local load_time_start = GetPreciseTicks()
local name = prefab_markers[prefab]
if not exported_prefabs[name] then
g_print("no such exported prefab", name)
return
end
local filename = GetPrefabFileObjs(name)
local err, bin = AsyncFileToString(nil, filename, nil, nil, "pstr")
if err then
g_print("failed to load prefab", name, ":", err)
return
end
defs = Unserialize(bin)
if not defs then
g_print("failed to unserialize objects from prefab", name)
return
end
prefab_defs[prefab] = (prefabs_count[prefab] or max_int) > 1 and defs or false
if debug then
AddPrefabStat(prefab, "obj_load_time", GetPreciseTicks() - load_time_start)
end
end
if not defs then
g_print("Uncached prefab", prefab_markers[prefab])
return
end
local place_time_start = GetPreciseTicks()
objs = {}
for _, def in ipairs(defs) do
do
local class, dpos, angle, daxis,
scale, rmf_flags, fade_dist,
ground_offset, normal_offset,
coll_idx, color, mirror = unpack(def, 1, base_prop_count)
assert(class ~= "Collection")
assert(not coll_idx or coll_idx ~= 0)
local classdef, entity, default_rmf_flags
local defaults = class_to_defaults[class]
if defaults then
classdef, entity, default_rmf_flags = unpack(defaults)
else
classdef = g_Classes[class]
assert(classdef)
if classdef then
default_rmf_flags = GetDefRandomMapFlags(classdef)
entity = classdef.entity or class
end
class_to_defaults[class] = {classdef, entity, default_rmf_flags}
end
if not classdef then
goto continue
end
rmf_flags = rmf_flags or default_rmf_flags
if optional_chance > 0 and (rmf_flags & rmfOptionalPlacement) ~= 0 then
local reduction = coll_idx ~= 0 and nested_opt_objs and nested_opt_objs[coll_idx] or 1
local chance = reduction > 1 and Max(1, optional_chance / reduction) or optional_chance
if crand(chance, max_chance) then
if coll_idx then
RemoveColl(coll_idx, nested_colls)
end
SkippedObject()
goto continue
end
end
local check_mark
if on_obj_overlap then
check_mark = bbox and mark
if coll_idx then
if removed_coll[coll_idx] then
SkippedObject()
goto continue
end
if ignore_partial_colls or delete_no_colls then
check_mark = nil
end
end
end
local mesh_overlap = use_mesh_overlap and (rmf_flags & rmfMeshOverlapCheck) ~= 0
local max_slope = (rmf_flags & rmfDeleteOnSteepSlope) ~= 0 and steep_slope_cos
local new_pos, new_angle, new_axis, mark_found = GetPrefabObjPos(
dpos, angle, daxis,
rem_faded_objs and fade_dist,
prefab_pos, prefab_angle,
ground_offset, normal_offset,
mark_grid, check_mark,
mesh_overlap, entity, scale, mirror,
max_slope)
if not new_pos then
if not ignore_colls and coll_idx then
RemoveColl(coll_idx, nested_colls)
end
SkippedObject()
goto continue
end
local handle, err = handle_provider(prefab, class, false, true)
local components = 0
if rmf_flags ~= default_rmf_flags then
components = components | cofComponentRandomMap
end
local obj = classdef:new{handle = handle}
if not IsValid(obj) then
g_print("Aborting object placement after a failure to create an object of type", class)
abort_prefab_placement = true
break
end
SetPrefabObjPos(obj, new_pos, new_angle, new_axis, scale, color, mirror)
for i=base_prop_count+1,#def,2 do
PropObjSetProperty(obj, def[i], def[i + 1])
end
if rmf_flags ~= default_rmf_flags then
SetRandomMapFlags(obj, rmf_flags)
end
objs[#objs + 1] = obj
if coll_idx then
obj_to_coll[obj] = coll_idx
local coll_objs = coll_to_objs[coll_idx]
if coll_objs then
coll_objs[#coll_objs + 1] = obj
else
coll_indice[#coll_indice + 1] = coll_idx
coll_to_objs[coll_idx] = { obj }
end
if ignore_partial_colls and not coll_is_partial[coll_idx] then
if bbox and mark_found ~= mark then
-- all seen objects from that collection are outside the prefab
goto continue
end
-- mark the collection as partial as at least one object is inside
coll_is_partial[coll_idx] = true
end
end
PlacedObject(obj, mark, prefab)
end
::continue::
end
if debug then
AddPrefabStat(prefab, "obj_place_time", GetPreciseTicks() - place_time_start)
end
if ignore_partial_colls then
if #coll_indice > 0 then
for _, coll_idx in ipairs(coll_indice) do
local coll_objs = coll_to_objs[coll_idx]
if coll_is_partial[coll_idx] then
-- at least on object from the collection is inside the prefab
for _, obj in ipairs(coll_objs) do
PlacedObject(obj, mark, prefab)
end
else
-- all objects from these collections are outside their prefabs
RemoveColl(coll_idx, nested_colls)
for _, obj in ipairs(coll_objs) do
RemoveObject(obj)
end
end
end
end
end
if save_collections then
for _, coll_idx in ipairs(coll_indice) do
local coll_objs = not removed_coll[coll_idx] and coll_to_objs[coll_idx] or ""
local count = #coll_objs
for i = count, 1, -1 do
if not IsValid(coll_objs[i]) then
coll_objs[i] = coll_objs[count]
coll_objs[count] = nil
count = count - 1
end
end
if count > 0 then
local col_idx = last_col_idx + 1
while collections[col_idx] do
col_idx = col_idx + 1
end
last_col_idx = col_idx
assert(col_idx <= max_collection_idx)
if col_idx > max_collection_idx then
g_print("max collections reached!", max_map_size)
else
local col = Collection:new{ Index = col_idx }
col:SetName(string.format("MapGen_%d", col_idx))
collections[col_idx] = col
PlacedObject(col)
for i=1,count do
SetCollectionIndex(coll_objs[i], col_idx)
end
placed_collections = placed_collections + 1
end
end
end
end
local has_removed_colls = next(removed_coll)
for _, obj in ipairs(objs) do
if IsValid(obj) then
local coll_idx = has_removed_colls and obj_to_coll[obj]
if coll_idx and removed_coll[coll_idx] then
-- delete placed objects from removed collections
RemoveObject(obj)
elseif obj.__ancestors.Object then
obj:PostLoad()
end
end
end
if has_removed_colls then
rem_coll_count = rem_coll_count + table.count(removed_coll)
removed_coll = {}
end
if #coll_indice > 0 then
coll_indice, coll_to_objs = {}, {}
end
if next(obj_to_coll) then obj_to_coll = {} end
if next(coll_is_partial) then coll_is_partial = {} end
return objs
end
-- place all prefab objects:
for mark, info in ipairs(prefab_list) do
local prefab, raster, add_idx, ptype, bbox = unpack(info)
assert(raster.place_idx == mark)
if not bbox or not placed_marks or placed_marks[mark] then
PlacePrefab(prefab, raster.pos, raster.angle, mark, ptype, bbox)
if step_prefab then
DbgClear()
local name = prefab_markers[prefab]
DbgShowPrefab(raster.pos, name, white, mark, prefab.min_radius, prefab.max_radius)
step("Objects %d %s", add_idx, name)
end
end
end
if mark_grid then
MapForEach(bx_changes or "map", "attached", false, nil, nil, gofPermanent, function(obj)
--bp(obj:IsEqualPos2D(1185431, 1650466))
local current_mark = GridGetMark(mark_grid, obj)
if current_mark == 0 then
-- existing map object outside the prefab zone
ClearCachedZ(obj)
elseif not placed_objects[obj] then
-- existing map object inside the prefab zone
DoneObject(obj)
end
end)
end
MapForEach(bx_changes or "map", "attached", false, "EditorCallbackObject", nil, nil, gofPermanent | gofGenerated, function(obj, ...)
obj:EditorCallbackGenerate(...)
end, self, object_source, placed_objects, prefab_list)
if debug then
self.ObjectTime = GetPreciseTicks() - start_time_po
self.ObjectCount = obj_count
self.RemObjects = removed_count
self.RemColls = rem_coll_count
self.PlacedColls = placed_collections
local class_to_count = {}
for obj in pairs(placed_objects) do
if IsValid(obj) then
class_to_count[obj.class] = (class_to_count[obj.class] or 0) + 1
end
end
local tmp = {}
local total_count = 0
for class, count in pairs(class_to_count) do
total_count = total_count + count
end
for class, count in pairs(class_to_count) do
local pct = total_count > 0 and MulDivRound(pct_100, count, total_count) or 0
tmp[#tmp + 1] = {count = count, class = class, pct = pct}
end
self.dbg_placed_objects = tmp
self.dbg_obj_to_prefab_mark = placed_objects
end
--DbgAddTerrainRect(bx_changes, red)
end
FindAndRasterPrefabs()
local orig_rand = HandleRand
local handle_seed = rand_init("HandleRand")
HandleRand = function(range)
range, handle_seed = BraidRandom(handle_seed, range)
return range
end
PlacePrefabObjects()
HandleRand = orig_rand
if self.SavePrefabLoc then
mapdata.PersistedPrefabs = prefabs_to_persist
if dump then
dump("\n----\nPersisted prefabs saved: %d", #(prefabs_to_persist or ""))
for i, entry in ipairs(prefabs_to_persist) do
local name, ptype, x, y, angle = unpack(entry)
dump("%3d | pos (%4d, %4d) | angle %6d | '%s'", i, x, y, angle, name)
end
end
end
if height_out_of_lims then
g_print("The resulting map height is outside the allowed range!")
end
if debug then
self.FirstRand = start_seed
self.LastRand = rand_seed
local unpack = table.unpack
local tmp = {}
for prefab, stat in pairs(prefab_stats) do
local counters = prefab_stat_count[prefab]
local avg_stats = {
name = prefab_markers[prefab],
count = prefabs_count[prefab],
objs = prefab.obj_count,
grid = sqrt(prefab.total_area),
}
local sum = 0
for name, value in pairs(stat) do
sum = sum + value
avg_stats[name] = value / counters[name]
end
avg_stats.impact = sum
tmp[#tmp + 1] = avg_stats
end
self.dbg_placed_prefabs = tmp
if dump then
dump("\n\n\nUsed Prefabs Meta:\n")
local names = {}
for prefab, count in pairs(prefabs_count) do
local name = prefab_markers[prefab]
names[#names + 1] = name
names[name] = prefab
end
table.sort(names)
for _, name in ipairs(names) do
local prefab = table.copy(names[name])
prefab.marker = nil -- too mush spam here
dump("%20s: %s\n", name, TableToLuaCode(prefab))
end
dump("\n\n\nResults:\n")
for i, prop in ipairs(self:GetProperties()) do
if prop.log then
local name = prop.name or prop.id
local value = tostring(self:GetProperty(prop.id))
if prop.editor == "text" then
dump("\n%s:", name)
dump(value)
else
dump("%20s: %s", name, value)
end
end
end
end
end
self:DbgUpdate()
self:DbgOnModified()
end