1 /*
2 Copyright (c) 2017-2018 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.logics.charactercontroller;
29 
30 import std.math;
31 
32 import dlib.core.memory;
33 import dlib.math.vector;
34 import dlib.math.matrix;
35 import dlib.math.transformation;
36 import dlib.math.utils;
37 
38 import dagon.logics.entity;
39 import dagon.logics.controller;
40 import dagon.physics.world;
41 import dagon.physics.rigidbody;
42 import dagon.physics.geometry;
43 import dagon.physics.shape;
44 import dagon.physics.raycast;
45 
46 /*
47  * CharacterController implements kinematic body on top of dmech dynamics: it allows direct
48  * velocity changes for a RigidBody. CharacterController is intended for generic action game
49  * character movement.
50  */
51 class CharacterController: EntityController
52 {
53     PhysicsWorld world;
54     RigidBody rbody;
55     bool onGround = false;
56     Vector3f direction = Vector3f(0, 0, 1);
57     float speed = 0.0f;
58     float jSpeed = 0.0f;
59     float maxVelocityChange = 0.75f;
60     float artificalGravity = 50.0f;
61     Vector3f rotation;
62     RigidBody floorBody;
63     Vector3f floorNormal;
64     bool flyMode = false;
65     bool clampY = true;
66     ShapeComponent sensor;
67     float selfTurn = 0.0f;
68 
69     this(Entity e, PhysicsWorld world, float mass, Geometry geom)
70     {
71         super(e);
72         this.world = world;
73         rbody = world.addDynamicBody(e.position);
74         rbody.bounce = 0.0f;
75         rbody.friction = 1.0f;
76         rbody.enableRotation = false;
77         rbody.useOwnGravity = true;
78         rbody.gravity = Vector3f(0.0f, -artificalGravity, 0.0f);
79         rbody.raycastable = false;
80         world.addShapeComponent(rbody, geom, Vector3f(0, 0, 0), mass);
81         rotation = Vector3f(0, 0, 0); // TODO: get from e
82     }
83 
84     ShapeComponent createSensor(Geometry geom, Vector3f point)
85     {
86         if (sensor is null)
87             sensor = world.addSensor(rbody, geom, point);
88         return sensor;
89     }
90     
91     void enableGravity(bool mode)
92     {
93         flyMode = !mode;
94         
95         if (mode)
96         {
97             rbody.gravity = Vector3f(0.0f, -artificalGravity, 0.0f);
98         }
99         else
100         {
101             rbody.gravity = Vector3f(0, 0, 0);
102         }
103     }
104     
105     void logicalUpdate()
106     {
107         Vector3f targetVelocity = direction * speed;
108 
109         if (!flyMode)
110         {
111             onGround = checkOnGround();
112         
113             if (onGround)
114                 rbody.gravity = Vector3f(0.0f, -artificalGravity * 0.1f, 0.0f);
115             else
116                 rbody.gravity = Vector3f(0.0f, -artificalGravity, 0.0f);
117                 
118             selfTurn = 0.0f;
119             if (onGround && floorBody)
120             {
121                 Vector3f relPos = rbody.position - floorBody.position;
122                 Vector3f rotVel = cross(floorBody.angularVelocity, relPos);
123                 targetVelocity += floorBody.linearVelocity;
124                 if (!floorBody.dynamic)
125                 {
126                     targetVelocity += rotVel;
127                     selfTurn = -floorBody.angularVelocity.y;
128                 }
129             }
130             
131             speed = 0.0f;
132             jSpeed = 0.0f;
133         }
134         else
135         {
136             speed *= 0.95f;
137             jSpeed *= 0.95f;
138         }
139         
140         Vector3f velocityChange = targetVelocity - rbody.linearVelocity;
141         velocityChange.x = clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
142         velocityChange.z = clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
143         
144         if (clampY && !flyMode)
145             velocityChange.y = 0;
146         else
147             velocityChange.y = clamp(velocityChange.y, -maxVelocityChange, maxVelocityChange);
148             
149         rbody.linearVelocity += velocityChange;
150     }
151 
152     override void update(double dt)
153     {        
154         entity.position = rbody.position;
155         entity.rotation = rbody.orientation; 
156         entity.transformation = rbody.transformation * scaleMatrix(entity.scaling);
157         entity.invTransformation = entity.transformation.inverse;
158     }
159 
160     bool checkOnGround()
161     {
162         floorBody = null;
163         CastResult cr;
164         bool hit = world.raycast(rbody.position, Vector3f(0, -1, 0), 10, cr, true, true);
165         if (hit)
166         {
167             floorBody = cr.rbody;
168             floorNormal = cr.normal;
169         }
170     
171         if (sensor)
172         {
173             if (sensor.numCollisions > 0)
174                 return true;
175         }
176         
177         return false;
178     }
179 
180     void turn(float angle)
181     {
182         rotation.y += angle;
183     }
184 
185     void move(Vector3f direction, float spd)
186     {
187         this.direction = direction;
188         this.speed = spd;
189     }
190 
191     void jump(float height)
192     {
193         if (onGround || flyMode)
194         {
195             jSpeed = jumpSpeed(height);
196             rbody.linearVelocity.y = jSpeed;
197         }
198     }
199 
200     float jumpSpeed(float jumpHeight)
201     {
202         return sqrt(2.0f * jumpHeight * artificalGravity);
203     }
204 }