WorldWindEarth / WorldWindAndroid

The WorldWind Android "Community Edition" SDK (WWA-CE) includes the library, examples and tutorials for building 3D virtual globe applications for phones and tablets.
https://worldwind.earth/WorldWindAndroid/
Other
16 stars 7 forks source link

Fix navigator lookAt point calculation considering terrain elevation #9

Closed ComBatVision closed 5 years ago

ComBatVision commented 5 years ago

Now Navigator takes point on zero altitude as a root point for gesture. It makes gestures to rotate around point below the surface. It is wrong!

Camera should take lookAt point on the surface and the best way is just use pick terrain on the gesture begin.

    @Override
    protected LookAt cameraToLookAt(Globe globe, Camera camera, LookAt result) {
        this.cameraToViewingMatrix(globe, camera, this.modelview);

        // Pick terrain located behind the viewport center point
        PickedObject terrainPickedObject = wwd.pick(wwd.getViewport().width / 2f, wwd.getViewport().height / 2f).terrainPickedObject();
        if (terrainPickedObject != null) {
            // Use picked terrain position including approximate rendered altitude
            this.originPos.set(terrainPickedObject.getTerrainPosition());
            globe.geographicToCartesian(this.originPos.latitude, this.originPos.longitude, this.originPos.altitude, this.originPoint);
            globe.cartesianToLocalTransform(this.originPoint.x, this.originPoint.y, this.originPoint.z, this.origin);
        } else {
            // Center is outside the globe - use point on horizon
            this.modelview.extractEyePoint(this.forwardRay.origin);
            this.modelview.extractForwardVector(this.forwardRay.direction);
            this.forwardRay.pointAt(globe.horizonDistance(camera.altitude), this.originPoint);
            globe.cartesianToGeographic(this.originPoint.x, this.originPoint.y, this.originPoint.z, this.originPos);
            globe.cartesianToLocalTransform(this.originPoint.x, this.originPoint.y, this.originPoint.z, this.origin);
        }

        // Calculate look at rotation matrix (code from original Navigator implementation)
        this.modelview.multiplyByMatrix(this.origin);

        result.latitude = this.originPos.latitude;
        result.longitude = this.originPos.longitude;
        result.altitude = this.originPos.altitude;
        result.range = -this.modelview.m[11];
        result.heading = this.modelview.extractHeading(camera.roll); // disambiguate heading and roll
        result.tilt = this.modelview.extractTilt();
        result.roll = camera.roll; // roll passes straight through

        return result;
    }

It is also required to remove one line from BasicFrameController.renderTerrainPickedObject() method: this.pickPos.altitude = 0; // report the actual altitude, which may not lie on the terrain's surface This line reset picked point altitude calculated using current terrain details tesellation and do not allows to use pick functionality in Navigator.

Movement event should be executed via handler, because its listeners may call Navigator.getAsLookAt(), which use pick() and render next frame from inside of current frame causing recursion. Initial frame rendering must call stop event to initialize UI with initial navigator state.

class CustomNavigatorEventSupport extends NavigatorEventSupport {

    private Handler moveHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            onNavigatorMoved();
            return false;
        }
    });

    CustomNavigatorEventSupport(CustomWorldWindow wwd) {
        super(wwd);
    }

    @Override
    public void onFrameRendered(RenderContext rc) {
        if (this.listeners.isEmpty()) {
            return; // no listeners to notify; ignore the event
        }

        if (this.lastModelview == null) { // this is the first frame; copy the frame's modelview
            this.lastModelview = new Matrix4(rc.modelview);
            // Notify listeners with stopped event on first frame
            this.stopHandler.removeMessages(0 /*what*/);
            this.stopHandler.sendEmptyMessage(0 /*what*/);
        } else if (!this.lastModelview.equals(rc.modelview)) { // the frame's modelview has changed
            this.lastModelview.set(rc.modelview);
            // Notify the listeners of a navigator moved event.
            this.moveHandler.removeMessages(0 /*what*/);
            this.moveHandler.sendEmptyMessage(0/*what*/);
            // Schedule a navigator stopped event after a specified delay in milliseconds.
            this.stopHandler.removeMessages(0 /*what*/);
            this.stopHandler.sendEmptyMessageDelayed(0 /*what*/, this.stoppedEventDelay);
        }
    }

    @Override
    public void reset() {
        super.reset();
        this.moveHandler.removeMessages(0/*what*/);
    }

}
PJHogan commented 5 years ago

Sufaev Super Android Man! Nice work!!!

ComBatVision commented 5 years ago

Pull request was created. Hope you will enjoy this fix :)