File size: 6,525 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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
if not const.SlabSizeX then return end

local offset = point(const.SlabSizeX, const.SlabSizeY, const.SlabSizeZ) / 2
local retrace = point(const.SlabSizeX, const.SlabSizeY, 0) / 2
local function snap_to_voxel_grid(pt)
	return SnapToVoxel(pt + offset) - retrace
end

DefineClass.XCreateGuidesTool = {
	__parents = { "XEditorTool" },
	properties = {
		persisted_setting = true,
		{ id = "Snapping", editor = "bool", default = true, },
		{ id = "Vertical", editor = "bool", default = true, },
		{ id = "Prg", name = "Apply Prg", editor = "choice", default = "", 
			items = function(self)
				return PresetsCombo("ExtrasGen", nil, "", function(prg)
					return prg.RequiresClass == "EditorLineGuide" and prg.RequiresGuideType == (self:GetVertical() and "Vertical" or "Horizontal")
				end)
			end,
		},
	},
	
	ToolTitle = "Create Guides",
	Description = {
		"(drag to place guide or guides)\n" ..
		"(<style GedHighlight>hold Ctrl</style> to disable snapping)",
	},
	UsesCodeRenderables = true,
	
	start_pos = false,
	guides = false,
	prg_applied = false,
	old_guides_hash = false,
}

function XCreateGuidesTool:OnEditorSetProperty(prop_id, old_value, ged)
	if prop_id == "Vertical" then
		self:SetPrg("")
	end
end

function XCreateGuidesTool:Done()
	if self.start_pos then -- tool destroyed while dragging
		ResumePassEdits("XCreateGuidesTool")
		self:CreateGuides(0)
	end
end

