|
if FirstLoad then |
|
|
|
ExtrasGenParams = { North = true, South = true, East = true, West = true } |
|
end |
|
|
|
function GenExtras(id, initial_selection, seed) |
|
SuspendPassEdits("GenExtras") |
|
XEditorUndo:BeginOp{ name = id } |
|
ExtrasGenPrgs[id](seed, initial_selection) |
|
XEditorUndo:EndOp() |
|
ResumePassEdits("GenExtras") |
|
end |
|
|
|
DefineClass.ExtrasGen = { |
|
__parents = { "PrgPreset" }, |
|
|
|
properties = { |
|
{ id = "Params", editor = false }, |
|
{ id = "ToolbarSection", editor = "combo", default = "", items = function(self) return PresetsPropCombo("ExtrasGen", "ToolbarSection", "") end, }, |
|
{ id = "Shortcut", editor = "shortcut", default = "", }, |
|
{ id = "Shortcut2", editor = "shortcut", default = "", }, |
|
{ id = "RequiresClass", editor = "choice", default = "", items = { "", "Slab", "EditorLineGuide", "Room" }, }, |
|
{ id = "RequiresGuideType", editor = "choice", default = "", items = { "", "Horizontal", "Vertical" }, }, |
|
}, |
|
|
|
EditorMenubarName = "ExtrasGen Presets", |
|
EditorMenubar = "Map", |
|
EditorShortcut = "Ctrl-Alt-G", |
|
EditorIcon = "CommonAssets/UI/Icons/atom molecule science.png", |
|
|
|
Params = { "initial_selection" }, |
|
StatementTags = { "Basics", "Objects", "ExtrasGen" }, |
|
FuncTable = "ExtrasGenPrgs", |
|
GlobalMap = "ExtrasGenPresets", |
|
} |
|
|
|
function ExtrasGen:GenerateCodeAtFunctionStart(code) |
|
code:append("\tinitial_selection = initial_selection or editor.GetSel()\n") |
|
PrgPreset.GenerateCodeAtFunctionStart(self, code) |
|
end |
|
|
|
function ExtrasGen.FindPrgStatement(id_or_self, classes, prop_id, value, fn) |
|
local ret = false |
|
local prg = IsKindOf(id_or_self, "ExtrasGen") and id_or_self or ExtrasGenPresets[id_or_self] |
|
if prg then |
|
prg:ForEachSubObject("PrgStatement", function(obj) |
|
if IsKindOfClasses(obj, unpack_params(classes)) and (not prop_id or obj[prop_id] == value) and (not fn or fn(obj)) then |
|
ret = true |
|
end |
|
end) |
|
end |
|
return ret |
|
end |
|
|
|
function OnMsg.ShortcutsReloaded() |
|
local directions = { "North", "South", "East", "West" } |
|
|
|
local LayDecalsAlongGuide = { "LayDecalsAlongGuide" } |
|
local PlaceRoomGuides = { "PlaceRoomGuides" } |
|
local function check_relation(prg1, prg2) |
|
if prg1.ToolbarSection == "Room" or prg2.ToolbarSection == "Room" then return false end |
|
return |
|
prg1:FindPrgStatement(LayDecalsAlongGuide) and prg2:FindPrgStatement(PlaceRoomGuides, "Direction", "Inwards (wall)") or |
|
prg1.RequiresGuideType == "Horizontal" and prg2:FindPrgStatement(PlaceRoomGuides, "Direction", "Outwards (room)") or |
|
prg1.RequiresGuideType == "Vertical" and prg2:FindPrgStatement(PlaceRoomGuides, "Horizontal", false, function(obj) return obj.PlaceOn:starts_with("Wall") end) |
|
end |
|
|
|
ForEachPreset("ExtrasGen", function(prg) |
|
if prg.ToolbarSection ~= "" then |
|
XAction:new({ |
|
ActionId = prg.id, |
|
ActionName = prg.Shortcut ~= "" and prg.id.."<right><style GedDefault><alpha 156>"..prg.Shortcut or prg.id, |
|
ActionTranslate = false, |
|
ActionMode = "Editor", |
|
ActionToolbar = "EditorRoomTools", |
|
ActionToolbarSection = prg.ToolbarSection, |
|
ActionShortcut = prg.Shortcut, |
|
ActionShortcut2 = prg.Shortcut2, |
|
RolloverText = prg.Comment, |
|
ActionState = function(self, host) |
|
if prg.RequiresClass ~= "" and not editor.IsSelectionKindOf(prg.RequiresClass) or not GetDialog("XEditorRoomTools") then |
|
return "disabled" |
|
end |
|
if prg.RequiresGuideType == "Horizontal" then |
|
for _, obj in ipairs(editor.GetSel()) do |
|
if not obj:IsHorizontal() then return "disabled" end |
|
end |
|
end |
|
if prg.RequiresGuideType == "Vertical" then |
|
for _, obj in ipairs(editor.GetSel()) do |
|
if not obj:IsVertical() then return "disabled" end |
|
end |
|
end |
|
end, |
|
OnAction = function(self, host) |
|
GenExtras(prg.id) |
|
end, |
|
OnAltAction = function(self, host) |
|
ExtrasGenPresets[prg.id]:OpenEditor() |
|
end, |
|
GetRelatedActions = function(self, host) |
|
local related = {} |
|
for _, action in ipairs(host:GetActions()) do |
|
local action_prg = ExtrasGenPresets[action.ActionId] |
|
if action_prg and (check_relation(prg, action_prg) or check_relation(action_prg, prg)) then |
|
related[#related + 1] = action |
|
end |
|
end |
|
if prg:FindPrgStatement({"PrgSelectRoomComponents", "PlaceRoomGuides"}, "UseParams", true) then |
|
table.iappend(related, table.map(directions, function(dir) return host:ActionById(dir) end)) |
|
end |
|
return related |
|
end, |
|
}, XShortcutsTarget) |
|
end |
|
end) |
|
|
|
for _, direction in ipairs(directions) do |
|
XAction:new({ |
|
ActionId = direction, |
|
ActionName = direction, |
|
ActionTranslate = false, |
|
ActionMode = "Editor", |
|
ActionToolbar = "EditorRoomWallSelection", |
|
ActionToolbarSection = "Wall Selection", |
|
ActionToggle = true, |
|
ActionToggled = function(self, host) |
|
return ExtrasGenParams[direction] |
|
end, |
|
OnAction = function(self, host) |
|
ExtrasGenParams[direction] = not ExtrasGenParams[direction] |
|
end, |
|
GetRelatedActions = function(self, host) |
|
local related = {} |
|
for _, action in ipairs(host:GetActions()) do |
|
if ExtrasGen.FindPrgStatement(action.ActionId, {"PrgSelectRoomComponents", "PlaceRoomGuides"}, "UseParams", true) then |
|
related[#related + 1] = action |
|
end |
|
end |
|
return related |
|
end, |
|
}, XShortcutsTarget) |
|
end |
|
|
|
XAction:new({ |
|
ActionId = "HoldShiftHelp", |
|
ActionName = "<center>(hold Shift for half-step)", |
|
ActionTranslate = false, |
|
ActionMode = "Editor", |
|
ActionState = function(self, host) return "disabled" end, |
|
ActionToolbar = "EditorRoomTools", |
|
ActionToolbarSection = "Guide Operations", |
|
}, XShortcutsTarget) |
|
end |
|
|
|
function OnMsg.EditorSelectionChanged() |
|
if GetDialog("XEditorRoomTools") then |
|
GetDialog("XEditorRoomTools"):ActionsUpdated() |
|
end |
|
end |
|
|
|
|
|
|
|
|
|
local function find_intersecting_slab(obj, class, tolerance) |
|
local bbox = obj:IsKindOf("Slab") and obj:GetWorldBBox() or obj:GetObjectBBox() |
|
return MapGetFirst(obj:GetPos(), const.SlabSizeX * 2, class, function(o) |
|
local bbox2 = o:GetWorldBBox() |
|
if tolerance then |
|
bbox2 = bbox2:sizex() > bbox2:sizey() and bbox2:grow(-tolerance, 0, 0) or bbox2:grow(0, -tolerance, 0) |
|
end |
|
return o ~= obj and (bbox:Intersect(bbox2) ~= const.irOutside or obj:GetPos():InBox(bbox2)) |
|
end) |
|
end |
|
|
|
local function get_wall_thickness(obj) |
|
local wall_slab = |
|
obj:IsKindOf("Room") and obj.spawned_walls.North[1] or |
|
obj:IsKindOf("SlabWallObject") and find_intersecting_slab(obj, "Slab") or |
|
obj:IsKindOf("Slab") and obj |
|
return wall_slab and wall_slab:GetEntityBBox():maxx() or 0 |
|
end |
|
|
|
|
|
local function rotate_to_match(obj, axis1, axis2) |
|
axis1, axis2 = SetLen(axis1, 4096), SetLen(axis2, 4096) |
|
local axis = Cross(axis1, axis2) |
|
if axis ~= point30 then |
|
obj:Rotate(axis, GetAngle(axis1, axis2)) |
|
end |
|
end |
|
|
|
local function create_collection(objs) |
|
XEditorUndo:BeginOp() |
|
local collection = Collection.Create() |
|
for _, obj in ipairs(objs) do |
|
obj:SetCollection(collection) |
|
end |
|
XEditorUndo:EndOp{collection} |
|
end |
|
|
|
|
|
|
|
|
|
DefineClass.PrgSelectRoomComponents = { |
|
__parents = { "PrgStatement" }, |
|
properties = { |
|
{ id = "RoomsVar", editor = "choice", default = "selected", items = PrgVarsCombo, }, |
|
{ id = "AssignTo", name = "Add to variable", editor = "combo", default = "", items = PrgVarsCombo, }, |
|
{ id = "All", editor = "bool", default = true, }, |
|
{ id = "Walls", editor = "bool", default = true, category = "Walls, Windows & Doors", }, |
|
{ id = "Doors", editor = "bool", default = true, category = "Walls, Windows & Doors", }, |
|
{ id = "Windows", editor = "bool", default = true, category = "Walls, Windows & Doors", }, |
|
{ id = "UseParams", editor = "bool", default = false, category = "Walls, Windows & Doors", help = "Get whether North, South, West and East object should be returned from parameters passed to the ExtrasGen Prg", }, |
|
{ id = "North", editor = "bool", default = true, category = "Walls, Windows & Doors", read_only = function(self) return self.UseParams end, }, |
|
{ id = "South", editor = "bool", default = true, category = "Walls, Windows & Doors", read_only = function(self) return self.UseParams end, }, |
|
{ id = "East", editor = "bool", default = true, category = "Walls, Windows & Doors", read_only = function(self) return self.UseParams end, }, |
|
{ id = "West", editor = "bool", default = true, category = "Walls, Windows & Doors", read_only = function(self) return self.UseParams end, }, |
|
{ id = "Corners", editor = "bool", default = true, category = "Corners", }, |
|
{ id = "CornersNW", editor = "bool", default = true, category = "Corners", }, |
|
{ id = "CornersSW", editor = "bool", default = true, category = "Corners", }, |
|
{ id = "CornersNE", editor = "bool", default = true, category = "Corners", }, |
|
{ id = "CornersSE", editor = "bool", default = true, category = "Corners", }, |
|
{ id = "Roof", editor = "bool", default = true, }, |
|
{ id = "Floors", editor = "bool", default = true, }, |
|
{ id = "FloorMin", editor = "number", default = 1, min = 1, max = 10, category = "Floor Range", }, |
|
{ id = "FloorMax", editor = "number", default = 10, min = 1, max = 10, category = "Floor Range", }, |
|
}, |
|
EditorName = "Select room components", |
|
EditorView = Untranslated("Select <ComponentsText><FloorsText> (of rooms in '<RoomsVar>')<DirectionsText>"), |
|
EditorSubmenu = "Objects", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function PrgSelectRoomComponents:OnEditorSetProperty(prop_id, old_value, ged) |
|
if prop_id == "All" then |
|
self.Walls = self.All |
|
self.Corners = self.All |
|
self.Roof = self.All |
|
self.Floors = self.All |
|
self.Doors = self.All |
|
self.Windows = self.All |
|
end |
|
if prop_id == "All" or prop_id == "Corners" then |
|
self.CornersNW = self.Corners |
|
self.CornersSW = self.Corners |
|
self.CornersNE = self.Corners |
|
self.CornersSE = self.Corners |
|
end |
|
self.Corners = self.CornersNW and self.CornersSW and self.CornersNE and self.CornersSE |
|
self.All = self.Walls and self.Corners and self.Roof and self.Floors and self.Doors and self.Windows |
|
GedForceUpdateObject(self) |
|
end |
|
|
|
function PrgSelectRoomComponents:GetComponentsText() |
|
local walls = self.North and self.South and self.East and self.West |
|
local corners = self.CornersNW and self.CornersSW and self.CornersNE and self.CornersSE |
|
local all = walls and corners and self.Roof and self.Floors and self.Doors and self.Windows |
|
if all then return "all components" end |
|
|
|
local texts = {} |
|
if corners then |
|
texts[#texts + 1] = "Corners" |
|
else |
|
for _, v in ipairs({ "CornersNW", "CornersSW", "CornersNE", "CornersSE" }) do |
|
texts[#texts + 1] = self[v] and v or nil |
|
end |
|
end |
|
for _, v in ipairs({ "Roof", "Floors", "Walls", "Doors", "Windows" }) do |
|
texts[#texts + 1] = self[v] and v or nil |
|
end |
|
return table.concat(texts, ", ") |
|
end |
|
|
|
function PrgSelectRoomComponents:GetFloorsText() |
|
if self.FloorMin == PrgSelectRoomComponents.FloorMin and self.FloorMax == PrgSelectRoomComponents.FloorMax then |
|
return "" |
|
end |
|
return string.format(", floors %s - %s", self.FloorMin, self.FloorMax) |
|
end |
|
|
|
function PrgSelectRoomComponents:GetDirectionsText() |
|
if self.UseParams then |
|
return "\n--> get directions for walls/doors/windows from params" |
|
end |
|
local texts = {} |
|
for _, v in ipairs({ "North", "South", "East", "West" }) do |
|
texts[#texts + 1] = self[v] and v or nil |
|
end |
|
return "\n--> only " .. table.concat(texts, ", ") .. " walls/doors/windows" |
|
end |
|
|
|
function PrgSelectRoomComponents.Get(room, component, North, South, East, West) |
|
if component == "CornersNW" then return room.spawned_corners.North |
|
elseif component == "CornersSW" then return room.spawned_corners.West |
|
elseif component == "CornersNE" then return room.spawned_corners.East |
|
elseif component == "CornersSE" then return room.spawned_corners.South |
|
elseif component == "Roof" then return room.roof_objs |
|
elseif component == "Floors" then return room.spawned_floors |
|
elseif component == "Walls" and room.spawned_walls.North then |
|
local objs = {} |
|
if North then table.iappend(objs, room.spawned_walls.North) end |
|
if South then table.iappend(objs, room.spawned_walls.South) end |
|
if East then table.iappend(objs, room.spawned_walls.East) end |
|
if West then table.iappend(objs, room.spawned_walls.West) end |
|
return objs |
|
elseif component == "Doors" and room.spawned_doors then |
|
local objs = {} |
|
if North then table.iappend(objs, room.spawned_doors.North) end |
|
if South then table.iappend(objs, room.spawned_doors.South) end |
|
if East then table.iappend(objs, room.spawned_doors.East) end |
|
if West then table.iappend(objs, room.spawned_doors.West) end |
|
return objs |
|
elseif component == "Windows" and room.spawned_windows then |
|
local objs = {} |
|
if North then table.iappend(objs, room.spawned_windows.North) end |
|
if South then table.iappend(objs, room.spawned_windows.South) end |
|
if East then table.iappend(objs, room.spawned_windows.East) end |
|
if West then table.iappend(objs, room.spawned_windows.West) end |
|
return objs |
|
end |
|
end |
|
|
|
function PrgSelectRoomComponents.Add(room, component, floor_min, floor_max, objs, North, South, East, West) |
|
for _, obj in ipairs(PrgSelectRoomComponents.Get(room, component, North, South, East, West) or empty_table) do |
|
if obj and obj.floor >= floor_min and obj.floor <= floor_max then |
|
objs[#objs + 1] = obj |
|
end |
|
end |
|
end |
|
|
|
function PrgSelectRoomComponents:GenerateCode(code, indent) |
|
code:appendf("%slocal __sel = %s\n", indent, self.RoomsVar) |
|
if self.RoomsVar == self.AssignTo then |
|
code:appendf("%s%s = {}\n", indent, self.AssignTo) |
|
else |
|
local var_exists = self:VarsInScope()[self.AssignTo] |
|
if var_exists then |
|
code:appendf("%s%s = %s or {}\n", indent, self.AssignTo, self.AssignTo) |
|
else |
|
code:appendf("%slocal %s = {}\n", indent, self.AssignTo) |
|
end |
|
end |
|
code:appendf("%sfor _, obj in ipairs(__sel) do\n", indent) |
|
code:appendf("%s\tif IsKindOf(obj, \"Room\") then\n", indent) |
|
for _, component in ipairs({ "CornersNW", "CornersSW", "CornersNE", "CornersSE", "Roof", "Floors", "Walls", "Doors", "Windows" }) do |
|
if self[component] then |
|
code:appendf("%s\t\tPrgSelectRoomComponents.Add(obj, \"%s\", %d, %d, %s, %s, %s, %s, %s)\n", |
|
indent, component, self.FloorMin, self.FloorMax, self.AssignTo, |
|
self.UseParams and "ExtrasGenParams.North" or tostring(self.North), |
|
self.UseParams and "ExtrasGenParams.South" or tostring(self.South), |
|
self.UseParams and "ExtrasGenParams.East" or tostring(self.East), |
|
self.UseParams and "ExtrasGenParams.West" or tostring(self.West)) |
|
end |
|
end |
|
code:appendf("%s\tend\n", indent) |
|
code:appendf("%send\n", indent) |
|
end |
|
|
|
function PrgSelectRoomComponents:GatherVars(vars) |
|
vars[self.AssignTo] = "local" |
|
end |
|
|
|
|
|
|
|
|
|
DefineClass.PrgModifyObjOrList = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ id = "Variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
}, |
|
ExtraParams = { "rand" }, |
|
EditorView = Untranslated("<EditorName> '<Variable>'"), |
|
EditorSubmenu = "Modify", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function PrgModifyObjOrList.Modify(obj, ...) |
|
|
|
end |
|
|
|
function PrgModifyObjOrList:Exec(rand, Variable, ...) |
|
if IsKindOf(Variable, "Object") then |
|
local objs = { Variable } |
|
XEditorUndo:BeginOp{ objects = objs } |
|
self.Modify(rand, Variable, ...) |
|
XEditorUndo:EndOp(objs) |
|
else |
|
XEditorUndo:BeginOp{ objects = Variable } |
|
for _, obj in ipairs(Variable or empty_table) do |
|
self.Modify(rand, obj, ...) |
|
end |
|
XEditorUndo:EndOp(Variable) |
|
end |
|
end |
|
|
|
|
|
local default_color = RGB(200, 200, 200) |
|
|
|
DefineClass.PrgColorize = { |
|
__parents = { "PrgModifyObjOrList" }, |
|
properties = { |
|
{ id = "Reset1", name = "Reset color 1", editor = "bool", default = false, }, |
|
{ id = "Color1", name = "Modify color 1", editor = "color", default = default_color, alpha = false, }, |
|
{ id = "Reset2", name = "Reset color 2", editor = "bool", default = false, }, |
|
{ id = "Color2", name = "Modify color 2", editor = "color", default = default_color, alpha = false, }, |
|
{ id = "Reset3", name = "Reset color 3", editor = "bool", default = false, }, |
|
{ id = "Color3", name = "Modify color 3", editor = "color", default = default_color, alpha = false, }, |
|
{ id = "Reset4", name = "Reset color 4", editor = "bool", default = false, }, |
|
{ id = "Color4", name = "Modify color 4", editor = "color", default = default_color, alpha = false, }, |
|
}, |
|
EditorName = "Colorize", |
|
} |
|
|
|
function PrgColorize.Modify(rand, obj, Reset1, Color1, Reset2, Color2, Reset3, Color3, Reset4, Color4) |
|
local slab |
|
if obj:IsKindOf("Slab") then |
|
if not obj.colors then |
|
obj.colors = ColorizationPropSet:new() |
|
end |
|
slab = obj |
|
obj = obj.colors |
|
end |
|
obj:SetEditableColor1(Reset1 and Color1 or InterpolateRGB(obj:GetEditableColor1(), Color1, 1, 2)) |
|
obj:SetEditableColor2(Reset2 and Color2 or InterpolateRGB(obj:GetEditableColor2(), Color2, 1, 2)) |
|
obj:SetEditableColor3(Reset3 and Color3 or InterpolateRGB(obj:GetEditableColor3(), Color3, 1, 2)) |
|
if slab then |
|
slab:SetProperty("colors", obj) |
|
end |
|
end |
|
|
|
DefineClass.PrgScale = { |
|
__parents = { "PrgModifyObjOrList" }, |
|
properties = { |
|
{ id = "Reset", editor = "bool", default = false, }, |
|
{ id = "Scale", editor = "number", default = 100, scale = "%", min = 10, max = 250, slider = true }, |
|
{ id = "Deviation", editor = "number", default = 0, min = 0, max = 250, slider = true }, |
|
}, |
|
EditorName = "Scale", |
|
} |
|
|
|
function PrgScale.Modify(rand, obj, Reset, Scale, Deviation) |
|
local scale = Scale + rand(Deviation * 2 + 1) - Deviation |
|
obj:SetScale(Reset and scale or MulDivRound(obj:GetScale(), scale, 100)) |
|
end |
|
|
|
DefineClass.PrgOffset = { |
|
__parents = { "PrgModifyObjOrList" }, |
|
properties = { |
|
{ id = "Local", "Local coordinates", editor = "bool", default = true, }, |
|
{ id = "X", "X offset", editor = "number", default = 0, scale = "m", }, |
|
{ id = "Xd", "X deviation", editor = "number", default = 0, scale = "m", }, |
|
{ id = "Y", "Y offset", editor = "number", default = 0, scale = "m", }, |
|
{ id = "Yd", "Y deviation", editor = "number", default = 0, scale = "m", }, |
|
{ id = "Z", "Z offset", editor = "number", default = 0, scale = "m", }, |
|
{ id = "Zd", "Z deviation", editor = "number", default = 0, scale = "m", }, |
|
}, |
|
EditorName = "Offset", |
|
} |
|
|
|
function PrgOffset.Modify(rand, obj, Local, X, Xd, Y, Yd, Z, Zd) |
|
local x = X + rand(Xd * 2 + 1) - Xd |
|
local y = Y + rand(Yd * 2 + 1) - Yd |
|
local z = Z + rand(Zd * 2 + 1) - Zd |
|
local offset = point(x, y, z) |
|
obj:SetPos(Local and obj:GetRelativePoint(offset) or (obj:GetPos() + offset)) |
|
end |
|
|
|
DefineClass.PrgRotate = { |
|
__parents = { "PrgModifyObjOrList" }, |
|
properties = { |
|
{ id = "Reset", editor = "bool", default = false, }, |
|
{ id = "Local", "Local coordinates", editor = "bool", default = true, no_edit = function(self) return self.Reset end, }, |
|
{ id = "Axis", editor = "point", default = axis_z, }, |
|
{ id = "Angle", editor = "number", default = 0, scale = "deg", min = 0, max = 360 * 60, slider = true }, |
|
{ id = "Deviation", editor = "number", default = 0, scale = "deg", min = 0, max = 180 * 60, slider = true }, |
|
}, |
|
EditorName = "Rotate", |
|
} |
|
|
|
function PrgRotate.Modify(rand, obj, Reset, Local, Axis, Angle, Deviation) |
|
local angle = Angle + rand(Deviation * 2 + 1) - Deviation |
|
local axis = Local and not Reset and (obj:GetRelativePoint(Axis) - obj:GetPos()) or Axis |
|
if Reset then |
|
obj:SetAxisAngle(axis, angle) |
|
else |
|
obj:SetAxisAngle(ComposeRotation(obj:GetAxis(), obj:GetAngle(), axis, angle)) |
|
end |
|
end |
|
|
|
DefineClass.PrgAlign = { |
|
__parents = { "PrgModifyObjOrList" }, |
|
properties = { |
|
{ id = "AlignTo", name = "Align to", editor = "choice", default = "Wall exterior", |
|
items = { "Wall exterior", "Wall interior", "Wall top", "Wall bottom", "Roof", "Floor" } }, |
|
}, |
|
EditorName = "Align", |
|
} |
|
|
|
function PrgAlign:GetEditorView() |
|
return string.format("Align '%s' to '%s'", self.Variable, self.AlignTo:lower()) |
|
end |
|
|
|
local wall_data = { |
|
{ coord = "x", box_member = "minx", setter = "SetX", axis = -axis_x, angle = 0 }, |
|
{ coord = "x", box_member = "maxx", setter = "SetX", axis = axis_x, angle = 180 * 60 }, |
|
{ coord = "y", box_member = "miny", setter = "SetY", axis = -axis_y, angle = 90 * 60 }, |
|
{ coord = "y", box_member = "maxy", setter = "SetY", axis = axis_y, angle = -90 * 60 }, |
|
} |
|
|
|
function PrgAlign.Modify(rand, obj, AlignTo) |
|
if not obj.room then return end |
|
|
|
local room_box = obj.room.box |
|
obj:ClearGameFlags(const.gofOnRoof) |
|
if AlignTo == "Roof" then |
|
obj.room:SnapObject(obj) |
|
elseif AlignTo == "Floor" then |
|
local x, y, z = obj:GetPosXYZ() |
|
obj:SetPos(x, y, room_box:minz()) |
|
obj:SetAxisAngle(axis_z, 0) |
|
elseif AlignTo:starts_with("Wall") then |
|
|
|
local pos = obj:GetPos() |
|
local min_dist, min_wall |
|
for i, wall in ipairs(wall_data) do |
|
local dist = abs(pos[wall.coord](pos) - room_box[wall.box_member](room_box)) |
|
if not min_dist or min_dist > dist then |
|
min_wall, min_dist = wall, dist |
|
end |
|
end |
|
|
|
|
|
pos = pos[min_wall.setter](pos, room_box[min_wall.box_member](room_box)) |
|
if AlignTo == "Wall bottom" then |
|
local bbox = obj:GetEntityBBox() |
|
pos = pos:SetZ(room_box:minz()) |
|
elseif AlignTo == "Wall top" then |
|
local bbox = obj:GetEntityBBox() |
|
pos = pos:SetZ(room_box:maxz()) |
|
elseif AlignTo == "Wall interior" then |
|
pos = pos - SetLen(min_wall.axis, get_wall_thickness(obj.room)) |
|
elseif AlignTo == "Wall exterior" then |
|
pos = pos + SetLen(min_wall.axis, get_wall_thickness(obj.room)) |
|
end |
|
obj:SetPos(pos) |
|
obj:SetOrientation(min_wall.axis, min_wall.angle) |
|
end |
|
end |
|
|
|
|
|
|
|
|
|
DefineClass.PlaceObjectData = { |
|
__parents = { "PropertyObject" }, |
|
properties = { |
|
{ id = "EditorClass", editor = "choice", default = "", items = function() return XEditorPlaceableObjectsCombo end, }, |
|
{ id = "Weight", editor = "number", default = 100, min = 1, max = 100, slider = true }, |
|
{ id = "Scale", name = "Scale", editor = "number", default = 100, min = 10, max = 250, slider = true, }, |
|
{ id = "Rotate", name = "Rotate", editor = "number", scale = "deg", default = 0, }, |
|
{ id = "Mirror", editor = "bool", default = false, }, |
|
}, |
|
EditorView = Untranslated("<EditorClass> (<Weight>)"), |
|
StoreAsTable = true, |
|
} |
|
|
|
function PlaceObjectData.FormatList(list) |
|
local classes = {} |
|
for _, item in ipairs(list or empty_table) do |
|
classes[#classes + 1] = string.format("%s (%d)", item.EditorClass, item.Weight) |
|
end |
|
return table.concat(classes, ", ") |
|
end |
|
|
|
function PlaceObjectData.PlaceRandomObject(rand, list, pos, angle, axis, scale) |
|
local data = table.weighted_rand(list, "Weight", rand()) |
|
local obj = XEditorPlaceObject(data.EditorClass) |
|
obj:SetGameFlags(const.gofPermanent) |
|
if IsKindOf(obj, "AlignedObj") then |
|
obj:AlignObj(pos, (angle or 0) + data.Rotate) |
|
else |
|
obj:SetPos(pos) |
|
local axis, angle = axis or axis_z, angle or 0 |
|
if data.Rotate ~= 0 then |
|
axis, angle = ComposeRotation(axis_z, data.Rotate, axis, angle) |
|
end |
|
obj:SetAxisAngle(axis, angle) |
|
end |
|
obj:SetScale(MulDivRound(data.Scale, scale or 100, 100)) |
|
obj:SetMirrored(data.Mirror) |
|
return obj, data |
|
end |
|
|
|
|
|
DefineClass.PrgPlaceObject = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ id = "Classes", editor = "nested_list", default = false, class = "PlaceObjectData" }, |
|
{ id = "AlignTo", name = "Align to", editor = "combo", default = "", items = PrgVarsCombo, variable = true, }, |
|
{ id = "Store", editor = "choice", default = "", items = { "", "Add to", "Assign to" }, }, |
|
{ id = "AssignTo", name = "Variable", editor = "combo", default = "", items = PrgVarsCombo, variable = true, |
|
no_edit = function(obj) return obj.Store == "" end, }, |
|
}, |
|
ExtraParams = { "rand" }, |
|
EditorName = "Place object", |
|
EditorSubmenu = "Objects", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function PrgPlaceObject:GetEditorView() |
|
local ret = "Place object " .. PlaceObjectData.FormatList(self.Classes) |
|
if self.AlignTo ~= "" then |
|
ret = ret .. string.format("\n--> Align to '%s'", self.AlignTo) |
|
end |
|
if self.Store ~= "" then |
|
ret = ret .. string.format("\n--> %s variable '%s'", self.Store, self.AssignTo) |
|
end |
|
return ret |
|
end |
|
|
|
function PrgPlaceObject:Exec(rand, Classes, AlignTo, Store, AssignTo) |
|
if not next(Classes) then return end |
|
|
|
local obj |
|
XEditorUndo:BeginOp() |
|
if AlignTo then |
|
obj = PlaceObjectData.PlaceRandomObject(rand, Classes, AlignTo:GetPos(), AlignTo:GetAngle(), AlignTo:GetAxis(), AlignTo:GetScale()) |
|
rawset(obj, "room", rawget(AlignTo, "room")) |
|
else |
|
obj = PlaceObjectData.PlaceRandomObject(rand, Classes) |
|
end |
|
Msg("EditorCallback", "EditorCallbackPlace", {obj}) |
|
XEditorUndo:EndOp{obj} |
|
|
|
if Store == "Assign to" then |
|
return obj |
|
elseif Store == "Add to" then |
|
local objs = IsKindOf(AssignTo, "Object") and { AssignTo } or AssignTo or {} |
|
objs[#objs + 1] = obj |
|
return objs |
|
end |
|
end |
|
|
|
|
|
|
|
|
|
local not_on_wall = function(obj) return not obj.PlaceOn:starts_with("Wall") end |
|
|
|
DefineClass.PlaceRoomGuides = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ category = "Input/output", id = "RoomsVar", name = "Rooms variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Input/output", id = "AssignTo", name = "Add to variable", editor = "combo", default = "", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Placement", id = "PlaceOn", name = "Place on", editor = "choice", default = "Wall exterior", items = { "Wall exterior", "Wall interior", "Floor", "Roof" }, }, |
|
{ category = "Placement", id = "UseParams", editor = "bool", default = false, no_edit = not_on_wall, help = "Get whether North, South, West and East object should be returned from parameters passed to the ExtrasGen Prg" }, |
|
{ category = "Placement", id = "North", editor = "bool", default = true, no_edit = not_on_wall, read_only = function(self) return self.UseParams end, }, |
|
{ category = "Placement", id = "South", editor = "bool", default = true, no_edit = not_on_wall, read_only = function(self) return self.UseParams end, }, |
|
{ category = "Placement", id = "East", editor = "bool", default = true, no_edit = not_on_wall, read_only = function(self) return self.UseParams end, }, |
|
{ category = "Placement", id = "West", editor = "bool", default = true, no_edit = not_on_wall, read_only = function(self) return self.UseParams end, }, |
|
{ category = "Placement", id = "Horizontal", editor = "bool", default = true, }, |
|
{ category = "Placement", id = "StartFrom", name = "Start from", editor = "choice", default = "Both", |
|
items = function(obj) return obj.Horizontal and { "Top", "Bottom", "Middle", "Both" } or { "Left", "Right", "Middle", "Both" } end, |
|
}, |
|
{ category = "Placement", id = "Count", editor = "number", default = 1, }, |
|
{ category = "Placement", id = "FirstOffset", name = "First offset (slabs)", editor = "number", default = 0, |
|
scale = function(obj) return obj.Horizontal and const.SlabSizeX or const.SlabSizeZ end, |
|
}, |
|
{ category = "Placement", id = "DiminishOffset", name = "Diminish offset", editor = "number", default = 0, min = 0, max = 100, scale = "%" }, |
|
{ category = "Placement", id = "Direction", editor = "choice", default = "Inwards (wall)", |
|
items = { "Inwards (wall)", "Outwards (wall)", "Inwards (room)", "Outwards (room)" }, |
|
read_only = function(self) return self.PlaceOn == "Floor" or self.PlaceOn == "Roof" end, |
|
}, |
|
}, |
|
EditorName = "Place room guides", |
|
EditorSubmenu = "Guides", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function PlaceRoomGuides:GetEditorView() |
|
local ret = string.format("Place %s %s guides on room %s:", self.Count, self.Horizontal and "horizontal" or "vertical", self.PlaceOn:lower()) |
|
|
|
ret = ret .. string.format("\n--> To rooms in variable '%s'", self.RoomsVar) |
|
ret = ret .. string.format("\n--> Starting from %s", self.StartFrom) |
|
|
|
local size = (self.Horizontal and const.SlabSizeX or const.SlabSizeZ) * 1.0 |
|
if self.FirstOffset ~= 0 then ret = ret .. string.format(", first offset %0.1f", self.FirstOffset / size) end |
|
if self.DiminishOffset ~= 0 then ret = ret .. string.format(", diminish by %d%%", self.DiminishOffset) end |
|
|
|
ret = ret .. string.format("\n--> Direction = %s", self.Direction) |
|
|
|
if self.AssignTo ~= "" then ret = ret .. string.format("\n--> Add to variable '%s'", self.AssignTo) end |
|
return ret |
|
end |
|
|
|
function PlaceRoomGuides:OnEditorSetProperty(prop_id, old_value, ged) |
|
if prop_id == "PlaceOn" and (self.PlaceOn == "Floor" or self.PlaceOn == "Roof") then |
|
self.Direction = "Inwards (wall)" |
|
end |
|
end |
|
|
|
|
|
function PlaceRoomGuides.OnWall(room, objs, edge_pt1, edge_pt2, along_vec, room_inward_vec, |
|
Horizontal, StartFrom, Count, FirstOffset, DiminishOffset, Direction, skip_first |
|
) |
|
if Horizontal then |
|
local along = edge_pt1 - edge_pt2 |
|
edge_pt1 = edge_pt2 |
|
edge_pt2 = edge_pt2 + along_vec |
|
along_vec = along |
|
end |
|
|
|
if StartFrom ~= "Right" and StartFrom ~= "Top" then |
|
local params = { room_inward_vec, false, "Right", Count, FirstOffset, DiminishOffset, Direction } |
|
if StartFrom == "Left" or StartFrom == "Bottom" then |
|
PlaceRoomGuides.OnWall(room, objs, edge_pt1 + along_vec, edge_pt2 + along_vec, -along_vec, unpack_params(params)) |
|
elseif StartFrom == "Middle" then |
|
PlaceRoomGuides.OnWall(room, objs, edge_pt1 + along_vec / 2, edge_pt2 + along_vec / 2, along_vec, unpack_params(params)) |
|
table.insert(params, "skip_first") |
|
PlaceRoomGuides.OnWall(room, objs, edge_pt1 + along_vec / 2, edge_pt2 + along_vec / 2, -along_vec, unpack_params(params)) |
|
elseif StartFrom == "Both" then |
|
PlaceRoomGuides.OnWall(room, objs, edge_pt1, edge_pt2, along_vec, unpack_params(params)) |
|
PlaceRoomGuides.OnWall(room, objs, edge_pt1 + along_vec, edge_pt2 + along_vec, -along_vec, unpack_params(params)) |
|
end |
|
return |
|
end |
|
|
|
local normal = |
|
Direction == "Inwards (wall)" and along_vec or |
|
Direction == "Outwards (wall)" and -along_vec or |
|
Direction == "Inwards (room)" and room_inward_vec or |
|
Direction == "Outwards (room)" and -room_inward_vec |
|
local pt1, pt2 = edge_pt1, edge_pt2 |
|
local offset, offset_add = FirstOffset, FirstOffset |
|
for i = 1, Count do |
|
local offset_vec = SetLen(along_vec, offset) |
|
pt1 = pt1 + offset_vec |
|
pt2 = pt2 + offset_vec |
|
offset_add = MulDivRound(offset_add, 100 - DiminishOffset, 100) |
|
offset = offset + offset_add |
|
if not (skip_first and i == 1 and FirstOffset == 0) then |
|
local guide = EditorLineGuide:new() |
|
guide:Set(pt1, pt2, normal) |
|
objs[#objs + 1] = guide |
|
end |
|
end |
|
end |
|
|
|
function PlaceRoomGuides:Exec(RoomsVar, AssignTo, PlaceOn, UseParams, North, South, East, West, Horizontal, ...) |
|
local objs = {} |
|
RoomsVar = IsKindOf(RoomsVar, "Object") and { RoomsVar } or RoomsVar |
|
for _, room in ipairs(RoomsVar or empty_table) do |
|
if PlaceOn:starts_with("Wall") then |
|
local size = PlaceOn:ends_with("interior") and -get_wall_thickness(room) or get_wall_thickness(room) |
|
local room_box = GrowBox(room.box, size, size, 0) |
|
local x1, y1, z1, x2, y2, z2 = room_box:xyzxyz() |
|
if UseParams and ExtrasGenParams.North or not UseParams and North then |
|
PlaceRoomGuides.OnWall(room, objs, point(x1, y1, z1), point(x1, y1, z2), point(x2 - x1, 0, 0), point(0, y2 - y1, 0), Horizontal, ...) |
|
end |
|
if UseParams and ExtrasGenParams.East or not UseParams and East then |
|
PlaceRoomGuides.OnWall(room, objs, point(x2, y1, z1), point(x2, y1, z2), point(0, y2 - y1, 0), point(x1 - x2, 0, 0), Horizontal, ...) |
|
end |
|
if UseParams and ExtrasGenParams.South or not UseParams and South then |
|
PlaceRoomGuides.OnWall(room, objs, point(x2, y2, z1), point(x2, y2, z2), point(x1 - x2, 0, 0), point(0, y1 - y2, 0), Horizontal, ...) |
|
end |
|
if UseParams and ExtrasGenParams.West or not UseParams and West then |
|
PlaceRoomGuides.OnWall(room, objs, point(x1, y2, z1), point(x1, y2, z2), point(0, y1 - y2, 0), point(x2 - x1, 0, 0), Horizontal, ...) |
|
end |
|
elseif PlaceOn == "Floor" or PlaceOn == "Roof" then |
|
local x1, y1, z1, x2, y2, z2 = room.box:xyzxyz() |
|
local z = PlaceOn == "Floor" and z1 or z2 |
|
local inwards_vec = PlaceOn == "Floor" and point(0, 0, z2 - z1) or point(0, 0, z1 - z2) |
|
local first_idx = #objs + 1 |
|
if room.roof_direction == "North-South" or room.roof_direction == "East" or room.roof_direction == "West" then |
|
PlaceRoomGuides.OnWall(room, objs, point(x1, y1, z), point(x2, y1, z), point(0, y2 - y1, 0), inwards_vec, Horizontal, ...) |
|
else |
|
PlaceRoomGuides.OnWall(room, objs, point(x1, y1, z), point(x1, y2, z), point(x2 - x1, 0, 0), inwards_vec, Horizontal, ...) |
|
end |
|
if PlaceOn == "Roof" then |
|
if room.roof_type == "Gable" then |
|
if not Horizontal then |
|
|
|
for i = first_idx, #objs do |
|
local old = objs[i] |
|
local new = EditorLineGuide:new() |
|
new:Set(old:GetPos1(), old:GetPos(), old:GetNormal()) |
|
objs[i] = new |
|
new = EditorLineGuide:new() |
|
new:Set(old:GetPos(), old:GetPos2(), old:GetNormal()) |
|
objs[#objs + 1] = new |
|
old:delete() |
|
end |
|
else |
|
|
|
for i = first_idx, #objs do |
|
local old = objs[i] |
|
if old:GetPos():Dist2D(room:GetPos()) < 5 * guic and Dot(old:GetNormal(), axis_z) == 0 then |
|
local new = EditorLineGuide:new() |
|
new:Set(old:GetPos1(), old:GetPos2(), -old:GetNormal()) |
|
objs[#objs + 1] = new |
|
break |
|
end |
|
end |
|
end |
|
end |
|
|
|
|
|
for i = first_idx, #objs do |
|
local guide = objs[i] |
|
|
|
local pt, pt1, pt2 = guide:GetPos(), guide:GetPos1(), guide:GetPos2() |
|
local z1, z2 = room:GetRoofZAndDir(pt1), room:GetRoofZAndDir(pt2) |
|
pt1, pt2 = pt1:SetZ(z1), pt2:SetZ(z2) |
|
|
|
local normal_start, normal_end = pt1, pt1 + SetLen(guide:GetNormal(), const.SlabSizeX * 2) |
|
local normal_end_z = room:GetRoofZAndDir(normal_end) |
|
normal_end = normal_end:SetZ(normal_end_z) |
|
|
|
guide:Set(pt1, pt2, normal_end - normal_start) |
|
end |
|
end |
|
end |
|
end |
|
|
|
create_collection(objs) |
|
XEditorUndo:BeginOp() |
|
XEditorUndo:EndOp(objs) |
|
return table.iappend(AssignTo or {}, objs) |
|
end |
|
|
|
DefineClass.PlaceGuidesAroundSlabs = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ category = "Input/output", id = "SlabsVar", name = "Slabs variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Input/output", id = "AssignTo", name = "Add to variable", editor = "combo", default = "", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Placement", id = "Top", editor = "bool", default = false, }, |
|
{ category = "Placement", id = "Bottom", editor = "bool", default = true, }, |
|
{ category = "Placement", id = "Left", editor = "bool", default = false, }, |
|
{ category = "Placement", id = "Right", editor = "bool", default = false, }, |
|
{ category = "Placement", id = "OnExterior", name = "On exterior wall", editor = "bool", default = true, }, |
|
{ category = "Placement", id = "OrientOutwards", name = "Orient outwards", editor = "bool", default = false, }, |
|
}, |
|
EditorName = "Place guides around slabs", |
|
EditorSubmenu = "Guides", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function PlaceGuidesAroundSlabs:GetEditorView() |
|
local dirs = {} |
|
if self.Top then dirs[#dirs + 1] = "top" end |
|
if self.Bottom then dirs[#dirs + 1] = "bottom" end |
|
if self.Left then dirs[#dirs + 1] = "left" end |
|
if self.Right then dirs[#dirs + 1] = "right" end |
|
|
|
local ret = string.format("Place guides at %s of slabs in '%s'", table.concat(dirs, ", "), self.SlabsVar) |
|
if self.OnExterior then ret = ret .. "\n--> On exterior wall" else ret = ret .. "\n--> On interior wall" end |
|
if self.AssignTo ~= "" then ret = ret .. string.format("\n--> Add to variable '%s'", self.AssignTo) end |
|
return ret |
|
end |
|
|
|
function PlaceGuidesAroundSlabs.CreateGuide(obj, pt1_local, pt2_local, axis_local, objs) |
|
local pt1 = obj:GetRelativePoint(pt1_local) |
|
local pt2 = obj:GetRelativePoint(pt2_local) |
|
local normal = obj:GetRelativePoint(axis_local) - obj:GetVisualPos() |
|
|
|
local guide = EditorLineGuide:new() |
|
guide:Set(pt1, pt2, normal) |
|
objs[#objs + 1] = guide |
|
end |
|
|
|
function PlaceGuidesAroundSlabs:Exec(SlabsVar, AssignTo, Top, Bottom, Left, Right, OnExterior, OrientOutwards) |
|
local objs = {} |
|
for _, obj in ipairs(SlabsVar or empty_table) do |
|
local bbox = obj:GetEntityBBox() |
|
local minx, miny, minz, maxx, maxy, maxz = bbox:xyzxyz() |
|
local size = OnExterior and get_wall_thickness(obj) or -get_wall_thickness(obj) |
|
local create_fn = PlaceGuidesAroundSlabs.CreateGuide |
|
local slab_axis = OnExterior and axis_x or -axis_x |
|
if Top then create_fn(obj, point(size, miny, maxz), point(size, maxy, maxz), OrientOutwards and slab_axis or axis_z, objs) end |
|
if Bottom then create_fn(obj, point(size, miny, minz), point(size, maxy, minz), OrientOutwards and slab_axis or -axis_z, objs) end |
|
if Left then create_fn(obj, point(size, maxy, minz), point(size, maxy, maxz), OrientOutwards and slab_axis or axis_y, objs) end |
|
if Right then create_fn(obj, point(size, miny, minz), point(size, miny, maxz), OrientOutwards and slab_axis or -axis_y, objs) end |
|
end |
|
|
|
create_collection(objs) |
|
XEditorUndo:BeginOp() |
|
XEditorUndo:EndOp(objs) |
|
return table.iappend(AssignTo or {}, objs) |
|
end |
|
|
|
DefineClass.PlaceGuidesBetweenSlabs = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ category = "Input/output", id = "SlabsVar", name = "Slabs variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Input/output", id = "AssignTo", name = "Add to variable", editor = "combo", default = "", items = PrgVarsCombo, variable = true, }, |
|
}, |
|
EditorName = "Place guides between slabs", |
|
EditorView = Untranslated("Place guides between each two slabs from <SlabsVar> in '<AssignTo>'"), |
|
EditorSubmenu = "Guides", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function PlaceGuidesBetweenSlabs:Exec(SlabsVar, AssignTo, Top, Bottom, Left, Right, OnExterior, OrientOutwards) |
|
local slabs = table.copy(SlabsVar or empty_table) |
|
if #slabs < 2 then |
|
print("Please select 2 or more slabs.") |
|
return |
|
end |
|
table.sort(slabs, function(a, b) |
|
local ax, ay = a:GetPos():xy() |
|
local bx, by = b:GetPos():xy() |
|
if ax == bx then return ay < by end |
|
return ax < bx |
|
end) |
|
|
|
local function slab_snap_x(x) return (x + const.SlabSizeX / 2) / const.SlabSizeX * const.SlabSizeX end |
|
local function slab_snap_y(y) return (y + const.SlabSizeY / 2) / const.SlabSizeY * const.SlabSizeY end |
|
local function slab_snap_z(z) return (z + const.SlabSizeZ / 2) / const.SlabSizeZ * const.SlabSizeZ end |
|
|
|
local objs = {} |
|
for i = 1, #slabs - 1 do |
|
local obj = EditorLineGuide:new() |
|
local o1, o2 = slabs[i], slabs[i + 1] |
|
local p1, p2 = o1:GetPos(), o2:GetPos() |
|
local b1, b2 = o1:GetWorldBBox(), o2:GetWorldBBox() |
|
local dx, dy, dz = p2:x() - p1:x(), p2:y() - p1:y(), abs(p2:z() - p1:z()) |
|
local normal = o1:GetRelativePoint(axis_x) - p1 |
|
if dx >= dy and dx >= dz then |
|
local x = (b1:maxx() + b2:minx()) / 2 |
|
local y = (p1:y() + p2:y()) / 2 |
|
local z1, z2 = Min(b1:minz(), b2:minz()), Max(b1:maxz(), b2:maxz()) |
|
obj:Set(point(x, y, slab_snap_z(z1)), point(x, y, slab_snap_z(z2)), normal) |
|
elseif dy >= dx and dy >= dz then |
|
local x = (p1:x() + p2:x()) / 2 |
|
local y = (b1:maxy() + b2:miny()) / 2 |
|
local z1, z2 = Min(b1:minz(), b2:minz()), Max(b1:maxz(), b2:maxz()) |
|
obj:Set(point(x, y, slab_snap_z(z1)), point(x, y, slab_snap_z(z2)), normal) |
|
else |
|
local z = p1:z() > p2:z() and (b1:minz() + b2:maxz()) / 2 or (b2:minz() + b1:maxz()) / 2 |
|
local x1, x2 = Min(b1:minx(), b2:minx()), Max(b1:maxx(), b2:maxx()) |
|
local y1, y2 = Min(b1:miny(), b2:miny()), Max(b1:maxy(), b2:maxy()) |
|
if abs(x1 - x2) > abs(y1 - y2) then |
|
obj:Set(point(slab_snap_x(x1), p1:y(), z), point(slab_snap_x(x2), p1:y(), z), normal) |
|
else |
|
obj:Set(point(p1:x(), slab_snap_y(y1), z), point(p1:x(), slab_snap_y(y2), z), normal) |
|
end |
|
end |
|
objs[#objs + 1] = obj |
|
end |
|
|
|
create_collection(objs) |
|
XEditorUndo:BeginOp() |
|
XEditorUndo:EndOp(objs) |
|
return table.iappend(AssignTo or {}, objs) |
|
end |
|
|
|
DefineClass.RemoveRoomGuides = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ category = "Input/output", id = "RoomsVar", name = "Rooms variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
}, |
|
EditorName = "Remove room guides", |
|
EditorSubmenu = "Guides", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function RemoveRoomGuides:Exec(RoomsVar) |
|
local objs = {} |
|
for _, room in ipairs(RoomsVar or empty_table) do |
|
for _, guide in ipairs(MapGet(GrowBox(room.box, const.SlabSizeX / 2), "EditorLineGuide")) do |
|
objs[#objs + 1] = guide |
|
end |
|
end |
|
XEditorUndo:BeginOp{ objects = objs } |
|
for _, obj in ipairs(objs) do obj:delete() end |
|
XEditorUndo:EndOp() |
|
end |
|
|
|
local slab_move_units = { |
|
{ text = "Meters", value = "m" }, |
|
{ text = "Slabs (auto detect direction)", value = 1 }, |
|
{ text = "Horizontal slabs", value = const.SlabSizeX }, |
|
{ text = "Vertical slabs", value = const.SlabSizeZ }, |
|
} |
|
|
|
DefineClass.MoveSizeGuides = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ id = "GuidesVar", name = "Guides variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
{ id = "UpDownScale", name = "Up / down scale", editor = "choice", default = "m", items = |
|
{ { text = "Meters", value = "m" }, { text = "Vertical slabs", value = const.SlabSizeZ }, }, }, |
|
{ id = "UpDown", name = "Up / down", editor = "number", default = 0, scale = function(self) return self.UpDownScale end, }, |
|
{ id = "AlongScale", name = "Along scale", editor = "choice", default = "m", items = slab_move_units }, |
|
{ id = "Along", name = "Along guide direction", editor = "number", default = 0, scale = function(self) return self.AlongScale end, }, |
|
{ id = "SizeChangeScale", name = "Size change scale", editor = "choice", default = "m", items = slab_move_units }, |
|
{ id = "SizeChange", name = "Size change", editor = "number", default = 0, scale = function(self) return self.SizeChangeScale end, }, |
|
{ id = "ShiftForHalfStep", name = "Shift for half-step", editor = "bool", default = false, }, |
|
}, |
|
EditorName = "Move/size room guides", |
|
EditorSubmenu = "Guides", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function MoveSizeGuides:GetEditorView() |
|
local function format_change(scale, amount, up_text, down_text) |
|
amount = amount * 1.0 / GetPropScale(scale) |
|
local units = scale == "m" and "m" or |
|
scale == const.SlabSizeX and " horiz. slabs" or |
|
scale == const.SlabSizeY and " vert. slabs" or " slabs" |
|
local prefix = "" |
|
if up_text and down_text then |
|
prefix = up_text .. " " |
|
if amount < 0 then |
|
amount = -amount |
|
prefix = down_text .. " " |
|
end |
|
end |
|
return string.format("%s%0.1f%s", prefix, amount, units) |
|
end |
|
local ret = string.format("Move guides %s, %s along, %s", |
|
format_change(self.UpDownScale, self.UpDown, "up", "down"), |
|
format_change(self.AlongScale, self.Along), |
|
format_change(self.SizeChangeScale, self.SizeChange, "increase size by", "decrease size by")) |
|
return self.ShiftForHalfStep and ret .. "\n--> Allow holding Shift for half-step" or ret |
|
end |
|
|
|
function MoveSizeGuides:Exec(GuidesVar, _, UpDown, AlongScale, Along, SizeChangeScale, SizeChange, ShiftForHalfStep) |
|
GuidesVar = GuidesVar or empty_table |
|
if IsValid(GuidesVar) then GuidesVar = { GuidesVar } end |
|
XEditorUndo:BeginOp{ objects = GuidesVar } |
|
for _, guide in ipairs(GuidesVar) do |
|
local along = AlongScale == 1 and Along * (CalcAngleBetween(guide:GetNormal(), axis_z) > 89*60 and const.SlabSizeX or const.SlabSizeZ) or Along |
|
local size_change = SizeChangeScale == 1 and SizeChange * (guide:IsVertical() and const.SlabSizeZ or const.SlabSizeX) or SizeChange |
|
local up_down = UpDown |
|
if ShiftForHalfStep and terminal.IsKeyPressed(const.vkShift) then |
|
up_down = up_down / 2 |
|
along = along / 2 |
|
size_change = size_change / 2 |
|
end |
|
guide:SetPos(guide:GetVisualPos() + point(0, 0, up_down) + SetLen(guide:GetNormal(), along)) |
|
local len = guide:GetLength() |
|
if guide:GetLength() + size_change >= guim then |
|
guide:SetLength(guide:GetLength() + size_change) |
|
end |
|
end |
|
XEditorUndo:EndOp(GuidesVar) |
|
end |
|
|
|
|
|
|
|
|
|
DefineClass.LaySlabsAlongGuides = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ category = "Input/output", id = "GuidesVar", name = "Guides variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Input/output", id = "AssignTo", name = "Add to variable", editor = "combo", default = "", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Placement", id = "_", editor = "help", default = false, |
|
help = "Starts placing slabs from left-to-right for horizontal guides that point *outwards*, and bottom-to-top for vertical ones." }, |
|
{ category = "Placement", id = "SnapToVoxels", name = "Snap", editor = "bool", default = true, }, |
|
{ category = "Placement", id = "SnapToVoxelEdge", name = "Snap to voxel edges", editor = "bool", default = true, no_edit = function(self) return not self.SnapToVoxels end, }, |
|
{ category = "Placement", id = "SnapToNearestWall", name = "Snap to nearest wall", editor = "bool", default = true, no_edit = function(self) return self.SnapToVoxels end, }, |
|
{ category = "Placement", id = "StartGap", name = "Start gap (slabs)", editor = "number", default = 0, }, |
|
{ category = "Placement", id = "Fill", name = "Fill entire length", editor = "bool", default = true, }, |
|
{ category = "Placement", id = "Count", name = "Slabs to fill", editor = "number", default = 1, |
|
no_edit = function(obj) return obj.Fill end, |
|
}, |
|
{ category = "Placement", id = "BetweenGap", name = "Between gap (slabs)", editor = "number", default = 0, }, |
|
{ category = "Placement", id = "EndGap", name = "End gap (slabs)", editor = "number", default = 0, }, |
|
{ category = "Placement", id = "SkipDoors", name = "Skip doors/windows", editor = "bool", default = true, }, |
|
{ category = "Placement", id = "SkipInterior", name = "Skip interior", editor = "bool", default = false, }, |
|
{ category = "Objects", id = "Start", name = "Start slab(s) 1", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
{ category = "Objects", id = "Start2", name = "Start slab(s) 2", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
{ category = "Objects", id = "Middle", name = "Middle slab(s)", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
{ category = "Objects", id = "End1", name = "End slab(s) 1", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
{ category = "Objects", id = "End2", name = "End slab(s) 2", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
{ category = "Object Settings", id = "StartIsCorner", name = "Start is corner slab", editor = "bool", default = false, }, |
|
{ category = "Object Settings", id = "EndIsCorner", name = "End is corner slab", editor = "bool", default = false, }, |
|
{ category = "Object Settings", id = "StartIsColumnBase", name = "Start is column base", editor = "bool", default = false, }, |
|
{ category = "Object Settings", id = "EndIsColumnTop", name = "End 1 is column top", editor = "bool", default = false, }, |
|
{ category = "Object Settings", id = "Start2FixedDist", name = "Start 2 at fixed distance", editor = "number", scale = "m", default = 0, }, |
|
{ category = "Object Settings", id = "End2FixedDist", name = "End 2 at fixed distance", editor = "number", scale = "m", default = 0, }, |
|
{ category = "Object Settings", id = "StartIsSlab", name = "Start is floor slab", editor = "bool", default = false, }, |
|
{ category = "Object Settings", id = "EndIsSlab", name = "End is floor slab", editor = "bool", default = false, }, |
|
}, |
|
ExtraParams = { "rand" }, |
|
EditorName = "Lay slabs along guides", |
|
EditorSubmenu = "Objects", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function LaySlabsAlongGuides:GetEditorView() |
|
local ret = string.format("Lay slabs along the guides in '%s'", self.GuidesVar) |
|
|
|
local items = {} |
|
if self.SnapToVoxels then items[#items + 1] = "snap to voxels" end |
|
if self.StartGap > 0 then items[#items + 1] = string.format("gap %d", self.StartGap) end |
|
if self.Fill then items[#items + 1] = "fill entire length" end |
|
if not self.Fill then items[#items + 1] = string.format("repeat %d", self.Count) end |
|
if self.BetweenGap > 0 then items[#items] = items[#items] .. string.format(" with gap %d", self.BetweenGap) end |
|
if self.EndGap > 0 then items[#items + 1] = string.format("gap %d", self.EndGap) end |
|
if self.SkipDoors then items[#items + 1] = "skip doors" end |
|
if #items > 0 then |
|
ret = ret .. string.format("\n--> %s", table.concat(items, ", ")) |
|
end |
|
|
|
if next(self.Start) then ret = ret .. string.format("\n--> Start slabs 1 %s", PlaceObjectData.FormatList(self.Start)) end |
|
if next(self.Start2) then ret = ret .. string.format("\n--> Start slabs 2 %s", PlaceObjectData.FormatList(self.Start2)) end |
|
if next(self.Middle) then ret = ret .. string.format("\n--> Middle slabs %s", PlaceObjectData.FormatList(self.Middle)) end |
|
if next(self.End1) then ret = ret .. string.format("\n--> End slabs 1 %s", PlaceObjectData.FormatList(self.End1)) end |
|
if next(self.End2) then ret = ret .. string.format("\n--> End slabs 2 %s", PlaceObjectData.FormatList(self.End2)) end |
|
if self.AssignTo ~= "" then ret = ret .. string.format("\n--> Add to variable '%s'", self.AssignTo) end |
|
return ret |
|
end |
|
|
|
function LaySlabsAlongGuides.GetModifiedBBox(obj) |
|
if not obj:IsKindOf("Slab") then |
|
return obj:GetObjectBBox() |
|
end |
|
|
|
local bbox = GetEntityBBox(obj) |
|
bbox = box(bbox:min():SetX(0), bbox:max()) |
|
return obj:GetRelativeBox(bbox) |
|
end |
|
|
|
function LaySlabsAlongGuides.Place(rand, list, vertical, pt, normal, SkipDoors, SkipInterior, objs, rotate) |
|
local angle = CalcSignedAngleBetween2D(axis_x, normal) - (rotate and 90*60 or 0) |
|
local obj = PlaceObjectData.PlaceRandomObject(rand, list, pt, angle) |
|
local slab_size = 1 |
|
if obj:IsKindOf("SlabWallObject") then |
|
slab_size = vertical and obj.height or obj.width |
|
end |
|
|
|
if SkipDoors and not obj:IsKindOf("SlabWallObject") and find_intersecting_slab(obj, "SlabWallObject", 20 * guic) then |
|
obj:delete() |
|
elseif SkipInterior and EnumVolumes(LaySlabsAlongGuides.GetModifiedBBox(obj), function(vol) |
|
local pos = obj:GetPos() |
|
if (pos:x() == vol.box:minx() or pos:x() == vol.box:maxx()) and (pos:y() == vol.box:miny() or pos:y() == vol.box:maxy()) then |
|
return false |
|
end |
|
return not vol.none_wall_mat_does_not_affect_nbrs |
|
end) then |
|
obj:delete() |
|
else |
|
objs[#objs + 1] = obj |
|
end |
|
return slab_size |
|
end |
|
|
|
function LaySlabsAlongGuides:Exec(rand, GuidesVar, AssignTo, SnapToVoxels, SnapToVoxelEdge, SnapToNearestWall, StartGap, Fill, Count, BetweenGap, EndGap, SkipDoors, SkipInterior, Start, Start2, Middle, End1, End2, StartIsCorner, EndIsCorner, StartIsColumnBase, EndIsColumnTop, Start2FixedDist, End2FixedDist, StartIsSlab, EndIsSlab) |
|
local all_objs = {} |
|
if IsValid(GuidesVar) then GuidesVar = { GuidesVar } end |
|
for _, guide in ipairs(GuidesVar or empty_table) do |
|
local objs = {} |
|
local slab_size = const.SlabSizeX |
|
local pt1, pt2, normal = guide:GetPos1(), guide:GetPos2(), guide:GetNormal() |
|
if SnapToVoxels then |
|
if SnapToVoxelEdge then |
|
local offset = point(const.SlabSizeX, const.SlabSizeY, const.SlabSizeZ) / 2 |
|
local retrace = point(const.SlabSizeX, const.SlabSizeY, 0) / 2 |
|
pt1, pt2 = SnapToVoxel(pt1 + offset) - retrace, SnapToVoxel(pt2 + offset) - retrace |
|
else |
|
local offset = point(0, 0, const.SlabSizeZ / 2) + SetLen(normal, slab_size / 2) |
|
local along = SetLen(pt2 - pt1, slab_size) / 2 |
|
pt1, pt2 = SnapToVoxel(pt1 + offset + along) - along, SnapToVoxel(pt2 + offset - along) + along |
|
end |
|
elseif SnapToNearestWall then |
|
local pos = guide:GetPos():SetInvalidZ() |
|
local slab = MapFindNearest(pos, pos, const.SlabSizeX / 2, "WallSlab") |
|
if slab then |
|
normal = slab:GetRelativePoint(axis_x) - slab:GetPos() |
|
local vec = pos - slab:GetPos() |
|
local len = Dot2D(vec, normal) / normal:Len() |
|
local offs = SetLen(normal, len) |
|
pt1 = pt1 - offs |
|
pt2 = pt2 - offs |
|
end |
|
end |
|
|
|
local angle = GetAngle(pt1 - pt2, axis_z) / 60 |
|
local vertical = abs(angle) < 30 or abs(angle - 180) < 30 |
|
if vertical then |
|
slab_size = const.SlabSizeZ |
|
if pt1:z() > pt2:z() then |
|
pt1, pt2 = pt2, pt1 |
|
end |
|
elseif Cross(pt1 - pt2, normal):z() > 0 then |
|
pt1, pt2 = pt2, pt1 |
|
end |
|
|
|
if pt1 ~= pt2 then |
|
local forward = SetLen(pt2 - pt1, slab_size) |
|
if not vertical then |
|
pt1 = pt1 + forward / 2 |
|
pt2 = pt2 + forward / 2 |
|
end |
|
pt1 = pt1 + forward * StartGap |
|
pt2 = pt2 - forward * EndGap |
|
|
|
if next(Start) then |
|
if StartIsSlab then |
|
LaySlabsAlongGuides.Place(rand, Start, vertical, pt1 - forward, normal, false, SkipInterior, objs) |
|
elseif StartIsCorner then |
|
LaySlabsAlongGuides.Place(rand, Start, vertical, pt1 - forward / 2, normal, false, SkipInterior, objs) |
|
elseif StartIsColumnBase then |
|
LaySlabsAlongGuides.Place(rand, Start, vertical, pt1, normal, SkipDoors, SkipInterior, objs) |
|
else |
|
local size = LaySlabsAlongGuides.Place(rand, Start, vertical, pt1, normal, SkipDoors, SkipInterior, objs) |
|
pt1 = pt1 + forward * size |
|
end |
|
if #objs > 0 and Dot(pt2 - pt1, forward) < 0 then |
|
table.remove(objs):delete() |
|
end |
|
end |
|
if next(Start2) then |
|
if Start2FixedDist ~= 0 then |
|
LaySlabsAlongGuides.Place(rand, Start2, vertical, pt1 + SetLen(forward, Start2FixedDist), normal, SkipDoors, SkipInterior, objs) |
|
else |
|
local size = LaySlabsAlongGuides.Place(rand, Start2, vertical, pt1, normal, SkipDoors, SkipInterior, objs) |
|
pt1 = pt1 + forward * size |
|
end |
|
if #objs > 0 and Dot(pt2 - pt1, forward) < 0 then |
|
table.remove(objs):delete() |
|
end |
|
end |
|
|
|
|
|
local endobjs = {} |
|
local pt = pt1 |
|
if next(End1) then |
|
if EndIsSlab then |
|
LaySlabsAlongGuides.Place(rand, End1, vertical, pt, normal, false, SkipInterior, endobjs) |
|
elseif EndIsCorner then |
|
LaySlabsAlongGuides.Place(rand, End1, vertical, pt - forward / 2, normal, false, SkipInterior, endobjs, "rotate") |
|
elseif EndIsColumnTop then |
|
LaySlabsAlongGuides.Place(rand, End1, vertical, pt - forward, normal, SkipDoors, SkipInterior, endobjs) |
|
else |
|
local size = LaySlabsAlongGuides.Place(rand, End1, vertical, pt, normal, SkipDoors, SkipInterior, endobjs) |
|
pt = pt + forward * size |
|
end |
|
if #endobjs > 0 and Dot(pt2 - pt, forward) < 0 then |
|
table.remove(endobjs):delete() |
|
end |
|
end |
|
if next(End2) then |
|
if End2FixedDist ~= 0 then |
|
LaySlabsAlongGuides.Place(rand, End2, vertical, pt - SetLen(forward, End2FixedDist), normal, SkipDoors, SkipInterior, endobjs) |
|
else |
|
local size = LaySlabsAlongGuides.Place(rand, End2, vertical, pt, normal, SkipDoors, SkipInterior, endobjs) |
|
pt = pt + forward * size |
|
end |
|
if #endobjs > 0 and Dot(pt2 - pt, forward) < 0 then |
|
table.remove(endobjs):delete() |
|
end |
|
end |
|
|
|
if BetweenGap > 0 and (next(Start) or next(Start2)) then |
|
pt = pt + forward * BetweenGap |
|
pt1 = pt1 + forward * BetweenGap |
|
end |
|
local available = Dot(pt2 - pt, forward) < 0 and 0 or (pt2 - pt + forward / 2):Len() / forward:Len() |
|
local slabs = Fill and available or Max(available, Count) |
|
local orig_slabs = slabs |
|
if BetweenGap > 0 and (next(End1) or next(End2)) then |
|
slabs = slabs - BetweenGap |
|
orig_slabs = orig_slabs + BetweenGap |
|
end |
|
|
|
if next(Middle) and slabs > 0 then |
|
local size |
|
while slabs > 0 do |
|
size = LaySlabsAlongGuides.Place(rand, Middle, vertical, pt1, normal, SkipDoors, SkipInterior, objs) |
|
pt1 = pt1 + forward * (size + BetweenGap) |
|
slabs = slabs - (size + BetweenGap) |
|
end |
|
slabs = slabs + BetweenGap |
|
if #objs > 0 and slabs < 0 then |
|
table.remove(objs):delete() |
|
slabs = slabs + size + BetweenGap |
|
end |
|
else |
|
slabs = 0 |
|
end |
|
|
|
for _, obj in ipairs(endobjs) do |
|
if obj:IsKindOf("AlignedObj") then |
|
obj:AlignObj(obj:GetVisualPos() + forward * (orig_slabs - slabs)) |
|
else |
|
obj:SetPos(obj:GetVisualPos() + forward * (orig_slabs - slabs)) |
|
end |
|
end |
|
|
|
table.iappend(objs, endobjs) |
|
create_collection(objs) |
|
table.iappend(all_objs, objs) |
|
end |
|
end |
|
|
|
ComputeSlabVisibilityOfObjects(all_objs) |
|
XEditorUndo:BeginOp() |
|
Msg("EditorCallback", "EditorCallbackPlace", all_objs) |
|
XEditorUndo:EndOp(all_objs) |
|
return table.iappend(AssignTo or {}, all_objs) |
|
end |
|
|
|
|
|
DefineClass.PlaceObjectDataDecal = { |
|
__parents = { "PlaceObjectData" }, |
|
properties = { |
|
{ category = "Decal Adjustments", id = "FlipVertically", editor = "bool", default = false, }, |
|
{ category = "Decal Adjustments", id = "MoveDownPercent", editor = "number", default = 0, }, |
|
{ category = "Decal Adjustments", id = "ScaleAfterPlace", editor = "number", default = 100, }, |
|
} |
|
} |
|
|
|
DefineClass.LayDecalsAlongGuide = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ category = "Input/output", id = "GuidesVar", name = "Guides variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Input/output", id = "AssignTo", name = "Add to variable", editor = "combo", default = "", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Objects", id = "Decals", name = "Decal(s)", editor = "nested_list", class = "PlaceObjectDataDecal", default = false, }, |
|
{ category = "Objects", id = "FitSingle", name = "Fit single decal", editor = "bool", default = false, }, |
|
}, |
|
ExtraParams = { "rand" }, |
|
EditorName = "Lay decals along guides", |
|
EditorSubmenu = "Objects", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function LayDecalsAlongGuide:GetEditorView() |
|
local ret = string.format("Lay decals along the guides in '%s'", self.GuidesVar) |
|
if next(self.Decals) then ret = ret .. string.format("\n--> Decals %s", PlaceObjectData.FormatList(self.Decals)) end |
|
return ret |
|
end |
|
|
|
function LayDecalsAlongGuide:Exec(rand, GuidesVar, AssignTo, Decals, FitSingle) |
|
if not next(Decals) then return AssignTo end |
|
|
|
local all_objs = {} |
|
if IsValid(GuidesVar) then GuidesVar = { GuidesVar } end |
|
for _, guide in ipairs(GuidesVar or empty_table) do |
|
local objs = {} |
|
local pt, pt_end, normal = guide:GetPos1(), guide:GetPos2(), guide:GetNormal() |
|
local along = SetLen(pt_end - pt, 4096) |
|
|
|
while Dot(pt_end - pt, along) > 0 do |
|
local obj, data = PlaceObjectData.PlaceRandomObject(rand, Decals, pt) |
|
obj:SetAxisAngle(guide:GetAxis(), guide:GetAngle()) |
|
obj:Rotate(obj:GetRelativePoint(axis_y) - obj:GetVisualPos(), -90*60) |
|
|
|
local b = obj:GetEntityBBox() |
|
local pt1 = obj:GetRelativePoint(point(0, b:miny(), 0)) |
|
if FitSingle then |
|
local pt2 = obj:GetRelativePoint(point(0, b:maxy(), 0)) |
|
obj:SetScale(MulDivRound(obj:GetScale(), (pt_end - pt):Len(), (pt2 - pt1):Len())) |
|
end |
|
|
|
local y = Dot(pt1 - pt, along) > 0 and b:miny() or b:maxy() |
|
obj:SetPos(obj:GetRelativePoint(point(b:maxx(), y, 0))) |
|
pt = obj:GetRelativePoint(point(b:minx(), y, 0)) |
|
objs[#objs + 1] = obj |
|
|
|
if data.MoveDownPercent ~= 0 then |
|
obj:SetPos(obj:GetRelativePoint(point(MulDivRound(-b:sizex(), data.MoveDownPercent, 100), 0, 0))) |
|
end |
|
if data.FlipVertically then |
|
|
|
obj:Rotate(obj:GetRelativePoint(axis_y) - obj:GetPos(), 90 * 60) |
|
obj:Rotate(obj:GetRelativePoint(axis_y) - obj:GetPos(), 90 * 60) |
|
end |
|
if data.ScaleAfterPlace ~= 100 then |
|
obj:SetScale(MulDivRound(obj:GetScale(), data.ScaleAfterPlace, 100)) |
|
end |
|
|
|
if FitSingle then break end |
|
end |
|
|
|
create_collection(objs) |
|
table.iappend(all_objs, objs) |
|
end |
|
|
|
XEditorUndo:BeginOp() |
|
Msg("EditorCallback", "EditorCallbackPlace", all_objs) |
|
XEditorUndo:EndOp(all_objs) |
|
return table.iappend(AssignTo or {}, all_objs) |
|
end |
|
|
|
DefineClass.LayObjectsAlongGuides = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ category = "Input/output", id = "GuidesVar", name = "Guides variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Input/output", id = "AssignTo", name = "Add to variable", editor = "combo", default = "", items = PrgVarsCombo, variable = true, }, |
|
{ category = "Placement", id = "StartGap", name = "Start gap", editor = "number", scale = "m", default = 0, }, |
|
{ category = "Placement", id = "MinDistance", name = "Min distance", editor = "number", scale = "m", default = 0, }, |
|
{ category = "Placement", id = "AngleDeviation", name = "Angle deviation", editor = "number", scale = "deg", default = 0, }, |
|
{ category = "Placement", id = "EndGap", name = "End gap", editor = "number", scale = "m", default = 0, }, |
|
{ category = "Placement", id = "PointUp", name = "Point up", editor = "bool", default = false, }, |
|
{ category = "Objects", id = "Start", name = "Start object(s)", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
{ category = "Objects", id = "Middle1", name = "Middle object(s)", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
{ category = "Objects", id = "Middle2", name = "Middle object(s) 2", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
{ category = "Objects", id = "End", name = "End object(s)", editor = "nested_list", class = "PlaceObjectData", default = false, }, |
|
}, |
|
ExtraParams = { "rand" }, |
|
EditorName = "Lay objects along guides", |
|
EditorSubmenu = "Objects", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function LayObjectsAlongGuides:GetEditorView() |
|
local ret = string.format("Lay objects along the guides in '%s'", self.GuidesVar) |
|
|
|
local items = {} |
|
if self.StartGap > 0 then items[#items + 1] = string.format("gap %0.1f", self.StartGap / (guim * 1.0)) end |
|
if self.MinDistance > 0 then items[#items + 1] = string.format("distance %0.1f", self.MinDistance / (guim * 1.0)) end |
|
if self.AngleDeviation > 0 then items[#items + 1] = string.format("angle ±%d", self.AngleDeviation / 60) end |
|
if self.EndGap > 0 then items[#items + 1] = string.format("gap %0.1f", self.EndGap / (guim * 1.0)) end |
|
if self.PointUp then items[#items + 1] = "objects point up" end |
|
if #items > 0 then |
|
ret = ret .. string.format("\n--> %s", table.concat(items, ", ")) |
|
end |
|
|
|
if next(self.Start) then ret = ret .. string.format("\n--> Start objs %s", PlaceObjectData.FormatList(self.Start)) end |
|
if next(self.Middle1) then ret = ret .. string.format("\n--> Middle objs %s", PlaceObjectData.FormatList(self.Middle1)) end |
|
if next(self.Middle2) then ret = ret .. string.format("\n--> Middle objs(2) %s", PlaceObjectData.FormatList(self.Middle2)) end |
|
if next(self.End) then ret = ret .. string.format("\n--> End objs %s", PlaceObjectData.FormatList(self.End)) end |
|
if self.AssignTo ~= "" then ret = ret .. string.format("\n--> Add to variable '%s'", self.AssignTo) end |
|
return ret |
|
end |
|
|
|
function LayObjectsAlongGuides.AlignObj(obj, rand, pt, along, normal, AngleDeviation, PointUp) |
|
obj:SetPos(pt) |
|
obj:SetOrientation(PointUp and axis_z or normal, 0) |
|
|
|
if not PointUp or along:SetZ(0) ~= point30 then |
|
local edge = obj:GetRelativePoint(point(guim, 0, 0)) - pt |
|
rotate_to_match(obj, edge, PointUp and along:SetZ(0) or along) |
|
end |
|
obj:Rotate(obj:GetRelativePoint(axis_z) - pt, rand(2 * AngleDeviation + 1) - AngleDeviation) |
|
return obj |
|
end |
|
|
|
function LayObjectsAlongGuides.MaxEntityBBoxSize(list) |
|
|
|
local max_size = 0 |
|
for _, data in ipairs(list) do |
|
local obj = XEditorPlaceObject(data.EditorClass) |
|
max_size = Max(max_size, obj:GetEntityBBox():sizex()) |
|
obj:delete() |
|
end |
|
return max_size |
|
end |
|
|
|
function LayObjectsAlongGuides:Exec(rand, GuidesVar, AssignTo, StartGap, MinDistance, AngleDeviation, EndGap, PointUp, Start, Middle1, Middle2, End) |
|
local all = {} |
|
if IsValid(GuidesVar) then GuidesVar = { GuidesVar } end |
|
for _, guide in ipairs(GuidesVar or empty_table) do |
|
local pt, pt_end, normal = guide:GetPos1(), guide:GetPos2(), guide:GetNormal() |
|
local along = pt_end - pt |
|
pt = pt + SetLen(along, StartGap) |
|
pt_end = pt_end - SetLen(along, EndGap) |
|
|
|
local first_obj |
|
if next(Start) then |
|
first_obj = PlaceObjectData.PlaceRandomObject(rand, Start, pt) |
|
LayObjectsAlongGuides.AlignObj(first_obj, rand, pt, along, normal, 0, PointUp) |
|
pt = pt + SetLen(along, first_obj:GetEntityBBox():maxx()) |
|
end |
|
local last_obj |
|
if next(End) then |
|
last_obj = PlaceObjectData.PlaceRandomObject(rand, End, pt_end) |
|
LayObjectsAlongGuides.AlignObj(last_obj, rand, pt_end, along, normal, 0, PointUp) |
|
pt_end = pt_end + SetLen(along, last_obj:GetEntityBBox():minx()) |
|
end |
|
|
|
local objs = {} |
|
if next(Middle1) then |
|
local orig_len = (pt_end - pt):Len() - MinDistance |
|
local len = orig_len |
|
if next(Middle2) then |
|
|
|
local obj = PlaceObjectData.PlaceRandomObject(rand, Middle1, pt_end) |
|
len = len - (obj:GetEntityBBox():sizex() + MinDistance) |
|
if len > 0 then |
|
objs[#objs + 1] = obj |
|
else |
|
obj:delete() |
|
end |
|
|
|
|
|
while true do |
|
local obj2 = PlaceObjectData.PlaceRandomObject(rand, Middle2, pt) |
|
local obj1 = PlaceObjectData.PlaceRandomObject(rand, Middle1, pt) |
|
local newlen = len - (obj1:GetEntityBBox():sizex() + obj2:GetEntityBBox():sizex() + MinDistance * 2) |
|
if newlen > 0 then |
|
objs[#objs + 1] = obj2 |
|
objs[#objs + 1] = obj1 |
|
len = newlen |
|
else |
|
obj1:delete() |
|
obj2:delete() |
|
break |
|
end |
|
end |
|
else |
|
while true do |
|
local obj = PlaceObjectData.PlaceRandomObject(rand, Middle1, pt) |
|
local newlen = len - (obj:GetEntityBBox():sizex() + MinDistance) |
|
if newlen > 0 then |
|
objs[#objs + 1] = obj |
|
len = newlen |
|
else |
|
obj:delete() |
|
break |
|
end |
|
end |
|
end |
|
|
|
|
|
local total_gaps = #objs + 1 |
|
for i = 1, #objs do |
|
local obj = objs[i] |
|
local gap = MulDivTrunc(len, i, total_gaps) - MulDivTrunc(len, i - 1, total_gaps) |
|
pt = pt + SetLen(along, -obj:GetEntityBBox():minx() + MinDistance + gap) |
|
LayObjectsAlongGuides.AlignObj(obj, rand, pt, along, normal, AngleDeviation, PointUp) |
|
pt = pt + SetLen(along, obj:GetEntityBBox():maxx()) |
|
end |
|
end |
|
|
|
if first_obj then table.insert(objs, 1, first_obj) end |
|
if last_obj then table.insert(objs, last_obj) end |
|
create_collection(objs) |
|
table.iappend(all, objs) |
|
end |
|
|
|
XEditorUndo:BeginOp() |
|
Msg("EditorCallback", "EditorCallbackPlace", all) |
|
XEditorUndo:EndOp(all) |
|
return table.iappend(AssignTo or {}, all) |
|
end |
|
|
|
|
|
|
|
|
|
DefineClass.SelectInEditor = { |
|
__parents = { "PrgExec" }, |
|
properties = { |
|
{ category = "Input/output", id = "ObjectsVar", name = "Objects variable", editor = "choice", default = "selected", items = PrgVarsCombo, variable = true, }, |
|
{ id = "SelectInEditor", name = "Select in editor", editor = "bool", default = true, }, |
|
{ id = "CreateCollection", name = "Create collection", editor = "bool", default = true, }, |
|
}, |
|
EditorName = "Select in editor", |
|
EditorView = Untranslated("Select '<ObjectsVar>' in the editor"), |
|
EditorSubmenu = "Objects", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function SelectInEditor:GetEditorView() |
|
if self.SelectInEditor and self.CreateCollection then |
|
return Untranslated("Select '<ObjectsVar>' in the editor, create collection") |
|
elseif self.SelectInEditor then |
|
return Untranslated("Select '<ObjectsVar>' in the editor") |
|
elseif self.CreateCollection then |
|
return Untranslated("Create collection from objects in '<ObjectsVar>'") |
|
end |
|
return "SelectInEditor - please check at least one operation!" |
|
end |
|
|
|
function SelectInEditor:Exec(ObjectsVar, SelectInEditor, CreateCollection) |
|
XEditorUndo:BeginOp{ objects = ObjectsVar } |
|
if CreateCollection then |
|
create_collection(ObjectsVar) |
|
end |
|
if SelectInEditor then |
|
editor.SetSel(ObjectsVar) |
|
end |
|
XEditorUndo:EndOp(ObjectsVar) |
|
end |
|
|
|
DefineClass.ReduceSpaceOut = { |
|
__parents = { "PrgFilterObjs" }, |
|
properties = { |
|
{ id = "MinDist", editor = "number", default = 2 * guim, min = guim, max = 25 * guim, scale = "m", slider = true, step = guim / 10 }, |
|
}, |
|
ExtraParams = { "rand" }, |
|
EditorName = "Reduce objects (space out)", |
|
StatementTag = "ExtrasGen", |
|
} |
|
|
|
function ReduceSpaceOut:GetEditorView() |
|
return string.format("Reduce objects in '%s'\n--> space them out %0.1fm or more", self.AssignTo, self.MinDist * 1.0 / guim) |
|
end |
|
|
|
function ReduceSpaceOut:Exec(rand, objs, MinDist) |
|
local ret = {} |
|
local obstacles = table.copy(objs) |
|
|
|
while #obstacles > 0 do |
|
local obj = obstacles[rand(#obstacles) + 1] |
|
local pos = obj:GetPos() |
|
for i = #obstacles, 1, -1 do |
|
if obstacles[i]:GetDist(pos) <= MinDist then |
|
table.remove(obstacles, i) |
|
end |
|
end |
|
ret[#ret + 1] = obj |
|
end |
|
return ret |
|
end |
|
|