Zoom invariant / Position Invariant entity

The proposed solution is not natively supported and may not work in all scenarios and versions.

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.

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

    }

    private Point3D ObjectRealPosition;

    public Model m { get; set; }

    public override bool IsInFrustum(FrustumParams data, Point3D center, double radius)
    {
        // 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)
        m.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 Scaling(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);
    }

    /// 
    /// Adjusts the Camera planes to avoid the clipping of the scaled symbol.
    /// 
    ///
    ///
    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)m.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)m.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(GfxDrawForSelectionParams 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   
    }
}

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.

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, Model.CameraEyePosType cameraEyePos)
    {
        return base.GetProjectionMatrix(nearDistance, farDistance, cameraEyePos);
    }
}

Then you can use it in this way:

public void MyFunc(Model m)
{
    m.Viewports[0].Camera = new MyCamera(m.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.m = m;

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

Blockreference

It is possible to create also a Blockreference zoom invariant. The code for that is the following:

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

    private Point3D ObjectRealPosition;

    public Model m { get; set; }

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

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

    //    base.DrawForSelection(myParams, drawCall);

    //    PostDraw(myParams.GfxParams);
    //}

    protected override void DrawForSelection(Environment.DrawForSelectionParams myParams, Environment.drawForSelectionCallback drawCall)
    {
        PreDraw(myParams.GfxParams);

        base.DrawForSelection(myParams, drawCall);

        PostDraw(myParams.GfxParams);
    }



    protected override void Draw(T myParams, Environment.drawCallback 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.Environment.renderContext.CurrentModelViewMatrix(), myParams.Environment.renderContext.CurrentProjectionMatrix());
        }

        base.Draw(myParams, drawCall);

        PostDraw(myParams.GfxParams);

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

    protected override bool DrawTrianglesForShadowMap(DrawEntitiesShadowParams data)
    {
        // not supported
        return true;
    }

    protected override void DrawForShadow(RenderParams data)
    {
        // not supported
        //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)
        m.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 Scaling(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);
    }

    /// 
    /// Adjusts the Camera planes to avoid the clipping of the scaled symbol.
    /// 
    ///
    ///
    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)m.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)m.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;
        }
    }
}

Note 1:

When deriving entities other than the Mesh or the Solid, since their Render() method actually calls the Draw(), it is important to not 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 origin symbol). If this is not desired they can be removed.

Note 4:

If you use Flat DisplayMode you have to override also this method:

protected override void DrawFlat(DrawParams data)
{
	PreDraw(data);
	
	base.DrawFlat(data);

	PostDraw(data);
}

Previous versions of this article: Eyeshot 10Eyeshot 8, Eyeshot 7

Was this article helpful?
0 out of 0 found this helpful
Have more questions? Submit a request

Comments

0 comments

Please sign in to leave a comment.