Merge pull request #68 from penolakushari/advsel_shapes

Advanced Select Clarity
Adds different shapes for different bone types in advanced selection mode (square - physical, circle - nonphysical, triangle - procedural, rotated triangle - parented)
Also bone that is being hovered over in advanced select mode will now pulse its scale so it will be easier to tell what we are about to select. (vlazed)
This commit is contained in:
penolakushari 2025-02-19 19:42:10 +03:00 committed by GitHub
commit f4d847360d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 164 additions and 33 deletions

View File

@ -1050,16 +1050,40 @@ function DrawEntName(ent)
draw.SimpleTextOutlined(name, "RagdollMoverFont", textpos.x, textpos.y, COLOR_RGMGREEN, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM, OUTLINE_WIDTH, COLOR_RGMBLACK) draw.SimpleTextOutlined(name, "RagdollMoverFont", textpos.x, textpos.y, COLOR_RGMGREEN, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM, OUTLINE_WIDTH, COLOR_RGMBLACK)
end end
local RGM_CIRCLE = { local RGM_SHAPES = {
{ x = -3, y = -3 }, { -- Square, phys
{ x = -5, y = -5 },
{ x = 0, y = -4 }, { x = 5, y = -5 },
{ x = 3, y = -3 }, { x = 5, y = 5 },
{ x = 4, y = 0 }, { x = -5, y = 5 }
{ x = 3, y = 3 }, },
{ x = 0, y = 4 },
{ x = -3, y = 3 }, { -- Circle, nonphys
{ x = -4, y = 0 } { x = -3, y = -3 },
{ x = 0, y = -4 },
{ x = 3, y = -3 },
{ x = 4, y = 0 },
{ x = 3, y = 3 },
{ x = 0, y = 4 },
{ x = -3, y = 3 },
{ x = -4, y = 0 }
},
{ -- Triangle, procedural
{ x = 0, y = -5 },
{ x = 5, y = 5 },
{ x = -5, y = 5 }
},
{ -- 180 rotated triangle, parented
{ x = -5, y = -5 },
{ x = 5, y = -5 },
{ x = 0, y = 5 }
}
} }
local LockGo = Material("icon16/lock_go.png", "alphatest") local LockGo = Material("icon16/lock_go.png", "alphatest")
@ -1165,25 +1189,27 @@ function AdvBoneSelectRender(ent, bonenodes, prevbones, calc, eyePos, eyeVector,
prevbones[i] = math.Clamp(math.ceil(fraction * NUM_GRADIENT_POINTS), 1, NUM_GRADIENT_POINTS ) prevbones[i] = math.Clamp(math.ceil(fraction * NUM_GRADIENT_POINTS), 1, NUM_GRADIENT_POINTS )
end end
local bonetype = bonenodes[ent][i] and bonenodes[ent][i].Type or 1
if dist < 576 then -- 24 pixels if dist < 576 then -- 24 pixels
surface.SetDrawColor(COLOR_BRIGHT_YELLOW:Unpack()) surface.SetDrawColor(COLOR_BRIGHT_YELLOW:Unpack())
table.insert(selectedBones, {name, i}) table.insert(selectedBones, {name, i})
else else
if nodesExist and bonenodes[ent][i] and bonenodes[ent][i].Type and prevbones[i] then if nodesExist and bonetype and prevbones[i] then
surface.SetDrawColor(BONETYPE_COLORS[bonenodes[ent][i].Type][prevbones[i]]:Unpack()) surface.SetDrawColor(BONETYPE_COLORS[bonetype][prevbones[i]]:Unpack())
else else
surface.SetDrawColor(COLOR_RGMGREEN:Unpack()) surface.SetDrawColor(COLOR_RGMGREEN:Unpack())
end end
end end
local circ = table.Copy(RGM_CIRCLE) local shape = table.Copy(RGM_SHAPES[bonetype])
for k, v in ipairs(circ) do for k, v in ipairs(shape) do
v.x = v.x + x v.x = v.x + x
v.y = v.y + y v.y = v.y + y
end end
draw.NoTexture() draw.NoTexture()
surface.DrawPoly(circ) surface.DrawPoly(shape)
if bonenodes[ent][i].bonelock then if bonenodes[ent][i].bonelock then
surface.SetMaterial(LockGo) surface.SetMaterial(LockGo)
@ -1210,6 +1236,7 @@ function AdvBoneSelectRender(ent, bonenodes, prevbones, calc, eyePos, eyeVector,
local scrH = ScrH() - 100 -- Some padding to keep the bones centered local scrH = ScrH() - 100 -- Some padding to keep the bones centered
local columns = 0 local columns = 0
local id = #selectedBones
-- List the selected bones. If they attempt to overflow through the screen, add the items to another column. -- List the selected bones. If they attempt to overflow through the screen, add the items to another column.
for i = 0, #selectedBones - 1 do for i = 0, #selectedBones - 1 do
local yPos = my + (i % maxItemsPerColumn) * (RGMFontSize + 3) local yPos = my + (i % maxItemsPerColumn) * (RGMFontSize + 3)
@ -1230,9 +1257,13 @@ function AdvBoneSelectRender(ent, bonenodes, prevbones, calc, eyePos, eyeVector,
end end
draw.SimpleTextOutlined(selectedBones[i + 1][1], "RagdollMoverFont", xPos, yPos, color, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM, OUTLINE_WIDTH, COLOR_RGMBLACK) draw.SimpleTextOutlined(selectedBones[i + 1][1], "RagdollMoverFont", xPos, yPos, color, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM, OUTLINE_WIDTH, COLOR_RGMBLACK)
-- Modify the data structure of the individual selectedBone so we don't have to worry about indexing later on
selectedBones[i + 1] = selectedBones[i + 1][2]
id = id + selectedBones[i + 1]
end end
return prevbones return prevbones, selectedBones, id
end end
function AdvBoneSelectPick(ent, bonenodes) function AdvBoneSelectPick(ent, bonenodes)
@ -1315,8 +1346,12 @@ function AdvBoneSelectRadialRender(ent, bones, bonenodes, isresetmode)
local thisrad = thisang / 180 * math.pi local thisrad = thisang / 180 * math.pi
local uix, uiy = (math.sin(thisrad) * 250 * modifier), (math.cos(thisrad) * -250 * modifier) local uix, uiy = (math.sin(thisrad) * 250 * modifier), (math.cos(thisrad) * -250 * modifier)
local color = COLOR_WHITE local color = COLOR_WHITE
if bonenodes and bonenodes[ent] and bonenodes[ent][bone] and bonenodes[ent][bone].Type then
color = BONETYPE_COLORS[bonenodes[ent][bone].Type][1] local nodecheck = (bonenodes and bonenodes[ent] and bonenodes[ent][bone] and bonenodes[ent][bone].Type) and true or false
local bonetype = bonenodes[ent][bone] and bonenodes[ent][bone].Type or 1
if nodecheck then
color = BONETYPE_COLORS[bonetype][1]
end end
uix, uiy = uix + midw, uiy + midh uix, uiy = uix + midw, uiy + midh
@ -1328,8 +1363,8 @@ function AdvBoneSelectRadialRender(ent, bones, bonenodes, isresetmode)
local pos = ent:GetBonePosition(bone) local pos = ent:GetBonePosition(bone)
pos = pos:ToScreen() pos = pos:ToScreen()
local circ = table.Copy(RGM_CIRCLE) local shape = table.Copy(RGM_SHAPES[bonetype])
for k, v in ipairs(circ) do for k, v in ipairs(shape) do
v.x = v.x + pos.x v.x = v.x + pos.x
v.y = v.y + pos.y v.y = v.y + pos.y
end end
@ -1339,15 +1374,11 @@ function AdvBoneSelectRadialRender(ent, bones, bonenodes, isresetmode)
color = COLOR_BRIGHT_YELLOW color = COLOR_BRIGHT_YELLOW
SelectedBone = bone SelectedBone = bone
else else
if bonenodes and bonenodes[ent] and bonenodes[ent][bone] and bonenodes[ent][bone].Type then surface.SetDrawColor(BONETYPE_COLORS[bonetype][1]:Unpack())
surface.SetDrawColor(BONETYPE_COLORS[bonenodes[ent][bone].Type][1]:Unpack())
else
surface.SetDrawColor(COLOR_RGMGREEN:Unpack())
end
end end
draw.NoTexture() draw.NoTexture()
surface.DrawPoly(circ) surface.DrawPoly(shape)
local ytextoffset = -14 local ytextoffset = -14
if uiy > (midh + 30) then ytextoffset = RGMFontSize + 14 end if uiy > (midh + 30) then ytextoffset = RGMFontSize + 14 end
@ -1365,6 +1396,7 @@ function AdvBoneSelectRadialRender(ent, bones, bonenodes, isresetmode)
draw.SimpleTextOutlined(name, "RagdollMoverFont", uix + xtextoffset, uiy + ytextoffset, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, OUTLINE_WIDTH, COLOR_RGMBLACK) draw.SimpleTextOutlined(name, "RagdollMoverFont", uix + xtextoffset, uiy + ytextoffset, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, OUTLINE_WIDTH, COLOR_RGMBLACK)
end end
return {SelectedBone}, SelectedBone and 1 + SelectedBone or 0
else else
local bone = bones[1] local bone = bones[1]
local btype = 2 local btype = 2
@ -1375,8 +1407,8 @@ function AdvBoneSelectRadialRender(ent, bones, bonenodes, isresetmode)
local pos = ent:GetBonePosition(bone) local pos = ent:GetBonePosition(bone)
pos = pos:ToScreen() pos = pos:ToScreen()
local circ = table.Copy(RGM_CIRCLE) local shape = table.Copy(RGM_SHAPES[btype])
for k, v in ipairs(circ) do for k, v in ipairs(shape) do
v.x = v.x + pos.x v.x = v.x + pos.x
v.y = v.y + pos.y v.y = v.y + pos.y
end end
@ -1384,7 +1416,7 @@ function AdvBoneSelectRadialRender(ent, bones, bonenodes, isresetmode)
surface.SetDrawColor(COLOR_WHITE:Unpack()) surface.SetDrawColor(COLOR_WHITE:Unpack())
draw.NoTexture() draw.NoTexture()
surface.DrawPoly(circ) surface.DrawPoly(shape)
local boneoptions = btype == 1 and FeaturesPhys or FeaturesNPhys local boneoptions = btype == 1 and FeaturesPhys or FeaturesNPhys
local count = #boneoptions local count = #boneoptions
@ -1451,7 +1483,7 @@ function AdvBoneSelectRadialRender(ent, bones, bonenodes, isresetmode)
k = k + 1 k = k + 1
end end
return {bone}, 1 + bone
end end
end end
@ -1460,6 +1492,19 @@ function AdvBoneSelectRadialPick()
return SelectedBone return SelectedBone
end end
do
local pi, sin = math.pi, math.sin
function AdvBoneSelectPulse(ent, bones, boneScales)
for _, bone in ipairs(bones) do
if not bone then continue end
if boneScales[bone] then
ent:ManipulateBoneScale(bone, boneScales[bone] + VECTOR_ONE * 0.05 * sin(2.666 * pi * RealTime()))
end
end
end
end
function DrawBoneConnections(ent, bone) function DrawBoneConnections(ent, bone)
local mainpos = ent:GetBonePosition(bone) local mainpos = ent:GetBonePosition(bone)
if not mainpos then if not mainpos then

View File

@ -2527,6 +2527,42 @@ end)
local GizmoWidth, SkeletonDraw local GizmoWidth, SkeletonDraw
-- A singleton to track bone manipulate state, particularly scale. Useful if we want to use
-- the bone manipulate state to indicate something (such as hovering over a bone in advanced bone select)
local ClientBoneState = {
entity = NULL,
Scales = {},
UpdateBoneScales = function(self)
for i = 0, self.entity:GetBoneCount() - 1 do
self.Scales[i] = self.entity:GetManipulateBoneScale(i)
end
end,
ResetBoneScales = function(self)
for i = 0, self.entity:GetBoneCount() - 1 do
self.Scales[i] = VECTOR_SCALEDEF
end
end,
SetBoneScales = function(self, scale)
for i = 0, self.entity:GetBoneCount() - 1 do
self.Scales[i] = scale
end
end,
SetBoneScale = function(self, bone, scale, recursive)
self.Scales[bone] = scale or self.entity:GetManipulateBoneScale(bone)
local childBones = self.entity:GetChildBones(bone)
if recursive and #childBones > 0 then
for _, cbone in ipairs(childBones) do
self:SetBoneScale(cbone, scale, true)
end
end
end,
SetEntity = function(self, newEntity)
self.entity = newEntity
self:UpdateBoneScales()
end
}
do do
local ConVars = { local ConVars = {
@ -3062,6 +3098,7 @@ end
local function RGMResetAllBones() local function RGMResetAllBones()
if not RAGDOLLMOVER[pl] or not RAGDOLLMOVER[pl].Entity then return end if not RAGDOLLMOVER[pl] or not RAGDOLLMOVER[pl].Entity then return end
ClientBoneState:ResetBoneScales()
NetStarter.rgmResetAllBones() NetStarter.rgmResetAllBones()
net.WriteEntity(RAGDOLLMOVER[pl].Entity) net.WriteEntity(RAGDOLLMOVER[pl].Entity)
net.SendToServer() net.SendToServer()
@ -3409,6 +3446,7 @@ NetStarter = {
local NodeFunctions = { local NodeFunctions = {
function(ent, id) -- 1 nodeReset function(ent, id) -- 1 nodeReset
ClientBoneState:SetBoneScale(id, VECTOR_SCALEDEF)
NetStarter.rgmResetAll() NetStarter.rgmResetAll()
net.WriteEntity(ent) net.WriteEntity(ent)
net.WriteUInt(id, 10) net.WriteUInt(id, 10)
@ -3433,6 +3471,7 @@ local NodeFunctions = {
end, end,
function(ent, id) -- 4 nodeResetScale function(ent, id) -- 4 nodeResetScale
ClientBoneState:SetBoneScale(id, VECTOR_SCALEDEF)
NetStarter.rgmResetScale() NetStarter.rgmResetScale()
net.WriteEntity(ent) net.WriteEntity(ent)
net.WriteBool(false) net.WriteBool(false)
@ -3441,6 +3480,7 @@ local NodeFunctions = {
end, end,
function(ent, id) -- 5 nodeResetCh function(ent, id) -- 5 nodeResetCh
ClientBoneState:SetBoneScale(id, VECTOR_SCALEDEF, true)
NetStarter.rgmResetAll() NetStarter.rgmResetAll()
net.WriteEntity(ent) net.WriteEntity(ent)
net.WriteUInt(id, 10) net.WriteUInt(id, 10)
@ -3465,6 +3505,7 @@ local NodeFunctions = {
end, end,
function(ent, id) -- 8 nodeResetScaleCh function(ent, id) -- 8 nodeResetScaleCh
ClientBoneState:SetBoneScale(id, VECTOR_SCALEDEF, true)
NetStarter.rgmResetScale() NetStarter.rgmResetScale()
net.WriteEntity(ent) net.WriteEntity(ent)
net.WriteBool(true) net.WriteBool(true)
@ -3473,6 +3514,7 @@ local NodeFunctions = {
end, end,
function(ent, id) -- 9 nodeScaleZero function(ent, id) -- 9 nodeScaleZero
ClientBoneState:SetBoneScale(id, vector_origin)
NetStarter.rgmScaleZero() NetStarter.rgmScaleZero()
net.WriteEntity(ent) net.WriteEntity(ent)
net.WriteBool(false) net.WriteBool(false)
@ -3481,6 +3523,7 @@ local NodeFunctions = {
end, end,
function(ent, id) -- 10 nodeScaleZeroCh function(ent, id) -- 10 nodeScaleZeroCh
ClientBoneState:SetBoneScale(id, vector_origin, true)
NetStarter.rgmScaleZero() NetStarter.rgmScaleZero()
net.WriteEntity(ent) net.WriteEntity(ent)
net.WriteBool(true) net.WriteBool(true)
@ -4321,7 +4364,7 @@ end
local function UpdateManipulationSliders(boneid, ent) local function UpdateManipulationSliders(boneid, ent)
if not IsValid(Pos1) then return end if not IsValid(Pos1) then return end
local pos, rot, scale = ent:GetManipulateBonePosition(boneid), ent:GetManipulateBoneAngles(boneid), ent:GetManipulateBoneScale(boneid) local pos, rot, scale = ent:GetManipulateBonePosition(boneid), ent:GetManipulateBoneAngles(boneid), ClientBoneState.Scales[boneid] or ent:GetManipulateBoneScale(boneid)
rot:Normalize() rot:Normalize()
ManipSliderUpdating = true ManipSliderUpdating = true
@ -4419,6 +4462,7 @@ local NETFUNC = {
physchildren[i] = net.ReadEntity() physchildren[i] = net.ReadEntity()
end end
ClientBoneState:SetEntity(selectedent)
if IsValid(BonePanel) then if IsValid(BonePanel) then
RGMBuildBoneMenu(ents, selectedent, BonePanel) RGMBuildBoneMenu(ents, selectedent, BonePanel)
end end
@ -4468,6 +4512,7 @@ local NETFUNC = {
physchildren[i] = net.ReadEntity() physchildren[i] = net.ReadEntity()
end end
ClientBoneState:SetEntity(ent)
if IsValid(BonePanel) then if IsValid(BonePanel) then
RGMBuildBoneMenu(ents, ent, BonePanel) RGMBuildBoneMenu(ents, ent, BonePanel)
end end
@ -4707,6 +4752,11 @@ function TOOL:Think()
local nowpressed = input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT) local nowpressed = input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT)
local isright = input.IsMouseDown(MOUSE_RIGHT) local isright = input.IsMouseDown(MOUSE_RIGHT)
-- Track that we are moving a bone, and store its bone scale.
if plTable.Moving then
ClientBoneState:UpdateBoneScales()
end
if nowpressed and not LastPressed and op == 2 then -- left click is a predicted function, so leftclick wouldn't work in singleplayer since i need data from client if nowpressed and not LastPressed and op == 2 then -- left click is a predicted function, so leftclick wouldn't work in singleplayer since i need data from client
local ent = plTable.Entity local ent = plTable.Entity
@ -4893,6 +4943,7 @@ hook.Add("KeyPress", "rgmSwitchSelectionMode", function(pl, key)
end) end)
local BoneColors = {} local BoneColors = {}
local BoneScaleGroup, LastId, LastOp = {}, 0, 0
local LastSelectThink, LastEnt = 0, nil local LastSelectThink, LastEnt = 0, nil
function TOOL:DrawHUD() function TOOL:DrawHUD()
@ -4952,16 +5003,17 @@ function TOOL:DrawHUD()
rgm.DrawSkeleton(ent, nodes) rgm.DrawSkeleton(ent, nodes)
end end
local id = 0
if self:GetOperation() == 2 and IsValid(ent) then if self:GetOperation() == 2 and IsValid(ent) then
local timecheck = (thinktime - LastSelectThink) > 0.1 local timecheck = (thinktime - LastSelectThink) > 0.1
local calc = ( not LastEnt or LastEnt ~= ent ) or timecheck local calc = ( not LastEnt or LastEnt ~= ent ) or timecheck
if self:GetStage() == 0 then if self:GetStage() == 0 then
BoneColors = rgm.AdvBoneSelectRender(ent, nodes, BoneColors, calc, eyepos, viewvec, fov) BoneColors, BoneScaleGroup, id = rgm.AdvBoneSelectRender(ent, nodes, BoneColors, calc, eyepos, viewvec, fov)
else else
rgm.AdvBoneSelectRadialRender(ent, plTable.SelectedBones, nodes, ResetMode) BoneScaleGroup, id = rgm.AdvBoneSelectRadialRender(ent, plTable.SelectedBones, nodes, ResetMode)
end end
LastEnt = ent LastEnt = ent
if timecheck then if timecheck then
LastSelectThink = thinktime LastSelectThink = thinktime
@ -4978,6 +5030,40 @@ function TOOL:DrawHUD()
rgm.DrawEntName(HoveredEnt) rgm.DrawEntName(HoveredEnt)
end end
-- Advanced Bone Select visual indicators
local opsDifferent = LastOp ~= self:GetOperation()
if IsValid(ent) then
-- We need to track the original manipulatebonescale (scales for short) while we also use the ManipulateBoneScale function
if opsDifferent then
if self:GetOperation() == 2 then
-- If we began advanced bone select, store the original scales
ClientBoneState:UpdateBoneScales()
else
-- If we just left it, restore the original scales
for i = 0, ent:GetBoneCount() - 1 do
ent:ManipulateBoneScale(i, ClientBoneState.Scales[i])
end
end
end
if self:GetOperation() == 2 then
-- If we're hovering over a different bone
if LastId ~= id then
-- Reset the bone to the original scale. This prevents us from adding or subtracting scales from
-- previous iterations of using advanced bone select
for i = 0, ent:GetBoneCount() - 1 do
ent:ManipulateBoneScale(i, ClientBoneState.Scales[i])
end
end
-- Show selected bone, regardless if any are selected
rgm.AdvBoneSelectPulse(ent, BoneScaleGroup, ClientBoneState.Scales)
end
LastId = id
end
LastOp = self:GetOperation()
end end
end end