Help with Particle Spiral Velocity

I need some help with making a particle spiral around a frozen entity. I feel like I am going about this the wrong way, this is the relevant piece of code. It “somewhat” works but it isn’t a clean spiral.

Not very familiar with velocity in a 3d space.

pos = initial entity pos (for all intents and purposes just assume (0,0,0))

[lua]
timer.Create(“ParticleVelocity”, .01, 80, function()
yaw = yaw + 8
if ( yaw > 360 ) then yaw = 0; end

           local yaw = yaw
           if ( yaw > 360 ) then yaw = yaw - 360; end
           x = radius * math.cos((yaw * math.pi) / 180) -- Just gets a point on a circle (radius 20)
           y = radius * math.sin((yaw * math.pi) / 180)

           local ppos = particle:GetPos()

           endPos = Vector(pos.x, pos.y, ppos.z) + Vector(x,y,4) --End pos is the original pos + the pos on the circle and a little bit higher.

           dx = endPos.x - ppos.x
           dy = endPos.y - ppos.y
           dz = endPos.z - ppos.z

           d = math.sqrt(dx*dx + dy*dy + dz*dz)

           vx = dx/d * 24
           vy = dy/d * 24
           vz = dz/d * 24

           particle:SetVelocity( Vector(vx,vy,vz) );

end)
[/lua]

Note that I am assuming that you want the particle to orbit around the entity, correct me if I am wrong

I would suggest using an effect for this and simply draw a sprite.
Effects are located in the lua/effects/ directory, or entities/effects/ for gamemodes.

In my example (orbit.lua), I start by marking the effect for download and create a varible that points to the material the effect will use for the sprite:



AddCSLuaFile()
EFFECT.matsprite = Material("effects/select_ring")


The functions we are interested in for this effect is Init, Think and Render.

Let us start with Init, it gets called when the effect is created.
The first and only argument is an CEffectData object, which contains data for the effect; example on that at the end.



function EFFECT:Init(data)
    local ent = data:GetEntity() -- get target entity
    if not IsValid(ent) then
        return -- if the entity is not valid, no need to continue
    end

    self:SetPos(ent:GetPos()) -- set the position of the effect at the entitys position
    -- NOTE: since your entity will be frozen we only set the position once here, if you plan
    --     to move the entity; set the position in the Think function

    -- get the render bounds of the entity
    local min, max = ent:GetRenderBounds()
    -- set the render bounds of the effect to be bigger than the entitys, because the sprite
    --     will most likely orbit outside the entitys render bounds; 50% larger will do(?)
    self:SetRenderBounds(min*1.5, max*1.5)

    self.entity = ent -- store the entity
    self.radius = data:GetRadius() -- store the radius
end


Think, this function is used for the logic of the effect; returning anything but true removes the effect.



function EFFECT:Think()
    local ent = self.entity -- use the entity specified in Init
    if not IsValid(ent) then
        return false -- if the entity is not valid, remove the effect
    end

    -- calculating the position of the sprite
    local radius = self.radius -- use the radius specified in Init
    local rad = RealTime() -- let us be lazy and just use the time as the radians :^)
    self.spritepos = ent:GetPos() +
        ent:GetRight()*math.cos(rad)*radius +
        ent:GetForward()*math.sin(rad)*radius

    return true --keep the effect alive
end


Render, the function that renders the effect; self-explanatory:



function EFFECT:Render()
    if self.spritepos then
        render.SetMaterial(self.matsprite)
        render.DrawSprite(self.spritepos, 16, 16, color_white)
    end
end


And that’s it!
Here is the full code:



AddCSLuaFile()
EFFECT.matsprite = Material("effects/select_ring")

function EFFECT:Init(data)
    local ent = data:GetEntity() -- get target entity
    if not IsValid(ent) then
        return -- if the entity is not valid, no need to continue
    end

    self:SetPos(ent:GetPos()) -- set the position of the effect at the entitys position
    -- NOTE: since your entity will be frozen we only set the position once here, if you plan
    --     to move the entity; set the position in the Think function

    -- get the render bounds of the entity
    local min, max = ent:GetRenderBounds()
    -- set the render bounds of the effect to be bigger than the entitys, because the sprite
    --     will most likely orbit outside the entitys render bounds; 50% larger will do(?)
    self:SetRenderBounds(min*1.5, max*1.5)

    self.entity = ent -- store the entity
    self.radius = data:GetRadius() -- store the radius
end

function EFFECT:Think()
    local ent = self.entity -- use the entity specified in Init
    if not IsValid(ent) then
        return false -- if the entity is not valid, remove the effect
    end

    -- calculating the position of the sprite
    local radius = self.radius -- use the radius specified in Init
    local rad = RealTime() -- let us be lazy and just use the time as the radians :^)
    self.spritepos = ent:GetPos() +
        ent:GetRight()*math.cos(rad)*radius +
        ent:GetForward()*math.sin(rad)*radius

    return true --keep the effect alive
end

function EFFECT:Render()
    if self.spritepos then
        render.SetMaterial(self.matsprite)
        render.DrawSprite(self.spritepos, 16, 16, color_white)
    end
end


To apply the effect, use util.Effect
I suggest applying the effect CLIENTSIDE, because some clients might not have discovered the entity you are targeting, which will remove the effect for them.

Applying the effect on an entity you are looking at:



lua_run_cl local data = EffectData() data:SetEntity(Entity(1):GetEyeTrace().Entity) data:SetRadius(24) util.Effect("orbit", data)


Applying the effect when the entity is initialized:



function ENT:Initialize()
    if CLIENT then
        local data = EffectData()
        data:SetEntity(self)
        data:SetRadius(24)
        util.Effect("orbit", data)
    end
end


Good luck!