1fc32e6a6842a96a7027cf3ebe884272013e4d97
[sdk] / ecere / src / gfx / 3D / Camera.ec
1 namespace gfx3D;
2
3 import "Display"
4
5 public enum CameraType { fixed, fixedQuaternion, attached, attachedQuaternion, lookAt, lookAtObject };
6 public enum FovDirection { widest, horizontal, vertical };
7
8 public enum ClippingPlane { left, right, top, bottom, near, far };
9
10 public class Camera
11 {
12 public:
13    // Change type to inherited types...
14    property CameraType type { set { type = value; } get { return type; } };
15    property Vector3D position { set { position = value; } get { value = position; } };
16    property Quaternion orientation { set { orientation = value; } get { value = orientation; } };
17    property Vector3D cPosition { get { value = cPosition; } };
18    property Quaternion cOrientation { get { value = cAngle; } };
19    property Degrees fov { set { fov = value; } get { return fov; } };
20    property float zMin { set { zMin = value; } get { return zMin; } };
21    property float zMax { set { zMax = value; } get { return zMax; } };
22    property Object target { set { target = value; } get { return target; } };
23    property FovDirection fovDirection { set { fovDirection = value; } get { return fovDirection; } };
24    property float aspectRatio { set { aspectRatio = value; } get { return aspectRatio; } };
25    property Size focal { get { value = { focalX, focalY }; } };
26
27    void Setup(int width, int height, Point origin)
28    {
29       if(this)
30       {
31          Vector3D vector {0,0,1};
32          Quaternion quat;
33
34          float aspectRatio = this.aspectRatio;
35          float l, r, t, b;
36
37          if(!aspectRatio)
38             aspectRatio = (float)width/height;
39
40          this.width = width;
41          this.height = height;
42          if(origin != null)
43             this.origin = origin;
44          else
45          {
46             this.origin.x = width/2;
47             this.origin.y = height/2;
48          }
49
50          l = this.origin.x - 0;
51          r = this.origin.x - width;
52          t = 0 - this.origin.y;
53          b = height - this.origin.y;
54
55          if(fovDirection == horizontal || (fovDirection == widest && width * aspectRatio > height))
56          {
57             focalX = (float)((width / 2) / tan(fov/2));
58             focalY = focalX * height / width;
59             focalY *= aspectRatio;
60             fovX = fov;
61          }
62          else
63          {
64             focalY = (float)((height / 2) / tan(fov/2));
65             focalX = focalY * width / height;
66             focalX /= aspectRatio;
67             fovY = fov;
68          }
69
70          fovX = atan((width / 2)  / focalX) * 2;
71          fovY = atan((height / 2) / focalY) * 2;
72
73          fovLeft   = atan(l / focalX);
74          fovRight  = atan(r / focalX);
75          fovTop    = atan(t / focalY);
76          fovBottom = atan(b / focalY);
77
78          // Compute Clipping Planes
79          {
80             Vector3D normal, point = {0,0,0};
81          
82             // --- Left ---
83             quat.Yaw(fovLeft - Pi/2);
84             quat.ToDirection(normal);
85             viewClippingPlanes[left].FromPointNormal(normal, point);
86
87             // --- Right ---
88             quat.Yaw(fovRight + Pi/2);
89             quat.ToDirection(normal);
90             viewClippingPlanes[right].FromPointNormal(normal, point);
91
92             // --- Top ---
93             quat.Pitch(fovTop + Pi/2);
94             quat.ToDirection(normal);
95             viewClippingPlanes[top].FromPointNormal(normal, point);
96
97             // --- Bottom ---
98             quat.Pitch(fovBottom - Pi/2);
99             quat.ToDirection(normal);
100             viewClippingPlanes[bottom].FromPointNormal(normal, point);
101
102             // --- Near ---
103             normal.x = 0; normal.y = 0; normal.z = 1;
104             point.z = zMin;
105             viewClippingPlanes[near].FromPointNormal(normal, point);
106
107             // --- Far ---
108             normal.x = 0; normal.y = 0; normal.z = -1;
109             point.z = zMax;
110             viewClippingPlanes[far].FromPointNormal(normal, point);
111          }
112
113          needUpdate = true;
114       }
115    }
116
117    void AdjustPosition(Vector3D position)
118    {
119       ClippingPlane c;
120       Matrix transpose = viewMatrix;
121
122       cPosition = position;
123
124       transpose.m[0][3] = cPosition.x;
125       transpose.m[1][3] = cPosition.y;
126       transpose.m[2][3] = cPosition.z;
127       inverseTranspose.Inverse(transpose);
128
129       for(c = 0; c<ClippingPlane::enumSize; c++)
130          worldClippingPlanes[c].MultMatrix(viewClippingPlanes[c], inverseTranspose);
131    }
132
133    void AdjustAngle(Quaternion angle)
134    {
135       cAngle = angle;
136       
137       inverseMatrix.RotationQuaternion(angle);
138       viewMatrix.Transpose(inverseMatrix);
139
140       AdjustPosition(cPosition);
141    }
142
143    bool Update()
144    {
145       bool result = false;
146       if(this)
147       {
148          Matrix matrix;
149          Transform * target = this.target ? &this.target.transform : null;
150          Vector3D oldPosition = cPosition, newPosition;
151
152          switch(this.type)
153          {
154             case fixed:
155             case fixedQuaternion:
156             {
157                newPosition = position;
158                toAngle = orientation;
159                break;
160             }
161             case attached:
162             {
163                toAngle = { 1,0,0,0 };
164                if(target)
165                {
166                   Euler eulerCamera = orientation, euler;
167
168                   euler.Add(eulerCamera, this.target.eulerOrientation);
169
170                   // Logf("yaw = %f, pitch = %f\n", eulerCamera.yaw, eulerCamera.pitch);
171
172                   toAngle = euler;
173                }
174                else
175                   toAngle = orientation;
176
177                matrix.RotationQuaternion(toAngle);
178                newPosition.MultMatrix(position, matrix);
179                if(target)
180                   newPosition.Add(newPosition, target->position);
181                break;
182             }
183             case attachedQuaternion:
184             {
185                toAngle = { 1,0,0,0 };
186                if(target)
187                {
188                   /*if(type == attached)
189                   {
190                      Euler eulerCamera = orientation, eulerTarget = target->orientation, euler;
191
192                      euler.Add(eulerCamera, eulerTarget);
193
194                      // Logf("yaw = %f, pitch = %f\n", eulerCamera.yaw, eulerCamera.pitch);
195
196                      toAngle = euler;
197                   }
198                   else if(type == attachedQuaternion)*/
199                      toAngle.Multiply(orientation, target->orientation); 
200                }
201                else
202                   toAngle = orientation;
203
204                matrix.RotationQuaternion(toAngle);
205                newPosition.MultMatrix(position, matrix);
206                if(target)
207                   newPosition.Add(newPosition, target->position);
208                break;
209             }
210             case lookAt:
211             {
212                Quaternion result;
213                Vector3D direction;
214                newPosition = position;
215                if(target)
216                {
217                   direction.Subtract(target->position, position);
218                   toAngle.RotationDirection(direction);
219                }
220                else
221                {
222                   Vector3D position { 0, 0, 0 };
223                   direction.Subtract(position, this.position);
224                   toAngle.RotationDirection(direction);
225                }
226                result.Multiply(orientation, toAngle);
227                toAngle = result;
228                break;
229             }
230             case lookAtObject:
231             {
232                Object cameraObject = this.cameraObject;
233                toAngle = cameraObject.transform.orientation;
234                if(cameraObject.flags.root || !cameraObject.parent)
235                   newPosition = cameraObject.transform.position;
236                else
237                   newPosition.MultMatrix(cameraObject.transform.position, cameraObject.parent.matrix);
238                break;
239             }
240          }
241
242          if(cAngle.w != toAngle.w || cAngle.x != toAngle.x || 
243             cAngle.y != toAngle.y || cAngle.z != toAngle.z ||
244             needUpdate)
245          {
246             cPosition = newPosition;
247             if(slerpAmount && slerpPosition < 1.0)
248             {
249                Quaternion angle;
250                slerpPosition += slerpAmount;
251                slerpPosition = Min(slerpPosition, 1.0);
252                angle.Slerp(fromAngle, toAngle, slerpPosition);
253                AdjustAngle(angle);
254             }
255             else
256                AdjustAngle(toAngle);
257
258             result = true;
259             this.needUpdate = false;
260          }
261          else if(newPosition.x != oldPosition.x || newPosition.y != oldPosition.y || newPosition.z != oldPosition.z)
262          {
263             AdjustPosition(newPosition);
264             result = true;
265          }
266       }
267       return result;
268    }
269
270    bool SphereVisible(Vector3D center, float radius)
271    {
272       // TURN BACK ON
273       /*
274       if(wLeftNormal.DotProduct(center) + wLeftD < -radius)
275          return false;
276       if(wRightNormal.DotProduct(center) + wRightD < -radius)
277          return false;
278       if(wTopNormal.DotProduct(center) + wTopD < -radius)
279          return false;
280       if(wBottomNormal.DotProduct(center) + wBottomD < -radius)
281          return false;
282       if(wNearNormal.DotProduct(center) + wNearD < -radius)
283          return false;
284    /-*   if(wFarNormal.DotProduct(center) + wFarD < -radius)
285          return false;*/
286       return true;
287    }
288
289
290    bool PointsVisible(Vector3D * points, int numPoints, double threshold)
291    {
292       ClippingPlane p;
293       int c;
294       for(p = 0; p<=ClippingPlane::bottom; p+=2)
295       {
296          bool out1a = true, out2a = true;
297          bool out1b = true, out2b = true;
298          Plane * plane = &worldClippingPlanes[p];
299          for(c = 0; c<numPoints; c++)
300          {
301             double dot = 
302                plane->a * points[c].x + 
303                plane->b * points[c].y +
304                plane->c * points[c].z;
305             if(dot + plane->d > 0)
306             {
307                out1a = out1b = false;
308                break;
309             }
310             else if(dot + plane->d > -threshold)
311                out1a = false;
312          }
313
314          plane = &worldClippingPlanes[p+1];
315          for(c = 0; c<numPoints; c++)
316          {
317             double dot = 
318                plane->a * points[c].x + 
319                plane->b * points[c].y + 
320                plane->c * points[c].z;
321             if(dot + plane->d > 0)
322             {
323                out2a = out2b = false;
324                break;
325             }
326             else if(dot + plane->d > -threshold)
327                out2a = false;
328          }
329
330          if((out1a && !out2b) || (out2a && !out1b))
331             return false;
332       }
333       return true;
334    }
335
336 /*
337    bool PointsVisible(Vector3D * origPoints, int numPoints, double threshold)
338    {
339       Plane * planes = worldClippingPlanes;
340       static byte goodPoints[50];
341       static Vector3D points[50];
342       static Vector3D newPoints[50];
343       bool outside = false;
344       int p;
345       int i;
346       int c = 0;
347       int n = numPoints;
348
349       for(c = 0; c<numPoints; c++)
350          points[c] = origPoints[c];
351
352       for(p = 0; p < 6; p++) 
353       {
354          Plane * plane = &planes[p];
355          int i;
356          int numGoodPoints = 0;
357  
358          memset(goodPoints, 0, n);
359               for(i = 0; i < n; i++) 
360          {
361             double dot = plane->normal.DotProduct(points[i]);
362             double distance = dot + plane->d;
363                       if(distance > -threshold)
364             {
365                numGoodPoints++;
366                goodPoints[i] = 1;
367             }
368               }
369          if(!numGoodPoints)
370          {
371             outside = true;
372             break;
373          }
374          
375          if(numGoodPoints < n)
376          {
377             // Clip the polygon
378             int newN = 0;
379             int lastGood = -1;
380             int j;
381
382             for(j = 0; j<n; )
383             {
384                if(goodPoints[j])
385                {
386                   newPoints[newN++] = points[j];
387                   lastGood = j++;
388                }
389                else
390                {
391                   Line edge; 
392                   int next;
393
394                   if(lastGood == -1)
395                      for(lastGood = n-1; !goodPoints[lastGood]; lastGood--);
396
397                   edge.p0 = points[lastGood];
398                   edge.delta.Subtract(points[j], edge.p0);
399                   plane->IntersectLine(edge, newPoints[newN++]);
400
401                   for(next = j+1; next != j; next++)
402                   {
403                      if(next == n) next = 0;
404                      if(goodPoints[next])
405                      {
406                         int prev = next - 1;
407                         if(prev < 0) prev = n-1;
408
409                         edge.p0 = points[prev];
410                         edge.delta.Subtract(points[next], edge.p0);
411                         plane->IntersectLine(edge, newPoints[newN++]);
412                         break;
413                      }
414                   }
415                   if(next <= j)
416                      break;
417                   else
418                      j = next;
419                }
420             }
421             // Use the new points
422             memcpy(points, newPoints, newN * sizeof(Vector3D));
423             n = newN;
424          }
425       }
426       return !outside;
427    }
428 */
429
430    void TransformPoint(Vector3D dest, Vector3D src)
431    {
432       Vector3D vector { src.x - cPosition.x, src.y - cPosition.y, src.z - cPosition.z };
433       dest.MultMatrix(vector, viewMatrix);
434    }
435
436    void TransformNormal(Vector3D dest, Vector3D src)
437    {
438       dest.MultMatrix(src, viewMatrix);
439    }
440
441    void TransformMatrix(Matrix dest, Matrix src)
442    {
443       Matrix matrix = src;
444       matrix.m[3][0] -= cPosition.x;
445       matrix.m[3][1] -= cPosition.y;
446       matrix.m[3][2] -= cPosition.z;
447       dest.Multiply(matrix, viewMatrix);
448    }
449
450    void RotatePitch(Degrees amount, Degrees min, Degrees max)
451    {
452       if(type == fixedQuaternion)
453       {
454          orientation.RotatePitch(amount);
455       }
456       else
457       {
458          Euler euler = orientation;
459          euler.pitch += amount;
460          if(min || max)
461          {
462             euler.pitch = Min(euler.pitch, max);
463             euler.pitch = Max(euler.pitch, min);
464          } 
465          orientation = euler;
466       }
467    }
468
469    void RotateYaw(Degrees amount, Degrees min, Degrees max)
470    {
471       if(type == fixedQuaternion)
472       {
473          orientation.RotateYaw(amount);
474       }
475       else
476       {
477          Euler euler = orientation;
478          euler.yaw += amount;
479          if(min || max)
480          {
481             euler.yaw = Min(euler.yaw, max);
482             euler.yaw = Max(euler.yaw, min);
483          } 
484          orientation = euler;
485       }
486    }
487
488    void RotateRoll(Degrees amount, Degrees min, Degrees max)
489    {
490       if(type == fixedQuaternion)
491       {
492          orientation.RotateRoll(amount);
493       }
494       else
495       {
496          Euler euler = orientation;
497          euler.roll += amount;
498          if(min || max)
499          {
500             euler.roll = Min(euler.roll, max);
501             euler.roll = Max(euler.roll, min);
502          } 
503          orientation = euler;
504       }
505    }
506
507    void Slerp(float amount)
508    {
509       fromAngle = cAngle;
510       slerpAmount = amount;
511       slerpPosition = 0;
512    }
513
514    void Move(Vector3D direction)
515    {
516       Matrix matrix;
517
518       switch(type)
519       {
520          case fixed:
521          {
522             Vector3D offset;
523             matrix.RotationQuaternion(orientation);
524             offset.MultMatrix(direction, matrix);
525             position.Add(position, offset);
526             break;
527          }
528          case attachedQuaternion:
529          case attached:
530          case lookAt:
531          {
532             position.Add(position, direction);
533             break;
534          }
535       }
536    }
537
538    bool Project(Vector3D vector, Vector3D point)
539    {
540       if(vector.z >= zMin)
541       {
542          float floatZ;
543          point.x = (vector.x*focalX/vector.z);
544          point.y = (vector.y*focalY/vector.z);
545          point.z = (((zMax * zMin / -vector.z) + zMax) / (zMax - zMin));
546          floatZ = ((((float)zMax * (float)zMin / -(float)vector.z) + (float)zMax) / ((float)zMax - (float)zMin));
547          point.x += origin.x;
548          point.y += origin.y;
549          return (point.x >= 0 && point.y >= 0 && 
550                  point.x < width && point.y < height);
551       }
552       return false;
553    }
554
555    void Unproject(Vector3D point, Vector3D vector)
556    {
557       vector.z = (zMax * zMin / (zMax - (double)point.z * (zMax-zMin)));
558       vector.y = ((point.y - origin.y) * (double)vector.z / focalY);
559       vector.x = ((point.x - origin.x) * (double)vector.z / focalX);
560    }
561
562    bool ProjectSize(Vector3D vector, Point point)
563    {
564       if(vector.z >= zMin)
565       {
566          point.x = (int)((double)vector.x*(double)focalX/(double)vector.z);
567          point.y = (int)((double)vector.y*(double)focalY/(double)vector.z);
568          return true;
569       }
570       return false;
571    }
572
573    void Untransform(Vector3D src, Vector3D result)
574    {
575       result.MultMatrix(src, inverseMatrix);
576       result.x += cPosition.x;
577       result.y += cPosition.y;
578       result.z += cPosition.z;
579    }
580
581 private:
582    Camera()
583    {
584       needUpdate = true;
585       type = fixed;
586       orientation.w = 1;
587       aspectRatio = 0;
588       fov = Pi/2;
589       zMin = 5;//0.1f;
590       zMax = 10000;
591    }
592
593    CameraType type;
594    FovDirection fovDirection;
595    Object cameraObject;
596    Object target;
597    Vector3D position;
598    Quaternion orientation;
599    float aspectRatio;
600    Degrees fov;
601    float zMin, zMax;
602
603    // Read only
604    Vector3D cPosition;
605    Quaternion cAngle;
606    Angle fovLeft, fovRight, fovTop, fovBottom;
607    float focalX, focalY;
608    Angle fovX, fovY;
609
610    float slerpAmount, slerpPosition;
611    Plane viewClippingPlanes[ClippingPlane], worldClippingPlanes[ClippingPlane];
612    Matrix inverseTranspose, inverseMatrix;
613    Quaternion fromAngle, toAngle;
614    bool needUpdate;
615    Matrix viewMatrix;
616    int width, height;
617    Point origin;
618 };