How to stop angle wrapping or normalize an angle?

Hey guys I’ve been trying to over come the problem of angles wrapping from 360 to 0 or from 0 to 360 and its playing on my mind how to do it I’ve tried all the methods I can think of and still get this problem. It’s a car that gets the angle from a joysticks position (npryces joystick) and i smoothed out the turning which cause the problem of the angle wrapping, anyway here’s the code -:

-- racing

-- Use this function to perform your initial setup
function setup()
    controller = VirtualStick {
        moved = function(v) steer = v end,
        released = function(v) steer = vec2(0,0) end
    }
    controller:activate()
    pos = vec2(WIDTH/2, HEIGHT/2)
    steer = vec2(0,0)
    speed = 400 -- pixels per second
    verts = {vec2(0,5),vec2(0,45),vec2(5,50),vec2(35,50),vec2(40,45),vec2(40,5),vec2(35,0),vec2(5,0)}
    car = physics.body(POLYGON,unpack(verts))
    car.position = vec2(WIDTH/2,HEIGHT/2)
    car.interpolate = true
    car.sleepingAllwoed = false
    car.gravityScale = 0
    carm = mesh()
    carm:setColors(255,255,255,255)
    carr = carm:addRect(car.x,car.y,40,50,0)  
    ang = 0    
end

function clamp(x, min, max)
    return math.max(min, math.min(max, x))
end

function angleOfPoint( pt )
   local x, y = pt.x, pt.y
   local radian = math.atan2(y,x)
   local angle = radian*180/math.pi
   if angle < 0 then angle = 360 + angle end
   return angle
end


-- This function gets called once every frame
function draw()
    pos = pos + steer*speed*DeltaTime
    -- This sets a dark background color ()
    background(40, 40, 50)
    -- This sets the line thickness
    strokeWidth(5)
    if steer:len()>0.2 then
        car.linearVelocity = car.linearVelocity+steer*30
    end
    car.linearVelocity.x = clamp(car.linearVelocity.x,-59,50)
    car.linearVelocity = car.linearVelocity*0.95   
    
    if steer ~= vec2() then
        angle = angleOfPoint(steer)
        ang = (angle+90-car.angle)/10
    else
        ang = 0
    end
    car.angle = clamp(car.angle + ang,-1,181)
    if car.angle > 180 then 
        car.angle = -180+car.angle
    end
    -- Do your drawing here
    controller:draw()
    carm:setRect(carr,car.x,car.y,40,50,car.angle/57.3)
    carm:draw()
end

-------------


Controller = class()

function Controller:activate()
    touched = function(t)
        self:touched(t)
    end
end

function Controller:draw()
    -- nothing
end

-- Utility functions

function touchPos(t)
    return vec2(t.x, t.y)
end

function clamp(x, min, max)
    return math.max(min, math.min(max, x))
end

function clampAbs(x, maxAbs)
    return clamp(x, -maxAbs, maxAbs)
end

function clampLen(vec, maxLen)
    if vec == vec2(0,0) then
        return vec
    else
        return vec:normalize() * math.min(vec:len(), maxLen)
    end
end

-- projects v onto the direction represented by the given unit vector
function project(v, unit)
    return v:dot(unit)
end

function sign(x)
    if x == 0 then
        return 0
    elseif x < 0 then
        return -1
    elseif x > 0 then
        return 1
    else
        return x -- x is NaN
    end
end

function doNothing()
end

--------


VirtualStick = class(Controller)

function VirtualStick:init(args)
    self.radius = args.radius or 100
    self.deadZoneRadius = args.deadZoneRadius or 25
    self.releasedCallback = args.released or doNothing
    self.steerCallback = args.moved or doNothing
    self.pressedCallback = args.pressed or doNothing
    touches = {}
    pos1 = vec2()
    pos2 = vec2()
    nt={}
end

function abs(v2)
    return math.abs(v2)
end

function VirtualStick:touched(t)     
    local pos = touchPos(t)
        if t.state == BEGAN and self.touchId == nil then
            self.touchId = t.id
            self.touchStart = pos
            self.stickOffset = vec2(0, 0)
            self.pressedCallback()
        elseif t.id == self.touchId then
            if t.state == MOVING then
                self.stickOffset = clampLen(pos - self.touchStart, self.radius)
                self.steerCallback(self:vector())
            elseif t.state == ENDED or t.state == CANCELLED then
                self:reset()
                self.releasedCallback()
            end
        end 
