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