// // Created by Иван Ильин on 14.01.2021. // #include "Camera.h" #include "utils/Log.h" std::vector &Camera::project(std::shared_ptr mesh) { if(!_ready) { Log::log("Camera::project(): cannot project _tris without camera initialization ( Camera::init() ) "); return this->_triangles; } if(!mesh->isVisible()) return this->_triangles; // Model transform matrix: translate _tris in the origin of body. Matrix4x4 M = Matrix4x4::Translation(mesh->position()); Matrix4x4 VM = _V * M; // We don't want to waste time re-allocating memory every time std::vector clippedTriangles, tempBuffer; for(auto& t : mesh->triangles()) { double dot = t.norm().dot((mesh->position() + t[0] - _position).normalized()); if(dot > 0) continue; Triangle clipped[2]; // It needs to be cleared because it's reused through iterations. Usually it doesn't free memory. clippedTriangles.clear(); // In the beginning we need to to translate drawTriangle from world coordinate to our camera system: // After that we apply clipping for all planes from _clipPlanes clippedTriangles.emplace_back(t * VM); for(auto& plane : _clipPlanes) { while(!clippedTriangles.empty()) { clipped[0] = clippedTriangles.back(); clipped[1] = clipped[0]; clippedTriangles.pop_back(); int additional = plane.clip(clipped[0], clipped[1]); for(int i = 0; i < additional; i++) tempBuffer.emplace_back(clipped[i]); } clippedTriangles.swap(tempBuffer); } for(auto& clippedTriangle : clippedTriangles) { sf::Color color = clippedTriangle.color(); sf::Color ambientColor = sf::Color(color.r * (0.3 * std::abs(dot) + 0.7), color.g * (0.3 * std::abs(dot) + 0.7), color.b * (0.3 * std::abs(dot) + 0.7), color.a); // Finally its time to project our clipped colored drawTriangle from 3D -> 2D // and transform it's coordinate to screen space (in pixels): clippedTriangle = clippedTriangle * _SP; clippedTriangle = Triangle(clippedTriangle[0] / clippedTriangle[0].w(), clippedTriangle[1] / clippedTriangle[1].w(), clippedTriangle[2] / clippedTriangle[2].w(), ambientColor); _triangles.emplace_back(clippedTriangle); } } return this->_triangles; } void Camera::init(int width, int height, double fov, double ZNear, double ZFar) { // We need to init camera only after creation or changing width, height, fov, ZNear or ZFar. // Because here we calculate matrix that does not change during the motion of _objects or camera _w = width; _h = height; _aspect = (double)width / (double)height; _P = Matrix4x4::Projection(fov, _aspect, ZNear, ZFar); _S = Matrix4x4::ScreenSpace(width, height); _SP = _S * _P; // screen-space-projections matrix // This is planes for clipping _tris. // Motivation: we are not interest in _tris that we cannot see. _clipPlanes.emplace_back(Plane(Point4D{0, 0, 1}, Point4D{0, 0, ZNear})); // near plane _clipPlanes.emplace_back(Plane(Point4D{0, 0, -1}, Point4D{0, 0, ZFar})); // far plane double thetta1 = M_PI*fov*0.5/180.0; double thetta2 = atan(_aspect * tan(thetta1)); _clipPlanes.emplace_back(Plane(Point4D{-cos(thetta2), 0, sin(thetta2)}, Point4D{0, 0, 0})); // left plane _clipPlanes.emplace_back(Plane(Point4D{cos(thetta2), 0, sin(thetta2)}, Point4D{0, 0, 0})); // right plane _clipPlanes.emplace_back(Plane(Point4D{0, cos(thetta1), sin(thetta1)}, Point4D{0, 0, 0})); // down plane _clipPlanes.emplace_back(Plane(Point4D{0, -cos(thetta1), sin(thetta1)}, Point4D{0, 0, 0})); // up plane _ready = true; Log::log("Camera::init(): camera successfully initialized."); } std::vector &Camera::sorted() { // Sort _tris from back to front // This is some replacement for Z-buffer std::sort(_triangles.begin(), _triangles.end(), [](Triangle &t1, Triangle &t2) { std::vector v_z1({t1[0].z(), t1[1].z(), t1[2].z()}); std::vector v_z2({t2[0].z(), t2[1].z(), t2[2].z()}); std::sort(v_z1.begin(), v_z1.end()); std::sort(v_z2.begin(), v_z2.end()); double z1 = v_z1[0] + v_z1[1] + v_z1[2]; double z2 = v_z2[0] + v_z2[1] + v_z2[2]; return z1 > z2; }); return _triangles; } void Camera::clear() { // Cleaning all _tris and recalculation of View matrix // That is like preparation for new camera shot: we need to set // the position of camera and insert new cartridge for photo. _triangles.clear(); _V = Matrix4x4::View(_left, _up, _lookAt, _position); } void Camera::rotateX(double rx) { _angle = Point4D{_angle.x() + rx, _angle.y(), _angle.z()}; _left = Matrix4x4::RotationX(rx) * _left; _up = Matrix4x4::RotationX(rx) * _up; _lookAt = Matrix4x4::RotationX(rx) * _lookAt; for(auto attached : _attachedObjects) attached->rotateRelativePoint(position(), Point4D{rx, 0, 0}); } void Camera::rotateY(double ry) { _angle = Point4D{_angle.x(), _angle.y() + ry, _angle.z()}; _left = Matrix4x4::RotationY(ry) * _left; _up = Matrix4x4::RotationY(ry) * _up; _lookAt = Matrix4x4::RotationY(ry) * _lookAt; for(auto attached : _attachedObjects) attached->rotateRelativePoint(position(), Point4D{0, ry, 0}); } void Camera::rotateZ(double rz) { _angle = Point4D{_angle.x(), _angle.y(), _angle.z() + rz}; _left = Matrix4x4::RotationZ(rz) * _left; _up = Matrix4x4::RotationZ(rz) * _up; _lookAt = Matrix4x4::RotationZ(rz) * _lookAt; for(auto attached : _attachedObjects) attached->rotateRelativePoint(position(), Point4D{0, 0, rz}); } void Camera::rotate(const Point4D& r) { rotateX(r.x()); rotateY(r.y()); rotateZ(r.z()); } void Camera::rotate(const Point4D& v, double rv) { _left = Matrix4x4::Rotation(v, rv) * _left; _up = Matrix4x4::Rotation(v, rv) * _up; _lookAt = Matrix4x4::Rotation(v, rv) * _lookAt; for(auto attached : _attachedObjects) attached->rotateRelativePoint(position(), v, rv); } void Camera::rotateLeft(double rl) { _angleLeftUpLookAt = Point4D{_angleLeftUpLookAt.x() + rl, _angleLeftUpLookAt.y(), _angleLeftUpLookAt.z()}; rotate(_left, rl); for(auto attached : _attachedObjects) attached->rotateRelativePoint(position(), _left, rl); } void Camera::rotateUp(double ru) { _angleLeftUpLookAt = Point4D{_angleLeftUpLookAt.x(), _angleLeftUpLookAt.y() + ru, _angleLeftUpLookAt.z()}; rotate(_up, ru); for(auto attached : _attachedObjects) attached->rotateRelativePoint(position(), _up, ru); } void Camera::rotateLookAt(double rlAt) { _angleLeftUpLookAt = Point4D{_angleLeftUpLookAt.x(), _angleLeftUpLookAt.y(), _angleLeftUpLookAt.z() + rlAt}; rotate(_lookAt, rlAt); for(auto attached : _attachedObjects) attached->rotateRelativePoint(position(), _lookAt, rlAt); } void Camera::rotateRelativePoint(const Point4D &s, const Point4D &r) { _angle = _angle + r; // Translate XYZ by vector r1 Point4D r1 = _position - s; // In translated coordinate system we rotate camera and position Point4D r2 = Matrix4x4::Rotation(r)*r1; rotate(r); // After rotation we translate XYZ by vector -r2 and recalculate position _position = s + r2; for(auto attached : _attachedObjects) attached->rotateRelativePoint(s, r); } void Camera::rotateRelativePoint(const Point4D &s, const Point4D &v, double r) { // Translate XYZ by vector r1 Point4D r1 = _position - s; // In translated coordinate system we rotate camera and position Point4D r2 = Matrix4x4::Rotation(v, r)*r1; rotate(v, r); // After rotation we translate XYZ by vector -r2 and recalculate position _position = s + r2; for(auto attached : _attachedObjects) attached->rotateRelativePoint(s, v, r); } void Camera::translateToPoint(const Point4D &point) { translate(point - _position); }