|
| 1 | +(******************************************************************************) |
| 2 | +(* uopengl_camera.pas 13.05.2025 *) |
| 3 | +(* *) |
| 4 | +(* Version : 0.01 *) |
| 5 | +(* *) |
| 6 | +(* Author : Uwe Schächterle (Corpsman) *) |
| 7 | +(* *) |
| 8 | +(* Support : www.Corpsman.de *) |
| 9 | +(* *) |
| 10 | +(* Description : Implements a OpenGL, 3D camera class *) |
| 11 | +(* By defining a Pos, Target and Up Vector, you could afterwords*) |
| 12 | +(* Translate / Rotate the CAM freely. *) |
| 13 | +(* *) |
| 14 | +(* License : See the file license.md, located under: *) |
| 15 | +(* https://github.com/PascalCorpsman/Software_Licenses/blob/main/license.md *) |
| 16 | +(* for details about the license. *) |
| 17 | +(* *) |
| 18 | +(* It is not allowed to change or remove this text from any *) |
| 19 | +(* source file of the project. *) |
| 20 | +(* *) |
| 21 | +(* Warranty : There is no warranty, neither in correctness of the *) |
| 22 | +(* implementation, nor anything other that could happen *) |
| 23 | +(* or go wrong, use at your own risk. *) |
| 24 | +(* *) |
| 25 | +(* Known Issues: none *) |
| 26 | +(* *) |
| 27 | +(* History : 0.01 - Initial version *) |
| 28 | +(* *) |
| 29 | +(******************************************************************************) |
| 30 | +Unit uopengl_camera; |
| 31 | + |
| 32 | +{$MODE ObjFPC}{$H+} |
| 33 | + |
| 34 | +Interface |
| 35 | + |
| 36 | +Uses |
| 37 | + Classes, SysUtils, dglOpenGL |
| 38 | + , uvectormath; |
| 39 | + |
| 40 | +Type |
| 41 | + |
| 42 | + { TOpenGLCamera } |
| 43 | + |
| 44 | + TOpenGLCamera = Class |
| 45 | + private |
| 46 | + fDefPos, fDefTarget, fDefUp: TVector3; |
| 47 | + fPos, fTarget, fup: TVector3; |
| 48 | + public |
| 49 | + |
| 50 | + Property Target: TVector3 read fTarget; |
| 51 | + Property Pos: TVector3 read fPos; |
| 52 | + |
| 53 | + Constructor Create(aPos, aTarget, aUp: TVector3); |
| 54 | + |
| 55 | + (* |
| 56 | + * Call this function right after you cleared all OpenGL Buffers and |
| 57 | + * glLoadIdentity in the OnRender Routine, it will initialize the |
| 58 | + * ModelView Matrix, so that you can directly render the szene. |
| 59 | + *) |
| 60 | + Procedure SetCam; |
| 61 | + |
| 62 | + (* |
| 63 | + * Resets, Camera Position, Up and Target to the values given on creation |
| 64 | + *) |
| 65 | + Procedure Reset; |
| 66 | + |
| 67 | + (* |
| 68 | + * Translate Camera and Target, no change in Direction |
| 69 | + * |
| 70 | + * So ganz Sauber sind die beiden Routinen nicht, da sie beide |
| 71 | + * die Kamera Richtung berücksichtigen. |
| 72 | + * Im Zweifel dürfte "TranslateByWorld" die gefühlt Richtigere sein |
| 73 | + *) |
| 74 | + Procedure TranslateByView(x, y, z: Single); |
| 75 | + Procedure TranslateByWorld(x, y, z: Single); |
| 76 | + |
| 77 | + (* |
| 78 | + * Roates Camera, without Gimbal Lock |
| 79 | + *) |
| 80 | + Procedure Rotate(dx, dy, dz: Single); // Attention: atm, dz is ignored ! |
| 81 | + |
| 82 | + (* |
| 83 | + * Zoom In / Out |
| 84 | + * Effects Cam - Target distance |
| 85 | + * aZoomValue: 1 = No Zoom , < 1 = Zoom in, > 1 = Zoom Out |
| 86 | + * Example: If you zoomed in with 1.1 you need to zoom out with 1/1.1 |
| 87 | + *) |
| 88 | + Procedure Zoom(aZoomValue: Single); |
| 89 | + |
| 90 | + (* |
| 91 | + * Renders a small little gizmo in the right bottom corner ;) |
| 92 | + * If used, call directly after SetCam ( |
| 93 | + *) |
| 94 | + Procedure RenderGizmo(aBorder, aWidth, aHeight: Integer; aSize: Single); // Size (= Size) -> -2 = Big, -9 = tiny |
| 95 | + End; |
| 96 | + |
| 97 | +Implementation |
| 98 | + |
| 99 | +Uses uquaternion; |
| 100 | + |
| 101 | +{ TOpenGLCamera } |
| 102 | + |
| 103 | +Procedure TOpenGLCamera.RenderGizmo(aBorder, aWidth, aHeight: Integer; aSize: Single); |
| 104 | +Var |
| 105 | + vp: Array[0..3] Of GLint; |
| 106 | + mv: TMatrix4x4; |
| 107 | + lw: GLfloat; |
| 108 | +Begin |
| 109 | + // Auslesen der Modelview (wie sie von SetCam gesetzt wurde) |
| 110 | + glGetFloatv(GL_MODELVIEW_MATRIX, @mv); |
| 111 | + |
| 112 | + // Anpassen des ViewPort für den Gizmo |
| 113 | + glGetIntegerv(GL_VIEWPORT, @vp); |
| 114 | + glViewport(vp[2] - aBorder - aWidth, aBorder, aWidth, aHeight); |
| 115 | + glMatrixMode(GL_PROJECTION); |
| 116 | + glPushMatrix; |
| 117 | + glLoadIdentity; |
| 118 | + gluPerspective(45.0, 1.0, 0.1, 10.0); |
| 119 | + |
| 120 | + glMatrixMode(GL_MODELVIEW); |
| 121 | + glPushMatrix; |
| 122 | + glLoadIdentity; |
| 123 | + |
| 124 | + // Rendern des Gizmo |
| 125 | + |
| 126 | + // 1. Übernehmen der SetCam Rotation Matrix, aber mit "eigener" Position |
| 127 | + mv[3, 0] := 0; |
| 128 | + mv[3, 1] := 0; |
| 129 | + mv[3, 2] := -aSize; |
| 130 | + glMultMatrixf(@mv); |
| 131 | + |
| 132 | + // Eigentlichen Rendern des Gizmo |
| 133 | + glGetFloatv(GL_LINE_WIDTH, @lw); |
| 134 | + glLineWidth(2.0); |
| 135 | + |
| 136 | + glBegin(GL_LINES); |
| 137 | + // X – Rot |
| 138 | + glColor3f(1, 0, 0); |
| 139 | + glVertex3f(0, 0, 0); |
| 140 | + glVertex3f(1, 0, 0); |
| 141 | + // Y – Grün |
| 142 | + glColor3f(0, 1, 0); |
| 143 | + glVertex3f(0, 0, 0); |
| 144 | + glVertex3f(0, 1, 0); |
| 145 | + // Z – Blau |
| 146 | + glColor3f(0, 0, 1); |
| 147 | + glVertex3f(0, 0, 0); |
| 148 | + glVertex3f(0, 0, 1); |
| 149 | + glEnd; |
| 150 | + |
| 151 | + // Zurücksetzen Aller Werte auf Zustand vor Aufruf RenderGizmo |
| 152 | + glLineWidth(lw); |
| 153 | + glPopMatrix; |
| 154 | + glMatrixMode(GL_PROJECTION); |
| 155 | + glPopMatrix; |
| 156 | + glMatrixMode(GL_MODELVIEW); |
| 157 | + glViewport(vp[0], vp[1], vp[2], vp[3]); |
| 158 | +End; |
| 159 | + |
| 160 | +Constructor TOpenGLCamera.Create(aPos, aTarget, aUp: TVector3); |
| 161 | +Begin |
| 162 | + fDefPos := aPos; |
| 163 | + fDefTarget := aTarget; |
| 164 | + fDefUp := aUp; |
| 165 | +End; |
| 166 | + |
| 167 | +Procedure TOpenGLCamera.Reset; |
| 168 | +Begin |
| 169 | + fPos := fDefPos; |
| 170 | + fTarget := fDefTarget; |
| 171 | + fup := fDefUp; |
| 172 | +End; |
| 173 | + |
| 174 | +Procedure TOpenGLCamera.SetCam; |
| 175 | +Begin |
| 176 | + // Hauptkamera setzen |
| 177 | + gluLookAt( |
| 178 | + FPos.x, FPos.y, FPos.z, |
| 179 | + FTarget.x, FTarget.y, FTarget.z, |
| 180 | + fUp.x, fUp.y, fUp.z |
| 181 | + ); |
| 182 | +End; |
| 183 | + |
| 184 | +Procedure TOpenGLCamera.TranslateByView(x, y, z: Single); |
| 185 | +Var |
| 186 | + forward, right, up, move: TVector3; |
| 187 | +Begin |
| 188 | + forward := NormV3(fTarget - fPos); |
| 189 | + right := NormV3(CrossV3(forward, fUp)); |
| 190 | + up := CrossV3(right, forward); |
| 191 | + move := right * x + up * y + forward * z; |
| 192 | + fPos := fPos + move; |
| 193 | + fTarget := fTarget + move; |
| 194 | +End; |
| 195 | + |
| 196 | +Procedure TOpenGLCamera.TranslateByWorld(x, y, z: Single); |
| 197 | +Var |
| 198 | + forward, right, up, move: TVector3; |
| 199 | +Begin |
| 200 | + forward := NormV3(fTarget - fPos); |
| 201 | + right := NormV3(CrossV3(forward, fUp)); |
| 202 | + up := CrossV3(right, forward); |
| 203 | + forward := NormV3(CrossV3(right, fDefUp)); |
| 204 | + up := NormV3(fDefUp); |
| 205 | + move := right * x + up * y + forward * z; |
| 206 | + fPos := fPos + move; |
| 207 | + fTarget := fTarget + move; |
| 208 | +End; |
| 209 | + |
| 210 | +Procedure TOpenGLCamera.Rotate(dx, dy, dz: Single); |
| 211 | +Var |
| 212 | + offset: TVector3; |
| 213 | + pitchAxis, yawAxis: TVector3; |
| 214 | + qPitch, qYaw: TQuaternion; |
| 215 | + rotation: TMatrix4x4; |
| 216 | + forward, right: TVector3; |
| 217 | +Begin |
| 218 | + // Richtung vom Ziel zur Kamera |
| 219 | + offset := fPos - fTarget; |
| 220 | + |
| 221 | + // forward-Vektor (Blickrichtung) |
| 222 | + forward := NormV3(fTarget - fPos); |
| 223 | + |
| 224 | + // yaw: immer um Welt-Up (z. B. Y-Achse) |
| 225 | + yawAxis := fDefUp; |
| 226 | + |
| 227 | + // pitch: um lokale X-Achse = Right-Vektor |
| 228 | + right := NormV3(CrossV3(forward, fDefUp)); |
| 229 | + pitchAxis := right; |
| 230 | + |
| 231 | + // Erzeuge Quaternionen aus dynamischen Achsen |
| 232 | + qPitch := V3ToQ(pitchAxis, dx); // Kamera-X (lokal) |
| 233 | + qYaw := V3ToQ(yawAxis, dy); // Welt-Y (global) |
| 234 | + |
| 235 | + // Kombinierte Rotation |
| 236 | + rotation := QToM4x4(qYaw * qPitch); |
| 237 | + |
| 238 | + // Versetze Kamera um rotierten Vektor |
| 239 | + fPos := rotation * v4(offset, 0) + fTarget; |
| 240 | + |
| 241 | + // Optional: Roll unterdrücken durch Up-Neuberechnung |
| 242 | + forward := NormV3(fTarget - fPos); |
| 243 | + right := NormV3(CrossV3(forward, fDefUp)); |
| 244 | + fUp := CrossV3(right, forward); // neue Up-Richtung (orthonormal) |
| 245 | +End; |
| 246 | + |
| 247 | +Procedure TOpenGLCamera.Zoom(aZoomValue: Single); |
| 248 | +Var |
| 249 | + offset: TVector3; |
| 250 | +Begin |
| 251 | + aZoomValue := Clamp(aZoomValue, 0.1, 10.0); |
| 252 | + // Richtung vom Ziel zur Kamera |
| 253 | + offset := fPos - fTarget; |
| 254 | + // Skaliere den Offset-Vektor |
| 255 | + offset := offset * aZoomValue; |
| 256 | + // Neue Position |
| 257 | + fPos := fTarget + offset; |
| 258 | +End; |
| 259 | + |
| 260 | +End. |
| 261 | + |
0 commit comments