Skip to content

Instantly share code, notes, and snippets.

@WillWetzel
Created October 6, 2018 22:54
Show Gist options
  • Save WillWetzel/df76b931d4bfd0f806f0f6dccecf42fd to your computer and use it in GitHub Desktop.
Save WillWetzel/df76b931d4bfd0f806f0f6dccecf42fd to your computer and use it in GitHub Desktop.
Software Rasteriser
#include "Colour.h"
Colour Colour::White(255,255,255,255);
/******************************************************************************
Class:Colour
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:Simple class to hold colours made up of 4 bytes.
You might be wondering why in the class, the bytes aren't arranged in rgb order
This is due to how Win32 internally stores its bitmaps - in bgra. In order to
speed up mem copies to the window buffer, we keep our colours in the same order
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
struct Colour
{
union
{
struct
{
unsigned char b;
unsigned char g;
unsigned char r;
unsigned char a;
};
unsigned int c;
};
Colour()
{
r = 0;
g = 0;
b = 0;
a = 255;
}
Colour(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
this->r = r;
this->g = g;
this->b = b;
this->a = a;
}
inline void Reset()
{
r = 0;
g = 0;
b = 0;
a = 255;
}
inline void Set(const unsigned char &red, const unsigned char &green, const unsigned char &blue,
const unsigned char &alpha)
{
r = red;
g = green;
b = blue;
a = alpha;
}
static Colour Lerp(const Colour &a, const Colour &b, float by)
{
Colour p;
float minusBy = 1.0f - by;
p.r = unsigned char((b.r * by) + (a.r * minusBy));
p.g = unsigned char((b.g * by) + (a.g * minusBy));
p.b = unsigned char((b.b * by) + (a.b * minusBy));
p.a = unsigned char((b.a * by) + (a.a * minusBy));
return p;
}
inline Colour operator*(const float factor) const
{
return Colour((unsigned char)(float)(r * factor), (unsigned char)(float)(g * factor),
(unsigned char)(float)(b * factor), (unsigned char)(float)(a * factor));
}
inline Colour operator+(const Colour &add) const
{
return Colour(r + add.r, g + add.g, b + add.b, a + add.a);
}
inline void operator+=(const Colour &add)
{
r += add.r;
g += add.g;
b += add.b;
a += add.a;
}
inline Colour operator-(const Colour &sub) const
{
return Colour(r - sub.r, g - sub.g, b - sub.b, a - sub.a);
}
static Colour White;
};
/******************************************************************************
Author:Rich Davison
Description: Some random variables and functions, for lack of a better place
to put them.
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
// It's pi(ish)...
static const float PI = 3.14159265358979323846f;
// It's pi...divided by 360.0f!
static const float PI_OVER_360 = PI / 360.0f;
// Radians to degrees
static inline double RadToDeg(const double deg)
{
return deg * 180.0 / PI;
};
// Degrees to radians
static inline double DegToRad(const double rad)
{
return rad * PI / 180.0;
};
// I blame Microsoft...
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define clamp(a, b, c) (a < b ? b : (a > c ? c : a))
typedef unsigned int uint;
/******************************************************************************
Class:InputDevice
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:Abstract base class for Windows RAW keyboard / mouse input
Input devices can be temporarily sent to sleep (so keyboard input doesn't work
when the game is minimised etc), and obviously woken up again.
Input devices may also keep track of 'holds' - i.e keys or buttons pressed for
more than one frame. This allows you to have both things that trigger once,
and are continuously active as long as a key / button is pressed.
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include <windows.h>
/*
Microsoft helpfully don't seem to have this in any of their header files,
despite it being how RAW input works....GG guys.
*/
#ifndef HID_USAGE_PAGE_GENERIC
#define HID_USAGE_PAGE_GENERIC ((USHORT)0x01)
#endif
#ifndef HID_USAGE_GENERIC_MOUSE
#define HID_USAGE_GENERIC_MOUSE ((USHORT)0x02)
#endif
#ifndef HID_USAGE_GENERIC_KEYBOARD
#define HID_USAGE_GENERIC_KEYBOARD ((USHORT)0x06)
#endif
class InputDevice
{
protected:
friend class Window;
InputDevice(void)
{
isAwake = true;
};
~InputDevice(void) {};
protected:
virtual void Update(RAWINPUT *raw) = 0;
virtual void UpdateHolds()
{
}
virtual void Sleep()
{
isAwake = false;
}
virtual void Wake()
{
isAwake = true;
}
bool isAwake; // Is the device awake...
RAWINPUTDEVICE rid; // Windows OS hook
};
#include "Keyboard.h"
Keyboard *Keyboard::instance = 0;
Keyboard::Keyboard(HWND &hwnd)
{
// Initialise the arrays to false!
ZeroMemory(keyStates, KEY_MAX * sizeof(bool));
ZeroMemory(holdStates, KEY_MAX * sizeof(bool));
// Tedious windows RAW input stuff
rid.usUsagePage = HID_USAGE_PAGE_GENERIC; // The keyboard isn't anything fancy
rid.usUsage = HID_USAGE_GENERIC_KEYBOARD; // but it's definitely a keyboard!
rid.dwFlags = RIDEV_INPUTSINK; // Yes, we want to always receive RAW input...
rid.hwndTarget = hwnd; // Windows OS window handle
RegisterRawInputDevices(&rid, 1, sizeof(rid)); // We just want one keyboard, please!
}
void Keyboard::Initialise(HWND &hwnd)
{
instance = new Keyboard(hwnd);
}
void Keyboard::Destroy()
{
delete instance;
}
/*
Updates variables controlling whether a keyboard key has been
held for multiple frames.
*/
void Keyboard::UpdateHolds()
{
memcpy(instance->holdStates, instance->keyStates, KEY_MAX * sizeof(bool));
}
/*
Sends the keyboard to sleep, so it doesn't process any
keypresses until it receives a Wake()
*/
void Keyboard::Sleep()
{
isAwake = false; // Night night!
// Prevents incorrectly thinking keys have been held / pressed when waking back up
ZeroMemory(instance->keyStates, KEY_MAX * sizeof(bool));
ZeroMemory(instance->holdStates, KEY_MAX * sizeof(bool));
}
/*
Returns if the key is down. Doesn't need bounds checking -
a KeyboardKeys enum is always in range
*/
bool Keyboard::KeyDown(KeyboardKeys key)
{
return instance->keyStates[key];
}
/*
Returns if the key is down, and has been held down for multiple updates.
Doesn't need bounds checking - a KeyboardKeys enum is always in range
*/
bool Keyboard::KeyHeld(KeyboardKeys key)
{
if (instance->KeyDown(key) && instance->holdStates[key])
{
return true;
}
return false;
}
/*
Returns true only if the key is down, but WASN't down last update.
Doesn't need bounds checking - a KeyboardKeys enum is always in range
*/
bool Keyboard::KeyTriggered(KeyboardKeys key)
{
return (instance->KeyDown(key) && !instance->KeyHeld(key));
}
/*
Updates the keyboard state with data received from the OS.
*/
void Keyboard::Update(RAWINPUT *raw)
{
if (isAwake)
{
DWORD key = (DWORD)raw->data.keyboard.VKey;
// We should do bounds checking!
if (key < 0 || key > KEY_MAX)
{
return;
}
// First bit of the flags tag determines whether the key is down or up
keyStates[key] = !(raw->data.keyboard.Flags & RI_KEY_BREAK);
}
}
/******************************************************************************
Class:Keyboard
Implements:InputDevice
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:RAW Input keyboard class. I've made absolutely no attempt to ensure
that this class works for multiple keyboards attached to a single system.
You shouldn't really have to mess with this class too much, there's nothing too
interesting about it!
STUDENT CHALLENGE! You could have a function pointer per key, that can be
automatically called when a key is triggered / held? (Checked from within the
Update function)
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include "InputDevice.h"
// http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx
enum KeyboardKeys
{
KEY_LBUTTON = 0x01, // Left mouse button
KEY_RBUTTON = 0x02, // Right mouse button
KEY_CANCEL = 0x03, // Control-break processing
KEY_MBUTTON = 0x04, // Middle mouse button (three-button mouse)
KEY_XBUTTON1 = 0x05, // Windows 2000/XP: X1 mouse button
KEY_XBUTTON2 = 0x06, // Windows 2000/XP: X2 mouse button
KEY_BACK = 0x08, // BACKSPACE key
KEY_TAB = 0x09, // TAB key
KEY_CLEAR = 0x0C, // CLEAR key
KEY_RETURN = 0x0D, // ENTER key
KEY_SHIFT = 0x10, // SHIFT key
KEY_CONTROL = 0x11, // CTRL key
KEY_MENU = 0x12, // ALT key
KEY_PAUSE = 0x13, // PAUSE key
KEY_CAPITAL = 0x14, // CAPS LOCK key
KEY_KANA = 0x15, // IME Kana mode
KEY_HANGUEL = 0x15, // IME Hanguel mode (maintained for compatibility use KEY_HANGUL)
KEY_HANGUL = 0x15, // IME Hangul mode
KEY_JUNJA = 0x17, // IME Junja mode
KEY_FINAL = 0x18, // IME final mode
KEY_HANJA = 0x19, // IME Hanja mode
KEY_KANJI = 0x19, // IME Kanji mode
KEY_ESCAPE = 0x1B, // ESC key
KEY_CONVERT = 0x1C, // IME convert
KEY_NONCONVERT = 0x1D, // IME nonconvert
KEY_ACCEPT = 0x1E, // IME accept
KEY_MODECHANGE = 0x1F, // IME mode change request
KEY_SPACE = 0x20, // SPACEBAR
KEY_PRIOR = 0x21, // PAGE UP key
KEY_NEXT = 0x22, // PAGE DOWN key
KEY_END = 0x23, // END key
KEY_HOME = 0x24, // HOME key
KEY_LEFT = 0x25, // LEFT ARROW key
KEY_UP = 0x26, // UP ARROW key
KEY_RIGHT = 0x27, // RIGHT ARROW key
KEY_DOWN = 0x28, // DOWN ARROW key
KEY_SELECT = 0x29, // SELECT key
KEY_PRINT = 0x2A, // PRINT key
KEY_EXECUT = 0x2B, // EXECUTE key
KEY_SNAPSHOT = 0x2C, // PRINT SCREEN key
KEY_INSERT = 0x2D, // INS key
KEY_DELETE = 0x2E, // DEL key
KEY_HELP = 0x2F, // HELP key
KEY_0 = 0x30, // 0 key
KEY_1 = 0x31, // 1 key
KEY_2 = 0x32, // 2 key
KEY_3 = 0x33, // 3 key
KEY_4 = 0x34, // 4 key
KEY_5 = 0x35, // 5 key
KEY_6 = 0x36, // 6 key
KEY_7 = 0x37, // 7 key
KEY_8 = 0x38, // 8 key
KEY_9 = 0x39, // 9 key
KEY_A = 0x41, // A key
KEY_B = 0x42, // B key
KEY_C = 0x43, // C key
KEY_D = 0x44, // D key
KEY_E = 0x45, // E key
KEY_F = 0x46, // F key
KEY_G = 0x47, // G key
KEY_H = 0x48, // H key
KEY_I = 0x49, // I key
KEY_J = 0x4A, // J key
KEY_K = 0x4B, // K key
KEY_L = 0x4C, // L key
KEY_M = 0x4D, // M key
KEY_N = 0x4E, // N key
KEY_O = 0x4F, // O key
KEY_P = 0x50, // P key
KEY_Q = 0x51, // Q key
KEY_R = 0x52, // R key
KEY_S = 0x53, // S key
KEY_T = 0x54, // T key
KEY_U = 0x55, // U key
KEY_V = 0x56, // V key
KEY_W = 0x57, // W key
KEY_X = 0x58, // X key
KEY_Y = 0x59, // Y key
KEY_Z = 0x5A, // Z key
KEY_LWIN = 0x5B, // Left Windows key (Microsoft� Natural� keyboard)
KEY_RWIN = 0x5C, // Right Windows key (Natural keyboard)
KEY_APPS = 0x5D, // Applications key (Natural keyboard)
KEY_SLEEP = 0x5F, // Computer Sleep key
KEY_NUMPAD0 = 0x60, // Numeric keypad 0 key
KEY_NUMPAD1 = 0x61, // Numeric keypad 1 key
KEY_NUMPAD2 = 0x62, // Numeric keypad 2 key
KEY_NUMPAD3 = 0x63, // Numeric keypad 3 key
KEY_NUMPAD4 = 0x64, // Numeric keypad 4 key
KEY_NUMPAD5 = 0x65, // Numeric keypad 5 key
KEY_NUMPAD6 = 0x66, // Numeric keypad 6 key
KEY_NUMPAD7 = 0x67, // Numeric keypad 7 key
KEY_NUMPAD8 = 0x68, // Numeric keypad 8 key
KEY_NUMPAD9 = 0x69, // Numeric keypad 9 key
KEY_MULTIPLY = 0x6A, // Multiply key
KEY_ADD = 0x6B, // Add key
KEY_SEPARATOR = 0x6C, // Separator key
KEY_SUBTRACT = 0x6D, // Subtract key
KEY_DECIMAL = 0x6E, // Decimal key
KEY_DIVIDE = 0x6F, // Divide key
KEY_F1 = 0x70, // F1 key
KEY_F2 = 0x71, // F2 key
KEY_F3 = 0x72, // F3 key
KEY_F4 = 0x73, // F4 key
KEY_F5 = 0x74, // F5 key
KEY_F6 = 0x75, // F6 key
KEY_F7 = 0x76, // F7 key
KEY_F8 = 0x77, // F8 key
KEY_F9 = 0x78, // F9 key
KEY_F10 = 0x79, // F10 key
KEY_F11 = 0x7A, // F11 key
KEY_F12 = 0x7B, // F12 key
KEY_F13 = 0x7C, // F13 key
KEY_F14 = 0x7D, // F14 key
KEY_F15 = 0x7E, // F15 key
KEY_F16 = 0x7F, // F16 key
KEY_F17 = 0x80, // F17 key
KEY_F18 = 0x81, // F18 key
KEY_F19 = 0x82, // F19 key
KEY_F20 = 0x83, // F20 key
KEY_F21 = 0x84, // F21 key
KEY_F22 = 0x85, // F22 key
KEY_F23 = 0x86, // F23 key
KEY_F24 = 0x87, // F24 key
KEY_NUMLOCK = 0x90, // NUM LOCK key
KEY_SCROLL = 0x91, // SCROLL LOCK key
KEY_LSHIFT = 0xA0, // Left SHIFT key
KEY_RSHIFT = 0xA1, // Right SHIFT key
KEY_LCONTROL = 0xA2, // Left CONTROL key
KEY_RCONTROL = 0xA3, // Right CONTROL key
KEY_LMENU = 0xA4, // Left MENU key
KEY_RMENU = 0xA5, // Right MENU key
KEY_PLUS = 0xBB, // Plus Key (+)
KEY_COMMA = 0xBC, // Comma Key (,)
KEY_MINUS = 0xBD, // Minus Key (-)
KEY_PERIOD = 0xBE, // Period Key (.)
KEY_ATTN = 0xF6, // Attn key
KEY_CRSEL = 0xF7, // CrSel key
KEY_EXSEL = 0xF8, // ExSel key
KEY_EREOF = 0xF9, // Erase EOF key
KEY_PLAY = 0xFA, // Play key
KEY_ZOOM = 0xFB, // Zoom key
KEY_PA1 = 0xFD, // PA1 key
KEY_OEM_CLEAR = 0xFE, // Clear key
KEY_MAX = 0xFF
};
class Keyboard : public InputDevice
{
public:
friend class Window;
// Is this key currently pressed down?
static bool KeyDown(KeyboardKeys key);
// Has this key been held down for multiple frames?
static bool KeyHeld(KeyboardKeys key);
// Is this the first update the key has been pressed for?
static bool KeyTriggered(KeyboardKeys key);
protected:
Keyboard(HWND &hwnd);
~Keyboard(void)
{
}
// Update the holdStates array...call this each frame!
virtual void UpdateHolds();
// Update the keyStates array etc...call this each frame!
virtual void Update(RAWINPUT *raw);
// Sends the keyboard to sleep
virtual void Sleep();
static void Initialise(HWND &hwnd);
static void Destroy();
static Keyboard *instance;
bool keyStates[KEY_MAX]; // Is the key down?
bool holdStates[KEY_MAX]; // Has the key been down for multiple updates?
};
/**
* Name: Will Wetzel - 130251255
* Project: CSC3223 Graphics for Games, Coursework 1
* Date: 15/11/2016
* Description: This code creates a space scene with a moving space ship using basic shapes such as triangles and
* N sided shapes. The software Rasteriser is complete up to Tutorial 12 given to us in Blackboard.
*/
#include "SoftwareRasteriser.h"
#include "Mesh.h"
void generateStarfield(vector<RenderObject *> &out, const int num = 100,
const float xyFact = 1.0f, const float zFact = 1.0f);
void generateAsteroid(vector<RenderObject *> &out, const Vector3 &position, const float scale);
int main() {
const int num = 100; //High number to increase the chance they will be shown in the window..
//.. Aiming for between 5 and 10 to be present.
SoftwareRasteriser r(800, 600);
const float aspect = (float)800 / (float)600;
r.SetProjectionMatrix(Matrix4::Perspective(0.1f, 100.0f, aspect, 45.0f));
vector<RenderObject *> elements; //Array to store objects to be displayed in.
generateStarfield(elements, 10000);
for (int i = 0; i < num; ++i)
{
const float x = ((float)((rand() % 100) - 50)) * 1;
const float y = ((float)((rand() % 100) - 50)) * 1;
const float z = ((float)((rand() % 100) - 50)) * 1;
const float scale = ((float)(rand() % 100)) / 100.0f;
generateAsteroid(elements, Vector3(x, y, z), scale);
}
RenderObject *ship = new RenderObject();
ship->mesh = Mesh::GenerateTriangle(); //Generate triangle for ship and rotate and scale.
ship->modelMatrix = Matrix4::Scale(Vector3(0.5, 0.5, 0.5)) *
Matrix4::Rotation(40.0f, Vector3(1.0, 1.0, 0.0));
elements.push_back(ship);
Matrix4 viewMatrix = Matrix4::Translation(Vector3(0.0f, 0.0f, -10.0f));
while (r.UpdateWindow()) {
//Moving ship if key is pressed.
if (Keyboard::KeyDown(KEY_A)) {
viewMatrix = viewMatrix *
Matrix4::Translation(Vector3(-0.01f, 0, 0));
}
if (Keyboard::KeyDown(KEY_D)) {
viewMatrix = viewMatrix *
Matrix4::Translation(Vector3(0.01f, 0, 0));
}
if (Keyboard::KeyDown(KEY_W)) {
viewMatrix = viewMatrix *
Matrix4::Translation(Vector3(0.0, 0.01f, 0));
}
if (Keyboard::KeyDown(KEY_S)) {
viewMatrix = viewMatrix *
Matrix4::Translation(Vector3(0.0, -0.01f, 0));
}
r.SetViewMatrix(viewMatrix);
r.ClearBuffers();
// Draw opbjects to be displayed.
for (vector<RenderObject *>::iterator it = elements.begin(); it != elements.end(); ++it)
r.DrawObject(*it);
r.SwapBuffers();
}
elements.clear();
return 0;
}
/*Generate star field for background. Position of stars is random.*/
void generateStarfield(vector<RenderObject *> &out, const int num, const float xyFact,
const float zFact)
{
for (int i = 0; i < num; ++i)
{
const float x = ((float)((rand() % 100) - 50)) * xyFact;
const float y = ((float)((rand() % 100) - 50)) * xyFact;
const float z = ((float)((rand() % 100) - 50)) * zFact;
// Generate random colour
const int r = (rand() % 100) + 155;
const int g = (rand() % 100) + 155;
const int b = (rand() % 100) + 155;
Colour c = Colour(r, g, b, 255);
RenderObject *o = new RenderObject();
o->mesh = Mesh::GeneratePoint(Vector3(), c);
o->modelMatrix = Matrix4::Translation(Vector3(x, y, z));
out.push_back(o);
}
}
/*Method to generate Asteroid using parameters passed from main program.*/
void generateAsteroid(vector<RenderObject *> &out, const Vector3 &position, const float scale)
{
const Matrix4 modelMat =
Matrix4::Translation(position) * Matrix4::Scale(Vector3(scale, scale, scale));
// Random rotation between 160 - 200
const float rot = (float)((rand() % 40) + 160);
RenderObject *o1 = new RenderObject();
o1->mesh = Mesh::GenerateNSided2D(7);
o1->modelMatrix = modelMat * Matrix4::Scale(Vector3(0.85f, 0.85f, 0.85f)) *
Matrix4::Rotation(rot, Vector3(0.0f, 0.0f, 1.0f));
out.push_back(o1);
}
#include "Matrix4.h"
Matrix4::Matrix4(void)
{
ToIdentity();
}
Matrix4::Matrix4(float elements[16])
{
memcpy(this->values, elements, 16 * sizeof(float));
}
Matrix4::~Matrix4(void)
{
ToIdentity();
}
void Matrix4::ToIdentity()
{
ToZero();
values[0] = 1.0f;
values[5] = 1.0f;
values[10] = 1.0f;
values[15] = 1.0f;
}
void Matrix4::ToZero()
{
for (int i = 0; i < 16; i++)
{
values[i] = 0.0f;
}
}
Vector3 Matrix4::GetPositionVector() const
{
return Vector3(values[12], values[13], values[14]);
}
void Matrix4::SetPositionVector(const Vector3 in)
{
values[12] = in.x;
values[13] = in.y;
values[14] = in.z;
}
Vector3 Matrix4::GetScalingVector() const
{
return Vector3(values[0], values[5], values[10]);
}
void Matrix4::SetScalingVector(const Vector3 &in)
{
values[0] = in.x;
values[5] = in.y;
values[10] = in.z;
}
Matrix4 Matrix4::Perspective(float znear, float zfar, float aspect, float fov)
{
Matrix4 m;
const float f = 1.0f / tan(fov * PI_OVER_360);
float negDepth = znear - zfar;
m.values[0] = f / aspect;
m.values[5] = f;
m.values[10] = (zfar + znear) / negDepth;
m.values[11] = -1.0f;
m.values[14] = 2.0f * (znear * zfar) / negDepth;
m.values[15] = 0.0f;
return m;
}
// http://www.opengl.org/sdk/docs/man/xhtml/glOrtho.xml
Matrix4 Matrix4::Orthographic(float znear, float zfar, float right, float left, float top,
float bottom)
{
Matrix4 m;
m.values[0] = 2.0f / (right - left);
m.values[5] = 2.0f / (top - bottom);
m.values[10] = -2.0f / (zfar - znear);
m.values[12] = -(right + left) / (right - left);
m.values[13] = -(top + bottom) / (top - bottom);
m.values[14] = -(zfar + znear) / (zfar - znear);
m.values[15] = 1.0f;
return m;
}
Matrix4 Matrix4::BuildViewMatrix(const Vector3 &from, const Vector3 &lookingAt,
const Vector3 up /*= Vector3(1,0,0)*/)
{
Matrix4 r;
r.SetPositionVector(Vector3(-from.x, -from.y, -from.z));
Matrix4 m;
Vector3 f = (lookingAt - from);
f.Normalise();
Vector3 s = Vector3::Cross(f, up);
Vector3 u = Vector3::Cross(s, f);
m.values[0] = s.x;
m.values[4] = s.y;
m.values[8] = s.z;
m.values[1] = u.x;
m.values[5] = u.y;
m.values[9] = u.z;
m.values[2] = -f.x;
m.values[6] = -f.y;
m.values[10] = -f.z;
return m * r;
}
Matrix4 Matrix4::Rotation(float degrees, const Vector3 &inaxis)
{
Matrix4 m;
Vector3 axis = inaxis;
axis.Normalise();
float c = cos((float)DegToRad(degrees));
float s = sin((float)DegToRad(degrees));
m.values[0] = (axis.x * axis.x) * (1.0f - c) + c;
m.values[1] = (axis.y * axis.x) * (1.0f - c) + (axis.z * s);
m.values[2] = (axis.z * axis.x) * (1.0f - c) - (axis.y * s);
m.values[4] = (axis.x * axis.y) * (1.0f - c) - (axis.z * s);
m.values[5] = (axis.y * axis.y) * (1.0f - c) + c;
m.values[6] = (axis.z * axis.y) * (1.0f - c) + (axis.x * s);
m.values[8] = (axis.x * axis.z) * (1.0f - c) + (axis.y * s);
m.values[9] = (axis.y * axis.z) * (1.0f - c) - (axis.x * s);
m.values[10] = (axis.z * axis.z) * (1.0f - c) + c;
return m;
}
Matrix4 Matrix4::Scale(const Vector3 &scale)
{
Matrix4 m;
m.values[0] = scale.x;
m.values[5] = scale.y;
m.values[10] = scale.z;
return m;
}
Matrix4 Matrix4::Translation(const Vector3 &translation)
{
Matrix4 m;
m.values[12] = translation.x;
m.values[13] = translation.y;
m.values[14] = translation.z;
return m;
}
// Yoinked from the Open Source Doom 3 release - all credit goes to id software!
Matrix4 Matrix4::Inverse()
{
float det, invDet;
Matrix4 mat = *this;
mat.values[0];
// 2x2 sub-determinants required to calculate 4x4 determinant
float det2_01_01 = mat.values[0] * mat.values[5] - mat.values[1] * mat.values[4];
float det2_01_02 = mat.values[0] * mat.values[6] - mat.values[2] * mat.values[4];
float det2_01_03 = mat.values[0] * mat.values[7] - mat.values[3] * mat.values[4];
float det2_01_12 = mat.values[1] * mat.values[6] - mat.values[2] * mat.values[5];
float det2_01_13 = mat.values[1] * mat.values[7] - mat.values[3] * mat.values[5];
float det2_01_23 = mat.values[2] * mat.values[7] - mat.values[3] * mat.values[6];
// 3x3 sub-determinants required to calculate 4x4 determinant
float det3_201_012 =
mat.values[8] * det2_01_12 - mat.values[9] * det2_01_02 + mat.values[10] * det2_01_01;
float det3_201_013 =
mat.values[8] * det2_01_13 - mat.values[9] * det2_01_03 + mat.values[11] * det2_01_01;
float det3_201_023 =
mat.values[8] * det2_01_23 - mat.values[10] * det2_01_03 + mat.values[11] * det2_01_02;
float det3_201_123 =
mat.values[9] * det2_01_23 - mat.values[10] * det2_01_13 + mat.values[11] * det2_01_12;
det = (-det3_201_123 * mat.values[12] + det3_201_023 * mat.values[13] -
det3_201_013 * mat.values[14] + det3_201_012 * mat.values[15]);
invDet = 1.0f / det;
// remaining 2x2 sub-determinants
float det2_03_01 = mat.values[0] * mat.values[13] - mat.values[1] * mat.values[12];
float det2_03_02 = mat.values[0] * mat.values[14] - mat.values[2] * mat.values[12];
float det2_03_03 = mat.values[0] * mat.values[15] - mat.values[3] * mat.values[12];
float det2_03_12 = mat.values[1] * mat.values[14] - mat.values[2] * mat.values[13];
float det2_03_13 = mat.values[1] * mat.values[15] - mat.values[3] * mat.values[13];
float det2_03_23 = mat.values[2] * mat.values[15] - mat.values[3] * mat.values[14];
float det2_13_01 = mat.values[4] * mat.values[13] - mat.values[5] * mat.values[12];
float det2_13_02 = mat.values[4] * mat.values[14] - mat.values[6] * mat.values[12];
float det2_13_03 = mat.values[4] * mat.values[15] - mat.values[7] * mat.values[12];
float det2_13_12 = mat.values[5] * mat.values[14] - mat.values[6] * mat.values[13];
float det2_13_13 = mat.values[5] * mat.values[15] - mat.values[7] * mat.values[13];
float det2_13_23 = mat.values[6] * mat.values[15] - mat.values[7] * mat.values[14];
// remaining 3x3 sub-determinants
float det3_203_012 =
mat.values[8] * det2_03_12 - mat.values[9] * det2_03_02 + mat.values[10] * det2_03_01;
float det3_203_013 =
mat.values[8] * det2_03_13 - mat.values[9] * det2_03_03 + mat.values[11] * det2_03_01;
float det3_203_023 =
mat.values[8] * det2_03_23 - mat.values[10] * det2_03_03 + mat.values[11] * det2_03_02;
float det3_203_123 =
mat.values[9] * det2_03_23 - mat.values[10] * det2_03_13 + mat.values[11] * det2_03_12;
float det3_213_012 =
mat.values[8] * det2_13_12 - mat.values[9] * det2_13_02 + mat.values[10] * det2_13_01;
float det3_213_013 =
mat.values[8] * det2_13_13 - mat.values[9] * det2_13_03 + mat.values[11] * det2_13_01;
float det3_213_023 =
mat.values[8] * det2_13_23 - mat.values[10] * det2_13_03 + mat.values[11] * det2_13_02;
float det3_213_123 =
mat.values[9] * det2_13_23 - mat.values[10] * det2_13_13 + mat.values[11] * det2_13_12;
float det3_301_012 =
mat.values[12] * det2_01_12 - mat.values[13] * det2_01_02 + mat.values[14] * det2_01_01;
float det3_301_013 =
mat.values[12] * det2_01_13 - mat.values[13] * det2_01_03 + mat.values[15] * det2_01_01;
float det3_301_023 =
mat.values[12] * det2_01_23 - mat.values[14] * det2_01_03 + mat.values[15] * det2_01_02;
float det3_301_123 =
mat.values[13] * det2_01_23 - mat.values[14] * det2_01_13 + mat.values[15] * det2_01_12;
mat.values[0] = -det3_213_123 * invDet;
mat.values[4] = +det3_213_023 * invDet;
mat.values[8] = -det3_213_013 * invDet;
mat.values[12] = +det3_213_012 * invDet;
mat.values[1] = +det3_203_123 * invDet;
mat.values[5] = -det3_203_023 * invDet;
mat.values[9] = +det3_203_013 * invDet;
mat.values[13] = -det3_203_012 * invDet;
mat.values[2] = +det3_301_123 * invDet;
mat.values[6] = -det3_301_023 * invDet;
mat.values[10] = +det3_301_013 * invDet;
mat.values[14] = -det3_301_012 * invDet;
mat.values[3] = -det3_201_123 * invDet;
mat.values[7] = +det3_201_023 * invDet;
mat.values[11] = -det3_201_013 * invDet;
mat.values[15] = +det3_201_012 * invDet;
return mat;
}
/******************************************************************************
Class:Matrix4
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:VERY simple 4 by 4 matrix class. Students are encouraged to modify
this as necessary! Overloading the [] operator to allow access to the values
array in a neater way might be a good start, as the floats that make the matrix
up are currently public.
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include <iostream>
#include "common.h"
#include "Vector3.h"
#include "Vector4.h"
class Vector3;
class Matrix4
{
public:
Matrix4(void);
Matrix4(float elements[16]);
~Matrix4(void);
float values[16];
// Set all matrix values to zero
void ToZero();
// Sets matrix to identity matrix (1.0 down the diagonal)
void ToIdentity();
// Gets the OpenGL position vector (floats 12,13, and 14)
Vector3 GetPositionVector() const;
// Sets the OpenGL position vector (floats 12,13, and 14)
void SetPositionVector(const Vector3 in);
// Gets the scale vector (floats 1,5, and 10)
Vector3 GetScalingVector() const;
// Sets the scale vector (floats 1,5, and 10)
void SetScalingVector(const Vector3 &in);
// Creates a rotation matrix that rotates by 'degrees' around the 'axis'
// Analogous to glRotatef
static Matrix4 Rotation(float degrees, const Vector3 &axis);
// Creates a scaling matrix (puts the 'scale' vector down the diagonal)
// Analogous to glScalef
static Matrix4 Scale(const Vector3 &scale);
// Creates a translation matrix (identity, with 'translation' vector at
// floats 12, 13, and 14. Analogous to glTranslatef
static Matrix4 Translation(const Vector3 &translation);
// Creates a perspective matrix, with 'znear' and 'zfar' as the near and
// far planes, using 'aspect' and 'fov' as the aspect ratio and vertical
// field of vision, respectively.
static Matrix4 Perspective(float znear, float zfar, float aspect, float fov);
// Creates an orthographic matrix with 'znear' and 'zfar' as the near and
// far planes, and so on. Descriptive variable names are a good thing!
static Matrix4 Orthographic(float znear, float zfar, float right, float left, float top,
float bottom);
// Builds a view matrix suitable for sending straight to the vertex shader.
// Puts the camera at 'from', with 'lookingAt' centered on the screen, with
//'up' as the...up axis (pointing towards the top of the screen)
static Matrix4 BuildViewMatrix(const Vector3 &from, const Vector3 &lookingAt,
const Vector3 up = Vector3(0, 1, 0));
Matrix4 Inverse();
Matrix4 GetTransposedRotation()
{
Matrix4 temp;
temp.values[1] = values[4];
temp.values[4] = values[1];
temp.values[2] = values[8];
temp.values[8] = values[2];
temp.values[6] = values[9];
temp.values[9] = values[6];
return temp;
}
void SetRow(unsigned int row, const Vector4 &val)
{
if (row < 3)
{
int start = 4 * row;
values[start += 4] = val.x;
values[start += 4] = val.y;
values[start += 4] = val.z;
values[start += 4] = val.w;
}
}
void SetColumn(unsigned int column, const Vector4 &val)
{
if (column < 3)
{
memcpy(&values[4 * column], &val, sizeof(Vector4));
}
}
Vector4 GetRow(unsigned int row)
{
Vector4 out(0, 0, 0, 1);
if (row < 3)
{
int start = 4 * row;
out.x = values[start += 4];
out.y = values[start += 4];
out.z = values[start += 4];
out.w = values[start += 4];
}
return out;
}
Vector4 GetColumn(unsigned int column)
{
Vector4 out(0, 0, 0, 1);
if (column < 3)
{
memcpy(&out, &values[4 * column], sizeof(Vector4));
}
return out;
}
// Multiplies 'this' matrix by matrix 'a'. Performs the multiplication in 'OpenGL' order (ie,
// backwards)
inline Matrix4 operator*(const Matrix4 &b) const
{
Matrix4 out;
for (unsigned int col = 0; col < 4; ++col)
{
for (unsigned int row = 0; row < 4; ++row)
{
int current = row + (col * 4);
out.values[row + (col * 4)] = 0.0f;
for (unsigned int i = 0; i < 4; ++i)
{
out.values[row + (col * 4)] += this->values[row + (i * 4)] * b.values[(col * 4) + i];
}
}
}
return out;
}
inline Vector3 operator*(const Vector3 &v) const
{
Vector3 vec;
float temp;
vec.x = v.x * values[0] + v.y * values[4] + v.z * values[8] + values[12];
vec.y = v.x * values[1] + v.y * values[5] + v.z * values[9] + values[13];
vec.z = v.x * values[2] + v.y * values[6] + v.z * values[10] + values[14];
temp = v.x * values[3] + v.y * values[7] + v.z * values[11] + values[15];
vec.x = vec.x / temp;
vec.y = vec.y / temp;
vec.z = vec.z / temp;
return vec;
};
inline Vector4 operator*(const Vector4 &v) const
{
return Vector4(v.x * values[0] + v.y * values[4] + v.z * values[8] + v.w * values[12],
v.x * values[1] + v.y * values[5] + v.z * values[9] + v.w * values[13],
v.x * values[2] + v.y * values[6] + v.z * values[10] + v.w * values[14],
v.x * values[3] + v.y * values[7] + v.z * values[11] + v.w * values[15]);
};
// Handy string output for the matrix. Can get a bit messy, but better than nothing!
inline friend std::ostream &operator<<(std::ostream &o, const Matrix4 &m)
{
o << "Mat4(";
o << "\t" << m.values[0] << "," << m.values[1] << "," << m.values[2] << "," << m.values[3]
<< std::endl;
o << "\t\t" << m.values[4] << "," << m.values[5] << "," << m.values[6] << "," << m.values[7]
<< std::endl;
o << "\t\t" << m.values[8] << "," << m.values[9] << "," << m.values[10] << "," << m.values[11]
<< std::endl;
o << "\t\t" << m.values[12] << "," << m.values[13] << "," << m.values[14] << "," << m.values[15]
<< " )" << std::endl;
return o;
}
};
#include "Mesh.h"
#include <vector>
Mesh::Mesh(void)
{
type = PRIMITIVE_POINTS;
numVertices = 0;
vertices = NULL;
colours = NULL;
textureCoords = NULL;
}
Mesh::~Mesh(void)
{
delete[] vertices;
delete[] colours;
delete[] textureCoords;
}
Mesh *Mesh::LoadMeshFile(const string &filename)
{
ifstream f(filename);
if (!f)
return NULL;
Mesh *m = new Mesh();
m->type = PRIMITIVE_TRIANGLES;
f >> m->numVertices;
int hasTex = 0;
f >> hasTex;
int hasColour = 0;
f >> hasColour;
m->vertices = new Vector4[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
m->colours = new Colour[m->numVertices];
for (unsigned int i = 0; i < m->numVertices; ++i)
{
f >> m->vertices[i].x;
f >> m->vertices[i].y;
f >> m->vertices[i].z;
}
if (hasColour)
{
for (unsigned int i = 0; i < m->numVertices; ++i)
{
f >> m->colours[i].r;
f >> m->colours[i].g;
f >> m->colours[i].b;
f >> m->colours[i].a;
}
}
if (hasTex)
{
for (unsigned int i = 0; i < m->numVertices; ++i)
{
f >> m->textureCoords[i].x;
f >> m->textureCoords[i].y;
}
}
return m;
}
Mesh *Mesh::GeneratePoint(const Vector3 &from, const Colour &col)
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_POINTS;
m->numVertices = 1;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
m->vertices[0] = Vector4(from.x, from.y, from.z, 1.0f);
m->colours[0] = Colour(col.r, col.g, col.b, col.a);
m->textureCoords[0] = Vector2(0.0f, 0.0f);
return m;
}
Mesh *Mesh::GenerateLine(const Vector3 &from, const Vector3 &to)
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_LINES;
m->numVertices = 2;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
m->vertices[0] = Vector4(from.x, from.y, from.z, 1.0f);
m->colours[0] = Colour(255, 0, 0, 255);
m->textureCoords[0] = Vector2(0.0f, 0.0f);
m->vertices[1] = Vector4(to.x, to.y, to.z, 1.0f);
m->colours[1] = Colour(0, 0, 255, 255);
m->textureCoords[1] = Vector2(1.0f, 1.0f);
return m;
}
Mesh *Mesh::GenerateNSided2D(const int n)
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_LINES;
m->numVertices = n * 2;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
const float p = 2 * PI / n;
for (int i = 0; i < m->numVertices; i += 2)
{
const float x1 = cos(p * i);
const float y1 = sin(p * i);
m->vertices[i] = Vector4(x1, y1, 0.0, 1.0f);
m->colours[i] = Colour(255, 255, 255, 255);
m->textureCoords[i] = Vector2(x1, y1);
const float x2 = cos(p * (i + 1));
const float y2 = sin(p * (i + 1));
m->vertices[i + 1] = Vector4(x2, y2, 0.0, 1.0f);
m->colours[i + 1] = Colour(255, 255, 255, 255);
m->textureCoords[i + 1] = Vector2(x2, y2);
}
return m;
}
Mesh *Mesh::GenerateTriangle()
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_TRIANGLES;
m->numVertices = 3;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
//m->vertices[0] = Vector4(0.55f, 0.55f, -0.6, 1.0f);
//m->vertices[1] = Vector4(0.5f, 0.65f, -0.6, 1.0f);
//m->vertices[2] = Vector4(0.45f, 0.55f, -0.6, 1.0f);
m->vertices[0] = Vector4(0.5f, -0.5f, 0.0f, 1.0f);
m->vertices[1] = Vector4(0.0f, 0.5f, 0.0f, 1.0f);
m->vertices[2] = Vector4(-0.5f, -0.5f, 0.0f, 1.0f);
m->colours[0] = Colour(255, 0, 0, 127);
m->colours[1] = Colour(0, 255, 0, 127);
m->colours[2] = Colour(0, 0, 255, 127);
m->textureCoords[0] = Vector2(0.0f, 0.0f);
m->textureCoords[1] = Vector2(0.5f, 1.0f);
m->textureCoords[2] = Vector2(1.0f, 0.0f);
return m;
}
Mesh *Mesh::GenerateTriangleStrip()
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_TRIANGLE_STRIP;
m->numVertices = 5;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
m->vertices[0] = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
m->vertices[1] = Vector4(0.25f, 0.5f, 0.0f, 1.0f);
m->vertices[2] = Vector4(0.5f, 0.0f, 0.0f, 1.0f);
m->vertices[3] = Vector4(0.75f, 0.5f, 0.0f, 1.0f);
m->vertices[4] = Vector4(1.0f, 0.0f, 0.0f, 1.0f);
m->colours[0] = Colour(255, 0, 0, 127);
m->colours[1] = Colour(0, 255, 0, 127);
m->colours[2] = Colour(0, 0, 255, 127);
m->colours[3] = Colour(255, 0, 0, 255);
m->colours[4] = Colour(0, 255, 0, 255);
m->textureCoords[0] = Vector2(0.0f, 0.0f);
m->textureCoords[1] = Vector2(0.5f, 1.0f);
m->textureCoords[2] = Vector2(1.0f, 0.0f);
m->textureCoords[3] = Vector2(1.0f, 0.5f);
m->textureCoords[4] = Vector2(0.5f, 0.5f);
return m;
}
Mesh *Mesh::GenerateTriangleFan()
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_TRIANGLE_FAN;
m->numVertices = 5;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
m->vertices[0] = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
m->vertices[1] = Vector4(0.5f, 0.5f, 0.0f, 1.0f);
m->vertices[2] = Vector4(0.5f, -0.5f, 0.0f, 1.0f);
m->vertices[3] = Vector4(-0.5f, -0.5f, 0.0f, 1.0f);
m->vertices[4] = Vector4(-0.5f, 0.5f, 0.0f, 1.0f);
m->colours[0] = Colour(0, 0, 0, 127);
m->colours[1] = Colour(255, 0, 0, 127);
m->colours[2] = Colour(0, 255, 0, 127);
m->colours[3] = Colour(0, 0, 255, 127);
m->colours[4] = Colour(255, 255, 255, 127);
m->textureCoords[0] = Vector2(0.0f, 0.0f);
m->textureCoords[1] = Vector2(0.5f, 1.0f);
m->textureCoords[2] = Vector2(1.0f, 0.0f);
m->textureCoords[3] = Vector2(1.0f, 0.0f);
m->textureCoords[4] = Vector2(1.0f, 0.0f);
return m;
}
/**
* Generates a 3D sphere.
*
* \param radius Radius (default 1.0)
* \param resolution Number of "slices" in lon and lat
* \param c Solid colour
* \return New mesh
*/
Mesh *Mesh::GenerateSphere(const float radius, const int resolution, const Colour &c)
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_TRIANGLE_STRIP;
m->numVertices = resolution * resolution * 2;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
const float deltaTheta = (PI / resolution);
const float deltaPhi = ((PI * 2) / resolution);
const float texEpsilon = 1.0f / resolution;
int n = 0;
for (int i = 0; i < resolution; i++)
{
const float theta1 = i * deltaTheta;
const float theta2 = (i + 1) * deltaTheta;
const float u1 = i * texEpsilon;
const float u2 = (i + 1) * texEpsilon;
for (int j = 0; j < resolution; j++)
{
const float phi = j * deltaPhi;
const float v = j * texEpsilon;
m->vertices[n] = Vector4(cos(theta1) * sin(phi) * radius, sin(theta1) * sin(phi) * radius,
cos(phi) * radius, 1.0f);
m->colours[n] = c;
m->textureCoords[n] = Vector2(u1, v);
n++;
m->vertices[n] = Vector4(cos(theta2) * sin(phi) * radius, sin(theta2) * sin(phi) * radius,
cos(phi) * radius, 1.0f);
m->colours[n] = c;
m->textureCoords[n] = Vector2(u2, v);
n++;
}
}
return m;
}
/**
* Generates a 2D filled circle/disc.
*
* \param radius Radius (default 1.0)
* \param resolution Number of "slices" in angle
* \return New mesh
*/
Mesh *Mesh::GenerateDisc2D(const float radius, const int resolution)
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_TRIANGLE_FAN;
m->numVertices = resolution + 2;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
const float deltaA = ((PI * 2) / resolution);
// "Origin" vertex
m->vertices[0] = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
m->colours[0] = Colour::White;
m->textureCoords[0] = Vector2(0.5f, 0.5f);
for (int i = 1; i < resolution + 2; ++i)
{
const float a = i * deltaA;
m->vertices[i] = Vector4(cos(a) * radius, sin(a) * radius, 0.0f, 1.0f);
m->colours[i] = Colour::White;
Vector2 v = Vector2(abs(cos(a)), abs(sin(a)));
m->textureCoords[i] = v;
}
return m;
}
/**
* Generates a 2D ring.
*
* \param radiusOuter Outer radius
* \param radiusInner Inner radius
* \param resolution Number of "slices" in angle
* \return New mesh
*/
Mesh *Mesh::GenerateRing2D(const float radiusOuter, const float radiusInner, const int resolution)
{
Mesh *m = new Mesh();
m->type = PRIMITIVE_TRIANGLE_STRIP;
m->numVertices = (resolution + 2) * 2;
m->vertices = new Vector4[m->numVertices];
m->colours = new Colour[m->numVertices];
m->textureCoords = new Vector2[m->numVertices];
const float deltaA = ((PI * 2) / resolution);
int n = 0;
for (int i = 0; i < resolution + 2; i++)
{
const float a = i * deltaA;
m->vertices[n] = Vector4(cos(a) * radiusOuter, sin(a) * radiusOuter, 0.0f, 1.0f);
m->colours[n] = Colour::White;
m->textureCoords[n] = Vector2(abs(cos(a)), abs(sin(a)));
n++;
m->vertices[n] = Vector4(cos(a) * radiusInner, sin(a) * radiusInner, 0.0f, 1.0f);
m->colours[n] = Colour::White;
m->textureCoords[n] = Vector2(abs(cos(a)), abs(sin(a)));
n++;
}
return m;
}
/******************************************************************************
Class:Mesh
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description: Class to represent the geometric data that makes up the meshes
we render on screen.
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include "Vector4.h"
#include "Colour.h"
#include "Vector3.h"
#include "Vector2.h"
#include "Common.h"
#include <string>
#include <fstream>
using std::ifstream;
using std::string;
enum PrimitiveType
{
PRIMITIVE_POINTS,
PRIMITIVE_LINES,
PRIMITIVE_TRIANGLES,
PRIMITIVE_TRIANGLE_STRIP,
PRIMITIVE_TRIANGLE_FAN
};
class Mesh
{
friend class SoftwareRasteriser;
public:
Mesh(void);
~Mesh(void);
static Mesh *LoadMeshFile(const string &filename);
static Mesh *GeneratePoint(const Vector3 &pos, const Colour &col = Colour(255, 255, 255, 255));
static Mesh *GenerateLine(const Vector3 &from, const Vector3 &to);
static Mesh *GenerateNSided2D(const int n);
static Mesh *GenerateTriangle();
static Mesh *GenerateTriangleStrip();
static Mesh *GenerateTriangleFan();
static Mesh *GenerateSphere(const float radius = 1.0f, const int resolution = 10,
const Colour &c = Colour::White);
static Mesh *GenerateDisc2D(const float radius = 1.0f, const int resolution = 10);
static Mesh *GenerateRing2D(const float radiusOuter = 1.0f, const float radiusInner = 0.8f,
const int resolution = 10);
PrimitiveType GetType()
{
return type;
}
protected:
PrimitiveType type;
uint numVertices;
Vector4 *vertices;
Colour *colours;
Vector2 *textureCoords;
};
#include "Mouse.h"
Mouse *Mouse::instance = 0;
Mouse::Mouse(HWND &hwnd)
{
ZeroMemory(buttons, sizeof(bool) * MOUSE_MAX);
ZeroMemory(holdButtons, sizeof(bool) * MOUSE_MAX);
ZeroMemory(doubleClicks, sizeof(bool) * MOUSE_MAX);
ZeroMemory(lastClickTime, sizeof(float) * MOUSE_MAX);
lastWheel = 0;
frameWheel = 0;
sensitivity = 0.07f; // Chosen for no other reason than it's a nice value for my Deathadder ;)
clickLimit = 200.0f;
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = hwnd;
RegisterRawInputDevices(&rid, 1, sizeof(rid));
}
void Mouse::Initialise(HWND &hwnd)
{
instance = new Mouse(hwnd);
}
void Mouse::Destroy()
{
delete instance;
}
void Mouse::Update(RAWINPUT *raw)
{
if (isAwake)
{
/*
Update the absolute and relative mouse movements
*/
relativePosition.x += ((float)raw->data.mouse.lLastX) * sensitivity;
relativePosition.y += ((float)raw->data.mouse.lLastY) * sensitivity;
absolutePosition.x += (float)raw->data.mouse.lLastX;
absolutePosition.y += (float)raw->data.mouse.lLastY;
/*
Bounds check the absolute position of the mouse, so it doesn't disappear off screen edges...
*/
absolutePosition.x = max(absolutePosition.x, 0.0f);
absolutePosition.x = min(absolutePosition.x, absolutePositionBounds.x);
absolutePosition.y = max(absolutePosition.y, 0.0f);
absolutePosition.y = min(absolutePosition.y, absolutePositionBounds.y);
/*
TODO: How framerate independent is this?
*/
if (raw->data.mouse.usButtonFlags & RI_MOUSE_WHEEL)
{
if (raw->data.mouse.usButtonData == 120)
{
frameWheel = 1;
}
else
{
frameWheel = -1;
}
}
/*
Oh, Microsoft...
*/
static int buttondowns[5] = { RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_2_DOWN,
RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_4_DOWN,
RI_MOUSE_BUTTON_5_DOWN };
static int buttonps[5] = { RI_MOUSE_BUTTON_1_UP, RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_UP,
RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_UP };
for (int i = 0; i < 5; ++i)
{
if (raw->data.mouse.usButtonFlags & buttondowns[i])
{
// The button was pressed!
buttons[i] = true;
/*
If it wasn't too long ago since we last clicked, we trigger a double click!
*/
if (lastClickTime[i] > 0)
{
doubleClicks[i] = true;
}
/*
No matter whether the mouse was double clicked or not, we reset the clicklimit
*/
lastClickTime[i] = clickLimit;
}
else if (raw->data.mouse.usButtonFlags & buttonps[i])
{
// The button has been released!
buttons[i] = false;
holdButtons[i] = false;
}
}
}
}
/*
Sets the mouse sensitivity (higher = mouse pointer moves more!)
Lower bounds checked.
*/
void Mouse::SetMouseSensitivity(float amount)
{
if (amount == 0.0f)
{
amount = 1.0f;
}
sensitivity = amount;
}
/*
Updates variables controlling whether a mouse button has been
held for multiple frames. Also updates relative movement.
*/
void Mouse::UpdateHolds()
{
memcpy(holdButtons, buttons, MOUSE_MAX * sizeof(bool));
// We sneak this in here, too. Resets how much the mouse has moved
// since last update
relativePosition.ToZero();
// And the same for the mouse wheel
frameWheel = 0;
}
/*
Sends the mouse to sleep, so it doesn't process any
movement or buttons until it receives a Wake()
*/
void Mouse::Sleep()
{
isAwake = false; // Bye bye for now
ZeroMemory(holdButtons, MOUSE_MAX * sizeof(bool));
ZeroMemory(buttons, MOUSE_MAX * sizeof(bool));
}
/*
Forces the mouse pointer to a specific point in absolute space.
*/
void Mouse::SetAbsolutePosition(unsigned int x, unsigned int y)
{
absolutePosition.x = (float)x;
absolutePosition.y = (float)y;
}
/*
Returns if the button is down. Doesn't need bounds checking -
an INPUT_KEYS enum is always in range
*/
bool Mouse::ButtonDown(MouseButtons b)
{
return instance->buttons[b];
}
/*
Returns if the button is down, and has been held down for multiple updates.
Doesn't need bounds checking - an INPUT_KEYS enum is always in range
*/
bool Mouse::ButtonHeld(MouseButtons b)
{
return instance->holdButtons[b];
}
/*
Returns how much the mouse has moved by since the last frame.
*/
Vector2 Mouse::GetRelativePosition()
{
return instance->relativePosition;
}
/*
Returns the mouse pointer position in absolute space.
*/
Vector2 Mouse::GetAbsolutePosition()
{
return instance->absolutePosition;
}
/*
Returns how much the mouse has moved by since the last frame.
*/
void Mouse::SetAbsolutePositionBounds(unsigned int maxX, unsigned int maxY)
{
absolutePositionBounds.x = (float)maxX;
absolutePositionBounds.y = (float)maxY;
}
/*
Has the mousewheel been moved since the last frame?
*/
bool Mouse::WheelMoved()
{
return instance->frameWheel != 0;
};
/*
Returns whether the button was double clicked in this frame
*/
bool Mouse::DoubleClicked(MouseButtons button)
{
return (instance->buttons[button] && instance->doubleClicks[button]);
}
/*
Get the mousewheel movement. Positive values mean the mousewheel
has moved up, negative down. Can be 0 (no movement)
*/
int Mouse::GetWheelMovement()
{
return (int)instance->frameWheel;
}
/*
Updates the double click timers for each mouse button. msec is milliseconds
since the last UpdateDoubleClick call. Timers going over the double click
limit set the relevant double click value to false.
*/
void Mouse::UpdateDoubleClick(float msec)
{
for (int i = 0; i < MOUSE_MAX; ++i)
{
if (lastClickTime[i] > 0)
{
lastClickTime[i] -= msec;
if (lastClickTime[i] <= 0.0f)
{
doubleClicks[i] = false;
lastClickTime[i] = 0.0f;
}
}
}
}
/******************************************************************************
Class:Mouse
Implements:InputDevice
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:Windows RAW input mouse, with a couple of game-related enhancements
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include "InputDevice.h"
#include "Vector2.h"
// Presumably RAW input does actually support those fancy mice with greater
// than 5 buttons in some capacity, but I have a 5 button mouse so I don't
// care to find out how ;)
enum MouseButtons
{
MOUSE_LEFT = 0,
MOUSE_RIGHT = 1,
MOUSE_MIDDLE = 2,
MOUSE_FOUR = 3,
MOUSE_FIVE = 4,
MOUSE_MAX = 5
};
class Mouse : public InputDevice
{
public:
friend class Window;
// Is this mouse button currently pressed down?
static bool ButtonDown(MouseButtons button);
// Has this mouse button been held down for multiple frames?
static bool ButtonHeld(MouseButtons button);
// Has this mouse button been double clicked?
static bool DoubleClicked(MouseButtons button);
// Get how much this mouse has moved since last frame
static Vector2 GetRelativePosition();
// Get the window position of the mouse pointer
static Vector2 GetAbsolutePosition();
// Determines the maximum amount of ms that can pass between
// 2 mouse presses while still counting as a 'double click'
static void SetDoubleClickLimit(float msec);
// Has the mouse wheel moved since the last update?
static bool WheelMoved();
// Get the mousewheel movement. Positive means scroll up,
// negative means scroll down, 0 means no movement.
static int GetWheelMovement();
// Sets the mouse sensitivity. Currently only affects the 'relative'
//(i.e FPS-style) mouse movement. Students! Maybe you'd like to
// implement a 'MenuSensitivity' for absolute movement?
void SetMouseSensitivity(float amount);
protected:
Mouse(HWND &hwnd);
~Mouse(void)
{
}
static void Initialise(HWND &hwnd);
static void Destroy();
static Mouse *instance;
// Internal function that updates the mouse variables from a
// raw input 'packet'
virtual void Update(RAWINPUT *raw);
// Updates the holdButtons array. Call once per frame!
virtual void UpdateHolds();
// Sends the mouse to sleep (i.e window has been alt-tabbed away etc)
virtual void Sleep();
// Updates the doubleclicks array. Call once per frame!
void UpdateDoubleClick(float msec);
// Set the mouse's current screen position. Maybe should be public?
void SetAbsolutePosition(unsigned int x, unsigned int y);
// Set the absolute screen bounds (<0 is always assumed dissallowed). Used
// by the window resize routine...
void SetAbsolutePositionBounds(unsigned int maxX, unsigned int maxY);
// Current mouse absolute position
Vector2 absolutePosition;
// Current mouse absolute position maximum bounds
Vector2 absolutePositionBounds;
// How much as the mouse moved since the last raw packet?
Vector2 relativePosition;
// Current button down state for each button
bool buttons[MOUSE_MAX];
// Current button held state for each button
bool holdButtons[MOUSE_MAX];
// Current doubleClick counter for each button
bool doubleClicks[MOUSE_MAX];
// Counter to remember when last mouse click occured
float lastClickTime[MOUSE_MAX];
// last mousewheel updated position
int lastWheel;
// Current mousewheel updated position
int frameWheel;
// Max amount of ms between clicks count as a 'double click'
float clickLimit;
// Mouse pointer sensitivity. Set this negative to get a headache!
float sensitivity;
};
#include "RenderObject.h"
RenderObject::RenderObject(void)
{
texture = NULL;
mesh = NULL;
}
RenderObject::~RenderObject(void)
{
if (texture != NULL)
delete texture;
if (mesh != NULL)
delete mesh;
}
/******************************************************************************
Class:RenderObject
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description: Class to represent an object in our basic rendering program
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include "Mesh.h"
#include "Texture.h"
#include "Matrix4.h"
class Texture;
class RenderObject
{
public:
RenderObject(void);
~RenderObject(void);
Mesh *GetMesh()
{
return mesh;
}
Texture *GetTexure()
{
return texture;
}
Matrix4 GetModelMatrix()
{
return modelMatrix;
}
// protected:
Matrix4 modelMatrix;
Texture *texture;
Mesh *mesh;
};
#include "SoftwareRasteriser.h"
#include <cmath>
#include <math.h>
/*
While less 'neat' than just doing a 'new', like in the tutorials, it's usually
possible to render a bit quicker to use direct pointers to the drawing area
that the OS gives you. For a bit of a speedup, you can uncomment the define below
to switch to using this method.
For those of you new to the preprocessor, here's a quick explanation:
Preprocessor definitions like #define allow parts of a file to be selectively enabled
or disabled at compile time. This is useful for hiding parts of the codebase on a
per-platform basis: if you have support for linux and windows in your codebase, obviously
the linux platform won't have the windows platform headers available, so compilation will
fail. So instead you can hide away all the platform specific stuff:
#if PLATFORM_WINDOWS
DoSomeWindowsStuff();
#elif PLATFORM_LINUX
DoSomeLinuxStuff();
#else
#error Unsupported Platform Specified!
#endif
As in our usage, it also allows you to selectively compile in some different functionality
without any 'run time' cost - if it's not enabled by the preprocessor, it won't make it to
the compiler, so no assembly will be generated.
Also, I've implemented the Resize method for you, in a manner that'll behave itself
no matter which method you use. I kinda forgot to do that, so there was a chance you'd
get exceptions if you resized to a bigger screen area. Sorry about that.
*/
//#define USE_OS_BUFFERS
#define MAX_VERTS 16
const int INSIDE_CS = 0;
const int LEFT_CS = 1;
const int RIGHT_CS = 2;
const int BOTTOM_CS = 4;
const int TOP_CS = 8;
const int FAR_CS = 16;
const int NEAR_CS = 32;
float SoftwareRasteriser::ScreenAreaOfTri(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2)
{
float area = ((v0.x * v1.y) + (v1.x * v2.y) + (v2.x * v0.y)) -
((v1.x * v0.y) + (v2.x * v1.y) + (v0.x * v2.y));
return area * 0.5f;
}
SoftwareRasteriser::SoftwareRasteriser(uint width, uint height)
: Window(width, height)
{
m_currentDrawBuffer = 0;
m_currentTexture = NULL;
m_texSampleState = SAMPLE_NEAREST;
m_blendState = BLEND_REPLACE;
#ifndef USE_OS_BUFFERS
// Hi! In the tutorials, it's mentioned that we need to form our front + back buffer like so:
for (int i = 0; i < 2; ++i)
{
m_buffers[i] = new Colour[screenWidth * screenHeight];
}
#else
// This works, but we can actually save a memcopy by rendering directly into the memory the
// windowing system gives us, which I've added to the Window class as the 'bufferData' pointers
for (int i = 0; i < 2; ++i)
{
buffers[i] = (Colour *)bufferData[i];
}
#endif
m_depthBuffer = new unsigned short[screenWidth * screenHeight];
float zScale = (pow(2.0f, 16) - 1) * 0.5f;
Vector3 halfScreen = Vector3((screenWidth - 1) * 0.5f, (screenHeight - 1) * 0.5f, zScale);
m_portMatrix = Matrix4::Translation(halfScreen) * Matrix4::Scale(halfScreen);
}
SoftwareRasteriser::~SoftwareRasteriser(void)
{
#ifndef USE_OS_BUFFERS
for (int i = 0; i < 2; ++i)
{
delete[] m_buffers[i];
}
#endif
delete[] m_depthBuffer;
}
void SoftwareRasteriser::Resize()
{
Window::Resize(); // make sure our base class gets to do anything it needs to
#ifndef USE_OS_BUFFERS
for (int i = 0; i < 2; ++i)
{
delete[] m_buffers[i];
m_buffers[i] = new Colour[screenWidth * screenHeight];
}
#else
for (int i = 0; i < 2; ++i)
{
buffers[i] = (Colour *)bufferData[i];
}
#endif
delete[] m_depthBuffer;
m_depthBuffer = new unsigned short[screenWidth * screenHeight];
float zScale = (pow(2.0f, 16) - 1) * 0.5f;
Vector3 halfScreen = Vector3((screenWidth - 1) * 0.5f, (screenHeight - 1) * 0.5f, zScale);
m_portMatrix = Matrix4::Translation(halfScreen) * Matrix4::Scale(halfScreen);
}
Colour *SoftwareRasteriser::GetCurrentBuffer()
{
return m_buffers[m_currentDrawBuffer];
}
void SoftwareRasteriser::ClearBuffers()
{
Colour *buffer = GetCurrentBuffer();
unsigned int clearVal = 0xFF000000;
unsigned int depthVal = ~0;
for (uint y = 0; y < screenHeight; ++y)
{
for (uint x = 0; x < screenWidth; ++x)
{
buffer[(y * screenWidth) + x].c = clearVal;
m_depthBuffer[(y * screenWidth) + x] = depthVal;
}
}
}
void SoftwareRasteriser::SwapBuffers()
{
PresentBuffer(m_buffers[m_currentDrawBuffer]);
m_currentDrawBuffer = !m_currentDrawBuffer;
}
void SoftwareRasteriser::DrawObject(RenderObject *o)
{
m_currentTexture = o->GetTexure();
switch (o->GetMesh()->GetType())
{
case PRIMITIVE_POINTS:
RasterisePointsMesh(o);
break;
case PRIMITIVE_LINES:
RasteriseLinesMesh(o);
break;
case PRIMITIVE_TRIANGLES:
RasteriseTriMesh(o);
break;
case PRIMITIVE_TRIANGLE_STRIP:
RasteriseTriMeshStrip(o);
break;
case PRIMITIVE_TRIANGLE_FAN:
RasteriseTriMeshFan(o);
break;
}
}
void SoftwareRasteriser::CalculateWeights(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2,
const Vector4 &p, float &alpha, float &beta, float &gamma)
{
const float triArea = ScreenAreaOfTri(v0, v1, v2);
const float areaRecip = 1.0f / triArea;
float subTriArea[3];
subTriArea[0] = abs(ScreenAreaOfTri(v0, p, v1));
subTriArea[1] = abs(ScreenAreaOfTri(v1, p, v2));
subTriArea[2] = abs(ScreenAreaOfTri(v2, p, v0));
alpha = subTriArea[1] * areaRecip;
beta = subTriArea[2] * areaRecip;
gamma = subTriArea[0] * areaRecip;
}
bool SoftwareRasteriser::CohenSutherlandLine(Vector4 &inA, Vector4 &inB, Colour &colA, Colour &colB,
Vector3 &texA, Vector3 &texB)
{
for (int i = 0; i < 6; ++i)
{
int planeCode = 1 << i;
int outsideA = (HomogeneousOutcode(inA) & planeCode);
int outsideB = (HomogeneousOutcode(inB) & planeCode);
if (outsideA && outsideB)
return false;
if (!outsideA && !outsideB)
continue;
float clipRatio = ClipEdge(inA, inB, planeCode);
if (outsideA)
{
texA = Vector3::Lerp(texA, texB, clipRatio);
colA = Colour::Lerp(colA, colB, clipRatio);
inA = Vector4::Lerp(inA, inB, clipRatio);
}
else
{
texB = Vector3::Lerp(texA, texB, clipRatio);
colB = Colour::Lerp(colA, colB, clipRatio);
inB = Vector4::Lerp(inA, inB, clipRatio);
}
}
return true;
}
void SoftwareRasteriser::SutherlandHodgmanTri(Vector4 &v0, Vector4 &v1, Vector4 &v2,
const Colour &c0, const Colour &c1, const Colour &c2,
const Vector2 &t0, const Vector2 &t1,
const Vector2 &t2)
{
Vector4 posIn[MAX_VERTS];
Colour colIn[MAX_VERTS];
Vector3 texIn[MAX_VERTS];
Vector4 posOut[MAX_VERTS];
Colour colOut[MAX_VERTS];
Vector3 texOut[MAX_VERTS];
posIn[0] = v0;
posIn[1] = v1;
posIn[2] = v2;
colIn[0] = c0;
colIn[1] = c1;
colIn[2] = c2;
texIn[0] = Vector3(t0.x, t0.y, 1);
texIn[1] = Vector3(t1.x, t1.y, 1);
texIn[2] = Vector3(t2.x, t2.y, 1);
int inSize = 3;
for (int i = 0; i <= 6; i++)
{
int planeCode = 1 << i;
Vector4 prevPos = posIn[inSize - 1];
Colour prevCol = colIn[inSize - 1];
Vector3 prevTex = texIn[inSize - 1];
int outSize = 0;
for (int j = 0; j < inSize; ++j)
{
int outsideA = HomogeneousOutcode(posIn[j]) & planeCode;
int outsideB = HomogeneousOutcode(prevPos) & planeCode;
if (outsideA ^ outsideB)
{
float clipRatio = ClipEdge(posIn[j], prevPos, planeCode);
colOut[outSize] = Colour::Lerp(colIn[j], prevCol, clipRatio);
texOut[outSize] = Vector3::Lerp(texIn[j], prevTex, clipRatio);
posOut[outSize] = Vector4::Lerp(posIn[j], prevPos, clipRatio);
outSize++;
}
if (!outsideA)
{
posOut[outSize] = posIn[j];
colOut[outSize] = colIn[j];
texOut[outSize] = texIn[j];
outSize++;
}
prevPos = posIn[j];
prevCol = colIn[j];
prevTex = texIn[j];
}
for (int j = 0; j < outSize; ++j)
{
posIn[j] = posOut[j];
colIn[j] = colOut[j];
texIn[j] = texOut[j];
}
inSize = outSize;
}
for (int i = 0; i < inSize; ++i)
{
texIn[i] = Vector3(texIn[i].x, texIn[i].y, 1.0f) / posIn[i].w;
posIn[i].SelfDivisionByW();
}
for (int i = 2; i < inSize; ++i)
{
RasteriseTri(posIn[0], posIn[i - 1], posIn[i], colIn[0], colIn[i - 1], colIn[i], texIn[0],
texIn[i - 1], texIn[i]);
}
}
float SoftwareRasteriser::ClipEdge(const Vector4 &inA, const Vector4 &inB, int axis)
{
float ratio = 0.0f;
switch (axis)
{
case LEFT_CS:
ratio = (-inA.w - inA.x) / ((inB.x - inA.x) + inB.w - inA.w);
break;
case RIGHT_CS:
ratio = (inA.w - inA.x) / ((inB.x - inA.x) + inB.w - inA.w);
break;
case BOTTOM_CS:
ratio = (-inA.w - inA.y) / ((inB.y - inA.y) + inB.w - inA.w);
break;
case TOP_CS:
ratio = (inA.w - inA.y) / ((inB.y - inA.y) + inB.w - inA.w);
break;
case NEAR_CS:
ratio = (-inA.w - inA.z) / ((inB.z - inA.z) + inB.w - inA.w);
break;
case FAR_CS:
ratio = (inA.w - inA.z) / ((inB.z - inA.z) + inB.w - inA.w);
break;
}
return min(1.0f, ratio);
}
int SoftwareRasteriser::HomogeneousOutcode(const Vector4 &in)
{
int outCode = INSIDE_CS;
if (in.x < -in.w)
outCode |= LEFT_CS;
else if (in.x > in.w)
outCode |= RIGHT_CS;
if (in.y < -in.w)
outCode |= BOTTOM_CS;
else if (in.y > in.w)
outCode |= TOP_CS;
if (in.z < -in.w)
outCode |= NEAR_CS;
else if (in.z > in.w)
outCode |= FAR_CS;
return outCode;
}
void SoftwareRasteriser::RasteriseLine(const Vector4 &v0, const Vector4 &v1, const Colour &colA,
const Colour &colB, const Vector3 &texA, const Vector3 &texB)
{
Vector4 v0p = m_portMatrix * v0;
Vector4 v1p = m_portMatrix * v1;
Vector4 dir = v1p - v0p;
int xDir = (dir.x < 0.0f) ? -1 : 1;
int yDir = (dir.y < 0.0f) ? -1 : 1;
int x = (int)v0p.x;
int y = (int)v0p.y;
int *target = NULL;
int *scan = NULL;
int targetVal = 0;
int scanVal = 0;
float slope = 0.0f;
int range = 0;
if (abs(dir.y) > abs(dir.x))
{
slope = dir.x / dir.y;
range = (int)abs(dir.y);
target = &x;
scan = &y;
targetVal = xDir;
scanVal = yDir;
}
else
{
slope = dir.y / dir.x;
range = (int)abs(dir.x);
target = &y;
scan = &x;
targetVal = yDir;
scanVal = xDir;
}
const float absSlope = abs(slope);
float error = 0.0f;
const float reciprocalRange = 1.0f / range;
for (int i = 0; i < range; i++)
{
const float t = i * reciprocalRange;
const float zVal = v1p.z * (i * reciprocalRange) + v0p.z * (1.0 - (i * reciprocalRange));
Colour c;
if (m_currentTexture == NULL)
{
c = colB * t + colA * (1.0f - t);
}
else
{
Vector3 subTex = texA * (i * reciprocalRange) + texB * (1.0f - (i * reciprocalRange));
subTex.x /= subTex.z;
subTex.y /= subTex.z;
c = m_currentTexture->ColourAtPoint((int)subTex.x, (int)subTex.y);
}
if (DepthFunc((int)x, (int)y, zVal))
BlendPixel(x, y, c);
error += absSlope;
if (error > 0.5f)
{
error -= 1.0f;
(*target) += targetVal;
}
(*scan) += scanVal;
}
}
void SoftwareRasteriser::RasteriseTri(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2,
const Colour &c0, const Colour &c1, const Colour &c2,
const Vector3 &t0, const Vector3 &t1, const Vector3 &t2)
{
Vector4 v0p = m_portMatrix * v0;
Vector4 v1p = m_portMatrix * v1;
Vector4 v2p = m_portMatrix * v2;
const BoundingBox b = CalculateBoxForTri(v0p, v1p, v2p);
const float triArea = abs(ScreenAreaOfTri(v0p, v1p, v2p));
const float areaRecip = 1.0f / triArea;
float subTriArea[3];
Vector4 screenPos(0, 0, 0, 1);
for (float y = b.topLeft.y; y < b.bottomRight.y; ++y)
{
for (float x = b.topLeft.x; x < b.bottomRight.x; ++x)
{
screenPos.x = x;
screenPos.y = y;
subTriArea[0] = abs(ScreenAreaOfTri(v0p, screenPos, v1p));
subTriArea[1] = abs(ScreenAreaOfTri(v1p, screenPos, v2p));
subTriArea[2] = abs(ScreenAreaOfTri(v2p, screenPos, v0p));
float triSum = subTriArea[0] + subTriArea[1] + subTriArea[2];
// Check if pixel is outside of triangle
if (triSum > (triArea + 1.0f))
continue;
// Check if tringle is very small
if (triSum < 1.0f)
continue;
const float alpha = subTriArea[1] * areaRecip;
const float beta = subTriArea[2] * areaRecip;
const float gamma = subTriArea[0] * areaRecip;
float zVal = (v0p.z * alpha) + (v1p.z * beta) + (v2p.z * gamma);
if (!DepthFunc((int)x, (int)y, zVal))
continue;
// Pixel is in triangle, so shade it
if (m_currentTexture)
{
Vector3 subTex = (t0 * alpha) + (t1 * beta) + (t2 * gamma);
subTex.x /= subTex.z;
subTex.y /= subTex.z;
switch (m_texSampleState)
{
case SAMPLE_BILINEAR:
BlendPixel((int)x, (int)y, m_currentTexture->BilinearTexSample(subTex));
break;
case SAMPLE_MIPMAP_NEAREST:
{
float xAlpha, xBeta, xGamma, yAlpha, yBeta, yGamma;
CalculateWeights(v0p, v1p, v2p, screenPos + Vector4(1, 0, 0, 0), xAlpha, xBeta, xGamma);
CalculateWeights(v0p, v1p, v2p, screenPos + Vector4(0, 1, 0, 0), yAlpha, yBeta, yGamma);
Vector3 xDerivs = (t0 * xAlpha) + (t1 * xBeta) + (t2 * xGamma);
Vector3 yDerivs = (t0 * yAlpha) + (t1 * yBeta) + (t2 * yGamma);
xDerivs.x /= xDerivs.z;
xDerivs.y /= xDerivs.z;
yDerivs.x /= yDerivs.z;
yDerivs.y /= yDerivs.z;
xDerivs = xDerivs - subTex;
yDerivs = yDerivs - subTex;
const float maxU = max(abs(xDerivs.x), abs(yDerivs.y));
const float maxV = max(abs(xDerivs.y), abs(yDerivs.y));
const float maxChange = abs(max(maxU, maxV));
const int lambda = (int)(abs(log(maxChange) / log(2.0)));
BlendPixel((int)x, (int)y, m_currentTexture->NearestTexSample(subTex, lambda));
break;
}
default:
BlendPixel((int)x, (int)y, m_currentTexture->NearestTexSample(subTex));
}
}
else
{
Colour c = ((c0 * alpha) + (c1 * beta) + (c2 * gamma));
BlendPixel((int)x, (int)y, c);
}
}
}
}
void SoftwareRasteriser::RasteriseTriSpans(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2,
const Colour &c0, const Colour &c1, const Colour &c2,
const Vector3 &t0, const Vector3 &t1, const Vector3 &t2)
{
Vector4 v0p = m_portMatrix * v0;
Vector4 v1p = m_portMatrix * v1;
Vector4 v2p = m_portMatrix * v2;
float edge0y = abs(v0p.y - v1p.y);
float edge1y = abs(v1p.y - v2p.y);
float edge2y = abs(v2p.y - v0p.y);
// Edhe 0 is longest
if (edge0y >= edge1y && edge0y >= edge2y)
{
RasteriseTriEdgeSpans(v0p, v1p, v2p, v0p);
RasteriseTriEdgeSpans(v0p, v1p, v1p, v2p);
}
// Edge 1 is longest
else if (edge1y >= edge0y && edge1y >= edge2y)
{
RasteriseTriEdgeSpans(v1p, v2p, v2p, v0p);
RasteriseTriEdgeSpans(v1p, v2p, v0p, v1p);
}
// Edge 2 is longest
else
{
RasteriseTriEdgeSpans(v2p, v0p, v0p, v1p);
RasteriseTriEdgeSpans(v2p, v0p, v1p, v2p);
}
}
void SoftwareRasteriser::RasteriseTriEdgeSpans(const Vector4 &v0, const Vector4 &v1,
const Vector4 &v2, const Vector4 &v3)
{
Vector4 longEdge0 = v0;
Vector4 longEdge1 = v1;
Vector4 shortEdge0 = v2;
Vector4 shortEdge1 = v3;
if (longEdge1.y < longEdge0.y)
{
longEdge0 = v1;
longEdge1 = v0;
}
if (shortEdge1.y < shortEdge0.y)
{
shortEdge0 = v3;
shortEdge1 = v2;
}
Vector4 longDiff = longEdge1 - longEdge0;
Vector4 shortDiff = shortEdge1 - shortEdge0;
float longStep = longDiff.x / longDiff.y;
float shortStep = shortDiff.x / shortDiff.y;
float startOff = (shortEdge0.y - longEdge0.y) / longDiff.y;
Vector4 start = longEdge0 + (longDiff * startOff);
float endOff = (start.y - shortEdge0.y) / shortDiff.y;
Vector4 end = shortEdge0 + (shortDiff * endOff);
for (float y = shortEdge0.y; y < shortEdge1.y; ++y)
{
float minX = min(start.x, end.x);
float maxX = max(start.x, end.x);
for (float x = minX; x < maxX; ++x)
BlendPixel((int)x, (int)y, Colour::White);
start.x += longStep;
end.x += shortStep;
}
}
void SoftwareRasteriser::RasterisePointsMesh(RenderObject *o)
{
Matrix4 mvp = m_viewProjMatrix * o->GetModelMatrix();
for (uint i = 0; i < o->GetMesh()->numVertices; i++)
{
Vector4 vertexPos = mvp * o->GetMesh()->vertices[i];
vertexPos.SelfDivisionByW();
Vector4 screenPos = m_portMatrix * vertexPos;
BlendPixel((uint)screenPos.x, (uint)screenPos.y, o->GetMesh()->colours[i]);
}
}
void SoftwareRasteriser::RasteriseLinesMesh(RenderObject *o)
{
Matrix4 mvp = m_viewProjMatrix * o->GetModelMatrix();
for (uint i = 0; i < o->GetMesh()->numVertices; i += 2)
{
Vector4 v0 = mvp * o->GetMesh()->vertices[i];
Vector4 v1 = mvp * o->GetMesh()->vertices[i + 1];
Colour c0 = o->GetMesh()->colours[0];
Colour c1 = o->GetMesh()->colours[1];
Vector3 t0 = Vector3(o->GetMesh()->textureCoords[i].x, o->GetMesh()->textureCoords[i].y, 1.0f);
Vector3 t1 =
Vector3(o->GetMesh()->textureCoords[i + 1].x, o->GetMesh()->textureCoords[i + 1].y, 1.0f);
if (!CohenSutherlandLine(v0, v1, c0, c1, t0, t1))
continue;
t0.z = 1.0f;
t1.z = 1.0f;
t0 /= v0.w;
t1 /= v1.w;
v0.SelfDivisionByW();
v1.SelfDivisionByW();
RasteriseLine(v0, v1, c0, c1, t0, t1);
}
}
void SoftwareRasteriser::RasteriseTriMesh(RenderObject *o)
{
Matrix4 mvp = m_viewProjMatrix * o->GetModelMatrix();
Mesh *m = o->GetMesh();
for (uint i = 0; i < o->GetMesh()->numVertices; i += 3)
{
Vector4 v0 = mvp * m->vertices[i];
Vector4 v1 = mvp * m->vertices[i + 1];
Vector4 v2 = mvp * m->vertices[i + 2];
SutherlandHodgmanTri(v0, v1, v2, m->colours[i], m->colours[i + 1], m->colours[i + 2],
m->textureCoords[i], m->textureCoords[i + 1], m->textureCoords[i + 2]);
}
}
void SoftwareRasteriser::RasteriseTriMeshStrip(RenderObject *o)
{
Matrix4 mvp = m_viewProjMatrix * o->GetModelMatrix();
Mesh *m = o->GetMesh();
for (uint i = 0; i < o->GetMesh()->numVertices - 2; ++i)
{
Vector4 vv0 = m->vertices[i];
Vector4 vv1 = m->vertices[i + 1];
Vector4 vv2 = m->vertices[i + 2];
Vector4 v0 = mvp * m->vertices[i];
Vector4 v1 = mvp * m->vertices[i + 1];
Vector4 v2 = mvp * m->vertices[i + 2];
SutherlandHodgmanTri(v0, v1, v2, m->colours[i], m->colours[i + 1], m->colours[i + 2],
m->textureCoords[i], m->textureCoords[i + 1], m->textureCoords[i + 2]);
}
}
void SoftwareRasteriser::RasteriseTriMeshFan(RenderObject *o)
{
Matrix4 mvp = m_viewProjMatrix * o->GetModelMatrix();
Mesh *m = o->GetMesh();
Vector4 v0 = mvp * m->vertices[0];
for (uint i = 1; i < o->GetMesh()->numVertices - 1; ++i)
{
Vector4 v1 = mvp * m->vertices[i];
Vector4 v2 = mvp * m->vertices[i + 1];
SutherlandHodgmanTri(v0, v1, v2, m->colours[0], m->colours[i], m->colours[i + 1],
m->textureCoords[0], m->textureCoords[i], m->textureCoords[i + 1]);
}
}
BoundingBox SoftwareRasteriser::CalculateBoxForTri(const Vector4 &a, const Vector4 &b,
const Vector4 &c)
{
BoundingBox box;
box.topLeft.x = a.x;
box.topLeft.x = min(box.topLeft.x, b.x);
box.topLeft.x = min(box.topLeft.x, c.x);
box.topLeft.x = max(box.topLeft.x, 0.0f);
box.topLeft.y = a.y;
box.topLeft.y = min(box.topLeft.y, b.y);
box.topLeft.y = min(box.topLeft.y, c.y);
box.topLeft.y = max(box.topLeft.y, 0.0f);
box.bottomRight.x = a.x;
box.bottomRight.x = max(box.bottomRight.x, b.x);
box.bottomRight.x = max(box.bottomRight.x, c.x);
box.bottomRight.x = min(box.bottomRight.x, screenWidth);
box.bottomRight.y = a.y;
box.bottomRight.y = max(box.bottomRight.y, b.y);
box.bottomRight.y = max(box.bottomRight.y, c.y);
box.bottomRight.y = min(box.bottomRight.y, screenHeight);
return box;
}
/******************************************************************************
Class:SoftwareRasteriser
Implements:Window
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description: Class to encapsulate the various rasterisation techniques looked
at in the course material.
This is the class you'll be modifying the most!
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include "Matrix4.h"
#include "Mesh.h"
#include "Texture.h"
#include "RenderObject.h"
#include "Common.h"
#include "Window.h"
#include <vector>
using std::vector;
enum BlendMode
{
BLEND_REPLACE,
BLEND_ALPHA,
BLEND_ADDITIVE
};
enum TextureSampleMode
{
SAMPLE_NEAREST,
SAMPLE_BILINEAR,
SAMPLE_MIPMAP_NEAREST
};
struct BoundingBox
{
Vector2 topLeft;
Vector2 bottomRight;
};
class RenderObject;
class Texture;
class SoftwareRasteriser : public Window
{
public:
static float ScreenAreaOfTri(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2);
SoftwareRasteriser(uint width, uint height);
~SoftwareRasteriser(void);
void DrawObject(RenderObject *o);
void ClearBuffers();
void SwapBuffers();
void SetViewMatrix(const Matrix4 &m)
{
m_viewMatrix = m;
m_viewProjMatrix = m_projectionMatrix * m_viewMatrix;
}
void SetProjectionMatrix(const Matrix4 &m)
{
m_projectionMatrix = m;
m_viewProjMatrix = m_projectionMatrix * m_viewMatrix;
}
inline bool DepthFunc(int x, int y, float depthValue)
{
if (y < 0 || x < 0 || y >= screenHeight || x >= screenWidth)
return false;
int index = (y * screenWidth) + x;
unsigned int castVal = (unsigned int)depthValue;
if (castVal > m_depthBuffer[index])
return false;
m_depthBuffer[index] = castVal;
return true;
}
void SetTextureSamplingMode(TextureSampleMode mode)
{
m_texSampleState = mode;
}
TextureSampleMode GetTextureSamplingMode()
{
return m_texSampleState;
}
void SetBlendMode(BlendMode mode)
{
m_blendState = mode;
}
BlendMode GetBlendMode()
{
return m_blendState;
}
bool CohenSutherlandLine(Vector4 &inA, Vector4 &inB, Colour &colA, Colour &colB, Vector3 &texA,
Vector3 &texB);
void SutherlandHodgmanTri(Vector4 &v0, Vector4 &v1, Vector4 &v2, const Colour &c0 = Colour(),
const Colour &c1 = Colour(), const Colour &c2 = Colour(),
const Vector2 &t0 = Vector2(), const Vector2 &t1 = Vector2(),
const Vector2 &t2 = Vector2());
float ClipEdge(const Vector4 &inA, const Vector4 &inB, int axis);
int HomogeneousOutcode(const Vector4 &in);
protected:
Colour *GetCurrentBuffer();
void CalculateWeights(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2, const Vector4 &p,
float &alpha, float &beta, float &gamma);
void RasterisePointsMesh(RenderObject *o);
void RasteriseLinesMesh(RenderObject *o);
void RasteriseTriMesh(RenderObject *o);
void RasteriseTriMeshStrip(RenderObject *o);
void RasteriseTriMeshFan(RenderObject *o);
BoundingBox CalculateBoxForTri(const Vector4 &a, const Vector4 &b, const Vector4 &c);
virtual void Resize();
void RasteriseLine(const Vector4 &v0, const Vector4 &v1,
const Colour &colA = Colour(255, 255, 255, 255),
const Colour &colB = Colour(255, 255, 255, 255),
const Vector3 &texA = Vector3(0, 0, 0),
const Vector3 &texB = Vector3(1, 1, 1));
void RasteriseTri(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2,
const Colour &c0 = Colour(), const Colour &c1 = Colour(),
const Colour &c2 = Colour(), const Vector3 &t0 = Vector3(),
const Vector3 &t1 = Vector3(), const Vector3 &t2 = Vector3());
void RasteriseTriSpans(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2,
const Colour &c0 = Colour(), const Colour &c1 = Colour(),
const Colour &c2 = Colour(), const Vector3 &t0 = Vector3(),
const Vector3 &t1 = Vector3(), const Vector3 &t2 = Vector3());
void RasteriseTriEdgeSpans(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2,
const Vector4 &v3);
inline void ShadePixel(uint x, uint y, const Colour &c)
{
if (y >= screenHeight)
return;
if (x >= screenWidth)
return;
const int index = (y * screenWidth) + x;
m_buffers[m_currentDrawBuffer][index] = c;
}
inline void BlendPixel(uint x, uint y, const Colour &c)
{
if (y >= screenHeight)
return;
if (x >= screenWidth)
return;
const int index = (y * screenWidth) + x;
Colour &dest = m_buffers[m_currentDrawBuffer][index];
switch (m_blendState)
{
case BLEND_ALPHA:
{
const unsigned char sFactor = c.a;
const unsigned char dFactor = (255 - c.a);
dest.r = ((c.r * sFactor) + (dest.r * dFactor)) / 255;
dest.g = ((c.g * sFactor) + (dest.g * dFactor)) / 255;
dest.b = ((c.b * sFactor) + (dest.b * dFactor)) / 255;
dest.a = ((c.a * sFactor) + (dest.a * dFactor)) / 255;
break;
}
case BLEND_ADDITIVE:
dest = dest + c;
break;
default:
dest = c;
}
}
int m_currentDrawBuffer;
Texture *m_currentTexture;
Colour *m_buffers[2];
unsigned short *m_depthBuffer;
Matrix4 m_viewMatrix;
Matrix4 m_projectionMatrix;
Matrix4 m_textureMatrix;
Matrix4 m_viewProjMatrix;
Matrix4 m_portMatrix;
TextureSampleMode m_texSampleState;
BlendMode m_blendState;
};
#include "Texture.h"
Texture::Texture(void)
{
width = 0;
height = 0;
texels = NULL;
CreateMipMaps();
}
Texture::~Texture(void)
{
delete[] texels;
}
Texture *Texture::TextureFromTGA(const string &filename)
{
Texture *t = new Texture();
std::ifstream file;
std::cout << "Loading TGA from(" << filename << ")" << std::endl;
file.open(filename.c_str(), std::ios::binary);
if (!file.is_open())
{
std::cout << "TextureFromTGA file error" << std::endl;
return t;
}
unsigned char TGAheader[18];
std::cout << "sizeof(TGAheader) is " << sizeof(TGAheader) << std::endl;
file.read((char *)TGAheader, sizeof(TGAheader));
t->width = (TGAheader[12] + (TGAheader[13] << 8));
t->height = (TGAheader[14] + (TGAheader[15] << 8));
int size = t->width * t->height * (TGAheader[16] / 8);
t->texels = new Colour[t->width * t->height];
file.read((char *)t->texels, size);
file.close();
return t;
}
const Colour &Texture::NearestTexSample(const Vector3 &coords, int miplevel)
{
miplevel = min(miplevel, mipLevels.size() - 1);
miplevel = (mipLevels.size() - 1) - miplevel;
const int texWidth = width >> miplevel;
const int texHeight = height >> miplevel;
int x = (int)(coords.x * (texWidth - 1));
int y = (int)(coords.y * (texHeight - 1));
return ColourAtPoint(x, y, miplevel);
}
const Colour &Texture::BilinearTexSample(const Vector3 &coords, int miplevel)
{
const int texWidth = width;
const int texHeight = height;
const int x = (int)(coords.x * texWidth);
const int y = (int)(coords.y * texHeight);
const Colour &tl = ColourAtPoint(x, y);
const Colour &tr = ColourAtPoint(x + 1, y);
const Colour &bl = ColourAtPoint(x, y + 1);
const Colour &br = ColourAtPoint(x + 1, y + 1);
const float fracX = (coords.x * texWidth) - x;
const float fracY = (coords.y * texHeight) - y;
Colour top = Colour::Lerp(tl, tr, fracX);
Colour bottom = Colour::Lerp(bl, br, fracX);
return Colour::Lerp(top, bottom, fracY);
}
void Texture::CreateMipMaps()
{
int tempWidth = width;
int tempHeight = height;
mipLevels.push_back(texels);
int numLevels = 0;
while (tempWidth > 1 && tempHeight > 1)
{
// Half existing values
tempWidth = tempWidth >> 1;
tempHeight = tempHeight >> 1;
Colour * newLevel = new Colour[tempWidth * tempHeight];
GenerateMipLevel(mipLevels.back(), newLevel, numLevels);
numLevels++;
mipLevels.push_back(newLevel);
}
}
void Texture::GenerateMipLevel(Colour *source, Colour *dest, int mipLevel)
{
int sourceWidth = width >> mipLevel;
int sourceHeight = height >> mipLevel;
int destWidth = width >> (mipLevel + 1);
int destHeight = height >> (mipLevel + 1);
int outY = 0;
for (int y = 0; y < sourceHeight; y += 2)
{
int outX = 0;
for (int x = 0; x < sourceWidth; x += 2)
{
Colour out;
out += source[(y * sourceHeight) + x] * 0.25f;
out += source[(y * sourceHeight) + x + 1] * 0.25f;
out += source[((y + 1) * sourceHeight) + x] * 0.25f;
out += source[((y + 1) * sourceHeight) + x + 1] * 0.25f;
dest[outY * destHeight + outX] = out;
outX++;
}
outY++;
}
}
#pragma once
#include "SoftwareRasteriser.h"
#include "Colour.h"
#include <string>
/******************************************************************************
Class:Texture
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:Simple class to hold texture data for our software rasteriser.
Later on in the tutorial series, the ability to performa mipmapping and
bilinear filtering is added to this class.
The provided TextureFromTGA function is very basic, and will only support
uncompressed targa files - you can save images in this format using paint.net,
which is free
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <fstream>
#include <vector>
using std::string;
using std::ifstream;
using std::vector;
class Texture
{
public:
friend class SoftwareRasteriser;
Texture(void);
~Texture(void);
static Texture *TextureFromTGA(const string &filename);
const Colour &NearestTexSample(const Vector3 &coords, int miplevel = 0);
const Colour &BilinearTexSample(const Vector3 &coords, int miplevel = 0);
const Colour &ColourAtPoint(int x, int y, int mipLevel = 0)
{
int texWidth = width >> mipLevel;
int texHeight = height >> mipLevel;
x = max(0, min(x, (int)texWidth - 1));
y = max(0, min(y, (int)texHeight - 1));
int index = (y * texHeight) + x;
return texels[index];
}
uint GetWidth()
{
return width;
}
uint GetHeight()
{
return height;
}
protected:
void CreateMipMaps();
void GenerateMipLevel(Colour *source, Colour *dest, int mipLevel);
vector<Colour *> mipLevels;
uint width;
uint height;
Colour *texels;
};
/******************************************************************************
Class:Vector2
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:Simple 2-component vector class, with associated math overloads
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include <iostream>
class Vector2
{
public:
Vector2(void)
{
ToZero();
}
Vector2(const float x, const float y)
{
this->x = x;
this->y = y;
}
~Vector2(void)
{
}
float x;
float y;
void ToZero()
{
x = 0.0f;
y = 0.0f;
}
static float Dot(const Vector2 &a, const Vector2 &b)
{
return (a.x * b.x) + (a.y * b.y);
}
static float Cross(const Vector2 &a, const Vector2 &b)
{
return (a.x * b.y) - (a.y * b.x);
}
float Length() const
{
return sqrt((x * x) + (y * y));
}
float LengthSquared() const
{
return (x * x) + (y * y);
}
void Normalise()
{
float length = Length();
if (length != 0.0f)
{
length = 1.0f / length;
x = x * length;
y = y * length;
}
}
static Vector2 Lerp(const Vector2 &a, const Vector2 &b, float by)
{
Vector2 p;
p.x = ((b.x * by) + (a.x * (1.0f - by)));
p.y = ((b.y * by) + (a.y * (1.0f - by)));
return p;
}
inline friend std::ostream &operator<<(std::ostream &o, const Vector2 &v)
{
o << "Vector2(" << v.x << "," << v.y << ")" << std::endl;
return o;
}
inline Vector2 operator-(const Vector2 &a) const
{
return Vector2(x - a.x, y - a.y);
}
inline Vector2 operator+(const Vector2 &a) const
{
return Vector2(x + a.x, y + a.y);
}
inline Vector2 operator*(const Vector2 &a) const
{
return Vector2(x * a.x, y * a.y);
}
inline Vector2 operator*(const float &a) const
{
return Vector2(x * a, y * a);
}
inline Vector2 operator/(const Vector2 &a) const
{
return Vector2(x / a.x, y / a.y);
}
inline Vector2 operator/(const float &a) const
{
return Vector2(x / a, y / a);
}
inline void operator/=(const float &a)
{
x /= a;
y /= a;
}
inline void operator*=(const float &a)
{
x *= a;
y *= a;
}
inline void operator+=(const Vector2 &a)
{
x += a.x;
y += a.y;
}
inline void operator-=(const Vector2 &a)
{
x -= a.x;
y -= a.y;
}
inline void operator*=(const Vector2 &a)
{
x *= a.x;
y *= a.y;
}
inline void operator/=(const Vector2 &a)
{
x /= a.x;
y /= a.y;
}
};
#include "Vector3.h"
#include "Vector4.h"
#include "Vector2.h"
#include "Common.h"
Vector4 Vector3::ToVector4(float w)
{
return Vector4(x, y, z, w);
}
float Vector3::GetMaxElement()
{
return max(x, max(y, z));
}
float Vector3::GetMinElement()
{
return min(x, min(y, z));
}
Vector2 Vector3::ToVector2()
{
return Vector2(x, y);
}
/******************************************************************************
Class:Vector3
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:Simple 3-component vector class, with associated math overloads
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include <cmath>
#include <iostream>
#include "Vector4.h"
#include "Vector2.h"
class Vector4;
class Vector2;
class Vector3
{
public:
Vector3(void)
{
ToZero();
}
Vector3(const float x, const float y, const float z)
{
this->x = x;
this->y = y;
this->z = z;
}
Vector3(const float size)
{
this->x = size;
this->y = size;
this->z = size;
}
~Vector3(void)
{
}
float x;
float y;
float z;
void Normalise()
{
float length = Length();
if (length != 0.0f)
{
length = 1.0f / length;
x = x * length;
y = y * length;
z = z * length;
}
}
void ToZero()
{
x = y = z = 0.0f;
}
float Length() const
{
return sqrt((x * x) + (y * y) + (z * z));
}
float LengthSquared() const
{
return (x * x) + (y * y) + (z * z);
}
void Invert()
{
x = -x;
y = -y;
z = -z;
}
Vector3 Inverse() const
{
return Vector3(-x, -y, -z);
}
static inline float Dot(const Vector3 &a, const Vector3 &b)
{
return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
}
static inline Vector3 Cross(const Vector3 &a, const Vector3 &b)
{
return Vector3((a.y * b.z) - (a.z * b.y), (a.z * b.x) - (a.x * b.z), (a.x * b.y) - (a.y * b.x));
}
inline friend std::ostream &operator<<(std::ostream &o, const Vector3 &v)
{
o << "Vector3(" << v.x << "," << v.y << "," << v.z << ")" << std::endl;
return o;
}
inline Vector3 operator+(const Vector3 &a) const
{
return Vector3(x + a.x, y + a.y, z + a.z);
}
inline Vector3 operator-(const Vector3 &a) const
{
return Vector3(x - a.x, y - a.y, z - a.z);
}
inline Vector3 operator-() const
{
return Vector3(-x, -y, -z);
}
inline void operator+=(const Vector3 &a)
{
x += a.x;
y += a.y;
z += a.z;
}
inline void operator-=(const Vector3 &a)
{
x -= a.x;
y -= a.y;
z -= a.z;
}
inline void operator/=(const Vector3 &a)
{
x /= a.x;
y /= a.y;
z /= a.z;
}
inline Vector3 operator*(const float a) const
{
return Vector3(x * a, y * a, z * a);
}
inline Vector3 operator*(const Vector3 &a) const
{
return Vector3(x * a.x, y * a.y, z * a.z);
}
inline Vector3 operator/(const Vector3 &a) const
{
return Vector3(x / a.x, y / a.y, z / a.z);
};
inline Vector3 operator/(const float v) const
{
return Vector3(x / v, y / v, z / v);
};
float GetMaxElement();
float GetMinElement();
static inline Vector3 Lerp(const Vector3 &a, const Vector3 &b, float by)
{
return (a * (1.0f - by)) + (b * by);
}
Vector4 ToVector4(float w = 0.0f);
Vector2 ToVector2();
inline bool operator==(const Vector3 &A) const
{
return (A.x == x && A.y == y && A.z == z) ? true : false;
};
inline bool operator!=(const Vector3 &A) const
{
return (A.x == x && A.y == y && A.z == z) ? false : true;
};
};
#include "Vector4.h"
#include "Vector3.h"
#include "Common.h"
Vector2 Vector4::ToVector2()
{
return Vector2(x, y);
}
Vector3 Vector4::ToVector3()
{
return Vector3(x, y, z);
}
Vector3 Vector4::DivisionByW()
{
return Vector3(x / w, y / w, z / w);
}
void Vector4::SelfDivisionByW()
{
float recip = 1.0f / w;
x *= recip;
y *= recip;
z *= recip;
w = 1.0f;
}
float Vector4::GetMaxElement()
{
return max(x, max(max(y, z), w));
}
float Vector4::GetMinElement()
{
return min(x, min(min(y, z), w));
}
/******************************************************************************
Class:Vector4
Implements:
Author:Rich Davison <richard.davison4@newcastle.ac.uk>
Description:Simple 4-component vector class, with associated math overloads
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\ NYANYANYAN
-_-_-_-_-_-_-~|__( ^ .^) /
_-_-_-_-_-_-_-"" ""
*/ /////////////////////////////////////////////////////////////////////////////
#pragma once
#include "Vector2.h"
#include "Vector3.h"
class Vector3;
class Vector2;
class Vector4
{
public:
Vector4(void)
{
x = y = z = w = 1.0f;
}
Vector4(float x, float y, float z, float w)
{
this->x = x;
this->y = y;
this->z = z;
this->w = w;
}
Vector2 ToVector2();
Vector3 ToVector3();
Vector3 DivisionByW();
void SelfDivisionByW();
~Vector4(void)
{
}
inline friend std::ostream &operator<<(std::ostream &o, const Vector4 &v)
{
o << "Vector4(" << v.x << "," << v.y << "," << v.z << "," << v.w << ")" << std::endl;
return o;
}
inline Vector4 operator+(const Vector4 &a) const
{
return Vector4(x + a.x, y + a.y, z + a.z, w + a.w);
}
inline Vector4 operator-(const Vector4 &a) const
{
return Vector4(x - a.x, y - a.y, z - a.z, w - a.w);
}
inline Vector4 operator-() const
{
return Vector4(-x, -y, -z, -w);
}
float Length() const
{
return sqrt((x * x) + (y * y) + (z * z) + (w * w));
}
float LengthSquared() const
{
return (x * x) + (y * y) + (z * z) + (w * w);
}
inline void operator+=(const Vector4 &a)
{
x += a.x;
y += a.y;
z += a.z;
w += a.w;
}
inline void operator-=(const Vector4 &a)
{
x -= a.x;
y -= a.y;
z -= a.z;
w -= a.w;
}
inline Vector4 operator*(const float a) const
{
return Vector4(x * a, y * a, z * a, w * a);
}
inline Vector4 operator*(const Vector4 &a) const
{
return Vector4(x * a.x, y * a.y, z * a.z, w * a.w);
}
inline Vector4 operator/(const Vector4 &a) const
{
return Vector4(x / a.x, y / a.y, z / a.z, w / a.w);
};
inline Vector4 operator/(const float v) const
{
return Vector4(x / v, y / v, z / v, w / v);
};
float GetMaxElement();
float GetMinElement();
static inline Vector4 Lerp(const Vector4 &a, const Vector4 &b, float by)
{
return (a * (1.0f - by)) + (b * by);
}
inline bool operator==(const Vector4 &A) const
{
return (A.x == x && A.y == y && A.z == z && A.w == w) ? true : false;
};
inline bool operator!=(const Vector4 &A) const
{
return (A.x == x && A.y == y && A.z == z && A.w == w) ? false : true;
};
inline float operator[](const int i) const
{
return array[i];
}
union
{
struct
{
float x;
float y;
float z;
float w;
};
float array[4];
};
};
#include "Window.h"
Window::Window(uint width, uint height)
{
hasInit = false;
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX windowClass;
ZeroMemory(&windowClass, sizeof(WNDCLASSEX));
if (!GetClassInfoEx(hInstance, WINDOWCLASS, &windowClass))
{
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = (WNDPROC)StaticWindowProc;
windowClass.hInstance = hInstance;
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.hbrBackground = (HBRUSH)COLOR_WINDOW;
windowClass.lpszClassName = WINDOWCLASS;
if (!RegisterClassEx(&windowClass))
{
return;
}
}
windowHandle = CreateWindowEx(NULL,
WINDOWCLASS, // name of the window class
"Software Rasteriser!", // title of the window
WS_OVERLAPPEDWINDOW | WS_POPUP | WS_VISIBLE | WS_SYSMENU |
WS_MAXIMIZEBOX | WS_MINIMIZEBOX, // window style
(int)100, // x-position of the window
(int)100, // y-position of the window
(int)width, // width of the window
(int)height, // height of the window
NULL, // No parent window!
NULL, // No Menus!
hInstance, // application handle
this); //
deviceContext = GetDC(windowHandle);
RECT rt;
GetClientRect(windowHandle, &rt);
screenWidth = rt.right;
screenHeight = rt.bottom;
BuildBitmap();
Keyboard::Initialise(windowHandle);
Mouse::Initialise(windowHandle);
hasInit = true;
forceQuit = false;
}
Window::~Window(void)
{
DeleteObject(bitBuffers[0]);
DeleteObject(bitBuffers[1]);
DeleteDC(drawDC);
Keyboard::Destroy();
Mouse::Destroy();
}
void Window::BuildBitmap()
{
DeleteObject(bitBuffers[0]);
DeleteObject(bitBuffers[1]);
DeleteDC(drawDC);
BITMAPINFO bmi;
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = screenWidth;
bmi.bmiHeader.biHeight = screenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; // four 8-bit components
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = screenWidth * screenHeight * 3;
drawDC = CreateCompatibleDC(deviceContext);
for (int i = 0; i < 2; ++i)
{
bitBuffers[i] = CreateDIBSection(drawDC, &bmi, DIB_RGB_COLORS, &bufferData[i], NULL, 0x0);
}
}
void Window::PresentBuffer(Colour *buffer)
{
if ((void *)buffer == bufferData[0])
{
SelectObject(drawDC, bitBuffers[0]);
}
else
{
SelectObject(drawDC, bitBuffers[1]);
// Using the student provided mem buffer, must memcopy!
if ((void *)buffer != bufferData[0] && ((void *)buffer != bufferData[1]))
{
memcpy(bufferData[1], buffer, screenWidth * screenHeight * sizeof(unsigned int));
}
}
BitBlt(deviceContext, 0, 0, screenWidth, screenHeight, drawDC, 0, 0, SRCCOPY);
}
LRESULT Window::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case (WM_CREATE):
{
Window *w = reinterpret_cast<Window *>(((LPCREATESTRUCT)lParam)->lpCreateParams);
SetWindowLongPtr(hWnd, GWL_USERDATA, reinterpret_cast<long>(w));
}
break;
case (WM_DESTROY):
{
PostQuitMessage(0);
forceQuit = true;
}
break;
case (WM_ACTIVATE):
{
if (LOWORD(wParam) == WA_INACTIVE)
{
ReleaseCapture();
ClipCursor(NULL);
if (hasInit)
{
Mouse::instance->Sleep();
Mouse::instance->Sleep();
}
}
else
{
if (hasInit)
{
Mouse::instance->Wake();
Mouse::instance->Wake();
}
}
return 0;
}
break;
case (WM_LBUTTONDOWN):
{
}
break;
case (WM_MOUSEMOVE):
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = windowHandle;
TrackMouseEvent(&tme);
}
break;
case (WM_SIZE):
{
screenWidth = LOWORD(lParam);
screenHeight = HIWORD(lParam);
BuildBitmap();
Resize();
}
break;
case (WM_SETFOCUS):
{
if (hasInit)
{
Mouse::instance->Wake();
Mouse::instance->Wake();
}
}
break;
case (WM_KILLFOCUS):
{
if (hasInit)
{
Mouse::instance->Sleep();
Mouse::instance->Sleep();
}
}
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
LRESULT CALLBACK Window::StaticWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
Window *window = (Window *)GetWindowLongPtr(hWnd, GWL_USERDATA);
return window->WindowProc(hWnd, message, wParam, lParam);
}
bool Window::UpdateWindow()
{
Keyboard::instance->UpdateHolds();
Mouse::instance->UpdateHolds();
MSG msg;
while (PeekMessage(&msg, windowHandle, 0, 0, PM_REMOVE))
{
CheckMessages(msg);
}
return !forceQuit;
}
void Window::CheckMessages(MSG &msg)
{
switch (msg.message)
{ // Is There A Message Waiting?
case (WM_QUIT):
case (WM_CLOSE):
{ // Have We Received A Quit Message?
forceQuit = true;
}
break;
case (WM_INPUT):
{
UINT dwSize;
GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
BYTE *lpb = new BYTE[dwSize];
GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER));
RAWINPUT *raw = (RAWINPUT *)lpb;
if (Keyboard::instance && raw->header.dwType == RIM_TYPEKEYBOARD)
{
Keyboard::instance->Update(raw);
}
else if (Mouse::instance && raw->header.dwType == RIM_TYPEMOUSE)
{
Mouse::instance->Update(raw);
}
delete lpb;
}
break;
default:
{ // If Not, Deal With Window Messages
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
}
#pragma once
#include "Common.h"
#include "Colour.h"
#include "Mouse.h"
#include "Keyboard.h"
#include <windows.h>
#include <fcntl.h>
// These two defines cut a lot of crap out of the Windows libraries
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define WINDOWCLASS "WindowClass"
// This is the OS-specific crap required to render our pixel blocks on screen
class Window
{
public:
Window(uint width, uint height);
~Window(void);
void PresentBuffer(Colour *buffer);
bool UpdateWindow();
protected:
void CheckMessages(MSG &msg);
void BuildBitmap();
virtual void Resize() {
};
// Windows requires a static callback function to handle certain incoming messages.
static LRESULT CALLBACK StaticWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
HWND windowHandle; // OS handle
HDC deviceContext;
uint screenWidth;
uint screenHeight;
HDC drawDC;
HBITMAP bitBuffers[2];
void *bufferData[2];
VOID *pvBits; // pointer to DIB section
bool forceQuit;
bool hasInit;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment