Object on the screen trembles if rendered with viewport coords

General discussion about LÖVE, Lua, game development, puns, and unicorns.
lysandre
Prole
Posts: 9
Joined: Sun Sep 01, 2024 3:31 pm

Object on the screen trembles if rendered with viewport coords

Post by lysandre »

Hi, I'm doing a RPG style game.

I use a Viewport obejct for representing the area of the game actually rendered onto the the screen, but I use it also like a camera, because it is possible to lock the center of the viewport on a specific object (usually the player).

I would like to develop the appearance of a location sign in the upper corner of the screen when then player cross athe border between two adjacent maps.

Now, the feature itself is pretty easy and it works, there's anyway a bug I would like to address, because could be a problem for a number of future features of the game I have in mind.

When I cross the border the sign appears, but when the player moves the sign trembles. I could think about three possible causes:
1. The sign is temporarly rendered at not integer coords
2. The x and y of the viewport are modified during the rendering
3. The use of

Code: Select all

math.floor
and

Code: Select all

math.ceil
, makes the value of the coord twitch rapidly

I looked into each of this reason, but I still have no answer.

I can say a similar problem (related to cause number 2) occured with the player rendering when I first tried to add more maps to one world. There I realized that the player should have relative coords to the map and some renderCoords, which the viewport should refer to. When I implemented this solution I first tried to set those renderCoords in the player directly when I rendered it, but this approach made the player "tremble" then I resolved initializing the renderCoords when I first loaded the player in the world and then updating them in the same tween where I updated the player map coords.

There are the code of the World and the Viewport class:

Code: Select all

local EventNameEnum = require 'data/enum/event/EventNameEnum'
local constants = require 'src/constants'
local EntityStateEnum = require 'data/enum/world/EntityStateEnum'
local EntityProximityMapBorderEnum = require 'data/enum/world/EntityProximityMapBorderEnum'
local Timer = require 'lib/knife/timer'
local LinesNamespaceEnum = require 'data/enum/general/LinesNamespaceEnum'
local DrawnPanel = require 'src/gui/panel/DrawnPanel'
local FixedText = require 'src/gui/text/FixedText'

---@class World
local World = Class {}

