Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Jagodas · May 14, 2017 at 09:54 PM · 2dzoomorthographicfloating

Faking orthographic zoom by scaling positions

I'm working on a 2D scene that displays a randomised solar system to scale. For display purposes, I divide the position (measured in metres, don't worry, I'm storing it as a double) of a planet by a zoom level, so that on starting a planet 1AU from it's star is 1 unit away in the game. I use a floating origin and do all my maths as doubles to avoid floating point errors.

In order to zoom, I adjust the zoom level, which changes the game position of the planet, giving a pseudo-orthographic zoom effect. I'm not actually using the orthographic zoom because the orthographic size becomes tiny at the planet scale.

However, I'm finding that my camera position doesn't change enough as I zoom, it always is bit less than it's supposed to be. Zooming towards is also implemented here, but the problem is there with or without. I expect that whatever is under the mouse to remain under the mouse, but whatever is under the mouse always ends up moving further away from the solar system origin than the camera. I've been scratching my head for ages now trying to figure out what I'm doing wrong. I don't doubt that there are many things. The relevant code attached to my Main Camera is below:

 private void Update()
 {
     //zoom according to mouse input
     if (Input.GetAxis("Mouse ScrollWheel") > 0)
     {
         ZoomOrthoCamera(cam.ScreenToWorldPoint(Input.mousePosition), true);
     }
 
     if (Input.GetAxis("Mouse ScrollWheel") < 0)
     {
         ZoomOrthoCamera(cam.ScreenToWorldPoint(Input.mousePosition), false);
     }
 
     //pan according to input
     float panHorizontal = Input.GetAxis("Horizontal");
     float panVertical = Input.GetAxis("Vertical");
     transform.position += new Vector3(panHorizontal * panSpeed, panVertical * panSpeed, 0f);
 }

The place where the magic happens (I'm using a double version of Vector3 called Vector3d and then converting back to Vector3 after all the maths, for accuracy):

 private void ZoomOrthoCamera(Vector3 zoomToward, bool isZoomingIn)
 {
     //change solar system zoom level by a fraction rather than a fixed amount since we want to change orders of magnitude
     SolarSystemView.instance.zoomLevel -= (isZoomingIn ? 1 : -1) * SolarSystemView.instance.zoomLevel * ActualZoomFactor(isZoomingIn);
 
     //since the solar system might not be at the game origin due to floating origin, we find our camera's position from the solar system's centre
     Vector3d vectorToCentre = new Vector3d(transform.position.x, transform.position.y, 0) - SolarSystemView.instance.positiond;
 
     //scale our solary system camera coordinates according to the new zoom level
     Vector3d newRelativePosition = new Vector3d(
         vectorToCentre.x + (isZoomingIn ? 1 : -1) * vectorToCentre.x * ActualZoomFactor(isZoomingIn),
         vectorToCentre.y + (isZoomingIn ? 1 : -1) * vectorToCentre.y * ActualZoomFactor(isZoomingIn),
         MagicNumbers.mainCameraPos.z
         );
 
     //convert solar system camera coordinates back into game coordinates
     Vector3d newVectorToCentre = new Vector3d(newRelativePosition.x, newRelativePosition.y, 0) + SolarSystemView.instance.positiond;
 
     //zoom towards mouse location
     Vector3d zoomTowardPrecise = new Vector3d(zoomToward.x, zoomToward.y);
 
     //since the solar system might not be at the game origin due to floating origin, we find our zoom towards position from the solar system's centre
     Vector3d zoomVectorToCentre = new Vector3d(zoomTowardPrecise.x, zoomTowardPrecise.y, 0) - SolarSystemView.instance.positiond;
 
     //scale our solar system zoom towards coordinates according to the new zoom level
     Vector3d zoomNewRelativePosition = new Vector3d(
         zoomVectorToCentre.x + (isZoomingIn ? 1 : -1) * zoomVectorToCentre.x * ActualZoomFactor(isZoomingIn),
         zoomVectorToCentre.y + (isZoomingIn ? 1 : -1) * zoomVectorToCentre.y * ActualZoomFactor(isZoomingIn),
         MagicNumbers.mainCameraPos.z
         );
 
     //convert solar system zoom towards coordinates back into game coordinates
     Vector3d zoomNewVectorToCentre = new Vector3d(zoomNewRelativePosition.x, zoomNewRelativePosition.y, 0) + SolarSystemView.instance.positiond;
 
     //find the vector we need to move the camera along to zoom towards the mouse position
     Vector3d zoomTranslation = (zoomNewVectorToCentre - newVectorToCentre) * ActualZoomTowardsFactor(isZoomingIn);
 
     //finally get new camera position in game coordinates
     transform.position = new Vector3(
         (float)(newVectorToCentre.x + (isZoomingIn ? 1 : -1) * zoomTranslation.x),
         (float)(newVectorToCentre.y + (isZoomingIn ? 1 : -1) * zoomTranslation.y),
         MagicNumbers.mainCameraPos.z
         );
 }

The extra functions referenced in the above (different in case they needed to be treated differently):

 private double ActualZoomFactor(bool isZoomingIn)
     {
         //we want to be able to get back to inital zoom level when we zoom out so do some maths that allows this 
         return isZoomingIn ? zoomFraction : zoomFraction / (1d - zoomFraction);
     }
     
     private double ActualZoomTowardsFactor(bool isZoomingIn)
     {
         //we want to be able to get back to inital zoom level when we zoom out so do some maths that allows this 
         return isZoomingIn ? zoomFraction : zoomFraction / (1d - zoomFraction);
     }

Much obliged for any help or advice given!

Comment
Add comment · Show 7
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image AlwaysSunny · May 14, 2017 at 09:54 PM 0
Share

You properly formatted your code! I could hug you. Trying to push these Q's through moderation. If nobody comes along to help, tag me in a comment and I'll give this a closer look. However, this Q is significantly more complicated than what we normally get at UA. It's probably better suited to the scripting forum, or perhaps a relevant StackExchange forum. Best,

avatar image Jagodas AlwaysSunny · May 21, 2017 at 03:44 PM 0
Share

I'll try where you recommend, since there's been no response so far. Thanks though!

avatar image Glurth · May 21, 2017 at 10:30 PM 0
Share

" I use a floating origin and do all my maths as doubles to avoid floating point errors."

Don't let this throw you off: Any non-interger numbering system with a limited number of digits will always result in precision errors. While the precision error for double is much, much smaller than for floats, precision errors ARE introduced.

"In order to zoom, I adjust the zoom level, which changes the game position of the planet," Looking at your code, it looks like you only change the position of the Camera.

"I expect that whatever is under the mouse to remain under the mouse" Hmm, this sounds tricky... I'm not quite sure how you would deter$$anonymous$$e the vector to move the camera such that a particular world point stays at the same (off-center) screen coordinate, it might even be a curve. I'll think about it, but expect it will need to use the camera's field of view angle somehow.

Edit: ok, much easier than expected with an Orthographic Camera. Not sure why you don't want to use orthographicSize; made it pretty easy (if this DOES work for you, let me know and I'll convert it to an answer):

 public float zoomSpeed = 0.1f;
 private void ZoomOrthoCamera(Vector3 zoomToward, bool isZoo$$anonymous$$gIn)
 {
     float negSpeed = zoomSpeed * (isZoo$$anonymous$$gIn ? 1 : -1);
     Camera cam = this.GetComponent<Camera>();
     cam.orthographicSize -= cam.orthographicSize * negSpeed;
     Vector3 camToTarget = zoomToward - transform.position;
     transform.position += (camToTarget* negSpeed);
 }

