2 * Copyright (c) 2023 Codefoco (codefoco@codefoco.com)
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:
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
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
24 using System.ComponentModel;
25 using System.Runtime.InteropServices;
26 using System.Collections.Generic;
28 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
32 using cpBody = System.IntPtr;
33 using cpArbiter = System.IntPtr;
34 using cpConstraint = System.IntPtr;
35 using cpShape = System.IntPtr;
36 using cpSpace = System.IntPtr;
37 using cpDataPointer = System.IntPtr;
38 using System.Diagnostics;
40 namespace Tizen.NUI.Physics2D.Chipmunk
43 /// Mass and moment are ignored when <see cref="BodyType"/> is <see cref="BodyType.Kinematic"/>
44 /// or <see cref="BodyType.Static"/>. Guessing the mass for a body is usually fine, but guessing
45 /// a moment of inertia can lead to a very poor simulation. It’s recommended to use Chipmunk’s
46 /// moment-calculating functions to estimate the moment for you.
48 [EditorBrowsable(EditorBrowsableState.Never)]
49 public class Body : IDisposable
51 #pragma warning disable IDE0032
52 private readonly cpBody body;
53 #pragma warning restore IDE0032
56 /// The native handle.
58 [EditorBrowsable(EditorBrowsableState.Never)]
59 public cpBody Handle => body;
62 /// Create a Dynamic Body with no mass and no moment.
64 [EditorBrowsable(EditorBrowsableState.Never)]
66 : this(BodyType.Dynamic)
70 internal Body(cpBody handle)
77 /// Create a <see cref="Body"/> of the given <see cref="BodyType"/>.
79 [EditorBrowsable(EditorBrowsableState.Never)]
80 public Body(BodyType type)
82 body = InitializeBody(type);
87 /// Creates a body with the given mass and moment.
89 [EditorBrowsable(EditorBrowsableState.Never)]
90 public Body(double mass, double moment) : this(mass, moment, BodyType.Dynamic)
95 /// Creates a body with the given mass and moment, of the give <see cref="BodyType"/>.
97 [EditorBrowsable(EditorBrowsableState.Never)]
98 public Body(double mass, double moment, BodyType type)
100 body = InitializeBody(type);
101 NativeMethods.cpBodySetMass(body, mass);
102 NativeMethods.cpBodySetMoment(body, moment);
106 void RegisterUserData()
108 cpDataPointer pointer = NativeInterop.RegisterHandle(this);
109 NativeMethods.cpBodySetUserData(body, pointer);
112 void ReleaseUserData()
114 cpDataPointer pointer = NativeMethods.cpBodyGetUserData(body);
115 NativeInterop.ReleaseHandle(pointer);
119 /// Get a <see cref="Body"/> object from a native cpBody handle.
121 [EditorBrowsable(EditorBrowsableState.Never)]
122 public static Body FromHandle(cpBody body)
124 cpDataPointer handle = NativeMethods.cpBodyGetUserData(body);
125 return NativeInterop.FromIntPtr<Body>(handle);
129 /// Get the managed <see cref="Body"/> object from the native handle.
131 [EditorBrowsable(EditorBrowsableState.Never)]
132 public static Body FromHandleSafe(cpBody nativeBodyHandle)
134 if (nativeBodyHandle == IntPtr.Zero)
139 return FromHandle(nativeBodyHandle);
142 private static cpBody InitializeBody(BodyType type)
144 if (type == BodyType.Kinematic)
146 return NativeMethods.cpBodyNewKinematic();
149 if (type == BodyType.Static)
151 return NativeMethods.cpBodyNewStatic();
154 return NativeMethods.cpBodyNew(0.0, 0.0);
158 /// Destroy and free the body.
160 [EditorBrowsable(EditorBrowsableState.Never)]
166 space.RemoveBody(this);
169 NativeMethods.cpBodyFree(body);
173 /// Dispose the body.
175 protected virtual void Dispose(bool dispose)
179 Debug.WriteLine("Disposing body {0} on finalizer... (consider Dispose explicitly)", body);
186 /// Dispose the body.
188 [EditorBrowsable(EditorBrowsableState.Never)]
189 public void Dispose()
192 GC.SuppressFinalize(this);
198 /// Arbitrary user data.
200 [EditorBrowsable(EditorBrowsableState.Never)]
201 public object Data { get; set; }
204 /// Rotation of the body in radians. When changing the rotation, you may also want to call
205 /// <see cref="Space.ReindexShapesForBody"/> to update the collision detection information
206 /// for the attached shapes if you plan to make any queries against the space. A body
207 /// rotates around its center of gravity, not its position.
209 [EditorBrowsable(EditorBrowsableState.Never)]
212 get => NativeMethods.cpBodyGetAngle(body);
213 set => NativeMethods.cpBodySetAngle(body, value);
217 /// Set body position and rotation angle (in radians)
219 /// <param name="position"></param>
220 /// <param name="angle"></param>
221 [EditorBrowsable(EditorBrowsableState.Never)]
222 public void SetTransform(Vect position, double angle)
224 NativeMethods.cpBodySetTransform(body, position, angle);
228 /// Get body position and rotation angle (in radians)
230 /// <param name="position"></param>
231 /// <param name="angle"></param>
232 [EditorBrowsable(EditorBrowsableState.Never)]
233 public void GetTransform(out Vect position, out double angle)
235 NativeMethods.cpBodyGetTransform(body, out position, out angle);
239 /// The way the body behaves in physics simulations.
241 [EditorBrowsable(EditorBrowsableState.Never)]
244 get => (BodyType)NativeMethods.cpBodyGetType(body);
245 set => NativeMethods.cpBodySetType(body, (int)value);
249 /// Mass of the rigid body. Mass does not have to be expressed in any particular units, but
250 /// relative masses should be consistent.
252 [EditorBrowsable(EditorBrowsableState.Never)]
255 get => NativeMethods.cpBodyGetMass(body);
256 set => NativeMethods.cpBodySetMass(body, value);
260 /// Moment of inertia of the body. The mass tells you how hard it is to push an object,
261 /// the MoI tells you how hard it is to spin the object. Don't try to guess the MoI, use the
262 /// MomentFor*() functions to estimate it, or the physics may behave strangely.
264 [EditorBrowsable(EditorBrowsableState.Never)]
267 get => NativeMethods.cpBodyGetMoment(body);
268 set => NativeMethods.cpBodySetMoment(body, value);
272 /// Get the space this body is associated with, or null if it is not currently associated.
274 [EditorBrowsable(EditorBrowsableState.Never)]
279 cpSpace space = NativeMethods.cpBodyGetSpace(body);
280 return Space.FromHandleSafe(space);
285 /// Position of the body. When changing the position, you may also want to call
286 /// <see cref="Space.ReindexShapesForBody"/> to update the collision detection information
287 /// for the attached shapes if you plan to make any queries against the space.
289 [EditorBrowsable(EditorBrowsableState.Never)]
292 get => NativeMethods.cpBodyGetPosition(body);
293 set => NativeMethods.cpBodySetPosition(body, value);
297 /// Location of the center of gravity in body-local coordinates. The default value is
298 /// (0, 0), meaning the center of gravity is the same as the position of the body.
300 [EditorBrowsable(EditorBrowsableState.Never)]
301 public Vect CenterOfGravity
303 get => NativeMethods.cpBodyGetCenterOfGravity(body);
304 set => NativeMethods.cpBodySetCenterOfGravity(body, value);
308 /// Linear velocity of the center of gravity of the body.
310 [EditorBrowsable(EditorBrowsableState.Never)]
313 get => NativeMethods.cpBodyGetVelocity(body);
314 set => NativeMethods.cpBodySetVelocity(body, value);
318 /// Force applied to the center of gravity of the body. This value is reset for every time
321 [EditorBrowsable(EditorBrowsableState.Never)]
324 get => NativeMethods.cpBodyGetForce(body);
325 set => NativeMethods.cpBodySetForce(body, value);
329 /// The angular velocity of the body in radians per second.
331 [EditorBrowsable(EditorBrowsableState.Never)]
332 public double AngularVelocity
334 get => NativeMethods.cpBodyGetAngularVelocity(body);
335 set => NativeMethods.cpBodySetAngularVelocity(body, value);
339 /// The torque applied to the body. This value is reset for every time step.
341 [EditorBrowsable(EditorBrowsableState.Never)]
344 get => NativeMethods.cpBodyGetTorque(body);
345 set => NativeMethods.cpBodySetTorque(body, value);
348 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
349 #pragma warning disable CA1416 // Validate platform compatibility
350 [MonoPInvokeCallback(typeof(BodyArbiterIteratorFunction))]
351 #pragma warning restore CA1416 // Validate platform compatibility
353 private static void AddEachArbiterToArray(cpBody body, cpArbiter arbiter, IntPtr data)
355 var list = (List<Arbiter>)GCHandle.FromIntPtr(data).Target;
356 var a = new Arbiter(arbiter);
360 private static BodyArbiterIteratorFunction eachArbiterFunc = AddEachArbiterToArray;
363 /// The rotation vector for the body. Can be used with cpvrotate() or cpvunrotate() to perform fast rotations.
365 [EditorBrowsable(EditorBrowsableState.Never)]
366 public Vect Rotation => NativeMethods.cpBodyGetRotation(body);
369 /// Get the list of body Arbiters
371 [EditorBrowsable(EditorBrowsableState.Never)]
372 public IReadOnlyList<Arbiter> Arbiters
376 var list = new List<Arbiter>();
377 var gcHandle = GCHandle.Alloc(list);
378 NativeMethods.cpBodyEachArbiter(body, eachArbiterFunc.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
384 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
385 #pragma warning disable CA1416 // Validate platform compatibility
386 [MonoPInvokeCallback(typeof(BodyArbiterIteratorFunction))]
387 #pragma warning restore CA1416 // Validate platform compatibility
389 private static void AddEachConstraintToArray(cpBody body, cpConstraint constraint, IntPtr data)
391 var list = (List<Constraint>)GCHandle.FromIntPtr(data).Target;
392 var c = Constraint.FromHandle(constraint);
396 private static BodyConstraintIteratorFunction eachConstraintFunc = AddEachConstraintToArray;
399 /// All constraints attached to the body
401 [EditorBrowsable(EditorBrowsableState.Never)]
402 public IReadOnlyList<Constraint> Constraints
406 var list = new List<Constraint>();
407 var gcHandle = GCHandle.Alloc(list);
408 NativeMethods.cpBodyEachConstraint(body, eachConstraintFunc.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
410 return list.ToArray();
414 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
415 #pragma warning disable CA1416 // Validate platform compatibility
416 [MonoPInvokeCallback(typeof(BodyShapeIteratorFunction))]
417 #pragma warning restore CA1416 // Validate platform compatibility
419 private static void AddEachShapeToArray(cpBody body, cpShape shape, IntPtr data)
421 var list = (List<Shape>)GCHandle.FromIntPtr(data).Target;
422 var s = Shape.FromHandle(shape);
426 private static BodyShapeIteratorFunction eachShapeFunc = AddEachShapeToArray;
429 /// All shapes attached to the body
431 [EditorBrowsable(EditorBrowsableState.Never)]
432 public IReadOnlyList<Shape> Shapes
436 var list = new List<Shape>();
437 var gcHandle = GCHandle.Alloc(list);
438 NativeMethods.cpBodyEachShape(body, eachShapeFunc.ToFunctionPointer(), GCHandle.ToIntPtr(gcHandle));
440 return list.ToArray();
445 /// Returns true if body is sleeping.
447 [EditorBrowsable(EditorBrowsableState.Never)]
448 public bool IsSleeping => NativeMethods.cpBodyIsSleeping(body) != 0;
453 /// Reset the idle timer on a body.
454 /// If it was sleeping, wake it and any other bodies it was touching.
456 [EditorBrowsable(EditorBrowsableState.Never)]
457 public void Activate() => NativeMethods.cpBodyActivate(body);
460 /// Similar in function to Activate(). Activates all bodies touching body. If filter is not NULL, then only bodies touching through filter will be awoken.
462 /// <param name="filter"></param>
463 [EditorBrowsable(EditorBrowsableState.Never)]
464 public void ActivateStatic(Shape filter) => NativeMethods.cpBodyActivateStatic(body, filter.Handle);
467 /// Add the local force force to body as if applied from the body local point.
469 /// <param name="force"></param>
470 /// <param name="point"></param>
471 [EditorBrowsable(EditorBrowsableState.Never)]
472 public void ApplyForceAtLocalPoint(Vect force, Vect point)
474 NativeMethods.cpBodyApplyForceAtLocalPoint(body, force, point);
480 /// <param name="torque"></param>
481 [EditorBrowsable(EditorBrowsableState.Never)]
482 public void ApplyTorque(double torque)
484 NativeMethods.cpBodyApplyTorque(body, torque);
488 /// Apply angular impulse.
490 /// <param name="impulse"></param>
491 [EditorBrowsable(EditorBrowsableState.Never)]
492 public void ApplyAngularImpulse(double impulse)
494 NativeMethods.cpBodyApplyAngularImpulse(body, impulse);
498 /// Add the force force to body as if applied from the world point.
499 /// People are sometimes confused by the difference between a force and an impulse.
500 /// An impulse is a very large force applied over a very short period of time.
501 /// Some examples are a ball hitting a wall or cannon firing.
502 /// Chipmunk treats impulses as if they occur instantaneously by adding directly to the velocity of an object.
503 /// Both impulses and forces are affected the mass of an object.
504 /// Doubling the mass of the object will halve the effect.
506 /// <param name="force"></param>
507 /// <param name="point"></param>
508 [EditorBrowsable(EditorBrowsableState.Never)]
509 public void ApplyForceAtWorldPoint(Vect force, Vect point)
511 NativeMethods.cpBodyApplyForceAtWorldPoint(body, force, point);
515 /// Apply an impulse to a body. Both the impulse and point are expressed in world coordinates.
517 /// <param name="impulse"></param>
518 /// <param name="point"></param>
519 [EditorBrowsable(EditorBrowsableState.Never)]
520 public void ApplyImpulseAtWorldPoint(Vect impulse, Vect point)
522 NativeMethods.cpBodyApplyImpulseAtWorldPoint(body, impulse, point);
526 /// Apply an impulse to a body. Both the impulse and point are expressed in body local coordinates.
528 /// <param name="impulse"></param>
529 /// <param name="point"></param>
530 [EditorBrowsable(EditorBrowsableState.Never)]
531 public void ApplyImpulseAtLocalPoint(Vect impulse, Vect point)
533 NativeMethods.cpBodyApplyImpulseAtLocalPoint(body, impulse, point);
537 /// Forces a body to fall asleep immediately even if it’s in midair. Cannot be called from a callback.
539 [EditorBrowsable(EditorBrowsableState.Never)]
542 NativeMethods.cpBodySleep(body);
546 /// When objects in Chipmunk sleep, they sleep as a group of all objects that are touching or jointed together.
547 /// When an object is woken up, all of the objects in its group are woken up.
548 /// SleepWithGroup() allows you group sleeping objects together. It acts identically to Sleep() if you pass null as
549 /// group by starting a new group.
550 /// If you pass a sleeping body for group, body will be awoken when group is awoken.
551 /// You can use this to initialize levels and start stacks of objects in a pre-sleeping state.
553 /// <param name="group"></param>
554 [EditorBrowsable(EditorBrowsableState.Never)]
555 public void SleepWithGroup(Body group)
557 NativeMethods.cpBodySleepWithGroup(body, group != null ? group.Handle : IntPtr.Zero);
560 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
561 #pragma warning disable CA1416 // Validate platform compatibility
562 [MonoPInvokeCallback(typeof(BodyVelocityFunction))]
563 #pragma warning restore CA1416 // Validate platform compatibility
565 private static void BodyVelocityFunctionCallback(cpBody bodyHandle, Vect gravity, double damping, double dt)
567 var body = FromHandle(bodyHandle);
569 body.velocityUpdateFunction(body, gravity, damping, dt);
572 private static BodyVelocityFunction BodyVelocityFunctionCallbackDelegate = BodyVelocityFunctionCallback;
574 private Action<Body, Vect, double, double> velocityUpdateFunction;
576 /// Set the callback used to update a body's velocity.
577 /// Parameters: body, gravity, damping and deltaTime
579 [EditorBrowsable(EditorBrowsableState.Never)]
580 public Action<Body, Vect, double, double> VelocityUpdateFunction
582 get => velocityUpdateFunction;
585 velocityUpdateFunction = value;
587 IntPtr callbackPointer;
590 callbackPointer = NativeMethods.cpBodyGetDefaultVelocityUpdateFunc();
592 callbackPointer = BodyVelocityFunctionCallbackDelegate.ToFunctionPointer();
594 NativeMethods.cpBodySetVelocityUpdateFunc(body, callbackPointer);
598 #if __IOS__ || __TVOS__ || __WATCHOS__ || __MACCATALYST__
599 #pragma warning disable CA1416 // Validate platform compatibility
600 [MonoPInvokeCallback(typeof(BodyPositionFunction))]
601 #pragma warning restore CA1416 // Validate platform compatibility
603 private static void BodyPositionFunctionCallback(cpBody bodyHandle, double dt)
605 var body = FromHandle(bodyHandle);
607 body.positionUpdateFunction(body, dt);
610 private static BodyPositionFunction BodyUpdateFunctionCallbackDelegate = BodyPositionFunctionCallback;
612 private Action<Body, double> positionUpdateFunction;
615 /// Set the callback used to update a body's position.
616 /// Parameters: body, deltaTime
618 [EditorBrowsable(EditorBrowsableState.Never)]
619 public Action<Body, double> PositionUpdateFunction
621 get => positionUpdateFunction;
624 positionUpdateFunction = value;
626 IntPtr callbackPointer;
629 callbackPointer = NativeMethods.cpBodyGetDefaultPositionUpdateFunc();
631 callbackPointer = BodyUpdateFunctionCallbackDelegate.ToFunctionPointer();
633 NativeMethods.cpBodySetPositionUpdateFunc(body, callbackPointer);
638 /// Default velocity integration function..
640 /// <param name="gravity"></param>
641 /// <param name="damping"></param>
642 /// <param name="dt"></param>
643 [EditorBrowsable(EditorBrowsableState.Never)]
644 public void UpdateVelocity(Vect gravity, double damping, double dt)
646 NativeMethods.cpBodyUpdateVelocity(body, gravity, damping, dt);
650 /// Default position integration function.
652 /// <param name="dt"></param>
653 [EditorBrowsable(EditorBrowsableState.Never)]
654 public void UpdatePosition(double dt)
656 NativeMethods.cpBodyUpdatePosition(body, dt);
660 /// Convert body relative/local coordinates to absolute/world coordinates.
662 /// <param name="point"></param>
663 /// <returns></returns>
664 [EditorBrowsable(EditorBrowsableState.Never)]
665 public Vect LocalToWorld(Vect point)
667 return NativeMethods.cpBodyLocalToWorld(body, point);
671 /// Convert body absolute/world coordinates to relative/local coordinates.
673 /// <param name="point"></param>
674 /// <returns></returns>
675 [EditorBrowsable(EditorBrowsableState.Never)]
676 public Vect WorldToLocal(Vect point)
678 return NativeMethods.cpBodyWorldToLocal(body, point);
682 /// Get the velocity on a body (in world units) at a point on the body in world coordinates.
684 /// <param name="point"></param>
685 /// <returns></returns>
686 [EditorBrowsable(EditorBrowsableState.Never)]
687 public Vect GetVelocityAtWorldPoint(Vect point)
689 return NativeMethods.cpBodyGetVelocityAtWorldPoint(body, point);
693 /// Get the velocity on a body (in world units) at a point on the body in local coordinates.
695 /// <param name="point"></param>
696 /// <returns></returns>
697 [EditorBrowsable(EditorBrowsableState.Never)]
698 public Vect GetVelocityAtLocalPoint(Vect point)
700 return NativeMethods.cpBodyGetVelocityAtLocalPoint(body, point);
704 /// Get the kinetic energy of a body.
706 [EditorBrowsable(EditorBrowsableState.Never)]
707 public double KineticEnergy => NativeMethods.cpBodyKineticEnergy(body);
710 /// Calculate the moment of inertia for a solid box centered on the body.
712 /// <param name="mass"></param>
713 /// <param name="width"></param>
714 /// <param name="height"></param>
715 /// <returns></returns>
716 [EditorBrowsable(EditorBrowsableState.Never)]
717 public static double MomentForBox(double mass, double width, double height)
719 return NativeMethods.cpMomentForBox(mass, width, height);
723 /// Get the list of all bodies in contact with this one
725 [EditorBrowsable(EditorBrowsableState.Never)]
726 public IReadOnlyList<Body> AllContactedBodies
730 int count = NativeMethods.cpBodyGetContactedBodiesCount(body);
733 return Array.Empty<Body>();
735 IntPtr ptrBodies = Marshal.AllocHGlobal(IntPtr.Size * count);
736 NativeMethods.cpBodyGetUserDataContactedBodies(body, ptrBodies);
738 IntPtr[] userDataArray = new IntPtr[count];
740 Marshal.Copy(ptrBodies, userDataArray, 0, count);
742 Marshal.FreeHGlobal(ptrBodies);
744 Body[] bodies = new Body[count];
746 for (int i = 0; i < count; i++)
748 Body b = NativeInterop.FromIntPtr<Body>(userDataArray[i]);
757 /// Check if a Body is in contact with another
759 /// <param name="other"></param>
760 /// <returns></returns>
761 [EditorBrowsable(EditorBrowsableState.Never)]
762 public bool ContactWith(Body other)
764 return NativeMethods.cpBodyContactWith(body, other.body) != 0;