end

function VirtualStick:vector()
    local stickRange = self.radius - self.deadZoneRadius
    local stickAmount = math.max(self.stickOffset:len() - self.deadZoneRadius, 0)
    local stickDirection = self.stickOffset:normalize()
    
    return stickDirection * (stickAmount/stickRange)
end

function VirtualStick:reset()
    self.touchId = nil
    self.touchStart = nil
    self.stickOffset = nil
end

function VirtualStick:draw()
    if self.touchId ~= nil then
        pushStyle()
        ellipseMode(RADIUS)
        strokeWidth(1)
        stroke(255, 255, 255, 255)
        noFill()

        pushMatrix()
        translate(self.touchStart.x,self.touchStart.y)
        fill(150,150,150,100)
        ellipse(0, 0, self.radius, self.radius)
        fill(164, 164, 164, 150)
        ellipse(0, 0, self.deadZoneRadius, self.deadZoneRadius)
        fill(150,150,150,100)
        translate(self.stickOffset.x, self.stickOffset.y)
        ellipse(0, 0, 25, 25)
        popMatrix()
        popStyle()
    end
end

function wrap360(angle)
    while angle > 360 do
        angle = angle - 360
    end
    
    while angle < 0 do
        angle = angle + 360
    end
    
    return angle
end

Ive tried all that, didnt work if you try the script you might get a better idea of it

Would angle % 360 not work? If it doesnt (havent tested) what about math.fmod? I remember something about it acting differently with negative numbers (excuse me if I am just brainfarting all over this discussion)

Change you draw() function to this. I commented out some code and added some. I’m not sure if this is what you’re after, but the rectangle points in the correct direction as it moves.


-- This function gets called once every frame
function draw()
    pos = pos + steer*speed*DeltaTime
    -- This sets a dark background color ()
    background(40, 40, 50)
    -- This sets the line thickness
    strokeWidth(5)
    if steer:len()>0.2 then
        car.linearVelocity = car.linearVelocity+steer*30
    end
    car.linearVelocity.x = clamp(car.linearVelocity.x,-59,50)
    car.linearVelocity = car.linearVelocity*0.95   

    if steer ~= vec2() then
        angle = angleOfPoint(steer)
        ang = (angle+90-car.angle)/10
    else
        ang = 0
    end
    
    --[[
    car.angle = clamp(car.angle + ang,-1,181)
    if car.angle > 180 then 
        car.angle = -180+car.angle
    end
    --]]
    
    if angle~=nil then
        car.angle=angle+90
    end
    
    -- Do your drawing here
    controller:draw()
    carm:setRect(carr,car.x,car.y,40,50,car.angle/57.3)
    carm:draw()
end

I don’t want it like that I did that but I want it to turn smoothly instead of snapping to position thanks though!

Maybe you can try using d = currentDirection:angleBetween(newDirection) since that always gives the smaller angle, and change rotation with the value d. Haven’t looked at thte code in detail though.

I’m pretty sure it’s to do with setting car.angle above 360 or below 0 as codea does it automatically, and angle between uses angle of point I think, it’s just setting one vector to 0,0

Try this:


-- racing

-- Use this function to perform your initial setup
function setup()
    controller = VirtualStick {
        moved = function(v) steer = v end,
        released = function(v) steer = vec2(0,0) end
    }
    controller:activate()
    pos = vec2(WIDTH/2, HEIGHT/2)
    steer = vec2(0,0)
    speed = 400 -- pixels per second
    verts = {vec2(0,5),vec2(0,45),vec2(5,50),vec2(35,50),vec2(40,45),vec2(40,5),vec2(35,0),vec2(5,0)}
    car = physics.body(POLYGON,unpack(verts))
    car.position = vec2(WIDTH/2,HEIGHT/2)
    car.interpolate = true
    car.sleepingAllowed = false
    car.gravityScale = 0
    carm = mesh()
    carm:setColors(255,255,255,255)
    carr = carm:addRect(car.x,car.y,40,50,0)  
    ang = 0    
end

function clamp(x, min, max)
    return math.max(min, math.min(max, x))
end

function angleOfPoint( pt )
   local x, y = pt.x, pt.y
   local radian = math.atan2(y,x)
   local angle = radian*180/math.pi
   return angle
