Skip to content

Get pos and set velocity world matrix mismatch #3581

@Doliman100

Description

@Doliman100

Describe the bug

The e:pos() function returns an interpolation of the m_world_last and m_world_next matrices, but the velocity is applied to an object relatively to the m_world_next matrix. So, currently we can't move an object directly relative to the result of the e:pos() function.

I implemented a destructive way of calculating the m_world_last matrix using Expression 2. To get m_world_next, just apply e:vel() and e:angVelVector() to it. It's destructive, because it changes the angular velocity of the object to rip the matrix.
Instead of calculating a m_world_last matrix, you can use the current physics tick factor, multiply it by the velocity and add the interpolated matrix. The way of finding the current physics tick begin and end time described in this issue: #3575.

On the Lua side the m_world_last matrix can be calculated using PhysObj:CalculateForceOffset() function in a non-destructive way. It works only if all rotation matrix elements are greater than 2^(-84), so 2^122 * m11 + 16384 * m12 == 2^127 * m11. All bits of the result beyond this range have to be truncated to prevent corruption. The PhysObj:CalculateForceOffset() function just calls CPhysicsObject::CalculateForceOffset() from the SDK.

local meter = 39.370079040527344 -- in
local radian = 57.295780181884766 -- deg; 180 / M_PI_F
local big_float = 2^122
local phys = Entity(76):GetPhysicsObject()

local _, r1 = phys:CalculateForceOffset(Vector(0, 0, meter), Vector(0, big_float * meter, 0))
local _, r2 = phys:CalculateForceOffset(Vector(meter, 0, 0), Vector(0, 0, big_float * meter))
local _, r3 = phys:CalculateForceOffset(Vector(0, meter, 0), Vector(big_float * meter, 0, 0))
r1 = r1 / radian / big_float
r2 = r2 / radian / big_float
r3 = r3 / radian / big_float
local m = Matrix({{r1[1], r1[2], r1[3], 0}, {r2[1], r2[2], r2[3], 0}, {r3[1], r3[2], r3[3], 0}, {0, 0, 0, 1}})

local _, t12 = phys:CalculateForceOffset(Vector(0, 0, meter), Vector(0, 0, 0))
local _, t3 = phys:CalculateForceOffset(Vector(0, meter, 0), Vector(0, 0, 0))
t12 = m * (t12 / radian)
t3 = m * (t3 / radian)
m:SetTranslation(Vector(t12[2], -t12[1], t3[1]) * meter)
print(m)

I'm not sure how to solve this on the Wiremod side. There are many functions using the interpolated matrix: e:toWorld(v), e:pos(), e:angles(), e:forward(), matrix(e), and probably everything related to get position and angle. On the other hand, all functions related to object movement use the m_world_next matrix: e:applyForce(v), e:propSetVelocity(v) and the gravity. Some specific functions use m_world_last: e:velAtPoint(v), e:applyOffsetForce(vv), e:applyAngForce(a). The e:applyTorque(v) function used to use m_world_last, but I recently patched it to use m_world_next: #3560. We need a way to convert values ​​between any functions to get rid of mismatches.
Currently I don't see any non-destructive way to direct access the m_world_last or m_world_next matrix from Expression 2. My current implementation calls e:applyTorque(v) twice per a matrix row extraction. Also, the values obtained using this method are dirty due to precision loss during conversion torque to angular velocity.

How to reproduce the bug

Run game with -tickrate 33. Spawn a cube025x025x025 and place the sample chip.
test_world_matrix.txt

TestType = 0. On the first game tick this sample sets the velocity which should move a prop by 1 in by the next game tick. On the second game tick it prints the current position 0.9 in and sets the velocity to zero, so the prop should stay on its current place. But, when the third tick prints the current position, we see that it's exactly 1 in. For the object to actually stay in place, we should think of its position as 1 in and calculate the velocity required to move it from that point to 0.9 in, instead of setting the velocity to zero.
TestType = 1. An implementation of a destructive way to find m_world_last. It also calculates m_world_next by add the object velocity to m_world_last. It prints the actual object position compared to the e:pos(): 0.5 in as the last pos and 1 in as the next pos, instead of an interpolated 0.9 in pos. As a side effect it sets the velocity to [0 0 0].
TestType = 2. A non-destructive way to calculate both matrices relatively to the interpolated matrix using game and physics tick sync. It requires to find GameTickOffset first, a way is described in the issue mentioned above. It calculates the factor F used by IVP_Core::calc_at_matrix() function and calculates m_world_last by undo it. Then it just applies the object velocity to m_world_last to get m_world_next, this is the same as if the factor is 1. It doesn't affect the object velocity, so it keeps moving up and rotating around Z. Currently this is the best way to access these matrices I know.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions