Entity Highlight and Fit to Zoom

1 2    next >
Marcellus Zebra
12/2/2011 9:18 AM

Hi,
two questions:

1) Is possible (End exists examples) implements entity highlight on mouse over?

2) How to implement a 'Fit to screen' function that maximize zoom for current control size? I want to use this function at loading time to show correctly the dwg and also add a 'Fit to screen' button as user tool.

Thank's very mutch,
marc.

rammi
12/2/2011 2:17 PM

Hi Marc,

regarding your second question

Marcellus Zebra wrote:

2) How to implement a 'Fit to screen' function that maximize zoom for current control size? I want to use this function at loading time to show correctly the dwg and also add a 'Fit to screen' button as user tool.

That is simple. Calculate the bounds of the current view, possibly including a transformation matrix if the view is rotated or projected perspectively. Use

C# Code:
       int width, height; // view size
       DxfModel model; // the model
       Matrix4D transform; // current transformation used for displaying the model

       BoundsCalculator boundsCalculator = new BoundsCalculator(graphicsConfig);
       boundsCalculator.GetBounds(model, transform);
       Bounds3D bounds = boundsCalculator.Bounds;

       if (bounds.Initialized) {
         const int margin = 1; // margin around border
         Matrix4D scaleTransform = DxfUtil.GetScaleTransform(
           bounds.Corner1,  // lower left model
           bounds.Corner2,  // upper right model
           bounds.Center,   // center model
           new Point3D(margin, height - margin - 1, 0d), // lower left screen
           new Point3D(width - margin - 1, margin, 0d),  // upper right screen
           new Point3D(0.5*(height -1), 0.5*(width - 1), 0d) // center screen
         );
         // new transform
         transform = scaleTransform * transform;
       }

The only tricky part is the -1 on width and height, which is because eg you want to map the right border of the model to the rightmost pixel column, and that has address width-1 and not width. Many people get that wrong. The above code is centering the view, and you can it use something similar to center on anything else, as long as you know its bounds or how to calculate them. BoundsCalculator will help in many cases.

Wout
12/5/2011 11:02 AM

Hi,

Assuming you are working with Win Forms:

About 1), there is currently no canned solution to this, but there have been a few enhancements in CadLib 4.0 this week that make this easier. First you have to find out which entity the mouse is over, you can use the EntitySelector for this (already exists). Then you can use the new WireframeGraphicsCache class that's a drawables container for entities. You'll have to change the WireframeGraphicsCache.Config to use a fixed foreground color.

C# Code:
      WireframeGraphicsCache wireframeGraphicsCache = new WireframeGraphicsCache(false, true);

The cache object has a method IList<IWireframeDrawable> GetDrawables(RenderedEntityInfo renderedEntityInfo), in which you can pass the rendered entity info you got from the EntitySelector. So now you have the drawables. Now call GDIGraphics3D.CreateGraphicsFactory(), and use this to draw the entity drawables on.

C# Code:
      IWireframeGraphicsFactory graphicsFactory = gdiGraphics3D.CreateGraphicsFactory();
      foreach (IWireframeDrawable drawable in drawablesSelection) {
        drawable.Draw(graphicsFactory);
      }

The code is fairly new, and the api might change a little in the near future. As I'm writing this, it might be nice to have a way to override a color, so the WireframeGraphicsCache can be used for filling the GDIGraphics3D drawables as well (saves a bit of duplicate work). E.g. (future):

C# Code:
      WireframeGraphicsCache wireframeGraphicsCache = new WireframeGraphicsCache(false, true);
      DxfLayout layout = model.ActiveLayout;
      DxfEntityCollection entities;
      Matrix4D vportTransform = Matrix4D.Identity;
      DateTime start = DateTime.UtcNow;
      if (layout == null || model.Header.ShowModelSpace) {
        DxfVPort activeVPort = model.VPorts.GetActiveVPort();
        if (activeVPort != null) {
          vportTransform = activeVPort.GetTransform(new Size2D(ClientSize.Width, ClientSize.Height)) * vportTransform;
        }
        vportTransform = Matrix4D.Identity;
        wireframeGraphicsCache.CreateDrawables(model, vportTransform);
      } else {
        wireframeGraphicsCache.CreateDrawables(model, layout, null);
      }
      IWireframeGraphicsFactory graphicsFactory = gdiGraphics3D.CreateGraphicsFactory();
      foreach (IWireframeDrawable drawable in wireframeGraphicsCache.Drawables) {
        drawable.Draw(graphicsFactory);
      }

- Wout

Marcellus Zebra
12/5/2011 11:17 AM

