I'm writing an n-body simulation as an exercise in C# using WPF and I ran into what seems like a fundamental design issue with displaying the results of my program.
I have an Entity class, which stores basic information like position, velocity, mass. Then there's the PhysicsEngine class which has an ObservableCollection of Entities and does all the maths. The problem arises when I have to bind the position of the Entities to some graphical elements to show their movement on screen. The distances in space are very big, so I obviously need to process the position information somehow and transform it to screen coordinates. As a quick hack, I added this to the Entity class:
public Vector3 ScreenPosition
{
get
{
return new Vector3(
(Pos.X / Scale) - (Diameter / 2),
(Pos.Y / Scale) - (Diameter / 2),
(Pos.Z / Scale) - (Diameter / 2)
// where Scale is an arbitrary number depending on how big the simulation is
);
}
}
which just returns the position with some math done to fit everything on the screen. That worked fine when the "camera" was static, but now I want to make it movable - maybe center the camera on a certain planet, or zoom in and out.
Continuing on using this hack seems ugly - I'd have to expose all kinds of details about the camera's position and zoom level to the low-level class of Entity, which really shouldn't care about the View or know anything about how it's being displayed.
I tried making a 2nd ObservableCollection of DisplayEntities which holds all the data needed to display each Entity, and on each simulation tick it'd loop through the List of Entities and then update their respective brethren in DisplayEntities, and in the code-behind of the View I programmatically add and bind geometrical shapes to each DisplayEntity, but that turned out to be really slow and impractical as well - I need to loop through all Entities, check if they already have a DisplayEntity, update if so, add a new DisplayEntity if not, not to mention what happens when an Entity is deleted.
I also tried wrapping the Entity class in another class which contains all the information needed to display it, which removes the issue with the two collections, but then it seems like the same abstraction problem as before shows up - the EntityVM must know the camera position, angle, zoom level, and I must loop over each and every one of them every tick and update their values - again slow and inflexible.
Coming from immediate graphics in WinForms, this situation seems really frustrating - in WinForms I could just make a function in code-behind that when called draws circles at such-and-such coordinates, doing whatever math I want to, since I don't need to think about binding. I just need to pass it a List of coordinates whenever I want to draw anything and it doesn't care about my actual objects at all, which ironically seems to separate the View better from the Model than the spaghetti I've cooked with WPF.
How do I approach the design of this in order to produce an elegant and non-clusterfuck solution?
(Thank you in advance and please let me know if my post is lacking in some aspect, it's my first time posting :) )
EDIT: For clarity, here's the important part of the code-behind of my view:
public void AddEntitiesToCanvas()
{
PhysicsEngine engine = (PhysicsEngine)this.DataContext;
for (int i = 0; i < engine.Entities.Count; i++)
{
Binding xBinding = new Binding("Entities[" + i + "].VPos.X");
Binding yBinding = new Binding("Entities[" + i + "].VPos.Y");
Binding DiameterBinding = new Binding("Entities[" + i + "].Diameter");
Ellipse EntityShape = new Ellipse();
EntityShape.Fill = new SolidColorBrush(Colors.Black);
EntityShape.SetBinding(WidthProperty, DiameterBinding);
EntityShape.SetBinding(HeightProperty, DiameterBinding);
EntityShape.SetBinding(Canvas.LeftProperty, xBinding);
EntityShape.SetBinding(Canvas.TopProperty, yBinding);
EntityShape.SetValue(Canvas.ZIndexProperty, 100);
canvas.Children.Add(EntityShape);
}
}
The XML file contains just an empty Canvas.
EDIT 2: Here's the important part of my updated view
<DataTemplate>
<Path Fill="Black">
<Path.RenderTransform>
<TranslateTransform X="{Binding VPos.X}" Y="{Binding VPos.Y}"/>
</Path.RenderTransform>
<Path.Data>
<EllipseGeometry Center="0, 0" RadiusX="{Binding Diameter}" RadiusY="{Binding Diameter}"/>
</Path.Data>
</Path>
</DataTemplate>
EDIT 3: I tried using a Binding converter; however, the converter also needs access to the camera information from the PhysicsEngine class in order to perform the calculations. I thought of making the converter a property of the PhysicsEngine class so that it has access to all the private information and then doing this, which obviously doesn't work:
<Path.RenderTransform>
<TranslateTransform X="{Binding Pos.X, Converter={Binding ScreenPosConverter}}" Y="{Binding Pos.Y, Converter={Binding ScreenPosConverter}}"/>
</Path.RenderTransform>
Is a Binding Converter the right tool for the job, and if yes, how can I pass the camera informaton to it?