SciChart WPF 3D Charts > Advanced 3D Chart APIs > The SceneEntity API > Adding Cubes and Custom Geometry to the Chart
Adding Cubes and Custom Geometry to the Chart

All of the visual types in SciChart inherit from the type BaseSceneEntity. This is a class which represents an entity on the screen in the 3D world.

It is possible to create entities which inherit BaseSceneEntity and add them to the SciChart3DSurface. For example, if you want to add some simple geometry to the 3D Chart, you can do it with this API.

 

Basics of the SceneEntity API 

In the example Add Geometry to a 3D Scene we demonstrate a simple CubeGeometry class which draws a cube with wireframe outline. The code for CubeGeometry is included below for example purposes.

 

CubeGeometry.cs
Copy Code
/// <summary>
/// A class to demonstrate a 3D Geometry added to the SciChart3D Scene. Created using our BaseSceneEntity and Mesh APIs
/// </summary>
public class CubeGeometry : BaseSceneEntity
{
    private readonly Vector3 bottomRight;
    private readonly Vector3 topLeft;
    private readonly Color cubeColor;

    public CubeGeometry(Vector3 topLeft, Vector3 bottomRight, Color cubeColor)
    {
        // Shady : Setting the position of scene entities will be used back when sorting them from camera perspective back to front
        using (TSRVector3 centerPosition = new TSRVector3(
                0.5f*(topLeft.x + bottomRight.x),
                0.5f*(topLeft.y + bottomRight.y),
                0.5f*(topLeft.z + bottomRight.z)))
        {
            SetPosition(centerPosition);
        }                       
        this.topLeft = topLeft;
        this.bottomRight = bottomRight;
        this.cubeColor = cubeColor;
    }
    /// <summary>
    /// Determines a kind of the entity. If SCRT_SCENE_ENTITY_KIND_TRANSPARENT then the 3D Engine must make some internal adjustments to allow order independent transparency
    /// </summary>
    public override eSCRTSceneEntityKind GetKind()
    {
        return cubeColor.A == 255 ? eSCRTSceneEntityKind.SCRT_SCENE_ENTITY_KIND_OPAQUE : eSCRTSceneEntityKind.SCRT_SCENE_ENTITY_KIND_TRANSPARENT;
    }
    /// <summary>
    ///     Called when the 3D Engine wishes to update the geometry in this element. This is where we need to cache geometry
    ///     before draw.
    /// </summary>
    /// <param name="e">The <see cref="IRenderPassInfo3D" /> containing parameters for the current render pass.</param>
    public override void UpdateScene(IRenderPassInfo3D e)
    {
    }
    /// <summary>
    ///     Called when the 3D Engine wishes to render this element. This is where geometry must be drawn to the 3D scene
    /// </summary>
    /// <param name="e">The <see cref="IRenderPassInfo3D" /> containing parameters for the current render pass.</param>
    public override void RenderScene(IRenderPassInfo3D e)
    {
        // y          1--------0
        // |         /|       /|
        // |       5--------4  |
        // |       |  |     |  |
        // |       |  |     |  |
        // |       |  2--------3
        // |  z    | /      |/   
        // | /     6--------7       
        // |/
        // ----------- X
        Vector3[] corners = {
            new Vector3(topLeft.X, topLeft.Y, topLeft.Z), //0
            new Vector3(bottomRight.X, topLeft.Y, topLeft.Z), //1
            new Vector3(bottomRight.X, bottomRight.Y, topLeft.Z), //2
            new Vector3(topLeft.X, bottomRight.Y, topLeft.Z), //3
            new Vector3(topLeft.X, topLeft.Y, bottomRight.Z), //4
            new Vector3(bottomRight.X, topLeft.Y, bottomRight.Z), //5
            new Vector3(bottomRight.X, bottomRight.Y, bottomRight.Z), //6
            new Vector3(topLeft.X, bottomRight.Y, bottomRight.Z), //7
        };
        Vector3[] normals = {
            new Vector3(+0.0f, +0.0f, -1.0f), //front
            new Vector3(+0.0f, +0.0f, +1.0f), //back
            new Vector3(+1.0f, +0.0f, +0.0f), //right
            new Vector3(-1.0f, +0.0f, +0.0f), //left
            new Vector3(+0.0f, +1.0f, +0.0f), //top
            new Vector3(+0.0f, -1.0f, +0.0f), //bottom
        };
        // We create a mesh context. There are various mesh render modes. The simplest is Triangles
        // For this mode we have to draw a single triangle (three vertices) for each corner of the cube
        // You can see
        using (var meshContext = base.BeginLitMesh(TSRRenderMode.TRIANGLES))
        {
            // Set the Rasterizer State for this entity
            SCRTImmediateDraw.PushRasterizerState(RasterizerStates.CullBackFacesState.TSRRasterizerState);
           
            // Set the color before drawing vertices
            meshContext.SetVertexColor(cubeColor);
           
            // Now draw the triangles. Each face of the cube is made up of two triangles
            // Front face
            SetNormal(meshContext, normals[0]);
            SetVertex(meshContext, corners[0]);
            SetVertex(meshContext, corners[2]);
            SetVertex(meshContext, corners[1]);
            SetVertex(meshContext, corners[2]);
            SetVertex(meshContext, corners[0]);
            SetVertex(meshContext, corners[3]);
           
            // Right side face
            SetNormal(meshContext, normals[2]);
            SetVertex(meshContext, corners[1]);
            SetVertex(meshContext, corners[2]);
            SetVertex(meshContext, corners[6]);
            SetVertex(meshContext, corners[1]);
            SetVertex(meshContext, corners[6]);
            SetVertex(meshContext, corners[5]);
           
            // Top face
            SetNormal(meshContext, normals[4]);
            SetVertex(meshContext, corners[2]);
            SetVertex(meshContext, corners[7]);
            SetVertex(meshContext, corners[6]);
            SetVertex(meshContext, corners[7]);
            SetVertex(meshContext, corners[2]);
            SetVertex(meshContext, corners[3]);
           
            // Left side face
            SetNormal(meshContext, normals[3]);
            SetVertex(meshContext, corners[3]);
            SetVertex(meshContext, corners[0]);
            SetVertex(meshContext, corners[4]);
            SetVertex(meshContext, corners[3]);
            SetVertex(meshContext, corners[4]);
            SetVertex(meshContext, corners[7]);
           
            // Back face
            SetNormal(meshContext, normals[1]);
            SetVertex(meshContext, corners[7]);
            SetVertex(meshContext, corners[5]);
            SetVertex(meshContext, corners[6]);
            SetVertex(meshContext, corners[7]);
            SetVertex(meshContext, corners[4]);
            SetVertex(meshContext, corners[5]);
           
            // Bottom face
            SetNormal(meshContext, normals[5]);
            SetVertex(meshContext, corners[0]);
            SetVertex(meshContext, corners[1]);
            SetVertex(meshContext, corners[5]);
            SetVertex(meshContext, corners[0]);
            SetVertex(meshContext, corners[5]);
            SetVertex(meshContext, corners[4]);
        }
           
        // Revert raster state
        SCRTImmediateDraw.PopRasterizerState();
        // Set the Rasterizer State for wireframe
        SCRTImmediateDraw.PushRasterizerState(RasterizerStates.WireframeState.TSRRasterizerState);
        // Create a Line Context for a continuous line and draw the outline of the cube
        var lineColor = Color.FromArgb(0xFF, cubeColor.R, cubeColor.G, cubeColor.B);
        CreateSquare(2.0f, true, lineColor, new[] { corners[0], corners[1], corners[2], corners[3] });
        CreateSquare(2.0f, true, lineColor, new[] { corners[4], corners[5], corners[6], corners[7] });
        CreateSquare(2.0f, true, lineColor, new[] { corners[0], corners[4], corners[7], corners[3] });
        CreateSquare(2.0f, true, lineColor, new[] { corners[5], corners[1], corners[2], corners[6] });
        // Revert raster state
        SCRTImmediateDraw.PopRasterizerState();
    }
    private void CreateSquare(float lineThickness, bool isAntiAlias, Color lineColor, Vector3[] vertices)
    {
        using (var lineContext = base.BeginLineStrips(lineThickness, isAntiAlias))
        {
            lineContext.SetVertexColor(lineColor);
            foreach (var v in vertices)
            {
                SetVertex(lineContext, v);
            }
            SetVertex(lineContext, vertices.First());
            lineContext.Freeze();
            lineContext.Draw();
        }
    }
    private void SetVertex(IImmediateLitMeshContext meshContext, Vector3 vector3)
    {
        meshContext.SetVertex3(vector3.X, vector3.Y, vector3.Z);
    }
    private void SetVertex(ILinesMesh linesContext, Vector3 vector3)
    {
        linesContext.SetVertex3(vector3.X, vector3.Y, vector3.Z);
    }
    private void SetNormal(IImmediateLitMeshContext meshContext, Vector3 vector3)
    {
        meshContext.Normal3(vector3.X, vector3.Y, vector3.Z);
    }
    /// <summary>
    ///     Performs selection on this entity, setting the IsSelected flag to True or False on the specified
    ///     <see cref="VertexId">Vertex Ids</see>
    /// </summary>
    /// <param name="isSelected">if set to <c>true</c> the vertices become .</param>
    /// <param name="vertexIds">The vertex ids.</param>
    public override void PerformSelection(bool isSelected, List<VertexId> vertexIds)
    {
        // Do nothing
    }
}

 