Hi rammi,
thank's very mutch.
Sorry but it is first time I works with cad and my questions can be really stupids.
I Used your code in WinFormsExtendedViewExample as:

C# Code:
public void FitToScreen()
    {
      int width = this.ClientRectangle.Width, height = this.ClientRectangle.Height; // view size
      //DxfModel model; // the model
      Matrix4D transform = CalculateTo2DTransform(); // current transformation used for displaying the model

      BoundsCalculator boundsCalculator = new BoundsCalculator();
      boundsCalculator.GetBounds(model, transform);
      Bounds3D bounds = boundsCalculator.Bounds;

      if (bounds.Initialized)
      {
        const int margin = 1; // margin around border
        Matrix4D scaleTransform = WW.Cad.Base.DxfUtil.GetScaleTransform(
          bounds.Corner1, // lower left model
          bounds.Corner2, // upper right model
          bounds.Center,  // center model
          new Point3D(margin, height - margin - 1, 0d), // lower left screen
          new Point3D(width - margin - 1, margin, 0d), // upper right screen
          new Point3D(0.5 * (height - 1), 0.5 * (width - 1), 0d) // center screen
        );
        // new transform
        transform = scaleTransform * transform;

        //My Code
        gdiGraphics3D.To2DTransform = transform;
        Invalidate();
      }
    }

And I tested it with attatched dwg.
Two problems appears:

1) Fitted image is fplipped!
2) Moving fitted image all returns to pre-fitted execution!

Thank's marc.

rammi
12/5/2011 1:22 PM

Hi Marcellus,

my fault, not yours. I mixed together two examples, but didn't test the code.

Use

C# Code:
        Matrix4D scaleTransform = WW.Cad.Base.DxfUtil.GetScaleTransform(
          bounds.Corner1, // upper left model (transform inverts Y!)
          bounds.Corner2, // lower right model (transform inverts Y!)
          bounds.Center,  // center model
          new Point3D(margin, margin, 0d), // upper left screen
          new Point3D(width - margin - 1, height - margin - 1, 0d), // lower right screen
          new Point3D(0.5 * (height - 1), 0.5 * (width - 1), 0d) // center screen
        );

Background:
The underlying problem is the fact that in DXF/DWG a right-handed coordinate system is used, so when looking at the X-Y plane X is pointing right and Y is pointing up. But screen coordinates work different: X is pointing right, but Y is pointing down. So at some point in the process the Y coordinate has to switch its direction. In this case the basic transformation already takes care of that, but in my former code the scaling transformation took care of it, too. So the Y axis was flipped around twice.

Sorry for the confusion,
Rammi

Marcellus Zebra
12/5/2011 2:10 PM

Thank's very mutch rammi.
Now work's good.
But, how to fix the transformation?

I apply calculated transofr with gdiGraphics3D.To2DTransform = transform; but now if I pan or zom all start from previous view!

Thank's
marc.

rammi
12/6/2011 5:39 PM

Yes, if panning and zooming use the standard code of the examples each of the operations will set To2DTransform without regards of the intermediate transformation. So you'd have to rewrite that, too.

Please excuse, but in order to be more general I'll elaborate a bit now, hopefully giving more users the chance to brew their own implementation.

Linear Algebra
Let's starting with a bit of linear algebra background, I'll add a simple example later. It's usually a good idea to think of the transformation from the model space of the DXF file to the screen space of the final display as not one complex transformation, but a chain of simpler one. So if we call the matrix used for To2DTransform M, it is build from simpler transformations (eg translations, rotations and scalings):

  M = Mn * ... * M2 * M1

You'll note that the indexes are running backward, the reason for that is that CadLib is using column orders, so the order in which transformations in the chain above are applied is right to left.
It's helpful to imagine that you are in model space at the right border, and then go step by step to the left, until you reach screen space:

  screen space ← Mn ← ... ← M2 ← M1 ← model space

(you could go the other way round if you invert each transformation)

One main property of matrixes and transformations is that their multiplication (chaining) is not commutative, so in general for two matixes A and B:

  A * B ≠ B * A
  
which makes math a bit unusual, but not really complicated, you'll just need some new tricks in your toolbox.

No you want to change the transformation M, and this changes usually either happen in screen space (eg panning three pixels to the right) or in model space (panning from one entity to another). It's crucial to be clear about this space in which the change happens.

If a changing transformation C happens in screen space, this means that the previous transformation is multiplied from the left, so you get

  Mnew = Cscreen * M

If it happens in model space, it is multiplied from the right:

  Mnew = M * Cmodel

But how to apply this to a chain of transformations? As said before, you'll usually have simpler transformations put together in your chain, and if C is eg a translation, you'll want to combine it with the translation matrix in your chain. Let's call this matrix of interest T, multiply all matrixes left of it together to get a matrix L, multiply all matrixes right of it together to get a matrix R, we have the situation

  M = L * T * R