avatar image Glurth Glurth · May 21, 2017 at 11:39 PM 0
Share

Here is a version that can do fake scaling, leaving the orthagraphicSize untouched. It uses a parent object on which to apply the scale (rather than applying the scaling to the transform of each object in view.) The fake zoom will only work right for objects that are children of this parent object. This camera must ALSO be made a child of this object for it to work right.

 public float zoomSpeed = 0.1f;
     public bool fakeZoomWithScale=false;
     public Transform fakeZoomParentObject;// all objects that need to be scaled, and this camera object, should be children of this transform.
     private void ZoomOrthoCamera(Vector3 zoomToward, bool isZoo$$anonymous$$gIn)
     {
         float negSpeed = zoomSpeed * (isZoo$$anonymous$$gIn ? 1 : -1);
         Vector3 camToTarget = zoomToward - transform.position;
         if (!fakeZoomParentObject)
         {
             Camera cam = GetComponent<Camera>();
             cam.orthographicSize -= cam.orthographicSize * negSpeed;
         }
         else
         {
             if (fakeZoomParentObject != null)
                 fakeZoomParentObject.localScale += fakeZoomParentObject.localScale * negSpeed;
         }
         transform.position += (camToTarget * negSpeed);
     }

avatar image Jagodas Glurth · May 22, 2017 at 06:10 PM 0
Share

"ok, much easier than expected with an Orthographic Camera. Not sure why you don't want to use orthographicSize; made it pretty easy"

$$anonymous$$y first approach was pretty much identical to the first snippet of code you posted. $$anonymous$$y motivation for not doing it this way was because I found myself ending up with orthographic sizes on the order of 10e-7 and less, and then my sprites all looked hideous. I toyed about with changing the clipping planes, but that didn't help.

Your second code snippet is pretty crafty, but alas I use the scaling of my objects to adjust sprite sizes. I also use line renderers to draw the orbits of the planets/moons/asteroids etc.

"Looking at your code, it looks like you only change the position of the Camera."

I did indeed neglect to post code from anywhere but my main camera's script. I use a modified version of the script found here to change the position of my SolarSystemView, which is the ultimate parent of all my individual planets etc., and they all change position/size relative to that "origin" as per the following:

 public Vector3d GetDisplayedBodyPosition(CelestialBody cb)
 {
     return cb.position / (UInt64)zoomLevel;
 }
 
 public float GetDisplayedSize(CelestialBody cb)
 {
     return (float)(cb.radius / zoomLevel);
 }

