myspace / CommonLua /MapGen /GridOps.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
93.4 kB
const.TagLookupTable["GridOpName"] = "<color 75 105 198>"
const.TagLookupTable["/GridOpName"] = "</color>"
const.TagLookupTable["GridOpStr"] = "<color 180 86 36>"
const.TagLookupTable["/GridOpStr"] = "</color>"
const.TagLookupTable["GridOpParam"] = "<color 16 160 160>"
const.TagLookupTable["/GridOpParam"] = "</color>"
const.TagLookupTable["GridOpValue"] = "<color 160 160 16>"
const.TagLookupTable["/GridOpValue"] = "</color>"
const.TagLookupTable["GridOpGlobal"] = "<color 160 86 160>"
const.TagLookupTable["/GridOpGlobal"] = "</color>"
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 "<GridOpParam><" .. param_id .. "></GridOpParam>", param
end
local value = self[prop_id]
if value ~= default then
return "<GridOpValue><" .. prop_id .. "></GridOpValue>", 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 "<GridOpType>"
elseif self.operation_text_only then
return "<Operation>"
else
return "<GridOpType> <Operation>"
end
end
function GridOp:GetLogText()
return self:GetEditorText()
end
function GridOp:GetEditorView()
local text = self:GetEditorText() or ""
if text == "" then
text = "<GridOpType>"
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 = "<style GedComment>--</style>"
elseif not self.Enabled then
text = "[<style GedHighlight>Disabled</style>] " .. text
elseif self.run_err then
text = "<style GedError>[Error] " .. text .. "</style>"
elseif self.ignored_err then
text = "[<style GedHighlight>Ignored</style>] " .. text
--[[
elseif my_run_modes ~= all_run_modes then
text = " <style GedConsole>[" .. table.concat(SetToList(my_run_modes), ",") .. "]</style> " .. text
--]]
elseif self.RunTime > 0 then
text = text .. " <color 128 128 0><RunTime></color>"
end
if (self.Comment or "") ~= "" then
text = "<style GedComment><Comment></style>\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("<style GedComment><Comment></style>")
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 "<GridOpParam><ParamName></GridOpParam> = <GridOpValue><ParamStr></GridOpValue>"
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 <GridOpValue><Iterations></GridOpValue>" or ""
return "<GridOpType> <GridOpStr><Sequence></GridOpStr>" .. iters
elseif op == "Func" then
if self.Func == "" then
return "<GridOpType>"
end
local params = {}
if self.InputName ~= "" then
params[1] = "<GridOpName><InputName></GridOpName>"
end
if self.Param1 ~= "" then
params[#params + 1] = "<GridOpParam><Param1></GridOpParam>"
elseif self.Param2 ~= "" or self.Param3 ~= "" then
params[#params + 1] = "nil"
end
if self.Param2 ~= "" then
params[#params + 1] = "<GridOpParam><Param2></GridOpParam>"
elseif self.Param3 ~= "" then
params[#params + 1] = "nil"
end
if self.Param3 ~= "" then
params[#params + 1] = "<GridOpParam><Param3></GridOpParam>"
end
local params_str = #params > 0 and table.concat(params, ", ") or ""
local func_str = "<GridOpGlobal><Func></GridOpGlobal>(" .. params_str .. ")"
if self.OutputName ~= "" then
return "<GridOpName><OutputName></GridOpName> = " .. func_str
end
return "<GridOpType> " .. func_str
elseif op == "Code" then
local source = FuncSource[self.Code]
local source_str = ""
if source and source[3] then
source_str = "\n<style GedConsole>" .. table.concat(source[3], "\n") .. "</style>"
end
if self.OutputName ~= "" then
if self.InputName ~= "" then
return "<GridOpName><OutputName></GridOpName> = <GridOpType>(<GridOpName><InputName></GridOpName>)" .. source_str
end
return "<GridOpName><OutputName></GridOpName> = <GridOpType>()" .. source_str
end
if self.InputName ~= "" then
return "<GridOpType> <Operation>(<GridOpName><InputName></GridOpName>)" .. source_str
end
return "<GridOpType>" .. source_str
end
return GridOp.GetEditorText(self)
end
function GridOpRun:GetLogText()
return "<GridOpType> <Operation>"
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 <GridOpStr><BaseDir></GridOpStr>"
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 <GridOpName><OutputName></GridOpName>"
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 .. " <GridOpName><InputName></GridOpName>"
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 .. " <GridOpName><OutputName></GridOpName>"
end
return ops_str .. " <GridOpName><InputName></GridOpName> to <GridOpName><OutputName></GridOpName>"
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 = { "<GridOpName><OutputName></GridOpName>" }
if self.OutputName2 ~= "" then
outputs[#outputs + 1] = "<GridOpName><OutputName2></GridOpName>"
end
if self.OutputName3 ~= "" then
outputs[#outputs + 1] = "<GridOpName><OutputName3></GridOpName>"
end
if self.OutputName4 ~= "" then
outputs[#outputs + 1] = "<GridOpName><OutputName4></GridOpName>"
end
local str = table.concat(outputs, ", ")
str = "<GridOpType> " .. str .. " from <GridOpStr><FileName></GridOpStr>"
if self.FileFormat ~= "" then
str = str .. " as <GridOpValue><FileFormat></GridOpValue>"
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 = "<GridOpType> <GridOpName><InputName></GridOpName> to <GridOpStr><FileName></GridOpStr>"
if self.FileFormat ~= "" then
str = str .. " as <GridOpValue><FileFormat></GridOpValue>"
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 " (<GridOpValue>x" .. self.Intensity .. "</GridOpValue>)" or ""
if op == "Smooth" then
local str = "Smooth <GridOpName><InputName></GridOpName>" .. str_intensity
if self.InputName ~= self.OutputName then
str = str .. " to <GridOpName><OutputName></GridOpName>"
end
return str
elseif op == "Convolution" then
local str = "Apply <GridOpValue><Filter></GridOpValue> filter" .. str_intensity .. " in <GridOpName><InputName></GridOpName>"
if self.InputName ~= self.OutputName then
str = str .. " to <GridOpName><OutputName></GridOpName>"
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 <GridOpName><InputName></GridOpName> - <GridOpName><TargetName></GridOpName> in <GridOpName><OutputName></GridOpName>"
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 <Operation> <GridOpName><InputName></GridOpName>"
if self.Depth > 1 then
str = str .. " x <GridOpValue><Depth></GridOpValue>"
end
if self.InputName ~= self.OutputName then
str = str .. " to <GridOpName><OutputName></GridOpName>"
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 = "<Operation>"
if self.InputName ~= "" then
text = text .. " <GridOpName><InputName></GridOpName>"
if self.OutputName ~= "" and self.InputName ~= self.OutputName then
text = text .. " to <GridOpName><OutputName></GridOpName>"
end
end
if self.Operation == "Repack" then
text = text .. " as <GridOpStr><GridType></GridOpStr>"
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 "<GridOpType>"
end
local str = "<Operation> <GridOpName><InputName></GridOpName>"
if self.InputName ~= self.OutputName then
str = str .. " in <GridOpName><OutputName></GridOpName>"
end
if self.RefName ~= "" then
str = str .. " as <GridOpName><RefName></GridOpName>"
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 = " <GridOpName><InputName></GridOpName>"
if self.OutputName ~= "" and self.InputName ~= self.OutputName then
grids_str = grids_str .. " to <GridOpName><OutputName></GridOpName>"
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 "<Operation>" .. 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 "<GridOpType>"
end
local str = "<GridOpName><OutputName></GridOpName> = <Operation>(<GridOpName><InputName></GridOpName>, "
if self.RefName ~= "" then
str = str .. "<GridOpName><RefName></GridOpName>"
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 = "<GridOpType>"
local negate = self:GetValue("Mul") == -1
if self.OutputName ~= "" and self.InputName ~= "" then
txt = "<GridOpName><OutputName></GridOpName> = "
if negate then
txt = txt .. "-"
end
txt = txt .. "<GridOpName><InputName></GridOpName>"
end
if self.MulName ~= "" then
txt = txt .. " x <GridOpName><MulName></GridOpName>"
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 .. " + <GridOpName><AddName></GridOpName>"
end
if self.SubName ~= "" then
txt = txt .. " - <GridOpName><SubName></GridOpName>"
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 "<GridOpType> " .. old_str .. " by " .. new_str .. " in <GridOpName><InputName></GridOpName> to <GridOpName><OutputName></GridOpName>"
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 "<Operation> <GridOpType> <GridOpName><InputName></GridOpName> and <GridOpName><OperandName></GridOpName> into <GridOpName><OutputName></GridOpName>"
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 "<GridOpName><OutputName></GridOpName> = <GridOpName><InputName></GridOpName> ^ (<GridOpValue><PowMul></GridOpValue>/<GridOpValue><PowDiv></GridOpValue>)"
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 <GridOpName><OutputName></GridOpName>"
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 <WaitFrames> 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 <GridOpName><Grid></GridOpName>"
end
elseif self.Show ~= "" then
if self.Show == "clear" then
txt[#txt + 1] = "Clear overlay"
else
txt[#txt + 1] = "Show <GridOpValue><Show></GridOpValue>"
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