df8c661356bdd5c13404137227f873a18c3630e2
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Physics2D / src / public / chipmunk / Space.cs
1 /*
2  * Copyright (c) 2023 Codefoco (codefoco@codefoco.com)
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  * 
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  * 
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22
23 using System;
24 using System.Collections.Generic;
25 using System.ComponentModel;
26 using System.Diagnostics;
27 using System.Runtime.InteropServices;
28 using cpBody = System.IntPtr;
29 using cpCollisionHandlerPointer = System.IntPtr;
30 using cpCollisionType = System.UIntPtr;
31 using cpConstraint = System.IntPtr;
32 using cpDataPointer = System.IntPtr;
33 using cpShape = System.IntPtr;
34 using cpSpace = System.IntPtr;
35 using voidptr_t = System.IntPtr;
36
37 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
38 using ObjCRuntime;
39 #endif
40
41 namespace Tizen.NUI.Physics2D.Chipmunk
42 {
43     /// <summary>
44     /// Spaces in Chipmunk are the basic unit of simulation. You add rigid bodies, shapes, and
45     /// constraints to the space and then step them all forward through time together.
46     /// </summary>
47     [EditorBrowsable(EditorBrowsableState.Never)]
48     public class Space : IDisposable
49     {
50 #pragma warning disable IDE0032
51         private readonly cpSpace space;
52 #pragma warning restore IDE0032
53
54         /// <summary>
55         /// Native handle cpSpace.
56         /// </summary>
57         [EditorBrowsable(EditorBrowsableState.Never)]
58         public cpSpace Handle => space;
59
60         /// <summary>
61         /// Create a new Space object.
62         /// </summary>
63         [EditorBrowsable(EditorBrowsableState.Never)]
64         public Space()
65         {
66             space = NativeMethods.cpSpaceNew();
67             RegisterUserData();
68         }
69
70         /// <summary>
71         /// Create a space from a native Handle (used by derived classes).
72         /// </summary>
73         /// <param name="handle"></param>
74         protected internal Space(cpSpace handle)
75         {
76             space = handle;
77             RegisterUserData();
78         }
79
80         /// <summary>
81         /// Destroys and frees.
82         /// </summary>
83         [EditorBrowsable(EditorBrowsableState.Never)]
84         public void Free()
85         {
86             ReleaseUserData();
87             FreeSpace(space);
88         }
89
90         /// <summary>
91         /// Destroy and free space.
92         /// </summary>
93         /// <param name="handle"></param>
94         protected virtual void FreeSpace(cpSpace handle)
95         {
96             NativeMethods.cpSpaceFree(handle);
97         }
98
99         /// <summary>
100         /// Destroy and free space.
101         /// </summary>
102         protected virtual void Dispose(bool disposing)
103         {
104             Free();
105         }
106         /// <summary>
107         /// Disposes the Space object.
108         /// </summary>
109         [EditorBrowsable(EditorBrowsableState.Never)]
110         public void Dispose()
111         {
112             Dispose(true);
113             GC.SuppressFinalize(this);
114         }
115
116         void RegisterUserData()
117         {
118             cpDataPointer pointer = NativeInterop.RegisterHandle(this);
119             NativeMethods.cpSpaceSetUserData(space, pointer);
120         }
121
122         void ReleaseUserData()
123         {
124             cpDataPointer pointer = NativeMethods.cpSpaceGetUserData(space);
125             NativeInterop.ReleaseHandle(pointer);
126         }
127
128         /// <summary>
129         /// Get a Space object from native cpSpace handle.
130         /// </summary>
131         [EditorBrowsable(EditorBrowsableState.Never)]
132         public static Space FromHandle(cpSpace space)
133         {
134             cpDataPointer handle = NativeMethods.cpSpaceGetUserData(space);
135             return NativeInterop.FromIntPtr<Space>(handle);
136         }
137
138         /// <summary>
139         /// Get a Space object from native cpSpace handle, but return null if the handle is 0.
140         /// </summary>
141         [EditorBrowsable(EditorBrowsableState.Never)]
142         public static Space FromHandleSafe(cpSpace space)
143         {
144             if (space == IntPtr.Zero)
145             {
146                 return null;
147             }
148
149             return FromHandle(space);
150         }
151
152         // Properties
153
154         /// <summary>
155         /// Arbitrary user data.
156         /// </summary>
157         [EditorBrowsable(EditorBrowsableState.Never)]
158         public object Data { get; set; }
159
160         /// <summary>
161         /// Number of iterations to use in the impulse solver to solve contacts and other
162         /// constraints.
163         /// </summary>
164         [EditorBrowsable(EditorBrowsableState.Never)]
165         public int Iterations
166         {
167             get => NativeMethods.cpSpaceGetIterations(space);
168             set => NativeMethods.cpSpaceSetIterations(space, value);
169         }
170
171         /// <summary>
172         /// Gravity to pass to rigid bodies when integrating velocity. 
173         /// </summary>
174         [EditorBrowsable(EditorBrowsableState.Never)]
175         public Vect Gravity
176         {
177             get => NativeMethods.cpSpaceGetGravity(space);
178             set => NativeMethods.cpSpaceSetGravity(space, value);
179         }
180
181         /// <summary>
182         /// Damping rate expressed as the fraction of velocity that bodies retain each second. A
183         /// value of 0.9 would mean that each body's velocity will drop 10% per second. The default
184         /// value is 1.0, meaning no damping is applied. Note: This damping value is different than
185         /// those of <see cref="DampedSpring"/> and <see cref="DampedRotarySpring"/>.
186         /// </summary>
187         [EditorBrowsable(EditorBrowsableState.Never)]
188         public double Damping
189         {
190             get => NativeMethods.cpSpaceGetDamping(space);
191             set => NativeMethods.cpSpaceSetDamping(space, value);
192         }
193
194         /// <summary>
195         /// Speed threshold for a body to be considered idle. The default value of 0 means to let
196         /// the space guess a good threshold based on gravity. 
197         /// </summary>
198         [EditorBrowsable(EditorBrowsableState.Never)]
199         public double IdleSpeedThreshold
200         {
201             get => NativeMethods.cpSpaceGetIdleSpeedThreshold(space);
202             set => NativeMethods.cpSpaceSetIdleSpeedThreshold(space, value);
203         }
204
205         /// <summary>
206         /// Time a group of bodies must remain idle in order to fall asleep. Enabling sleeping also
207         /// implicitly enables the the contact graph. The default value of infinity disables the
208         /// sleeping algorithm. 
209         /// </summary>
210         [EditorBrowsable(EditorBrowsableState.Never)]
211         public double SleepTimeThreshold
212         {
213             get => NativeMethods.cpSpaceGetSleepTimeThreshold(space);
214             set => NativeMethods.cpSpaceSetSleepTimeThreshold(space, value);
215         }
216
217         /// <summary>
218         /// Amount of encouraged penetration between colliding shapes. This is used to reduce
219         /// oscillating contacts and keep the collision cache warm. Defaults to 0.1. If you have
220         /// poor simulation quality, increase this number as much as possible without allowing
221         /// visible amounts of overlap. 
222         /// </summary>
223         [EditorBrowsable(EditorBrowsableState.Never)]
224         public double CollisionSlop
225         {
226             get => NativeMethods.cpSpaceGetCollisionSlop(space);
227             set => NativeMethods.cpSpaceSetCollisionSlop(space, value);
228         }
229
230         /// <summary>
231         /// Determines how fast overlapping shapes are pushed apart.
232         /// </summary>
233         [EditorBrowsable(EditorBrowsableState.Never)]
234         public double CollisionBias
235         {
236             get => NativeMethods.cpSpaceGetCollisionBias(space);
237             set => NativeMethods.cpSpaceSetCollisionBias(space, value);
238         }
239
240         /// <summary>
241         /// Number of frames that contact information should persist. Defaults to 3. There is
242         /// probably never a reason to change this value.
243         /// </summary>
244         [EditorBrowsable(EditorBrowsableState.Never)]
245         public int CollisionPersistence
246         {
247             get => (int)NativeMethods.cpSpaceGetCollisionPersistence(space);
248             set => NativeMethods.cpSpaceSetCollisionPersistence(space, (uint)value);
249         }
250
251         /// <summary>
252         /// The Space provided static body for a given <see cref="Space"/>.
253         /// </summary>
254         [EditorBrowsable(EditorBrowsableState.Never)]
255         public Body StaticBody
256         {
257             get
258             {
259                 cpBody bodyHandle = NativeMethods.cpSpaceGetStaticBody(space);
260                 cpDataPointer gcHandle = NativeMethods.cpBodyGetUserData(bodyHandle);
261
262                 if (gcHandle != IntPtr.Zero)
263                 {
264                     return NativeInterop.FromIntPtr<Body>(gcHandle);
265                 }
266
267                 return new Body(bodyHandle);
268             }
269         }
270
271         /// <summary>
272         /// Returns the current (or most recent) time step used with the given space.
273         /// Useful from callbacks if your time step is not a compile-time global.
274         /// </summary>
275         [EditorBrowsable(EditorBrowsableState.Never)]
276         public double CurrentTimeStep => NativeMethods.cpSpaceGetCurrentTimeStep(space);
277
278         /// <summary>
279         /// Returns true from inside a callback when objects cannot be added/removed.
280         /// </summary>
281         [EditorBrowsable(EditorBrowsableState.Never)]
282         public bool IsLocked => NativeMethods.cpSpaceIsLocked(space) != 0;
283
284
285         // Collision Handlers
286
287         /// <summary>
288         /// Create or return the existing collision handler that is called for all collisions that are
289         /// not handled by a more specific collision handler.
290         /// </summary>
291         [EditorBrowsable(EditorBrowsableState.Never)]
292         public CollisionHandler GetOrCreateDefaultCollisionHandler()
293         {
294             cpCollisionHandlerPointer collisionHandle = NativeMethods.cpSpaceAddDefaultCollisionHandler(space);
295             return CollisionHandler.GetOrCreate(collisionHandle);
296         }
297
298
299         /// <summary>
300         /// Create or return the existing collision handler for the specified pair of collision
301         /// types. If wildcard handlers are used with either of the collision types, it's the
302         /// responsibility of the custom handler to invoke the wildcard handlers.
303         /// </summary>
304         [EditorBrowsable(EditorBrowsableState.Never)]
305         public CollisionHandler GetOrCreateCollisionHandler(int typeA, int typeB)
306         {
307             uint utypeA = unchecked((uint)typeA);
308             uint utypeB = unchecked((uint)typeB);
309
310             cpCollisionType collisionTypeA = new cpCollisionType(utypeA);
311             cpCollisionType collisionTypeB = new cpCollisionType(utypeB);
312
313             cpCollisionHandlerPointer collisionHandle = NativeMethods.cpSpaceAddCollisionHandler(space, collisionTypeA, collisionTypeB);
314             return CollisionHandler.GetOrCreate(collisionHandle);
315         }
316
317
318         /// <summary>
319         /// Create or return the existing wildcard collision handler for the specified type.
320         /// </summary>
321         [EditorBrowsable(EditorBrowsableState.Never)]
322         public CollisionHandler GetOrCreateWildcardHandler(int type)
323         {
324             cpCollisionHandlerPointer collisionHandle = NativeMethods.cpSpaceAddWildcardHandler(space, (cpCollisionType)type);
325             return CollisionHandler.GetOrCreate(collisionHandle);
326         }
327
328         /// <summary>
329         /// Add a collision shape to the simulation.
330         /// </summary>
331         [EditorBrowsable(EditorBrowsableState.Never)]
332         public void AddShape(Shape shape)
333         {
334             NativeMethods.cpSpaceAddShape(space, shape.Handle);
335         }
336
337         /// <summary>
338         /// Add a rigid body to the simulation. 
339         /// </summary>
340         [EditorBrowsable(EditorBrowsableState.Never)]
341         public void AddBody(Body body)
342         {
343             NativeMethods.cpSpaceAddBody(space, body.Handle);
344         }
345
346         /// <summary>
347         /// Add a constraint to the simulation.
348         /// </summary>
349         [EditorBrowsable(EditorBrowsableState.Never)]
350         public void AddConstraint(Constraint constraint)
351         {
352             NativeMethods.cpSpaceAddConstraint(space, constraint.Handle);
353         }
354
355         /// <summary>
356         /// Remove a collision shape from the simulation.
357         /// </summary>
358         [EditorBrowsable(EditorBrowsableState.Never)]
359         public void RemoveShape(Shape shape)
360         {
361             NativeMethods.cpSpaceRemoveShape(space, shape.Handle);
362         }
363
364         /// <summary>
365         /// Remove a rigid body from the simulation.
366         /// </summary>
367         [EditorBrowsable(EditorBrowsableState.Never)]
368         public void RemoveBody(Body body)
369         {
370             NativeMethods.cpSpaceRemoveBody(space, body.Handle);
371         }
372
373         /// <summary>
374         /// Remove a constraint from the simulation.
375         /// </summary>
376         [EditorBrowsable(EditorBrowsableState.Never)]
377         public void RemoveConstraint(Constraint constraint)
378         {
379             NativeMethods.cpSpaceRemoveConstraint(space, constraint.Handle);
380         }
381
382         /// <summary>
383         /// Test if a collision shape has been added to the space.
384         /// </summary>
385         [EditorBrowsable(EditorBrowsableState.Never)]
386         public bool Contains(Shape shape)
387         {
388             return NativeMethods.cpSpaceContainsShape(space, shape.Handle) != 0;
389         }
390
391         /// <summary>
392         /// Test if a rigid body has been added to the space.
393         /// </summary>
394         [EditorBrowsable(EditorBrowsableState.Never)]
395         public bool Contains(Body body)
396         {
397             return NativeMethods.cpSpaceContainsBody(space, body.Handle) != 0;
398         }
399
400         /// <summary>
401         /// Test if a constraint has been added to the space.
402         /// </summary>
403         [EditorBrowsable(EditorBrowsableState.Never)]
404         public bool Contains(Constraint constraint)
405         {
406             return NativeMethods.cpSpaceContainsConstraint(space, constraint.Handle) != 0;
407         }
408
409 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
410 #pragma warning disable CA1416 // Validate platform compatibility
411         [MonoPInvokeCallback(typeof(PostStepFunction))]
412 #pragma warning restore CA1416 // Validate platform compatibility
413 #endif
414         private static void PostStepCallBack(cpSpace handleSpace, voidptr_t handleKey, voidptr_t handleData)
415         {
416             var space = FromHandle(handleSpace);
417             var key = NativeInterop.FromIntPtr<object>(handleKey);
418             var data = NativeInterop.FromIntPtr<PostStepCallbackInfo>(handleData);
419
420             Action<Space, object, object> callback = data.Callback;
421
422             callback(space, key, data.Data);
423
424             NativeInterop.ReleaseHandle(handleKey);
425             NativeInterop.ReleaseHandle(handleData);
426         }
427
428         private static PostStepFunction postStepCallBack = PostStepCallBack;
429
430         /// <summary>
431         /// Schedule a post-step callback to be called when <see cref="Step"/> finishes. You can
432         /// only register one callback per unique value for <paramref name="key"/>. Returns true
433         /// only if <paramref name="key"/> has never been scheduled before. It's possible to pass
434         /// null for <paramref name="callback"/> if you only want to mark <paramref name="key"/> as
435         /// being used.
436         /// </summary>
437         [EditorBrowsable(EditorBrowsableState.Never)]
438         public bool AddPostStepCallback(Action<Space, object, object> callback, object key, object data)
439         {
440             var info = new PostStepCallbackInfo(callback, data);
441
442             IntPtr dataHandle = NativeInterop.RegisterHandle(info);
443             IntPtr keyHandle = NativeInterop.RegisterHandle(key);
444
445             return NativeMethods.cpSpaceAddPostStepCallback(space, postStepCallBack.ToFunctionPointer(), keyHandle, dataHandle) != 0;
446         }
447
448 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
449 #pragma warning disable CA1416 // Validate platform compatibility
450         [MonoPInvokeCallback(typeof(SpacePointQueryFunction))]
451 #pragma warning restore CA1416 // Validate platform compatibility
452 #endif
453         private static void EachPointQuery(cpShape shapeHandle, Vect point, double distance, Vect gradient, voidptr_t data)
454         {
455             var list = (List<PointQueryInfo>)GCHandle.FromIntPtr(data).Target;
456
457             var shape = Shape.FromHandle(shapeHandle);
458             var pointQuery = new PointQueryInfo(shape, point, distance, gradient);
459
460             list.Add(pointQuery);
461         }
462
463         private static SpacePointQueryFunction eachPointQuery = EachPointQuery;
464
465         /// <summary>
466         /// Get the shapes within a radius of the point location that are part of this space. The
467         /// filter is applied to the query and follows the same rules as the collision detection.
468         /// If a maxDistance of 0.0 is used, the point must lie inside a shape. Negative
469         /// <paramref name="maxDistance"/> is also allowed meaning that the point must be a under a
470         /// certain depth within a shape to be considered a match.
471         /// </summary>
472         /// <param name="point">Where to check for shapes in the space.</param>
473         /// <param name="maxDistance">Match only within this distance.</param>
474         /// <param name="filter">Only pick shapes matching the filter.</param>
475         [EditorBrowsable(EditorBrowsableState.Never)]
476         public IReadOnlyList<PointQueryInfo> PointQuery(Vect point, double maxDistance, ShapeFilter filter)
477         {
478             var list = new List<PointQueryInfo>();
479             var gcHandle = GCHandle.Alloc(list);
480
481             NativeMethods.cpSpacePointQuery(space, point, maxDistance, filter, eachPointQuery.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
482
483             gcHandle.Free();
484             return list;
485         }
486
487         /// <summary>
488         /// Get the nearest shape within a radius of a point that is part of this space. The filter
489         /// is applied to the query and follows the same rules as the collision detection. If a
490         /// <paramref name="maxDistance"/> of 0.0 is used, the point must lie inside a shape.
491         /// Negative <paramref name="maxDistance"/> is also allowed, meaning that the point must be
492         /// under a certain depth within a shape to be considered a match.
493         /// </summary>
494         /// <param name="point">Where to check for collision in the space.</param>
495         /// <param name="maxDistance">Match only within this distance.</param>
496         /// <param name="filter">Only pick shapes matching the filter.</param>
497         [EditorBrowsable(EditorBrowsableState.Never)]
498         public PointQueryInfo PointQueryNearest(Vect point, double maxDistance, ShapeFilter filter)
499         {
500             var queryInfo = new cpPointQueryInfo();
501
502             cpShape shape = NativeMethods.cpSpacePointQueryNearest(space, point, maxDistance, filter, ref queryInfo);
503             if (shape == IntPtr.Zero)
504                 return null;
505
506             return PointQueryInfo.FromQueryInfo(queryInfo);
507         }
508
509 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
510 #pragma warning disable CA1416 // Validate platform compatibility
511         [MonoPInvokeCallback(typeof(SpaceSegmentQueryFunction))]
512 #pragma warning restore CA1416 // Validate platform compatibility
513 #endif
514         private static void EachSegmentQuery(cpShape shapeHandle, Vect point, Vect normal, double alpha, voidptr_t data)
515         {
516             var list = (List<SegmentQueryInfo>)GCHandle.FromIntPtr(data).Target;
517
518             var shape = Shape.FromHandle(shapeHandle);
519             var pointQuery = new SegmentQueryInfo(shape, point, normal, alpha);
520
521             list.Add(pointQuery);
522         }
523
524         private static SpaceSegmentQueryFunction eachSegmentQuery = EachSegmentQuery;
525
526         /// <summary>
527         /// Get the shapes within a capsule-shaped radius of a line segment that is part of this
528         /// space. The filter is applied to the query and follows the same rules as the collision
529         /// detection.
530         /// </summary>
531         [EditorBrowsable(EditorBrowsableState.Never)]
532         public IReadOnlyList<SegmentQueryInfo> SegmentQuery(Vect start, Vect end, double radius, ShapeFilter filter)
533         {
534             var list = new List<SegmentQueryInfo>();
535             var gcHandle = GCHandle.Alloc(list);
536
537             NativeMethods.cpSpaceSegmentQuery(space, start, end, radius, filter, eachSegmentQuery.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
538
539             gcHandle.Free();
540             return list;
541         }
542
543         /// <summary>
544         /// Get the first shape within a capsule-shaped radius of a line segment that is part of
545         /// this space. The filter is applied to the query and follows the same rules as the
546         /// collision detection.
547         /// </summary>
548         [EditorBrowsable(EditorBrowsableState.Never)]
549         public SegmentQueryInfo SegmentQueryFirst(Vect start, Vect end, double radius, ShapeFilter filter)
550         {
551             var queryInfo = new cpSegmentQueryInfo();
552
553             cpShape shape = NativeMethods.cpSpaceSegmentQueryFirst(space, start, end, radius, filter, ref queryInfo);
554             if (shape == IntPtr.Zero)
555                 return null;
556
557             return SegmentQueryInfo.FromQueryInfo(queryInfo);
558         }
559
560
561 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
562 #pragma warning disable CA1416 // Validate platform compatibility
563         [MonoPInvokeCallback(typeof(SpaceBBQueryFunction))]
564 #pragma warning restore CA1416 // Validate platform compatibility
565 #endif
566         private static void EachBBQuery(cpShape shapeHandle, voidptr_t data)
567         {
568             var list = (List<Shape>)GCHandle.FromIntPtr(data).Target;
569
570             var shape = Shape.FromHandle(shapeHandle);
571
572             list.Add(shape);
573         }
574
575         private static SpaceBBQueryFunction eachBBQuery = EachBBQuery;
576
577
578         /// <summary>
579         /// Get all shapes within the axis-aligned bounding box that are part of this shape. The
580         /// filter is applied to the query and follows the same rules as the collision detection.
581         /// </summary>
582         [EditorBrowsable(EditorBrowsableState.Never)]
583         public IReadOnlyList<Shape> BoundBoxQuery(BoundingBox bb, ShapeFilter filter)
584         {
585             var list = new List<Shape>();
586
587             var gcHandle = GCHandle.Alloc(list);
588
589             NativeMethods.cpSpaceBBQuery(space, bb, filter, eachBBQuery.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
590
591             gcHandle.Free();
592             return list;
593         }
594
595         /// <summary>
596         /// Get all bodies in the space.
597         /// </summary>
598         [EditorBrowsable(EditorBrowsableState.Never)]
599         public IReadOnlyList<Body> Bodies
600         {
601             get
602             {
603                 int count = NativeMethods.cpSpaceGetBodyCount(space);
604
605                 if (count == 0)
606                     return Array.Empty<Body>();
607
608                 IntPtr ptrBodies = Marshal.AllocHGlobal(IntPtr.Size * count);
609                 NativeMethods.cpSpaceGetBodiesUserDataArray(space, ptrBodies);
610
611                 IntPtr[] userDataArray = new IntPtr[count];
612
613                 Marshal.Copy(ptrBodies, userDataArray, 0, count);
614
615                 Marshal.FreeHGlobal(ptrBodies);
616
617                 Body[] bodies = new Body[count];
618
619                 for (int i = 0; i < count; i++)
620                 {
621                     Body b = NativeInterop.FromIntPtr<Body>(userDataArray[i]);
622                     bodies[i] = b;
623                 }
624
625                 return bodies;
626             }
627         }
628
629         /// <summary>
630         /// Get dynamic bodies in the space.
631         /// </summary>
632         [EditorBrowsable(EditorBrowsableState.Never)]
633         public IReadOnlyList<Body> DynamicBodies
634         {
635             get
636             {
637                 int count = NativeMethods.cpSpaceGetDynamicBodyCount(space);
638
639                 if (count == 0)
640                     return Array.Empty<Body>();
641
642                 IntPtr ptrBodies = Marshal.AllocHGlobal(IntPtr.Size * count);
643                 NativeMethods.cpSpaceGetDynamicBodiesUserDataArray(space, ptrBodies);
644
645                 IntPtr[] userDataArray = new IntPtr[count];
646
647                 Marshal.Copy(ptrBodies, userDataArray, 0, count);
648
649                 Marshal.FreeHGlobal(ptrBodies);
650
651                 Body[] bodies = new Body[count];
652
653                 for (int i = 0; i < count; i++)
654                 {
655                     Body b = NativeInterop.FromIntPtr<Body>(userDataArray[i]);
656                     bodies[i] = b;
657                 }
658
659                 return bodies;
660             }
661         }
662
663 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
664 #pragma warning disable CA1416 // Validate platform compatibility
665         [MonoPInvokeCallback(typeof(SpaceObjectIteratorFunction))]
666 #pragma warning restore CA1416 // Validate platform compatibility
667 #endif
668         private static void EachShape(cpShape shapeHandle, voidptr_t data)
669         {
670             var list = (List<Shape>)GCHandle.FromIntPtr(data).Target;
671
672             var shape = Shape.FromHandle(shapeHandle);
673
674             list.Add(shape);
675         }
676
677         private static SpaceObjectIteratorFunction eachShape = EachShape;
678
679         /// <summary>
680         /// Get all shapes in the space.
681         /// </summary>
682         [EditorBrowsable(EditorBrowsableState.Never)]
683         public IReadOnlyList<Shape> Shapes
684         {
685             get
686             {
687                 var list = new List<Shape>();
688
689                 var gcHandle = GCHandle.Alloc(list);
690
691                 NativeMethods.cpSpaceEachShape(space, eachShape.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
692
693                 gcHandle.Free();
694
695                 return list;
696             }
697         }
698
699 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
700 #pragma warning disable CA1416 // Validate platform compatibility
701         [MonoPInvokeCallback(typeof(SpaceShapeQueryFunction))]
702 #pragma warning restore CA1416 // Validate platform compatibility
703 #endif
704         private static void ShapeQueryCallback(cpShape shape, IntPtr pointsPointer, voidptr_t data)
705         {
706             var list = (List<ContactPointSet>)GCHandle.FromIntPtr(data).Target;
707
708             var pointSet = NativeInterop.PtrToStructure<cpContactPointSet>(pointsPointer);
709
710             list.Add(ContactPointSet.FromContactPointSet(pointSet));
711         }
712
713         private static SpaceShapeQueryFunction shapeQueryCallback = ShapeQueryCallback;
714
715         /// <summary>
716         /// Get all shapes in the space that are overlapping the given shape.
717         /// </summary>
718         [EditorBrowsable(EditorBrowsableState.Never)]
719         public IReadOnlyList<ContactPointSet> ShapeQuery(Shape shape)
720         {
721             var list = new List<ContactPointSet>();
722             var gcHandle = GCHandle.Alloc(list);
723
724             NativeMethods.cpSpaceShapeQuery(space, shape.Handle, shapeQueryCallback.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
725
726             gcHandle.Free();
727             return list;
728         }
729
730 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
731 #pragma warning disable CA1416 // Validate platform compatibility
732         [MonoPInvokeCallback(typeof(SpaceObjectIteratorFunction))]
733 #pragma warning restore CA1416 // Validate platform compatibility
734 #endif
735         private static void EachConstraint(cpConstraint constraintHandle, voidptr_t data)
736         {
737             var list = (List<Constraint>)GCHandle.FromIntPtr(data).Target;
738
739             var constraint = Constraint.FromHandle(constraintHandle);
740
741             list.Add(constraint);
742         }
743
744         private static SpaceObjectIteratorFunction eachConstraint = EachConstraint;
745
746
747         /// <summary>
748         /// Get all constraints in the space.
749         /// </summary>
750         [EditorBrowsable(EditorBrowsableState.Never)]
751         public IReadOnlyList<Constraint> Constraints
752         {
753             get
754             {
755                 var list = new List<Constraint>();
756
757                 var gcHandle = GCHandle.Alloc(list);
758
759                 NativeMethods.cpSpaceEachConstraint(space, eachConstraint.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
760
761                 gcHandle.Free();
762
763                 return list;
764             }
765         }
766
767         /// <summary>
768         /// Update the collision detection info for the static shapes in the space.
769         /// </summary>
770         [EditorBrowsable(EditorBrowsableState.Never)]
771         public void ReindexStatic()
772         {
773             NativeMethods.cpSpaceReindexStatic(space);
774         }
775
776         /// <summary>
777         /// Update the collision detection data for a specific shape in the space.
778         /// </summary>
779         [EditorBrowsable(EditorBrowsableState.Never)]
780         public void ReindexShape(Shape shape)
781         {
782             NativeMethods.cpSpaceReindexShape(space, shape.Handle);
783         }
784
785         /// <summary>
786         /// Update the collision detection data for all shapes attached to a body.
787         /// </summary>
788         [EditorBrowsable(EditorBrowsableState.Never)]
789         public void ReindexShapesForBody(Body body)
790         {
791             NativeMethods.cpSpaceReindexShapesForBody(space, body.Handle);
792         }
793
794         /// <summary>
795         /// Switch the space to use a spatial hash as its spatial index.
796         /// </summary>
797         [EditorBrowsable(EditorBrowsableState.Never)]
798         public void UseSpatialHash(double dim, int count)
799         {
800             NativeMethods.cpSpaceUseSpatialHash(space, dim, count);
801         }
802
803         /// <summary>
804         /// Update the space for the given time step. Using a fixed time step is highly recommended.
805         /// Doing so will increase the efficiency of the contact persistence, requiring an order of
806         /// magnitude fewer iterations to resolve the collisions in the usual case. It is not the
807         /// same to call step 10 times with a dt of 0.1, or 100 times with a dt of 0.01 even if the
808         /// end result is that the simulation moved forward 100 units. Performing multiple calls
809         /// with a smaller dt creates a more stable and accurate simulation. Therefore, it sometimes
810         /// makes sense to have a little for loop around the step call.
811         /// </summary>
812         [EditorBrowsable(EditorBrowsableState.Never)]
813         public virtual void Step(double dt)
814         {
815             NativeMethods.cpSpaceStep(space, dt);
816         }
817
818         /// <summary>
819         /// Draw all objects in the space for debugging purposes.
820         /// </summary>
821         [EditorBrowsable(EditorBrowsableState.Never)]
822         public void DebugDraw(IDebugDraw debugDraw)
823         {
824             DebugDraw(debugDraw, DebugDrawFlags.All, DebugDrawColors.Default);
825         }
826
827         /// <summary>
828         /// Draw all objects in the space for debugging purposes using flags.
829         /// </summary>
830         [EditorBrowsable(EditorBrowsableState.Never)]
831         public void DebugDraw(IDebugDraw debugDraw, DebugDrawFlags flags)
832         {
833             DebugDraw(debugDraw, flags, DebugDrawColors.Default);
834         }
835
836         /// <summary>
837         /// Draw all objects in the space for debugging purposes using flags and colors.
838         /// </summary>
839         [EditorBrowsable(EditorBrowsableState.Never)]
840         public void DebugDraw(IDebugDraw debugDraw, DebugDrawFlags flags, DebugDrawColors colors)
841         {
842             var debugDrawOptions = new cpSpaceDebugDrawOptions();
843             IntPtr debugDrawOptionsPointer = debugDrawOptions.AcquireDebugDrawOptions(debugDraw, flags, colors);
844
845             NativeMethods.cpSpaceDebugDraw(space, debugDrawOptionsPointer);
846
847             debugDrawOptions.ReleaseDebugDrawOptions(debugDrawOptionsPointer);
848         }
849
850     }
851 }