Zoom invariant / Position Invariant entity

To create a zoom invariant entity, meaning an entity that stays the same size on screen regardless of the camera position and zoom, you can use the following mesh-derived class.

 

Update 1: The example has been improved to support also for Position Invariance, so the entity stays at a fixed position on-screen (see the line 

 transf * // Comment this line if the position invariance is not needed

in the PreDraw() method).

 

Update 2: The example has been improved so that there is no more need to specify a custom scene size to avoid the clipping by the camera planes: they are dynamically adjusted as the entity is drawn (see the AdjustCameraPlanes() method).

class ZoomInvariantMesh : Mesh
{
    public ZoomInvariantMesh(Mesh copy)
        : base(copy)
    {

    }

    private Point3D ObjectRealPosition;

    public ViewportLayout vp { get; set; }

    public override bool IsInFrustum(PlaneEquation[] frustum, Point3D center, double radius, bool quick)
    {
        // so it's not discarded when it goes out of the viewport
        return true;
    }

    protected override void DrawSilhouettes(DrawSilhouettesParams data)
    {
        PreDraw(data);

        double[] prevmvproj = data.ModelViewProj;

        data.ModelViewProj = Utility.MultMatrixd(data.RenderContext.CurrentModelViewMatrix(), data.RenderContext.CurrentProjectionMatrix());
        base.DrawSilhouettes(data);
        PostDraw(data);

        data.ModelViewProj = prevmvproj;
    }


    protected override void DrawForShadow(RenderParams data)
    {
        //base.DrawForShadow(data);
    }

    private void PreDraw(DrawParams data)
    {
        data.RenderContext.PushMatrices();

        float multFactor = 50; // The multiplying factor depends on the model size
        float screenToWorld = (float)ComputePreciseScreenToWorld(data) * multFactor;  // Uncomment this line for a precise factor.

        ////////////////////////////////////// For position invariance

        var plane = data.Viewport.Camera.NearPlane;
        plane.Origin = ObjectRealPosition;

        Point3D pt;

        // Compute the world position of the screen point where we want to draw our entity (in this case 50, 50)
        vp.ScreenToPlane(new System.Drawing.Point(50, 50), plane, out pt);

        Translation transf = new Translation(pt.X - ObjectRealPosition.X, pt.Y - ObjectRealPosition.Y,
            pt.Z - ObjectRealPosition.Z);
        ///////////////////////////////////////

        var transformation =  
            transf *  // Comment this line if the position invariance is not needed
            new Translation(ObjectRealPosition.X, ObjectRealPosition.Y, ObjectRealPosition.Z) * // move back to its position 
            new Scale(screenToWorld, screenToWorld, screenToWorld) * // scale  
            new Translation(-ObjectRealPosition.X, -ObjectRealPosition.Y, -ObjectRealPosition.Z); // Move to the Origin   ;

        data.RenderContext.MultMatrixModelView(transformation); // Move to the Origin                                 

        AdjustCameraPlanes(data, transformation);

        // Comment this line if don't want the entity to go over the geometry in the scene
        data.RenderContext.SetViewport(data.Viewport.GetViewFrame(), 0, (float)RenderContextBase.OverlayDepthRange);
    }

    /// <summary>
    /// Adjusts the Camera planes to avoid the clipping of the scaled symbol.
    /// </summary>
    /// <param name="data"></param>
    /// <param name="transformation"></param>
    private void AdjustCameraPlanes(DrawParams data, Transformation transformation)
    {
        if (data.Viewport.Camera is MyCamera)
        {
            MyCamera camera = (MyCamera) data.Viewport.Camera;

            var pts = Utility.GetBoundingBoxCorners(BoxMin, BoxMax);

            for (int i = 0; i < pts.Length; i++)
                    
                pts[i] = transformation * pts[i];

            double min = double.MaxValue;
            double max = double.MinValue;

            var normal = camera.ViewNormal;
            for (int i = 0; i < pts.Length; i++)
            {
                var vec = Vector3D.Subtract(camera.Location, pts[i]);
                double dist = Vector3D.Dot(vec, normal);
                if (dist < min)
                    min = dist;

                if (dist > max)
                    max = dist;
            }
            if (camera.Near > min || camera.Far < max)
            {
                if (camera.ProjectionMode == projectionType.Perspective && (min < 0 || max < 0))
                    return;

                var projMat = camera.GetProjectionMatrix(min, max, ViewportLayout.CameraEyePosType.Center);
                restoreMatrix = true;
                data.RenderContext.PushProjection();
                data.RenderContext.SetProjectionMatrix(projMat);
            }
            else
                restoreMatrix = false;
        }
    }


