Question

I'm trying to add the maths to control a panorama with the gyroscope and struggling. This is a mobile app built in AS3.

I've got the data coming through from the gyroscope (x and y), and I've got the current angle (pan and tilt). What I want to do is update the cameraController with the new angle based on the data from the gyro.

I've been attempting to convert the Javascript I found on https://github.com/fieldOfView/krpano_fovplugins/blob/master/gyro/source/gyro.source.js into Actionscript 3, and it kind of works - but not really.

EDIT

Thanks I tried those changes and I added camera roll back in because the euler maths needed it, it runs but there is something wrong with the Maths.

The panorama only seems to drift up and left, after a while of moving the phone the other way it drifts down, and then moves right.

Can you see anything important I'm missing from the Javascript?

import com.adobe.nativeExtensions.GyroscopeEvent;

import flash.events.Event;
import flash.geom.Orientation3D;

public class GyroscopeMaths
{
    public function GyroscopeMaths()
    {
        super();
    }

    private var isTopAccessible:Boolean = false;
    private var isDeviceAvailable:Boolean;
    private var isEnabled:Boolean = false;
    private var vElasticity:Number = 0;
    private var isVRelative:Boolean = false;
    private var isCamRoll:Boolean = false;
    private var friction:Number = 0.5;
    private var isTouching:Boolean = false;
    private var validSample:Boolean = false;
    private var firstSample:* = null;
    private var hOffset:Number = 0;
    private var vOffset:Number = 0;
    private var hLookAt:Number = 0;
    private var vLookAt:Number = 0;
    private var camRoll:Number = 0;
    private var vLookAtNow:Number = 0;
    private var hLookAtNow:Number = 0;
    private var hSpeed:Number = 0;
    private var vSpeed:Number = 0;
    private var vElasticSpeed:Number = 0;
    private var camRollNow:Number;
    private var pitch:Number;
    private var yaw:Number;
    private var altYaw:Number;
    private var factor:Number;
    private var degRad:Number = Math.PI / 180;

    public function handleDeviceOrientation(x:Number, y:Number, z:Number):void {
        // Process event.alpha, event.beta and event.gamma
        var orientation:* = rotateEuler({
            "yaw":y * degRad,
            "pitch":x * degRad,
            "roll": z * degRad
        });
        yaw = wrapAngle(orientation.yaw / degRad);
        pitch = orientation.pitch / degRad;
        altYaw = yaw, factor;
        hLookAtNow = Pano.instance.pan;
        vLookAtNow = Pano.instance.tilt;
        hSpeed = hLookAtNow - hLookAt,
            vSpeed = vLookAtNow - vLookAt;

        // Ignore all sample until we get a sample that is different from the first sample
        if (!validSample) {
            if (firstSample == null) {
                firstSample = orientation;
            } else {
                if (orientation.yaw != firstSample.yaw || orientation.pitch != firstSample.pitch || orientation.roll != firstSample.roll) {
                    firstSample = null;
                    validSample = true;
                    if (isVRelative) {
                        vOffset = -pitch;
                    }
                }
            }
            return;
        }

        // Fix gimbal lock
        if (Math.abs(pitch) > 70) {
            altYaw = y;


            var altYaw:Number = wrapAngle(altYaw);
            if (Math.abs(altYaw - yaw) > 180) {
                altYaw += (altYaw < yaw) ? 360 :-360;
            }

            var factor:Number = Math.min(1, (Math.abs(pitch) - 70) / 10);
            yaw = yaw * (1 - factor) + altYaw * factor;

            //camRoll *= (1 - factor);
        }

        // Track view change since last orientation event
        // ie:user has manually panned, or krpano has altered lookat
        hOffset += hSpeed;
        vOffset += vSpeed;

        // Clamp vOffset
        if (Math.abs(pitch + vOffset) > 90) {
            vOffset = (pitch + vOffset > 0) ? (90 - pitch) :(-90 - pitch)
        }

        hLookAt = wrapAngle(-yaw - 180 + hOffset);
        vLookAt = Math.max(Math.min((pitch + vOffset), 90), -90);

        // Dampen lookat
        if (Math.abs(hLookAt - hLookAtNow) > 180) {
            hLookAtNow += (hLookAt > hLookAtNow) ? 360 :-360;
        }

        hLookAt = (1 - friction) * hLookAt + friction * hLookAtNow;
        vLookAt = (1 - friction) * vLookAt + friction * vLookAtNow;

        if (Math.abs(camRoll - camRollNow) > 180) {
            camRollNow += (camRoll > camRollNow) ? 360 :-360;
        }

        camRoll = (1 - friction) * camRoll + friction * camRollNow;

        var wAh:Number = wrapAngle(hLookAt);
        Pano.instance.panoGyroChange(wAh, vLookAt);
        //krpano.view.camroll = wrapAngle(camRoll);

        if (vOffset != 0 && vElasticity > 0) {
            if (vSpeed == 0) {
                if (vElasticity == 1) {
                    vOffset = 0;
                    vElasticSpeed = 0;
                } else {
                    //  vElasticSpeed = 1 - ((1 - vElasticSpeed) * krpano.control.touchfriction);
                    vOffset *= 1 - (Math.pow(vElasticity, 2) * vElasticSpeed); // use Math.pow to be able to use saner values

                    if (Math.abs(vOffset) < 0.1) {
                        vOffset = 0;
                        vElasticSpeed = 0;
                    }
                }
            } else {
                vElasticSpeed = 0;
            }
        }
    }

