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 }