Reading over your comment, I suspect that you are right in that I should stick with the normal orthographic camera, and use tricks elsewhere to achieve my goals. This changes the focus of the original question somewhat, but would there be anything you'd recommend?

avatar image Glurth Jagodas · May 22, 2017 at 07:39 PM 0
Share

"alas I use the scaling of my objects to adjust sprite sizes. I also use line renderers to draw the orbits of the planets/moons/asteroids etc."

hmm, not quite clear why this is an issue: if you make the sprites ALSO children of the same parent object, they will scale along with with the planets, and everything else that's a child of the object. I would think the global position of the planets (as opposed to localPosition), would yield the correct coordinates of the scaled planets- what goes wrong with them when using that second snippet?
Possibly helpful for that: $$anonymous$$eep in $$anonymous$$d, even if working with an object that is NOT a child of the object in question: you can still transform a point using that object's transform: check out transform.TransformPoint()

I understand now why you are doing the fake zoom: and it makes sense to avoid those odd camera scales. But I would think the main problem is designating a full Astronomical Unit Distance, a value of 1.0 units. If you made an AU say... 10,000 units ins$$anonymous$$d, you would be able to use much more reasonable scales in the camera when looking at a planet or star.
If you plan on... say... zoo$$anonymous$$g out to show the whole galaxy (or zoo$$anonymous$$g in to show humans on the surface) ALSO, a zoom change of $$anonymous$$ANY orders of magnitude, only THEN I could understand the need for using the fake scaling.

Show more comments

1 Reply

· Add your reply
  • Sort: 
avatar image
1
Best Answer

Answer by Glurth · May 23, 2017 at 09:58 PM

Here is a version that can do fake scaling, leaving the orthagraphicSize untouched. It uses a parent object on which to apply the scale (rather than applying the scaling to the transform of each object in view.) The fake zoom will only work right for objects that are children of this parent object. This camera must ALSO be made a child of this object for it to work right.

  public float zoomSpeed = 0.1f;
      public bool fakeZoomWithScale=false;
      public Transform fakeZoomParentObject;// all objects that need to be scaled, and this camera object, should be children of this transform.
      private void ZoomOrthoCamera(Vector3 zoomToward, bool isZoomingIn)
      {
          float negSpeed = zoomSpeed * (isZoomingIn ? 1 : -1);
          Vector3 camToTarget = zoomToward - transform.position;
          if (!fakeZoomParentObject)
          {
              Camera cam = GetComponent<Camera>();
              cam.orthographicSize -= cam.orthographicSize * negSpeed;
          }
          else
          {
              if (fakeZoomParentObject != null)
                  fakeZoomParentObject.localScale += fakeZoomParentObject.localScale * negSpeed;
          }
          transform.position += (camToTarget * negSpeed);
      }

I would expect problems designating a full Astronomical Unit Distance, a value of 1.0 "units". If you made an AU say... 10,000 "units" instead, you would be able to use much more reasonable scales in the camera when looking at a planet or star.

In other words (and different numbers): If a moon, is the smallest object you can see - it's radius of 1,000miles should probably be "less than, but near 1.0" in your "units"... maybe a radius of 0.001f (a small number, but not unreasonably so), a planet can have a radius of about 0.004f (4,000 mi), and a star can have a radius of about .40f (400,000 mi). Planetary distances would be 93e3f for one AU, but still gets pretty high at 2.8e6f (for Neptune's orbital radius of 30AU)
This RANGE does indeed make single-precision floats insufficient. E.g. Adding a moon's radius to the position of Neptune, using a float, will have NO effect on the position, it's too small (relatively) to register (float's can work, -with a possible loss of accuracy- with ratios of at most, about 1 in 8.3e6- or [2^23]). The double precision SHOULD be sufficient (working ratio: aprox 1 in 4.5e15!! [2^52]). This also seems like a good reason to do the fake zoom.

Regarding the parent scaling object: yes, all children objects will be scaled by this object's transform. So, if you have say.. sprites, scaled to your planet, by making it a child of the planet: When you scale the root object, both the sprite AND the object will be scaled.

Keep in mind the "final" or Global scale of the object in a transform hierarchy is the computed "lossyScale" (readonly), not the localScale (these are the same only when the localScale object has no parents, or all the parents have an even scaling of 1.0f).

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Jagodas · May 24, 2017 at 08:03 AM 0
Share

That's really helpful, thanks so much!

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

5 People are following this question.

avatar image avatar image avatar image avatar image avatar image

Related Questions

2D camera zoom in comparison to the height of target 2 Answers

Zoom out in orthographic view? 2 Answers

Dynamic Orthagraphic Camera Zoom 1 Answer

2d orthographic camera follow 1 Answer

ScreenToWorldPoint not accurate 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges