How to scale an object in order to defeat the effects of perspective?

Software: Away3D 4.x

Mr Margaret Scratcher, Sr. Member
Posted: 25 July 2012 11:19 PM   Total Posts: 344

Hi there, what I want to do, is to scale up an object so that as the camera moves away, it effectively remains the same size on screen.

I have no idea how to even go about starting this, beyond obviously scaling up with distance, but I’m assuming there is going to be some sort of ratio involved,related to the lens atributes.

Has anyone done anything similar?

   

Richard Olsson, Administrator
Posted: 26 July 2012 09:12 AM   Total Posts: 1192   [ # 1 ]

The easiest way to do this, if you don’t want to deal with the matrix or projection math, is to calculate the scale coefficient by projecting to vectors of known distance from each other, and calculating the scale factor between those.

For example, do this:

// Two points at different distance from the camera (1000u apart.)
v0 _view.camera.project(new Vector3D(010));
v1 _view.camera.project(new Vector3D(011000));
_scaleFactor = (v1.y/v0.y) / 1000

This calculates how much the size of the (0,1,0) vector changes depending on it’s distance from the camera, as a scale factor per unit of distance. It does so by first calculating how much it changes in 1000 units, and then dividing those 1000 units away from the outcome. Of course you don’t have to have the 1000 in there (you could replace it with 1) but the larger numbers make it slightly easier to debug if you need to trace the values, for example.

The variable _scaleFactor will now hold a value that you can multiply with the distance of your camera from your object to figure out how much to scale it. So you can do this:

scale = (_myObject._view.camera.z) * _scaleFactor;
_myObject.scaleX scale;
_myObject.scaleY scale;
_myObject.scaleZ scale

Note though that if the object is not symmetrical and in the center of the screen (the vanishing point) it will look to the eye as if the object or parts of it are moving up/down or left/right when you move the camera, because it’s distance from the vanishing point on the projection plane will change, but not it’s visible size.

If you do not want this effect, chances are that you actually do not want perspective projection at all. In that case, use the OrthographicLens for your camera instead, and then you can do away with all this scaling stuff because orthographic projection doesn’t scale with distance.

   

Mr Margaret Scratcher, Sr. Member
Posted: 26 July 2012 01:03 PM   Total Posts: 344   [ # 2 ]
Richard Olsson - 26 July 2012 09:12 AM

The easiest way to do this, if you don’t want to deal with the matrix or projection math, is to calculate the scale coefficient by projecting to vectors of known distance from each other, and calculating the scale factor between those.

For example, do this:

// Two points at different distance from the camera (1000u apart.)
v0 _view.camera.project(new Vector3D(010));
v1 _view.camera.project(new Vector3D(011000));
_scaleFactor = (v1.y/v0.y) / 1000

This calculates how much the size of the (0,1,0) vector changes depending on it’s distance from the camera, as a scale factor per unit of distance. It does so by first calculating how much it changes in 1000 units, and then dividing those 1000 units away from the outcome. Of course you don’t have to have the 1000 in there (you could replace it with 1) but the larger numbers make it slightly easier to debug if you need to trace the values, for example.

The variable _scaleFactor will now hold a value that you can multiply with the distance of your camera from your object to figure out how much to scale it. So you can do this:

scale = (_myObject._view.camera.z) * _scaleFactor;
_myObject.scaleX scale;
_myObject.scaleY scale;
_myObject.scaleZ scale

Note though that if the object is not symmetrical and in the center of the screen (the vanishing point) it will look to the eye as if the object or parts of it are moving up/down or left/right when you move the camera, because it’s distance from the vanishing point on the projection plane will change, but not it’s visible size.

If you do not want this effect, chances are that you actually do not want perspective projection at all. In that case, use the OrthographicLens for your camera instead, and then you can do away with all this scaling stuff because orthographic projection doesn’t scale with distance.

That seems to be exactly what i want, do I need to face the camera along the z axis to do that initial calculation or not?

I have played with the Orthographic lens, but in this case I have a 3d structure with the nodes indicated by spheres which I want to keep at a constant size on screen regardless of how close to the structure the camera is.

I’d seen the .project method mentioned a few times, but never gone in depth with it. Am I right in thinking that it can be used to position traditional 2d movieclips in a position relative to where a 3d vector appears on screen? That’s handy if so, as it solves part of another project that I am yet to tackle…

Many thanks once again!

   

Richard Olsson, Administrator
Posted: 26 July 2012 01:57 PM   Total Posts: 1192   [ # 3 ]

The camera has to be in it’s default position for the code to work flawlessly. The important thing is that the points that are used for evaluation are 1000 units apart along a line along which the camera points. The easiest way to achieve that is to do it while the camera is still in it’s default (0,0,-1000) position.

The project method projects a 3D point into screen space. The vector returned by Camera3D.project() contains the position of 3D point in view space.

In my code, I use it to take two points that are both on the same position in the XY plane (which is parallel to the viewport plane thanks to the position and orientation of the camera) and calculate how much they differ when displayed on the screen. That way I know how big of a screen distance that an extra 1000 units of distance from the camera corresponds to.

   

Mr Margaret Scratcher, Sr. Member
Posted: 26 July 2012 02:48 PM   Total Posts: 344   [ # 4 ]

Hmmm… So using your advice I thought I’d smashed it! Zooming in and out saw the node spheres remain at a constant size on screen.

http://www.margaretscratcher.co.uk/projects/3DFlashPlotter/constantNodeSize/index.html

(You can zoom in and out with the scrollwheel)

However, if I rotated the camera, the spheres grew and shrank on screen…


Here’s what I added to my code:

private function calcDistScale():void
  {
    
// Two points at different distance from the camera (1000u apart.)
   
var v0:Point view.camera.project(new Vector3D(010));
   var 
v1:Point view.camera.project(new Vector3D(011000));
   
_scaleFactor = (v1.v0.y) / 1000;  
   
   
trace ("scale Factor is " _scaleFactor);
  

 

and then every frame:

if (scaleNode)
   
{
     
for (var i:Number 0nodeArray.lengthi++)
     
{
      
var nodeToScale nodeArray[i].nodeMesh;
      
scale = (nodeToScale.view.camera.z) * _scaleFactor;
      
nodeToScale.scaleX scale;
      
nodeToScale.scaleY scale;
      
nodeToScale.scaleZ scale;  
      
//trace (nodeToScale.scaleX);
     
}
   } 

with scaleNode set to true.

   

Mr Margaret Scratcher, Sr. Member
Posted: 26 July 2012 02:52 PM   Total Posts: 344   [ # 5 ]

Forgot to add: the nodes were initially added to an OC3D, which were then in turn added to another OC3D. I tried adding their OC3Ds to the scene directly, and this does not seem to make a difference…

   

Richard Olsson, Administrator
Posted: 26 July 2012 03:24 PM   Total Posts: 1192   [ # 6 ]

Note that it’s the distance in 3D space from the camera to the object that should dictate it’s scale, not z position of the camera. So you will have to use Vector3D.distance() or something similar to calculate the distance, not just (nodeToScale.z - view.camera.z) which will only work if the camera is only allowed to move on the Z axis.

   

frank_, Newbie
Posted: 27 July 2012 01:02 PM   Total Posts: 12   [ # 7 ]

Doubt this makes a difference but doesn’t

view.camera.project() 

Return a Vector3D?

How are you translating this into a Point?

   

Mr Margaret Scratcher, Sr. Member
Posted: 27 July 2012 01:47 PM   Total Posts: 344   [ # 8 ]
Richard Olsson - 26 July 2012 03:24 PM

Note that it’s the distance in 3D space from the camera to the object that should dictate it’s scale, not z position of the camera. So you will have to use Vector3D.distance() or something similar to calculate the distance, not just (nodeToScale.z - view.camera.z) which will only work if the camera is only allowed to move on the Z axis.

OF COURSE!

*facepalm!*

   

frank_, Newbie
Posted: 27 July 2012 01:51 PM   Total Posts: 12   [ # 9 ]

I’ve implemented something similar but I’m getting strange results. Perhaps this is because I’m chaining the rotation and pos of my ObjectContainer3Ds

this is getting called on enterframe

private function adjustScale():void{
   
for(var i:int floorNumbers.length i++){
    
var scale:Number = (floorNumbers[i].view.camera.z) * returnScaleDist();
    
floorNumbers[i].scaleX scale;
    
floorNumbers[i].scaleY scale;
    
floorNumbers[i].scaleZ scale
   
}
  }
 
  
private function returnScaleDist():Number{
   
var v0:Vector3D view.camera.project(new Vector3D(010));
   var 
v1:Vector3D view.camera.project(new Vector3D(011000));
   
   
   return (
v1.v0.y) / 1000;  
  


These OC3D’s are getting put into the floorNumbers vector

private function returnFloorNum(aInt:int):ObjectContainer3D{    
var g:ObjectC ObjectContainer3D();
   
   
   var 
text:IBitmapDrawable ExtAssets.returnNumber(aInt);
   
   
   var 
bmd:BitmapData = new BitmapData(6464true0x000000);
   
bmd.draw(textnullnullnullnulltrue);
   var 
bmt:BitmapTexture = new BitmapTexture(bmd);
   
   var 
textMat:TextureMaterial = new TextureMaterial(bmt);
   
   
//front face
   
var pFront:PlaneGeometry = new PlaneGeometry(6464);
   var 
mFront:Mesh = new Mesh(pFronttextMat);
   
//pTest.doubleSided = true;
   
mFront.moveLeft(500);
   
mFront.moveUp(((aInt 105) + 2) - 350);    mFront.rotati    mFront.rotati    mFront.rotati
   
   g
.addChild(mFront);
   
//back face
   
var pBack:PlaneGeometry = new PlaneGeometry(6464);
   var 
mBack:Mesh = new Mesh(pBacktextMat);
   
mBack.moveLeft(500);
   
mBack.moveUp(((aInt 105) + 2) - 350);
   
mBack.rotati   mBack.rotati    mBack.rotati    
   g
.addChild(mBack);
   
   return 
g;
  
   

Mr Margaret Scratcher, Sr. Member
Posted: 27 July 2012 01:51 PM   Total Posts: 344   [ # 10 ]
frank_ - 27 July 2012 01:02 PM

Doubt this makes a difference but doesn’t

view.camera.project() 

Return a Vector3D?

How are you translating this into a Point?

I think it takes a vector3d and returns a point.

When I was initially trying to follow Richard’s advice I did:

var v0:Vector3D = view.camera.project(new Vector3D(0, 1, 0));

and got an error which mentioned that I was trying to assign a Vector3D to a Point (you know the one…), and after initially thinking ‘right I’m stuck again’ I decided to give it what it was asking for and all was fine.

 

   

frank_, Newbie
Posted: 27 July 2012 02:12 PM   Total Posts: 12   [ # 11 ]

In your case how are you rotating the camera?

I’m working on trying to recreate your results but it seems like my objects only change scale on rotation, not zoom.

this is my frame loop:

protected function handleRender(e:Event):void{
   
if(move){
    controller
.panAngle .3 * (stage.mouseX lastMouseX) + lastPanlAngel;
    
controller.tiltAngle .3 * (stage.mouseY lastMouseY) + lastTiltAngle;
    
//trace(controller.panAngle + " " + controller.tiltAngle);
    

   
}
   
   pointLight
.position cam.position;
   
view.render();
   
this.adjustScale();
   
  

and controller is a HoverController

controller = new HoverController(camnull03601500, -9090NaNNaN81false
   

Mr Margaret Scratcher, Sr. Member
Posted: 27 July 2012 05:01 PM   Total Posts: 344   [ # 12 ]

Hi there, I’m not doing anything with the controller, the only modification is the two bits of code I posted earlier - the first to determine and set the scale factor, and the second to find the distance from the object to the camera and scale accordingly.

I have now changed the second part to:

for (var i:Number 0nodeArray.lengthi++)
     
{
      
var nodeToScale nodeArray[i].nodeMesh;
      var 
dist:Number Vector3D.distance(camera.positionnodeToScale.position);
      
scale = (dist) * _scaleFactor;
      
nodeToScale.scaleX scale;
      
nodeToScale.scaleY scale;
      
nodeToScale.scaleZ scale;  
     


Which pretty much works, but you can still see that the nodes further away are smaller than the ones close up, especially if you zoom right in..

http://www.margaretscratcher.co.uk/projects/3DFlashPlotter/index.html

Is this to do ith my earlier question, about whether the camera needs to be positioned/angled a certain way to do the inital calculation?

   

Mr Margaret Scratcher, Sr. Member
Posted: 27 July 2012 05:09 PM   Total Posts: 344   [ # 13 ]

Just re-read… And triggered the calculation before the camera is positioned, whcih made a difference. This I realised that the camera is added to the scene, but the nodes etc are added to a container which is shifted down by an amount…. That’s probably what’s doing it smile

   

frank_, Newbie
Posted: 27 July 2012 05:13 PM   Total Posts: 12   [ # 14 ]

Thanks a lot for your help! I should have a good jumping off point now.

For some reason this code seems to be manipulating my OC3D’s scale when you rotate the camera but doesn’t seem to be changing anything when you adjust the FOV

   

Richard Olsson, Administrator
Posted: 28 July 2012 09:24 AM   Total Posts: 1192   [ # 15 ]

For it to update when you change the FOV, you have to redo the projection test (to calculate scale factor) every frame, as I hinted at in your other thread. This is because the projection test that is performed at init time by my first snippet of code in this thread will no longer be valid if you change the FOV.

   
   

X

Away3D Forum

Member Login

Username

Password

Remember_me



X