function XCreateGuidesTool:CreateGuides(count)
	self.guides = self.guides or {}
	
	local guides = self.guides
	if count == #guides then return end
	
	for i = 1, Max(count, #guides) do
		if i <= count and not guides[i] then
			guides[i] = EditorLineGuide:new()
			guides[i]:SetOpacity(self:GetPrg() == "" and 100 or 0)
		elseif i > count and guides[i] then
			DoneObject(guides[i])
			guides[i] = nil
		end
	end
end

function XCreateGuidesTool:UpdateGuides(pt_min, pt_max)
	local x1, y1 = pt_min:xy()
	local x2, y2 = pt_max:xy()
	local z = Max(
		terrain.GetHeight(point(x1, y1)),
		terrain.GetHeight(point(x1, y2)),
		terrain.GetHeight(point(x2, y1)),
		terrain.GetHeight(point(x2, y2))
	)
	if x1 == x2 then
		local count = y1 == y2 and 0 or 1
		self:CreateGuides(count)
		if count == 0 then return end
		
		local pos, lookat = GetCamera()
		local dot = Dot(SetLen((lookat - pos):SetZ(0), 4096), axis_x)
		self.guides[1]:Set(point(x1, y1, z), point(x1, y2, z), dot > 0 and -axis_x or axis_x)
	elseif y1 == y2 then
		self:CreateGuides(1)
		
		local pos, lookat = GetCamera()
		local dot = Dot(SetLen((lookat - pos):SetZ(0), 4096), axis_y)
		self.guides[1]:Set(point(x1, y1, z), point(x2, y1, z), dot > 0 and -axis_y or axis_y)
	else
		self:CreateGuides(4)
		self.guides[1]:Set(point(x1, y1, z), point(x1, y2, z), -axis_x)
		self.guides[2]:Set(point(x2, y1, z), point(x2, y2, z),  axis_x)
		self.guides[3]:Set(point(x1, y1, z), point(x2, y1, z), -axis_y)
		self.guides[4]:Set(point(x1, y2, z), point(x2, y2, z),  axis_y)
	end
end

function XCreateGuidesTool:GetGuidesHash()
	local hash = 42
	for _, guide in ipairs(self.guides or empty_table) do
		hash = xxhash(hash, guide:GetPos1(), guide:GetPos2(), guide:GetNormal())
	end
	return hash
end	

function XCreateGuidesTool:ApplyPrg()
	local hash = self:GetGuidesHash()
	if self:GetPrg() ~= "" and hash ~= self.old_guides_hash and self.guides and #self.guides ~= 0 then
		if self.prg_applied then
			XEditorUndo:UndoRedo("undo")
		end
		
		-- create copies of the guides and apple the Prg to them (some Prgs change the guides)
		local guides = {}
		for _, guide in ipairs(self.guides) do
			local g = EditorLineGuide:new()
			g:Set(guide:GetPos1(), guide:GetPos2(), guide:GetNormal())
			guides[#guides + 1] = g
		end
		GenExtras(self:GetPrg(), guides)
		for _, guide in ipairs(guides) do
			DoneObject(guide)
		end
		
		self.old_guides_hash = hash
		self.prg_applied = true
	end
end

function XCreateGuidesTool:OnMouseButtonDown(pt, button)
	if button == "L" then
		self.start_pos = GetTerrainCursor()
		self.snapping = self:GetSnapping() and not terminal.IsKeyPressed(const.vkControl)
		if self.snapping then
			self.start_pos = snap_to_voxel_grid(self.start_pos)
		end
		self.desktop:SetMouseCapture(self)
		SuspendPassEdits("XCreateGuidesTool")
		return "break"
	end
	return XEditorTool.OnMouseButtonDown(self, pt, button)
end

local function MinMaxPtXY(f, p1, p2)
	return point(f(p1:x(), p2:x()), f(p1:y(), p2:y()))
end

function XCreateGuidesTool:OnMousePos(pt, button)
	local start_pos = self.start_pos
	if start_pos then
		if self:GetVertical() then
			local eye, lookat = GetCamera()
			local cursor = ScreenToGame(pt)
			
			-- vertical plane parallel to screen
			local pt1, pt2, pt3 = start_pos, start_pos + axis_z, start_pos + SetLen(Cross(lookat - eye, axis_z), 4096)
			local intersection = IntersectRayPlane(eye, cursor, pt1, pt2, pt3)
			intersection = ProjectPointOnLine(pt1, pt2, intersection)
			intersection = self.snapping and snap_to_voxel_grid(intersection) or intersection
			if start_pos ~= intersection then
				local angle = CalcSignedAngleBetween2D(axis_x, eye - lookat)
				local axis = Rotate(axis_x, CardinalDirection(angle))
				if start_pos:Dist(intersection) > guim / 2 then
					self:CreateGuides(1)
					self.guides[1]:Set(start_pos, intersection, axis)
				end
			end
			self:ApplyPrg()
			return "break"
		end
		
		local pt_new = GetTerrainCursor()
		if self.snapping then
			pt_new = snap_to_voxel_grid(pt_new)
		else
			if abs(pt_new:x() - start_pos:x()) < guim / 2 then pt_new = pt_new:SetX(start_pos:x()) end
			if abs(pt_new:y() - start_pos:y()) < guim / 2 then pt_new = pt_new:SetY(start_pos:y()) end
		end
		local pt_min = MinMaxPtXY(Min, pt_new, start_pos)
		local pt_max = MinMaxPtXY(Max, pt_new, start_pos)
		self:UpdateGuides(pt_min, pt_max)
		self:ApplyPrg()
		return "break"
	end
	return XEditorTool.OnMousePos(self, pt, button)
end

function XCreateGuidesTool:OnMouseButtonUp(pt, button)
	local start_pos = self.start_pos
	if start_pos then
		if self.prg_applied then
			self:CreateGuides(0)
		elseif self.guides and #self.guides > 1 then
			XEditorUndo:BeginOp{ name = "Created guides" }
			local collection = Collection.Create()
			for _, obj in ipairs(self.guides) do
				obj:SetCollection(collection)
			end
			editor.ChangeSelWithUndoRedo(self.guides)
			XEditorUndo:EndOp(table.iappend(self.guides, { collection }))
		end
		
		self.desktop:SetMouseCapture()
		self.start_pos = nil
		self.prg_applied = nil
		self.guides = nil
		ResumePassEdits("XCreateGuidesTool")
		return "break"
	end
	return XEditorTool.OnMouseButtonUp(self, pt, button)
end