myspace / Lua /Dev /MapValidation.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
23.3 kB
if Platform.cmdline then return end
local merc_size1 = point(40 * guic, 80 * guic, 150 * guic)
local merc_size2 = point(80 * guic, 40 * guic, 150 * guic)
g_ZuluMapValidationBoxes = rawget(_G, "g_ZuluMapValidationBoxes") or {}
function OnMsg.ChangeMap()
g_ZuluMapValidationBoxes = {}
end
local function ClearValidationBoxes(bbox)
local old = g_ZuluMapValidationBoxes
g_ZuluMapValidationBoxes = {}
for _, obj in ipairs(old) do
if not bbox or obj:GetPos():InBox(bbox) then
obj:delete()
else
g_ZuluMapValidationBoxes[#g_ZuluMapValidationBoxes + 1] = obj
end
end
end
function ConstructPassability(bbox)
-- construct a copy of the passability, considering tunnels and walkable slabs as passable
local pass_tile = const.PassTileSize
local pass_grid = editor.GetGrid("pass")
-- TODO: Check errorneous results on Savannah Camp map
--[[ for x = bbox:minx(), bbox:maxx(), pass_tile do
for y = bbox:miny(), bbox:maxy(), pass_tile do
local slab = WalkableSlabByPoint(x, y, terrain.GetHeight(x, y))
if slab then
pass_grid:set(x / const.PassTileSize, y / const.PassTileSize, terrain.IsPassable(slab:GetPos()) and 1 or 0)
end
end
end]]
local half_tile = point(pass_tile / 2, pass_tile / 2)
pf.ForEachTunnel(bbox, function(obj, x1, y1, z1, x2, y2, z2)
x1 = x1 - pass_tile / 2
y1 = y1 - pass_tile / 2
x2 = x2 - pass_tile / 2
y2 = y2 - pass_tile / 2
if x1 == x2 then
if y1 > y2 then y1, y2 = y2, y1 end
for y = y1, y2, pass_tile do
pass_grid:set(x1 / const.PassTileSize, y / const.PassTileSize, 1)
--Visualize(point(x1, y) + half_tile)
end
elseif y1 == y2 then
if x1 > x2 then x1, x2 = x2, x1 end
for x = x1, x2, pass_tile do
pass_grid:set(x / const.PassTileSize, y1 / const.PassTileSize, 1)
--Visualize(point(x, y1) + half_tile)
end
elseif x1 + y1 == x2 + y2 then
if x1 > x2 then x1, x2 = x2, x1 end
for x = x1, x2, pass_tile do
pass_grid:set(x / const.PassTileSize, (y1 - x + x1) / const.PassTileSize, 1)
--Visualize(point(x, y1 - x + x1) + half_tile)
end
elseif x1 - y1 == x2 - y2 then
if x1 > x2 then x1, x2 = x2, x1 end
for x = x1, x2, pass_tile do
pass_grid:set(x / const.PassTileSize, (y1 + x - x1) / const.PassTileSize, 1)
--Visualize(point(x, y1 + x - x1) + half_tile)
end
end
end)
return pass_grid
end
local covers_ids = { false, false, false, false }
covers_ids[const.CoverLow] = true
covers_ids[const.CoverHigh] = true
function CheckPassInFrontOfCovers(bbox)
bbox = bbox and bbox:grow(const.SlabSizeX) or sizebox(point20, terrain.GetMapSize())
bbox = box(bbox:min():SetInvalidZ(), bbox:max():SetInvalidZ())
local sizex, sizey = terrain.GetMapSize()
sizex, sizey = sizex / const.PassTileSize, sizey / const.PassTileSize
local grid = NewGrid(sizex, sizey, 1, 0)
PauseInfiniteLoopDetection("CheckPassInFrontOfCovers")
local pass_tile = const.PassTileSize
local pass_grid = ConstructPassability(bbox)
local is_passable = function(x, y)
return pass_grid:get((x - pass_tile / 2) / pass_tile, (y - pass_tile / 2) / pass_tile) == 1
end
ForEachCover(bbox, -1, function(x, y, z, up, right, down, left)
up = covers_ids[up]
right = covers_ids[right]
down = covers_ids[down]
left = covers_ids[left]
local function has_cover(xoffs, yoffs, dir)
local u, r, d, l = GetCover(x + xoffs * const.SlabSizeX, y + yoffs * const.SlabSizeY, z)
if not u then return end
if dir == "up" then
return covers_ids[u]
elseif dir == "right" then
return covers_ids[r]
elseif dir == "down" then
return covers_ids[d]
elseif dir == "left" then
return covers_ids[l]
end
end
local pass_tile = const.PassTileSize
local xp, yp = x / pass_tile, y / pass_tile
if up or down then
grid:set(xp, yp, 1)
if up then
local yoffs, dir = -1, "up"
if not left then
local l2, l1 = has_cover(-1, yoffs, "right"), has_cover(-1, 0, dir)
if l2 then grid:set(xp - 2, yp, 1) end
if l2 or l1 then grid:set(xp - 1, yp, 1) end
end
if not right then
local r1, r2 = has_cover(1, 0, dir), has_cover(1, yoffs, "left")
if r2 or r1 then grid:set(xp + 1, yp, 1) end
if r2 then grid:set(xp + 2, yp, 1) end
end
end
if down then
local yoffs, dir = 1, "down"
if not left then
local l2, l1 = has_cover(-1, yoffs, "right"), has_cover(-1, 0, dir)
if l2 then grid:set(xp - 2, yp, 1) end
if l2 or l1 then grid:set(xp - 1, yp, 1) end
end
if not right then
local r1, r2 = has_cover(1, 0, dir), has_cover(1, yoffs, "left")
if r2 or r1 then grid:set(xp + 1, yp, 1) end
if r2 then grid:set(xp + 2, yp, 1) end
end
end
end
if right or left then
grid:set(xp, yp, 1)
if right then
local xoffs, dir = 1, "right"
if not up then
local u2, u1 = has_cover(xoffs, -1, "down"), has_cover(0, -1, dir)
if u2 then grid:set(xp, yp - 2, 1) end
if u2 or u1 then grid:set(xp, yp - 1, 1) end
end
if not down then
local d1, d2 = has_cover(0, 1, dir), has_cover(xoffs, 1, "up")
if d2 or d1 then grid:set(xp, yp + 1, 1) end
if d2 then grid:set(xp, yp + 2, 1) end
end
end
if left then
local xoffs, dir = -1, "left"
local u2, u1 = has_cover(xoffs, -1, "down"), has_cover(0, -1, dir)
if not up then
if u2 then grid:set(xp, yp - 2, 1) end
if u2 or u1 then grid:set(xp, yp - 1, 1) end
end
if not down then
local d1, d2 = has_cover(0, 1, dir), has_cover(xoffs, 1, "up")
if d2 or d1 then grid:set(xp, yp + 1, 1) end
if d2 then grid:set(xp, yp + 2, 1) end
end
end
end
end)
local pass_tile = const.PassTileSize
local pass_size = point(pass_tile, pass_tile, 30 * guic)
local mask_any = const.cmObstruction + const.cmPassability
local shrink = 15 * guic
local shrink_offs = point(shrink, shrink, 0)
local shrink_size = pass_size - 2 * shrink_offs
local function has_tall_obstacle(pos)
local count = 0
for i = 2, 5 do
local bbox = sizebox(pos + shrink_offs + i * point(0, 0, 30 * guic), shrink_size)
local collide = collision.Collide(bbox, point30, const.cqfSingleResult, 0, mask_any, empty_func) > 0
count = count + (collide and 1 or 0)
end
return count >= 2
end
local function guess_intended_blockers(pos)
local shrink = 12 * guic
local shrink_offs = point(shrink, shrink, 0)
local shrink_size = pass_size - 2 * shrink_offs
local shrinked_box = sizebox(pos + shrink_offs + point(0, 0, 15 * guic), shrink_size)
if collision.Collide(shrinked_box, point30, const.cqfSingleResult, 0, mask_any, empty_func) == 0 then
return false
end
-- find colliding objects at least 15 cm above the ground level
local objs = {}
local bbox
local collide_box = sizebox(pos + point(0, 0, 15 * guic), pass_size:SetZ(120 * guic))
collision.Collide(collide_box, point30, 0, 0, mask_any, function(obj)
local obj_box = obj:GetObjectBBox()
bbox = bbox and Extend(bbox, obj_box) or obj_box
objs[obj] = obj:GetApplyToGrids()
end)
if not bbox then return false end
bbox = box(
(bbox:minx() + pass_tile / 2) / pass_tile * pass_tile,
(bbox:miny() + pass_tile / 2) / pass_tile * pass_tile,
bbox:minz(),
(bbox:maxx() + pass_tile / 2) / pass_tile * pass_tile,
(bbox:maxy() + pass_tile / 2) / pass_tile * pass_tile,
bbox:maxz()
)
-- does turning off ApplyToGrids only remove problematic tiles as per this logic?
local old = g_dbgCoversShown
g_dbgCoversShown = false
-- remember old impassable
local old_impass = {}
for x = bbox:minx(), bbox:maxx(), pass_tile do
for y = bbox:miny(), bbox:maxy(), pass_tile do
if not terrain.IsPassable(x, y) then
old_impass[x * 4000000 + y] = true
end
end
end
-- stop applying to grids
SuspendPassEdits("guess_intended_blockers")
for obj in pairs(objs) do
obj:SetApplyToGrids(false)
end
ResumePassEdits("guess_intended_blockers")
local ret = true
for x = bbox:minx(), bbox:maxx(), pass_tile do
for y = bbox:miny(), bbox:maxy(), pass_tile do
local pos = point(x, y)
if old_impass[x * 4000000 + y] and terrain.IsPassable(x, y) and grid:get(x / pass_tile, y / pass_tile) == 0 then
ret = false
end
end
end
-- restore grids
SuspendPassEdits("guess_intended_blockers")
for obj, value in pairs(objs) do
obj:SetApplyToGrids(value)
end
ResumePassEdits("guess_intended_blockers")
g_dbgCoversShown = old
return ret
end
local half_tile = point(pass_tile / 2, pass_tile / 2)
for x = bbox:minx() / const.SlabSizeX, bbox:maxx() / const.SlabSizeX do
for y = bbox:miny() / const.SlabSizeY, bbox:maxy() / const.SlabSizeY do
if grid:get(x, y) == 1 then
local pos = point(x * pass_tile, y * pass_tile)
if not is_passable(pos:x(), pos:y()) then
pos = pos - half_tile
pos = pos:SetTerrainZ()
if not has_tall_obstacle(pos) and (not XEditorSettings:GetGuessIntendedBlockers() or not guess_intended_blockers(pos)) then
g_ZuluMapValidationBoxes[#g_ZuluMapValidationBoxes + 1] = PlaceBox(sizebox(pos, pass_size), RGB(255, 0, 0), false, true)
for i = 2, 5 do
local bbox = sizebox(pos + shrink_offs + i * point(0, 0, 30 * guic), shrink_size)
g_ZuluMapValidationBoxes[#g_ZuluMapValidationBoxes + 1] = PlaceBox(bbox, RGB(255, 255, 0), false, true)
end
end
end
end
end
end
ResumeInfiniteLoopDetection("CheckPassInFrontOfCovers")
grid:free()
pass_grid:free()
end
function ForEachMercPosition(bbox, callback, voxel_filter)
local had_box = bbox
bbox = bbox and bbox:grow(const.SlabSizeX) or sizebox(point20, terrain.GetMapSize())
PauseInfiniteLoopDetection("CheckPartiallyPassableTiles")
local pass_tile = const.PassTileSize
local pass_grid = ConstructPassability(bbox)
local is_passable = function(x, y)
return pass_grid:get((x - pass_tile / 2) / pass_tile, (y - pass_tile / 2) / pass_tile) == 1
end
local offs = point(0, 0, 0 * guic)
local check_free_space = function(x, y, merc_size)
local x_step = (const.SlabSizeX - merc_size:x()) / 2
local y_step = (const.SlabSizeY - merc_size:y()) / 2
for xo = 0, x_step * 2, x_step do
for yo = 0, y_step * 2, y_step do
local pos = point(x + xo, y + yo)
pos = pos:SetTerrainZ() + offs
callback(sizebox(pos, merc_size))
end
end
end
voxel_filter = voxel_filter or function() return true end
for x = bbox:minx(), bbox:maxx(), const.SlabSizeX do
for y = bbox:miny(), bbox:maxy(), const.SlabSizeY do
if voxel_filter(x, y, is_passable) then
check_free_space(x, y, merc_size1)
check_free_space(x, y, merc_size2)
end
end
end
ResumeInfiniteLoopDetection("CheckPartiallyPassableTiles")
pass_grid:free()
end
function CheckPartiallyPassableTiles(bbox)
local bush_size = 10*guim
local bush_grow, bush_grow_z = -10*guic, 100*guic
local box_size = point(const.SlabSizeX + 40 * guic, const.SlabSizeY + 40 * guic, 150 * guic)
local offs = point(-20 * guic, -20 * guic, 30 * guic)
local voxel_has_passable = function(x, y, is_passable)
local s = const.PassTileSize
return is_passable(x - s, y - s) or is_passable(x - s, y) or is_passable(x - s, y + s) or
is_passable(x , y - s) or is_passable(x , y) or is_passable(x , y + s) or
is_passable(x + s, y - s) or is_passable(x + s, y) or is_passable(x + s, y + s)
end
local mask_any = const.cmObstruction + const.cmPassability
ForEachMercPosition(bbox,
function(merc_box)
if collision.Collide(merc_box, point30, const.cqfSingleResult, 0, mask_any, empty_func) == 0 then
if not XEditorSettings:GetIgnoreBushFilledAreas() or
MapCount(merc_box:grow(bush_size), function(obj)
local data = EntityData[obj:GetEntity()] or empty_table
return data.editor_subcategory == "Bush" and
obj:GetObjectBBox():grow(bush_grow, bush_grow, bush_grow_z):Intersect(merc_box) == const.irInside
end) == 0
then
g_ZuluMapValidationBoxes[#g_ZuluMapValidationBoxes + 1] = PlaceBox(merc_box, RGB(255, 255, 0), false, true)
end
end
end,
function(x, y, is_passable)
local xv, yv = x + const.SlabSizeX / 2, y + const.SlabSizeY / 2
return not is_passable(xv, yv) and voxel_has_passable(xv, yv, is_passable) and
not (XEditorSettings:GetIgnoreEmptySpaces() and
collision.Collide(sizebox(point(x, y):SetTerrainZ() + offs, box_size), point30, const.cqfSingleResult, 0, mask_any, empty_func) == 0)
end
)
end
function ValidateMap(bbox)
ClearValidationBoxes(bbox)
if g_dbgCoversShown then
if XEditorSettings:GetDetectGaps() then
CheckPartiallyPassableTiles(bbox)
end
if XEditorSettings:GetCoverPass() then
CheckPassInFrontOfCovers(bbox)
end
end
end
function OnMsg.DbgCoversUpdated(bbox) CreateRealTimeThread(ValidateMap, bbox) end
----- Settings
table.iappend(XEditorSettings.properties, {
{ category = "Voxel validation", id = "DetectGaps", name = "Detect gaps", editor = "bool", default = false },
{ category = "Voxel validation", id = "IgnoreEmptySpaces", name = "Ignore empty spaces", editor = "bool", default = true,
read_only = function(self) return not self:GetDetectGaps() end,
},
{ category = "Voxel validation", id = "IgnoreBushFilledAreas", name = "Ignore bush-filled areas", editor = "bool", default = true,
read_only = function(self) return not self:GetDetectGaps() end,
},
{ category = "Voxel validation", id = "CoverPass", name = "Check cover passability", editor = "bool", default = false },
{ category = "Voxel validation", id = "GuessIntendedBlockers", name = "Guess intended blockers", editor = "bool", default = false,
read_only = function(self) return not self:GetCoverPass() end,
},
})
function OnMsg.EditorSettingChanged(setting, value)
if (setting == "DetectGaps" or setting == "CoverPass") and value and not g_dbgCoversShown then
DbgDrawCovers()
return
end
if XEditorSettings:GetPropertyMetadata(setting).category == "Passability validation" then
ValidateMap()
end
end
----- Auto-adjustment
g_DebugBoxes = rawget(_G, "g_DebugBoxes") or {}
function OnMsg.ChangeMap()
g_DebugBoxes = {}
end
-- tries to move and/or rotate the object according to editor settings, trying to leave no gaps around it
function AdjustSelectionToVoxels(adjust_angle)
for _, obj in ipairs(g_DebugBoxes) do
obj:delete()
end
g_DebugBoxes = {}
local PlaceBox = empty_func
-- some variables
local objs = editor.GetSel()
local bbox = GetObjectsBBox(objs)
local slab_size = const.SlabSizeX
local pass_size = const.PassTileSize
local vsize = point(slab_size, slab_size, 150 * guic)
local psize = point(pass_size, pass_size, 150 * guic)
local tolerance = 27
local ptolerance = tolerance * point(guic, guic, guic)
local voffs = (vsize - psize) / 2
local mask_any = const.cmObstruction + const.cmPassability
if bbox:IsEmpty() then return end
if bbox:sizex() > 120 * guim or bbox:sizey() > 120 * guim then
print("<color 255 0 0>Objects to adjust cover a too large area.</color>")
return
end
-- gather all "passable" tiles around the objects that we should try to keep free
local free_map = {} -- indexed by x * 4000000 + y
local p = function(x, y) return x * 4000000 + y end
-- add the contours of impassable voxels, we should keep those "passable"
local valign = function(x) return x / slab_size * slab_size end
for x = valign(bbox:minx()), valign(bbox:maxx()), slab_size do
for y = valign(bbox:miny()), valign(bbox:maxy()), slab_size do
local pos = point(x, y):SetTerrainZ()
local vbox = sizebox(pos, vsize - 2 * voffs - ptolerance) + voffs + ptolerance / 2
local collision = collision.Collide(vbox, point30, const.cqfSingleResult, 0, mask_any, empty_func) > 0
if collision then
local s, s2 = pass_size, pass_size * 2
free_map[p(x + s2, y + s2)] = true
free_map[p(x + s2, y + s )] = true
free_map[p(x + s2, y )] = true
free_map[p(x + s2, y - s )] = true
free_map[p(x + s2, y - s2)] = true
free_map[p(x + s , y + s2)] = true
free_map[p(x , y + s2)] = true
free_map[p(x - s , y + s2)] = true
free_map[p(x + s , y - s2)] = true
free_map[p(x , y - s2)] = true
free_map[p(x - s , y - s2)] = true
free_map[p(x - s2, y + s2)] = true
free_map[p(x - s2, y + s )] = true
free_map[p(x - s2, y )] = true
free_map[p(x - s2, y - s )] = true
free_map[p(x - s2, y - s2)] = true
end
end
end
-- subtract from those the inside ones
local valign = function(x) return x / slab_size * slab_size end
for x = valign(bbox:minx()), valign(bbox:maxx()), slab_size do
for y = valign(bbox:miny()), valign(bbox:maxy()), slab_size do
local pos = point(x, y):SetTerrainZ()
local vbox = sizebox(pos, vsize - 2 * voffs - ptolerance) + voffs + ptolerance / 2
local collision = collision.Collide(vbox, point30, const.cqfSingleResult, 0, mask_any, empty_func) > 0
if collision then
local s = pass_size
free_map[p(x , y + s)] = nil
free_map[p(x , y )] = nil
free_map[p(x , y - s)] = nil
free_map[p(x + s, y + s)] = nil
free_map[p(x + s, y )] = nil
free_map[p(x + s, y - s)] = nil
free_map[p(x - s, y + s)] = nil
free_map[p(x - s, y )] = nil
free_map[p(x - s, y - s)] = nil
end
end
end
-- assign boxes in place of true, debug display
for k in pairs(free_map) do
local x, y = k / 4000000, k % 4000000
local pos = point(x, y):SetTerrainZ()
local p31 = point(guic, guic, guic)
for t = 0, tolerance do
local ptolerance = p31 * t
local vbox = sizebox(pos, psize - ptolerance) + psize:SetZ(0) / 2 + ptolerance / 2
if collision.Collide(vbox, point30, const.cqfSingleResult, 0, mask_any, empty_func) == 0 then
g_DebugBoxes[#g_DebugBoxes + 1] = PlaceBox(vbox, RGB(0, 255, 255), false, true)
free_map[k] = vbox
break
end
end
if free_map[k] == true then
free_map[k] = nil
end
end
--VisualizeClear()
-- gather all gaps that we strive to be filled
local gaps = {}
local palign = function(x) return x / pass_size * pass_size end
local count = 0
local aligned_box = box(valign(bbox:minx()) - slab_size, valign(bbox:miny()) - slab_size, valign(bbox:maxx()) + 2 * slab_size, valign(bbox:maxy()) + 2 * slab_size)
g_DebugBoxes[#g_DebugBoxes + 1] = PlaceBox(aligned_box, RGB(255, 0, 0), false, true)
ForEachMercPosition(aligned_box,
function(merc_box)
local found
for x = palign(merc_box:minx()) - pass_size, palign(merc_box:maxx()) + pass_size, pass_size do
for y = palign(merc_box:miny()) - pass_size, palign(merc_box:maxy()) + pass_size, pass_size do
local pass = free_map[(x - pass_size) * 4000000 + y - pass_size]
if pass then
found = true
break
end
end
end
if found then
--g_DebugBoxes[#g_DebugBoxes + 1] = PlaceBox(merc_box:grow(-30*guic), RGB(255, 0, 0), false, true)
gaps[#gaps + 1] = merc_box
end
end,
function(x, y, is_passable)
x, y = x / slab_size * slab_size, y / slab_size * slab_size
local pos = point(x, y):SetTerrainZ()
local vbox = sizebox(pos, vsize - 2 * voffs - ptolerance) + voffs + ptolerance / 2
return collision.Collide(vbox, point30, const.cqfSingleResult, 0, mask_any, empty_func) > 0
end
)
print("Number of gaps:", #gaps)
print("Number of pass tiles:", #table.keys(free_map))
-- start iterating & evaluating the best object position
SuspendPassEdits("AdjustSelectionToVoxels")
PauseInfiniteLoopDetection("AdjustSelectionToVoxels")
XEditorUndo:BeginOp{ objects = objs, name = string.format("Adjusted %d objects to voxels", #objs) }
local function GetOrgPositions(objs)
local ret = {}
for _, obj in ipairs(objs) do
ret[#ret + 1] = { pos = obj:GetPos(), angle = obj:GetAngle(), axis = obj:GetAxis(), scale = obj:GetScale() }
end
ret.center = CenterOfMasses(objs)
return ret
end
local function SetNewRelPos(objs, orgp, x, y, angle, scale)
local offs = point(x, y, 0)
for i, obj in ipairs(objs) do
local data = orgp[i]
local scale2 = MulDivRound(data.scale, scale, 100)
local rotate_offs = RotateAxis(data.pos - orgp.center, axis_z, angle)
if rotate_offs:Len() > 0 then
obj:SetPos(orgp.center + SetLen(rotate_offs, MulDivRound(rotate_offs:Len(), scale2, 100)) + offs)
else
obj:SetPos(orgp.center + offs)
end
obj:SetAxisAngle(ComposeRotation(data.axis, data.angle, axis_z, angle))
obj:SetScale(scale2)
end
end
local function EvaluateScore(action, init_gaps, init_pass)
local score = 0
--local PlaceBox = _G["PlaceBox"]
for idx, gap in ipairs(gaps) do
--local shrink = 2
local scores = { [0] = 16, [1] = 4, [2] = 1 }
for shrink = 0, 2 do
local b, key = gap:grow(-shrink * 9 * guic), idx * 3 + shrink
local collision = collision.Collide(b, point30, const.cqfSingleResult, 0, mask_any, empty_func) > 0
if action == "store" then
init_gaps[key] = collision
elseif action == "show" and init_gaps[key] ~= collision then
local color = collision and RGB(0, 255, 0) or RGB(255, 0, 0)
g_DebugBoxes[#g_DebugBoxes + 1] = PlaceBox(b:grow(3 *guic), color, false, false)
end
score = score + (collision and scores[shrink] or 0) -- closed gaps
end
end
for key, b in pairs(free_map) do
local collision = collision.Collide(b, point30, const.cqfSingleResult, 0, mask_any, empty_func) > 0
if action == "store" then
init_pass[key] = collision
elseif action == "show" and init_pass[key] ~= collision then
local color = (not collision) and RGB(0, 255, 0) or RGB(255, 0, 0)
g_DebugBoxes[#g_DebugBoxes + 1] = PlaceBox(b:grow(3 *guic), color, false, false)
end
score = score + (collision and -900 or 0) -- tresspassed passability
end
return score
end
local init_gaps, init_pass = {}, {}
local base_score = EvaluateScore("store", init_gaps, init_pass)
local pos_step, pos_max = 15 * guic, 40 * guic -- 10 * guic, 50 * guic
local ang_step, ang_max = 8 * 60, 16 * 60
local scale_to_max = Min(MulDivRound(bbox:sizex() + pos_max, 100, bbox:sizex()), MulDivRound(bbox:sizey() + pos_max, 100, bbox:sizey())) - 100
local scl_step, scl_max = scale_to_max / 5, scale_to_max
local time = GetPreciseTicks()
local orgp = GetOrgPositions(objs)
local best = { x = 0, y = 0, angle = 0, scale = 100 }
local best_score, best_dist = 0, 100 * guim
for x = -pos_max, pos_max, pos_step do
for y = -pos_max, pos_max, pos_step do
local angle, scale = 0, 100
--for angle = -ang_max, ang_max, ang_step do
--for scale = 100, 100 + scl_max, scl_step do
SetNewRelPos(objs, orgp, x, y, angle, scale)
local score, dist = EvaluateScore(), x*x + y*y + angle * angle + scale * scale
if score > best_score or score == best_score and dist < best_dist then
best.x, best.y, best.angle, best.scale = x, y, angle, scale
best_score, best_dist = score, dist
end
--end
--end
end
end
print("Best score/dist:", base_score - best_score, sqrt(best_dist) / guic, "cm")
print("Time taken", GetPreciseTicks() - time, "ms")
SetNewRelPos(objs, orgp, best.x, best.y, best.angle, best.scale)
EvaluateScore("show", init_gaps, init_pass)
XEditorUndo:EndOp(objs)
ResumeInfiniteLoopDetection("AdjustSelectionToVoxels")
ResumePassEdits("AdjustSelectionToVoxels")
end