This is still perfectly general, but in some case L or R might be the indenty matrix, and could be left away.

Now back to a transformation happening in screen space:

  Mnew = C * M = C * L * T * R

This is what we have. But what we want is

  Mnew = L * Tnew * R

i.e. only T is influenced by C, the rest stays as it was before.

So we have to solve this equation

  C * L * T * R = L * Tnew * R

Remember that we cannot just exchange C and L on the left side, because that is not allowed for matrixes. But we can multiply with the inverse of R (i.e. R-1) from right on both sides, which will remove R from the equation, because R * R-1 is the identity matrix:

  C * L * T  = L * Tnew

Now we use the same trick to remove L from the right side of the equation: multiply both sides with L-1 from left (and switch sides to get a more pleasing equation):

  Tnew = L-1 * C * L * T

That is how we calculate a new matrix  Tnew in our chain when a transformation happens in screen space:

  • multiply up all matrixes to the left of it (gives L)
  • invert this (gives L-1)
  • use the above equation to calculate Tnew
The corresponding equation for transformations happening in model space is

  Tnew = T * R * C * R-1
  
You should be able to create that yourself, which might be a good thing to try.

A Simple Example

Not lets assume that our chain of simple transformations consists of a translation matrix T(x,y,z) and a scaling matrix S(s,-s,s) (-s is just here because we know that the y axis is inverting its direction between model and screen). This is suffice for most 2D programs.
Hint: you can easily create translation and scale matrixes in CadLib by using one of the static methods in WW.Math.Geometry.Transformation4D. And as inversion of a matrix is a costly operation as a side note let's remember that the inverses of simple matrixes can be calculated more easily directly:
  T-1(x,y,z) = T(-x,-y,-z)
  S-1(s,-s,s) = S(1/s,-1/s,1/s)
But when using this don't forget that when inverting a chain of matrixes you'll have to invert their order, too:
  (A * B)-1 = B-1 * A-1
