From 5c943548422f3a45af66ffa76c563ff25e286725 Mon Sep 17 00:00:00 2001 From: norrbotten <–5593757+norrbotten@users.noreply.github.com> Date: Sun, 9 Oct 2022 19:27:54 +0200 Subject: [PATCH 1/5] Add 3d panning to the model previews --- lua/vgui/acf_panel.lua | 167 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 15 deletions(-) diff --git a/lua/vgui/acf_panel.lua b/lua/vgui/acf_panel.lua index 32823dd7b..c026b2393 100644 --- a/lua/vgui/acf_panel.lua +++ b/lua/vgui/acf_panel.lua @@ -229,17 +229,73 @@ function PANEL:AddCollapsible(Text, State) return Base, Category end +-- Lerps linearly between two matrices +-- This is in no way correct, but works fine for this purpose +-- Matrices need to be affine, shear is not preserved +local function LerpMatrix(A, B, t) + if not A:IsRotationMatrix() or not B:IsRotationMatrix() then + return B + end + + t = math.Clamp(t, 0, 1) + + local Pos = A:GetTranslation() * (1 - t) + B:GetTranslation() * t + + -- rotate A angle towards B angle + local Ang = A:GetAngles() + local A_Dir = A:GetForward() + local B_Dir = B:GetForward() + + local Dot = A_Dir:Dot(B_Dir) + if Dot < 0.999999 then + -- else use cross product + RotAxis = A_Dir:Cross(B_Dir) + RotAxis:Normalize() + + RotAngle = 180 / math.pi * math.acos(Dot) + Ang:RotateAroundAxis(RotAxis, RotAngle * t) + end + + local Scale = A:GetScale() * (1 - t) + B:GetScale() * t + + local C = Matrix() + C:SetTranslation(Pos) + C:SetAngles(Ang) + C:SetScale(Scale) + + return C +end + +-- Rotates the matrix roll towards zero +local function RotateMatrixRollToZero(A, t) + local Angles = A:GetAngles() + Angles:RotateAroundAxis(A:GetForward(), -t * Angles.r) + + local B = Matrix(A) + B:SetAngles(Angles) + + return B +end + function PANEL:AddModelPreview(Model, Rotate) local Settings = { - Height = 120, - FOV = 90, + Height = 120, + FOV = 60, + + Pitch = 15, -- Default pitch angle, camera will kinda bob up and down with nonzero setting + Rotation = Angle(0, -35, 0) -- Default rotation rate } local Panel = self:AddPanel("DModelPanel") Panel.Rotate = tobool(Rotate) - Panel.Rotation = Angle(0, -75) Panel.Settings = Settings -- Storing the default settings + Panel.IsMouseDown = false + Panel.InitialMouseOffset = Vector(0, 0) + Panel.LastMouseOffset = Vector(0, 0) + + Panel.RotationDirection = 1 + function Panel:SetRotateModel(Bool) self.Rotate = tobool(Bool) end @@ -273,14 +329,32 @@ function PANEL:AddModelPreview(Model, Rotate) local Size = ModelData.GetModelSize(Path) + local StartMatrix = Matrix() + + print(Path) + + -- looks a bit nicer with this + if string.find(Path, "engines") ~= nil then + StartMatrix:Rotate(Angle(self.Settings.Pitch, 0, 0)) + elseif string.find(Path, "reciever") ~= nil then + StartMatrix:Rotate(Angle(self.Settings.Pitch, 180, 0)) + else + StartMatrix:Rotate(Angle(self.Settings.Pitch, -90, 0)) + end + + self.RotMatrix = Matrix(StartMatrix) + self.TargetRotMatrix = Matrix(StartMatrix) + self.CamCenter = Center - self.CamOffset = Vector(0, Size:Length()) - self.LastTime = RealTime() + self.CamDistance = 1.2 * math.max(Size.x, math.max(Size.y, Size.z)) + + self.CamDistanceMul = 1 + self.CamDistanceMulTarget = 1 self:DrawEntity(true) self:SetModel(Path) - self:SetLookAt(Center) - self:SetCamPos(Center + self.CamOffset) + self:SetFOV(self.Settings.FOV) + self:SetCamPos(Center + Vector(-self.CamDistance, 0, 0)) end function Panel:UpdateSettings(Data) @@ -290,6 +364,35 @@ function PANEL:AddModelPreview(Model, Rotate) self:SetFOV(Data and Data.FOV or Settings.FOV) end + function Panel:OnMousePressed(Button) + if Button ~= MOUSE_LEFT then return end + + local MouseOffset = Vector(self:ScreenToLocal(input.GetCursorPos())) + + if MouseOffset:WithinAABox(Vector(0, 0, -1), Vector(self:GetWide(), self:GetTall(), 1)) then + self.IsMouseDown = true + self.InitialMouseOffset = MouseOffset + self.LastMouseOffset = MouseOffset + end + end + + function Panel:OnMouseReleased_impl(Button) + if Button ~= MOUSE_LEFT then return end + + self.IsMouseDown = false + + -- Reset target angles + self.TargetRotMatrix:SetAngles(Angle(self.Settings.Pitch, self.TargetRotMatrix:GetAngles().y, 0)) + + -- Find what direction user was rotating, and keep rotating in the same direction + local YawDiff = self.TargetRotMatrix:GetAngles().y - self.RotMatrix:GetAngles().y + self.RotationDirection = (YawDiff <= 0) and 1 or -1 + end + + function Panel:OnMouseReleased(Button) + self:OnMouseReleased_impl(Button) + end + function Panel:LayoutEntity() if self.NotDrawn then return end @@ -298,18 +401,52 @@ function PANEL:AddModelPreview(Model, Rotate) end if not self.Rotate then return end - if not self.CamOffset then return end + if not self.RotMatrix then return end + + -- Handle mouse movement + if self.IsMouseDown then + local MouseOffset = Vector(self:ScreenToLocal(input.GetCursorPos())) + local Delta = MouseOffset - self.LastMouseOffset + + -- Rotate towards mouse movement + local Rotation = Angle(Delta.y * 0.7, -Delta.x, 0) * FrameTime() * 48 - local Time = RealTime() - local Delta = Time - self.LastTime - local Rotation = self.Rotation * Delta - local Offset = self.CamOffset + Rotation.p = math.Clamp(Rotation.p, -5, 5) + Rotation.y = math.Clamp(Rotation.y, -15, 15) - Offset:Rotate(Rotation) + self.TargetRotMatrix:Rotate(Rotation) - self:SetCamPos(self.CamCenter + Offset) + self.LastMouseOffset = MouseOffset + else + -- Spin around like normal when not panning + self.TargetRotMatrix:Rotate(self.Settings.Rotation * FrameTime() * self.RotationDirection) + end + + -- Lerp rotation towards target + local LerpT = math.Clamp(FrameTime() * 4, 0.05, 0.3) + self.RotMatrix = LerpMatrix(self.RotMatrix, self.TargetRotMatrix, LerpT) - self.LastTime = Time + -- Rotate roll towards zero when not panning + if not self.IsMouseDown then + self.RotMatrix = RotateMatrixRollToZero(self.RotMatrix, LerpT * 0.25) + end + + -- Lerp distance towards target + self.CamDistanceMulTarget = (self.IsMouseDown and input.IsMouseDown(MOUSE_RIGHT)) and 0.76 or 1 + self.CamDistanceMul = self.CamDistanceMul + (self.CamDistanceMulTarget - self.CamDistanceMul) * LerpT + + local CamTransform = Matrix() + CamTransform:Translate(self.CamCenter) + CamTransform:Rotate(self.RotMatrix:GetAngles()) + CamTransform:Translate(Vector(-self.CamDistance * self.CamDistanceMul, 0, 0)) + + self:SetLookAng(CamTransform:GetAngles()) + self:SetCamPos(CamTransform:GetTranslation()) + + -- Mousedown state gets "stuck" if cursor is outside the panel when releasing, so, adding this + if self.IsMouseDown and not input.IsMouseDown(MOUSE_LEFT) then + self:OnMouseReleased_impl(MOUSE_LEFT) + end end Panel:UpdateModel(Model) From 184d7c5eb9393188a5319ad04ea168e8bf925b68 Mon Sep 17 00:00:00 2001 From: norrbotten <–5593757+norrbotten@users.noreply.github.com> Date: Sun, 9 Oct 2022 19:44:42 +0200 Subject: [PATCH 2/5] Improve zooming --- lua/vgui/acf_panel.lua | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/lua/vgui/acf_panel.lua b/lua/vgui/acf_panel.lua index c026b2393..5274c64b4 100644 --- a/lua/vgui/acf_panel.lua +++ b/lua/vgui/acf_panel.lua @@ -277,6 +277,17 @@ local function RotateMatrixRollToZero(A, t) return B end +-- Returns the distance from P to the closest point on a box +-- From https://iquilezles.org/articles/distfunctions/ +local function BoxSDF(P, Box) + local Q = Vector(math.abs(P.x), math.abs(P.y), math.abs(P.z)) - Box + + local D1 = Vector(math.max(0, Q.x), math.max(0, Q.y), math.max(0, Q.z)):Length() + local D2 = math.min(math.max(Q.x, math.max(Q.y, Q.z)), 0) + + return D1 + D2 +end + function PANEL:AddModelPreview(Model, Rotate) local Settings = { Height = 120, @@ -331,8 +342,6 @@ function PANEL:AddModelPreview(Model, Rotate) local StartMatrix = Matrix() - print(Path) - -- looks a bit nicer with this if string.find(Path, "engines") ~= nil then StartMatrix:Rotate(Angle(self.Settings.Pitch, 0, 0)) @@ -348,6 +357,8 @@ function PANEL:AddModelPreview(Model, Rotate) self.CamCenter = Center self.CamDistance = 1.2 * math.max(Size.x, math.max(Size.y, Size.z)) + self.BoxSize = Vector(Size) -- Used for zooming + self.CamDistanceMul = 1 self.CamDistanceMulTarget = 1 @@ -431,9 +442,18 @@ function PANEL:AddModelPreview(Model, Rotate) self.RotMatrix = RotateMatrixRollToZero(self.RotMatrix, LerpT * 0.25) end + -- Compute zoom distance + if self.IsMouseDown and input.IsMouseDown(MOUSE_RIGHT) then + local DistToBox = BoxSDF(self.RotMatrix * Vector(-self.CamDistance, 0, 0), self.BoxSize) + local Fraction = math.pow(1 - DistToBox / self.CamDistance, 0.5) + + self.CamDistanceMulTarget = Fraction + else + self.CamDistanceMulTarget = 1 + end + -- Lerp distance towards target - self.CamDistanceMulTarget = (self.IsMouseDown and input.IsMouseDown(MOUSE_RIGHT)) and 0.76 or 1 - self.CamDistanceMul = self.CamDistanceMul + (self.CamDistanceMulTarget - self.CamDistanceMul) * LerpT + self.CamDistanceMul = self.CamDistanceMul + (self.CamDistanceMulTarget - self.CamDistanceMul) * LerpT * 0.5 local CamTransform = Matrix() CamTransform:Translate(self.CamCenter) From 64f4d0e1f2b17954ddf45d2364dc686a114421d4 Mon Sep 17 00:00:00 2001 From: norrbotten <–5593757+norrbotten@users.noreply.github.com> Date: Tue, 11 Oct 2022 17:07:59 +0200 Subject: [PATCH 3/5] Fix trailing whitespaces --- lua/vgui/acf_panel.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/vgui/acf_panel.lua b/lua/vgui/acf_panel.lua index 5274c64b4..9bd2180de 100644 --- a/lua/vgui/acf_panel.lua +++ b/lua/vgui/acf_panel.lua @@ -249,7 +249,7 @@ local function LerpMatrix(A, B, t) local Dot = A_Dir:Dot(B_Dir) if Dot < 0.999999 then -- else use cross product - RotAxis = A_Dir:Cross(B_Dir) + RotAxis = A_Dir:Cross(B_Dir) RotAxis:Normalize() RotAngle = 180 / math.pi * math.acos(Dot) @@ -281,7 +281,7 @@ end -- From https://iquilezles.org/articles/distfunctions/ local function BoxSDF(P, Box) local Q = Vector(math.abs(P.x), math.abs(P.y), math.abs(P.z)) - Box - + local D1 = Vector(math.max(0, Q.x), math.max(0, Q.y), math.max(0, Q.z)):Length() local D2 = math.min(math.max(Q.x, math.max(Q.y, Q.z)), 0) @@ -292,7 +292,7 @@ function PANEL:AddModelPreview(Model, Rotate) local Settings = { Height = 120, FOV = 60, - + Pitch = 15, -- Default pitch angle, camera will kinda bob up and down with nonzero setting Rotation = Angle(0, -35, 0) -- Default rotation rate } From 8b920cb2bf8a7923903434a6d8ee602daa0188a5 Mon Sep 17 00:00:00 2001 From: norrbotten Date: Tue, 11 Oct 2022 17:44:25 +0200 Subject: [PATCH 4/5] Remove stray comment --- lua/vgui/acf_panel.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/vgui/acf_panel.lua b/lua/vgui/acf_panel.lua index 9bd2180de..0f4587dae 100644 --- a/lua/vgui/acf_panel.lua +++ b/lua/vgui/acf_panel.lua @@ -248,7 +248,6 @@ local function LerpMatrix(A, B, t) local Dot = A_Dir:Dot(B_Dir) if Dot < 0.999999 then - -- else use cross product RotAxis = A_Dir:Cross(B_Dir) RotAxis:Normalize() From 39ef7efcef5fb24e42c10a319f28d9de4e982f15 Mon Sep 17 00:00:00 2001 From: norrbotten Date: Tue, 11 Oct 2022 18:33:36 +0200 Subject: [PATCH 5/5] Change Settings.FOV to 90 and remove SetFOV call Seems like FOV gets reset from somewhere else in the code, so removing this since its quite pointless anyways. This also fixes the issue of models being way too close initially. --- lua/vgui/acf_panel.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/vgui/acf_panel.lua b/lua/vgui/acf_panel.lua index 0f4587dae..dd8a6511e 100644 --- a/lua/vgui/acf_panel.lua +++ b/lua/vgui/acf_panel.lua @@ -322,7 +322,7 @@ function PANEL:AddModelPreview(Model, Rotate) function Panel:UpdateModel(Path) if not isstring(Path) then - Path = "models/props_junk/PopCan01a.mdl" + return self:DrawEntity(false) end local Center = ModelData.GetModelCenter(Path) @@ -363,7 +363,6 @@ function PANEL:AddModelPreview(Model, Rotate) self:DrawEntity(true) self:SetModel(Path) - self:SetFOV(self.Settings.FOV) self:SetCamPos(Center + Vector(-self.CamDistance, 0, 0)) end