Zoom 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 used the following mesh-derived class.

 

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

        } 

        private Point3D ObjectRealPosition; 

        public ViewportLayout vp { get; set; } 

        private const double depthRange = 0.001; 

        private void PreDraw(float screenToWorld) 
        { 
            gl.Enable(gl.NORMALIZE); 
            gl.PushMatrix(); 

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

            gl.Translated(ObjectRealPosition.X, ObjectRealPosition.Y, ObjectRealPosition.Z); // move back to its position 
            gl.Scaled(screenToWorld, screenToWorld, screenToWorld); // scale  
            gl.Translated(-ObjectRealPosition.X, -ObjectRealPosition.Y, -ObjectRealPosition.Z); // Move to the Origin                 

            gl.DepthRange(0, depthRange); 
        } 

        void PostDraw() 
        { 
            gl.PopMatrix(); 
            gl.Disable(gl.NORMALIZE); 
            gl.DepthRange(depthRange, 1); 
        } 

        double ComputePreciseScreenToWorld() 
        { 

            ObjectRealPosition = (BoxMin + BoxMax) / 2; 

            double winX, winY, winZ; 
            Viewport view = vp.Viewports[0]; 
            view.Project(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) }, vp.Height); 

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

        protected override void DrawSelected(DrawParams drawParams) 
        { 
            PreDraw(drawParams.ScreenToWorld); 

            base.DrawSelected(drawParams); 

            PostDraw(); 
        } 

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

             base.DrawForSelection(data); 

            PostDraw(); 
        } 
         
        protected override void Draw(DrawParams data) 
        { 
            PreDraw(data.ScreenToWorld); 

            base.Draw(data); 

            PostDraw(); 
        } 

        protected override void DrawWireframe(DrawParams drawParams) 
        { 
            PreDraw(drawParams.ScreenToWorld); 

            base.DrawWireframe(drawParams); 

            PostDraw(); 
        } 

        protected override void Render(RenderParams data) 
        { 
            PreDraw(data.ScreenToWorld); 

            base.Render(data); 

            PostDraw(); 
        } 
         
        protected override void DrawEdges() 
        { 
            return; // not supported 
        } 

        protected override void DrawIsocurves() 
        { 
            //not supported 
        } 

        protected override void DrawSilhouettes(DrawSilhouettesParams drawSilhouettesParams) 
        { 
            PreDraw(drawSilhouettesParams.ScreenToWorld); 

            base.DrawSilhouettes(drawSilhouettesParams); 

            PostDraw(); 
        } 
}

Since the entity is automatically scaled to keep the same size on screen, it may be clipped by the Camera planes.

To avoid this, within a given range of camera movement, we are defining a custom BoundingBox: 

public static void MyFunc(ViewportLayout vp)
{ 
    vp.Viewports[0].Camera.FocalLength = 50;
    Mesh Arrow = Mesh.CreateArrow(0.2, 0.4, 0.4, 0.8, 16, meshNatureType.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); 

    vp.BoundingBox.OverrideSceneExtents = true; 
    vp.BoundingBox.Min = new Point3D(-1000, -1000,-1000); 
    vp.BoundingBox.Max= new Point3D(1000, 1000,1000); 

    return; 
}

 

Note: 

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).

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

Comments

0 comments

Please sign in to leave a comment.