Skip to content

Commit

Permalink
Added animation support
Browse files Browse the repository at this point in the history
  • Loading branch information
KallDrexx committed Nov 17, 2020
1 parent 5213e1a commit 1bc7b60
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 16 deletions.
2 changes: 2 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Scripts are given access to a global object that represents the graph. For C# s

All methods (except `Log()`) can optionally have a `Color` value specified as the first argument for the color the drawings should be done with. If no color is specified than they will default to white.

Graphs can be animated by defining multiple frames. Each frame consists of it's own distinct set of drawn objects and each frame is its own blank canvas. A new frame can be defined by calling the `StartNextFrame()` graph method followed by drawing commands that should be run for that frame. All drawing commands called before the first `StartNextFrame()` will be drawn for the first / initial frame. By default each frame is rendered for one second, but that is changeable by calling `SetFrameTime()` on the graph and passing in the number of seconds each frame should be rendered for.

## Scripting Languages

As of right now 3 languages are supported - C#, Javascript, and Python. It is important that all script files use the correct language extension (`.cs`, `.js` and `.py` respectively), as that is how SharpPlotter knows which scripting engine to use for each file.
Expand Down
82 changes: 66 additions & 16 deletions SharpPlotter/GraphedItems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ namespace SharpPlotter
{
public class GraphedItems
{
private readonly List<RenderedPoint> _points = new List<RenderedPoint>();
private readonly List<RenderedSegment> _segments = new List<RenderedSegment>();
private readonly List<RenderedFunction> _functions = new List<RenderedFunction>();
private readonly List<RenderedArrow> _arrows = new List<RenderedArrow>();
private readonly List<RenderedPolygon> _polygons = new List<RenderedPolygon>();
private readonly List<Frame> _frames;
private int _currentRenderedFrameIndex;
private double _secondsSinceCreation;

public Queue<string> Messages { get; } = new Queue<string>();

Expand All @@ -29,8 +27,15 @@ public class GraphedItems
/// </summary>
public Point2d? MaxCoordinates { get; private set; }

/// <summary>
/// How long each frame should be rendered
/// </summary>
public double SecondsPerFrame { get; set; } = 1;

public GraphedItems()
{
_frames = new List<Frame> {new Frame()};

// Always start with true, as if this is a new object then obviously something has changed.
ItemsChangedSinceLastRender = true;
}
Expand All @@ -48,7 +53,7 @@ public void AddPoints(Color color, IEnumerable<Point2d> points)
{
points ??= Array.Empty<Point2d>();

_points.AddRange(points.Select(x => new RenderedPoint(x, color)));
_frames[^1].Points.AddRange(points.Select(x => new RenderedPoint(x, color)));
GraphItemsUpdated();
}

Expand All @@ -64,7 +69,7 @@ public void AddSegments(Color color, IEnumerable<Point2d> points)
{
if (lastPoint != null)
{
_segments.Add(new RenderedSegment(lastPoint.Value, point, color));
_frames[^1].Segments.Add(new RenderedSegment(lastPoint.Value, point, color));
}

lastPoint = point;
Expand All @@ -80,45 +85,81 @@ public void AddFunction(Color color, Func<float, float> function)
{
if (function == null) throw new ArgumentNullException(nameof(function));

_functions.Add(new RenderedFunction(color, function));
_frames[^1].Functions.Add(new RenderedFunction(color, function));
}

/// <summary>
/// Adds a line segment with a pointer at the end from the starting point to and ending point
/// </summary>
public void AddArrow(Color color, Point2d start, Point2d end)
{
_arrows.Add(new RenderedArrow(start, end, color));
_frames[^1].Arrows.Add(new RenderedArrow(start, end, color));
GraphItemsUpdated();
}

/// <summary>
/// Adds a filled in polygon between 3 or more points
/// </summary>
public void AddPolygon(Color color, IEnumerable<Point2d> points)
{
points ??= Array.Empty<Point2d>();

var polygon = new RenderedPolygon(color, points);
_polygons.Add(polygon);
_frames[^1].Polygons.Add(polygon);
GraphItemsUpdated();
}

/// <summary>
/// Creates a new frame to add items into
/// </summary>
public void StartNextFrame()
{
_frames.Add(new Frame());
}

/// <summary>
/// Provides the list of items that should be rendered. This will reset `ItemsChangedSinceLastRender`
/// </summary>
internal ItemsToRender GetItemsToRender()
{
ItemsChangedSinceLastRender = false;
return new ItemsToRender(_points, _segments, _functions, _arrows, _polygons);
var frame = _frames[_currentRenderedFrameIndex];

return new ItemsToRender(frame.Points,
frame.Segments,
frame.Functions,
frame.Arrows,
frame.Polygons);
}

/// <summary>
/// Updates the graphed items based on how long it's been since the last frame. Used mainly to control
/// multiple frames
/// </summary>
internal void Update(double secondsSinceLastFrame)
{
_secondsSinceCreation += secondsSinceLastFrame;

var totalCycleTime = _frames.Count * SecondsPerFrame;
var frameToRender = (int) (_secondsSinceCreation / totalCycleTime * _frames.Count) % _frames.Count;

if (frameToRender != _currentRenderedFrameIndex)
{
_currentRenderedFrameIndex = frameToRender;
ItemsChangedSinceLastRender = true;
}
}

private void GraphItemsUpdated()
{
ItemsChangedSinceLastRender = true;

var allCoordinates = _points.Select(x => x.Point)
.Union(_segments.Select(x => x.Start))
.Union(_segments.Select(x => x.End))
.Union(_arrows.Select(x => x.Start))
.Union(_arrows.Select(x => x.End))
var allCoordinates = _frames.SelectMany(x => x.Points).Select(x => x.Point)
.Union(_frames.SelectMany(x => x.Segments).Select(x => x.Start))
.Union(_frames.SelectMany(x => x.Segments).Select(x => x.End))
.Union(_frames.SelectMany(x => x.Arrows).Select(x => x.Start))
.Union(_frames.SelectMany(x => x.Arrows).Select(x => x.End))
.Union(_frames.SelectMany(x => x.Polygons).SelectMany(x => x.Points))
.Distinct()
.ToArray();

Expand Down Expand Up @@ -146,5 +187,14 @@ private void GraphItemsUpdated()
MaxCoordinates = null;
}
}

private class Frame
{
public List<RenderedPoint> Points { get; set; } = new List<RenderedPoint>();
public List<RenderedSegment> Segments { get; set; } = new List<RenderedSegment>();
public List<RenderedFunction> Functions { get; set; } = new List<RenderedFunction>();
public List<RenderedArrow> Arrows { get; set; } = new List<RenderedArrow>();
public List<RenderedPolygon> Polygons { get; set; } = new List<RenderedPolygon>();
}
}
}
1 change: 1 addition & 0 deletions SharpPlotter/MonoGame/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ protected override void Update(GameTime gameTime)
}
}

