away3d / away3d-core-fp11

Away3D engine for Flash Player 11
http://www.away3d.com
Other
640 stars 276 forks source link

Feature Request: userDistance property for optimal Sprite3D facing #289

Closed TrickyWidget closed 12 years ago

TrickyWidget commented 12 years ago

The ultimate intention of Sprite3D is to present an image that appears to be always facing the user of the program. Currently this is approximated by always facing the plane of the screen. I have already suggested that is should instead face the center of the screen for more accuracy. However, I've thought of what I believe would be a far more robust solution.

A Sprite3D should have something like a userDistance property. This is the distance behind the camera that Sprite3D orients itself to, which is where the user actually is. It is the estimated physical location of the user from the screen in virtual space. It could default to something approximating the average distance of a desktop user from their screen and common coordinate scales, or simply default to being disabled and face the center or plane of the screen. But it could be set to represent the specific scale that the developer is using and the distance they anticipate the user might be from the screen (perhaps somewhat different for a mobile device, for example). This would give the best chance for the Sprite3D to actually be facing the user.

For example:

// Default to face the center or plane of the screen
mySprite3D.userDistance = 0;
// Face a point 1,000 units behind the camera, estimating the physical location of the user
mySprite3D.userDistance = 1000;

I believe that this would provide the maximum fulfillment of the aesthetic intention of Sprite3D for a relatively small cost in complexity.

richardolsson commented 12 years ago

I'm afraid this doesn't really make sense to me. With (normal) computer screens, the displayed content is always projected onto the flat surface that is the screen. Moving your head around does not (need to) move the screen content around. Hence, Sprite3D should be oriented such that it looks as if it was flat on the screen, without any 3D perspective distortion. Orienting it towards a point behind the camera will give it such unwanted distortion.

Correct me if you believe I am wrong!

TrickyWidget commented 12 years ago

It is an unusual way of thinking about it, but I discovered this while working on the matter of facing for #284.

When the sprite faces the plane of the screen, it looks good until you get very close and begin to pass it to one side. At that point the fact that it is still facing "forward" without distortion makes it feel like you're passing a cardboard cutout. As it begins to enter the virtual equivalent of your peripheral vision, it needs to continue rotating towards the user to keep up the illusion until it passes off the screen.

As an interim, I implemented my own pseudo-Sprite3D using a PlaneGeometry Mesh and updating their facing manually in the render loop. In this, I made them face the center of the screen (the camera). This was a definite improvement over facing the plane of the screen as it eliminated the "cardboard" effect when passing them. However, I noticed that they still looked slightly odd up close.

After experimenting with it for a while, I realized that it was because that they were now turning slightly too early. While the error was much smaller than with the screen-faced sprites, they were still appeared slightly distorted right as I passed them because they were orienting themselves to a point several inches in front of me (the center of the physical screen) instead of towards my actual point of vision (my eyes).

Since I was manually facing the sprites anyway, I added the sort of offset that I've mentioned here to have them face my estimated distance behind the camera. Voila! The sprites looked really great. They faced me almost exactly all the time, even in close-up while passing them to one side.

Here is a demo of the difference. Use the arrow keys to move between the sprites. (The impact is more pronounced with a real image, so feel free to replace the colored boxes if you care to.) Admittedly, the difference is mostly a subtle one. But perhaps the most dramatic evidence is that after the regular sprite has vanished "behind" the camera, the trailing edge of the more realistically faced sprite continues past. Which is exactly what would happen if you passed an actual object occupying that space.

package 
{
    import away3d.containers.View3D;
    import away3d.entities.Mesh;
    import away3d.entities.Sprite3D;
    import away3d.materials.TextureMaterial;
    import away3d.primitives.PlaneGeometry;
    import away3d.textures.BitmapTexture;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.geom.Vector3D;

    [SWF (width="960", height="540")]

    public class Main extends Sprite 
    {
        public var
            view        :View3D = new View3D(),
            newSprite3d :Mesh;

        public function Main():void 
        {
            // Build a material for the sprites
            var surface:Sprite = new Sprite();
            surface.graphics.beginFill(0xff0000);
            surface.graphics.drawRect(0, 0, 128, 128);
            surface.graphics.beginFill(0x00ff00);
            surface.graphics.drawRect(0, 16, 128, 96);
            surface.graphics.beginFill(0x0000ff);
            surface.graphics.drawRect(0, 32, 128, 64);
            surface.graphics.beginFill(0xff0000);
            surface.graphics.drawRect(0, 48, 128, 32);
            var bitmapData:BitmapData = new BitmapData(128, 128);
            bitmapData.draw(surface);
            // Leave jagged edges to highlight distortion
            var material:TextureMaterial = new TextureMaterial(new BitmapTexture(bitmapData), false, false, false);

            // Add a traditional Sprite3D
            var oldSprite3d:Sprite3D = new Sprite3D(material, 128, 128);
            oldSprite3d.x = -128;
            view.scene.addChild(oldSprite3d);

            // Add a modified "Sprite3D" (with yUp false to fix bug 288)
            newSprite3d = new Mesh(new PlaneGeometry(128, 128, 1, 1, false), material);
            newSprite3d.x = 128;
            view.scene.addChild(newSprite3d);

            // Set up engine
            view.camera.z = -200;
            addChild(view);
            stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        }

        private function onEnterFrame(event:Event):void
        {
            // Face the new "Sprite3D" 1,000 units behind the camera
            newSprite3d.lookAt(new Vector3D(view.camera.x, view.camera.y, view.camera.z - 1000));
            newSprite3d.rotationY += 180;   // Flip plane over to fix bug 288

            view.render();
        }

        private function onKeyDown(event:KeyboardEvent):void
        {
            // Move the camera with the arrow keys
            if (event.keyCode == 38) view.camera.z += 5;
            if (event.keyCode == 40) view.camera.z -= 5;
        }
  } 
}
DerSchmale commented 12 years ago

Hi TrickyWidget,

I think you misinterpret what it is Sprite3D is supposed to do, tho I admit the docs can be a bit misleading, as it states it's "a renderable rectangular area that always faces the camera." "Facing the camera" doesn't mean "points directly at the camera". The doc means that the Sprite3D is rendered as a rectangle (thus no perspective transformation happens on it - which is traditionally what's meant by a 3D Sprite, or "billboards" - used for games like Doom or distant impostors). The reason for the existence of a Sprite3D object like this is purely for computational efficiency. In any case, I'll update the docs for clarity. To do what you suggest, use the LookAtController class and set the lookAtPosition object - which is in addition more flexible since you're not limited to orienting planar objects (which a different sort of Sprite3D).

Hope that helps clarify some things!