├── .gitattributes
├── .gitignore
├── .vs
└── config
│ └── applicationhost.config
├── Images
├── cycle.gif
├── firework.gif
├── metaball.gif
├── newton_scradle.gif
├── poly_rod.gif
└── spring.gif
├── Physics2D.sln
├── Physics2D
├── Collision
│ ├── Basic
│ │ ├── ParticleLink.cs
│ │ ├── ParticleRod.cs
│ │ └── ParticleRope.cs
│ ├── ContactType.cs
│ ├── ParticleCollisionDetector.cs
│ ├── ParticleContact.cs
│ ├── ParticleContactGenerator.cs
│ ├── ParticleContactResolver.cs
│ └── Shapes
│ │ ├── Circle.cs
│ │ ├── Edge.cs
│ │ ├── Point.cs
│ │ ├── Shape.cs
│ │ └── ShapeType.cs
├── Common
│ ├── Events
│ │ └── ContactEvent.cs
│ ├── Exceptions
│ │ └── InvalidArgumentException.cs
│ ├── MathHelper.cs
│ └── Vector2D.cs
├── ConvertUnits.cs
├── Core
│ ├── ContactRegistry.cs
│ ├── ForceRegistry.cs
│ └── World.cs
├── Factories
│ ├── ContactFactory.cs
│ ├── ParticleFactory.cs
│ └── ZoneFactory.cs
├── Force
│ ├── ParticleConstantForce.cs
│ ├── ParticleDrag.cs
│ ├── ParticleElastic.cs
│ ├── ParticleForceGenerator.cs
│ ├── ParticleGravity.cs
│ └── Zones
│ │ ├── GlobalZone.cs
│ │ ├── RectangleZone.cs
│ │ └── Zone.cs
├── Object
│ ├── CombinedParticle.cs
│ ├── CustomObject.cs
│ ├── Particle.cs
│ ├── PhysicsObject.cs
│ └── Tools
│ │ ├── Handle.cs
│ │ └── IPin.cs
├── Physics2D.csproj
├── Settings.cs
└── stylecop.json
├── README.md
├── UnitTest
├── Collision
│ ├── Basic
│ │ ├── ParticleRodTest.cs
│ │ └── ParticleRopeTest.cs
│ ├── ParticleCollisionDetectorTest.cs
│ ├── ParticleContactResolverTest.cs
│ ├── ParticleContactTest.cs
│ └── Shapes
│ │ ├── CircleTest.cs
│ │ ├── EdgeTest.cs
│ │ ├── PointTest.cs
│ │ └── ShapeTest.cs
├── Common
│ ├── MathHelperTest.cs
│ └── Vector2DTest.cs
├── ConvertUnitsTest.cs
├── Core
│ ├── ContactRegistryTest.cs
│ ├── ForceRegistryTest.cs
│ └── WorldTest.cs
├── Factories
│ ├── ContactFactoryTest.cs
│ ├── ParticleFactoryTest.cs
│ └── ZoneFactoryTest.cs
├── Force
│ ├── ParticleConstantForceTest.cs
│ ├── ParticleDragTest.cs
│ ├── ParticleElasticTest.cs
│ ├── ParticleGravityTest.cs
│ └── Zones
│ │ ├── GlobalZoneTest.cs
│ │ └── RectangleZoneTest.cs
├── Object
│ ├── CombinedParticleTest.cs
│ ├── ParticleTest.cs
│ └── Tools
│ │ └── HandleTest.cs
├── Properties
│ └── AssemblyInfo.cs
├── UnitTest.csproj
└── packages.config
└── WPFDemo
├── App.config
├── App.xaml
├── App.xaml.cs
├── CircleDemo
├── Circle.xaml
├── Circle.xaml.cs
└── CircleDemo.cs
├── ContactDemo
├── Ball.cs
├── Contact.xaml
├── Contact.xaml.cs
└── ContactDemo.cs
├── DrawingCanvas.cs
├── ElasticDemo
├── DestructibleElastic.cs
├── Elastic.xaml
├── Elastic.xaml.cs
├── ElasticDemo.cs
└── ElasticatedNet.cs
├── FireworksDemo
├── Fireworks.xaml
├── Fireworks.xaml.cs
└── FireworksDemo.cs
├── FluidDemo
├── Fluid.xaml
├── Fluid.xaml.cs
├── FluidDemo.cs
└── Water.cs
├── Graphic
├── IDrawable.cs
├── PhysicsGraphic.cs
└── TimeTracker.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
├── Resources.resx
├── Settings.Designer.cs
└── Settings.settings
├── RobDemo
├── Rob.xaml
├── Rob.xaml.cs
└── RobDemo.cs
├── WPFDemo.csproj
├── packages.config
└── stylecop.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.pch
68 | *.pdb
69 | *.pgc
70 | *.pgd
71 | *.rsp
72 | *.sbr
73 | *.tlb
74 | *.tli
75 | *.tlh
76 | *.tmp
77 | *.tmp_proj
78 | *.log
79 | *.vspscc
80 | *.vssscc
81 | .builds
82 | *.pidb
83 | *.svclog
84 | *.scc
85 |
86 | # Chutzpah Test files
87 | _Chutzpah*
88 |
89 | # Visual C++ cache files
90 | ipch/
91 | *.aps
92 | *.ncb
93 | *.opendb
94 | *.opensdf
95 | *.sdf
96 | *.cachefile
97 | *.VC.db
98 | *.VC.VC.opendb
99 |
100 | # Visual Studio profiler
101 | *.psess
102 | *.vsp
103 | *.vspx
104 | *.sap
105 |
106 | # Visual Studio Trace Files
107 | *.e2e
108 |
109 | # TFS 2012 Local Workspace
110 | $tf/
111 |
112 | # Guidance Automation Toolkit
113 | *.gpState
114 |
115 | # ReSharper is a .NET coding add-in
116 | _ReSharper*/
117 | *.[Rr]e[Ss]harper
118 | *.DotSettings.user
119 |
120 | # JustCode is a .NET coding add-in
121 | .JustCode
122 |
123 | # TeamCity is a build add-in
124 | _TeamCity*
125 |
126 | # DotCover is a Code Coverage Tool
127 | *.dotCover
128 |
129 | # AxoCover is a Code Coverage Tool
130 | .axoCover/*
131 | !.axoCover/settings.json
132 |
133 | # Visual Studio code coverage results
134 | *.coverage
135 | *.coveragexml
136 |
137 | # NCrunch
138 | _NCrunch_*
139 | .*crunch*.local.xml
140 | nCrunchTemp_*
141 |
142 | # MightyMoose
143 | *.mm.*
144 | AutoTest.Net/
145 |
146 | # Web workbench (sass)
147 | .sass-cache/
148 |
149 | # Installshield output folder
150 | [Ee]xpress/
151 |
152 | # DocProject is a documentation generator add-in
153 | DocProject/buildhelp/
154 | DocProject/Help/*.HxT
155 | DocProject/Help/*.HxC
156 | DocProject/Help/*.hhc
157 | DocProject/Help/*.hhk
158 | DocProject/Help/*.hhp
159 | DocProject/Help/Html2
160 | DocProject/Help/html
161 |
162 | # Click-Once directory
163 | publish/
164 |
165 | # Publish Web Output
166 | *.[Pp]ublish.xml
167 | *.azurePubxml
168 | # Note: Comment the next line if you want to checkin your web deploy settings,
169 | # but database connection strings (with potential passwords) will be unencrypted
170 | *.pubxml
171 | *.publishproj
172 |
173 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
174 | # checkin your Azure Web App publish settings, but sensitive information contained
175 | # in these scripts will be unencrypted
176 | PublishScripts/
177 |
178 | # NuGet Packages
179 | *.nupkg
180 | # The packages folder can be ignored because of Package Restore
181 | **/[Pp]ackages/*
182 | # except build/, which is used as an MSBuild target.
183 | !**/[Pp]ackages/build/
184 | # Uncomment if necessary however generally it will be regenerated when needed
185 | #!**/[Pp]ackages/repositories.config
186 | # NuGet v3's project.json files produces more ignorable files
187 | *.nuget.props
188 | *.nuget.targets
189 |
190 | # Microsoft Azure Build Output
191 | csx/
192 | *.build.csdef
193 |
194 | # Microsoft Azure Emulator
195 | ecf/
196 | rcf/
197 |
198 | # Windows Store app package directories and files
199 | AppPackages/
200 | BundleArtifacts/
201 | Package.StoreAssociation.xml
202 | _pkginfo.txt
203 | *.appx
204 |
205 | # Visual Studio cache files
206 | # files ending in .cache can be ignored
207 | *.[Cc]ache
208 | # but keep track of directories ending in .cache
209 | !*.[Cc]ache/
210 |
211 | # Others
212 | ClientBin/
213 | ~$*
214 | *~
215 | *.dbmdl
216 | *.dbproj.schemaview
217 | *.jfm
218 | *.pfx
219 | *.publishsettings
220 | orleans.codegen.cs
221 |
222 | # Including strong name files can present a security risk
223 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
224 | #*.snk
225 |
226 | # Since there are multiple workflows, uncomment next line to ignore bower_components
227 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
228 | #bower_components/
229 |
230 | # RIA/Silverlight projects
231 | Generated_Code/
232 |
233 | # Backup & report files from converting an old project file
234 | # to a newer Visual Studio version. Backup files are not needed,
235 | # because we have git ;-)
236 | _UpgradeReport_Files/
237 | Backup*/
238 | UpgradeLog*.XML
239 | UpgradeLog*.htm
240 |
241 | # SQL Server files
242 | *.mdf
243 | *.ldf
244 | *.ndf
245 |
246 | # Business Intelligence projects
247 | *.rdl.data
248 | *.bim.layout
249 | *.bim_*.settings
250 |
251 | # Microsoft Fakes
252 | FakesAssemblies/
253 |
254 | # GhostDoc plugin setting file
255 | *.GhostDoc.xml
256 |
257 | # Node.js Tools for Visual Studio
258 | .ntvs_analysis.dat
259 | node_modules/
260 |
261 | # TypeScript v1 declaration files
262 | typings/
263 |
264 | # Visual Studio 6 build log
265 | *.plg
266 |
267 | # Visual Studio 6 workspace options file
268 | *.opt
269 |
270 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
271 | *.vbw
272 |
273 | # Visual Studio LightSwitch build output
274 | **/*.HTMLClient/GeneratedArtifacts
275 | **/*.DesktopClient/GeneratedArtifacts
276 | **/*.DesktopClient/ModelManifest.xml
277 | **/*.Server/GeneratedArtifacts
278 | **/*.Server/ModelManifest.xml
279 | _Pvt_Extensions
280 |
281 | # Paket dependency manager
282 | .paket/paket.exe
283 | paket-files/
284 |
285 | # FAKE - F# Make
286 | .fake/
287 |
288 | # JetBrains Rider
289 | .idea/
290 | *.sln.iml
291 |
292 | # CodeRush
293 | .cr/
294 |
295 | # Python Tools for Visual Studio (PTVS)
296 | __pycache__/
297 | *.pyc
298 |
299 | # Cake - Uncomment if you are using it
300 | # tools/**
301 | # !tools/packages.config
302 |
303 | # Tabs Studio
304 | *.tss
305 |
306 | # Telerik's JustMock configuration file
307 | *.jmconfig
308 |
309 | # BizTalk build output
310 | *.btp.cs
311 | *.btm.cs
312 | *.odx.cs
313 | *.xsd.cs
314 |
315 | # OpenCover UI analysis results
316 | OpenCover/
317 |
318 | # Azure Stream Analytics local run output
319 | ASALocalRun/
320 |
321 | # MSBuild Binary and Structured Log
322 | *.binlog
--------------------------------------------------------------------------------
/Images/cycle.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Blueve/Physics2D/42a133a0eab403a7e9797f6629b81916f267c812/Images/cycle.gif
--------------------------------------------------------------------------------
/Images/firework.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Blueve/Physics2D/42a133a0eab403a7e9797f6629b81916f267c812/Images/firework.gif
--------------------------------------------------------------------------------
/Images/metaball.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Blueve/Physics2D/42a133a0eab403a7e9797f6629b81916f267c812/Images/metaball.gif
--------------------------------------------------------------------------------
/Images/newton_scradle.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Blueve/Physics2D/42a133a0eab403a7e9797f6629b81916f267c812/Images/newton_scradle.gif
--------------------------------------------------------------------------------
/Images/poly_rod.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Blueve/Physics2D/42a133a0eab403a7e9797f6629b81916f267c812/Images/poly_rod.gif
--------------------------------------------------------------------------------
/Images/spring.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Blueve/Physics2D/42a133a0eab403a7e9797f6629b81916f267c812/Images/spring.gif
--------------------------------------------------------------------------------
/Physics2D.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2010
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTest", "UnitTest\UnitTest.csproj", "{EAA4C780-0963-4479-9D11-E162127C0014}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFDemo", "WPFDemo\WPFDemo.csproj", "{7BEAE24F-5041-441D-99D9-D82657A8BBE7}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Physics2D", "Physics2D\Physics2D.csproj", "{E102F925-90FE-41F2-856E-3FAB4BB2AD58}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Debug|ARM = Debug|ARM
16 | Debug|x64 = Debug|x64
17 | Debug|x86 = Debug|x86
18 | Release|Any CPU = Release|Any CPU
19 | Release|ARM = Release|ARM
20 | Release|x64 = Release|x64
21 | Release|x86 = Release|x86
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {EAA4C780-0963-4479-9D11-E162127C0014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {EAA4C780-0963-4479-9D11-E162127C0014}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {EAA4C780-0963-4479-9D11-E162127C0014}.Debug|ARM.ActiveCfg = Debug|Any CPU
27 | {EAA4C780-0963-4479-9D11-E162127C0014}.Debug|ARM.Build.0 = Debug|Any CPU
28 | {EAA4C780-0963-4479-9D11-E162127C0014}.Debug|x64.ActiveCfg = Debug|Any CPU
29 | {EAA4C780-0963-4479-9D11-E162127C0014}.Debug|x64.Build.0 = Debug|Any CPU
30 | {EAA4C780-0963-4479-9D11-E162127C0014}.Debug|x86.ActiveCfg = Debug|Any CPU
31 | {EAA4C780-0963-4479-9D11-E162127C0014}.Debug|x86.Build.0 = Debug|Any CPU
32 | {EAA4C780-0963-4479-9D11-E162127C0014}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {EAA4C780-0963-4479-9D11-E162127C0014}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {EAA4C780-0963-4479-9D11-E162127C0014}.Release|ARM.ActiveCfg = Release|Any CPU
35 | {EAA4C780-0963-4479-9D11-E162127C0014}.Release|ARM.Build.0 = Release|Any CPU
36 | {EAA4C780-0963-4479-9D11-E162127C0014}.Release|x64.ActiveCfg = Release|Any CPU
37 | {EAA4C780-0963-4479-9D11-E162127C0014}.Release|x64.Build.0 = Release|Any CPU
38 | {EAA4C780-0963-4479-9D11-E162127C0014}.Release|x86.ActiveCfg = Release|Any CPU
39 | {EAA4C780-0963-4479-9D11-E162127C0014}.Release|x86.Build.0 = Release|Any CPU
40 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Debug|ARM.ActiveCfg = Debug|Any CPU
43 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Debug|ARM.Build.0 = Debug|Any CPU
44 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Debug|x64.ActiveCfg = Debug|Any CPU
45 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Debug|x64.Build.0 = Debug|Any CPU
46 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Debug|x86.ActiveCfg = Debug|Any CPU
47 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Debug|x86.Build.0 = Debug|Any CPU
48 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Release|ARM.ActiveCfg = Release|Any CPU
51 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Release|ARM.Build.0 = Release|Any CPU
52 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Release|x64.ActiveCfg = Release|Any CPU
53 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Release|x64.Build.0 = Release|Any CPU
54 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Release|x86.ActiveCfg = Release|Any CPU
55 | {7BEAE24F-5041-441D-99D9-D82657A8BBE7}.Release|x86.Build.0 = Release|Any CPU
56 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Debug|ARM.ActiveCfg = Debug|Any CPU
59 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Debug|ARM.Build.0 = Debug|Any CPU
60 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Debug|x64.ActiveCfg = Debug|Any CPU
61 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Debug|x64.Build.0 = Debug|Any CPU
62 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Debug|x86.ActiveCfg = Debug|Any CPU
63 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Debug|x86.Build.0 = Debug|Any CPU
64 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Release|Any CPU.ActiveCfg = Release|Any CPU
65 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Release|Any CPU.Build.0 = Release|Any CPU
66 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Release|ARM.ActiveCfg = Release|Any CPU
67 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Release|ARM.Build.0 = Release|Any CPU
68 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Release|x64.ActiveCfg = Release|Any CPU
69 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Release|x64.Build.0 = Release|Any CPU
70 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Release|x86.ActiveCfg = Release|Any CPU
71 | {E102F925-90FE-41F2-856E-3FAB4BB2AD58}.Release|x86.Build.0 = Release|Any CPU
72 | EndGlobalSection
73 | GlobalSection(SolutionProperties) = preSolution
74 | HideSolutionNode = FALSE
75 | EndGlobalSection
76 | GlobalSection(ExtensibilityGlobals) = postSolution
77 | SolutionGuid = {EF498AD8-8378-4329-B8DE-97A6D1141002}
78 | EndGlobalSection
79 | EndGlobal
80 |
--------------------------------------------------------------------------------
/Physics2D/Collision/Basic/ParticleLink.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision.Basic
2 | {
3 | using Physics2D.Object;
4 |
5 | public abstract class ParticleLink : ParticleContactGenerator
6 | {
7 | ///
8 | /// 质体A
9 | ///
10 | public Particle ParticleA;
11 |
12 | ///
13 | /// 质体B
14 | ///
15 | public Particle ParticleB;
16 |
17 | ///
18 | /// 返回当前连接的长度
19 | ///
20 | ///
21 | protected double CurrentLength()
22 | {
23 | return (this.ParticleA.Position - this.ParticleB.Position).Length();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Physics2D/Collision/Basic/ParticleRod.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision.Basic
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Object;
5 |
6 | public class ParticleRod : ParticleLink
7 | {
8 | ///
9 | /// 长度
10 | ///
11 | public double Length { get; }
12 |
13 | public ParticleRod(Particle pA, Particle pB)
14 | {
15 | this.Length = (pB.Position - pA.Position).Length();
16 | this.ParticleA = pA;
17 | this.ParticleB = pB;
18 | }
19 |
20 | public override IEnumerator GetEnumerator()
21 | {
22 | double length = this.CurrentLength();
23 | double penetration = length - this.Length;
24 |
25 | if (penetration == 0)
26 | {
27 | yield break;
28 | }
29 |
30 | var normal = (this.ParticleB.Position - this.ParticleA.Position).Normalize();
31 |
32 | if (length <= this.Length)
33 | {
34 | normal *= -1;
35 | penetration *= -1;
36 | }
37 |
38 | yield return new ParticleContact(this.ParticleA, this.ParticleB, 0, penetration, normal);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Physics2D/Collision/Basic/ParticleRope.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision.Basic
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Object;
5 |
6 | public class ParticleRope : ParticleLink
7 | {
8 | ///
9 | /// 最大长度
10 | ///
11 | public double MaxLength { get; }
12 |
13 | ///
14 | /// 弹性系数
15 | ///
16 | public double Restitution { get; }
17 |
18 | public ParticleRope(double maxLength, double restitution, Particle pA, Particle pB)
19 | {
20 | this.MaxLength = maxLength;
21 | this.Restitution = restitution;
22 | this.ParticleA = pA;
23 | this.ParticleB = pB;
24 | }
25 |
26 | public override IEnumerator GetEnumerator()
27 | {
28 | double length = this.CurrentLength();
29 |
30 | // 未超过绳索长度
31 | if (length < this.MaxLength)
32 | {
33 | yield break;
34 | }
35 |
36 | var normal = (this.ParticleB.Position - this.ParticleA.Position).Normalize();
37 |
38 | yield return new ParticleContact(this.ParticleA, this.ParticleB, this.Restitution, length - this.MaxLength, normal);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Physics2D/Collision/ContactType.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision
2 | {
3 | public enum ContactType
4 | {
5 | CircleAndCircle,
6 | CircleAndEdge,
7 | CircleAndBox,
8 | EdgeAndBox,
9 | BoxAndBox,
10 | NotSupport
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Physics2D/Collision/ParticleCollisionDetector.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision
2 | {
3 | using System;
4 | using Physics2D.Collision.Shapes;
5 | using Physics2D.Common;
6 |
7 | public class ParticleCollisionDetector
8 | {
9 | ///
10 | /// 检测圆形与圆形间的碰撞
11 | ///
12 | ///
13 | ///
14 | ///
15 | ///
16 | public static ParticleContact CircleAndCircle(Circle A, Circle B)
17 | {
18 | var d = (A.Body.Position - B.Body.Position).Length();
19 |
20 | // 碰撞检测
21 | var l = A.R + B.R;
22 |
23 | if (d >= l)
24 | {
25 | return null;
26 | }
27 |
28 | // 产生一组碰撞
29 | return new ParticleContact(
30 | A.Body, B.Body,
31 | (A.Body.Restitution + B.Body.Restitution) / 2,
32 | l - d,
33 | (A.Body.Position - B.Body.Position).Normalize());
34 | }
35 |
36 | ///
37 | /// 检测圆形与边缘的碰撞
38 | ///
39 | ///
40 | ///
41 | ///
42 | ///
43 | public static ParticleContact CircleAndEdge(Circle circle, Edge edge)
44 | {
45 | var BA = edge.PointB - edge.PointA;
46 | var BALengthSquared = BA.LengthSquared();
47 |
48 | var intersectionPoint = MathHelper.LineIntersection(
49 | circle.Body.PrePosition,
50 | circle.Body.Position,
51 | edge.PointA,
52 | edge.PointB);
53 |
54 | // 检测物体的运动路径是否发生穿越
55 | if (intersectionPoint != null)
56 | {
57 | // 发生穿越则认为发生碰撞 将质体位置退至相交点
58 | circle.Body.Position = (Vector2D)intersectionPoint;
59 | }
60 |
61 | // 若未发生穿越则计算
62 | double n1 = (edge.PointA - circle.Body.Position) * (edge.PointA - edge.PointB);
63 | double n2 = (edge.PointB - circle.Body.Position) * (edge.PointA - edge.PointB);
64 |
65 | if (n1 * n2 <= 0)
66 | {
67 | // 圆心的投影在线上
68 | var normal = BA * (circle.Body.Position - edge.PointA) * BA / BALengthSquared;
69 | normal = (circle.Body.Position - edge.PointA) - normal;
70 |
71 | // 线到圆心的距离
72 | var d = normal.Length();
73 | if (circle.R > d)
74 | {
75 | // 针对圆心正好处于线上的情况的作处理
76 | if (Math.Abs(d) < Settings.Percision)
77 | {
78 | normal = BA * (circle.Body.PrePosition - edge.PointA) * BA / BALengthSquared;
79 | normal = (circle.Body.PrePosition - edge.PointA) - normal;
80 | }
81 |
82 | return new ParticleContact(
83 | circle.Body, null,
84 | circle.Body.Restitution,
85 | circle.R - d,
86 | normal.Normalize());
87 | }
88 | }
89 | else
90 | {
91 | // 圆心的投影在线外
92 | var dAO = (circle.Body.Position - edge.PointA).LengthSquared();
93 | var dBO = (circle.Body.Position - edge.PointB).LengthSquared();
94 | if (circle.R * circle.R > Math.Min(dAO, dBO))
95 | {
96 | var normal = circle.Body.Position - (dAO < dBO ? edge.PointA : edge.PointB);
97 | return new ParticleContact(
98 | circle.Body, null,
99 | circle.Body.Restitution,
100 | circle.R - Math.Sqrt(dAO < dBO ? dAO : dBO),
101 | normal.Normalize());
102 | }
103 | }
104 |
105 | return null;
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Physics2D/Collision/ParticleContact.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision
2 | {
3 | using System;
4 | using Physics2D.Common;
5 | using Physics2D.Object;
6 |
7 | public class ParticleContact
8 | {
9 | ///
10 | /// 接触物体A
11 | ///
12 | public PhysicsObject PA;
13 |
14 | ///
15 | /// 接触物体B
16 | ///
17 | public PhysicsObject PB;
18 |
19 | ///
20 | /// 碰撞恢复系数
21 | ///
22 | public double Restitution;
23 |
24 | ///
25 | /// 相交深度
26 | ///
27 | public double Penetration;
28 |
29 | ///
30 | /// 碰撞法线
31 | ///
32 | public Vector2D ContactNormal;
33 |
34 | ///
35 | /// 物体A的移动
36 | ///
37 | public Vector2D MovementA;
38 |
39 | ///
40 | /// 物体B的移动
41 | ///
42 | public Vector2D MovementB;
43 |
44 | public ParticleContact(
45 | PhysicsObject a, PhysicsObject b,
46 | double restitution,
47 | double penetration,
48 | Vector2D contactNormal)
49 | {
50 | this.PA = a;
51 | this.PB = b;
52 | this.Restitution = restitution;
53 | this.Penetration = penetration;
54 | this.ContactNormal = contactNormal;
55 | this.MovementA = this.MovementB = Vector2D.Zero;
56 | }
57 |
58 | ///
59 | /// 解决碰撞问题
60 | /// 解决速度及相交
61 | ///
62 | /// 持续时间
63 | public void Resolve(double duration)
64 | {
65 | this.ResolveInterpenetration(duration);
66 | this.ResolveVelocity(duration);
67 | }
68 |
69 | ///
70 | /// 计算分离速度
71 | ///
72 | /// 分离速度
73 | public double CalculateSeparatingVelocity()
74 | {
75 | return (this.PA.Velocity - (this.PB?.Velocity ?? Vector2D.Zero)) * this.ContactNormal;
76 | }
77 |
78 | ///
79 | /// 解决碰撞后速度
80 | ///
81 | ///
82 | private void ResolveVelocity(double duration)
83 | {
84 | double separatingVelocity = this.CalculateSeparatingVelocity();
85 |
86 | if (separatingVelocity > 0)
87 | {
88 | // 两个物体正在分离
89 | return;
90 | }
91 |
92 | double newSeparatingVelocity = -separatingVelocity * this.Restitution;
93 |
94 | // 检查仅由加速度产生的速度
95 | double accCausedSeparatingVelocity = (this.PA.Acceleration - (this.PB?.Acceleration ?? Vector2D.Zero)) * this.ContactNormal * duration;
96 | if (accCausedSeparatingVelocity < 0)
97 | {
98 | // 补偿由加速度产生的速度
99 | newSeparatingVelocity += this.Restitution * accCausedSeparatingVelocity;
100 |
101 | // 避免过度补偿
102 | if (newSeparatingVelocity < 0)
103 | {
104 | newSeparatingVelocity = 0;
105 | }
106 | }
107 |
108 | double deltaVelocity = newSeparatingVelocity - separatingVelocity;
109 | double totalInverseMass = this.PA.InverseMass + (this.PB?.InverseMass ?? 0);
110 |
111 | // 两个物体全为固定或匀速物体则不处理
112 | if (totalInverseMass <= 0)
113 | {
114 | return;
115 | }
116 |
117 | // 计算冲量
118 | double impulse = deltaVelocity / totalInverseMass;
119 |
120 | // 施加冲量
121 | var impulsePerIMass = this.ContactNormal * impulse;
122 | this.PA.Velocity += impulsePerIMass * this.PA.InverseMass;
123 |
124 | if (this.PB != null)
125 | this.PB.Velocity -= impulsePerIMass * this.PB.InverseMass;
126 | else
127 | {
128 | // 静态碰撞的处理
129 |
130 | // 计算PA在碰撞法线上的速度分量
131 | //var vP = PA.Velocity * ContactNormal;
132 | //// 计算PA加速度在未来产生的速度在碰撞法线上的分量
133 | //var vF = PA.Acceleration * duration * ContactNormal;
134 | //if (Math.Abs(vP + vF) < Math.Abs(vF))
135 | //{
136 | // PA.Velocity -= vP * ContactNormal;
137 | //}
138 | }
139 | }
140 |
141 | ///
142 | /// 解决碰撞后相交
143 | ///
144 | ///
145 | private void ResolveInterpenetration(double duration)
146 | {
147 | // 对象未相交
148 | if (this.Penetration <= 0)
149 | {
150 | return;
151 | }
152 |
153 | double vA = Math.Abs(this.PA.Velocity * this.ContactNormal);
154 | double vB = Math.Abs(this.PB?.Velocity * this.ContactNormal ?? .0);
155 | var totalVec = vA + vB;
156 |
157 | // 不处理两个均为固定或常速运动的物体
158 | double totalInverseMass = this.PA.InverseMass + (this.PB?.InverseMass ?? 0);
159 | if (totalInverseMass <= 0)
160 | {
161 | return;
162 | }
163 |
164 | // 两质体速度和不为0时根据速度将物体分离
165 | if (Math.Abs(totalVec) > Settings.Percision)
166 | {
167 | var totalInverseVec = 1 / totalVec;
168 | this.MovementA = this.ContactNormal * this.Penetration * totalInverseVec * vA;
169 | this.MovementB = -this.ContactNormal * this.Penetration * totalInverseVec * vB;
170 | }
171 |
172 | // 两质体速度和为0时根据质量将物体分离
173 | else
174 | {
175 | var movePerIMass = this.ContactNormal * (this.Penetration / totalInverseMass);
176 |
177 | this.MovementA = this.PA.InverseMass * movePerIMass;
178 | this.MovementB = -this.PB?.InverseMass * movePerIMass ?? Vector2D.Zero;
179 | }
180 |
181 | this.PA.Position += this.MovementA;
182 | if (this.PB != null)
183 | {
184 | this.PB.Position += this.MovementB;
185 | }
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/Physics2D/Collision/ParticleContactGenerator.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision
2 | {
3 | using System.Collections;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// 碰撞生成器
8 | ///
9 | public abstract class ParticleContactGenerator : IEnumerable
10 | {
11 | public abstract IEnumerator GetEnumerator();
12 |
13 | IEnumerator IEnumerable.GetEnumerator()
14 | {
15 | return this.GetEnumerator();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Physics2D/Collision/ParticleContactResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: InternalsVisibleTo("UnitTest")]
5 |
6 | namespace Physics2D.Collision
7 | {
8 | internal class ParticleContactResolver
9 | {
10 | public ParticleContactResolver(int iterations)
11 | {
12 | this.Iterations = iterations;
13 | }
14 |
15 | public void ResolveContacts(List contactList, double duration)
16 | {
17 | if (contactList.Count == 0)
18 | {
19 | return;
20 | }
21 |
22 | int iterationsUsed = 0;
23 | while (iterationsUsed++ < this.Iterations)
24 | {
25 | // 找到权值最小(碰撞程度最为严重)的一组碰撞 优先处理
26 | double max = 0;
27 | int maxI = contactList.Count;
28 | for (int i = 0; i < contactList.Count; i++)
29 | {
30 | // 计算权值 = 分离速度 * 时间 - 相交深度
31 | double weight = contactList[i].CalculateSeparatingVelocity() * duration - contactList[i].Penetration;
32 | if (weight < max)
33 | {
34 | max = weight;
35 | maxI = i;
36 | }
37 | }
38 |
39 | // 解决碰撞
40 | if (maxI == contactList.Count)
41 | {
42 | break;
43 | }
44 |
45 | contactList[maxI].Resolve(duration);
46 |
47 | // 更新相交长度
48 | var maxItem = contactList[maxI];
49 | var movementA = contactList[maxI].MovementA;
50 | var movementB = contactList[maxI].MovementB;
51 | foreach (var item in contactList)
52 | {
53 | if (item.PA == maxItem.PA)
54 | {
55 | item.Penetration -= movementA * item.ContactNormal;
56 | }
57 | else if (item.PA == maxItem.PB)
58 | {
59 | item.Penetration -= movementB * item.ContactNormal;
60 | }
61 |
62 | if (item.PB != null)
63 | {
64 | if (item.PB == maxItem.PA)
65 | {
66 | item.Penetration += movementA * item.ContactNormal;
67 | }
68 | else if (item.PB == maxItem.PB)
69 | {
70 | item.Penetration += movementB * item.ContactNormal;
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
77 | public int Iterations { get; set; }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Physics2D/Collision/Shapes/Circle.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision.Shapes
2 | {
3 | public sealed class Circle : Shape
4 | {
5 | public double R;
6 |
7 | public Circle(double r, int id = 0)
8 | {
9 | this.R = r;
10 | this.Id = id;
11 | }
12 |
13 | public override ShapeType Type
14 | {
15 | get { return ShapeType.Circle; }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Physics2D/Collision/Shapes/Edge.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision.Shapes
2 | {
3 | using Physics2D.Common;
4 |
5 | public sealed class Edge : Shape
6 | {
7 | public Vector2D PointA;
8 | public Vector2D PointB;
9 |
10 | public Edge(Vector2D pA, Vector2D pB)
11 | {
12 | this.PointA = pA;
13 | this.PointB = pB;
14 | }
15 |
16 | public Edge(double x1, double y1, double x2, double y2)
17 | {
18 | this.PointA = new Vector2D(x1, y1);
19 | this.PointB = new Vector2D(x2, y2);
20 | }
21 |
22 | public override ShapeType Type
23 | {
24 | get { return ShapeType.Edge; }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Physics2D/Collision/Shapes/Point.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision.Shapes
2 | {
3 | ///
4 | /// 点形状适用于无形体的物体,通常将其作为物体的默认形状
5 | /// 点状物体不参与碰撞检测
6 | ///
7 | public sealed class Point : Shape
8 | {
9 | public override ShapeType Type
10 | {
11 | get { return ShapeType.Point; }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Physics2D/Collision/Shapes/Shape.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision.Shapes
2 | {
3 | using Physics2D.Object;
4 |
5 | public abstract class Shape
6 | {
7 | ///
8 | /// 绑定的物体
9 | ///
10 | public PhysicsObject Body;
11 |
12 | ///
13 | /// 标识符
14 | /// 标识符相同且不为0的形状不执行碰撞检测
15 | ///
16 | public int Id = 0;
17 |
18 | ///
19 | /// 标识符基数
20 | ///
21 | private static int idBase = 1;
22 |
23 | ///
24 | /// 产生一个新的Id
25 | ///
26 | ///
27 | public static int NewId() => idBase++;
28 |
29 | ///
30 | /// 返回当前形状的类型
31 | ///
32 | public abstract ShapeType Type
33 | {
34 | get;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Physics2D/Collision/Shapes/ShapeType.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Collision.Shapes
2 | {
3 | public enum ShapeType
4 | {
5 | Point = -1,
6 | Circle = 0,
7 | Edge = 1,
8 | Box = 2
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Physics2D/Common/Events/ContactEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Common.Events
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using Physics2D.Collision;
6 |
7 | public delegate void ContactHandle(object sender, ContactEventArgs e);
8 |
9 | public sealed class ContactEventArgs : EventArgs
10 | {
11 | public IReadOnlyList ContactList { get; }
12 |
13 | public ContactEventArgs(IReadOnlyList contact)
14 | {
15 | this.ContactList = contact;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Physics2D/Common/Exceptions/InvalidArgumentException.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Common.Exceptions
2 | {
3 | using System;
4 |
5 | public class InvalidArgumentException : ArgumentException
6 | {
7 | public InvalidArgumentException(string message, string paramName)
8 | : base(message, paramName)
9 | { }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Physics2D/Common/MathHelper.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Common
2 | {
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | public static class MathHelper
7 | {
8 | ///
9 | /// 计算一个点到一个线段的向量
10 | /// 返回点到线的距离最短的向量
11 | ///
12 | /// 点
13 | /// 线段端点A
14 | /// 线段端点B
15 | ///
16 | public static Vector2D PointToLineVector(Vector2D point, Vector2D linePA, Vector2D linePB)
17 | {
18 | var lineBA = linePB - linePA;
19 | var normal = lineBA * (point - linePA) * lineBA / lineBA.LengthSquared() - (point - linePA);
20 |
21 | return normal;
22 | }
23 |
24 | ///
25 | /// 计算两个线段的交点
26 | ///
27 | ///
28 | ///
29 | ///
30 | ///
31 | ///
32 | public static Vector2D? LineIntersection(Vector2D lineA1, Vector2D lineA2, Vector2D lineB1, Vector2D lineB2)
33 | {
34 | var area1 = SignedTriangleArea(lineA1, lineA2, lineB2);
35 | var area2 = SignedTriangleArea(lineA1, lineA2, lineB1);
36 |
37 | if (area1 * area2 < 0)
38 | {
39 | var area3 = SignedTriangleArea(lineB1, lineB2, lineA1);
40 | var area4 = area3 + area2 - area1;
41 | if (area3 * area4 < 0)
42 | {
43 | var intersectionScale = area3 / (area3 - area4);
44 | var intersectionPoint = (lineA2 - lineA1) * intersectionScale + lineA1;
45 |
46 | // 返回交点
47 | return intersectionPoint;
48 | }
49 | }
50 |
51 | return null;
52 | }
53 |
54 | ///
55 | /// 计算三角形的有向面积
56 | ///
57 | /// 顶点A
58 | /// 顶点B
59 | /// 顶点C
60 | ///
61 | public static double SignedTriangleArea(Vector2D a, Vector2D b, Vector2D c)
62 | {
63 | return (a.X - c.X) * (b.Y - c.Y) - (a.Y - c.Y) * (b.X - c.X);
64 | }
65 |
66 | ///
67 | /// 测试一个点是否在一个多边形的内部
68 | ///
69 | /// 多边形的顺时针点集,最后一个点应当与第一个点为同一点
70 | ///
71 | ///
72 | public static bool IsInside(IReadOnlyList vertexs, Vector2D point)
73 | {
74 | int count = 0;
75 | int num = vertexs.Count() - 1;
76 |
77 | if (num < 3)
78 | {
79 | // 当点集不能围成多边形时该函数永假
80 | return false;
81 | }
82 |
83 | for (int i = 0; i < num; i++)
84 | {
85 | var slope = (vertexs[i + 1].Y - vertexs[i].Y) / (vertexs[i + 1].X - vertexs[i].X);
86 | bool cond1 = (vertexs[i].X <= point.X) && (point.X < vertexs[i + 1].X);
87 | bool cond2 = (vertexs[i + 1].X <= point.X) && (point.X < vertexs[i].X);
88 | bool above = point.Y < slope * (point.X - vertexs[i].X) + vertexs[i].Y;
89 | if ((cond1 || cond2) && above)
90 | {
91 | count++;
92 | }
93 | }
94 |
95 | return count % 2 != 0;
96 | }
97 |
98 | ///
99 | /// 计算点到直线的距离的平方
100 | ///
101 | ///
102 | ///
103 | ///
104 | ///
105 | public static double PointToLineDistenceSquared(Vector2D point, Vector2D linePA, Vector2D linePB)
106 | {
107 | return PointToLineVector(point, linePA, linePB).LengthSquared();
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Physics2D/Common/Vector2D.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Common
2 | {
3 | using System;
4 | using static System.Math;
5 |
6 | public struct Vector2D : IEquatable
7 | {
8 | public double X;
9 | public double Y;
10 |
11 | public static Vector2D Zero { get; } = new Vector2D();
12 |
13 | public static Vector2D One { get; } = new Vector2D(1, 1);
14 |
15 | public static Vector2D UnitX { get; } = new Vector2D(1, 0);
16 |
17 | public static Vector2D UnitY { get; } = new Vector2D(0, 1);
18 |
19 | public Vector2D(double x, double y)
20 | {
21 | this.X = x;
22 | this.Y = y;
23 | }
24 |
25 | public Vector2D(Vector2D vec)
26 | {
27 | this.X = vec.X;
28 | this.Y = vec.Y;
29 | }
30 |
31 | public static double DistanceSquared(Vector2D value1, Vector2D value2)
32 | {
33 | return (value1.X - value2.X) * (value1.X - value2.X) + (value1.Y - value2.Y) * (value1.Y - value2.Y);
34 | }
35 |
36 | public static double Distance(Vector2D value1, Vector2D value2) => Sqrt(DistanceSquared(value1, value2));
37 |
38 | public double LengthSquared() => DistanceSquared(this, Zero);
39 |
40 | public double Length() => Distance(this, Zero);
41 |
42 | public static Vector2D Normalize(Vector2D value)
43 | {
44 | double distance = Distance(value, Zero);
45 |
46 | // 零向量标准化仍为零向量
47 | if (distance == 0)
48 | {
49 | return Zero;
50 | }
51 |
52 | var factor = 1 / distance;
53 |
54 | return new Vector2D(value.X * factor, value.Y * factor);
55 | }
56 |
57 | public Vector2D Normalize() => Normalize(this);
58 |
59 | public void Set(double x, double y)
60 | {
61 | this.X = x;
62 | this.Y = y;
63 | }
64 |
65 | public static Vector2D operator +(Vector2D left, Vector2D right) => new Vector2D(left.X + right.X, left.Y + right.Y);
66 |
67 | public static Vector2D operator -(Vector2D left, Vector2D right) => new Vector2D(left.X - right.X, left.Y - right.Y);
68 |
69 | public static Vector2D operator -(Vector2D right) => new Vector2D(.0 - right.X, .0 - right.Y);
70 |
71 | public static double operator *(Vector2D left, Vector2D right) => left.X * right.X + left.Y * right.Y;
72 |
73 | public static Vector2D operator *(Vector2D left, double factor) => new Vector2D(left.X * factor, left.Y * factor);
74 |
75 | public static Vector2D operator *(double factor, Vector2D right) => right * factor;
76 |
77 | public static Vector2D operator /(Vector2D left, double divisor) => new Vector2D(left.X / divisor, left.Y / divisor);
78 |
79 | public static bool operator ==(Vector2D left, Vector2D right) => Abs(left.X - right.X) < Settings.Percision && Abs(left.Y - right.Y) < Settings.Percision;
80 |
81 | public static bool operator !=(Vector2D left, Vector2D right) => !(left == right);
82 |
83 | public override bool Equals(object obj)
84 | {
85 | if (ReferenceEquals(null, obj))
86 | {
87 | return false;
88 | }
89 |
90 | return obj is Vector2D && this.Equals((Vector2D)obj);
91 | }
92 |
93 | public bool Equals(Vector2D other) => Abs(this.X - other.X) < Settings.Percision && Abs(this.Y - other.Y) < Settings.Percision;
94 |
95 | public override string ToString() => $"({this.X:f2}, {this.Y:f2})";
96 |
97 | public override int GetHashCode() => base.GetHashCode();
98 | }
99 | }
--------------------------------------------------------------------------------
/Physics2D/ConvertUnits.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D
2 | {
3 | using Physics2D.Common;
4 |
5 | ///
6 | /// The units convert class.
7 | /// This is a static helper class for convert units between real world unit and graphic units.
8 | ///
9 | public static class ConvertUnits
10 | {
11 | ///
12 | /// The radio of display units to physical units.
13 | ///
14 | private static double displayUnitsToSimUnitsRatio = 50;
15 |
16 | ///
17 | /// The radio of physical units to display units.
18 | ///
19 | private static double simUnitsToDisplayUnitsRatio = 1 / displayUnitsToSimUnitsRatio;
20 |
21 | ///
22 | /// Set the ratio.
23 | ///
24 | /// The ratio of display units to physical units.
25 | public static void SetDisplayUnitToSimUnitRatio(double displayUnitsPerSimUnit)
26 | {
27 | displayUnitsToSimUnitsRatio = displayUnitsPerSimUnit;
28 | simUnitsToDisplayUnitsRatio = 1 / displayUnitsPerSimUnit;
29 | }
30 |
31 | ///
32 | /// Convert to display size.
33 | ///
34 | /// The double type size by physical units.
35 | /// The size by display units.
36 | public static int ToDisplayUnits(this double simUnits)
37 | {
38 | return (int)(simUnits * displayUnitsToSimUnitsRatio);
39 | }
40 |
41 | ///
42 | /// Convert to display size.
43 | ///
44 | /// The int type size by physical units.
45 | /// The size by display units.
46 | public static int ToDisplayUnits(this int simUnits)
47 | {
48 | return (int)(simUnits * displayUnitsToSimUnitsRatio);
49 | }
50 |
51 | ///
52 | /// Convert to display size.
53 | ///
54 | /// The type size by physical units.
55 | /// The size by display units.
56 | public static Vector2D ToDisplayUnits(this Vector2D simUnits)
57 | {
58 | return simUnits * displayUnitsToSimUnitsRatio;
59 | }
60 |
61 | ///
62 | /// Convert to physical size.
63 | ///
64 | /// The double type size by display units.
65 | /// The size by physical units.
66 | public static double ToSimUnits(this double displayUnits)
67 | {
68 | return displayUnits / displayUnitsToSimUnitsRatio;
69 | }
70 |
71 | ///
72 | /// Convert to physical size.
73 | ///
74 | /// The int type size by display units.
75 | /// The size by physical units.
76 | public static double ToSimUnits(this int displayUnits)
77 | {
78 | return displayUnits / displayUnitsToSimUnitsRatio;
79 | }
80 |
81 | ///
82 | /// Convert to physical size.
83 | ///
84 | /// The type size by display units.
85 | /// The size by physical units.
86 | public static Vector2D ToSimUnits(this Vector2D displayUnits)
87 | {
88 | return displayUnits / displayUnitsToSimUnitsRatio;
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/Physics2D/Core/ForceRegistry.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Core
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Force;
5 | using Physics2D.Object;
6 |
7 | ///
8 | /// 粒子的作用力发生器管理模块
9 | ///
10 | public sealed class ForceRegistry
11 | {
12 | ///
13 | /// 作用力发生器集合
14 | ///
15 | private readonly HashSet generators = new HashSet();
16 |
17 | ///
18 | /// 添加一个新项目
19 | ///
20 | /// 作用力发生器
21 | public void Add(ParticleForceGenerator forceGenerator) => this.generators.Add(forceGenerator);
22 |
23 | ///
24 | /// 删除一个作用力发生器
25 | ///
26 | /// 作用力发生器
27 | public void Remove(ParticleForceGenerator forceGenerator) => this.generators.Remove(forceGenerator);
28 |
29 | ///
30 | /// 删除一个项目
31 | /// 依据粒子进行删除,只要包含该粒子,即执行删除操作
32 | ///
33 | /// 粒子
34 | public void Remove(Particle particle)
35 | {
36 | foreach (var particleForceGenerator in this.generators)
37 | {
38 | particleForceGenerator.Remove(particle);
39 | }
40 | }
41 |
42 | ///
43 | /// 执行所有的作用力发生器
44 | ///
45 | ///
46 | public void Update(double duration)
47 | {
48 | foreach (var particleForceGenerator in this.generators)
49 | {
50 | particleForceGenerator.Apply(duration);
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/Physics2D/Core/World.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Core
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 | using Physics2D.Collision.Shapes;
7 | using Physics2D.Common;
8 | using Physics2D.Force.Zones;
9 | using Physics2D.Object;
10 | using Physics2D.Object.Tools;
11 |
12 | public sealed class World
13 | {
14 | ///
15 | /// 物体集合
16 | ///
17 | private readonly HashSet objects;
18 |
19 | ///
20 | /// 边缘集合
21 | ///
22 | private readonly HashSet edges;
23 |
24 | ///
25 | /// Pin集合
26 | ///
27 | private readonly Dictionary pins = new Dictionary();
28 |
29 | ///
30 | /// 作用力区域集合
31 | ///
32 | public readonly HashSet Zones = new HashSet();
33 |
34 | ///
35 | /// 质体作用力管理器
36 | ///
37 | public readonly ForceRegistry ForceGenerators = new ForceRegistry();
38 |
39 | ///
40 | /// 质体碰撞管理器
41 | ///
42 | public readonly ContactRegistry ContactGenerators;
43 |
44 | public World()
45 | {
46 | this.objects = new HashSet();
47 | this.edges = new HashSet();
48 | this.ContactGenerators = new ContactRegistry(this.objects, this.edges);
49 | }
50 |
51 | ///
52 | /// 向物理世界中添加一个物体
53 | ///
54 | ///
55 | public void AddObject(PhysicsObject obj)
56 | {
57 | this.objects.Add(obj);
58 | }
59 |
60 | ///
61 | /// 从物理世界中移除一个物体
62 | ///
63 | ///
64 | public void RemoveObject(PhysicsObject obj)
65 | {
66 | this.objects.Remove(obj);
67 |
68 | // 仅在物体为质体时执行注销操作
69 | var particle = obj as Particle;
70 | if (particle != null)
71 | {
72 | this.ForceGenerators.Remove(particle);
73 | }
74 | }
75 |
76 | ///
77 | /// 向物理世界添加一个定制的物体
78 | ///
79 | ///
80 | public void AddObject(CustomObject obj)
81 | {
82 | this.AddObject((PhysicsObject)obj);
83 | obj.OnInit(this);
84 | }
85 |
86 | ///
87 | /// 从物理世界移除一个定制的物体
88 | ///
89 | ///
90 | public void RemoveObject(CustomObject obj)
91 | {
92 | this.RemoveObject((PhysicsObject)obj);
93 | obj.OnRemove(this);
94 | }
95 |
96 | ///
97 | /// 向物理世界添加一条边
98 | ///
99 | ///
100 | public void AddEdge(Edge edge)
101 | {
102 | this.edges.Add(edge);
103 | }
104 |
105 | ///
106 | /// 从物理世界移除一条边
107 | ///
108 | ///
109 | public void RemoveEdge(Edge edge)
110 | {
111 | this.edges.Remove(edge);
112 | }
113 |
114 | ///
115 | /// 将制定物体Pin在物理世界
116 | ///
117 | ///
118 | ///
119 | ///
120 | public Handle Pin(IPin obj, Vector2D position)
121 | {
122 | if (!this.pins.ContainsKey(obj))
123 | {
124 | this.pins[obj] = obj.Pin(this, position);
125 | }
126 | else
127 | {
128 | throw new InvalidOperationException("Can't pin target object which was alreadly pinned.");
129 | }
130 |
131 | return this.pins[obj];
132 | }
133 |
134 | ///
135 | /// 解除指定物体在物理世界的Pin
136 | ///
137 | ///
138 | public void UnPin(IPin obj)
139 | {
140 | if (this.pins.ContainsKey(obj))
141 | {
142 | obj.Unpin(this);
143 | this.pins[obj].Release();
144 | this.pins.Remove(obj);
145 | }
146 | else
147 | {
148 | throw new InvalidOperationException("Can't unpin target object which was not pinned.");
149 | }
150 | }
151 |
152 | ///
153 | /// 按时间间隔更新整个物理世界
154 | ///
155 | /// 时间间隔
156 | public void Update(double duration)
157 | {
158 | // 为粒子施加作用力
159 | this.ForceGenerators.Update(duration);
160 |
161 | // 更新物理对象
162 | Parallel.ForEach(this.objects, item =>
163 | {
164 | // 为物理对象施加区域作用力
165 | foreach (var z in this.Zones)
166 | {
167 | z.TryApplyTo(item, duration);
168 | }
169 |
170 | // 对物理对象进行积分
171 | item.Update(duration);
172 | });
173 |
174 | // 质体碰撞检测
175 | this.ContactGenerators.ResolveContacts(duration);
176 | }
177 | }
178 | }
--------------------------------------------------------------------------------
/Physics2D/Factories/ContactFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Factories
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Collision;
5 | using Physics2D.Collision.Basic;
6 | using Physics2D.Collision.Shapes;
7 | using Physics2D.Common;
8 | using Physics2D.Common.Exceptions;
9 | using Physics2D.Core;
10 | using Physics2D.Object;
11 |
12 | public static class ContactFactory
13 | {
14 | ///
15 | /// 在物理世界创建一条边缘
16 | ///
17 | ///
18 | ///
19 | ///
20 | ///
21 | ///
22 | ///
23 | public static Edge CreateEdge(this World world, Vector2D pointA, Vector2D pointB)
24 | {
25 | var edge = new Edge(pointA, pointB);
26 | world.AddEdge(edge);
27 | return edge;
28 | }
29 |
30 | ///
31 | /// 在物理世界创建一条边缘
32 | ///
33 | ///
34 | ///
35 | ///
36 | ///
37 | ///
38 | ///
39 | public static Edge CreateEdge(this World world, double x1, double y1, double x2, double y2)
40 | {
41 | var edge = new Edge(x1, y1, x2, y2);
42 | world.AddEdge(edge);
43 | return edge;
44 | }
45 |
46 | ///
47 | /// 在物理世界创建一个由边缘围成的封闭多边形
48 | ///
49 | ///
50 | /// 多边形点集(逆时针)
51 | ///
52 | public static IEnumerable CreatePolygonEdge(this World world, params Vector2D[] points)
53 | {
54 | List result = new List();
55 |
56 | if (points.Length < 3)
57 | {
58 | throw new InvalidArgumentException(
59 | $"Can't create a polygon by given points. points.Length = {points.Length}", nameof(points));
60 | }
61 |
62 | for (int i = 1; i < points.Length; i++)
63 | {
64 | result.Add(world.CreateEdge(points[i - 1], points[i]));
65 | }
66 |
67 | result.Add(world.CreateEdge(points[points.Length - 1], points[0]));
68 | return result;
69 | }
70 |
71 | ///
72 | /// 在物理世界创建一条质体绳索
73 | /// 该绳索由两个质体(质点)组成,两个质体的距离被限定在指定的数值内
74 | ///
75 | ///
76 | ///
77 | ///
78 | ///
79 | ///
80 | ///
81 | public static ParticleRope CreateRope(this World world, double maxLength, double restitution, Particle a, Particle b)
82 | {
83 | var rope = new ParticleRope(maxLength, restitution, a, b);
84 | return world.CreateContact(rope);
85 | }
86 |
87 | ///
88 | /// 在物理世界创建一根质体硬棒
89 | /// 该硬棒由两个质体(质点)组成,两个质体的距离不会发生变化
90 | ///
91 | ///
92 | ///
93 | ///
94 | ///
95 | public static ParticleRod CreateRod(this World world, Particle a, Particle b)
96 | {
97 | var rod = new ParticleRod(a, b);
98 | return world.CreateContact(rod);
99 | }
100 |
101 | ///
102 | /// 在物理世界中创建一组关联
103 | ///
104 | ///
105 | ///
106 | ///
107 | ///
108 | public static T CreateContact(this World world, T contact)
109 | where T : ParticleContactGenerator
110 | {
111 | world.ContactGenerators.Add(contact);
112 | return contact;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Physics2D/Factories/ParticleFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Factories
2 | {
3 | using Physics2D.Common;
4 | using Physics2D.Core;
5 | using Physics2D.Object;
6 |
7 | ///
8 | /// 粒子工厂
9 | ///
10 | public static class ParticleFactory
11 | {
12 | ///
13 | /// 创建一个粒子
14 | ///
15 | /// 物理世界
16 | /// 初位置
17 | /// 初速度
18 | /// 质量
19 | ///
20 | public static Particle CreateParticle(this World world, Vector2D p, Vector2D v, double m, double restitution = 1)
21 | {
22 | var particle = new Particle
23 | {
24 | Position = p,
25 | Mass = m,
26 | Velocity = v,
27 | PrePosition = p,
28 | Restitution = restitution
29 | };
30 | world.AddObject(particle);
31 | return particle;
32 | }
33 |
34 | ///
35 | /// 创建固定不动的粒子
36 | ///
37 | /// 物理世界
38 | /// 初位置
39 | ///
40 | public static Particle CreateFixedParticle(this World world, Vector2D p)
41 | {
42 | var particle = new Particle
43 | {
44 | Position = p,
45 | InverseMass = 0f,
46 | PrePosition = p
47 | };
48 | world.AddObject(particle);
49 | return particle;
50 | }
51 |
52 | ///
53 | /// 创建永远保持匀速运动的粒子
54 | ///
55 | /// 物理世界
56 | /// 初位置
57 | /// 初速度
58 | ///
59 | public static Particle CreateUnstoppableParticle(this World world, Vector2D p, Vector2D v)
60 | {
61 | var particle = new Particle
62 | {
63 | Position = p,
64 | InverseMass = 0f,
65 | Velocity = v,
66 | PrePosition = p
67 | };
68 | world.AddObject(particle);
69 | return particle;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/Physics2D/Factories/ZoneFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Factories
2 | {
3 | using Physics2D.Common;
4 | using Physics2D.Core;
5 | using Physics2D.Force;
6 | using Physics2D.Force.Zones;
7 |
8 | ///
9 | /// 区域作用力工厂
10 | ///
11 | public static class ZoneFactory
12 | {
13 | ///
14 | /// 在物理世界创建一个全局作用力区域
15 | ///
16 | /// 物理世界
17 | /// 作用力发生器
18 | ///
19 | public static GlobalZone CreateGlobalZone(
20 | this World world,
21 | ParticleForceGenerator particleForceGenerator)
22 | {
23 | var zone = new GlobalZone();
24 | return world.CreateZone(zone, particleForceGenerator);
25 | }
26 |
27 | ///
28 | /// 在物理世界创建一个矩形区域作用力
29 | ///
30 | /// 物理世界
31 | /// 作用力发生器
32 | ///
33 | ///
34 | ///
35 | ///
36 | ///
37 | public static RectangleZone CreateRectangleZone(
38 | this World world,
39 | ParticleForceGenerator particleForceGenerator,
40 | double x1,
41 | double y1,
42 | double x2,
43 | double y2)
44 | {
45 | var zone = new RectangleZone(x1, y1, x2, y2);
46 | return world.CreateZone(zone, particleForceGenerator);
47 | }
48 |
49 | ///
50 | /// 为物理世界创建重力
51 | /// 重力的方向向下
52 | ///
53 | ///
54 | ///
55 | ///
56 | public static GlobalZone CreateGravity(this World world, double g)
57 | {
58 | return world.CreateGlobalZone(new ParticleGravity(new Vector2D(0, g)));
59 | }
60 |
61 | ///
62 | /// 在物理世界中创建一个区域
63 | ///
64 | ///
65 | ///
66 | ///
67 | ///
68 | ///
69 | public static T CreateZone(
70 | this World world,
71 | T zone,
72 | ParticleForceGenerator particleForceGenerator)
73 | where T : Zone
74 | {
75 | zone.Add(particleForceGenerator);
76 | world.Zones.Add(zone);
77 | return zone;
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/Physics2D/Force/ParticleConstantForce.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Force
2 | {
3 | using Physics2D.Common;
4 | using Physics2D.Object;
5 |
6 | public class ParticleConstantForce : ParticleForceGenerator
7 | {
8 | private readonly Vector2D force;
9 |
10 | public ParticleConstantForce(Vector2D force)
11 | {
12 | this.force = force;
13 | }
14 |
15 | public override void ApplyTo(Particle particle, double duration)
16 | {
17 | particle.AddForce(this.force);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Physics2D/Force/ParticleDrag.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Force
2 | {
3 | using Physics2D.Common;
4 | using Physics2D.Object;
5 |
6 | public class ParticleDrag : ParticleForceGenerator
7 | {
8 | private readonly double k1;
9 | private readonly double k2;
10 |
11 | public ParticleDrag(double k1, double k2)
12 | {
13 | this.k1 = k1;
14 | this.k2 = k2;
15 | }
16 |
17 | public override void ApplyTo(Particle particle, double duration)
18 | {
19 | if (particle.Velocity == Vector2D.Zero)
20 | {
21 | return;
22 | }
23 |
24 | double c = particle.Velocity.Length();
25 | c = this.k1 * c + this.k2 * c * c;
26 |
27 | particle.AddForce(particle.Velocity.Normalize() * -c);
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Physics2D/Force/ParticleElastic.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Force
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Object;
5 |
6 | public class ParticleElastic : ParticleForceGenerator
7 | {
8 | private readonly List linked = new List();
9 |
10 | private readonly double k;
11 | private readonly double length;
12 |
13 | public ParticleElastic(double k, double length)
14 | {
15 | this.k = k;
16 | this.length = length;
17 | }
18 |
19 | public void LinkWith(Particle item)
20 | {
21 | this.linked.Add(item);
22 | }
23 |
24 | public override void ApplyTo(Particle particle, double duration)
25 | {
26 | foreach (var item in this.linked)
27 | {
28 | var d = particle.Position - item.Position;
29 |
30 | double force = (this.length - d.Length()) * this.k;
31 | d.Normalize();
32 | particle.AddForce(d * force);
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/Physics2D/Force/ParticleForceGenerator.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Force
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Object;
5 |
6 | public abstract class ParticleForceGenerator
7 | {
8 | ///
9 | /// 受管理的质体集合
10 | ///
11 | protected readonly HashSet Objects = new HashSet();
12 |
13 | ///
14 | /// 添加新的受力对象
15 | ///
16 | /// 受力对象
17 | public void Add(Particle particle) => this.Objects.Add(particle);
18 |
19 | ///
20 | /// 移除受力对象
21 | ///
22 | /// 受力对象
23 | public void Remove(Particle particle) => this.Objects.Remove(particle);
24 |
25 | ///
26 | /// 为指定质体施加作用力
27 | ///
28 | /// 受力对象
29 | ///
30 | public abstract void ApplyTo(Particle particle, double duration);
31 |
32 | ///
33 | /// 为所管理的所有对象施加作用力
34 | ///
35 | ///
36 | public void Apply(double duration)
37 | {
38 | foreach (var particle in this.Objects)
39 | {
40 | this.ApplyTo(particle, duration);
41 | }
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Physics2D/Force/ParticleGravity.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Force
2 | {
3 | using Physics2D.Common;
4 | using Physics2D.Object;
5 |
6 | public class ParticleGravity : ParticleForceGenerator
7 | {
8 | private readonly Vector2D gravity;
9 |
10 | public ParticleGravity(Vector2D gravity)
11 | {
12 | this.gravity = gravity;
13 | }
14 |
15 | public override void ApplyTo(Particle particle, double duration)
16 | {
17 | // 质量无限大的物体不受重力影响
18 | if (particle.InverseMass == 0)
19 | {
20 | return;
21 | }
22 |
23 | // 施加重力
24 | particle.AddForce(this.gravity * particle.Mass);
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/Physics2D/Force/Zones/GlobalZone.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Force.Zones
2 | {
3 | ///
4 | /// 能对所有物体施加作用力的区域
5 | ///
6 | public class GlobalZone : Zone
7 | {
8 | protected override bool IsIn(Object.PhysicsObject obj) => true;
9 | }
10 | }
--------------------------------------------------------------------------------
/Physics2D/Force/Zones/RectangleZone.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Force.Zones
2 | {
3 | using Physics2D.Object;
4 |
5 | ///
6 | /// 在矩形内施加作用力的区域
7 | ///
8 | public class RectangleZone : Zone
9 | {
10 | public double X1 { get; }
11 | public double Y1 { get; }
12 | public double X2 { get; }
13 | public double Y2 { get; }
14 |
15 | public RectangleZone(double x1, double y1, double x2, double y2)
16 | {
17 | this.X1 = x1;
18 | this.Y1 = y1;
19 | this.X2 = x2;
20 | this.Y2 = y2;
21 | }
22 |
23 | protected override bool IsIn(PhysicsObject obj)
24 | {
25 | return obj.Position.X > this.X1 && obj.Position.X < this.X2 &&
26 | obj.Position.Y > this.Y1 && obj.Position.Y < this.Y2;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Physics2D/Force/Zones/Zone.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Force.Zones
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Object;
5 |
6 | ///
7 | /// 作用力区域
8 | /// 使在区域内的粒子均受到指定的作用力
9 | ///
10 | public abstract class Zone
11 | {
12 | ///
13 | /// 区域内粒子作用力发生器
14 | ///
15 | private readonly HashSet particleForceGenerators = new HashSet();
16 |
17 | ///
18 | /// 添加一个作用力发生器
19 | ///
20 | ///
21 | public void Add(ParticleForceGenerator particleForceGenerator)
22 | => this.particleForceGenerators.Add(particleForceGenerator);
23 |
24 | ///
25 | /// 移除一个作用力发生器
26 | ///
27 | ///
28 | public void Remove(ParticleForceGenerator particleForceGenerator)
29 | => this.particleForceGenerators.Remove(particleForceGenerator);
30 |
31 | ///
32 | /// 判断给定物体是否存在于当前区域
33 | ///
34 | /// 物体
35 | ///
36 | protected abstract bool IsIn(PhysicsObject obj);
37 |
38 | ///
39 | /// 尝试为给定物体施加作用力
40 | ///
41 | /// 给定物体
42 | /// 施加作用力的时间
43 | public void TryApplyTo(PhysicsObject obj, double duration)
44 | {
45 | if (!this.IsIn(obj))
46 | {
47 | return;
48 | }
49 |
50 | if (obj is Particle)
51 | {
52 | foreach (var item in this.particleForceGenerators)
53 | {
54 | item.ApplyTo((Particle)obj, duration);
55 | }
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/Physics2D/Object/CombinedParticle.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Object
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Collision.Basic;
5 | using Physics2D.Common;
6 | using Physics2D.Common.Exceptions;
7 | using Physics2D.Core;
8 | using Physics2D.Factories;
9 | using Physics2D.Object.Tools;
10 |
11 | ///
12 | /// 由多个质点组合成的物体
13 | /// 质点由刚性连杆连接,可获得类似刚体的自由度
14 | ///
15 | public class CombinedParticle : CustomObject, IPin
16 | {
17 | ///
18 | /// 质体顶点表(顺时针)
19 | ///
20 | private readonly List vertexs = new List();
21 |
22 | ///
23 | /// 刚性连杆表(顺时针)
24 | ///
25 | private readonly List rods = new List();
26 |
27 | ///
28 | /// 锚点连杆表
29 | ///
30 | private List pinRods = new List();
31 |
32 | ///
33 | /// 是否封闭(首尾相连)
34 | ///
35 | private readonly bool isClose;
36 |
37 | ///
38 | /// 质体顶点表(顺时针)
39 | ///
40 | public IReadOnlyList Vertexs => this.vertexs;
41 |
42 | ///
43 | /// 刚性连杆表(顺时针)
44 | ///
45 | public IReadOnlyList Rods => this.rods;
46 |
47 | ///
48 | /// 是否封闭(首尾相连)
49 | ///
50 | public bool IsClose => this.isClose;
51 |
52 | public IReadOnlyList PinRods => this.pinRods;
53 |
54 | ///
55 | /// 创建一个联合质体
56 | ///
57 | ///
58 | ///
59 | ///
60 | ///
61 | ///
62 | public CombinedParticle(List vertexs, double mass = 1, double restitution = 1, bool isClose = true)
63 | {
64 | var num = vertexs.Count;
65 | if (num < 2)
66 | {
67 | throw new InvalidArgumentException(
68 | $"Can't create a combined particle by given vertexs. vertexs.Count = {vertexs.Count}", nameof(vertexs));
69 | }
70 | else if (isClose && num < 3)
71 | {
72 | throw new InvalidArgumentException(
73 | $"Can't create a closed combined particle by given vertexs. vertexs.Count = {vertexs.Count}", nameof(vertexs));
74 | }
75 |
76 | foreach (var vertex in vertexs)
77 | {
78 | this.vertexs.Add(new Particle
79 | {
80 | Position = vertex,
81 | Mass = mass / num,
82 | Restitution = restitution
83 | });
84 | }
85 |
86 | // 在可形成多边形的时候允许链接为封闭图形
87 | this.isClose = isClose;
88 | if (isClose && num > 2)
89 | {
90 | this.vertexs.Add(this.vertexs[0]);
91 | }
92 |
93 | // 创建刚性连杆
94 | for (int i = 1; i < this.vertexs.Count; i++)
95 | {
96 | this.rods.Add(new ParticleRod(this.vertexs[i - 1], this.vertexs[i]));
97 | }
98 | }
99 |
100 | ///
101 | /// 装载到物理世界
102 | ///
103 | ///
104 | public override void OnInit(World world)
105 | {
106 | foreach (var vertex in this.vertexs)
107 | {
108 | world.AddObject(vertex);
109 | }
110 |
111 | foreach (var rod in this.rods)
112 | {
113 | world.ContactGenerators.Add(rod);
114 | }
115 | }
116 |
117 | ///
118 | /// 从物理世界中移除
119 | ///
120 | ///
121 | public override void OnRemove(World world)
122 | {
123 | foreach (var vertex in this.vertexs)
124 | {
125 | world.RemoveObject(vertex);
126 | }
127 | }
128 |
129 | ///
130 | /// 更新物体
131 | ///
132 | ///
133 | public override void Update(double duration)
134 | {
135 | // 组织质体的位移被视为所有质体的偏移量
136 | // 该偏移量在每次施加偏移过后置0
137 | var N = this.isClose ? this.vertexs.Count - 1 : this.vertexs.Count;
138 | for (int i = 0; i < N; i++)
139 | {
140 | this.vertexs[i].Position += this.Position;
141 | }
142 |
143 | this.Position = Vector2D.Zero;
144 | }
145 |
146 | Handle IPin.Pin(World world, Vector2D position)
147 | {
148 | return this.Pin(world, position);
149 | }
150 |
151 | void IPin.Unpin(World world)
152 | {
153 | this.UnPin(world);
154 | }
155 |
156 | protected Handle Pin(World world, Vector2D position)
157 | {
158 | var pin = new Particle
159 | {
160 | Position = position,
161 | InverseMass = 0
162 | };
163 |
164 | // 对于封闭图形,任意连接三个点即可固定住形状
165 | // 对于不封闭图形,需要每个点都连接才可固定
166 | var N = this.isClose ? 3 : this.vertexs.Count;
167 | for (int i = 0; i < N; i++)
168 | {
169 | this.pinRods.Add(world.CreateRod(this.vertexs[i], pin));
170 | }
171 |
172 | var handle = new Handle(position);
173 | handle.PropertyChanged += (obj, e) =>
174 | {
175 | var p = ((Handle)obj).Position;
176 | var d = p - pin.Position;
177 | this.Position = d;
178 | pin.Position = p;
179 | };
180 |
181 | return handle;
182 | }
183 |
184 | protected void UnPin(World world)
185 | {
186 | // 移除连接
187 | foreach (var rod in this.pinRods)
188 | {
189 | world.ContactGenerators.Remove(rod);
190 | }
191 |
192 | this.pinRods.Clear();
193 |
194 | // 恢复速度
195 | foreach (var vertex in this.vertexs)
196 | {
197 | vertex.Velocity = this.Velocity;
198 | }
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/Physics2D/Object/CustomObject.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Object
2 | {
3 | using Physics2D.Core;
4 |
5 | ///
6 | /// 自定义的物体需要实现该抽象类
7 | ///
8 | public abstract class CustomObject : PhysicsObject
9 | {
10 | ///
11 | /// 实现自定义的物理世界装载
12 | ///
13 | ///
14 | public abstract void OnInit(World world);
15 |
16 | ///
17 | /// 实现自定义的物理世界移除
18 | ///
19 | ///
20 | public abstract void OnRemove(World world);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Physics2D/Object/Particle.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Object
2 | {
3 | using Physics2D.Common;
4 |
5 | public class Particle : PhysicsObject
6 | {
7 | ///
8 | /// 更新质体
9 | ///
10 | ///
11 | public override void Update(double duration)
12 | {
13 | this.PrePosition = this.Position;
14 |
15 | // 对位置速度以及加速度进行更新
16 | this.Acceleration = this.forceAccum * this.inverseMass;
17 |
18 | this.Position += this.Velocity * duration;
19 | this.Velocity += this.Acceleration * duration;
20 |
21 | // 清除作用力
22 | this.forceAccum = Vector2D.Zero;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Physics2D/Object/PhysicsObject.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Object
2 | {
3 | using System;
4 | using Physics2D.Collision.Shapes;
5 | using Physics2D.Common;
6 |
7 | public abstract class PhysicsObject
8 | {
9 | ///
10 | /// 位置
11 | ///
12 | public Vector2D Position;
13 |
14 | ///
15 | /// 速度
16 | ///
17 | public Vector2D Velocity;
18 |
19 | ///
20 | /// 加速度
21 | ///
22 | public Vector2D Acceleration;
23 |
24 | ///
25 | /// 上一帧的位置
26 | ///
27 | public Vector2D PrePosition;
28 |
29 | ///
30 | /// 碰撞回弹系数
31 | ///
32 | public double Restitution = 1;
33 |
34 | ///
35 | /// 质量
36 | ///
37 | protected double mass;
38 |
39 | ///
40 | /// 质量的倒数
41 | ///
42 | protected double inverseMass;
43 |
44 | ///
45 | /// 物体绑定的形状
46 | ///
47 | protected Shape shape = new Point();
48 |
49 | ///
50 | /// 物体所受的力的合力
51 | ///
52 | protected Vector2D forceAccum;
53 |
54 | ///
55 | /// 为物体施加力
56 | ///
57 | ///
58 | public void AddForce(Vector2D force)
59 | {
60 | this.forceAccum += force;
61 | }
62 |
63 | ///
64 | /// 质量
65 | ///
66 | public double Mass
67 | {
68 | set
69 | {
70 | if (value != 0)
71 | {
72 | this.mass = value;
73 | this.inverseMass = 1.0 / value;
74 | }
75 | else
76 | throw new ArgumentOutOfRangeException("Particle's mass cannot be zero.");
77 | }
78 | get { return this.mass; }
79 | }
80 |
81 | ///
82 | /// 质量的倒数
83 | ///
84 | public double InverseMass
85 | {
86 | set
87 | {
88 | this.mass = value == 0 ? double.MaxValue : 1.0 / value;
89 | this.inverseMass = value;
90 | }
91 | get { return this.inverseMass; }
92 | }
93 |
94 | public Shape Shape { get { return this.shape; } }
95 |
96 | ///
97 | /// 为物体绑定一个形状
98 | ///
99 | ///
100 | public void BindShape(Shape shape)
101 | {
102 | shape.Body = this;
103 | this.shape = shape;
104 | }
105 |
106 | ///
107 | /// 更新物体
108 | ///
109 | ///
110 | public abstract void Update(double duration);
111 | }
112 | }
--------------------------------------------------------------------------------
/Physics2D/Object/Tools/Handle.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Object.Tools
2 | {
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 | using Physics2D.Common;
6 |
7 | public class Handle : INotifyPropertyChanged
8 | {
9 | ///
10 | /// The position.
11 | ///
12 | private Vector2D position;
13 |
14 | ///
15 | /// The position.
16 | ///
17 | public Vector2D Position
18 | {
19 | get { return this.position; }
20 | set { this.SetProperty(ref this.position, value); }
21 | }
22 |
23 | public Handle(Vector2D position)
24 | {
25 | this.position = position;
26 | }
27 |
28 | ///
29 | /// Set property's value.
30 | /// Trigger property changed event when value changed.
31 | ///
32 | /// The type.
33 | /// Current value.
34 | /// Aim value.
35 | /// The property's name.
36 | /// True if property value updated.
37 | protected bool SetProperty(ref T storge, T value, [CallerMemberName]string propertyName = null)
38 | {
39 | if (object.Equals(storge, value))
40 | {
41 | return false;
42 | }
43 |
44 | storge = value;
45 | this.OnPropertyChanged(propertyName);
46 | return true;
47 | }
48 |
49 | ///
50 | /// The event of property changed.
51 | ///
52 | /// The property's name.
53 | protected void OnPropertyChanged([CallerMemberName]string propertyName = null)
54 | {
55 | this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
56 | }
57 |
58 | ///
59 | /// Release all events.
60 | ///
61 | public void Release()
62 | {
63 | var delegates = this.PropertyChanged.GetInvocationList();
64 | foreach (var d in delegates)
65 | {
66 | this.PropertyChanged -= d as PropertyChangedEventHandler;
67 | }
68 | }
69 |
70 | ///
71 | /// The property changed event.
72 | /// Registed delegation will fired on property changed.
73 | ///
74 | public event PropertyChangedEventHandler PropertyChanged;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Physics2D/Object/Tools/IPin.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D.Object.Tools
2 | {
3 | using Physics2D.Common;
4 | using Physics2D.Core;
5 |
6 | public interface IPin
7 | {
8 | ///
9 | /// Pin self to world.
10 | ///
11 | /// The .
12 | /// The
13 | /// The pined point.
14 | Handle Pin(World world, Vector2D position);
15 |
16 | ///
17 | /// Unpin self from world.
18 | ///
19 | /// The .
20 | void Unpin(World world);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Physics2D/Physics2D.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Physics2D/Settings.cs:
--------------------------------------------------------------------------------
1 | namespace Physics2D
2 | {
3 | public class Settings
4 | {
5 | ///
6 | /// Gets or sets the percision of double type.
7 | ///
8 | public static double Percision { get; set; } = 1e-8;
9 |
10 | ///
11 | /// Gets or sets the max contact number.
12 | ///
13 | public static int MaxContacts { get; set; } = 500;
14 |
15 | ///
16 | /// Gets or sets the iteration nuumber of contact resolver.
17 | ///
18 | public static int ContactIteration { get; set; } = 1;
19 | }
20 | }
--------------------------------------------------------------------------------
/Physics2D/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | // ACTION REQUIRED: This file was automatically added to your project, but it
3 | // will not take effect until additional steps are taken to enable it. See the
4 | // following page for additional information:
5 | //
6 | // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md
7 |
8 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
9 | "settings": {
10 | "documentationRules": {
11 | "companyName": "PlaceholderCompany"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Physics2D
2 | =========
3 |
4 | 一个使用C#编写的2D质体物理引擎,项目包含了
5 |
6 | - 质体物理引擎
7 | - 单元测试
8 | - 基于WPF绘制的简单Demo合集
9 |
10 | 该项目是一个实验项目(Toy Project),用于实践我所学习到的关于框架设计、语言特性等知识,因此该项目不具备工业项目的稳定性及性能(说到性能,其实用C#做这样的项目似乎就不算太合适),但是制作一些基于WPF的小型游戏应该还是可以做到的。为了简化系统自身,并且基于项目的实现目的,该引擎并暂时没有考虑实现刚体物理模型,但是各个模块的设计为实现刚体物理模型提供了可能性和便利性,也就是说你可以很容易的将刚体物理模型加入到本引擎当中。
11 |
12 | 该项目力求追随工程项目的最佳实践,希望能够从设计、重构、文档、测试方面都能够较为完备。目前大部分代码都包含详实(但不啰嗦)的注释,大部分方法和属性都标注了文档注释,可以使用Doxygen等第三方软件生成可读的API文档。物理引擎类库部分的所有代码都被被单元测试覆盖,但遗憾的是,单元测试的代码覆盖率并不能证明程序执行的正确性,所以测试用例还需在后续的开发过程中不断地补充完善。
13 |
14 | ## 功能(包含计划中的)
15 |
16 | - [x] 质体(粒子)
17 | - [x] 可以定制的质体作用力发生器
18 | - [x] 可以定制的区域作用力
19 | - [x] 质体碰撞处理
20 | - [x] 质体连杆、绳索
21 | - [ ] 摩擦力
22 | - [ ] 液体模拟
23 | - [ ] 沙盒模拟器
24 |
25 | ## 开始使用
26 |
27 | 基本使用
28 | ```csharp
29 | // 创建一个物理世界
30 | var world = new World();
31 |
32 | // 在物理世界里创建一个质体(粒子)
33 | // 位置 (0, 0) 初速度 (1, 0) 质量 1 kg 碰撞恢复系数0.9
34 | world.CreateParticle(Vector2D.Zero, new Vector2D(1, 0), 1, 0.9);
35 |
36 | // 创建一个全局有效的重力场
37 | world.CreateGravity(9.8);
38 |
39 | // 执行 1/60 s
40 | world.Update(1/60.0);
41 | ```
42 |
43 | 添加碰撞
44 | ```csharp
45 | // 创建一条长5m深4m的底边
46 | var edge = world.CreateEdge(0, 4, 5, 4);
47 |
48 | // 将质点视为可与底边接触的球(圆),半径为0.2m
49 | particle.BindSharp(new Circle(0.2));
50 |
51 | // 执行 1/60 s
52 | world.Update(1/60.0);
53 | ```
54 |
55 | ## 分支说明
56 |
57 | 目前该项目的Demo图形依赖于WriteableBitmapEx(通过nuget引入到项目),限于GDI+的性能问题,也希望能够尝试使用SharpDX或Win2D这样的由DirectX驱动的图形库,为此我创建了两个分支:
58 |
59 | - SharpDXonWPF
60 | 在WPFDemo项目中增加了基于SharpDX中Direct2D的Image扩展,使用Direct2D作为渲染驱动。该尝试仍处于试验状态。
61 |
62 | - UWPDemo
63 | 由于Win2D仅支持UWP等沙盒内应用,所以增加了UWPDemo项目,由于将Physics2D项目更改为了PCL类型,所以Physics2D可以同时支持WPF以及UWP应用。还未正式启动该项尝试。
64 |
65 | 如果你喜欢相关的技术,欢迎为相关的分支贡献代码。
66 |
67 | ## 演示动画
68 |
69 | 由于图片比较大,加载完毕可能需要一定时间
70 |
71 | - 烟花
72 |
73 | 
74 |
75 | - 圆周运动
76 |
77 | 
78 |
79 | - 弹性材料
80 |
81 | 
82 |
83 | - 变形球
84 |
85 | 
86 |
87 | - 牛顿摆
88 |
89 | 
90 |
91 | - 多边形连杆(模拟刚体)
92 |
93 | 
94 |
--------------------------------------------------------------------------------
/UnitTest/Collision/Basic/ParticleRodTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Collision.Basic
2 | {
3 | using System.Collections;
4 | using System.Collections.Generic;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 | using Physics2D.Collision;
7 | using Physics2D.Collision.Basic;
8 | using Physics2D.Common;
9 | using Physics2D.Object;
10 |
11 | [TestClass]
12 | public class ParticleRodTest
13 | {
14 | [TestMethod]
15 | public void TestConstructor()
16 | {
17 | var pA = new Particle { Mass = 1 };
18 | var pB = new Particle { Mass = 1, Position = new Vector2D(5, 0) };
19 | var rod = new ParticleRod(pA, pB);
20 |
21 | Assert.AreEqual(pA, rod.ParticleA);
22 | Assert.AreEqual(pB, rod.ParticleB);
23 | }
24 |
25 | [TestMethod]
26 | public void TestGetEnumerator()
27 | {
28 | var pA = new Particle { Mass = 1 };
29 | var pB = new Particle { Mass = 1, Position = new Vector2D(5, 0) };
30 | var rod = new ParticleRod(pA, pB);
31 |
32 | var contacts = new List();
33 | contacts.AddRange(rod);
34 | Assert.AreEqual(0, contacts.Count, "长度未变化时不产生任何碰撞");
35 |
36 | pB.Position = new Vector2D(6, 0);
37 | foreach (var contact in rod)
38 | {
39 | Assert.AreEqual(1, contact.Penetration);
40 | Assert.AreEqual(new Vector2D(1, 0), contact.ContactNormal);
41 | }
42 |
43 | pB.Position = new Vector2D(4, 0);
44 | foreach (var contact in rod)
45 | {
46 | Assert.AreEqual(1, contact.Penetration);
47 | Assert.AreEqual(new Vector2D(-1, 0), contact.ContactNormal);
48 | }
49 |
50 | pB.Position = new Vector2D(4, 0);
51 | IEnumerable iEnum = rod;
52 | foreach (ParticleContact contact in iEnum)
53 | {
54 | Assert.AreEqual(1, contact.Penetration);
55 | Assert.AreEqual(new Vector2D(-1, 0), contact.ContactNormal);
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/UnitTest/Collision/Basic/ParticleRopeTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Collision.Basic
2 | {
3 | using System.Collections.Generic;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Physics2D.Collision;
6 | using Physics2D.Collision.Basic;
7 | using Physics2D.Common;
8 | using Physics2D.Object;
9 |
10 | [TestClass]
11 | public class ParticleRopeTest
12 | {
13 | [TestMethod]
14 | public void TestConstructor()
15 | {
16 | var pA = new Particle { Mass = 1 };
17 | var pB = new Particle { Mass = 1 };
18 | var rope = new ParticleRope(10, 0.5, pA, pB);
19 |
20 | Assert.AreEqual(10, rope.MaxLength);
21 | Assert.AreEqual(pA, rope.ParticleA);
22 | Assert.AreEqual(pB, rope.ParticleB);
23 | Assert.AreEqual(0.5, rope.Restitution);
24 | }
25 |
26 | [TestMethod]
27 | public void TestGetEnumerator()
28 | {
29 | var pA = new Particle { Mass = 1 };
30 | var pB = new Particle { Mass = 1 };
31 | var rope = new ParticleRope(10, 0.5, pA, pB);
32 |
33 | var contacts = new List();
34 | contacts.AddRange(rope);
35 | Assert.AreEqual(0, contacts.Count, "长度未变化时不产生任何碰撞");
36 |
37 | pB.Position = new Vector2D(20, 0);
38 | foreach (var contact in rope)
39 | {
40 | Assert.AreEqual(10, contact.Penetration);
41 | Assert.AreEqual(new Vector2D(1, 0), contact.ContactNormal);
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/UnitTest/Collision/ParticleCollisionDetectorTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Collision
2 | {
3 | using System;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Physics2D.Collision;
6 | using Physics2D.Collision.Shapes;
7 | using Physics2D.Common;
8 | using Physics2D.Object;
9 |
10 | [TestClass]
11 | public class ParticleCollisionDetectorTest
12 | {
13 | [TestMethod]
14 | public void TestCircleAndCircleCollided()
15 | {
16 | Particle pA = new Particle { Position = new Vector2D(0, 0), Restitution = 1 };
17 | Particle pB = new Particle { Position = new Vector2D(0, 3), Restitution = 0.5 };
18 |
19 | pA.BindShape(new Circle(2));
20 | pB.BindShape(new Circle(2));
21 |
22 | TestDetectorsResult(
23 | new ParticleContact(pA, pB, 0.75, 1, new Vector2D(0, -1)),
24 | ParticleCollisionDetector.CircleAndCircle(pA.Shape as Circle, pB.Shape as Circle));
25 | }
26 |
27 | [TestMethod]
28 | public void TestCircleAndCircleNotCollided()
29 | {
30 | Particle pA = new Particle { Position = new Vector2D(0, 0) };
31 | Particle pB = new Particle { Position = new Vector2D(0, 3) };
32 |
33 | pA.BindShape(new Circle(1));
34 | pB.BindShape(new Circle(1));
35 |
36 | Assert.IsNull(ParticleCollisionDetector.CircleAndCircle(pA.Shape as Circle, pB.Shape as Circle));
37 | }
38 |
39 | [TestMethod]
40 | public void TestCircleAndEdgeCollided()
41 | {
42 | Particle pA = new Particle { Position = new Vector2D(0, 2), Restitution = 1 };
43 | Edge edge = new Edge(0, 0, 5, 0);
44 | pA.BindShape(new Circle(5));
45 |
46 | TestDetectorsResult(
47 | new ParticleContact(pA, null, 1, 3, new Vector2D(0, 1)),
48 | ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "圆心投影在边沿上");
49 |
50 | pA.Position = new Vector2D(-1, 2);
51 | TestDetectorsResult(
52 | new ParticleContact(pA, null, 1, 5 - Math.Sqrt(5), new Vector2D(-1, 2).Normalize()),
53 | ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "圆心投影在边沿左延长线上");
54 |
55 | pA.Position = new Vector2D(6, 2);
56 | TestDetectorsResult(
57 | new ParticleContact(pA, null, 1, 5 - Math.Sqrt(5), new Vector2D(1, 2).Normalize()),
58 | ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "圆心投影在边沿右延长线上");
59 |
60 | pA.Position = new Vector2D(-1, 0);
61 | TestDetectorsResult(
62 | new ParticleContact(pA, null, 1, 4, new Vector2D(-1, 0)),
63 | ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "圆心在边沿的延长线上");
64 |
65 | pA.PrePosition = new Vector2D(2.5, 10);
66 | pA.Position = new Vector2D(2.5, -10);
67 | TestDetectorsResult(
68 | new ParticleContact(pA, null, 1, 5, new Vector2D(0, 1)),
69 | ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "发生了穿越");
70 | }
71 |
72 | [TestMethod]
73 | public void TestCircleAndEdgeNotCollided()
74 | {
75 | Particle pA = new Particle { Position = new Vector2D(-1, 6), Restitution = 1 };
76 | Edge edge = new Edge(0, 0, 5, 0);
77 | pA.BindShape(new Circle(5));
78 |
79 | Assert.IsNull(ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "圆心投影在边沿左延长线上");
80 |
81 | pA.Position = new Vector2D(6, 6);
82 | Assert.IsNull(ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "圆心投影在边沿右延长线上");
83 |
84 | pA.Position = new Vector2D(2.5, 6);
85 | Assert.IsNull(ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "圆心投影在边沿上");
86 |
87 | pA.Position = new Vector2D(-6, 0);
88 | Assert.IsNull(ParticleCollisionDetector.CircleAndEdge(pA.Shape as Circle, edge), "圆心在边沿的延长线上");
89 | }
90 |
91 | private static void TestDetectorsResult(
92 | ParticleContact expectContact,
93 | ParticleContact contact,
94 | string message = "")
95 | {
96 | Assert.IsNotNull(contact, $"{message} 产生了碰撞");
97 | Assert.AreEqual(expectContact.PA, contact.PA, $"{message} 物体A");
98 | Assert.AreEqual(expectContact.PB, contact.PB, $"{message} 物体B");
99 | Assert.AreEqual(expectContact.ContactNormal, contact.ContactNormal, $"{message} 碰撞法线");
100 | Assert.AreEqual(expectContact.Restitution, contact.Restitution, $"{message} 回弹系数");
101 | Assert.AreEqual(expectContact.Penetration, contact.Penetration, $"{message} 相交深度");
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/UnitTest/Collision/ParticleContactResolverTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Collision
2 | {
3 | using System.Collections.Generic;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Physics2D.Collision;
6 | using Physics2D.Common;
7 | using Physics2D.Object;
8 |
9 | [TestClass]
10 | public class ParticleContactResolverTest
11 | {
12 | [TestMethod]
13 | public void TestConstructor()
14 | {
15 | var resolver = new ParticleContactResolver(100);
16 |
17 | Assert.AreEqual(100, resolver.Iterations);
18 | }
19 |
20 | [TestMethod]
21 | public void TestGetterAndSetterOfIterations()
22 | {
23 | var resolver = new ParticleContactResolver(100);
24 | resolver.Iterations = 1000;
25 |
26 | Assert.AreEqual(1000, resolver.Iterations);
27 | }
28 |
29 | [TestMethod]
30 | public void TestResolveContacts()
31 | {
32 | var resolver = new ParticleContactResolver(100);
33 |
34 | var p = new List
35 | {
36 | new Particle
37 | {
38 | Position = new Vector2D(0, 0),
39 | Velocity = new Vector2D(1, 0),
40 | Mass = 1
41 | },
42 | new Particle
43 | {
44 | Position = new Vector2D(2, 0),
45 | Velocity = new Vector2D(0, 0),
46 | Mass = 1
47 | }
48 | };
49 | var contact = new ParticleContact(p[0], p[1], 1, 1, new Vector2D(-1, 0));
50 |
51 | var contactList = new List();
52 | resolver.ResolveContacts(contactList, 1 / 60.0);
53 |
54 | contactList.Add(contact);
55 | resolver.ResolveContacts(contactList, 1 / 60.0);
56 | Assert.AreEqual(new Vector2D(-1, 0), p[0].Position, "物体0依据速度分量分离");
57 | Assert.AreEqual(new Vector2D(0, 0), p[0].Velocity, "物体0碰撞后速度相反");
58 | Assert.AreEqual(new Vector2D(1, 0), p[1].Velocity, "物体1碰撞后速度相反");
59 |
60 | p = new List
61 | {
62 | new Particle
63 | {
64 | Position = new Vector2D(0, 0),
65 | Velocity = new Vector2D(1, 0),
66 | Mass = 1
67 | },
68 | new Particle
69 | {
70 | Position = new Vector2D(2, 0),
71 | Velocity = new Vector2D(0, 0),
72 | Mass = 1
73 | }
74 | };
75 | contactList = new List
76 | {
77 | new ParticleContact(p[1], p[0], 1, 1, new Vector2D(1, 0))
78 | };
79 | resolver.ResolveContacts(contactList, 1 / 60.0);
80 | Assert.AreEqual(new Vector2D(-1, 0), p[0].Position, "函数满足对称性");
81 | Assert.AreEqual(new Vector2D(0, 0), p[0].Velocity, "函数满足对称性");
82 | Assert.AreEqual(new Vector2D(1, 0), p[1].Velocity, "函数满足对称性");
83 |
84 | }
85 |
86 | [TestMethod]
87 | public void TestResovleMultiContacts()
88 | {
89 | var resolver = new ParticleContactResolver(100);
90 | var p = new List
91 | {
92 | new Particle
93 | {
94 | Position = new Vector2D(0, 0),
95 | Velocity = new Vector2D(0, 0),
96 | Mass = 1
97 | },
98 | new Particle
99 | {
100 | Position = new Vector2D(2, 0),
101 | Velocity = new Vector2D(0, 0),
102 | Mass = 1
103 | },
104 | new Particle
105 | {
106 | Position = new Vector2D(4, 0),
107 | Velocity = new Vector2D(0, 0),
108 | Mass = 1
109 | }
110 | };
111 | var contactList = new List
112 | {
113 | new ParticleContact(p[0], p[1], 1, 1, new Vector2D(-1, 0)),
114 | new ParticleContact(p[2], p[1], 1, 1, new Vector2D(1, 0))
115 | };
116 | resolver.ResolveContacts(contactList, 1 / 60.0);
117 | Assert.AreEqual(new Vector2D(-1, 0), p[0].Position, "物体0向左分离");
118 | Assert.AreEqual(new Vector2D(2, 0), p[1].Position, "物体1位置不变");
119 | Assert.AreEqual(new Vector2D(5, 0), p[2].Position, "物体2向右分离");
120 | }
121 |
122 | [TestMethod]
123 | public void TestResolveContinuousContacts()
124 | {
125 | var resolver = new ParticleContactResolver(100);
126 | var p = new List
127 | {
128 | new Particle
129 | {
130 | Position = new Vector2D(0, 0),
131 | Velocity = new Vector2D(1, 0),
132 | Mass = 1
133 | },
134 | new Particle
135 | {
136 | Position = new Vector2D(2, 0),
137 | Velocity = new Vector2D(0, 0),
138 | Mass = 1
139 | },
140 | new Particle
141 | {
142 | Position = new Vector2D(4, 0),
143 | Velocity = new Vector2D(0, 0),
144 | Mass = 1
145 | }
146 | };
147 | var contactList = new List
148 | {
149 | new ParticleContact(p[0], p[1], 1, 1, new Vector2D(-1, 0)),
150 | new ParticleContact(p[1], p[2], 1, 0, new Vector2D(-1, 0))
151 | };
152 | resolver.ResolveContacts(contactList, 1 / 60.0);
153 | Assert.AreEqual(new Vector2D(-1, 0), p[0].Position, "物体0向左分离");
154 | Assert.AreEqual(new Vector2D(2, 0), p[1].Position, "物体1位置不变");
155 | Assert.AreEqual(new Vector2D(4, 0), p[2].Position, "物体2位置不变");
156 |
157 | Assert.AreEqual(new Vector2D(0, 0), p[0].Velocity, "物体0碰撞后无速度");
158 | Assert.AreEqual(new Vector2D(0, 0), p[1].Velocity, "物体1碰撞后无速度");
159 | Assert.AreEqual(new Vector2D(1, 0), p[2].Velocity, "物体2获得物体0的速度");
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/UnitTest/Collision/Shapes/CircleTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Collision.Shapes
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Collision.Shapes;
5 |
6 | [TestClass]
7 | public class CircleTest
8 | {
9 | [TestMethod]
10 | public void TestConstructor()
11 | {
12 | var circle = new Circle(10);
13 | Assert.AreEqual(10, circle.R);
14 | Assert.AreEqual(0, circle.Id);
15 |
16 | circle = new Circle(10, 1);
17 | Assert.AreEqual(1, circle.Id);
18 | }
19 |
20 | [TestMethod]
21 | public void TestType()
22 | {
23 | var circle = new Circle(10);
24 | Assert.AreEqual(ShapeType.Circle, circle.Type);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/UnitTest/Collision/Shapes/EdgeTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Collision.Shapes
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Collision.Shapes;
5 | using Physics2D.Common;
6 |
7 | [TestClass]
8 | public class EdgeTest
9 | {
10 | [TestMethod]
11 | public void TestConstructor()
12 | {
13 | var edge = new Edge(new Vector2D(0, 0), new Vector2D(1, 1));
14 | Assert.AreEqual(new Vector2D(0, 0), edge.PointA);
15 | Assert.AreEqual(new Vector2D(1, 1), edge.PointB);
16 |
17 | edge = new Edge(0, 0, 1, 1);
18 | Assert.AreEqual(new Vector2D(0, 0), edge.PointA);
19 | Assert.AreEqual(new Vector2D(1, 1), edge.PointB);
20 | Assert.AreEqual(0, edge.Id);
21 |
22 | }
23 |
24 | [TestMethod]
25 | public void TestType()
26 | {
27 | var edge = new Edge(new Vector2D(0, 0), new Vector2D(1, 1));
28 |
29 | Assert.AreEqual(ShapeType.Edge, edge.Type);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/UnitTest/Collision/Shapes/PointTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Collision.Shapes
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Collision.Shapes;
5 |
6 | [TestClass]
7 | public class PointTest
8 | {
9 | [TestMethod]
10 | public void TestType()
11 | {
12 | var p = new Point();
13 |
14 | Assert.AreEqual(ShapeType.Point, p.Type);
15 | Assert.AreEqual(0, p.Id);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/UnitTest/Collision/Shapes/ShapeTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Collision.Shapes
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Collision.Shapes;
5 |
6 | [TestClass]
7 | public class ShapeTest
8 | {
9 | [TestMethod]
10 | public void TestNewId()
11 | {
12 | Assert.AreEqual(1, Shape.NewId());
13 | Assert.AreEqual(2, Shape.NewId());
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/UnitTest/Common/MathHelperTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Common
2 | {
3 | using System.Collections.Generic;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Physics2D.Common;
6 |
7 | [TestClass]
8 | public class MathHelperTest
9 | {
10 | [TestMethod]
11 | public void TestPointToLineVector()
12 | {
13 | Vector2D linePA = new Vector2D(0, 0);
14 | Vector2D linePB = new Vector2D(5, 0);
15 | Vector2D point = new Vector2D(2.5, 5);
16 |
17 | Assert.AreEqual(new Vector2D(0, -5), MathHelper.PointToLineVector(point, linePA, linePB));
18 | }
19 |
20 | [TestMethod]
21 | public void TestLineIntersection()
22 | {
23 | // 有交点的情况
24 | Vector2D pA1 = new Vector2D(0, 3);
25 | Vector2D pA2 = new Vector2D(6, 3);
26 |
27 | Vector2D pB1 = new Vector2D(3, 0);
28 | Vector2D pB2 = new Vector2D(3, 6);
29 |
30 | var actual = MathHelper.LineIntersection(pA1, pA2, pB1, pB2);
31 | Assert.AreEqual(new Vector2D(3, 3), actual);
32 |
33 | // 无交点的情况
34 | Vector2D pC1 = new Vector2D(1, 4);
35 | Vector2D pC2 = new Vector2D(7, 4);
36 | Assert.IsNull(MathHelper.LineIntersection(pA1, pA2, pC1, pC2));
37 |
38 | Vector2D pD1 = new Vector2D(12, 6);
39 | Vector2D pD2 = new Vector2D(12, 0);
40 | Assert.IsNull(MathHelper.LineIntersection(pA2, pA1, pD1, pD2));
41 | }
42 |
43 | [TestMethod]
44 | public void TestIsInside()
45 | {
46 | // 测试正常情况
47 | var vertexs = new List
48 | {
49 | new Vector2D(0, 0),
50 | new Vector2D(100, 0),
51 | new Vector2D(0, 100)
52 | };
53 | vertexs.Add(vertexs[0]);
54 |
55 | Assert.IsTrue(MathHelper.IsInside(vertexs, new Vector2D(25, 25)));
56 | Assert.IsFalse(MathHelper.IsInside(vertexs, new Vector2D(100, 100)));
57 | }
58 |
59 | [TestMethod]
60 | public void TestIsInsideFail()
61 | {
62 | // 测试不能围成多边形的情况
63 | var vertexs = new List
64 | {
65 | new Vector2D(0, 0),
66 | new Vector2D(100, 0),
67 | new Vector2D(0, 100)
68 | };
69 |
70 | Assert.IsFalse(MathHelper.IsInside(vertexs, new Vector2D(25, 25)));
71 | }
72 |
73 | [TestMethod]
74 | public void TestPointToLineDistanceSquared()
75 | {
76 | Assert.AreEqual(25, MathHelper.PointToLineDistenceSquared(
77 | new Vector2D(0, 5),
78 | new Vector2D(-5, 0), new Vector2D(5, 0)));
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/UnitTest/Common/Vector2DTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Common
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 |
6 | [TestClass]
7 | public class Vector2DTest
8 | {
9 | [TestMethod]
10 | public void TestConstructor()
11 | {
12 | Vector2D vector = new Vector2D();
13 | vector.X = vector.Y = 0;
14 |
15 | Assert.AreEqual(vector, new Vector2D(0, 0));
16 | Assert.AreEqual(vector, new Vector2D(vector));
17 | }
18 |
19 | [TestMethod]
20 | public void TestEquals()
21 | {
22 | Vector2D vector = new Vector2D(1, 2);
23 |
24 | Assert.IsTrue(vector.Equals(new Vector2D(1, 2)));
25 | Assert.IsFalse(vector.Equals(new Vector2D(2, 1)));
26 | Assert.IsFalse(vector.Equals("(1, 2)"));
27 | Assert.IsFalse(vector.Equals(null));
28 | }
29 |
30 | [TestMethod]
31 | public void TestSet()
32 | {
33 | Vector2D vector = new Vector2D(0, 0);
34 | vector.Set(5, 2);
35 |
36 | Assert.AreEqual(new Vector2D(5, 2), vector);
37 | }
38 |
39 | [TestMethod]
40 | public void TestSpecificProperty()
41 | {
42 | Assert.AreEqual(new Vector2D(1, 1), Vector2D.One);
43 | Assert.AreEqual(new Vector2D(1, 0), Vector2D.UnitX);
44 | Assert.AreEqual(new Vector2D(0, 1), Vector2D.UnitY);
45 | Assert.AreEqual(new Vector2D(0, 0), Vector2D.Zero);
46 | }
47 |
48 | [TestMethod]
49 | public void TestToString()
50 | {
51 | Vector2D vector = new Vector2D(1, 2);
52 |
53 | Assert.AreEqual("(1.23, 4.56)", new Vector2D(1.23, 4.56).ToString());
54 | }
55 |
56 | [TestMethod]
57 | public void TestGetHashCode()
58 | {
59 | Vector2D vector = new Vector2D(1, 2);
60 |
61 | Assert.IsNotNull(vector.GetHashCode());
62 | }
63 |
64 | [TestMethod]
65 | public void TestAddition()
66 | {
67 | Vector2D left = new Vector2D(3, 4);
68 | Vector2D right = new Vector2D(2, 2);
69 |
70 | Assert.AreEqual(new Vector2D(5, 6), left + right);
71 | }
72 |
73 | [TestMethod]
74 | public void TestSubtraction()
75 | {
76 | Vector2D left = new Vector2D(3, 4);
77 | Vector2D right = new Vector2D(2, 2);
78 |
79 | Assert.AreEqual(new Vector2D(1, 2), left - right);
80 | Assert.AreEqual(new Vector2D(-3, -4), -left);
81 | }
82 |
83 | [TestMethod]
84 | public void TestMultiply()
85 | {
86 | Vector2D left = new Vector2D(3, 4);
87 | Vector2D right = new Vector2D(2, 2);
88 |
89 | Assert.AreEqual(3 * 2 + 4 * 2, left * right);
90 | Assert.AreEqual(new Vector2D(6, 8), left * 2);
91 | Assert.AreEqual(new Vector2D(-10, -10), -5 * right);
92 | }
93 |
94 | [TestMethod]
95 | public void TestDivision()
96 | {
97 | Vector2D vector = new Vector2D(3, 4);
98 |
99 | Assert.AreEqual(new Vector2D(1.5, 2), vector / 2);
100 | }
101 |
102 | [TestMethod]
103 | public void TestUnaryNegation()
104 | {
105 | Vector2D vector = new Vector2D(3, 4);
106 |
107 | Assert.AreEqual(new Vector2D(-3, -4), -vector);
108 | }
109 |
110 | [TestMethod]
111 | public void TestEquality()
112 | {
113 | Vector2D left = new Vector2D(3, 4);
114 | Vector2D right = new Vector2D(2, 2);
115 |
116 | Assert.IsFalse(left == right);
117 | Assert.IsTrue(left == new Vector2D(3, 4));
118 | }
119 |
120 | [TestMethod]
121 | public void TestInequality()
122 | {
123 | Vector2D left = new Vector2D(3, 4);
124 | Vector2D right = new Vector2D(2, 2);
125 |
126 | Assert.IsTrue(left != right);
127 | Assert.IsFalse(left != new Vector2D(3, 4));
128 | }
129 |
130 | [TestMethod]
131 | public void TestNormalize()
132 | {
133 | Vector2D vect1 = new Vector2D(3, 0);
134 | Vector2D vect2 = new Vector2D(0, 4);
135 |
136 | Assert.AreEqual(Vector2D.UnitX, Vector2D.Normalize(vect1));
137 | Assert.AreEqual(Vector2D.UnitY, vect2.Normalize());
138 | Assert.AreEqual(Vector2D.Zero, Vector2D.Zero.Normalize());
139 | }
140 |
141 | [TestMethod]
142 | public void TestLength()
143 | {
144 | Vector2D vect1 = new Vector2D(3, 0);
145 | Vector2D vect2 = new Vector2D(0, 4);
146 |
147 | Assert.AreEqual(3f, vect1.Length());
148 | Assert.AreEqual(16f, vect2.LengthSquared());
149 | }
150 |
151 | [TestMethod]
152 | public void TestDistance()
153 | {
154 | Vector2D vect1 = new Vector2D(3, 0);
155 | Vector2D vect2 = new Vector2D(0, 4);
156 |
157 | Assert.AreEqual(25.0, Vector2D.DistanceSquared(vect1, vect2));
158 | Assert.AreEqual(5.0, Vector2D.Distance(vect1, vect2));
159 | Assert.AreEqual(0, Vector2D.Distance(vect1, vect1));
160 | }
161 | }
162 | }
--------------------------------------------------------------------------------
/UnitTest/ConvertUnitsTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D;
5 | using Physics2D.Common;
6 |
7 | [TestClass]
8 | public class ConvertUnitsTest
9 | {
10 | [TestMethod]
11 | public void TestToSimUnits()
12 | {
13 | Assert.AreEqual(2, 100.ToSimUnits());
14 | Assert.AreEqual(2, 100.0.ToSimUnits());
15 | Assert.AreEqual(new Vector2D(2, 1), new Vector2D(100, 50).ToSimUnits());
16 | }
17 |
18 | [TestMethod]
19 | public void TestToDisplayUnits()
20 | {
21 | Assert.AreEqual(100, 2.ToDisplayUnits());
22 | Assert.AreEqual(100, 2.0.ToDisplayUnits());
23 | Assert.AreEqual(new Vector2D(100, 50), new Vector2D(2, 1).ToDisplayUnits());
24 | }
25 |
26 | [TestMethod]
27 | public void TestSetDisplayUnitToSimUnitRatio()
28 | {
29 | ConvertUnits.SetDisplayUnitToSimUnitRatio(50);
30 |
31 | this.TestToSimUnits();
32 | this.TestToDisplayUnits();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/UnitTest/Core/ForceRegistryTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Core
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Core;
6 | using Physics2D.Force;
7 | using Physics2D.Object;
8 |
9 | [TestClass]
10 | public class ForceRegistryTest
11 | {
12 | [TestMethod]
13 | public void TestUpdate()
14 | {
15 | var forceRegistry = new ForceRegistry();
16 | var force = new ParticleConstantForce(new Vector2D(5, 0));
17 | var p = new Particle { Mass = 1 };
18 | force.Add(p);
19 |
20 | this.TestAddForceGenerator(forceRegistry, force);
21 | forceRegistry.Update(1 / 60.0);
22 | p.Update(1 / 60.0);
23 | Assert.AreEqual(new Vector2D(5, 0), p.Acceleration, "物体被赋予正确的加速度");
24 |
25 | p.Acceleration = Vector2D.Zero;
26 | this.TestRemoveForceGenerator(forceRegistry, force);
27 | forceRegistry.Update(1 / 60.0);
28 | p.Update(1 / 60.0);
29 | Assert.AreEqual(new Vector2D(0, 0), p.Acceleration, "删去作用力发生器,物体不再受该作用力发生器所产生的力");
30 |
31 | force.Add(p);
32 | this.TestAddForceGenerator(forceRegistry, force);
33 | this.TestRemoveParticle(forceRegistry, p);
34 | forceRegistry.Update(1 / 60.0);
35 | p.Update(1 / 60.0);
36 | Assert.AreEqual(new Vector2D(0, 0), p.Acceleration, "删去物体,物体不再受该作用力发生器所产生的力");
37 | }
38 |
39 | private void TestAddForceGenerator(
40 | ForceRegistry forceRegistry,
41 | ParticleForceGenerator force)
42 | {
43 | forceRegistry.Add(force);
44 | }
45 |
46 | private void TestRemoveForceGenerator(
47 | ForceRegistry forceRegistry,
48 | ParticleForceGenerator force)
49 | {
50 | forceRegistry.Remove(force);
51 | }
52 |
53 | private void TestRemoveParticle(
54 | ForceRegistry forceRegistry,
55 | Particle p)
56 | {
57 | forceRegistry.Remove(p);
58 | }
59 |
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/UnitTest/Core/WorldTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Core
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 | using Physics2D.Collision.Shapes;
7 | using Physics2D.Common;
8 | using Physics2D.Core;
9 | using Physics2D.Force;
10 | using Physics2D.Force.Zones;
11 | using Physics2D.Object;
12 | using Physics2D.Object.Tools;
13 |
14 | [TestClass]
15 | public class WorldTest
16 | {
17 | [TestMethod]
18 | public void TestUpdate()
19 | {
20 | var world = new World();
21 |
22 | // 两种物体
23 | var objA = new CombinedParticle(
24 | new List {
25 | new Vector2D(0, 0),
26 | new Vector2D(5, 0),
27 | new Vector2D(0, 5)
28 | });
29 | var objB = new Particle { Mass = 1, Position = Vector2D.Zero };
30 |
31 | // 一个全局作用力
32 | var zone = new GlobalZone();
33 | zone.Add(new ParticleGravity(new Vector2D(0, 9.8)));
34 | world.Zones.Add(zone);
35 |
36 | this.TestAddCustomObject(world, objA);
37 | var handle = this.TestPin(world, objA);
38 | this.TestUnPin(world, objA, handle);
39 | this.TestRemoveCustomObject(world, objA);
40 |
41 | // 一个作用力
42 | var force = new ParticleConstantForce(new Vector2D(5, 0));
43 | force.Add(objB);
44 | world.ForceGenerators.Add(force);
45 |
46 | this.TestAddObject(world, objB);
47 | this.TestRemoveObject(world, objB);
48 | }
49 |
50 | [TestMethod]
51 | public void TestAddAndRemoveEdge()
52 | {
53 | var world = new World();
54 | var obj = new Particle { Mass = 1, Position = new Vector2D(0, 5), Velocity = new Vector2D(0, 5) };
55 | obj.BindShape(new Circle(2));
56 | var edgeA = new Edge(-5, 0, 5, 0);
57 | var edgeB = new Edge(-5, 10, 5, 10);
58 |
59 | world.AddObject(obj);
60 | world.AddEdge(edgeA);
61 | world.AddEdge(edgeB);
62 |
63 | world.Update(1);
64 | Assert.AreEqual(new Vector2D(0, -5), obj.Velocity, "质体会被反弹");
65 |
66 | world.Update(1);
67 | world.Update(1);
68 | Assert.AreEqual(new Vector2D(0, 5), obj.Velocity, "质体会被再次反弹");
69 |
70 | world.RemoveEdge(edgeB);
71 | world.Update(1);
72 | world.Update(1);
73 | Assert.AreEqual(new Vector2D(0, 5), obj.Velocity, "移除边缘后质体不会被反弹");
74 | }
75 |
76 | private void TestAddCustomObject(World world, CombinedParticle obj)
77 | {
78 | world.AddObject(obj);
79 | world.Update(1);
80 |
81 | var vertexs = obj.Vertexs;
82 | for (var i = 0; i < vertexs.Count; i++)
83 | {
84 | Assert.AreEqual(new Vector2D(0, 9.8), vertexs[i].Acceleration, $"作用力应被正确地施加到了物体{i}上");
85 | }
86 | }
87 |
88 | private Handle TestPin(World world, CombinedParticle obj)
89 | {
90 | obj.Position = Vector2D.Zero;
91 | var handle = world.Pin(obj, Vector2D.Zero);
92 |
93 | try
94 | {
95 | world.Pin(obj, Vector2D.UnitX);
96 | }
97 | catch (Exception e)
98 | {
99 | Assert.IsInstanceOfType(e, typeof(InvalidOperationException), "不允许对同一物体Pin多次");
100 | }
101 |
102 | handle.Position = new Vector2D(100, 100);
103 | Assert.AreEqual(new Vector2D(100, 100), obj.Position, "Handle能对其生效");
104 |
105 | return handle;
106 | }
107 |
108 | private void TestUnPin(World world, CombinedParticle obj, Handle handle)
109 | {
110 | obj.Position = Vector2D.Zero;
111 | world.UnPin(obj);
112 | handle.Position = new Vector2D(100, 100);
113 | Assert.AreEqual(new Vector2D(0, 0), obj.Position, "Handle不能对其生效");
114 |
115 | try
116 | {
117 | world.UnPin(obj);
118 | }
119 | catch (Exception e)
120 | {
121 | Assert.IsInstanceOfType(e, typeof(InvalidOperationException), "不允许对未Pin的物体执行UnPin");
122 | }
123 | }
124 |
125 | private void TestRemoveCustomObject(World world, CombinedParticle obj)
126 | {
127 | var vertexs = obj.Vertexs;
128 | foreach (var vertex in vertexs)
129 | {
130 | vertex.Acceleration = Vector2D.Zero;
131 | }
132 |
133 | world.RemoveObject(obj);
134 | world.Update(1);
135 |
136 | for (var i = 0; i < vertexs.Count; i++)
137 | {
138 | Assert.AreEqual(new Vector2D(0, 0), vertexs[i].Acceleration, $"物体{i}上");
139 | }
140 | }
141 |
142 | private void TestAddObject(World world, PhysicsObject obj)
143 | {
144 | world.AddObject(obj);
145 | world.Update(1);
146 |
147 | Assert.AreEqual(new Vector2D(5, 9.8), obj.Acceleration, "作用力应被正确地施加到了物体上");
148 | }
149 |
150 | private void TestRemoveObject(World world, PhysicsObject obj)
151 | {
152 | obj.Acceleration = Vector2D.Zero;
153 | world.RemoveObject(obj);
154 | world.Update(1);
155 |
156 | Assert.AreEqual(new Vector2D(0, 0), obj.Acceleration, "作用力不再作用于物体上");
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/UnitTest/Factories/ContactFactoryTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Factories
2 | {
3 | using System;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Physics2D.Collision.Basic;
6 | using Physics2D.Collision.Shapes;
7 | using Physics2D.Common;
8 | using Physics2D.Common.Exceptions;
9 | using Physics2D.Core;
10 | using Physics2D.Factories;
11 | using Physics2D.Object;
12 |
13 | [TestClass]
14 | public class ContactFactoryTest
15 | {
16 | [TestMethod]
17 | public void TestCreateEdge()
18 | {
19 | var world = new World();
20 |
21 | var edge = world.CreateEdge(new Vector2D(0, 0), new Vector2D(0, 5));
22 | this.TestEdgeProperty(edge, new Vector2D(0, 0), new Vector2D(0, 5));
23 |
24 | edge = world.CreateEdge(0, 0, 0, 5);
25 | this.TestEdgeProperty(edge, new Vector2D(0, 0), new Vector2D(0, 5));
26 | }
27 |
28 | private void TestEdgeProperty(Edge edge, Vector2D pointA, Vector2D pointB)
29 | {
30 | Assert.AreEqual(pointA, edge.PointA);
31 | Assert.AreEqual(pointB, edge.PointB);
32 | Assert.AreEqual(0, edge.Id);
33 | Assert.IsNull(edge.Body);
34 | }
35 |
36 | [TestMethod]
37 | public void TestCreatePolygonEdge()
38 | {
39 | var world = new World();
40 |
41 | var poly = world.CreatePolygonEdge(new Vector2D(0, 0), new Vector2D(0, 5), new Vector2D(5, 0));
42 |
43 | var it = poly.GetEnumerator();
44 | it.MoveNext();
45 | this.TestEdgeProperty(it.Current, new Vector2D(0, 0), new Vector2D(0, 5));
46 |
47 | it.MoveNext();
48 | this.TestEdgeProperty(it.Current, new Vector2D(0, 5), new Vector2D(5, 0));
49 |
50 | it.MoveNext();
51 | this.TestEdgeProperty(it.Current, new Vector2D(5, 0), new Vector2D(0, 0));
52 |
53 | try
54 | {
55 | poly = world.CreatePolygonEdge(new Vector2D(0, 0), new Vector2D(0, 5));
56 | }
57 | catch (Exception e)
58 | {
59 | Assert.IsInstanceOfType(e, typeof(InvalidArgumentException), "点集不能构成多边形的时候抛出异常");
60 | }
61 | }
62 |
63 | [TestMethod]
64 | [ExpectedException(typeof(InvalidArgumentException))]
65 | public void TestCreatePolygonEdgeFail()
66 | {
67 | var world = new World();
68 |
69 | var poly = world.CreatePolygonEdge(new Vector2D(0, 0), new Vector2D(0, 5));
70 | }
71 |
72 | [TestMethod]
73 | public void TestCreateRod()
74 | {
75 | var world = new World();
76 |
77 | var rod = world.CreateRod(
78 | new Particle { Position = new Vector2D(0, 0) },
79 | new Particle { Position = new Vector2D(0, 5) });
80 |
81 | this.TestLinkProperty(rod, new Vector2D(0, 0), new Vector2D(0, 5));
82 | Assert.AreEqual(5, rod.Length);
83 |
84 | }
85 |
86 | private void TestLinkProperty(ParticleLink link, Vector2D pointA, Vector2D pointB)
87 | {
88 | Assert.AreEqual(pointA, link.ParticleA.Position);
89 | Assert.AreEqual(pointB, link.ParticleB.Position);
90 | }
91 |
92 | [TestMethod]
93 | public void TestCreateRope()
94 | {
95 | var world = new World();
96 |
97 | var rope = world.CreateRope(
98 | 10,
99 | 1,
100 | new Particle { Position = new Vector2D(0, 0) },
101 | new Particle { Position = new Vector2D(0, 5) });
102 |
103 | this.TestLinkProperty(rope, new Vector2D(0, 0), new Vector2D(0, 5));
104 | Assert.AreEqual(10, rope.MaxLength);
105 | Assert.AreEqual(1, rope.Restitution);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/UnitTest/Factories/ParticleFactoryTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Factories
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Core;
6 | using Physics2D.Factories;
7 | using Physics2D.Object;
8 |
9 | [TestClass]
10 | public class ParticleFactoryTest
11 | {
12 | [TestMethod]
13 | public void TestCreateParticle()
14 | {
15 | var world = new World();
16 |
17 | var p = world.CreateParticle(new Vector2D(0, 1), new Vector2D(1, 0), 1, 1);
18 | this.TestParticleProperty(p, new Vector2D(0, 1), new Vector2D(1, 0), 1, 1);
19 | }
20 |
21 | [TestMethod]
22 | public void TestCreateFixedParticle()
23 | {
24 | var world = new World();
25 |
26 | var p = world.CreateFixedParticle(new Vector2D(0, 1));
27 | this.TestParticleProperty(p, new Vector2D(0, 1), Vector2D.Zero, double.MaxValue, 1);
28 | }
29 |
30 | [TestMethod]
31 | public void TestCreateUnstoppableParticle()
32 | {
33 | var world = new World();
34 |
35 | var p = world.CreateUnstoppableParticle(new Vector2D(0, 1), new Vector2D(1, 0));
36 | this.TestParticleProperty(p, new Vector2D(0, 1), new Vector2D(1, 0), double.MaxValue, 1);
37 | }
38 |
39 | private void TestParticleProperty(
40 | Particle particle,
41 | Vector2D p,
42 | Vector2D v,
43 | double m,
44 | double restitution)
45 | {
46 | Assert.AreEqual(p, particle.Position, "位置");
47 | Assert.AreEqual(v, particle.Velocity, "速度");
48 | Assert.AreEqual(m, particle.Mass, "质量");
49 | Assert.AreEqual(restitution, particle.Restitution, "回弹系数");
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/UnitTest/Factories/ZoneFactoryTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Factories
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Core;
6 | using Physics2D.Factories;
7 | using Physics2D.Force;
8 |
9 | [TestClass]
10 | public class ZoneFactoryTest
11 | {
12 | [TestMethod]
13 | public void TestCreateGlobalZone()
14 | {
15 | var world = new World();
16 | var zone = world.CreateGlobalZone(new ParticleConstantForce(new Vector2D(0, 5)));
17 | Assert.IsNotNull(zone);
18 | }
19 |
20 | [TestMethod]
21 | public void TestCreateRectangleZone()
22 | {
23 | var world = new World();
24 | var zone = world.CreateRectangleZone(
25 | new ParticleConstantForce(new Vector2D(0, 5)),
26 | 1, 2, 3, 4);
27 | Assert.IsNotNull(zone);
28 | Assert.AreEqual(zone.X1, 1);
29 | Assert.AreEqual(zone.Y1, 2);
30 | Assert.AreEqual(zone.X2, 3);
31 | Assert.AreEqual(zone.Y2, 4);
32 | }
33 |
34 | [TestMethod]
35 | public void TestCreateGravity()
36 | {
37 | var world = new World();
38 | var zone = world.CreateGravity(9.8);
39 | Assert.IsNotNull(zone);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/UnitTest/Force/ParticleConstantForceTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Force
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Force;
6 | using Physics2D.Object;
7 |
8 | [TestClass]
9 | public class ParticleConstantForceTest
10 | {
11 | [TestMethod]
12 | public void TestConstructor()
13 | {
14 | var force = new ParticleConstantForce(new Vector2D(5, 0));
15 | Assert.IsNotNull(force);
16 | }
17 |
18 | [TestMethod]
19 | public void TestApplyTo()
20 | {
21 | var force = new ParticleConstantForce(new Vector2D(5, 0));
22 | var particle = new Particle
23 | {
24 | Mass = 1
25 | };
26 | force.Add(particle);
27 | force.Apply(1);
28 |
29 | particle.Update(1);
30 |
31 | Assert.AreEqual(new Vector2D(5, 0), particle.Acceleration);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/UnitTest/Force/ParticleDragTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Force
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Force;
6 | using Physics2D.Object;
7 |
8 | [TestClass]
9 | public class ParticleDragTest
10 | {
11 | [TestMethod]
12 | public void TestConstructor()
13 | {
14 | var force = new ParticleDrag(0.1, 0.1);
15 | Assert.IsNotNull(force);
16 | }
17 |
18 | [TestMethod]
19 | public void TestApplyTo()
20 | {
21 | var force = new ParticleDrag(0.1, 0.1);
22 | var particle = new Particle
23 | {
24 | Mass = 1
25 | };
26 | force.Add(particle);
27 | force.Apply(1);
28 |
29 | particle.Update(1);
30 | Assert.AreEqual(new Vector2D(0, 0), particle.Acceleration, "速度为0的物体不受阻力影响");
31 |
32 | particle.Velocity = new Vector2D(1, 0);
33 |
34 | force.Apply(1);
35 | particle.Update(1);
36 | Assert.AreEqual(new Vector2D(-0.2, 0), particle.Acceleration, "速度不为0的物体按照公式计算阻力大小");
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/UnitTest/Force/ParticleElasticTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Force
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Force;
6 | using Physics2D.Object;
7 |
8 | [TestClass]
9 | public class ParticleElasticTest
10 | {
11 | [TestMethod]
12 | public void TestConstructor()
13 | {
14 | var force = new ParticleElastic(1, 2);
15 | Assert.IsNotNull(force);
16 | }
17 |
18 | [TestMethod]
19 | public void TestApplyTo()
20 | {
21 | var force = new ParticleElastic(1, 2);
22 | var pA = new Particle
23 | {
24 | Position = new Vector2D(0, 0),
25 | Mass = 1
26 | };
27 | var pB = new Particle
28 | {
29 | Position = new Vector2D(1, 0),
30 | Mass = 1
31 | };
32 | force.Add(pA);
33 | force.LinkWith(pB);
34 |
35 | force.Apply(1);
36 |
37 | pA.Update(1);
38 | Assert.AreEqual(new Vector2D(-1, 0), pA.Acceleration);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/UnitTest/Force/ParticleGravityTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Force
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Force;
6 | using Physics2D.Object;
7 |
8 | [TestClass]
9 | public class ParticleGravityTest
10 | {
11 | [TestMethod]
12 | public void TestConstructor()
13 | {
14 | var force = new ParticleGravity(new Vector2D(0, 9.8));
15 | Assert.IsNotNull(force);
16 | }
17 |
18 | [TestMethod]
19 | public void TestApplyTo()
20 | {
21 | var force = new ParticleGravity(new Vector2D(0, 9.8));
22 | var particle = new Particle
23 | {
24 | Mass = 2
25 | };
26 | force.Add(particle);
27 | force.Apply(1);
28 | particle.Update(1);
29 | Assert.AreEqual(new Vector2D(0, 9.8), particle.Acceleration, "根据公式计算物体所应受到的重力");
30 |
31 | particle.InverseMass = 0;
32 | force.Apply(1);
33 | particle.Update(1);
34 | Assert.AreEqual(new Vector2D(0, 0), particle.Acceleration, "质量无限大的物体设定为不受重力影响");
35 |
36 | force.Remove(particle);
37 | force.Apply(1);
38 | particle.Update(1);
39 | Assert.AreEqual(new Vector2D(0, 0), particle.Acceleration, "被从作用力发生器中移除的物体不受重力影响");
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/UnitTest/Force/Zones/GlobalZoneTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Force.Zones
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Force;
6 | using Physics2D.Force.Zones;
7 | using Physics2D.Object;
8 |
9 | [TestClass]
10 | public class GlobalZoneTest
11 | {
12 | [TestMethod]
13 | public void TestTryApplyTo()
14 | {
15 | var p = new Particle
16 | {
17 | Mass = 1
18 | };
19 | var zone = new GlobalZone();
20 | zone.Add(new ParticleConstantForce(new Vector2D(5, 0)));
21 |
22 | zone.TryApplyTo(p, 1 / 60.0);
23 | p.Update(1 / 60.0);
24 |
25 | Assert.AreEqual(new Vector2D(5, 0), p.Acceleration);
26 | }
27 |
28 | [TestMethod]
29 | public void TestRemove()
30 | {
31 | var p = new Particle
32 | {
33 | Mass = 1
34 | };
35 | var zone = new GlobalZone();
36 | var force = new ParticleConstantForce(new Vector2D(5, 0));
37 | zone.Add(force);
38 | zone.Remove(force);
39 | zone.TryApplyTo(p, 1 / 60.0);
40 | p.Update(1 / 60.0);
41 |
42 | Assert.AreEqual(new Vector2D(0, 0), p.Acceleration);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/UnitTest/Force/Zones/RectangleZoneTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Force.Zones
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Force;
6 | using Physics2D.Force.Zones;
7 | using Physics2D.Object;
8 |
9 | [TestClass]
10 | public class RectangleZoneTest
11 | {
12 | [TestMethod]
13 | public void TestTryApplyTo()
14 | {
15 | var pIn = new Particle
16 | {
17 | Position = new Vector2D(2, 3),
18 | Mass = 1
19 | };
20 | var pOut = new Particle
21 | {
22 | Position = new Vector2D(6, 6),
23 | Mass = 1
24 | };
25 | var zone = new RectangleZone(0, 0, 5, 5);
26 | zone.Add(new ParticleConstantForce(new Vector2D(5, 0)));
27 |
28 | zone.TryApplyTo(pIn, 1 / 60.0);
29 | zone.TryApplyTo(pOut, 1 / 60.0);
30 | pIn.Update(1 / 60.0);
31 | pOut.Update(1 / 60.0);
32 |
33 | Assert.AreEqual(new Vector2D(5, 0), pIn.Acceleration);
34 | Assert.AreEqual(new Vector2D(0, 0), pOut.Acceleration);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/UnitTest/Object/ParticleTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Object
2 | {
3 | using System;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Physics2D.Collision.Shapes;
6 | using Physics2D.Common;
7 | using Physics2D.Object;
8 |
9 | [TestClass]
10 | public class ParticleTest
11 | {
12 | [TestMethod]
13 | public void TestConstructor()
14 | {
15 | Particle obj = new Particle
16 | {
17 | Mass = 1,
18 | Position = new Vector2D(0, 50)
19 | };
20 |
21 | Assert.IsNotNull(obj);
22 | }
23 |
24 | [TestMethod]
25 | public void TestSetMass()
26 | {
27 | Particle obj = new Particle
28 | {
29 | Position = new Vector2D(0, 50)
30 | };
31 |
32 | // Normal
33 | obj.Mass = 2;
34 | Assert.AreEqual(2, obj.Mass);
35 | Assert.AreEqual(0.5, obj.InverseMass);
36 |
37 | obj.InverseMass = 2;
38 | Assert.AreEqual(0.5, obj.Mass);
39 | Assert.AreEqual(2, obj.InverseMass);
40 |
41 | // Zero
42 | try
43 | {
44 | obj.Mass = 0;
45 | }
46 | catch (ArgumentOutOfRangeException) { }
47 | catch (Exception)
48 | {
49 | Assert.Fail();
50 | }
51 |
52 | obj.InverseMass = 0;
53 | Assert.AreEqual(0, obj.InverseMass);
54 | Assert.AreEqual(double.MaxValue, obj.Mass);
55 | }
56 |
57 | [TestMethod]
58 | public void TestUpdate()
59 | {
60 | Particle obj = new Particle
61 | {
62 | Mass = 1,
63 | Position = new Vector2D(0, 0)
64 | };
65 | obj.AddForce(new Vector2D(1, 0));
66 |
67 | obj.Update(1);
68 | Assert.AreEqual(new Vector2D(0, 0), obj.Position);
69 | Assert.AreEqual(new Vector2D(1, 0), obj.Velocity);
70 | Assert.AreEqual(new Vector2D(1, 0), obj.Acceleration);
71 |
72 | obj.Update(1);
73 | obj.Update(1);
74 | Assert.AreEqual(new Vector2D(2, 0), obj.Position);
75 | Assert.AreEqual(new Vector2D(1, 0), obj.Velocity);
76 | Assert.AreEqual(new Vector2D(0, 0), obj.Acceleration);
77 | }
78 |
79 | [TestMethod]
80 | public void TestBindShape()
81 | {
82 | Particle obj = new Particle
83 | {
84 | Position = new Vector2D(0, 50)
85 | };
86 | obj.BindShape(new Circle(10));
87 |
88 | Assert.IsNotNull(obj.Shape as Circle);
89 | Assert.IsTrue(obj.Shape.Body == obj);
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/UnitTest/Object/Tools/HandleTest.cs:
--------------------------------------------------------------------------------
1 | namespace UnitTest.Object.Tools
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Physics2D.Common;
5 | using Physics2D.Object.Tools;
6 |
7 | [TestClass]
8 | public class HandleTest
9 | {
10 | [TestMethod]
11 | public void TestConstructor()
12 | {
13 | Handle handle = new Handle(new Vector2D(1, 0));
14 |
15 | Assert.IsNotNull(handle);
16 | Assert.AreEqual(new Vector2D(1, 0), handle.Position);
17 | }
18 |
19 | [TestMethod]
20 | public void TestPropertyChangedEvent()
21 | {
22 | Handle handle = new Handle(new Vector2D(0, 0));
23 |
24 | Vector2D position = Vector2D.Zero;
25 | handle.PropertyChanged += (sender, e) =>
26 | {
27 | position = (sender as Handle).Position;
28 | };
29 |
30 | handle.Position = new Vector2D(1, 0);
31 | Assert.AreEqual(new Vector2D(1, 0), position);
32 |
33 | handle.Position = new Vector2D(1, 0);
34 | Assert.AreEqual(new Vector2D(1, 0), position);
35 | }
36 |
37 | [TestMethod]
38 | public void TestRelease()
39 | {
40 | Handle handle = new Handle(new Vector2D(0, 0));
41 |
42 | Vector2D position = Vector2D.Zero;
43 | handle.PropertyChanged += (sender, e) =>
44 | {
45 | position = (sender as Handle).Position;
46 | };
47 | handle.Release();
48 |
49 | handle.Position = new Vector2D(1, 0);
50 | Assert.AreEqual(Vector2D.Zero, position);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/UnitTest/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("UnitTest")]
5 | [assembly: AssemblyDescription("")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("")]
8 | [assembly: AssemblyProduct("UnitTest")]
9 | [assembly: AssemblyCopyright("Copyright © 2014")]
10 | [assembly: AssemblyTrademark("")]
11 | [assembly: AssemblyCulture("")]
12 | [assembly: ComVisible(false)]
13 | [assembly: Guid("58d989d3-e99c-47b9-865d-e46238d0e1bb")]
14 | [assembly: AssemblyVersion("1.0.0.0")]
15 | [assembly: AssemblyFileVersion("1.0.0.0")]
--------------------------------------------------------------------------------
/UnitTest/UnitTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {EAA4C780-0963-4479-9D11-E162127C0014}
7 | Library
8 | Properties
9 | UnitTest
10 | UnitTest
11 | v4.6.1
12 | 512
13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 10.0
15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
17 | False
18 | UnitTest
19 |
20 |
21 |
22 | true
23 | full
24 | false
25 | bin\Debug\
26 | DEBUG;TRACE
27 | prompt
28 | 4
29 |
30 |
31 | pdbonly
32 | true
33 | bin\Release\
34 | TRACE
35 | prompt
36 | 4
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | False
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | {e102f925-90fe-41f2-856e-3fab4bb2ad58}
88 | Physics2D
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | False
103 |
104 |
105 | False
106 |
107 |
108 | False
109 |
110 |
111 | False
112 |
113 |
114 |
115 |
116 |
117 |
118 |
125 |
--------------------------------------------------------------------------------
/UnitTest/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/WPFDemo/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/WPFDemo/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/WPFDemo/App.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo
2 | {
3 | using System.Windows;
4 |
5 | ///
6 | /// App.xaml 的交互逻辑
7 | ///
8 | public partial class App : Application
9 | {
10 | }
11 | }
--------------------------------------------------------------------------------
/WPFDemo/CircleDemo/Circle.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/WPFDemo/CircleDemo/Circle.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.CircleDemo
2 | {
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | ///
8 | /// Circle.xaml 的交互逻辑
9 | ///
10 | public partial class Circle : Window
11 | {
12 | private readonly CircleDemo circleDemo;
13 |
14 | public Circle()
15 | {
16 | this.InitializeComponent();
17 | this.circleDemo = new CircleDemo(this.ImageSurface);
18 |
19 | this.ImageSurface.Source = this.circleDemo.Bitmap;
20 | CompositionTarget.Rendering += this.circleDemo.Update;
21 | }
22 |
23 | private void ImageSurface_MouseDown(object sender, MouseButtonEventArgs e)
24 | {
25 | this.circleDemo.Fire();
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/WPFDemo/CircleDemo/CircleDemo.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.CircleDemo
2 | {
3 | using System.Collections.Generic;
4 | using System.Windows.Controls;
5 | using System.Windows.Media;
6 | using System.Windows.Media.Imaging;
7 | using Physics2D;
8 | using Physics2D.Common;
9 | using Physics2D.Factories;
10 | using Physics2D.Force;
11 | using Physics2D.Object;
12 | using WPFDemo.Graphic;
13 |
14 | public class CircleDemo : PhysicsGraphic, IDrawable
15 | {
16 | ///
17 | /// 中心点
18 | ///
19 | private readonly Particle centerObj;
20 |
21 | ///
22 | /// 物体列表
23 | ///
24 | private readonly List objList = new List();
25 |
26 | public CircleDemo(Image image)
27 | : base(image)
28 | {
29 | // 初始化中心点
30 | this.centerObj = this.PhysicsWorld.CreateFixedParticle(
31 | new Vector2D(
32 | 250.ToSimUnits(),
33 | 200.ToSimUnits()));
34 |
35 | // 注册绘制对象
36 | this.DrawQueue.Add(this);
37 | }
38 |
39 | protected override void UpdatePhysics(double duration)
40 | {
41 | foreach (var item in this.objList)
42 | {
43 | var v = this.centerObj.Position - item.Position;
44 | item.AddForce(v.Normalize() * 30);
45 | }
46 |
47 | this.PhysicsWorld.Update(duration);
48 | }
49 |
50 | public void Fire()
51 | {
52 | if (!this.Start)
53 | {
54 | // 全局增加一个小阻尼
55 | this.PhysicsWorld.CreateGlobalZone(new ParticleDrag(0.01, 0.02));
56 | this.Start = true;
57 | }
58 |
59 | var item = this.PhysicsWorld.CreateParticle(
60 | new Vector2D(200.ToSimUnits(), 200.ToSimUnits()),
61 | new Vector2D(0, 5),
62 | 1);
63 | this.objList.Add(item);
64 | }
65 |
66 | public void Draw(WriteableBitmap bitmap)
67 | {
68 | bitmap.FillEllipseCentered(
69 | this.centerObj.Position.X.ToDisplayUnits(),
70 | this.centerObj.Position.Y.ToDisplayUnits(), 6, 6, Colors.Red);
71 | for (int i = this.objList.Count - 1; i >= 0; i--)
72 | {
73 | int x = this.objList[i].Position.X.ToDisplayUnits();
74 | int y = this.objList[i].Position.Y.ToDisplayUnits();
75 |
76 | bitmap.FillEllipseCentered(x, y, 4, 4, Colors.Black);
77 | }
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/WPFDemo/ContactDemo/Ball.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.ContactDemo
2 | {
3 | using System.Windows.Media;
4 | using System.Windows.Media.Imaging;
5 | using Physics2D;
6 | using Physics2D.Object;
7 | using WPFDemo.Graphic;
8 |
9 | internal class Ball : IDrawable
10 | {
11 | public Particle FixedParticle;
12 | public Particle Particle;
13 | public int R;
14 |
15 | public void Draw(WriteableBitmap bitmap)
16 | {
17 | bitmap.DrawLineAa(
18 | this.FixedParticle.Position.X.ToDisplayUnits(),
19 | this.FixedParticle.Position.Y.ToDisplayUnits(),
20 | this.Particle.Position.X.ToDisplayUnits(),
21 | this.Particle.Position.Y.ToDisplayUnits(),
22 | Colors.DarkGray);
23 | bitmap.FillEllipseCentered(
24 | this.Particle.Position.X.ToDisplayUnits(),
25 | this.Particle.Position.Y.ToDisplayUnits(), this.R, this.R, Colors.DarkRed);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WPFDemo/ContactDemo/Contact.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/WPFDemo/ContactDemo/Contact.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.ContactDemo
2 | {
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | ///
8 | /// Contact.xaml 的交互逻辑
9 | ///
10 | public partial class Contact : Window
11 | {
12 | private readonly ContactDemo contactDemo;
13 |
14 | public Contact()
15 | {
16 | this.InitializeComponent();
17 |
18 | this.contactDemo = new ContactDemo(this.ImageSurface);
19 | this.ImageSurface.Source = this.contactDemo.Bitmap;
20 | CompositionTarget.Rendering += this.contactDemo.Update;
21 | }
22 |
23 | private void ImageSurface_MouseDown(object sender, MouseButtonEventArgs e)
24 | {
25 | this.contactDemo.Fire();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WPFDemo/ContactDemo/ContactDemo.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.ContactDemo
2 | {
3 | using System.Collections.Generic;
4 | using System.Windows.Controls;
5 | using Physics2D;
6 | using Physics2D.Collision.Shapes;
7 | using Physics2D.Common;
8 | using Physics2D.Factories;
9 | using Physics2D.Force;
10 | using WPFDemo.Graphic;
11 |
12 | internal class ContactDemo : PhysicsGraphic
13 | {
14 | ///
15 | /// 钢珠列表
16 | ///
17 | private readonly List ballList = new List();
18 |
19 | public ContactDemo(Image image)
20 | : base(image)
21 | {
22 | Settings.ContactIteration = 1;
23 |
24 | const int num = 5;
25 |
26 | for (int i = 0; i < num; i++)
27 | {
28 | var fB = this.PhysicsWorld.CreateFixedParticle(new Vector2D(160 + 40 * i, 0).ToSimUnits());
29 | var pB = this.PhysicsWorld.CreateParticle(new Vector2D(160 + 40 * i, 200).ToSimUnits(), new Vector2D(0, 0), 2);
30 |
31 | var ball = new Ball
32 | {
33 | FixedParticle = fB,
34 | Particle = pB,
35 | R = 20
36 | };
37 |
38 | // 为质体绑定形状
39 | ball.Particle.BindShape(new Circle(ball.R.ToSimUnits()));
40 |
41 | this.PhysicsWorld.CreateRope(200.ToSimUnits(), 0, fB, pB);
42 | this.DrawQueue.Add(ball);
43 | this.ballList.Add(ball);
44 | }
45 |
46 | // 增加重力和空气阻力
47 | this.PhysicsWorld.CreateGlobalZone(new ParticleGravity(new Vector2D(0, 40)));
48 | this.PhysicsWorld.CreateParticle(Vector2D.Zero, new Vector2D(1, 0), 1);
49 | this.Slot = 1 / 120.0;
50 |
51 | this.Start = true;
52 | }
53 |
54 | protected override void UpdatePhysics(double duration)
55 | {
56 | this.PhysicsWorld.Update(duration);
57 | }
58 |
59 | public void Fire()
60 | {
61 | this.ballList[0].Particle.Velocity = new Vector2D(-10, 0);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/WPFDemo/DrawingCanvas.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo
2 | {
3 | using System.Collections.Generic;
4 | using System.Windows.Controls;
5 | using System.Windows.Media;
6 |
7 | public class DrawingCanvas : Panel
8 | {
9 | private readonly List visuals = new List();
10 |
11 | protected override Visual GetVisualChild(int index)
12 | {
13 | return this.visuals[index];
14 | }
15 |
16 | protected override int VisualChildrenCount => this.visuals.Count;
17 |
18 | public void AddVisual(Visual visual)
19 | {
20 | this.visuals.Add(visual);
21 |
22 | this.AddVisualChild(visual);
23 | this.AddLogicalChild(visual);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/WPFDemo/ElasticDemo/DestructibleElastic.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.ElasticDemo
2 | {
3 | using System.Collections.Generic;
4 | using Physics2D.Force;
5 | using Physics2D.Object;
6 |
7 | internal sealed class DestructibleElastic : ParticleForceGenerator
8 | {
9 | ///
10 | /// 所有链接
11 | ///
12 | private readonly List linked = new List();
13 |
14 | ///
15 | /// 弹性常量
16 | ///
17 | private readonly double k;
18 |
19 | ///
20 | /// 静息长度
21 | ///
22 | private readonly double length;
23 |
24 | ///
25 | /// 延展系数
26 | /// 当链接的长度与静息长度的比值超过该数值的时发生断裂
27 | ///
28 | private readonly double lengthFactor;
29 |
30 | public DestructibleElastic(double k, double length, double lengthFactor = 4)
31 | {
32 | this.k = k;
33 | this.length = length;
34 | this.lengthFactor = lengthFactor;
35 | }
36 |
37 | public void Joint(Particle item)
38 | {
39 | this.linked.Add(new LinkedItem
40 | {
41 | Particle = item,
42 | IsValid = true
43 | });
44 | }
45 |
46 | public override void ApplyTo(Particle particle, double duration)
47 | {
48 | for (int i = this.linked.Count - 1; i >= 0; i--)
49 | {
50 | if (this.linked[i].IsValid)
51 | {
52 | var d = particle.Position - this.linked[i].Particle.Position;
53 | if (d.Length() / this.length > this.lengthFactor)
54 | {
55 | this.linked[i].IsValid = false;
56 | continue;
57 | }
58 |
59 | double force = (this.length - d.Length()) * this.k;
60 | particle.AddForce(d.Normalize() * force);
61 | }
62 | }
63 | }
64 |
65 | private class LinkedItem
66 | {
67 | public Particle Particle;
68 | public bool IsValid;
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/WPFDemo/ElasticDemo/Elastic.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/WPFDemo/ElasticDemo/Elastic.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.ElasticDemo
2 | {
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | ///
8 | /// ElasticDemo.xaml 的交互逻辑
9 | ///
10 | public partial class Elastic : Window
11 | {
12 | private ElasticDemo elasticDemo;
13 |
14 | public Elastic()
15 | {
16 | this.InitializeComponent();
17 |
18 | this.elasticDemo = new ElasticDemo(this.ImageSurface);
19 |
20 | this.ImageSurface.Source = this.elasticDemo.Bitmap;
21 | CompositionTarget.Rendering += this.elasticDemo.Update;
22 | }
23 |
24 | private void ImageSurface_MouseDown(object sender, MouseButtonEventArgs e)
25 | {
26 | this.elasticDemo.Fire();
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/WPFDemo/ElasticDemo/ElasticDemo.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.ElasticDemo
2 | {
3 | using System.Windows.Controls;
4 | using Physics2D;
5 | using Physics2D.Common;
6 | using Physics2D.Factories;
7 | using Physics2D.Force;
8 | using WPFDemo.Graphic;
9 |
10 | public class ElasticDemo : PhysicsGraphic
11 | {
12 | ///
13 | /// 网格宽
14 | ///
15 | private const int Width = 20;
16 |
17 | ///
18 | /// 网格高
19 | ///
20 | private const int Height = 10;
21 |
22 | ///
23 | /// 延展系数
24 | ///
25 | private const double Factor = 4;
26 |
27 | ///
28 | /// 网格尺寸
29 | ///
30 | private static readonly double GridSize = 20.ToSimUnits();
31 |
32 | ///
33 | /// 弹性网
34 | ///
35 | private ElasticatedNet elasticatedNet;
36 |
37 | public ElasticDemo(Image image)
38 | : base(image)
39 | {
40 | // 设置全局的阻力用以增加稳定性
41 | this.PhysicsWorld.CreateGlobalZone(new ParticleDrag(0.8f, 0.6f));
42 | }
43 |
44 | ///
45 | /// 更新物理世界
46 | ///
47 | ///
48 | protected override void UpdatePhysics(double duration)
49 | {
50 | this.PhysicsWorld.Update(duration);
51 | }
52 |
53 | ///
54 | /// 响应鼠标的操作
55 | ///
56 | public void Fire()
57 | {
58 | if (this.Start)
59 | {
60 | this.PhysicsWorld.RemoveObject(this.elasticatedNet);
61 | this.DrawQueue.Remove(this.elasticatedNet);
62 | }
63 |
64 | // 创建弹性网并加入到物理世界和绘制队列
65 | this.elasticatedNet = new ElasticatedNet(
66 | new Vector2D(40, 120).ToSimUnits(),
67 | Width, Height, GridSize, Factor);
68 | this.DrawQueue.Add(this.elasticatedNet);
69 | this.PhysicsWorld.AddObject(this.elasticatedNet);
70 |
71 | // 拉扯弹性网
72 | var net = this.elasticatedNet.Net;
73 | for (int i = 0; i < Width; i++)
74 | {
75 | net[i, Height - 1].Velocity = new Vector2D(0, 0.1) * 4 * (i >= Width / 2 ? -1 : 1);
76 | net[i, Height - 1].InverseMass = 0;
77 | }
78 |
79 | this.Start = true;
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/WPFDemo/ElasticDemo/ElasticatedNet.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.ElasticDemo
2 | {
3 | using System.Windows.Media;
4 | using System.Windows.Media.Imaging;
5 | using Physics2D;
6 | using Physics2D.Common;
7 | using Physics2D.Core;
8 | using Physics2D.Factories;
9 | using Physics2D.Object;
10 | using WPFDemo.Graphic;
11 |
12 | internal sealed class ElasticatedNet : CustomObject, IDrawable
13 | {
14 | private readonly Particle[,] net;
15 | private readonly int width;
16 | private readonly int height;
17 | private readonly Vector2D startPosition;
18 | private readonly double gridSize;
19 | private readonly double factor;
20 |
21 | ///
22 | /// 弹簧网网格
23 | ///
24 | public Particle[,] Net
25 | {
26 | get { return this.net; }
27 | }
28 |
29 | public ElasticatedNet(Vector2D startPosition, int width, int height, double gridSize, double factor)
30 | {
31 | this.width = width;
32 | this.height = height;
33 | this.startPosition = startPosition;
34 | this.gridSize = gridSize;
35 | this.factor = factor;
36 | this.net = new Particle[width, height];
37 | }
38 |
39 | ///
40 | /// 实现图形接口的绘图逻辑
41 | ///
42 | ///
43 | public void Draw(WriteableBitmap bitmap)
44 | {
45 | bitmap.Clear();
46 | for (int i = 0; i < this.width; i++)
47 | {
48 | for (int j = 0; j < this.height; j++)
49 | {
50 | int x = this.net[i, j].Position.X.ToDisplayUnits();
51 | int y = this.net[i, j].Position.Y.ToDisplayUnits();
52 |
53 | // 绘制弹性连线
54 | double dLeft;
55 | double dDown;
56 | if (i > 0 && (dLeft = (this.net[i, j].Position - this.net[i - 1, j].Position).Length()) / this.gridSize < this.factor)
57 | {
58 | byte colorRow = dLeft > this.gridSize ? (byte)((int)(255 - (dLeft - this.gridSize) / ((this.factor - 1) * this.gridSize) * 200)) : (byte)255;
59 | bitmap.DrawLineAa(
60 | x, y,
61 | this.net[i - 1, j].Position.X.ToDisplayUnits(), this.net[i - 1, j].Position.Y.ToDisplayUnits(),
62 | Color.FromArgb(colorRow, 0, 0, 0));
63 | }
64 |
65 | if (j > 0 && (dDown = (this.net[i, j].Position - this.net[i, j - 1].Position).Length()) / this.gridSize < this.factor)
66 | {
67 | byte colorCol = dDown > this.gridSize ? (byte)((int)(255 - (dDown - this.gridSize) / ((this.factor - 1) * this.gridSize) * 200)) : (byte)255;
68 | bitmap.DrawLineAa(
69 | x, y,
70 | this.net[i, j - 1].Position.X.ToDisplayUnits(), this.net[i, j - 1].Position.Y.ToDisplayUnits(),
71 | Color.FromArgb(colorCol, 0, 0, 0));
72 | }
73 |
74 | // 绘制点
75 | bitmap.FillEllipseCentered(x, y, 2, 2, Colors.Black);
76 | }
77 | }
78 | }
79 |
80 | public override void OnInit(World world)
81 | {
82 | // 根据参数创建弹性网
83 | for (int i = 0; i < this.width; i++)
84 | {
85 | for (int j = 0; j < this.height; j++)
86 | {
87 | this.net[i, j] = world.CreateParticle(
88 | new Vector2D(
89 | this.startPosition.X + i * this.gridSize,
90 | this.startPosition.Y + j * this.gridSize),
91 | Vector2D.Zero,
92 | 1);
93 | }
94 | }
95 |
96 | // 设置弹性网
97 | for (int i = 0; i < this.width; i++)
98 | {
99 | for (int j = 0; j < this.height; j++)
100 | {
101 | var spring = new DestructibleElastic(12, this.gridSize, this.factor);
102 | if (i > 0)
103 | {
104 | spring.Joint(this.net[i - 1, j]);
105 | }
106 |
107 | if (i < this.width - 1)
108 | {
109 | spring.Joint(this.net[i + 1, j]);
110 | }
111 |
112 | if (j > 0)
113 | {
114 | spring.Joint(this.net[i, j - 1]);
115 | }
116 |
117 | if (j < this.height - 1)
118 | {
119 | spring.Joint(this.net[i, j + 1]);
120 | }
121 |
122 | spring.Add(this.net[i, j]);
123 | world.ForceGenerators.Add(spring);
124 | }
125 | }
126 | }
127 |
128 | public override void OnRemove(World world)
129 | {
130 | foreach (var item in this.net)
131 | {
132 | world.RemoveObject(item);
133 | }
134 | }
135 |
136 | public override void Update(double duration)
137 | {
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/WPFDemo/FireworksDemo/Fireworks.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/WPFDemo/FireworksDemo/Fireworks.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.FireworksDemo
2 | {
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 | using Physics2D;
7 |
8 | ///
9 | /// Fireworks.xaml 的交互逻辑
10 | ///
11 | public partial class Fireworks : Window
12 | {
13 | private FireworksDemo fireworksDemo;
14 |
15 | public Fireworks()
16 | {
17 | this.InitializeComponent();
18 | this.fireworksDemo = new FireworksDemo(this.ImageSurface);
19 |
20 | this.ImageSurface.Source = this.fireworksDemo.Bitmap;
21 | CompositionTarget.Rendering += this.fireworksDemo.Update;
22 | }
23 |
24 | private void ImageSurface_MouseDown(object sender, MouseButtonEventArgs e)
25 | {
26 | this.fireworksDemo.Fire(e.GetPosition(this.ImageSurface).X.ToSimUnits(), e.GetPosition(this.ImageSurface).Y.ToSimUnits());
27 | }
28 |
29 | private void Checked(object sender, RoutedEventArgs e)
30 | {
31 | if (this.Drag.IsChecked == true)
32 | {
33 | this.fireworksDemo.Type = FireworksDemo.PhysicsType.Water;
34 | }
35 | else if (this.Wind.IsChecked == true)
36 | {
37 | this.fireworksDemo.Type = FireworksDemo.PhysicsType.Wind;
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/WPFDemo/FireworksDemo/FireworksDemo.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.FireworksDemo
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Windows.Controls;
6 | using System.Windows.Media;
7 | using System.Windows.Media.Imaging;
8 | using Physics2D;
9 | using Physics2D.Collision.Shapes;
10 | using Physics2D.Common;
11 | using Physics2D.Factories;
12 | using Physics2D.Force;
13 | using Physics2D.Force.Zones;
14 | using Physics2D.Object;
15 | using WPFDemo.Graphic;
16 |
17 | public class FireworksDemo : PhysicsGraphic, IDrawable
18 | {
19 | ///
20 | /// 选项状态枚举
21 | ///
22 | public enum PhysicsType
23 | {
24 | None,
25 | Water,
26 | Wind
27 | }
28 |
29 | ///
30 | /// 当前选项
31 | ///
32 | private PhysicsType type = PhysicsType.None;
33 |
34 | ///
35 | /// 当前选项
36 | ///
37 | public PhysicsType Type
38 | {
39 | get
40 | {
41 | return this.type;
42 | }
43 |
44 | set
45 | {
46 | this.type = value;
47 |
48 | // 设置力场
49 | if (this.type == PhysicsType.Water)
50 | {
51 | this.PhysicsWorld.Zones.Add(this.dragZone);
52 | this.PhysicsWorld.Zones.Remove(this.windZone);
53 | }
54 | else if (this.type == PhysicsType.Wind)
55 | {
56 | this.PhysicsWorld.Zones.Add(this.windZone);
57 | this.PhysicsWorld.Zones.Remove(this.dragZone);
58 | }
59 | }
60 | }
61 |
62 | ///
63 | /// 阻力
64 | ///
65 | private readonly ParticleDrag drag = new ParticleDrag(2, 1);
66 |
67 | ///
68 | /// 风
69 | ///
70 | private readonly ParticleConstantForce wind = new ParticleConstantForce(new Vector2D(20, -5));
71 |
72 | ///
73 | /// 阻力区
74 | ///
75 | private readonly Zone dragZone;
76 |
77 | ///
78 | /// 有风区
79 | ///
80 | private readonly Zone windZone;
81 |
82 | ///
83 | /// 横板
84 | ///
85 | private Edge edge;
86 |
87 | ///
88 | /// 物体列表
89 | ///
90 | private readonly List objList = new List();
91 |
92 | ///
93 | /// 粒子形状Id
94 | /// 所有粒子都使用相同的Id,从而使粒子之间可以交叠
95 | ///
96 | private readonly int shapeId = Shape.NewId();
97 |
98 | ///
99 | /// 粒子半径
100 | ///
101 | private const int BallSize = 4;
102 | private const int WorldHeight = 400;
103 | private const int WorldWidth = 500;
104 |
105 | public FireworksDemo(Image image)
106 | : base(image)
107 | {
108 | this.DrawQueue.Add(this);
109 |
110 | this.dragZone = new RectangleZone(
111 | 0.ToSimUnits(),
112 | (WorldHeight * 2 / 3.0).ToSimUnits(),
113 | 500.ToSimUnits(),
114 | 400.ToSimUnits());
115 | this.dragZone.Add(this.drag);
116 |
117 | this.windZone = new RectangleZone(
118 | 0.ToSimUnits(),
119 | (WorldHeight * 1 / 3.0).ToSimUnits(),
120 | 500.ToSimUnits(),
121 | (WorldHeight * 2 / 3.0).ToSimUnits());
122 | this.windZone.Add(this.wind);
123 | }
124 |
125 | protected override void UpdatePhysics(double duration)
126 | {
127 | this.PhysicsWorld.Update(duration);
128 | }
129 |
130 | public void Draw(WriteableBitmap bitmap)
131 | {
132 | if (this.type == PhysicsType.Water)
133 | {
134 | bitmap.FillRectangle(0, WorldHeight * 2 / 3, WorldWidth, WorldHeight, Colors.SkyBlue);
135 | }
136 | else if (this.type == PhysicsType.Wind)
137 | {
138 | bitmap.FillRectangle(0, WorldHeight * 1 / 3, WorldWidth, WorldHeight * 2 / 3, Colors.LightGray);
139 | }
140 |
141 | bitmap.DrawLineAa(
142 | this.edge.PointA.X.ToDisplayUnits(),
143 | this.edge.PointA.Y.ToDisplayUnits(),
144 | this.edge.PointB.X.ToDisplayUnits(),
145 | this.edge.PointB.Y.ToDisplayUnits(), Colors.Black);
146 |
147 | for (int i = this.objList.Count - 1; i >= 0; i--)
148 | {
149 | int x = this.objList[i].Position.X.ToDisplayUnits();
150 | int y = this.objList[i].Position.Y.ToDisplayUnits();
151 |
152 | if (y > WorldHeight || x > WorldWidth || x < 0 || y < 0)
153 | {
154 | this.PhysicsWorld.RemoveObject(this.objList[i]);
155 | this.objList.Remove(this.objList[i]);
156 | }
157 | else
158 | {
159 | if (this.type == PhysicsType.Water)
160 | {
161 | bitmap.FillEllipseCentered(x, y, BallSize, BallSize, y > WorldHeight * 2 / 3 ? Colors.DarkBlue : Colors.Black);
162 | }
163 | else
164 | {
165 | bitmap.FillEllipseCentered(x, y, BallSize, BallSize, Colors.Black);
166 | }
167 | }
168 | }
169 | }
170 |
171 | public void Fire(double x, double y)
172 | {
173 | if (!this.Start)
174 | {
175 | this.Start = true;
176 |
177 | // 增加重力
178 | this.PhysicsWorld.CreateGravity(9.8);
179 |
180 | // 添加边缘
181 | this.edge = this.PhysicsWorld.CreateEdge(
182 | 100.ToSimUnits(),
183 | 350.ToSimUnits(),
184 | 400.ToSimUnits(),
185 | 200.ToSimUnits());
186 | this.Slot = 1 / 60.0;
187 | }
188 |
189 | var rnd = new Random();
190 |
191 | for (int i = 0; i < 10; i++)
192 | {
193 | var paritcle = this.PhysicsWorld.CreateParticle(
194 | new Vector2D(x, y),
195 | new Vector2D(rnd.NextDouble() * 6 - 3, rnd.NextDouble() * 6 - 3),
196 | 1f, 0.1);
197 | paritcle.BindShape(new Circle(BallSize.ToSimUnits(), this.shapeId));
198 | this.objList.Add(paritcle);
199 | }
200 | }
201 | }
202 | }
--------------------------------------------------------------------------------
/WPFDemo/FluidDemo/Fluid.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/WPFDemo/FluidDemo/Fluid.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.FluidDemo
2 | {
3 | using System.Windows.Input;
4 | using System.Windows.Media;
5 |
6 | ///
7 | /// Fluid.xaml 的交互逻辑
8 | ///
9 | public partial class Fluid
10 | {
11 | private readonly FluidDemo fluidDemo;
12 |
13 | public Fluid()
14 | {
15 | this.InitializeComponent();
16 | this.fluidDemo = new FluidDemo(this.ImageSurface);
17 |
18 | this.ImageSurface.Source = this.fluidDemo.Bitmap;
19 | CompositionTarget.Rendering += this.fluidDemo.Update;
20 | }
21 |
22 | private void imageSurface_MouseDown(object sender, MouseButtonEventArgs e)
23 | {
24 | this.fluidDemo.Fire();
25 | }
26 |
27 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
28 | {
29 | CompositionTarget.Rendering -= this.fluidDemo.Update;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/WPFDemo/FluidDemo/FluidDemo.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.FluidDemo
2 | {
3 | using System;
4 | using System.Windows.Controls;
5 | using Physics2D;
6 | using Physics2D.Collision.Shapes;
7 | using Physics2D.Common;
8 | using Physics2D.Factories;
9 | using Physics2D.Force;
10 | using WPFDemo.Graphic;
11 |
12 | public class FluidDemo : PhysicsGraphic
13 | {
14 | private readonly Water water;
15 |
16 | private readonly Vector2D center = new Vector2D(ConvertUnits.ToSimUnits(250f), ConvertUnits.ToSimUnits(200f));
17 |
18 | public FluidDemo(Image image)
19 | : base(image)
20 | {
21 | // 创建液体容器
22 | this.water = new Water((int)image.Width, (int)image.Height);
23 |
24 | // 添加到绘制队列
25 | this.DrawQueue.Add(this.water);
26 | }
27 |
28 | protected override void UpdatePhysics(double duration)
29 | {
30 | if (!this.flag)
31 | foreach (var item in this.water.ObjList)
32 | {
33 | Vector2D v = this.center - item.Position;
34 | double d = v.Length();
35 | item.AddForce(v.Normalize() * 5 * d);
36 | }
37 |
38 | this.PhysicsWorld.Update(duration);
39 | }
40 |
41 | public void Fire()
42 | {
43 | Random rnd = new Random();
44 | if (!this.Start)
45 | {
46 | this.Start = true;
47 |
48 | // 设置全局的阻力
49 | this.PhysicsWorld.CreateGlobalZone(new ParticleDrag(0.5, 0.5));
50 |
51 | // 初始化水
52 | for (int i = 0; i < 20; i++)
53 | {
54 | var item = this.PhysicsWorld.CreateParticle(
55 | new Vector2D(
56 | rnd.Next((int)this.Bitmap.Width).ToSimUnits(),
57 | rnd.Next((int)this.Bitmap.Height).ToSimUnits()),
58 | Vector2D.Zero,
59 | 1);
60 | item.BindShape(new Circle(2.ToSimUnits()));
61 | this.water.ObjList.Add(item);
62 | }
63 | }
64 |
65 | // 抖动
66 | this.water.ObjList.ForEach(obj => obj.Velocity.Set(rnd.Next(5) - 2.5, rnd.Next(5) - 2.5));
67 | }
68 |
69 | private bool flag = false;
70 |
71 | }
72 | }
--------------------------------------------------------------------------------
/WPFDemo/FluidDemo/Water.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.FluidDemo
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 | using System.Windows.Media.Imaging;
7 | using Physics2D;
8 | using Physics2D.Object;
9 | using WPFDemo.Graphic;
10 |
11 | internal class Water : IDrawable
12 | {
13 | public readonly List ObjList = new List();
14 |
15 | private const int Threshold = 900;
16 | private const int GridR = 60;
17 |
18 | private const int R = 150;
19 |
20 | private readonly int[] metaTable;
21 | private readonly int[,] cacheTable;
22 | private readonly object[,] cacheLocks;
23 | private int[,] cache;
24 |
25 | public Water(int maxWidth, int maxHeight)
26 | {
27 | // 计算势能函数缓存
28 | this.metaTable = new int[GridR];
29 | this.metaTable[0] = Threshold;
30 | for (int i = 1; i < GridR; i++)
31 | {
32 | this.metaTable[i] = R * R / (i * i);
33 | }
34 |
35 | // 计算势能缓存
36 | this.cacheTable = new int[2 * GridR, 2 * GridR];
37 | for (int i = 0; i < 2 * GridR; i++)
38 | {
39 | for (int j = 0; j < 2 * GridR; j++)
40 | {
41 | int d = (int)Math.Sqrt((i - GridR) * (i - GridR) + (j - GridR) * (j - GridR) + 0.5);
42 | this.cacheTable[i, j] = d < GridR ? this.metaTable[d] : 0;
43 | }
44 | }
45 |
46 | // 初始化锁
47 | int w = maxWidth;
48 | int h = maxHeight;
49 | this.cacheLocks = new object[w, h];
50 | for (int i = 0; i < w; i++)
51 | {
52 | for (int j = 0; j < h; j++)
53 | {
54 | this.cacheLocks[i, j] = new object();
55 | }
56 | }
57 | }
58 |
59 | public unsafe void Draw(WriteableBitmap bitmap)
60 | {
61 | // 绘制Metaball
62 | using (var wc = bitmap.GetBitmapContext())
63 | {
64 | int w = wc.Width;
65 | int h = wc.Height;
66 | var pixels = wc.Pixels;
67 | this.cache = this.cache ?? new int[w, h];
68 | Array.Clear(this.cache, 0, this.cache.Length);
69 |
70 | // 叠加每个球的势能
71 | Parallel.ForEach(this.ObjList, obj =>
72 | {
73 | int x = obj.Position.X.ToDisplayUnits();
74 | int y = obj.Position.Y.ToDisplayUnits();
75 |
76 | for (int i = x - GridR, I = 0; i < x + GridR; i++, I++)
77 | {
78 | for (int j = y - GridR, J = 0; j < y + GridR; j++, J++)
79 | {
80 | if (i < 0 || i >= w || j < 0 || j >= h) continue;
81 | else
82 | {
83 | lock (this.cacheLocks[i, j])
84 | {
85 | this.cache[i, j] += this.cacheTable[I, J];
86 | }
87 | }
88 | }
89 | }
90 | });
91 |
92 | // 渲染画布
93 | Parallel.For(0, h, y =>
94 | {
95 | for (int x = 0; x < w; x++)
96 | {
97 | if (this.cache[x, y] >= Threshold)
98 | {
99 | pixels[y * w + x] = (255 << 24) | (16 << 68) | (146 << 8) | 216;
100 | }
101 | }
102 | });
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/WPFDemo/Graphic/IDrawable.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.Graphic
2 | {
3 | using System.Windows.Media.Imaging;
4 |
5 | public interface IDrawable
6 | {
7 | void Draw(WriteableBitmap bitmap);
8 | }
9 | }
--------------------------------------------------------------------------------
/WPFDemo/Graphic/PhysicsGraphic.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.Graphic
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Windows.Controls;
6 | using System.Windows.Media.Imaging;
7 | using Physics2D.Core;
8 |
9 | public abstract class PhysicsGraphic
10 | {
11 | ///
12 | /// 物理世界
13 | ///
14 | protected World PhysicsWorld = new World();
15 |
16 | ///
17 | /// 帧率控制计时器
18 | ///
19 | protected TimeTracker TimeTracker = new TimeTracker();
20 |
21 | protected double TimeSpan = 0;
22 |
23 | ///
24 | /// 是否渲染标记
25 | ///
26 | public bool Start = false;
27 |
28 | ///
29 | /// 绘制层
30 | ///
31 | public readonly WriteableBitmap Bitmap;
32 |
33 | ///
34 | /// 绘制队列
35 | ///
36 | protected readonly List DrawQueue = new List();
37 |
38 | ///
39 | /// 物理演算时间槽大小
40 | ///
41 | protected double Slot = 1 / 60.0;
42 |
43 | protected PhysicsGraphic(Image image)
44 | {
45 | this.Bitmap = BitmapFactory.New((int)image.Width, (int)image.Height);
46 | }
47 |
48 | public void Update(object sender, EventArgs e)
49 | {
50 | var interval = this.TimeTracker.Update();
51 | if (!this.Start)
52 | {
53 | return;
54 | }
55 |
56 | // 更新物理世界
57 | this.TimeSpan += interval;
58 | for (; this.TimeSpan >= this.Slot; this.TimeSpan -= this.Slot)
59 | {
60 | this.UpdatePhysics(this.Slot);
61 | }
62 |
63 | // 更新图形
64 | this.Bitmap.Clear();
65 | this.DrawQueue.ForEach(item => item.Draw(this.Bitmap));
66 | }
67 |
68 | ///
69 | /// 更新物理世界
70 | ///
71 | /// 持续时间
72 | protected abstract void UpdatePhysics(double duration);
73 | }
74 | }
--------------------------------------------------------------------------------
/WPFDemo/Graphic/TimeTracker.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.Graphic
2 | {
3 | using System;
4 | using System.Diagnostics;
5 |
6 | public class TimeTracker
7 | {
8 | public double TimerInterval { get; set; } = -1;
9 |
10 | public DateTime ElapsedTime { get; private set; }
11 |
12 | public double DeltaSeconds { get; private set; }
13 |
14 | public event EventHandler TimerFired;
15 |
16 | public TimeTracker()
17 | {
18 | this.ElapsedTime = DateTime.Now;
19 | }
20 |
21 | public double Update()
22 | {
23 | DateTime currentTime = DateTime.Now;
24 | TimeSpan diffTime = currentTime - this.ElapsedTime;
25 |
26 | this.DeltaSeconds = diffTime.TotalSeconds;
27 | if (this.TimerInterval > 0)
28 | {
29 | if (currentTime != this.ElapsedTime)
30 | {
31 | Debug.Assert(this.TimerFired != null, "TimerFired != null");
32 | this.TimerFired(this, null);
33 | }
34 | }
35 |
36 | this.ElapsedTime = currentTime;
37 |
38 | return this.DeltaSeconds;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/WPFDemo/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/WPFDemo/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo
2 | {
3 | using System.Windows;
4 | using WPFDemo.ContactDemo;
5 | using WPFDemo.ElasticDemo;
6 | using WPFDemo.FireworksDemo;
7 | using WPFDemo.FluidDemo;
8 | using WPFDemo.RobDemo;
9 |
10 | ///
11 | /// MainWindow.xaml 的交互逻辑
12 | ///
13 | public partial class MainWindow : Window
14 | {
15 | public MainWindow()
16 | {
17 | this.InitializeComponent();
18 | }
19 |
20 | private void fireworksDemo_Click(object sender, RoutedEventArgs e)
21 | {
22 | var fwW = new Fireworks();
23 | fwW.Show();
24 | }
25 |
26 | private void circlrDemo_Click(object sender, RoutedEventArgs e)
27 | {
28 | var cW = new CircleDemo.Circle();
29 | cW.Show();
30 | }
31 |
32 | private void elasticDemo_Click(object sender, RoutedEventArgs e)
33 | {
34 | var eW = new Elastic();
35 | eW.Show();
36 | }
37 |
38 | private void fluidDemo_Click(object sender, RoutedEventArgs e)
39 | {
40 | var fW = new Fluid();
41 | fW.Show();
42 | }
43 |
44 | private void contactDemo_Click(object sender, RoutedEventArgs e)
45 | {
46 | var cW = new Contact();
47 | cW.Show();
48 | }
49 |
50 | private void RobDemo_Click(object sender, RoutedEventArgs e)
51 | {
52 | var rW = new Rob();
53 | rW.Show();
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/WPFDemo/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 | using System.Windows;
4 |
5 | [assembly: AssemblyTitle("WPFDemo")]
6 | [assembly: AssemblyDescription("")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("")]
9 | [assembly: AssemblyProduct("WPFDemo")]
10 | [assembly: AssemblyCopyright("Copyright © 2014")]
11 | [assembly: AssemblyTrademark("")]
12 | [assembly: AssemblyCulture("")]
13 | [assembly: ComVisible(false)]
14 | [assembly: ThemeInfo(
15 | ResourceDictionaryLocation.None,
16 | ResourceDictionaryLocation.SourceAssembly)]
17 | [assembly: AssemblyVersion("1.0.0.0")]
18 | [assembly: AssemblyFileVersion("1.0.0.0")]
--------------------------------------------------------------------------------
/WPFDemo/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace WPFDemo.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WPFDemo.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/WPFDemo/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/WPFDemo/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace WPFDemo.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/WPFDemo/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/WPFDemo/RobDemo/Rob.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/WPFDemo/RobDemo/Rob.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.RobDemo
2 | {
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 | using Physics2D;
7 |
8 | ///
9 | /// Rob.xaml 的交互逻辑
10 | ///
11 | public partial class Rob : Window
12 | {
13 | private readonly RobDemo robDemo;
14 |
15 | public Rob()
16 | {
17 | this.InitializeComponent();
18 |
19 | this.robDemo = new RobDemo(this.ImageSurface);
20 | this.ImageSurface.Source = this.robDemo.Bitmap;
21 | CompositionTarget.Rendering += this.robDemo.Update;
22 | }
23 |
24 | private void ImageSurface_OnMouseDown(object sender, MouseButtonEventArgs e)
25 | {
26 | this.robDemo.Down(e.GetPosition(this.ImageSurface).X.ToSimUnits(), e.GetPosition(this.ImageSurface).Y.ToSimUnits());
27 | }
28 |
29 | private void ImageSurface_OnMouseUp(object sender, MouseButtonEventArgs e)
30 | {
31 | this.robDemo.Up();
32 | }
33 |
34 | private void ImageSurface_OnMouseMove(object sender, MouseEventArgs e)
35 | {
36 | this.robDemo.Move(e.GetPosition(this.ImageSurface).X.ToSimUnits(), e.GetPosition(this.ImageSurface).Y.ToSimUnits());
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/WPFDemo/RobDemo/RobDemo.cs:
--------------------------------------------------------------------------------
1 | namespace WPFDemo.RobDemo
2 | {
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows.Controls;
6 | using System.Windows.Media;
7 | using System.Windows.Media.Imaging;
8 | using Physics2D;
9 | using Physics2D.Collision.Shapes;
10 | using Physics2D.Common;
11 | using Physics2D.Factories;
12 | using Physics2D.Object;
13 | using Physics2D.Object.Tools;
14 | using WPFDemo.Graphic;
15 |
16 | internal class RobDemo : PhysicsGraphic, IDrawable
17 | {
18 | ///
19 | /// 多边形顶点集
20 | ///
21 | private readonly List vertexs = new List
22 | {
23 | new Vector2D(200, 20).ToSimUnits(),
24 | new Vector2D(300, 40).ToSimUnits(),
25 | new Vector2D(300, 100).ToSimUnits()
26 | };
27 |
28 | ///
29 | /// 多边形边集
30 | ///
31 | private readonly List edgePoints = new List
32 | {
33 | new Vector2D(10, 10).ToSimUnits(),
34 | new Vector2D(490, 10).ToSimUnits(),
35 | new Vector2D(490, 390).ToSimUnits(),
36 | new Vector2D(10, 390).ToSimUnits()
37 | };
38 |
39 | ///
40 | /// 组合质体
41 | ///
42 | private readonly CombinedParticle combinedParticle;
43 |
44 | ///
45 | /// 当前状态
46 | ///
47 | private State state = State.Up;
48 |
49 | ///
50 | /// 鼠标位置
51 | ///
52 | private Vector2D mousePosition = Vector2D.Zero;
53 |
54 | ///
55 | /// 固定点操纵杆
56 | ///
57 | private Handle handle;
58 |
59 | ///
60 | /// 状态枚举
61 | ///
62 | private enum State
63 | {
64 | Down, Pinned, Up
65 | }
66 |
67 | public RobDemo(Image image)
68 | : base(image)
69 | {
70 | Settings.ContactIteration = 20;
71 |
72 | this.combinedParticle = new CombinedParticle(this.vertexs, 3, 1, true);
73 | this.PhysicsWorld.AddObject(this.combinedParticle);
74 |
75 | // 为顶点绑定形状
76 | foreach (var vertex in this.combinedParticle.Vertexs)
77 | {
78 | vertex.BindShape(new Circle(4.ToSimUnits()));
79 | }
80 |
81 | // 增加边缘
82 | this.PhysicsWorld.CreatePolygonEdge(this.edgePoints.ToArray());
83 |
84 | // 增加重力
85 | this.PhysicsWorld.CreateGravity(9.8);
86 |
87 | this.DrawQueue.Add(this);
88 | this.Start = true;
89 | }
90 |
91 | protected override void UpdatePhysics(double duration)
92 | {
93 | if (this.state == State.Pinned)
94 | {
95 | var d = this.mousePosition - this.handle.Position;
96 | this.combinedParticle.Velocity = d / duration;
97 | this.handle.Position = this.mousePosition;
98 | }
99 |
100 | this.PhysicsWorld.Update(duration);
101 | }
102 |
103 | public void Down(double x, double y)
104 | {
105 | this.mousePosition.Set(x, y);
106 |
107 | var points = from v in this.combinedParticle.Vertexs
108 | select v.Position;
109 | if (this.state != State.Down && MathHelper.IsInside(points.ToList(), this.mousePosition))
110 | {
111 | this.handle = this.PhysicsWorld.Pin(this.combinedParticle, this.mousePosition);
112 | this.state = State.Pinned;
113 | }
114 | else
115 | {
116 | this.state = State.Down;
117 | }
118 | }
119 |
120 | public void Move(double x, double y)
121 | {
122 | if (this.state == State.Pinned)
123 | {
124 | this.mousePosition.Set(x, y);
125 | }
126 | }
127 |
128 | public void Up()
129 | {
130 | if (this.state == State.Pinned)
131 | {
132 | this.PhysicsWorld.UnPin(this.combinedParticle);
133 | }
134 |
135 | this.state = State.Up;
136 | }
137 |
138 | public void Draw(WriteableBitmap bitmap)
139 | {
140 | // 绘制物体
141 | var points = new List();
142 | foreach (var vertex in this.combinedParticle.Vertexs)
143 | {
144 | points.Add(vertex.Position.X.ToDisplayUnits());
145 | points.Add(vertex.Position.Y.ToDisplayUnits());
146 | }
147 |
148 | bitmap.FillPolygon(points.ToArray(), Colors.LightCoral);
149 |
150 | // 绘制边缘
151 | points.Clear();
152 | foreach (var point in this.edgePoints)
153 | {
154 | points.Add(point.X.ToDisplayUnits());
155 | points.Add(point.Y.ToDisplayUnits());
156 | }
157 |
158 | points.Add(this.edgePoints[0].X.ToDisplayUnits());
159 | points.Add(this.edgePoints[0].Y.ToDisplayUnits());
160 | bitmap.DrawPolyline(points.ToArray(), Colors.Black);
161 |
162 | // 绘制PinRod
163 | foreach (var e in this.combinedParticle.PinRods)
164 | {
165 | bitmap.DrawLineAa(
166 | e.ParticleA.Position.X.ToDisplayUnits(), e.ParticleA.Position.Y.ToDisplayUnits(),
167 | e.ParticleB.Position.X.ToDisplayUnits(), e.ParticleB.Position.Y.ToDisplayUnits(), Colors.Red);
168 | }
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/WPFDemo/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/WPFDemo/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | // ACTION REQUIRED: This file was automatically added to your project, but it
3 | // will not take effect until additional steps are taken to enable it. See the
4 | // following page for additional information:
5 | //
6 | // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md
7 |
8 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
9 | "settings": {
10 | "documentationRules": {
11 | "companyName": "PlaceholderCompany"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------