_graphedItems?.Update(gameTime.ElapsedGameTime.TotalSeconds);
base.Update(gameTime);
}

Expand Down
18 changes: 18 additions & 0 deletions SharpPlotter/Scripting/CSharpScriptRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public class CSharpScriptRunner : IScriptRunner
// drawn as. The `Points()` and `Segments()` methods can be given an `Enumerable<(float, float)>` to be given
// any number of points.
//
// The graph can be animated by calling `Graph.StartNextFrame()`. All drawing commands called after the `StartNextFrame`
// method will be attached to a new animation frame, and previous animation frames will be un-editable. All drawing
// commands called before the first `StartNextFrame` call will be for rendering the first animation frame. The time
// each frame is displayed for defaults to 1 second, but can be changed by calling `Graph.SetFrameTime(seconds)`.
//
using System;
using System.Linq;
Expand Down Expand Up @@ -331,6 +336,19 @@ public void Arrow((double x, double y) start, (double x, double y) end)
new Point2d((float) start.x, (float) start.y),
new Point2d((float) end.x, (float) end.y));
}

public void StartNextFrame()
{
_graphedItems.StartNextFrame();
}

public void SetFrameTime(double seconds)
{
if (seconds > 0)
{
_graphedItems.SecondsPerFrame = seconds;
}
}
}
}
}
19 changes: 19 additions & 0 deletions SharpPlotter/Scripting/JavascriptRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public class JavascriptRunner : IScriptRunner
//
// All graph functions except `Log()` can have a first parameter being a color value to change the color of the
// drawn data.
//
// The graph can be animated by calling `graph.StartNextFrame()`. All drawing commands called after the `StartNextFrame`
// method will be attached to a new animation frame, and previous animation frames will be un-editable. All drawing
// commands called before the first `StartNextFrame` call will be for rendering the first animation frame. The time
// each frame is displayed for defaults to 1 second, but can be changed by calling `Graph.SetFrameTime(seconds)`.
//
".TrimStart();

Expand Down Expand Up @@ -146,6 +152,19 @@ public void Arrow(object start, object end)
_graphedItems.AddArrow(Color.White, startPoints[0], endPoints[0]);
}

public void StartNextFrame()
{
_graphedItems.StartNextFrame();
}

public void SetFrameTime(double seconds)
{
if (seconds > 0)
{
_graphedItems.SecondsPerFrame = seconds;
}
}

private static (Color color, Point2d[] points) ParseObjects(params object[] objects)
{
// JInt will always call this as an array of objects, so we'll have to manually parse each argument out
Expand Down
18 changes: 18 additions & 0 deletions SharpPlotter/Scripting/PythonScriptRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public class PythonScriptRunner : IScriptRunner
# All functions except `Log()` can optionally take a color value as the first parameter to change what color each
# drawing is rendered as.
#
# The graph can be animated by calling `graph.StartNextFrame()`. All drawing commands called after the `StartNextFrame`
# method will be attached to a new animation frame, and previous animation frames will be un-editable. All drawing
# commands called before the first `StartNextFrame` call will be for rendering the first animation frame. The time
# each frame is displayed for defaults to 1 second, but can be changed by calling `Graph.SetFrameTime(seconds)`.
#
".TrimStart();

Expand Down Expand Up @@ -152,6 +157,19 @@ public void Arrow(object start, object end)
_graphedItems.AddArrow(Color.White, startPoints[0], endPoints[0]);
}

public void StartNextFrame()
{
_graphedItems.StartNextFrame();
}

public void SetFrameTime(double seconds)
{
if (seconds > 0)
{
_graphedItems.SecondsPerFrame = seconds;
}
}

private static (Color color, Point2d[] points) ParseObjects(params object[] objects)
{
// Due to python's dynamic nature, the incoming objects can be one of several type of objects
Expand Down

0 comments on commit 1bc7b60

Please sign in to comment.