Theme Catppuccin
Make yourself at homeCatppuccin

A softer light for long learning sessions.

Appearance

Loading your preference…

Dark palette

Choose a dark palette to try it.

Light uses Latte. Your choices stay in this browser.

Back to your source shelf

Inside the companion

The Shader Workshop

Open a native OpenGL window, draw a triangle, then move a camera around three exhibits.

Project files 8

Predict the transformed point

GPU-independent camera, projection, geometry, and size-boundary checks.

57 lines contracts_test.cpp
#include "contracts.hpp"
#include <iostream>
#include <stdexcept>

int main() {
    std::size_t checks=0;
    const auto require=[&](bool condition,const char* message){++checks;if(!condition)throw std::runtime_error(message);};
    try {
        using namespace observatory;
        require(!unit({0,0,0}),"zero direction rejected");
        const auto normal=unit({0,3,4});
        require(normal&&std::abs(normal->y-0.6F)<1e-6F&&std::abs(normal->z-0.8F)<1e-6F,"known normalization");
        require(!unit({std::numeric_limits<float>::infinity(),0,0}),"nonfinite direction rejected");
        const auto rotated=multiply(identity(),rotate_z(std::numbers::pi_v<float>/2));
        require(std::abs(rotated.data[0])<1e-6F&&std::abs(rotated.data[1]-1)<1e-6F,"quarter rotation and identity multiply");
        require(!perspective(45,0,0.1F,100),"zero aspect rejected");
        require(!perspective(45,1,1,0.1F),"reversed depth rejected");
        const auto projection=perspective(60,1,0.1F,10);
        require(projection.has_value(),"valid perspective accepted");
        const auto ndc=[&](float z){return (projection->data[10]*z+projection->data[14])/(-z);};
        require(std::abs(ndc(-0.1F)+1)<1e-5F&&std::abs(ndc(-10)-1)<1e-5F,"OpenGL near/far NDC convention");
        const auto close=[](float a,float b){return std::abs(a-b)<1e-5F;};
        const auto view=look_at({0,0,4},{0,0,0},{0,1,0});
        require(view.has_value(),"axis camera accepted");
        const auto origin=transform(*view,{0,0,0,1});
        const auto eye=transform(*view,{0,0,4,1});
        require(close(origin[2],-4)&&close(origin[3],1),"target is four units along view negative Z");
        require(close(eye[0],0)&&close(eye[1],0)&&close(eye[2],0),"eye maps to view origin");
        const auto side_view=look_at({4,0,0},{0,0,0},{0,1,0});
        require(side_view.has_value(),"side camera accepted");
        const auto side_target=transform(*side_view,{0,0,0,1});
        const auto side_right=transform(*side_view,{0,0,-1,1});
        require(close(side_target[2],-4)&&close(side_right[0],1),"side camera basis and handedness");
        require(!look_at({0,0,0},{0,0,0},{0,1,0}),"coincident eye and target rejected");
        require(!look_at({0,0,4},{0,0,0},{0,0,1}),"parallel up rejected");
        require(!look_at({0,0,4},{0,0,0},{0,0,0}),"zero up rejected");
        require(!look_at({0,0,std::numeric_limits<float>::quiet_NaN()},{0,0,0},{0,1,0}),"nonfinite view rejected");
        const auto model=translation({1,2,3});
        const auto moved=transform(model,{0,0,0,1});
        const auto direction=transform(model,{0,1,0,0});
        require(close(moved[0],1)&&close(moved[1],2)&&close(moved[2],3),"point translation");
        require(close(direction[0],0)&&close(direction[1],1)&&close(direction[2],0),"direction is not translated");
        const auto composed=transform(multiply(*projection,multiply(*view,model)),{0,0,0,1});
        const auto staged=transform(*projection,transform(*view,transform(model,{0,0,0,1})));
        require(composed==staged&&close(composed[3],1),"projection view model agrees with staged transform");
        const auto wide=perspective(60,2,0.1F,10);
        require(wide&&close(wide->data[0]*2,projection->data[0])&&close(wide->data[5],projection->data[5]),"resize aspect changes horizontal scale only");
        const auto edge=transform(*projection,{std::tan(std::numbers::pi_v<float>/6),0,-1,1});
        require(close(edge[0]/edge[3],1),"known frustum right edge maps to NDC one");
        require(!rgb_bytes(0,10)&&!rgb_bytes(-1,10),"invalid capture extents rejected");
        require(rgb_bytes(4096,4096)==16U*1024U*1024U*3U,"exact capture budget");
        require(!rgb_bytes(4096,4097),"one row beyond budget rejected");
        std::cout<<checks<<" CPU contracts passed. No OpenGL context was exercised.\n";
        return 0;
    } catch(const std::exception& error){std::cerr<<error.what()<<'\n';return 1;}
}

This is the file included with the course. Copy and download give you its complete source.

Taking your work offline? Download all course companions