File size: 14,763 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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
local supported_fmt = {[".tga"] = true, [".raw"] = true}
local thumb_fmt = {[".tga"] = true, [".png"] = true }
local textures_folders = {"svnAssets/Source/Editor/ZBrush"}
local thumbs_folders = {"svnAssets/Source/Editor/ZBrushThumbs"}
local function store_as_by_category(self, prop_meta) return prop_meta.id .. "_for_" .. self:GetCategory() end
DefineClass.XZBrush = {
__parents = { "XEditorTool" },
properties = {
persisted_setting = true,
store_as = function(self, prop_meta) -- store settings per texture
if prop_meta.id == "BrushPattern" then
return prop_meta.id
else
return prop_meta.id .. "_for_" .. self:GetBrushPattern()
end
end,
{ id = "BrushHeightChange", name = "Height change", editor = "number", default = 500*guim, min = -1000*guim, max = 1000*guim, scale = "m", slider = true, help = "Height change corresponding to the texture levels", buttons = {{name = "Invert", func = "ActionHeightChangeInvert"}} },
{ id = "BrushZeroLevel", name = "Texture zero level", editor = "number", default = -1, min = -1, max = 255, slider = true, help = "The grayscale level corresponding to zero height. If negative, the top-left corner value would be used." },
{ id = "BrushDistortAmp", name = "Distort amplitude", editor = "number", default = 10, min = 1, max = 30, slider = true },
{ id = "BrushDistortFreq", name = "Distort frequency", editor = "number", default = 1, min = 1, max = 10, slider = true },
{ id = "BrushMode", name = "Mode", editor = "text_picker", default = "Add", items = { "Add", "Max", "Min" }, horizontal = true, store_as = false, },
{ id = "ClampMin", name = "Min <style GedHighlight>(Ctrl-click)</style>", editor = "number", scale = "m", default = 0 },
{ id = "ClampMax", name = "Max <style GedHighlight>(Shift-click)</style>", editor = "number", scale = "m", default = 0 },
{ id = "BrushPattern", name = "Pattern", editor = "texture_picker", default = "", thumb_size = 100, items = function(self) return self:GetZBrushTexturesList() end, small_font = true },
{ id = "TerrainR", name = "Terrain red", editor = "choice", default = "", items = GetTerrainNamesCombo, no_edit = function(self) return not self.pattern_terrains_file end, },
{ id = "TerrainG", name = "Terrain green", editor = "choice", default = "", items = GetTerrainNamesCombo, no_edit = function(self) return not self.pattern_terrains_file end, },
{ id = "TerrainB", name = "Terrain blue", editor = "choice", default = "", items = GetTerrainNamesCombo, no_edit = function(self) return not self.pattern_terrains_file end, },
{ id = "_", editor = "buttons", buttons = {{name = "See Texture Locations", func = "OpenTextureLocationHelp"}}, default = false },
},
ToolSection = "Height",
ToolTitle = "Z Brush",
Description = {
"Select pattern and drag to place and size it.",
"<style GedHighlight>hold Ctrl</style> - Move <style GedHighlight>hold Shift</style> - Rotate \n<style GedHighlight>hold Alt</style> - Height <style GedHighlight>hold Space</style> - Distort"
},
ActionSortKey = "15",
ActionIcon = "CommonAssets/UI/Editor/Tools/Zbrush.tga",
ActionShortcut = "Ctrl-H",
pattern_grid = false,
pattern_raw = false,
pattern_terrains_file = false,
height_change = false,
-- bools
distorting = false,
-- resizing
z_resize_start = false,
resize_start = false,
last_resize_delta = false,
-- angle of brush rotation in minutes
last_rotation_delta = false,
angle_start = false,
angle = false,
distort_grid_x = false,
distort_grid_y = false,
distorting_start = false,
distort_amp_xy = false,
distort_distance = 0,
initial_point_z = false,
center_point = false,
current_point = false,
box_radius = 0,
box_size = 0,
old_box = false,
cursor_start_pos = false, -- used with rotation (Shift)
is_editing = false,
}
function XZBrush:Init()
self:InitDistort()
self:InitBrushPattern()
XShortcutsTarget:SetStatusTextRight("ZBrush Editor")
end
function XZBrush:Done()
self:CancelOperation()
if self.pattern_grid then self.pattern_grid:free() end
if self.distort_grid_x then self.distort_grid_x:free() end
if self.distort_grid_y then self.distort_grid_y:free() end
editor.ClearOriginalHeightGrid()
end
function XZBrush:InitBrushPattern()
local brush_pattern = self:GetBrushPattern()
local had_terrains = not not self.pattern_terrains_file
if brush_pattern then
local dir, name, ext = SplitPath(brush_pattern)
XShortcutsTarget:SetStatusTextRight(name)
if self.pattern_grid then
self.pattern_grid:free()
end
self.pattern_raw = string.find(brush_pattern, ".raw") and true or false
self.pattern_grid = ImageToGrids(brush_pattern, self.pattern_raw)
self.pattern_terrains_file = dir .. name .. "_Mask.png"
self.pattern_terrains_file = io.exists(self.pattern_terrains_file) and self.pattern_terrains_file
end
if had_terrains ~= not not self.pattern_terrains_file then
ObjModified(self)
end
end
function XZBrush:InitDistort()
if self.distort_grid_x then self.distort_grid_x:free() end
if self.distort_grid_y then self.distort_grid_y:free() end
local dist_size = editor.ZBrushDistortSize
self.distort_grid_x = NewComputeGrid(dist_size, dist_size, "F")
self.distort_grid_y = NewComputeGrid(dist_size, dist_size, "F")
local seed = AsyncRand()
local noise = PerlinNoise:new()
noise:SetMainOctave(1 + MulDivRound( editor.ZBrushParamsCount - 1, self:GetBrushDistortFreq()* 100 , 1024))
noise:GetNoise(seed, self.distort_grid_x, self.distort_grid_y)
GridNormalize(self.distort_grid_x, 0, 1)
GridNormalize(self.distort_grid_y, 0, 1)
self.distort_amp_xy = point(0, 0)
self.distort_distance = 0
end
function XZBrush:OnEditorSetProperty(prop_id, old_value, ged)
local brush_pattern = false
if prop_id == "BrushPattern" then
self:InitBrushPattern()
elseif prop_id == "BrushDistortFreq" then
self:InitDistort()
end
end
function XZBrush:ActionHeightChangeInvert()
self:SetBrushHeightChange(-self:GetBrushHeightChange())
self:OnEditorSetProperty("SetBrushHeightChange")
ObjModified(self)
end
function XZBrush:CalculateResizeDelta()
if not self.resize_start then
return self.last_resize_delta
end
if self.last_resize_delta ~= point(0, 0, 0) then
local dCCP = self.current_point:Dist2D(self.center_point)
local dCLP = self.resize_start:Dist2D(self.center_point)
return MulDivRound(self.last_resize_delta, dCCP, dCLP)
else
return self.current_point - self.resize_start
end
end
function XZBrush:UpdateParameters(screen_point)
local isRotating = false
local isScalingZ = false
local isMoving = false
if terminal.IsKeyPressed(const.vkAlt) then
isScalingZ = true
SetMouseDeltaMode(true)
self.height_change = self.height_change - MulDivRound(GetMouseDelta():y(), self:GetBrushHeightChange(), 100)
else
SetMouseDeltaMode(false)
end
local isDistorting = false
if terminal.IsKeyPressed(const.vkSpace) then
isDistorting = true
if not self.distorting then
self.distorting_start = screen_point
end
self.distort_distance = self.distorting_start:Dist2D(screen_point)
self.distort_amp_xy = self:GetBrushDistortAmp() * (self.distorting_start - screen_point)
end
self.distorting = isDistorting
local ptDelta = screen_point - self.cursor_start_pos
if terminal.IsKeyPressed(const.vkShift) then
local absDiff = Max(abs(ptDelta:x()), abs(ptDelta:y()))
if absDiff > 0 then
self.angle = atan(ptDelta:y(), ptDelta:x())
if not self.angle_start then
self.angle_start = self.angle
end
end
isRotating = true
else
if self.angle_start then
self.last_rotation_delta = self.last_rotation_delta + (self.angle - self.angle_start)
self.angle_start = false
end
end
local mouse_world_pos = GetTerrainCursor()
if terminal.IsKeyPressed(const.vkControl) then
self.center_point = self.center_point + mouse_world_pos - self.current_point
isMoving = true
end
self.current_point = mouse_world_pos
if not isScalingZ and not isDistorting and not isRotating and not isMoving then
-- if not all that, then we do default action - resize
if not self.resize_start then
self.resize_start = self.current_point
end
else
if self.resize_start then
self.last_resize_delta = self:CalculateResizeDelta()
self.resize_start = false
end
end
end
function XZBrush:OnMouseButtonDown(screen_point, button)
if button == "R" and self.is_editing then
self:CancelOperation()
return "break"
end
if button == "L" then
if terminal.IsKeyPressed(const.vkControl) then
self:SetClampMin(GetTerrainCursor():z())
ObjModified(self)
return "break"
end
if terminal.IsKeyPressed(const.vkShift) then
self:SetClampMax(GetTerrainCursor():z())
ObjModified(self)
return "break"
end
XEditorUndo:BeginOp{ height = true, terrain_type = not not self.pattern_terrains_file, name = "Z Brush" }
editor.StoreOriginalHeightGrid(true)
self.is_editing = true
self.cursor_start_pos = screen_point
local game_pt = GetTerrainCursor()
self.center_point = game_pt
self.current_point = game_pt
self.resize_start = game_pt
self.last_resize_delta = point30
self.initial_point_z = game_pt:z()
local w, h = terrain.HeightMapSize()
self.height_change = self:GetBrushHeightChange() / const.TerrainHeightScale
self.last_rotation_delta = 0
self.desktop:SetMouseCapture(self)
return "break"
end
return XEditorTool.OnMouseButtonDown(self, screen_point, button)
end
function XZBrush:OnMouseButtonUp(screen_point, button)
if self.is_editing then
self:UpdateParameters(screen_point)
local bbox = editor.GetSegmentBoundingBox(self.center_point, self.center_point, self.box_radius, true)
Msg("EditorHeightChanged", true, bbox)
if self.pattern_terrains_file then
self:ApplyTerrainTextures(self.pattern_terrains_file)
Msg("EditorTerrainTypeChanged", bbox)
end
XEditorUndo:EndOp()
self.is_editing = false
self.center_point = false
self.current_point = false
self.scalingZ = false
self.distorting = false
self.angle_start = false
self.last_rotation_delta = 0
self.distort_amp_xy = point(0, 0)
self.distort_distance = 0
local dir, name, ext = SplitPath(self:GetBrushPattern())
XShortcutsTarget:SetStatusTextRight(name or "ZBrush Editor")
SetMouseDeltaMode(false)
self.desktop:SetMouseCapture()
UnforceHideMouseCursor("XEditorBrushTool")
return "break"
end
return XEditorTool.OnMouseButtonUp(self, screen_point, button)
end
function XZBrush:OnMousePos(screen_point, button)
if self.is_editing and self.pattern_grid then
if terminal.IsKeyPressed(const.vkEsc) then
self:CancelOperation()
return "break"
end
self:UpdateParameters(screen_point)
local angleDelta = self.last_rotation_delta + (self.angle_start and (self.angle - self.angle_start) or 0)
local sin, cos = sincos(angleDelta)
local ptDelta = self:CalculateResizeDelta()
local box_size = Max(abs(ptDelta:x()), abs(ptDelta:y()))
self.box_radius = box_size > 0 and MulDivRound(box_size, abs(sin) + abs(cos), 4096) or const.HeightTileSize / 2
local bBox = editor.GetSegmentBoundingBox(self.center_point, self.center_point, self.box_radius, true)
local extended_box = AddRects(self.old_box or bBox, bBox)
local min, max = editor.ApplyZBrushToGrid(self.pattern_grid, self.distort_grid_x, self.distort_grid_y, extended_box, self.center_point:SetZ(self.initial_point_z),
self.distort_amp_xy, self.distort_distance, angleDelta, box_size, self.height_change, self:GetBrushZeroLevel(), self.pattern_raw,
self:GetBrushMode(), self:GetClampMin(), self:GetClampMax())
if max and min then
XShortcutsTarget:SetStatusTextRight(string.format("Size %d, Min height %dm, Max height %dm", (2 * box_size) / guim, min * const.TerrainHeightScale / guim, max * const.TerrainHeightScale / guim))
end
self.old_box = bBox
self.box_size = box_size
Msg("EditorHeightChanged", false, extended_box)
return "break"
end
XEditorTool.OnMousePos(self, screen_point, button)
end
function XZBrush:OnKbdKeyDown(key, ...)
if self.is_editing and key == const.vkEsc then
self:CancelOperation()
return "break"
end
XEditorTool.OnKbdKeyDown(self, key, ...)
end
function XZBrush:CancelOperation()
if self.editing then
local w, h = terrain.HeightMapSize()
local mask = NewComputeGrid(w, h, "F")
local box = editor.DrawMaskSegment(mask, self.center_point, self.center_point, self.box_radius, self.box_radius, "min")
editor.SetHeightWithMask(0, mask, box)
mask:clear()
self:OnMouseButtonUp(self.center_point, 'L')
end
end
function XZBrush:ApplyTerrainTextures(filename)
local r, g, b = ImageToGrids(filename)
local bbox = editor.GetSegmentBoundingBox(self.center_point, self.center_point, self.box_radius, true)
local angle = self.last_rotation_delta + (self.angle_start and (self.angle - self.angle_start) or 0)
-- places terrain based on the texture in filename (different terrain for R, G, B channels); pattern_grid, pattern_raw are ignored
editor.ApplyZBrushToGrid(self.pattern_grid, self.distort_grid_x, self.distort_grid_y, bbox, self.center_point:SetZ(self.initial_point_z),
self.distort_amp_xy, self.distort_distance, angle, self.box_size, self.height_change, self:GetBrushZeroLevel(), self.pattern_raw,
self:GetBrushMode(), self:GetClampMin(), self:GetClampMax(),
r, g, b, self:GetTerrainR(), self:GetTerrainG(), self:GetTerrainB())
end
function XZBrush:OpenTextureLocationHelp()
local paths = { "Texture folders:" }
for i = 1, #textures_folders do
paths[#paths + 1] = ConvertToOSPath(textures_folders[i])
end
paths[#paths + 1] = "Thumb folders:"
for i = 1, #thumbs_folders do
paths[#paths + 1] = ConvertToOSPath(thumbs_folders[i])
end
CreateMessageBox(self, Untranslated("Texture Location"), Untranslated(table.concat(paths, "\n")))
end
function XZBrush:GetZBrushTexturesList()
local texture_list = {}
for i = 1, #textures_folders do
local textures_folder = textures_folders[i] or ""
local thumbs_folder = thumbs_folders[i] or ""
local err, thumbs, textures
if thumbs_folder ~= "" then
err, thumbs = AsyncListFiles(thumbs_folder, "*.png")
end
if textures_folder ~= "" then
err, textures = AsyncListFiles(textures_folder)
end
for _, texture in ipairs(textures or empty_table) do
local dir, name, ext = SplitPath(texture)
if supported_fmt[ext] then
local thumb = thumbs_folder .. "/" .. name .. ".png"
if not table.find(thumbs or empty_table, thumb) and thumb_fmt[ext] then
thumb = texture
end
texture_list[#texture_list + 1] = { text = name, value = texture, image = thumb }
end
end
end
table.sort(texture_list, function(a, b) return a.text < b.text or a.text == b.text and a.value < b.value end )
return texture_list
end |