├── Resources
├── logo-square-256px.png
├── logo-square-512px.png
├── logo.svg
└── logo-square.svg
├── .gitignore
├── Boing
├── Boing.csproj.DotSettings
├── Boing.csproj
├── IForce.cs
├── Forces
│ ├── ViscousForce.cs
│ ├── FlowDownwardForce.cs
│ ├── OriginAttractionForce.cs
│ ├── ColoumbForce.cs
│ ├── KeepWithinBoundsForce.cs
│ └── Spring.cs
├── FixedTimeStepUpdater.cs
├── LineSegment2f.cs
├── Simulation.cs
├── Vector2f.cs
├── Rectangle2f.cs
└── PointMass.cs
├── Directory.Build.targets
├── Boing.Tests
├── Boing.Tests.csproj
├── Rectangle2fTest.cs
└── ReadmeSample.cs
├── Directory.Build.props
├── Boing.sln
├── README.md
└── LICENSE
/Resources/logo-square-256px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drewnoakes/boing/HEAD/Resources/logo-square-256px.png
--------------------------------------------------------------------------------
/Resources/logo-square-512px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drewnoakes/boing/HEAD/Resources/logo-square-512px.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | *.user
4 | *.suo
5 | *.nupkg
6 | packages/
7 | /.vs/
8 | project.lock.json
9 | /.idea/
10 |
--------------------------------------------------------------------------------
/Boing/Boing.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Boing.Tests/Boing.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp1.0
5 | portable
6 | Boing.Tests
7 | Boing.Tests
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Boing/Boing.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A simple library for 2D physics simulations in .NET.
5 | net35;netstandard1.0
6 | true
7 | true
8 | true
9 | Physics simulation;Physics;Physics engine;Physics simulation library;Simulation
10 | https://github.com/drewnoakes/boing
11 | git
12 | https://github.com/drewnoakes/boing.git
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Copyright Drew Noakes, Krzysztof Dul 2015-2020
5 | 1.1.0
6 | Drew Noakes;Krzysztof Dul
7 | 8
8 | enable
9 | Apache-2.0
10 | logo-square-512px.png
11 | portable
12 | true
13 | true
14 | true
15 | snupkg
16 | true
17 | true
18 | true
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Boing/IForce.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | namespace Boing
20 | {
21 | // TODO new local force: axis-align force for pairs of point masses
22 | // TODO new local force: hysteresis spring force
23 |
24 | ///
25 | /// A force that potentially applies to every in the .
26 | ///
27 | public interface IForce
28 | {
29 | ///
30 | /// Evaluates the impact of this force on the simulation at the current point in time,
31 | /// and applies forces to point masses as required.
32 | ///
33 | ///
34 | /// Implementations should call on point masses.
35 | /// They can update any number of point masses, or none at all. The force may be identical across
36 | /// all point masses, but is more likely to vary from point to point.
37 | ///
38 | /// The simulation to apply this force to.
39 | void ApplyTo(Simulation simulation);
40 | }
41 | }
--------------------------------------------------------------------------------
/Boing/Forces/ViscousForce.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | namespace Boing
20 | {
21 | ///
22 | /// A force that acts upon point masses as a viscous environment would, exerting a force
23 | /// that inhibits movement and is proportional to point mass velocity.
24 | ///
25 | public sealed class ViscousForce : IForce
26 | {
27 | ///
28 | /// Gets and sets the coefficient of viscosity.
29 | ///
30 | public float Coefficient { get; set; }
31 |
32 | ///
33 | /// Initialises a new instance of .
34 | ///
35 | /// The initial coefficient of viscosity.
36 | public ViscousForce(float coefficient)
37 | {
38 | Coefficient = coefficient;
39 | }
40 |
41 | ///
42 | void IForce.ApplyTo(Simulation simulation)
43 | {
44 | foreach (var pointMass in simulation.PointMasses)
45 | {
46 | if (pointMass.IsPinned)
47 | continue;
48 |
49 | pointMass.ApplyForce(pointMass.Velocity*-Coefficient);
50 | }
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/Boing/Forces/FlowDownwardForce.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | namespace Boing
20 | {
21 | ///
22 | /// A constant downwards force applied to all non-pinned points, akin to gravity.
23 | ///
24 | public sealed class FlowDownwardForce : IForce
25 | {
26 | ///
27 | /// The magnitude of the downward force, in Newtons.
28 | ///
29 | public float Magnitude { get; set; }
30 |
31 | ///
32 | /// Initialises a new instance of .
33 | ///
34 | /// The magnitude of the downward force, in Newtons. The default value is 10.
35 | public FlowDownwardForce(float magnitude = 10.0f)
36 | {
37 | Magnitude = magnitude;
38 | }
39 |
40 | ///
41 | void IForce.ApplyTo(Simulation simulation)
42 | {
43 | var force = new Vector2f(0, Magnitude);
44 |
45 | foreach (var pointMass in simulation.PointMasses)
46 | {
47 | if (pointMass.IsPinned)
48 | continue;
49 |
50 | pointMass.ApplyForce(force);
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/Boing/Forces/OriginAttractionForce.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | namespace Boing
20 | {
21 | ///
22 | /// A force that attracts all non-pinned point masses towards the origin of the coordinate system.
23 | ///
24 | public sealed class OriginAttractionForce : IForce
25 | {
26 | ///
27 | /// Gets and sets the magnitude of the force.
28 | ///
29 | ///
30 | /// Positive values cause attraction to the origin, while negative values
31 | /// cause repulsion from it. Larger values cause larger forces, and a zero
32 | /// value effectively disables this force.
33 | ///
34 | public float Stiffness { get; set; }
35 |
36 | ///
37 | /// Initialises a new instance of .
38 | ///
39 | ///
40 | public OriginAttractionForce(float stiffness = 40)
41 | {
42 | Stiffness = stiffness;
43 | }
44 |
45 | ///
46 | void IForce.ApplyTo(Simulation simulation)
47 | {
48 | // ReSharper disable once CompareOfFloatsByEqualityOperator
49 | if (Stiffness == 0)
50 | return;
51 |
52 | var f = -Stiffness;
53 |
54 | foreach (var pointMass in simulation.PointMasses)
55 | {
56 | if (pointMass.IsPinned)
57 | continue;
58 |
59 | pointMass.ApplyForce(f*pointMass.Position);
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/Boing.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30611.23
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F6B981AF-06BE-409F-8D26-6803E9A9886A}"
7 | ProjectSection(SolutionItems) = preProject
8 | .gitignore = .gitignore
9 | Directory.Build.props = Directory.Build.props
10 | Directory.Build.targets = Directory.Build.targets
11 | LICENSE = LICENSE
12 | Resources\logo-square-256px.png = Resources\logo-square-256px.png
13 | Resources\logo-square-512px.png = Resources\logo-square-512px.png
14 | Resources\logo-square.svg = Resources\logo-square.svg
15 | Resources\logo.svg = Resources\logo.svg
16 | README.md = README.md
17 | EndProjectSection
18 | EndProject
19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Boing", "Boing\Boing.csproj", "{C4FAC4BA-CC6A-4FFB-ABEE-CB67E23745DA}"
20 | EndProject
21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Boing.Tests", "Boing.Tests\Boing.Tests.csproj", "{CF371345-F529-46F0-AD32-4FFB6759C091}"
22 | EndProject
23 | Global
24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
25 | Debug|Any CPU = Debug|Any CPU
26 | Release|Any CPU = Release|Any CPU
27 | EndGlobalSection
28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
29 | {C4FAC4BA-CC6A-4FFB-ABEE-CB67E23745DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {C4FAC4BA-CC6A-4FFB-ABEE-CB67E23745DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {C4FAC4BA-CC6A-4FFB-ABEE-CB67E23745DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {C4FAC4BA-CC6A-4FFB-ABEE-CB67E23745DA}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {CF371345-F529-46F0-AD32-4FFB6759C091}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {CF371345-F529-46F0-AD32-4FFB6759C091}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {CF371345-F529-46F0-AD32-4FFB6759C091}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {CF371345-F529-46F0-AD32-4FFB6759C091}.Release|Any CPU.Build.0 = Release|Any CPU
37 | EndGlobalSection
38 | GlobalSection(SolutionProperties) = preSolution
39 | HideSolutionNode = FALSE
40 | EndGlobalSection
41 | GlobalSection(ExtensibilityGlobals) = postSolution
42 | SolutionGuid = {1AF0827F-AA78-42F2-9606-C13451BF922F}
43 | EndGlobalSection
44 | EndGlobal
45 |
--------------------------------------------------------------------------------
/Boing.Tests/Rectangle2fTest.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using Xunit;
20 |
21 | namespace Boing.Tests
22 | {
23 | public sealed class Rectangle2fTest
24 | {
25 | [Fact]
26 | public void Corners()
27 | {
28 | var rect = new Rectangle2f(9, 9, 2, 2);
29 |
30 | Assert.Equal(new Vector2f(9, 9), rect.Min);
31 | Assert.Equal(new Vector2f(11, 11), rect.Max);
32 |
33 | Assert.Equal(new Vector2f(9, 9), rect.TopLeft);
34 | Assert.Equal(new Vector2f(11, 9), rect.TopRight);
35 | Assert.Equal(new Vector2f(9, 11), rect.BottomLeft);
36 | Assert.Equal(new Vector2f(11, 11), rect.BottomRight);
37 | }
38 |
39 | [Fact]
40 | public void Intersect()
41 | {
42 | var rect = new Rectangle2f(9, 9, 2, 2);
43 |
44 | var centre = new Vector2f(10, 10);
45 | var below = new Vector2f(10, 0);
46 | var above = new Vector2f(10, 20);
47 | var left = new Vector2f(0, 10);
48 | var right = new Vector2f(20, 10);
49 |
50 | Vector2f intersectionPoint;
51 | float t;
52 |
53 | Assert.True(rect.TryIntersect(new LineSegment2f(below, centre), out intersectionPoint, out t));
54 | Assert.Equal(new Vector2f(10, 9), intersectionPoint);
55 | Assert.Equal(0.9, t, precision: 4);
56 |
57 | Assert.True(rect.TryIntersect(new LineSegment2f(above, centre), out intersectionPoint, out t));
58 | Assert.Equal(new Vector2f(10, 11), intersectionPoint);
59 | Assert.Equal(0.9, t, precision: 4);
60 |
61 | Assert.True(rect.TryIntersect(new LineSegment2f(left, centre), out intersectionPoint, out t));
62 | Assert.Equal(new Vector2f(9, 10), intersectionPoint);
63 | Assert.Equal(0.9, t, precision: 4);
64 |
65 | Assert.True(rect.TryIntersect(new LineSegment2f(right, centre), out intersectionPoint, out t));
66 | Assert.Equal(new Vector2f(11, 10), intersectionPoint);
67 | Assert.Equal(0.9, t, precision: 4);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Boing.Tests/ReadmeSample.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using System;
20 | using System.Threading;
21 | using System.Threading.Tasks;
22 |
23 | namespace Boing.Tests
24 | {
25 | public static class ReadmeSample
26 | {
27 | public static void Code()
28 | {
29 | // create some point masses
30 | var pointMass1 = new PointMass(mass: 1.0f);
31 | var pointMass2 = new PointMass(mass: 2.0f);
32 |
33 | // create a new simulation
34 | var simulation = new Simulation
35 | {
36 | // add the point masses
37 | pointMass1,
38 | pointMass2,
39 |
40 | // create a spring between these point masses
41 | new Spring(pointMass1, pointMass2, length: 20),
42 |
43 | // point masses are attracted to one another
44 | new ColoumbForce(),
45 |
46 | // point masses move towards the origin
47 | new OriginAttractionForce(stiffness: 10),
48 |
49 | // gravity
50 | new FlowDownwardForce(magnitude: 100)
51 | };
52 |
53 | void RunAtMaxSpeed()
54 | {
55 | while (true)
56 | {
57 | // update the simulation
58 | simulation.Update(dt: 0.01f);
59 |
60 | // use the resulting positions somehow
61 | Console.WriteLine($"PointMass1 at {pointMass1.Position}, PointMass2 at {pointMass2.Position}");
62 | }
63 | }
64 |
65 | async Task RunAtFixedRateAsync(CancellationToken token)
66 | {
67 | var updater = new FixedTimeStepUpdater(simulation, timeStepSeconds: 1f/200);
68 |
69 | while (!token.IsCancellationRequested)
70 | {
71 | updater.Update();
72 |
73 | // TODO render frame
74 |
75 | await Task.Delay(millisecondsDelay: 1/60, cancellationToken: token);
76 | }
77 | }
78 |
79 | // Reference methods to remove IDE warnings
80 | RunAtMaxSpeed();
81 | RunAtFixedRateAsync(CancellationToken.None).Wait();
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/Boing/FixedTimeStepUpdater.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using System;
20 | using System.Diagnostics;
21 |
22 | namespace Boing
23 | {
24 | ///
25 | /// Updates a simulation according to a time scale of fixed repeating interval,
26 | /// when compared with a stopwatch (wall time).
27 | ///
28 | /// Each call to may update the simulation zero or more times,
29 | ///
30 | public sealed class FixedTimeStepUpdater
31 | {
32 | private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
33 | private readonly float _timeStepSeconds;
34 | private readonly long _timeStepTicks;
35 | private readonly Simulation _simulation;
36 | private long _leftOverTicks;
37 | private long _lastTicks;
38 |
39 | ///
40 | /// Initialises a .
41 | ///
42 | /// The simulation to apply to.
43 | /// The number of seconds to
44 | public FixedTimeStepUpdater(Simulation simulation, float timeStepSeconds)
45 | {
46 | _simulation = simulation;
47 | _timeStepSeconds = timeStepSeconds;
48 | _timeStepTicks = TimeSpan.FromSeconds(timeStepSeconds).Ticks;
49 | }
50 |
51 | ///
52 | /// Reset the current time
53 | ///
54 | public void Reset()
55 | {
56 | _leftOverTicks = 0;
57 | _lastTicks = 0;
58 | // Note we can't use Stopwatch.Restart here as we target netstandard1.0
59 | _stopwatch.Reset();
60 | _stopwatch.Start();
61 | }
62 |
63 | ///
64 | /// Updates the simulations enough times to bring it up to date.
65 | ///
66 | ///
67 | /// If was called recently enough, it may not actually
68 | /// update the simulation. It may also call it very many times if it has
69 | /// been a long time since it last ran.
70 | ///
71 | public void Update()
72 | {
73 | var ticks = _stopwatch.Elapsed.Ticks;
74 | var delta = ticks - _lastTicks;
75 | _lastTicks = ticks;
76 |
77 | delta += _leftOverTicks;
78 |
79 | var stepsThisUpdate = delta/_timeStepTicks;
80 |
81 | _leftOverTicks = delta%_timeStepTicks;
82 |
83 | for (var i = 0; i < stepsThisUpdate; i++)
84 | _simulation.Update(_timeStepSeconds);
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://ci.appveyor.com/project/drewnoakes/boing)
4 | [](https://www.nuget.org/packages/Boing/)
5 | [](https://opensource.org/licenses/Apache-2.0)
6 |
7 | A simple library for 2D physics simulations in .NET.
8 |
9 | ## Installation
10 |
11 | The easiest way to use this library is via its [NuGet package](https://www.nuget.org/packages/Boing/):
12 |
13 | PM> Install-Package Boing
14 |
15 | Boing supports `net35` (.NET Framework 3.5 and above) and `netstandard1.0` (.NET Standard 1.0 and above) for .NET Core and other platforms.
16 |
17 | ## Usage
18 |
19 | Build a `Simulation` comprising:
20 |
21 | - `PointMass` objects
22 | - Forces such as springs, gravity, Coloumb, viscosity
23 |
24 | Periodically update the simulation with a time step.
25 |
26 | ## Example
27 |
28 | Set up a simulation having some point masses and a few forces:
29 |
30 | ```csharp
31 | // create some point masses
32 | var pointMass1 = new PointMass(mass: 1.0f);
33 | var pointMass2 = new PointMass(mass: 2.0f);
34 |
35 | // create a new simulation
36 | var simulation = new Simulation
37 | {
38 | // add the point masses
39 | pointMass1,
40 | pointMass2,
41 |
42 | // create a spring between these point masses
43 | new Spring(pointMass1, pointMass2, length: 20),
44 |
45 | // point masses are attracted to one another
46 | new ColoumbForce(),
47 |
48 | // point masses move towards the origin
49 | new OriginAttractionForce(stiffness: 10),
50 |
51 | // gravity
52 | new FlowDownwardForce(magnitude: 100)
53 | };
54 | ```
55 |
56 | With the simulation configured, we must run it step by step in a loop. You could do this in
57 | several ways, but here are two common approaches.
58 |
59 | #### Max speed
60 |
61 | If you call `Update` as fast as possible, the simulation will most likely run faster than real time.
62 |
63 | ```csharp
64 | void RunAtMaxSpeed()
65 | {
66 | while (true)
67 | {
68 | // update the simulation
69 | simulation.Update(dt: 0.01f);
70 |
71 | // TODO use the resulting positions somehow
72 | Console.WriteLine($"PointMass1 at {pointMass1.Position}, PointMass2 at {pointMass2.Position}");
73 | }
74 | }
75 | ```
76 |
77 | #### At a fixed rate
78 |
79 | A more controlled approach is to use `FixedTimeStepUpdater`. This allows you to update the simulation
80 | at one rate, and integrate the simulation's output at another rate. For example, you might run the
81 | simulation at 200Hz, and render the output at 60Hz, as shown here:
82 |
83 | ```csharp
84 | async Task RunAtFixedRateAsync(CancellationToken token)
85 | {
86 | var updater = new FixedTimeStepUpdater(simulation, timeStepSeconds: 1f/200);
87 |
88 | while (!token.IsCancellationRequested)
89 | {
90 | updater.Update();
91 |
92 | Render();
93 |
94 | await Task.Delay(millisecondsDelay: 1/60, cancellationToken: token);
95 | }
96 | }
97 | ```
98 |
99 | For each `Render` call, the simulation will have multiple updates with smaller time deltas.
100 |
101 | This example code is `async`, but only to support a non-blocking delay. You could make it synchronous and use `Thread.Sleep` if you prefer.
102 |
--------------------------------------------------------------------------------
/Boing/LineSegment2f.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | namespace Boing
20 | {
21 | ///
22 | /// A line segment in a two dimensional coordinate system.
23 | ///
24 | public readonly struct LineSegment2f
25 | {
26 | /// Gets the starting point of the line segment.
27 | public Vector2f From { get; }
28 | /// Gets the ending point of the line segment.
29 | public Vector2f To { get; }
30 |
31 | ///
32 | /// Initialises a new instance of .
33 | ///
34 | /// The starting point of the line segment
35 | /// The ending point of the line segment
36 | public LineSegment2f(Vector2f from, Vector2f to)
37 | {
38 | From = from;
39 | To = to;
40 | }
41 |
42 | /// Gets a vector representing the difference of the line segment's endpoints.
43 | /// Computed as minus .
44 | public Vector2f Delta => new Vector2f(To.X - From.X, To.Y - From.Y);
45 |
46 | ///
47 | /// Attempt to intersect two line segments.
48 | ///
49 | ///
50 | /// Even if the line segments do not intersect, and will be set.
51 | /// If the lines are parallel, and are set to .
52 | ///
53 | /// The line to attempt intersection of this line with.
54 | /// The point of intersection if within the line segments, or empty..
55 | /// The distance along this line at which intersection would occur, or NaN if lines are collinear/parallel.
56 | /// The distance along the other line at which intersection would occur, or NaN if lines are collinear/parallel.
57 | /// true if the line segments intersect, otherwise false.
58 | public bool TryIntersect(LineSegment2f other, out Vector2f intersectionPoint, out float t, out float u)
59 | {
60 | static float Fake2DCross(Vector2f a, Vector2f b) => a.X * b.Y - a.Y * b.X;
61 |
62 | var p = From;
63 | var q = other.From;
64 | var r = Delta;
65 | var s = other.Delta;
66 |
67 | // t = (q − p) × s / (r × s)
68 | // u = (q − p) × r / (r × s)
69 |
70 | var denom = Fake2DCross(r, s);
71 |
72 | if (denom == 0)
73 | {
74 | // lines are collinear or parallel
75 | t = float.NaN;
76 | u = float.NaN;
77 | intersectionPoint = default;
78 | return false;
79 | }
80 |
81 | var tNumer = Fake2DCross(q - p, s);
82 | var uNumer = Fake2DCross(q - p, r);
83 |
84 | t = tNumer / denom;
85 | u = uNumer / denom;
86 |
87 | if (t < 0 || t > 1 || u < 0 || u > 1)
88 | {
89 | // line segments do not intersect within their ranges
90 | intersectionPoint = default;
91 | return false;
92 | }
93 |
94 | intersectionPoint = p + r * t;
95 | return true;
96 | }
97 | }
98 | }
--------------------------------------------------------------------------------
/Boing/Forces/ColoumbForce.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | namespace Boing
20 | {
21 | ///
22 | /// A force between point masses that follows an inverse square law.
23 | ///
24 | ///
25 | /// All pairs of unpinned point masses in the simulation will
26 | /// attract or repel one another with equal and opposite force.
27 | ///
28 | /// The force is proportional to the inverse square of the distance, meaning it
29 | /// is strongest for close particles, and the intensity diminishes quadratically
30 | /// as distance increases.
31 | ///
32 | /// The intensity of this effect can be linearly scaled using .
33 | ///
34 | /// As an optimisation, you may set the property to
35 | /// prevent this force applying over some distance threshold. By default this is
36 | /// 20,000.
37 | ///
38 | /// See https://en.wikipedia.org/wiki/Coulomb%27s_law for more information.
39 | ///
40 | public sealed class ColoumbForce : IForce
41 | {
42 | ///
43 | /// Initialises a new instance of .
44 | ///
45 | /// The to use. The default value is 20,000. Positive values cause repulsion, while negative values cause attraction.
46 | /// The to use. The default value is .
47 | public ColoumbForce(float scale = 20_000, float maxDistance = float.MaxValue)
48 | {
49 | Scale = scale;
50 | MaxDistance = maxDistance;
51 | }
52 |
53 | ///
54 | /// Gets and sets a value that linearly scales the intensity of the force applied between pairs of point masses.
55 | ///
56 | ///
57 | /// This value is equivalent to the force, in Newtons, applied to point masses one unit apart.
58 | ///
59 | /// Positive values cause repulsion, while negative values cause attraction.
60 | /// This value may be safely modified while the simulation is running.
61 | ///
62 | public float Scale { get; set; }
63 |
64 | ///
65 | /// Gets and sets the maximum distance over which the force applies.
66 | /// Point masses separated by distances greater than this threshold will not have a force applied.
67 | ///
68 | ///
69 | /// A value less than or equal to zero effectively disables this force.
70 | /// This value may be safely modified while the simulation is running.
71 | ///
72 | public float MaxDistance { get; set; }
73 |
74 | ///
75 | void IForce.ApplyTo(Simulation simulation)
76 | {
77 | if (MaxDistance <= 0)
78 | return;
79 |
80 | foreach (var pointMass1 in simulation.PointMasses)
81 | {
82 | foreach (var pointMass2 in simulation.PointMasses)
83 | {
84 | if (ReferenceEquals(pointMass1, pointMass2) || pointMass2.IsPinned)
85 | continue;
86 |
87 | var delta = pointMass2.Position - pointMass1.Position;
88 | var distance = delta.Norm();
89 |
90 | // ReSharper disable once CompareOfFloatsByEqualityOperator
91 | if (distance == 0.0f || distance > MaxDistance)
92 | continue;
93 |
94 | pointMass2.ApplyForce(delta*Scale/(distance*distance));
95 | }
96 | }
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/Boing/Forces/KeepWithinBoundsForce.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using System;
20 |
21 | namespace Boing
22 | {
23 | ///
24 | /// A force that works to keep point masses within a given rectangular boundary.
25 | ///
26 | ///
27 | /// No force is exerted on point masses within .
28 | /// As a point moves outside the boundary, the force exerted back towards the boundary increases exponentially
29 | /// (with power determined by ). Forces are capped at
30 | /// to prevent destabilising the simulation.
31 | ///
32 | public sealed class KeepWithinBoundsForce : IForce
33 | {
34 | ///
35 | /// Gets and sets the rectangular bounds to which point masses are constrained.
36 | ///
37 | public Rectangle2f Bounds { get; set; }
38 |
39 | ///
40 | /// Gets and sets the exponential power to use when computing the force to apply
41 | /// to a point mass found outside , based on its distance from
42 | /// the boundary.
43 | ///
44 | public float Magnitude { get; set; }
45 |
46 | ///
47 | /// Gets and sets an upper limit on the force applied to particles, in Newtons,
48 | /// to prevent destabilising the simulation.
49 | ///
50 | public float MaximumForce { get; set; }
51 |
52 | ///
53 | /// Initialises a new instance of .
54 | ///
55 | /// The bounds to which point masses are constrained.
56 | ///
57 | /// The upper limit on the force applied to particles. The default value is 1000 Newtonss.
58 | public KeepWithinBoundsForce(Rectangle2f bounds, float magnitude = 3.0f, float maximumForce = 1000.0f)
59 | {
60 | Bounds = bounds;
61 | Magnitude = magnitude;
62 | MaximumForce = maximumForce;
63 | }
64 |
65 | ///
66 | void IForce.ApplyTo(Simulation simulation)
67 | {
68 | foreach (var pointMass in simulation.PointMasses)
69 | {
70 | if (pointMass.IsPinned)
71 | continue;
72 |
73 | if (pointMass.Position.X > Bounds.Right)
74 | {
75 | var force = (float)Math.Pow(pointMass.Position.X - Bounds.Right, Magnitude);
76 | if (force > MaximumForce)
77 | force = MaximumForce;
78 | pointMass.ApplyForce(new Vector2f(-force, 0));
79 | }
80 | else if (pointMass.Position.X < Bounds.Left)
81 | {
82 | var force = (float)Math.Pow(Bounds.Left - pointMass.Position.X, Magnitude);
83 | if (force > MaximumForce)
84 | force = MaximumForce;
85 | pointMass.ApplyForce(new Vector2f(force, 0));
86 | }
87 |
88 | if (pointMass.Position.Y > Bounds.Bottom)
89 | {
90 | var force = (float)Math.Pow(pointMass.Position.Y - Bounds.Bottom, Magnitude);
91 | if (force > MaximumForce)
92 | force = MaximumForce;
93 | pointMass.ApplyForce(new Vector2f(0, -force));
94 | }
95 | else if (pointMass.Position.Y < Bounds.Top)
96 | {
97 | var force = (float)Math.Pow(Bounds.Top - pointMass.Position.Y, Magnitude);
98 | if (force > MaximumForce)
99 | force = MaximumForce;
100 | pointMass.ApplyForce(new Vector2f(0, force));
101 | }
102 | }
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/Boing/Simulation.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using System;
20 | using System.Collections;
21 | using System.Collections.Generic;
22 |
23 | namespace Boing
24 | {
25 | ///
26 | /// Top-level object for a Boing physical simulation.
27 | /// Contains instances of and ,
28 | /// and can update them one time step at a time.
29 | ///
30 | public sealed class Simulation : IEnumerable
31 | {
32 | private readonly HashSet _pointMasses = new HashSet();
33 | private readonly HashSet _forces = new HashSet();
34 |
35 | ///
36 | /// Gets the set of point masses within this simulation.
37 | ///
38 | public IEnumerable PointMasses => _pointMasses;
39 |
40 | ///
41 | /// Gets the set of forces within this simulation.
42 | ///
43 | public IEnumerable Forces => _forces;
44 |
45 | ///
46 | /// Progresses the simulation one time step, updating the and
47 | /// of all point masses within the simulation.
48 | ///
49 | /// Elapsed time since the last update, in seconds.
50 | public void Update(float dt)
51 | {
52 | foreach (var force in _forces)
53 | force.ApplyTo(this);
54 |
55 | foreach (var pointMass in _pointMasses)
56 | pointMass.Update(dt);
57 | }
58 |
59 | ///
60 | /// Aggregates the kinetic energy of all point masses in the simulation, and
61 | /// returns the sum.
62 | ///
63 | ///
64 | /// Kinetic energy is computed based on point mass velocities.
65 | /// It can be useful in determining whether a simulation has come to rest,
66 | /// although a moving average should be applied to prevent incorrectly taking
67 | /// a transiently near-static moment to mean equilibrium is reached.
68 | ///
69 | ///
70 | public float GetTotalKineticEnergy()
71 | {
72 | // Ek = 1/2 m v^2
73 |
74 | float sum = 0;
75 |
76 | // ReSharper disable once LoopCanBeConvertedToQuery
77 | foreach (var pointMass in _pointMasses)
78 | {
79 | var speed = pointMass.Velocity.Norm();
80 | sum += pointMass.Mass*speed*speed;
81 | }
82 |
83 | return sum / 2;
84 | }
85 |
86 | ///
87 | /// Removes all and objects from the simulation.
88 | ///
89 | public void Clear()
90 | {
91 | _pointMasses.Clear();
92 | _forces.Clear();
93 | }
94 |
95 | ///
96 | /// Adds to the simulation.
97 | ///
98 | /// The to add to the simulation.
99 | /// already exists.
100 | public void Add(IForce force)
101 | {
102 | if (!_forces.Add(force))
103 | throw new ArgumentException("Already exists.", nameof(force));
104 | }
105 |
106 | ///
107 | /// Removes from the simulation.
108 | ///
109 | /// The to remove from the simulation.
110 | public bool Remove(IForce force) => _forces.Remove(force);
111 |
112 | ///
113 | /// Adds to the simulation.
114 | ///
115 | /// The to add to the simulation.
116 | /// already exists.
117 | public void Add(PointMass pointMass)
118 | {
119 | if (!_pointMasses.Add(pointMass))
120 | throw new ArgumentException("Already exists.", nameof(pointMass));
121 | }
122 |
123 | ///
124 | /// Removes from the simulation.
125 | ///
126 | /// The to remove from the simulation.
127 | public bool Remove(PointMass pointMass) => _pointMasses.Remove(pointMass);
128 |
129 | ///
130 | IEnumerator IEnumerable.GetEnumerator()
131 | {
132 | throw new NotSupportedException($"{nameof(Simulation)} only implements {nameof(IEnumerable)} to enable C# object initialisers.");
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/Boing/Forces/Spring.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using System;
20 | using System.Diagnostics;
21 |
22 | namespace Boing
23 | {
24 | ///
25 | /// A force that models a linear spring between two point masses according to Hooke's law.
26 | ///
27 | ///
28 | /// Unlike many other implementations, a single
29 | /// instance does not apply to all point masses in the simulation. It applies to two, only.
30 | ///
31 | /// The spring is defined by its two point masses, a length and a spring constant.
32 | /// At each time step, the distance between the point masses is computed and compared
33 | /// against the spring's length.
34 | ///
35 | /// If the distance matches the length, no force is applied.
36 | /// When the distance diverges from the spring's length, the spring undergoes compression
37 | /// or elongation and exerts a restorative force against both point masses.
38 | ///
39 | /// Note that if one point mass is pinned, the force applies doubly to the unpinned end.
40 | /// If both point masses are pinned, the spring has no effect.
41 | ///
42 | /// See https://en.wikipedia.org/wiki/Hooke%27s_law for more information.
43 | ///
44 | public sealed class Spring : IForce
45 | {
46 | ///
47 | /// Gets one of the two point masses connected by this spring.
48 | ///
49 | ///
50 | /// The orientation of the spring has no effect. and
51 | /// could be swapped and the outcome would be identical.
52 | ///
53 | public PointMass Source { get; }
54 |
55 | ///
56 | /// Gets one of the two point masses connected by this spring.
57 | ///
58 | ///
59 | /// The orientation of the spring has no effect. and
60 | /// could be swapped and the outcome would be identical.
61 | ///
62 | public PointMass Target { get; }
63 |
64 | ///
65 | /// Gets and sets the resting length of the spring.
66 | ///
67 | public float Length { get; set; }
68 |
69 | ///
70 | /// Gets and sets the spring constant.
71 | ///
72 | public float K { get; set; }
73 |
74 | ///
75 | /// Initialises a new instance of .
76 | ///
77 | /// One of the two point masses.
78 | /// One of the two point masses.
79 | /// The resting length of the spring. The default value is 100 units.
80 | /// The string constant. The default value is 80.
81 | public Spring(PointMass source, PointMass target, float length = 100.0f, float k = 80.0f)
82 | {
83 | Source = source;
84 | Target = target;
85 | Length = length;
86 | K = k;
87 | }
88 |
89 | ///
90 | /// Gets a 2D line segment whose start and end points match those of
91 | /// the and point mass positions.
92 | ///
93 | public LineSegment2f LineSegment => new LineSegment2f(Source.Position, Target.Position);
94 |
95 | ///
96 | /// Gets the 2D axis-aligned bounding box that encloses the and
97 | /// point mass positions.
98 | ///
99 | public Rectangle2f Bounds => new Rectangle2f(
100 | Math.Min(Source.Position.X, Target.Position.X),
101 | Math.Min(Source.Position.Y, Target.Position.Y),
102 | Math.Abs(Source.Position.X - Target.Position.X),
103 | Math.Abs(Source.Position.Y - Target.Position.Y));
104 |
105 | ///
106 | void IForce.ApplyTo(Simulation simulation)
107 | {
108 | var source = Source;
109 | var target = Target;
110 |
111 | var delta = target.Position - source.Position;
112 | var direction = delta.Normalized(out float deltaNorm);
113 | var displacement = Length - deltaNorm;
114 |
115 | Debug.Assert(!float.IsNaN(displacement), "!float.IsNaN(displacement)");
116 |
117 | if (!source.IsPinned && !target.IsPinned)
118 | {
119 | source.ApplyForce(direction*(K*displacement*-0.5f));
120 | target.ApplyForce(direction*(K*displacement*0.5f));
121 | }
122 | else if (source.IsPinned && !target.IsPinned)
123 | {
124 | target.ApplyForce(direction*(K*displacement));
125 | }
126 | else if (!source.IsPinned && target.IsPinned)
127 | {
128 | source.ApplyForce(direction*(K*-displacement));
129 | }
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/Boing/Vector2f.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using System;
20 |
21 | // ReSharper disable CompareOfFloatsByEqualityOperator
22 |
23 | namespace Boing
24 | {
25 | ///
26 | /// A point in a two dimensional coordinate system.
27 | ///
28 | public readonly struct Vector2f
29 | {
30 | ///
31 | /// Initialises a .
32 | ///
33 | /// The x coordinate value.
34 | /// The y coordinate value.
35 | public Vector2f(float x, float y)
36 | {
37 | X = x;
38 | Y = y;
39 | }
40 |
41 | /// Gets the x coordinate value.
42 | public readonly float X;
43 | /// Gets the y coordinate value.
44 | public readonly float Y;
45 |
46 | /// Gets the norm (length) of the vector.
47 | public float Norm() => (float)Math.Sqrt(X*X + Y*Y);
48 |
49 | ///
50 | /// Returns a normalized version of this vector.
51 | ///
52 | ///
53 | /// The normalized vector has length one (it is a unit vector).
54 | ///
55 | /// The normalized version of this vector.
56 | public Vector2f Normalized() => this/Norm();
57 |
58 | ///
59 | /// Returns a normalized version of this vector.
60 | ///
61 | ///
62 | /// The normalized vector has length one (it is a unit vector).
63 | /// This overload is useful if you need to know the length of a vector,
64 | /// and need its normalized form, in which case it is both convenient and efficient.
65 | ///
66 | /// The length of the vector before normalization.
67 | /// The normalized version of this vector.
68 | public Vector2f Normalized(out float norm)
69 | {
70 | norm = Norm();
71 | return this/norm;
72 | }
73 |
74 | ///
75 | /// Gets whether either the or values are .
76 | ///
77 | public bool HasNaN => float.IsNaN(X) || float.IsNaN(Y);
78 |
79 | /// Gets whether either the or values are infinite.
80 | public bool HasInfinity => float.IsInfinity(X) || float.IsInfinity(Y);
81 |
82 | #region Factories
83 |
84 | private static readonly Random _random = new Random();
85 |
86 | ///
87 | /// Produces random points with and values distributed evenly
88 | /// over a square of side length that is centered at the origin.
89 | ///
90 | /// The width and height of the square over which random points are distributed.
91 | /// The next random point in the specified range.
92 | public static Vector2f Random(float valueRange = 1.0f)
93 | {
94 | return new Vector2f(
95 | valueRange*((float)_random.NextDouble() - 0.5f),
96 | valueRange*((float)_random.NextDouble() - 0.5f));
97 | }
98 |
99 | ///
100 | /// Gets a vector with both and equalling zero.
101 | ///
102 | public static Vector2f Zero => default;
103 |
104 | #endregion
105 |
106 | #region Equality / Hashing
107 |
108 | ///
109 | /// Indicates whether this instance equals .
110 | ///
111 | /// The vector to compare against.
112 | /// true if the vectors are equal, otherwise false.
113 | public bool Equals(Vector2f other) => X.Equals(other.X) && Y.Equals(other.Y);
114 |
115 | ///
116 | public override bool Equals(object obj) => obj is Vector2f v && Equals(v);
117 |
118 | ///
119 | public override int GetHashCode() => unchecked((X.GetHashCode()*397) ^ Y.GetHashCode());
120 |
121 | #endregion
122 |
123 | #region Operators
124 |
125 | #pragma warning disable 1591
126 | public static bool operator ==(Vector2f a, Vector2f b) => a.X == b.X && a.Y == b.Y;
127 | public static bool operator !=(Vector2f a, Vector2f b) => a.X != b.X || a.Y != b.Y;
128 |
129 | public static Vector2f operator +(Vector2f a, Vector2f b) => new Vector2f(a.X + b.X, a.Y + b.Y);
130 | public static Vector2f operator -(Vector2f a, Vector2f b) => new Vector2f(a.X - b.X, a.Y - b.Y);
131 | public static Vector2f operator *(Vector2f a, float b) => new Vector2f(a.X*b, a.Y*b);
132 | public static Vector2f operator *(float a, Vector2f b) => new Vector2f(b.X*a, b.Y*a);
133 | public static Vector2f operator /(Vector2f a, float b) => b == 0.0f ? Zero : new Vector2f(a.X/b, a.Y/b);
134 | public static Vector2f operator -(Vector2f v) => new Vector2f(-v.X, -v.Y);
135 | #pragma warning restore 1591
136 |
137 | #endregion
138 |
139 | ///
140 | public override string ToString() => $"X: {X}, Y: {Y}";
141 | }
142 | }
--------------------------------------------------------------------------------
/Boing/Rectangle2f.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using System;
20 | using System.Collections.Generic;
21 |
22 | namespace Boing
23 | {
24 | ///
25 | /// An axis-aligned rectangular area in a two dimensional coordinate system.
26 | ///
27 | public readonly struct Rectangle2f
28 | {
29 | ///
30 | /// The minimum point in the rectangle, which is also the top left corner.
31 | ///
32 | public Vector2f Min { get; }
33 |
34 | ///
35 | /// The maximum point in the rectangle, which is also the bottom right corner.
36 | ///
37 | public Vector2f Max { get; }
38 |
39 | ///
40 | /// Initialises a new instance of from the pair of its minimum and maximum points.
41 | ///
42 | /// The minimum point, which is also the top left corner.
43 | /// The maximum point, which is also the bottom right corner.
44 | ///
45 | public Rectangle2f(Vector2f min, Vector2f max)
46 | {
47 | if (min.X > max.X)
48 | throw new ArgumentException("min.X is greater than max.X");
49 | if (min.Y > max.Y)
50 | throw new ArgumentException("min.Y is greater than max.Y");
51 |
52 | Min = min;
53 | Max = max;
54 | }
55 |
56 | ///
57 | /// Initialises a new instance of from its minimum point, and a width and height.
58 | ///
59 | /// The x value of the minimum point.
60 | /// The y value of the minimum point.
61 | /// The width of the rectangle.
62 | /// The height of the rectangle.
63 | public Rectangle2f(float x, float y, float width, float height)
64 | : this(new Vector2f(x, y), new Vector2f(x + width, y + height))
65 | {}
66 |
67 | /// Gets the x value of the left edge.
68 | public float Left => Min.X;
69 | /// Gets the x value of the right edge.
70 | public float Right => Max.X;
71 | /// Gets the y value of the top edge.
72 | public float Top => Min.Y;
73 | /// Gets the y value of the bottom edge.
74 | public float Bottom => Max.Y;
75 |
76 | /// Gets the width of the rectangle.
77 | public float Width => Right - Left;
78 | /// Gets the height of the rectangle.
79 | public float Height => Bottom - Top;
80 |
81 | /// Gets the position of the top left corner.
82 | public Vector2f TopLeft => Min;
83 | /// Gets the position of the top right corner.
84 | public Vector2f TopRight => new Vector2f(Max.X, Min.Y);
85 | /// Gets the position of the bottom left corner.
86 | public Vector2f BottomLeft => new Vector2f(Min.X, Max.Y);
87 | /// Gets the position of the bottom right corner.
88 | public Vector2f BottomRight => Max;
89 |
90 | ///
91 | /// Enumerates the four edges of the rectangle.
92 | ///
93 | ///
94 | /// Edges are produce in clockwise order: top, right, bottom, left.
95 | ///
96 | public IEnumerable Edges
97 | {
98 | get
99 | {
100 | yield return new LineSegment2f(TopLeft, TopRight);
101 | yield return new LineSegment2f(TopRight, BottomRight);
102 | yield return new LineSegment2f(BottomLeft, BottomRight);
103 | yield return new LineSegment2f(TopLeft, BottomLeft);
104 | }
105 | }
106 |
107 | ///
108 | /// Attempt to intersect a line segment with this rectangle.
109 | ///
110 | /// The line to intersect with this rectangle.
111 | /// The point of intersection.
112 | /// the distance along this line at which intersection would occur, or zero if no intersection occurs.
113 | /// true if an intersection exists, otherwise false.
114 | public bool TryIntersect(LineSegment2f line, out Vector2f intersectionPoint, out float t)
115 | {
116 | var minT = float.MaxValue;
117 | var found = false;
118 |
119 | intersectionPoint = default;
120 |
121 | foreach (var edge in Edges)
122 | {
123 | if (edge.TryIntersect(line, out Vector2f point, out float edgeT, out float lineT))
124 | {
125 | if (lineT > minT)
126 | continue;
127 | found = true;
128 | minT = lineT;
129 | intersectionPoint = point;
130 | }
131 | }
132 |
133 | if (found)
134 | {
135 | t = minT;
136 | return true;
137 | }
138 |
139 | t = 0;
140 | return false;
141 | }
142 | }
143 | }
--------------------------------------------------------------------------------
/Boing/PointMass.cs:
--------------------------------------------------------------------------------
1 | #region License
2 |
3 | // Copyright 2015-2020 Drew Noakes, Krzysztof Dul
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | #endregion
18 |
19 | using System.Diagnostics;
20 |
21 | namespace Boing
22 | {
23 | ///
24 | /// Models an idealised physical body for the purposes of simulation, having mass and infinitesimal volume and dimension.
25 | ///
26 | ///
27 | /// Point masses may be pinned, in which case forces and impulses should not be applied.
28 | /// It is up to implementations of to ensure this.
29 | ///
30 | /// You may store custom data in the property.
31 | ///
32 | /// Whilst all properties of this type are mutable, in normal usage and
33 | /// are modified by implementations during simulation .
34 | ///
35 | /// and may safely be modified at any time, having a direct effect on the simulation.
36 | ///
37 | public sealed class PointMass
38 | {
39 | private Vector2f _force;
40 |
41 | ///
42 | /// Gets and sets the mass of this point mass.
43 | ///
44 | ///
45 | /// This value may be safely modified while the simulation is running.
46 | ///
47 | public float Mass { get; set; }
48 |
49 | ///
50 | /// Gets and sets whether this point mass is pinned. When pinned, it should not be moved.
51 | ///
52 | ///
53 | /// This value may be safely modified while the simulation is running.
54 | ///
55 | public bool IsPinned { get; set; }
56 |
57 | ///
58 | /// Gets and sets an object of miscellaneous data to associate with this point mass.
59 | ///
60 | ///
61 | /// This value is optional. It is provided for convenience, and is unused by the library.
62 | ///
63 | public object? Tag { get; set; }
64 |
65 | ///
66 | /// Gets and sets the position of this point mass.
67 | ///
68 | ///
69 | /// In normal usage this property is modified by implementations
70 | /// during simulation .
71 | ///
72 | public Vector2f Position { get; set; }
73 |
74 | ///
75 | /// Gets and sets the position of this point mass.
76 | ///
77 | ///
78 | /// In normal usage this property is modified by implementations
79 | /// during simulation .
80 | ///
81 | public Vector2f Velocity { get; set; }
82 |
83 | ///
84 | /// Initialises a new instance of .
85 | ///
86 | /// The initial mass. The default value is 1.
87 | /// The initial position. The default value of null causes a random point between +/-0.5 in both X and Y to be set.
88 | public PointMass(float mass = 1.0f, Vector2f? position = null)
89 | {
90 | Mass = mass;
91 | Position = position ?? Vector2f.Random();
92 | }
93 |
94 | ///
95 | /// Applies to the point mass.
96 | ///
97 | ///
98 | /// Forces are accumulated in each time step, and this method may be called multiple times in a
99 | /// single time step. Only when is called on this point mass will the
100 | /// velocity and position be updated based upon any accumulated force.
101 | ///
102 | /// The force to apply to the point mass.
103 | public void ApplyForce(Vector2f force)
104 | {
105 | Debug.Assert(!float.IsNaN(force.X) && !float.IsNaN(force.Y), "!float.IsNaN(force.X) && !float.IsNaN(force.Y)");
106 | Debug.Assert(!float.IsInfinity(force.X) && !float.IsInfinity(force.Y), "!float.IsInfinity(force.X) && !float.IsInfinity(force.Y)");
107 |
108 | // Accumulate force
109 | _force += force;
110 | }
111 |
112 | ///
113 | /// Applies to the point mass.
114 | ///
115 | ///
116 | /// Impulses are applied directly to the point mass's , and as such their impact is
117 | /// unaffected by the simulation's step size. However will only be modified by the next call
118 | /// to .
119 | ///
120 | /// The impulse to apply to the point mass.
121 | public void ApplyImpulse(Vector2f impulse)
122 | {
123 | // Update velocity
124 | Velocity += impulse/Mass;
125 | }
126 |
127 | ///
128 | /// Updates according to any applied forces and the time delta, and then
129 | /// updates .
130 | ///
131 | /// Elapsed time since the last update, in seconds.
132 | public void Update(float dt)
133 | {
134 | // Update velocity
135 | Velocity += _force/Mass*dt;
136 |
137 | // Update position
138 | Position += Velocity*dt;
139 |
140 | Debug.Assert(!float.IsNaN(Position.X) && !float.IsNaN(Position.Y), "!float.IsNaN(Position.X) && !float.IsNaN(Position.Y)");
141 | Debug.Assert(!float.IsInfinity(Position.X) && !float.IsInfinity(Position.Y), "!float.IsInfinity(Position.X) && !float.IsInfinity(Position.Y)");
142 |
143 | // Clear force
144 | _force = Vector2f.Zero;
145 | }
146 | }
147 | }
--------------------------------------------------------------------------------
/Resources/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
137 |
--------------------------------------------------------------------------------
/Resources/logo-square.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
137 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "{}"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2015-2020 Drew Noakes, Krzysztof Dul
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
204 | ------------------------------------------------------------------------------
205 |
206 | Boing depends upon the following 3rd party packages with their own
207 | license terms:
208 |
209 | - xunit, licensed under Apache 2.0 (used during development)
210 |
--------------------------------------------------------------------------------