const.TagLookupTable["GridOpName"] = "" const.TagLookupTable["/GridOpName"] = "" const.TagLookupTable["GridOpStr"] = "" const.TagLookupTable["/GridOpStr"] = "" const.TagLookupTable["GridOpParam"] = "" const.TagLookupTable["/GridOpParam"] = "" const.TagLookupTable["GridOpValue"] = "" const.TagLookupTable["/GridOpValue"] = "" const.TagLookupTable["GridOpGlobal"] = "" const.TagLookupTable["/GridOpGlobal"] = "" local max_uint16 = 65535 local eval = prop_eval local developer = Platform.developer function DivToStr(v, s) local v100 = 1000 * v / (s or 1) v = v100 / 1000 local r = v100 % 1000 if r == 0 then return v elseif r < 10 then return v .. ".00" .. r elseif r < 100 then return v .. ".0" .. r end return v .. "." .. r end -- Return 'grid' in the same format and size as 'ref' function GridMakeSame(grid, ref) grid = GridResample(grid, ref:size()) grid = GridRepack(grid, IsComputeGrid(ref)) return grid end -- Return true if 'grid' is the same format and size as 'ref' function GridCheckSame(grid, ref) local fmt1, bits1 = IsComputeGrid(ref) local fmt2, bits2 = IsComputeGrid(grid) if fmt1 ~= fmt2 or bits1 ~= bits2 then return end local w1, h1 = ref:size() local w2, h2 = grid:size() if w1 ~= w2 or h1 ~= h2 then return end return true end function GridMapGet(grid, mx, my, coord_scale, value_scale) coord_scale = coord_scale or 1 value_scale = value_scale or 1 local mw, mh = terrain.GetMapSize() local gw, gh = grid:size() local gx, gy = MulDivTrunc(coord_scale * gw, mx, mw), MulDivTrunc(coord_scale * gh, my, mh) local gv = GridGet(grid, gx, gy, value_scale, coord_scale) return gv, gx, gy end ---- if FirstLoad then LastGridProcName = "" LastGridProcDump = "" end function OnMsg.ChangeMap() LastGridProcName = "" LastGridProcDump = "" end local function OnChangeCombo() return { {value = "", name = "Do nothing"}, {value = "recalc_op", name = "Run cursor only"}, {value = "recalc_to", name = "Run to cursor"}, {value = "recalc_from", name = "Run from cursor"}, {value = "recalc_all", name = "Run All"}, } end local run_mode_items = {"Debug", "Release", "GM", "Profile"} local all_run_modes = set(table.unpack(run_mode_items)) DefineClass.GridProc = { __parents = { "PropertyObject" }, properties = { { category = "Operations", id = "OnChange", name = "On Change", editor = "choice", default = "recalc_op", items = OnChangeCombo }, { category = "Operations", id = "Lightmodel", name = "Lightmodel", editor = "preset_id", default = "", preset_class = "LightmodelPreset" }, { category = "Operations", id = "RunMode", name = "Run Mode", editor = "choice", default = "Release", items = run_mode_items, max_items_in_set = 1 }, { category = "Operations", id = "RunOnce", name = "Run Once", editor = "bool", default = false, }, { category = "Operations", id = "Randomize", name = "Randomize", editor = "bool", default = false, no_edit = PropGetter("LoadSeed") }, { category = "Operations", id = "Seed", name = "Fixed Seed", editor = "number", default = 0, no_edit = function(self) return self.Randomize or self.LoadSeed end, buttons = {{ name = "Rand", func = "ActionRand" }} }, { category = "Operations", id = "SaveSeed", name = "Save Seed", editor = "bool", default = false, help = "Save the generation seed", no_edit = function(self) return self.LoadSeed or not self:GetSeedSaveDest() end }, { category = "Operations", id = "LoadSeed", name = "Load Seed", editor = "bool", default = false, help = "Load the generation seed", no_edit = function(self) return not self:GetSeedSaveDest() end }, { category = "Operations", id = "Dump", name = "Dump", editor = "bool", default = false, help = "Will create a generation dump log in Release and Debug runs. May affect performance." }, { category = "Stats", id = "Count", name = "Own Ops Count", editor = "number", default = 0, read_only = true, dont_save = true }, { category = "Stats", id = "ExecCount", name = "Executed Ops", editor = "number", default = 0, read_only = true, dont_save = true }, { category = "Stats", id = "Time", name = "Total Time (ms)", editor = "number", default = -1, read_only = true, dont_save = true }, { category = "Stats", id = "Log", name = "Log", editor = "text", default = "", lines = 2, max_lines = 20, read_only = true, dont_save = true }, }, start_time = 0, log = false, err_msg = false, err_op = false, } function GridProc:GetSeedSaveDest() end function GridProc:ActionRand() self.Seed = AsyncRand() ObjModified(self) end function GridProc:GetLog() return self.log and table.concat(self.log, "\n") or "" end function GridProc:GetCount() return #self end function GridProc:Invalidate(state) end function GridProc:RunOps(state, from, to) local old_indent = state.indent local new_indent = (old_indent or "") .. " . " for i = 1, #self do local op = self[i] if not from or op == from then SuspendObjModified("RunOp") from = false state.indent = new_indent local err = op:RunOp(state) state.indent = old_indent if err and op.Optional then op.ignored_err = err err = nil else op.ignored_err = nil end op.run_err = err op.proc = state.proc ObjModified(op) ResumeObjModified("RunOp") if err then self.err_op = op self.err_msg = err return err end end if op == to then break end end end function GridProc:Run(state, from, to) state = state or {} PauseInfiniteLoopDetection("GridProc.Run") GridStatsReset() DbgStopInspect() local run_mode = state.run_mode or developer and self.RunMode or "GM" if run_mode == "Profile" then run_mode = "GM" FunctionProfilerStart() end if run_mode == "GM" then SuspendObjModified("GridProc.Run") end state.run_mode = run_mode local seed = state.rand if not seed then local seed_key, seed_tbl = self:GetSeedSaveDest() assert(not seed_key or seed_tbl) if self.LoadSeed then seed = seed_key and seed_tbl and seed_tbl[seed_key] or 0 else seed = self.Randomize and AsyncRand() or self.Seed if self.SaveSeed and seed_key and seed_tbl then seed_tbl[seed_key] = seed end end state.rand = seed end local grids = state.grids or {} local params = state.params or {} state.env = state.env or setmetatable({}, {__index = function(tbl, key) local grid = rawget(grids, key) if grid ~= nil then return grid end local param = rawget(params, key) if param ~= nil then return param end assert(rawget(tbl, key) == nil) return rawget(_G, key) end}) state.grids = grids state.params = params state.tags = state.tags or {} state.refs = state.refs or {} state.proc = state.proc or self local dump_str if self.Dump and state.run_mode ~= "GM" and state.proc == self then dump_str = pstr("", 1024*1024) local unpack = table.unpack local appendf = dump_str.appendf local append = dump_str.append state.dump = function(fmt, ...) appendf(dump_str, fmt, ...) append(dump_str, "\n") end end local start_exec_count = state.exec_count or 0 state.exec_count = start_exec_count self.err_msg = nil self.err_op = nil self.start_time = GetPreciseTicks() self.Time = 0 self.log = {} if self.Lightmodel ~= "" then SetLightmodelOverride(false, self.Lightmodel) end self:AddLog("Running") ObjModified(self) state.test = true local err = self:RunOps(state, from, to) if not err then state.test = nil self:RunInit(state) self:RunOps(state, from, to) end self.ExecCount = state.exec_count - start_exec_count local finalize_idx = self:AddLog("Finalize generation...") local start_finalize = GetPreciseTicks() self:RunDone(state) if dump_str then LastGridProcName = self:GetFullName() LastGridProcDump = dump_str CreateRealTimeThread(function() local filename = LastGridProcName .. ".txt" local err = AsyncStringToFile(filename, dump_str) if err then print("failed to write log:", err) elseif config.DebugMapGen then ---[[ local path = ConvertToOSPath(filename) print("log file saved to:", path) OpenTextFileWithEditorOfChoice(path) --]] end end) end local stat_alive = GridStatsAlive() or "" if #stat_alive > 0 then DebugPrint("Grids:\n") DebugPrint(print_format(stat_alive)) DebugPrint("\n") end local stat_usage = GridStatsUsage() or "" if #stat_usage > 0 then DebugPrint("Grid ops:\n") DebugPrint(print_format(stat_usage)) DebugPrint("\n") end ResumeObjModified("GridProc.Run") ResumeInfiniteLoopDetection("GridProc.Run") FunctionProfilerStop() local time_finalize = GetPreciseTicks() - start_finalize if finalize_idx and time_finalize > 0 then self.log[finalize_idx] = "Finalize generation: " .. time_finalize .. " ms" end self:AddLog(self:GetError() or "Finished with success") return self.err_msg end function GridProc:GetFullName() return self.class end function GridProc:AddLog(text, state) if not text then return end text = (state and state.indent or "") .. text local log = self.log local idx = #log + 1 log[idx] = text self.Time = GetPreciseTicks() - self.start_time ObjModified(self) return idx end function GridProc:RunInit(state) end function GridProc:RunDone(state) end function GridProc:GetError() return self.err_msg and string.format("Error in '%s': '%s'", self.err_op.GridOpType, self.err_msg) end ---- DefineClass.GridProcPreset = { __parents = { "Preset", "GridProc" }, EditorCustomActions = { { Menubar = "Action", Toolbar = "main", Name = "Run All", FuncName = "ActionRunAll", Icon = "CommonAssets/UI/Ged/play.tga", Shortcut = "R", }, { Menubar = "Action", Toolbar = "main", Name = "Run From Cursor", FuncName = "ActionRunFromCursor", Icon = "CommonAssets/UI/Ged/right.tga", }, { Menubar = "Action", Toolbar = "main", Name = "Run To Cursor", FuncName = "ActionRunToCursor", Icon = "CommonAssets/UI/Ged/log-focused.tga", }, { Menubar = "Action", Toolbar = "main", Name = "Run Cursor Only", FuncName = "ActionRunCursorOnly", Icon = "CommonAssets/UI/Ged/filter.tga", Split = true}, }, ContainerClass = "GridOp", StoreAsTable = false, EnableReloading = false, run_thread = false, run_start = 0, run_state = false, run_from = false, run_to = false, EditorMenubarName = false, } function GridProcPreset:ActionRunAll(ged) self:ScheduleRun() end local function RecalcProc(proc, op, recalc) recalc = recalc or proc.OnChange local from, to if recalc == "recalc_op" then from = op to = op elseif recalc == "recalc_to" then to = op elseif recalc == "recalc_from" then from = op elseif recalc ~= "recalc_all" then return end local prev_state = proc.run_state or empty_table local state = { grids = prev_state.grids, params = prev_state.params, } proc:ScheduleRun(state, from, to) end function GridProcPreset:ActionRunToCursor(ged) if IsKindOf(ged.selected_object, "GridOp") then RecalcProc(self, ged.selected_object, "recalc_to") end end function GridProcPreset:ActionRunFromCursor(ged) if IsKindOf(ged.selected_object, "GridOp") then RecalcProc(self, ged.selected_object, "recalc_from") end end function GridProcPreset:ActionRunCursorOnly(ged) if IsKindOf(ged.selected_object, "GridOp") then RecalcProc(self, ged.selected_object, "recalc_op") end end function ActionOpenMapgenFolder(ged) local path = ConvertToOSPath("svnAssets/Source/MapGen/" .. GetMapName()) CreateRealTimeThread(function() AsyncExec(string.format('cmd /c start /D "%s" .', path)) end) end function GridProcPreset:ScheduleRun(state, from, to, delay) self.run_start = RealTime() + (delay or 0) self.run_state = state or {} self.run_from = from self.run_to = to if IsValidThread(self.run_thread) then return end self.run_thread = CreateRealTimeThread(function(self) while self.run_start > RealTime() do Sleep(self.run_start - RealTime()) end self:Run(self.run_state, self.run_from, self.run_to) end, self) end function GridProcPreset:Run(state, ...) local err = GridProc.Run(self, state, ...) local proc = state and state.proc or self if proc == self then ObjModified(Presets[self.class]) end return err end function GridProcPreset:GetFullName() return self.id end ---- local preview_size = 512 local preview_recision = 1000 local float_recision = 1000000 local function ResampleGridForPreview(grid) grid = grid and not IsComputeGrid(grid) and GridRepack(grid, "F") or grid return GridResample(grid, preview_size, preview_size, false) end local function no_outputs(op) for id in pairs(op.output_props or empty_table) do if (op[id] or "") ~= "" then return end end return true end local function output_names(op) local items = {} local outputs = op.outputs or empty_table local default = op.output_default for id in pairs(op.output_props or empty_table) do items[#items + 1] = { value = id ~= default and id, name = op[id] } end table.sortby_field(items, "name") return items end local function allowed_run_mode_items(op) local def_modes = getmetatable(op).RunModes or all_run_modes if def_modes == all_run_modes then return run_mode_items end local items = {} for _, mode in ipairs(run_mode_items) do if def_modes[mode] then items[#items + 1] = mode end end return items end DefineClass.GridOp = { __parents = { "PropertyObject" }, properties = { { category = "General", id = "Enabled", name = "Enabled", editor = "bool", default = true }, { category = "General", id = "Optional", name = "Optional", editor = "bool", default = false }, { category = "General", id = "Breakpoint", name = "Breakpoint", editor = "bool", default = false }, { category = "General", id = "GridOpType", name = "Type", editor = "text", read_only = true, dont_save = true }, { category = "General", id = "RunModes", name = "Run Modes", editor = "set", default = all_run_modes, items = allowed_run_mode_items }, { category = "General", id = "UseParams", name = "Use Params", editor = "bool", default = false, no_edit = function(self) return not self.param_props end }, { category = "General", id = "Operations", name = "Operation", editor = "set", default = empty_table, items = function(self) return self.operations or empty_table end, max_items_in_set = 1, dont_save = true, no_edit = function(self) return #(self.operations or "") <= 1 end }, { category = "General", id = "Operation", editor = "text", default = "", no_edit = true }, { category = "General", id = "Seed", name = "Seed", editor = "number", default = false, buttons = {{name = "Rand", func = "RandSeed"}}, help = "Custom rand seed. If not specified, it will be generated based on the operation name." }, { category = "General", id = "Comment", name = "Comment", editor = "text", default = "" }, { category = "Stats", id = "RunTime", name = "Time (ms)", editor = "number", default = -1, read_only = true, dont_save = true }, { category = "Stats", id = "RunError", name = "Error", editor = "text", default = false, read_only = true, dont_save = true }, { category = "Stats", id = "ParamValues", name = "Params", editor = "text", default = false, lines = 1, max_lines = 3, read_only = true, dont_save = true }, { category = "Preview", id = "OutputSelect", name = "Output Select", editor = "choice", default = false, items = output_names, dont_save = true, max_items_in_set = 1, no_edit = no_outputs }, { category = "Preview", id = "OutputSize", name = "Output Size", editor = "point", default = false, read_only = true, dont_save = true, no_edit = no_outputs }, { category = "Preview", id = "OutputLims", name = "Output Lims", editor = "point", default = false, scale = preview_recision, read_only = true, dont_save = true, no_edit = no_outputs }, { category = "Preview", id = "OutputType", name = "Output Type", editor = "text", default = false, read_only = true, dont_save = true, no_edit = no_outputs }, { category = "Preview", id = "OutputPreview", name = "Output Grid", editor = "grid", default = false, min = preview_size, max = preview_size, read_only = true, dont_save = true, no_edit = no_outputs }, }, GridOpType = "", EditorName = false, start_time = 0, recalc_on_change = true, proc = false, reset_props = false, inputs = false, input_props = false, input_fmt = false, input_bits = false, outputs = false, output_props = false, output_default = false, prop_to_param = false, params = false, param_props = false, operations = false, operation_text_only = false, props_processed = false, run_err = false, ignored_err = false, } local function is_optional(obj, prop) return eval(prop.optional, obj, prop) end local function is_ignored(obj, prop) return eval(prop.ignore_errors, obj, prop) end local function is_disabled(obj, prop) return eval(prop.no_edit, obj, prop) end function OnMsg.ClassesPostprocess() ClassDescendants("GridOp", function(class, def) if not def.props_processed then print("GridOp class", class, "requires GridOpType value") end local prop_to_param, param_props local input_props, output_props, reset_props for _, prop in ipairs(def.properties or empty_table) do if prop.grid_param then if not param_props then param_props = {} def.param_props = param_props end param_props[prop.id] = prop elseif prop.use_param then if not prop_to_param then prop_to_param = {} def.prop_to_param = prop_to_param end prop_to_param[prop.id] = prop.use_param end if prop.grid_input then if not input_props then input_props = {} def.input_props = input_props end input_props[prop.id] = prop end if prop.grid_output then if not output_props then output_props = {} def.output_props = output_props def.output_default = prop.id end output_props[prop.id] = prop end if prop.to_reset then if not reset_props then reset_props = {} def.reset_props = reset_props end reset_props[prop.id] = prop end end end) end function OnMsg.ClassesGenerate(classdefs) for class, def in pairs(classdefs) do local op_type = def.GridOpType if op_type then def.props_processed = true local operations = def.operations if op_type ~= "" then if operations then def.Operation = operations[1] if #operations > 1 then op_type = op_type .. ": " .. table.concat(operations, '-') end end def.EditorName = op_type end local prop_to_param, param_props local props = def.properties or empty_table local prop_idx = 1 while prop_idx <= #props do local prop = props[prop_idx] local category = prop.category if prop.grid_param then local no_edit = prop.no_edit prop.no_edit = function(self, prop) return not self.UseParams or eval(no_edit, self, prop) end elseif prop.use_param then local no_edit = prop.no_edit local param_id = prop.id .. "Param" prop.use_param = param_id table.insert(props, prop_idx + 1, { id = param_id, name = prop.name .. " Param", editor = "choice", default = "", items = GridOpParams, grid_param = true, optional = true, category = category, operation = prop.operation, enabled_by = prop.enabled_by, no_edit = no_edit, }) prop.no_edit = function(self, prop) return self.UseParams and self[param_id] ~= "" or eval(no_edit, self, prop) end end if prop.operation then local no_edit = prop.no_edit local list = prop.operation if type(list) ~= "table" then list = { list } end local disable_by, enable_by for _, name in ipairs(list) do if string.starts_with(name, "!") then name = string.sub(name, 2) disable_by = table.create_set(disable_by, name, true) else enable_by = table.create_set(enable_by, name, true) end end prop.no_edit = function(self, prop) if disable_by and disable_by[self.Operation] or enable_by and not enable_by[self.Operation] then return true end return eval(no_edit, self, prop) end end if prop.enabled_by then local no_edit = prop.no_edit local list = prop.enabled_by if type(list) ~= "table" then list = { list } end local disable_by, enable_by for _, name in ipairs(list) do if string.starts_with(name, "!") then name = string.sub(name, 2) disable_by = table.create_set(disable_by, name, true) else enable_by = table.create_set(enable_by, name, true) end end prop.no_edit = function(self, prop) for prop_id in pairs(disable_by) do if (self[prop_id] or "") ~= "" then return true end end if enable_by then local found for prop_id in pairs(enable_by) do if (self[prop_id] or "") ~= "" then found = true break end end if not found then return true end end return eval(no_edit, self, prop) end end if prop.optional and prop.help then prop.help = prop.help .. " (optional)" end if category == "Preview" or category == "Stats" then prop.dont_save = true prop.to_reset = prop.read_only prop.dont_recalc = true end prop_idx = prop_idx + 1 end end end end function GridOp:RandSeed() self.Seed = AsyncRand() ObjModified(self) end function GridOp:SetOutputSelect(name) self.OutputSelect = name self.OutputSize = nil self.OutputLims = nil self.OutputType = nil self.OutputPreview = nil end function GridOp:SetGridOutput(name, grid) local outputs = (name or "") ~= "" and self.outputs if outputs then assert(grid) outputs[name] = grid end end function GridOp:GetGridInput(name) local inputs = (name or "") and self.inputs return inputs and inputs[name] or nil end function GridOp:GetOutputSelectGrid() local output = self.OutputSelect or self.output_default local name = output and self[output] return name and (self.outputs or empty_table)[name] end function GridOp:GetOutputSize() local size = self.OutputSize if not size then local grid = self:GetOutputSelectGrid() size = grid and point(grid:size()) or point20 self.OutputSize = size end return size end function GridOp:GetOutputLims() local lims = self.OutputLims if not lims then local grid = self:GetOutputSelectGrid() lims = IsComputeGrid(grid) and point(GridMinMax(grid, preview_recision)) or point20 self.OutputLims = lims end return lims end function GridOp:GetOutputType() local gtype = self.OutputType if not gtype then local grid = self:GetOutputSelectGrid() gtype = grid and GridGetPID(grid) or "" self.OutputType = gtype end return gtype end function GridOp:GetOutputPreview() local preview = self.OutputPreview if not preview then local grid = self:GetOutputSelectGrid() preview = grid and ResampleGridForPreview(grid) self.OutputPreview = preview end return preview end function GridOp:SetOperations(opset) local op for key, value in pairs(opset) do if value then op = key break end end self.Operation = op end function GridOp:GetOperations() return self.Operation ~= "" and set(self.Operation) or set() end function GridOp:GetValue(prop_id) local prop_to_param = self.UseParams and self.prop_to_param local param_id = prop_to_param and prop_to_param[prop_id] local param = param_id and self[param_id] or "" if param ~= "" then return self.params and self.params[param] end return self[prop_id] end function GridOp:GetValueText(prop_id, default) local prop_to_param = self.UseParams and self.prop_to_param local param_id = prop_to_param and prop_to_param[prop_id] local param = param_id and self[param_id] or "" if param ~= "" then return "<" .. param_id .. ">", param end local value = self[prop_id] if value ~= default then return "<" .. prop_id .. ">", value end return "" end function GridOp:CollectTags(tags) end function GridOp:RunTest(state) end function GridOp:RunOp(state) local run_mode = state.run_mode if not self.Enabled or not self.RunModes[run_mode] then return end local test = state.test local grids = state.grids local refs = state.refs local params local param_props = self.param_props if param_props then params = self.params or {} self.params = params local state_params = state.params for id, prop in pairs(param_props) do if not is_disabled(self, prop) then local name = self[id] or "" if name ~= "" then if not test then -- TODO: remove non-existent params from the previous run local param = state_params[name] if not param then -- possible in partial runs param = params[name] state_params[name] = param end if param ~= nil then params[name] = param else return "Param Not Found: " .. name end end elseif not is_optional(self, prop) then return "Param Name Expected: " .. prop.name end end end end local outputs local output_props = self.output_props if output_props then outputs = {} self.outputs = outputs for id, prop in pairs(output_props) do if not is_disabled(self, prop) and not is_optional(self, prop) then local name = self[id] or "" if name == "" then return "Output Name Expected: " .. prop.name end end end end local inputs local input_props = self.input_props if input_props then local input_fmt, input_bits = self.input_fmt, self.input_bits inputs = self.inputs or {} self.inputs = inputs for id, prop in pairs(input_props) do if not is_disabled(self, prop) then local name = self[id] or "" if name ~= "" then if not test then local grid = grids[name] if not grid then -- possible in partial runs grid = inputs[name] grids[name] = grid end if grid then if input_fmt then grid = GridRepack(grid, input_fmt, input_bits or nil) end inputs[name] = grid elseif not (output_props or empty_table)[id] then if not is_ignored(self, prop) then return "Input Not Found: " .. name end end else refs[name] = (refs[name] or 0) + 1 end elseif not is_optional(self, prop) then return "Input Name Expected: " .. prop.name end end end end local operations = self.operations if operations and not table.find(operations, self.Operation) then return "Grid Operation Expected" end if test then self.RunTime = nil self:CollectTags(state.tags) return self:RunTest(state) end for id, prop in pairs(self.reset_props or empty_table) do self[id] = nil end local exec_count = state.exec_count + 1 state.exec_count = exec_count local name = self:GetFullName() local prev_rand = state.rand local rand = xxhash(state.rand, self.Seed or name) state.rand = rand bp(self.Breakpoint) local dump = state.dump if dump then dump("\nGridOp %03d 0x%016X: %s", exec_count, rand, name) end self.start_time = GetPreciseTicks() local err = self:Run(state) self.RunTime = GetPreciseTicks() - self.start_time state.rand = prev_rand -- restore the original seed, thus avoiding the order/count of operations affecting the randomness if err then return err end if output_props then for id, prop in pairs(output_props) do local name = self[id] or "" if name ~= "" then local grid = outputs[name] if not grid and not is_optional(self, prop) then return "Output Missing: " .. name end if dump and grid then local w, h = grid:size() local t, b = IsComputeGrid(grid) dump("* grid '%s' %d x %d '%s%s' 0x%016X", name, w, h, t and tostring(t) or "", b and tostring(b) or "", xxhash(grid)) end if run_mode == "GM" then local prev_grid = grids[name] if prev_grid then prev_grid:free() end end grids[name] = grid end end end state.proc:AddLog(self:GetLogMessage(), state) if run_mode == "GM" then self.inputs = nil self.outputs = nil self.params = nil for name, grid in pairs(inputs or empty_table) do refs[name] = refs[name] - 1 if grid ~= grids[name] then grid:free() end end for name, grid in pairs(grids) do if (refs[name] or 0) <= 0 then grid:free() grids[name] = nil end end end end function GridOp:GetFullName() return string.strip_tags(_InternalTranslate(T(self:GetLogText()), self, false)) end function GridOp:GetLogMessage() if self.RunTime <= 1 then return end return string.format("%s: %d ms", self:GetFullName(), self.RunTime) end function GridOp:GetParamValues() local prop_to_param = self.UseParams and self.prop_to_param local params = self.params if not next(params) or not next(prop_to_param) then return "" end local list, passed = {}, {} for prop_id, param_id in pairs(prop_to_param) do local param = param_id and self[param_id] or "" if not passed[param] then passed[param] = true local value = param ~= "" and params[param] if value then list[#list + 1] = string.format("%s = %s", param, tostring(value)) end end end table.sort(list) return table.concat(list, ", ") end function GridOp:Run() end function GridOp:GetError() return self.run_err end function GridOp:GetRunError() return self.run_err or self.ignored_err end function GridOp:GetEditorText() if self.Operation == "" then return "" elseif self.operation_text_only then return "" else return " " end end function GridOp:GetLogText() return self:GetEditorText() end function GridOp:GetEditorView() local text = self:GetEditorText() or "" if text == "" then text = "" end local my_run_modes = self.RunModes local run_mode = (self:GetPreset() or empty_table).RunMode if run_mode and not my_run_modes[run_mode] then text = "" elseif not self.Enabled then text = "[] " .. text elseif self.run_err then text = "" elseif self.ignored_err then text = "[] " .. text --[[ elseif my_run_modes ~= all_run_modes then text = " " .. text --]] elseif self.RunTime > 0 then text = text .. " " end if (self.Comment or "") ~= "" then text = "\n" .. text end return Untranslated(text) end function GridOp:GetPreset() return GetParentTable(self) end function GridOp:Recalc() local proc = self.proc if proc then RecalcProc(proc, self) end end function GridOp:OnEditorSetProperty(prop_id, old_value, ged) local proc = self.recalc_on_change and self.proc if not proc then return end local meta = self:GetPropertyMetadata(prop_id) or empty_table if meta.dont_recalc then return end RecalcProc(proc, self) end ---- DefineClass.GridOpComment = { __parents = { "GridOp" }, GridOpType = "Comment", recalc_on_change = false, } function GridOpComment:GetEditorView() if (self.Comment or "") ~= "" then return Untranslated("") end return "" end ---- local function GridOpItems(grid_op, def, callback) local local_items, global_items = {}, {} local parent = grid_op and grid_op:GetPreset() if not parent then return {} end ForEachPreset(parent.class, function(preset) local is_local = preset == parent local items = is_local and local_items or global_items for _, op in ipairs(preset) do callback(op, items, is_local) end end) for key in pairs(local_items) do global_items[key] = nil end local names = table.keys(local_items, true) table.iappend(names, table.keys(global_items, true)) if def then table.insert(names, 1, def) end return names end function GridOpOutputNames(grid_op) return GridOpItems(grid_op, nil, function(op, items) for _, prop in ipairs(op:GetProperties() or empty_table) do if prop.grid_output then items[op[prop.id]] = true end end end) end function GridOpParams(grid_op) return GridOpItems(grid_op, "", function(op, items, is_local) if IsKindOf(op, "GridOpParam") then if not op.ParamLocal or is_local then items[op.ParamName] = true end end end) end DefineClass.GridOpParam = { __parents = { "GridOp" }, properties = { { category = "General", id = "ParamName", name = "Name", editor = "combo", default = "", items = GridOpParams }, { category = "General", id = "ParamValue", name = "Value", editor = "text", default = "" }, { category = "General", id = "ParamLocal", name = "Local", editor = "bool", default = false }, }, GridOpType = "", } function GridOpParam:GetParamStr() return tostring(self.ParamValue) end function GridOpParam:GetParam(state) return self.ParamValue end function GridOpParam:Run(state) if self.ParamName == "" then return "Missing Param Name" end local value, err = self:GetParam(state) if err then return err end state.params[self.ParamName] = value local dump = state.dump if dump then dump("* %s '%s' = %s", type(value), self.ParamName, ValueToLuaCode(value)) end end function GridOpParam:GetEditorText() return " = " end ---- DefineClass.GridOpParamEval = { __parents = { "GridOpParam" }, properties = { { category = "Preview", id = "ParamPreview", name = "Evaluated", editor = "text", default = "", read_only = true, dont_save = true }, }, GridOpType = "Param", value = false, } function GridOpParamEval:GetParam(state) local func, err = load("return " .. self.ParamValue, nil, nil, state.env) if not func then return nil, err end local success, value = pcall(func) if not success then return nil, value end self.value = value return value end function GridOpParamEval:GetParamPreview() if not self.proc then return "" end return ValueToLuaCode(self.value) end ---- DefineClass.GridOpRun = { __parents = { "GridOp" }, properties = { { category = "General", id = "Sequence", name = "Sequence", editor = "preset_id", default = "", preset_class = function(self) return (self:GetPreset() or empty_table).class end, operation = "Proc" }, { category = "General", id = "Iterations", name = "Iterations", editor = "number", default = 1, min = 1, operation = "Proc" }, { category = "General", id = "InputName", name = "Input Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true, operation = {"Code", "Func"} }, { category = "General", id = "OutputName", name = "Output Name", editor = "combo", default = "", items = GridOpOutputNames, grid_output = true, optional = true, operation = {"Code", "Func"} }, { category = "General", id = "Tags", name = "Tags", editor = "set", items = {"Terrain", "Objects"}, default = false }, { category = "General", id = "Code", name = "Code", editor = "func", default = false, lines = 1, max_lines = 100, params = "state, grid", operation = "Code" }, { category = "General", id = "Func", name = "Func", editor = "text", default = "", operation = "Func" }, { category = "General", id = "Param1", name = "Param 1", editor = "choice", default = "", operation = "Func", items = GridOpParams, optional = true, grid_param = true }, { category = "General", id = "Param2", name = "Param 2", editor = "choice", default = "", operation = "Func", items = GridOpParams, optional = true, grid_param = true }, { category = "General", id = "Param3", name = "Param 3", editor = "choice", default = "", operation = "Func", items = GridOpParams, optional = true, grid_param = true }, }, GridOpType = "Run", operations = {"Proc", "Code", "Func"}, } function GridOpRun:CollectTags(tags) table.append(tags, self.tags) end function GridOpRun:GetEditorText() local op = self.Operation if op == "Proc" then local iters = self.Iterations > 1 and " x " or "" return " " .. iters elseif op == "Func" then if self.Func == "" then return "" end local params = {} if self.InputName ~= "" then params[1] = "" end if self.Param1 ~= "" then params[#params + 1] = "" elseif self.Param2 ~= "" or self.Param3 ~= "" then params[#params + 1] = "nil" end if self.Param2 ~= "" then params[#params + 1] = "" elseif self.Param3 ~= "" then params[#params + 1] = "nil" end if self.Param3 ~= "" then params[#params + 1] = "" end local params_str = #params > 0 and table.concat(params, ", ") or "" local func_str = "(" .. params_str .. ")" if self.OutputName ~= "" then return " = " .. func_str end return " " .. func_str elseif op == "Code" then local source = FuncSource[self.Code] local source_str = "" if source and source[3] then source_str = "\n" end if self.OutputName ~= "" then if self.InputName ~= "" then return " = ()" .. source_str end return " = ()" .. source_str end if self.InputName ~= "" then return " ()" .. source_str end return "" .. source_str end return GridOp.GetEditorText(self) end function GridOpRun:GetLogText() return " " end function GridOpRun:GetTarget(state) local sequences = {} local parent = state.proc ForEachPreset(parent.class, function(preset, group, sequence, sequences) if preset.id == sequence then sequences[#sequences + 1] = preset end end, self.Sequence, sequences) if #sequences == 0 then return nil, "Cannot Find Sequence: " .. self.Sequence elseif #sequences > 1 then return nil, "Multiple Sequences Named: " .. self.Sequence elseif sequences[1] == parent then return nil, "Cannot Run Itself: " .. self.Sequence end return sequences[1] end function GridOpRun:RunTest(state) local op = self.Operation if op == "Proc" then local target, err = self:GetTarget(state) if err then return err end self.target = target for it = 1,self.Iterations do local err = target:RunOps(state) if err then return err end end elseif op == "Code" then local source = FuncSource[self.Code] if source and source[4] then return source[4] end elseif op == "Func" then local name = self.Func or "" if name == "" then return "Function name expected" end if not _G[name] then return "No such global function" end end end function GridOpRun:Run(state) local op = self.Operation if op == "Proc" then local target = self.target if not target then return "Gather Run Error" end state.running = state.running or {} if state.running[target] then return "Infinite Recursion" end state.completed = state.completed or {} if state.completed[target] and target.RunOnce then return end state.running[target] = true local iters_str = self.Iterations > 1 and " x " .. self.Iterations or "" state.proc:AddLog("Running " .. self.Sequence .. iters_str, state) for it = 1,self.Iterations do local err = target:RunOps(state) if err then return err end end state.running[target] = nil state.completed[target] = true elseif op == "Code" then local input_grid = self:GetGridInput(self.InputName) if self.InputName ~= "" and not input_grid then return "Input grid " .. self.InputName .. "not found" end local success, err, output_grid = pcall(self.Code, state, input_grid) -- todo: debug missing error propagation if err then return err end if self.OutputName ~= "" then if not output_grid then return "Grid result expected" end self:SetGridOutput(self.OutputName, output_grid) end elseif op == "Func" then local input_grid = self:GetGridInput(self.InputName) if self.InputName ~= "" and not input_grid then return "Input grid " .. self.InputName .. "not found" end local func = _G[self.Func] local params = self.params local param1 = params and params[self.Param1] local param2 = params and params[self.Param2] local param3 = params and params[self.Param3] local success, output_grid = pcall(func, input_grid, param1, param2, param3) if not success then return output_grid end if self.OutputName ~= "" then if not output_grid then return "Grid result expected" end self:SetGridOutput(self.OutputName, output_grid) end end end ---- DefineClass.GridOpDir = { __parents = { "GridOp" }, properties = { { category = "General", id = "BaseDir", name = "Base Dir", editor = "browse", default = "", folder = "svnAssets" }, }, GridOpType = "Directory Change", } function GridOpDir:GetEditorText() return "Set Directory " end function GridOpDir:SetBaseDir(path) local path, fname, ext = SplitPath(path) self.BaseDir = SlashTerminate(path) end function GridOpDir:Run(state) if self.BaseDir == "" then return "Base Dir Expected" end if not io.exists(self.BaseDir) then return "Base Dir Do Not Exists" end state.base_dir = self.BaseDir end local function GridOpBaseDirs(grid_op) local base_dirs = GridOpItems(grid_op, "svnAssets/Source/MapGen", function(op, items) if IsKindOf(op, "GridOpDir") then items[op.BaseDir] = true end end) return base_dirs end ---- DefineClass.GridOpOutput = { __parents = { "GridOp" }, properties = { { category = "General", id = "OutputName", name = "Output Name", editor = "combo", default = "", items = GridOpOutputNames, grid_output = true }, }, output_preview = false, GridOpType = "", } function GridOpOutput:GetEditorText() local op_str = GridOp.GetEditorText(self) return op_str .. " to " end function GridOpOutput:Run(state) local err, grid = self:GetGridOutput(state) if err then return err end self:SetGridOutput(self.OutputName, grid) end function GridOpOutput:GetGridOutput(state) end ---- DefineClass.GridOpInput = { __parents = { "GridOp" }, properties = { { category = "General", id = "InputName", name = "Input Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true }, }, GridOpType = "", } function GridOpInput:GetEditorText() local op_str = GridOp.GetEditorText(self) return op_str .. " " end function GridOpInput:Run(state) return self:SetGridInput(state, self:GetGridInput(self.InputName)) end function GridOpInput:SetGridInput(state, grid) end ---- DefineClass.GridOpInputOutput = { __parents = { "GridOpInput", "GridOpOutput" }, properties = { { category = "Preview", id = "OutputCurtain", name = "Output Curtain (%)", editor = "number", default = 100, min = 0, max = 100, slider = true, dont_save = true }, }, input_preview = false, output_preview = false, GridOpType = "", } function GridOpInputOutput:GetGridOutputFromInput(state, grid) end function GridOpInputOutput:SetGridInput(state, input) return self:GetGridOutputFromInput(state, input) end function GridOpInputOutput:GetGridOutput(state) return GridOpInput.Run(self, state) end function GridOpInputOutput:Run(state) self.input_preview = nil self.output_preview = nil return GridOpOutput.Run(self, state) end function GridOpInputOutput:GetEditorText() local ops_str = GridOp.GetEditorText(self) if self.InputName == self.OutputName then return ops_str .. " " end return ops_str .. " to " end function GridOpInputOutput:SetOutputCurtain(curtain) self.OutputCurtain = curtain self.OutputPreview = nil end function GridOpInputOutput:SetOutputSelect(name) self.input_preview = nil self.output_preview = nil GridOp.SetOutputSelect(self, name) end function GridOpInputOutput:GetOutputPreview() local preview = self.OutputPreview if not preview then local output = self.output_preview or self:GetOutputSelectGrid() local input = self.input_preview or (self.inputs or empty_table)[self.InputName] if output and input then local curtain = self.OutputCurtain output = ResampleGridForPreview(output) self.output_preview = output preview = output if curtain < 100 then input = ResampleGridForPreview(input) input = GridRepack(input, IsComputeGrid(output)) self.input_preview = input preview = input if curtain > 0 then local l = MulDivRound(preview_size, curtain, 100) local mask = NewComputeGrid(preview_size, preview_size, "U", 8) for x = 0, l do GridDrawColumn(mask, x, preview_size - 1, 1, 1, 1) end preview = GridDest(output) GridRepack(mask, preview) GridLerp(input, preview, output, preview) end end self.OutputPreview = preview end end return preview end ---- local ref_available = function(self) return self.RefName ~= "" end local grid_fmts = {"", "float", "uint16", "uint8"} local function GridTypeToFmt(gt) if gt == "float" then return "f", 32 elseif gt == "uint16" then return "u", 16 elseif gt == "uint8" then return "u", 8 end end DefineClass.GridOpDest = { __parents = { "GridOpOutput" }, properties = { { category = "General", id = "RefName", name = "Grid Reference", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true, help = "Needed to match the same grid size and type" }, { category = "General", id = "Width", name = "Grid Width", editor = "number", default = 0, min = 0, use_param = true, no_edit = ref_available }, { category = "General", id = "Height", name = "Grid Height", editor = "number", default = 0, min = 0, use_param = true, no_edit = ref_available }, { category = "General", id = "GridType", name = "Grid Type", editor = "choice", default = "", items = grid_fmts, no_edit = ref_available }, { category = "General", id = "GridDefault", name = "Grid Default", editor = "number", default = 0 }, }, GridOpType = "", } function GridOpDest:GetGridOutput(state) local ref = self:GetGridInput(self.RefName) local value = self.GridDefault if ref then if value == 0 then return nil, GridDest(ref, true) else local grid = GridDest(ref) GridFill(grid, value) return nil, grid end end local w = self:GetValue("Width") local h = self:GetValue("Height") local t, b = GridTypeToFmt(self.GridType) if not t then return "Grid Type Not Specified" end local grid = NewComputeGrid(w, h, t, b) if value ~= 0 then GridFill(grid, value) end return nil, grid end ---- DefineClass.GridOpFile = { __parents = { "PropertyObject" }, properties = { { category = "General", id = "FileRelative", name = "File Relative", editor = "bool", default = true }, { category = "General", id = "FileName", name = "File Name", editor = "browse", default = "", folder = GridOpBaseDirs, dont_validate = true, allow_missing = function(self) return self.AllowMissing end }, { category = "General", id = "FileFormat", name = "File Format", editor = "choice", default = "", items = {"", "image", "grid", "raw8", "raw16"} }, { category = "Preview", id = "FilePath", name = "File Path Game",editor = "text", default = "", read_only = true, dont_save = true }, { category = "Preview", id = "FilePathOs", name = "File Path OS", editor = "text", default = "", read_only = true, dont_save = true }, }, AllowMissing = false, DefaultFormat = "image", } function GridOpFile:SetFileName(path) if self.FileRelative then local dir, fname, ext = SplitPath(path) path = fname .. ext end self.FileName = path end function GridOpFile:ResolveFilePath(state) if self.FileRelative and not state.base_dir then return "Base Dir Not Set" end if self.FileName == "" then return "File Name Expected" end local path = self.FileName if self.FileRelative then path = state.base_dir .. path end if not self.AllowMissing and not io.exists(path) then return "File Does Not Exist" end self.FilePath = path return nil, path end function GridOpFile:GetFilePathOs() return ConvertToOSPath(self.FilePath) end function GridOpFile:ResolveFileFormat() local fmt = self.FileFormat if fmt == "" then local ext = string.lower(GetPathExt(self.FileName)) if ext == "grid" then fmt = "grid" elseif ext == "r16" then fmt = "raw16" elseif ext == "raw" or ext == "r8" then fmt = "raw8" elseif ext == "tga" or ext == "png" or ext == "jpg" then fmt = "image" else fmt = self.DefaultFormat end end return fmt end ---- local function not_img(op) return op:ResolveFileFormat() ~= "image" end DefineClass.GridOpRead = { __parents = { "GridOpOutput", "GridOpFile" }, properties = { { category = "General", id = "OutputName2", name = "Output Name 2", editor = "combo", default = "", items = GridOpOutputNames, grid_output = true, optional = true, no_edit = not_img}, { category = "General", id = "OutputName3", name = "Output Name 3", editor = "combo", default = "", items = GridOpOutputNames, grid_output = true, optional = true, no_edit = not_img }, { category = "General", id = "OutputName4", name = "Output Name 4", editor = "combo", default = "", items = GridOpOutputNames, grid_output = true, optional = true, no_edit = not_img }, }, GridOpType = "Read", } function GridOpRead:GetEditorText() local outputs = { "" } if self.OutputName2 ~= "" then outputs[#outputs + 1] = "" end if self.OutputName3 ~= "" then outputs[#outputs + 1] = "" end if self.OutputName4 ~= "" then outputs[#outputs + 1] = "" end local str = table.concat(outputs, ", ") str = " " .. str .. " from " if self.FileFormat ~= "" then str = str .. " as " end return str end function GridOpRead:GetGridOutput(state) local err, path = self:ResolveFilePath(state) if err then return err end local grid local fmt = self:ResolveFileFormat() if fmt == "grid" then grid, err = GridReadFile(path) elseif fmt == "image" then local r, g, b, a = ImageToGrids(path) if not r then err = g else grid = r if self.OutputName2 ~= "" then self:SetGridOutput(self.OutputName2, g) end if self.OutputName3 ~= "" then self:SetGridOutput(self.OutputName3, b) end if self.OutputName4 ~= "" then self:SetGridOutput(self.OutputName4, a) end end elseif fmt == "raw16" then grid = NewComputeGrid(0, 0, "U", 16) err = GridLoadRaw(path, grid) elseif fmt == "raw8" then grid = NewComputeGrid(0, 0, "U", 8) err = GridLoadRaw(path, grid) else err = "Unknown File Format" end return err, grid end ---- DefineClass.GridOpWrite = { __parents = { "GridOpInput", "GridOpFile" }, properties = { { category = "General", id = "Normalize", name = "Normalize", editor = "bool", default = true, no_edit = not_img }, }, GridOpType = "Write", FileRelative = false, AllowMissing = true, DefaultFormat = "", } function GridOpWrite:GetEditorText() local str = " to " if self.FileFormat ~= "" then str = str .. " as " end return str end function GridOpWrite:SetGridInput(state, grid) local err, path = self:ResolveFilePath(state) if err then return err end local fmt = self:ResolveFileFormat() if fmt == "grid" then local success success, err = GridWriteFile(grid, path) elseif fmt == "image" then if self.Normalize then grid = GridNormalize(grid, GridDest(grid), 0, 255) end err = GridToImage(path, grid) elseif fmt == "raw8" then local grid_fmt, grid_bits = IsComputeGrid(grid) if grid_fmt ~= "U" or grid_bits ~= 8 then return "Incompatible grid format" end err = GridSaveRaw(path, grid) elseif fmt == "raw16" then local grid_fmt, grid_bits = IsComputeGrid(grid) if grid_fmt ~= "U" or grid_bits ~= 16 then return "Incompatible grid format" end err = GridSaveRaw(path, grid) else return "Unsupported File Format" end return err end ---- local empty_kernel = { 0, 0, 0, 0, 1, 0, 0, 0, 0 } local function GridFilters() return { { value = "", name = "Custom", }, { value = "none", name = "None", kernel = empty_kernel, }, { value = "gaussian", name = "Blur (Gaussian)", kernel = { 1, 2, 1, 2, 4, 2, 1, 2, 1 }, scale = 16, }, { value = "box", name = "Blur (Box)", kernel = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, scale = 9, }, { value = "laplacian", name = "Edges (Laplacian)", kernel = { -1, -1, -1, -1, 8, -1, -1, -1, -1, }, scale = 8, }, { value = "sobel", name = "Slope (Sobel)", kernel = { -2, -2, 0, -2, 0, 2, 0, 2, 2, }, scale = 6, }, { value = "sharpen", name = "Sharpen", kernel = { 0, -1, 0, -1, 5, -1, 0, -1, 0 }, scale = 1, }, } end DefineClass.GridOpFilter = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "Intensity", name = "Filter Degree", editor = "number", default = 1, min = 1, max = 10, step = 1, buttons_step = 1, slider = true, help = "Defines the filter strength" }, { category = "General", id = "Strength", name = "Filter Strength",editor = "number", default = 100, min = 0, max = 100, scale = "%", slider = true }, { category = "General", id = "Filter", name = "Filter Preset", editor = "choice", default = "none", items = GridFilters, dont_save = true, operation = "Convolution" }, { category = "General", id = "Kernel", name = "Filter Kernel", editor = "prop_table", default = empty_kernel, operation = "Convolution" }, { category = "General", id = "Scale", name = "Filter Scale", editor = "number", default = 0, operation = "Convolution" }, { category = "General", id = "Fast", name = "Fast Mode", editor = "bool", default = true, operation = "Smooth" }, { category = "General", id = "RestoreLims", name = "Restore Limits", editor = "bool", default = false }, }, input_fmt = "F", GridOpType = "Filter", operations = {"Smooth", "Convolution"}, } function GridOpFilter:SetFilter(value) local filter = table.find_value(GridFilters(), "value", value) or empty_table if not filter.kernel then return end self.Kernel = table.icopy(filter.kernel) self.Scale = filter.scale end function GridOpFilter:GetFilter() local kernel = self.Kernel local scale = self.Scale for _, filter in ipairs(GridFilters()) do if scale == (filter.scale or 0) and table.iequal(kernel, filter.kernel) then return filter.value end end return "" end function GridOpFilter:GetGridOutputFromInput(state, grid) local strength = self.Strength if strength == 0 then return nil, grid:clone() end local filtered local count = self.Intensity local op = self.Operation local restore = self.RestoreLims if op == "Convolution" then local kernel = self.Kernel if not kernel then return "Missing kernel" end local scale = self.Scale local tmp = GridDest(grid) filtered = grid for i=1,count do GridFilter(filtered, tmp, kernel, scale, restore) filtered, tmp = tmp, filtered end elseif op == "Smooth" then filtered = GridDest(grid) local w, h = grid:size() local fast = self.Fast if fast and (not IsPowerOf2(w) or not IsPowerOf2(h)) then fast = false state.proc:AddLog("Ignoring Fast smooth - the grid size is not a power of 2") end GridSmooth(grid, filtered, count, fast, restore) end if strength < 100 then GridLerp(grid, filtered, filtered, strength, 0, 100) end return nil, filtered end function GridOpFilter:GetEditorText() local op = self.Operation local str_intensity = self.Intensity > 1 and " (x" .. self.Intensity .. ")" or "" if op == "Smooth" then local str = "Smooth " .. str_intensity if self.InputName ~= self.OutputName then str = str .. " to " end return str elseif op == "Convolution" then local str = "Apply filter" .. str_intensity .. " in " if self.InputName ~= self.OutputName then str = str .. " to " end return str end return GridOpInputOutput.GetEditorText(self) end ---- DefineClass.GridOpLerp = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "TargetName", name = "Target Grid Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true }, { category = "General", id = "MaskName", name = "Mask Grid Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true }, { category = "General", id = "Normalize", name = "Mask Normalize", editor = "bool", default = true }, { category = "General", id = "MaskMin", name = "Mask Min", editor = "number", default = 0, enabled_by = "!Normalize" }, { category = "General", id = "MaskMax", name = "Mask Max", editor = "number", default = 100, enabled_by = "!Normalize" }, { category = "General", id = "Convert", name = "Convert Grids", editor = "bool", default = true, help = "Allow converting the grid params to match" }, }, GridOpType = "Lerp", } function GridOpLerp:GetGridOutputFromInput(state, grid) local target = self:GetGridInput(self.TargetName) local mask = self:GetGridInput(self.MaskName) if self.Convert then target = GridMakeSame(target, grid) mask = GridMakeSame(mask, grid) end local res = GridDest(grid) if self.Normalize then GridLerp(grid, res, target, mask) else GridLerp(grid, res, target, mask, self.MaskMin, self.MaskMax) end return nil, res end function GridOpLerp:GetEditorText() return "Lerp - in " end ---- ---- DefineClass.GridOpMorph = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "Depth", name = "Depth", editor = "number", default = 1, min = 1 }, }, GridOpType = "Binary Morphology", operations = {"Erode", "Dilate", "Open", "Close"}, } function GridOpMorph:GetGridOutputFromInput(state, grid) local dst, src = grid, GridDest(grid) local function do_morph(erode) local depth = self.Depth while depth > 0 do src, dst = dst, src if dst == grid then dst = GridDest(grid) end if erode then GridErode(src, dst) else GridDilate(src, dst) end depth = depth - 1 end end local op = self.Operation if op == "Erode" then do_morph(true) elseif op == "Dilate" then do_morph(false) elseif op == "Open" then do_morph(true) do_morph(false) elseif op == "Close" then do_morph(false) do_morph(true) end return nil, dst end function GridOpMorph:GetEditorText() local str = "Morphologically " if self.Depth > 1 then str = str .. " x " end if self.InputName ~= self.OutputName then str = str .. " to " end return str end ---- DefineClass.GridOpConvert = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "GridType", name = "Grid Type", editor = "choice", default = "", items = {"", "float", "uint16", "uint8"}, operation = "Repack", no_edit = ref_available }, { category = "General", id = "RefName", name = "Grid Reference", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true, operation = "Repack", help = "Needed to match the same grid type" }, { category = "General", id = "GridRound", name = "Grid Round", editor = "bool", default = false, operation = "Repack" }, { category = "General", id = "Granularity", name = "Granularity", editor = "number", default = 1, operation = "Round" }, }, GridOpType = "Convert", operations = {"Invert", "Abs", "Not", "Round", "Copy", "Repack"}, } function GridOpConvert:GetGridOutputFromInput(state, grid) local op = self.Operation if op == "Repack" then local t, b = GridTypeToFmt(self.GridType) if not t then local ref = self:GetGridInput(self.RefName) t, b = IsComputeGrid(ref) if not t then return "Grid Type Not Specified" end end local src = grid if self.GridRound then src = GridDest(src) GridRound(grid, src) end return nil, GridRepack(src, t, b, true) end local res = GridDest(grid) if op == "Abs" then GridAbs(grid, res) elseif op == "Not" then GridNot(grid, res) elseif op == "Invert" then GridInvert(grid, res) elseif op == "Round" then GridRound(grid, res, self.Granularity) elseif op == "Copy" then res:copy(grid) end return nil, res end function GridOpConvert:GetEditorText() local text = "" if self.InputName ~= "" then text = text .. " " if self.OutputName ~= "" and self.InputName ~= self.OutputName then text = text .. " to " end end if self.Operation == "Repack" then text = text .. " as " end return text end ---- local function ExtendModeItems() return { { value = 0, text = "" }, { value = 1, text = "Filled" }, { value = 2, text = "Centered" }, } end DefineClass.GridOpResample = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "RefName", name = "Ref Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true, help = "Needed to match the same grid size and type" }, { category = "General", id = "Width", name = "Width", editor = "number", default = 256, min = 0, use_param = true, enabled_by = "!RefName" }, { category = "General", id = "Height", name = "Height", editor = "number", default = 256, min = 0, use_param = true, enabled_by = "!RefName" }, { category = "General", id = "InPercents", name = "In Percents", editor = "bool", default = false, enabled_by = "!RefName" }, { category = "General", id = "Interpolate", name = "Interpolate", editor = "bool", default = true, operation = "Resample" }, { category = "General", id = "RestoreLims", name = "Restore Limits", editor = "bool", default = false, operation = "Resample" }, { category = "General", id = "ExtendMode", name = "Mode", editor = "choice", default = 0, items = ExtendModeItems, operation = "Extend" }, }, GridOpType = "Change Dimensions", operations = {"Resample", "Extend"}, operation_text_only = true, } local function CanFastResample(dim2, dim1) if dim2 == dim1 then return end local dir = 1 if dim1 > dim2 then dim2, dim1 = dim1, dim2 dir = -1 end if dim2 % dim1 ~= 0 then return end local k = dim2 / dim1 if not IsPowerOf2(k) then return end return k * dir end function GridOpResample:GetGridOutputFromInput(state, grid) local w, h local gw, gh = grid:size() local ref = self:GetGridInput(self.RefName) if ref then w, h = ref:size() else w = self:GetValue("Width") h = self:GetValue("Height") if self.InPercents then w = MulDivRound(gw, w, 100) h = MulDivRound(gh, h, 100) end end if w == 0 or h == 0 then return "Invalid Size" end if w == gw and h == gh then return nil, grid:clone() end local op = self.Operation if op == "Resample" then local interpolate, restore = self.Interpolate, self.RestoreLims if interpolate then local kw, kh = CanFastResample(gw, w), CanFastResample(gh, h) if kw and kw == kh then while gw < w do gw, gh = 2 * gw, 2 * gh grid = GridResample(grid, gw, gh, true, restore) end while gw > w do gw, gh = gw / 2, gh / 2 grid = GridResample(grid, gw, gh, true, restore) end return nil, grid end end return nil, GridResample(grid, w, h, interpolate, restore, true) elseif op == "Extend" then return nil, GridExtend(grid, w, h, self.ExtendMode) end end function GridOpResample:GetEditorText() if self.InputName == "" or self.OutputName == "" then return "" end local str = " " if self.InputName ~= self.OutputName then str = str .. " in " end if self.RefName ~= "" then str = str .. " as " else local wstr, w = self:GetValueText("Width") local hstr, h = self:GetValueText("Height") if not self.InPercents then str = str .. " to (" .. wstr .. ", " .. hstr .. ")" elseif w ~= h then str = str .. " to (" .. wstr .. "%, " .. hstr .. "%)" else str = str .. " to " .. wstr .. "%" end end return str end ---- DefineClass.GridOpChangeLim = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "Min", name = "Min", editor = "number", default = 0, use_param = true }, { category = "General", id = "Max", name = "Max", editor = "number", default = 1, use_param = true }, { category = "General", id = "Scale", name = "Scale", editor = "number", default = 1, use_param = true }, { category = "General", id = "Smooth", name = "Smooth", editor = "bool", default = false }, { category = "General", id = "Remap", name = "Remap", editor = "bool", default = false }, { category = "General", id = "RemapMin", name = "Remap Min", editor = "number", default = 0, use_param = true, enabled_by = "Remap" }, { category = "General", id = "RemapMax", name = "Remap Max", editor = "number", default = 1, use_param = true, enabled_by = "Remap" }, }, operations = {"Normalize", "Mask", "Clamp", "Band", "Remap"}, GridOpType = "Change Limits", } function GridOpChangeLim:GetGridOutputFromInput(state, grid) local min = self:GetValue("Min") local max = self:GetValue("Max") local scale = self:GetValue("Scale") local new_min, new_max local op = self.Operation local res = GridDest(grid) if op == "Normalize" then if min >= max then return "Invalid Range" end res = GridNormalize(grid, res, min, max, scale) elseif op == "Mask" then GridMask(grid, res, min, max, scale) min, max = 0, 1 elseif op == "Clamp" then if min >= max then return "Invalid Range" end GridClamp(grid, res, min, max, scale) elseif op == "Band" then if min >= max then return "Invalid Range" end GridBand(grid, res, min, max, scale) elseif op == "Remap" then res:copy(grid) elseif op == "Max" then GridMax(grid, res, min, scale) elseif op == "Min" then GridMin(grid, res, max, scale) end if self.Smooth and not GridIsFlat(res) then GridSin(res, min, max) new_min, new_max = min, max min, max = -1, 1 end if self.Remap then new_min = self:GetValue("RemapMin") new_max = self:GetValue("RemapMax") end if min ~= (new_min or min) or max ~= (new_max or max) then GridRemap(res, min, max, new_min, new_max) end return nil, res end function GridOpChangeLim:GetEditorText() local min_str = self:GetValueText("Min") local max_str = self:GetValueText("Max") local range_str, grids_str, remap_str = " between ", "", "" if self.InputName ~= "" then grids_str = " " if self.OutputName ~= "" and self.InputName ~= self.OutputName then grids_str = grids_str .. " to " end end if self.Remap then local from_str = self:GetValueText("RemapMin") local to_str = self:GetValueText("RemapMax") remap_str = " to " .. from_str .. " - " .. to_str range_str = " from " end return "" .. grids_str .. range_str .. min_str .. " - " .. max_str .. remap_str end ---- ---- DefineClass.GridOpMinMax = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "RefName", name = "Grid Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true }, { category = "General", id = "RefValue", name = "Value", editor = "number", default = 0, use_param = true, enabled_by = "!GridName" }, { category = "General", id = "Scale", name = "Scale", editor = "number", default = 1, use_param = true }, }, operations = {"Min", "Max"}, GridOpType = "MinMax", } function GridOpMinMax:GetGridOutputFromInput(state, grid) local ref_value, scale = self:GetValue("RefValue"), self:GetValue("Scale") local ref_grid = self:GetGridInput(self.RefName) local op = self.Operation local res = GridDest(grid) if op == "Max" then if ref_grid then GridMax(grid, res, ref_grid) else GridMax(grid, res, ref_value, scale) end elseif op == "Min" then if ref_grid then GridMin(grid, res, ref_grid) else GridMin(grid, res, ref_value, scale) end end return nil, res end function GridOpMinMax:GetEditorText() if self.InputName == "" or self.OutputName == "" then return "" end local str = " = (, " if self.RefName ~= "" then str = str .. "" else str = str .. self:GetValueText("RefValue") end return str .. ")" end ---- DefineClass.GridOpMulDivAdd = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "MulName", name = "Mul Grid Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true }, { category = "General", id = "AddName", name = "Add Grid Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true }, { category = "General", id = "SubName", name = "Sub Grid Name", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, optional = true }, { category = "General", id = "Mul", name = "Mul", editor = "number", default = 1, use_param = true }, { category = "General", id = "Div", name = "Div", editor = "number", default = 1, use_param = true }, { category = "General", id = "Add", name = "Add", editor = "number", default = 0, use_param = true }, { category = "General", id = "Sub", name = "Sub", editor = "number", default = 0, use_param = true }, { category = "General", id = "Convert", name = "Convert Grids", editor = "bool", default = true, help = "Allow converting the grid params to match" }, }, GridOpType = "Mul Div Add", } function GridOpMulDivAdd:GetGridOutputFromInput(state, grid) local grid_mul = self:GetGridInput(self.MulName) local grid_add = self:GetGridInput(self.AddName) local grid_sub = self:GetGridInput(self.SubName) local mul = self:GetValue("Mul") local div = self:GetValue("Div") local add = self:GetValue("Add") local sub = self:GetValue("Sub") if div == 0 then return "Division By Zero" end local res = grid:clone() if grid_mul then if self.Convert then grid_mul = GridMakeSame(grid_mul, res) end GridMulDiv(res, grid_mul, 1) end GridMulDiv(res, mul, div) GridAdd(res, add - sub) if grid_add then if self.Convert then grid_add = GridMakeSame(grid_add, res) end GridAdd(res, grid_add) end if grid_sub then if self.Convert then grid_sub = GridMakeSame(grid_sub, res) end GridAddMulDiv(res, grid_sub, -1) end return nil, res end function GridOpMulDivAdd:GetEditorText() local txt = "" local negate = self:GetValue("Mul") == -1 if self.OutputName ~= "" and self.InputName ~= "" then txt = " = " if negate then txt = txt .. "-" end txt = txt .. "" end if self.MulName ~= "" then txt = txt .. " x " end local mul_str = not negate and self:GetValueText("Mul", 1) or "" local div_str = self:GetValueText("Div", 1) local add_str = self:GetValueText("Add", 0) local sub_str = self:GetValueText("Sub", 0) if mul_str ~= "" then txt = txt .. " x " .. mul_str end if div_str ~= "" then txt = txt .. " / " .. div_str end if add_str ~= "" then txt = txt .. " + " .. add_str end if sub_str ~= "" then txt = txt .. " - " .. sub_str end if self.AddName ~= "" then txt = txt .. " + " end if self.SubName ~= "" then txt = txt .. " - " end return txt end ---- DefineClass.GridOpReplace = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "Old", name = "Old", editor = "number", default = 0, use_param = true }, { category = "General", id = "New", name = "New", editor = "number", default = 1, use_param = true }, }, GridOpType = "Replace", } function GridOpReplace:GetGridOutputFromInput(state, grid) local old = self:GetValue("Old") local new = self:GetValue("New") local res = GridDest(grid) res = GridReplace(grid, res, old, new) return nil, res end function GridOpReplace:GetEditorText() local old_str = self:GetValueText("Old") local new_str = self:GetValueText("New") return " " .. old_str .. " by " .. new_str .. " in to " end ---- DefineClass.GridOpRandPos = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "Tile", name = "Tile", editor = "number", default = 1, use_param = true }, { category = "General", id = "Count", name = "Count", editor = "number", default = -1, use_param = true }, { category = "General", id = "ForEachPos", name = "ForEachPos", editor = "func", default = false, lines = 1, max_lines = 100, params = "x, y, v, area, tile, state", }, }, GridOpType = "Rand Pos", } function GridOpRandPos:GetGridOutputFromInput(state, grid) local tile = self:GetValue("Tile") local min, max = GridMinMax(grid) local output, remap local limit = 0xffff if min < 0 or max > limit then remap = true output = GridDest(grid) GridMax(grid, output, 0) GridMulDiv(output, limit, max) output = GridRepack(output, "u", 16) else output = GridRepack(grid, "u", 16, true) end local count = 0 local max_count = self:GetValue("Count") GridRandomEnumMarkDist(output, state.rand, tile, function(x, y, v, area) if count == max_count then return end count = count + 1 if remap then v = v * limit / max end local success, radius = pcall(self.ForEachPos, x, y, v, area, tile, state) -- todo: debug missing error propagation if not success or not radius then return end if remap then radius = radius * max / limit end return radius end) return nil, output end ---- DefineClass.GridOpDistance = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "Min", name = "Min", editor = "number", default = 0, use_param = true }, { category = "General", id = "Max", name = "Max", editor = "number", default = -1, use_param = true }, { category = "General", id = "Tile", name = "Tile", editor = "number", default = 1, use_param = true }, }, GridOpType = "Distance", operations = {"Transform", "Wave"}, } function GridOpDistance:GetGridOutputFromInput(state, grid) local op = self.Operation local res = GridDest(grid) local tile, max_dist, min_dist = self:GetValue("Tile"), self:GetValue("Max"), self:GetValue("Min") if max_dist < 0 then max_dist = max_int end if op == "Transform" then GridDistance(grid, res, tile, max_dist) elseif op == "Wave" then GridWave(grid, res, tile, max_dist) end if min_dist > 0 then GridBand(res, min_dist, max_dist) end return nil, res end ---- DefineClass.GridOpEnumAreas = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "MinArea", name = "Min Tiles Count", editor = "number", default = 0, use_param = true, min = 0, help = "Will be computed based on the min border distance if not specified" }, { category = "General", id = "MinBorder", name = "Min Border Dist", editor = "number", default = 0, min = 0, use_param = true }, { category = "General", id = "Tile", name = "Tile Size", editor = "number", default = 1, min = 1, use_param = true }, { category = "Stats", id = "AreasCount", name = "Areas Found", editor = "number", default = 0, read_only = true, dont_save = true }, }, GridOpType = "Enum Areas", } function GridOpEnumAreas:GetGridOutputFromInput(state, grid) local tile, min_border, min_area = self:GetValue("Tile"), self:GetValue("MinBorder"), self:GetValue("MinArea") min_area = Max(min_area, (min_border * min_border * 22) / (tile * tile * 7)) if min_area == 0 then return "Zero area size" end local work_grid = min_border > max_uint16 and GridRepack(grid, "f") or grid local zones = GridEnumZones(work_grid, min_area) local level_dist local gw, gh = work_grid:size() local found = 0 local res = GridDest(work_grid) res:clear() for i=1,#zones do local zone = zones[i] level_dist = level_dist or GridDest(work_grid) GridMask(work_grid, level_dist, zone.level) local accepted = true if min_border > 0 then GridDistance(level_dist, tile, min_border) local minv, maxv = GridMinMax(level_dist) accepted = maxv >= min_border end if accepted then found = found + 1 GridPaint(res, level_dist, found) end end self.AreasCount = found if found == 0 then return "No areas found" end res = GridRepack(res, IsComputeGrid(grid)) return nil, res end ---- DefineClass.GridOpMean = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "OperandName", name = "Second Operand", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true }, }, GridOpType = "Mean", input_fmt = "F", operations = {"Arithmetic", "Geometric", "Root Square"}, } function GridOpMean:GetGridOutputFromInput(state, grid) local operand = self:GetGridInput(self.OperandName) local op = self.Operation local res = GridDest(grid) if op == "Arithmetic" then GridAdd(grid, res, operand) GridMulDiv(res, 1, 2) elseif op == "Geometric" then GridMulDiv(grid, res, operand) GridPow(res, 1, 2) else local res2 = GridDest(grid) GridPow(grid, res, 2) GridPow(operand, res2, 2) GridAdd(res, res2) GridPow(res, 1, 2) res2:free() end return nil, res end function GridOpMean:GetEditorText() return " and into " end ---- DefineClass.GridOpPow = { __parents = { "GridOpInputOutput" }, properties = { { category = "General", id = "PowMul", name = "Pow Mul", editor = "number", default = 1, min = 1, }, { category = "General", id = "PowDiv", name = "Pow Div", editor = "number", default = 1, min = 1, }, }, GridOpType = "Pow", input_fmt = "F", } function GridOpPow:GetGridOutputFromInput(state, grid) local res = GridDest(grid) GridPow(grid, res, self.PowMul, self.PowDiv) return nil, res end function GridOpPow:GetEditorText() return " = ^ (/)" end ---- DefineClass.GridOpNoise = { __parents = { "GridOpDest", "PerlinNoiseBase" }, properties = { { category = "General", id = "NoisePreset", name = "Noise Preset", editor = "preset_id", default = "", preset_class = "NoisePreset" }, }, GridOpType = "Noise", } function GridOpNoise:GetGridOutput(state) local ref = self:GetGridInput(self.RefName) local err, noise if ref then noise = GridDest(ref) else err, noise = GridOpDest.GetGridOutput(self, state) if err then return err end end if self.NoisePreset ~= "" then local preset = NoisePresets[self.NoisePreset] if not preset then return "No such noise preset " .. self.NoisePreset end preset:GetNoise(state.rand, noise) elseif not GridPerlin(state.rand, self:ExportOctaves(), noise) then return "Perlin Noise Failed" end return nil, noise end function GridOpNoise:GetEditorText() return "Generate Noise in " end ---- DefineClass.GridOpDistort = { __parents = { "GridOpInputOutput", "PerlinNoiseBase" }, properties = { { category = "General", id = "Strength", name = "Strength", editor = "number", default = 50, min = 0, max = 100, scale = 100, slider = true, help = "Distortion Strength" }, { category = "General", id = "Scale", name = "Scale", editor = "number", default = 100, min = 1, max = 1000, scale = 100, slider = true, help = "Distortion Strength" }, { category = "General", id = "Iterations", name = "Iterations", editor = "number", default = 1, min = 1, max = 10, slider = true, help = "Distortion Iterations" }, { category = "General", id = "NoiseX", name = "Noise X", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, grid_output = true, optional = true }, { category = "General", id = "NoiseY", name = "Noise Y", editor = "combo", default = "", items = GridOpOutputNames, grid_input = true, grid_output = true, optional = true }, { category = "General", id = "NoiseAmp", name = "Noise Amp", editor = "number", default = 4096, min = 1 }, }, GridOpType = "Distort", } function GridOpDistort:GetGridOutputFromInput(state, grid) local unity = self.NoiseAmp local noise_x = self:GetGridInput(self.NoiseX) local noise_y = self:GetGridInput(self.NoiseY) if not noise_x then noise_x, noise_y = GridDest(grid), GridDest(grid) if not GridPerlin(state.rand, self:ExportOctaves(), noise_x, noise_y) then return "Perlin Noise Failed" end GridNormalize(noise_x, 0, unity) GridNormalize(noise_y, 0, unity) end local stength = MulDivRound(self.Scale * unity, self.Strength, 1000 * 100) local src, res = grid, grid for i=1,self.Iterations do src, res = res, src res = res == grid and GridDest(grid) or res if not GridPerturb(src, res, noise_x, noise_y, stength, unity) then return "Grid Perturb Failed" end end self:SetGridOutput(self.NoiseX, noise_x) self:SetGridOutput(self.NoiseY, noise_y) return nil, res end ---- DefineClass.GridOpDraw = { __parents = { "GridOpDest" }, properties = { { category = "General", id = "DrawValue", name = "Value", editor = "number", default = 1 }, { category = "General", id = "DrawBorder", name = "Border", editor = "number", default = 1, min = 1, use_param = true, operation = "Frame" }, { category = "General", id = "DrawBox", name = "Box", editor = "box", default = false, operation = "Box" }, { category = "General", id = "DrawCenter", name = "Center", editor = "point", default = false, operation = "Circle" }, { category = "General", id = "DrawRadius", name = "Radius", editor = "number", default = false, min = 1, operation = "Circle" }, { category = "General", id = "DrawFallout", name = "Fallout", editor = "number", default = 0, min = 0, operation = "Circle" }, }, GridOpType = "Draw", operations = {"Frame", "Box", "Circle", "Blank"}, } function GridOpDraw:GetGridOutput(state) local err, res = GridOpDest.GetGridOutput(self, state) if err then return err end local op = self.Operation if op == "Frame" then GridFrame(res, self:GetValue("DrawBorder"), self.DrawValue) elseif op == "Box" then GridDrawBox(res, self.DrawBox, self.DrawValue) elseif op == "Circle" then local center = self.DrawCenter or point(res:size()) / 2 local radius = self.DrawRadius or Min(res:size()) / 2 GridCircleSet(res, self.DrawValue, center, radius + self.DrawFallout, self.DrawFallout) end return nil, res end ---- local function show_not_grid(op) return op.Show ~= "grid" end local function no_edit_colors(op) return op.Show ~= "grid" or op.ColorRand end local function no_rand_colors(op) return op.Show ~= "grid" or not op.ColorRand end DefineClass.GridOpDbg = { __parents = { "GridOp", "DebugOverlayControl" }, properties = { { category = "General", id = "Show", name = "Show", editor = "choice", default = "grid",items = {"clear", "grid", "biome", "passability", "grass" } }, { category = "General", id = "Grid", name = "Grid", editor = "choice", default = "", items = GridOpOutputNames, grid_input = true, optional = true, no_edit = function(self) return self.Show ~= "grid" end }, { category = "General", id = "AllowInspect", name = "Allow Inspect", editor = "bool", default = false, no_edit = show_not_grid }, { category = "General", id = "ColorRand", name = "Color Rand", editor = "bool", default = false, no_edit = show_not_grid }, { category = "General", id = "InvalidValue", name = "Invalid Value", editor = "number", default = -1, no_edit = no_rand_colors }, { category = "General", id = "Granularity", name = "Granularity", editor = "number", default = 1, no_edit = show_not_grid }, { category = "General", id = "ColorFrom", name = "Color From", editor = "color", default = red, no_edit = no_edit_colors }, { category = "General", id = "ColorTo", name = "Color To", editor = "color", default = green, no_edit = no_edit_colors }, { category = "General", id = "ColorLimits", name = "Color Limits", editor = "bool", default = false, no_edit = no_edit_colors }, { category = "General", id = "ColorMin", name = "Color Min", editor = "color", default = 0, no_edit = no_edit_colors, enabled_by = "ColorLimits" }, { category = "General", id = "ColorMax", name = "Color Max", editor = "color", default = blue, no_edit = no_edit_colors, enabled_by = "ColorLimits" }, { category = "General", id = "Normalize", name = "Normalize", editor = "bool", default = true, no_edit = no_edit_colors }, { category = "General", id = "ValueMin", name = "Value Min", editor = "number", default = 0, no_edit = no_edit_colors, enabled_by = "!Normalize" }, { category = "General", id = "ValueMax", name = "Value Max", editor = "number", default = 1, no_edit = no_edit_colors, enabled_by = "!Normalize" }, { category = "General", id = "OverlayAlpha", name = "Overlay Alpha (%)", editor = "number", default = 60, slider = true, buttons_step = 1, min = 0, max = 100, dont_save = true, dont_recalc = true }, { category = "General", id = "WaitFrames", name = "Wait Frames", editor = "number", default = 0 }, { category = "General", id = "Invalidate", name = "Invalidate", editor = "bool", default = false }, }, GridOpType = "Debug", RunModes = set("Debug", "Release"), palette = false, } function GridOpDbg:Run(state) if GetMap() == "" then return "No Map Loaded" end local show = self.Show if show == "clear" then hr.TerrainDebugDraw = 0 elseif show == "grid" then local grid = self:GetGridInput(self.Grid) if not grid then return end local dbg_grid = grid local palette = self.palette or {} if self.ColorRand then local invalid = self.InvalidValue for i=0,255 do palette[i] = i == invalid and 0 or RandColor(i) end if self.Granularity ~= 1 then dbg_grid = GridDest(grid) GridRound(grid, dbg_grid, self.Granularity) end else dbg_grid = GridDest(grid) if self.Normalize then GridNormalize(grid, dbg_grid, 0, 255) else GridRemap(grid, dbg_grid, self.ValueMin, self.ValueMax, 0, 255) end local cfrom, cto = self.ColorFrom, self.ColorTo local InterpolateRGB = InterpolateRGB for i=1,254 do palette[i] = InterpolateRGB(cfrom, cto, i, 255) end if self.ColorLimits then palette[0], palette[255] = self.ColorMin, self.ColorMax else palette[0], palette[255] = cfrom, cto end end self.palette = palette DbgShowTerrainGrid(dbg_grid, palette) if self.AllowInspect then DbgStartInspectPos(function(pos) if not self.AllowInspect then return end local mx, my = pos:xy() local gv, gx, gy = GridMapGet(grid, mx, my, 1, 100) return string.format("%s(%d : %d) = %s\n", self.Grid, gx, gy, DivToStr(gv, 100)) end) end else hr.TerrainDebugDraw = 1 local palette if show == "biome" then palette = DbgGetBiomePalette() end DbgSetTerrainOverlay(show, palette) end if self.Invalidate then state.proc:InvalidateProc(state) end if self.WaitFrames ~= 0 then WaitNextFrame(self.WaitFrames) end end --[[ Example function DebugGrid3D() local grid_width = 64 local grid_height = 64 local grid_depth = 64 local grid = NewComputeGrid(grid_width, grid_height * grid_depth, "U", 8) GridAdd(grid, 255) DbgSetTerrainOverlay3D("grid", 0, grid, 179178, 170817, 6949, grid_width, grid_height, grid_depth, 1200, 1200, 700) hr.TerrainDebug3DDraw = 1 end]] if FirstLoad then g_ShowPassability3DThread = false end function EnablePassability3DVisualization(enable) DeleteThread(g_ShowPassability3DThread) if enable then hr.TerrainDebug3DDraw = 1 g_ShowPassability3DThread = CreateRealTimeThread(function() local grid_width = 256 local grid_height = 256 local grid_depth = 128 while true do local cursor = GetTerrainGamepadCursor() if cursor then local x, y, z = cursor:xyz() DbgSetTerrainOverlay3D("passability", 0, x, y, z, grid_width, grid_height, grid_depth, 1200, 1200, 700) end Sleep(200) end end) else hr.TerrainDebug3DDraw = 0 end end function GridOpDbg:GetEditorText() local txt = {} if self.WaitFrames ~= 0 then txt[#txt + 1] = "Wait frames" end if self.Invalidate then txt[#txt + 1] = "Invalidate terrain" end if self.Show == "grid" then if self.Grid ~= "" then txt[#txt + 1] = "Show " end elseif self.Show ~= "" then if self.Show == "clear" then txt[#txt + 1] = "Clear overlay" else txt[#txt + 1] = "Show " end end return table.concat(txt, ", ") end DefineClass.GridOpHistogram = { __parents = { "GridOpInput" }, properties = { { category = "General", id = "Levels", name = "Histo Levels", editor = "number", default = 100, min = 10, max = 1000 }, { category = "General", id = "Normalize", name = "Normalize", editor = "bool", default = true }, { category = "General", id = "MinValue", name = "From", editor = "number", default = 0, enabled_by = "!Normalize" }, { category = "General", id = "MaxValue", name = "To", editor = "number", default = 100, enabled_by = "!Normalize" }, { category = "Preview", id = "Histogram", name = "Histogram", editor = "grid", default = false, dont_save = true, min = 128, read_only = true, dont_normalize = true, frame = 1 }, { category = "Preview", id = "Average", name = "Average", editor = "number", default = 0, scale = preview_recision, read_only = true }, { category = "Preview", id = "Deviation", name = "Deviation", editor = "number", default = 0, scale = preview_recision, read_only = true }, { category = "Preview", id = "Volume", name = "Volume", editor = "number", default = 0, scale = preview_recision, read_only = true }, }, GridOpType = "Histogram", RunModes = set("Debug", "Release"), } function GridOpHistogram:SetGridInput(state, grid) local hsize = self.Levels local from, to if not self.Normalize then from, to = self.MinValue, self.MaxValue end local histogram, maxh = GridHistogram(grid, hsize, from, to) if not histogram then return "Histogram Failed" end local hgrid = self.Histogram local w, h = hsize, 64 if not hgrid or hgrid:size() ~= hsize then hgrid = NewComputeGrid(w, h, "U", 8) self.Histogram = hgrid else hgrid:clear() end if maxh == 0 then return end for gx = 0, w-1 do local gy = MulDivRound(h - 1, maxh - histogram[gx + 1], maxh) GridDrawColumn(hgrid, gx, gy, 0, 255) end local avg, dev, vol = GridStats(grid, preview_recision) if not avg then return "Statistics Failed" end self.Average = avg self.Deviation = dev self.Volume = vol end