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