|
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") |
|
|
|
|
|
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 |
|
|
|
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 |
|
local _place_x, _place_y, _radius_target, _radius_range |
|
|
|
local radius_getters = PrefabRadiusEstimators() |
|
local get_radius |
|
|
|
local repeat_weights = {} |
|
local function prefab_weight(prefab) |
|
local weight = MulDivRound(unit_weight, prefab.weight, 100) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
weight = MulDivWeight(weight, prefab.min_radius, prefab.max_radius, _overlap_reduct) |
|
local radius = get_radius(prefab) |
|
local radius_err = abs(_radius_target - radius) |
|
weight = MulDivWeight(weight, _radius_range - radius_err, _radius_range, _fit_effort) |
|
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((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 |
|
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() |
|
|
|
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) |
|
|
|
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 |
|
_fit_effort = ptype_preset.FitEffort > 0 and 2 ^ (ptype_preset.FitEffort - 1) or 0 |
|
|
|
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 |
|
|
|
local ptype_idx = ptype_to_idx[ptype] |
|
local mix_grid_idx = respect_bounds and ptype_idx |
|
local min_fill_ratio = ptype_preset.MinFillRatio |
|
local max_fill_error = ptype_preset.MaxFillError |
|
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) |
|
_place_x, _place_y = x, y |
|
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 |
|
|
|
|
|
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) |
|
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) |
|
|
|
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 |
|
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_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 |
|
|
|
goto continue |
|
end |
|
|
|
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 |
|
|
|
for _, obj in ipairs(coll_objs) do |
|
PlacedObject(obj, mark, prefab) |
|
end |
|
else |
|
|
|
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 |
|
|
|
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 |
|
|
|
|
|
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) |
|
|
|
local current_mark = GridGetMark(mark_grid, obj) |
|
if current_mark == 0 then |
|
|
|
ClearCachedZ(obj) |
|
elseif not placed_objects[obj] then |
|
|
|
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 |
|
|
|
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 |
|
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 |