function World:init(name, maps, mapPositions, eventManager, linesProvider, viewport)
    local timer = Timer
    local showLocationSign = false

    local locationSignPanel = DrawnPanel {
        width = 4 * constants.TILE_SIZE,
        height = 1 * constants.TILE_SIZE
    }
    local locationSignText = FixedText {
        text = '',
        align = 'center',
        width = 4 * constants.TILE_SIZE,
        height = 1 * constants.TILE_SIZE,
        color = constants.COLORS.black,
        isVisible = true,
        isActive = true,
        opacity = 1
    }

    function self.getName() return name end
    function self.getMaps() return maps end
    function self.getMapPositions() return mapPositions end

    ---@param mapId string
    ---@param toX integer
    ---@param toY integer
    ---@return string?, integer?, integer?, EntityProximityMapBorderEnum?
    local function getDestinationMap(mapId, toX, toY)
        local originMapPosition = mapPositions[mapId]
        local destinationX = originMapPosition.x + toX * constants.TILE_SIZE
        local destinationY = originMapPosition.y + toY * constants.TILE_SIZE

        for mapName, position in pairs(mapPositions) do
            local mapWidth = maps[mapName].getWidth() * constants.TILE_SIZE
            local mapHeight = maps[mapName].getHeight() * constants.TILE_SIZE

            if destinationX >= position.x and destinationX < position.x + mapWidth and
                destinationY >= position.y and destinationY < position.y + mapHeight then
                local tileX = math.floor((destinationX - position.x) / constants.TILE_SIZE)
                local tileY = math.floor((destinationY - position.y) / constants.TILE_SIZE)

                local relativePosition
                if position.x < originMapPosition.x then
                    relativePosition = EntityProximityMapBorderEnum.West
                elseif position.x > originMapPosition.x then
                    relativePosition = EntityProximityMapBorderEnum.East
                elseif position.y < originMapPosition.y then
                    relativePosition = EntityProximityMapBorderEnum.North
                elseif position.y > originMapPosition.y then
                    relativePosition = EntityProximityMapBorderEnum.South
                end

                return mapName, tileX, tileY, relativePosition
            end
        end

        return nil, nil, nil
    end

    function self.initialize()
        ---@param _ EntityProximityMapBorderEnum
        eventManager.subscribe(EventNameEnum.ProximityToBorderEvent, self, function (_)
            -- TODO: this intersect if the entity is near the map border, could be useful
        end)
        ---@param data {entity: Entity, toX: integer, toY: integer}
        eventManager.subscribe(EventNameEnum.DestinationOutOfBorderEvent, self, function (data)
            local entity, toX, toY = data.entity, data.toX, data.toY
            -- get the current map
            local originMap = maps[entity.getMapId()]
            -- get the destination map
            local destinationMapId, destinationMapX, destinationMapY, relativePosition = getDestinationMap(
                entity.getMapId(), toX, toY)
            if destinationMapId and destinationMapX and destinationMapY then
                local destinationMap = maps[destinationMapId]
                -- add the entity to the destination map
                entity.setMapId(destinationMapId)
                local destx, desty = destinationMapX, destinationMapY
                if relativePosition == EntityProximityMapBorderEnum.West then
                    destx = destx + 1
                elseif relativePosition == EntityProximityMapBorderEnum.North then
                    desty = desty + 1
                elseif relativePosition == EntityProximityMapBorderEnum.East then
                    destx = destx - 1
                elseif relativePosition == EntityProximityMapBorderEnum.South then
                    desty = desty - 1
                end
                entity.setMapX(destx)
                entity.setMapY(desty)
                entity.setReservedX(destinationMapX)
                entity.setReservedY(destinationMapY)
                destinationMap.addEntity(entity, entity.getId())
                -- remove the entity from the origin map
                originMap.removeEntity(entity.getId())
                if destinationMap.getMetadata().outdoor then
                    locationSignText.updateChunks(linesProvider.getLine(LinesNamespaceEnum.World, entity.getMapId()))
                    showLocationSign = true
                    timer.after(3, function ()
                        locationSignText.setText('')
                        showLocationSign = false
                    end)
                end
                return
            end
            entity.changeState(EntityStateEnum.Idle)
        end)
    end

    function self.update(dt)
        timer.update(dt)
        for _, map in pairs(maps) do
            map.update(dt)
        end
    end

    function self.render()
        -- render maps tiles without priority
        for mapName, map in pairs(maps) do
            map.renderTiles(mapPositions[mapName].x, mapPositions[mapName].y, false)
        end

        -- render entities
        for mapName, map in pairs(maps) do
            map.renderEntities(mapPositions[mapName].x, mapPositions[mapName].y)
            map.renderEntityAnimations(mapPositions[mapName].x, mapPositions[mapName].y)
        end

        -- render maps tiles with priority
        for mapName, map in pairs(maps) do
            map.renderTiles(mapPositions[mapName].x, mapPositions[mapName].y, true)
        end

        if showLocationSign then
            locationSignPanel.render(math.floor(viewport.getX()) + 2, math.floor(viewport.getY()) + 2)
            locationSignText.render(math.floor(viewport.getX()) + 2, math.floor(viewport.getY()) + 2)
        end
    end
end

return World

Code: Select all

---@class Viewport
local Viewport = Class {}

function Viewport:init(params)
    local virtualWidth = params.virtualWidth
    local virtualHeight = params.virtualHeight
    local tileSize = params.tileSize

    local x = math.floor(params.x) or 0
    local y = math.floor(params.y) or 0
    local width = math.floor(params.width) or virtualWidth
    local height = math.floor(params.height) or virtualHeight

    local object = nil

    local center = {x = 0, y = 0}

    local function setCenter()
        center = {
            x = math.floor(x + (width / 2)),
            y = math.floor(y + (height / 2))
        }
    end

    setCenter()

    function self.getX() return x end
    function self.getY() return y end

    function self.update()
        if object then
            x = math.floor(object.x - virtualWidth * 0.5)
            y = math.floor(object.y - virtualHeight * 0.5)
        end
    end

    function self.render()
        if object then
            love.graphics.translate(
                math.floor(-object.x + virtualWidth * 0.5),
                math.floor(-object.y + virtualHeight * 0.5)
            )
        end
    end

    function self.setOrigin()
        x = math.floor(center.x - (width / 2))
        y = math.floor(center.y - (height / 2))
    end

    function self.pointToObject(o)
        object = o
        x = object.x - virtualWidth * 0.5
        y = object.y - virtualWidth * 0.5
    end

    function self.contains(ox, oy, owidth, oheight)
        if (
                x < ox + owidth + 8 and
                x + width + 8 > ox and
                y < oy + oheight + 8 and
                y + height + 8 > oy
            ) then
            return true
        end

        return false
    end