end


-- This function gets called once every frame
function draw()
    pos = pos + steer*speed*DeltaTime
    -- This sets a dark background color ()
    background(40, 40, 50)
    -- This sets the line thickness
    strokeWidth(5)
    if steer:len()>0.2 then
        car.linearVelocity = car.linearVelocity+steer*30
    end
    car.linearVelocity.x = clamp(car.linearVelocity.x,-59,50)
    car.linearVelocity = car.linearVelocity*0.95   

    if steer ~= vec2() then
        angle = angleOfPoint(steer)
        while angle - car.angle > 180 do
            angle = angle - 360
        end
        while angle - car.angle < -180 do
            angle = angle + 360
        end
        ang = (angle-car.angle)/10
    else
        ang = 0
    end
    --car.angle = clamp(car.angle + ang,-1,181)
    car.angle = car.angle + ang
    while car.angle > 360 do 
        car.angle = car.angle - 360
    end
    while car.angle < 0 do 
        car.angle = car.angle + 360
    end
    -- Do your drawing here
    controller:draw()
    carm:setRect(carr,car.x,car.y,40,50,(car.angle+90)/57.3)
    carm:draw()
end

-------------


Controller = class()

function Controller:activate()
    touched = function(t)
        self:touched(t)
    end
end

function Controller:draw()
    -- nothing
end

-- Utility functions

function touchPos(t)
    return vec2(t.x, t.y)
end

function clamp(x, min, max)
    return math.max(min, math.min(max, x))
end

function clampAbs(x, maxAbs)
    return clamp(x, -maxAbs, maxAbs)
end

function clampLen(vec, maxLen)
    if vec == vec2(0,0) then
        return vec
    else
        return vec:normalize() * math.min(vec:len(), maxLen)
    end
end

-- projects v onto the direction represented by the given unit vector
function project(v, unit)
    return v:dot(unit)
end

function sign(x)
    if x == 0 then
        return 0
    elseif x < 0 then
        return -1
    elseif x > 0 then
        return 1
    else
        return x -- x is NaN
    end
end

function doNothing()
end

--------


VirtualStick = class(Controller)

function VirtualStick:init(args)
    self.radius = args.radius or 100
    self.deadZoneRadius = args.deadZoneRadius or 25
    self.releasedCallback = args.released or doNothing
    self.steerCallback = args.moved or doNothing
    self.pressedCallback = args.pressed or doNothing
    touches = {}
    pos1 = vec2()
    pos2 = vec2()
    nt={}
end

function abs(v2)
    return math.abs(v2)
end

function VirtualStick:touched(t)     
    local pos = touchPos(t)
        if t.state == BEGAN and self.touchId == nil then
            self.touchId = t.id
            self.touchStart = pos
            self.stickOffset = vec2(0, 0)
            self.pressedCallback()
        elseif t.id == self.touchId then
            if t.state == MOVING then
                self.stickOffset = clampLen(pos - self.touchStart, self.radius)
                self.steerCallback(self:vector())
            elseif t.state == ENDED or t.state == CANCELLED then
                self:reset()
                self.releasedCallback()
            end
        end 
end

function VirtualStick:vector()
    local stickRange = self.radius - self.deadZoneRadius
    local stickAmount = math.max(self.stickOffset:len() - self.deadZoneRadius, 0)
    local stickDirection = self.stickOffset:normalize()

    return stickDirection * (stickAmount/stickRange)
end

function VirtualStick:reset()
    self.touchId = nil
    self.touchStart = nil
    self.stickOffset = nil
end

function VirtualStick:draw()
    if self.touchId ~= nil then
        pushStyle()
        ellipseMode(RADIUS)
        strokeWidth(1)
        stroke(255, 255, 255, 255)
        noFill()

        pushMatrix()
        translate(self.touchStart.x,self.touchStart.y)
        fill(150,150,150,100)
        ellipse(0, 0, self.radius, self.radius)
        fill(164, 164, 164, 150)
        ellipse(0, 0, self.deadZoneRadius, self.deadZoneRadius)
        fill(150,150,150,100)
        translate(self.stickOffset.x, self.stickOffset.y)
        ellipse(0, 0, 25, 25)
        popMatrix()
        popStyle()
    end
end

Absolutely brilliant, thanks @Andrew_Stacey and the others!