Download CommonLua/MapGen/BiomeFiller.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 73 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/MapGen/BiomeFiller.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/MapGen/BiomeFiller.lua
-
curl -L -o BiomeFiller.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/MapGen/BiomeFiller.lua
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 |