Skip to content

Commit

Permalink
Merge pull request #327 from norrbotten/menu-improvement-3dpanning
Browse files Browse the repository at this point in the history
Menu improvement: Allow user to rotate and zoom the 3d preview models
  • Loading branch information
TwistedTail committed Oct 12, 2022
2 parents ea93cc7 + 39ef7ef commit 88b02d6
Showing 1 changed file with 171 additions and 16 deletions.
187 changes: 171 additions & 16 deletions lua/vgui/acf_panel.lua
Original file line number Diff line number Diff line change
Expand Up @@ -229,17 +229,83 @@ 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
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

-- 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,
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
Expand All @@ -256,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)
Expand All @@ -273,14 +339,31 @@ function PANEL:AddModelPreview(Model, Rotate)

local Size = ModelData.GetModelSize(Path)

local StartMatrix = Matrix()

-- 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.BoxSize = Vector(Size) -- Used for zooming

self.CamDistanceMul = 1
self.CamDistanceMulTarget = 1

self:DrawEntity(true)
self:SetModel(Path)
self:SetLookAt(Center)
self:SetCamPos(Center + self.CamOffset)
self:SetCamPos(Center + Vector(-self.CamDistance, 0, 0))
end

function Panel:UpdateSettings(Data)
Expand All @@ -290,6 +373,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

Expand All @@ -298,18 +410,61 @@ 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

local Time = RealTime()
local Delta = Time - self.LastTime
local Rotation = self.Rotation * Delta
local Offset = self.CamOffset
-- Handle mouse movement
if self.IsMouseDown then
local MouseOffset = Vector(self:ScreenToLocal(input.GetCursorPos()))
local Delta = MouseOffset - self.LastMouseOffset

Offset:Rotate(Rotation)
-- Rotate towards mouse movement
local Rotation = Angle(Delta.y * 0.7, -Delta.x, 0) * FrameTime() * 48

self:SetCamPos(self.CamCenter + Offset)
Rotation.p = math.Clamp(Rotation.p, -5, 5)
Rotation.y = math.Clamp(Rotation.y, -15, 15)

self.LastTime = Time
self.TargetRotMatrix:Rotate(Rotation)

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)

-- Rotate roll towards zero when not panning
if not self.IsMouseDown then
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.CamDistanceMul = self.CamDistanceMul + (self.CamDistanceMulTarget - self.CamDistanceMul) * LerpT * 0.5

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)
Expand Down

0 comments on commit 88b02d6

Please sign in to comment.