(eg when you have a translation followed by a rotation, to undo it you'll have first do the reverse rotation, then the reverse translation)

What else: screen coordinates always run from 0 to screenWidth-1 for x and 0 to screenHeight-1 for y, and the screen center sc has coordinates (in 3D)
  sc = [0.5*(screenWidth-1), 0.5*(screenHeight-1), 0.0]

Only thing left to decide is the order in which we want to apply the simple operations T and S. There is not much difference, but some operation may be slightly easier one way or the other. Here we'll first use T, then S. So our complete To2DTransform M becomes
  M = S(s,-s,s) * T(x,y,z) = S * T
with some scaling factor s and some x,y,z translation.

Region to screen
First thing needed is how to make a given region fit the screen. This is usually necessary intially to display the whole model and maybe in between to zoom to a given entity. Let's assume the the bounding box of the region has a width of w and a height of h (use the Delta property of Bounds3D for that), and that its center is at bc. To make this box fill the whole screen it has to be scaled. The scaling necessary to fill out the screen horizontally is
  shor = screenWidth / w
Vertically it's
  svert = screenHeight / h;

We don't want to scale differently in x and y, because it would distort the view, so we'll have to use the same scaling in both directions. And to keep everything visible we have to choose the smaller of both scalings:
  s = min(shor, svert)
So now we have our scaling:
  S = S(s,-s,s)

The translation is a bit more difficult. It's easier to do in two steps. First we will move the bounding box so its center is at the origin of the coordinate system. This is done with the translation
  Tbbox = T(-bcx, -bcy, -bcz)
The we'll do the scaling with the S from above, because scalings keep the origin, so the center of the scaled bbox stays at the origin. This already gives the bbox the correct size, but it is now centered at the screen origin [0,0] (i.e. the upper left corner). But we want it centered at the screen's center, which is easily achieved by another translation
  Tscreen = T(scx, scy, 0)

This gives the complete transformation as

  M = Tscreen * S * Tbbox

Comparing with the desired form

  M = S * T

we can again use the equation like we did before

  S * T = Tscreen * S * Tbbox

and get

  T = S-1 * Tscreen * S * Tbbox

Panning by mouse
When we use the mouse movement [mx,my], we can easily convert that into the translation Tmouse = T(mx,my,0). Remembering our linear algebra mouse movement is happening in screen space, we have to multiply from the right, so we have

  Mnew = Tmouse * M = Tmouse * S * T = S * Tnew

and can calculate the new translation matrix

  Tnew = S-1 * Tmouse * S * T

Zooming around a given screen coordinate
This happens when a zooming rectangle is used, or when zooming by mouse wheel around the mouse position, and even when just zooming around the center of the screen. The zooming factor f is easily calculated, it's either just a fix factor near to 1  (maybe multiplied by wheel clicks) or in case of the rectangle a ratio between screen width and rect width (or their heights).

Scaling is always happening around the origin of the coordinate system, so in order to keep the desired mouse operation fix we have do a bit more steps:

  • Move the fix mouse position to the origin: T(-mx, -my, 0)
  • Do the scaling around the origin: S(f)
  • Move back to the fix mouse position: T(mx, my, 0)
So
  Mnew = T(mx, my, 0) * S(f) * T(-mx, -my, 0) * M = T(mx, my, 0) * S(f) * T(-mx, -my, 0) * S * T

Because the change includes both a scaling and a translation we'll have to calculate a new scaling Snew and a new translation Tnew:
  Snew * Tnew = T(mx, my, 0) * S(f) * T(-m_x, -my, 0) * S * T

We have to apply our old trick from above more than once, but if we do we'll get
  Snew = S(f) * S
  Tnew = T' * T' * T
with
  T' = S-1 * T(-mx, -my, 0) * S
  T' = S-1new * T(mx, my, 0) * Snew

The End
Okay, this was a lot of stuff, but it should give you all the building blocks necessary to create a finetuned handling of the model to screen transformation. I didn't put these things into code, because you'd be stuck again as soon as you'd like to add something new like rotation. But with the knowledge above it should not be too difficult.

Good luck,
Rammi

JoseM
11/1/2016 7:47 PM

Hello Marc

If you want to reset the object transform this is my code:

C# Code:
            int width = this.ClientRectangle.Width;
            int height = this.ClientRectangle.Height;

            //Reset transform
            transformationProvider.ResetTransforms();
            //Reset Events (Opcional) I don't know if it's necessary.
            zoomWheelInteractor.ResetState();
            rectZoomInteractor.ResetState();
            panInteractor.ResetState();

            Matrix4D transform = CalculateTo2DTransform(); //Transformacion actual del dibujo por pantalla del modelo

            BoundsCalculator boundsCalculator = new BoundsCalculator();
            boundsCalculator.GetBounds(model, transform);
            Bounds3D bounds = boundsCalculator.Bounds;

            if (bounds.Initialized)
            {
                const int margin = 5; 

                Matrix4D scaleTransform = WW.Cad.Base.DxfUtil.GetScaleTransform(
                 bounds.Corner1, // upper left model (transform inverts Y!)
                  bounds.Corner2, // lower right model (transform inverts Y!)
                  bounds.Center,  // center model
                  new Point3D(margin, margin, 0d), // upper left screen
                  new Point3D(width - margin - 1, height - margin - 1, 0d), // lower right screen
                  new Point3D(0.5 * (width - 1), 0.5 * (height - 1), 0d) // center screen
                );

                // new transform
                transform = scaleTransform * transform;

                //My Code
                gdiGraphics3D.To2DTransform = transform;

                from2DTransform = gdiGraphics3D.To2DTransform.GetInverse();

                //Caché
                Invalidate();


Is tested full ok!

sarge
3/29/2017 6:53 PM

Hi,

I knew this thread is old but I didn't find my issue.

At first some images:
1. with the fiting method above
2. if I perform pan

Most code is from the exmaples with some fitting to my purposes. I think the problem will come from the transformation provider and the scaling. For now it's nearly impossible to find that point where it happens (using VS2017CE) maybe with the ep edtion it esay to find ..

Rammi mentioned above "Yes, if panning and zooming use the standard code of the examples each of the operations will set To2DTransform without regards of the intermediate transformation. So you'd have to rewrite that, too."

I think this snippet is the cause of my problem:

C# Code:
private Matrix4D CalculateTo2DTransform()
        {
            transformationProvider.ViewWindow = GetClientRectangle2D();
            Matrix4D to2DTransform = Matrix4D.Identity;
            if (model != null && bounds != null)
                to2DTransform = transformationProvider.CompleteTransform;
            gdiGraphics3D.To2DTransform = to2DTransform;
            from2DTransform = gdiGraphics3D.To2DTransform.GetInverse();
            return to2DTransform;
        }

Any how I have to avoid that "correct value" in gdiGrahics3D will changed by the "wrong value" from transformprovider. But I don't have any idea how to achive this.

I suggestion, sample code or (non math) explaination would help me alot.

kind regards


Wout
3/29/2017 7:02 PM

Which TransformationProvider are you using? The 2D one or the 3D? The 3D has some margin around the drawing by default to allow for 3D rotation within the window.

- Wout

1 2    next >