File size: 6,930 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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() -- will rebuild surfaces if required
				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