When placed on a chart this becomes a re-usable 3D object, as demonstrated in the Add Geometry to a 3D Scene example:

 

 

Adding BaseSceneEntity Types to the SciChart3DSurface

The CubeGeometry objects are added to the SciChart3DSurface.Viewport3D.RootEntity which contains a scene graph of the entire 3D scene. Have a look at the code below:

 

Adding BaseSceneEntities to the Chart
Copy Code
var cubeA = new CubeGeometry(new Vector3(0.0f, 0.0f, 0.0f), new Vector3(50.0f, 50.0f, 50.0f), Color.FromArgb(128, 255,0,0));           
var cubeB = new CubeGeometry(new Vector3(50.0f, 0.0f, 50.0f), new Vector3(100.0f, 100.0f, 100.0f), Color.FromArgb(128, 0, 255, 0));           
var cubeC = new CubeGeometry(new Vector3(100.0f, 0.0f, 100.0f), new Vector3(150.0f, 150.0f, 150.0f), Color.FromArgb(255, 0, 0, 255));           
var cubeD = new CubeGeometry(new Vector3(-100.0f, 0.0f, -100.0f), new Vector3(0.0f, 150.0f, 0.0f), Color.FromArgb(255, 0, 255, 255));           
var cubeE = new CubeGeometry(new Vector3(-150.0f, 0.0f, -150.0f), new Vector3(50.0f, 200.0f, 50.0f), Color.FromArgb(128, 255, 255, 255));
           
// force a far position on this cube ( user can do this in cases where a box inside another )
var cubeF = new CubeGeometry(new Vector3(-150.0f, 0.0f, -150.0f), new Vector3(-100.0f, 50.0f, -100.0f), Color.FromArgb(128, 255, 0, 0));
TSRVector3 farPosition = new TSRVector3(20000.0f, 20000.0f, 2000.0f);
cubeF.SetPosition(farPosition);
// Add the cubes to the 3D Scene
sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeA);
sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeB);
sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeC);
sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeD);
sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeE);
sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeF);

 

Our 3D Cubes are added at various positions defined by Vector3 structs. It should be noted that the coordinates are in World Coordinates. Conversion between World and Data Coordinates is performed by the axis, and discussed here.

 

What can the BaseSceneEntity API do?

Functions exposed to the user include:

  • Drawing vertices, normals and geometry to draw lines, simple shapes in 3D space
  • Draw text on the chart at specific X,Y,Z locations

It is also theoretically possible to create custom 3D Chart types, however we haven't exposed enough of the API yet to allow is. If you do have a custom requirement, then contact us as we would love to hear it.