    private function rotateEuler(euler:Object):Object {
        // This function is based on http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToMatrix/index.htm
        // and http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm
        trace(euler);
        var heading:Number;
        var bank:Number;
        var attitude:Number;
        var ch:Number = Math.cos(euler.yaw);
        var sh:Number = Math.sin(euler.yaw);
        var ca:Number = Math.cos(euler.pitch);
        var sa:Number = Math.sin(euler.pitch);
        var cb:Number = Math.cos(euler.roll);
        var sb:Number = Math.sin(euler.roll);

        var matrix:Array = [
            sh * sb - ch * sa * cb, -ch * ca, ch * sa * sb + sh * cb,
            ca * cb, -sa, -ca * sb,
            sh * sa * cb + ch * sb, sh * ca, -sh * sa * sb + ch * cb
        ]; // Note:Includes 90 degree rotation around z axis

        /* [m00 m01 m02] 0 1 2
        * [m10 m11 m12] 3 4 5
        * [m20 m21 m22] 6 7 8 */

        if (matrix[3] > 0.9999) {
            // Deal with singularity at north pole
            heading = Math.atan2(matrix[2], matrix[8]);
            attitude = Math.PI / 2;
            bank = 0;
        } else if (matrix[3] < -0.9999) {
            // Deal with singularity at south pole
            heading = Math.atan2(matrix[2], matrix[8]);
            attitude = -Math.PI / 2;
            bank = 0;
        } else {
            heading = Math.atan2(-matrix[6], matrix[0]);
            bank = Math.atan2(-matrix[5], matrix[4]);
            attitude = Math.asin(matrix[3]);
        }

        return {
            yaw:heading,
            pitch:attitude,
            roll:bank
        };
    }

    private function wrapAngle(value:Number):Number {
        value = value % 360;
        return (value <= 180) ? value :value - 360;
    } // wrap a value between -180 and 180
    //function stringToBoolean(value:Number):String 
    //{ return (String("yesontrue1").indexOf( String(value:Number) ) >= 0) };
}
Was it helpful?

Solution

