]> git.lizzy.rs Git - shadowclad.git/blob - src/game/player.c
Resolve slides before crossing cell boundaries so we don't get stuck on corners
[shadowclad.git] / src / game / player.c
1 /**
2  * Copyright 2019-2020 Iwo 'Outfrost' Bujkiewicz
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7  */
8
9 #include "player.h"
10
11 #include "engine/asset.h"
12 #include "engine/logger.h"
13 #include "engine/render.h"
14
15 #include "level.h"
16
17 static const float movementSpeed = 1.5f;
18 static const float collisionRadius = 0.5f;
19
20 Scene* playerCharacter;
21 Scene* playerProjectedMovement;
22 static Transform screenToWorldMovementTransform;
23 static Vector worldMovementUp;
24 static Vector worldMovementDown;
25 static Vector worldMovementLeft;
26 static Vector worldMovementRight;
27 static Direction movementDirection;
28
29 static void movePlayer(Vector direction, float delta);
30 static Vector worldMovementDirection(float x, float y);
31
32
33
34 void initPlayer() {
35         screenToWorldMovementTransform = identity();
36         rotate(&screenToWorldMovementTransform, (Vector) { 0.0f, 1.0f, 0.0f }, - TAU / 8.0f);
37
38         worldMovementUp = worldMovementDirection(0.0f, 1.0f);
39         worldMovementDown = worldMovementDirection(0.0f, -1.0f);
40         worldMovementLeft = worldMovementDirection(-1.0f, 0.0f);
41         worldMovementRight = worldMovementDirection(1.0f, 0.0f);
42
43         playerCharacter = newScene();
44         cameraAnchor = playerCharacter;
45         playerCharacter->solid = importSolid("assets/playercharacter.3ds");
46
47         playerProjectedMovement = newScene();
48 }
49
50 void spawnPlayer(Transform transform) {
51         playerCharacter->transform = transform;
52         reparentScene(playerCharacter, currentScene);
53         reparentScene(playerProjectedMovement, currentScene);
54 }
55
56 void updatePlayer(float delta) {
57         Vector direction = zeroVector();
58         if (movementDirection & DIRECTION_UP) {
59                 direction = addVectors(direction, worldMovementUp);
60         }
61         if (movementDirection & DIRECTION_DOWN) {
62                 direction = addVectors(direction, worldMovementDown);
63         }
64         if (movementDirection & DIRECTION_LEFT) {
65                 direction = addVectors(direction, worldMovementLeft);
66         }
67         if (movementDirection & DIRECTION_RIGHT) {
68                 direction = addVectors(direction, worldMovementRight);
69         }
70         movePlayer(direction, delta);
71 }
72
73 void startMovement(Direction direction) {
74         movementDirection |= direction;
75 }
76
77 void stopMovement(Direction direction) {
78         movementDirection &= ~direction;
79 }
80
81 static void movePlayer(Vector direction, float delta) {
82         direction = clampMagnitude(direction, 1.0f);
83         Vector displacement = scaleVector(direction, delta * movementSpeed);
84
85         playerProjectedMovement->transform = playerCharacter->transform;
86
87         Vector initialPosition = translationOf(playerCharacter->transform);
88         Vector position = initialPosition;
89
90         GridLocation location = gridLocationFromPosition(position);
91         bool enteredNewCell = true;
92
93         while (enteredNewCell) {
94                 enteredNewCell = false;
95                 Obstacle obstacle = getObstacles(location);
96
97                 // Eliminate redundant corner checks
98                 if (obstacle & OBSTACLE_XP) {
99                         obstacle &= ~(OBSTACLE_XP_ZP | OBSTACLE_XP_ZN);
100                 }
101                 if (obstacle & OBSTACLE_XN) {
102                         obstacle &= ~(OBSTACLE_XN_ZP | OBSTACLE_XN_ZN);
103                 }
104                 if (obstacle & OBSTACLE_ZP) {
105                         obstacle &= ~(OBSTACLE_XP_ZP | OBSTACLE_XN_ZP);
106                 }
107                 if (obstacle & OBSTACLE_ZN) {
108                         obstacle &= ~(OBSTACLE_XP_ZN | OBSTACLE_XN_ZN);
109                 }
110
111                 float edgeXp = cellBoundaryCoord(location.x + 1);
112                 float edgeXn = cellBoundaryCoord(location.x);
113                 float edgeZp = cellBoundaryCoord(location.z + 1);
114                 float edgeZn = cellBoundaryCoord(location.z);
115                 float distanceXp = edgeXp - position.x;
116                 if (obstacle & OBSTACLE_XP) distanceXp -= collisionRadius;
117                 float distanceXn = edgeXn - position.x;
118                 if (obstacle & OBSTACLE_XN) distanceXn += collisionRadius;
119                 float distanceZp = edgeZp - position.z;
120                 if (obstacle & OBSTACLE_ZP) distanceZp -= collisionRadius;
121                 float distanceZn = edgeZn - position.z;
122                 if (obstacle & OBSTACLE_ZN) distanceZn += collisionRadius;
123
124                 // Check all edges for intersecting already
125                 if (distanceXp < 0.0f) {
126                         position.x += distanceXp;
127                         displacement = growVectorNoFlip(displacement, distanceXp);
128                         distanceXp = 0.0f;
129                 }
130                 if (distanceXn > 0.0f) {
131                         position.x += distanceXn;
132                         displacement = growVectorNoFlip(displacement, - distanceXn);
133                         distanceXn = 0.0f;
134                 }
135                 if (distanceZp < 0.0f) {
136                         position.z += distanceZp;
137                         displacement = growVectorNoFlip(displacement, distanceZp);
138                         distanceZp = 0.0f;
139                 }
140                 if (distanceZn > 0.0f) {
141                         position.z += distanceZn;
142                         displacement = growVectorNoFlip(displacement, - distanceZn);
143                         distanceZn = 0.0f;
144                 }
145
146                 // Calculate direct movement limits
147                 Vector displacementToLimit = displacement;
148                 bool reachedXp = false;
149                 bool reachedXn = false;
150                 bool reachedZp = false;
151                 bool reachedZn = false;
152                 if (displacementToLimit.x > distanceXp) {
153                         displacementToLimit = scaleVector(
154                                 displacementToLimit, distanceXp / displacementToLimit.x);
155                         reachedXp = true;
156                 }
157                 if (displacementToLimit.x < distanceXn) {
158                         displacementToLimit = scaleVector(
159                                 displacementToLimit, distanceXn / displacementToLimit.x);
160                         reachedXn = true;
161                 }
162                 if (displacementToLimit.z > distanceZp) {
163                         displacementToLimit = scaleVector(
164                                 displacementToLimit, distanceZp / displacementToLimit.z);
165                         reachedZp = true;
166                 }
167                 if (displacementToLimit.z < distanceZn) {
168                         displacementToLimit = scaleVector(
169                                 displacementToLimit, distanceZn / displacementToLimit.z);
170                         reachedZn = true;
171                 }
172                 // This is how far we can move until we collide or leave the grid cell
173                 position = addVectors(position, displacementToLimit);
174                 displacement = subtractVectors(displacement, displacementToLimit);
175                 // Update distances
176                 distanceXp -= displacementToLimit.x;
177                 distanceXn -= displacementToLimit.x;
178                 distanceZp -= displacementToLimit.z;
179                 distanceZn -= displacementToLimit.z;
180
181                 // Slide along obstacles
182                 if ((reachedXp && obstacle & OBSTACLE_XP)
183                         || (reachedXn && obstacle & OBSTACLE_XN))
184                 {
185                         float dz = displacement.z;
186                         if (dz > distanceZp) {
187                                 dz = distanceZp;
188                                 reachedZp = true;
189                         }
190                         if (dz < distanceZn) {
191                                 dz = distanceZn;
192                                 reachedZn = true;
193                         }
194                         position.z += dz;
195                         displacement = scaleVector(displacement, 1.0f - (dz / displacement.z));
196                 }
197
198                 if ((reachedZp && obstacle & OBSTACLE_ZP)
199                         || (reachedZn && obstacle & OBSTACLE_ZN))
200                 {
201                         float dx = displacement.x;
202                         if (dx > distanceXp) {
203                                 dx = distanceXp;
204                                 reachedXp = true;
205                         }
206                         if (dx < distanceXn) {
207                                 dx = distanceXn;
208                                 reachedXn = true;
209                         }
210                         position.x += dx;
211                         displacement = scaleVector(displacement, 1.0f - (dx / displacement.x));
212                 }
213
214                 // Resolve crossing cell boundaries
215                 // in reverse order to direct movement limits, because
216                 // we only want to cross the closest cell boundary.
217                 if (reachedZn && !(obstacle & OBSTACLE_ZN)) {
218                         // Enter new grid cell
219                         location.z -= 1;
220                         enteredNewCell = true;
221                 }
222
223                 if (!enteredNewCell && reachedZp && !(obstacle & OBSTACLE_ZP)) {
224                         location.z += 1;
225                         enteredNewCell = true;
226                 }
227
228                 if (!enteredNewCell && reachedXn && !(obstacle & OBSTACLE_XN)) {
229                         location.x -= 1;
230                         enteredNewCell = true;
231                 }
232
233                 if (!enteredNewCell && reachedXp && !(obstacle & OBSTACLE_XP)) {
234                         location.x += 1;
235                         enteredNewCell = true;
236                 }
237         }
238
239         translate(&playerCharacter->transform, subtractVectors(position, initialPosition));
240 }
241
242 static Vector worldMovementDirection(float x, float y) {
243         Vector direction = (Vector) { x, 0.0f, -y };
244         direction = normalized(
245                 applyTransform(screenToWorldMovementTransform, direction));
246         return direction;
247 }