I modified LC slightly so you can get the camera location, and calculate the distance to center of the globe or annotations.
It's a .NET4 LC assemblies pack.
This is ExampleGlobeSurface3D.cs code, which shows route label annotations only if they are in the visible side of globe. When you rotate the globe, annotations' visibility get updated.
Code: Select all
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Arction.LightningChartUltimate;
using Arction.LightningChartUltimate.Series3D;
using Arction.LightningChartUltimate.Views.View3D;
using Arction.LightningChartUltimate.Axes;
using Arction.LightningChartUltimate.Annotations;
using System.Diagnostics;
using System.Reflection;
namespace DemoAppLightningChartUltimate
{
/// <summary>
/// Globe surface example
/// </summary>
public partial class ExampleGlobeSurface3D : UserControl
{
//Chart
private LightningChartUltimate m_chart = null;
//2D array of elevation data
private double[,] m_aElevationData = null;
//Photo that is shown over the surface
private Image m_photo = null;
private const double EarthDiameterKm = 6371;
/// <summary>
/// Constructor.
/// </summary>
public ExampleGlobeSurface3D()
{
InitializeComponent();
m_photo = Bitmap.FromFile(Application.StartupPath + "\\Resources\\WorldPhoto3600x1800.jpg");
CreateChart();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
if (m_chart != null)
{
m_chart.Dispose(); //Chart should be explicitely disposed, to free graphics resources instantly
m_chart = null;
}
base.Dispose(disposing);
}
/// <summary>
/// Get demo description and source file name.
/// </summary>
/// <param name="sourceCodeFilename">Demo source file name</param>
/// <returns>description</returns>
public string GetDescription(out string sourceCodeFilename)
{
//Resolve source code file name by throwing an exception and investigating the source file name
try
{
throw new Exception();
}
catch (Exception e)
{
sourceCodeFilename = ChartTools.GetSourceCodeFilenameFromStackTrace(e.StackTrace);
}
return "Globe demo, earth surface is made with SurfaceMeshSeries3D, from manipulated SRTM-90 (CGIAR-CSI) elevation data and a photo (NASA)."
+ "\nFlight routes are calculated with ChartTools methods from city coordinates using shortest Great Circle route. "
+ "PointLineSeries3D and Annotation objects are used to present them.";
}
/// <summary>
/// Create chart.
/// </summary>
private void CreateChart()
{
//Create new chart
m_chart = new LightningChartUltimate(LicenseKeys.LicenseKeyStrings.LightningChartUltimate);
//Disable rendering, strongly recommended before updating chart properties
m_chart.BeginUpdate();
//Set active view
m_chart.ActiveView = ActiveView.View3D;
//Chart parent must be set
m_chart.Parent = splitContainer1.Panel1;
//Fill parent area with chart
m_chart.Dock = DockStyle.Fill;
//Chart name
m_chart.Name = "Globe chart";
//Hide walls
List<WallBase> listWalls = m_chart.View3D.GetWalls();
foreach (WallBase wall in listWalls)
wall.Visible = false;
//Set all axis range same
m_chart.View3D.XAxisPrimary3D.SetRange(-EarthDiameterKm, EarthDiameterKm);
m_chart.View3D.YAxisPrimary3D.SetRange(-EarthDiameterKm, EarthDiameterKm);
m_chart.View3D.ZAxisPrimary3D.SetRange(-EarthDiameterKm, EarthDiameterKm);
//Disable second light
m_chart.View3D.Lights[1].Enabled = false;
m_chart.View3D.Lights[1].LocationFromCamera = true;
//Set enhanced surface rendering mode
m_chart.View3D.UsePerPixelLighting = true;
//Setup dimensions to same value
m_chart.View3D.Dimensions.X = 200;
m_chart.View3D.Dimensions.Y = 200;
m_chart.View3D.Dimensions.Z = 200;
//Setup camera
m_chart.View3D.Camera.MinimumViewDistance = 10;
m_chart.View3D.Camera.SetPredefinedCamera(PredefinedCamera.FrontPerspective);
m_chart.View3D.Camera.ViewDistance = 200;
m_chart.View3D.Camera.RotationX = 60;
m_chart.View3D.Camera.RotationY = -25;
//Hide legend box
m_chart.View3D.LegendBox.Visible = false;
//Don't allow panning because it shifts the center point of rotation
m_chart.View3D.ZoomPanOptions.RightMouseButtonAction = MouseButtonAction3D.Rotate;
//Hide all axes
List<Axis3DBase> listAxes = m_chart.View3D.GetAxes();
foreach (Axis3DBase axis in listAxes)
axis.Visible = false;
//Make data
MakeElevationData();
//Create series
CreateSurfaceSeries();
CreateRoutes();
//Set view point
viewPointEditor1.Chart = m_chart;
m_chart.View3D.CameraViewChanged += new View3D.CameraViewChangedHandler(View3D_CameraViewChanged);
UpdateAnnotationsVisiblity();
//Allow chart rendering
m_chart.EndUpdate();
}
void View3D_CameraViewChanged(Camera3D newCameraViewPoint, View3D view, LightningChartUltimate chart)
{
UpdateAnnotationsVisiblity();
}
void UpdateAnnotationsVisiblity()
{
//Check if annotations' visibility must be changed.
//Don't update their status if not necessary to avoid extra refresh.
bool bShowLabels = checkBoxShowRouteLabels.Checked;
PointDouble3D cameraLocation = m_chart.View3D.Camera.GetLocationAs3DWorldCoord();
//Use any series here that is bound to same axes than the annotations. Surface mesh is in this example.
SeriesBase3D dummySeries = m_chart.View3D.SurfaceMeshSeries3D[0];
//Camera distance from center of earth
double dCamDistFromCenter = CalcDistance(cameraLocation, new PointDouble3D(0, 0, 0));
bool bOneOrMoreAnnotVisibilityNeedsUpdating = false;
bool[] aNearEnoughToShow = new bool[m_chart.View3D.Annotations.Count];
int iAnnotIndex = 0;
foreach(Annotation3D annot in m_chart.View3D.Annotations)
{
PointDouble3D annotLocation = m_chart.View3D.ConvertSeriesValueTo3DWorldCoord(dummySeries, annot.TargetAxisValues.X, annot.TargetAxisValues.Y, annot.TargetAxisValues.Z);
double dist = CalcDistance(annotLocation, cameraLocation);
aNearEnoughToShow[iAnnotIndex] = (dist < dCamDistFromCenter) && bShowLabels;
if (aNearEnoughToShow[iAnnotIndex] != annot.Visible)
bOneOrMoreAnnotVisibilityNeedsUpdating = true;
iAnnotIndex++;
}
if (bOneOrMoreAnnotVisibilityNeedsUpdating)
{
m_chart.BeginUpdate();
iAnnotIndex = 0;
foreach (Annotation3D annot in m_chart.View3D.Annotations)
{
annot.Visible = aNearEnoughToShow[iAnnotIndex];
iAnnotIndex++;
}
m_chart.EndUpdate();
}
}
/// <summary>
/// Calculate distance between two points
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
double CalcDistance(PointDouble3D a, PointDouble3D b)
{
return Math.Sqrt ((b.X - a.X) * (b.X - a.X) + (b.Y - a.Y) * (b.Y - a.Y) + (b.Z - a.Z) * (b.Z - a.Z));
}
/// <summary>
/// Make elevation data.
/// </summary>
private void MakeElevationData()
{
//Get surface elevation data from bitmap. In this elevation bitmap, white value represents high elevation,
//and dark value low elevation
Bitmap bitmapElevation = (Bitmap)ChartTools.ImageFromResource("Resources." + "WorldElevation1024x512.png", Assembly.GetExecutingAssembly());
//Use fast method for getting pixel colors
Color[,] aElevationData = ChartTools.GetPixelColors(bitmapElevation);
int iWidth = aElevationData.GetLength(0);
int iHeight = aElevationData.GetLength(1);
m_aElevationData = new double[iWidth, iHeight];
for (int iColumn = 0; iColumn < iWidth; iColumn++)
{
for (int iRow = 0; iRow < iHeight; iRow++)
{
m_aElevationData[iColumn, iRow] = (double)(
aElevationData[iColumn, iRow].R +
aElevationData[iColumn, iRow].G +
aElevationData[iColumn, iRow].B) / (3.0 * 255.0);
}
}
}
/// <summary>
/// Create routes.
/// </summary>
private void CreateRoutes()
{
//Route1 : Helsinki, Finland - New York, USA
MapCoordinate[] route1WayPoints = new MapCoordinate[2];
route1WayPoints[0] = new MapCoordinate(60, 10, 0, LatitudePostfix.N, 24, 53, 0, LongitudePostfix.E);
route1WayPoints[1] = new MapCoordinate(40, 47, 0, LatitudePostfix.N, 73, 58, 0, LongitudePostfix.W);
CreateRoute(route1WayPoints, Color.Green, new string[] { "Helsinki, Finland", "New York, USA" });
//Route2: Los Angeles, USA - Tokyo, Japan - Zürich, Switzerland
MapCoordinate[] route2WayPoints = new MapCoordinate[3];
route2WayPoints[0] = new MapCoordinate(34, 37, 4, LatitudePostfix.N, 117, 50, 1, LongitudePostfix.W);
route2WayPoints[1] = new MapCoordinate(35, 40, 60, LatitudePostfix.N, 139, 46, 0, LongitudePostfix.E);
route2WayPoints[2] = new MapCoordinate(47, 22, 0, LatitudePostfix.N, 8, 33, 0, LongitudePostfix.E);
CreateRoute(route2WayPoints, Color.Red, new string[] { "Los Angeles, USA", "Tokyo, Japan", "Zürich, Switzerland" });
//Melbourne, Australia - Wellington, New Zealand - Sao Paulo, Brazil
MapCoordinate[] route4WayPoints = new MapCoordinate[3];
route4WayPoints[0] = new MapCoordinate(37, 48, 49, LatitudePostfix.S, 144, 57, 47, LongitudePostfix.E);
route4WayPoints[1] = new MapCoordinate(41, 17, 20, LatitudePostfix.S, 174, 46, 38, LongitudePostfix.E);
route4WayPoints[2] = new MapCoordinate(23, 31, 60, LatitudePostfix.S, 46, 37, 0, LongitudePostfix.W);
CreateRoute(route4WayPoints, Color.Blue, new string[] { "Melbourne, Australia", "Wellington, New Zealand", "Sao Paulo, Brazil" });
}
/// <summary>
/// Create route as PointLineSeries3D.
/// </summary>
/// <param name="wayPoints">Route points</param>
/// <param name="lineColor">Route color</param>
/// <param name="wayPointNames">Names of waypoints</param>
private void CreateRoute(MapCoordinate[] wayPoints, Color lineColor, string[] wayPointNames)
{
const double AngleStep = 3.0;
const double Radius = EarthDiameterKm * 1.02; //above the globe surface
for (int iLeg = 0; iLeg < wayPoints.Length - 1; iLeg++)
{
//Line series per successive route point pair (leg)
PointLineSeries3D lineSeries = new PointLineSeries3D(m_chart.View3D, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
lineSeries.PointsVisible = false;
lineSeries.LineVisible = true;
lineSeries.LineStyle.Width = 3;
lineSeries.LineStyle.Color = lineColor;
//Coordinates as "Great Circle route" for current and next point
MapCoordinate[] aCoords = ChartTools.CalculateSphericalRoute(wayPoints[iLeg], wayPoints[iLeg + 1], Radius, AngleStep);
if (aCoords != null)
{
int iCoordCount = aCoords.Length;
PointDouble3D[] aPoints = new PointDouble3D[iCoordCount];
int iPoint = 0;
foreach (MapCoordinate coord in aCoords)
{
aPoints[iPoint++] = ChartTools.ConvertMapCoordTo3DPointOnSphere(coord, Radius);
}
lineSeries.Points = aPoints;
m_chart.View3D.PointLineSeries3D.Add(lineSeries);
double dDistanceKm = ChartTools.CalculateMapDistance(wayPoints[iLeg], wayPoints[iLeg + 1]);
//Add label to show the statistics of the route
Annotation3D label = new Annotation3D(m_chart.View3D, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
label.Fill.Style = RectFillStyle.None;
label.Shadow.Visible = false;
label.BorderVisible = false;
label.Style = AnnotationStyle.Rectangle;
label.TextStyle.Color = lineColor;
label.LocationCoordinateSystem = CoordinateSystem.RelativeCoordinatesToTarget;
label.LocationRelativeOffset.SetValues(0, 0);
label.MouseInteraction = false;
label.TextStyle.Font = new Font(FontFamily.GenericSansSerif, 9, FontStyle.Bold);
label.TextStyle.Shadow.Style = TextShadowStyle.HighContrast;
label.TextStyle.Shadow.ContrastColor = Color.FromArgb(200, Color.White);
label.Text = wayPointNames[iLeg] + " - " + wayPointNames[iLeg + 1] + "\nDistance: " + dDistanceKm.ToString("0") + " km";
PointDouble3D midPoint = aPoints[aPoints.Length / 2];
label.TargetAxisValues.SetValues(midPoint.X, midPoint.Y, midPoint.Z);
label.Visible = false;
m_chart.View3D.Annotations.Add(label);
}
}
//Add waypoints separate point line series
PointLineSeries3D pointSeries = new PointLineSeries3D(m_chart.View3D, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
pointSeries.PointsVisible = true;
pointSeries.LineVisible = false;
pointSeries.PointStyle.Shape = PointShape3D.Sphere;
pointSeries.PointStyle.Size.SetValues(2, 2, 2);
PointDouble3D[] aWaypoints3D = new PointDouble3D[wayPoints.Length];
for (int iWaypoint = 0; iWaypoint < wayPoints.Length; iWaypoint++)
{
aWaypoints3D[iWaypoint] = ChartTools.ConvertMapCoordTo3DPointOnSphere(wayPoints[iWaypoint], Radius);
}
pointSeries.Points = aWaypoints3D;
m_chart.View3D.PointLineSeries3D.Add(pointSeries);
}
/// <summary>
/// Delete routes (labels and point line series).
/// </summary>
private void DeleteRoutes()
{
m_chart.View3D.PointLineSeries3D.Clear();
m_chart.View3D.Annotations.Clear();
}
/// <summary>
/// Use surface mesh series to represent globe.
/// </summary>
private void CreateSurfaceSeries()
{
//Remove old series
if (m_chart.View3D.SurfaceMeshSeries3D.Count > 0)
m_chart.View3D.SurfaceMeshSeries3D.Clear();
SurfaceMeshSeries3D surface = new SurfaceMeshSeries3D(m_chart.View3D, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
surface.BitmapFill.Image = m_photo;
surface.BitmapFill.MirrorHorizontal = false;
surface.BitmapFill.MirrorVertical = true;
surface.Fill = SurfaceFillStyle.Bitmap;
surface.WireframeType = SurfaceWireframeType.None;
surface.ContourLineType = ContourLineType.None;
//Lit the other side, as the surface is constructed backwards
surface.LightedSurface = LightedSurfaceSide.Bottom;
surface.Material.DiffuseColor = Color.FromArgb(200, 200, 200);
surface.Material.SpecularPower = 5;
surface.ColorSaturation = 80;
int iSlices = m_aElevationData.GetLength(0);
int iStacks = m_aElevationData.GetLength(1);
surface.SizeX = iSlices;
surface.SizeZ = iStacks;
SurfacePoint[,] data = surface.Data;
double dRadius = EarthDiameterKm;
double dX, dY, dZ;
double dScale;
double dTheta;
double dElevation;
double dPhi;
double dElevationFactor = (double)trackBarElevation.Value * dRadius / 100.0;
for (int iStack = 0; iStack < iStacks; iStack++)
{
dPhi = Math.PI / 2.0 - (double)iStack * Math.PI / (double)(iStacks - 1);
for (int iSlice = 0; iSlice < iSlices; iSlice++)
{
dElevation = m_aElevationData[iSlice, iStack] * dElevationFactor;
dY = (dRadius + dElevation) * Math.Sin(dPhi);
dScale = -Math.Cos(dPhi) * (dRadius + dElevation);
dTheta = -(double)iSlice * 2.0 * Math.PI / (double)(iSlices - 1) - Math.PI;
dX = dScale * Math.Sin(dTheta);
dZ = dScale * Math.Cos(dTheta);
data[iSlice, iStack].X = dX;
data[iSlice, iStack].Y = dY;
data[iSlice, iStack].Z = dZ;
}
}
m_chart.View3D.SurfaceMeshSeries3D.Add(surface);
}
/// <summary>
/// Delete grid.
/// </summary>
private void DeleteGrid()
{
//Disable rendering, strongly recommended before updating chart properties
m_chart.BeginUpdate();
int iCount = m_chart.View3D.PointLineSeries3D.Count;
for (int iLineSeries = iCount - 1; iLineSeries >= 0; iLineSeries--)
{
if (m_chart.View3D.PointLineSeries3D[iLineSeries].Title.Text.IndexOf("LatLon") >= 0)
{
m_chart.View3D.PointLineSeries3D.RemoveAt(iLineSeries);
}
}
//Allow chart rendering
m_chart.EndUpdate();
}
/// <summary>
/// Create grid.
/// </summary>
private void CreateGrid()
{
const double Radius = EarthDiameterKm * 1.01;
Color lineColor = Color.FromArgb(60, Color.Cyan);
//Disable rendering, strongly recommended before updating chart properties
m_chart.BeginUpdate();
//Longitude circles
int iCircle = 0;
for (double dLongitude = -180; dLongitude < 0; dLongitude += 15)
{
PointLineSeries3D circleSeries = new PointLineSeries3D(m_chart.View3D, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
circleSeries.PointsVisible = false;
circleSeries.LineStyle.Color = lineColor;
circleSeries.Title.Text = "LatLon " + iCircle.ToString();
MapCoordinate[] aCoords = ChartTools.CalculateSphereFullGreatCircleCoords(new MapCoordinate(100, dLongitude), new MapCoordinate(-100, dLongitude), 5);
int iCount = 0;
if (aCoords != null)
{
iCount = aCoords.Length;
PointDouble3D[] aPoints = new PointDouble3D[iCount];
int iPoint = 0;
foreach (MapCoordinate coord in aCoords)
{
aPoints[iPoint++] = ChartTools.ConvertMapCoordTo3DPointOnSphere(coord, Radius);
}
circleSeries.Points = aPoints;
}
m_chart.View3D.PointLineSeries3D.Add(circleSeries);
iCircle++;
}
//Latitude circles
//Great circle routine is not valid for this, because the radius center is not in the center of the globe.
for (double dLatitude = -75; dLatitude <= 75; dLatitude += 15)
{
PointLineSeries3D circleSeries = new PointLineSeries3D(m_chart.View3D, Axis3DBinding.Primary, Axis3DBinding.Primary, Axis3DBinding.Primary);
circleSeries.PointsVisible = false;
circleSeries.LineStyle.Color = lineColor;
circleSeries.Title.Text = "LatLon " + iCircle.ToString();
PointDouble3D[] aPoints = new PointDouble3D[360 / 5 + 1];
int iPoint = 0;
for (double dLongitude = -180; dLongitude <= 180; dLongitude += 5)
{
aPoints[iPoint++] = ChartTools.ConvertMapCoordTo3DPointOnSphere(new MapCoordinate(dLatitude, dLongitude), Radius);
}
circleSeries.Points = aPoints;
m_chart.View3D.PointLineSeries3D.Add(circleSeries);
iCircle++;
}
//Allow chart rendering
m_chart.EndUpdate();
}
/// <summary>
/// Create new series if elevation changes.
/// </summary>
/// <param name="sender">sender</param>
/// <param name="args">arguments</param>
private void trackBarElevation_Scroll(object sender, EventArgs e)
{
//Disable rendering, strongly recommended before updating chart properties
m_chart.BeginUpdate();
CreateSurfaceSeries();
//Allow chart rendering
m_chart.EndUpdate();
}
/// <summary>
/// Show/hide routes.
/// </summary>
/// <param name="sender">sender</param>
/// <param name="args">arguments</param>
private void checkBoxRoute_CheckedChanged(object sender, EventArgs e)
{
//Disable rendering, strongly recommended before updating chart properties
m_chart.BeginUpdate();
if (checkBoxRoute.Checked)
{
CreateRoutes();
}
else
{
DeleteRoutes();
}
//Allow chart rendering
m_chart.EndUpdate();
}
/// <summary>
/// Toggle route labels visibility.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void checkBoxShowRouteLabels_CheckedChanged(object sender, EventArgs e)
{
if (m_chart != null)
{
UpdateAnnotationsVisiblity();
}
}
/// <summary>
/// Show/hide lon/lat grid.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void checkBoxLonLatGrid_CheckedChanged(object sender, EventArgs e)
{
DeleteGrid();
if (checkBoxLonLatGrid.Checked)
{
CreateGrid();
}
}
}
}