    private bool restoreMatrix;

    double ComputePreciseScreenToWorld(DrawParams data)
    {
        ObjectRealPosition = (BoxMin + BoxMax) / 2;

        double winX, winY, winZ;
        devDept.Eyeshot.Viewport view = data.Viewport;
        view.Project((int)vp.Height, ObjectRealPosition.X, ObjectRealPosition.Y, ObjectRealPosition.Z, out winX, out winY, out winZ);

        Point3D[] pts = view.UnProject(new Point3D[] { new Point3D(0, 0, winZ), new Point3D(1, 0, winZ) }, (int)vp.Height);

        return Point3D.Distance(pts[0], pts[1]);
    }

    private void PostDraw(DrawParams data)
    {
        data.RenderContext.PopMatrices();

        // Comment this line if don't want the entity to go over the geometry in the scene
        data.RenderContext.SetViewport(data.Viewport.GetViewFrame(), (float)RenderContextBase.OverlayDepthRange, 1);

        if (restoreMatrix)
        {
            data.RenderContext.PopProjection();
            restoreMatrix = false;
        }
    }

    protected override void DrawSelected(DrawParams data)
    {
        PreDraw(data);

        base.DrawSelected(data);

        PostDraw(data);
    }

    protected override void DrawForSelection(DrawParams data)
    {
        PreDraw(data);

        base.DrawForSelection(data);

        PostDraw(data);
    }

    protected override void Draw(DrawParams data)
    {
        PreDraw(data);

        base.Draw(data);

        PostDraw(data);
    }

    protected override void DrawWireframe(DrawParams data)
    {
        PreDraw(data);

        base.DrawWireframe(data);

        PostDraw(data);
    }

