forked from zeichensystem/GeBurtstAg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcamera.c
144 lines (124 loc) · 5.16 KB
/
camera.c
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
#include <string.h>
#include <math.h>
#include <tonc_bios.h>
#include <tonc_video.h>
#include "camera.h"
#include "math.h"
#include "logutils.h"
#include "model.h"
#include "globals.h"
static FIXED tmpmat[16];
Camera cameraNew(Vec3 pos, FIXED fov, FIXED near, FIXED far, int mode)
{
Camera new;
// Read only:
int w, h;
switch (mode) {
case DCNT_MODE5:
w = M5_SCALED_W;
h = M5_SCALED_H;
break;
case DCNT_MODE4:
w = M4_WIDTH;
h = M4_HEIGHT;
break;
default:
panic("camera.c: No valid mode.");
w = M5_SCALED_W;
h = M5_SCALED_H;
break;
}
new.canvasWidth = int2fx(w);
new.canvasHeight = int2fx(h);
new.viewportWidth = float2fx(w / 100.f);
new.viewportHeight = float2fx(h / 100.f);
new.aspect = fxdiv(new.viewportWidth, new.viewportHeight);
new.fov = fov;
new.near = near;
new.far = far;
matrix4x4setIdentity(new.cam2world);
matrix4x4setIdentity(new.world2cam);
// Read/write:
new.pos = pos;
new.yaw = int2fx12(0);
new.pitch = int2fx12(0);
new.roll = int2fx12(0);
new.lookAt = (Vec3){.x=0, .y=0, .z=0};
cameraComputePerspectiveMatrix(&new);
return new;
}
static void cameraComputeRotMatrix(Camera *cam, FIXED result[16])
{
// TODO: precompute yawPitchRollmatrix
// ROTMAT = yaw * pitch * roll
matrix4x4createRotY(result, cam->yaw);
matrix4x4createRotX(tmpmat, cam->pitch);
matrix4x4Mul(result, tmpmat);
matrix4x4createRotZ(tmpmat, cam->roll);
matrix4x4Mul(result, tmpmat);
}
void cameraComputeWorldToCamSpace(Camera *cam)
{
matrix4x4setIdentity(cam->world2cam);
// Compute new basis of the matrix from our lookAt point:
Vec3 forward = vecUnit(vecSub(cam->pos, cam->lookAt));
Vec3 tmp = {.x = 0, .y = int2fx(1), .z =0};
Vec3 right;
Vec3 up;
FIXED eps = 25; // Is about 0.009; assumes FIX_SHIFT == 8
// TODO/FIXME: Handling of this special case (camera looking completely down/up) *does not work* at all for non-static scenes (interpolation is messed up).
if (ABS(forward.x - tmp.x) < eps && ABS(forward.y - tmp.y) < eps && ABS(forward.z - tmp.z) < eps ) { // Special case where the camera looks straight up/down and the cross product "fails".
mgba_printf("camera.c: cameraComputeWorldToCamSpace: Handle singularity");
right = vecUnit((Vec3){.x=forward.y, .y=0, .z=0});
up = vecUnit(vecCross(forward, right));
} else {
right = vecUnit(vecCross(tmp, forward));
up = vecUnit(vecCross(forward, right));
}
matrix4x4SetBasis(cam->world2cam, right, up, forward);
FIXED rotmat[16];
cameraComputeRotMatrix(cam, rotmat);
matrix4x4Mul(cam->world2cam, rotmat);
// Invert the matrix so we get the world-to-camera matrix from our current camera-to-world matrix.
// (The transposition of square orthonormal matrices is equivalent to their inversion. If something goes wrong, our matrix is not orthonormal; try the numerical solution.)
matrix4x4Transpose(cam->world2cam);
// Compute translation matrix and put it into 'transMat'.
FIXED transMat[16];
matrix4x4setIdentity(transMat);
matrix4x4SetTranslation(transMat, (Vec3){.x=-cam->pos.x, .y=-cam->pos.y, .z=-cam->pos.z} );
// Post-multiply the translation matrix with the rotation matrix.
matrix4x4Mul(cam->world2cam, transMat); // world2cam' = world2cam * transMat
}
void cameraComputePerspectiveMatrix(Camera *cam)
{
// cf. https://cg.informatik.uni-freiburg.de/course_notes/graphics_04_projection.pdf (last retrieved 2021-07-09)
FIXED near = cam->near;
FIXED far = cam->far;
FIXED top = fxmul(float2fx(tan(fx2float(cam->fov) / 2. )), near);
FIXED bottom = -top;
FIXED right = fxmul(top, cam->aspect);
FIXED left = -right;
FIXED persp[16] = {
fxdiv( fxmul(near, int2fx(2)), right - left), 0, fxdiv(right + left, right - left), 0,
0, fxdiv(fxmul(near, int2fx(2)), top - bottom), fxdiv(top + bottom, top - bottom), 0,
0, 0, -fxdiv(far + near, far - near), -fxdiv(fxmul(near, fxmul(int2fx(2), far)), far - near),
0, 0, int2fx(-1), 0
};
FIXED viewport2image[16] = {
// fxmul(cam->aspect): not sure, but seems to work for now?
fxmul(cam->aspect, fxmul(float2fx(0.5), fxdiv(cam->canvasWidth, cam->viewportWidth))), 0, 0, fxdiv(cam->canvasWidth, int2fx(2)),
0, -fxmul(float2fx(0.5), fxdiv(cam->canvasHeight, cam->viewportHeight)), 0, fxdiv(cam->canvasHeight, int2fx(2)),
0, 0, int2fx(1), 0,
0, 0, 0, int2fx(1)
};
memcpy(cam->perspMat, persp, sizeof(persp));
memcpy(cam->viewport2imageMat, viewport2image, sizeof(viewport2image));
// We can ignore fxdiv(right + left, right - left) in the symmetric case where right == -left and top == -right.
// We use the following variables to project/transform without matrices (more efficient, less convenient).
cam->perspFacX = persp[0];
cam->perspFacY = persp[5];
cam->viewportTransFacX = viewport2image[0];
cam->viewportTransAddX = viewport2image[3];
cam->viewportTransFacY = viewport2image[5];
cam->viewportTransAddY = viewport2image[7];
}