Without the rest of your program, I won't be able to compile this, but I've corrected what syntactical errors I could find. That said, if you can program in either JS or As3, you should be able to follow the logic and write your own class (they're both EMCAScript). Continue pursuing that until you've arrived at a more solid problem than "it doesn't work" (which is generally a question no one wants to answer).

private var isTopAccessible:Boolean = false;
private var isDeviceAvailable:Boolean;
private var isEnabled:Boolean = false;
private var vElasticity:Number = 0;
private var isVRelative:Boolean = false;
private var isCamRoll:Boolean = false;
private var friction:Number = 0.5;
private var isTouching:Boolean = false;
private var validSample:Boolean = false;
private var firstSample:* = null;
private var hOffset:Number = 0;
private var vOffset:Number = 0;
private var hLookAt:Number = 0;
private var vLookAt:Number = 0;
private var camRoll:Number = 0;
private var vLookAtNow:Number = 0;
private var hLookAtNow:Number = 0;
private var hSpeed:Number = 0;
private var vSpeed:Number = 0;
private var vElasticSpeed:Number = 0;
private var camRollNow:Number;
private var pitch:Number;
private var yaw:Number;
private var altYaw:Number;
private var factor:Number;
private var degRad:Number = Math.PI / 180;

public function handleDeviceOrientation(x:Number, y:Number):void {
    // Process event.alpha, event.beta and event.gamma
    var orientation:* = rotateEuler({
        "yaw":y * degRad,
        "pitch":x * degRad,
        "roll":0
    });
    yaw = wrapAngle(orientation.yaw / degRad);
    pitch = orientation.pitch / degRad;
    altYaw = yaw, factor;
    hLookAtNow = Pano.instance.pan;
    vLookAtNow = Pano.instance.tilt;
    hSpeed = hLookAtNow - hLookAt,
    vSpeed = vLookAtNow - vLookAt;

    // Ignore all sample until we get a sample that is different from the first sample
    if (!validSample) {
        if (firstSample == null) {
            firstSample = orientation;
        } else {
            if (orientation.yaw != firstSample.yaw || orientation.pitch != firstSample.pitch || orientation.roll != firstSample.roll) {
                firstSample = null;
                validSample = true;
                if (isVRelative) {
                    vOffset = -pitch;
                }
            }
        }
        return;
    }

    // Fix gimbal lock
    if (Math.abs(pitch) > 70) {
        altYaw = y;

        /*switch(deviceOrientation) {
            case 0:
                if ( pitch>0 )
                altYaw += 180;
                break;
            case 90:
                altYaw += 90;
                break;
            case -90:
                altYaw += -90;
                break;
            case 180:
                if ( pitch<0 )
                altYaw += 180;
                break;
        }*/

        var altYaw:Number = wrapAngle(altYaw);
        if (Math.abs(altYaw - yaw) > 180) {
            altYaw += (altYaw < yaw) ? 360 :-360;
        }

        var factor:Number = Math.min(1, (Math.abs(pitch) - 70) / 10);
        yaw = yaw * (1 - factor) + altYaw * factor;

        //camRoll *= (1 - factor);
    }

    // Track view change since last orientation event
    // ie:user has manually panned, or krpano has altered lookat
    hOffset += hSpeed;
    vOffset += vSpeed;

    // Clamp vOffset
    if (Math.abs(pitch + vOffset) > 90) {
        vOffset = (pitch + vOffset > 0) ? (90 - pitch) :(-90 - pitch)
    }

    hLookAt = wrapAngle(-yaw - 180 + hOffset);
    vLookAt = Math.max(Math.min((pitch + vOffset), 90), -90);

    // Dampen lookat
    if (Math.abs(hLookAt - hLookAtNow) > 180) {
        hLookAtNow += (hLookAt > hLookAtNow) ? 360 :-360;
    }

    hLookAt = (1 - friction) * hLookAt + friction * hLookAtNow;
    vLookAt = (1 - friction) * vLookAt + friction * vLookAtNow;

    if (Math.abs(camRoll - camRollNow) > 180) {
        camRollNow += (camRoll > camRollNow) ? 360 :-360;
    }

    camRoll = (1 - friction) * camRoll + friction * camRollNow;

    var wAh:Number = wrapAngle(hLookAt);
    Pano.instance.panoGyroChange(wAh, vLookAt);
    //krpano.view.camroll = wrapAngle(camRoll);

    if (vOffset != 0 && vElasticity > 0) {
        if (vSpeed == 0) {
            if (vElasticity == 1) {
                vOffset = 0;
                vElasticSpeed = 0;
            } else {
                //  vElasticSpeed = 1 - ((1 - vElasticSpeed) * krpano.control.touchfriction);
                vOffset *= 1 - (Math.pow(vElasticity, 2) * vElasticSpeed); // use Math.pow to be able to use saner values

                if (Math.abs(vOffset) < 0.1) {
                    vOffset = 0;
                    vElasticSpeed = 0;
                }
            }
        } else {
            vElasticSpeed = 0;
        }
    }
}

private function rotateEuler(euler:Object):Object {
    // This function is based on http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToMatrix/index.htm
    // and http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm
    trace(euler);
    var heading:Number;
    var bank:Number;
    var attitude:Number;
    var ch:Number = Math.cos(euler.yaw);
    var sh:Number = Math.sin(euler.yaw);
    var ca:Number = Math.cos(euler.pitch);
    var sa:Number = Math.sin(euler.pitch);
    var cb:Number = Math.cos(euler.roll);
    var sb:Number = Math.sin(euler.roll);

    var matrix:Array = [
        sh * sb - ch * sa * cb, -ch * ca, ch * sa * sb + sh * cb,
        ca * cb, -sa, -ca * sb,
        sh * sa * cb + ch * sb, sh * ca, -sh * sa * sb + ch * cb
    ]; // Note:Includes 90 degree rotation around z axis

    /* [m00 m01 m02] 0 1 2
     * [m10 m11 m12] 3 4 5
     * [m20 m21 m22] 6 7 8 */

    if (matrix[3] > 0.9999) {
        // Deal with singularity at north pole
        heading = Math.atan2(matrix[2], matrix[8]);
        attitude = Math.PI / 2;
        bank = 0;
    } else if (matrix[3] < -0.9999) {
        // Deal with singularity at south pole
        heading = Math.atan2(matrix[2], matrix[8]);
        attitude = -Math.PI / 2;
        bank = 0;
    } else {
        heading = Math.atan2(-matrix[6], matrix[0]);
        bank = Math.atan2(-matrix[5], matrix[4]);
        attitude = Math.asin(matrix[3]);
    }

    return {
        yaw:heading,
        pitch:attitude,
        roll:bank
    };
}

private function wrapAngle(value:Number):Number {
    value = value % 360;
    return (value <= 180) ? value :value - 360;
} // wrap a value between -180 and 180
//function stringToBoolean(value:Number):String 
//{ return (String("yesontrue1").indexOf( String(value:Number) ) >= 0) };
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top