Archive Browser
Download ChemotaxisSimulation.zip, last updated 02/11/2020 (529.88 KB)
Download- md5: d0f0a74f8f3c043fabf60aeb07356f69
- sha1: 645a77d0f7184873fe70c2bb375833e564b348be
- sha256: da9ab3cfc5e17e120b6660a137012b6eca68a7a3218cb213b3e0e3eb9445b104
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
// Simulating Bacterial Chemotaxis
// https://www.cyotek.com/blog/simulating-bacterial-chemotaxis
// Copyright © 2020 Cyotek Ltd. All Rights Reserved.
// This work is licensed under the MIT License.
// See LICENSE.TXT for the full text
// Found this example useful?
// https://www.paypal.me/cyotek
namespace Cyotek.ChemotaxisSimulation.Renderer
{
public class GdiSimulationRenderer
{
#region Private Fields
private static readonly Point[] _attractorShape = new[]
{
new Point(-2, -1),
new Point(-2, 1),
new Point(-1, 2),
new Point(1, 2),
new Point(2, 1),
new Point(2, -1),
new Point(1, -2),
new Point(-1, -2),
};
private static readonly Point[] _repellentShape = new[]
{
new Point(0, -2),
new Point(-2, 0),
new Point(0, 2),
new Point(2, 0),
};
private static readonly Point[] _strandShape = new[]
{
new Point(0, 0),
new Point(-1, 1),
new Point(-1, 2),
new Point(0, 3),
new Point(1, 2),
new Point(1, 1)
};
private readonly Pen _attractorPen;
private readonly Pen _repellentPen;
private readonly Pen _strandPen;
private bool _drawShapes;
private bool _outlinesOnly;
private float _scale;
private bool _showFoodDetectionZone;
private bool _showFoodSources;
private bool _showNoxiousDetectionZone;
private bool _showNoxiousSources;
private bool _showStrands;
private bool _showTails;
private Pen _tailPen;
#endregion Private Fields
#region Public Constructors
public GdiSimulationRenderer()
{
_scale = 1;
_showFoodDetectionZone = true;
_showStrands = true;
_showFoodSources = true;
_showNoxiousSources = true;
_showNoxiousDetectionZone = true;
_drawShapes = true;
_strandPen = new Pen(Color.DarkGoldenrod)
{
EndCap = LineCap.Round,
LineJoin = LineJoin.Round,
StartCap = LineCap.Round
};
_attractorPen = new Pen(Color.SeaGreen)
{
EndCap = LineCap.Round,
LineJoin = LineJoin.Round,
StartCap = LineCap.Round
};
_tailPen = new Pen(Color.CornflowerBlue)
{
EndCap = LineCap.Round,
StartCap = LineCap.Round,
LineJoin = LineJoin.Round
};
_repellentPen = new Pen(Color.Firebrick);
}
#endregion Public Constructors
#region Public Properties
public bool DrawShapes
{
get { return _drawShapes; }
set { _drawShapes = value; }
}
public bool OutlinesOnly
{
get { return _outlinesOnly; }
set { _outlinesOnly = value; }
}
public float Scale
{
get { return _scale; }
set { _scale = value; }
}
public bool ShowFoodDetectionZone
{
get { return _showFoodDetectionZone; }
set { _showFoodDetectionZone = value; }
}
public bool ShowFoodSources
{
get { return _showFoodSources; }
set { _showFoodSources = value; }
}
public bool ShowNoxiousDetectionZone
{
get { return _showNoxiousDetectionZone; }
set { _showNoxiousDetectionZone = value; }
}
public bool ShowNoxiousSources
{
get { return _showNoxiousSources; }
set { _showNoxiousSources = value; }
}
public bool ShowStrands
{
get { return _showStrands; }
set { _showStrands = value; }
}
public bool ShowTails
{
get { return _showTails; }
set { _showTails = value; }
}
#endregion Public Properties
#region Public Methods
public void Draw(Simulation simulation, Graphics graphics, Rectangle clip)
{
graphics.Clear(SystemColors.Control);
graphics.ScaleTransform(_scale, _scale);
graphics.FillRectangle(Brushes.White, new Rectangle(Point.Empty, simulation.Size));
if (_showFoodSources)
{
for (int i = 0; i < simulation.Attractors.Count; i++)
{
this.DrawFood(graphics, clip, simulation.Attractors[i]);
}
}
if (_showNoxiousSources)
{
for (int i = 0; i < simulation.Repellents.Count; i++)
{
this.DrawNoxious(graphics, clip, simulation.Repellents[i]);
}
}
if (_showStrands)
{
if (_showTails)
{
for (int i = 0; i < simulation.Strands.Count; i++)
{
this.DrawStrandTail(graphics, simulation.Strands[i].PreviousPositions);
}
}
for (int i = 0; i < simulation.Strands.Count; i++)
{
this.DrawStrand(graphics, clip, simulation.Strands[i]);
}
}
}
#endregion Public Methods
#region Private Methods
private void DrawChemoeffector(Graphics graphics, Rectangle clip, Chemoeffector chemoeffector, Pen pen, Point[] shape, bool showDetectionZone)
{
Rectangle bounds;
bounds = new Rectangle(chemoeffector.Position.X - (chemoeffector.Strength / 2), chemoeffector.Position.Y - (chemoeffector.Strength / 2), chemoeffector.Strength, chemoeffector.Strength);
if (this.ShouldRender(clip, bounds))
{
if (showDetectionZone)
{
if (_outlinesOnly)
{
graphics.DrawEllipse(pen, bounds);
}
else
{
using (GraphicsPath ellipsePath = new GraphicsPath())
{
ellipsePath.AddEllipse(bounds);
using (PathGradientBrush brush = new PathGradientBrush(ellipsePath))
{
brush.CenterPoint = chemoeffector.Position;
brush.CenterColor = Color.FromArgb(128, pen.Color);
brush.SurroundColors = new[] { Color.Transparent };
graphics.FillEllipse(brush, bounds);
}
}
}
}
if (_drawShapes)
{
int x;
int y;
GraphicsState state;
x = chemoeffector.Position.X;
y = chemoeffector.Position.Y;
state = graphics.Save();
graphics.TranslateTransform(x, y);
graphics.RotateTransform(Compass.GetAngle(chemoeffector.Heading));
graphics.DrawPolygon(pen, shape);
graphics.Restore(state);
}
else
{
graphics.DrawLine(pen, new Point(chemoeffector.Position.X - 1, chemoeffector.Position.Y), new Point(chemoeffector.Position.X + 1, chemoeffector.Position.Y));
graphics.DrawLine(pen, new Point(chemoeffector.Position.X, chemoeffector.Position.Y - 1), new Point(chemoeffector.Position.X, chemoeffector.Position.Y + 1));
}
}
}
private void DrawFood(Graphics graphics, Rectangle clip, Chemoeffector food)
{
this.DrawChemoeffector(graphics, clip, food, _attractorPen, _attractorShape, _showFoodDetectionZone);
}
private void DrawNoxious(Graphics graphics, Rectangle clip, Chemoeffector noxious)
{
this.DrawChemoeffector(graphics, clip, noxious, _repellentPen, _repellentShape, _showNoxiousDetectionZone);
}
private void DrawSection(Graphics g, PointBuffer points, int start, int length)
{
if (length > 1) // DrawLines crashes if there isn't at least two points
{
Point[] _buffer;
_buffer = ArrayPool<Point>.Shared.Allocate(length);
points.CopyTo(start, _buffer, 0, length);
g.DrawLines(_tailPen, _buffer);
ArrayPool<Point>.Shared.Free(_buffer);
}
}
private void DrawStrand(Graphics graphics, Rectangle clip, Strand strand)
{
int x;
int y;
x = strand.Position.X;
y = strand.Position.Y;
if (this.ShouldRender(clip, x, y))
{
if (_drawShapes)
{
GraphicsState state;
state = graphics.Save();
graphics.TranslateTransform(x, y);
graphics.RotateTransform(Compass.GetAngle(strand.Heading));
graphics.DrawPolygon(_strandPen, _strandShape);
graphics.Restore(state);
}
else
{
graphics.DrawLine(_strandPen, new Point(strand.Position.X - 1, strand.Position.Y - 1), new Point(strand.Position.X + 1, strand.Position.Y + 1));
graphics.DrawLine(_strandPen, new Point(strand.Position.X - 1, strand.Position.Y + 1), new Point(strand.Position.X + 1, strand.Position.Y - 1));
}
}
}
private void DrawStrandTail(Graphics g, PointBuffer points)
{
if (points.Size > 1)
{
// don't do this!
//g.DrawLines(_bodyPen, points.ToArray());
if (!this.HasSplitResults(points))
{
Point[] _buffer;
// this isn't great, but as Graphics.DrawLines
// isn't enlightened enough to take a start and length,
// we copy the buffer out of the CircularBuffer into an
// existing byte array so in theory we aren't allocating
// an array over and over again
_buffer = ArrayPool<Point>.Shared.Allocate(points.Size);
points.CopyTo(_buffer);
g.DrawLines(_tailPen, _buffer);
ArrayPool<Point>.Shared.Free(_buffer);
}
else
{
int start;
Point previous;
Point current;
// if we've wrapped the playing field, I can't just
// call DrawLines with the entire buffer as we'll get
// lines drawn across the entire playing field, so
// instead I need to break it down into smaller buffers
start = 0;
previous = points.PeekAt(0);
for (int i = 1; i < points.Size; i++)
{
current = points.PeekAt(i);
if (Geometry.GetDistance(previous, current) > 1)
{
// here we have a split, so let us grab a subset of
// the buffer and draw our lines
this.DrawSection(g, points, start, i - start);
start = i;
}
previous = current;
}
if (start < points.Size)
{
this.DrawSection(g, points, start, points.Size - start);
}
}
}
}
private bool HasSplitResults(PointBuffer _snakeBody)
{
bool result;
result = false;
for (int i = 1; i < _snakeBody.Size; i++)
{
if (Geometry.GetDistance(_snakeBody.PeekAt(i - 1), _snakeBody.PeekAt(i)) > 1)
{
result = true;
break;
}
}
return result;
}
private bool ShouldRender(Rectangle clip, Rectangle bounds)
{
int x;
int y;
int w;
int h;
Rectangle newBounds;
x = Convert.ToInt32(bounds.X * _scale);
y = Convert.ToInt32(bounds.Y * _scale);
w = Convert.ToInt32(bounds.Width * _scale);
h = Convert.ToInt32(bounds.Height * _scale);
newBounds = new Rectangle(x, y, w, h);
return clip.IntersectsWith(newBounds);
}
private bool ShouldRender(Rectangle clip, int x, int y)
{
x = Convert.ToInt32(x * _scale);
y = Convert.ToInt32(y * _scale);
return clip.Contains(x, y);
}
#endregion Private Methods
}
}
Donate
This software may be used free of charge, but as with all free software there are costs involved to develop and maintain.
If this site or its services have saved you time, please consider a donation to help with running costs and timely updates.
Donate