Codea 1.5 (Beta 9 and 10)

Another, more geometrical, experiment with RotationRate:


--
-- Cube Rotate
--

supportedOrientations(LANDSCAPE_LEFT)
function setup()
    if deviceMetrics().platformName == "iPad 1G" then
        print("This code needs a gyroscope."..
            " An iPad 1 does not have one.")
    end
    local size = math.min(HEIGHT, WIDTH) / 2
    d = 1000
    m = cubeMesh(size)
    dir = vec3(0, 0, 1)
    dir2 = vec3(0, 1, 0)
end

function draw()
    background(0)
    perspective()
    local mat = matrix()
    local dxa = RotationRate.y
    local dya = - RotationRate.x
    local dza = - RotationRate.z
    mat = mat:rotate(dxa, 1, 0, 0)
    mat = mat:rotate(dya, 0, 1, 0)
    mat = mat:rotate(dza, 0, 0, 1)
    dir = mult(mat, dir)
    dir2 = mult(mat, dir2)
    local x = dir.x * d
    local y = dir.y * d
    local z = dir.z * d
    camera(x, y, z, 0, 0, 0, dir2.x, dir2.y, dir2.z)
    m:draw()
end

function cubeMesh(size)
    local v = {}
    local c = {}
    for i = 0, 7 do
        local x = (i % 2) * 2 - 1
        local y = (math.floor(i / 2) % 2) * 2 - 1
        local z = (math.floor(i / 4) % 2) * 2 - 1
        v[i] = vec3(x, y, z) * size / 2
        c[i] = color(127 + 127 * x, 127 + 127 * y, 127 + 127 * z)
    end
    local ver = {}
    local col = {}
    for v1 = 1, 3 do
        local v2 = v1 * 3 % 7
        local v3 = 7 - v1
        local v4 = 7 - v2
        local vt = {v[0], v[v1], v[v2], v[0], v[v3], v[v4], 
            v[7], v[v2], v[v1], v[7], v[v4], v[v3]}
        for i = 1, #vt do
            ver[(v1 - 1) * #vt + i] = vt[i]
        end
        local ct = {c[0], c[v1], c[v2], c[0], c[v3], c[v4], 
            c[7], c[v2], c[v1], c[7], c[v4], c[v3]}
        for i = 1, #ct do
            col[(v1 - 1) * #ct + i] = ct[i]
        end
    end
    local m = mesh()
    m.vertices = ver
    m.colors = col
    return m
end

function mult(mat, vec)
    local v1 = mat[1] * vec.x + mat[2] * vec.y + mat[3] * vec.z
    local v2 = mat[5] * vec.x + mat[6] * vec.y + mat[7] * vec.z  
    local v3 = mat[9] * vec.x + mat[10] * vec.y + mat[11] * vec.z
    return vec3(v1, v2, v3)    
end

.@mpilgrem

mesh:normal(i) works (may not be documented at the moment). mesh:clear() clears all buffers including custom attributes. Triangulate is winding order agnostic, will have to update the documentation.

.@Jmv38

[EDIT] i found what generates the problem: any time i use the special button to popup the special keys… Then it happen! This is reallu annoying then. Please a quick update?

Ah it seems to be an iOS 5.x bug — it works okay in iOS 6.0. I’ve made Codea use the old-style popover when running on iOS 5.x.

.@mpilgrem

That’s a fantastic example of RotationRate. Works really well.

Thank you for the documentation bug report — I’ve fixed these issues.

Having a second strip of keyboard keys is a good idea, though I’m finding that I like using the drag-to-select expression keys for symbolic input.

The next build will fix the bugs with iOS 5, keyboard gestures, and adds an extra row of keys to the expression popup. It will also allow hunt-and-peck style usage like the previous style popover.

.@mpilgrem what is supposed to be happening with your rotaterate examples? I have just a static image. I am in ipad1 and ios5.1

Hello @Jmv38. If the iPad1 does not have a gyroscope, the new RotationRate in beta 1.5(9) will not be meaningful. On an iPad2 or later, in my examples, the image ‘stays put’ when the iPad is tilted or rotated. (Edit) I’ve added the following to my examples:


if deviceMetrics().platformName == "iPad 1G" then
    print("This code needs a gyroscope."..
        " An iPad 1 does not have one.")
end

Ok! Thanks.

.@mpilgrem Strange, your example doesn’t work for me. When I rotate around more than one axis in turn and come back to the start then the cube doesn’t return to its initial state. Maybe different versions of iPads return their data differently.

Here’s what I came up after reading far more about Euler angles than I ever wanted to.

--displayMode(FULLSCREEN)
supportedOrientations(PORTRAIT)

function setup()

    parameter.watch("RotationRate")
    parameter.watch("rot")
    m = mesh()
    m.vertices = {
        vec3(0,0,0),
        vec3(1,0,0),
        vec3(0,1,0),
        vec3(0,0,0),
        vec3(0,1,0),
        vec3(0,0,1),
        vec3(0,0,0),
        vec3(0,0,1),
        vec3(1,0,0),
        vec3(1,0,0),
        vec3(0,1,0),
        vec3(0,0,1)
    }
    m.colors = {
        color(255, 255, 255, 255),
        color(255, 0, 0, 255),
        color(0, 255, 0, 255),
        color(255, 255, 255, 255),
        color(0, 255, 0, 255),
        color(0, 0, 255, 255),
        color(255, 255, 255, 255),
        color(0, 0, 255, 255),
        color(255, 0, 0, 255),
        color(255, 0, 0, 255),
        color(0, 255, 0, 255),
        color(0, 0, 255, 255)
    }
    rot = matrix()
end

function draw()
    background(40,40,50)
    perspective()
    camera(0,0,5,0,0,0,0,1,0)
    rot = rot:rotate(RotationRate.z,0,0,1)
    rot = rot:rotate(RotationRate.y,0,1,0)
    rot = rot:rotate(RotationRate.x,1,0,0)
    modelMatrix(rot:inverse())
    m:draw()
end

function touched(touch)
    if touch.state == ENDED then
        rot = matrix()
    end
end

I’m not convinced that I have the order of rotations right, it would be useful to know exactly what information RotationRate encodes (what I found on the Apple developer docs was useful but there are various things that it could be so it would be useful to know precisely what it is).

.@Andrew_Stacey RotationRate won’t allow you to accurately track the device rotation — the error will accumulate and it will eventually be off. I believe it is supposed to be used in conjunction with the other sensors (like accelerometer) to re-calibrate and reduce errors.

There is also the device attitude (CMAttitude in the iOS dev docs, if you’re interested) — this has a rotation matrix, quaternion, and yaw/pitch/roll representing the orientation of the device. It can be corrected by using a reference frame. This might be useful to expose in the future.

.@Simeon I was very excited to see RotationRate, until I started playing with and reading about it when I realised that it wasn’t what I wanted. Nonetheless, it’s better than nothing and even if it isn’t what I need to do I want to do, I’m sure I’ll find a use for it somewhere so learning about it seems a good thing to do.

What I do want is what the device attitude would give me. If you remember way way back to my “shape explorer”, I had it set up so that it would use Gravity to ensure that “down” was always “down”. What I’d also like to be able to do is also ensure that “left” was always “left”. At the moment, I could combine Gravity and RotationRate to give me something that worked to within a rough amount but then made it possible for the user to correct it. This would fix one inelegance in my current system: that when you go “over the top” then the picture swings wildly due to gravity coinciding (or being close to) the normal vector to the iPad.

So please, please do expose the device attitude. But also it would be good to know exactly what the RotationRate is saying. The above code seems to work to within reasonable error no matter how I turn my iPad (4th generation).

Hello @Andrew_Stacey. I think our two examples are mathematically similar, if not the same. I do not think there is a ‘correct’ order for the rotation operators - I was assuming that if the rotation angles were ‘small enough’ they would more or less commute and if they were not then reordering would not solve the problem.

.@Andrew_Stacey the problem with attitude is that it takes a portion of CPU power to compute the corrected rotation matrix. I’m assuming it combines accelerometer and gyroscope in some way to maintain a corrected rotation matrix (I don’t think there’s any extra data that it’s using — as the compass needs user calibration to work). Having it on and available all the time might not be a good thing.

Hello @Simeon. When you write up the RotationRate in-app documentation, you might also document the deviceMetrics() function and provide detecting an iPad 1G as a code example.

(Edit) Another passing thought on in-app documentation: Codea’s class() function - it is used so much, perhaps it deserves its own ‘Chapter’…

.@mpilgrem That’s not a safe assumption. Commutation vanishes to first order but is very definitely non-trivial at second order. So to make that assumption you need to know that second order is insignificant and the magnitude that I see is not sufficiently small for that assumption. More importantly, though, is the fact that you are accumulating the rotations as you are storing them in the vectors dir and dir2. So the errors combine and order becomes very significant.

I wonder if the difference in our code is to do with the initial orientation of the iPad. I notice that you rotate by y about the x-axis and -x about the y-axis. What orientation do you have the iPad in when you start your program?

.@Simeon so it’s no better (theoretically - I expect there would be some accuracy gains) than accumulating the RotationRate readings? That’s disappointing.

.@Andrew_Stacey it’s probably better than simply accumulating rotation rate readings, as there is probably some basic filtering and correction going on. But it draws from the same sensors.

After doing some more reading it does use the compass (if available and calibrated by the user) for certain reference frame settings:

CMAttitudeReferenceFrameXArbitraryCorrectedZVertical

Describes the same reference frame as CMAttitudeReferenceFrameXArbitraryZVertical except that the magnetometer, when available and calibrated, is used to improve long-term yaw accuracy. Using this constant instead of CMAttitudeReferenceFrameXArbitraryZVertical results in increased CPU usage.

.@mpilgrem Here’s my version of your code.

--
-- Cube Rotate
--

supportedOrientations(LANDSCAPE_LEFT)
function setup()
    local size = math.min(HEIGHT, WIDTH) / 2
    d = 1000
    m = cubeMesh(size)
    dirz = vec3(0, 0, 1)
    diry = vec3(0, 1, 0)
    dirx = vec3(1,0,0)
    mat = matrix()
end

function draw()
    background(0)
    perspective()
    local mat = matrix()
    local dxa = RotationRate.y
    local dya = - RotationRate.x
    local dza = RotationRate.z
    mat = mat:rotate(dza, dirz.x,dirz.y,dirz.z)
    mat = mat:rotate(dya, diry.x,diry.y,diry.z)
    mat = mat:rotate(dxa, dirx.x,dirx.y,dirx.z)
    dirx = mult(mat, dirx)
    diry = mult(mat, diry)
    dirz = mult(mat, dirz)
    local x = dirz.x * d
    local y = dirz.y * d
    local z = dirz.z * d
    camera(x, y, z, 0, 0, 0, diry.x, diry.y, diry.z)
    m:draw()
end

function cubeMesh(size)
    local v = {}
    local c = {}
    for i = 0, 7 do
        local x = (i % 2) * 2 - 1
        local y = (math.floor(i / 2) % 2) * 2 - 1
        local z = (math.floor(i / 4) % 2) * 2 - 1
        v[i] = vec3(x, y, z) * size / 2
        c[i] = color(127 + 127 * x, 127 + 127 * y, 127 + 127 * z)
    end
    local ver = {}
    local col = {}
    for v1 = 1, 3 do
        local v2 = v1 * 3 % 7
        local v3 = 7 - v1
        local v4 = 7 - v2
        local vt = {v[0], v[v1], v[v2], v[0], v[v3], v[v4], 
            v[7], v[v2], v[v1], v[7], v[v4], v[v3]}
        for i = 1, #vt do
            ver[(v1 - 1) * #vt + i] = vt[i]
        end
        local ct = {c[0], c[v1], c[v2], c[0], c[v3], c[v4], 
            c[7], c[v2], c[v1], c[7], c[v4], c[v3]}
        for i = 1, #ct do
            col[(v1 - 1) * #ct + i] = ct[i]
        end
    end
    local m = mesh()
    m.vertices = ver
    m.colors = col
    return m
end

function mult(mat, vec)
    local v1 = mat[1] * vec.x + mat[5] * vec.y + mat[9] * vec.z
    local v2 = mat[2] * vec.x + mat[6] * vec.y + mat[10] * vec.z  
    local v3 = mat[3] * vec.x + mat[7] * vec.y + mat[11] * vec.z
    return vec3(v1, v2, v3)    
end

Two main changes: the rotations are effected using the current axes, not the standard ones. And the matrix multiplication function is modified since OpenGL uses row vectors and not column vectors so multiplication is with the vector on the left. This now keeps the cube in what I think is “invariant position” on my iPad, even with large rotations, with no precession.

BETA 10 good to have the key picker working again on 5.1! Thanks. Dont forget to repair the ‘copy from output print fields’ functionnality. I had an error message but i cant copy it to paste it in the forum: annoying.

.@Simeon but it won’t be a “magic bullet”. There will still be the same accumulation errors, just smaller.

What about direct access to the compass? For providing a global frame of reference, then compass+gravity would be better than rotation rate. (Do non-3G iPads have a compass?)

.@Simeon did you notice my request about being able to load specific pages from a PDF via readImage?

.@Andrew_Stacey I did notice your request about specific pages. I need to make some modifications to the PDF reader to support this, but it’s possible. At the moment I’m focusing on completing the Shader Lab. But I’ll see if I can put it in for 1.5. If not it should be soon after.

On the compass. I’m unsure if non-3G iPads have them, but they need to be user calibrated (i.e., moved in a figure eight, so that the iPad can distinguish between local magnetic fields and the Earth’s magnetic field). The OS presents a calibration diagram and dismisses it if the user follows instructions (the app can optionally dismiss it after some time as well).

The CMAttitudeReferenceFrameXArbitraryCorrectedZVertical setting I mentioned above uses the compass to provide a global frame of reference for “long-term yaw accuracy”. But because it uses the compass it also requires a user calibration step.

non-3G iPads have a compass, but not GPS.