1 /** 2d kinematic body */
2 
3 module re.phys.kin2d;
4 
5 import re.ecs.component;
6 import re.ecs.updatable;
7 import re.math;
8 import std.math;
9 import re.time;
10 
11 class KinBody2D : Component, Updatable {
12     mixin Reflect;
13     public float mass = 1.0f;
14     public Vector2 velocity = Vector2Zero;
15     public Vector2 max_velocity = Vector2Zero;
16     public Vector2 accel = Vector2Zero;
17     public Vector2 drag = Vector2Zero;
18     public float max_angular = 0;
19     public float angular_velocity = 0;
20     public float angular_accel = 0;
21     public float angular_drag = 0;
22 
23     // - transform
24 
25     @property public Vector2 pos() {
26         return transform.position2;
27     }
28 
29     @property public Vector2 pos(Vector2 value) {
30         return transform.position2 = value;
31     }
32 
33     @property public float angle() {
34         return transform.rotation_z;
35     }
36 
37     @property public float angle(float value) {
38         return transform.rotation_z = value;
39     }
40 
41     @property public float stdAngle() {
42         return -angle + std.math.PI / 2;
43     }
44 
45     @property public float stdAngle(float value) {
46         return angle = -(value - std.math.PI / 2);
47     }
48 
49     // misc. getters
50     @property public Vector2 momentum() {
51         return mass * velocity;
52     }
53 
54     override void setup() {
55     }
56 
57     void update() {
58         auto dt = Time.delta_time;
59 
60         // apply max velocity
61         auto vls = velocity.LengthSquared();
62         auto mvls = max_velocity.LengthSquared();
63         if (mvls > double.epsilon && vls > mvls) {
64             // convert to unit and rescale
65             auto unit_vel = velocity.Normalize();
66             auto ratio = mvls / vls;
67             auto reduction_fac = pow(ratio, (1 / 12f));
68             // smoothly reduce to max velocity
69             velocity = velocity * reduction_fac;
70         }
71 
72         // new velocity components
73         auto nvel_x = velocity.x;
74         auto nvel_y = velocity.y;
75 
76         // apply acceleration
77         nvel_x += accel.x * dt;
78         nvel_y += accel.y * dt;
79 
80         if (nvel_x > drag.x * dt) {
81             nvel_x = nvel_x - drag.x * dt;
82         }
83 
84         if (nvel_x < -drag.x * dt) {
85             nvel_x = nvel_x + drag.x * dt;
86         }
87 
88         if (abs(nvel_x) < drag.x * dt) {
89             nvel_x = 0;
90         }
91 
92         if (nvel_y > drag.y * dt) {
93             nvel_y = nvel_y - drag.y * dt;
94         }
95 
96         if (nvel_y < -drag.y * dt) {
97             nvel_y = nvel_y + drag.y * dt;
98         }
99 
100         if (abs(nvel_y) < drag.y * dt) {
101             nvel_y = 0;
102         }
103 
104         velocity = Vector2(nvel_x, nvel_y);
105 
106         apply_motion(velocity * dt);
107 
108         // apply max angular
109         if (max_angular > 0) {
110             if (angular_velocity > max_angular) {
111                 angular_velocity = max_angular;
112             }
113 
114             if (angular_velocity < -max_angular) {
115                 angular_velocity = -max_angular;
116             }
117         }
118 
119         // apply angular accel
120         angular_velocity += angular_accel * dt;
121 
122         // apply angular drag
123         if (angular_velocity > angular_drag * dt) {
124             angular_velocity -= angular_drag * dt;
125         }
126 
127         if (angular_velocity < -angular_drag * dt) {
128             angular_velocity += angular_drag * dt;
129         }
130 
131         transform.rotation_z = transform.rotation_z + (angular_velocity * dt);
132     }
133 
134     protected void apply_motion(Vector2 pos_delta) {
135         // apply motion to this object. typically this is passed onto a physics body.
136         transform.position2 = transform.position2 + pos_delta;
137     }
138 }
139 
140 @("phys-kin2d")
141 unittest {
142     import re.ng.scene : Scene2D;
143     import re.util.test : test_scene;
144 
145     class TestScene : Scene2D {
146         override void on_start() {
147             auto nt = create_entity("block", Vector2(0, 0));
148             // add kin body
149             auto bod = new KinBody2D();
150             bod.accel = Vector2(0, 9.8);
151             bod.angular_accel = C_PI_2;
152             nt.add_component(bod);
153         }
154     }
155 
156     auto test = test_scene(new TestScene());
157     test.game.run();
158 
159     // check conditions
160     assert(test.scene.get_entity("block").get_component!KinBody2D().pos.y > 0,
161             "KinBody2D did not move");
162     assert(test.scene.get_entity("block").get_component!KinBody2D()
163             .angular_velocity > 0, "KinBody2D did not rotate");
164 
165     test.game.destroy();
166 }