    protected override void Render(RenderParams data)
    {
if (data.PlanarReflections) // Requires Eyeshot >= 9.0.320
return;
PreDraw(data); base.Render(data); PostDraw(data); } protected override void DrawEdges(DrawParams data) { return; // not supported } protected override void DrawIsocurves(DrawParams data) { //not supported } }

 

Then you need to derive the Camera class in order to expose the GetProjectionMatrix method:

        class MyCamera : Camera
        {
            public MyCamera(Camera another) : base(another)
            {
            }

            public double[] GetProjectionMatrix(double nearDistance, double farDistance, ViewportLayout.CameraEyePosType cameraEyePos)
            {
                return base.GetProjectionMatrix(nearDistance, farDistance, cameraEyePos);
            }
        }

 

Then use it like this:

        public void MyFunc(ViewportLayout vp)
        {
            vp.Viewports[0].Camera = new MyCamera(vp.Viewports[0].Camera);
            Mesh Arrow = Mesh.CreateArrow(0.2, 0.4, 0.4, 0.8, 16, Mesh.natureType.Smooth);
            Arrow.ColorMethod = colorMethodType.byEntity;
            Arrow.Color = Color.Blue;
            Arrow.Rotate(Math.PI/2, Vector3D.AxisZ);
            Arrow.Translate(new Vector3D(0, 0, 0));
            ZoomInvariantMesh aArrow = new ZoomInvariantMesh(Arrow);
            aArrow.vp = vp;

            aArrow.Translate(1, 2, 3);
            vp.Entities.Add(aArrow, 0);
        }

 

Update 3: 

Starting from build 9.0.364 it's possible to extend the zoom-invariance also to the BlockReference with this code (the methods PreDraw(), PostDraw(), ComputePreciseScreenToWorld(), AdjustCameraPlanes() are omitted because they are the same as in the ZoomInvariantMesh class): 

        class ZoomInvariantBlockRef : BlockReference
        {
            public ZoomInvariantBlockRef(BlockReference copy, ViewportLayout vp)
                : base(copy)
            {
                this.vp = vp;
            }

            private Point3D ObjectRealPosition;

            public ViewportLayout vp { get; set; }

            public override bool IsInFrustum(PlaneEquation[] frustum, Point3D center, double radius, bool quick)
            {
                // so it's not discarded when it goes out of the viewport
                return true;
            }

            protected override void DrawForSelection<T>(ViewportLayout.DrawForSelectionParams<T> myParams, ViewportLayout.drawForSelectionCallback<T> drawCall)
            {
                PreDraw(myParams.GfxParams);

                base.DrawForSelection(myParams, drawCall);

                PostDraw(myParams.GfxParams);
            }

            protected override void Draw<T, Q>(ViewportLayout.DrawEntitiesParams<T, Q> myParams, ViewportLayout.drawCallback<T, Q> drawCall)                                                                    
            {
                if (myParams.GfxParams.PlanarReflections) // Requires Eyeshot >= 9.0.320
                    return;

                PreDraw(myParams.GfxParams);

                double[] prevmvproj = null;
                DrawSilhouettesParams p = null;

                if (myParams.GfxParams is DrawSilhouettesParams)
                {
                    p = myParams.GfxParams as DrawSilhouettesParams;
                    prevmvproj = p.ModelViewProj;

                    p.ModelViewProj = Utility.MultMatrixd(myParams.ViewportLayout.renderContext.CurrentModelViewMatrix(), myParams.ViewportLayout.renderContext.CurrentProjectionMatrix());
                }

                base.Draw(myParams, drawCall);

                PostDraw(myParams.GfxParams);

                if (myParams.GfxParams is DrawSilhouettesParams)
                    p.ModelViewProj = prevmvproj;
            }

            protected override bool DrawTrianglesForShadowMap(DrawShadowParams myParams)
            {
// not supported return true; } protected override void DrawForShadow(RenderParams data) {
// not supported //base.DrawForShadow(data); } }

 

Note 1: 

When deriving entities other than the Mesh or the Solid, since their Render() method actually calls the Draw() it's important not to override their Render() method in order to avoid issues in the Rendered DisplayMode (due to the fact that the PreDraw() and PostDraw() would be called twice).

Note 2:

For the Line entity, the PostDraw() method must be like this:

void PostDraw(DrawParams data)
{
data.RenderContext.EndDrawBufferedLines();
data.RenderContext.PopMatrices();

// Comment this line if don't want the entity to go over the geometry in the scene
data.RenderContext.SetViewport(data.Viewport.GetViewFrame(), depthRange, 1);
}

 

Note 3:

The calls to SetViewport() in the PreDraw() and PostDraw() methods make the zoom invariant geometry go over the other geometry in the scene (like the OriginSymbol). If this is not desired they can be removed.

Have more questions? Submit a request

11 Comments

  • 0
    Avatar
    ilyaz yılmaz

    Dear Sir ,

     

    I want to make same for line class.

    I just changed class name and I write all codes same.

    But it doesnt work.

    How can I do zoom invarian Line?

     

    class ZoomInvariantLine : Line

    {

    public ZoomInvariantLine(Line copy)

    : base(copy)

    {

    }

    private Point3D ObjectRealPosition;

    public ViewportLayout vp { get; set; }

    private const float depthRange = 0.001f;

    private void PreDraw(DrawParams data)

    {

    data.RenderContext.PushMatrices();

    float screenToWorld = (float)ComputePreciseScreenToWorld(data) * 50; // Uncomment this line for a precise factor

    data.RenderContext.MultMatrixModelView(

    new Translation(ObjectRealPosition.X, ObjectRealPosition.Y, ObjectRealPosition.Z) * // move back to its position

    new Scale(screenToWorld, screenToWorld, screenToWorld) * // scale

    new Translation(-ObjectRealPosition.X, -ObjectRealPosition.Y, -ObjectRealPosition.Z)); // Move to the Origin

     

    // Comment this line if don't want the entity to go over the geometry in the scene

    data.RenderContext.SetViewport(data.Viewport.GetViewFrame(), 0, depthRange);

    }

    void PostDraw(DrawParams data)

    {

    data.RenderContext.PopMatrices();

    // Comment this line if don't want the entity to go over the geometry in the scene

    data.RenderContext.SetViewport(data.Viewport.GetViewFrame(), depthRange, 1);

    }

    double ComputePreciseScreenToWorld(DrawParams data)

    {

    ObjectRealPosition = (BoxMin + BoxMax) / 2;

    double winX, winY, winZ;

    devDept.Eyeshot.Viewport view = data.Viewport;

    view.Project((int)vp.Height, ObjectRealPosition.X, ObjectRealPosition.Y, ObjectRealPosition.Z, out winX, out winY, out winZ);

    Point3D[] pts = view.UnProject(new Point3D[] { new Point3D(0, 0, winZ), new Point3D(1, 0, winZ) }, (int)vp.Height);

    return Point3D.Distance(pts[0], pts[1]);

    }

    protected override void DrawSelected(DrawParams data)

    {

    PreDraw(data);

    base.DrawSelected(data);

    PostDraw(data);

    }

    protected override void DrawForSelection(DrawParams data)

    {

    PreDraw(data);

    base.DrawForSelection(data);

    PostDraw(data);

    }

    protected override void Draw(DrawParams data)

    {

    PreDraw(data);

    base.Draw(data);

    PostDraw(data);

    }

    protected override void DrawWireframe(DrawParams data)

    {

    PreDraw(data);

    base.DrawWireframe(data);

    PostDraw(data);

    }

    protected override void Render(RenderParams data)

    {

    PreDraw(data);

    base.Render(data);

    PostDraw(data);

    }

    protected override void DrawEdges(DrawParams data)

    {

    return; // not supported

    }

    protected override void DrawIsocurves(DrawParams data)

    {

    //not supported

    }

    protected override void DrawSilhouettes(DrawSilhouettesParams data)

    {

    PreDraw(data);

    base.DrawSilhouettes(data);

    PostDraw(data);

    }

    }

     

  • 0
    Avatar
    Luca Cornazzani

    Hi,

     

    did you read the Note about not overriding the Render method?

  • 0
    Avatar
    ilyaz yılmaz

    Yes.Actulayy Melissa told me I canceled override...

    But know I realized another problem...

    I draw two lines (cross symbol) When I make zoom cross line doesn't intersect each others.

     

     

  • 0
    Avatar
    Luca Cornazzani

    You have to remove the overrides on Render() and DrawWireFrame(), then use this version of the PostDraw() method:

    void PostDraw(DrawParams data)

    {

    data.RenderContext.EndDrawBufferedLines();

    data.RenderContext.PopMatrices();

    // Comment this line if don't want the entity to go over the geometry in the scene

    data.RenderContext.SetViewport(data.Viewport.GetViewFrame(), depthRange, 1);

    }

     

  • 0
    Avatar
    Luca Cornazzani

    You have to remove the overrides on Render() and DrawWireFrame(), then use this version of the PostDraw() method:

    void PostDraw(DrawParams data)

    {

    data.RenderContext.EndDrawBufferedLines();

    data.RenderContext.PopMatrices();

    // Comment this line if don't want the entity to go over the geometry in the scene

    data.RenderContext.SetViewport(data.Viewport.GetViewFrame(), depthRange, 1);

    }

     

  • 0
    Avatar
    ilyaz yılmaz

     data.RenderContext.EndDrawBufferedLines();

     

    EndDrawBufferedLines function doesn't seem in my version.I use Professional version 8.0.265...

     

    Best regards,

  • 0
    Avatar
    Luca Cornazzani

    Please update to the latest stable version (8.0.367).

  • 0
    Avatar
    ilyaz yılmaz

    Thanks I will try update...

    Best regards,

  • 0
    Avatar
    James Trounson

    This works great. Now I'm trying to make a "LeaderAndText" zoom invariant.

    Or I could avoid all of this if I could create many of the origin Coordinate symbol icons and display them anywhere in the scene simultaneously.

    Either option would solve my problem. Any ideas?

    Thanks, Jas

  • 0
    Avatar
    Alexander Rüther

    Hello, everyone.

    We need the function for Eyeshot 10, the code in the forum does not compile.
    It would be great if someone could help.

    And is there any way to implement this without using the ViewPortLayout in the constructor?
    We create the entities before we know the ViewPort.

    Thank you in advance
    Alex

    Edited by Alexander Rüther
  • 0
    Avatar
    Alexander Rüther

    I got it done.
    Actually, only a few overloads were wrong.
    And I had to find a way to set the ViewPortLayout before the scene is drawn for the first time.

Please sign in to leave a comment.