1 /*
2 Copyright (c) 2017-2020 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.ext.physics.charactercontroller;
29 
30 import std.math;
31 
32 import dlib.math.vector;
33 import dlib.math.matrix;
34 import dlib.math.transformation;
35 import dlib.math.utils;
36 
37 import dagon.core.event;
38 import dagon.core.time;
39 import dagon.graphics.entity;
40 
41 import dagon.ext.physics.rigidbody;
42 import dagon.ext.physics.geometry;
43 import dagon.ext.physics.shape;
44 import dagon.ext.physics.world;
45 import dagon.ext.physics.raycast;
46 
47 /*
48  * CharacterController implements kinematic body on top of dmech dynamics: it allows direct
49  * velocity changes for a RigidBody. CharacterController is intended for generic action game
50  * character movement.
51  */
52 class CharacterController: EntityComponent
53 {
54     PhysicsWorld world;
55     RigidBody rbody;
56     bool onGround = false;
57     Vector3f direction = Vector3f(0, 0, 1);
58     float speed = 0.0f;
59     float jumpSpeed = 0.0f;
60     float maxVelocityChange = 0.75f;
61     float artificalGravity = 50.0f;
62     //Vector3f rotation;
63     RigidBody floorBody;
64     Vector3f floorNormal;
65     bool flyMode = false;
66     bool clampY = true;
67     ShapeComponent sensor;
68     //float selfTurn = 0.0f;
69     Matrix4x4f prevTransformation;
70 
71     this(EventManager em, Entity e, PhysicsWorld world, float mass, Geometry geom)
72     {
73         super(em, e);
74         this.world = world;
75         rbody = world.addDynamicBody(e.position);
76         //rbody.orientation = e.rotation;
77         rbody.bounce = 0.0f;
78         rbody.friction = 1.0f;
79         rbody.enableRotation = false;
80         rbody.useOwnGravity = true;
81         rbody.gravity = Vector3f(0.0f, -artificalGravity, 0.0f);
82         rbody.raycastable = false;
83         world.addShapeComponent(rbody, geom, Vector3f(0, 0, 0), mass);
84         //rotation = Vector3f(0, 0, 0); // TODO: get from e
85         prevTransformation = Matrix4x4f.identity;
86     }
87 
88     ShapeComponent createSensor(Geometry geom, Vector3f point)
89     {
90         if (sensor is null)
91             sensor = world.addSensor(rbody, geom, point);
92         return sensor;
93     }
94     
95     void enableGravity(bool mode)
96     {
97         flyMode = !mode;
98         
99         if (mode)
100         {
101             rbody.gravity = Vector3f(0.0f, -artificalGravity, 0.0f);
102         }
103         else
104         {
105             rbody.gravity = Vector3f(0, 0, 0);
106         }
107     }
108     
109     void logicalUpdate()
110     {
111         Vector3f targetVelocity = direction * speed;
112 
113         if (!flyMode)
114         {
115             onGround = checkOnGround();
116         
117             if (onGround)
118                 rbody.gravity = Vector3f(0.0f, -artificalGravity * 0.1f, 0.0f);
119             else
120                 rbody.gravity = Vector3f(0.0f, -artificalGravity, 0.0f);
121                 
122             //selfTurn = 0.0f;
123             if (onGround && floorBody)
124             {
125                 Vector3f relPos = rbody.position - floorBody.position;
126                 Vector3f rotVel = cross(floorBody.angularVelocity, relPos);
127                 targetVelocity += floorBody.linearVelocity;
128                 if (!floorBody.dynamic)
129                 {
130                     targetVelocity += rotVel;
131                     //selfTurn = -floorBody.angularVelocity.y;
132                 }
133             }
134             
135             speed = 0.0f;
136             jumpSpeed = 0.0f;
137         }
138         else
139         {
140             speed *= 0.95f;
141             jumpSpeed *= 0.95f;
142         }
143         
144         Vector3f velocityChange = targetVelocity - rbody.linearVelocity;
145         velocityChange.x = clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
146         velocityChange.z = clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
147         
148         if (clampY && !flyMode)
149             velocityChange.y = 0;
150         else
151             velocityChange.y = clamp(velocityChange.y, -maxVelocityChange, maxVelocityChange);
152             
153         rbody.linearVelocity += velocityChange;
154     }
155 
156     override void update(Time t)
157     {
158         entity.position = rbody.position;
159         entity.rotation = rbody.orientation;
160         
161         entity.transformation = rbody.transformation * scaleMatrix(entity.scaling);
162         entity.invTransformation = entity.transformation.inverse;
163         entity.prevTransformation = prevTransformation;
164         
165         entity.absoluteTransformation = entity.transformation;
166         entity.invAbsoluteTransformation = entity.invTransformation;
167         entity.prevAbsoluteTransformation = entity.prevTransformation;
168 
169         prevTransformation = entity.transformation;
170     }
171 
172     bool checkOnGround()
173     {
174         floorBody = null;
175         CastResult cr;
176         bool hit = world.raycast(rbody.position, Vector3f(0, -1, 0), 10, cr, true, true);
177         if (hit)
178         {
179             floorBody = cr.rbody;
180             floorNormal = cr.normal;
181         }
182     
183         if (sensor)
184         {
185             if (sensor.numCollisions > 0)
186                 return true;
187         }
188         
189         return false;
190     }
191 
192     /*
193     void turn(float angle)
194     {
195         rotation.y += angle;
196     }
197     */
198 
199     void move(Vector3f direction, float spd)
200     {
201         this.direction = direction;
202         this.speed = spd;
203     }
204 
205     void jump(float height)
206     {
207         if (onGround || flyMode)
208         {
209             jumpSpeed = sqrt(2.0f * height * artificalGravity);
210             rbody.linearVelocity.y = jumpSpeed;
211         }
212     }
213 }