|
DefineClass.XPlaceMultipleObjectsToolBase = { |
|
__parents = { "XEditorBrushTool" }, |
|
properties = { |
|
{ id = "Distance", editor = "number", default = 5 * guim, scale = "m", min = guim , max = 100 * guim, step = guim / 10, |
|
slider = true, persisted_setting = true, auto_select_all = true, sort_order = -1, }, |
|
{ id = "MinDistance", name = "Min distance", editor = "number", default = 0, scale = "m", min = 0, max = function(self) return self:GetDistance() end, step = guim / 10, |
|
slider = true, persisted_setting = true, auto_select_all = true, sort_order = -1, }, |
|
}, |
|
|
|
deleted_objects = false, |
|
new_objects = false, |
|
new_positions = false, |
|
box_changed = false, |
|
init_terrain_type = false, |
|
terrain_normal = false, |
|
distance_visualization = false, |
|
} |
|
|
|
function XPlaceMultipleObjectsToolBase:Init() |
|
self.distance_visualization = Mesh:new() |
|
self.distance_visualization:SetMeshFlags(const.mfWorldSpace) |
|
self.distance_visualization:SetShader(ProceduralMeshShaders.mesh_linelist) |
|
self.distance_visualization:SetPos(point(0, 0)) |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:Done() |
|
self.distance_visualization:delete() |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:OnEditorSetProperty(prop_id, old_value, ged) |
|
if prop_id == "Distance" then |
|
self:SetMinDistance(Min(self:GetMinDistance(), self:GetDistance())) |
|
end |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:UpdateCursor() |
|
local v_pstr = self:CreateCircleCursor() |
|
|
|
local strength = self:GetCursorHeight() |
|
if strength then |
|
v_pstr:AppendVertex(point(0, 0, 0)) |
|
v_pstr:AppendVertex(point(0, 0, strength)) |
|
end |
|
|
|
self.cursor_mesh:SetMeshFlags(self.cursor_default_flags + self:GetCursorExtraFlags()) |
|
local pt = self:GetWorldMousePos() |
|
local radius = self:GetCursorRadius() |
|
self.box_changed = box(pt:x() - radius, pt:y() - radius, pt:x() + radius, pt:y() + radius) |
|
self.box_changed = terrain.ClampBox(self.box_changed) |
|
local distance = self:GetDistance() |
|
local bxDistanceGrid = self.box_changed / distance |
|
local vpstr = pstr("") |
|
for i = bxDistanceGrid:miny(), bxDistanceGrid:maxy() do |
|
for j = bxDistanceGrid:minx(), bxDistanceGrid:maxx() do |
|
local ptDistance = point(j, i) |
|
local ptReal = ptDistance * distance |
|
local color = self:GetCursorColor() |
|
if ptReal:Dist(pt) <= self:GetCursorRadius() then |
|
ptReal = ptReal:SetZ(terrain.GetHeight(ptReal)) |
|
vpstr:AppendVertex(ptReal + point(-200, -200, 0), color) |
|
vpstr:AppendVertex(ptReal + point(200, 200, 0)) |
|
vpstr:AppendVertex(ptReal + point(-200, 200, 0), color) |
|
vpstr:AppendVertex(ptReal + point(200, -200, 0)) |
|
end |
|
end |
|
end |
|
self.distance_visualization:SetMesh(vpstr) |
|
self.cursor_mesh:SetMesh(v_pstr) |
|
self.cursor_mesh:SetPos(GetTerrainCursor()) |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:GetCursorRadius() |
|
local radius = self:GetSize() / 2 |
|
return radius, radius |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:MarkObjectsForDelete() |
|
local radius = self:GetCursorRadius() |
|
MapForEach(GetTerrainCursor(), radius, function(o) |
|
local classes = self:GetClassesForDelete() or {} |
|
if not self.deleted_objects[o] and not self.new_objects[o] and XEditorFilters:IsVisible(o) and IsKindOfClasses(o, classes) |
|
and (not self.init_terrain_type or self.init_terrain_type[terrain.GetTerrainType(o:GetPos())])then |
|
self.deleted_objects[o] = true |
|
o:ClearEnumFlags(const.efVisible) |
|
end |
|
end) |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:PlaceObjects(pt) |
|
local newobjs = {} |
|
local distance = self:GetDistance() |
|
local min_distance = self:GetMinDistance() |
|
local bxDistanceGrid = self.box_changed / distance |
|
for i = bxDistanceGrid:miny(), bxDistanceGrid:maxy() do |
|
for j = bxDistanceGrid:minx(), bxDistanceGrid:maxx() do |
|
local ptDistance = point(j, i) |
|
local ptReal = ptDistance * distance |
|
local offset = (distance - min_distance) / 2 |
|
local randX = -offset + AsyncRand(2 * offset) |
|
local randY = -offset + AsyncRand(2 * offset) |
|
ptReal = ptReal + point(randX, randY) |
|
local classes = self:GetClassesForPlace(ptReal) |
|
local class = classes and classes[AsyncRand(#classes) + 1] |
|
local terrainNormal, scale, scaleDeviation, angle, colorMin, colorMax = self:GetParams(ptReal) |
|
if ptReal:InBox2D(GetMapBox()) and ptReal:Dist(pt) <= self:GetCursorRadius() and (not self.new_positions[j] or not self.new_positions[j][i]) and class and scale |
|
and (not self.init_terrain_type or self.init_terrain_type[terrain.GetTerrainType(ptReal)]) then |
|
local axis = terrainNormal and terrain.GetTerrainNormal(ptReal) or axis_z |
|
local obj = XEditorPlaceObject(class) |
|
scaleDeviation = scaleDeviation == 0 and 0 or -scaleDeviation + AsyncRand(2 * scaleDeviation) |
|
scale = Clamp(MulDivRound(scale, 100 + scaleDeviation, 100), 10, 250) |
|
angle = angle * 60 |
|
angle = AsyncRand(-angle, angle) |
|
local minR, minG, minB = GetRGB(colorMin) |
|
local maxR, maxG, maxB = GetRGB(colorMax) |
|
minR, maxR = MinMax(minR, maxR) |
|
minG, maxG = MinMax(minG, maxG) |
|
minB, maxB = MinMax(minB, maxB) |
|
local color = RGB(AsyncRand(minR, maxR), AsyncRand(minG, maxG), AsyncRand(minB, maxB)) |
|
obj:SetPos(ptReal) |
|
obj:SetInvalidZ() |
|
obj:SetScale(scale) |
|
obj:SetOrientation(axis, angle) |
|
obj:SetColorModifier(color) |
|
obj:RestoreHierarchyEnumFlags() |
|
obj:SetHierarchyEnumFlags(const.efVisible) |
|
obj:SetGameFlags(const.gofPermanent) |
|
obj:SetCollection(Collections[editor.GetLockedCollectionIdx()]) |
|
self.new_positions[j] = self.new_positions[j] or {} |
|
self.new_positions[j][i] = true |
|
self.new_objects[obj] = true |
|
newobjs[#newobjs + 1] = obj |
|
end |
|
end |
|
end |
|
Msg("EditorCallback", "EditorCallbackPlace", newobjs) |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:StartDraw(pt) |
|
SuspendPassEdits("XEditorPlaceMultipleObjects") |
|
self.deleted_objects = {} |
|
self.new_objects = {} |
|
self.new_positions = {} |
|
if terminal.IsKeyPressed(const.vkControl) then self.init_terrain_type = {[terrain.GetTerrainType(pt)] = true} end |
|
if terminal.IsKeyPressed(const.vkAlt) then self.terrain_normal = true end |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:Draw(pt1, pt2) |
|
self:MarkObjectsForDelete() |
|
self:PlaceObjects(pt2) |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:EndDraw(pt) |
|
local objs = table.validate(table.keys(self.deleted_objects)) |
|
for _, obj in ipairs(objs) do obj:SetEnumFlags(const.efVisible) end |
|
XEditorUndo:BeginOp({ objects = objs, name = "Placed multiple objects" }) |
|
Msg("EditorCallback", "EditorCallbackDelete", objs) |
|
for _, obj in ipairs(objs) do obj:delete() end |
|
XEditorUndo:EndOp(table.keys(self.new_objects)) |
|
|
|
ResumePassEdits("XEditorPlaceMultipleObjects", true) |
|
self.deleted_objects = false |
|
self.new_objects = false |
|
self.new_positions = false |
|
self.init_terrain_type = false |
|
self.terrain_normal = false |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:GetParams() |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:GetClassesForDelete() |
|
end |
|
|
|
function XPlaceMultipleObjectsToolBase:GetClassesForPlace(pt) |
|
end |