|
local height_scale = const.TerrainHeightScale |
|
local height_tile = const.HeightTileSize |
|
local height_max = const.MaxTerrainHeight |
|
local type_tile = const.TypeTileSize |
|
local developer = Platform.developer |
|
local unity = 1000 |
|
|
|
local function print_concat(tbl) |
|
return table.concat(tbl, " ") |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapExport = { |
|
__parents = { "GridOpOutput" }, |
|
GridOpType = "Map Export", |
|
operations = {"Type", "Height", "Biome", "Grass", "Water"} |
|
} |
|
|
|
function GridOpMapExport:GetGridOutput(state) |
|
local grid |
|
local op = self.Operation |
|
if op == "Height" then |
|
grid = terrain.GetHeightGrid() |
|
elseif op == "Type" then |
|
grid = terrain.GetTypeGrid() |
|
elseif op == "Grass" then |
|
grid = terrain.GetGrassGrid() |
|
elseif op == "Water" then |
|
grid = terrain.GetWaterGrid() |
|
elseif op == "Biome" then |
|
grid = BiomeGrid:clone() |
|
end |
|
if not grid then |
|
return "Export Grid Failed" |
|
end |
|
return nil, grid |
|
end |
|
|
|
function GridOpMapExport:GetEditorText() |
|
return "Export <Operation> to <GridOpName><OutputName></GridOpName>" |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapImport = { |
|
__parents = { "GridOpInput" }, |
|
properties = { |
|
{ category = "General", id = "TextureParam", name = "Texture Param", editor = "choice", default = "", items = GridOpParams, grid_param = true, optional = true, operation = "Type" }, |
|
{ category = "General", id = "TextureType", name = "Texture Type", editor = "choice", default = "", items = GetTerrainNamesCombo(), use_param = "TextureParam", operation = "Type" }, |
|
{ category = "General", id = "TexturePreview", name = "Texture Preview", editor = "image", default = false, img_size = 128, img_box = 1, dont_save = true, base_color_map = true, operation = "Type", no_edit = function(self) return self.TextureType == "" or self.UseParams and self.TextureParam ~= "" end }, |
|
{ category = "General", id = "Alpha", name = "Alpha", editor = "number", default = unity, min = 0, max = unity, slider = true, scale = unity, operation = "Type" }, |
|
{ category = "General", id = "Contrast", name = "Contrast", editor = "number", default = 0, min = -unity/2, max = unity/2, slider = true, scale = unity, operation = "Type" }, |
|
{ category = "General", id = "Normalize", name = "Normalize", editor = "bool", default = false, operation = {"Height", "Color"} }, |
|
{ category = "General", id = "HeightMin", name = "Height Min (m)", editor = "number", default = 0, scale = guim, min = 0, max = height_max, slider = true, operation = "Height", enabled_by = "Normalize" }, |
|
{ category = "General", id = "HeightMax", name = "Height Max (m)", editor = "number", default = height_max, scale = guim, min = 0, max = height_max, slider = true, operation = "Height", enabled_by = "Normalize" }, |
|
{ category = "General", id = "ColorRed", name = "Red", editor = "number", default = 0, min = -unity, max = unity, scale = unity, slider = true, operation = "Color" }, |
|
{ category = "General", id = "ColorGreen", name = "Green", editor = "number", default = 0, min = -unity, max = unity, scale = unity, slider = true, operation = "Color" }, |
|
{ category = "General", id = "ColorBlue", name = "Blue", editor = "number", default = 0, min = -unity, max = unity, scale = unity, slider = true, operation = "Color" }, |
|
{ category = "General", id = "ColorAlpha", name = "Alpha", editor = "number", default = unity, min = 0, max = unity, scale = unity, slider = true, operation = "Color" }, |
|
{ category = "General", id = "MaskMin", name = "Mask Min", editor = "number", default = 0, scale = unity, operation = "Color", }, |
|
{ category = "General", id = "MaskMax", name = "Mask Max", editor = "number", default = 100 * unity, scale = unity, operation = "Color", }, |
|
|
|
}, |
|
GridOpType = "Map Import", |
|
operations = {"Type", "Height", "Biome", "Grass", "Color"}, |
|
} |
|
|
|
function GridOpMapImport:CollectTags(tags) |
|
tags.Terrain = true |
|
return GridOp.CollectTags(self, tags) |
|
end |
|
|
|
function GridOpMapImport:SetGridInput(state, grid) |
|
local success, err |
|
local op = self.Operation |
|
if op == "Height" then |
|
if not self.Normalize then |
|
local min, max = GridMinMax(grid) |
|
if min < 0 or max * height_scale > height_max then |
|
return "Height Limits Exceeded" |
|
end |
|
success, err = terrain.ImportHeightMap(grid) |
|
else |
|
success, err = terrain.ImportHeightMap(grid, self.HeightMin, self.HeightMax) |
|
end |
|
terrain.InvalidateHeight() |
|
elseif op == "Type" then |
|
local type_idx |
|
local type_name = self:GetValue("TextureType") or "" |
|
if type_name ~= "" then |
|
type_idx = GetTerrainTextureIndex(type_name) |
|
if not type_idx then |
|
return "No such terrain type: " .. type_name |
|
end |
|
end |
|
if not type_idx then |
|
err = terrain.SetTypeGrid(grid) |
|
success = not err |
|
else |
|
success = terrain.ImportTypeDithered{ |
|
grid = GridRepack(grid, "F"), |
|
seed = state.rand, |
|
type = type_idx, |
|
gamma_mul = unity - self.Contrast, |
|
gamma_div = unity + self.Contrast, |
|
alpha_mul = self.Alpha, |
|
alpha_div = unity, |
|
} |
|
end |
|
terrain.InvalidateType() |
|
elseif op == "Biome" then |
|
BiomeGrid:copy(grid) |
|
success = true |
|
elseif op == "Grass" then |
|
success = terrain.SetGrassGrid(grid) |
|
elseif op == "Color" then |
|
local min, max = self.MaskMin, self.MaskMax |
|
local gmin, gmax = GridMinMax(grid, unity) |
|
if self.Normalize then |
|
min, max = gmin, gmax |
|
elseif min > gmin or max < gmax then |
|
return "Mask Limits Exceeded" |
|
end |
|
success = GridSetTerrainColor(grid, self.ColorRed, self.ColorGreen, self.ColorBlue, min, max, unity, self.ColorAlpha) |
|
end |
|
if not success then |
|
return err or "Map Import Failed" |
|
end |
|
end |
|
|
|
function GridOpMapImport:GetEditorText() |
|
local value = " " |
|
if self.Operation == "Type" then |
|
local type_str = self:GetValueText("TextureType", "") |
|
if type_str ~= "" then |
|
value = " " .. type_str .. " " |
|
end |
|
end |
|
local grid_str = self.InputName ~= "" and "from <GridOpName><InputName></GridOpName>" or "" |
|
return "Import <Operation>" .. value .. grid_str |
|
end |
|
|
|
function GridOpMapImport:GetTexturePreview() |
|
return GetTerrainTexturePreview(self.TextureType) |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapReset = { |
|
__parents = { "GridOp" }, |
|
properties = { |
|
{ category = "General", id = "Type", name = "Texture Type", editor = "choice", default = "", items = GetTerrainNamesCombo(), operation = "Type", help = "If not specified, the default invalid terrain will be used" }, |
|
{ category = "General", id = "TypePreview", name = "Preview", editor = "image", default = false, img_size = 128, img_box = 1, base_color_map = true, dont_save = true, operation = "Type" }, |
|
{ category = "General", id = "Height", name = "Height", editor = "number", default = 10*guim, min = 0, max = height_max, slider = true, scale = "m", operation = "Height" }, |
|
{ category = "General", id = "Grass", name = "Grass", editor = "number", default = 0, min = 0, max = 100, slider = true, operation = "Grass" }, |
|
{ category = "General", id = "Color", name = "Color", editor = "color", default = RGB(200, 200, 200), operation = "Color" }, |
|
{ category = "General", id = "Overwrite", name = "Overwrite", editor = "bool", default = false, operation = "Backup" }, |
|
{ category = "General", id = "DeleteObjects", name = "Delete Objects", editor = "bool", default = true, operation = "Backup" }, |
|
{ category = "General", id = "FilterClass", name = "Class", editor = "text", default = "", operation = "Objects" }, |
|
{ category = "General", id = "FilterFlagsAll",name = "Flags All", editor = "set", default = set("Generated"), items = {"Generated", "Permanent"}, operation = "Objects" }, |
|
{ category = "General", id = "FilterFlagsAny",name = "Flags Any", editor = "set", default = set(), items = {"Generated", "Permanent"}, operation = "Objects" }, |
|
{ category = "General", id = "DeletedCount", name = "Deleted", editor = "number", default = 0, operation = "Objects", read_only = true, dont_save = true }, |
|
{ category = "General", id = "HeightMap", name = "Height Map", editor = "combo", default = "", items = GridOpOutputNames, grid_output = true, optional = true, operation = "Backup" }, |
|
{ category = "General", id = "TypeMap", name = "Type Map", editor = "combo", default = "", items = GridOpOutputNames, grid_output = true, optional = true, operation = "Backup" }, |
|
}, |
|
GridOpType = "Map Reset", |
|
operations = {"Type", "Height", "Grass", "Biome", "Objects", "Color", "Backup"}, |
|
} |
|
|
|
function GridOpMapReset:CollectTags(tags) |
|
local op = self.Operation |
|
if op == "Type" or op == "Height" or op == "Biome" or op == "Backup" then |
|
tags.Terrain = true |
|
end |
|
if op == "Objects" or op == "Backup" and self.DeleteObjects then |
|
tags.Objects = true |
|
end |
|
return GridOp.CollectTags(self, tags) |
|
end |
|
|
|
function GridOpMapReset:ResolveTerrainType() |
|
local ttype = self.Type or "" |
|
if not TerrainNameToIdx[ttype] then ttype = mapdata and mapdata.BaseLayer or "" end |
|
if not TerrainNameToIdx[ttype] then ttype = const.Prefab.InvalidTerrain or "" end |
|
if not TerrainNameToIdx[ttype] then ttype = TerrainTextures[0].id end |
|
return ttype |
|
end |
|
|
|
function GridOpMapReset:GetTypePreview() |
|
return GetTerrainTexturePreview(self:ResolveTerrainType()) |
|
end |
|
|
|
local function CreatePath(path, param, ...) |
|
if not io.exists(path) then |
|
local err = AsyncCreatePath(path) |
|
if err then |
|
return false, err |
|
end |
|
SVNAddFile(path) |
|
end |
|
if not param then |
|
return path |
|
end |
|
return CreatePath(path .. "/" .. param, ...) |
|
end |
|
|
|
local function ExtractFlags(flags) |
|
local gameFlags = 0 |
|
gameFlags = gameFlags + (flags.Generated and const.gofGenerated or 0) |
|
gameFlags = gameFlags + (flags.Permanent and const.gofPermanent or 0) |
|
return gameFlags ~= 0 and gameFlags or nil |
|
end |
|
|
|
function GridOpMapReset:Run() |
|
local success, err = true |
|
local op = self.Operation |
|
if op == "Type" then |
|
success = terrain.SetTerrainType{type = self:ResolveTerrainType()} |
|
elseif op == "Height" then |
|
success = terrain.SetHeight{height = self.Height} |
|
elseif op == "Biome" then |
|
BiomeGrid:clear() |
|
success = true |
|
elseif op == "Grass" then |
|
success = terrain.ClearGrassGrid(self.Grass) |
|
elseif op == "Color" then |
|
success = terrain.ClearColorizeGrid(self.Color) |
|
elseif op == "Objects" then |
|
local enumFlagsAll, enumFlagsAny |
|
local gameFlagsAll = ExtractFlags(self.FilterFlagsAll) |
|
local gameFlagsAny = ExtractFlags(self.FilterFlagsAny) |
|
if (self.FilterClass or "") == "" then |
|
self.DeletedCount = MapDelete(true, enumFlagsAll, enumFlagsAny, gameFlagsAll, gameFlagsAny) |
|
else |
|
self.DeletedCount = MapDelete(true, self.FilterClass, enumFlagsAll, enumFlagsAny, gameFlagsAll, gameFlagsAny) |
|
end |
|
elseif op == "Backup" then |
|
local trunc |
|
trunc, err = CreatePath("svnAssets/Source/MapGen", GetMapName()) |
|
if err then |
|
return err |
|
end |
|
local overwrite = self.Overwrite |
|
local height_file = trunc .. "/height.grid" |
|
local height_exists = io.exists(height_file) |
|
local height_grid = not overwrite and height_exists and GridReadFile(height_file) |
|
if not height_grid then |
|
height_grid = terrain.GetHeightGrid() |
|
success, err = GridWriteFile(height_grid, height_file, true) |
|
if success and not height_exists then |
|
SVNAddFile(height_file) |
|
end |
|
else |
|
err = terrain.SetHeightGrid(height_grid) |
|
terrain.InvalidateHeight() |
|
end |
|
if err then |
|
return err |
|
end |
|
if self.HeightMap ~= "" then |
|
self:SetGridOutput(self.HeightMap, height_grid) |
|
end |
|
local type_file = trunc .. "/type.grid" |
|
local type_exists = io.exists(height_file) |
|
local type_grid = not overwrite and type_exists and GridReadFile(type_file) |
|
if not type_grid then |
|
type_grid = terrain.GetTypeGrid() |
|
success, err = GridWriteFile(type_grid, type_file, true) |
|
if success and not type_exists then |
|
SVNAddFile(type_file) |
|
end |
|
else |
|
err = terrain.SetTypeGrid(type_grid) |
|
terrain.InvalidateType() |
|
end |
|
if err then |
|
return err |
|
end |
|
if self.TypeMap ~= "" then |
|
self:SetGridOutput(self.TypeMap, type_grid) |
|
end |
|
if self.DeleteObjects then |
|
MapDelete("map", nil, nil, const.gofGenerated) |
|
end |
|
mapdata.IsPartialGen = true |
|
end |
|
if not success then |
|
return op .. " Reset Failed" |
|
end |
|
end |
|
|
|
function GridOpMapReset:GetEditorText() |
|
local value = "" |
|
local op = self.Operation |
|
if op == "Type" then |
|
value = "<GridOpValue><Type></GridOpValue>" |
|
elseif op == "Height" then |
|
value = "<GridOpValue><Height></GridOpValue>" |
|
elseif op == "Grass" then |
|
value = "<GridOpValue><Grass></GridOpValue>" |
|
elseif op == "Objects" then |
|
value = "<GridOpValue><FilterClass></GridOpValue>" |
|
end |
|
return "Reset <Operation> " .. value |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapSlope = { |
|
__parents = { "GridOpInputOutput" }, |
|
properties = { |
|
{ category = "General", id = "Units", name = "Units", editor = "choice", default = "degrees", items = {"", "degrees", "minutes", "radians", "normalized"} }, |
|
{ category = "General", id = "SunAzimuth", name = "Sun Azimuth (deg)", editor = "number", default = 0, scale = 60, min = -180*60, max = 180*60, slider = true, operation = "Orientation", step = 60, buttons_step = 60, }, |
|
{ category = "General", id = "SunElevation", name = "Sun Elevation (deg)", editor = "number", default = 0, scale = 60, min = 0, max = 90*60, slider = true, operation = "Orientation", step = 60, buttons_step = 60 }, |
|
{ category = "General", id = "Approx", name = "Approximate", editor = "bool", default = true, help = "Computation speed at the cost of precision" }, |
|
}, |
|
GridOpType = "Map Slope", |
|
operations = {"Slope", "Orientation"}, |
|
input_fmt = "F", |
|
} |
|
|
|
function GridOpMapSlope:GetGridOutputFromInput(state, grid) |
|
local res = GridDest(grid) |
|
local units_to_unity = { |
|
radians = 0, |
|
normalized = 1, |
|
degrees = 180, |
|
minutes = 180*60, |
|
} |
|
local unity = units_to_unity[self.Units] |
|
local apporx = self.Approx |
|
local op = self.Operation |
|
if op == "Slope" then |
|
GridSlope(grid, res, height_tile, height_scale) |
|
if unity then |
|
GridASin(res, apporx, unity) |
|
end |
|
elseif op == "Orientation" then |
|
GridOrientation(grid, res, height_tile, height_scale, self.SunAzimuth, self.SunElevation) |
|
if unity then |
|
GridACos(res, apporx, unity) |
|
end |
|
end |
|
return nil, res |
|
end |
|
|
|
function GridOpMapSlope:GetEditorText() |
|
return "Calc <Operation> of <GridOpName><InputName></GridOpName> in <GridOpName><OutputName></GridOpName>" |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapParamType = { |
|
__parents = { "GridOpParam" }, |
|
properties = { |
|
{ category = "General", id = "ParamValue", name = "Type", editor = "choice", default = "", items = GetTerrainNamesCombo() }, |
|
{ category = "General", id = "TypePreview", name = "Preview", editor = "image", default = false, img_size = 128, img_box = 1, base_color_map = true, dont_save = true }, |
|
}, |
|
GridOpType = "Map Param Terrain Type", |
|
} |
|
|
|
function GridOpMapParamType:GetTypePreview() |
|
return GetTerrainTexturePreview(self.ParamValue) |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapParamColor = { |
|
__parents = { "GridOpParam" }, |
|
properties = { |
|
{ category = "General", id = "ParamValue", name = "Color", editor = "color", default = white }, |
|
{ category = "General", id = "R", name = "R", editor = "number", default = 0, min = 0, max = 255, slider = true, dont_save = true, buttons_step = 1 }, |
|
{ category = "General", id = "G", name = "G", editor = "number", default = 0, min = 0, max = 255, slider = true, dont_save = true, buttons_step = 1 }, |
|
{ category = "General", id = "B", name = "B", editor = "number", default = 0, min = 0, max = 255, slider = true, dont_save = true, buttons_step = 1 }, |
|
}, |
|
GridOpType = "Map Param Color", |
|
} |
|
|
|
function GridOpMapParamColor:GetParamStr() |
|
return string.format("%d %d %d", GetRGB(self.ParamValue)) |
|
end |
|
|
|
function GridOpMapParamColor:SetParamValue(value) |
|
self.ParamValue = value |
|
self.R, self.G, self.B = GetRGB(value) |
|
end |
|
|
|
function GridOpMapParamColor:SetR(c) self:SetParamValue(SetR(self.ParamValue, c)) end |
|
function GridOpMapParamColor:SetG(c) self:SetParamValue(SetG(self.ParamValue, c)) end |
|
function GridOpMapParamColor:SetB(c) self:SetParamValue(SetB(self.ParamValue, c)) end |
|
|
|
|
|
|
|
DefineClass.GridOpMapColorDist = { |
|
__parents = { "GridOpOutput" }, |
|
properties = { |
|
{ category = "General", id = "GridR", name = "Input Name R", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true }, |
|
{ category = "General", id = "GridG", name = "Input Name G", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true }, |
|
{ category = "General", id = "GridB", name = "Input Name B", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true }, |
|
{ category = "General", id = "Color", name = "Color Value", editor = "color", default = white, alpha = false, use_param = true }, |
|
}, |
|
GridOpType = "Map Color Dist", |
|
} |
|
|
|
function GridOpMapColorDist:GetGridOutput(state) |
|
local err |
|
local cr, cg, cb = GetRGB(self:GetValue("Color")) |
|
local gr, gg, gb = self:GetGridInput(self.GridR), self:GetGridInput(self.GridG), self:GetGridInput(self.GridB) |
|
local mr, mg, mb = GridDest(gr), GridDest(gg), GridDest(gb) |
|
GridAdd(gr, mr, -cr) |
|
GridAdd(gg, mg, -cg) |
|
GridAdd(gb, mb, -cb) |
|
GridPow(mr, 2) |
|
GridPow(mg, 2) |
|
GridPow(mb, 2) |
|
GridAdd(mr, mg) |
|
GridAdd(mr, mb) |
|
GridPow(mr, 1, 2) |
|
return err, mr |
|
end |
|
|
|
function GridOpMapColorDist:GetEditorText() |
|
local color = self.UseParams and self.ColorParam ~= "" and "<GridOpParam><ColorParam></GridOpParam>" or "<GridOpValue>" .. string.format("%d %d %d", GetRGB(self.ColorValue)) .. "</GridOpValue>" |
|
return "Color Dist of " .. color .. " from <GridOpName><GridR></GridOpName> <GridOpName><GridG></GridOpName> <GridOpName><GridB></GridOpName> in <GridOpName><OutputName></GridOpName>" |
|
end |
|
|
|
|
|
|
|
DefineClass.GridInspect = { |
|
__parents = { "DebugOverlayControl" }, |
|
properties = { |
|
{ category = "Debug", id = "AllowInspect", name = "Allow Inspect", editor = "bool", default = false, buttons = {{ name = "Toggle", func = "ToggleInspect" }} }, |
|
{ category = "Debug", id = "OverlayAlpha", name = "Overlay Alpha (%)", editor = "number", default = 60, slider = true, min = 0, max = 100, dont_save = true }, |
|
}, |
|
inspect_thread = false, |
|
} |
|
|
|
function GridInspect:GetInspectInfo() |
|
end |
|
|
|
function GridInspect:ToggleInspect() |
|
if IsValidThread(self.inspect_thread) then |
|
DbgStopInspect() |
|
DbgShowTerrainGrid(false) |
|
return |
|
end |
|
local grid, palette, callback = self:GetInspectInfo() |
|
if not grid then |
|
print("Inpsect grid not found!") |
|
return |
|
end |
|
DbgShowTerrainGrid(grid, palette) |
|
self.inspect_thread = DbgStartInspectPos(callback, grid) |
|
end |
|
|
|
function ToggleInspectDelayed(self) |
|
self:ToggleInspect() |
|
end |
|
|
|
function GridInspect:AutoStartInspect(state) |
|
if developer and state.run_mode == "Debug" and self.AllowInspect then |
|
DelayedCall(0, ToggleInspectDelayed, self) |
|
end |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapBiomeMatch = { |
|
__parents = { "GridOpOutput", "GridInspect" }, |
|
properties = { |
|
{ category = "General", id = "BiomeGroup", name = "Biome Group", editor = "choice", default = "", items = PresetGroupsCombo("Biome"), use_param = true }, |
|
{ category = "Preview", id = "Biomes", name = "All Biomes", editor = "number", default = 0, dont_save = true, read_only = true}, |
|
{ category = "Debug", id = "MatchedBiomes", name = "Matched Biomes", editor = "text", default = false, dont_save = true, lines = 10, max_lines = 10, text_style = "GedConsole" }, |
|
{ category = "Debug", id = "ClickPosition", name = "Map Position", editor = "point", default = false, dont_save = true}, |
|
{ category = "Debug", id = "GridPosition", name = "Grid Position", editor = "point", default = false, dont_save = true}, |
|
}, |
|
GridOpType = "Map Biome Match", |
|
input_fmt = "F", |
|
match_grids = false, |
|
match_biomes = false, |
|
bvalue_to_preset = false, |
|
} |
|
|
|
for _, match in ipairs(BiomeMatchParams) do |
|
local id, name, help = match.id, match.name, match.help |
|
table.iappend(GridOpMapBiomeMatch.properties, { |
|
{ category = "General", id = id .. "Map", name = name .. " Match", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true, ignore_errors = true, help = help }, |
|
}) |
|
end |
|
|
|
function GridOpMapBiomeMatch:OnMoveCallback(pos) |
|
local biome_map = self.outputs[self.OutputName] |
|
if not biome_map then |
|
return |
|
end |
|
local mx, my = pos:xy() |
|
local bvalue, gx, gy = GridMapGet(biome_map, mx, my) |
|
local tmp = { "", |
|
print_concat{"map", DivToStr(mx, guim), ":", DivToStr(my, guim), "(m)"}, |
|
print_concat{"grid", gx, ":", gy}, |
|
} |
|
local biome_preset = self.bvalue_to_preset[bvalue] |
|
tmp[#tmp + 1] = print_concat{ "Biome", bvalue, biome_preset and biome_preset.id or "" } |
|
local grids = self.match_grids |
|
for i, params in ipairs(BiomeMatchParams) do |
|
local grid = grids[i] |
|
if grid then |
|
local v = GridMapGet(grid, mx, my, 1024) |
|
tmp[#tmp + 1] = print_concat{ params.id, DivToStr(v, params.scale), params.units } |
|
end |
|
end |
|
local h = #tmp |
|
for i=1,h do |
|
tmp[#tmp + 1] = "" |
|
end |
|
return table.concat(tmp, "\n") |
|
end |
|
|
|
function GridOpMapBiomeMatch:OnClickCallback(pos) |
|
local biome_map = self.outputs[self.OutputName] |
|
if not biome_map then |
|
return |
|
end |
|
local mx, my = pos:xy() |
|
self.ClickPosition = pos |
|
local _, gx, gy = GridMapGet(biome_map, mx, my) |
|
self.GridPosition = point(gx, gy) |
|
local err, biome_weights = BiomePosMatch(gx, gy, self.match_biomes, self.match_grids, BiomeMatchParams) |
|
local matched_dump_str |
|
if not err then |
|
local bvalue_to_preset = self.bvalue_to_preset |
|
table.sortby_field_descending(biome_weights, "weight") |
|
for i, entry in ipairs(biome_weights) do |
|
entry.value = bvalue_to_preset[entry.biome].id |
|
end |
|
local nweight = biome_weights[1].weight / 1000 |
|
for i, entry in ripairs(biome_weights) do |
|
entry.weight = DivRound(entry.weight, nweight) |
|
if entry.weight == 0 then |
|
table.remove(biome_weights, i) |
|
end |
|
end |
|
local header = string.format("%20s | %6s", "Biome", "Weight") |
|
local line = "---------------------+--------" |
|
for _, entry in ipairs(BiomeMatchParams) do |
|
header = string.format("%s | %9s", header, entry.id) |
|
line = line .. "+-----------" |
|
end |
|
matched_dump_str = { header, line } |
|
for _, entry in ipairs(biome_weights) do |
|
local str = string.format("%20s | %6d", entry.value, entry.weight) |
|
for _, weight in ipairs(entry) do |
|
str = string.format("%s | %9d", str, weight) |
|
end |
|
matched_dump_str[#matched_dump_str + 1] = str |
|
end |
|
matched_dump_str = table.concat(matched_dump_str, "\n") |
|
end |
|
self.MatchedBiomes = matched_dump_str |
|
ObjModified(self) |
|
end |
|
|
|
function GridOpMapBiomeMatch:GetInspectInfo() |
|
local biome_map = self.outputs[self.OutputName] |
|
if not biome_map then |
|
return |
|
end |
|
local palette = DbgGetBiomePalette() |
|
self.bvalue_to_preset = BiomeValueToPreset() |
|
local function MoveCallback(pos) |
|
return self:OnMoveCallback(pos) |
|
end |
|
local function ClickCallback(pos) |
|
return self:OnClickCallback(pos) |
|
end |
|
return biome_map, palette, { MoveCallback, ClickCallback } |
|
end |
|
|
|
function GridOpMapBiomeMatch:GetGridOutput(state) |
|
local grids = {} |
|
for _, match in ipairs(BiomeMatchParams) do |
|
local prop_id = match.id .. "Map" |
|
local grid_name = self[prop_id] |
|
local grid = self:GetGridInput(grid_name) |
|
grids[#grids + 1] = grid or false |
|
if grid then |
|
local prec = 10 |
|
local min, max = GridMinMax(grid, prec) |
|
if min < match.min * prec or max > match.max * prec then |
|
if min < match.min then |
|
print("Match grid", match.id, "is below its min:", min * 1.0 / prec, "<", match.min) |
|
else |
|
print("Match grid", match.id, "is above its max:", max * 1.0 / prec, ">", match.max) |
|
end |
|
return "Match grid out of bounds" |
|
end |
|
end |
|
end |
|
local match_group = self:GetValue("BiomeGroup") or "" |
|
if match_group == "" then |
|
return "Biome group not specified!" |
|
end |
|
state.BiomeGroup = match_group |
|
local biomes = {} |
|
ForEachPreset("Biome", function(preset) |
|
if preset.group == match_group then |
|
local biome = { preset.grid_value } |
|
for _, match in ipairs(BiomeMatchParams) do |
|
local id = match.id |
|
for _, prop in ipairs{"From", "To", "Best", "Weight"} do |
|
biome[#biome + 1] = preset[id .. prop] |
|
end |
|
end |
|
biomes[#biomes + 1] = biome |
|
end |
|
end) |
|
if #biomes == 0 then |
|
return "No biome presets found" |
|
end |
|
self.Biomes = #biomes |
|
local gw, gh = grids[1]:size() |
|
local biome_map = NewComputeGrid(gw, gh, "U", 8) |
|
biome_map:clear() |
|
local err = BiomeGridMatch(biome_map, biomes, grids, BiomeMatchParams) |
|
if err then |
|
return err |
|
end |
|
self.match_grids = grids |
|
self.match_biomes = biomes |
|
if developer then |
|
local x, y = GridFind(biome_map, 0) |
|
if x then |
|
local w, h = terrain.GetMapSize() |
|
StoreErrorSource(point(x * w / gw, y * h / gh), "Biome non matched!") |
|
end |
|
end |
|
self:AutoStartInspect(state) |
|
return nil, biome_map |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapPrefabTypes = { |
|
__parents = { "GridOpInputOutput" }, |
|
properties = { |
|
{ category = "General", id = "AllowEmptyTypes", name = "Allow Empty Types", editor = "bool", default = false }, |
|
{ category = "Preview", id = "PrefabsFound", name = "Prefabs Found", editor = "string_list", default = false, read_only = true, dont_save = true }, |
|
}, |
|
GridOpType = "Map Biome Prefab Types", |
|
} |
|
|
|
function GridOpMapPrefabTypes:GetGridOutputFromInput(state, grid) |
|
local levels = GridLevels(grid) |
|
local bvalue_to_preset = BiomeValueToPreset() |
|
local ptype_to_idx = table.invert(GetPrefabTypeList()) |
|
local type_to_prefabs = PrefabTypeToPrefabs |
|
local allow_empty_types = self.AllowEmptyTypes |
|
local debug = state.run_mode ~= "GM" |
|
local biomes, ptypes = {}, {} |
|
for value, count in pairs(levels) do |
|
if value == 0 then |
|
goto continue |
|
end |
|
local preset = bvalue_to_preset[value] |
|
local weights = preset and preset.PrefabTypeWeights or empty_table |
|
local invalid_type |
|
local valid_weights = {} |
|
for _, pw in ipairs(weights) do |
|
local ptype = pw.PrefabType |
|
if not ptype_to_idx[ptype] then |
|
invalid_type = ptype |
|
break |
|
end |
|
if allow_empty_types or type_to_prefabs[ptype] then |
|
valid_weights[#valid_weights + 1] = pw |
|
end |
|
end |
|
if not preset then |
|
print("Missing preset with value:", value) |
|
elseif #weights == 0 then |
|
print("Biome without prefab types:", preset.id) |
|
elseif invalid_type then |
|
print("Biome", preset.id, "contains an invalid prefab type", invalid_type) |
|
elseif #weights > 1 and not NoisePresets[preset.TypeMixingPreset] then |
|
print("Biome", preset.id, "has an invalid mixing pattern", preset.TypeMixingPreset) |
|
elseif #valid_weights == 0 then |
|
print("Biome", preset.id, "doesn't match any prefabs") |
|
else |
|
biomes[#biomes + 1] = { |
|
preset = preset, |
|
count = count, |
|
weights = valid_weights, |
|
} |
|
if debug then |
|
for _, pw in ipairs(valid_weights) do |
|
ptypes[pw.PrefabType] = true |
|
end |
|
end |
|
end |
|
::continue:: |
|
end |
|
|
|
if debug then |
|
local legend = {} |
|
for ptype in pairs(ptypes) do |
|
legend[#legend + 1] = string.format("%d. %s: %d", ptype_to_idx[ptype], ptype, #(type_to_prefabs[ptype] or empty_table)) |
|
end |
|
table.sort(legend) |
|
self.PrefabsFound = legend |
|
end |
|
|
|
table.sort(biomes, function(a, b) return a.preset.grid_value < b.preset.grid_value end) |
|
local remap = {} |
|
local w, h = grid:size() |
|
local rand = state.rand |
|
for _, biome in ipairs(biomes) do |
|
local value = biome.preset.grid_value |
|
local valid_weights = biome.weights |
|
local weights = biome.preset.PrefabTypeWeights or empty_table |
|
if #valid_weights == 0 then |
|
|
|
elseif #weights > 1 then |
|
local type_mix = NewComputeGrid(w, h, "U", 16) |
|
rand = BraidRandom(rand) |
|
biome.preset:GetTypeMixingGrid(type_mix, rand, ptype_to_idx) |
|
remap[value] = type_mix |
|
else |
|
local type_idx = ptype_to_idx[weights[1].PrefabType] |
|
remap[value] = type_idx |
|
end |
|
end |
|
local mix_grid = NewComputeGrid(w, h, "U", 16) |
|
BiomeGridRemap(grid, mix_grid, remap) |
|
return nil, mix_grid |
|
end |
|
|
|
|
|
|
|
DefineClass.GridOpMapErosion = { |
|
__parents = { "GridOpInputOutput" }, |
|
properties = { |
|
{ category = "General", id = "Iterations", name = "Iterations", editor = "number", default = 100, min = 1 }, |
|
{ category = "General", id = "DropSize", name = "Drop Size (m)", editor = "number", default = 10, min = 0, max = unity, scale = unity, slider = true }, |
|
{ category = "General", id = "Capacity", name = "Capacity", editor = "number", default = 10, min = 0, max = unity, scale = unity, slider = true }, |
|
{ category = "General", id = "Evaporation", name = "Evaporation", editor = "number", default = unity/2, min = 0, max = unity, scale = unity, slider = true }, |
|
{ category = "General", id = "Solubility", name = "Solubility", editor = "number", default = 10, min = 0, max = unity, scale = unity, slider = true }, |
|
{ category = "General", id = "ThermalErosion",name = "Thermal Erosion", editor = "number", default = 10, min = 0, max = unity, scale = unity, slider = true }, |
|
{ category = "General", id = "WindForce", name = "Wind Force", editor = "number", default = unity, min = 0, max = unity, scale = unity, slider = true }, |
|
{ category = "General", id = "TalusAngle", name = "Talus Angle (deg)", editor = "number", default = 45*60, min = 0, max = 90*60, scale = 60, slider = true }, |
|
{ category = "General", id = "WaterMap", name = "Water Map", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, grid_output = true, optional = true }, |
|
{ category = "General", id = "SedimentMap", name = "Sediment Map", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, grid_output = true, optional = true }, |
|
}, |
|
GridOpType = "Map Erosion", |
|
} |
|
|
|
function GridOpMapErosion:GetGridOutputFromInput(state, grid) |
|
local eroded = GridRepack(grid, "F", 32, true) |
|
local water = self:GetGridInput(self.WaterMap) or GridDest(eroded, true) |
|
local sediment = self:GetGridInput(self.SedimentMap) or GridDest(eroded, true) |
|
GridErosion( |
|
eroded, water, sediment, self.Iterations, |
|
self.DropSize, self.Capacity, self.Evaporation, self.Solubility, |
|
self.ThermalErosion, self.WindForce, self.TalusAngle, |
|
unity, state.rand) |
|
if self.WaterMap ~= "" then |
|
self:SetGridOutput(self.WaterMap, water) |
|
end |
|
if self.SedimentMap ~= "" then |
|
self:SetGridOutput(self.SedimentMap, sediment) |
|
end |
|
return nil, eroded |
|
end |
|
|
|
|
|
|
|
local def_tex = set("Main", "Noise", "Flow") |
|
local function no_flow(self) return not self.Textures.Flow end |
|
local function no_noise(self) return not self.Textures.Noise end |
|
|
|
DefineClass.GridOpMapBiomeTexture = { |
|
__parents = { "GridOpInput", "GridInspect" }, |
|
properties = { |
|
{ category = "General", id = "Textures", name = "Textures", editor = "set", default = def_tex, items = {"Main", "Noise", "Flow"}, }, |
|
{ category = "General", id = "FlowMap", name = "Flow Map", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true, no_edit = no_flow }, |
|
{ category = "General", id = "FlowMax", name = "Flow Max", editor = "number", default = 1, use_param = true, no_edit = no_flow }, |
|
{ category = "General", id = "HeightMap", name = "Height Map", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true, no_edit = no_noise }, |
|
{ category = "General", id = "Transition", name = "Transition", editor = "bool", default = true, }, |
|
{ category = "General", id = "ApplyGrass", name = "Apply Grass", editor = "bool", default = true, }, |
|
{ category = "General", id = "GrassMap", name = "Grass Map", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true, enabled_by = "ApplyGrass" }, |
|
{ category = "General", id = "InvalidTerrain",name = "Invalid Terrain", editor = "choice", default = "", items = GetTerrainNamesCombo(), help = "If not specified, the default invalid terrain will be used" }, |
|
}, |
|
GridOpType = "Map Biome Import Textures", |
|
inspect_thread = false, |
|
} |
|
|
|
function GridOpMapBiomeTexture:CollectTags(tags) |
|
tags.Terrain = true |
|
return GridOp.CollectTags(self, tags) |
|
end |
|
|
|
function GridOpMapBiomeTexture:SetGridInput(state, grid) |
|
local invalid_terrain = self.InvalidTerrain or "" |
|
if invalid_terrain == "" then |
|
invalid_terrain = const.Prefab.InvalidTerrain or "" |
|
end |
|
local invalid_idx = invalid_terrain ~= "" and GetTerrainTextureIndex(invalid_terrain) or 0 |
|
local ptype_list = GetPrefabTypeList() |
|
local type_presets, ptype_to_idx = {}, {} |
|
for i, ptype in ipairs(ptype_list) do |
|
if GridFind(grid, i) then |
|
type_presets[#type_presets + 1] = PrefabTypeToPreset[ptype] |
|
ptype_to_idx[ptype] = i |
|
end |
|
end |
|
table.sort(type_presets, function(a, b) |
|
local sa, sb = a.TexturingOrder, b.TexturingOrder |
|
if sa ~= sb then |
|
return sa < sb |
|
end |
|
local ta, tb = a.Transition, b.Transition |
|
if ta ~= tb then |
|
return ta > tb |
|
end |
|
return a.id < b.id |
|
end) |
|
local rand = state.rand |
|
local textures = self.Textures |
|
local flow_map = self:GetGridInput(self.FlowMap) |
|
local height_map = self:GetGridInput(self.HeightMap) |
|
local grass_map = self:GetGridInput(self.GrassMap) |
|
local marks, maski = GridDest(grid), GridDest(grid) |
|
local mask, noise, hmod |
|
local last_idx = 0 |
|
local idx_to_type, idx_to_grass = {}, {} |
|
local function add_idx(type_idx, grass_mod) |
|
last_idx = last_idx + 1 |
|
idx_to_type[last_idx] = type_idx |
|
idx_to_grass[last_idx] = grass_mod |
|
return last_idx |
|
end |
|
marks:clear() |
|
for i, type_preset in ipairs(type_presets) do |
|
local idx = ptype_to_idx[type_preset.id] |
|
GridMask(grid, maski, idx) |
|
if mask then |
|
GridRepack(maski, mask) |
|
else |
|
mask = GridRepack(maski, "F") |
|
end |
|
local transition = self.Transition and type_preset.Transition or 0 |
|
if transition > 0 then |
|
GridNot(mask) |
|
GridDistance(mask, type_tile, transition) |
|
GridRemap(mask, 0, transition, 1, 0) |
|
end |
|
rand = BraidRandom(rand) |
|
local apply_config = { marks = marks, mask = mask, seed = rand } |
|
if textures.Main and type_preset.TextureMain ~= "" then |
|
local main_idx = GetTerrainTextureIndex(type_preset.TextureMain) |
|
if not main_idx then |
|
return "Invalid main terrain type: " .. type_preset.TextureMain |
|
end |
|
apply_config.main_idx = add_idx(main_idx, type_preset.GrassMain) |
|
end |
|
if textures.Noise and type_preset.TextureNoise ~= "" then |
|
local noise_idx = GetTerrainTextureIndex(type_preset.TextureNoise) |
|
local noise_preset = NoisePresets[type_preset.NoisePreset] |
|
if not noise_preset then |
|
return "Noise preset missing: " .. type_preset.id |
|
elseif noise_preset.Min ~= 0 then |
|
return "Invalid noise preset: " .. type_preset.id |
|
elseif not noise_idx then |
|
return "Invalid noise terrain type: " .. type_preset.TextureNoise |
|
else |
|
rand = BraidRandom(rand) |
|
noise = noise or GridDest(mask) |
|
noise_preset:GetNoise(rand, noise) |
|
if type_preset.HeightModulated then |
|
if not height_map then |
|
return "Height map not provided!" |
|
end |
|
if not hmod then |
|
hmod = GridDest(noise) |
|
GridHeightMaskLevels(height_map, hmod) |
|
end |
|
noise = GridModulate(noise, hmod, hmod) |
|
end |
|
apply_config.noise = noise |
|
apply_config.noise_idx = add_idx(noise_idx, type_preset.GrassNoise) |
|
apply_config.noise_max = noise_preset.Max |
|
apply_config.noise_stength = type_preset.NoiseStrength |
|
apply_config.noise_contrast = type_preset.NoiseContrast |
|
end |
|
end |
|
if textures.Flow and flow_map and type_preset.TextureFlow ~= "" then |
|
local flow_idx = GetTerrainTextureIndex(type_preset.TextureFlow) |
|
local flow_max = self:GetValue("FlowMax") |
|
if not flow_max then |
|
return "Undefined max flow value!" |
|
elseif not flow_idx then |
|
return "Invalid flow terrain type: " .. type_preset.TextureFlow |
|
end |
|
apply_config.flow = flow_map |
|
apply_config.flow_idx = add_idx(flow_idx, type_preset.GrassFlow) |
|
apply_config.flow_max = flow_max |
|
apply_config.flow_strength = type_preset.FlowStrength |
|
apply_config.flow_contrast = type_preset.FlowContrast |
|
end |
|
GridMarkPrefabTypeTerrain(apply_config) |
|
end |
|
if last_idx > 0 then |
|
if self.ApplyGrass then |
|
if not grass_map then |
|
return "Grass map expected" |
|
end |
|
GridModPrefabTypeGrass(marks, grass_map, idx_to_grass) |
|
end |
|
GridReplace(marks, idx_to_type, invalid_idx) |
|
else |
|
marks:clear(invalid_idx) |
|
end |
|
local err = terrain.SetTypeGrid(marks) |
|
if err then |
|
return err |
|
end |
|
terrain.InvalidateType() |
|
self:AutoStartInspect(state) |
|
end |
|
|
|
function GridOpMapBiomeTexture:GetInspectInfo() |
|
local grid = self.inputs[self.InputName] |
|
if not grid then |
|
return |
|
end |
|
local ptype_list = GetPrefabTypeList() |
|
local level_map = GridLevels(grid) |
|
local ptype_to_preset = PrefabTypeToPreset |
|
local palette = {} |
|
for ptype_idx in pairs(level_map) do |
|
local ptype = ptype_list[ptype_idx] |
|
local preset = ptype_to_preset[ptype] |
|
local color = preset and preset.OverlayColor or RandColor(xxhash(ptype)) |
|
palette[ptype_idx] = color |
|
end |
|
local bvalue_to_preset = BiomeValueToPreset() |
|
return grid, palette, function(pos) |
|
local idx = terrain.GetTerrainType(pos) |
|
local texture = TerrainTextures[idx] |
|
local bvalue = BiomeGrid:get(pos) |
|
local biome_preset = bvalue_to_preset[bvalue] |
|
local ptype_idx = GridMapGet(grid, pos:xy()) |
|
local ptype = ptype_list[ptype_idx] |
|
local tmp = { |
|
print_concat{"Texture", idx, texture and texture.id or ""}, |
|
print_concat{"Prefab Type", ptype_idx, ptype or ""}, |
|
print_concat{"Biome", bvalue, biome_preset and biome_preset.id or ""}, |
|
} |
|
return table.concat(tmp, "\n") |
|
end |
|
end |
|
|
|
function OnMsg.GedPropertyEdited(_, obj, prop_id) |
|
local op_classes |
|
if IsKindOf(obj, "Biome") then |
|
local category = obj:GetPropertyMetadata(prop_id).category |
|
if category == "Prefabs" then |
|
return |
|
end |
|
op_classes = {"GridOpMapBiomeMatch", "GridOpMapPrefabTypes"} |
|
elseif IsKindOf(obj, "PrefabType") then |
|
op_classes = {"GridOpMapBiomeTexture"} |
|
else |
|
return |
|
end |
|
local proc, target |
|
ForEachPreset("MapGen", function(preset) |
|
if GedObjects[preset] then |
|
for _, op in ipairs(preset) do |
|
if not op.proc or not table.find(op_classes, op.class) then |
|
|
|
elseif proc == op.proc then |
|
if not target or target.start_time > op.start_time then |
|
target = op |
|
end |
|
elseif not proc or proc.start_time < op.proc.start_time then |
|
proc = op.proc |
|
target = op |
|
end |
|
end |
|
end |
|
end) |
|
if target then |
|
target:Recalc() |
|
end |
|
end |
|
|
|
|
|
|
|
DefineClass.MapGen = { |
|
__parents = { "GridProcPreset" }, |
|
GlobalMap = "MapGenProcs", |
|
EditorMenubarName = "Map Gen", |
|
EditorMenubar = "Map.Generate", |
|
EditorIcon = "CommonAssets/UI/Icons/gear option setting setup.png", |
|
EditorMapGenActions = { |
|
{ Menubar = "Action", Toolbar = "main", Name = "Open MapGen Folder", FuncName = "ActionOpenMapgenFolder", Icon = "CommonAssets/UI/Ged/explorer.tga", }, |
|
}, |
|
} |
|
|
|
function MapGen:GatherEditorCustomActions(actions) |
|
GridProcPreset.GatherEditorCustomActions(self, actions) |
|
table.iappend(actions, self.EditorMapGenActions) |
|
end |
|
|
|
function MapGen:GetSeedSaveDest() |
|
return "MapGenSeed", mapdata |
|
end |
|
|
|
function MapGen:RunOps(state, ...) |
|
if GetMap() == "" then |
|
return "No Map Loaded" |
|
end |
|
return GridProcPreset.RunOps(self, state, ...) |
|
end |
|
|
|
function GetMapGenSource(map_name) |
|
return string.format("svnAssets/Source/MapGen/%s/", map_name) |
|
end |
|
|
|
function MapGen:RunInit(state) |
|
if state.proc ~= self then |
|
return |
|
end |
|
local map_name = GetMapName() or "" |
|
if map_name == "" then |
|
return |
|
end |
|
Msg("MapGenStart", self) |
|
state.base_dir = GetMapGenSource(map_name) |
|
self:AddLog("Output dir: " .. state.base_dir, state) |
|
if state.tags.Pause then |
|
Pause("MapGen") |
|
end |
|
if state.tags.Terrain then |
|
SuspendTerrainInvalidations("MapGen") |
|
end |
|
if state.tags.Objects then |
|
NetPauseUpdateHash("MapGen") |
|
table.change(config, "MapGen", { |
|
PartialPassEdits = false, |
|
BillboardsSuspendInvalidate = true, |
|
}) |
|
SuspendPassEdits("MapGen") |
|
DisablePassTypes() |
|
collision.Activate(false) |
|
end |
|
table.change(_G, "MapGen", { |
|
pairs = g_old_pairs, |
|
GetDiagnosticMessage = empty_func, |
|
DiagnosticMessageSuspended = true, |
|
}) |
|
return GridProcPreset.RunInit(self, state) |
|
end |
|
|
|
function MapGen:InvalidateProc(state) |
|
if state.tags.Terrain then |
|
ResumeTerrainInvalidations("MapGen", true) |
|
end |
|
end |
|
|
|
function MapGen:RunDone(state) |
|
if state.proc ~= self then |
|
return |
|
end |
|
self:InvalidateProc(state) |
|
if state.tags.Objects then |
|
|
|
MapForEach(true, "EditorObject", function(obj) |
|
obj:ClearEnumFlags(const.efVisible) |
|
end) |
|
collision.Activate(true) |
|
ResumePassEdits("MapGen") |
|
table.restore(config, "MapGen", true) |
|
NetResumeUpdateHash("MapGen") |
|
XEditorFiltersReset() |
|
EnablePassTypes() |
|
end |
|
if state.tags.Pause then |
|
assert(IsPaused()) |
|
Resume("MapGen") |
|
end |
|
table.restore(_G, "MapGen", true) |
|
Msg("MapGenDone", self) |
|
return GridProcPreset.RunDone(self, state) |
|
end |
|
|
|
function TestOcclude(pt0, hg, count) |
|
count = count or 1 |
|
hg = hg or terrain.GetHeightGrid() |
|
|
|
|
|
local occlude = GridDest(hg) |
|
local gw, gh = hg:size() |
|
local mw, mh = terrain.GetMapSize() |
|
local goffset = 10*guim/height_scale |
|
while true do |
|
pt0 = pt0 or GetTerrainCursor() |
|
local x, y = pt0:xy() |
|
x, y = Clamp(x, 0, mw - 1), Clamp(y, 0, mh - 1) |
|
local pt = point(x, y) |
|
DbgClear() |
|
DbgAddCircle(pt, 5*guim) |
|
DbgAddVector(pt, 10*guim) |
|
if GridOccludeHeight(hg, occlude, x * gw / mw, y * gh / mh, goffset) then |
|
|
|
terrain.SetHeightGrid(occlude) terrain.InvalidateHeight() |
|
end |
|
count = count - 1 |
|
if count <= 0 then |
|
return pt0 |
|
end |
|
while pt0 == GetTerrainCursor() do |
|
WaitNextFrame(1) |
|
end |
|
pt0 = GetTerrainCursor() |
|
end |
|
end |
|
|
|
function OccludePlayable(hg, eyeZ) |
|
hg = hg or terrain.GetHeightGrid() |
|
local st = GetPreciseTicks() |
|
eyeZ = eyeZ or 10*guim |
|
|
|
local border_divs, playable_divs = 8, 4 |
|
local gw, gh = hg:size() |
|
local mw, mh = terrain.GetMapSize() |
|
local goffset = eyeZ / height_scale |
|
|
|
local occlude = GridDest(hg) |
|
local result = GridDest(hg) |
|
result:clear(height_max / height_scale) |
|
|
|
|
|
local function Occlude(gx, gy) |
|
|
|
if GridOccludeHeight(hg, occlude, gx, gy, goffset) then |
|
GridMin(result, occlude) |
|
end |
|
end |
|
|
|
local bbox = GetPlayBox() |
|
local minx, miny, maxx, maxy = bbox:xyxy() |
|
|
|
local pts = {{minx, miny}, {maxx - 1, miny}, {maxx - 1, maxy - 1}, {minx, maxy - 1}} |
|
local pt0 = pts[#pts] |
|
for _, pt1 in ipairs(pts) do |
|
local x0, y0, x1, y1 = pt0[1], pt0[2], pt1[1], pt1[2] |
|
for k = 1, border_divs do |
|
local x = x0 + (x1 - x0) * k / border_divs |
|
local y = y0 + (y1 - y0) * k / border_divs |
|
Occlude(x * gw / mw, y * gh / mh) |
|
end |
|
pt0 = pt1 |
|
end |
|
local dx, dy = maxx - minx, maxy - miny |
|
local y0 = miny |
|
for i = 1, playable_divs do |
|
local y1 = miny + dy * i / playable_divs |
|
local x0 = minx |
|
for j = 1, playable_divs do |
|
local x1 = minx + dx * j / playable_divs |
|
local v, gx, gy = GridGetMaxHeight(hg, x0 * gw / mw, y0 * gw / mw, x1 * gw / mw, y1 * gw / mw) |
|
Occlude(gx, gy) |
|
x0 = x1 |
|
end |
|
y0 = y1 |
|
end |
|
|
|
return result |
|
end |
|
|
|
function OnMsg.ChangeMap() |
|
ForEachPreset("MapGen", function(preset) |
|
preset.run_state = nil |
|
for _, op in ipairs(preset) do |
|
op.inputs = nil |
|
op.outputs = nil |
|
op.params = nil |
|
end |
|
end) |
|
end |
|
|
|
function OnMsg.SaveMap() |
|
if LastGridProcDump == "" then |
|
return |
|
end |
|
CreateRealTimeThread(function(name, str) |
|
local filename = GetMap() .. LastGridProcName .. ".log" |
|
local err = AsyncStringToFile(filename, str) |
|
if err then |
|
print("Mapgen dump write error:", err) |
|
else |
|
local path = ConvertToOSPath(filename) |
|
print("Mapgen dump file saved to:", path) |
|
end |
|
end, LastGridProcName, LastGridProcDump) |
|
LastGridProcDump = "" |
|
end |
|
|
|
AppendClass.MapDataPreset = { properties = { |
|
{ category = "Random Map", id = "MapGenSeed", editor = "number", default = 0 }, |
|
}} |