end

return Viewport
RNavega
Party member
Posts: 404
Joined: Sun Aug 16, 2020 1:28 pm

Re: Object on the screen trembles if rendered with viewport coords

Post by RNavega »

Aye, I'd try rounding the resolved coordinates and seeing if it makes any difference, using that "Round towards zero" recipe from dusoft's link.
Like the final coordinates used directly with the draw call.

One thing I'm concerned is that you're defining the API for this Viewport thing as a bunch of closures (functions inside functions), and I wonder if this won't give you some problems later:

Code: Select all

    function self.getX() return x end
    function self.getY() return y end
Those return the local variables to function :init(), right? There are no x or y keys inside the Viewport table.
So if you happened to access Viewport.x (by accident of course, because you're establishing that you should use the the getX() and getY() accessors), then it'd fail in finding those keys. You're probably doing this to keep those variables private.
I think you could do the same by storing them in a table and not exposing that table in the 'return' from that script, but this is just an opinion.
lysandre
Prole
Posts: 9
Joined: Sun Sep 01, 2024 3:31 pm

Re: Object on the screen trembles if rendered with viewport coords

Post by lysandre »

This is actually pretty cool, I am not so familiar with the use of Transformation, but I understand that a possible solution could be the mapping between the "screen coords" used for example by the love.mouse module and the "world coords", in a similar way to what I tried to do with the Viewport object. I don't know how I can achieve this with a Trasform object though, even if I guess that could be the path to follow.

If you have some examples or ideas in that way I could really use them.

I tried the usage of round (Round towards zero implementation): if I apply round instead of floor/ceil in the viewport the sign still trembles, additionally the player also trembles (LOL). When I use round in the location sign rendering as in:

Code: Select all

if showLocationSign then
    locationSignPanel.render(MathHelper.round(viewport.getX()) + 2, MathHelper.round(viewport.getY()) + 2)
    locationSignText.render(MathHelper.round(viewport.getX()) + 2, MathHelper.round(viewport.getY()) + 2)
end
It improves the tremble decrease and it's a little bit smoother, but still I get some shake. Maybe I could rethink a little the method implementation to make the movement even smoother...
RNavega wrote: Sun Dec 29, 2024 12:04 am One thing I'm concerned is that you're defining the API for this Viewport thing as a bunch of closures (functions inside functions), and I wonder if this won't give you some problems later:

Code: Select all

    function self.getX() return x end
    function self.getY() return y end
Those return the local variables to function :init(), right? There are no x or y keys inside the Viewport table.
So if you happened to access Viewport.x (by accident of course, because you're establishing that you should use the the getX() and getY() accessors), then it'd fail in finding those keys. You're probably doing this to keep those variables private.
I think you could do the same by storing them in a table and not exposing that table in the 'return' from that script, but this is just an opinion.
The main purpose of that OOP implementation is, as you correctly guessed, to keep the implementation details of the object private. This is useful to provide to the outside code a unique point to read/write the object fields, because you MUST use the accessors to get/set those fields (very useful in debugging). This approach is generalized in my codebase (aka every class is implemented this way).

Of course if you try to access the field x or y of a Viewport instance the lua interpreter would raise an error, beacuse they don't exist. This isn't a problem as long as you implement the code that interact with these classes, you use the accessors (get/set) and you are ok. Could be a problem when you use external libraries that usually expect to have direct access to the field. For those cases the solution are basically two:

1. Use an external table for store the values you want to access directly (as I did in the World class I posted, in the "mapPositions" table): is cleaner, but I try to avoid this one when the complexity grows, as keep things separate is confusing when there are many

2. Use the proxy pattern: this is a quite advanced way to do it, and elegant too, but introduces abstraction you have to manage

Here I provide an example of a proxy pattern implementation:

Code: Select all

function ClassHelper.createProxy(object, fieldMap)
    fieldMap = fieldMap or {}
    return setmetatable({}, {
        __index = function (_, key)
            local mappedKey = fieldMap[key] or key
            local methodName = 'get' .. mappedKey:sub(1, 1):upper() .. (mappedKey:sub(2) or '')
            return object[methodName]()
        end,
        __newindex = function (_, key, value)
            local mappedKey = fieldMap[key] or key
            local methodName = 'set' .. mappedKey:sub(1, 1):upper() .. (mappedKey:sub(2) or '')
            return object[methodName](value)
        end
    })
end
You could use this way:

Code: Select all

local entityProxy = ClassHelper.createProxy(entity)
    tween = Tween.new(
        speed,
        entityProxy,
        {
            x = destX,
            y = destY
        }
    )
Or even in this way, remapping the fields on others:

Code: Select all

local player = requires.ClassHelper.createProxy(entity, {x = 'RenderX', y = 'RenderY'})
I hope this example/explanation could be useful.

I didn't understand the usage of an additional table you suggest.
Last edited by lysandre on Mon Dec 30, 2024 8:33 am, edited 1 time in total.
RNavega
Party member
Posts: 404
Joined: Sun Aug 16, 2020 1:28 pm

Re: Object on the screen trembles if rendered with viewport coords

Post by RNavega »

lysandre wrote: Sun Dec 29, 2024 12:03 pm It improves the tremble decrease and it's a little bit smoother, but still I get some shake. Maybe I could rethink a little the method implementation to make the movement even smoother...
To clarify, are you doing a pixel art aesthetic, like rendering to a low resolution canvas and then drawing that canvas upscaled?
If so, the trembling might be the object simply snapping between the fat pixels, as its soft (high precision) coordinates get truncated into the stepped coordinates of the low resolution canvas, the only solution to which is to not use a canvas and to draw all your pixel art graphics directly upscaled so they can be positioned directly with screen coordinates rather than being positioned within the low resolution canvas coordinates.
lysandre wrote: Sun Dec 29, 2024 12:03 pm The main purpose of that OOP implementation is, as you correctly guessed, to keep the implementation details of the object private. This is useful to provide to the outside code a unique point to read/write the object fields, because you MUST use the accessors to get/set those fields (very useful in debugging). This approach is generalized in my codebase (aka every class is implemented this way).
I see. I think I saw this being used in JavaScript, enclosing a whole class into an anonymous function so that everything inside is private. And thank you for that proxy pattern example.

What I meant by privacy through a local table storing the data --which is way less sophisticated than it sounds-- was this:

Code: Select all

local viewportState = {
    x = 0, y = 0,
    width = 0, height = 0,
    zoom = 1.0,
}

local Viewport = {}


function Viewport.resize(x, y, width, height)
    viewportState.x = x
    viewportState.y = y
    viewportState.width = width
    viewportState.height = height
end


function Viewport.getX()
    return viewportState.x
end


function Viewport.getY()
    return viewportState.y
end


return Viewport

Code: Select all

local viewport = require('core.viewport')
print(viewport.getX())
assert(viewportState, "Can't touch this")
Any caller that 'require's the script will only see what's returned from it, so anything not returned and defined as 'local' in there will not be visible.
lysandre
Prole
Posts: 9
Joined: Sun Sep 01, 2024 3:31 pm

Re: Object on the screen trembles if rendered with viewport coords

Post by lysandre »

At the end I resolved like this:

Code: Select all

if showLocationSign then
    local ox, oy = love.graphics.inverseTransformPoint(0, 0)
    locationSignPanel.render(ox + 2, oy + 2)
    locationSignText.render(ox + 4, oy + 6)
end
Is not what dusoft suggested but it's pretty close. This method given the "screen coords" returns the "world coords". This way the location sign appears perfectly aligned in the upper-left corner of the screen, perfectly steady too, even when the player is moving.
RNavega wrote: Sun Dec 29, 2024 4:57 pm To clarify, are you doing a pixel art aesthetic, like rendering to a low resolution canvas and then drawing that canvas upscaled?
If so, the trembling might be the object simply snapping between the fat pixels, as its soft (high precision) coordinates get truncated into the stepped coordinates of the low resolution canvas, the only solution to which is to not use a canvas and to draw all your pixel art graphics directly upscaled so they can be positioned directly with screen coordinates rather than being positioned within the low resolution canvas coordinates.
Yes I'm going with pixel art aestethic, I use the push library for the resolution-handling and the upscaling, that internally make use of canvases. I set up this configuration since the beginning of the project long time ago. I had problems in the past when I first tried to run the game on Android. I resolved branching the execution directly in the application bootstrap and using the push library when executed on Desktop (Linux/Win/Mac) and directly upscaling when executed on Android.

Code: Select all

function love.load()
    love.graphics.setDefaultFilter('nearest', 'nearest', 0)
    love.graphics.setFont(constants.FONTS.small)

    if constants.OS ~= 'Android' then
        push:setupScreen(constants.VIRTUAL_WIDTH, constants.VIRTUAL_HEIGHT,
            constants.WINDOW_WIDTH, constants.WINDOW_HEIGHT, {
                fullscreen = false,
                resizable = false,
                pixelperfect = true
            })
    end

    -- initialize keyboard keyPressed table
    love.keyboard.keysPressed = {}

    -- initialize mouse buttonPressed table
    love.mouse.buttonPressed = {}
    
    -- omissis...
end

-- omissis...

function love.draw()
    if constants.OS == 'Android' then
        love.graphics.push()
        love.graphics.scale(constants.SCALE_X, constants.SCALE_Y)
    else
        push:start()
    end

    viewport.render()
    stateStack.render()

    if constants.OS == 'Android' then
        mobilePad.render()
        love.graphics.pop()
    else
        push:finish()
    end
end
Maybe I should totally remove the usage of the library and do as you suggest... For now I think I'll stick with the status quo until it works.
RNavega wrote: Sun Dec 29, 2024 4:57 pm I see. I think I saw this being used in JavaScript, enclosing a whole class into an anonymous function so that everything inside is private. And thank you for that proxy pattern example.
Yeah, well every language with first class function and with a proper support for closure should allow this kind of construct. In JS anyway enclose the whole class in one anonymous function it's pretty hardcore since you could create a class with private fields and methods with the "#" syntax.
RNavega wrote: Sun Dec 29, 2024 4:57 pm What I meant by privacy through a local table storing the data --which is way less sophisticated than it sounds-- was this:

Code: Select all

local viewportState = {
    x = 0, y = 0,
    width = 0, height = 0,
    zoom = 1.0,
}

local Viewport = {}


function Viewport.resize(x, y, width, height)
    viewportState.x = x
    viewportState.y = y
    viewportState.width = width
    viewportState.height = height
end


function Viewport.getX()
    return viewportState.x
end


function Viewport.getY()
    return viewportState.y
end


return Viewport

Code: Select all

local viewport = require('core.viewport')
print(viewport.getX())
assert(viewportState, "Can't touch this")
Any caller that 'require's the script will only see what's returned from it, so anything not returned and defined as 'local' in there will not be visible.
I see, at first sight it's basically the same. The great difference it's that the Viewport you implemented is a static class (that in this case could make sense), mine can be instantiated and have instance fields and methods (private or public) and static fields and methods (private or public). I don't see a way to implement something like "protected" fields and methods, but although useful it's not essential.

Here's a complete example of what I mean:

Code: Select all

local Test = Class {} -- from libary hump.class imported globally

local staticPrivateField = 'something'

local function staticPrivateMethod() -- no access to self
    -- do something
end

Test.staticPublicField = 'something other'

function Test.staticPublicMethod() -- no access to self, visible from outside
    -- do something
end

function Test:init(dependency1, dependency2) -- public instance method, although this has internal use and act as constructor
    local instancePrivateField = 'some other thing'
	
    local function instancePrivateMethod() -- access to self
        -- do something
    end
	
    self.instancePublicField = 'some another thing'
	
    -- getters and setters
    function self.getDependency1() return dependency1 end
    function self.getInstancePrivateField() return instancePrivateField end
    function self.setDependency1(value) dependency1 = value end
    function self.setInstancePrivateField(value) instancePrivateField = value end
	
    function self.instancePublicMethod() -- access to self
        -- do something
    end
end

return Test
Then outside:

Code: Select all

local Test = require 'src/Test'

-- now I can yet access to the public static methods and fields, but I cannot access the instance public fields and methods
print(Test.staticPublicField)
Test.staticPublicMethod()

local test = Test() -- constructor of the object, internally call init, among other things
-- now I can use the instance fields and methods, this is a unique object
print(test.instancePublicField)
print(test.getDependency1())
test.setInstancePrivateField()
-- etc.
User avatar
dusoft
Party member
Posts: 676
Joined: Fri Nov 08, 2013 12:07 am
Location: Europe usually
Contact:

Re: Object on the screen trembles if rendered with viewport coords

Post by dusoft »

Yep, push translation could be the culprit, but hard to say without going through your source code in detail.
RNavega
Party member
Posts: 404
Joined: Sun Aug 16, 2020 1:28 pm

Re: Object on the screen trembles if rendered with viewport coords

Post by RNavega »

To be honest I still don't get what was the solution here heh. That is, why it was solved numerically.
lysandre
Prole
Posts: 9
Joined: Sun Sep 01, 2024 3:31 pm

Re: Object on the screen trembles if rendered with viewport coords

Post by lysandre »

I agree with you, there's no clear explanation on why it works in this way.

In my opinion the approach with the Viewport I implemented should be as good as the call to the "love.graphics.inverseTransformPoint" API.

I think in some way there's a problem in the way the coords are interpolated in the stack of "translations" that are applied. Maybe the overlap of the graphic transformations made by the push library interfere in some way to the viewport coords update, while directly using the "love.graphics.inverseTransformPoint" API I obtain more accurate data.

This could be ascribed to the approximation made with rounding, although this doesn't fully explain why the player is correctly rendered.
lysandre
Prole
Posts: 9
Joined: Sun Sep 01, 2024 3:31 pm

Re: Object on the screen trembles if rendered with viewport coords

Post by lysandre »

Just out of curiosity, I post the output of this piece of code:

Code: Select all

if showLocationSign then
    local ox, oy = love.graphics.inverseTransformPoint(0, 0)
    local vx, vy = viewport.getX(), viewport.getY()
    print(('ox: %s, vx: %s, delta: %s, oy: %s, vy: %s, delta %s'):format(ox, vx, ox - vx, oy, vy, oy - vy))
    locationSignPanel.render(ox + 2, oy + 2)
    locationSignText.render(ox + 4, oy + 6)
end
This is the output of the player making two step up after the location sign appears:

Code: Select all

ox: 192, vx: 192, delta: 0, oy: 532, vy: 532.07869104, delta -0.078691039999967
ox: 192, vx: 192, delta: 0, oy: 531, vy: 531.52070336, delta -0.52070335999997
ox: 192, vx: 192, delta: 0, oy: 531, vy: 530.832208256, delta 0.16779174399994
ox: 192, vx: 192, delta: 0, oy: 530, vy: 530.450445248, delta -0.45044524800005
ox: 192, vx: 192, delta: 0, oy: 530, vy: 529.838830368, delta 0.161169632
ox: 192, vx: 192, delta: 0, oy: 529, vy: 529.307509728, delta -0.30750972800001
ox: 192, vx: 192, delta: 0, oy: 529, vy: 528.773356576, delta 0.22664342400003
ox: 192, vx: 192, delta: 0, oy: 528, vy: 528.238541728, delta -0.23854172799997
ox: 192, vx: 192, delta: 0, oy: 528, vy: 527.7620824, delta 0.23791759999995
ox: 192, vx: 192, delta: 0, oy: 527, vy: 527.171331264, delta -0.17133126399995
ox: 192, vx: 192, delta: 0, oy: 527, vy: 526.66752368, delta 0.33247631999996
ox: 192, vx: 192, delta: 0, oy: 526, vy: 526.096968832, delta -0.096968832000016
ox: 192, vx: 192, delta: 0, oy: 526, vy: 525.585361024, delta 0.41463897599999
ox: 192, vx: 192, delta: 0, oy: 525, vy: 525.016163264, delta -0.016163263999943
ox: 192, vx: 192, delta: 0, oy: 524, vy: 524.366032928, delta -0.36603292799998
ox: 192, vx: 192, delta: 0, oy: 524, vy: 523.975264224, delta 0.024735775999943
ox: 192, vx: 192, delta: 0, oy: 523, vy: 523.37649584, delta -0.37649583999996
ox: 192, vx: 192, delta: 0, oy: 523, vy: 522.894940224, delta 0.10505977599996
ox: 192, vx: 192, delta: 0, oy: 522, vy: 522.365147904, delta -0.36514790399997
ox: 192, vx: 192, delta: 0, oy: 522, vy: 521.832244768, delta 0.16775523199999
ox: 192, vx: 192, delta: 0, oy: 521, vy: 521.307773152, delta -0.30777315199998
ox: 192, vx: 192, delta: 0, oy: 521, vy: 520.830894144, delta 0.16910585599999
ox: 192, vx: 192, delta: 0, oy: 520, vy: 520.225887488, delta -0.22588748800001
ox: 192, vx: 192, delta: 0, oy: 520, vy: 519.77552736, delta 0.22447263999993
ox: 192, vx: 192, delta: 0, oy: 519, vy: 519.170008992, delta -0.17000899200002
ox: 192, vx: 192, delta: 0, oy: 519, vy: 518.6268784, delta 0.37312159999999
ox: 192, vx: 192, delta: 0, oy: 518, vy: 518.150195264, delta -0.15019526399999
ox: 192, vx: 192, delta: 0, oy: 518, vy: 517.563093856, delta 0.43690614399998
ox: 192, vx: 192, delta: 0, oy: 517, vy: 517.06148304, delta -0.061483039999985
ox: 192, vx: 192, delta: 0, oy: 516, vy: 516.49347568, delta -0.49347567999996
ox: 192, vx: 192, delta: 0, oy: 516, vy: 516, delta 0
ox: 192, vx: 192, delta: 0, oy: 515, vy: 515.421019104, delta -0.42101910400004
ox: 192, vx: 192, delta: 0, oy: 515, vy: 514.89844096, delta 0.10155903999998
ox: 192, vx: 192, delta: 0, oy: 514, vy: 514.45225792, delta -0.45225791999997
ox: 192, vx: 192, delta: 0, oy: 514, vy: 513.82027008, delta 0.17972992
ox: 192, vx: 192, delta: 0, oy: 513, vy: 513.305704096, delta -0.305704096
ox: 192, vx: 192, delta: 0, oy: 513, vy: 512.74267504, delta 0.25732496000001
ox: 192, vx: 192, delta: 0, oy: 512, vy: 512.270167744, delta -0.27016774399999
ox: 192, vx: 192, delta: 0, oy: 512, vy: 511.693317888, delta 0.30668211199998
ox: 192, vx: 192, delta: 0, oy: 511, vy: 511.196367328, delta -0.19636732799995
ox: 192, vx: 192, delta: 0, oy: 511, vy: 510.651309248, delta 0.34869075200004
ox: 192, vx: 192, delta: 0, oy: 510, vy: 510.09627696, delta -0.096276959999955
ox: 192, vx: 192, delta: 0, oy: 509, vy: 509.548545728, delta -0.54854572800002
ox: 192, vx: 192, delta: 0, oy: 509, vy: 508.973579392, delta 0.026420607999967
ox: 192, vx: 192, delta: 0, oy: 508, vy: 508.558225696, delta -0.55822569600002
ox: 192, vx: 192, delta: 0, oy: 508, vy: 507.944971904, delta 0.055028096000001
ox: 192, vx: 192, delta: 0, oy: 507, vy: 507.399049792, delta -0.39904979200003
ox: 192, vx: 192, delta: 0, oy: 507, vy: 506.799631872, delta 0.20036812800004
ox: 192, vx: 192, delta: 0, oy: 506, vy: 506.311987712, delta -0.31198771200002
ox: 192, vx: 192, delta: 0, oy: 506, vy: 505.830267936, delta 0.16973206399996
ox: 192, vx: 192, delta: 0, oy: 505, vy: 505.291307392, delta -0.29130739200002
ox: 192, vx: 192, delta: 0, oy: 505, vy: 504.809084288, delta 0.19091571199999
ox: 192, vx: 192, delta: 0, oy: 504, vy: 504.255215456, delta -0.25521545599997
ox: 192, vx: 192, delta: 0, oy: 504, vy: 503.723877824, delta 0.27612217599994
ox: 192, vx: 192, delta: 0, oy: 503, vy: 503.168435168, delta -0.16843516799997
ox: 192, vx: 192, delta: 0, oy: 503, vy: 502.609781664, delta 0.39021833599998
ox: 192, vx: 192, delta: 0, oy: 502, vy: 502.039697472, delta -0.039697471999943
ox: 192, vx: 192, delta: 0, oy: 501, vy: 501.597905056, delta -0.59790505599995
ox: 192, vx: 192, delta: 0, oy: 501, vy: 500.968308288, delta 0.031691711999997
ox: 192, vx: 192, delta: 0, oy: 500, vy: 500.563156128, delta -0.56315612799995
ox: 192, vx: 192, delta: 0, oy: 500, vy: 500, delta 0
Post Reply

Who is online

Users browsing this forum: No registered users and 5 guests