-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmyglwidget.cpp
More file actions
331 lines (264 loc) · 9.73 KB
/
myglwidget.cpp
File metadata and controls
331 lines (264 loc) · 9.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#define _USE_MATH_DEFINES
#include <GL/glu.h>
#include <QApplication>
#include <QTime>
#include "myglwidget.h"
#include <cmath>
#include "cannon.h"
#include "corridor.h"
#include "player.h"
#include <QKeyEvent>
MyGLWidget::MyGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
// Initialize timer for animations
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(16); // ~60 FPS
// Initialize last frame time
m_lastFrameTime = QTime::currentTime();
// Position the player's sword at the center of the cylindrical grid
// Using (0.0, 0.0) which places it in the center of the grid
positionPlayerOnGrid(0.0, 0.0);
// Set focus policy to explicitly capture keyboard input
setFocusPolicy(Qt::StrongFocus);
// Accept keyboard input when in focus
setFocus();
// Do not initialize m_corridor here (OpenGL not ready)
m_corridor = nullptr;
}
MyGLWidget::~MyGLWidget()
{
// Clean up resources
delete timer;
if (m_corridor) delete m_corridor;
}
void MyGLWidget::initializeGL()
{
// Initialize OpenGL functions
initializeOpenGLFunctions();
glEnable(GL_NORMALIZE); // Normalize normals after glScalef for correct lighting
glDisable(GL_COLOR_MATERIAL); // Disable the influence of glColor on materials
// Basic configuration
glClearColor(0.05f, 0.05f, 0.1f, 1.0f); // Darker background
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_COLOR_MATERIAL);
// Configure matrices
viewMatrix.setToIdentity();
viewMatrix.lookAt(QVector3D(0.0f, 2.5f, 0.0f), // Camera position elevated
QVector3D(0.0f, 0.0f, -1.0f), // View direction
QVector3D(0.0f, 1.0f, 0.0f)); // Up vector
// Configure ProjectileManager to launch projectiles from the cannon
float cannonLength = 3.0f;
float cannonAngle = 20.0f * M_PI / 180.0f;
float cannonPos[3] = {
0.0f,
1.2f + cannonLength * std::sin(cannonAngle),
-corridorLength + cannonLength * std::cos(cannonAngle)};
// Direction with 20° angle towards the player
float cannonDir[3] = {0.0f, std::sin(cannonAngle), std::cos(cannonAngle)};
// Access private attributes via specific ProjectileManager methods
m_projectileManager.setCannonPosition(cannonPos);
m_projectileManager.setCannonDirection(cannonDir);
m_projectileManager.setInitialSpeed(15.0f);
// Initialize corridor after OpenGL is ready
if (m_corridor) delete m_corridor;
m_corridor = new Corridor();
// Add a soft global ambient light to avoid blue in the shadows
float ambientLightStrength = 0.4f;
GLfloat globalAmbient[4] = {0.18f * ambientLightStrength, 0.16f * ambientLightStrength, 0.13f * ambientLightStrength, 1.0f}; // very soft beige/gray
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, globalAmbient);
}
void MyGLWidget::resizeGL(int width, int height)
{
// Adjust viewport
glViewport(0, 0, width, height);
// Update projection matrix
float aspectRatio = static_cast<float>(width) / height;
projectionMatrix.setToIdentity();
projectionMatrix.perspective(45.0f, aspectRatio, 0.1f, 100.0f);
}
void MyGLWidget::paintGL()
{
// Clear buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Apply projection and view matrices
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLfloat aspectRatio = static_cast<float>(width()) / height();
gluPerspective(45.0f, aspectRatio, 0.1f, 100.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0f, 2.5f, 0.0f,
0.0f, 0.0f, -corridorLength,
0.0f, 1.0f, 0.0f);
// Draw coordinate axes (X, Y, Z)
drawAxes();
// Calculate deltaTime for physics updates
QTime currentTime = QTime::currentTime();
float deltaTime = m_lastFrameTime.msecsTo(currentTime) / 1000.0f;
m_lastFrameTime = currentTime;
// Limit deltaTime to avoid issues with large values
if (deltaTime > 0.1f)
deltaTime = 0.1f;
// Update projectile manager
m_projectileManager.update(deltaTime);
// Call the game update function if it exists
if (m_gameUpdateFunc)
{
m_gameUpdateFunc();
}
// Draw scene elements
if (m_corridor) m_corridor->draw();
drawCannon();
drawCylindricalGrid();
// drawTestObject();
m_projectileManager.draw();
// Draw the player's sword at the center of the grid
// The positioning is handled by the positionPlayerOnGrid method,
// which ensures the sword is properly aligned with the grid
m_player.draw();
}
void MyGLWidget::drawCannon()
{
// Draw cannon
glPushMatrix();
// Use the Cannon class to draw the cannon
Cannon cannon;
cannon.setPosition(QVector3D(0.0f, 0, -corridorLength));
cannon.setAngle(-20.0f);
cannon.setLength(3.0f);
cannon.setRadius(0.8f);
cannon.setColor(QColor(50, 50, 50));
// Wheel parameters
cannon.setWheelRadius(0.7f);
cannon.setWheelThickness(0.3f);
cannon.draw();
glPopMatrix();
}
void MyGLWidget::drawCylindricalGrid()
{
// Cylinder portion (60°)
const int segments = 15;
const float angleStep = gridAngle / segments;
const float startAngle = -gridAngle / 2;
const float height = corridorHeight * 0.5f;
// Draw cylindrical grid (60 degrees)
glPushMatrix();
glColor3f(0.0f, 0.8f, 0.8f); // Cyan
glTranslatef(0.0f, 2.0f, 0.0f); // Move grid closer to camera
GLUquadric *quad = gluNewQuadric();
gluQuadricDrawStyle(quad, GLU_LINE); // Wireframe mode
glBegin(GL_LINES);
// Longitudinal lines
for (int i = 0; i <= segments; i++)
{
float angle = (startAngle + i * angleStep) * M_PI / 180.0f;
float x = gridRadius * sin(angle);
float z = -gridRadius * cos(angle);
glVertex3f(x, -height / 2, z);
glVertex3f(x, height / 2, z);
}
// Horizontal lines
const int heightSegments = 10;
for (int j = 0; j <= heightSegments; j++)
{
float y = -height / 2 + j * (height / heightSegments);
glBegin(GL_LINE_STRIP);
for (int i = 0; i <= segments; i++)
{
float angle = (startAngle + i * angleStep) * M_PI / 180.0f;
float x = gridRadius * sin(angle);
float z = -gridRadius * cos(angle);
glVertex3f(x, y, z);
}
glEnd();
}
glEnd();
gluDeleteQuadric(quad);
glPopMatrix();
}
void MyGLWidget::drawAxes()
{
// Temporarily disable lighting to make axes clearly visible
glDisable(GL_LIGHTING);
const float axisLength = 2.0f; // Axes length
// X axis in red
glBegin(GL_LINES);
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(axisLength, 0.0f, 0.0f);
glEnd();
// Y axis in green
glBegin(GL_LINES);
glColor3f(0.0f, 1.0f, 0.0f); // Green
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, axisLength, 0.0f);
glEnd();
// Z axis in blue
glBegin(GL_LINES);
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, axisLength); // Negative Z for depth
glEnd();
// Re-enable lighting
glEnable(GL_LIGHTING);
}
// --- Smoothing state for sword position (Exponential Moving Average) ---
static float smoothedGridX = 0.0f;
static float smoothedGridY = 0.0f;
static const float SMOOTHING_ALPHA = 0.35f; // Smoothing factor (0: no smoothing, 1: no lag)
void MyGLWidget::positionPlayerOnGrid(float gridX, float gridY)
{
// Exponential Moving Average (EMA) smoothing for sword movement
// This algorithm provides a smooth, low-latency interpolation between the previous and current hand positions.
// https://en.wikipedia.org/wiki/Exponential_smoothing
smoothedGridX = SMOOTHING_ALPHA * gridX + (1.0f - SMOOTHING_ALPHA) * smoothedGridX;
smoothedGridY = SMOOTHING_ALPHA * gridY + (1.0f - SMOOTHING_ALPHA) * smoothedGridY;
// Calculate the angle based on the smoothed gridX coordinate and gridAngle
float angle = (smoothedGridX * (gridAngle / 2.0f)) * M_PI / 180.0f;
// Get the base Y-coordinate of the grid (height)
float baseY = 2.0f;
// Calculate the height offset based on smoothed gridY
float heightOffset = smoothedGridY * (corridorHeight * 0.25f);
float worldY = baseY + heightOffset;
// Use a fixed radius for the grid cylinder
float radius = gridRadius;
// Convert from polar coordinates (angle, radius) to Cartesian coordinates (x, z)
float worldX = radius * std::sin(angle);
float worldZ = -radius * std::cos(angle); // Negative for OpenGL z-axis orientation
// Set the player's position directly on the grid surface
m_player.setPosition(QVector3D(worldX, worldY, worldZ));
// Set the player's rotation to face perpendicular to the grid surface
float rotationY = angle * 180.0f / M_PI;
m_player.setRotation(0.0f, rotationY, 0.0f);
}
void MyGLWidget::keyPressEvent(QKeyEvent *event)
{
// Handle numpad keys specifically, which sometimes need special treatment
int key = event->key();
// Map numpad keys to their corresponding standard keys if needed
if (event->modifiers() & Qt::KeypadModifier)
{
switch (key)
{
case Qt::Key_Plus:
key = Qt::Key_Plus; // Keep as is
break;
case Qt::Key_Minus:
key = Qt::Key_Minus; // Keep as is
break;
}
}
// Pass key events to the keyboard handler with possibly modified key
m_keyboardHandler.keyPressed(key);
// Allow standard processing
QOpenGLWidget::keyPressEvent(event);
}
void MyGLWidget::keyReleaseEvent(QKeyEvent *event)
{
// Pass key events to the keyboard handler
m_keyboardHandler.keyReleased(event->key());
// Allow standard processing
QOpenGLWidget::keyReleaseEvent(event);
}