mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 21:53:46 +03:00
Upload
This commit is contained in:
505
lua/includes/circles/circles.lua
Normal file
505
lua/includes/circles/circles.lua
Normal file
@@ -0,0 +1,505 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
if SERVER then return false end
|
||||
|
||||
local _R = debug.getregistry()
|
||||
if _R.Circles then return _R.Circles end
|
||||
|
||||
local CIRCLE = {}
|
||||
CIRCLE.__index = CIRCLE
|
||||
|
||||
CIRCLE_FILLED = 0
|
||||
CIRCLE_OUTLINED = 1
|
||||
CIRCLE_BLURRED = 2
|
||||
|
||||
local New do
|
||||
local err_number = "bad argument #%i to 'New' (number expected, got %s)"
|
||||
|
||||
function New(t, r, x, y, ...)
|
||||
assert(isnumber(t), string.format(err_number, 1, type(t)))
|
||||
assert(isnumber(r), string.format(err_number, 2, type(r)))
|
||||
assert(isnumber(x), string.format(err_number, 3, type(x)))
|
||||
assert(isnumber(y), string.format(err_number, 4, type(y)))
|
||||
|
||||
local circle = setmetatable({}, CIRCLE)
|
||||
|
||||
circle:SetType(t)
|
||||
circle:SetRadius(r)
|
||||
circle:SetX(x)
|
||||
circle:SetY(y)
|
||||
|
||||
circle:SetVertices({Count = 0})
|
||||
|
||||
if t == CIRCLE_OUTLINED then
|
||||
local outline_width = ...
|
||||
assert(outline_width == nil or isnumber(outline_width), string.format(err_number, 5, type(outline_width)))
|
||||
|
||||
circle:SetOutlineWidth(outline_width)
|
||||
elseif t == CIRCLE_BLURRED then
|
||||
local blur_layers, blur_density = ...
|
||||
assert(blur_layers == nil or isnumber(blur_layers), string.format(err_number, 5, type(blur_layers)))
|
||||
assert(blur_density == nil or isnumber(blur_density), string.format(err_number, 6, type(blur_density)))
|
||||
|
||||
circle:SetBlurLayers(blur_layers)
|
||||
circle:SetBlurDensity(blur_density)
|
||||
end
|
||||
|
||||
return circle
|
||||
end
|
||||
end
|
||||
|
||||
local RotateVertices do
|
||||
local err_table = "bad argument #1 to 'RotateVertices' (table expected, got %s)"
|
||||
local err_number = "bad argument #%i to 'RotateVertices' (number expected, got %s)"
|
||||
|
||||
function RotateVertices(vertices, ox, oy, rotation, rotate_uv)
|
||||
assert(istable(vertices), string.format(err_table, type(vertices)))
|
||||
assert(isnumber(ox), string.format(err_number, 2, type(ox)))
|
||||
assert(isnumber(oy), string.format(err_number, 3, type(oy)))
|
||||
assert(isnumber(rotation), string.format(err_number, 4, type(rotation)))
|
||||
|
||||
local rotation = math.rad(rotation)
|
||||
local c = math.cos(rotation)
|
||||
local s = math.sin(rotation)
|
||||
|
||||
for i = 1, vertices.Count or #vertices do
|
||||
local vertex = vertices[i]
|
||||
local vx, vy = vertex.x, vertex.y
|
||||
|
||||
vx = vx - ox
|
||||
vy = vy - oy
|
||||
|
||||
vertex.x = ox + (vx * c - vy * s)
|
||||
vertex.y = oy + (vx * s + vy * c)
|
||||
|
||||
if rotate_uv == false then
|
||||
local u, v = vertex.u, vertex.v
|
||||
u, v = u - 0.5, v - 0.5
|
||||
|
||||
vertex.u = 0.5 + (u * c - v * s)
|
||||
vertex.v = 0.5 + (u * s + v * c)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local CalculateVertices do
|
||||
local err_number = "bad argument #%i to 'CalculateVertices' (number expected, got %s)"
|
||||
|
||||
function CalculateVertices(x, y, radius, rotation, start_angle, end_angle, distance, rotate_uv)
|
||||
assert(isnumber(x), string.format(err_number, 1, type(x)))
|
||||
assert(isnumber(y), string.format(err_number, 2, type(y)))
|
||||
assert(isnumber(radius), string.format(err_number, 3, type(radius)))
|
||||
assert(isnumber(rotation), string.format(err_number, 4, type(rotation)))
|
||||
assert(isnumber(start_angle), string.format(err_number, 5, type(start_angle)))
|
||||
assert(isnumber(end_angle), string.format(err_number, 6, type(end_angle)))
|
||||
assert(isnumber(distance), string.format(err_number, 7, type(distance)))
|
||||
|
||||
local vertices = {Count = 0}
|
||||
local step = distance / radius
|
||||
|
||||
local rad_start_angle = math.rad(start_angle)
|
||||
local rad_end_angle = math.rad(end_angle)
|
||||
local rad_rotation = math.rad(rotation)
|
||||
|
||||
for a = rad_start_angle, rad_end_angle + step, step do
|
||||
a = math.min(a, rad_end_angle)
|
||||
|
||||
local c = math.cos(a + rad_rotation)
|
||||
local s = math.sin(a + rad_rotation)
|
||||
|
||||
local vertex = {
|
||||
x = x + c * radius,
|
||||
y = y + s * radius,
|
||||
}
|
||||
|
||||
if rotate_uv == false then
|
||||
vertex.u = 0.5 + math.cos(a) / 2
|
||||
vertex.v = 0.5 + math.sin(a) / 2
|
||||
else
|
||||
vertex.u = 0.5 + c / 2
|
||||
vertex.v = 0.5 + s / 2
|
||||
end
|
||||
|
||||
vertices.Count = vertices.Count + 1
|
||||
vertices[vertices.Count] = vertex
|
||||
end
|
||||
|
||||
if end_angle - start_angle ~= 360 then
|
||||
table.insert(vertices, 1, {
|
||||
x = x, y = y,
|
||||
u = 0.5, v = 0.5,
|
||||
})
|
||||
|
||||
vertices.Count = vertices.Count + 1
|
||||
else
|
||||
table.remove(vertices)
|
||||
vertices.Count = vertices.Count - 1
|
||||
end
|
||||
|
||||
return vertices
|
||||
end
|
||||
end
|
||||
|
||||
function CIRCLE:__tostring()
|
||||
return string.format("Circle: %p", self)
|
||||
end
|
||||
|
||||
function CIRCLE:Copy()
|
||||
return table.Copy(self)
|
||||
end
|
||||
|
||||
function CIRCLE:IsValid()
|
||||
return (
|
||||
not self.m_Dirty and
|
||||
self.m_Vertices.Count >= 3 and
|
||||
self.m_Radius >= 1 and
|
||||
self.m_Distance >= 1
|
||||
)
|
||||
end
|
||||
|
||||
function CIRCLE:Calculate()
|
||||
local rotate_uv = self.m_RotateMaterial
|
||||
|
||||
local radius = self.m_Radius
|
||||
local x, y = self.m_X, self.m_Y
|
||||
|
||||
local rotation = self.m_Rotation
|
||||
local start_angle = self.m_StartAngle
|
||||
local end_angle = self.m_EndAngle
|
||||
|
||||
local distance = self.m_Distance
|
||||
|
||||
assert(radius >= 1, string.format("circle radius should be >= 1 (%.4f)", radius))
|
||||
assert(distance >= 1, string.format("circle distance should be >= 1 (%.4f)", distance))
|
||||
|
||||
self:SetVertices(CalculateVertices(x, y, radius, rotation, start_angle, end_angle, distance, rotate_uv))
|
||||
|
||||
if self.m_Type == CIRCLE_OUTLINED then
|
||||
local inner = self.m_ChildCircle or self:Copy()
|
||||
local inner_r = radius - self.m_OutlineWidth
|
||||
|
||||
inner:SetType(CIRCLE_FILLED)
|
||||
|
||||
inner:SetPos(x, y)
|
||||
inner:SetRadius(inner_r)
|
||||
inner:SetRotation(rotation)
|
||||
inner:SetAngles(start_angle, end_angle)
|
||||
inner:SetDistance(distance)
|
||||
|
||||
inner:SetColor(false)
|
||||
inner:SetMaterial(false)
|
||||
|
||||
inner:SetShouldRender(inner_r >= 1)
|
||||
inner:SetDirty(inner.m_ShouldRender)
|
||||
|
||||
self:SetShouldRender(inner_r < radius)
|
||||
self:SetChildCircle(inner)
|
||||
elseif self.m_ChildCircle then
|
||||
self.m_ChildCircle = nil
|
||||
end
|
||||
|
||||
self:SetDirty(false)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
do
|
||||
local blur = Material("pp/blurscreen")
|
||||
|
||||
function CIRCLE:__call()
|
||||
if self.m_Dirty then self:Calculate() end
|
||||
|
||||
if not self:IsValid() then return false end
|
||||
if not self.m_ShouldRender then return false end
|
||||
|
||||
do
|
||||
local col, mat = self.m_Color, self.m_Material
|
||||
|
||||
if IsColor(col) then
|
||||
if col.a <= 0 then return end
|
||||
surface.SetDrawColor(col.r, col.g, col.b, col.a)
|
||||
end
|
||||
|
||||
if mat == true then
|
||||
draw.NoTexture()
|
||||
elseif TypeID(mat) == TYPE_MATERIAL then
|
||||
surface.SetMaterial(mat)
|
||||
end
|
||||
end
|
||||
|
||||
if self.m_Type == CIRCLE_OUTLINED then
|
||||
render.ClearStencil()
|
||||
|
||||
render.SetStencilEnable(true)
|
||||
render.SetStencilTestMask(0xFF)
|
||||
render.SetStencilWriteMask(0xFF)
|
||||
render.SetStencilReferenceValue(0x01)
|
||||
|
||||
render.SetStencilCompareFunction(STENCIL_NEVER)
|
||||
render.SetStencilFailOperation(STENCIL_REPLACE)
|
||||
render.SetStencilZFailOperation(STENCIL_REPLACE)
|
||||
|
||||
self.m_ChildCircle()
|
||||
|
||||
render.SetStencilCompareFunction(STENCIL_GREATER)
|
||||
render.SetStencilFailOperation(STENCIL_KEEP)
|
||||
render.SetStencilZFailOperation(STENCIL_KEEP)
|
||||
|
||||
surface.DrawPoly(self.m_Vertices)
|
||||
render.SetStencilEnable(false)
|
||||
elseif self.m_Type == CIRCLE_BLURRED then
|
||||
render.ClearStencil()
|
||||
|
||||
render.SetStencilEnable(true)
|
||||
render.SetStencilTestMask(0xFF)
|
||||
render.SetStencilWriteMask(0xFF)
|
||||
render.SetStencilReferenceValue(0x01)
|
||||
|
||||
render.SetStencilCompareFunction(STENCIL_NEVER)
|
||||
render.SetStencilFailOperation(STENCIL_REPLACE)
|
||||
render.SetStencilZFailOperation(STENCIL_REPLACE)
|
||||
|
||||
surface.DrawPoly(self.m_Vertices)
|
||||
|
||||
render.SetStencilCompareFunction(STENCIL_LESSEQUAL)
|
||||
render.SetStencilFailOperation(STENCIL_KEEP)
|
||||
render.SetStencilZFailOperation(STENCIL_KEEP)
|
||||
|
||||
surface.SetMaterial(blur)
|
||||
|
||||
local sw, sh = ScrW(), ScrH()
|
||||
|
||||
for i = 1, self.m_BlurLayers do
|
||||
blur:SetFloat("$blur", (i / self.m_BlurLayers) * self.m_BlurDensity)
|
||||
blur:Recompute()
|
||||
|
||||
render.UpdateScreenEffectTexture()
|
||||
surface.DrawTexturedRect(0, 0, sw, sh)
|
||||
end
|
||||
render.SetStencilEnable(false)
|
||||
else
|
||||
surface.DrawPoly(self.m_Vertices)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
CIRCLE.Draw = CIRCLE.__call
|
||||
end
|
||||
|
||||
do
|
||||
local err_number = "bad argument #%i to 'Translate' (number expected, got %s)"
|
||||
|
||||
function CIRCLE:Translate(x, y)
|
||||
assert(isnumber(x), string.format(err_number, 1, type(x)))
|
||||
assert(isnumber(y), string.format(err_number, 2, type(y)))
|
||||
|
||||
if x ~= 0 or y ~= 0 then
|
||||
self.m_X = self.m_X + x
|
||||
self.m_Y = self.m_Y + y
|
||||
|
||||
if self:IsValid() then
|
||||
for i = 1, self.m_Vertices.Count do
|
||||
local vertex = self.m_Vertices[i]
|
||||
|
||||
vertex.x = vertex.x + x
|
||||
vertex.y = vertex.y + y
|
||||
end
|
||||
|
||||
if self.m_Type == CIRCLE_OUTLINED and self.m_ChildCircle then
|
||||
self.m_ChildCircle:Translate(x, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local err_number = "bad argument #1 to 'Scale' (number expected, got %s)"
|
||||
|
||||
function CIRCLE:Scale(scale)
|
||||
assert(isnumber(scale), string.format(err_number, type(scale)))
|
||||
|
||||
if scale ~= 1 then
|
||||
self.m_Radius = self.m_Radius * scale
|
||||
|
||||
if self:IsValid() then
|
||||
local x, y = self.m_X, self.m_Y
|
||||
|
||||
for i = 1, self.m_Vertices.Count do
|
||||
local vertex = self.m_Vertices[i]
|
||||
|
||||
vertex.x = x + (vertex.x - x) * scale
|
||||
vertex.y = y + (vertex.y - y) * scale
|
||||
end
|
||||
|
||||
if self.m_Type == CIRCLE_OUTLINED and self.m_ChildCircle then
|
||||
self.m_ChildCircle:Scale(scale)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local err_number = "bad argument #1 to 'Rotate' (number expected, got %s)"
|
||||
|
||||
function CIRCLE:Rotate(rotation)
|
||||
assert(isnumber(rotation), string.format(err_number, type(rotation)))
|
||||
|
||||
if rotation ~= 0 then
|
||||
self.m_Rotation = self.m_Rotation + rotation
|
||||
|
||||
if self:IsValid() then
|
||||
local x, y = self.m_X, self.m_Y
|
||||
local vertices = self.m_Vertices
|
||||
local rotate_uv = self.m_RotateMaterial
|
||||
|
||||
RotateVertices(vertices, x, y, rotation, rotate_uv)
|
||||
|
||||
if self.m_Type == CIRCLE_OUTLINED and self.m_ChildCircle then
|
||||
self.m_ChildCircle:Rotate(rotation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local function AccessorFunc(name, default, dirty, callback)
|
||||
local varname = "m_" .. name
|
||||
|
||||
CIRCLE["Get" .. name] = function(self)
|
||||
return self[varname]
|
||||
end
|
||||
|
||||
CIRCLE["Set" .. name] = function(self, value)
|
||||
if default ~= nil and value == nil then
|
||||
value = default
|
||||
end
|
||||
|
||||
if self[varname] ~= value then
|
||||
if dirty then
|
||||
self[dirty] = true
|
||||
end
|
||||
|
||||
if callback ~= nil then
|
||||
local new = callback(self, self[varname], value)
|
||||
value = new ~= nil and new or value
|
||||
end
|
||||
|
||||
self[varname] = value
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
CIRCLE[varname] = default
|
||||
end
|
||||
|
||||
local function OffsetVerticesX(circle, old, new)
|
||||
circle:Translate(new - old, 0)
|
||||
|
||||
if circle.m_Type == CIRCLE_OUTLINED and circle.m_ChildCircle then
|
||||
circle.m_ChildCircle:Translate(new - old, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function OffsetVerticesY(circle, old, new)
|
||||
circle:Translate(0, new - old)
|
||||
|
||||
if circle.m_Type == CIRCLE_OUTLINED and circle.m_ChildCircle then
|
||||
circle.m_ChildCircle:Translate(0, new - old)
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateRotation(circle, old, new)
|
||||
circle:Rotate(new - old)
|
||||
|
||||
if circle.m_Type == CIRCLE_OUTLINED and circle.m_ChildCircle then
|
||||
circle.m_ChildCircle:Rotate(new - old)
|
||||
end
|
||||
end
|
||||
|
||||
-- These are set internally. Only use them if you know what you're doing.
|
||||
AccessorFunc("Dirty", true)
|
||||
AccessorFunc("Vertices", false)
|
||||
AccessorFunc("ChildCircle", false)
|
||||
AccessorFunc("ShouldRender", true)
|
||||
|
||||
AccessorFunc("Color", false) -- The colour you want the circle to be. If set to false then surface.SetDrawColor's can be used.
|
||||
AccessorFunc("Material", false) -- The material you want the circle to render. If set to false then surface.SetMaterial can be used.
|
||||
AccessorFunc("RotateMaterial", true) -- Sets whether or not the circle's UV points should be rotated with the vertices.
|
||||
|
||||
AccessorFunc("Type", CIRCLE_FILLED, "m_Dirty") -- The circle's type.
|
||||
AccessorFunc("X", 0, false, OffsetVerticesX) -- The circle's X position relative to the top left of the screen.
|
||||
AccessorFunc("Y", 0, false, OffsetVerticesY) -- The circle's Y position relative to the top left of the screen.
|
||||
AccessorFunc("Radius", 8, "m_Dirty") -- The circle's radius.
|
||||
AccessorFunc("Rotation", 0, false, UpdateRotation) -- The circle's rotation, measured in degrees.
|
||||
AccessorFunc("StartAngle", 0, "m_Dirty") -- The circle's start angle, measured in degrees.
|
||||
AccessorFunc("EndAngle", 360, "m_Dirty") -- The circle's end angle, measured in degrees.
|
||||
AccessorFunc("Distance", 10, "m_Dirty") -- The maximum distance between each of the circle's vertices. This should typically be used for large circles in 3D2D.
|
||||
|
||||
AccessorFunc("BlurLayers", 3) -- The circle's blur layers if Type is set to CIRCLE_BLURRED.
|
||||
AccessorFunc("BlurDensity", 2) -- The circle's blur density if Type is set to CIRCLE_BLURRED.
|
||||
AccessorFunc("OutlineWidth", 10, "m_Dirty") -- The circle's outline width if Type is set to CIRCLE_OUTLINED.
|
||||
|
||||
function CIRCLE:SetPos(x, y)
|
||||
x = tonumber(x) or self.m_X
|
||||
y = tonumber(y) or self.m_Y
|
||||
|
||||
if self:IsValid() then
|
||||
self:Translate(x - self.m_X, y - self.m_Y)
|
||||
else
|
||||
self.m_X = x
|
||||
self.m_Y = y
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function CIRCLE:SetAngles(s, e)
|
||||
s = tonumber(s) or self.m_StartAngle
|
||||
e = tonumber(e) or self.m_EndAngle
|
||||
|
||||
self:SetDirty(self.m_Dirty or s ~= self.m_StartAngle or e ~= self.m_EndAngle)
|
||||
|
||||
self.m_StartAngle = s
|
||||
self.m_EndAngle = e
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function CIRCLE:GetPos()
|
||||
return self.m_X, self.m_Y
|
||||
end
|
||||
|
||||
function CIRCLE:GetAngles()
|
||||
return self.m_StartAngle, self.m_EndAngle
|
||||
end
|
||||
end
|
||||
|
||||
_R.Circles = {
|
||||
_MT = CIRCLE,
|
||||
|
||||
New = New,
|
||||
RotateVertices = RotateVertices,
|
||||
CalculateVertices = CalculateVertices,
|
||||
}
|
||||
|
||||
return _R.Circles
|
||||
Reference in New Issue
Block a user