commit a16a5a4102a37c1d661e985ab0f6c343927019ea Author: Raimon Zamora Date: Thu Feb 12 10:51:02 2026 +0100 - First commit to gitea diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..bba1a12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/* +biomed +biomed_debug diff --git a/Editor.cpp b/Editor.cpp new file mode 100644 index 0000000..c7e4845 --- /dev/null +++ b/Editor.cpp @@ -0,0 +1,34 @@ +#include "Editor.h" +#include "Model.h" +#include + +namespace Editor +{ + Mode mode {Mode::Cube}; + State state {State::None}; + char textureFilename[400] {"/"}; + unsigned texture {0}; + char forcedAxis {-1}; + float t {0.0f}; + uint8_t bitmap[32*32*4] {255}; + + void SetMode(const Mode value) + { + mode = value; + if (mode == Mode::Cube) { + model.SelectAllFaces(false); + } else if (mode == Mode::Face) { + model.SelectAllCubes(false); + } + } + + void SetState(const State value) + { + forcedAxis = -1; + if (state == value) { + state = State::None; + } else { + state = value; + } + } +} diff --git a/Editor.h b/Editor.h new file mode 100644 index 0000000..bba0fb8 --- /dev/null +++ b/Editor.h @@ -0,0 +1,19 @@ +#pragma once + +namespace Editor +{ + enum class Mode { Face, Cube, Anim, Paint }; + enum class State { None, Grab, Scale, Rotate, Pivot }; + + extern Mode mode; + extern State state; + extern char textureFilename[400]; + extern unsigned texture; + extern char forcedAxis; + extern float t; + + extern unsigned char bitmap[32*32*4]; + + void SetMode(const Mode value); + void SetState(const State value); +} diff --git a/GUI.cpp b/GUI.cpp new file mode 100644 index 0000000..08c9032 --- /dev/null +++ b/GUI.cpp @@ -0,0 +1,941 @@ +#include "GUI.h" + +#include "screen.h" +#include +#include "GUIDraw.h" +#include "font.h" +#include "GUIMouse.h" +#include "GUIKeyboard.h" +#include "GUIViewport.h" +#include "Model.h" +#include "renderer.h" +#include "Editor.h" +//#include "GUIDialogs.h" +#include "texture.h" +#include + +const Color winBack{30, 30, 30, 255}; +const Color panelBack{53, 53, 60, 255}; +const Color panel3DBack{40, 40, 43, 255}; +const Color textBoxBack{61, 61, 66, 255}; +const Color textBoxBorder{76, 77, 81, 255}; +const Color buttonBack{63, 63, 70, 255}; +const Color buttonPressed{62, 153, 247, 255}; + +namespace GUI +{ + Font* font {nullptr}; + float viewPos{0}; + Vector4 viewSize{0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; + Vector4 viewStack[10]; + char viewStackPos{0}; + int focus{-1}; + int id{0}; + bool dot{false}; + bool empty{false}; + + bool menuVisible {false}; + Vector2 menuPos {0, 0}; + int subMenu{-1}; + Vector2 subMenuPos {0, 0}; + + bool comboBoxVisible {false}; + + int selectedViewport {0}; + bool zoomed {false}; + + Color currentColor {0, 0, 0, 255}; + + float a = 6.5f; + float b = 12.0f; + float c = 24.0f; + + void ResetFocus() + { + focus = -1; + dot = false; + empty = false; + menuVisible = false; + } + + void StartSplitV(const float& value) + { + assert(viewStackPos < 10); + + viewPos = 0; + viewStack[viewStackPos] = viewSize; + if (value > 0) { + viewStack[viewStackPos].w -= (value + 5); + viewSize.w = value; + } else { + viewStack[viewStackPos].w = -value; + viewSize.w -= (-value + 5); + } + viewStack[viewStackPos].y = viewSize.y + viewSize.w + 5; + viewStackPos++; + } + + void StartSplitH(const float& value) + { + assert(viewStackPos < 10); + + viewPos = 0; + viewStack[viewStackPos] = viewSize; + if (value > 0) { + viewStack[viewStackPos].z -= (value + 5); + viewSize.z = value; + } else { + viewStack[viewStackPos].z = -value; + viewSize.z -= (-value + 5); + } + viewStack[viewStackPos].x = viewSize.x + viewSize.z + 5; + viewStackPos++; + } + + void NextSplit() + { + assert(viewStackPos > 0); + viewPos = 0; + viewStackPos--; + viewSize = viewStack[viewStackPos]; + } + + void DrawPanel() + { + viewPos = 0; + GUIDraw::FillRect(viewSize, panelBack); + viewSize.x += 10; + viewSize.y += 10; + viewSize.z -= 20; + viewSize.w -= 20; + } + + void Draw3DPanel(const int index) + { + if (not menuVisible and GUIMouse::position.inside(viewSize.GetPosition(), viewSize.GetSize())) { + selectedViewport = index; + if (GUIKeyboard::key == SDLK_z) { + GUIKeyboard::key = SDLK_UNKNOWN; + zoomed = not zoomed; + } + } + + const Color color = (index == selectedViewport ? Color(128, 0, 0, 255) : panel3DBack); + viewPos = 0; + GUIDraw::FillRect(viewSize, color); + viewSize.x += 5; + viewSize.y += 5; + viewSize.z -= 10; + viewSize.w -= 10; + } + + void DrawTitle(const char* title) + { + viewPos = 0; + GUIDraw::FillRect({viewSize.x, viewSize.y, viewSize.z, 25}, winBack); + Vector2 textSize = font->GetSize(title); + const float pos = (viewSize.z-5) * 0.5f - textSize.x * 0.5f; + + font->Print(viewSize.x+5+pos, viewSize.y+4, title); + + viewSize.y += 30; + viewSize.w -= 30; + } + + void DrawCaption(const char* title) + { + viewPos = 0; + font->Print(viewSize.x, viewSize.y, title); + + viewSize.y += 20; + viewSize.w -= 20; + } + + const bool DrawComboBox(char* text) + { + bool retVal = false; + Vector4 rect {viewPos+viewSize.x, viewSize.y, viewSize.z, 22}; + Color backColor = textBoxBack; + if (text != nullptr and GUIMouse::position.inside(rect.GetPosition(), rect.GetSize())) { + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + retVal = true; + } + backColor = textBoxBorder; + } + GUIDraw::FillRect(rect, backColor); + GUIDraw::DrawRect(rect, textBoxBorder); + + if (text != nullptr) font->Print(viewPos+viewSize.x+5, viewSize.y+2, text, 15); + + viewPos = 0; + viewSize.y += 27; + viewSize.w -= 27; + + return retVal; + } + + void DrawTextBox(char* text) + { + Vector4 rect {viewPos+viewSize.x, viewSize.y, viewSize.z, 22}; + Color backColor = textBoxBack; + if (text != nullptr and GUIMouse::position.inside(rect.GetPosition(), rect.GetSize())) { + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + focus = id; + } + backColor = textBoxBorder; + } + GUIDraw::FillRect(rect, backColor); + GUIDraw::DrawRect(rect, textBoxBorder); + + if (text != nullptr) { + char textColor = 15; + if (focus == id) { + textColor = 11; + if (GUIKeyboard::key >= SDLK_SPACE and GUIKeyboard::key <= SDLK_z) { + const size_t len = strlen(text); + if (len < 9) { + text[len] = GUIKeyboard::key; + text[len+1] = '\0'; + GUIKeyboard::key = SDLK_UNKNOWN; + } + } else if (GUIKeyboard::key == SDLK_BACKSPACE) { + GUIKeyboard::key = SDLK_UNKNOWN; + const size_t len = strlen(text); + if (len > 0) text[len-1] = '\0'; + } else if (GUIKeyboard::key == SDLK_RETURN) { + GUIKeyboard::key = SDLK_UNKNOWN; + ResetFocus(); + textColor = 15; + } else if (GUIKeyboard::key == SDLK_TAB) { + GUIKeyboard::key = SDLK_UNKNOWN; + focus++; + textColor = 15; + } + if (focus == id) { + const Vector2 size = font->GetSize(text); + font->Print(size.x+viewPos+viewSize.x+5, viewSize.y+2, "_", textColor); + } + } + font->Print(viewPos+viewSize.x+5, viewSize.y+2, text, textColor); + } + + viewPos = 0; + viewSize.y += 27; + viewSize.w -= 27; + id++; + } + + void DrawNumTextBox(float* number, const float size) + { + Vector4 rect {viewPos+viewSize.x, viewSize.y, size, 22}; + Color backColor = textBoxBack; + + if (number != nullptr and GUIMouse::position.inside(rect.GetPosition(), rect.GetSize())) { + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + dot = false; + empty = false; + focus = id; + } + backColor = textBoxBorder; + } + GUIDraw::FillRect(rect, backColor); + GUIDraw::DrawRect(rect, textBoxBorder); + + if (number != nullptr) { + int i; + char s1[20]; + char* s2; + //sprintf(text, "%.f", number); + + sprintf(s1, "%f", *number); + s2 = strchr(s1, '.'); + i = int(s2 - s1); + sprintf(s1, "%.*g", (i+2), *number); + + char textColor = 15; + if (focus == id) { + textColor = 11; + if (dot) strcat(s1, "."); + if (empty) s1[0] = '\0'; + const bool dotAlready = (strchr(s1, '.') != nullptr); + if (GUIKeyboard::key >= SDLK_0 and GUIKeyboard::key <= SDLK_9) { + if (not empty and s1[0] == '0' and not (strlen(s1)>1 and s1[1] == '.' )) s1[0] = '\0'; + empty = false; + const size_t len = strlen(s1); + s1[len] = GUIKeyboard::key; + s1[len+1] = '\0'; + GUIKeyboard::key = SDLK_UNKNOWN; + } else if (not dotAlready and GUIKeyboard::key == SDLK_PERIOD) { + empty = false; + const size_t len = strlen(s1); + s1[len] = GUIKeyboard::key; + s1[len+1] = '\0'; + GUIKeyboard::key = SDLK_UNKNOWN; + } else if (GUIKeyboard::key == SDLK_BACKSPACE) { + GUIKeyboard::key = SDLK_UNKNOWN; + const size_t len = strlen(s1); + if (len == 1) empty = true; + s1[len-1] = '\0'; + } else if (GUIKeyboard::key == SDLK_RIGHT) { + GUIKeyboard::key = SDLK_UNKNOWN; + (*number)++; + sprintf(s1, "%f", *number); + s2 = strchr(s1, '.'); + i = int(s2 - s1); + sprintf(s1, "%.*g", (i+2), *number); + } else if (GUIKeyboard::key == SDLK_LEFT) { + GUIKeyboard::key = SDLK_UNKNOWN; + (*number)--; + sprintf(s1, "%f", *number); + s2 = strchr(s1, '.'); + i = int(s2 - s1); + sprintf(s1, "%.*g", (i+2), *number); + } else if (GUIKeyboard::key == SDLK_RETURN) { + GUIKeyboard::key = SDLK_UNKNOWN; + ResetFocus(); + textColor = 15; + const size_t len = strlen(s1); + if (s1[len-1] == '.') s1[len-1] = '\0'; + } else if (GUIKeyboard::key == SDLK_TAB) { + GUIKeyboard::key = SDLK_UNKNOWN; + focus++; + textColor = 15; + const size_t len = strlen(s1); + if (s1[len-1] == '.') s1[len-1] = '\0'; + } + if (focus == id) { + const size_t len = strlen(s1); + dot = (s1[len-1] == '.'); + *number = atof(s1); + + const Vector2 size = font->GetSize(s1); + font->Print(size.x+viewPos+viewSize.x+5, viewSize.y+2, "_", textColor); + } + } + font->Print(viewPos+viewSize.x+5, viewSize.y+2, s1, textColor); + } + + int numElements = viewSize.z / size; + float separation = (viewSize.z - (size*numElements)) / (numElements-1); + viewPos += size+separation; + + if (viewPos > viewSize.z) { + viewPos = 0; + viewSize.y += 27; + viewSize.w -= 27; + } + id++; + } + + void DrawViewport(const int index) + { + glViewport(viewSize.x, SCREEN_HEIGHT-(viewSize.y+viewSize.w), viewSize.z, viewSize.w); + glEnable(GL_SCISSOR_TEST); + glScissor(viewSize.x, SCREEN_HEIGHT-(viewSize.y+viewSize.w), viewSize.z, viewSize.w); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + float zoom = viewports[index].zoom; + if (zoom < 0) zoom = 1.0f/-zoom; + const float x = (viewSize.z * 0.5f) / zoom; + const float y = (viewSize.w * 0.5f) / zoom; + glOrtho(-x, x, y, -y, 1000, -1000); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glTranslatef(viewports[index].offset.x, viewports[index].offset.y, 0); + glRotatef(viewports[index].rotation.x, 1, 0, 0); + glRotatef(viewports[index].rotation.y, 0, 1, 0); + + + if (not menuVisible and GUIMouse::position.inside(viewSize.GetPosition(), viewSize.GetSize())) { + if (GUIMouse::buttons[GUIMouse::Button::Right] == GUIMouse::ButtonState::Down) { + menuVisible = true; + menuPos = GUIMouse::position; + } + if (not GUIKeyboard::alt and GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + if (Editor::state == Editor::State::None) { + if (Editor::mode == Editor::Mode::Paint) { + if (viewports[index].renderMode == Viewport::RenderMode::UV) { + Renderer::SetState(RENDER_FILL); + Color color = model.PickPixel(GUIMouse::position.x, GUIMouse::position.y); + Editor::bitmap[(color.x*4)+color.y*128] = currentColor.r; + Editor::bitmap[(color.x*4)+1+color.y*128] = currentColor.g; + Editor::bitmap[(color.x*4)+2+color.y*128] = currentColor.b; + Editor::bitmap[(color.x*4)+3+color.y*128] = currentColor.a; + Texture::Update(Editor::texture, 32, 32, Editor::bitmap); + + } else { + Renderer::SetState(RENDER_DEPTH | RENDER_FILL); + Color color = model.PickPaint(GUIMouse::position.x, GUIMouse::position.y); + Editor::bitmap[(color.y*4)+color.z*128] = currentColor.r; + Editor::bitmap[(color.y*4)+1+color.z*128] = currentColor.g; + Editor::bitmap[(color.y*4)+2+color.z*128] = currentColor.b; + Editor::bitmap[(color.y*4)+3+color.z*128] = currentColor.a; + Texture::Update(Editor::texture, 32, 32, Editor::bitmap); + } + } else { + Renderer::SetState(RENDER_DEPTH | RENDER_FILL); + Color color = model.Pick(GUIMouse::position.x, GUIMouse::position.y); + if (Editor::mode == Editor::Mode::Cube) { + model.SelectCube(color.r, GUIKeyboard::shift); + } else if (Editor::mode == Editor::Mode::Face) { + model.SelectFace(color.r, color.g, GUIKeyboard::shift); + } + } + } + ResetFocus(); + } + if (GUIMouse::delta.x != 0 or GUIMouse::delta.y != 0) { + if (GUIKeyboard::alt or GUIMouse::buttons[GUIMouse::Button::Middle] == GUIMouse::ButtonState::Pressed) { + if (GUIKeyboard::shift) { + viewports[index].offset += GUIMouse::delta / viewports[index].zoom; + } else { + viewports[index].rotation += Vector2(GUIMouse::delta.y, -GUIMouse::delta.x)*1; + if (viewports[index].rotation.x < -90) viewports[index].rotation.x = -90; + if (viewports[index].rotation.x > 90) viewports[index].rotation.x = 90; + } + } + } + if (GUIMouse::wheel.x != 0 or GUIMouse::wheel.y != 0) { + if (GUIKeyboard::shift) { + viewports[index].offset += GUIMouse::wheel / viewports[index].zoom; + } else { + viewports[index].zoom -= GUIMouse::wheel.y; + } + } + } + + glClearColor(float(panel3DBack.r)/255.0f, float(panel3DBack.g)/255.0f, float(panel3DBack.b)/255.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + if (viewports[index].renderMode == Viewport::RenderMode::Solid) { + Renderer::SetState(RENDER_DEPTH | RENDER_FILL); + if (viewports[index].light) Renderer::Light(ON); + model.DrawSolid(); + } else if (viewports[index].renderMode == Viewport::RenderMode::Textured) { + Renderer::SetState(RENDER_DEPTH | RENDER_FILL | RENDER_TEXTURE); + if (viewports[index].light) Renderer::Light(ON); + glBindTexture(GL_TEXTURE_2D, Editor::texture); + model.DrawSolid(); + } else if (viewports[index].renderMode == Viewport::RenderMode::Wire) { + Renderer::SetState(RENDER_DEPTH); + model.DrawWire(); + } else if (viewports[index].renderMode == Viewport::RenderMode::UV) { + Renderer::SetState(RENDER_FILL | RENDER_TEXTURE); + glBindTexture(GL_TEXTURE_2D, Editor::texture); + model.DrawUV(); + } + + glDisable(GL_SCISSOR_TEST); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + } + + const bool DrawButton(const char* text, const SDL_Keycode key, const bool state, const float size = 85) + { + Vector4 rect {viewPos+viewSize.x, viewSize.y, size, 22}; + Color backColor = buttonBack; + Color borders[4] = {textBoxBorder, winBack, winBack, textBoxBorder}; + bool retVal {false}; + ivec2 textPadding {5,2}; + + if (state) { + backColor = textBoxBorder; + borders[0] = borders[3] = winBack; + borders[1] = borders[2] = textBoxBorder; + textPadding += {1,1}; + } + + if (GUIMouse::position.inside(rect.GetPosition(), rect.GetSize())) { + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Pressed) { + borders[0] = borders[3] = winBack; + borders[1] = borders[2] = textBoxBorder; + textPadding += {1,1}; + } + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Up) { + borders[0] = borders[3] = winBack; + borders[1] = borders[2] = textBoxBorder; + textPadding += {1,1}; + ResetFocus(); + retVal = true; + } + backColor = textBoxBorder; + } + if (focus == -1 and key != SDLK_UNKNOWN and GUIKeyboard::key == key) { + ResetFocus(); + retVal = true; + } + + GUIDraw::FillRect({viewPos+viewSize.x, viewSize.y, size, 22}, backColor); + GUIDraw::DrawRect({viewPos+viewSize.x, viewSize.y, size, 22}, borders); + font->Print(viewPos+viewSize.x+textPadding.x, viewSize.y+textPadding.y, text, 15); + + int numElements = viewSize.z / size; + float separation = (viewSize.z - (size*numElements)) / (numElements-1); + viewPos += size+separation; + + if (viewPos > viewSize.z) { + viewPos = 0; + viewSize.y += 27; + viewSize.w -= 27; + } + //id++; + + return retVal; + } + + void DrawStaticTextBox(const char* text, const float size = 85) + { + Vector4 rect {viewPos+viewSize.x, viewSize.y, size, 22}; + GUIDraw::FillRect(rect, textBoxBack); + GUIDraw::DrawRect(rect, textBoxBorder); + font->Print(viewPos+viewSize.x+5, viewSize.y+2, text, 7); + + viewPos += size+10; + + if (viewPos > viewSize.z) { + viewPos = 0; + viewSize.y += 27; + viewSize.w -= 27; + } + } + + void DrawSpace(const int space) + { + viewPos = 0; + viewSize.y += space; + viewSize.w -= space; + } + + void Reset() + { + if (font == nullptr) { + font = new Font(); + font->Load("font"); + } + glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 100, -100); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + viewStackPos = 0; + viewPos = 0; + viewSize = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}; + id = 0; + } + + void DrawMenu(const int numItems, const float size) + { + viewSize = Vector4(menuPos.x, menuPos.y, size, 25*numItems); + if (menuPos.x+size > SCREEN_WIDTH) viewSize.x = SCREEN_WIDTH - size; + if (menuPos.y+25*numItems > SCREEN_HEIGHT) viewSize.y = SCREEN_HEIGHT - 25*numItems; + + GUIDraw::FillRect(viewSize, panelBack); + GUIDraw::DrawRect(viewSize, winBack); + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + if (not GUIMouse::position.inside(viewSize.GetPosition(), viewSize.GetSize())) { + ResetFocus(); + } + } + } + + void DrawSubMenu(const int numItems, const float size) + { + viewSize = Vector4(subMenuPos.x, subMenuPos.y, size, 25*numItems); + if (subMenuPos.x+size > SCREEN_WIDTH) viewSize.x = subMenuPos.x - (size*2); + if (subMenuPos.y+25*numItems > SCREEN_HEIGHT) viewSize.y = SCREEN_HEIGHT - 25*numItems; + + GUIDraw::FillRect(viewSize, panelBack); + GUIDraw::DrawRect(viewSize, winBack); + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + if (not GUIMouse::position.inside(viewSize.GetPosition(), viewSize.GetSize())) { + ResetFocus(); + } + } + } + + const bool DrawMenuItem(const char* text, const bool state) + { + bool retVal = false; + Vector4 rect {viewSize.x, viewSize.y, viewSize.z, 25}; + if (GUIMouse::position.inside(rect.GetPosition(), rect.GetSize())) { + subMenu = -1; + GUIDraw::FillRect(rect, textBoxBack); + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + retVal = true; + menuVisible = false; + comboBoxVisible = false; + } + } + if (state) font->Print(viewSize.x+3, viewSize.y+2, "+", 15); + font->Print(viewSize.x+13, viewSize.y+2, text, 15); + + viewSize.y += 25; + viewSize.w -= 25; + + return retVal; + } + + void DrawMenuSubItem(const char* text, const int subMenuIndex) + { + Vector4 rect {viewSize.x, viewSize.y, viewSize.z, 25}; + if (GUIMouse::position.inside(rect.GetPosition(), rect.GetSize())) { + GUIDraw::FillRect(rect, textBoxBack); + subMenu = subMenuIndex; + subMenuPos = Vector2(viewSize.x+viewSize.z, viewSize.y); + } else if (subMenu == subMenuIndex) { + GUIDraw::FillRect(rect, textBoxBack); + } + + font->Print(viewSize.x+viewSize.z-13, viewSize.y+2, ">", 15); + font->Print(viewSize.x+13, viewSize.y+2, text, 15); + + viewSize.y += 25; + viewSize.w -= 25; + } + + const bool DrawSubMenuItem(const char* text, const bool state) + { + bool retVal = false; + Vector4 rect {viewSize.x, viewSize.y, viewSize.z, 25}; + if (GUIMouse::position.inside(rect.GetPosition(), rect.GetSize())) { + GUIDraw::FillRect(rect, textBoxBack); + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + retVal = true; + menuVisible = false; + } + } + if (state) font->Print(viewSize.x+3, viewSize.y+2, "+", 15); + font->Print(viewSize.x+13, viewSize.y+2, text, 15); + + viewSize.y += 25; + viewSize.w -= 25; + + return retVal; + } + + + Vector2 GetTransformedAxesUV(const float x, const float y) + { + Vector2 result {x, y}; + if (Editor::forcedAxis == 0) result.y = 0; + else if (Editor::forcedAxis == 1) result.x = 0; + return result; + } + + Vector3 GetTransformedAxes(const float x, const float y) + { + Vector3 result {0, 0, 0}; + + if (Editor::forcedAxis == 0) { + result.x = x + y; + result.y = 0; + result.z = 0; + } else if (Editor::forcedAxis == 1) { + result.x = 0; + result.y = x + y; + result.z = 0; + } else if (Editor::forcedAxis == 2) { + result.x = 0; + result.y = 0; + result.z = x + y; + } else { + float rx = viewports[selectedViewport].rotation.x; while (rx >= 360) rx -= 360; while (rx < 0) rx += 360; + float ry = viewports[selectedViewport].rotation.y; while (ry >= 360) ry -= 360; while (ry < 0) ry += 360; + if (rx <= 45 or rx >= 315 ) { + if (ry <= 45 or ry >= 315) { + // x, y + result.x = x; + result.y = y; + } else if (ry > 45 and ry < 135) { + // z, y + result.z = x; + result.y = y; + } else if (ry >= 135 and ry < 225) { + // -x, y + result.x = -x; + result.y = y; + } else { + // -z, y + result.z = -x; + result.y = y; + } + } else if (rx > 45 and rx < 180) { + if (ry <= 45 or ry >= 315) { + // x, -z + result.x = x; + result.z = -y; + } else if (ry > 45 and ry < 135) { + // z, x + result.z = x; + result.x = y; + } else if (ry >= 135 and ry < 225) { + // -x, z + result.x = -x; + result.z = y; + } else { + // -z, -x + result.z = -x; + result.x = -y; + } + } else { + if (ry <= 45 or ry >= 315) { + // x, z + result.x = x; + result.z = y; + } else if (ry > 45 and ry < 135) { + // -z, x + result.z = x; + result.x = -y; + } else if (ry >= 135 and ry < 225) { + // -x, -z + result.x = -x; + result.z = -y; + } else { + // z, -x + result.z = -x; + result.x = y; + } + } + } + return result; + } + + void Draw() + { + if (Editor::state != Editor::State::None) { + if (Editor::mode == Editor::Mode::Face) { + if (GUIKeyboard::key == SDLK_x) { Editor::forcedAxis = 0; GUIKeyboard::key = SDLK_UNKNOWN; } + if (GUIKeyboard::key == SDLK_y) { Editor::forcedAxis = 1; GUIKeyboard::key = SDLK_UNKNOWN; } + if (GUIMouse::slowDelta.x != 0 or GUIMouse::slowDelta.y != 0) { + Vector2 axes = GetTransformedAxesUV(GUIMouse::slowDelta.x, GUIMouse::slowDelta.y); + if (Editor::state == Editor::State::Grab) model.GrabFaces(axes.x, axes.y); + if (Editor::state == Editor::State::Scale) model.ScaleFaces(axes.x, axes.y); + } + } else if (Editor::mode == Editor::Mode::Cube) { + if (GUIKeyboard::key == SDLK_x) { Editor::forcedAxis = 0; GUIKeyboard::key = SDLK_UNKNOWN; } + if (GUIKeyboard::key == SDLK_y) { Editor::forcedAxis = 1; GUIKeyboard::key = SDLK_UNKNOWN; } + if (GUIKeyboard::key == SDLK_z) { Editor::forcedAxis = 2; GUIKeyboard::key = SDLK_UNKNOWN; } + if (GUIMouse::slowDelta.x != 0 or GUIMouse::slowDelta.y != 0) { + Vector3 axes = GetTransformedAxes(GUIMouse::slowDelta.x, GUIMouse::slowDelta.y); + if (Editor::state == Editor::State::Grab) model.GrabCubes(axes.x, axes.y, axes.z); + if (Editor::state == Editor::State::Scale) model.ScaleCubes(axes.x, axes.y, axes.z); + } + } + } + + Reset(); + //model.cubes[0].selected = true; + + StartSplitV(30); //TOOLBAR + + DrawPanel(); + + NextSplit(); + StartSplitV(-30); + StartSplitH(200); + // INFO PANEL + DrawPanel(); + DrawTitle("State"); + if (DrawButton("Cubes", SDLK_1, Editor::mode == Editor::Mode::Cube)) Editor::SetMode(Editor::Mode::Cube); + if (DrawButton("Faces", SDLK_2, Editor::mode == Editor::Mode::Face)) Editor::SetMode(Editor::Mode::Face); + if (DrawButton("Paint", SDLK_3, Editor::mode == Editor::Mode::Paint)) Editor::SetMode(Editor::Mode::Paint); + if (DrawButton("Anim", SDLK_4, Editor::mode == Editor::Mode::Anim)) Editor::SetMode(Editor::Mode::Anim); + + DrawSpace(20); + if (Editor::mode == Editor::Mode::Cube) { + int index = model.GetSelectedCube(); + DrawTitle("Cube Info"); + DrawCaption("Name:"); + DrawTextBox(index != -1 ? model.cubes[index].name : nullptr); + + float* v[9] = {nullptr}; + if (index != -1) { + v[0] = &model.cubes[index].position.x; + v[1] = &model.cubes[index].position.y; + v[2] = &model.cubes[index].position.z; + v[3] = &model.cubes[index].size.x; + v[4] = &model.cubes[index].size.y; + v[5] = &model.cubes[index].size.z; + v[6] = &model.cubes[index].pivot.x; + v[7] = &model.cubes[index].pivot.y; + v[8] = &model.cubes[index].pivot.z; + } + DrawCaption("Position:"); + DrawNumTextBox(v[0], 50); + DrawNumTextBox(v[1], 50); + DrawNumTextBox(v[2], 50); + DrawCaption("Size:"); + DrawNumTextBox(v[3], 50); + DrawNumTextBox(v[4], 50); + DrawNumTextBox(v[5], 50); + DrawCaption("Pivot:"); + DrawNumTextBox(v[6], 50); + DrawNumTextBox(v[7], 50); + DrawNumTextBox(v[8], 50); + DrawCaption("Parent:"); + char parent[10] = ""; + if (index != -1 and model.cubes[index].parent != -1) strcpy(parent, model.cubes[model.cubes[index].parent].name); + if (DrawComboBox(index != -1 ? parent : nullptr)) { + comboBoxVisible = not comboBoxVisible; + if (comboBoxVisible) { + subMenuPos.x = viewSize.z; + menuPos = {viewSize.x, viewSize.y}; + } + } + + DrawSpace(20); + DrawTitle("Cube Ops"); + if (DrawButton("New", SDLK_n, false)) model.NewCube(); + if (DrawButton("Delete", SDLK_BACKSPACE, false)) model.DeleteCube(); + if (DrawButton("Dup", SDLK_d, false)) { + model.DupCube(); + Editor::SetState(Editor::State::Grab); + } + if (DrawButton("Grab", SDLK_g, Editor::state == Editor::State::Grab)) Editor::SetState(Editor::State::Grab); + if (DrawButton("Scale", SDLK_s, Editor::state == Editor::State::Scale)) Editor::SetState(Editor::State::Scale); + if (DrawButton("Pivot", SDLK_p, Editor::state == Editor::State::Pivot)) Editor::SetState(Editor::State::Pivot); + + } else if (Editor::mode == Editor::Mode::Face) { + DrawTitle("Face Info"); + float* v[8] = {nullptr}; + ivec2 index = model.GetSelectedFace(); + if (index.x != -1) { + v[0] = &model.cubes[index.x].faces[index.y].uv[2].x; + v[1] = &model.cubes[index.x].faces[index.y].uv[2].y; + v[2] = &model.cubes[index.x].faces[index.y].uv[1].x; + v[3] = &model.cubes[index.x].faces[index.y].uv[1].y; + v[4] = &model.cubes[index.x].faces[index.y].uv[3].x; + v[5] = &model.cubes[index.x].faces[index.y].uv[3].y; + v[6] = &model.cubes[index.x].faces[index.y].uv[0].x; + v[7] = &model.cubes[index.x].faces[index.y].uv[0].y; + } + DrawCaption("UV:"); + DrawNumTextBox(v[0], 42); + DrawNumTextBox(v[1], 42); + DrawNumTextBox(v[2], 42); + DrawNumTextBox(v[3], 42); + DrawNumTextBox(v[4], 42); + DrawNumTextBox(v[5], 42); + DrawNumTextBox(v[6], 42); + DrawNumTextBox(v[7], 42); + + DrawSpace(20); + DrawTitle("Face Ops"); + if (DrawButton("Clear", SDLK_c, false)) model.ClearFaces(); + if (DrawButton("Rotate", SDLK_r, false)) model.RotateFaces(); + if (DrawButton("FlipH", SDLK_h, false)) model.FlipHFaces(); + if (DrawButton("FlipV", SDLK_v, false)) model.FlipVFaces(); + if (DrawButton("Grab", SDLK_g, Editor::state == Editor::State::Grab)) Editor::SetState(Editor::State::Grab); + if (DrawButton("Scale", SDLK_s, Editor::state == Editor::State::Scale)) Editor::SetState(Editor::State::Scale); + + } + DrawSpace(20); + DrawTitle("Texture"); + char* shortPath = strrchr(Editor::textureFilename, '/')+1; + DrawStaticTextBox(shortPath, 145); + if (DrawButton("...", SDLK_UNKNOWN, false, 25)) { + char oldPath[400]; + strcpy(oldPath, Editor::textureFilename); + const char* path = nullptr;//GUIDialogs::ChooseFile(); + if (path != nullptr) { + strcpy(Editor::textureFilename, path); + //free(path); + uint8_t* data = Texture::Load(Editor::textureFilename); + if (data == nullptr) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "File is not a correct PNG!", nullptr); + strcpy(Editor::textureFilename, oldPath); + } else { + memcpy(Editor::bitmap, data, 32*32*4); + free(data); + Texture::Update(Editor::texture, 32, 32, Editor::bitmap); + } + } + } + + NextSplit(); + // 3D VIEWS + if (zoomed) { + Draw3DPanel(selectedViewport); + DrawViewport(selectedViewport); + } else { + StartSplitH(2.0f*viewSize.z/3.0f); + Draw3DPanel(0); + DrawViewport(0); + NextSplit(); + StartSplitV(viewSize.w/2.0f); + Draw3DPanel(1); + DrawViewport(1); + NextSplit(); + Draw3DPanel(2); + DrawViewport(2); + } + NextSplit(); + // STATUS BAR + DrawPanel(); + + if (comboBoxVisible) { + int numItems = model.numCubes; + DrawMenu(numItems, subMenuPos.x); + int sel = model.GetSelectedCube(); + for (int i = 0; i < model.numCubes; i++) { + if (i != sel and DrawMenuItem(model.cubes[i].name, model.cubes[sel].parent == i)) model.cubes[sel].parent = i; + } + if (DrawMenuItem("", model.cubes[sel].parent == -1)) model.cubes[sel].parent = -1; + } + + if (menuVisible) { + DrawMenu(6, 120); + DrawMenuSubItem("Render", 0); + DrawMenuSubItem("View", 1); + DrawMenuSubItem("Projection", 2); + if (DrawMenuItem("Light", viewports[selectedViewport].light)) viewports[selectedViewport].light = not viewports[selectedViewport].light; + if (DrawMenuItem("Grid", viewports[selectedViewport].grid)) viewports[selectedViewport].grid = not viewports[selectedViewport].grid; + if (DrawMenuItem("Zoomed", zoomed)) zoomed = not zoomed; + + if (subMenu == 0) { + DrawSubMenu(4, 120); + if (DrawSubMenuItem("Wire", viewports[selectedViewport].renderMode == Viewport::RenderMode::Wire)) viewports[selectedViewport].renderMode = Viewport::RenderMode::Wire; + if (DrawSubMenuItem("Solid", viewports[selectedViewport].renderMode == Viewport::RenderMode::Solid)) viewports[selectedViewport].renderMode = Viewport::RenderMode::Solid; + if (DrawSubMenuItem("Textured", viewports[selectedViewport].renderMode == Viewport::RenderMode::Textured)) viewports[selectedViewport].renderMode = Viewport::RenderMode::Textured; + if (DrawSubMenuItem("UV", viewports[selectedViewport].renderMode == Viewport::RenderMode::UV)) viewports[selectedViewport].renderMode = Viewport::RenderMode::UV; + } else if (subMenu == 1) { + DrawSubMenu(6, 120); + if (DrawSubMenuItem("Front", viewports[selectedViewport].rotation.x == 0 and viewports[selectedViewport].rotation.y == 0)) { + viewports[selectedViewport].rotation.x = 0; + viewports[selectedViewport].rotation.y = 0; + } + DrawSubMenuItem("Left", false); + DrawSubMenuItem("Top", false); + DrawSubMenuItem("Back", false); + DrawSubMenuItem("Right", false); + DrawSubMenuItem("Bottom", false); + } else if (subMenu == 2) { + DrawSubMenu(2, 120); + DrawSubMenuItem("Orthogonal", true); + DrawSubMenuItem("Perspective", false); + } + } + + if (Editor::state != Editor::State::None) { + if (GUIKeyboard::key == SDLK_RETURN) { + GUIKeyboard::key = SDLK_UNKNOWN; + Editor::SetState(Editor::State::None); + } + if (GUIMouse::buttons[GUIMouse::Button::Left] == GUIMouse::ButtonState::Down) { + Editor::SetState(Editor::State::None); + } + } + + } +} diff --git a/GUI.h b/GUI.h new file mode 100644 index 0000000..01abad6 --- /dev/null +++ b/GUI.h @@ -0,0 +1,6 @@ +#pragma once + +namespace GUI +{ + void Draw(); +} diff --git a/GUIDialogs.h b/GUIDialogs.h new file mode 100644 index 0000000..24fce37 --- /dev/null +++ b/GUIDialogs.h @@ -0,0 +1,6 @@ +#pragma once + +namespace GUIDialogs +{ + char* ChooseFile(); +} diff --git a/GUIDialogs.mm b/GUIDialogs.mm new file mode 100644 index 0000000..34dea2e --- /dev/null +++ b/GUIDialogs.mm @@ -0,0 +1,29 @@ +#include "GUIDialogs.h" + +#import + +namespace GUIDialogs +{ + char* ChooseFile() + { + NSWindow *keyWindow = [NSApp keyWindow]; + char* path = nullptr; + NSOpenPanel *panel = [NSOpenPanel openPanel]; + [panel setCanChooseFiles:YES]; + [panel setCanChooseDirectories:NO]; + [panel setAllowsMultipleSelection:NO]; + + NSInteger clicked = [panel runModal]; + + if (clicked == NSFileHandlingPanelOKButton) { + for (NSURL *url in [panel URLs]) { + path = strdup([url fileSystemRepresentation]); + //const char* urlPath = [url fileSystemRepresentation]; + //path = (char*)malloc(strlen(urlPath)); + //strcpy(path, urlPath); + } + } + [keyWindow makeKeyWindow]; + return path; + } +} diff --git a/GUIDraw.cpp b/GUIDraw.cpp new file mode 100644 index 0000000..7639101 --- /dev/null +++ b/GUIDraw.cpp @@ -0,0 +1,75 @@ +#include "GUIDraw.h" + +#include +#include "renderer.h" + +namespace GUIDraw +{ + void DrawLine(const Vector2& a, const Vector2& b, const Color& color) + { + Renderer::SetState(RENDER_BLEND); + glColor4ub(color.r, color.g, color.b, color.a); + glBegin(GL_LINES); + glVertex2i(a.x, a.y); + glVertex2i(b.x, b.y); + glEnd(); + } + + void DrawRect(const Vector4& rect, const Color& color) + { + Renderer::SetState(RENDER_BLEND); + glColor4ub(color.r, color.g, color.b, color.a); + glBegin(GL_LINE_LOOP); + glVertex2i(rect.x, rect.y); + glVertex2i(rect.x, rect.y+rect.w); + glVertex2i(rect.x+rect.z, rect.y+rect.w); + glVertex2i(rect.x+rect.z, rect.y); + glEnd(); + } + + void DrawRect(const Vector4& rect, const Color* colors) + { + Renderer::SetState(RENDER_BLEND); + glBegin(GL_LINES); + glColor4ub(colors[0].r, colors[0].g, colors[0].b, colors[0].a); + glVertex2i(rect.x, rect.y); + glVertex2i(rect.x, rect.y+rect.w); + glColor4ub(colors[1].r, colors[1].g, colors[1].b, colors[1].a); + glVertex2i(rect.x, rect.y+rect.w); + glVertex2i(rect.x+rect.z, rect.y+rect.w); + glColor4ub(colors[2].r, colors[2].g, colors[2].b, colors[2].a); + glVertex2i(rect.x+rect.z, rect.y+rect.w); + glVertex2i(rect.x+rect.z, rect.y); + glColor4ub(colors[3].r, colors[3].g, colors[3].b, colors[3].a); + glVertex2i(rect.x+rect.z, rect.y); + glVertex2i(rect.x, rect.y); + glEnd(); + } + + void FillRect(const Vector4& rect, const Color& color) + { + Renderer::SetState(RENDER_BLEND | RENDER_FILL); + glColor4ub(color.r, color.g, color.b, color.a); + glBegin(GL_QUADS); + glVertex2i(rect.x, rect.y); + glVertex2i(rect.x, rect.y+rect.w); + glVertex2i(rect.x+rect.z, rect.y+rect.w); + glVertex2i(rect.x+rect.z, rect.y); + glEnd(); + } + + void FillRect(const Vector4& rect, const Color* colors) + { + Renderer::SetState(RENDER_BLEND | RENDER_FILL); + glBegin(GL_QUADS); + glColor4ub(colors[0].r, colors[0].g, colors[0].b, colors[0].a); + glVertex2i(rect.x, rect.y); + glColor4ub(colors[1].r, colors[1].g, colors[1].b, colors[1].a); + glVertex2i(rect.x, rect.y+rect.w); + glColor4ub(colors[2].r, colors[2].g, colors[2].b, colors[2].a); + glVertex2i(rect.x+rect.z, rect.y+rect.w); + glColor4ub(colors[3].r, colors[3].g, colors[3].b, colors[3].a); + glVertex2i(rect.x+rect.z, rect.y); + glEnd(); + } +} diff --git a/GUIDraw.h b/GUIDraw.h new file mode 100644 index 0000000..edd3159 --- /dev/null +++ b/GUIDraw.h @@ -0,0 +1,14 @@ +#pragma once + +#include "common.h" + +namespace GUIDraw +{ + void DrawLine(const Vector2& a, const Vector2& b, const Color& color); + void DrawRect(const Vector4& rect, const Color& color); + void DrawRect(const Vector4& rect, const Color* colors); + void FillRect(const Vector4& rect, const Color& color); + void FillRect(const Vector4& rect, const Color* colors); + + //void DrawNumTextBox(const Vector4& rect, float& num); +} diff --git a/GUIKeyboard.cpp b/GUIKeyboard.cpp new file mode 100644 index 0000000..fdb4b97 --- /dev/null +++ b/GUIKeyboard.cpp @@ -0,0 +1,27 @@ +#include "GUIKeyboard.h" + +#include "application.h" + +namespace GUIKeyboard +{ + SDL_Keycode key {SDLK_UNKNOWN}; + bool shift {false}; + bool alt {false}; + bool cmd {false}; + + void ReceiveKeyboardEvent(const SDL_Keycode key) + { + GUIKeyboard::key = key; + SDL_Keymod mod = SDL_GetModState(); + shift = ((mod & KMOD_SHIFT) != KMOD_NONE); + alt = ((mod & KMOD_ALT ) != KMOD_NONE); + cmd = ((mod & KMOD_GUI ) != KMOD_NONE); + Application::NeedsUpdate(); + } + + void Reset() + { + key = SDLK_UNKNOWN; + //shift = alt = cmd = false; + } +} diff --git a/GUIKeyboard.h b/GUIKeyboard.h new file mode 100644 index 0000000..a655ff4 --- /dev/null +++ b/GUIKeyboard.h @@ -0,0 +1,15 @@ +#pragma once + +#include "common.h" +#include + +namespace GUIKeyboard +{ + extern SDL_Keycode key; + extern bool shift; + extern bool alt; + extern bool cmd; + + void ReceiveKeyboardEvent(const SDL_Keycode key); + void Reset(); +} diff --git a/GUIMouse.cpp b/GUIMouse.cpp new file mode 100644 index 0000000..61d7223 --- /dev/null +++ b/GUIMouse.cpp @@ -0,0 +1,72 @@ +#include "GUIMouse.h" + +#include "application.h" + +namespace GUIMouse +{ + Vector2 position {0, 0}; + Vector2 delta {0, 0}; + Vector2 slowDelta {0, 0}; + Vector2 wheel {0, 0}; + ButtonState buttons[3] {ButtonState::Released, ButtonState::Released, ButtonState::Released}; + Gesture gesture {}; + + Vector2 slowAccum {0, 0}; + + void ReceiveMouseUpEvent(const Button button) + { + buttons[button] = ButtonState::Up; + Application::NeedsUpdate(); + } + + void ReceiveMouseDownEvent(const Button button) + { + buttons[button] = ButtonState::Down; + Application::NeedsUpdate(); + } + + void ReceiveMouseMoveEvent(const float x, const float y) + { + delta = {x-position.x, y-position.y}; + slowAccum += delta * 0.2f; + if (slowAccum.x >= 1 or slowAccum.x <= -1) { + slowDelta.x = int(slowAccum.x); + slowAccum.x = slowAccum.x - int(slowAccum.x); + } + if (slowAccum.y >= 1 or slowAccum.y <= -1) { + slowDelta.y = int(slowAccum.y); + slowAccum.y = slowAccum.y - int(slowAccum.y); + } + position = Vector2(x, y); + Application::NeedsUpdate(); + } + + void ReceiveMouseWheelEvent(const float x, const float y) + { + wheel = Vector2(x, y); + Application::NeedsUpdate(); + } + + void ReceiveMultigestureEvent(const float theta, const float dist, const int numFingers) + { + gesture.theta = theta; + gesture.dist = dist; + gesture.numFingers = numFingers; + Application::NeedsUpdate(); + } + + void Reset() + { + for (int i = 0; i < 3; i++) { + if (buttons[i] == ButtonState::Down) { + buttons[i] = ButtonState::Pressed; + } else if (buttons[i] == ButtonState::Up) { + buttons[i] = ButtonState::Released; + } + } + wheel = {0,0}; + delta = {0,0}; + slowDelta = {0,0}; + gesture.theta = gesture.dist = gesture.numFingers = 0; + } +} diff --git a/GUIMouse.h b/GUIMouse.h new file mode 100644 index 0000000..a2f3d54 --- /dev/null +++ b/GUIMouse.h @@ -0,0 +1,30 @@ +#pragma once + +#include "common.h" + +namespace GUIMouse +{ + enum class ButtonState { Released, Down, Pressed, Up }; + enum Button : char { Left = 0, Right = 1, Middle = 2 }; + + struct Gesture + { + float theta {0}; + float dist {0}; + int numFingers {0}; + }; + + extern Vector2 position; + extern Vector2 delta; + extern Vector2 slowDelta; + extern Vector2 wheel; + extern ButtonState buttons[3]; + extern Gesture gesture; + + void ReceiveMouseUpEvent(const Button button); + void ReceiveMouseDownEvent(const Button button); + void ReceiveMouseMoveEvent(const float x, const float y); + void ReceiveMouseWheelEvent(const float x, const float y); + void ReceiveMultigestureEvent(const float theta, const float dist, const int numFingers); + void Reset(); +} diff --git a/GUIViewport.cpp b/GUIViewport.cpp new file mode 100644 index 0000000..ee7908d --- /dev/null +++ b/GUIViewport.cpp @@ -0,0 +1,3 @@ +#include "GUIViewport.h" + +Viewport viewports[3]; diff --git a/GUIViewport.h b/GUIViewport.h new file mode 100644 index 0000000..04aaa67 --- /dev/null +++ b/GUIViewport.h @@ -0,0 +1,18 @@ +#pragma once + +#include "common.h" + +struct Viewport +{ + enum RenderMode { Wire, Solid, Textured, UV }; + + Vector4 bounds {}; + RenderMode renderMode {Solid}; + Vector2 offset {0, 0}; + Vector2 rotation {10, 20}; + float zoom {10.0f}; + bool light {true}; + bool grid {true}; +}; + +extern Viewport viewports[3]; diff --git a/Matrices.cpp b/Matrices.cpp new file mode 100755 index 0000000..929c392 --- /dev/null +++ b/Matrices.cpp @@ -0,0 +1,777 @@ +/////////////////////////////////////////////////////////////////////////////// +// Matrice.cpp +// =========== +// NxN Matrix Math classes +// +// The elements of the matrix are stored as column major order. +// | 0 2 | | 0 3 6 | | 0 4 8 12 | +// | 1 3 | | 1 4 7 | | 1 5 9 13 | +// | 2 5 8 | | 2 6 10 14 | +// | 3 7 11 15 | +// +// AUTHOR: Song Ho Ahn (song.ahn@gmail.com) +// CREATED: 2005-06-24 +// UPDATED: 2014-09-21 +// +// Copyright (C) 2005 Song Ho Ahn +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "Matrices.h" + +const float DEG2RAD = 3.141593f / 180; +const float EPSILON = 0.00001f; + +bool closeEnough(const float& a, const float& b, const float& epsilon = std::numeric_limits::epsilon()) { + return (epsilon > std::abs(a - b)); +} + + +/////////////////////////////////////////////////////////////////////////////// +// transpose 2x2 matrix +/////////////////////////////////////////////////////////////////////////////// +Matrix2& Matrix2::transpose() +{ + std::swap(m[1], m[2]); + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// return the determinant of 2x2 matrix +/////////////////////////////////////////////////////////////////////////////// +float Matrix2::getDeterminant() +{ + return m[0] * m[3] - m[1] * m[2]; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// inverse of 2x2 matrix +// If cannot find inverse, set identity matrix +/////////////////////////////////////////////////////////////////////////////// +Matrix2& Matrix2::invert() +{ + float determinant = getDeterminant(); + if(fabs(determinant) <= EPSILON) + { + return identity(); + } + + float tmp = m[0]; // copy the first element + float invDeterminant = 1.0f / determinant; + m[0] = invDeterminant * m[3]; + m[1] = -invDeterminant * m[1]; + m[2] = -invDeterminant * m[2]; + m[3] = invDeterminant * tmp; + + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// transpose 3x3 matrix +/////////////////////////////////////////////////////////////////////////////// +Matrix3& Matrix3::transpose() +{ + std::swap(m[1], m[3]); + std::swap(m[2], m[6]); + std::swap(m[5], m[7]); + + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// return determinant of 3x3 matrix +/////////////////////////////////////////////////////////////////////////////// +float Matrix3::getDeterminant() +{ + return m[0] * (m[4] * m[8] - m[5] * m[7]) - + m[1] * (m[3] * m[8] - m[5] * m[6]) + + m[2] * (m[3] * m[7] - m[4] * m[6]); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// inverse 3x3 matrix +// If cannot find inverse, set identity matrix +/////////////////////////////////////////////////////////////////////////////// +Matrix3& Matrix3::invert() +{ + float determinant, invDeterminant; + float tmp[9]; + + tmp[0] = m[4] * m[8] - m[5] * m[7]; + tmp[1] = m[2] * m[7] - m[1] * m[8]; + tmp[2] = m[1] * m[5] - m[2] * m[4]; + tmp[3] = m[5] * m[6] - m[3] * m[8]; + tmp[4] = m[0] * m[8] - m[2] * m[6]; + tmp[5] = m[2] * m[3] - m[0] * m[5]; + tmp[6] = m[3] * m[7] - m[4] * m[6]; + tmp[7] = m[1] * m[6] - m[0] * m[7]; + tmp[8] = m[0] * m[4] - m[1] * m[3]; + + // check determinant if it is 0 + determinant = m[0] * tmp[0] + m[1] * tmp[3] + m[2] * tmp[6]; + if(fabs(determinant) <= EPSILON) + { + return identity(); // cannot inverse, make it idenety matrix + } + + // divide by the determinant + invDeterminant = 1.0f / determinant; + m[0] = invDeterminant * tmp[0]; + m[1] = invDeterminant * tmp[1]; + m[2] = invDeterminant * tmp[2]; + m[3] = invDeterminant * tmp[3]; + m[4] = invDeterminant * tmp[4]; + m[5] = invDeterminant * tmp[5]; + m[6] = invDeterminant * tmp[6]; + m[7] = invDeterminant * tmp[7]; + m[8] = invDeterminant * tmp[8]; + + return *this; +} + +#define RAD2DEG 57.2957795f + +const Vector3 Matrix4::eulerAngles() { + + //check for gimbal lock + if (closeEnough(m[0+2*4], -1.0f)) { + float x = 0; //gimbal lock, value of x doesn't matter + float y = M_PI / 2; + float z = x + atan2(m[1+0*4], m[2+0*4]); + return { -x*RAD2DEG, -y*RAD2DEG, -z*RAD2DEG }; + } else if (closeEnough(m[0+2*4], 1.0f)) { + float x = 0; + float y = -M_PI / 2; + float z = -x + atan2(-m[1+0*4], -m[2+0*4]); + return { -x*RAD2DEG, -y*RAD2DEG, -z*RAD2DEG }; + } else { //two solutions exist + float x1 = -asin(m[0+2*4]); + float x2 = M_PI - x1; + + float y1 = atan2(m[1+2*4] / cos(x1), m[2+2*4] / cos(x1)); + float y2 = atan2(m[1+2*4] / cos(x2), m[2+2*4] / cos(x2)); + + float z1 = atan2(m[0+1*4] / cos(x1), m[0+0*4] / cos(x1)); + float z2 = atan2(m[0+1*4] / cos(x2), m[0+0*4] / cos(x2)); + + //choose one solution to return + //for example the "shortest" rotation + if ((std::abs(x1) + std::abs(y1) + std::abs(z1)) <= (std::abs(x2) + std::abs(y2) + std::abs(z2))) { + return { -x1*RAD2DEG, -y1*RAD2DEG, -z1*RAD2DEG }; + } else { + return { -x2*RAD2DEG, -y2*RAD2DEG, -z2*RAD2DEG }; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// transpose 4x4 matrix +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::transpose() +{ + std::swap(m[1], m[4]); + std::swap(m[2], m[8]); + std::swap(m[3], m[12]); + std::swap(m[6], m[9]); + std::swap(m[7], m[13]); + std::swap(m[11], m[14]); + + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// inverse 4x4 matrix +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::invert() +{ + // If the 4th row is [0,0,0,1] then it is affine matrix and + // it has no projective transformation. + if(m[3] == 0 && m[7] == 0 && m[11] == 0 && m[15] == 1) + this->invertAffine(); + else + { + this->invertGeneral(); + /*@@ invertProjective() is not optimized (slower than generic one) + if(fabs(m[0]*m[5] - m[1]*m[4]) > EPSILON) + this->invertProjective(); // inverse using matrix partition + else + this->invertGeneral(); // generalized inverse + */ + } + + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// compute the inverse of 4x4 Euclidean transformation matrix +// +// Euclidean transformation is translation, rotation, and reflection. +// With Euclidean transform, only the position and orientation of the object +// will be changed. Euclidean transform does not change the shape of an object +// (no scaling). Length and angle are reserved. +// +// Use inverseAffine() if the matrix has scale and shear transformation. +// +// M = [ R | T ] +// [ --+-- ] (R denotes 3x3 rotation/reflection matrix) +// [ 0 | 1 ] (T denotes 1x3 translation matrix) +// +// y = M*x -> y = R*x + T -> x = R^-1*(y - T) -> x = R^T*y - R^T*T +// (R is orthogonal, R^-1 = R^T) +// +// [ R | T ]-1 [ R^T | -R^T * T ] (R denotes 3x3 rotation matrix) +// [ --+-- ] = [ ----+--------- ] (T denotes 1x3 translation) +// [ 0 | 1 ] [ 0 | 1 ] (R^T denotes R-transpose) +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::invertEuclidean() +{ + // transpose 3x3 rotation matrix part + // | R^T | 0 | + // | ----+-- | + // | 0 | 1 | + float tmp; + tmp = m[1]; m[1] = m[4]; m[4] = tmp; + tmp = m[2]; m[2] = m[8]; m[8] = tmp; + tmp = m[6]; m[6] = m[9]; m[9] = tmp; + + // compute translation part -R^T * T + // | 0 | -R^T x | + // | --+------- | + // | 0 | 0 | + float x = m[12]; + float y = m[13]; + float z = m[14]; + m[12] = -(m[0] * x + m[4] * y + m[8] * z); + m[13] = -(m[1] * x + m[5] * y + m[9] * z); + m[14] = -(m[2] * x + m[6] * y + m[10]* z); + + // last row should be unchanged (0,0,0,1) + + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// compute the inverse of a 4x4 affine transformation matrix +// +// Affine transformations are generalizations of Euclidean transformations. +// Affine transformation includes translation, rotation, reflection, scaling, +// and shearing. Length and angle are NOT preserved. +// M = [ R | T ] +// [ --+-- ] (R denotes 3x3 rotation/scale/shear matrix) +// [ 0 | 1 ] (T denotes 1x3 translation matrix) +// +// y = M*x -> y = R*x + T -> x = R^-1*(y - T) -> x = R^-1*y - R^-1*T +// +// [ R | T ]-1 [ R^-1 | -R^-1 * T ] +// [ --+-- ] = [ -----+---------- ] +// [ 0 | 1 ] [ 0 + 1 ] +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::invertAffine() +{ + // R^-1 + Matrix3 r(m[0],m[1],m[2], m[4],m[5],m[6], m[8],m[9],m[10]); + r.invert(); + m[0] = r[0]; m[1] = r[1]; m[2] = r[2]; + m[4] = r[3]; m[5] = r[4]; m[6] = r[5]; + m[8] = r[6]; m[9] = r[7]; m[10]= r[8]; + + // -R^-1 * T + float x = m[12]; + float y = m[13]; + float z = m[14]; + m[12] = -(r[0] * x + r[3] * y + r[6] * z); + m[13] = -(r[1] * x + r[4] * y + r[7] * z); + m[14] = -(r[2] * x + r[5] * y + r[8] * z); + + // last row should be unchanged (0,0,0,1) + //m[3] = m[7] = m[11] = 0.0f; + //m[15] = 1.0f; + + return * this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// inverse matrix using matrix partitioning (blockwise inverse) +// It devides a 4x4 matrix into 4 of 2x2 matrices. It works in case of where +// det(A) != 0. If not, use the generic inverse method +// inverse formula. +// M = [ A | B ] A, B, C, D are 2x2 matrix blocks +// [ --+-- ] det(M) = |A| * |D - ((C * A^-1) * B)| +// [ C | D ] +// +// M^-1 = [ A' | B' ] A' = A^-1 - (A^-1 * B) * C' +// [ ---+--- ] B' = (A^-1 * B) * -D' +// [ C' | D' ] C' = -D' * (C * A^-1) +// D' = (D - ((C * A^-1) * B))^-1 +// +// NOTE: I wrap with () if it it used more than once. +// The matrix is invertable even if det(A)=0, so must check det(A) before +// calling this function, and use invertGeneric() instead. +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::invertProjective() +{ + // partition + Matrix2 a(m[0], m[1], m[4], m[5]); + Matrix2 b(m[8], m[9], m[12], m[13]); + Matrix2 c(m[2], m[3], m[6], m[7]); + Matrix2 d(m[10], m[11], m[14], m[15]); + + // pre-compute repeated parts + a.invert(); // A^-1 + Matrix2 ab = a * b; // A^-1 * B + Matrix2 ca = c * a; // C * A^-1 + Matrix2 cab = ca * b; // C * A^-1 * B + Matrix2 dcab = d - cab; // D - C * A^-1 * B + + // check determinant if |D - C * A^-1 * B| = 0 + //NOTE: this function assumes det(A) is already checked. if |A|=0 then, + // cannot use this function. + float determinant = dcab[0] * dcab[3] - dcab[1] * dcab[2]; + if(fabs(determinant) <= EPSILON) + { + return identity(); + } + + // compute D' and -D' + Matrix2 d1 = dcab; // (D - C * A^-1 * B) + d1.invert(); // (D - C * A^-1 * B)^-1 + Matrix2 d2 = -d1; // -(D - C * A^-1 * B)^-1 + + // compute C' + Matrix2 c1 = d2 * ca; // -D' * (C * A^-1) + + // compute B' + Matrix2 b1 = ab * d2; // (A^-1 * B) * -D' + + // compute A' + Matrix2 a1 = a - (ab * c1); // A^-1 - (A^-1 * B) * C' + + // assemble inverse matrix + m[0] = a1[0]; m[4] = a1[2]; /*|*/ m[8] = b1[0]; m[12]= b1[2]; + m[1] = a1[1]; m[5] = a1[3]; /*|*/ m[9] = b1[1]; m[13]= b1[3]; + /*-----------------------------+-----------------------------*/ + m[2] = c1[0]; m[6] = c1[2]; /*|*/ m[10]= d1[0]; m[14]= d1[2]; + m[3] = c1[1]; m[7] = c1[3]; /*|*/ m[11]= d1[1]; m[15]= d1[3]; + + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// compute the inverse of a general 4x4 matrix using Cramer's Rule +// If cannot find inverse, return indentity matrix +// M^-1 = adj(M) / det(M) +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::invertGeneral() +{ + // get cofactors of minor matrices + float cofactor0 = getCofactor(m[5],m[6],m[7], m[9],m[10],m[11], m[13],m[14],m[15]); + float cofactor1 = getCofactor(m[4],m[6],m[7], m[8],m[10],m[11], m[12],m[14],m[15]); + float cofactor2 = getCofactor(m[4],m[5],m[7], m[8],m[9], m[11], m[12],m[13],m[15]); + float cofactor3 = getCofactor(m[4],m[5],m[6], m[8],m[9], m[10], m[12],m[13],m[14]); + + // get determinant + float determinant = m[0] * cofactor0 - m[1] * cofactor1 + m[2] * cofactor2 - m[3] * cofactor3; + if(fabs(determinant) <= EPSILON) + { + return identity(); + } + + // get rest of cofactors for adj(M) + float cofactor4 = getCofactor(m[1],m[2],m[3], m[9],m[10],m[11], m[13],m[14],m[15]); + float cofactor5 = getCofactor(m[0],m[2],m[3], m[8],m[10],m[11], m[12],m[14],m[15]); + float cofactor6 = getCofactor(m[0],m[1],m[3], m[8],m[9], m[11], m[12],m[13],m[15]); + float cofactor7 = getCofactor(m[0],m[1],m[2], m[8],m[9], m[10], m[12],m[13],m[14]); + + float cofactor8 = getCofactor(m[1],m[2],m[3], m[5],m[6], m[7], m[13],m[14],m[15]); + float cofactor9 = getCofactor(m[0],m[2],m[3], m[4],m[6], m[7], m[12],m[14],m[15]); + float cofactor10= getCofactor(m[0],m[1],m[3], m[4],m[5], m[7], m[12],m[13],m[15]); + float cofactor11= getCofactor(m[0],m[1],m[2], m[4],m[5], m[6], m[12],m[13],m[14]); + + float cofactor12= getCofactor(m[1],m[2],m[3], m[5],m[6], m[7], m[9], m[10],m[11]); + float cofactor13= getCofactor(m[0],m[2],m[3], m[4],m[6], m[7], m[8], m[10],m[11]); + float cofactor14= getCofactor(m[0],m[1],m[3], m[4],m[5], m[7], m[8], m[9], m[11]); + float cofactor15= getCofactor(m[0],m[1],m[2], m[4],m[5], m[6], m[8], m[9], m[10]); + + // build inverse matrix = adj(M) / det(M) + // adjugate of M is the transpose of the cofactor matrix of M + float invDeterminant = 1.0f / determinant; + m[0] = invDeterminant * cofactor0; + m[1] = -invDeterminant * cofactor4; + m[2] = invDeterminant * cofactor8; + m[3] = -invDeterminant * cofactor12; + + m[4] = -invDeterminant * cofactor1; + m[5] = invDeterminant * cofactor5; + m[6] = -invDeterminant * cofactor9; + m[7] = invDeterminant * cofactor13; + + m[8] = invDeterminant * cofactor2; + m[9] = -invDeterminant * cofactor6; + m[10]= invDeterminant * cofactor10; + m[11]= -invDeterminant * cofactor14; + + m[12]= -invDeterminant * cofactor3; + m[13]= invDeterminant * cofactor7; + m[14]= -invDeterminant * cofactor11; + m[15]= invDeterminant * cofactor15; + + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// return determinant of 4x4 matrix +/////////////////////////////////////////////////////////////////////////////// +float Matrix4::getDeterminant() +{ + return m[0] * getCofactor(m[5],m[6],m[7], m[9],m[10],m[11], m[13],m[14],m[15]) - + m[1] * getCofactor(m[4],m[6],m[7], m[8],m[10],m[11], m[12],m[14],m[15]) + + m[2] * getCofactor(m[4],m[5],m[7], m[8],m[9], m[11], m[12],m[13],m[15]) - + m[3] * getCofactor(m[4],m[5],m[6], m[8],m[9], m[10], m[12],m[13],m[14]); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// compute cofactor of 3x3 minor matrix without sign +// input params are 9 elements of the minor matrix +// NOTE: The caller must know its sign. +/////////////////////////////////////////////////////////////////////////////// +float Matrix4::getCofactor(float m0, float m1, float m2, + float m3, float m4, float m5, + float m6, float m7, float m8) +{ + return m0 * (m4 * m8 - m5 * m7) - + m1 * (m3 * m8 - m5 * m6) + + m2 * (m3 * m7 - m4 * m6); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// translate this matrix by (x, y, z) +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::translate(const Vector3& v) +{ + return translate(v.x, v.y, v.z); +} + +Matrix4& Matrix4::translate(float x, float y, float z) +{ + m[0] += m[3] * x; m[4] += m[7] * x; m[8] += m[11]* x; m[12]+= m[15]* x; + m[1] += m[3] * y; m[5] += m[7] * y; m[9] += m[11]* y; m[13]+= m[15]* y; + m[2] += m[3] * z; m[6] += m[7] * z; m[10]+= m[11]* z; m[14]+= m[15]* z; + + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// uniform scale +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::scale(float s) +{ + return scale(s, s, s); +} + +Matrix4& Matrix4::scale(float x, float y, float z) +{ + m[0] *= x; m[4] *= x; m[8] *= x; m[12] *= x; + m[1] *= y; m[5] *= y; m[9] *= y; m[13] *= y; + m[2] *= z; m[6] *= z; m[10]*= z; m[14] *= z; + return *this; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// build a rotation matrix with given angle(degree) and rotation axis, then +// multiply it with this object +/////////////////////////////////////////////////////////////////////////////// +Matrix4& Matrix4::rotate(float angle, const Vector3& axis) +{ + return rotate(angle, axis.x, axis.y, axis.z); +} + +Matrix4& Matrix4::rotate(float angle, float x, float y, float z) +{ + float c = cosf(angle * DEG2RAD); // cosine + float s = sinf(angle * DEG2RAD); // sine + float c1 = 1.0f - c; // 1 - c + float m0 = m[0], m4 = m[4], m8 = m[8], m12= m[12], + m1 = m[1], m5 = m[5], m9 = m[9], m13= m[13], + m2 = m[2], m6 = m[6], m10= m[10], m14= m[14]; + + // build rotation matrix + float r0 = x * x * c1 + c; + float r1 = x * y * c1 + z * s; + float r2 = x * z * c1 - y * s; + float r4 = x * y * c1 - z * s; + float r5 = y * y * c1 + c; + float r6 = y * z * c1 + x * s; + float r8 = x * z * c1 + y * s; + float r9 = y * z * c1 - x * s; + float r10= z * z * c1 + c; + + // multiply rotation matrix + m[0] = r0 * m0 + r4 * m1 + r8 * m2; + m[1] = r1 * m0 + r5 * m1 + r9 * m2; + m[2] = r2 * m0 + r6 * m1 + r10* m2; + m[4] = r0 * m4 + r4 * m5 + r8 * m6; + m[5] = r1 * m4 + r5 * m5 + r9 * m6; + m[6] = r2 * m4 + r6 * m5 + r10* m6; + m[8] = r0 * m8 + r4 * m9 + r8 * m10; + m[9] = r1 * m8 + r5 * m9 + r9 * m10; + m[10]= r2 * m8 + r6 * m9 + r10* m10; + m[12]= r0 * m12+ r4 * m13+ r8 * m14; + m[13]= r1 * m12+ r5 * m13+ r9 * m14; + m[14]= r2 * m12+ r6 * m13+ r10* m14; + + return *this; +} + +Matrix4& Matrix4::rotateX(float angle) +{ + float c = cosf(angle * DEG2RAD); + float s = sinf(angle * DEG2RAD); + float m1 = m[1], m2 = m[2], + m5 = m[5], m6 = m[6], + m9 = m[9], m10= m[10], + m13= m[13], m14= m[14]; + + m[1] = m1 * c + m2 *-s; + m[2] = m1 * s + m2 * c; + m[5] = m5 * c + m6 *-s; + m[6] = m5 * s + m6 * c; + m[9] = m9 * c + m10*-s; + m[10]= m9 * s + m10* c; + m[13]= m13* c + m14*-s; + m[14]= m13* s + m14* c; + + return *this; +} + +Matrix4& Matrix4::rotateY(float angle) +{ + float c = cosf(angle * DEG2RAD); + float s = sinf(angle * DEG2RAD); + float m0 = m[0], m2 = m[2], + m4 = m[4], m6 = m[6], + m8 = m[8], m10= m[10], + m12= m[12], m14= m[14]; + + m[0] = m0 * c + m2 * s; + m[2] = m0 *-s + m2 * c; + m[4] = m4 * c + m6 * s; + m[6] = m4 *-s + m6 * c; + m[8] = m8 * c + m10* s; + m[10]= m8 *-s + m10* c; + m[12]= m12* c + m14* s; + m[14]= m12*-s + m14* c; + + return *this; +} + +Matrix4& Matrix4::rotateZ(float angle) +{ + float c = cosf(angle * DEG2RAD); + float s = sinf(angle * DEG2RAD); + float m0 = m[0], m1 = m[1], + m4 = m[4], m5 = m[5], + m8 = m[8], m9 = m[9], + m12= m[12], m13= m[13]; + + m[0] = m0 * c + m1 *-s; + m[1] = m0 * s + m1 * c; + m[4] = m4 * c + m5 *-s; + m[5] = m4 * s + m5 * c; + m[8] = m8 * c + m9 *-s; + m[9] = m8 * s + m9 * c; + m[12]= m12* c + m13*-s; + m[13]= m12* s + m13* c; + + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// +// glFrustum() +/////////////////////////////////////////////////////////////////////////////// +Matrix4 setFrustum(float l, float r, float b, float t, float n, float f) +{ + Matrix4 mat; + mat[0] = 2 * n / (r - l); + mat[5] = 2 * n / (t - b); + mat[8] = (r + l) / (r - l); + mat[9] = (t + b) / (t - b); + mat[10] = -(f + n) / (f - n); + mat[11] = -1; + mat[14] = -(2 * f * n) / (f - n); + mat[15] = 0; + return mat; +} + +/////////////////////////////////////////////////////////////////////////////// +// gluPerspective() +/////////////////////////////////////////////////////////////////////////////// +Matrix4 setFrustum(float fovY, float aspect, float front, float back) +{ + float tangent = tanf(fovY/2 * DEG2RAD); // tangent of half fovY + float height = front * tangent; // half height of near plane + float width = height * aspect; // half width of near plane + + // params: left, right, bottom, top, near, far + return setFrustum(-width, width, -height, height, front, back); +} + +/////////////////////////////////////////////////////////////////////////////// +// glOrtho() +/////////////////////////////////////////////////////////////////////////////// +Matrix4 setOrthoFrustum(float l, float r, float b, float t, float n, float f) +{ + Matrix4 mat; + mat[0] = 2 / (r - l); + mat[5] = 2 / (t - b); + mat[10] = -2 / (f - n); + mat[12] = -(r + l) / (r - l); + mat[13] = -(t + b) / (t - b); + mat[14] = -(f + n) / (f - n); + return mat; +} + +Matrix4 lookAt(const Vector3& eye, const Vector3& center, const Vector3& up) +{ + /* + Matrix4 mat; + Vector3 forward = (center - eye); + forward.normalize(); + Vector3 side = (up.cross(forward)); + side.normalize(); + + // Recompute up as: up = side x forward + const Vector3 up2 = forward.cross(side); + + mat.identity(); + mat[0] = side.x; + mat[4] = side.y; + mat[8] = side.z; + + mat[1] = up2.x; + mat[5] = up2.y; + mat[9] = up2.z; + + mat[2] = forward.x; + mat[6] = forward.y; + mat[10] = forward.z; + + //mat[3] = -side.dot(eye); + //mat[7] = -up2.dot(eye); + //mat[11] = forward.dot(eye); + //mat[15] = 1.0f; + Matrix4 trans; + trans.identity(); + trans.translate(-eye.x, -eye.y, eye.z); + mat = mat * trans; +*/ + + float m[16]; + float x[3], y[3], z[3]; + float mag; + + // Make rotation matrix + + // Z vector + z[0] = eye.x - center.x; + z[1] = eye.y - center.y; + z[2] = eye.z - center.z; + mag = sqrt(z[0] * z[0] + z[1] * z[1] + z[2] * z[2]); + if (mag) { // mpichler, 19950515 + z[0] /= mag; + z[1] /= mag; + z[2] /= mag; + } + + // Y vector + y[0] = up.x; + y[1] = up.y; + y[2] = up.z; + + // X vector = Y cross Z + x[0] = y[1] * z[2] - y[2] * z[1]; + x[1] = -y[0] * z[2] + y[2] * z[0]; + x[2] = y[0] * z[1] - y[1] * z[0]; + + // Recompute Y = Z cross X + y[0] = z[1] * x[2] - z[2] * x[1]; + y[1] = -z[0] * x[2] + z[2] * x[0]; + y[2] = z[0] * x[1] - z[1] * x[0]; + + // mpichler, 19950515 + // cross product gives area of parallelogram, which is < 1.0 for + //non-perpendicular unit-length vectors; so normalize x, y here + + mag = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]); + if (mag) { + x[0] /= mag; + x[1] /= mag; + x[2] /= mag; + } + + mag = sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]); + if (mag) { + y[0] /= mag; + y[1] /= mag; + y[2] /= mag; + } + +#define M(row,col) m[col*4+row] + M(0, 0) = x[0]; + M(0, 1) = x[1]; + M(0, 2) = x[2]; + M(0, 3) = 0.0; + M(1, 0) = y[0]; + M(1, 1) = y[1]; + M(1, 2) = y[2]; + M(1, 3) = 0.0; + M(2, 0) = z[0]; + M(2, 1) = z[1]; + M(2, 2) = z[2]; + M(2, 3) = 0.0; + M(3, 0) = 0.0; + M(3, 1) = 0.0; + M(3, 2) = 0.0; + M(3, 3) = 1.0; +#undef M + + Matrix4 mat(m); + + // Translate Eye to Origin + Matrix4 trans; + trans.identity(); + trans.translate(-eye.x, -eye.y, -eye.z); + mat = mat * trans; + + return mat; +} diff --git a/Matrices.h b/Matrices.h new file mode 100755 index 0000000..72787de --- /dev/null +++ b/Matrices.h @@ -0,0 +1,914 @@ +/////////////////////////////////////////////////////////////////////////////// +// Matrice.h +// ========= +// NxN Matrix Math classes +// +// The elements of the matrix are stored as column major order. +// | 0 2 | | 0 3 6 | | 0 4 8 12 | +// | 1 3 | | 1 4 7 | | 1 5 9 13 | +// | 2 5 8 | | 2 6 10 14 | +// | 3 7 11 15 | +// +// AUTHOR: Song Ho Ahn (song.ahn@gmail.com) +// CREATED: 2005-06-24 +// UPDATED: 2013-09-30 +// +// Copyright (C) 2005 Song Ho Ahn +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MATH_MATRICES_H +#define MATH_MATRICES_H + +#include +#include +#include "Vectors.h" + +/////////////////////////////////////////////////////////////////////////// +// 2x2 matrix +/////////////////////////////////////////////////////////////////////////// +class Matrix2 +{ +public: + // constructors + Matrix2(); // init with identity + Matrix2(const float src[4]); + Matrix2(float m0, float m1, float m2, float m3); + + void set(const float src[4]); + void set(float m0, float m1, float m2, float m3); + void setRow(int index, const float row[2]); + void setRow(int index, const Vector2& v); + void setColumn(int index, const float col[2]); + void setColumn(int index, const Vector2& v); + + const float* get() const; + float getDeterminant(); + + Matrix2& identity(); + Matrix2& transpose(); // transpose itself and return reference + Matrix2& invert(); + + // operators + Matrix2 operator+(const Matrix2& rhs) const; // add rhs + Matrix2 operator-(const Matrix2& rhs) const; // subtract rhs + Matrix2& operator+=(const Matrix2& rhs); // add rhs and update this object + Matrix2& operator-=(const Matrix2& rhs); // subtract rhs and update this object + Vector2 operator*(const Vector2& rhs) const; // multiplication: v' = M * v + Matrix2 operator*(const Matrix2& rhs) const; // multiplication: M3 = M1 * M2 + Matrix2& operator*=(const Matrix2& rhs); // multiplication: M1' = M1 * M2 + bool operator==(const Matrix2& rhs) const; // exact compare, no epsilon + bool operator!=(const Matrix2& rhs) const; // exact compare, no epsilon + float operator[](int index) const; // subscript operator v[0], v[1] + float& operator[](int index); // subscript operator v[0], v[1] + + friend Matrix2 operator-(const Matrix2& m); // unary operator (-) + friend Matrix2 operator*(float scalar, const Matrix2& m); // pre-multiplication + friend Vector2 operator*(const Vector2& vec, const Matrix2& m); // pre-multiplication + friend std::ostream& operator<<(std::ostream& os, const Matrix2& m); + +protected: + +private: + float m[4]; + +}; + + + +/////////////////////////////////////////////////////////////////////////// +// 3x3 matrix +/////////////////////////////////////////////////////////////////////////// +class Matrix3 +{ +public: + // constructors + Matrix3(); // init with identity + Matrix3(const float src[9]); + Matrix3(float m0, float m1, float m2, // 1st column + float m3, float m4, float m5, // 2nd column + float m6, float m7, float m8); // 3rd column + + void set(const float src[9]); + void set(float m0, float m1, float m2, // 1st column + float m3, float m4, float m5, // 2nd column + float m6, float m7, float m8); // 3rd column + void setRow(int index, const float row[3]); + void setRow(int index, const Vector3& v); + void setColumn(int index, const float col[3]); + void setColumn(int index, const Vector3& v); + + const float* get() const; + float getDeterminant(); + + Matrix3& identity(); + Matrix3& transpose(); // transpose itself and return reference + Matrix3& invert(); + + // operators + Matrix3 operator+(const Matrix3& rhs) const; // add rhs + Matrix3 operator-(const Matrix3& rhs) const; // subtract rhs + Matrix3& operator+=(const Matrix3& rhs); // add rhs and update this object + Matrix3& operator-=(const Matrix3& rhs); // subtract rhs and update this object + Vector3 operator*(const Vector3& rhs) const; // multiplication: v' = M * v + Matrix3 operator*(const Matrix3& rhs) const; // multiplication: M3 = M1 * M2 + Matrix3& operator*=(const Matrix3& rhs); // multiplication: M1' = M1 * M2 + bool operator==(const Matrix3& rhs) const; // exact compare, no epsilon + bool operator!=(const Matrix3& rhs) const; // exact compare, no epsilon + float operator[](int index) const; // subscript operator v[0], v[1] + float& operator[](int index); // subscript operator v[0], v[1] + + friend Matrix3 operator-(const Matrix3& m); // unary operator (-) + friend Matrix3 operator*(float scalar, const Matrix3& m); // pre-multiplication + friend Vector3 operator*(const Vector3& vec, const Matrix3& m); // pre-multiplication + friend std::ostream& operator<<(std::ostream& os, const Matrix3& m); + +protected: + +private: + float m[9]; + +}; + + + +/////////////////////////////////////////////////////////////////////////// +// 4x4 matrix +/////////////////////////////////////////////////////////////////////////// +class Matrix4 +{ +public: + // constructors + Matrix4(); // init with identity + Matrix4(const float src[16]); + Matrix4(float m00, float m01, float m02, float m03, // 1st column + float m04, float m05, float m06, float m07, // 2nd column + float m08, float m09, float m10, float m11, // 3rd column + float m12, float m13, float m14, float m15);// 4th column + + void set(const float src[16]); + void set(float m00, float m01, float m02, float m03, // 1st column + float m04, float m05, float m06, float m07, // 2nd column + float m08, float m09, float m10, float m11, // 3rd column + float m12, float m13, float m14, float m15);// 4th column + void setRow(int index, const float row[4]); + void setRow(int index, const Vector4& v); + void setRow(int index, const Vector3& v); + void setColumn(int index, const float col[4]); + void setColumn(int index, const Vector4& v); + void setColumn(int index, const Vector3& v); + + const float* get() const; + const float* getTranspose(); // return transposed matrix + float getDeterminant(); + + const Vector3 eulerAngles(); + Matrix4& identity(); + Matrix4& transpose(); // transpose itself and return reference + Matrix4& invert(); // check best inverse method before inverse + Matrix4& invertEuclidean(); // inverse of Euclidean transform matrix + Matrix4& invertAffine(); // inverse of affine transform matrix + Matrix4& invertProjective(); // inverse of projective matrix using partitioning + Matrix4& invertGeneral(); // inverse of generic matrix + + // transform matrix + Matrix4& translate(float x, float y, float z); // translation by (x,y,z) + Matrix4& translate(const Vector3& v); // + Matrix4& rotate(float angle, const Vector3& axis); // rotate angle(degree) along the given axix + Matrix4& rotate(float angle, float x, float y, float z); + Matrix4& rotateX(float angle); // rotate on X-axis with degree + Matrix4& rotateY(float angle); // rotate on Y-axis with degree + Matrix4& rotateZ(float angle); // rotate on Z-axis with degree + Matrix4& scale(float scale); // uniform scale + Matrix4& scale(float sx, float sy, float sz); // scale by (sx, sy, sz) on each axis + + // operators + Matrix4 operator+(const Matrix4& rhs) const; // add rhs + Matrix4 operator-(const Matrix4& rhs) const; // subtract rhs + Matrix4& operator+=(const Matrix4& rhs); // add rhs and update this object + Matrix4& operator-=(const Matrix4& rhs); // subtract rhs and update this object + Vector4 operator*(const Vector4& rhs) const; // multiplication: v' = M * v + Vector3 operator*(const Vector3& rhs) const; // multiplication: v' = M * v + Matrix4 operator*(const Matrix4& rhs) const; // multiplication: M3 = M1 * M2 + Matrix4& operator*=(const Matrix4& rhs); // multiplication: M1' = M1 * M2 + bool operator==(const Matrix4& rhs) const; // exact compare, no epsilon + bool operator!=(const Matrix4& rhs) const; // exact compare, no epsilon + float operator[](int index) const; // subscript operator v[0], v[1] + float& operator[](int index); // subscript operator v[0], v[1] + + friend Matrix4 operator-(const Matrix4& m); // unary operator (-) + friend Matrix4 operator*(float scalar, const Matrix4& m); // pre-multiplication + friend Vector3 operator*(const Vector3& vec, const Matrix4& m); // pre-multiplication + friend Vector4 operator*(const Vector4& vec, const Matrix4& m); // pre-multiplication + friend std::ostream& operator<<(std::ostream& os, const Matrix4& m); + +protected: + +private: + float getCofactor(float m0, float m1, float m2, + float m3, float m4, float m5, + float m6, float m7, float m8); + + float m[16]; + float tm[16]; // transpose m + +}; + +Matrix4 setFrustum(float l, float r, float b, float t, float n, float f); +Matrix4 setFrustum(float fovY, float aspect, float front, float back); +Matrix4 setOrthoFrustum(float l, float r, float b, float t, float n, float f); +Matrix4 lookAt(const Vector3& eye, const Vector3& center, const Vector3& up); + + +/////////////////////////////////////////////////////////////////////////// +// inline functions for Matrix2 +/////////////////////////////////////////////////////////////////////////// +inline Matrix2::Matrix2() +{ + // initially identity matrix + identity(); +} + + + +inline Matrix2::Matrix2(const float src[4]) +{ + set(src); +} + + + +inline Matrix2::Matrix2(float m0, float m1, float m2, float m3) +{ + set(m0, m1, m2, m3); +} + + + +inline void Matrix2::set(const float src[4]) +{ + m[0] = src[0]; m[1] = src[1]; m[2] = src[2]; m[3] = src[3]; +} + + + +inline void Matrix2::set(float m0, float m1, float m2, float m3) +{ + m[0]= m0; m[1] = m1; m[2] = m2; m[3]= m3; +} + + + +inline void Matrix2::setRow(int index, const float row[2]) +{ + m[index] = row[0]; m[index + 2] = row[1]; +} + + + +inline void Matrix2::setRow(int index, const Vector2& v) +{ + m[index] = v.x; m[index + 2] = v.y; +} + + + +inline void Matrix2::setColumn(int index, const float col[2]) +{ + m[index*2] = col[0]; m[index*2 + 1] = col[1]; +} + + + +inline void Matrix2::setColumn(int index, const Vector2& v) +{ + m[index*2] = v.x; m[index*2 + 1] = v.y; +} + + + +inline const float* Matrix2::get() const +{ + return m; +} + + + +inline Matrix2& Matrix2::identity() +{ + m[0] = m[3] = 1.0f; + m[1] = m[2] = 0.0f; + return *this; +} + + + +inline Matrix2 Matrix2::operator+(const Matrix2& rhs) const +{ + return Matrix2(m[0]+rhs[0], m[1]+rhs[1], m[2]+rhs[2], m[3]+rhs[3]); +} + + + +inline Matrix2 Matrix2::operator-(const Matrix2& rhs) const +{ + return Matrix2(m[0]-rhs[0], m[1]-rhs[1], m[2]-rhs[2], m[3]-rhs[3]); +} + + + +inline Matrix2& Matrix2::operator+=(const Matrix2& rhs) +{ + m[0] += rhs[0]; m[1] += rhs[1]; m[2] += rhs[2]; m[3] += rhs[3]; + return *this; +} + + + +inline Matrix2& Matrix2::operator-=(const Matrix2& rhs) +{ + m[0] -= rhs[0]; m[1] -= rhs[1]; m[2] -= rhs[2]; m[3] -= rhs[3]; + return *this; +} + + + +inline Vector2 Matrix2::operator*(const Vector2& rhs) const +{ + return Vector2(m[0]*rhs.x + m[2]*rhs.y, m[1]*rhs.x + m[3]*rhs.y); +} + + + +inline Matrix2 Matrix2::operator*(const Matrix2& rhs) const +{ + return Matrix2(m[0]*rhs[0] + m[2]*rhs[1], m[1]*rhs[0] + m[3]*rhs[1], + m[0]*rhs[2] + m[2]*rhs[3], m[1]*rhs[2] + m[3]*rhs[3]); +} + + + +inline Matrix2& Matrix2::operator*=(const Matrix2& rhs) +{ + *this = *this * rhs; + return *this; +} + + + +inline bool Matrix2::operator==(const Matrix2& rhs) const +{ + return (m[0] == rhs[0]) && (m[1] == rhs[1]) && (m[2] == rhs[2]) && (m[3] == rhs[3]); +} + + + +inline bool Matrix2::operator!=(const Matrix2& rhs) const +{ + return (m[0] != rhs[0]) || (m[1] != rhs[1]) || (m[2] != rhs[2]) || (m[3] != rhs[3]); +} + + + +inline float Matrix2::operator[](int index) const +{ + return m[index]; +} + + + +inline float& Matrix2::operator[](int index) +{ + return m[index]; +} + + + +inline Matrix2 operator-(const Matrix2& rhs) +{ + return Matrix2(-rhs[0], -rhs[1], -rhs[2], -rhs[3]); +} + + + +inline Matrix2 operator*(float s, const Matrix2& rhs) +{ + return Matrix2(s*rhs[0], s*rhs[1], s*rhs[2], s*rhs[3]); +} + + + +inline Vector2 operator*(const Vector2& v, const Matrix2& rhs) +{ + return Vector2(v.x*rhs[0] + v.y*rhs[1], v.x*rhs[2] + v.y*rhs[3]); +} + + + +inline std::ostream& operator<<(std::ostream& os, const Matrix2& m) +{ + os << std::fixed << std::setprecision(5); + os << "[" << std::setw(10) << m[0] << " " << std::setw(10) << m[2] << "]\n" + << "[" << std::setw(10) << m[1] << " " << std::setw(10) << m[3] << "]\n"; + os << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield); + return os; +} +// END OF MATRIX2 INLINE ////////////////////////////////////////////////////// + + + + +/////////////////////////////////////////////////////////////////////////// +// inline functions for Matrix3 +/////////////////////////////////////////////////////////////////////////// +inline Matrix3::Matrix3() +{ + // initially identity matrix + identity(); +} + + + +inline Matrix3::Matrix3(const float src[9]) +{ + set(src); +} + + + +inline Matrix3::Matrix3(float m0, float m1, float m2, + float m3, float m4, float m5, + float m6, float m7, float m8) +{ + set(m0, m1, m2, m3, m4, m5, m6, m7, m8); +} + + + +inline void Matrix3::set(const float src[9]) +{ + m[0] = src[0]; m[1] = src[1]; m[2] = src[2]; + m[3] = src[3]; m[4] = src[4]; m[5] = src[5]; + m[6] = src[6]; m[7] = src[7]; m[8] = src[8]; +} + + + +inline void Matrix3::set(float m0, float m1, float m2, + float m3, float m4, float m5, + float m6, float m7, float m8) +{ + m[0] = m0; m[1] = m1; m[2] = m2; + m[3] = m3; m[4] = m4; m[5] = m5; + m[6] = m6; m[7] = m7; m[8] = m8; +} + + + +inline void Matrix3::setRow(int index, const float row[3]) +{ + m[index] = row[0]; m[index + 3] = row[1]; m[index + 6] = row[2]; +} + + + +inline void Matrix3::setRow(int index, const Vector3& v) +{ + m[index] = v.x; m[index + 3] = v.y; m[index + 6] = v.z; +} + + + +inline void Matrix3::setColumn(int index, const float col[3]) +{ + m[index*3] = col[0]; m[index*3 + 1] = col[1]; m[index*3 + 2] = col[2]; +} + + + +inline void Matrix3::setColumn(int index, const Vector3& v) +{ + m[index*3] = v.x; m[index*3 + 1] = v.y; m[index*3 + 2] = v.z; +} + + + +inline const float* Matrix3::get() const +{ + return m; +} + + + +inline Matrix3& Matrix3::identity() +{ + m[0] = m[4] = m[8] = 1.0f; + m[1] = m[2] = m[3] = m[5] = m[6] = m[7] = 0.0f; + return *this; +} + + + +inline Matrix3 Matrix3::operator+(const Matrix3& rhs) const +{ + return Matrix3(m[0]+rhs[0], m[1]+rhs[1], m[2]+rhs[2], + m[3]+rhs[3], m[4]+rhs[4], m[5]+rhs[5], + m[6]+rhs[6], m[7]+rhs[7], m[8]+rhs[8]); +} + + + +inline Matrix3 Matrix3::operator-(const Matrix3& rhs) const +{ + return Matrix3(m[0]-rhs[0], m[1]-rhs[1], m[2]-rhs[2], + m[3]-rhs[3], m[4]-rhs[4], m[5]-rhs[5], + m[6]-rhs[6], m[7]-rhs[7], m[8]-rhs[8]); +} + + + +inline Matrix3& Matrix3::operator+=(const Matrix3& rhs) +{ + m[0] += rhs[0]; m[1] += rhs[1]; m[2] += rhs[2]; + m[3] += rhs[3]; m[4] += rhs[4]; m[5] += rhs[5]; + m[6] += rhs[6]; m[7] += rhs[7]; m[8] += rhs[8]; + return *this; +} + + + +inline Matrix3& Matrix3::operator-=(const Matrix3& rhs) +{ + m[0] -= rhs[0]; m[1] -= rhs[1]; m[2] -= rhs[2]; + m[3] -= rhs[3]; m[4] -= rhs[4]; m[5] -= rhs[5]; + m[6] -= rhs[6]; m[7] -= rhs[7]; m[8] -= rhs[8]; + return *this; +} + + + +inline Vector3 Matrix3::operator*(const Vector3& rhs) const +{ + return Vector3(m[0]*rhs.x + m[3]*rhs.y + m[6]*rhs.z, + m[1]*rhs.x + m[4]*rhs.y + m[7]*rhs.z, + m[2]*rhs.x + m[5]*rhs.y + m[8]*rhs.z); +} + + + +inline Matrix3 Matrix3::operator*(const Matrix3& rhs) const +{ + return Matrix3(m[0]*rhs[0] + m[3]*rhs[1] + m[6]*rhs[2], m[1]*rhs[0] + m[4]*rhs[1] + m[7]*rhs[2], m[2]*rhs[0] + m[5]*rhs[1] + m[8]*rhs[2], + m[0]*rhs[3] + m[3]*rhs[4] + m[6]*rhs[5], m[1]*rhs[3] + m[4]*rhs[4] + m[7]*rhs[5], m[2]*rhs[3] + m[5]*rhs[4] + m[8]*rhs[5], + m[0]*rhs[6] + m[3]*rhs[7] + m[6]*rhs[8], m[1]*rhs[6] + m[4]*rhs[7] + m[7]*rhs[8], m[2]*rhs[6] + m[5]*rhs[7] + m[8]*rhs[8]); +} + + + +inline Matrix3& Matrix3::operator*=(const Matrix3& rhs) +{ + *this = *this * rhs; + return *this; +} + + + +inline bool Matrix3::operator==(const Matrix3& rhs) const +{ + return (m[0] == rhs[0]) && (m[1] == rhs[1]) && (m[2] == rhs[2]) && + (m[3] == rhs[3]) && (m[4] == rhs[4]) && (m[5] == rhs[5]) && + (m[6] == rhs[6]) && (m[7] == rhs[7]) && (m[8] == rhs[8]); +} + + + +inline bool Matrix3::operator!=(const Matrix3& rhs) const +{ + return (m[0] != rhs[0]) || (m[1] != rhs[1]) || (m[2] != rhs[2]) || + (m[3] != rhs[3]) || (m[4] != rhs[4]) || (m[5] != rhs[5]) || + (m[6] != rhs[6]) || (m[7] != rhs[7]) || (m[8] != rhs[8]); +} + + + +inline float Matrix3::operator[](int index) const +{ + return m[index]; +} + + + +inline float& Matrix3::operator[](int index) +{ + return m[index]; +} + + + +inline Matrix3 operator-(const Matrix3& rhs) +{ + return Matrix3(-rhs[0], -rhs[1], -rhs[2], -rhs[3], -rhs[4], -rhs[5], -rhs[6], -rhs[7], -rhs[8]); +} + + + +inline Matrix3 operator*(float s, const Matrix3& rhs) +{ + return Matrix3(s*rhs[0], s*rhs[1], s*rhs[2], s*rhs[3], s*rhs[4], s*rhs[5], s*rhs[6], s*rhs[7], s*rhs[8]); +} + + + +inline Vector3 operator*(const Vector3& v, const Matrix3& m) +{ + return Vector3(v.x*m[0] + v.y*m[1] + v.z*m[2], v.x*m[3] + v.y*m[4] + v.z*m[5], v.x*m[6] + v.y*m[7] + v.z*m[8]); +} + + + +inline std::ostream& operator<<(std::ostream& os, const Matrix3& m) +{ + os << std::fixed << std::setprecision(5); + os << "[" << std::setw(10) << m[0] << " " << std::setw(10) << m[3] << " " << std::setw(10) << m[6] << "]\n" + << "[" << std::setw(10) << m[1] << " " << std::setw(10) << m[4] << " " << std::setw(10) << m[7] << "]\n" + << "[" << std::setw(10) << m[2] << " " << std::setw(10) << m[5] << " " << std::setw(10) << m[8] << "]\n"; + os << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield); + return os; +} +// END OF MATRIX3 INLINE ////////////////////////////////////////////////////// + + + + +/////////////////////////////////////////////////////////////////////////// +// inline functions for Matrix4 +/////////////////////////////////////////////////////////////////////////// +inline Matrix4::Matrix4() +{ + // initially identity matrix + identity(); +} + + + +inline Matrix4::Matrix4(const float src[16]) +{ + set(src); +} + + + +inline Matrix4::Matrix4(float m00, float m01, float m02, float m03, + float m04, float m05, float m06, float m07, + float m08, float m09, float m10, float m11, + float m12, float m13, float m14, float m15) +{ + set(m00, m01, m02, m03, m04, m05, m06, m07, m08, m09, m10, m11, m12, m13, m14, m15); +} + + + +inline void Matrix4::set(const float src[16]) +{ + m[0] = src[0]; m[1] = src[1]; m[2] = src[2]; m[3] = src[3]; + m[4] = src[4]; m[5] = src[5]; m[6] = src[6]; m[7] = src[7]; + m[8] = src[8]; m[9] = src[9]; m[10]= src[10]; m[11]= src[11]; + m[12]= src[12]; m[13]= src[13]; m[14]= src[14]; m[15]= src[15]; +} + + + +inline void Matrix4::set(float m00, float m01, float m02, float m03, + float m04, float m05, float m06, float m07, + float m08, float m09, float m10, float m11, + float m12, float m13, float m14, float m15) +{ + m[0] = m00; m[1] = m01; m[2] = m02; m[3] = m03; + m[4] = m04; m[5] = m05; m[6] = m06; m[7] = m07; + m[8] = m08; m[9] = m09; m[10]= m10; m[11]= m11; + m[12]= m12; m[13]= m13; m[14]= m14; m[15]= m15; +} + + + +inline void Matrix4::setRow(int index, const float row[4]) +{ + m[index] = row[0]; m[index + 4] = row[1]; m[index + 8] = row[2]; m[index + 12] = row[3]; +} + + + +inline void Matrix4::setRow(int index, const Vector4& v) +{ + m[index] = v.x; m[index + 4] = v.y; m[index + 8] = v.z; m[index + 12] = v.w; +} + + + +inline void Matrix4::setRow(int index, const Vector3& v) +{ + m[index] = v.x; m[index + 4] = v.y; m[index + 8] = v.z; +} + + + +inline void Matrix4::setColumn(int index, const float col[4]) +{ + m[index*4] = col[0]; m[index*4 + 1] = col[1]; m[index*4 + 2] = col[2]; m[index*4 + 3] = col[3]; +} + + + +inline void Matrix4::setColumn(int index, const Vector4& v) +{ + m[index*4] = v.x; m[index*4 + 1] = v.y; m[index*4 + 2] = v.z; m[index*4 + 3] = v.w; +} + + + +inline void Matrix4::setColumn(int index, const Vector3& v) +{ + m[index*4] = v.x; m[index*4 + 1] = v.y; m[index*4 + 2] = v.z; +} + + + +inline const float* Matrix4::get() const +{ + return m; +} + + + +inline const float* Matrix4::getTranspose() +{ + tm[0] = m[0]; tm[1] = m[4]; tm[2] = m[8]; tm[3] = m[12]; + tm[4] = m[1]; tm[5] = m[5]; tm[6] = m[9]; tm[7] = m[13]; + tm[8] = m[2]; tm[9] = m[6]; tm[10]= m[10]; tm[11]= m[14]; + tm[12]= m[3]; tm[13]= m[7]; tm[14]= m[11]; tm[15]= m[15]; + return tm; +} + + + +inline Matrix4& Matrix4::identity() +{ + m[0] = m[5] = m[10] = m[15] = 1.0f; + m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[11] = m[12] = m[13] = m[14] = 0.0f; + return *this; +} + + + +inline Matrix4 Matrix4::operator+(const Matrix4& rhs) const +{ + return Matrix4(m[0]+rhs[0], m[1]+rhs[1], m[2]+rhs[2], m[3]+rhs[3], + m[4]+rhs[4], m[5]+rhs[5], m[6]+rhs[6], m[7]+rhs[7], + m[8]+rhs[8], m[9]+rhs[9], m[10]+rhs[10], m[11]+rhs[11], + m[12]+rhs[12], m[13]+rhs[13], m[14]+rhs[14], m[15]+rhs[15]); +} + + + +inline Matrix4 Matrix4::operator-(const Matrix4& rhs) const +{ + return Matrix4(m[0]-rhs[0], m[1]-rhs[1], m[2]-rhs[2], m[3]-rhs[3], + m[4]-rhs[4], m[5]-rhs[5], m[6]-rhs[6], m[7]-rhs[7], + m[8]-rhs[8], m[9]-rhs[9], m[10]-rhs[10], m[11]-rhs[11], + m[12]-rhs[12], m[13]-rhs[13], m[14]-rhs[14], m[15]-rhs[15]); +} + + + +inline Matrix4& Matrix4::operator+=(const Matrix4& rhs) +{ + m[0] += rhs[0]; m[1] += rhs[1]; m[2] += rhs[2]; m[3] += rhs[3]; + m[4] += rhs[4]; m[5] += rhs[5]; m[6] += rhs[6]; m[7] += rhs[7]; + m[8] += rhs[8]; m[9] += rhs[9]; m[10]+= rhs[10]; m[11]+= rhs[11]; + m[12]+= rhs[12]; m[13]+= rhs[13]; m[14]+= rhs[14]; m[15]+= rhs[15]; + return *this; +} + + + +inline Matrix4& Matrix4::operator-=(const Matrix4& rhs) +{ + m[0] -= rhs[0]; m[1] -= rhs[1]; m[2] -= rhs[2]; m[3] -= rhs[3]; + m[4] -= rhs[4]; m[5] -= rhs[5]; m[6] -= rhs[6]; m[7] -= rhs[7]; + m[8] -= rhs[8]; m[9] -= rhs[9]; m[10]-= rhs[10]; m[11]-= rhs[11]; + m[12]-= rhs[12]; m[13]-= rhs[13]; m[14]-= rhs[14]; m[15]-= rhs[15]; + return *this; +} + + + +inline Vector4 Matrix4::operator*(const Vector4& rhs) const +{ + return Vector4(m[0]*rhs.x + m[4]*rhs.y + m[8]*rhs.z + m[12]*rhs.w, + m[1]*rhs.x + m[5]*rhs.y + m[9]*rhs.z + m[13]*rhs.w, + m[2]*rhs.x + m[6]*rhs.y + m[10]*rhs.z + m[14]*rhs.w, + m[3]*rhs.x + m[7]*rhs.y + m[11]*rhs.z + m[15]*rhs.w); +} + + + +inline Vector3 Matrix4::operator*(const Vector3& rhs) const +{ + return Vector3(m[0]*rhs.x + m[4]*rhs.y + m[8]*rhs.z, + m[1]*rhs.x + m[5]*rhs.y + m[9]*rhs.z, + m[2]*rhs.x + m[6]*rhs.y + m[10]*rhs.z); +} + + + +inline Matrix4 Matrix4::operator*(const Matrix4& n) const +{ + return Matrix4(m[0]*n[0] + m[4]*n[1] + m[8]*n[2] + m[12]*n[3], m[1]*n[0] + m[5]*n[1] + m[9]*n[2] + m[13]*n[3], m[2]*n[0] + m[6]*n[1] + m[10]*n[2] + m[14]*n[3], m[3]*n[0] + m[7]*n[1] + m[11]*n[2] + m[15]*n[3], + m[0]*n[4] + m[4]*n[5] + m[8]*n[6] + m[12]*n[7], m[1]*n[4] + m[5]*n[5] + m[9]*n[6] + m[13]*n[7], m[2]*n[4] + m[6]*n[5] + m[10]*n[6] + m[14]*n[7], m[3]*n[4] + m[7]*n[5] + m[11]*n[6] + m[15]*n[7], + m[0]*n[8] + m[4]*n[9] + m[8]*n[10] + m[12]*n[11], m[1]*n[8] + m[5]*n[9] + m[9]*n[10] + m[13]*n[11], m[2]*n[8] + m[6]*n[9] + m[10]*n[10] + m[14]*n[11], m[3]*n[8] + m[7]*n[9] + m[11]*n[10] + m[15]*n[11], + m[0]*n[12] + m[4]*n[13] + m[8]*n[14] + m[12]*n[15], m[1]*n[12] + m[5]*n[13] + m[9]*n[14] + m[13]*n[15], m[2]*n[12] + m[6]*n[13] + m[10]*n[14] + m[14]*n[15], m[3]*n[12] + m[7]*n[13] + m[11]*n[14] + m[15]*n[15]); +} + + + +inline Matrix4& Matrix4::operator*=(const Matrix4& rhs) +{ + *this = *this * rhs; + return *this; +} + + + +inline bool Matrix4::operator==(const Matrix4& n) const +{ + return (m[0] == n[0]) && (m[1] == n[1]) && (m[2] == n[2]) && (m[3] == n[3]) && + (m[4] == n[4]) && (m[5] == n[5]) && (m[6] == n[6]) && (m[7] == n[7]) && + (m[8] == n[8]) && (m[9] == n[9]) && (m[10]== n[10]) && (m[11]== n[11]) && + (m[12]== n[12]) && (m[13]== n[13]) && (m[14]== n[14]) && (m[15]== n[15]); +} + + + +inline bool Matrix4::operator!=(const Matrix4& n) const +{ + return (m[0] != n[0]) || (m[1] != n[1]) || (m[2] != n[2]) || (m[3] != n[3]) || + (m[4] != n[4]) || (m[5] != n[5]) || (m[6] != n[6]) || (m[7] != n[7]) || + (m[8] != n[8]) || (m[9] != n[9]) || (m[10]!= n[10]) || (m[11]!= n[11]) || + (m[12]!= n[12]) || (m[13]!= n[13]) || (m[14]!= n[14]) || (m[15]!= n[15]); +} + + + +inline float Matrix4::operator[](int index) const +{ + return m[index]; +} + + + +inline float& Matrix4::operator[](int index) +{ + return m[index]; +} + + + +inline Matrix4 operator-(const Matrix4& rhs) +{ + return Matrix4(-rhs[0], -rhs[1], -rhs[2], -rhs[3], -rhs[4], -rhs[5], -rhs[6], -rhs[7], -rhs[8], -rhs[9], -rhs[10], -rhs[11], -rhs[12], -rhs[13], -rhs[14], -rhs[15]); +} + + + +inline Matrix4 operator*(float s, const Matrix4& rhs) +{ + return Matrix4(s*rhs[0], s*rhs[1], s*rhs[2], s*rhs[3], s*rhs[4], s*rhs[5], s*rhs[6], s*rhs[7], s*rhs[8], s*rhs[9], s*rhs[10], s*rhs[11], s*rhs[12], s*rhs[13], s*rhs[14], s*rhs[15]); +} + + + +inline Vector4 operator*(const Vector4& v, const Matrix4& m) +{ + return Vector4(v.x*m[0] + v.y*m[1] + v.z*m[2] + v.w*m[3], v.x*m[4] + v.y*m[5] + v.z*m[6] + v.w*m[7], v.x*m[8] + v.y*m[9] + v.z*m[10] + v.w*m[11], v.x*m[12] + v.y*m[13] + v.z*m[14] + v.w*m[15]); +} + + + +inline Vector3 operator*(const Vector3& v, const Matrix4& m) +{ + return Vector3(v.x*m[0] + v.y*m[1] + v.z*m[2], v.x*m[4] + v.y*m[5] + v.z*m[6], v.x*m[8] + v.y*m[9] + v.z*m[10]); +} + + + +inline std::ostream& operator<<(std::ostream& os, const Matrix4& m) +{ + os << std::fixed << std::setprecision(5); + os << "[" << std::setw(10) << m[0] << " " << std::setw(10) << m[4] << " " << std::setw(10) << m[8] << " " << std::setw(10) << m[12] << "]\n" + << "[" << std::setw(10) << m[1] << " " << std::setw(10) << m[5] << " " << std::setw(10) << m[9] << " " << std::setw(10) << m[13] << "]\n" + << "[" << std::setw(10) << m[2] << " " << std::setw(10) << m[6] << " " << std::setw(10) << m[10] << " " << std::setw(10) << m[14] << "]\n" + << "[" << std::setw(10) << m[3] << " " << std::setw(10) << m[7] << " " << std::setw(10) << m[11] << " " << std::setw(10) << m[15] << "]\n"; + os << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield); + return os; +} +// END OF MATRIX4 INLINE ////////////////////////////////////////////////////// +#endif diff --git a/Model.cpp b/Model.cpp new file mode 100644 index 0000000..982ace8 --- /dev/null +++ b/Model.cpp @@ -0,0 +1,615 @@ +#include "Model.h" + +#include +#include "renderer.h" +#include "screen.h" +#include "Editor.h" + +const float vb[8][3] = {{-0.5f, 0.5f, -0.5f},{-0.5f, 0.5f, 0.5f},{0.5f, 0.5f, 0.5f},{0.5f, 0.5f, -0.5f},{-0.5f, -0.5f, -0.5f},{-0.5f, -0.5f, 0.5f},{0.5f, -0.5f, 0.5f},{0.5f, -0.5f, -0.5f}}; +const char fb[6][4] = {{1, 5, 6, 2},{3, 7, 4, 0},{0, 1, 2, 3},{7, 6, 5, 4},{2, 6, 7, 3},{0, 4, 5, 1}}; +const char nb[6][3] = {{0, 0, 1},{0, 0, -1},{0, 1, 0},{0, -1, 0},{1, 0, 0},{-1, 0, 0}}; +//const char t[5][2] = {{0, 1},{0, 3},{2, 3},{2, 1},{0, 1}}; + +Model model; + +#pragma mark Model::File Ops + +void Model::Save(const char* filename) +{ +} + +void Model::Load(const char* filename) +{ +} + +#pragma mark Model::Calculations + +const Vector3 Model::GetCurrentValue(const int& cube, const AnimKey::Type& type) +{ + int currentAnim = 0; + int i1 = -1; + int i2 = -1; + const Anim& anim = anims[currentAnim]; + for (int i = 0; i < anim.numKeys; i++) { + const AnimKey& k = anim.keys[i]; + if (k.cube == cube && k.type == type) { + if (k.t == Editor::t) { + i1 = i2 = i; + } else if (k.t < Editor::t) { + i1 = i; + } else if (k.t > Editor::t) { + if (i1 == -1) { + i1 = i2 = i; + break; + } else { + i2 = i; + break; + } + } + } + } + if (i1 == -1) { + if (type == AnimKey::Type::Rotation) { + return Vector3(0, 0, 0); + } else if (type == AnimKey::Type::Position) { + return cubes[cube].position; + } else { + return cubes[cube].size; + } + } + if (i1 == i2 or i2 == -1) return anim.keys[i1].value; + + // MUCH OBFUSCATED, SO DEBUGGABLE + return anim.keys[i1].value + ((anim.keys[i2].value - anim.keys[i1].value) * (Editor::t - anim.keys[i1].t)/(anim.keys[i2].t - anim.keys[i1].t)); +} + +void Model::GetCurrentTransformation(const int& cube, Vector3& position, Vector3& rotation, Vector3& scale) +{ + position = GetCurrentValue(cube, AnimKey::Type::Position); + rotation = GetCurrentValue(cube, AnimKey::Type::Rotation); + scale = GetCurrentValue(cube, AnimKey::Type::Scale); +} + +void Model::Calculate() +{ + for (int i = 0; i < numCubes; i++) { + Vector3 position, rotation, scale, pivot; + GetCurrentTransformation(i, position, rotation, scale); + pivot = cubes[i].pivot; + localMatrix[i].identity(); + localMatrix[i].translate(pivot); + localMatrix[i].scale(scale.x, scale.y, scale.z); + localMatrix[i].rotateX(rotation.x); + localMatrix[i].rotateY(rotation.y); + localMatrix[i].rotateZ(rotation.z); + localMatrix[i].translate(position-pivot); + + } + + for (int i = 0; i < numCubes; i++) { + finalMatrix[i] = localMatrix[i]; + int parent = cubes[i].parent; + while (parent != -1) { + finalMatrix[i] *= localMatrix[parent]; + parent = cubes[parent].parent; + } + } +} + +#pragma mark Model::Draw + +void Model::DrawSolid() +{ + glColor4ub(255, 255, 255, 255); + for (int c = 0; c < numCubes; c++) { + if (cubes[c].selected) continue; + const Cube& cube = cubes[c]; + glPushMatrix(); + glBegin(GL_QUADS); + for (int f = 0; f<6; f++) { + if (cube.faces[f].selected) continue; + for (int i = 0; i < 4; i++) { + glNormal3f(nb[f][0], nb[f][1], nb[f][2]); + glTexCoord2f(float(cube.faces[f].uv[i].x)/32.0f, float(cube.faces[f].uv[i].y)/32.0f); + glVertex3f(cube.position[0]+vb[fb[f][i]][0]*cube.size[0], cube.position[1]+vb[fb[f][i]][1]*cube.size[1], cube.position[2]+vb[fb[f][i]][2]*cube.size[2]); + } + } + glEnd(); + glPopMatrix(); + } + glColor4ub(255, 0, 0, 255); + for (int c = 0; c < numCubes; c++) { + const Cube& cube = cubes[c]; + glPushMatrix(); + //glRotatef(cube.rot[0], 1, 0, 0); + //glRotatef(cube.rot[1], 0, 1, 0); + //glRotatef(cube.rot[2], 0, 0, 1); + glBegin(GL_QUADS); + for (int f = 0; f<6; f++) { + if (!cube.selected and !cube.faces[f].selected) continue; + for (int i = 0; i < 4; i++) { + glNormal3f(nb[f][0], nb[f][1], nb[f][2]); + glTexCoord2f(float(cube.faces[f].uv[i].x)/32.0f, float(cube.faces[f].uv[i].y)/32.0f); + glVertex3f(cube.position[0]+vb[fb[f][i]][0]*cube.size[0], cube.position[1]+vb[fb[f][i]][1]*cube.size[1], cube.position[2]+vb[fb[f][i]][2]*cube.size[2]); + } + } + glEnd(); + glPopMatrix(); + } +} + +void Model::DrawWire() +{ + Renderer::SetState(RENDER_DEPTH); + glCullFace(GL_FRONT); + glDepthMask(GL_FALSE); + + glColor4ub(128, 128, 128, 255); + for (int c = 0; c < numCubes; c++) { + if (cubes[c].selected) continue; + const Cube& cube = cubes[c]; + glPushMatrix(); + //glRotatef(cube.rot[0], 1, 0, 0); + //glRotatef(cube.rot[1], 0, 1, 0); + //glRotatef(cube.rot[2], 0, 0, 1); + glBegin(GL_QUADS); + for (int f = 0; f<6; f++) { + if (cube.faces[f].selected) continue; + for (int i = 0; i < 4; i++) { + glNormal3f(nb[f][0], nb[f][1], nb[f][2]); + glVertex3f(cube.position[0]+vb[fb[f][i]][0]*cube.size[0], cube.position[1]+vb[fb[f][i]][1]*cube.size[1], cube.position[2]+vb[fb[f][i]][2]*cube.size[2]); + } + } + glEnd(); + glPopMatrix(); + } + glColor4ub(128, 0, 0, 255); + for (int c = 0; c < numCubes; c++) { + const Cube& cube = cubes[c]; + glPushMatrix(); + //glRotatef(cube.rot[0], 1, 0, 0); + //glRotatef(cube.rot[1], 0, 1, 0); + //glRotatef(cube.rot[2], 0, 0, 1); + glBegin(GL_QUADS); + for (int f = 0; f<6; f++) { + if (!cube.selected and !cube.faces[f].selected) continue; + for (int i = 0; i < 4; i++) { + glNormal3f(nb[f][0], nb[f][1], nb[f][2]); + glVertex3f(cube.position[0]+vb[fb[f][i]][0]*cube.size[0], cube.position[1]+vb[fb[f][i]][1]*cube.size[1], cube.position[2]+vb[fb[f][i]][2]*cube.size[2]); + } + } + glEnd(); + glPopMatrix(); + } + + glDepthMask(GL_TRUE); + glCullFace(GL_BACK); + + glColor4ub(255, 255, 255, 255); + for (int c = 0; c < numCubes; c++) { + if (cubes[c].selected) continue; + const Cube& cube = cubes[c]; + glPushMatrix(); + //glRotatef(cube.rot[0], 1, 0, 0); + //glRotatef(cube.rot[1], 0, 1, 0); + //glRotatef(cube.rot[2], 0, 0, 1); + glBegin(GL_QUADS); + for (int f = 0; f<6; f++) { + if (cube.faces[f].selected) continue; + for (int i = 0; i < 4; i++) { + glNormal3f(nb[f][0], nb[f][1], nb[f][2]); + glVertex3f(cube.position[0]+vb[fb[f][i]][0]*cube.size[0], cube.position[1]+vb[fb[f][i]][1]*cube.size[1], cube.position[2]+vb[fb[f][i]][2]*cube.size[2]); + } + } + glEnd(); + glPopMatrix(); + } + glColor4f(1, 0, 0, 1); + for (int c = 0; c < numCubes; c++) { + const Cube& cube = cubes[c]; + glPushMatrix(); + //glRotatef(cube.rot[0], 1, 0, 0); + //glRotatef(cube.rot[1], 0, 1, 0); + //glRotatef(cube.rot[2], 0, 0, 1); + glBegin(GL_QUADS); + for (int f = 0; f<6; f++) { + if (!cube.selected and !cube.faces[f].selected) continue; + for (int i = 0; i < 4; i++) { + glNormal3f(nb[f][0], nb[f][1], nb[f][2]); + glVertex3f(cube.position[0]+vb[fb[f][i]][0]*cube.size[0], cube.position[1]+vb[fb[f][i]][1]*cube.size[1], cube.position[2]+vb[fb[f][i]][2]*cube.size[2]); + } + } + glEnd(); + glPopMatrix(); + } +} + +void Model::DrawUV() +{ + glColor4ub(255, 255, 255, 255); + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 0.0f); + glVertex2f(-16.0f, -16.0f); + glTexCoord2f(0.0f, 1.0f); + glVertex2f(-16.0f, 16.0f); + glTexCoord2f(1.0f, 1.0f); + glVertex2f(16.0f, 16.0f); + glTexCoord2f(1.0f, 0.0f); + glVertex2f(16.0f, -16.0f); + glEnd(); + + Renderer::SetState(RENDER_NONE); + + glColor4ub(255, 255, 255, 255); + for (int c = 0; c < numCubes; c++) { + if (cubes[c].selected) continue; + const Cube& cube = cubes[c]; + glBegin(GL_QUADS); + for (int f = 0; f<6; f++) { + if (cube.faces[f].selected) continue; + const CubeFace& face = cube.faces[f]; + for (int i = 0; i < 4; i++) { + glVertex2f(-(16-face.uv[i].x), -(16-face.uv[i].y)); + } + } + glEnd(); + } + glColor4ub(255, 0, 0, 255); + for (int c = 0; c < numCubes; c++) { + const Cube& cube = cubes[c]; + glBegin(GL_QUADS); + for (int f = 0; f<6; f++) { + if (!cube.selected and !cube.faces[f].selected) continue; + const CubeFace& face = cube.faces[f]; + for (int i = 0; i < 4; i++) { + glVertex2f(-(16-face.uv[i].x), -(16-face.uv[i].y)); + } + } + glEnd(); + } + +} + +Color Model::Pick(const float x, const float y) +{ + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + for (int c = 0; c < numCubes; c++) { + const Cube& cube = cubes[c]; + glPushMatrix(); + glBegin(GL_QUADS); + for (int f = 0; f < 6; f++) { + for (int i = 0; i < 4; i++) { + glColor4ub(c, f, 0, 255); + //glNormal3f(nb[f][0], nb[f][1], nb[f][2]); + //glTexCoord2f(float(cube.faces[f].uv[i].x)/32.0f, float(cube.faces[f].uv[i].y)/32.0f); + glVertex3f(cube.position[0]+vb[fb[f][i]][0]*cube.size[0], cube.position[1]+vb[fb[f][i]][1]*cube.size[1], cube.position[2]+vb[fb[f][i]][2]*cube.size[2]); + } + } + glEnd(); + glPopMatrix(); + } + Color color; + glReadPixels(x, SCREEN_HEIGHT-y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color.r); + printf("PICK: %i, %i, %i, %i\n", color.r, color.g, color.b, color.a); + return color; +} + +Color Model::PickPaint(const float x, const float y) +{ + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + for (int c = 0; c < numCubes; c++) { + const Cube& cube = cubes[c]; + glPushMatrix(); + glBegin(GL_QUADS); + for (int f = 0; f < 6; f++) { + for (int i = 0; i < 4; i++) { + uint8_t val = (c << 3) + f; + glColor4ub(val, cube.faces[f].uv[i].x*4, cube.faces[f].uv[i].y*4, 255); + //glNormal3f(nb[f][0], nb[f][1], nb[f][2]); + //glTexCoord2f(float(cube.faces[f].uv[i].x)/32.0f, float(cube.faces[f].uv[i].y)/32.0f); + glVertex3f(cube.position[0]+vb[fb[f][i]][0]*cube.size[0], cube.position[1]+vb[fb[f][i]][1]*cube.size[1], cube.position[2]+vb[fb[f][i]][2]*cube.size[2]); + } + } + glEnd(); + glPopMatrix(); + } + Color color; + glReadPixels(x, SCREEN_HEIGHT-y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color.r); + color /= 4; + printf("PICK: %i, %i, %i, %i\n", color.r, color.g, color.b, color.a); + return color; +} + +Color Model::PickPixel(const float x, const float y) +{ + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glBegin(GL_QUADS); + glColor4ub(0, 0, 255, 255); + glVertex2f(-16.0f, -16.0f); + glColor4ub(0, 128, 255, 255); + glVertex2f(-16.0f, 16.0f); + glColor4ub(128, 128, 255, 255); + glVertex2f(16.0f, 16.0f); + glColor4ub(128, 0, 255, 255); + glVertex2f(16.0f, -16.0f); + glEnd(); + + Color color; + glReadPixels(x, SCREEN_HEIGHT-y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color.r); + color /= 4; + printf("PICK: %i, %i, %i, %i\n", color.r, color.g, color.b, color.a); + return color; +} + +#pragma mark Model::Cubes + +int Model::GetSelectedCube() +{ + int selected = -1; + for (int i = 0; i < numCubes; i++) { + if (cubes[i].selected) { + if (selected == -1) { + selected = i; + } else { + return -1; + } + } + } + return selected; +} + +void Model::SelectCube(const uint8_t index, const bool inclusive) +{ + if (not inclusive) SelectAllCubes(false); + if (index == 255) return; + assert(index < numCubes); + cubes[index].selected = not cubes[index].selected; +} + +void Model::SelectAllCubes(const bool select) +{ + for (int i = 0; i < numCubes; i++) { + cubes[i].selected = select; + } +} + +void Model::NewCube() +{ + if (numCubes == MAX_CUBES) return; + SelectAllCubes(false); + cubes[numCubes].name[0] = '\0'; + cubes[numCubes].parent = -1; + cubes[numCubes].pivot = {0, 0, 0}; + cubes[numCubes].position = {0, 0, 0}; + cubes[numCubes].size = {8, 8, 8}; + cubes[numCubes].selected = true; + for (int i = 0; i < 6; i++) { + cubes[numCubes].faces[i].selected = false; + cubes[numCubes].faces[i].uv[0] = {32, 32}; + cubes[numCubes].faces[i].uv[1] = {32, 0}; + cubes[numCubes].faces[i].uv[2] = {0, 0}; + cubes[numCubes].faces[i].uv[3] = {0, 32}; + } + numCubes++; +} + +void Model::DeleteCube() +{ + if (numCubes == 0) return; + + int numCubesSelected = 0; + for (int i = 0; i < numCubes; i++) if (cubes[i].selected) numCubesSelected++; + if (numCubesSelected == 0) return; + + for (int i = 0; i < numCubes; i++) { + if (cubes[i].selected) { + numCubes--; + if (numCubes > 1 and i != numCubes) { + cubes[i] = cubes[numCubes]; + for (int c = 0; c < numCubes; c++) { + if (cubes[c].parent == i) { + cubes[c].parent = -1; + } else if (cubes[c].parent == numCubes) { + cubes[c].parent = i; + } + } + for (int a = 0; a < numAnims; a++) { + for (int k = 0; k < anims[a].numKeys; k++) { + if (anims[a].keys[k].cube == i) { + anims[a].numKeys--; + if (anims[a].numKeys > 1 and k != anims[a].numKeys) { + anims[a].keys[k] = anims[a].keys[anims[a].numKeys]; + } + k--; + } else if (anims[a].keys[k].cube == anims[a].numKeys) { + anims[a].keys[k].cube = i; + } + } + } + i--; + } + } + } +} + +void Model::DupCube() +{ + int numCubesSelected = 0; + for (int i = 0; i < numCubes; i++) if (cubes[i].selected) numCubesSelected++; + if (numCubes+numCubesSelected > MAX_CUBES) return; + + int numCubesBeforeEditing = numCubes; + for (int c = 0; c < numCubesBeforeEditing; c++) { + if (cubes[c].selected) { + cubes[c].selected = false; + cubes[numCubes].name[0] = '\0'; + cubes[numCubes].parent = cubes[c].parent; + cubes[numCubes].pivot = cubes[c].pivot; + cubes[numCubes].position = cubes[c].position; + cubes[numCubes].size = cubes[c].size; + cubes[numCubes].selected = true; + for (int i = 0; i < 6; i++) { + cubes[numCubes].faces[i].selected = false; + cubes[numCubes].faces[i].uv[0] = cubes[c].faces[i].uv[0]; + cubes[numCubes].faces[i].uv[1] = cubes[c].faces[i].uv[1]; + cubes[numCubes].faces[i].uv[2] = cubes[c].faces[i].uv[2]; + cubes[numCubes].faces[i].uv[3] = cubes[c].faces[i].uv[3]; + } + numCubes++; + } + } +} + +void Model::GrabCubes(const float x, const float y, const float z) +{ + for (int i = 0; i < numCubes; i++) { + if (cubes[i].selected) { + cubes[i].position.x += x; + cubes[i].position.y += y; + cubes[i].position.z += z; + } + } +} + +void Model::ScaleCubes(const float x, const float y, const float z) +{ + for (int i = 0; i < numCubes; i++) { + if (cubes[i].selected) { + cubes[i].size.x += x; + cubes[i].size.y += y; + cubes[i].size.z += z; + } + } +} + +#pragma mark Model::Faces + +ivec2 Model::GetSelectedFace() +{ + ivec2 selected {-1, -1}; + for (int i = 0; i < numCubes; i++) { + for (int j = 0; j < 6; j++) { + if (cubes[i].faces[j].selected) { + if (selected.x == -1) { + selected = {i, j}; + } else { + return {-1, -1}; + } + } + } + } + return selected; +} + + +void Model::SelectFace(const uint8_t index, const uint8_t face, const bool inclusive) +{ + if (not inclusive) SelectAllFaces(false); + if (index == 255) return; + assert(index < numCubes); + assert(face < 6); + cubes[index].faces[face].selected = not cubes[index].faces[face].selected; +} + +void Model::SelectAllFaces(const bool select) +{ + for (int i = 0; i < numCubes; i++) { + for (int j = 0; j < 6; j++) { + cubes[i].faces[j].selected = select; + } + } +} + +void Model::ClearFaces() +{ + for (int i = 0; i < numCubes; i++) { + for (int j = 0; j < 6; j++) { + if (cubes[i].faces[j].selected) cubes[i].faces[j].Clear(); + } + } +} + +void Model::RotateFaces() +{ + for (int i = 0; i < numCubes; i++) { + for (int j = 0; j < 6; j++) { + if (cubes[i].faces[j].selected) cubes[i].faces[j].Rotate(); + } + } +} + +void Model::FlipHFaces() +{ + for (int i = 0; i < numCubes; i++) { + for (int j = 0; j < 6; j++) { + if (cubes[i].faces[j].selected) cubes[i].faces[j].FlipH(); + } + } +} + +void Model::FlipVFaces() +{ + for (int i = 0; i < numCubes; i++) { + for (int j = 0; j < 6; j++) { + if (cubes[i].faces[j].selected) cubes[i].faces[j].FlipV(); + } + } +} + +void Model::GrabFaces(const float x, const float y) +{ + for (int i = 0; i < numCubes; i++) { + for (int j = 0; j < 6; j++) { + if (cubes[i].faces[j].selected) { + for (int k = 0; k < 4; k++) { + cubes[i].faces[j].uv[k].x += x; + cubes[i].faces[j].uv[k].y += y; + } + } + } + } +} + +void Model::ScaleFaces(const float x, const float y) +{ + for (int i = 0; i < numCubes; i++) { + for (int j = 0; j < 6; j++) { + if (cubes[i].faces[j].selected) { + cubes[i].faces[j].uv[0].x += x; + cubes[i].faces[j].uv[0].y += y; + cubes[i].faces[j].uv[1].x += x; + cubes[i].faces[j].uv[3].y += y; + } + } + } +} + +#pragma mark CubeFace::Methods + +void CubeFace::Clear() +{ + uv[0] = uv[1] = {0,0}; + uv[2] = uv[3] = {32,32}; +} + +void CubeFace::Rotate() +{ + Swap(uv[0], uv[1]); + Swap(uv[1], uv[2]); + Swap(uv[2], uv[3]); +} + +void CubeFace::FlipH() +{ + Swap(uv[0], uv[3]); + Swap(uv[1], uv[2]); +} + +void CubeFace::FlipV() +{ + Swap(uv[0], uv[1]); + Swap(uv[2], uv[3]); +} diff --git a/Model.h b/Model.h new file mode 100644 index 0000000..db24e34 --- /dev/null +++ b/Model.h @@ -0,0 +1,96 @@ +#pragma once + +#include "common.h" + +#define MAX_CUBES 20 +#define MAX_ANIMS 40 +#define MAX_KEYS 256 + +struct CubeFace +{ + Vector2 uv[4] {{32, 32}, {32, 0}, {0, 0}, {0, 32}}; + char rot {0}; + bool selected {false}; + + void Clear(); + void Rotate(); + void FlipH(); + void FlipV(); +}; + +struct Cube +{ + char name[10] {""}; + CubeFace faces[6]; + Vector3 position {0, 0, 0}; + Vector3 pivot {0, 0, 0}; + Vector3 size {8, 8, 8}; + int8_t parent {-1}; + bool selected {false}; +}; + +struct AnimKey +{ + enum class Type { Position, Rotation, Scale }; + + float t {0.0f}; + uint8_t cube {0}; + Type type {Type::Rotation}; + Vector3 value {0, 0, 0}; +}; + +struct Anim +{ + char name[10] {""}; + uint8_t numKeys {0}; + AnimKey keys[MAX_KEYS]; +}; + +struct Model +{ + uint8_t numCubes {1}; + Cube cubes[MAX_CUBES]; + uint8_t numAnims {1}; + Anim anims[MAX_ANIMS]; + + Matrix4 localMatrix[MAX_CUBES]; + Matrix4 finalMatrix[MAX_CUBES]; + + void Save(const char* filename); + void Load(const char* filename); + void Calculate(); + void DrawSolid(); + void DrawWire(); + void DrawUV(); + Color Pick(const float x, const float y); + Color PickPaint(const float x, const float y); + Color PickPixel(const float x, const float y); + + int GetSelectedCube(); + void SelectCube(const uint8_t index, const bool inclusive); + void SelectAllCubes(const bool select = true); + void NewCube(); + void DeleteCube(); + void DupCube(); + void GrabCubes(const float x, const float y, const float z); + void ScaleCubes(const float x, const float y, const float z); + + ivec2 GetSelectedFace(); + void SelectFace(const uint8_t index, const uint8_t face, const bool inclusive); + void SelectAllFaces(const bool select = true); + void ClearFaces(); + void RotateFaces(); + void FlipHFaces(); + void FlipVFaces(); + void GrabFaces(const float x, const float y); + void ScaleFaces(const float x, const float y); + + void DeleteKey(const int anim, const int key); + +private: + + const Vector3 GetCurrentValue(const int& cube, const AnimKey::Type& type); + void GetCurrentTransformation(const int& cube, Vector3& position, Vector3& rotation, Vector3& scale); +}; + +extern Model model; diff --git a/Vectors.h b/Vectors.h new file mode 100755 index 0000000..4fff31f --- /dev/null +++ b/Vectors.h @@ -0,0 +1,687 @@ +/////////////////////////////////////////////////////////////////////////////// +// Vectors.h +// ========= +// 2D/3D/4D vectors +// +// AUTHOR: Song Ho Ahn (song.ahn@gmail.com) +// CREATED: 2007-02-14 +// UPDATED: 2013-01-20 +// +// Copyright (C) 2007-2013 Song Ho Ahn +/////////////////////////////////////////////////////////////////////////////// + + +#ifndef VECTORS_H_DEF +#define VECTORS_H_DEF + +#include +#include + +struct Rectangle { + float x, y, w, h; +}; + +template +struct generic_vec2 +{ + union { + struct { + T x; + T y; + }; + struct { + T r; + T g; + }; + struct { + T u; + T v; + }; + }; + + generic_vec2() : x(0.0f), y(0.0f) {} + generic_vec2(const T& x, const T& y) : x(x), y(y) {} + + inline generic_vec2 operator-() const { return {-x,-y}; } + inline generic_vec2 operator+(const generic_vec2& o) const { return {x+o.x, y+o.y}; } + inline generic_vec2 operator-(const generic_vec2& o) const { return {x-o.x, y-o.y}; } + inline generic_vec2 operator*(const generic_vec2& o) const { return {x*o.x, y*o.y}; } + inline generic_vec2 operator/(const generic_vec2& o) const { return {x/o.x, y/o.y}; } + inline generic_vec2& operator+=(const generic_vec2& o) { x+=o.x; y+=o.y; return *this; } + inline generic_vec2& operator-=(const generic_vec2& o) { x-=o.x; y-=o.y; return *this; } + inline generic_vec2& operator*=(const generic_vec2& o) { x*=o.x; y*=o.y; return *this; } + inline generic_vec2& operator/=(const generic_vec2& o) { x/=o.x; y/=o.y; return *this; } + + inline generic_vec2 operator+(const T& o) const { return {x+o, y+o}; } + inline generic_vec2 operator-(const T& o) const { return {x-o, y-o}; } + inline generic_vec2 operator*(const T& o) const { return {x*o, y*o}; } + inline generic_vec2 operator/(const T& o) const { return {x/o, y/o}; } + inline generic_vec2& operator+=(const T& o) { x+=o; y+=o; return *this; } + inline generic_vec2& operator-=(const T& o) { x-=o; y-=o; return *this; } + inline generic_vec2& operator*=(const T& o) { x*=o; y*=o; return *this; } + inline generic_vec2& operator/=(const T& o) { x/=o; y/=o; return *this; } + + inline bool operator==(const generic_vec2& o) const { return (x==o.x) and (y==o.y); } + inline bool operator!=(const generic_vec2& o) const { return (x!=o.x) or (y!=o.y); } + inline T operator[](int index) const { return (&x)[index]; } + inline T& operator[](int index) { return (&x)[index]; } +}; +typedef generic_vec2 bvec2; +typedef generic_vec2 ivec2; +typedef generic_vec2 uvec2; +typedef generic_vec2 vec2; + +template +struct generic_vec3 +{ + union { + struct { T x, y, z; }; + struct { T r, g, b; }; + struct { T u, v, s; }; + }; + + generic_vec3(const T& x, const T& y, const T& z) : x(x), y(y), z(z) {} + generic_vec3(const generic_vec2& xy, const T& z) : x(xy.x), y(xy.y), z(z) {} + generic_vec3(const T& x, const generic_vec2& yz) : x(x), y(yz.x), z(yz.y) {} + + inline generic_vec3 operator-() const { return {-x,-y,-z}; } + inline generic_vec3 operator+(const generic_vec3& o) const { return {x+o.x, y+o.y, z+o.z}; } + inline generic_vec3 operator-(const generic_vec3& o) const { return {x-o.x, y-o.y, z-o.z}; } + inline generic_vec3 operator*(const generic_vec3& o) const { return {x*o.x, y*o.y, z*o.z}; } + inline generic_vec3 operator/(const generic_vec3& o) const { return {x/o.x, y/o.y, z/o.z}; } + inline generic_vec3& operator+=(const generic_vec3& o) { x+=o.x; y+=o.y; z+=o.z; return *this; } + inline generic_vec3& operator-=(const generic_vec3& o) { x-=o.x; y-=o.y; z-=o.z; return *this; } + inline generic_vec3& operator*=(const generic_vec3& o) { x*=o.x; y*=o.y; z*=o.z; return *this; } + inline generic_vec3& operator/=(const generic_vec3& o) { x/=o.x; y/=o.y; z/=o.z; return *this; } + + inline generic_vec3 operator+(const T& o) const { return {x+o, y+o, z+o}; } + inline generic_vec3 operator-(const T& o) const { return {x-o, y-o, z-o}; } + inline generic_vec3 operator*(const T& o) const { return {x*o, y*o, z*o}; } + inline generic_vec3 operator/(const T& o) const { return {x/o, y/o, z/o}; } + inline generic_vec3& operator+=(const T& o) { x+=o; y+=o; z+=o; return *this; } + inline generic_vec3& operator-=(const T& o) { x-=o; y-=o; z-=o; return *this; } + inline generic_vec3& operator*=(const T& o) { x*=o; y*=o; z*=o; return *this; } + inline generic_vec3& operator/=(const T& o) { x/=o; y/=o; z/=o; return *this; } + + inline bool operator==(const generic_vec3& o) const { return (x==o.x) and (y==o.y) and (z==o.z); } + inline bool operator!=(const generic_vec3& o) const { return (x!=o.x) or (y!=o.y) or (z!=o.z); } + inline T operator[](int index) const { return (&x)[index]; } + inline T& operator[](int index) { return (&x)[index]; } +}; +typedef generic_vec3 bvec3; +typedef generic_vec3 ivec3; +typedef generic_vec3 uvec3; +typedef generic_vec3 vec3; + +template +struct generic_vec4 +{ + union { + struct { T x, y, z, w; }; + struct { T r, g, b, a; }; + struct { T u, v, s, t; }; + }; + + generic_vec4() : x(0), y(0), z(0), w(0) {} + generic_vec4(const T& x,const T& y, const T& z, const T& w) : x(x), y(y), z(z), w(w) {} + generic_vec4(const generic_vec2& xy, const T& z, const T& w) : x(xy.x), y(xy.y), z(z), w(w) {} + generic_vec4(const T& x, const generic_vec2& yz, const T& w) : x(x), y(yz.x), z(yz.y), w(w) {} + generic_vec4(const T& x, const T& y, const generic_vec2& zw) : x(x), y(y), z(zw.x), w(zw.y) {} + generic_vec4(const generic_vec2& xy, const generic_vec2& zw) : x(xy.x), y(xy.y), z(zw.x), w(zw.y) {} + + generic_vec4(const generic_vec3& xyz, const T& w) : x(xyz.x), y(xyz.y), z(xyz.z), w(w) {} + generic_vec4(const T& x, const generic_vec3& yzw) : x(x), y(yzw.x), z(yzw.y), w(yzw.z) {} + + + inline generic_vec4 operator-() const { return {-x,-y,-z, -w}; } + inline generic_vec4 operator+(const generic_vec4& o) const { return {x+o.x, y+o.y, z+o.z, w+o.w}; } + inline generic_vec4 operator-(const generic_vec4& o) const { return {x-o.x, y-o.y, z-o.z, w-o.w}; } + inline generic_vec4 operator*(const generic_vec4& o) const { return {x*o.x, y*o.y, z*o.z, w*o.w}; } + inline generic_vec4 operator/(const generic_vec4& o) const { return {x/o.x, y/o.y, z/o.z, w/o.w}; } + inline generic_vec4& operator+=(const generic_vec4& o) { x+=o.x; y+=o.y; z+=o.z; w+=o.w; return *this; } + inline generic_vec4& operator-=(const generic_vec4& o) { x-=o.x; y-=o.y; z-=o.z; w-=o.w; return *this; } + inline generic_vec4& operator*=(const generic_vec4& o) { x*=o.x; y*=o.y; z*=o.z; w*=o.w; return *this; } + inline generic_vec4& operator/=(const generic_vec4& o) { x/=o.x; y/=o.y; z/=o.z; w/=o.w; return *this; } + + inline generic_vec4 operator+(const T& o) const { return {x+o, y+o, z+o, w+o}; } + inline generic_vec4 operator-(const T& o) const { return {x-o, y-o, z-o, w-o}; } + inline generic_vec4 operator*(const T& o) const { return {x*o, y*o, z*o, w*o}; } + inline generic_vec4 operator/(const T& o) const { return {x/o, y/o, z/o, w/o}; } + inline generic_vec4& operator+=(const T& o) { x+=o; y+=o; z+=o; w+=o; return *this; } + inline generic_vec4& operator-=(const T& o) { x-=o; y-=o; z-=o; w-=o; return *this; } + inline generic_vec4& operator*=(const T& o) { x*=o; y*=o; z*=o; w*=o; return *this; } + inline generic_vec4& operator/=(const T& o) { x/=o; y/=o; z/=o; w/=o; return *this; } + + inline bool operator==(const generic_vec4& o) const { return (x==o.x) and (y==o.y) and (z==o.z) and (w==o.w); } + inline bool operator!=(const generic_vec4& o) const { return (x!=o.x) or (y!=o.y) or (z!=o.z) or (w!=o.w); } + inline T operator[](int index) const { return (&x)[index]; } + inline T& operator[](int index) { return (&x)[index]; } +}; +typedef generic_vec4 bvec4; +typedef generic_vec4 ivec4; +typedef generic_vec4 uvec4; +typedef generic_vec4 vec4; +typedef generic_vec4 ubvec4; +typedef ubvec4 Color; + +/////////////////////////////////////////////////////////////////////////////// +// 2D vector +/////////////////////////////////////////////////////////////////////////////// +struct Vector2 +{ + float x; + float y; + + // ctors + Vector2() : x(0), y(0) {}; + Vector2(float x, float y) : x(x), y(y) {}; + + // utils functions + void set(float x, float y); + float length() const; // + float distance(const Vector2& vec) const; // distance between two vectors + Vector2& normalize(); // + float dot(const Vector2& vec) const; // dot product + bool equal(const Vector2& vec, float e) const; // compare with epsilon + bool inside(const Vector2& pos, const Vector2& size) const; + + // operators + Vector2 operator-() const; // unary operator (negate) + Vector2 operator+(const Vector2& rhs) const; // add rhs + Vector2 operator-(const Vector2& rhs) const; // subtract rhs + Vector2& operator+=(const Vector2& rhs); // add rhs and update this object + Vector2& operator-=(const Vector2& rhs); // subtract rhs and update this object + Vector2 operator*(const float scale) const; // scale + Vector2 operator*(const Vector2& rhs) const; // multiply each element + Vector2& operator*=(const float scale); // scale and update this object + Vector2& operator*=(const Vector2& rhs); // multiply each element and update this object + Vector2 operator/(const float scale) const; // inverse scale + Vector2& operator/=(const float scale); // scale and update this object + bool operator==(const Vector2& rhs) const; // exact compare, no epsilon + bool operator!=(const Vector2& rhs) const; // exact compare, no epsilon + bool operator<(const Vector2& rhs) const; // comparison for sort + float operator[](int index) const; // subscript operator v[0], v[1] + float& operator[](int index); // subscript operator v[0], v[1] + + friend Vector2 operator*(const float a, const Vector2 vec); + friend std::ostream& operator<<(std::ostream& os, const Vector2& vec); +}; + + + +/////////////////////////////////////////////////////////////////////////////// +// 3D vector +/////////////////////////////////////////////////////////////////////////////// +struct Vector3 +{ + float x; + float y; + float z; + + // ctors + Vector3() : x(0), y(0), z(0) {}; + Vector3(float x, float y, float z) : x(x), y(y), z(z) {}; + + // utils functions + void set(float x, float y, float z); + float length() const; // + float distance(const Vector3& vec) const; // distance between two vectors + Vector3& normalize(); // + float dot(const Vector3& vec) const; // dot product + Vector3 cross(const Vector3& vec) const; // cross product + bool equal(const Vector3& vec, float e) const; // compare with epsilon + + // operators + Vector3 operator-() const; // unary operator (negate) + Vector3 operator+(const Vector3& rhs) const; // add rhs + Vector3 operator-(const Vector3& rhs) const; // subtract rhs + Vector3& operator+=(const Vector3& rhs); // add rhs and update this object + Vector3& operator-=(const Vector3& rhs); // subtract rhs and update this object + Vector3 operator*(const float scale) const; // scale + Vector3 operator*(const Vector3& rhs) const; // multiplay each element + Vector3& operator*=(const float scale); // scale and update this object + Vector3& operator*=(const Vector3& rhs); // product each element and update this object + Vector3 operator/(const float scale) const; // inverse scale + Vector3& operator/=(const float scale); // scale and update this object + bool operator==(const Vector3& rhs) const; // exact compare, no epsilon + bool operator!=(const Vector3& rhs) const; // exact compare, no epsilon + bool operator<(const Vector3& rhs) const; // comparison for sort + float operator[](int index) const; // subscript operator v[0], v[1] + float& operator[](int index); // subscript operator v[0], v[1] + + friend Vector3 operator*(const float a, const Vector3 vec); + friend std::ostream& operator<<(std::ostream& os, const Vector3& vec); +}; + + + +/////////////////////////////////////////////////////////////////////////////// +// 4D vector +/////////////////////////////////////////////////////////////////////////////// +struct Vector4 +{ + float x; + float y; + float z; + float w; + + // ctors + Vector4() : x(0), y(0), z(0), w(0) {}; + Vector4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}; + Vector4(Vector3 v, float w = 1) : x(v.x), y(v.y), z(v.z), w(w) {}; + Vector4(Vector2 a, Vector2 b) : x(a.x), y(a.y), z(b.x), w(b.y) {}; + + // utils functions + void set(float x, float y, float z, float w); + float length() const; // + float distance(const Vector4& vec) const; // distance between two vectors + Vector4& normalize(); // + float dot(const Vector4& vec) const; // dot product + bool equal(const Vector4& vec, float e) const; // compare with epsilon + + // operators + Vector4 operator-() const; // unary operator (negate) + Vector4 operator+(const Vector4& rhs) const; // add rhs + Vector4 operator-(const Vector4& rhs) const; // subtract rhs + Vector4& operator+=(const Vector4& rhs); // add rhs and update this object + Vector4& operator-=(const Vector4& rhs); // subtract rhs and update this object + Vector4 operator*(const float scale) const; // scale + Vector4 operator*(const Vector4& rhs) const; // multiply each element + Vector4& operator*=(const float scale); // scale and update this object + Vector4& operator*=(const Vector4& rhs); // multiply each element and update this object + Vector4 operator/(const float scale) const; // inverse scale + Vector4& operator/=(const float scale); // scale and update this object + bool operator==(const Vector4& rhs) const; // exact compare, no epsilon + bool operator!=(const Vector4& rhs) const; // exact compare, no epsilon + bool operator<(const Vector4& rhs) const; // comparison for sort + float operator[](int index) const; // subscript operator v[0], v[1] + float& operator[](int index); // subscript operator v[0], v[1] + + Vector2 GetPosition() { return Vector2(x, y); } + Vector2 GetSize() { return Vector2(z, w); } + + friend Vector4 operator*(const float a, const Vector4 vec); + friend std::ostream& operator<<(std::ostream& os, const Vector4& vec); +}; + + + +// fast math routines from Doom3 SDK +inline float invSqrt(float x) +{ + float xhalf = 0.5f * x; + int i = *(int*)&x; // get bits for floating value + i = 0x5f3759df - (i>>1); // gives initial guess + x = *(float*)&i; // convert bits back to float + x = x * (1.5f - xhalf*x*x); // Newton step + return x; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// inline functions for Vector2 +/////////////////////////////////////////////////////////////////////////////// +inline Vector2 Vector2::operator-() const { + return Vector2(-x, -y); +} + +inline Vector2 Vector2::operator+(const Vector2& rhs) const { + return Vector2(x+rhs.x, y+rhs.y); +} + +inline Vector2 Vector2::operator-(const Vector2& rhs) const { + return Vector2(x-rhs.x, y-rhs.y); +} + +inline Vector2& Vector2::operator+=(const Vector2& rhs) { + x += rhs.x; y += rhs.y; return *this; +} + +inline Vector2& Vector2::operator-=(const Vector2& rhs) { + x -= rhs.x; y -= rhs.y; return *this; +} + +inline Vector2 Vector2::operator*(const float a) const { + return Vector2(x*a, y*a); +} + +inline Vector2 Vector2::operator*(const Vector2& rhs) const { + return Vector2(x*rhs.x, y*rhs.y); +} + +inline Vector2& Vector2::operator*=(const float a) { + x *= a; y *= a; return *this; +} + +inline Vector2& Vector2::operator*=(const Vector2& rhs) { + x *= rhs.x; y *= rhs.y; return *this; +} + +inline Vector2 Vector2::operator/(const float a) const { + return Vector2(x/a, y/a); +} + +inline Vector2& Vector2::operator/=(const float a) { + x /= a; y /= a; return *this; +} + +inline bool Vector2::operator==(const Vector2& rhs) const { + return (x == rhs.x) && (y == rhs.y); +} + +inline bool Vector2::operator!=(const Vector2& rhs) const { + return (x != rhs.x) || (y != rhs.y); +} + +inline bool Vector2::operator<(const Vector2& rhs) const { + if(x < rhs.x) return true; + if(x > rhs.x) return false; + if(y < rhs.y) return true; + if(y > rhs.y) return false; + return false; +} + +inline float Vector2::operator[](int index) const { + return (&x)[index]; +} + +inline float& Vector2::operator[](int index) { + return (&x)[index]; +} + +inline void Vector2::set(float x, float y) { + this->x = x; this->y = y; +} + +inline float Vector2::length() const { + return sqrtf(x*x + y*y); +} + +inline float Vector2::distance(const Vector2& vec) const { + return sqrtf((vec.x-x)*(vec.x-x) + (vec.y-y)*(vec.y-y)); +} + +inline Vector2& Vector2::normalize() { + //@@const float EPSILON = 0.000001f; + float xxyy = x*x + y*y; + //@@if(xxyy < EPSILON) + //@@ return *this; + + //float invLength = invSqrt(xxyy); + float invLength = 1.0f / sqrtf(xxyy); + x *= invLength; + y *= invLength; + return *this; +} + +inline float Vector2::dot(const Vector2& rhs) const { + return (x*rhs.x + y*rhs.y); +} + +inline bool Vector2::equal(const Vector2& rhs, float epsilon) const { + return fabs(x - rhs.x) < epsilon && fabs(y - rhs.y) < epsilon; +} + +inline bool Vector2::inside(const Vector2& pos, const Vector2& size) const { + return (x >= pos.x and y >= pos.y and x <= (pos.x+size.x) and y <= (pos.y+size.y)); +} + +inline Vector2 operator*(const float a, const Vector2 vec) { + return Vector2(a*vec.x, a*vec.y); +} + +inline std::ostream& operator<<(std::ostream& os, const Vector2& vec) { + os << "(" << vec.x << ", " << vec.y << ")"; + return os; +} +// END OF VECTOR2 ///////////////////////////////////////////////////////////// + + + + +/////////////////////////////////////////////////////////////////////////////// +// inline functions for Vector3 +/////////////////////////////////////////////////////////////////////////////// +inline Vector3 Vector3::operator-() const { + return Vector3(-x, -y, -z); +} + +inline Vector3 Vector3::operator+(const Vector3& rhs) const { + return Vector3(x+rhs.x, y+rhs.y, z+rhs.z); +} + +inline Vector3 Vector3::operator-(const Vector3& rhs) const { + return Vector3(x-rhs.x, y-rhs.y, z-rhs.z); +} + +inline Vector3& Vector3::operator+=(const Vector3& rhs) { + x += rhs.x; y += rhs.y; z += rhs.z; return *this; +} + +inline Vector3& Vector3::operator-=(const Vector3& rhs) { + x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this; +} + +inline Vector3 Vector3::operator*(const float a) const { + return Vector3(x*a, y*a, z*a); +} + +inline Vector3 Vector3::operator*(const Vector3& rhs) const { + return Vector3(x*rhs.x, y*rhs.y, z*rhs.z); +} + +inline Vector3& Vector3::operator*=(const float a) { + x *= a; y *= a; z *= a; return *this; +} + +inline Vector3& Vector3::operator*=(const Vector3& rhs) { + x *= rhs.x; y *= rhs.y; z *= rhs.z; return *this; +} + +inline Vector3 Vector3::operator/(const float a) const { + return Vector3(x/a, y/a, z/a); +} + +inline Vector3& Vector3::operator/=(const float a) { + x /= a; y /= a; z /= a; return *this; +} + +inline bool Vector3::operator==(const Vector3& rhs) const { + return (x == rhs.x) && (y == rhs.y) && (z == rhs.z); +} + +inline bool Vector3::operator!=(const Vector3& rhs) const { + return (x != rhs.x) || (y != rhs.y) || (z != rhs.z); +} + +inline bool Vector3::operator<(const Vector3& rhs) const { + if(x < rhs.x) return true; + if(x > rhs.x) return false; + if(y < rhs.y) return true; + if(y > rhs.y) return false; + if(z < rhs.z) return true; + if(z > rhs.z) return false; + return false; +} + +inline float Vector3::operator[](int index) const { + return (&x)[index]; +} + +inline float& Vector3::operator[](int index) { + return (&x)[index]; +} + +inline void Vector3::set(float x, float y, float z) { + this->x = x; this->y = y; this->z = z; +} + +inline float Vector3::length() const { + return sqrtf(x*x + y*y + z*z); +} + +inline float Vector3::distance(const Vector3& vec) const { + return sqrtf((vec.x-x)*(vec.x-x) + (vec.y-y)*(vec.y-y) + (vec.z-z)*(vec.z-z)); +} + +inline Vector3& Vector3::normalize() { + //@@const float EPSILON = 0.000001f; + float xxyyzz = x*x + y*y + z*z; + //@@if(xxyyzz < EPSILON) + //@@ return *this; // do nothing if it is ~zero vector + + //float invLength = invSqrt(xxyyzz); + float invLength = 1.0f / sqrtf(xxyyzz); + x *= invLength; + y *= invLength; + z *= invLength; + return *this; +} + +inline float Vector3::dot(const Vector3& rhs) const { + return (x*rhs.x + y*rhs.y + z*rhs.z); +} + +inline Vector3 Vector3::cross(const Vector3& rhs) const { + return Vector3(y*rhs.z - z*rhs.y, z*rhs.x - x*rhs.z, x*rhs.y - y*rhs.x); +} + +inline bool Vector3::equal(const Vector3& rhs, float epsilon) const { + return fabs(x - rhs.x) < epsilon && fabs(y - rhs.y) < epsilon && fabs(z - rhs.z) < epsilon; +} + +inline Vector3 operator*(const float a, const Vector3 vec) { + return Vector3(a*vec.x, a*vec.y, a*vec.z); +} + +inline std::ostream& operator<<(std::ostream& os, const Vector3& vec) { + os << "(" << vec.x << ", " << vec.y << ", " << vec.z << ")"; + return os; +} +// END OF VECTOR3 ///////////////////////////////////////////////////////////// + + + +/////////////////////////////////////////////////////////////////////////////// +// inline functions for Vector4 +/////////////////////////////////////////////////////////////////////////////// +inline Vector4 Vector4::operator-() const { + return Vector4(-x, -y, -z, -w); +} + +inline Vector4 Vector4::operator+(const Vector4& rhs) const { + return Vector4(x+rhs.x, y+rhs.y, z+rhs.z, w+rhs.w); +} + +inline Vector4 Vector4::operator-(const Vector4& rhs) const { + return Vector4(x-rhs.x, y-rhs.y, z-rhs.z, w-rhs.w); +} + +inline Vector4& Vector4::operator+=(const Vector4& rhs) { + x += rhs.x; y += rhs.y; z += rhs.z; w += rhs.w; return *this; +} + +inline Vector4& Vector4::operator-=(const Vector4& rhs) { + x -= rhs.x; y -= rhs.y; z -= rhs.z; w -= rhs.w; return *this; +} + +inline Vector4 Vector4::operator*(const float a) const { + return Vector4(x*a, y*a, z*a, w*a); +} + +inline Vector4 Vector4::operator*(const Vector4& rhs) const { + return Vector4(x*rhs.x, y*rhs.y, z*rhs.z, w*rhs.w); +} + +inline Vector4& Vector4::operator*=(const float a) { + x *= a; y *= a; z *= a; w *= a; return *this; +} + +inline Vector4& Vector4::operator*=(const Vector4& rhs) { + x *= rhs.x; y *= rhs.y; z *= rhs.z; w *= rhs.w; return *this; +} + +inline Vector4 Vector4::operator/(const float a) const { + return Vector4(x/a, y/a, z/a, w/a); +} + +inline Vector4& Vector4::operator/=(const float a) { + x /= a; y /= a; z /= a; w /= a; return *this; +} + +inline bool Vector4::operator==(const Vector4& rhs) const { + return (x == rhs.x) && (y == rhs.y) && (z == rhs.z) && (w == rhs.w); +} + +inline bool Vector4::operator!=(const Vector4& rhs) const { + return (x != rhs.x) || (y != rhs.y) || (z != rhs.z) || (w != rhs.w); +} + +inline bool Vector4::operator<(const Vector4& rhs) const { + if(x < rhs.x) return true; + if(x > rhs.x) return false; + if(y < rhs.y) return true; + if(y > rhs.y) return false; + if(z < rhs.z) return true; + if(z > rhs.z) return false; + if(w < rhs.w) return true; + if(w > rhs.w) return false; + return false; +} + +inline float Vector4::operator[](int index) const { + return (&x)[index]; +} + +inline float& Vector4::operator[](int index) { + return (&x)[index]; +} + +inline void Vector4::set(float x, float y, float z, float w) { + this->x = x; this->y = y; this->z = z; this->w = w; +} + +inline float Vector4::length() const { + return sqrtf(x*x + y*y + z*z + w*w); +} + +inline float Vector4::distance(const Vector4& vec) const { + return sqrtf((vec.x-x)*(vec.x-x) + (vec.y-y)*(vec.y-y) + (vec.z-z)*(vec.z-z) + (vec.w-w)*(vec.w-w)); +} + +inline Vector4& Vector4::normalize() { + //NOTE: leave w-component untouched + //@@const float EPSILON = 0.000001f; + float xxyyzz = x*x + y*y + z*z; + //@@if(xxyyzz < EPSILON) + //@@ return *this; // do nothing if it is zero vector + + //float invLength = invSqrt(xxyyzz); + float invLength = 1.0f / sqrtf(xxyyzz); + x *= invLength; + y *= invLength; + z *= invLength; + return *this; +} + +inline float Vector4::dot(const Vector4& rhs) const { + return (x*rhs.x + y*rhs.y + z*rhs.z + w*rhs.w); +} + +inline bool Vector4::equal(const Vector4& rhs, float epsilon) const { + return fabs(x - rhs.x) < epsilon && fabs(y - rhs.y) < epsilon && + fabs(z - rhs.z) < epsilon && fabs(w - rhs.w) < epsilon; +} + +inline Vector4 operator*(const float a, const Vector4 vec) { + return Vector4(a*vec.x, a*vec.y, a*vec.z, a*vec.w); +} + +inline std::ostream& operator<<(std::ostream& os, const Vector4& vec) { + os << "(" << vec.x << ", " << vec.y << ", " << vec.z << ", " << vec.w << ")"; + return os; +} +// END OF VECTOR4 ///////////////////////////////////////////////////////////// + +#endif diff --git a/application.cpp b/application.cpp new file mode 100644 index 0000000..4f675ad --- /dev/null +++ b/application.cpp @@ -0,0 +1,41 @@ +#include "application.h" + +//#include "model.h" +//#include "console.h" + +namespace Application +{ + bool quit {false}; + char dirty {1}; + + const bool& ShouldQuit() + { + return quit; + } + + void Quit() + { + //if (Model::IsModified()) { + // Console::Ask("SAVE BEFORE QUITTING (YES/NO/CANCEL)?", Application::QuitHandler); + //} else { + quit = true; + //} + } + + void QuitHandler(const char* reply) + { + //if (reply[0] == 'Y') { + // Model::Save(); + //} else if (reply[0] == 'N') { + // quit = true; + //} + } + + bool NeedsUpdate(const char cycles) + { + if (cycles > 0 and cycles > dirty) dirty = cycles; + return dirty > 0; + } + + void Updated() { if (dirty > 0) dirty--; } +} diff --git a/application.h b/application.h new file mode 100644 index 0000000..e7a3ab4 --- /dev/null +++ b/application.h @@ -0,0 +1,10 @@ +#pragma once + +namespace Application +{ + void Quit(); + const bool& ShouldQuit(); + void QuitHandler(const char* reply); + bool NeedsUpdate(const char cycles = 2); + void Updated(); +} diff --git a/common.h b/common.h new file mode 100644 index 0000000..ea6580c --- /dev/null +++ b/common.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "Vectors.h" +#include "Matrices.h" +#include "geometry.h" + +#define QUERY 0 +#define ON 1 +#define OFF 2 +#define YES ON +#define NO OFF + +#define Swap(x, y) { x -= y = (x += y) - y; } \ No newline at end of file diff --git a/font.cpp b/font.cpp new file mode 100644 index 0000000..d21cc02 --- /dev/null +++ b/font.cpp @@ -0,0 +1,108 @@ +#include "font.h" +#include +#include +#include +#include +#include "texture.h" +#include "renderer.h" + +static float colors[16][4] = { + {0, 0, 0, 1}, + {0.5f, 0, 0, 1}, + {0, 0.5f, 0, 1}, + {0.5f, 0.5f, 0, 1}, + {0, 0, 0.5f, 1}, + {0.5f, 0, 0.5f, 1}, + {0, 0.5f, 0.5f, 1}, + {0.75f, 0.75f, 0.75f, 1}, + {0.5f, 0.5f, 0.5f, 1}, + {1, 0, 0, 1}, + {0, 1, 0, 1}, + {1, 1, 0, 1}, + {0, 0, 1, 1}, + {1, 0, 1, 1}, + {0, 1, 1, 1}, + {1, 1, 1, 1}, +}; + +void Font::Load(const char* filename) +{ + char name[16]; strcpy(name, filename); strcat(name, ".fnt"); + + FILE* fin = fopen(name, "ra"); + if (fin == NULL) return; + + char line[256]; + + fgets(line, 255, fin); + fgets(line, 255, fin); + int lh, sw, sh; + assert(sscanf(line, "common lineHeight=%i base=%*i scaleW=%i scaleH=%i", &lh, &sw, &sh) == 3); + lineHeight = lh; + bw = sw; + bh = sh; + + fgets(line, 255, fin); + fgets(line, 255, fin); + int numChars; + assert(sscanf(line, "chars count=%i", &numChars) == 1); + + for (int i = 0; i < numChars; i++) { + fgets(line, 255, fin); + int id, x, y, w, h, xo, yo, xa; + assert(sscanf(line, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i", &id, &x, &y, &w, &h, &xo, &yo, &xa) == 8); + FontChar& c = chars[id-32]; + c.w = w; + c.h = h; + c.x1 = float(x)/float(bw); + c.y1 = float(y)/float(bh); + c.x2 = c.x1 + float(w)/float(bw); + c.y2 = c.y1 + float(h)/float(bh);; + c.xo = xo; + c.yo = yo; + c.xa = xa; + } + + texture = Texture::Create(); + Texture::Load(texture, filename); + fclose(fin); +} + +void Font::Print(int x, int y, const char* text, char color) +{ + const unsigned long len = strlen(text); + + Renderer::SetState(RENDER_FILL | RENDER_BLEND | RENDER_TEXTURE); + glBindTexture(GL_TEXTURE_2D, texture); + glBegin(GL_QUADS); + glColor4fv(colors[color]); + for (int i = 0; i < len; i++) { + FontChar& c = chars[text[i]-32]; + glTexCoord2f(c.x1, c.y1); + glVertex2i(x+c.xo, y+c.yo); + glTexCoord2f(c.x1, c.y2); + glVertex2i(x+c.xo, y+c.yo+c.h); + glTexCoord2f(c.x2, c.y2); + glVertex2i(x+c.xo+c.w, y+c.yo+c.h); + glTexCoord2f(c.x2, c.y1); + glVertex2i(x+c.xo+c.w, y+c.yo); + x += c.xa; + } + glEnd(); + +} + +Vector2 Font::GetSize(const char* text) +{ + Vector2 size; + size.y = lineHeight; + const unsigned long len = strlen(text); + for (int i = 0; i < len; i++) { + //if (i == len-1) { + // size.x += chars[text[i]-32].w; + //} else { + size.x += chars[text[i]-32].xa; + //} + } + return size; +} diff --git a/font.fnt b/font.fnt new file mode 100644 index 0000000..57867f3 --- /dev/null +++ b/font.fnt @@ -0,0 +1,99 @@ +info face="Segoe UI" size=14 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=0,0 +common lineHeight=19 base=15 scaleW=128 scaleH=128 pages=1 packed=0 +page id=0 file="font.png" +chars count=95 +char id=32 x=40 y=76 width=0 height=0 xoffset=0 yoffset=15 xadvance=4 page=0 chnl=0 letter="space" +char id=33 x=58 y=54 width=4 height=12 xoffset=1 yoffset=3 xadvance=4 page=0 chnl=0 letter="!" +char id=34 x=121 y=66 width=6 height=6 xoffset=1 yoffset=2 xadvance=5 page=0 chnl=0 letter=""" +char id=35 x=62 y=54 width=10 height=11 xoffset=0 yoffset=3 xadvance=8 page=0 chnl=0 letter="#" +char id=36 x=22 y=0 width=8 height=15 xoffset=1 yoffset=1 xadvance=8 page=0 chnl=0 letter="$" +char id=37 x=94 y=0 width=13 height=13 xoffset=1 yoffset=2 xadvance=11 page=0 chnl=0 letter="%" +char id=38 x=107 y=0 width=13 height=13 xoffset=1 yoffset=2 xadvance=11 page=0 chnl=0 letter="&" +char id=39 x=4 y=76 width=4 height=6 xoffset=1 yoffset=2 xadvance=3 page=0 chnl=0 letter="'" +char id=40 x=30 y=0 width=6 height=15 xoffset=1 yoffset=2 xadvance=4 page=0 chnl=0 letter="(" +char id=41 x=36 y=0 width=6 height=15 xoffset=0 yoffset=2 xadvance=4 page=0 chnl=0 letter=")" +char id=42 x=105 y=66 width=7 height=7 xoffset=1 yoffset=3 xadvance=6 page=0 chnl=0 letter="*" +char id=43 x=63 y=66 width=9 height=9 xoffset=2 yoffset=5 xadvance=10 page=0 chnl=0 letter="+" +char id=44 x=0 y=76 width=4 height=6 xoffset=0 yoffset=11 xadvance=3 page=0 chnl=0 letter="," +char id=45 x=34 y=76 width=6 height=3 xoffset=1 yoffset=9 xadvance=6 page=0 chnl=0 letter="-" +char id=46 x=22 y=76 width=4 height=4 xoffset=1 yoffset=11 xadvance=3 page=0 chnl=0 letter="." +char id=47 x=78 y=0 width=8 height=14 xoffset=-0 yoffset=3 xadvance=5 page=0 chnl=0 letter="/" +char id=48 x=59 y=16 width=9 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="0" +char id=49 x=32 y=29 width=8 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="1" +char id=50 x=115 y=42 width=8 height=12 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=0 letter="2" +char id=51 x=8 y=29 width=8 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="3" +char id=52 x=88 y=42 width=9 height=12 xoffset=0 yoffset=3 xadvance=8 page=0 chnl=0 letter="4" +char id=53 x=8 y=54 width=8 height=12 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=0 letter="5" +char id=54 x=68 y=16 width=9 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="6" +char id=55 x=106 y=42 width=9 height=12 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=0 letter="7" +char id=56 x=77 y=16 width=9 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="8" +char id=57 x=86 y=16 width=9 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="9" +char id=58 x=29 y=66 width=4 height=10 xoffset=1 yoffset=5 xadvance=3 page=0 chnl=0 letter=":" +char id=59 x=72 y=54 width=5 height=11 xoffset=0 yoffset=6 xadvance=3 page=0 chnl=0 letter=";" +char id=60 x=72 y=66 width=8 height=9 xoffset=2 yoffset=5 xadvance=10 page=0 chnl=0 letter="<" +char id=61 x=112 y=66 width=9 height=6 xoffset=2 yoffset=7 xadvance=10 page=0 chnl=0 letter="=" +char id=62 x=80 y=66 width=8 height=9 xoffset=2 yoffset=5 xadvance=10 page=0 chnl=0 letter=">" +char id=63 x=40 y=29 width=7 height=13 xoffset=1 yoffset=2 xadvance=6 page=0 chnl=0 letter="?" +char id=64 x=64 y=0 width=14 height=14 xoffset=1 yoffset=2 xadvance=13 page=0 chnl=0 letter="@" +char id=65 x=89 y=29 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0 letter="A" +char id=66 x=97 y=42 width=9 height=12 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=0 letter="B" +char id=67 x=22 y=16 width=10 height=13 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0 letter="C" +char id=68 x=111 y=29 width=10 height=12 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=0 letter="D" +char id=69 x=16 y=54 width=8 height=12 xoffset=1 yoffset=3 xadvance=7 page=0 chnl=0 letter="E" +char id=70 x=32 y=54 width=7 height=12 xoffset=1 yoffset=3 xadvance=7 page=0 chnl=0 letter="F" +char id=71 x=12 y=16 width=10 height=13 xoffset=1 yoffset=2 xadvance=10 page=0 chnl=0 letter="G" +char id=72 x=40 y=42 width=10 height=12 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=0 letter="H" +char id=73 x=52 y=54 width=6 height=12 xoffset=0 yoffset=3 xadvance=4 page=0 chnl=0 letter="I" +char id=74 x=46 y=54 width=6 height=12 xoffset=0 yoffset=3 xadvance=5 page=0 chnl=0 letter="J" +char id=75 x=79 y=42 width=9 height=12 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=0 letter="K" +char id=76 x=24 y=54 width=8 height=12 xoffset=1 yoffset=3 xadvance=7 page=0 chnl=0 letter="L" +char id=77 x=77 y=29 width=12 height=12 xoffset=1 yoffset=3 xadvance=13 page=0 chnl=0 letter="M" +char id=78 x=0 y=42 width=10 height=12 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=0 letter="N" +char id=79 x=0 y=16 width=12 height=13 xoffset=1 yoffset=2 xadvance=11 page=0 chnl=0 letter="O" +char id=80 x=0 y=54 width=8 height=12 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=0 letter="P" +char id=81 x=10 y=0 width=12 height=15 xoffset=1 yoffset=3 xadvance=11 page=0 chnl=0 letter="Q" +char id=82 x=50 y=42 width=10 height=12 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=0 letter="R" +char id=83 x=0 y=29 width=8 height=13 xoffset=1 yoffset=2 xadvance=7 page=0 chnl=0 letter="S" +char id=84 x=70 y=42 width=9 height=12 xoffset=0 yoffset=3 xadvance=7 page=0 chnl=0 letter="T" +char id=85 x=20 y=42 width=10 height=12 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=0 letter="U" +char id=86 x=100 y=29 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0 letter="V" +char id=87 x=62 y=29 width=15 height=12 xoffset=0 yoffset=3 xadvance=13 page=0 chnl=0 letter="W" +char id=88 x=10 y=42 width=10 height=12 xoffset=0 yoffset=3 xadvance=8 page=0 chnl=0 letter="X" +char id=89 x=30 y=42 width=10 height=12 xoffset=0 yoffset=3 xadvance=8 page=0 chnl=0 letter="Y" +char id=90 x=60 y=42 width=10 height=12 xoffset=0 yoffset=3 xadvance=8 page=0 chnl=0 letter="Z" +char id=91 x=54 y=0 width=5 height=15 xoffset=1 yoffset=2 xadvance=4 page=0 chnl=0 letter="[" +char id=92 x=86 y=0 width=8 height=14 xoffset=-0 yoffset=3 xadvance=5 page=0 chnl=0 letter="\" +char id=93 x=59 y=0 width=5 height=15 xoffset=0 yoffset=2 xadvance=4 page=0 chnl=0 letter="]" +char id=94 x=96 y=66 width=9 height=8 xoffset=2 yoffset=3 xadvance=10 page=0 chnl=0 letter="^" +char id=95 x=26 y=76 width=8 height=3 xoffset=0 yoffset=14 xadvance=6 page=0 chnl=0 letter="_" +char id=96 x=17 y=76 width=5 height=5 xoffset=1 yoffset=2 xadvance=4 page=0 chnl=0 letter="`" +char id=97 x=107 y=54 width=8 height=10 xoffset=1 yoffset=5 xadvance=7 page=0 chnl=0 letter="a" +char id=98 x=41 y=16 width=9 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="b" +char id=99 x=8 y=66 width=8 height=10 xoffset=1 yoffset=5 xadvance=6 page=0 chnl=0 letter="c" +char id=100 x=50 y=16 width=9 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="d" +char id=101 x=98 y=54 width=9 height=10 xoffset=1 yoffset=5 xadvance=7 page=0 chnl=0 letter="e" +char id=102 x=47 y=29 width=7 height=13 xoffset=0 yoffset=2 xadvance=4 page=0 chnl=0 letter="f" +char id=103 x=95 y=16 width=9 height=13 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=0 letter="g" +char id=104 x=24 y=29 width=8 height=13 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=0 letter="h" +char id=105 x=54 y=29 width=4 height=13 xoffset=1 yoffset=2 xadvance=3 page=0 chnl=0 letter="i" +char id=106 x=0 y=0 width=6 height=16 xoffset=-1 yoffset=2 xadvance=3 page=0 chnl=0 letter="j" +char id=107 x=16 y=29 width=8 height=13 xoffset=1 yoffset=2 xadvance=7 page=0 chnl=0 letter="k" +char id=108 x=58 y=29 width=4 height=13 xoffset=1 yoffset=2 xadvance=3 page=0 chnl=0 letter="l" +char id=109 x=77 y=54 width=12 height=10 xoffset=1 yoffset=5 xadvance=12 page=0 chnl=0 letter="m" +char id=110 x=115 y=54 width=8 height=10 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=0 letter="n" +char id=111 x=89 y=54 width=9 height=10 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=0 letter="o" +char id=112 x=104 y=16 width=9 height=13 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=0 letter="p" +char id=113 x=113 y=16 width=9 height=13 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=0 letter="q" +char id=114 x=23 y=66 width=6 height=10 xoffset=1 yoffset=5 xadvance=5 page=0 chnl=0 letter="r" +char id=115 x=16 y=66 width=7 height=10 xoffset=1 yoffset=5 xadvance=6 page=0 chnl=0 letter="s" +char id=116 x=39 y=54 width=7 height=12 xoffset=0 yoffset=3 xadvance=5 page=0 chnl=0 letter="t" +char id=117 x=0 y=66 width=8 height=10 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=0 letter="u" +char id=118 x=45 y=66 width=9 height=9 xoffset=0 yoffset=6 xadvance=7 page=0 chnl=0 letter="v" +char id=119 x=33 y=66 width=12 height=9 xoffset=0 yoffset=6 xadvance=10 page=0 chnl=0 letter="w" +char id=120 x=54 y=66 width=9 height=9 xoffset=0 yoffset=6 xadvance=6 page=0 chnl=0 letter="x" +char id=121 x=32 y=16 width=9 height=13 xoffset=0 yoffset=5 xadvance=7 page=0 chnl=0 letter="y" +char id=122 x=88 y=66 width=8 height=9 xoffset=0 yoffset=6 xadvance=6 page=0 chnl=0 letter="z" +char id=123 x=42 y=0 width=6 height=15 xoffset=1 yoffset=2 xadvance=4 page=0 chnl=0 letter="{" +char id=124 x=6 y=0 width=4 height=16 xoffset=1 yoffset=2 xadvance=3 page=0 chnl=0 letter="|" +char id=125 x=48 y=0 width=6 height=15 xoffset=0 yoffset=2 xadvance=4 page=0 chnl=0 letter="}" +char id=126 x=8 y=76 width=9 height=5 xoffset=1 yoffset=7 xadvance=10 page=0 chnl=0 letter="~" diff --git a/font.h b/font.h new file mode 100644 index 0000000..4ab3957 --- /dev/null +++ b/font.h @@ -0,0 +1,21 @@ +#pragma once + +#include "common.h" + +struct FontChar +{ + float x1, y1, x2, y2; + char w, h, xo, yo, xa; +}; + +struct Font +{ + char lineHeight; + unsigned char bw, bh; + FontChar chars[96]; + unsigned texture; + + void Load(const char* filename); + void Print(int x, int y, const char* text, char color = 15); + Vector2 GetSize(const char* text); +}; \ No newline at end of file diff --git a/font.png b/font.png new file mode 100644 index 0000000..1556dbd Binary files /dev/null and b/font.png differ diff --git a/geometry.cpp b/geometry.cpp new file mode 100644 index 0000000..7b8c369 --- /dev/null +++ b/geometry.cpp @@ -0,0 +1,26 @@ +#include "geometry.h" + +Vector3 calculate_normal(const Vector3& v1, const Vector3& v2, const Vector3& v3) +{ + Vector3 normal; + const Vector3 U = v2 - v1; + const Vector3 V = v3 - v1; + + normal.x = U.y * V.z - U.z * V.y; + normal.y = U.z * V.x - U.x * V.z; + normal.z = U.x * V.y - U.y * V.x; + + return normal.normalize(); +} + +const float random(const float& rangeMin, const float& rangeMax) +{ + const float range = rangeMax - rangeMin; + return rangeMin + (float(rand())/float(RAND_MAX))*range; +} + +const float random_int(const int& rangeMin, const int& rangeMax) +{ + const int range = rangeMax - rangeMin; + return rangeMin + (rand() % range); +} diff --git a/geometry.h b/geometry.h new file mode 100644 index 0000000..22af219 --- /dev/null +++ b/geometry.h @@ -0,0 +1,85 @@ +#pragma once + +#include "Vectors.h" +#include +#include +#include + +#define MAX(x, y) x > y ? x : y +#define MIN(x, y) x < y ? x : y + +#define List std::list + +template +class Array +{ +public: + Array() : m_allocated(1000), m_dynamic(true) + { + m_values = (T*)malloc(sizeof(T)*m_allocated); + } + Array(int size, bool dynamic) : m_allocated(size), m_dynamic(dynamic) + { + m_values = (T*)malloc(sizeof(T)*m_allocated); + } + Array(const Array& other) : m_allocated(other.m_filled), m_dynamic(other.m_dynamic) + { + m_filled = other.m_filled; + m_allocated = other.m_filled; + m_values = (T*)malloc(sizeof(T)*m_allocated); + memcpy((T*)m_values, (T*)other.m_values, sizeof(T)*m_filled); + } + ~Array() { free(m_values); } + void Prealloc(int size) + { + m_allocated = size; + m_values = (T*)realloc(m_values, sizeof(T)*m_allocated); + } + const int push_back(const T& value) + { + if (m_filled >= m_allocated) { + if (not m_dynamic) {assert(false); } + if (m_allocated == 0) m_allocated = 1; else m_allocated*=2; + m_values = (T*)realloc(m_values, sizeof(T)*m_allocated); + } + m_values[m_filled++] = value; + + return m_filled-1; + } + void remove_simple(int index) + { + assert(index < m_filled); + m_filled--; + if (index != m_filled) m_values[index] = m_values[m_filled]; + } + const int exists(const T& value) const + { + for (int i = 0; i < m_filled; i++) if (m_values[i] == value) return i; + return -1; + } + void clear() { m_filled = 0; } + const unsigned size() const { return m_filled; } + const bool empty() const { return m_filled == 0; } + T operator[](int index) const { return m_values[index]; } + T& operator[](int index) { return m_values[index]; } + Array& operator=( const Array& other ) { + if (other.m_filled > m_allocated) { + m_values = (T*)realloc(m_values, sizeof(T)*other.m_filled); + } + m_allocated = other.m_filled; + m_filled = other.m_filled; + m_dynamic = other.m_dynamic; + memcpy(m_values, other.m_values, sizeof(T)*m_filled); + return *this; + } +private: + unsigned m_allocated{0}; + unsigned m_filled{0}; + bool m_dynamic{true}; + T* m_values{nullptr}; +}; + +Vector3 calculate_normal(const Vector3& v1, const Vector3& v2, const Vector3& v3); + +const float random(const float& rangeMin, const float& rangeMax); +const float random_int(const int& rangeMin, const int& rangeMax); diff --git a/grid.cpp b/grid.cpp new file mode 100644 index 0000000..4c39db5 --- /dev/null +++ b/grid.cpp @@ -0,0 +1,77 @@ +#include "grid.h" +#include + +//#include "viewport.h" +#include "renderer.h" + +namespace Grid +{ + bool visible = true; + + void Toggle() + { + visible = not visible; + } + + void Draw(const char viewport, int size) + { + if (not visible) return; + + int planeOffset = 0; + char axis = 1; //AXIS_Y; +/* Viewport::Viewport v = Viewport::GetViewport(viewport); + if (v.perspective == Viewport::Perspective::Ortho) { + if (v.yaw == 0 and v.pitch == 90) { + planeOffset = 999; + axis = AXIS_Y; + } else if (v.yaw == 0 and v.pitch == -90) { + planeOffset = -999; + axis = AXIS_Y; + } else if (v.yaw == 90 and v.pitch == 0) { + planeOffset = -999; + axis = AXIS_X; + } else if (v.yaw == -90 and v.pitch == 0) { + planeOffset = 999; + axis = AXIS_X; + } else if (v.yaw == 0 and v.pitch == 0) { + planeOffset = 999; + axis = AXIS_Z; + } else if (v.yaw == 180 and v.pitch == 0) { + planeOffset = -999; + axis = AXIS_Z; + } + } +*/ + const char axis_x = 0; //axis == AXIS_Z ? AXIS_X : axis == AXIS_X ? AXIS_Z : AXIS_X; + const char axis_y = 1; //axis == AXIS_Z ? AXIS_Y : axis == AXIS_X ? AXIS_Y : AXIS_Z; + + + Renderer::Push(); + Renderer::Light(OFF); + Renderer::Depth(OFF); + if (size == 0) size = planeOffset == 0 ? 20 : 30; + glBegin(GL_LINES); + for (int i = -size; i <= size; i++) { + if (i == 0) { + glColor4f(0.5f, 0.5f, 0.5f, 1); + } else { + glColor4f(0.25f, 0.25f, 0.25f, 1); + } + char pos[2][3] {{0,0,0}, {0,0,0}}; + pos[0][axis_x] = -size; + pos[1][axis_x] = size; + pos[0][axis_y] = pos[1][axis_y] = i; + pos[0][axis] = pos[1][axis] = planeOffset; + glVertex3f(pos[0][0], pos[0][1], pos[0][2]); + glVertex3f(pos[1][0], pos[1][1], pos[1][2]); + pos[0][axis_y] = -size; + pos[1][axis_y] = size; + pos[0][axis_x] = pos[1][axis_x] = i; + pos[0][axis] = pos[1][axis] = planeOffset; + glVertex3f(pos[0][0], pos[0][1], pos[0][2]); + glVertex3f(pos[1][0], pos[1][1], pos[1][2]); + } + glEnd(); + Renderer::Pop(); + } +} diff --git a/grid.h b/grid.h new file mode 100644 index 0000000..ac4db9c --- /dev/null +++ b/grid.h @@ -0,0 +1,7 @@ +#pragma once + +namespace Grid +{ + void Toggle(); + void Draw(const char viewport, int size = 0); +} diff --git a/lagueirtofile b/lagueirtofile new file mode 100644 index 0000000..eea0c0b --- /dev/null +++ b/lagueirtofile @@ -0,0 +1,5 @@ +libs = -lSDL2 -lGL +cppflags = -D DEBUG -g +executable = biomed_debug +sourcepath = . +buildpath = build diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0908fc0 --- /dev/null +++ b/main.cpp @@ -0,0 +1,156 @@ +#include "screen.h" +#include + +#include "application.h" +//#include "console.h" +//#include "interpreter.h" +//#include "worker.h" +//#include "layout.h" +//#include "texture.h" +//#include "turtle.h" +//#include "keystrokes.h" +#include "GUI.h" +#include "GUIMouse.h" +#include "GUIKeyboard.h" +#include "font.h" +#include "GUIViewport.h" +#include "Editor.h" +#include "texture.h" + +int main(int argc, const char* argv[]) +{ + // SDL INIT + + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { puts("SDL_Init error"); return -1; } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + + SDL_Window* gWindow = SDL_CreateWindow("BiomED", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + if (gWindow == NULL) { puts("SDL_CreateWindow error"); return -1; } + + SDL_GLContext gContext = SDL_GL_CreateContext(gWindow); + if (gContext == NULL) { puts("SDL_GL_CreateContext error"); return -1; } + + if (SDL_GL_SetSwapInterval(1) < 0) { puts("WARNING: no vsync!"); } + + + // OPENGL INIT + + glClearColor(0.12f, 0.12f, 0.12f, 1.0f); + glDisable(GL_DEPTH_TEST); + glClearDepth(1.0f); + glDepthFunc(GL_LEQUAL); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glEnable(GL_NORMALIZE); + glEnable(GL_CULL_FACE); + + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + glEnable(GL_LIGHT0); + //static const GLfloat LightPosition[]= {0.408248f, 0.408248f, -0.816497f, 0.0f};//{ 0.5f, 0.5f, 1.0f, 0.0f }; // 5 4 2 0 + static const GLfloat LightPosition[]= {0, 0, -1.0f, 0.0f};//{ 0.5f, 0.5f, 1.0f, 0.0f }; // 5 4 2 0 + static const GLfloat LightAmbient[]= { 0.2f, 0.2f, 0.2f, 1.0f }; + static const GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; + glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse); + glLightfv(GL_LIGHT0, GL_POSITION, LightPosition); + + + // SUBSYSTEMS INIT + + //Console::Init(); + //Worker::RegisterCommands(); + //Layout::Load("_last", SILENT); + //Texture::texture = Texture::Create(); + //Turtle::Clear(); + + Editor::texture = Texture::Create(32, 32); + for (int i = 0; i < 32*32*4; i++) Editor::bitmap[i] = 255; + Texture::Update(Editor::texture, 32, 32, Editor::bitmap); + + Font* f = new Font(); + f->Load("font"); + + viewports[1].rotation = {0,0}; + + // MAIN LOOP + + SDL_Event e; + while (not Application::ShouldQuit()) { + + // SDL EVENTS + + while (SDL_PollEvent(&e) != 0) { + if (e.type == SDL_QUIT) { + Application::Quit(); + } else if (e.type == SDL_WINDOWEVENT) { + if (e.window.event == SDL_WINDOWEVENT_RESIZED) { + SCREEN_WIDTH = e.window.data1; + SCREEN_HEIGHT = e.window.data2; + //GUIElement::root->SetSize(Vector2(e.window.data1, e.window.data2)); + //} else if (e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { + // GUIState::NeedsRedraw(); + } + } else if (e.type == SDL_MOUSEMOTION) { + GUIMouse::ReceiveMouseMoveEvent(e.motion.x, e.motion.y); + } else if (e.type == SDL_MOUSEBUTTONDOWN) { + GUIMouse::Button b = e.button.button == 1 ? GUIMouse::Button::Left : e.button.button == 2 ? GUIMouse::Button::Middle : GUIMouse::Button::Right; + GUIMouse::ReceiveMouseDownEvent(b); + } else if (e.type == SDL_MOUSEBUTTONUP) { + GUIMouse::Button b = e.button.button == 1 ? GUIMouse::Button::Left : e.button.button == 2 ? GUIMouse::Button::Middle : GUIMouse::Button::Right; + GUIMouse::ReceiveMouseUpEvent(b); + } else if (e.type == SDL_KEYDOWN) { + GUIKeyboard::ReceiveKeyboardEvent(e.key.keysym.sym); + } else if (e.type == SDL_KEYUP) { + GUIKeyboard::ReceiveKeyboardEvent(SDLK_UNKNOWN); + } else if (e.type == SDL_MOUSEWHEEL) { + GUIMouse::ReceiveMouseWheelEvent(e.wheel.x, e.wheel.y); + } else if (e.type == SDL_MULTIGESTURE) { + printf("GESTURE: %.2f, %.2f, %i\n", e.mgesture.dTheta*100, e.mgesture.dDist*100, e.mgesture.numFingers); + GUIMouse::ReceiveMultigestureEvent(e.mgesture.dTheta, e.mgesture.dDist, e.mgesture.numFingers); + } + /*if (Console::Enable(QUERY)) { + if (e.type == SDL_TEXTINPUT) { + Console::PutChar(toupper(e.text.text[0])); + //printf("%c",e.text.text[0]); + } else if (e.type == SDL_KEYDOWN) { + if (e.key.keysym.sym == SDLK_RETURN) { + char command[80]; + Console::Enter(command); + Worker::ExecuteCommand(Interpreter::Do(command)); + } else if (e.key.keysym.sym == SDLK_BACKSPACE) { + Console::DelChar(); + } else if (e.key.keysym.sym == SDLK_UP) { + Console::PrevComm(); + } else if (e.key.keysym.sym == SDLK_DOWN) { + Console::NextComm(); + } + } + } else { + if (e.type == SDL_KEYDOWN) { + Keystrokes::KeyPressed(e.key.keysym.sym); + } + }*/ + } + + + // DRAW + + if (Application::NeedsUpdate(0)) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + //GUIState::Update(); + GUI::Draw(); + //Layout::Draw(); + //Console::Draw(); + + SDL_GL_SwapWindow(gWindow); + glClearColor(0.12f, 0.12f, 0.12f, 1.0f); + Application::Updated(); + GUIMouse::Reset(); + GUIKeyboard::Reset(); + } + } + + //Layout::Save("_last"); +} + diff --git a/renderer.cpp b/renderer.cpp new file mode 100644 index 0000000..2bd9596 --- /dev/null +++ b/renderer.cpp @@ -0,0 +1,171 @@ +#include "renderer.h" + +#include + +namespace Renderer +{ + State currentState {RENDER_DEPTH}; + State stackedStates[10]; + char stackPos {0}; + + void DoLightOn(); + void DoLightOff(); + void DoFillOn(); + void DoFillOff(); + void DoDepthOn(); + void DoDepthOff(); + void DoBlendOn(); + void DoBlendOff(); + void DoTextureOn(); + void DoTextureOff(); + + bool Light(const char state) + { + if (state == ON) { + DoLightOn(); + } else if (state == OFF) { + DoLightOff(); + } + return (currentState & RENDER_LIGHT) == RENDER_LIGHT; + } + + bool Fill(const char state) + { + if (state == ON) { + DoFillOn(); + } else if (state == OFF) { + DoFillOff(); + } + return (currentState & RENDER_FILL) == RENDER_FILL; + } + + bool Depth(const char state) + { + if (state == ON) { + DoDepthOn(); + } else if (state == OFF) { + DoDepthOff(); + } + return (currentState & RENDER_DEPTH) == RENDER_DEPTH; + } + + bool Blend(const char state) + { + if (state == ON) { + DoBlendOn(); + } else if (state == OFF) { + DoBlendOff(); + } + return (currentState & RENDER_BLEND) == RENDER_BLEND; + } + + bool Texture(const char state) + { + if (state == ON) { + DoTextureOn(); + } else if (state == OFF) { + DoTextureOff(); + } + return (currentState & RENDER_TEXTURE) == RENDER_TEXTURE; + } + + void Push() + { + if (stackPos == 10) return; + stackedStates[stackPos++] = currentState; + } + + void Pop() + { + if (stackPos == 0) return; + SetState(stackedStates[--stackPos]); + } + + void SetState(const State& state) + { + if ((state & RENDER_LIGHT) == RENDER_LIGHT) DoLightOn(); else DoLightOff(); + if ((state & RENDER_FILL) == RENDER_FILL) DoFillOn(); else DoFillOff(); + if ((state & RENDER_BLEND) == RENDER_BLEND) DoBlendOn(); else DoBlendOff(); + if ((state & RENDER_DEPTH) == RENDER_DEPTH) DoDepthOn(); else DoDepthOff(); + if ((state & RENDER_TEXTURE) == RENDER_TEXTURE) DoTextureOn(); else DoTextureOff(); + } + + State GetState() + { + return currentState; + } + + + void DoLightOn() + { + if ((currentState & RENDER_LIGHT) == RENDER_LIGHT) return; + glEnable(GL_LIGHTING); + glEnable(GL_COLOR_MATERIAL); + currentState |= RENDER_LIGHT; + } + + void DoLightOff() + { + if ((currentState & RENDER_LIGHT) != RENDER_LIGHT) return; + glDisable(GL_LIGHTING); + glDisable(GL_COLOR_MATERIAL); + currentState &= uint8_t(~RENDER_LIGHT); + } + + void DoFillOn() + { + if ((currentState & RENDER_FILL) == RENDER_FILL) return; + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + currentState |= RENDER_FILL; + } + + void DoFillOff() + { + if ((currentState & RENDER_FILL) != RENDER_FILL) return; + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + currentState &= uint8_t(~RENDER_FILL); + } + + void DoDepthOn() + { + if ((currentState & RENDER_DEPTH) == RENDER_DEPTH) return; + glEnable(GL_DEPTH_TEST); + currentState |= RENDER_DEPTH; + } + + void DoDepthOff() + { + if ((currentState & RENDER_DEPTH) != RENDER_DEPTH) return; + glDisable(GL_DEPTH_TEST); + currentState &= uint8_t(~RENDER_DEPTH); + } + + void DoBlendOn() + { + if ((currentState & RENDER_BLEND) == RENDER_BLEND) return; + glEnable(GL_BLEND); + currentState |= RENDER_BLEND; + } + + void DoBlendOff() + { + if ((currentState & RENDER_BLEND) != RENDER_BLEND) return; + glDisable(GL_BLEND); + currentState &= uint8_t(~RENDER_BLEND); + } + + void DoTextureOn() + { + if ((currentState & RENDER_TEXTURE) == RENDER_TEXTURE) return; + glEnable(GL_TEXTURE_2D); + currentState |= RENDER_TEXTURE; + } + + void DoTextureOff() + { + if ((currentState & RENDER_TEXTURE) != RENDER_TEXTURE) return; + glDisable(GL_TEXTURE_2D); + currentState &= uint8_t(~RENDER_TEXTURE); + } + +} diff --git a/renderer.h b/renderer.h new file mode 100644 index 0000000..6b99613 --- /dev/null +++ b/renderer.h @@ -0,0 +1,26 @@ +#pragma once + +#include "common.h" + +#define RENDER_NONE 0 +#define RENDER_LIGHT 1 +#define RENDER_FILL 2 +#define RENDER_DEPTH 4 +#define RENDER_BLEND 8 +#define RENDER_TEXTURE 16 + +namespace Renderer +{ + typedef unsigned char State; + + bool Light(const char state = QUERY); + bool Fill(const char state = QUERY); + bool Depth(const char state = QUERY); + bool Blend(const char state = QUERY); + bool Texture(const char state = QUERY); + + void Push(); + void Pop(); + void SetState(const State& state); + State GetState(); +} diff --git a/screen.cpp b/screen.cpp new file mode 100644 index 0000000..4637477 --- /dev/null +++ b/screen.cpp @@ -0,0 +1,17 @@ +#include "screen.h" + +#include +#include "application.h" + +float SCREEN_WIDTH = 1280; +float SCREEN_HEIGHT = 700; + +namespace Screen +{ + SDL_Window* gWindow = nullptr; + + void Beep(unsigned char r, unsigned char g, unsigned char b){ + glClearColor(float(r)/255.0f, float(g)/255.0f, float(b)/255.0f, 1.0f); + Application::NeedsUpdate(2); + } +} diff --git a/screen.h b/screen.h new file mode 100644 index 0000000..40528df --- /dev/null +++ b/screen.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +extern float SCREEN_WIDTH; +extern float SCREEN_HEIGHT; + +namespace Screen +{ + extern SDL_Window* gWindow; + void Beep(unsigned char r = 255, unsigned char g = 255, unsigned char b = 255); +} diff --git a/stb_image.h b/stb_image.h new file mode 100644 index 0000000..83748e4 --- /dev/null +++ b/stb_image.h @@ -0,0 +1,4687 @@ +/* stb_image - v1.46 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c + when you control the images you're loading + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + #define STBI_ASSERT(x) to avoid using assert.h. + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline (no JPEG progressive) + PNG 8-bit-per-channel only + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - overridable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) + + Latest revisions: + 1.46 (2014-08-26) fix broken tRNS chunk in non-paletted PNG + 1.45 (2014-08-16) workaround MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) warnings + 1.43 (2014-07-15) fix MSVC-only bug in 1.42 + 1.42 (2014-07-09) no _CRT_SECURE_NO_WARNINGS; error-path fixes; STBI_ASSERT + 1.41 (2014-06-25) fix search&replace that messed up comments/error messages + 1.40 (2014-06-22) gcc warning + 1.39 (2014-06-15) TGA optimization bugfix, multiple BMP fixes + 1.38 (2014-06-06) suppress MSVC run-time warnings, fix accidental rename of 'skip' + 1.37 (2014-06-04) remove duplicate typedef + 1.36 (2014-06-03) converted to header file, allow reading incorrect iphoned-images without iphone flag + 1.35 (2014-05-27) warnings, bugfixes, TGA optimization, etc + + See end of file for full revision history. + + TODO: + stbi_info support for BMP,PSD,HDR,PIC + + + ============================ Contributors ========================= + + Image formats Bug fixes & warning fixes + Sean Barrett (jpeg, png, bmp) Marc LeBlanc + Nicolas Schulz (hdr, psd) Christpher Lloyd + Jonathan Dummer (tga) Dave Moore + Jean-Marc Lienher (gif) Won Chun + Tom Seddon (pic) the Horde3D community + Thatcher Ulrich (psd) Janez Zemva + Jonathan Blow + Laurent Gomila + Extensions, features Aruelien Pocheville + Jetro Lauha (stbi_info) Ryamond Barbiero + James "moose2000" Brown (iPhone PNG) David Woo + Ben "Disch" Wenger (io callbacks) Roy Eltham + Martin "SpartanJ" Golini Luke Graham + Thomas Ruf + John Bartholomew + Optimizations & bugfixes Ken Hamada + Fabian "ryg" Giesen Cort Stratton + Arseny Kapoulkine Blazej Dariusz Roszkowski + Thibault Reuille + Paul Du Bois + Guillaume George + Jerry Jansson + If your name should be here but Hayaki Saito + isn't, let Sean know. Johan Duparc + Ronny Chevalier + Michal Cichon + */ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// Limitations: +// - no jpeg progressive support +// - non-HDR formats support 8-bit samples only (jpeg, png) +// - no delayed line count (jpeg) -- IJG doesn't support either +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to easily see if it's opaque. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB; nominally they +// would silently load as BGR, except the existing code should have just +// failed on such iPhone PNGs. But you can disable this conversion by +// by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through. +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + + ////////////////////////////////////////////////////////////////////////////// + // + // PRIMARY API - works on images of any type + // + + // + // load image by filename, open file, or memory buffer + // + + STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO + STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + // for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + + typedef struct + { + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data + } stbi_io_callbacks; + + STBIDEF stbi_uc *stbi_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_HDR + STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); + + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_HDR + + // stbi_is_hdr is always defined + STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); + STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO + STBIDEF int stbi_is_hdr (char const *filename); + STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + + // get a VERY brief reason for failure + // NOT THREADSAFE + STBIDEF const char *stbi_failure_reason (void); + + // free the loaded image -- this is just free() + STBIDEF void stbi_image_free (void *retval_from_stbi_load); + + // get image dimensions & components without fully decoding + STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); + STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO + STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); + STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + + // for image formats that explicitly notate that they have premultiplied alpha, + // we just return the colors as stored in the file. set this flag to force + // unpremultiplication. results are undefined if the unpremultiply overflow. + STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + + // indicate whether we should process iphone images back to canonical format, + // or just pass them through "as-is" + STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + + + // ZLIB client - used by PNG, available for other purposes + + STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); + STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); + STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); + STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); + STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + + // define faster low-level operations (typically SIMD support) +#ifdef STBI_SIMD + typedef void (*stbi_idct_8x8)(stbi_uc *out, int out_stride, short data[64], unsigned short *dequantize); + // compute an integer IDCT on "input" + // input[x] = data[x] * dequantize[x] + // write results to 'out': 64 samples, each run of 8 spaced by 'out_stride' + // CLAMP results to 0..255 + typedef void (*stbi_YCbCr_to_RGB_run)(stbi_uc *output, stbi_uc const *y, stbi_uc const *cb, stbi_uc const *cr, int count, int step); + // compute a conversion from YCbCr to RGB + // 'count' pixels + // write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B + // y: Y input channel + // cb: Cb input channel; scale/biased to be 0..255 + // cr: Cr input channel; scale/biased to be 0..255 + + STBIDEF void stbi_install_idct(stbi_idct_8x8 func); + STBIDEF void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); +#endif // STBI_SIMD + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#ifndef STBI_NO_HDR +#include // ldexp +#include // strcmp, strtok +#endif + +#ifndef STBI_NO_STDIO +#include +#endif +#include +#include +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif +#include +#include // ptrdiff_t on osx + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbi_inline inline +#else +#define stbi_inline +#endif +#else +#define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL +#define stbi_lrot(x,y) _lrotl(x,y) +#else +#define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; +} + +static int stbi__jpeg_test(stbi__context *s); +static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_test(stbi__context *s); +static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__bmp_test(stbi__context *s); +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__tga_test(stbi__context *s); +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_test(stbi__context *s); +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +#endif +static int stbi__pic_test(stbi__context *s); +static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__gif_test(stbi__context *s); +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); + + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return malloc(size); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS +#define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) +#define stbi__err(x,y) stbi__err(y) +#else +#define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + free(retval_from_stbi_load); +} + +#ifndef STBI_NO_HDR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static unsigned char *stbi_load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); + +#ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } +#endif + + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp); + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO + +FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi_load_main(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} +#endif //!STBI_NO_STDIO + +STBIDEF unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi_load_main(&s,x,y,comp,req_comp); +} + +unsigned char *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_load_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_HDR + +float *stbi_loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; +#ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) + return stbi__hdr_load(s,x,y,comp,req_comp); +#endif + data = stbi_load_main(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} + +float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_HDR + +// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is +// defined, for API simplicity; if STBI_NO_HDR is defined, it always +// reports false! + +int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ +#ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); +#else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; +#endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ +#ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file(&s,f); + return stbi__hdr_test(&s); +#else + return 0; +#endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ +#ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); +#else + return 0; +#endif +} + +#ifndef STBI_NO_HDR +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + +void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + SCAN_load=0, + SCAN_type, + SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} + +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc(req_comp * x * y); + if (good == NULL) { + free(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + +#define COMBO(a,b) ((a)*8+(b)) +#define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: STBI_ASSERT(0); + } +#undef CASE + } + + free(data); + return good; +} + +#ifndef STBI_NO_HDR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); + if (output == NULL) { free(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + free(data); + return output; +} + +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); + if (output == NULL) { free(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + free(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) +// +// simple implementation +// - channel subsampling of at most 2 in each dimension +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - uses a lot of intermediate memory, could cache poorly +// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 +// stb_jpeg: 1.34 seconds (MSVC6, default release build) +// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) +// IJL11.dll: 1.08 seconds (compiled by intel) +// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) +// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ +#ifdef STBI_SIMD + unsigned short dequant2[4][64]; +#endif + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi_uc dequant[4][64]; + + // sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + + // definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data; + stbi_uc *linebuf; + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int scan_n, order[4]; + int restart_interval, todo; +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// combined JPEG 'receive' and JPEG 'extend', since baseline +// always extends everything it receives. +stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) +{ + unsigned int m = 1 << (n-1); + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + +#if 1 + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; +#else + k = (j->code_buffer >> (32 - n)) & stbi__bmask[n]; + j->code_bits -= n; + j->code_buffer <<= n; +#endif + // the following test is probably a random branch that won't + // predict well. I tried to table accelerate it but failed. + // maybe it's compiling as a conditional move? + if (k < m) + return (-1 << n) + k + 1; + else + return k; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, int b) +{ + int diff,dc,k; + int t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) dc; + + // decode AC components, see JPEG spec + k = 1; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + data[stbi__jpeg_dezigzag[k++]] = (short) stbi__extend_receive(j,s); + } + } while (k < 64); + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) (int) (((x) * 4096 + 0.5)) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ +int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ +p2 = s2; \ +p3 = s6; \ +p1 = (p2+p3) * stbi__f2f(0.5411961f); \ +t2 = p1 + p3*stbi__f2f(-1.847759065f); \ +t3 = p1 + p2*stbi__f2f( 0.765366865f); \ +p2 = s0; \ +p3 = s4; \ +t0 = stbi__fsh(p2+p3); \ +t1 = stbi__fsh(p2-p3); \ +x0 = t0+t3; \ +x3 = t0-t3; \ +x1 = t1+t2; \ +x2 = t1-t2; \ +t0 = s7; \ +t1 = s5; \ +t2 = s3; \ +t3 = s1; \ +p3 = t0+t2; \ +p4 = t1+t3; \ +p1 = t0+t3; \ +p2 = t1+t2; \ +p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ +t0 = t0*stbi__f2f( 0.298631336f); \ +t1 = t1*stbi__f2f( 2.053119869f); \ +t2 = t2*stbi__f2f( 3.072711026f); \ +t3 = t3*stbi__f2f( 1.501321110f); \ +p1 = p5 + p1*stbi__f2f(-0.899976223f); \ +p2 = p5 + p2*stbi__f2f(-2.562915447f); \ +p3 = p3*stbi__f2f(-1.961570560f); \ +p4 = p4*stbi__f2f(-0.390180644f); \ +t3 += p1+p4; \ +t2 += p2+p3; \ +t1 += p2+p4; \ +t0 += p1+p3; + +#ifdef STBI_SIMD +typedef unsigned short stbi_dequantize_t; +#else +typedef stbi_uc stbi_dequantize_t; +#endif + +// .344 seconds on 3*anemones.jpg +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64], stbi_dequantize_t *dequantize) +{ + int i,val[64],*v=val; + stbi_dequantize_t *dq = dequantize; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d,++dq, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] * dq[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], + d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SIMD +static stbi_idct_8x8 stbi__idct_installed = stbi__idct_block; + +STBIDEF void stbi_install_idct(stbi_idct_8x8 func) +{ + stbi__idct_installed = func; +} +#endif + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (z->scan_n == 1) { + int i,j; +#ifdef STBI_SIMD + __declspec(align(16)) +#endif + short data[64]; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; +#ifdef STBI_SIMD + stbi__idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); +#else + stbi__idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); +#endif + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + } else { // interleaved! + int i,j,k,x,y; + short data[64]; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; +#ifdef STBI_SIMD + stbi__idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); +#else + stbi__idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); +#endif + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + } + return 1; +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xC2: // stbi__SOF - progressive + return stbi__err("progressive jpeg","JPEG format not supported (progressive)"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); +#ifdef STBI_SIMD + for (i=0; i < 64; ++i) + z->dequant2[t][i] = z->dequant[t][i]; +#endif + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + L -= n; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + stbi__skip(z->s, stbi__get16be(z->s)-2); + return 1; + } + return 0; +} + +// after we see stbi__SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad stbi__SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad stbi__SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + if (stbi__get8(z->s) != 0) return stbi__err("bad stbi__SOS","Corrupt JPEG"); + stbi__get8(z->s); // should be 63, but might be 0 + if (stbi__get8(z->s) != 0) return stbi__err("bad stbi__SOS","Corrupt JPEG"); + + return 1; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad stbi__SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad stbi__SOF len","Corrupt JPEG"); + + for (i=0; i < s->img_n; ++i) { + z->img_comp[i].id = stbi__get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return stbi__err("bad component ID","Corrupt JPEG"); + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + free(z->img_comp[i].raw_data); + z->img_comp[i].data = NULL; + } + return stbi__err("outofmem", "Out of memory"); + } + // align blocks for installable-idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. stbi__SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1) +#define stbi__SOS(x) ((x) == 0xda) + +static int decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no stbi__SOI","Corrupt JPEG"); + if (scan == SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no stbi__SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static int decode_jpeg_image(stbi__jpeg *j) +{ + int m; + j->restart_interval = 0; + if (!decode_jpeg_header(j, SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } else if (x != 0) { + return 0; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) + +// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) +// VC6 without processor=Pro is generating multiple LEAs per multiply! +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#ifdef STBI_SIMD +static stbi_YCbCr_to_RGB_run stbi__YCbCr_installed = stbi__YCbCr_to_RGB_row; + +STBIDEF void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func) +{ + stbi__YCbCr_installed = func; +} +#endif + + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].raw_data) { + free(j->img_comp[i].raw_data); + j->img_comp[i].raw_data = NULL; + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].linebuf) { + free(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source + if (!decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = stbi__resample_row_hv_2; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { +#ifdef STBI_SIMD + stbi__YCbCr_installed(out, y, coutput[1], coutput[2], z->s->img_x, n); +#else + stbi__YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s->img_x, n); +#endif + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__jpeg j; + j.s = s; + return load_jpeg_image(&j, x,y,comp,req_comp); +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg j; + j.s = s; + r = decode_jpeg_header(&j, SCAN_type); + stbi__rewind(s); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!decode_jpeg_header(j, SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__jpeg j; + j.s = s; + return stbi__jpeg_info_raw(&j, x, y, comp); +} + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 255, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + STBI_ASSERT(sizes[i] <= (1 << i)); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt JPEG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int k = stbi__bit_reverse(next_code[s],s); + while (k < (1 << STBI__ZFAST_BITS)) { + z->fast[k] = (stbi__uint16) c; + k += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b < 0xffff) { + s = z->size[b]; + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; + } + + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +static int stbi__zexpand(stbi__zbuf *z, int n) // need to make room for n bytes +{ + char *q; + int cur, limit; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) realloc(z->zout_start, limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, + 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (a->zout >= a->zout_end) if (!stbi__zexpand(a, 1)) return 0; + *a->zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) return 1; + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (a->zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (a->zout + len > a->zout_end) if (!stbi__zexpand(a, len)) return 0; + p = (stbi_uc *) (a->zout - dist); + while (len--) + *a->zout++ = *p++; + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = stbi__zhuffman_decode(a, &z_codelength); + STBI_ASSERT(c >= 0 && c < 19); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else if (c == 16) { + c = stbi__zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncomperssed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncomperssed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + + +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; +} stbi__png; + + +enum { + STBI__F_none=0, STBI__F_sub=1, STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, + STBI__F_avg_first, STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, STBI__F_sub, STBI__F_none, STBI__F_avg_first, STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y) +{ + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n; + int k; + int img_n = s->img_n; // copy it into a local for later + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc(x * y * out_n); + if (!a->out) return stbi__err("outofmem", "Out of memory"); + if (s->img_x == x && s->img_y == y) { + if (raw_len != (img_n * x + 1) * y) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < (img_n * x + 1) * y) return stbi__err("not enough pixels","Corrupt PNG"); + } + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior = cur - stride; + int filter = *raw++; + if (filter > 4) return stbi__err("invalid filter","Corrupt PNG"); + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + // handle first pixel explicitly + for (k=0; k < img_n; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + if (img_n != out_n) cur[img_n] = 255; + raw += img_n; + cur += out_n; + prior += out_n; + // this is a little gross, so that we don't switch per-pixel or per-component + if (img_n == out_n) { +#define CASE(f) \ +case f: \ +for (i=x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n) \ +for (k=0; k < img_n; ++k) + switch (filter) { + CASE(STBI__F_none) cur[k] = raw[k]; break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-img_n]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-img_n])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-img_n] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-img_n],0,0)); break; + } +#undef CASE + } else { + STBI_ASSERT(img_n+1 == out_n); +#define CASE(f) \ +case f: \ +for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ +for (k=0; k < img_n; ++k) + switch (filter) { + CASE(STBI__F_none) cur[k] = raw[k]; break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-out_n]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-out_n])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-out_n] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],0,0)); break; + } +#undef CASE + } + } + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, int interlaced) +{ + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, raw, raw_len, out_n, a->s->img_x, a->s->img_y); + + // de-interlacing + final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + if (!stbi__create_png_image_raw(a, raw, raw_len, out_n, x, y)) { + free(final); + return 0; + } + for (j=0; j < y; ++j) + for (i=0; i < x; ++i) + memcpy(final + (j*yspc[p]+yorig[p])*a->s->img_x*out_n + (i*xspc[p]+xorig[p])*out_n, + a->out + (j*x+i)*out_n, out_n); + free(a->out); + raw += (x*out_n+1)*y; + raw_len -= (x*out_n+1)*y; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + free(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case PNG_TYPE('I','H','D','R'): { + int depth,color,comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + depth = stbi__get8(s); if (depth != 8) return stbi__err("8bit only","PNG not supported: 8-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + for (k=0; k < s->img_n; ++k) + tc[k] = (stbi_uc) (stbi__get16be(s) & 255); // non 8-bit images will be larger + } + break; + } + + case PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; } + if (ioff + c.length > idata_limit) { + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + p = (stbi_uc *) realloc(z->idata, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, 16384, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + free(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, interlace)) return 0; + if (has_trans) + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + free(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { +#ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); +#endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, SCAN_load, req_comp)) { + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_out_n; + } + free(p->out); p->out = NULL; + free(p->expanded); p->expanded = NULL; + free(p->idata); p->idata = NULL; + + return result; +} + +static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +// Microsoft/Windows BMP image +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, fake_a=0; + stbi_uc pal[256][4]; + int psize=0,i,j,compress=0,width; + int bpp, flip_vertically, pad, target, offset, hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + offset = stbi__get32le(s); + hsz = stbi__get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + bpp = stbi__get16le(s); + if (bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + if (hsz == 12) { + if (bpp < 24) + psize = (offset - 14 - 24) / 3; + } else { + compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (bpp == 16 || bpp == 32) { + mr = mg = mb = 0; + if (compress == 0) { + if (bpp == 32) { + mr = 0xffu << 16; + mg = 0xffu << 8; + mb = 0xffu << 0; + ma = 0xffu << 24; + fake_a = 1; // @TODO: check for cases like alpha value is all 0 and switch it to 255 + STBI_NOTUSED(fake_a); + } else { + mr = 31u << 10; + mg = 31u << 5; + mb = 31u << 0; + } + } else if (compress == 3) { + mr = stbi__get32le(s); + mg = stbi__get32le(s); + mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (mr == mg && mg == mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + STBI_ASSERT(hsz == 108 || hsz == 124); + mr = stbi__get32le(s); + mg = stbi__get32le(s); + mb = stbi__get32le(s); + ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + if (bpp < 16) + psize = (offset - 14 - hsz) >> 2; + } + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { free(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); + if (bpp == 4) width = (s->img_x + 1) >> 1; + else if (bpp == 8) width = s->img_x; + else { free(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, offset - 14 - hsz); + if (bpp == 24) width = 3 * s->img_x; + else if (bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (bpp == 24) { + easy = 1; + } else if (bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { free(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + if (target == 4) out[z++] = a; + } + } else { + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (stbi__uint32) (bpp == 16 ? stbi__get16le(s) : stbi__get32le(s)); + int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} + +// Targa Truevision - TGA +// by Jonathan Dummer + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp; + int sz; + stbi__get8(s); // discard Offset + sz = stbi__get8(s); // color type + if( sz > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + sz = stbi__get8(s); // image type + // only RGB or grey allowed, +/- RLE + if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0; + stbi__skip(s,9); + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + sz = stbi__get8(s); // bits per pixel + // only RGB or RGBA or grey allowed + if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) { + stbi__rewind(s); + return 0; + } + tga_comp = sz; + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp / 8; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res; + int sz; + stbi__get8(s); // discard Offset + sz = stbi__get8(s); // color type + if ( sz > 1 ) return 0; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE + stbi__get16be(s); // discard palette start + stbi__get16be(s); // discard palette length + stbi__get8(s); // discard bits per palette color entry + stbi__get16be(s); // discard x origin + stbi__get16be(s); // discard y origin + if ( stbi__get16be(s) < 1 ) return 0; // test width + if ( stbi__get16be(s) < 1 ) return 0; // test height + sz = stbi__get8(s); // bits per pixel + if ( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) + res = 0; + else + res = 1; + stbi__rewind(s); + return res; +} + +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp = tga_bits_per_pixel / 8; + int tga_inverted = stbi__get8(s); + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + /* int tga_alpha_bits = tga_inverted & 15; */ + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // error check + if ( //(tga_indexed) || + (tga_width < 1) || (tga_height < 1) || + (tga_image_type < 1) || (tga_image_type > 3) || + ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && + (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) + ) + { + return NULL; // we don't report this as a bad TGA because we don't even know if it's TGA + } + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) + { + tga_comp = tga_palette_bits / 8; + } + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + tga_data = (unsigned char*)stbi__malloc( tga_width * tga_height * tga_comp ); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE) { + for (i=0; i < tga_height; ++i) { + int y = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + y*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_palette_bits / 8 ); + if (!tga_palette) { + free(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (!stbi__getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 )) { + free(tga_data); + free(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in 1 byte, then perform the lookup + int pal_idx = stbi__get8(s); + if ( pal_idx >= tga_palette_len ) + { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_bits_per_pixel / 8; + for (j = 0; j*8 < tga_bits_per_pixel; ++j) + { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else + { + // read in the data raw + for (j = 0; j*8 < tga_bits_per_pixel; ++j) + { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + free( tga_palette ); + } + } + + // swap RGB + if (tga_comp >= 3) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int w,h; + stbi_uc *out; + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + if (stbi__get16be(s) != 8) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) stbi__malloc(4 * w*h); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4; + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; + if (channel > channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4; + } else { + // Read the data. + for (i = 0; i < pixelCount; i++) + *p = stbi__get8(s), p += 4; + } + } + } + + if (req_comp && req_comp != 4) { + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = channelCount; + *y = h; + *x = w; + + return out; +} + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + int i; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + free(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif g; + if (!stbi__gif_header(s, &g, comp, 1)) { + stbi__rewind( s ); + return 0; + } + if (x) *x = g.w; + if (y) *y = g.h; + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (code = 0; code < clear; code++) { + g->codes[code].prefix = -1; + g->codes[code].first = (stbi_uc) code; + g->codes[code].suffix = (stbi_uc) code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background(stbi__gif *g) +{ + int i; + stbi_uc *c = g->pal[g->bgindex]; + // @OPTIMIZE: write a dword at a time + for (i = 0; i < g->w * g->h * 4; i += 4) { + stbi_uc *p = &g->out[i]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +{ + int i; + stbi_uc *old_out = 0; + + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + stbi__fill_gif_background(g); + } else { + // animated-gif-only path + if (((g->eflags & 0x1C) >> 2) == 3) { + old_out = g->out; + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + memcpy(g->out, old_out, g->w*g->h*4); + } + } + + for (;;) { + switch (stbi__get8(s)) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + for (i=0; i < 256; ++i) // @OPTIMIZE: stbi__jpeg_reset only the previous transparent + g->pal[i][3] = 255; + if (g->transparent >= 0 && (g->eflags & 0x01)) + g->pal[g->transparent][3] = 0; + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (req_comp && req_comp != 4) + o = stbi__convert_format(o, 4, req_comp, g->w, g->h); + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + stbi__get16le(s); // delay + g->transparent = stbi__get8(s); + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) + stbi__skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + + u = stbi__gif_load_next(s, &g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + } + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} + + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s); + stbi__rewind(s); + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + free(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { free(hdr_data); free(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + free(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + + if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') { + stbi__rewind( s ); + return 0; + } + stbi__skip(s,12); + hsz = stbi__get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) { + stbi__rewind( s ); + return 0; + } + if (hsz == 12) { + *x = stbi__get16le(s); + *y = stbi__get16le(s); + } else { + *x = stbi__get32le(s); + *y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) { + stbi__rewind( s ); + return 0; + } + *comp = stbi__get16le(s) / 8; + return 1; +} + +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + if (stbi__get16be(s) != 8) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + stbi__pic_packet packets[10]; + + stbi__skip(s, 92); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) return 0; + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + if (stbi__jpeg_info(s, x, y, comp)) + return 1; + if (stbi__png_info(s, x, y, comp)) + return 1; + if (stbi__gif_info(s, x, y, comp)) + return 1; + if (stbi__bmp_info(s, x, y, comp)) + return 1; + if (stbi__psd_info(s, x, y, comp)) + return 1; + if (stbi__pic_info(s, x, y, comp)) + return 1; +#ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) + return 1; +#endif + // test tga last because it's a crappy test! + if (stbi__tga_info(s, x, y, comp)) + return 1; + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 2008-08-02 + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 first released version + */ \ No newline at end of file diff --git a/texture.cpp b/texture.cpp new file mode 100644 index 0000000..348c030 --- /dev/null +++ b/texture.cpp @@ -0,0 +1,71 @@ +#include "texture.h" +#define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_FAILURE_STRINGS +#include "stb_image.h" +#include + +namespace Texture +{ + unsigned texture = 0; + + unsigned Create(const int w, const int h) + { + unsigned texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + if (w != -1) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + return texture; + } + + const bool Load(const unsigned& texture, const char* filename) + { + int x,y,n; + char name[400]; strcpy(name, filename); + if (strrchr(filename, '.') == nullptr) strcat(name, ".png"); + unsigned char *data = stbi_load(name, &x, &y, &n, 4); + if (data == nullptr) return false; + + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + stbi_image_free(data); + + return true; + } + + uint8_t* Load(const char* filename) + { + int x,y,n; + char name[400]; strcpy(name, filename); + if (strrchr(filename, '.') == nullptr) strcat(name, ".png"); + unsigned char *data = stbi_load(name, &x, &y, &n, 4); + return data; + } + + void Update(const unsigned& texture, const int w, const int h, const uint8_t* data) + { + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } +} + +namespace Bitmap +{ + void PutPixel(uint8_t* data, const int x, const int y, const Color& color) + { + memcpy(&data[x+y*128], &color, 4); + } + + const Color GetPixel(uint8_t* data, const int x, const int y) + { + Color color; + data += (x+y*128); + color.r = *(data++); + color.g = *(data++); + color.b = *(data++); + color.a = *(data++); + return color; + } +} \ No newline at end of file diff --git a/texture.h b/texture.h new file mode 100644 index 0000000..40322a9 --- /dev/null +++ b/texture.h @@ -0,0 +1,19 @@ +#pragma once + +#include "common.h" + +namespace Texture +{ + extern unsigned texture; + + unsigned Create(const int w = -1, const int h = -1); + const bool Load(const unsigned& texture, const char* filename); + uint8_t* Load(const char* filename); + void Update(const unsigned& texture, const int w, const int h, const uint8_t* data); +} + +namespace Bitmap +{ + void PutPixel(uint8_t* data, const int x, const int y, const Color& color); + const Color GetPixel(uint8_t* data, const int x, const int y); +}