diff --git a/Chapter01/Chapter01-mac.xcodeproj/project.pbxproj b/Chapter01/Chapter01-mac.xcodeproj/project.pbxproj new file mode 100644 index 00000000..cf8a87e1 --- /dev/null +++ b/Chapter01/Chapter01-mac.xcodeproj/project.pbxproj @@ -0,0 +1,316 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9223C4781F009428009A94D7 /* Game.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4671F009428009A94D7 /* Game.cpp */; }; + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4711F009428009A94D7 /* Main.cpp */; }; + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92D324FA1B697389005A86C7 /* CoreFoundation.framework */; }; + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92E46E931B6353E50035CD21 /* OpenGL.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 9223C4671F009428009A94D7 /* Game.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Game.cpp; sourceTree = ""; }; + 9223C4701F009428009A94D7 /* Game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Game.h; sourceTree = ""; }; + 9223C4711F009428009A94D7 /* Main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Main.cpp; sourceTree = ""; }; + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 92E46DF71B634EA30035CD21 /* Game-mac */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Game-mac"; sourceTree = BUILT_PRODUCTS_DIR; }; + 92E46E931B6353E50035CD21 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 92E46DF41B634EA30035CD21 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */, + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 92E46DEE1B634EA30035CD21 = { + isa = PBXGroup; + children = ( + 9223C4671F009428009A94D7 /* Game.cpp */, + 9223C4701F009428009A94D7 /* Game.h */, + 9223C4711F009428009A94D7 /* Main.cpp */, + 92E46DF81B634EA30035CD21 /* Products */, + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */, + 92E46E931B6353E50035CD21 /* OpenGL.framework */, + ); + sourceTree = ""; + }; + 92E46DF81B634EA30035CD21 /* Products */ = { + isa = PBXGroup; + children = ( + 92E46DF71B634EA30035CD21 /* Game-mac */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 92E46DF61B634EA30035CD21 /* Game-mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */; + buildPhases = ( + 92E46DF31B634EA30035CD21 /* Sources */, + 92E46DF41B634EA30035CD21 /* Frameworks */, + 92E46EA11B63615B0035CD21 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Game-mac"; + productName = "Game-mac"; + productReference = 92E46DF71B634EA30035CD21 /* Game-mac */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 92E46DEF1B634EA30035CD21 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Sanjay Madhav"; + TargetAttributes = { + 92E46DF61B634EA30035CD21 = { + CreatedOnToolsVersion = 6.4; + }; + }; + }; + buildConfigurationList = 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter01-mac" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 92E46DEE1B634EA30035CD21; + productRefGroup = 92E46DF81B634EA30035CD21 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 92E46DF61B634EA30035CD21 /* Game-mac */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 92E46EA11B63615B0035CD21 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -d \"$BUILD_DIR/Debug\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Debug\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Debug\nfi\n\nif [ -d \"$BUILD_DIR/Release\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Release\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Release\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 92E46DF31B634EA30035CD21 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */, + 9223C4781F009428009A94D7 /* Game.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 92E46DFC1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 92E46DFD1B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 92E46DFF1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 92E46E001B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter01-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFC1B634EA40035CD21 /* Debug */, + 92E46DFD1B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFF1B634EA40035CD21 /* Debug */, + 92E46E001B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 92E46DEF1B634EA30035CD21 /* Project object */; +} diff --git a/Chapter01/Chapter01-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Chapter01/Chapter01-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..403d96cd --- /dev/null +++ b/Chapter01/Chapter01-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Chapter01/Chapter01-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme b/Chapter01/Chapter01-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme new file mode 100644 index 00000000..21a91dfd --- /dev/null +++ b/Chapter01/Chapter01-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter01/Chapter01-windows.sln b/Chapter01/Chapter01-windows.sln new file mode 100644 index 00000000..f560df0f --- /dev/null +++ b/Chapter01/Chapter01-windows.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "Game.vcxproj", "{BC508D87-495F-4554-932D-DD68388B63CC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.Build.0 = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.ActiveCfg = Release|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Chapter01/Game.cpp b/Chapter01/Game.cpp new file mode 100644 index 00000000..3b18199e --- /dev/null +++ b/Chapter01/Game.cpp @@ -0,0 +1,257 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" + +const int thickness = 15; +const float paddleH = 100.0f; + +Game::Game() +:mWindow(nullptr) +,mRenderer(nullptr) +,mTicksCount(0) +,mIsRunning(true) +,mPaddleDir(0) +{ + +} + +bool Game::Initialize() +{ + // Initialize SDL + int sdlResult = SDL_Init(SDL_INIT_VIDEO); + if (sdlResult != 0) + { + SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); + return false; + } + + // Create an SDL Window + mWindow = SDL_CreateWindow( + "Game Programming in C++ (Chapter 1)", // Window title + 100, // Top left x-coordinate of window + 100, // Top left y-coordinate of window + 1024, // Width of window + 768, // Height of window + 0 // Flags (0 for no flags set) + ); + + if (!mWindow) + { + SDL_Log("Failed to create window: %s", SDL_GetError()); + return false; + } + + //// Create SDL renderer + mRenderer = SDL_CreateRenderer( + mWindow, // Window to create renderer for + -1, // Usually -1 + SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC + ); + + if (!mRenderer) + { + SDL_Log("Failed to create renderer: %s", SDL_GetError()); + return false; + } + // + mPaddlePos.x = 10.0f; + mPaddlePos.y = 768.0f/2.0f; + mBallPos.x = 1024.0f/2.0f; + mBallPos.y = 768.0f/2.0f; + mBallVel.x = -200.0f; + mBallVel.y = 235.0f; + return true; +} + +void Game::RunLoop() +{ + while (mIsRunning) + { + ProcessInput(); + UpdateGame(); + GenerateOutput(); + } +} + +void Game::ProcessInput() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + // If we get an SDL_QUIT event, end loop + case SDL_QUIT: + mIsRunning = false; + break; + } + } + + // Get state of keyboard + const Uint8* state = SDL_GetKeyboardState(NULL); + // If escape is pressed, also end loop + if (state[SDL_SCANCODE_ESCAPE]) + { + mIsRunning = false; + } + + // Update paddle direction based on W/S keys + mPaddleDir = 0; + if (state[SDL_SCANCODE_W]) + { + mPaddleDir -= 1; + } + if (state[SDL_SCANCODE_S]) + { + mPaddleDir += 1; + } +} + +void Game::UpdateGame() +{ + // Wait until 16ms has elapsed since last frame + while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16)) + ; + + // Delta time is the difference in ticks from last frame + // (converted to seconds) + float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f; + + // Clamp maximum delta time value + if (deltaTime > 0.05f) + { + deltaTime = 0.05f; + } + + // Update tick counts (for next frame) + mTicksCount = SDL_GetTicks(); + + // Update paddle position based on direction + if (mPaddleDir != 0) + { + mPaddlePos.y += mPaddleDir * 300.0f * deltaTime; + // Make sure paddle doesn't move off screen! + if (mPaddlePos.y < (paddleH/2.0f + thickness)) + { + mPaddlePos.y = paddleH/2.0f + thickness; + } + else if (mPaddlePos.y > (768.0f - paddleH/2.0f - thickness)) + { + mPaddlePos.y = 768.0f - paddleH/2.0f - thickness; + } + } + + // Update ball position based on ball velocity + mBallPos.x += mBallVel.x * deltaTime; + mBallPos.y += mBallVel.y * deltaTime; + + // Bounce if needed + // Did we intersect with the paddle? + float diff = mPaddlePos.y - mBallPos.y; + // Take absolute value of difference + diff = (diff > 0.0f) ? diff : -diff; + if ( + // Our y-difference is small enough + diff <= paddleH / 2.0f && + // We are in the correct x-position + mBallPos.x <= 25.0f && mBallPos.x >= 20.0f && + // The ball is moving to the left + mBallVel.x < 0.0f) + { + mBallVel.x *= -1.0f; + } + // Did the ball go off the screen? (if so, end game) + else if (mBallPos.x <= 0.0f) + { + mIsRunning = false; + } + // Did the ball collide with the right wall? + else if (mBallPos.x >= (1024.0f - thickness) && mBallVel.x > 0.0f) + { + mBallVel.x *= -1.0f; + } + + // Did the ball collide with the top wall? + if (mBallPos.y <= thickness && mBallVel.y < 0.0f) + { + mBallVel.y *= -1; + } + // Did the ball collide with the bottom wall? + else if (mBallPos.y >= (768 - thickness) && + mBallVel.y > 0.0f) + { + mBallVel.y *= -1; + } +} + +void Game::GenerateOutput() +{ + // Set draw color to blue + SDL_SetRenderDrawColor( + mRenderer, + 0, // R + 0, // G + 255, // B + 255 // A + ); + + // Clear back buffer + SDL_RenderClear(mRenderer); + + // Draw walls + SDL_SetRenderDrawColor(mRenderer, 255, 255, 255, 255); + + // Draw top wall + SDL_Rect wall{ + 0, // Top left x + 0, // Top left y + 1024, // Width + thickness // Height + }; + SDL_RenderFillRect(mRenderer, &wall); + + // Draw bottom wall + wall.y = 768 - thickness; + SDL_RenderFillRect(mRenderer, &wall); + + // Draw right wall + wall.x = 1024 - thickness; + wall.y = 0; + wall.w = thickness; + wall.h = 1024; + SDL_RenderFillRect(mRenderer, &wall); + + // Draw paddle + SDL_Rect paddle{ + static_cast(mPaddlePos.x), + static_cast(mPaddlePos.y - paddleH/2), + thickness, + static_cast(paddleH) + }; + SDL_RenderFillRect(mRenderer, &paddle); + + // Draw ball + SDL_Rect ball{ + static_cast(mBallPos.x - thickness/2), + static_cast(mBallPos.y - thickness/2), + thickness, + thickness + }; + SDL_RenderFillRect(mRenderer, &ball); + + // Swap front buffer and back buffer + SDL_RenderPresent(mRenderer); +} + +void Game::Shutdown() +{ + SDL_DestroyRenderer(mRenderer); + SDL_DestroyWindow(mWindow); + SDL_Quit(); +} diff --git a/Chapter01/Game.h b/Chapter01/Game.h new file mode 100644 index 00000000..3d59a468 --- /dev/null +++ b/Chapter01/Game.h @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "SDL/SDL.h" + +// Vector2 struct just stores x/y coordinates +// (for now) +struct Vector2 +{ + float x; + float y; +}; + +// Game class +class Game +{ +public: + Game(); + // Initialize the game + bool Initialize(); + // Runs the game loop until the game is over + void RunLoop(); + // Shutdown the game + void Shutdown(); +private: + // Helper functions for the game loop + void ProcessInput(); + void UpdateGame(); + void GenerateOutput(); + + // Window created by SDL + SDL_Window* mWindow; + // Renderer for 2D drawing + SDL_Renderer* mRenderer; + // Number of ticks since start of game + Uint32 mTicksCount; + // Game should continue to run + bool mIsRunning; + + // Pong specific + // Direction of paddle + int mPaddleDir; + // Position of paddle + Vector2 mPaddlePos; + // Position of ball + Vector2 mBallPos; + // Velocity of ball + Vector2 mBallVel; +}; diff --git a/Chapter01/Game.vcxproj b/Chapter01/Game.vcxproj new file mode 100644 index 00000000..1f8e815e --- /dev/null +++ b/Chapter01/Game.vcxproj @@ -0,0 +1,110 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + {BC508D87-495F-4554-932D-DD68388B63CC} + Win32Proj + Game + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + /NODEFAULTLIB:msvcrt.lib %(AdditionalOptions) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + true + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + + \ No newline at end of file diff --git a/Chapter01/Game.vcxproj.filters b/Chapter01/Game.vcxproj.filters new file mode 100644 index 00000000..013c77e9 --- /dev/null +++ b/Chapter01/Game.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/Chapter01/Main.cpp b/Chapter01/Main.cpp new file mode 100644 index 00000000..22ea0c69 --- /dev/null +++ b/Chapter01/Main.cpp @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" + +int main(int argc, char** argv) +{ + Game game; + bool success = game.Initialize(); + if (success) + { + game.RunLoop(); + } + game.Shutdown(); + return 0; +} diff --git a/Chapter02/Actor.cpp b/Chapter02/Actor.cpp new file mode 100644 index 00000000..91278a65 --- /dev/null +++ b/Chapter02/Actor.cpp @@ -0,0 +1,83 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Actor.h" +#include "Game.h" +#include "Component.h" +#include + +Actor::Actor(Game* game) + :mState(EActive) + , mPosition(Vector2::Zero) + , mScale(1.0f) + , mRotation(0.0f) + , mGame(game) +{ + mGame->AddActor(this); +} + +Actor::~Actor() +{ + mGame->RemoveActor(this); + // Need to delete components + // Because ~Component calls RemoveComponent, need a different style loop + while (!mComponents.empty()) + { + delete mComponents.back(); + } +} + +void Actor::Update(float deltaTime) +{ + if (mState == EActive) + { + UpdateComponents(deltaTime); + UpdateActor(deltaTime); + } +} + +void Actor::UpdateComponents(float deltaTime) +{ + for (auto comp : mComponents) + { + comp->Update(deltaTime); + } +} + +void Actor::UpdateActor(float deltaTime) +{ +} + +void Actor::AddComponent(Component* component) +{ + // Find the insertion point in the sorted vector + // (The first element with a order higher than me) + int myOrder = component->GetUpdateOrder(); + auto iter = mComponents.begin(); + for (; + iter != mComponents.end(); + ++iter) + { + if (myOrder < (*iter)->GetUpdateOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mComponents.insert(iter, component); +} + +void Actor::RemoveComponent(Component* component) +{ + auto iter = std::find(mComponents.begin(), mComponents.end(), component); + if (iter != mComponents.end()) + { + mComponents.erase(iter); + } +} diff --git a/Chapter02/Actor.h b/Chapter02/Actor.h new file mode 100644 index 00000000..6e435bf7 --- /dev/null +++ b/Chapter02/Actor.h @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include "Math.h" +class Actor +{ +public: + enum State + { + EActive, + EPaused, + EDead + }; + + Actor(class Game* game); + virtual ~Actor(); + + // Update function called from Game (not overridable) + void Update(float deltaTime); + // Updates all the components attached to the actor (not overridable) + void UpdateComponents(float deltaTime); + // Any actor-specific update code (overridable) + virtual void UpdateActor(float deltaTime); + + // Getters/setters + const Vector2& GetPosition() const { return mPosition; } + void SetPosition(const Vector2& pos) { mPosition = pos; } + float GetScale() const { return mScale; } + void SetScale(float scale) { mScale = scale; } + float GetRotation() const { return mRotation; } + void SetRotation(float rotation) { mRotation = rotation; } + + State GetState() const { return mState; } + void SetState(State state) { mState = state; } + + class Game* GetGame() { return mGame; } + + + // Add/remove components + void AddComponent(class Component* component); + void RemoveComponent(class Component* component); +private: + // Actor's state + State mState; + + // Transform + Vector2 mPosition; + float mScale; + float mRotation; + + std::vector mComponents; + class Game* mGame; +}; diff --git a/Chapter02/AnimSpriteComponent.cpp b/Chapter02/AnimSpriteComponent.cpp new file mode 100644 index 00000000..8b5f15e9 --- /dev/null +++ b/Chapter02/AnimSpriteComponent.cpp @@ -0,0 +1,49 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "AnimSpriteComponent.h" +#include "Math.h" + +AnimSpriteComponent::AnimSpriteComponent(Actor* owner, int drawOrder) + :SpriteComponent(owner, drawOrder) + , mCurrFrame(0.0f) + , mAnimFPS(24.0f) +{ +} + +void AnimSpriteComponent::Update(float deltaTime) +{ + SpriteComponent::Update(deltaTime); + + if (mAnimTextures.size() > 0) + { + // Update the current frame based on frame rate + // and delta time + mCurrFrame += mAnimFPS * deltaTime; + + // Wrap current frame if needed + while (mCurrFrame >= mAnimTextures.size()) + { + mCurrFrame -= mAnimTextures.size(); + } + + // Set the current texture + SetTexture(mAnimTextures[static_cast(mCurrFrame)]); + } +} + +void AnimSpriteComponent::SetAnimTextures(const std::vector& textures) +{ + mAnimTextures = textures; + if (mAnimTextures.size() > 0) + { + // Set the active texture to first frame + mCurrFrame = 0.0f; + SetTexture(mAnimTextures[0]); + } +} diff --git a/Chapter02/AnimSpriteComponent.h b/Chapter02/AnimSpriteComponent.h new file mode 100644 index 00000000..d5aa387a --- /dev/null +++ b/Chapter02/AnimSpriteComponent.h @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "SpriteComponent.h" +#include +class AnimSpriteComponent : public SpriteComponent +{ +public: + AnimSpriteComponent(class Actor* owner, int drawOrder = 100); + // Update animation every frame (overriden from component) + void Update(float deltaTime) override; + // Set the textures used for animation + void SetAnimTextures(const std::vector& textures); + // Set/get the animation FPS + float GetAnimFPS() const { return mAnimFPS; } + void SetAnimFPS(float fps) { mAnimFPS = fps; } +private: + // All textures in the animation + std::vector mAnimTextures; + // Current frame displayed + float mCurrFrame; + // Animation frame rate + float mAnimFPS; +}; diff --git a/Chapter02/Assets/Enemy01.png b/Chapter02/Assets/Enemy01.png new file mode 100644 index 00000000..e9869958 Binary files /dev/null and b/Chapter02/Assets/Enemy01.png differ diff --git a/Chapter02/Assets/Enemy02.png b/Chapter02/Assets/Enemy02.png new file mode 100644 index 00000000..480e86df Binary files /dev/null and b/Chapter02/Assets/Enemy02.png differ diff --git a/Chapter02/Assets/Enemy03.png b/Chapter02/Assets/Enemy03.png new file mode 100644 index 00000000..1f2a9b7c Binary files /dev/null and b/Chapter02/Assets/Enemy03.png differ diff --git a/Chapter02/Assets/Enemy04.png b/Chapter02/Assets/Enemy04.png new file mode 100644 index 00000000..f22b4de3 Binary files /dev/null and b/Chapter02/Assets/Enemy04.png differ diff --git a/Chapter02/Assets/Enemy05.png b/Chapter02/Assets/Enemy05.png new file mode 100644 index 00000000..1f2a9b7c Binary files /dev/null and b/Chapter02/Assets/Enemy05.png differ diff --git a/Chapter02/Assets/Enemy06.png b/Chapter02/Assets/Enemy06.png new file mode 100644 index 00000000..480e86df Binary files /dev/null and b/Chapter02/Assets/Enemy06.png differ diff --git a/Chapter02/Assets/Farback01.png b/Chapter02/Assets/Farback01.png new file mode 100644 index 00000000..bd04827e Binary files /dev/null and b/Chapter02/Assets/Farback01.png differ diff --git a/Chapter02/Assets/Farback02.png b/Chapter02/Assets/Farback02.png new file mode 100644 index 00000000..f6dec9a8 Binary files /dev/null and b/Chapter02/Assets/Farback02.png differ diff --git a/Chapter02/Assets/LICENSE.txt b/Chapter02/Assets/LICENSE.txt new file mode 100644 index 00000000..c7c07f86 --- /dev/null +++ b/Chapter02/Assets/LICENSE.txt @@ -0,0 +1,5 @@ +These sprites were designed by Jacob Zinman-Jeanes (http://jeanes.co) for Gamedevtuts+ (http://gamedev.tutsplus.com/). + +To find out more about what's in this mini sprite pack, check out this post: http://gamedev.tutsplus.com/articles/news/enjoy-these-totally-free-space-based-shoot-em-up-sprites/ + +These sprites are licensed under the CC BY 3.0 license: http://creativecommons.org/licenses/by/3.0/ \ No newline at end of file diff --git a/Chapter02/Assets/Laser.png b/Chapter02/Assets/Laser.png new file mode 100644 index 00000000..c65bc5e0 Binary files /dev/null and b/Chapter02/Assets/Laser.png differ diff --git a/Chapter02/Assets/MapLayer1.csv b/Chapter02/Assets/MapLayer1.csv new file mode 100644 index 00000000..619c90f2 --- /dev/null +++ b/Chapter02/Assets/MapLayer1.csv @@ -0,0 +1,24 @@ +27,42,43,44,42,43,44,42,43,44,42,43,44,42,43,44,42,43,44,42,43,44,42,43,44,42,43,44,42,43,44,42 +21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +29,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +37,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +29,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,48,49,52,53,54,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +27,11,12,13,14,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +27,43,44,45,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +29,-1,-1,-1,-1,-1,48,49,52,53,54,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +37,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,48,49,52,53,54,-1,-1,-1,48,49,50,51,52,50 +21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +27,10,11,12,11,12,10,11,12,10,11,12,10,11,12,13,14,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +27,43,44,42,43,44,42,43,44,42,43,44,42,43,44,45,-1,-1,-1,-1,-1,-1,48,49,53,54,-1,-1,-1,-1,-1,-1 +21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +29,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,56,57,58,59,57 +37,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,8,9,10,11,12,10,11,12,11,13,65,66,67,65 +21,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,41,42,42,43,44,42,43,44,45,-1,-1,-1,-1 +27,10,11,12,13,14,-1,-1,-1,-1,-1,-1,-1,48,49,53,54,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +20,26,27,28,18,11,12,13,14,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +28,34,35,36,34,35,36,27,11,12,13,14,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +36,42,43,44,44,42,43,44,42,43,44,52,50,51,52,50,51,52,50,51,52,53,54,-1,-1,-1,-1,-1,-1,-1,-1,-1 +37,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,48,49,52,53,54,-1,8,9,10 +29,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,25,26 diff --git a/Chapter02/Assets/MapLayer2.csv b/Chapter02/Assets/MapLayer2.csv new file mode 100644 index 00000000..49139b94 --- /dev/null +++ b/Chapter02/Assets/MapLayer2.csv @@ -0,0 +1,24 @@ +70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70 +70,70,70,70,70,70,70,70,70,70,70,61,62,63,70,70,70,70,70,70,70,70,70,61,63,61,62,62,63,70,61,62 +70,70,70,70,70,70,70,70,70,70,70,69,0,71,70,70,70,70,70,70,61,62,63,69,71,69,0,0,71,70,69,0 +70,70,70,70,70,70,70,70,70,70,70,69,0,75,62,63,70,70,70,70,69,0,71,69,71,69,0,0,71,70,69,0 +70,70,70,70,70,61,62,62,62,62,62,76,0,0,0,71,70,70,70,61,76,0,71,69,75,76,0,0,71,70,69,0 +70,70,70,70,70,69,0,0,0,0,0,0,0,0,0,71,70,70,70,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,70,70,70,69,0,0,0,0,0,0,0,0,0,75,63,61,63,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,70,70,70,69,0,0,0,0,0,0,0,0,0,0,75,76,71,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,70,61,62,76,0,0,0,0,0,0,0,0,0,0,0,0,71,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,61,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,73,79,69,0,0,71,69,0,0,0,0,71,70,69,0 +70,70,77,78,78,74,0,0,0,0,0,0,0,0,0,0,0,71,70,77,78,78,79,69,0,0,0,0,71,70,77,78 +70,70,70,70,70,77,78,78,78,74,0,0,0,0,73,78,78,79,70,70,70,70,70,69,0,0,0,0,71,70,70,70 +70,70,70,70,70,70,70,70,70,69,73,78,74,0,71,70,70,70,70,70,70,70,70,77,78,78,78,78,79,70,70,70 +70,70,70,70,70,70,70,70,70,77,79,70,77,78,79,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70 +70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70 +70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70 +70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70 +70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70 +70,70,70,70,70,70,70,70,70,70,70,70,70,70,61,63,61,62,62,63,61,63,70,70,70,70,70,70,70,70,70,70 +70,70,70,70,70,70,70,70,70,70,70,70,70,61,76,71,69,0,0,75,76,71,70,70,70,70,70,70,70,70,70,70 diff --git a/Chapter02/Assets/MapLayer3.csv b/Chapter02/Assets/MapLayer3.csv new file mode 100644 index 00000000..ed9cb2cf --- /dev/null +++ b/Chapter02/Assets/MapLayer3.csv @@ -0,0 +1,24 @@ +96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97 +104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105 +112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113 +96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97 +104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105 +112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113 +96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97 +104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105 +112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113 +96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97 +104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105 +112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113 +96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97 +104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105 +112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113 +96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97 +104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105 +112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113 +96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97 +104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105 +112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113 +96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97,98,96,97 +104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105,106,104,105 +112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113,114,112,113 diff --git a/Chapter02/Assets/Ship01.png b/Chapter02/Assets/Ship01.png new file mode 100644 index 00000000..cf23a63d Binary files /dev/null and b/Chapter02/Assets/Ship01.png differ diff --git a/Chapter02/Assets/Ship02.png b/Chapter02/Assets/Ship02.png new file mode 100644 index 00000000..ec84d67f Binary files /dev/null and b/Chapter02/Assets/Ship02.png differ diff --git a/Chapter02/Assets/Ship03.png b/Chapter02/Assets/Ship03.png new file mode 100644 index 00000000..fb31dc32 Binary files /dev/null and b/Chapter02/Assets/Ship03.png differ diff --git a/Chapter02/Assets/Ship04.png b/Chapter02/Assets/Ship04.png new file mode 100644 index 00000000..51f040be Binary files /dev/null and b/Chapter02/Assets/Ship04.png differ diff --git a/Chapter02/Assets/Stars.png b/Chapter02/Assets/Stars.png new file mode 100644 index 00000000..3ceb00ec Binary files /dev/null and b/Chapter02/Assets/Stars.png differ diff --git a/Chapter02/Assets/Tiles.png b/Chapter02/Assets/Tiles.png new file mode 100644 index 00000000..58409387 Binary files /dev/null and b/Chapter02/Assets/Tiles.png differ diff --git a/Chapter02/BGSpriteComponent.cpp b/Chapter02/BGSpriteComponent.cpp new file mode 100644 index 00000000..4c143704 --- /dev/null +++ b/Chapter02/BGSpriteComponent.cpp @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "BGSpriteComponent.h" +#include "Actor.h" + +BGSpriteComponent::BGSpriteComponent(class Actor* owner, int drawOrder) + :SpriteComponent(owner, drawOrder) + ,mScrollSpeed(0.0f) +{ +} + +void BGSpriteComponent::Update(float deltaTime) +{ + SpriteComponent::Update(deltaTime); + for (auto& bg : mBGTextures) + { + // Update the x offset + bg.mOffset.x += mScrollSpeed * deltaTime; + // If this is completely off the screen, reset offset to + // the right of the last bg texture + if (bg.mOffset.x < -mScreenSize.x) + { + bg.mOffset.x = (mBGTextures.size() - 1) * mScreenSize.x - 1; + } + } +} + +void BGSpriteComponent::Draw(SDL_Renderer* renderer) +{ + // Draw each background texture + for (auto& bg : mBGTextures) + { + SDL_Rect r; + // Assume screen size dimensions + r.w = static_cast(mScreenSize.x); + r.h = static_cast(mScreenSize.y); + // Center the rectangle around the position of the owner + r.x = static_cast(mOwner->GetPosition().x - r.w / 2 + bg.mOffset.x); + r.y = static_cast(mOwner->GetPosition().y - r.h / 2 + bg.mOffset.y); + + // Draw this background + SDL_RenderCopy(renderer, + bg.mTexture, + nullptr, + &r + ); + } +} + +void BGSpriteComponent::SetBGTextures(const std::vector& textures) +{ + int count = 0; + for (auto tex : textures) + { + BGTexture temp; + temp.mTexture = tex; + // Each texture is screen width in offset + temp.mOffset.x = count * mScreenSize.x; + temp.mOffset.y = 0; + mBGTextures.emplace_back(temp); + count++; + } +} diff --git a/Chapter02/BGSpriteComponent.h b/Chapter02/BGSpriteComponent.h new file mode 100644 index 00000000..0d4c9e50 --- /dev/null +++ b/Chapter02/BGSpriteComponent.h @@ -0,0 +1,37 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "SpriteComponent.h" +#include +#include "Math.h" +class BGSpriteComponent : public SpriteComponent +{ +public: + // Set draw order to default to lower (so it's in the background) + BGSpriteComponent(class Actor* owner, int drawOrder = 10); + // Update/draw overriden from parent + void Update(float deltaTime) override; + void Draw(SDL_Renderer* renderer) override; + // Set the textures used for the background + void SetBGTextures(const std::vector& textures); + // Get/set screen size and scroll speed + void SetScreenSize(const Vector2& size) { mScreenSize = size; } + void SetScrollSpeed(float speed) { mScrollSpeed = speed; } + float GetScrollSpeed() const { return mScrollSpeed; } +private: + // Struct to encapsulate each bg image and its offset + struct BGTexture + { + SDL_Texture* mTexture; + Vector2 mOffset; + }; + std::vector mBGTextures; + Vector2 mScreenSize; + float mScrollSpeed; +}; diff --git a/Chapter02/Chapter02-mac.xcodeproj/project.pbxproj b/Chapter02/Chapter02-mac.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ee08bd01 --- /dev/null +++ b/Chapter02/Chapter02-mac.xcodeproj/project.pbxproj @@ -0,0 +1,358 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9223C4781F009428009A94D7 /* Game.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4671F009428009A94D7 /* Game.cpp */; }; + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4681F009428009A94D7 /* Actor.cpp */; }; + 9223C47A1F009428009A94D7 /* AnimSpriteComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C46A1F009428009A94D7 /* AnimSpriteComponent.cpp */; }; + 9223C47B1F009428009A94D7 /* BGSpriteComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C46C1F009428009A94D7 /* BGSpriteComponent.cpp */; }; + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C46E1F009428009A94D7 /* Component.cpp */; }; + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4711F009428009A94D7 /* Main.cpp */; }; + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4721F009428009A94D7 /* Math.cpp */; }; + 9223C47F1F009428009A94D7 /* Ship.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4741F009428009A94D7 /* Ship.cpp */; }; + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4761F009428009A94D7 /* SpriteComponent.cpp */; }; + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92D324FA1B697389005A86C7 /* CoreFoundation.framework */; }; + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92E46E931B6353E50035CD21 /* OpenGL.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 9223C4671F009428009A94D7 /* Game.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Game.cpp; sourceTree = ""; }; + 9223C4681F009428009A94D7 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 9223C4691F009428009A94D7 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 9223C46A1F009428009A94D7 /* AnimSpriteComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AnimSpriteComponent.cpp; sourceTree = ""; }; + 9223C46B1F009428009A94D7 /* AnimSpriteComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnimSpriteComponent.h; sourceTree = ""; }; + 9223C46C1F009428009A94D7 /* BGSpriteComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGSpriteComponent.cpp; sourceTree = ""; }; + 9223C46D1F009428009A94D7 /* BGSpriteComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGSpriteComponent.h; sourceTree = ""; }; + 9223C46E1F009428009A94D7 /* Component.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Component.cpp; sourceTree = ""; }; + 9223C46F1F009428009A94D7 /* Component.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Component.h; sourceTree = ""; }; + 9223C4701F009428009A94D7 /* Game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Game.h; sourceTree = ""; }; + 9223C4711F009428009A94D7 /* Main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Main.cpp; sourceTree = ""; }; + 9223C4721F009428009A94D7 /* Math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Math.cpp; sourceTree = ""; }; + 9223C4731F009428009A94D7 /* Math.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Math.h; sourceTree = ""; }; + 9223C4741F009428009A94D7 /* Ship.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Ship.cpp; sourceTree = ""; }; + 9223C4751F009428009A94D7 /* Ship.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ship.h; sourceTree = ""; }; + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpriteComponent.cpp; sourceTree = ""; }; + 9223C4771F009428009A94D7 /* SpriteComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpriteComponent.h; sourceTree = ""; }; + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 92E46DF71B634EA30035CD21 /* Game-mac */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Game-mac"; sourceTree = BUILT_PRODUCTS_DIR; }; + 92E46E931B6353E50035CD21 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 92E46DF41B634EA30035CD21 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */, + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 92E46DEE1B634EA30035CD21 = { + isa = PBXGroup; + children = ( + 9223C4681F009428009A94D7 /* Actor.cpp */, + 9223C4691F009428009A94D7 /* Actor.h */, + 9223C46A1F009428009A94D7 /* AnimSpriteComponent.cpp */, + 9223C46B1F009428009A94D7 /* AnimSpriteComponent.h */, + 9223C46C1F009428009A94D7 /* BGSpriteComponent.cpp */, + 9223C46D1F009428009A94D7 /* BGSpriteComponent.h */, + 9223C46E1F009428009A94D7 /* Component.cpp */, + 9223C46F1F009428009A94D7 /* Component.h */, + 9223C4671F009428009A94D7 /* Game.cpp */, + 9223C4701F009428009A94D7 /* Game.h */, + 9223C4711F009428009A94D7 /* Main.cpp */, + 9223C4721F009428009A94D7 /* Math.cpp */, + 9223C4731F009428009A94D7 /* Math.h */, + 9223C4741F009428009A94D7 /* Ship.cpp */, + 9223C4751F009428009A94D7 /* Ship.h */, + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */, + 9223C4771F009428009A94D7 /* SpriteComponent.h */, + 92E46DF81B634EA30035CD21 /* Products */, + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */, + 92E46E931B6353E50035CD21 /* OpenGL.framework */, + ); + sourceTree = ""; + }; + 92E46DF81B634EA30035CD21 /* Products */ = { + isa = PBXGroup; + children = ( + 92E46DF71B634EA30035CD21 /* Game-mac */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 92E46DF61B634EA30035CD21 /* Game-mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */; + buildPhases = ( + 92E46DF31B634EA30035CD21 /* Sources */, + 92E46DF41B634EA30035CD21 /* Frameworks */, + 92E46EA11B63615B0035CD21 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Game-mac"; + productName = "Game-mac"; + productReference = 92E46DF71B634EA30035CD21 /* Game-mac */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 92E46DEF1B634EA30035CD21 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Sanjay Madhav"; + TargetAttributes = { + 92E46DF61B634EA30035CD21 = { + CreatedOnToolsVersion = 6.4; + }; + }; + }; + buildConfigurationList = 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter02-mac" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 92E46DEE1B634EA30035CD21; + productRefGroup = 92E46DF81B634EA30035CD21 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 92E46DF61B634EA30035CD21 /* Game-mac */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 92E46EA11B63615B0035CD21 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -d \"$BUILD_DIR/Debug\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Debug\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Debug\nfi\n\nif [ -d \"$BUILD_DIR/Release\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Release\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Release\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 92E46DF31B634EA30035CD21 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */, + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */, + 9223C47A1F009428009A94D7 /* AnimSpriteComponent.cpp in Sources */, + 9223C47B1F009428009A94D7 /* BGSpriteComponent.cpp in Sources */, + 9223C4781F009428009A94D7 /* Game.cpp in Sources */, + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */, + 9223C47F1F009428009A94D7 /* Ship.cpp in Sources */, + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */, + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 92E46DFC1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 92E46DFD1B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 92E46DFF1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 92E46E001B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter02-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFC1B634EA40035CD21 /* Debug */, + 92E46DFD1B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFF1B634EA40035CD21 /* Debug */, + 92E46E001B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 92E46DEF1B634EA30035CD21 /* Project object */; +} diff --git a/Chapter02/Chapter02-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Chapter02/Chapter02-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..2bc09709 --- /dev/null +++ b/Chapter02/Chapter02-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Chapter02/Chapter02-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme b/Chapter02/Chapter02-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme new file mode 100644 index 00000000..52bf4f32 --- /dev/null +++ b/Chapter02/Chapter02-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter02/Chapter02-windows.sln b/Chapter02/Chapter02-windows.sln new file mode 100644 index 00000000..f560df0f --- /dev/null +++ b/Chapter02/Chapter02-windows.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "Game.vcxproj", "{BC508D87-495F-4554-932D-DD68388B63CC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.Build.0 = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.ActiveCfg = Release|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Chapter02/Component.cpp b/Chapter02/Component.cpp new file mode 100644 index 00000000..c4ed432d --- /dev/null +++ b/Chapter02/Component.cpp @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Component.h" +#include "Actor.h" + +Component::Component(Actor* owner, int updateOrder) + :mOwner(owner) + ,mUpdateOrder(updateOrder) +{ + // Add to actor's vector of components + mOwner->AddComponent(this); +} + +Component::~Component() +{ + mOwner->RemoveComponent(this); +} + +void Component::Update(float deltaTime) +{ +} diff --git a/Chapter02/Component.h b/Chapter02/Component.h new file mode 100644 index 00000000..19e21f02 --- /dev/null +++ b/Chapter02/Component.h @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +class Component +{ +public: + // Constructor + // (the lower the update order, the earlier the component updates) + Component(class Actor* owner, int updateOrder = 100); + // Destructor + virtual ~Component(); + // Update this component by delta time + virtual void Update(float deltaTime); + + int GetUpdateOrder() const { return mUpdateOrder; } +protected: + // Owning actor + class Actor* mOwner; + // Update order of component + int mUpdateOrder; +}; diff --git a/Chapter02/Game.cpp b/Chapter02/Game.cpp new file mode 100644 index 00000000..2bf0af03 --- /dev/null +++ b/Chapter02/Game.cpp @@ -0,0 +1,302 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" +#include "SDL/SDL_image.h" +#include +#include "Actor.h" +#include "SpriteComponent.h" +#include "Ship.h" +#include "BGSpriteComponent.h" + +Game::Game() +:mWindow(nullptr) +,mRenderer(nullptr) +,mIsRunning(true) +,mUpdatingActors(false) +{ + +} + +bool Game::Initialize() +{ + if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) + { + SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); + return false; + } + + mWindow = SDL_CreateWindow("Game Programming in C++ (Chapter 2)", 100, 100, 1024, 768, 0); + if (!mWindow) + { + SDL_Log("Failed to create window: %s", SDL_GetError()); + return false; + } + + mRenderer = SDL_CreateRenderer(mWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!mRenderer) + { + SDL_Log("Failed to create renderer: %s", SDL_GetError()); + return false; + } + + if (IMG_Init(IMG_INIT_PNG) == 0) + { + SDL_Log("Unable to initialize SDL_image: %s", SDL_GetError()); + return false; + } + + LoadData(); + + mTicksCount = SDL_GetTicks(); + + return true; +} + +void Game::RunLoop() +{ + while (mIsRunning) + { + ProcessInput(); + UpdateGame(); + GenerateOutput(); + } +} + +void Game::ProcessInput() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + mIsRunning = false; + break; + } + } + + const Uint8* state = SDL_GetKeyboardState(NULL); + if (state[SDL_SCANCODE_ESCAPE]) + { + mIsRunning = false; + } + + // Process ship input + mShip->ProcessKeyboard(state); +} + +void Game::UpdateGame() +{ + // Compute delta time + // Wait until 16ms has elapsed since last frame + while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16)) + ; + + float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f; + if (deltaTime > 0.05f) + { + deltaTime = 0.05f; + } + mTicksCount = SDL_GetTicks(); + + // Update all actors + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->Update(deltaTime); + } + mUpdatingActors = false; + + // Move any pending actors to mActors + for (auto pending : mPendingActors) + { + mActors.emplace_back(pending); + } + mPendingActors.clear(); + + // Add any dead actors to a temp vector + std::vector deadActors; + for (auto actor : mActors) + { + if (actor->GetState() == Actor::EDead) + { + deadActors.emplace_back(actor); + } + } + + // Delete dead actors (which removes them from mActors) + for (auto actor : deadActors) + { + delete actor; + } +} + +void Game::GenerateOutput() +{ + SDL_SetRenderDrawColor(mRenderer, 0, 0, 0, 255); + SDL_RenderClear(mRenderer); + + // Draw all sprite components + for (auto sprite : mSprites) + { + sprite->Draw(mRenderer); + } + + SDL_RenderPresent(mRenderer); +} + +void Game::LoadData() +{ + // Create player's ship + mShip = new Ship(this); + mShip->SetPosition(Vector2(100.0f, 384.0f)); + mShip->SetScale(1.5f); + + // Create actor for the background (this doesn't need a subclass) + Actor* temp = new Actor(this); + temp->SetPosition(Vector2(512.0f, 384.0f)); + // Create the "far back" background + BGSpriteComponent* bg = new BGSpriteComponent(temp); + bg->SetScreenSize(Vector2(1024.0f, 768.0f)); + std::vector bgtexs = { + GetTexture("Assets/Farback01.png"), + GetTexture("Assets/Farback02.png") + }; + bg->SetBGTextures(bgtexs); + bg->SetScrollSpeed(-100.0f); + // Create the closer background + bg = new BGSpriteComponent(temp, 50); + bg->SetScreenSize(Vector2(1024.0f, 768.0f)); + bgtexs = { + GetTexture("Assets/Stars.png"), + GetTexture("Assets/Stars.png") + }; + bg->SetBGTextures(bgtexs); + bg->SetScrollSpeed(-200.0f); +} + +void Game::UnloadData() +{ + // Delete actors + // Because ~Actor calls RemoveActor, have to use a different style loop + while (!mActors.empty()) + { + delete mActors.back(); + } + + // Destroy textures + for (auto i : mTextures) + { + SDL_DestroyTexture(i.second); + } + mTextures.clear(); +} + +SDL_Texture* Game::GetTexture(const std::string& fileName) +{ + SDL_Texture* tex = nullptr; + // Is the texture already in the map? + auto iter = mTextures.find(fileName); + if (iter != mTextures.end()) + { + tex = iter->second; + } + else + { + // Load from file + SDL_Surface* surf = IMG_Load(fileName.c_str()); + if (!surf) + { + SDL_Log("Failed to load texture file %s", fileName.c_str()); + return nullptr; + } + + // Create texture from surface + tex = SDL_CreateTextureFromSurface(mRenderer, surf); + SDL_FreeSurface(surf); + if (!tex) + { + SDL_Log("Failed to convert surface to texture for %s", fileName.c_str()); + return nullptr; + } + + mTextures.emplace(fileName.c_str(), tex); + } + return tex; +} + +void Game::Shutdown() +{ + UnloadData(); + IMG_Quit(); + SDL_DestroyRenderer(mRenderer); + SDL_DestroyWindow(mWindow); + SDL_Quit(); +} + +void Game::AddActor(Actor* actor) +{ + // If we're updating actors, need to add to pending + if (mUpdatingActors) + { + mPendingActors.emplace_back(actor); + } + else + { + mActors.emplace_back(actor); + } +} + +void Game::RemoveActor(Actor* actor) +{ + // Is it in pending actors? + auto iter = std::find(mPendingActors.begin(), mPendingActors.end(), actor); + if (iter != mPendingActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mPendingActors.end() - 1); + mPendingActors.pop_back(); + } + + // Is it in actors? + iter = std::find(mActors.begin(), mActors.end(), actor); + if (iter != mActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mActors.end() - 1); + mActors.pop_back(); + } +} + +void Game::AddSprite(SpriteComponent* sprite) +{ + // Find the insertion point in the sorted vector + // (The first element with a higher draw order than me) + int myDrawOrder = sprite->GetDrawOrder(); + auto iter = mSprites.begin(); + for ( ; + iter != mSprites.end(); + ++iter) + { + if (myDrawOrder < (*iter)->GetDrawOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mSprites.insert(iter, sprite); +} + +void Game::RemoveSprite(SpriteComponent* sprite) +{ + // (We can't swap because it ruins ordering) + auto iter = std::find(mSprites.begin(), mSprites.end(), sprite); + mSprites.erase(iter); +} diff --git a/Chapter02/Game.h b/Chapter02/Game.h new file mode 100644 index 00000000..03f0548c --- /dev/null +++ b/Chapter02/Game.h @@ -0,0 +1,57 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "SDL/SDL.h" +#include +#include +#include + +class Game +{ +public: + Game(); + bool Initialize(); + void RunLoop(); + void Shutdown(); + + void AddActor(class Actor* actor); + void RemoveActor(class Actor* actor); + + void AddSprite(class SpriteComponent* sprite); + void RemoveSprite(class SpriteComponent* sprite); + + SDL_Texture* GetTexture(const std::string& fileName); +private: + void ProcessInput(); + void UpdateGame(); + void GenerateOutput(); + void LoadData(); + void UnloadData(); + + // Map of textures loaded + std::unordered_map mTextures; + + // All the actors in the game + std::vector mActors; + // Any pending actors + std::vector mPendingActors; + + // All the sprite components drawn + std::vector mSprites; + + SDL_Window* mWindow; + SDL_Renderer* mRenderer; + Uint32 mTicksCount; + bool mIsRunning; + // Track if we're updating actors right now + bool mUpdatingActors; + + // Game-specific + class Ship* mShip; // Player's ship +}; diff --git a/Chapter02/Game.vcxproj b/Chapter02/Game.vcxproj new file mode 100644 index 00000000..ff4157f9 --- /dev/null +++ b/Chapter02/Game.vcxproj @@ -0,0 +1,124 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + {BC508D87-495F-4554-932D-DD68388B63CC} + Win32Proj + Game + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + /NODEFAULTLIB:msvcrt.lib %(AdditionalOptions) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + true + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + + \ No newline at end of file diff --git a/Chapter02/Game.vcxproj.filters b/Chapter02/Game.vcxproj.filters new file mode 100644 index 00000000..760534ef --- /dev/null +++ b/Chapter02/Game.vcxproj.filters @@ -0,0 +1,64 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Chapter02/Main.cpp b/Chapter02/Main.cpp new file mode 100644 index 00000000..22ea0c69 --- /dev/null +++ b/Chapter02/Main.cpp @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" + +int main(int argc, char** argv) +{ + Game game; + bool success = game.Initialize(); + if (success) + { + game.RunLoop(); + } + game.Shutdown(); + return 0; +} diff --git a/Chapter02/Math.cpp b/Chapter02/Math.cpp new file mode 100644 index 00000000..a16e7261 --- /dev/null +++ b/Chapter02/Math.cpp @@ -0,0 +1,239 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Math.h" + +const Vector2 Vector2::Zero(0.0f, 0.0f); +const Vector2 Vector2::UnitX(1.0f, 0.0f); +const Vector2 Vector2::UnitY(0.0f, 1.0f); +const Vector2 Vector2::NegUnitX(-1.0f, 0.0f); +const Vector2 Vector2::NegUnitY(0.0f, -1.0f); + +const Vector3 Vector3::Zero(0.0f, 0.0f, 0.f); +const Vector3 Vector3::UnitX(1.0f, 0.0f, 0.0f); +const Vector3 Vector3::UnitY(0.0f, 1.0f, 0.0f); +const Vector3 Vector3::UnitZ(0.0f, 0.0f, 1.0f); +const Vector3 Vector3::NegUnitX(-1.0f, 0.0f, 0.0f); +const Vector3 Vector3::NegUnitY(0.0f, -1.0f, 0.0f); +const Vector3 Vector3::NegUnitZ(0.0f, 0.0f, -1.0f); +const Vector3 Vector3::Infinity(Math::Infinity, Math::Infinity, Math::Infinity); +const Vector3 Vector3::NegInfinity(Math::NegInfinity, Math::NegInfinity, Math::NegInfinity); + +static float m3Ident[3][3] = +{ + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f } +}; +const Matrix3 Matrix3::Identity(m3Ident); + +static float m4Ident[4][4] = +{ + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; + +const Matrix4 Matrix4::Identity(m4Ident); + +const Quaternion Quaternion::Identity(0.0f, 0.0f, 0.0f, 1.0f); + +Vector2 Vector2::Transform(const Vector2& vec, const Matrix3& mat, float w /*= 1.0f*/) +{ + Vector2 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + w * mat.mat[2][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + w * mat.mat[2][1]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +Vector3 Vector3::Transform(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +// This will transform the vector and renormalize the w component +Vector3 Vector3::TransformWithPerspDiv(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + float transformedW = vec.x * mat.mat[0][3] + vec.y * mat.mat[1][3] + + vec.z * mat.mat[2][3] + w * mat.mat[3][3]; + if (!Math::NearZero(Math::Abs(transformedW))) + { + transformedW = 1.0f / transformedW; + retVal *= transformedW; + } + return retVal; +} + +// Transform a Vector3 by a quaternion +Vector3 Vector3::Transform(const Vector3& v, const Quaternion& q) +{ + // v + 2.0*cross(q.xyz, cross(q.xyz,v) + q.w*v); + Vector3 qv(q.x, q.y, q.z); + Vector3 retVal = v; + retVal += 2.0f * Vector3::Cross(qv, Vector3::Cross(qv, v) + q.w * v); + return retVal; +} + +void Matrix4::Invert() +{ + // Thanks slow math + // This is a really janky way to unroll everything... + float tmp[12]; + float src[16]; + float dst[16]; + float det; + + // Transpose matrix + // row 1 to col 1 + src[0] = mat[0][0]; + src[4] = mat[0][1]; + src[8] = mat[0][2]; + src[12] = mat[0][3]; + + // row 2 to col 2 + src[1] = mat[1][0]; + src[5] = mat[1][1]; + src[9] = mat[1][2]; + src[13] = mat[1][3]; + + // row 3 to col 3 + src[2] = mat[2][0]; + src[6] = mat[2][1]; + src[10] = mat[2][2]; + src[14] = mat[2][3]; + + // row 4 to col 4 + src[3] = mat[3][0]; + src[7] = mat[3][1]; + src[11] = mat[3][2]; + src[15] = mat[3][3]; + + // Calculate cofactors + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + dst[0] = tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]; + dst[0] -= tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]; + dst[1] = tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]; + dst[1] -= tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]; + dst[2] = tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]; + dst[2] -= tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]; + dst[3] = tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]; + dst[3] -= tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]; + dst[4] = tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]; + dst[4] -= tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]; + dst[5] = tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]; + dst[5] -= tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]; + dst[6] = tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]; + dst[6] -= tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]; + dst[7] = tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]; + dst[7] -= tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]; + + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + dst[8] = tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]; + dst[8] -= tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]; + dst[9] = tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]; + dst[9] -= tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]; + dst[10] = tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]; + dst[10] -= tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]; + dst[11] = tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]; + dst[11] -= tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]; + dst[12] = tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]; + dst[12] -= tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]; + dst[13] = tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]; + dst[13] -= tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]; + dst[14] = tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]; + dst[14] -= tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]; + dst[15] = tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]; + dst[15] -= tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]; + + // Calculate determinant + det = src[0] * dst[0] + src[1] * dst[1] + src[2] * dst[2] + src[3] * dst[3]; + + // Inverse of matrix is divided by determinant + det = 1 / det; + for (int j = 0; j < 16; j++) + { + dst[j] *= det; + } + + // Set it back + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + mat[i][j] = dst[i * 4 + j]; + } + } +} + +Matrix4 Matrix4::CreateFromQuaternion(const class Quaternion& q) +{ + float mat[4][4]; + + mat[0][0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z; + mat[0][1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z; + mat[0][2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y; + mat[0][3] = 0.0f; + + mat[1][0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z; + mat[1][1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z; + mat[1][2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x; + mat[1][3] = 0.0f; + + mat[2][0] = 2.0f * q.x * q.z + 2.0f * q.w * q.y; + mat[2][1] = 2.0f * q.y * q.z - 2.0f * q.w * q.x; + mat[2][2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y; + mat[2][3] = 0.0f; + + mat[3][0] = 0.0f; + mat[3][1] = 0.0f; + mat[3][2] = 0.0f; + mat[3][3] = 1.0f; + + return Matrix4(mat); +} diff --git a/Chapter02/Math.h b/Chapter02/Math.h new file mode 100644 index 00000000..752963f1 --- /dev/null +++ b/Chapter02/Math.h @@ -0,0 +1,1033 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +namespace Math +{ + const float Pi = 3.1415926535f; + const float TwoPi = Pi * 2.0f; + const float PiOver2 = Pi / 2.0f; + const float Infinity = std::numeric_limits::infinity(); + const float NegInfinity = -std::numeric_limits::infinity(); + + inline float ToRadians(float degrees) + { + return degrees * Pi / 180.0f; + } + + inline float ToDegrees(float radians) + { + return radians * 180.0f / Pi; + } + + inline bool NearZero(float val, float epsilon = 0.001f) + { + if (fabs(val) <= epsilon) + { + return true; + } + else + { + return false; + } + } + + template + T Max(const T& a, const T& b) + { + return (a < b ? b : a); + } + + template + T Min(const T& a, const T& b) + { + return (a < b ? a : b); + } + + template + T Clamp(const T& value, const T& lower, const T& upper) + { + return Min(upper, Max(lower, value)); + } + + inline float Abs(float value) + { + return fabs(value); + } + + inline float Cos(float angle) + { + return cosf(angle); + } + + inline float Sin(float angle) + { + return sinf(angle); + } + + inline float Tan(float angle) + { + return tanf(angle); + } + + inline float Acos(float value) + { + return acosf(value); + } + + inline float Atan2(float y, float x) + { + return atan2f(y, x); + } + + inline float Cot(float angle) + { + return 1.0f / Tan(angle); + } + + inline float Lerp(float a, float b, float f) + { + return a + f * (b - a); + } + + inline float Sqrt(float value) + { + return sqrtf(value); + } + + inline float Fmod(float numer, float denom) + { + return fmod(numer, denom); + } +} + +// 2D Vector +class Vector2 +{ +public: + float x; + float y; + + Vector2() + :x(0.0f) + ,y(0.0f) + {} + + explicit Vector2(float inX, float inY) + :x(inX) + ,y(inY) + {} + + // Set both components in one line + void Set(float inX, float inY) + { + x = inX; + y = inY; + } + + // Vector addition (a + b) + friend Vector2 operator+(const Vector2& a, const Vector2& b) + { + return Vector2(a.x + b.x, a.y + b.y); + } + + // Vector subtraction (a - b) + friend Vector2 operator-(const Vector2& a, const Vector2& b) + { + return Vector2(a.x - b.x, a.y - b.y); + } + + // Component-wise multiplication + // (a.x * b.x, ...) + friend Vector2 operator*(const Vector2& a, const Vector2& b) + { + return Vector2(a.x * b.x, a.y * b.y); + } + + // Scalar multiplication + friend Vector2 operator*(const Vector2& vec, float scalar) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar multiplication + friend Vector2 operator*(float scalar, const Vector2& vec) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar *= + Vector2& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + return *this; + } + + // Vector += + Vector2& operator+=(const Vector2& right) + { + x += right.x; + y += right.y; + return *this; + } + + // Vector -= + Vector2& operator-=(const Vector2& right) + { + x -= right.x; + y -= right.y; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + } + + // Normalize the provided vector + static Vector2 Normalize(const Vector2& vec) + { + Vector2 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector2& a, const Vector2& b) + { + return (a.x * b.x + a.y * b.y); + } + + // Lerp from A to B by f + static Vector2 Lerp(const Vector2& a, const Vector2& b, float f) + { + return Vector2(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector2 Reflect(const Vector2& v, const Vector2& n) + { + return v - 2.0f * Vector2::Dot(v, n) * n; + } + + // Transform vector by matrix + static Vector2 Transform(const Vector2& vec, const class Matrix3& mat, float w = 1.0f); + + static const Vector2 Zero; + static const Vector2 UnitX; + static const Vector2 UnitY; + static const Vector2 NegUnitX; + static const Vector2 NegUnitY; +}; + +// 3D Vector +class Vector3 +{ +public: + float x; + float y; + float z; + + Vector3() + :x(0.0f) + ,y(0.0f) + ,z(0.0f) + {} + + explicit Vector3(float inX, float inY, float inZ) + :x(inX) + ,y(inY) + ,z(inZ) + {} + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&x); + } + + // Set all three components in one line + void Set(float inX, float inY, float inZ) + { + x = inX; + y = inY; + z = inZ; + } + + // Vector addition (a + b) + friend Vector3 operator+(const Vector3& a, const Vector3& b) + { + return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); + } + + // Vector subtraction (a - b) + friend Vector3 operator-(const Vector3& a, const Vector3& b) + { + return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); + } + + // Component-wise multiplication + friend Vector3 operator*(const Vector3& left, const Vector3& right) + { + return Vector3(left.x * right.x, left.y * right.y, left.z * right.z); + } + + // Scalar multiplication + friend Vector3 operator*(const Vector3& vec, float scalar) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar multiplication + friend Vector3 operator*(float scalar, const Vector3& vec) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar *= + Vector3& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + z *= scalar; + return *this; + } + + // Vector += + Vector3& operator+=(const Vector3& right) + { + x += right.x; + y += right.y; + z += right.z; + return *this; + } + + // Vector -= + Vector3& operator-=(const Vector3& right) + { + x -= right.x; + y -= right.y; + z -= right.z; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y + z*z); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + } + + // Normalize the provided vector + static Vector3 Normalize(const Vector3& vec) + { + Vector3 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector3& a, const Vector3& b) + { + return (a.x * b.x + a.y * b.y + a.z * b.z); + } + + // Cross product between two vectors (a cross b) + static Vector3 Cross(const Vector3& a, const Vector3& b) + { + Vector3 temp; + temp.x = a.y * b.z - a.z * b.y; + temp.y = a.z * b.x - a.x * b.z; + temp.z = a.x * b.y - a.y * b.x; + return temp; + } + + // Lerp from A to B by f + static Vector3 Lerp(const Vector3& a, const Vector3& b, float f) + { + return Vector3(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector3 Reflect(const Vector3& v, const Vector3& n) + { + return v - 2.0f * Vector3::Dot(v, n) * n; + } + + static Vector3 Transform(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + // This will transform the vector and renormalize the w component + static Vector3 TransformWithPerspDiv(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + + // Transform a Vector3 by a quaternion + static Vector3 Transform(const Vector3& v, const class Quaternion& q); + + static const Vector3 Zero; + static const Vector3 UnitX; + static const Vector3 UnitY; + static const Vector3 UnitZ; + static const Vector3 NegUnitX; + static const Vector3 NegUnitY; + static const Vector3 NegUnitZ; + static const Vector3 Infinity; + static const Vector3 NegInfinity; +}; + +// 3x3 Matrix +class Matrix3 +{ +public: + float mat[3][3]; + + Matrix3() + { + *this = Matrix3::Identity; + } + + explicit Matrix3(float inMat[3][3]) + { + memcpy(mat, inMat, 9 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication + friend Matrix3 operator*(const Matrix3& left, const Matrix3& right) + { + Matrix3 retVal; + // row 0 + retVal.mat[0][0] = + left.mat[0][0] * right.mat[0][0] + + left.mat[0][1] * right.mat[1][0] + + left.mat[0][2] * right.mat[2][0]; + + retVal.mat[0][1] = + left.mat[0][0] * right.mat[0][1] + + left.mat[0][1] * right.mat[1][1] + + left.mat[0][2] * right.mat[2][1]; + + retVal.mat[0][2] = + left.mat[0][0] * right.mat[0][2] + + left.mat[0][1] * right.mat[1][2] + + left.mat[0][2] * right.mat[2][2]; + + // row 1 + retVal.mat[1][0] = + left.mat[1][0] * right.mat[0][0] + + left.mat[1][1] * right.mat[1][0] + + left.mat[1][2] * right.mat[2][0]; + + retVal.mat[1][1] = + left.mat[1][0] * right.mat[0][1] + + left.mat[1][1] * right.mat[1][1] + + left.mat[1][2] * right.mat[2][1]; + + retVal.mat[1][2] = + left.mat[1][0] * right.mat[0][2] + + left.mat[1][1] * right.mat[1][2] + + left.mat[1][2] * right.mat[2][2]; + + // row 2 + retVal.mat[2][0] = + left.mat[2][0] * right.mat[0][0] + + left.mat[2][1] * right.mat[1][0] + + left.mat[2][2] * right.mat[2][0]; + + retVal.mat[2][1] = + left.mat[2][0] * right.mat[0][1] + + left.mat[2][1] * right.mat[1][1] + + left.mat[2][2] * right.mat[2][1]; + + retVal.mat[2][2] = + left.mat[2][0] * right.mat[0][2] + + left.mat[2][1] * right.mat[1][2] + + left.mat[2][2] * right.mat[2][2]; + + return retVal; + } + + Matrix3& operator*=(const Matrix3& right) + { + *this = *this * right; + return *this; + } + + // Create a scale matrix with x and y scales + static Matrix3 CreateScale(float xScale, float yScale) + { + float temp[3][3] = + { + { xScale, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + static Matrix3 CreateScale(const Vector2& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y); + } + + // Create a scale matrix with a uniform factor + static Matrix3 CreateScale(float scale) + { + return CreateScale(scale, scale); + } + + // Create a rotation matrix about the Z axis + // theta is in radians + static Matrix3 CreateRotation(float theta) + { + float temp[3][3] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + // Create a translation matrix (on the xy-plane) + static Matrix3 CreateTranslation(const Vector2& trans) + { + float temp[3][3] = + { + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, 1.0f }, + }; + return Matrix3(temp); + } + + static const Matrix3 Identity; +}; + +// 4x4 Matrix +class Matrix4 +{ +public: + float mat[4][4]; + + Matrix4() + { + *this = Matrix4::Identity; + } + + explicit Matrix4(float inMat[4][4]) + { + memcpy(mat, inMat, 16 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication (a * b) + friend Matrix4 operator*(const Matrix4& a, const Matrix4& b) + { + Matrix4 retVal; + // row 0 + retVal.mat[0][0] = + a.mat[0][0] * b.mat[0][0] + + a.mat[0][1] * b.mat[1][0] + + a.mat[0][2] * b.mat[2][0] + + a.mat[0][3] * b.mat[3][0]; + + retVal.mat[0][1] = + a.mat[0][0] * b.mat[0][1] + + a.mat[0][1] * b.mat[1][1] + + a.mat[0][2] * b.mat[2][1] + + a.mat[0][3] * b.mat[3][1]; + + retVal.mat[0][2] = + a.mat[0][0] * b.mat[0][2] + + a.mat[0][1] * b.mat[1][2] + + a.mat[0][2] * b.mat[2][2] + + a.mat[0][3] * b.mat[3][2]; + + retVal.mat[0][3] = + a.mat[0][0] * b.mat[0][3] + + a.mat[0][1] * b.mat[1][3] + + a.mat[0][2] * b.mat[2][3] + + a.mat[0][3] * b.mat[3][3]; + + // row 1 + retVal.mat[1][0] = + a.mat[1][0] * b.mat[0][0] + + a.mat[1][1] * b.mat[1][0] + + a.mat[1][2] * b.mat[2][0] + + a.mat[1][3] * b.mat[3][0]; + + retVal.mat[1][1] = + a.mat[1][0] * b.mat[0][1] + + a.mat[1][1] * b.mat[1][1] + + a.mat[1][2] * b.mat[2][1] + + a.mat[1][3] * b.mat[3][1]; + + retVal.mat[1][2] = + a.mat[1][0] * b.mat[0][2] + + a.mat[1][1] * b.mat[1][2] + + a.mat[1][2] * b.mat[2][2] + + a.mat[1][3] * b.mat[3][2]; + + retVal.mat[1][3] = + a.mat[1][0] * b.mat[0][3] + + a.mat[1][1] * b.mat[1][3] + + a.mat[1][2] * b.mat[2][3] + + a.mat[1][3] * b.mat[3][3]; + + // row 2 + retVal.mat[2][0] = + a.mat[2][0] * b.mat[0][0] + + a.mat[2][1] * b.mat[1][0] + + a.mat[2][2] * b.mat[2][0] + + a.mat[2][3] * b.mat[3][0]; + + retVal.mat[2][1] = + a.mat[2][0] * b.mat[0][1] + + a.mat[2][1] * b.mat[1][1] + + a.mat[2][2] * b.mat[2][1] + + a.mat[2][3] * b.mat[3][1]; + + retVal.mat[2][2] = + a.mat[2][0] * b.mat[0][2] + + a.mat[2][1] * b.mat[1][2] + + a.mat[2][2] * b.mat[2][2] + + a.mat[2][3] * b.mat[3][2]; + + retVal.mat[2][3] = + a.mat[2][0] * b.mat[0][3] + + a.mat[2][1] * b.mat[1][3] + + a.mat[2][2] * b.mat[2][3] + + a.mat[2][3] * b.mat[3][3]; + + // row 3 + retVal.mat[3][0] = + a.mat[3][0] * b.mat[0][0] + + a.mat[3][1] * b.mat[1][0] + + a.mat[3][2] * b.mat[2][0] + + a.mat[3][3] * b.mat[3][0]; + + retVal.mat[3][1] = + a.mat[3][0] * b.mat[0][1] + + a.mat[3][1] * b.mat[1][1] + + a.mat[3][2] * b.mat[2][1] + + a.mat[3][3] * b.mat[3][1]; + + retVal.mat[3][2] = + a.mat[3][0] * b.mat[0][2] + + a.mat[3][1] * b.mat[1][2] + + a.mat[3][2] * b.mat[2][2] + + a.mat[3][3] * b.mat[3][2]; + + retVal.mat[3][3] = + a.mat[3][0] * b.mat[0][3] + + a.mat[3][1] * b.mat[1][3] + + a.mat[3][2] * b.mat[2][3] + + a.mat[3][3] * b.mat[3][3]; + + return retVal; + } + + Matrix4& operator*=(const Matrix4& right) + { + *this = *this * right; + return *this; + } + + // Invert the matrix - super slow + void Invert(); + + // Get the translation component of the matrix + Vector3 GetTranslation() const + { + return Vector3(mat[3][0], mat[3][1], mat[3][2]); + } + + // Get the X axis of the matrix (forward) + Vector3 GetXAxis() const + { + return Vector3::Normalize(Vector3(mat[0][0], mat[0][1], mat[0][2])); + } + + // Get the Y axis of the matrix (left) + Vector3 GetYAxis() const + { + return Vector3::Normalize(Vector3(mat[1][0], mat[1][1], mat[1][2])); + } + + // Get the Z axis of the matrix (up) + Vector3 GetZAxis() const + { + return Vector3::Normalize(Vector3(mat[2][0], mat[2][1], mat[2][2])); + } + + // Extract the scale component from the matrix + Vector3 GetScale() const + { + Vector3 retVal; + retVal.x = Vector3(mat[0][0], mat[0][1], mat[0][2]).Length(); + retVal.y = Vector3(mat[1][0], mat[1][1], mat[1][2]).Length(); + retVal.z = Vector3(mat[2][0], mat[2][1], mat[2][2]).Length(); + return retVal; + } + + // Create a scale matrix with x, y, and z scales + static Matrix4 CreateScale(float xScale, float yScale, float zScale) + { + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, zScale, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateScale(const Vector3& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y, scaleVector.z); + } + + // Create a scale matrix with a uniform factor + static Matrix4 CreateScale(float scale) + { + return CreateScale(scale, scale, scale); + } + + // Rotation about x-axis + static Matrix4 CreateRotationX(float theta) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f , 0.0f }, + { 0.0f, Math::Cos(theta), Math::Sin(theta), 0.0f }, + { 0.0f, -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about y-axis + static Matrix4 CreateRotationY(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), 0.0f, -Math::Sin(theta), 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { Math::Sin(theta), 0.0f, Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about z-axis + static Matrix4 CreateRotationZ(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f, 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Create a rotation matrix from a quaternion + static Matrix4 CreateFromQuaternion(const class Quaternion& q); + + static Matrix4 CreateTranslation(const Vector3& trans) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateLookAt(const Vector3& eye, const Vector3& target, const Vector3& up) + { + Vector3 zaxis = Vector3::Normalize(target - eye); + Vector3 xaxis = Vector3::Normalize(Vector3::Cross(up, zaxis)); + Vector3 yaxis = Vector3::Normalize(Vector3::Cross(zaxis, xaxis)); + Vector3 trans; + trans.x = -Vector3::Dot(xaxis, eye); + trans.y = -Vector3::Dot(yaxis, eye); + trans.z = -Vector3::Dot(zaxis, eye); + + float temp[4][4] = + { + { xaxis.x, yaxis.x, zaxis.x, 0.0f }, + { xaxis.y, yaxis.y, zaxis.y, 0.0f }, + { xaxis.z, yaxis.z, zaxis.z, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateOrtho(float width, float height, float near, float far) + { + float temp[4][4] = + { + { 2.0f / width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f / height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f / (far - near), 0.0f }, + { 0.0f, 0.0f, near / (near - far), 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreatePerspectiveFOV(float fovY, float width, float height, float near, float far) + { + float yScale = Math::Cot(fovY / 2.0f); + float xScale = yScale * height / width; + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, far / (far - near), 1.0f }, + { 0.0f, 0.0f, -near * far / (far - near), 0.0f } + }; + return Matrix4(temp); + } + + // Create "Simple" View-Projection Matrix from Chapter 6 + static Matrix4 CreateSimpleViewProj(float width, float height) + { + float temp[4][4] = + { + { 2.0f/width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f } + }; + return Matrix4(temp); + } + + static const Matrix4 Identity; +}; + +// (Unit) Quaternion +class Quaternion +{ +public: + float x; + float y; + float z; + float w; + + Quaternion() + { + *this = Quaternion::Identity; + } + + // This directly sets the quaternion components -- + // don't use for axis/angle + explicit Quaternion(float inX, float inY, float inZ, float inW) + { + Set(inX, inY, inZ, inW); + } + + // Construct the quaternion from an axis and angle + // It is assumed that axis is already normalized, + // and the angle is in radians + explicit Quaternion(const Vector3& axis, float angle) + { + float scalar = Math::Sin(angle / 2.0f); + x = axis.x * scalar; + y = axis.y * scalar; + z = axis.z * scalar; + w = Math::Cos(angle / 2.0f); + } + + // Directly set the internal components + void Set(float inX, float inY, float inZ, float inW) + { + x = inX; + y = inY; + z = inZ; + w = inW; + } + + void Conjugate() + { + x *= -1.0f; + y *= -1.0f; + z *= -1.0f; + } + + float LengthSq() const + { + return (x*x + y*y + z*z + w*w); + } + + float Length() const + { + return Math::Sqrt(LengthSq()); + } + + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + w /= length; + } + + // Normalize the provided quaternion + static Quaternion Normalize(const Quaternion& q) + { + Quaternion retVal = q; + retVal.Normalize(); + return retVal; + } + + // Linear interpolation + static Quaternion Lerp(const Quaternion& a, const Quaternion& b, float f) + { + Quaternion retVal; + retVal.x = Math::Lerp(a.x, b.x, f); + retVal.y = Math::Lerp(a.y, b.y, f); + retVal.z = Math::Lerp(a.z, b.z, f); + retVal.w = Math::Lerp(a.w, b.w, f); + retVal.Normalize(); + return retVal; + } + + static float Dot(const Quaternion& a, const Quaternion& b) + { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + // Spherical Linear Interpolation + static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float f) + { + float rawCosm = Quaternion::Dot(a, b); + + float cosom = -rawCosm; + if (rawCosm >= 0.0f) + { + cosom = rawCosm; + } + + float scale0, scale1; + + if (cosom < 0.9999f) + { + const float omega = Math::Acos(cosom); + const float invSin = 1.f / Math::Sin(omega); + scale0 = Math::Sin((1.f - f) * omega) * invSin; + scale1 = Math::Sin(f * omega) * invSin; + } + else + { + // Use linear interpolation if the quaternions + // are collinear + scale0 = 1.0f - f; + scale1 = f; + } + + if (rawCosm < 0.0f) + { + scale1 = -scale1; + } + + Quaternion retVal; + retVal.x = scale0 * a.x + scale1 * b.x; + retVal.y = scale0 * a.y + scale1 * b.y; + retVal.z = scale0 * a.z + scale1 * b.z; + retVal.w = scale0 * a.w + scale1 * b.w; + retVal.Normalize(); + return retVal; + } + + // Concatenate + // Rotate by q FOLLOWED BY p + static Quaternion Concatenate(const Quaternion& q, const Quaternion& p) + { + Quaternion retVal; + + // Vector component is: + // ps * qv + qs * pv + pv x qv + Vector3 qv(q.x, q.y, q.z); + Vector3 pv(p.x, p.y, p.z); + Vector3 newVec = p.w * qv + q.w * pv + Vector3::Cross(pv, qv); + retVal.x = newVec.x; + retVal.y = newVec.y; + retVal.z = newVec.z; + + // Scalar component is: + // ps * qs - pv . qv + retVal.w = p.w * q.w - Vector3::Dot(pv, qv); + + return retVal; + } + + static const Quaternion Identity; +}; + +namespace Color +{ + static const Vector3 Black(0.0f, 0.0f, 0.0f); + static const Vector3 White(1.0f, 1.0f, 1.0f); + static const Vector3 Red(1.0f, 0.0f, 0.0f); + static const Vector3 Green(0.0f, 1.0f, 0.0f); + static const Vector3 Blue(0.0f, 0.0f, 1.0f); + static const Vector3 Yellow(1.0f, 1.0f, 0.0f); + static const Vector3 LightYellow(1.0f, 1.0f, 0.88f); + static const Vector3 LightBlue(0.68f, 0.85f, 0.9f); + static const Vector3 LightPink(1.0f, 0.71f, 0.76f); + static const Vector3 LightGreen(0.56f, 0.93f, 0.56f); +} diff --git a/Chapter02/Ship.cpp b/Chapter02/Ship.cpp new file mode 100644 index 00000000..5daca54a --- /dev/null +++ b/Chapter02/Ship.cpp @@ -0,0 +1,78 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Ship.h" +#include "AnimSpriteComponent.h" +#include "Game.h" + +Ship::Ship(Game* game) + :Actor(game) + ,mRightSpeed(0.0f) + ,mDownSpeed(0.0f) +{ + // Create an animated sprite component + AnimSpriteComponent* asc = new AnimSpriteComponent(this); + std::vector anims = { + game->GetTexture("Assets/Ship01.png"), + game->GetTexture("Assets/Ship02.png"), + game->GetTexture("Assets/Ship03.png"), + game->GetTexture("Assets/Ship04.png"), + }; + asc->SetAnimTextures(anims); +} + +void Ship::UpdateActor(float deltaTime) +{ + Actor::UpdateActor(deltaTime); + // Update position based on speeds and delta time + Vector2 pos = GetPosition(); + pos.x += mRightSpeed * deltaTime; + pos.y += mDownSpeed * deltaTime; + // Restrict position to left half of screen + if (pos.x < 25.0f) + { + pos.x = 25.0f; + } + else if (pos.x > 500.0f) + { + pos.x = 500.0f; + } + if (pos.y < 25.0f) + { + pos.y = 25.0f; + } + else if (pos.y > 743.0f) + { + pos.y = 743.0f; + } + SetPosition(pos); +} + +void Ship::ProcessKeyboard(const uint8_t* state) +{ + mRightSpeed = 0.0f; + mDownSpeed = 0.0f; + // right/left + if (state[SDL_SCANCODE_D]) + { + mRightSpeed += 250.0f; + } + if (state[SDL_SCANCODE_A]) + { + mRightSpeed -= 250.0f; + } + // up/down + if (state[SDL_SCANCODE_S]) + { + mDownSpeed += 300.0f; + } + if (state[SDL_SCANCODE_W]) + { + mDownSpeed -= 300.0f; + } +} diff --git a/Chapter02/Ship.h b/Chapter02/Ship.h new file mode 100644 index 00000000..8a34e962 --- /dev/null +++ b/Chapter02/Ship.h @@ -0,0 +1,22 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Ship : public Actor +{ +public: + Ship(class Game* game); + void UpdateActor(float deltaTime) override; + void ProcessKeyboard(const uint8_t* state); + float GetRightSpeed() const { return mRightSpeed; } + float GetDownSpeed() const { return mDownSpeed; } +private: + float mRightSpeed; + float mDownSpeed; +}; \ No newline at end of file diff --git a/Chapter02/SpriteComponent.cpp b/Chapter02/SpriteComponent.cpp new file mode 100644 index 00000000..56884fbc --- /dev/null +++ b/Chapter02/SpriteComponent.cpp @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "SpriteComponent.h" +#include "Actor.h" +#include "Game.h" + +SpriteComponent::SpriteComponent(Actor* owner, int drawOrder) + :Component(owner) + ,mTexture(nullptr) + ,mDrawOrder(drawOrder) + ,mTexWidth(0) + ,mTexHeight(0) +{ + mOwner->GetGame()->AddSprite(this); +} + +SpriteComponent::~SpriteComponent() +{ + mOwner->GetGame()->RemoveSprite(this); +} + +void SpriteComponent::Draw(SDL_Renderer* renderer) +{ + if (mTexture) + { + SDL_Rect r; + // Scale the width/height by owner's scale + r.w = static_cast(mTexWidth * mOwner->GetScale()); + r.h = static_cast(mTexHeight * mOwner->GetScale()); + // Center the rectangle around the position of the owner + r.x = static_cast(mOwner->GetPosition().x - r.w / 2); + r.y = static_cast(mOwner->GetPosition().y - r.h / 2); + + // Draw (have to convert angle from radians to degrees, and clockwise to counter) + SDL_RenderCopyEx(renderer, + mTexture, + nullptr, + &r, + -Math::ToDegrees(mOwner->GetRotation()), + nullptr, + SDL_FLIP_NONE); + } +} + +void SpriteComponent::SetTexture(SDL_Texture* texture) +{ + mTexture = texture; + // Set width/height + SDL_QueryTexture(texture, nullptr, nullptr, &mTexWidth, &mTexHeight); +} diff --git a/Chapter02/SpriteComponent.h b/Chapter02/SpriteComponent.h new file mode 100644 index 00000000..c430e888 --- /dev/null +++ b/Chapter02/SpriteComponent.h @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "SDL/SDL.h" +class SpriteComponent : public Component +{ +public: + // (Lower draw order corresponds with further back) + SpriteComponent(class Actor* owner, int drawOrder = 100); + ~SpriteComponent(); + + virtual void Draw(SDL_Renderer* renderer); + virtual void SetTexture(SDL_Texture* texture); + + int GetDrawOrder() const { return mDrawOrder; } + int GetTexHeight() const { return mTexHeight; } + int GetTexWidth() const { return mTexWidth; } +protected: + SDL_Texture* mTexture; + int mDrawOrder; + int mTexWidth; + int mTexHeight; +}; diff --git a/Chapter03/Actor.cpp b/Chapter03/Actor.cpp new file mode 100644 index 00000000..4b8eea91 --- /dev/null +++ b/Chapter03/Actor.cpp @@ -0,0 +1,101 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Actor.h" +#include "Game.h" +#include "Component.h" +#include + +Actor::Actor(Game* game) + :mState(EActive) + , mPosition(Vector2::Zero) + , mScale(1.0f) + , mRotation(0.0f) + , mGame(game) +{ + mGame->AddActor(this); +} + +Actor::~Actor() +{ + mGame->RemoveActor(this); + // Need to delete components + // Because ~Component calls RemoveComponent, need a different style loop + while (!mComponents.empty()) + { + delete mComponents.back(); + } +} + +void Actor::Update(float deltaTime) +{ + if (mState == EActive) + { + UpdateComponents(deltaTime); + UpdateActor(deltaTime); + } +} + +void Actor::UpdateComponents(float deltaTime) +{ + for (auto comp : mComponents) + { + comp->Update(deltaTime); + } +} + +void Actor::UpdateActor(float deltaTime) +{ +} + +void Actor::ProcessInput(const uint8_t* keyState) +{ + if (mState == EActive) + { + // First process input for components + for (auto comp : mComponents) + { + comp->ProcessInput(keyState); + } + + ActorInput(keyState); + } +} + +void Actor::ActorInput(const uint8_t* keyState) +{ +} + +void Actor::AddComponent(Component* component) +{ + // Find the insertion point in the sorted vector + // (The first element with a order higher than me) + int myOrder = component->GetUpdateOrder(); + auto iter = mComponents.begin(); + for (; + iter != mComponents.end(); + ++iter) + { + if (myOrder < (*iter)->GetUpdateOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mComponents.insert(iter, component); +} + +void Actor::RemoveComponent(Component* component) +{ + auto iter = std::find(mComponents.begin(), mComponents.end(), component); + if (iter != mComponents.end()) + { + mComponents.erase(iter); + } +} diff --git a/Chapter03/Actor.h b/Chapter03/Actor.h new file mode 100644 index 00000000..767d3c1a --- /dev/null +++ b/Chapter03/Actor.h @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include "Math.h" +#include + +class Actor +{ +public: + enum State + { + EActive, + EPaused, + EDead + }; + + Actor(class Game* game); + virtual ~Actor(); + + // Update function called from Game (not overridable) + void Update(float deltaTime); + // Updates all the components attached to the actor (not overridable) + void UpdateComponents(float deltaTime); + // Any actor-specific update code (overridable) + virtual void UpdateActor(float deltaTime); + + // ProcessInput function called from Game (not overridable) + void ProcessInput(const uint8_t* keyState); + // Any actor-specific input code (overridable) + virtual void ActorInput(const uint8_t* keyState); + + // Getters/setters + const Vector2& GetPosition() const { return mPosition; } + void SetPosition(const Vector2& pos) { mPosition = pos; } + float GetScale() const { return mScale; } + void SetScale(float scale) { mScale = scale; } + float GetRotation() const { return mRotation; } + void SetRotation(float rotation) { mRotation = rotation; } + + Vector2 GetForward() const { return Vector2(Math::Cos(mRotation), -Math::Sin(mRotation)); } + + State GetState() const { return mState; } + void SetState(State state) { mState = state; } + + class Game* GetGame() { return mGame; } + + + // Add/remove components + void AddComponent(class Component* component); + void RemoveComponent(class Component* component); +private: + // Actor's state + State mState; + + // Transform + Vector2 mPosition; + float mScale; + float mRotation; + + std::vector mComponents; + class Game* mGame; +}; diff --git a/Chapter03/Assets/Asteroid.png b/Chapter03/Assets/Asteroid.png new file mode 100644 index 00000000..98e831e9 Binary files /dev/null and b/Chapter03/Assets/Asteroid.png differ diff --git a/Chapter03/Assets/Laser.png b/Chapter03/Assets/Laser.png new file mode 100644 index 00000000..95352d27 Binary files /dev/null and b/Chapter03/Assets/Laser.png differ diff --git a/Chapter03/Assets/Ship.png b/Chapter03/Assets/Ship.png new file mode 100644 index 00000000..96514436 Binary files /dev/null and b/Chapter03/Assets/Ship.png differ diff --git a/Chapter03/Assets/ShipWithThrust.png b/Chapter03/Assets/ShipWithThrust.png new file mode 100644 index 00000000..ed9a43e2 Binary files /dev/null and b/Chapter03/Assets/ShipWithThrust.png differ diff --git a/Chapter03/Asteroid.cpp b/Chapter03/Asteroid.cpp new file mode 100644 index 00000000..c1dddb9e --- /dev/null +++ b/Chapter03/Asteroid.cpp @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Asteroid.h" +#include "SpriteComponent.h" +#include "MoveComponent.h" +#include "Game.h" +#include "Random.h" +#include "CircleComponent.h" + +Asteroid::Asteroid(Game* game) + :Actor(game) + ,mCircle(nullptr) +{ + // Initialize to random position/orientation + Vector2 randPos = Random::GetVector(Vector2::Zero, + Vector2(1024.0f, 768.0f)); + SetPosition(randPos); + + SetRotation(Random::GetFloatRange(0.0f, Math::TwoPi)); + + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this); + sc->SetTexture(game->GetTexture("Assets/Asteroid.png")); + + // Create a move component, and set a forward speed + MoveComponent* mc = new MoveComponent(this); + mc->SetForwardSpeed(150.0f); + + // Create a circle component (for collision) + mCircle = new CircleComponent(this); + mCircle->SetRadius(40.0f); + + // Add to mAsteroids in game + game->AddAsteroid(this); +} + +Asteroid::~Asteroid() +{ + GetGame()->RemoveAsteroid(this); +} diff --git a/Chapter03/Asteroid.h b/Chapter03/Asteroid.h new file mode 100644 index 00000000..45305770 --- /dev/null +++ b/Chapter03/Asteroid.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Asteroid : public Actor +{ +public: + Asteroid(class Game* game); + ~Asteroid(); + + class CircleComponent* GetCircle() { return mCircle; } +private: + class CircleComponent* mCircle; +}; diff --git a/Chapter03/Chapter03-mac.xcodeproj/project.pbxproj b/Chapter03/Chapter03-mac.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c3c8099d --- /dev/null +++ b/Chapter03/Chapter03-mac.xcodeproj/project.pbxproj @@ -0,0 +1,382 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9223C4781F009428009A94D7 /* Game.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4671F009428009A94D7 /* Game.cpp */; }; + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4681F009428009A94D7 /* Actor.cpp */; }; + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C46E1F009428009A94D7 /* Component.cpp */; }; + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4711F009428009A94D7 /* Main.cpp */; }; + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4721F009428009A94D7 /* Math.cpp */; }; + 9223C47F1F009428009A94D7 /* Ship.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4741F009428009A94D7 /* Ship.cpp */; }; + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4761F009428009A94D7 /* SpriteComponent.cpp */; }; + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92D324FA1B697389005A86C7 /* CoreFoundation.framework */; }; + 92E391801FE87CA300D8C362 /* MoveComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391741FE87CA200D8C362 /* MoveComponent.cpp */; }; + 92E391811FE87CA300D8C362 /* Laser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391771FE87CA200D8C362 /* Laser.cpp */; }; + 92E391821FE87CA300D8C362 /* Random.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391781FE87CA200D8C362 /* Random.cpp */; }; + 92E391831FE87CA300D8C362 /* InputComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E3917A1FE87CA200D8C362 /* InputComponent.cpp */; }; + 92E391841FE87CA300D8C362 /* Asteroid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E3917D1FE87CA300D8C362 /* Asteroid.cpp */; }; + 92E391851FE87CA300D8C362 /* CircleComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E3917E1FE87CA300D8C362 /* CircleComponent.cpp */; }; + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92E46E931B6353E50035CD21 /* OpenGL.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 9223C4671F009428009A94D7 /* Game.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Game.cpp; sourceTree = ""; }; + 9223C4681F009428009A94D7 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 9223C4691F009428009A94D7 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 9223C46E1F009428009A94D7 /* Component.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Component.cpp; sourceTree = ""; }; + 9223C46F1F009428009A94D7 /* Component.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Component.h; sourceTree = ""; }; + 9223C4701F009428009A94D7 /* Game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Game.h; sourceTree = ""; }; + 9223C4711F009428009A94D7 /* Main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Main.cpp; sourceTree = ""; }; + 9223C4721F009428009A94D7 /* Math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Math.cpp; sourceTree = ""; }; + 9223C4731F009428009A94D7 /* Math.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Math.h; sourceTree = ""; }; + 9223C4741F009428009A94D7 /* Ship.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Ship.cpp; sourceTree = ""; }; + 9223C4751F009428009A94D7 /* Ship.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ship.h; sourceTree = ""; }; + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpriteComponent.cpp; sourceTree = ""; }; + 9223C4771F009428009A94D7 /* SpriteComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpriteComponent.h; sourceTree = ""; }; + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 92E391741FE87CA200D8C362 /* MoveComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MoveComponent.cpp; sourceTree = ""; }; + 92E391751FE87CA200D8C362 /* Asteroid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Asteroid.h; sourceTree = ""; }; + 92E391761FE87CA200D8C362 /* MoveComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MoveComponent.h; sourceTree = ""; }; + 92E391771FE87CA200D8C362 /* Laser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Laser.cpp; sourceTree = ""; }; + 92E391781FE87CA200D8C362 /* Random.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Random.cpp; sourceTree = ""; }; + 92E391791FE87CA200D8C362 /* CircleComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleComponent.h; sourceTree = ""; }; + 92E3917A1FE87CA200D8C362 /* InputComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InputComponent.cpp; sourceTree = ""; }; + 92E3917B1FE87CA200D8C362 /* Laser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Laser.h; sourceTree = ""; }; + 92E3917C1FE87CA300D8C362 /* Random.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Random.h; sourceTree = ""; }; + 92E3917D1FE87CA300D8C362 /* Asteroid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Asteroid.cpp; sourceTree = ""; }; + 92E3917E1FE87CA300D8C362 /* CircleComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CircleComponent.cpp; sourceTree = ""; }; + 92E3917F1FE87CA300D8C362 /* InputComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputComponent.h; sourceTree = ""; }; + 92E46DF71B634EA30035CD21 /* Game-mac */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Game-mac"; sourceTree = BUILT_PRODUCTS_DIR; }; + 92E46E931B6353E50035CD21 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 92E46DF41B634EA30035CD21 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */, + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 92E46DEE1B634EA30035CD21 = { + isa = PBXGroup; + children = ( + 9223C4681F009428009A94D7 /* Actor.cpp */, + 9223C4691F009428009A94D7 /* Actor.h */, + 92E3917D1FE87CA300D8C362 /* Asteroid.cpp */, + 92E391751FE87CA200D8C362 /* Asteroid.h */, + 92E3917E1FE87CA300D8C362 /* CircleComponent.cpp */, + 92E391791FE87CA200D8C362 /* CircleComponent.h */, + 9223C46E1F009428009A94D7 /* Component.cpp */, + 9223C46F1F009428009A94D7 /* Component.h */, + 9223C4671F009428009A94D7 /* Game.cpp */, + 9223C4701F009428009A94D7 /* Game.h */, + 92E3917A1FE87CA200D8C362 /* InputComponent.cpp */, + 92E3917F1FE87CA300D8C362 /* InputComponent.h */, + 92E391771FE87CA200D8C362 /* Laser.cpp */, + 92E3917B1FE87CA200D8C362 /* Laser.h */, + 9223C4711F009428009A94D7 /* Main.cpp */, + 9223C4721F009428009A94D7 /* Math.cpp */, + 9223C4731F009428009A94D7 /* Math.h */, + 92E391741FE87CA200D8C362 /* MoveComponent.cpp */, + 92E391761FE87CA200D8C362 /* MoveComponent.h */, + 92E391781FE87CA200D8C362 /* Random.cpp */, + 92E3917C1FE87CA300D8C362 /* Random.h */, + 9223C4741F009428009A94D7 /* Ship.cpp */, + 9223C4751F009428009A94D7 /* Ship.h */, + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */, + 9223C4771F009428009A94D7 /* SpriteComponent.h */, + 92E46DF81B634EA30035CD21 /* Products */, + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */, + 92E46E931B6353E50035CD21 /* OpenGL.framework */, + ); + sourceTree = ""; + }; + 92E46DF81B634EA30035CD21 /* Products */ = { + isa = PBXGroup; + children = ( + 92E46DF71B634EA30035CD21 /* Game-mac */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 92E46DF61B634EA30035CD21 /* Game-mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */; + buildPhases = ( + 92E46DF31B634EA30035CD21 /* Sources */, + 92E46DF41B634EA30035CD21 /* Frameworks */, + 92E46EA11B63615B0035CD21 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Game-mac"; + productName = "Game-mac"; + productReference = 92E46DF71B634EA30035CD21 /* Game-mac */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 92E46DEF1B634EA30035CD21 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Sanjay Madhav"; + TargetAttributes = { + 92E46DF61B634EA30035CD21 = { + CreatedOnToolsVersion = 6.4; + }; + }; + }; + buildConfigurationList = 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter03-mac" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 92E46DEE1B634EA30035CD21; + productRefGroup = 92E46DF81B634EA30035CD21 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 92E46DF61B634EA30035CD21 /* Game-mac */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 92E46EA11B63615B0035CD21 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -d \"$BUILD_DIR/Debug\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Debug\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Debug\nfi\n\nif [ -d \"$BUILD_DIR/Release\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Release\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Release\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 92E46DF31B634EA30035CD21 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 92E391811FE87CA300D8C362 /* Laser.cpp in Sources */, + 92E391851FE87CA300D8C362 /* CircleComponent.cpp in Sources */, + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */, + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */, + 9223C4781F009428009A94D7 /* Game.cpp in Sources */, + 92E391841FE87CA300D8C362 /* Asteroid.cpp in Sources */, + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */, + 92E391801FE87CA300D8C362 /* MoveComponent.cpp in Sources */, + 9223C47F1F009428009A94D7 /* Ship.cpp in Sources */, + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */, + 92E391821FE87CA300D8C362 /* Random.cpp in Sources */, + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */, + 92E391831FE87CA300D8C362 /* InputComponent.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 92E46DFC1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 92E46DFD1B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 92E46DFF1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 92E46E001B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter03-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFC1B634EA40035CD21 /* Debug */, + 92E46DFD1B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFF1B634EA40035CD21 /* Debug */, + 92E46E001B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 92E46DEF1B634EA30035CD21 /* Project object */; +} diff --git a/Chapter03/Chapter03-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Chapter03/Chapter03-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..71b69c85 --- /dev/null +++ b/Chapter03/Chapter03-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Chapter03/Chapter03-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme b/Chapter03/Chapter03-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme new file mode 100644 index 00000000..08c75a44 --- /dev/null +++ b/Chapter03/Chapter03-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter03/Chapter03-windows.sln b/Chapter03/Chapter03-windows.sln new file mode 100644 index 00000000..f560df0f --- /dev/null +++ b/Chapter03/Chapter03-windows.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "Game.vcxproj", "{BC508D87-495F-4554-932D-DD68388B63CC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.Build.0 = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.ActiveCfg = Release|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Chapter03/CircleComponent.cpp b/Chapter03/CircleComponent.cpp new file mode 100644 index 00000000..d41aab2c --- /dev/null +++ b/Chapter03/CircleComponent.cpp @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "CircleComponent.h" +#include "Actor.h" + +CircleComponent::CircleComponent(class Actor* owner) +:Component(owner) +,mRadius(0.0f) +{ + +} + +const Vector2& CircleComponent::GetCenter() const +{ + return mOwner->GetPosition(); +} + +float CircleComponent::GetRadius() const +{ + return mOwner->GetScale() * mRadius; +} + +bool Intersect(const CircleComponent& a, const CircleComponent& b) +{ + // Calculate distance squared + Vector2 diff = a.GetCenter() - b.GetCenter(); + float distSq = diff.LengthSq(); + + // Calculate sum of radii squared + float radiiSq = a.GetRadius() + b.GetRadius(); + radiiSq *= radiiSq; + + return distSq <= radiiSq; +} diff --git a/Chapter03/CircleComponent.h b/Chapter03/CircleComponent.h new file mode 100644 index 00000000..4eecb2a4 --- /dev/null +++ b/Chapter03/CircleComponent.h @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "Math.h" + +class CircleComponent : public Component +{ +public: + CircleComponent(class Actor* owner); + + void SetRadius(float radius) { mRadius = radius; } + float GetRadius() const; + + const Vector2& GetCenter() const; +private: + float mRadius; +}; + +bool Intersect(const CircleComponent& a, const CircleComponent& b); diff --git a/Chapter03/Component.cpp b/Chapter03/Component.cpp new file mode 100644 index 00000000..c4ed432d --- /dev/null +++ b/Chapter03/Component.cpp @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Component.h" +#include "Actor.h" + +Component::Component(Actor* owner, int updateOrder) + :mOwner(owner) + ,mUpdateOrder(updateOrder) +{ + // Add to actor's vector of components + mOwner->AddComponent(this); +} + +Component::~Component() +{ + mOwner->RemoveComponent(this); +} + +void Component::Update(float deltaTime) +{ +} diff --git a/Chapter03/Component.h b/Chapter03/Component.h new file mode 100644 index 00000000..fb41dd75 --- /dev/null +++ b/Chapter03/Component.h @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include + +class Component +{ +public: + // Constructor + // (the lower the update order, the earlier the component updates) + Component(class Actor* owner, int updateOrder = 100); + // Destructor + virtual ~Component(); + // Update this component by delta time + virtual void Update(float deltaTime); + // Process input for this component + virtual void ProcessInput(const uint8_t* keyState) {} + + int GetUpdateOrder() const { return mUpdateOrder; } +protected: + // Owning actor + class Actor* mOwner; + // Update order of component + int mUpdateOrder; +}; diff --git a/Chapter03/Game.cpp b/Chapter03/Game.cpp new file mode 100644 index 00000000..ed758b2a --- /dev/null +++ b/Chapter03/Game.cpp @@ -0,0 +1,309 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" +#include "SDL/SDL_image.h" +#include +#include "Actor.h" +#include "SpriteComponent.h" +#include "Ship.h" +#include "Asteroid.h" +#include "Random.h" + +Game::Game() +:mWindow(nullptr) +,mRenderer(nullptr) +,mIsRunning(true) +,mUpdatingActors(false) +{ + +} + +bool Game::Initialize() +{ + if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) + { + SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); + return false; + } + + mWindow = SDL_CreateWindow("Game Programming in C++ (Chapter 3)", 100, 100, 1024, 768, 0); + if (!mWindow) + { + SDL_Log("Failed to create window: %s", SDL_GetError()); + return false; + } + + mRenderer = SDL_CreateRenderer(mWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!mRenderer) + { + SDL_Log("Failed to create renderer: %s", SDL_GetError()); + return false; + } + + if (IMG_Init(IMG_INIT_PNG) == 0) + { + SDL_Log("Unable to initialize SDL_image: %s", SDL_GetError()); + return false; + } + + Random::Init(); + + LoadData(); + + mTicksCount = SDL_GetTicks(); + + return true; +} + +void Game::RunLoop() +{ + while (mIsRunning) + { + ProcessInput(); + UpdateGame(); + GenerateOutput(); + } +} + +void Game::ProcessInput() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + mIsRunning = false; + break; + } + } + + const Uint8* keyState = SDL_GetKeyboardState(NULL); + if (keyState[SDL_SCANCODE_ESCAPE]) + { + mIsRunning = false; + } + + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->ProcessInput(keyState); + } + mUpdatingActors = false; +} + +void Game::UpdateGame() +{ + // Compute delta time + // Wait until 16ms has elapsed since last frame + while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16)) + ; + + float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f; + if (deltaTime > 0.05f) + { + deltaTime = 0.05f; + } + mTicksCount = SDL_GetTicks(); + + // Update all actors + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->Update(deltaTime); + } + mUpdatingActors = false; + + // Move any pending actors to mActors + for (auto pending : mPendingActors) + { + mActors.emplace_back(pending); + } + mPendingActors.clear(); + + // Add any dead actors to a temp vector + std::vector deadActors; + for (auto actor : mActors) + { + if (actor->GetState() == Actor::EDead) + { + deadActors.emplace_back(actor); + } + } + + // Delete dead actors (which removes them from mActors) + for (auto actor : deadActors) + { + delete actor; + } +} + +void Game::GenerateOutput() +{ + SDL_SetRenderDrawColor(mRenderer, 220, 220, 220, 255); + SDL_RenderClear(mRenderer); + + // Draw all sprite components + for (auto sprite : mSprites) + { + sprite->Draw(mRenderer); + } + + SDL_RenderPresent(mRenderer); +} + +void Game::LoadData() +{ + // Create player's ship + mShip = new Ship(this); + mShip->SetPosition(Vector2(512.0f, 384.0f)); + mShip->SetRotation(Math::PiOver2); + + // Create asteroids + const int numAsteroids = 20; + for (int i = 0; i < numAsteroids; i++) + { + new Asteroid(this); + } +} + +void Game::UnloadData() +{ + // Delete actors + // Because ~Actor calls RemoveActor, have to use a different style loop + while (!mActors.empty()) + { + delete mActors.back(); + } + + // Destroy textures + for (auto i : mTextures) + { + SDL_DestroyTexture(i.second); + } + mTextures.clear(); +} + +SDL_Texture* Game::GetTexture(const std::string& fileName) +{ + SDL_Texture* tex = nullptr; + // Is the texture already in the map? + auto iter = mTextures.find(fileName); + if (iter != mTextures.end()) + { + tex = iter->second; + } + else + { + // Load from file + SDL_Surface* surf = IMG_Load(fileName.c_str()); + if (!surf) + { + SDL_Log("Failed to load texture file %s", fileName.c_str()); + return nullptr; + } + + // Create texture from surface + tex = SDL_CreateTextureFromSurface(mRenderer, surf); + SDL_FreeSurface(surf); + if (!tex) + { + SDL_Log("Failed to convert surface to texture for %s", fileName.c_str()); + return nullptr; + } + + mTextures.emplace(fileName.c_str(), tex); + } + return tex; +} + +void Game::AddAsteroid(Asteroid* ast) +{ + mAsteroids.emplace_back(ast); +} + +void Game::RemoveAsteroid(Asteroid* ast) +{ + auto iter = std::find(mAsteroids.begin(), + mAsteroids.end(), ast); + if (iter != mAsteroids.end()) + { + mAsteroids.erase(iter); + } +} + +void Game::Shutdown() +{ + UnloadData(); + IMG_Quit(); + SDL_DestroyRenderer(mRenderer); + SDL_DestroyWindow(mWindow); + SDL_Quit(); +} + +void Game::AddActor(Actor* actor) +{ + // If we're updating actors, need to add to pending + if (mUpdatingActors) + { + mPendingActors.emplace_back(actor); + } + else + { + mActors.emplace_back(actor); + } +} + +void Game::RemoveActor(Actor* actor) +{ + // Is it in pending actors? + auto iter = std::find(mPendingActors.begin(), mPendingActors.end(), actor); + if (iter != mPendingActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mPendingActors.end() - 1); + mPendingActors.pop_back(); + } + + // Is it in actors? + iter = std::find(mActors.begin(), mActors.end(), actor); + if (iter != mActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mActors.end() - 1); + mActors.pop_back(); + } +} + +void Game::AddSprite(SpriteComponent* sprite) +{ + // Find the insertion point in the sorted vector + // (The first element with a higher draw order than me) + int myDrawOrder = sprite->GetDrawOrder(); + auto iter = mSprites.begin(); + for ( ; + iter != mSprites.end(); + ++iter) + { + if (myDrawOrder < (*iter)->GetDrawOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mSprites.insert(iter, sprite); +} + +void Game::RemoveSprite(SpriteComponent* sprite) +{ + // (We can't swap because it ruins ordering) + auto iter = std::find(mSprites.begin(), mSprites.end(), sprite); + mSprites.erase(iter); +} diff --git a/Chapter03/Game.h b/Chapter03/Game.h new file mode 100644 index 00000000..5a7b8261 --- /dev/null +++ b/Chapter03/Game.h @@ -0,0 +1,63 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "SDL/SDL.h" +#include +#include +#include + +class Game +{ +public: + Game(); + bool Initialize(); + void RunLoop(); + void Shutdown(); + + void AddActor(class Actor* actor); + void RemoveActor(class Actor* actor); + + void AddSprite(class SpriteComponent* sprite); + void RemoveSprite(class SpriteComponent* sprite); + + SDL_Texture* GetTexture(const std::string& fileName); + + // Game-specific (add/remove asteroid) + void AddAsteroid(class Asteroid* ast); + void RemoveAsteroid(class Asteroid* ast); + std::vector& GetAsteroids() { return mAsteroids; } +private: + void ProcessInput(); + void UpdateGame(); + void GenerateOutput(); + void LoadData(); + void UnloadData(); + + // Map of textures loaded + std::unordered_map mTextures; + + // All the actors in the game + std::vector mActors; + // Any pending actors + std::vector mPendingActors; + + // All the sprite components drawn + std::vector mSprites; + + SDL_Window* mWindow; + SDL_Renderer* mRenderer; + Uint32 mTicksCount; + bool mIsRunning; + // Track if we're updating actors right now + bool mUpdatingActors; + + // Game-specific + class Ship* mShip; // Player's ship + std::vector mAsteroids; +}; diff --git a/Chapter03/Game.vcxproj b/Chapter03/Game.vcxproj new file mode 100644 index 00000000..f88503fe --- /dev/null +++ b/Chapter03/Game.vcxproj @@ -0,0 +1,132 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {BC508D87-495F-4554-932D-DD68388B63CC} + Win32Proj + Game + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + /NODEFAULTLIB:msvcrt.lib %(AdditionalOptions) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + true + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + + \ No newline at end of file diff --git a/Chapter03/Game.vcxproj.filters b/Chapter03/Game.vcxproj.filters new file mode 100644 index 00000000..f6b3a112 --- /dev/null +++ b/Chapter03/Game.vcxproj.filters @@ -0,0 +1,88 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Chapter03/InputComponent.cpp b/Chapter03/InputComponent.cpp new file mode 100644 index 00000000..148ffc95 --- /dev/null +++ b/Chapter03/InputComponent.cpp @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "InputComponent.h" +#include "Actor.h" + +InputComponent::InputComponent(class Actor* owner) +:MoveComponent(owner) +,mForwardKey(0) +,mBackKey(0) +,mClockwiseKey(0) +,mCounterClockwiseKey(0) +{ + +} + +void InputComponent::ProcessInput(const uint8_t* keyState) +{ + // Calculate forward speed for MoveComponent + float forwardSpeed = 0.0f; + if (keyState[mForwardKey]) + { + forwardSpeed += mMaxForwardSpeed; + } + if (keyState[mBackKey]) + { + forwardSpeed -= mMaxForwardSpeed; + } + SetForwardSpeed(forwardSpeed); + + // Calculate angular speed for MoveComponent + float angularSpeed = 0.0f; + if (keyState[mClockwiseKey]) + { + angularSpeed += mMaxAngularSpeed; + } + if (keyState[mCounterClockwiseKey]) + { + angularSpeed -= mMaxAngularSpeed; + } + SetAngularSpeed(angularSpeed); +} diff --git a/Chapter03/InputComponent.h b/Chapter03/InputComponent.h new file mode 100644 index 00000000..57b32ccb --- /dev/null +++ b/Chapter03/InputComponent.h @@ -0,0 +1,45 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "MoveComponent.h" +#include + +class InputComponent : public MoveComponent +{ +public: + // Lower update order to update first + InputComponent(class Actor* owner); + + void ProcessInput(const uint8_t* keyState) override; + + // Getters/setters for private variables + float GetMaxForward() const { return mMaxForwardSpeed; } + float GetMaxAngular() const { return mMaxAngularSpeed; } + int GetForwardKey() const { return mForwardKey; } + int GetBackKey() const { return mBackKey; } + int GetClockwiseKey() const { return mClockwiseKey; } + int GetCounterClockwiseKey() const { return mCounterClockwiseKey; } + + void SetMaxForwardSpeed(float speed) { mMaxForwardSpeed = speed; } + void SetMaxAngularSpeed(float speed) { mMaxAngularSpeed = speed; } + void SetForwardKey(int key) { mForwardKey = key; } + void SetBackKey(int key) { mBackKey = key; } + void SetClockwiseKey(int key) { mClockwiseKey = key; } + void SetCounterClockwiseKey(int key) { mCounterClockwiseKey = key; } +private: + // The maximum forward/angular speeds + float mMaxForwardSpeed; + float mMaxAngularSpeed; + // Keys for forward/back movement + int mForwardKey; + int mBackKey; + // Keys for angular movement + int mClockwiseKey; + int mCounterClockwiseKey; +}; diff --git a/Chapter03/Laser.cpp b/Chapter03/Laser.cpp new file mode 100644 index 00000000..a03c1981 --- /dev/null +++ b/Chapter03/Laser.cpp @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Laser.h" +#include "SpriteComponent.h" +#include "MoveComponent.h" +#include "Game.h" +#include "CircleComponent.h" +#include "Asteroid.h" + +Laser::Laser(Game* game) + :Actor(game) + ,mDeathTimer(1.0f) +{ + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this); + sc->SetTexture(game->GetTexture("Assets/Laser.png")); + + // Create a move component, and set a forward speed + MoveComponent* mc = new MoveComponent(this); + mc->SetForwardSpeed(800.0f); + + // Create a circle component (for collision) + mCircle = new CircleComponent(this); + mCircle->SetRadius(11.0f); +} + +void Laser::UpdateActor(float deltaTime) +{ + // If we run out of time, laser is dead + mDeathTimer -= deltaTime; + if (mDeathTimer <= 0.0f) + { + SetState(EDead); + } + else + { + // Do we intersect with an asteroid? + for (auto ast : GetGame()->GetAsteroids()) + { + if (Intersect(*mCircle, *(ast->GetCircle()))) + { + // The first asteroid we intersect with, + // set ourselves and the asteroid to dead + SetState(EDead); + ast->SetState(EDead); + break; + } + } + } +} diff --git a/Chapter03/Laser.h b/Chapter03/Laser.h new file mode 100644 index 00000000..f1afd638 --- /dev/null +++ b/Chapter03/Laser.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Laser : public Actor +{ +public: + Laser(class Game* game); + + void UpdateActor(float deltaTime) override; +private: + class CircleComponent* mCircle; + float mDeathTimer; +}; diff --git a/Chapter03/Main.cpp b/Chapter03/Main.cpp new file mode 100644 index 00000000..22ea0c69 --- /dev/null +++ b/Chapter03/Main.cpp @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" + +int main(int argc, char** argv) +{ + Game game; + bool success = game.Initialize(); + if (success) + { + game.RunLoop(); + } + game.Shutdown(); + return 0; +} diff --git a/Chapter03/Math.cpp b/Chapter03/Math.cpp new file mode 100644 index 00000000..a16e7261 --- /dev/null +++ b/Chapter03/Math.cpp @@ -0,0 +1,239 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Math.h" + +const Vector2 Vector2::Zero(0.0f, 0.0f); +const Vector2 Vector2::UnitX(1.0f, 0.0f); +const Vector2 Vector2::UnitY(0.0f, 1.0f); +const Vector2 Vector2::NegUnitX(-1.0f, 0.0f); +const Vector2 Vector2::NegUnitY(0.0f, -1.0f); + +const Vector3 Vector3::Zero(0.0f, 0.0f, 0.f); +const Vector3 Vector3::UnitX(1.0f, 0.0f, 0.0f); +const Vector3 Vector3::UnitY(0.0f, 1.0f, 0.0f); +const Vector3 Vector3::UnitZ(0.0f, 0.0f, 1.0f); +const Vector3 Vector3::NegUnitX(-1.0f, 0.0f, 0.0f); +const Vector3 Vector3::NegUnitY(0.0f, -1.0f, 0.0f); +const Vector3 Vector3::NegUnitZ(0.0f, 0.0f, -1.0f); +const Vector3 Vector3::Infinity(Math::Infinity, Math::Infinity, Math::Infinity); +const Vector3 Vector3::NegInfinity(Math::NegInfinity, Math::NegInfinity, Math::NegInfinity); + +static float m3Ident[3][3] = +{ + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f } +}; +const Matrix3 Matrix3::Identity(m3Ident); + +static float m4Ident[4][4] = +{ + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; + +const Matrix4 Matrix4::Identity(m4Ident); + +const Quaternion Quaternion::Identity(0.0f, 0.0f, 0.0f, 1.0f); + +Vector2 Vector2::Transform(const Vector2& vec, const Matrix3& mat, float w /*= 1.0f*/) +{ + Vector2 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + w * mat.mat[2][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + w * mat.mat[2][1]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +Vector3 Vector3::Transform(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +// This will transform the vector and renormalize the w component +Vector3 Vector3::TransformWithPerspDiv(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + float transformedW = vec.x * mat.mat[0][3] + vec.y * mat.mat[1][3] + + vec.z * mat.mat[2][3] + w * mat.mat[3][3]; + if (!Math::NearZero(Math::Abs(transformedW))) + { + transformedW = 1.0f / transformedW; + retVal *= transformedW; + } + return retVal; +} + +// Transform a Vector3 by a quaternion +Vector3 Vector3::Transform(const Vector3& v, const Quaternion& q) +{ + // v + 2.0*cross(q.xyz, cross(q.xyz,v) + q.w*v); + Vector3 qv(q.x, q.y, q.z); + Vector3 retVal = v; + retVal += 2.0f * Vector3::Cross(qv, Vector3::Cross(qv, v) + q.w * v); + return retVal; +} + +void Matrix4::Invert() +{ + // Thanks slow math + // This is a really janky way to unroll everything... + float tmp[12]; + float src[16]; + float dst[16]; + float det; + + // Transpose matrix + // row 1 to col 1 + src[0] = mat[0][0]; + src[4] = mat[0][1]; + src[8] = mat[0][2]; + src[12] = mat[0][3]; + + // row 2 to col 2 + src[1] = mat[1][0]; + src[5] = mat[1][1]; + src[9] = mat[1][2]; + src[13] = mat[1][3]; + + // row 3 to col 3 + src[2] = mat[2][0]; + src[6] = mat[2][1]; + src[10] = mat[2][2]; + src[14] = mat[2][3]; + + // row 4 to col 4 + src[3] = mat[3][0]; + src[7] = mat[3][1]; + src[11] = mat[3][2]; + src[15] = mat[3][3]; + + // Calculate cofactors + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + dst[0] = tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]; + dst[0] -= tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]; + dst[1] = tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]; + dst[1] -= tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]; + dst[2] = tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]; + dst[2] -= tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]; + dst[3] = tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]; + dst[3] -= tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]; + dst[4] = tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]; + dst[4] -= tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]; + dst[5] = tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]; + dst[5] -= tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]; + dst[6] = tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]; + dst[6] -= tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]; + dst[7] = tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]; + dst[7] -= tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]; + + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + dst[8] = tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]; + dst[8] -= tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]; + dst[9] = tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]; + dst[9] -= tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]; + dst[10] = tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]; + dst[10] -= tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]; + dst[11] = tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]; + dst[11] -= tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]; + dst[12] = tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]; + dst[12] -= tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]; + dst[13] = tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]; + dst[13] -= tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]; + dst[14] = tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]; + dst[14] -= tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]; + dst[15] = tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]; + dst[15] -= tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]; + + // Calculate determinant + det = src[0] * dst[0] + src[1] * dst[1] + src[2] * dst[2] + src[3] * dst[3]; + + // Inverse of matrix is divided by determinant + det = 1 / det; + for (int j = 0; j < 16; j++) + { + dst[j] *= det; + } + + // Set it back + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + mat[i][j] = dst[i * 4 + j]; + } + } +} + +Matrix4 Matrix4::CreateFromQuaternion(const class Quaternion& q) +{ + float mat[4][4]; + + mat[0][0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z; + mat[0][1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z; + mat[0][2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y; + mat[0][3] = 0.0f; + + mat[1][0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z; + mat[1][1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z; + mat[1][2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x; + mat[1][3] = 0.0f; + + mat[2][0] = 2.0f * q.x * q.z + 2.0f * q.w * q.y; + mat[2][1] = 2.0f * q.y * q.z - 2.0f * q.w * q.x; + mat[2][2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y; + mat[2][3] = 0.0f; + + mat[3][0] = 0.0f; + mat[3][1] = 0.0f; + mat[3][2] = 0.0f; + mat[3][3] = 1.0f; + + return Matrix4(mat); +} diff --git a/Chapter03/Math.h b/Chapter03/Math.h new file mode 100644 index 00000000..752963f1 --- /dev/null +++ b/Chapter03/Math.h @@ -0,0 +1,1033 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +namespace Math +{ + const float Pi = 3.1415926535f; + const float TwoPi = Pi * 2.0f; + const float PiOver2 = Pi / 2.0f; + const float Infinity = std::numeric_limits::infinity(); + const float NegInfinity = -std::numeric_limits::infinity(); + + inline float ToRadians(float degrees) + { + return degrees * Pi / 180.0f; + } + + inline float ToDegrees(float radians) + { + return radians * 180.0f / Pi; + } + + inline bool NearZero(float val, float epsilon = 0.001f) + { + if (fabs(val) <= epsilon) + { + return true; + } + else + { + return false; + } + } + + template + T Max(const T& a, const T& b) + { + return (a < b ? b : a); + } + + template + T Min(const T& a, const T& b) + { + return (a < b ? a : b); + } + + template + T Clamp(const T& value, const T& lower, const T& upper) + { + return Min(upper, Max(lower, value)); + } + + inline float Abs(float value) + { + return fabs(value); + } + + inline float Cos(float angle) + { + return cosf(angle); + } + + inline float Sin(float angle) + { + return sinf(angle); + } + + inline float Tan(float angle) + { + return tanf(angle); + } + + inline float Acos(float value) + { + return acosf(value); + } + + inline float Atan2(float y, float x) + { + return atan2f(y, x); + } + + inline float Cot(float angle) + { + return 1.0f / Tan(angle); + } + + inline float Lerp(float a, float b, float f) + { + return a + f * (b - a); + } + + inline float Sqrt(float value) + { + return sqrtf(value); + } + + inline float Fmod(float numer, float denom) + { + return fmod(numer, denom); + } +} + +// 2D Vector +class Vector2 +{ +public: + float x; + float y; + + Vector2() + :x(0.0f) + ,y(0.0f) + {} + + explicit Vector2(float inX, float inY) + :x(inX) + ,y(inY) + {} + + // Set both components in one line + void Set(float inX, float inY) + { + x = inX; + y = inY; + } + + // Vector addition (a + b) + friend Vector2 operator+(const Vector2& a, const Vector2& b) + { + return Vector2(a.x + b.x, a.y + b.y); + } + + // Vector subtraction (a - b) + friend Vector2 operator-(const Vector2& a, const Vector2& b) + { + return Vector2(a.x - b.x, a.y - b.y); + } + + // Component-wise multiplication + // (a.x * b.x, ...) + friend Vector2 operator*(const Vector2& a, const Vector2& b) + { + return Vector2(a.x * b.x, a.y * b.y); + } + + // Scalar multiplication + friend Vector2 operator*(const Vector2& vec, float scalar) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar multiplication + friend Vector2 operator*(float scalar, const Vector2& vec) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar *= + Vector2& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + return *this; + } + + // Vector += + Vector2& operator+=(const Vector2& right) + { + x += right.x; + y += right.y; + return *this; + } + + // Vector -= + Vector2& operator-=(const Vector2& right) + { + x -= right.x; + y -= right.y; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + } + + // Normalize the provided vector + static Vector2 Normalize(const Vector2& vec) + { + Vector2 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector2& a, const Vector2& b) + { + return (a.x * b.x + a.y * b.y); + } + + // Lerp from A to B by f + static Vector2 Lerp(const Vector2& a, const Vector2& b, float f) + { + return Vector2(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector2 Reflect(const Vector2& v, const Vector2& n) + { + return v - 2.0f * Vector2::Dot(v, n) * n; + } + + // Transform vector by matrix + static Vector2 Transform(const Vector2& vec, const class Matrix3& mat, float w = 1.0f); + + static const Vector2 Zero; + static const Vector2 UnitX; + static const Vector2 UnitY; + static const Vector2 NegUnitX; + static const Vector2 NegUnitY; +}; + +// 3D Vector +class Vector3 +{ +public: + float x; + float y; + float z; + + Vector3() + :x(0.0f) + ,y(0.0f) + ,z(0.0f) + {} + + explicit Vector3(float inX, float inY, float inZ) + :x(inX) + ,y(inY) + ,z(inZ) + {} + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&x); + } + + // Set all three components in one line + void Set(float inX, float inY, float inZ) + { + x = inX; + y = inY; + z = inZ; + } + + // Vector addition (a + b) + friend Vector3 operator+(const Vector3& a, const Vector3& b) + { + return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); + } + + // Vector subtraction (a - b) + friend Vector3 operator-(const Vector3& a, const Vector3& b) + { + return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); + } + + // Component-wise multiplication + friend Vector3 operator*(const Vector3& left, const Vector3& right) + { + return Vector3(left.x * right.x, left.y * right.y, left.z * right.z); + } + + // Scalar multiplication + friend Vector3 operator*(const Vector3& vec, float scalar) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar multiplication + friend Vector3 operator*(float scalar, const Vector3& vec) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar *= + Vector3& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + z *= scalar; + return *this; + } + + // Vector += + Vector3& operator+=(const Vector3& right) + { + x += right.x; + y += right.y; + z += right.z; + return *this; + } + + // Vector -= + Vector3& operator-=(const Vector3& right) + { + x -= right.x; + y -= right.y; + z -= right.z; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y + z*z); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + } + + // Normalize the provided vector + static Vector3 Normalize(const Vector3& vec) + { + Vector3 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector3& a, const Vector3& b) + { + return (a.x * b.x + a.y * b.y + a.z * b.z); + } + + // Cross product between two vectors (a cross b) + static Vector3 Cross(const Vector3& a, const Vector3& b) + { + Vector3 temp; + temp.x = a.y * b.z - a.z * b.y; + temp.y = a.z * b.x - a.x * b.z; + temp.z = a.x * b.y - a.y * b.x; + return temp; + } + + // Lerp from A to B by f + static Vector3 Lerp(const Vector3& a, const Vector3& b, float f) + { + return Vector3(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector3 Reflect(const Vector3& v, const Vector3& n) + { + return v - 2.0f * Vector3::Dot(v, n) * n; + } + + static Vector3 Transform(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + // This will transform the vector and renormalize the w component + static Vector3 TransformWithPerspDiv(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + + // Transform a Vector3 by a quaternion + static Vector3 Transform(const Vector3& v, const class Quaternion& q); + + static const Vector3 Zero; + static const Vector3 UnitX; + static const Vector3 UnitY; + static const Vector3 UnitZ; + static const Vector3 NegUnitX; + static const Vector3 NegUnitY; + static const Vector3 NegUnitZ; + static const Vector3 Infinity; + static const Vector3 NegInfinity; +}; + +// 3x3 Matrix +class Matrix3 +{ +public: + float mat[3][3]; + + Matrix3() + { + *this = Matrix3::Identity; + } + + explicit Matrix3(float inMat[3][3]) + { + memcpy(mat, inMat, 9 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication + friend Matrix3 operator*(const Matrix3& left, const Matrix3& right) + { + Matrix3 retVal; + // row 0 + retVal.mat[0][0] = + left.mat[0][0] * right.mat[0][0] + + left.mat[0][1] * right.mat[1][0] + + left.mat[0][2] * right.mat[2][0]; + + retVal.mat[0][1] = + left.mat[0][0] * right.mat[0][1] + + left.mat[0][1] * right.mat[1][1] + + left.mat[0][2] * right.mat[2][1]; + + retVal.mat[0][2] = + left.mat[0][0] * right.mat[0][2] + + left.mat[0][1] * right.mat[1][2] + + left.mat[0][2] * right.mat[2][2]; + + // row 1 + retVal.mat[1][0] = + left.mat[1][0] * right.mat[0][0] + + left.mat[1][1] * right.mat[1][0] + + left.mat[1][2] * right.mat[2][0]; + + retVal.mat[1][1] = + left.mat[1][0] * right.mat[0][1] + + left.mat[1][1] * right.mat[1][1] + + left.mat[1][2] * right.mat[2][1]; + + retVal.mat[1][2] = + left.mat[1][0] * right.mat[0][2] + + left.mat[1][1] * right.mat[1][2] + + left.mat[1][2] * right.mat[2][2]; + + // row 2 + retVal.mat[2][0] = + left.mat[2][0] * right.mat[0][0] + + left.mat[2][1] * right.mat[1][0] + + left.mat[2][2] * right.mat[2][0]; + + retVal.mat[2][1] = + left.mat[2][0] * right.mat[0][1] + + left.mat[2][1] * right.mat[1][1] + + left.mat[2][2] * right.mat[2][1]; + + retVal.mat[2][2] = + left.mat[2][0] * right.mat[0][2] + + left.mat[2][1] * right.mat[1][2] + + left.mat[2][2] * right.mat[2][2]; + + return retVal; + } + + Matrix3& operator*=(const Matrix3& right) + { + *this = *this * right; + return *this; + } + + // Create a scale matrix with x and y scales + static Matrix3 CreateScale(float xScale, float yScale) + { + float temp[3][3] = + { + { xScale, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + static Matrix3 CreateScale(const Vector2& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y); + } + + // Create a scale matrix with a uniform factor + static Matrix3 CreateScale(float scale) + { + return CreateScale(scale, scale); + } + + // Create a rotation matrix about the Z axis + // theta is in radians + static Matrix3 CreateRotation(float theta) + { + float temp[3][3] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + // Create a translation matrix (on the xy-plane) + static Matrix3 CreateTranslation(const Vector2& trans) + { + float temp[3][3] = + { + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, 1.0f }, + }; + return Matrix3(temp); + } + + static const Matrix3 Identity; +}; + +// 4x4 Matrix +class Matrix4 +{ +public: + float mat[4][4]; + + Matrix4() + { + *this = Matrix4::Identity; + } + + explicit Matrix4(float inMat[4][4]) + { + memcpy(mat, inMat, 16 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication (a * b) + friend Matrix4 operator*(const Matrix4& a, const Matrix4& b) + { + Matrix4 retVal; + // row 0 + retVal.mat[0][0] = + a.mat[0][0] * b.mat[0][0] + + a.mat[0][1] * b.mat[1][0] + + a.mat[0][2] * b.mat[2][0] + + a.mat[0][3] * b.mat[3][0]; + + retVal.mat[0][1] = + a.mat[0][0] * b.mat[0][1] + + a.mat[0][1] * b.mat[1][1] + + a.mat[0][2] * b.mat[2][1] + + a.mat[0][3] * b.mat[3][1]; + + retVal.mat[0][2] = + a.mat[0][0] * b.mat[0][2] + + a.mat[0][1] * b.mat[1][2] + + a.mat[0][2] * b.mat[2][2] + + a.mat[0][3] * b.mat[3][2]; + + retVal.mat[0][3] = + a.mat[0][0] * b.mat[0][3] + + a.mat[0][1] * b.mat[1][3] + + a.mat[0][2] * b.mat[2][3] + + a.mat[0][3] * b.mat[3][3]; + + // row 1 + retVal.mat[1][0] = + a.mat[1][0] * b.mat[0][0] + + a.mat[1][1] * b.mat[1][0] + + a.mat[1][2] * b.mat[2][0] + + a.mat[1][3] * b.mat[3][0]; + + retVal.mat[1][1] = + a.mat[1][0] * b.mat[0][1] + + a.mat[1][1] * b.mat[1][1] + + a.mat[1][2] * b.mat[2][1] + + a.mat[1][3] * b.mat[3][1]; + + retVal.mat[1][2] = + a.mat[1][0] * b.mat[0][2] + + a.mat[1][1] * b.mat[1][2] + + a.mat[1][2] * b.mat[2][2] + + a.mat[1][3] * b.mat[3][2]; + + retVal.mat[1][3] = + a.mat[1][0] * b.mat[0][3] + + a.mat[1][1] * b.mat[1][3] + + a.mat[1][2] * b.mat[2][3] + + a.mat[1][3] * b.mat[3][3]; + + // row 2 + retVal.mat[2][0] = + a.mat[2][0] * b.mat[0][0] + + a.mat[2][1] * b.mat[1][0] + + a.mat[2][2] * b.mat[2][0] + + a.mat[2][3] * b.mat[3][0]; + + retVal.mat[2][1] = + a.mat[2][0] * b.mat[0][1] + + a.mat[2][1] * b.mat[1][1] + + a.mat[2][2] * b.mat[2][1] + + a.mat[2][3] * b.mat[3][1]; + + retVal.mat[2][2] = + a.mat[2][0] * b.mat[0][2] + + a.mat[2][1] * b.mat[1][2] + + a.mat[2][2] * b.mat[2][2] + + a.mat[2][3] * b.mat[3][2]; + + retVal.mat[2][3] = + a.mat[2][0] * b.mat[0][3] + + a.mat[2][1] * b.mat[1][3] + + a.mat[2][2] * b.mat[2][3] + + a.mat[2][3] * b.mat[3][3]; + + // row 3 + retVal.mat[3][0] = + a.mat[3][0] * b.mat[0][0] + + a.mat[3][1] * b.mat[1][0] + + a.mat[3][2] * b.mat[2][0] + + a.mat[3][3] * b.mat[3][0]; + + retVal.mat[3][1] = + a.mat[3][0] * b.mat[0][1] + + a.mat[3][1] * b.mat[1][1] + + a.mat[3][2] * b.mat[2][1] + + a.mat[3][3] * b.mat[3][1]; + + retVal.mat[3][2] = + a.mat[3][0] * b.mat[0][2] + + a.mat[3][1] * b.mat[1][2] + + a.mat[3][2] * b.mat[2][2] + + a.mat[3][3] * b.mat[3][2]; + + retVal.mat[3][3] = + a.mat[3][0] * b.mat[0][3] + + a.mat[3][1] * b.mat[1][3] + + a.mat[3][2] * b.mat[2][3] + + a.mat[3][3] * b.mat[3][3]; + + return retVal; + } + + Matrix4& operator*=(const Matrix4& right) + { + *this = *this * right; + return *this; + } + + // Invert the matrix - super slow + void Invert(); + + // Get the translation component of the matrix + Vector3 GetTranslation() const + { + return Vector3(mat[3][0], mat[3][1], mat[3][2]); + } + + // Get the X axis of the matrix (forward) + Vector3 GetXAxis() const + { + return Vector3::Normalize(Vector3(mat[0][0], mat[0][1], mat[0][2])); + } + + // Get the Y axis of the matrix (left) + Vector3 GetYAxis() const + { + return Vector3::Normalize(Vector3(mat[1][0], mat[1][1], mat[1][2])); + } + + // Get the Z axis of the matrix (up) + Vector3 GetZAxis() const + { + return Vector3::Normalize(Vector3(mat[2][0], mat[2][1], mat[2][2])); + } + + // Extract the scale component from the matrix + Vector3 GetScale() const + { + Vector3 retVal; + retVal.x = Vector3(mat[0][0], mat[0][1], mat[0][2]).Length(); + retVal.y = Vector3(mat[1][0], mat[1][1], mat[1][2]).Length(); + retVal.z = Vector3(mat[2][0], mat[2][1], mat[2][2]).Length(); + return retVal; + } + + // Create a scale matrix with x, y, and z scales + static Matrix4 CreateScale(float xScale, float yScale, float zScale) + { + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, zScale, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateScale(const Vector3& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y, scaleVector.z); + } + + // Create a scale matrix with a uniform factor + static Matrix4 CreateScale(float scale) + { + return CreateScale(scale, scale, scale); + } + + // Rotation about x-axis + static Matrix4 CreateRotationX(float theta) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f , 0.0f }, + { 0.0f, Math::Cos(theta), Math::Sin(theta), 0.0f }, + { 0.0f, -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about y-axis + static Matrix4 CreateRotationY(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), 0.0f, -Math::Sin(theta), 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { Math::Sin(theta), 0.0f, Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about z-axis + static Matrix4 CreateRotationZ(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f, 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Create a rotation matrix from a quaternion + static Matrix4 CreateFromQuaternion(const class Quaternion& q); + + static Matrix4 CreateTranslation(const Vector3& trans) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateLookAt(const Vector3& eye, const Vector3& target, const Vector3& up) + { + Vector3 zaxis = Vector3::Normalize(target - eye); + Vector3 xaxis = Vector3::Normalize(Vector3::Cross(up, zaxis)); + Vector3 yaxis = Vector3::Normalize(Vector3::Cross(zaxis, xaxis)); + Vector3 trans; + trans.x = -Vector3::Dot(xaxis, eye); + trans.y = -Vector3::Dot(yaxis, eye); + trans.z = -Vector3::Dot(zaxis, eye); + + float temp[4][4] = + { + { xaxis.x, yaxis.x, zaxis.x, 0.0f }, + { xaxis.y, yaxis.y, zaxis.y, 0.0f }, + { xaxis.z, yaxis.z, zaxis.z, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateOrtho(float width, float height, float near, float far) + { + float temp[4][4] = + { + { 2.0f / width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f / height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f / (far - near), 0.0f }, + { 0.0f, 0.0f, near / (near - far), 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreatePerspectiveFOV(float fovY, float width, float height, float near, float far) + { + float yScale = Math::Cot(fovY / 2.0f); + float xScale = yScale * height / width; + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, far / (far - near), 1.0f }, + { 0.0f, 0.0f, -near * far / (far - near), 0.0f } + }; + return Matrix4(temp); + } + + // Create "Simple" View-Projection Matrix from Chapter 6 + static Matrix4 CreateSimpleViewProj(float width, float height) + { + float temp[4][4] = + { + { 2.0f/width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f } + }; + return Matrix4(temp); + } + + static const Matrix4 Identity; +}; + +// (Unit) Quaternion +class Quaternion +{ +public: + float x; + float y; + float z; + float w; + + Quaternion() + { + *this = Quaternion::Identity; + } + + // This directly sets the quaternion components -- + // don't use for axis/angle + explicit Quaternion(float inX, float inY, float inZ, float inW) + { + Set(inX, inY, inZ, inW); + } + + // Construct the quaternion from an axis and angle + // It is assumed that axis is already normalized, + // and the angle is in radians + explicit Quaternion(const Vector3& axis, float angle) + { + float scalar = Math::Sin(angle / 2.0f); + x = axis.x * scalar; + y = axis.y * scalar; + z = axis.z * scalar; + w = Math::Cos(angle / 2.0f); + } + + // Directly set the internal components + void Set(float inX, float inY, float inZ, float inW) + { + x = inX; + y = inY; + z = inZ; + w = inW; + } + + void Conjugate() + { + x *= -1.0f; + y *= -1.0f; + z *= -1.0f; + } + + float LengthSq() const + { + return (x*x + y*y + z*z + w*w); + } + + float Length() const + { + return Math::Sqrt(LengthSq()); + } + + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + w /= length; + } + + // Normalize the provided quaternion + static Quaternion Normalize(const Quaternion& q) + { + Quaternion retVal = q; + retVal.Normalize(); + return retVal; + } + + // Linear interpolation + static Quaternion Lerp(const Quaternion& a, const Quaternion& b, float f) + { + Quaternion retVal; + retVal.x = Math::Lerp(a.x, b.x, f); + retVal.y = Math::Lerp(a.y, b.y, f); + retVal.z = Math::Lerp(a.z, b.z, f); + retVal.w = Math::Lerp(a.w, b.w, f); + retVal.Normalize(); + return retVal; + } + + static float Dot(const Quaternion& a, const Quaternion& b) + { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + // Spherical Linear Interpolation + static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float f) + { + float rawCosm = Quaternion::Dot(a, b); + + float cosom = -rawCosm; + if (rawCosm >= 0.0f) + { + cosom = rawCosm; + } + + float scale0, scale1; + + if (cosom < 0.9999f) + { + const float omega = Math::Acos(cosom); + const float invSin = 1.f / Math::Sin(omega); + scale0 = Math::Sin((1.f - f) * omega) * invSin; + scale1 = Math::Sin(f * omega) * invSin; + } + else + { + // Use linear interpolation if the quaternions + // are collinear + scale0 = 1.0f - f; + scale1 = f; + } + + if (rawCosm < 0.0f) + { + scale1 = -scale1; + } + + Quaternion retVal; + retVal.x = scale0 * a.x + scale1 * b.x; + retVal.y = scale0 * a.y + scale1 * b.y; + retVal.z = scale0 * a.z + scale1 * b.z; + retVal.w = scale0 * a.w + scale1 * b.w; + retVal.Normalize(); + return retVal; + } + + // Concatenate + // Rotate by q FOLLOWED BY p + static Quaternion Concatenate(const Quaternion& q, const Quaternion& p) + { + Quaternion retVal; + + // Vector component is: + // ps * qv + qs * pv + pv x qv + Vector3 qv(q.x, q.y, q.z); + Vector3 pv(p.x, p.y, p.z); + Vector3 newVec = p.w * qv + q.w * pv + Vector3::Cross(pv, qv); + retVal.x = newVec.x; + retVal.y = newVec.y; + retVal.z = newVec.z; + + // Scalar component is: + // ps * qs - pv . qv + retVal.w = p.w * q.w - Vector3::Dot(pv, qv); + + return retVal; + } + + static const Quaternion Identity; +}; + +namespace Color +{ + static const Vector3 Black(0.0f, 0.0f, 0.0f); + static const Vector3 White(1.0f, 1.0f, 1.0f); + static const Vector3 Red(1.0f, 0.0f, 0.0f); + static const Vector3 Green(0.0f, 1.0f, 0.0f); + static const Vector3 Blue(0.0f, 0.0f, 1.0f); + static const Vector3 Yellow(1.0f, 1.0f, 0.0f); + static const Vector3 LightYellow(1.0f, 1.0f, 0.88f); + static const Vector3 LightBlue(0.68f, 0.85f, 0.9f); + static const Vector3 LightPink(1.0f, 0.71f, 0.76f); + static const Vector3 LightGreen(0.56f, 0.93f, 0.56f); +} diff --git a/Chapter03/MoveComponent.cpp b/Chapter03/MoveComponent.cpp new file mode 100644 index 00000000..2f0b8a6a --- /dev/null +++ b/Chapter03/MoveComponent.cpp @@ -0,0 +1,43 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "MoveComponent.h" +#include "Actor.h" + +MoveComponent::MoveComponent(class Actor* owner, int updateOrder) +:Component(owner, updateOrder) +,mAngularSpeed(0.0f) +,mForwardSpeed(0.0f) +{ + +} + +void MoveComponent::Update(float deltaTime) +{ + if (!Math::NearZero(mAngularSpeed)) + { + float rot = mOwner->GetRotation(); + rot += mAngularSpeed * deltaTime; + mOwner->SetRotation(rot); + } + + if (!Math::NearZero(mForwardSpeed)) + { + Vector2 pos = mOwner->GetPosition(); + pos += mOwner->GetForward() * mForwardSpeed * deltaTime; + + // (Screen wrapping code only for asteroids) + if (pos.x < 0.0f) { pos.x = 1022.0f; } + else if (pos.x > 1024.0f) { pos.x = 2.0f; } + + if (pos.y < 0.0f) { pos.y = 766.0f; } + else if (pos.y > 768.0f) { pos.y = 2.0f; } + + mOwner->SetPosition(pos); + } +} diff --git a/Chapter03/MoveComponent.h b/Chapter03/MoveComponent.h new file mode 100644 index 00000000..482c1e66 --- /dev/null +++ b/Chapter03/MoveComponent.h @@ -0,0 +1,29 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" + +class MoveComponent : public Component +{ +public: + // Lower update order to update first + MoveComponent(class Actor* owner, int updateOrder = 10); + + void Update(float deltaTime) override; + + float GetAngularSpeed() const { return mAngularSpeed; } + float GetForwardSpeed() const { return mForwardSpeed; } + void SetAngularSpeed(float speed) { mAngularSpeed = speed; } + void SetForwardSpeed(float speed) { mForwardSpeed = speed; } +private: + // Controls rotation (radians/second) + float mAngularSpeed; + // Controls forward movement (units/second) + float mForwardSpeed; +}; diff --git a/Chapter03/Random.cpp b/Chapter03/Random.cpp new file mode 100644 index 00000000..05a3a32a --- /dev/null +++ b/Chapter03/Random.cpp @@ -0,0 +1,51 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Random.h" + +void Random::Init() +{ + std::random_device rd; + Random::Seed(rd()); +} + +void Random::Seed(unsigned int seed) +{ + sGenerator.seed(seed); +} + +float Random::GetFloat() +{ + return GetFloatRange(0.0f, 1.0f); +} + +float Random::GetFloatRange(float min, float max) +{ + std::uniform_real_distribution dist(min, max); + return dist(sGenerator); +} + +int Random::GetIntRange(int min, int max) +{ + std::uniform_int_distribution dist(min, max); + return dist(sGenerator); +} + +Vector2 Random::GetVector(const Vector2& min, const Vector2& max) +{ + Vector2 r = Vector2(GetFloat(), GetFloat()); + return min + (max - min) * r; +} + +Vector3 Random::GetVector(const Vector3& min, const Vector3& max) +{ + Vector3 r = Vector3(GetFloat(), GetFloat(), GetFloat()); + return min + (max - min) * r; +} + +std::mt19937 Random::sGenerator; diff --git a/Chapter03/Random.h b/Chapter03/Random.h new file mode 100644 index 00000000..3ae92fe5 --- /dev/null +++ b/Chapter03/Random.h @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include "Math.h" + +class Random +{ +public: + static void Init(); + + // Seed the generator with the specified int + // NOTE: You should generally not need to manually use this + static void Seed(unsigned int seed); + + // Get a float between 0.0f and 1.0f + static float GetFloat(); + + // Get a float from the specified range + static float GetFloatRange(float min, float max); + + // Get an int from the specified range + static int GetIntRange(int min, int max); + + // Get a random vector given the min/max bounds + static Vector2 GetVector(const Vector2& min, const Vector2& max); + static Vector3 GetVector(const Vector3& min, const Vector3& max); +private: + static std::mt19937 sGenerator; +}; diff --git a/Chapter03/Ship.cpp b/Chapter03/Ship.cpp new file mode 100644 index 00000000..e5b236db --- /dev/null +++ b/Chapter03/Ship.cpp @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Ship.h" +#include "SpriteComponent.h" +#include "InputComponent.h" +#include "Game.h" +#include "Laser.h" + +Ship::Ship(Game* game) + :Actor(game) + ,mLaserCooldown(0.0f) +{ + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this, 150); + sc->SetTexture(game->GetTexture("Assets/Ship.png")); + + // Create an input component and set keys/speed + InputComponent* ic = new InputComponent(this); + ic->SetForwardKey(SDL_SCANCODE_W); + ic->SetBackKey(SDL_SCANCODE_S); + ic->SetClockwiseKey(SDL_SCANCODE_A); + ic->SetCounterClockwiseKey(SDL_SCANCODE_D); + ic->SetMaxForwardSpeed(300.0f); + ic->SetMaxAngularSpeed(Math::TwoPi); +} + +void Ship::UpdateActor(float deltaTime) +{ + mLaserCooldown -= deltaTime; +} + +void Ship::ActorInput(const uint8_t* keyState) +{ + if (keyState[SDL_SCANCODE_SPACE] && mLaserCooldown <= 0.0f) + { + // Create a laser and set its position/rotation to mine + Laser* laser = new Laser(GetGame()); + laser->SetPosition(GetPosition()); + laser->SetRotation(GetRotation()); + + // Reset laser cooldown (half second) + mLaserCooldown = 0.5f; + } +} diff --git a/Chapter03/Ship.h b/Chapter03/Ship.h new file mode 100644 index 00000000..808639ff --- /dev/null +++ b/Chapter03/Ship.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Ship : public Actor +{ +public: + Ship(class Game* game); + + void UpdateActor(float deltaTime) override; + void ActorInput(const uint8_t* keyState) override; +private: + float mLaserCooldown; +}; \ No newline at end of file diff --git a/Chapter03/SpriteComponent.cpp b/Chapter03/SpriteComponent.cpp new file mode 100644 index 00000000..56884fbc --- /dev/null +++ b/Chapter03/SpriteComponent.cpp @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "SpriteComponent.h" +#include "Actor.h" +#include "Game.h" + +SpriteComponent::SpriteComponent(Actor* owner, int drawOrder) + :Component(owner) + ,mTexture(nullptr) + ,mDrawOrder(drawOrder) + ,mTexWidth(0) + ,mTexHeight(0) +{ + mOwner->GetGame()->AddSprite(this); +} + +SpriteComponent::~SpriteComponent() +{ + mOwner->GetGame()->RemoveSprite(this); +} + +void SpriteComponent::Draw(SDL_Renderer* renderer) +{ + if (mTexture) + { + SDL_Rect r; + // Scale the width/height by owner's scale + r.w = static_cast(mTexWidth * mOwner->GetScale()); + r.h = static_cast(mTexHeight * mOwner->GetScale()); + // Center the rectangle around the position of the owner + r.x = static_cast(mOwner->GetPosition().x - r.w / 2); + r.y = static_cast(mOwner->GetPosition().y - r.h / 2); + + // Draw (have to convert angle from radians to degrees, and clockwise to counter) + SDL_RenderCopyEx(renderer, + mTexture, + nullptr, + &r, + -Math::ToDegrees(mOwner->GetRotation()), + nullptr, + SDL_FLIP_NONE); + } +} + +void SpriteComponent::SetTexture(SDL_Texture* texture) +{ + mTexture = texture; + // Set width/height + SDL_QueryTexture(texture, nullptr, nullptr, &mTexWidth, &mTexHeight); +} diff --git a/Chapter03/SpriteComponent.h b/Chapter03/SpriteComponent.h new file mode 100644 index 00000000..c430e888 --- /dev/null +++ b/Chapter03/SpriteComponent.h @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "SDL/SDL.h" +class SpriteComponent : public Component +{ +public: + // (Lower draw order corresponds with further back) + SpriteComponent(class Actor* owner, int drawOrder = 100); + ~SpriteComponent(); + + virtual void Draw(SDL_Renderer* renderer); + virtual void SetTexture(SDL_Texture* texture); + + int GetDrawOrder() const { return mDrawOrder; } + int GetTexHeight() const { return mTexHeight; } + int GetTexWidth() const { return mTexWidth; } +protected: + SDL_Texture* mTexture; + int mDrawOrder; + int mTexWidth; + int mTexHeight; +}; diff --git a/Chapter04/AIComponent.cpp b/Chapter04/AIComponent.cpp new file mode 100644 index 00000000..e88ac6ab --- /dev/null +++ b/Chapter04/AIComponent.cpp @@ -0,0 +1,54 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "AIComponent.h" +#include "Actor.h" +#include "AIState.h" +#include + +AIComponent::AIComponent(class Actor* owner) +:Component(owner) +,mCurrentState(nullptr) +{ +} + +void AIComponent::Update(float deltaTime) +{ + if (mCurrentState) + { + mCurrentState->Update(deltaTime); + } +} + +void AIComponent::ChangeState(const std::string& name) +{ + // First exit the current state + if (mCurrentState) + { + mCurrentState->OnExit(); + } + + // Try to find the new state from the map + auto iter = mStateMap.find(name); + if (iter != mStateMap.end()) + { + mCurrentState = iter->second; + // We're entering the new state + mCurrentState->OnEnter(); + } + else + { + SDL_Log("Could not find AIState %s in state map", name.c_str()); + mCurrentState = nullptr; + } +} + +void AIComponent::RegisterState(AIState* state) +{ + mStateMap.emplace(state->GetName(), state); +} diff --git a/Chapter04/AIComponent.h b/Chapter04/AIComponent.h new file mode 100644 index 00000000..932de9a1 --- /dev/null +++ b/Chapter04/AIComponent.h @@ -0,0 +1,29 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include +#include + +class AIComponent : public Component +{ +public: + AIComponent(class Actor* owner); + + void Update(float deltaTime) override; + void ChangeState(const std::string& name); + + // Add a new state to the map + void RegisterState(class AIState* state); +private: + // Maps name of state to AIState instance + std::unordered_map mStateMap; + // Current state we're in + class AIState* mCurrentState; +}; diff --git a/Chapter04/AIState.cpp b/Chapter04/AIState.cpp new file mode 100644 index 00000000..673e9b42 --- /dev/null +++ b/Chapter04/AIState.cpp @@ -0,0 +1,61 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "AIState.h" +#include "AIComponent.h" +#include + +void AIPatrol::Update(float deltaTime) +{ + SDL_Log("Updating %s state", GetName()); + bool dead = true; + if (dead) + { + mOwner->ChangeState("Death"); + } +} + +void AIPatrol::OnEnter() +{ + SDL_Log("Entering %s state", GetName()); +} + +void AIPatrol::OnExit() +{ + SDL_Log("Exiting %s state", GetName()); +} + +void AIDeath::Update(float deltaTime) +{ + SDL_Log("Updating %s state", GetName()); +} + +void AIDeath::OnEnter() +{ + SDL_Log("Entering %s state", GetName()); +} + +void AIDeath::OnExit() +{ + SDL_Log("Exiting %s state", GetName()); +} + +void AIAttack::Update(float deltaTime) +{ + SDL_Log("Updating %s state", GetName()); +} + +void AIAttack::OnEnter() +{ + SDL_Log("Entering %s state", GetName()); +} + +void AIAttack::OnExit() +{ + SDL_Log("Exiting %s state", GetName()); +} diff --git a/Chapter04/AIState.h b/Chapter04/AIState.h new file mode 100644 index 00000000..67ef2fd1 --- /dev/null +++ b/Chapter04/AIState.h @@ -0,0 +1,71 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once + +class AIState +{ +public: + AIState(class AIComponent* owner) + :mOwner(owner) + { } + // State-specific behavior + virtual void Update(float deltaTime) = 0; + virtual void OnEnter() = 0; + virtual void OnExit() = 0; + // Getter for string name of state + virtual const char* GetName() const = 0; +protected: + class AIComponent* mOwner; +}; + +class AIPatrol : public AIState +{ +public: + AIPatrol(class AIComponent* owner) + :AIState(owner) + { } + + // Override with behaviors for this state + void Update(float deltaTime) override; + void OnEnter() override; + void OnExit() override; + + const char* GetName() const override + { return "Patrol"; } +}; + +class AIDeath : public AIState +{ +public: + AIDeath(class AIComponent* owner) + :AIState(owner) + { } + + void Update(float deltaTime) override; + void OnEnter() override; + void OnExit() override; + + const char* GetName() const override + { return "Death"; } +}; + +class AIAttack : public AIState +{ +public: + AIAttack(class AIComponent* owner) + :AIState(owner) + { } + + void Update(float deltaTime) override; + void OnEnter() override; + void OnExit() override; + + const char* GetName() const override + { return "Attack"; } +}; diff --git a/Chapter04/Actor.cpp b/Chapter04/Actor.cpp new file mode 100644 index 00000000..4b8eea91 --- /dev/null +++ b/Chapter04/Actor.cpp @@ -0,0 +1,101 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Actor.h" +#include "Game.h" +#include "Component.h" +#include + +Actor::Actor(Game* game) + :mState(EActive) + , mPosition(Vector2::Zero) + , mScale(1.0f) + , mRotation(0.0f) + , mGame(game) +{ + mGame->AddActor(this); +} + +Actor::~Actor() +{ + mGame->RemoveActor(this); + // Need to delete components + // Because ~Component calls RemoveComponent, need a different style loop + while (!mComponents.empty()) + { + delete mComponents.back(); + } +} + +void Actor::Update(float deltaTime) +{ + if (mState == EActive) + { + UpdateComponents(deltaTime); + UpdateActor(deltaTime); + } +} + +void Actor::UpdateComponents(float deltaTime) +{ + for (auto comp : mComponents) + { + comp->Update(deltaTime); + } +} + +void Actor::UpdateActor(float deltaTime) +{ +} + +void Actor::ProcessInput(const uint8_t* keyState) +{ + if (mState == EActive) + { + // First process input for components + for (auto comp : mComponents) + { + comp->ProcessInput(keyState); + } + + ActorInput(keyState); + } +} + +void Actor::ActorInput(const uint8_t* keyState) +{ +} + +void Actor::AddComponent(Component* component) +{ + // Find the insertion point in the sorted vector + // (The first element with a order higher than me) + int myOrder = component->GetUpdateOrder(); + auto iter = mComponents.begin(); + for (; + iter != mComponents.end(); + ++iter) + { + if (myOrder < (*iter)->GetUpdateOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mComponents.insert(iter, component); +} + +void Actor::RemoveComponent(Component* component) +{ + auto iter = std::find(mComponents.begin(), mComponents.end(), component); + if (iter != mComponents.end()) + { + mComponents.erase(iter); + } +} diff --git a/Chapter04/Actor.h b/Chapter04/Actor.h new file mode 100644 index 00000000..767d3c1a --- /dev/null +++ b/Chapter04/Actor.h @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include "Math.h" +#include + +class Actor +{ +public: + enum State + { + EActive, + EPaused, + EDead + }; + + Actor(class Game* game); + virtual ~Actor(); + + // Update function called from Game (not overridable) + void Update(float deltaTime); + // Updates all the components attached to the actor (not overridable) + void UpdateComponents(float deltaTime); + // Any actor-specific update code (overridable) + virtual void UpdateActor(float deltaTime); + + // ProcessInput function called from Game (not overridable) + void ProcessInput(const uint8_t* keyState); + // Any actor-specific input code (overridable) + virtual void ActorInput(const uint8_t* keyState); + + // Getters/setters + const Vector2& GetPosition() const { return mPosition; } + void SetPosition(const Vector2& pos) { mPosition = pos; } + float GetScale() const { return mScale; } + void SetScale(float scale) { mScale = scale; } + float GetRotation() const { return mRotation; } + void SetRotation(float rotation) { mRotation = rotation; } + + Vector2 GetForward() const { return Vector2(Math::Cos(mRotation), -Math::Sin(mRotation)); } + + State GetState() const { return mState; } + void SetState(State state) { mState = state; } + + class Game* GetGame() { return mGame; } + + + // Add/remove components + void AddComponent(class Component* component); + void RemoveComponent(class Component* component); +private: + // Actor's state + State mState; + + // Transform + Vector2 mPosition; + float mScale; + float mRotation; + + std::vector mComponents; + class Game* mGame; +}; diff --git a/Chapter04/Assets/Airplane.png b/Chapter04/Assets/Airplane.png new file mode 100644 index 00000000..df752ef1 Binary files /dev/null and b/Chapter04/Assets/Airplane.png differ diff --git a/Chapter04/Assets/Base.png b/Chapter04/Assets/Base.png new file mode 100644 index 00000000..363b5a26 Binary files /dev/null and b/Chapter04/Assets/Base.png differ diff --git a/Chapter04/Assets/LICENSE.txt b/Chapter04/Assets/LICENSE.txt new file mode 100644 index 00000000..bc9e114b --- /dev/null +++ b/Chapter04/Assets/LICENSE.txt @@ -0,0 +1,3 @@ +These sprites created by Kenney.nl + +Used under CC0 license. \ No newline at end of file diff --git a/Chapter04/Assets/Missile.png b/Chapter04/Assets/Missile.png new file mode 100644 index 00000000..87bfed1a Binary files /dev/null and b/Chapter04/Assets/Missile.png differ diff --git a/Chapter04/Assets/Projectile.png b/Chapter04/Assets/Projectile.png new file mode 100644 index 00000000..25722fc3 Binary files /dev/null and b/Chapter04/Assets/Projectile.png differ diff --git a/Chapter04/Assets/TileBrown.png b/Chapter04/Assets/TileBrown.png new file mode 100644 index 00000000..285f393f Binary files /dev/null and b/Chapter04/Assets/TileBrown.png differ diff --git a/Chapter04/Assets/TileBrownSelected.png b/Chapter04/Assets/TileBrownSelected.png new file mode 100644 index 00000000..afdfec98 Binary files /dev/null and b/Chapter04/Assets/TileBrownSelected.png differ diff --git a/Chapter04/Assets/TileGreen.png b/Chapter04/Assets/TileGreen.png new file mode 100644 index 00000000..b890b603 Binary files /dev/null and b/Chapter04/Assets/TileGreen.png differ diff --git a/Chapter04/Assets/TileGreenSelected.png b/Chapter04/Assets/TileGreenSelected.png new file mode 100644 index 00000000..56156bd6 Binary files /dev/null and b/Chapter04/Assets/TileGreenSelected.png differ diff --git a/Chapter04/Assets/TileGrey.png b/Chapter04/Assets/TileGrey.png new file mode 100644 index 00000000..af6bf875 Binary files /dev/null and b/Chapter04/Assets/TileGrey.png differ diff --git a/Chapter04/Assets/TileGreySelected.png b/Chapter04/Assets/TileGreySelected.png new file mode 100644 index 00000000..50623317 Binary files /dev/null and b/Chapter04/Assets/TileGreySelected.png differ diff --git a/Chapter04/Assets/TileTan.png b/Chapter04/Assets/TileTan.png new file mode 100644 index 00000000..40c68003 Binary files /dev/null and b/Chapter04/Assets/TileTan.png differ diff --git a/Chapter04/Assets/TileTanSelected.png b/Chapter04/Assets/TileTanSelected.png new file mode 100644 index 00000000..4e39aae7 Binary files /dev/null and b/Chapter04/Assets/TileTanSelected.png differ diff --git a/Chapter04/Assets/Tower.png b/Chapter04/Assets/Tower.png new file mode 100644 index 00000000..c7c1d179 Binary files /dev/null and b/Chapter04/Assets/Tower.png differ diff --git a/Chapter04/Bullet.cpp b/Chapter04/Bullet.cpp new file mode 100644 index 00000000..fddb6949 --- /dev/null +++ b/Chapter04/Bullet.cpp @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE.txt for full details. +// ---------------------------------------------------------------- + +#include "Bullet.h" +#include "SpriteComponent.h" +#include "MoveComponent.h" +#include "CircleComponent.h" +#include "Game.h" +#include "Enemy.h" + +Bullet::Bullet(class Game* game) +:Actor(game) +{ + SpriteComponent* sc = new SpriteComponent(this); + sc->SetTexture(game->GetTexture("Assets/Projectile.png")); + + MoveComponent* mc = new MoveComponent(this); + mc->SetForwardSpeed(400.0f); + + mCircle = new CircleComponent(this); + mCircle->SetRadius(5.0f); + + mLiveTime = 1.0f; +} + +void Bullet::UpdateActor(float deltaTime) +{ + Actor::UpdateActor(deltaTime); + + // Check for collision vs enemies + for (Enemy* e : GetGame()->GetEnemies()) + { + if (Intersect(*mCircle, *(e->GetCircle()))) + { + // We both die on collision + e->SetState(EDead); + SetState(EDead); + break; + } + } + + mLiveTime -= deltaTime; + if (mLiveTime <= 0.0f) + { + // Time limit hit, die + SetState(EDead); + } +} diff --git a/Chapter04/Bullet.h b/Chapter04/Bullet.h new file mode 100644 index 00000000..2c1690c4 --- /dev/null +++ b/Chapter04/Bullet.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" + +class Bullet : public Actor +{ +public: + Bullet(class Game* game); + void UpdateActor(float deltaTime) override; +private: + class CircleComponent* mCircle; + float mLiveTime; +}; diff --git a/Chapter04/Chapter04-mac.xcodeproj/project.pbxproj b/Chapter04/Chapter04-mac.xcodeproj/project.pbxproj new file mode 100644 index 00000000..82443e7c --- /dev/null +++ b/Chapter04/Chapter04-mac.xcodeproj/project.pbxproj @@ -0,0 +1,402 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9203E9F01F0DD69900F9FFC2 /* Tower.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9203E9EE1F0DD69900F9FFC2 /* Tower.cpp */; }; + 9203E9F31F0DE24000F9FFC2 /* Enemy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9203E9F11F0DE24000F9FFC2 /* Enemy.cpp */; }; + 9203E9F61F0DF13600F9FFC2 /* NavComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9203E9F41F0DF13600F9FFC2 /* NavComponent.cpp */; }; + 9203E9F91F0F12FE00F9FFC2 /* Bullet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9203E9F71F0F12FE00F9FFC2 /* Bullet.cpp */; }; + 9223C4781F009428009A94D7 /* Game.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4671F009428009A94D7 /* Game.cpp */; }; + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4681F009428009A94D7 /* Actor.cpp */; }; + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C46E1F009428009A94D7 /* Component.cpp */; }; + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4711F009428009A94D7 /* Main.cpp */; }; + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4721F009428009A94D7 /* Math.cpp */; }; + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4761F009428009A94D7 /* SpriteComponent.cpp */; }; + 9223C48B1F0CA3CE009A94D7 /* MoveComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */; }; + 9223C48F1F0CA67A009A94D7 /* Tile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C48D1F0CA67A009A94D7 /* Tile.cpp */; }; + 9223C4951F0CA766009A94D7 /* CircleComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */; }; + 9223C4981F0DBD69009A94D7 /* Grid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4961F0DBD69009A94D7 /* Grid.cpp */; }; + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92D324FA1B697389005A86C7 /* CoreFoundation.framework */; }; + 92E3918B1FE87D6000D8C362 /* AIState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391861FE87D6000D8C362 /* AIState.cpp */; }; + 92E3918C1FE87D6000D8C362 /* AIComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391881FE87D6000D8C362 /* AIComponent.cpp */; }; + 92E3918D1FE87D6000D8C362 /* Search.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E3918A1FE87D6000D8C362 /* Search.cpp */; }; + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92E46E931B6353E50035CD21 /* OpenGL.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 9203E9EE1F0DD69900F9FFC2 /* Tower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tower.cpp; sourceTree = ""; }; + 9203E9EF1F0DD69900F9FFC2 /* Tower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tower.h; sourceTree = ""; }; + 9203E9F11F0DE24000F9FFC2 /* Enemy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Enemy.cpp; sourceTree = ""; }; + 9203E9F21F0DE24000F9FFC2 /* Enemy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Enemy.h; sourceTree = ""; }; + 9203E9F41F0DF13600F9FFC2 /* NavComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NavComponent.cpp; sourceTree = ""; }; + 9203E9F51F0DF13600F9FFC2 /* NavComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NavComponent.h; sourceTree = ""; }; + 9203E9F71F0F12FE00F9FFC2 /* Bullet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Bullet.cpp; sourceTree = ""; }; + 9203E9F81F0F12FE00F9FFC2 /* Bullet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Bullet.h; sourceTree = ""; }; + 9223C4671F009428009A94D7 /* Game.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Game.cpp; sourceTree = ""; }; + 9223C4681F009428009A94D7 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 9223C4691F009428009A94D7 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 9223C46E1F009428009A94D7 /* Component.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Component.cpp; sourceTree = ""; }; + 9223C46F1F009428009A94D7 /* Component.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Component.h; sourceTree = ""; }; + 9223C4701F009428009A94D7 /* Game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Game.h; sourceTree = ""; }; + 9223C4711F009428009A94D7 /* Main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Main.cpp; sourceTree = ""; }; + 9223C4721F009428009A94D7 /* Math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Math.cpp; sourceTree = ""; }; + 9223C4731F009428009A94D7 /* Math.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Math.h; sourceTree = ""; }; + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpriteComponent.cpp; sourceTree = ""; }; + 9223C4771F009428009A94D7 /* SpriteComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpriteComponent.h; sourceTree = ""; }; + 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MoveComponent.cpp; sourceTree = ""; }; + 9223C48C1F0CA3D4009A94D7 /* MoveComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MoveComponent.h; sourceTree = ""; }; + 9223C48D1F0CA67A009A94D7 /* Tile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tile.cpp; sourceTree = ""; }; + 9223C48E1F0CA67A009A94D7 /* Tile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tile.h; sourceTree = ""; }; + 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CircleComponent.cpp; sourceTree = ""; }; + 9223C4931F0CA766009A94D7 /* CircleComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleComponent.h; sourceTree = ""; }; + 9223C4961F0DBD69009A94D7 /* Grid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Grid.cpp; sourceTree = ""; }; + 9223C4971F0DBD69009A94D7 /* Grid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Grid.h; sourceTree = ""; }; + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 92E391861FE87D6000D8C362 /* AIState.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AIState.cpp; sourceTree = ""; }; + 92E391871FE87D6000D8C362 /* AIComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIComponent.h; sourceTree = ""; }; + 92E391881FE87D6000D8C362 /* AIComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AIComponent.cpp; sourceTree = ""; }; + 92E391891FE87D6000D8C362 /* AIState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AIState.h; sourceTree = ""; }; + 92E3918A1FE87D6000D8C362 /* Search.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Search.cpp; sourceTree = ""; }; + 92E46DF71B634EA30035CD21 /* Game-mac */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Game-mac"; sourceTree = BUILT_PRODUCTS_DIR; }; + 92E46E931B6353E50035CD21 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 92E46DF41B634EA30035CD21 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */, + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 92E46DEE1B634EA30035CD21 = { + isa = PBXGroup; + children = ( + 9223C4681F009428009A94D7 /* Actor.cpp */, + 9223C4691F009428009A94D7 /* Actor.h */, + 92E391881FE87D6000D8C362 /* AIComponent.cpp */, + 92E391871FE87D6000D8C362 /* AIComponent.h */, + 92E391861FE87D6000D8C362 /* AIState.cpp */, + 92E391891FE87D6000D8C362 /* AIState.h */, + 9203E9F71F0F12FE00F9FFC2 /* Bullet.cpp */, + 9203E9F81F0F12FE00F9FFC2 /* Bullet.h */, + 9223C46E1F009428009A94D7 /* Component.cpp */, + 9223C46F1F009428009A94D7 /* Component.h */, + 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */, + 9223C4931F0CA766009A94D7 /* CircleComponent.h */, + 9203E9F11F0DE24000F9FFC2 /* Enemy.cpp */, + 9203E9F21F0DE24000F9FFC2 /* Enemy.h */, + 9223C4671F009428009A94D7 /* Game.cpp */, + 9223C4701F009428009A94D7 /* Game.h */, + 9223C4961F0DBD69009A94D7 /* Grid.cpp */, + 9223C4971F0DBD69009A94D7 /* Grid.h */, + 9223C4711F009428009A94D7 /* Main.cpp */, + 9223C4721F009428009A94D7 /* Math.cpp */, + 9223C4731F009428009A94D7 /* Math.h */, + 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */, + 9223C48C1F0CA3D4009A94D7 /* MoveComponent.h */, + 9203E9F41F0DF13600F9FFC2 /* NavComponent.cpp */, + 9203E9F51F0DF13600F9FFC2 /* NavComponent.h */, + 92E3918A1FE87D6000D8C362 /* Search.cpp */, + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */, + 9223C4771F009428009A94D7 /* SpriteComponent.h */, + 9223C48D1F0CA67A009A94D7 /* Tile.cpp */, + 9223C48E1F0CA67A009A94D7 /* Tile.h */, + 9203E9EE1F0DD69900F9FFC2 /* Tower.cpp */, + 9203E9EF1F0DD69900F9FFC2 /* Tower.h */, + 92E46DF81B634EA30035CD21 /* Products */, + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */, + 92E46E931B6353E50035CD21 /* OpenGL.framework */, + ); + sourceTree = ""; + }; + 92E46DF81B634EA30035CD21 /* Products */ = { + isa = PBXGroup; + children = ( + 92E46DF71B634EA30035CD21 /* Game-mac */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 92E46DF61B634EA30035CD21 /* Game-mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */; + buildPhases = ( + 92E46DF31B634EA30035CD21 /* Sources */, + 92E46DF41B634EA30035CD21 /* Frameworks */, + 92E46EA11B63615B0035CD21 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Game-mac"; + productName = "Game-mac"; + productReference = 92E46DF71B634EA30035CD21 /* Game-mac */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 92E46DEF1B634EA30035CD21 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Sanjay Madhav"; + TargetAttributes = { + 92E46DF61B634EA30035CD21 = { + CreatedOnToolsVersion = 6.4; + }; + }; + }; + buildConfigurationList = 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter04-mac" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 92E46DEE1B634EA30035CD21; + productRefGroup = 92E46DF81B634EA30035CD21 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 92E46DF61B634EA30035CD21 /* Game-mac */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 92E46EA11B63615B0035CD21 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -d \"$BUILD_DIR/Debug\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Debug\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Debug\nfi\n\nif [ -d \"$BUILD_DIR/Release\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Release\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Release\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 92E46DF31B634EA30035CD21 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */, + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */, + 9203E9F01F0DD69900F9FFC2 /* Tower.cpp in Sources */, + 9223C4781F009428009A94D7 /* Game.cpp in Sources */, + 92E3918B1FE87D6000D8C362 /* AIState.cpp in Sources */, + 9203E9F61F0DF13600F9FFC2 /* NavComponent.cpp in Sources */, + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */, + 9223C48B1F0CA3CE009A94D7 /* MoveComponent.cpp in Sources */, + 9203E9F31F0DE24000F9FFC2 /* Enemy.cpp in Sources */, + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */, + 9203E9F91F0F12FE00F9FFC2 /* Bullet.cpp in Sources */, + 9223C4951F0CA766009A94D7 /* CircleComponent.cpp in Sources */, + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */, + 92E3918C1FE87D6000D8C362 /* AIComponent.cpp in Sources */, + 92E3918D1FE87D6000D8C362 /* Search.cpp in Sources */, + 9223C48F1F0CA67A009A94D7 /* Tile.cpp in Sources */, + 9223C4981F0DBD69009A94D7 /* Grid.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 92E46DFC1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 92E46DFD1B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 92E46DFF1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 92E46E001B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter04-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFC1B634EA40035CD21 /* Debug */, + 92E46DFD1B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFF1B634EA40035CD21 /* Debug */, + 92E46E001B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 92E46DEF1B634EA30035CD21 /* Project object */; +} diff --git a/Chapter04/Chapter04-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Chapter04/Chapter04-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..b36b9db7 --- /dev/null +++ b/Chapter04/Chapter04-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Chapter04/Chapter04-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme b/Chapter04/Chapter04-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme new file mode 100644 index 00000000..2667789e --- /dev/null +++ b/Chapter04/Chapter04-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter04/Chapter04-windows.sln b/Chapter04/Chapter04-windows.sln new file mode 100644 index 00000000..f560df0f --- /dev/null +++ b/Chapter04/Chapter04-windows.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "Game.vcxproj", "{BC508D87-495F-4554-932D-DD68388B63CC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.Build.0 = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.ActiveCfg = Release|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Chapter04/CircleComponent.cpp b/Chapter04/CircleComponent.cpp new file mode 100644 index 00000000..d41aab2c --- /dev/null +++ b/Chapter04/CircleComponent.cpp @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "CircleComponent.h" +#include "Actor.h" + +CircleComponent::CircleComponent(class Actor* owner) +:Component(owner) +,mRadius(0.0f) +{ + +} + +const Vector2& CircleComponent::GetCenter() const +{ + return mOwner->GetPosition(); +} + +float CircleComponent::GetRadius() const +{ + return mOwner->GetScale() * mRadius; +} + +bool Intersect(const CircleComponent& a, const CircleComponent& b) +{ + // Calculate distance squared + Vector2 diff = a.GetCenter() - b.GetCenter(); + float distSq = diff.LengthSq(); + + // Calculate sum of radii squared + float radiiSq = a.GetRadius() + b.GetRadius(); + radiiSq *= radiiSq; + + return distSq <= radiiSq; +} diff --git a/Chapter04/CircleComponent.h b/Chapter04/CircleComponent.h new file mode 100644 index 00000000..4eecb2a4 --- /dev/null +++ b/Chapter04/CircleComponent.h @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "Math.h" + +class CircleComponent : public Component +{ +public: + CircleComponent(class Actor* owner); + + void SetRadius(float radius) { mRadius = radius; } + float GetRadius() const; + + const Vector2& GetCenter() const; +private: + float mRadius; +}; + +bool Intersect(const CircleComponent& a, const CircleComponent& b); diff --git a/Chapter04/Component.cpp b/Chapter04/Component.cpp new file mode 100644 index 00000000..c4ed432d --- /dev/null +++ b/Chapter04/Component.cpp @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Component.h" +#include "Actor.h" + +Component::Component(Actor* owner, int updateOrder) + :mOwner(owner) + ,mUpdateOrder(updateOrder) +{ + // Add to actor's vector of components + mOwner->AddComponent(this); +} + +Component::~Component() +{ + mOwner->RemoveComponent(this); +} + +void Component::Update(float deltaTime) +{ +} diff --git a/Chapter04/Component.h b/Chapter04/Component.h new file mode 100644 index 00000000..fb41dd75 --- /dev/null +++ b/Chapter04/Component.h @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include + +class Component +{ +public: + // Constructor + // (the lower the update order, the earlier the component updates) + Component(class Actor* owner, int updateOrder = 100); + // Destructor + virtual ~Component(); + // Update this component by delta time + virtual void Update(float deltaTime); + // Process input for this component + virtual void ProcessInput(const uint8_t* keyState) {} + + int GetUpdateOrder() const { return mUpdateOrder; } +protected: + // Owning actor + class Actor* mOwner; + // Update order of component + int mUpdateOrder; +}; diff --git a/Chapter04/Enemy.cpp b/Chapter04/Enemy.cpp new file mode 100644 index 00000000..ce3d5951 --- /dev/null +++ b/Chapter04/Enemy.cpp @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Enemy.h" +#include "Game.h" +#include "SpriteComponent.h" +#include "NavComponent.h" +#include "Grid.h" +#include "Tile.h" +#include "CircleComponent.h" +#include + +Enemy::Enemy(class Game* game) +:Actor(game) +{ + // Add to enemy vector + game->GetEnemies().emplace_back(this); + + SpriteComponent* sc = new SpriteComponent(this); + sc->SetTexture(game->GetTexture("Assets/Airplane.png")); + // Set position at start tile + SetPosition(GetGame()->GetGrid()->GetStartTile()->GetPosition()); + // Setup a nav component at the start tile + NavComponent* nc = new NavComponent(this); + nc->SetForwardSpeed(150.0f); + nc->StartPath(GetGame()->GetGrid()->GetStartTile()); + // Setup a circle for collision + mCircle = new CircleComponent(this); + mCircle->SetRadius(25.0f); +} + +Enemy::~Enemy() +{ + // Remove from enemy vector + auto iter = std::find(GetGame()->GetEnemies().begin(), + GetGame()->GetEnemies().end(), + this); + GetGame()->GetEnemies().erase(iter); +} + +void Enemy::UpdateActor(float deltaTime) +{ + Actor::UpdateActor(deltaTime); + + // Am I near the end tile? + Vector2 diff = GetPosition() - GetGame()->GetGrid()->GetEndTile()->GetPosition(); + if (Math::NearZero(diff.Length(), 10.0f)) + { + SetState(EDead); + } +} diff --git a/Chapter04/Enemy.h b/Chapter04/Enemy.h new file mode 100644 index 00000000..72e33f08 --- /dev/null +++ b/Chapter04/Enemy.h @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" + +class Enemy : public Actor +{ +public: + Enemy(class Game* game); + ~Enemy(); + void UpdateActor(float deltaTime) override; + class CircleComponent* GetCircle() { return mCircle; } +private: + class CircleComponent* mCircle; +}; diff --git a/Chapter04/Game.cpp b/Chapter04/Game.cpp new file mode 100644 index 00000000..c044257c --- /dev/null +++ b/Chapter04/Game.cpp @@ -0,0 +1,329 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" +#include "SDL/SDL_image.h" +#include +#include "Actor.h" +#include "SpriteComponent.h" +#include "Grid.h" +#include "Enemy.h" +#include "AIComponent.h" +#include "AIState.h" + +Game::Game() +:mWindow(nullptr) +,mRenderer(nullptr) +,mIsRunning(true) +,mUpdatingActors(false) +{ + +} + +bool Game::Initialize() +{ + if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) + { + SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); + return false; + } + + mWindow = SDL_CreateWindow("Game Programming in C++ (Chapter 4)", 100, 100, 1024, 768, 0); + if (!mWindow) + { + SDL_Log("Failed to create window: %s", SDL_GetError()); + return false; + } + + mRenderer = SDL_CreateRenderer(mWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!mRenderer) + { + SDL_Log("Failed to create renderer: %s", SDL_GetError()); + return false; + } + + if (IMG_Init(IMG_INIT_PNG) == 0) + { + SDL_Log("Unable to initialize SDL_image: %s", SDL_GetError()); + return false; + } + + LoadData(); + + mTicksCount = SDL_GetTicks(); + + return true; +} + +void Game::RunLoop() +{ + while (mIsRunning) + { + ProcessInput(); + UpdateGame(); + GenerateOutput(); + } +} + +void Game::ProcessInput() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + mIsRunning = false; + break; + } + } + + const Uint8* keyState = SDL_GetKeyboardState(NULL); + if (keyState[SDL_SCANCODE_ESCAPE]) + { + mIsRunning = false; + } + + if (keyState[SDL_SCANCODE_B]) + { + mGrid->BuildTower(); + } + + // Process mouse + int x, y; + Uint32 buttons = SDL_GetMouseState(&x, &y); + if (SDL_BUTTON(buttons) & SDL_BUTTON_LEFT) + { + mGrid->ProcessClick(x, y); + } + + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->ProcessInput(keyState); + } + mUpdatingActors = false; +} + +void Game::UpdateGame() +{ + // Compute delta time + // Wait until 16ms has elapsed since last frame + while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16)) + ; + + float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f; + if (deltaTime > 0.05f) + { + deltaTime = 0.05f; + } + mTicksCount = SDL_GetTicks(); + + // Update all actors + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->Update(deltaTime); + } + mUpdatingActors = false; + + // Move any pending actors to mActors + for (auto pending : mPendingActors) + { + mActors.emplace_back(pending); + } + mPendingActors.clear(); + + // Add any dead actors to a temp vector + std::vector deadActors; + for (auto actor : mActors) + { + if (actor->GetState() == Actor::EDead) + { + deadActors.emplace_back(actor); + } + } + + // Delete dead actors (which removes them from mActors) + for (auto actor : deadActors) + { + delete actor; + } +} + +void Game::GenerateOutput() +{ + SDL_SetRenderDrawColor(mRenderer, 34, 139, 34, 255); + SDL_RenderClear(mRenderer); + + // Draw all sprite components + for (auto sprite : mSprites) + { + sprite->Draw(mRenderer); + } + + SDL_RenderPresent(mRenderer); +} + +void Game::LoadData() +{ + mGrid = new Grid(this); + + // For testing AIComponent + //Actor* a = new Actor(this); + //AIComponent* aic = new AIComponent(a); + //// Register states with AIComponent + //aic->RegisterState(new AIPatrol(aic)); + //aic->RegisterState(new AIDeath(aic)); + //aic->RegisterState(new AIAttack(aic)); + //// Start in patrol state + //aic->ChangeState("Patrol"); +} + +void Game::UnloadData() +{ + // Delete actors + // Because ~Actor calls RemoveActor, have to use a different style loop + while (!mActors.empty()) + { + delete mActors.back(); + } + + // Destroy textures + for (auto i : mTextures) + { + SDL_DestroyTexture(i.second); + } + mTextures.clear(); +} + +SDL_Texture* Game::GetTexture(const std::string& fileName) +{ + SDL_Texture* tex = nullptr; + // Is the texture already in the map? + auto iter = mTextures.find(fileName); + if (iter != mTextures.end()) + { + tex = iter->second; + } + else + { + // Load from file + SDL_Surface* surf = IMG_Load(fileName.c_str()); + if (!surf) + { + SDL_Log("Failed to load texture file %s", fileName.c_str()); + return nullptr; + } + + // Create texture from surface + tex = SDL_CreateTextureFromSurface(mRenderer, surf); + SDL_FreeSurface(surf); + if (!tex) + { + SDL_Log("Failed to convert surface to texture for %s", fileName.c_str()); + return nullptr; + } + + mTextures.emplace(fileName.c_str(), tex); + } + return tex; +} + +void Game::Shutdown() +{ + UnloadData(); + IMG_Quit(); + SDL_DestroyRenderer(mRenderer); + SDL_DestroyWindow(mWindow); + SDL_Quit(); +} + +void Game::AddActor(Actor* actor) +{ + // If we're updating actors, need to add to pending + if (mUpdatingActors) + { + mPendingActors.emplace_back(actor); + } + else + { + mActors.emplace_back(actor); + } +} + +void Game::RemoveActor(Actor* actor) +{ + // Is it in pending actors? + auto iter = std::find(mPendingActors.begin(), mPendingActors.end(), actor); + if (iter != mPendingActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mPendingActors.end() - 1); + mPendingActors.pop_back(); + } + + // Is it in actors? + iter = std::find(mActors.begin(), mActors.end(), actor); + if (iter != mActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mActors.end() - 1); + mActors.pop_back(); + } +} + +void Game::AddSprite(SpriteComponent* sprite) +{ + // Find the insertion point in the sorted vector + // (The first element with a higher draw order than me) + int myDrawOrder = sprite->GetDrawOrder(); + auto iter = mSprites.begin(); + for ( ; + iter != mSprites.end(); + ++iter) + { + if (myDrawOrder < (*iter)->GetDrawOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mSprites.insert(iter, sprite); +} + +void Game::RemoveSprite(SpriteComponent* sprite) +{ + // (We can't swap because it ruins ordering) + auto iter = std::find(mSprites.begin(), mSprites.end(), sprite); + mSprites.erase(iter); +} + +Enemy* Game::GetNearestEnemy(const Vector2& pos) +{ + Enemy* best = nullptr; + + if (mEnemies.size() > 0) + { + best = mEnemies[0]; + // Save the distance squared of first enemy, and test if others are closer + float bestDistSq = (pos - mEnemies[0]->GetPosition()).LengthSq(); + for (size_t i = 1; i < mEnemies.size(); i++) + { + float newDistSq = (pos - mEnemies[i]->GetPosition()).LengthSq(); + if (newDistSq < bestDistSq) + { + bestDistSq = newDistSq; + best = mEnemies[i]; + } + } + } + + return best; +} diff --git a/Chapter04/Game.h b/Chapter04/Game.h new file mode 100644 index 00000000..56a0ecb4 --- /dev/null +++ b/Chapter04/Game.h @@ -0,0 +1,64 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "SDL/SDL.h" +#include +#include +#include +#include "Math.h" + +class Game +{ +public: + Game(); + bool Initialize(); + void RunLoop(); + void Shutdown(); + + void AddActor(class Actor* actor); + void RemoveActor(class Actor* actor); + + void AddSprite(class SpriteComponent* sprite); + void RemoveSprite(class SpriteComponent* sprite); + + SDL_Texture* GetTexture(const std::string& fileName); + + class Grid* GetGrid() { return mGrid; } + std::vector& GetEnemies() { return mEnemies; } + class Enemy* GetNearestEnemy(const Vector2& pos); +private: + void ProcessInput(); + void UpdateGame(); + void GenerateOutput(); + void LoadData(); + void UnloadData(); + + // Map of textures loaded + std::unordered_map mTextures; + + // All the actors in the game + std::vector mActors; + // Any pending actors + std::vector mPendingActors; + + // All the sprite components drawn + std::vector mSprites; + + SDL_Window* mWindow; + SDL_Renderer* mRenderer; + Uint32 mTicksCount; + bool mIsRunning; + // Track if we're updating actors right now + bool mUpdatingActors; + + // Game-specific + std::vector mEnemies; + class Grid* mGrid; + float mNextEnemy; +}; diff --git a/Chapter04/Game.vcxproj b/Chapter04/Game.vcxproj new file mode 100644 index 00000000..23567660 --- /dev/null +++ b/Chapter04/Game.vcxproj @@ -0,0 +1,139 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {BC508D87-495F-4554-932D-DD68388B63CC} + Win32Proj + Game + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + /NODEFAULTLIB:msvcrt.lib %(AdditionalOptions) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + true + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + + \ No newline at end of file diff --git a/Chapter04/Game.vcxproj.filters b/Chapter04/Game.vcxproj.filters new file mode 100644 index 00000000..964c2b83 --- /dev/null +++ b/Chapter04/Game.vcxproj.filters @@ -0,0 +1,109 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Chapter04/Grid.cpp b/Chapter04/Grid.cpp new file mode 100644 index 00000000..b81a7265 --- /dev/null +++ b/Chapter04/Grid.cpp @@ -0,0 +1,246 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Grid.h" +#include "Tile.h" +#include "Tower.h" +#include "Enemy.h" +#include + +Grid::Grid(class Game* game) +:Actor(game) +,mSelectedTile(nullptr) +{ + // 7 rows, 16 columns + mTiles.resize(NumRows); + for (size_t i = 0; i < mTiles.size(); i++) + { + mTiles[i].resize(NumCols); + } + + // Create tiles + for (size_t i = 0; i < NumRows; i++) + { + for (size_t j = 0; j < NumCols; j++) + { + mTiles[i][j] = new Tile(GetGame()); + mTiles[i][j]->SetPosition(Vector2(TileSize/2.0f + j * TileSize, StartY + i * TileSize)); + } + } + + // Set start/end tiles + GetStartTile()->SetTileState(Tile::EStart); + GetEndTile()->SetTileState(Tile::EBase); + + // Set up adjacency lists + for (size_t i = 0; i < NumRows; i++) + { + for (size_t j = 0; j < NumCols; j++) + { + if (i > 0) + { + mTiles[i][j]->mAdjacent.push_back(mTiles[i-1][j]); + } + if (i < NumRows - 1) + { + mTiles[i][j]->mAdjacent.push_back(mTiles[i+1][j]); + } + if (j > 0) + { + mTiles[i][j]->mAdjacent.push_back(mTiles[i][j-1]); + } + if (j < NumCols - 1) + { + mTiles[i][j]->mAdjacent.push_back(mTiles[i][j+1]); + } + } + } + + // Find path (in reverse) + FindPath(GetEndTile(), GetStartTile()); + UpdatePathTiles(GetStartTile()); + + mNextEnemy = EnemyTime; +} + +void Grid::SelectTile(size_t row, size_t col) +{ + // Make sure it's a valid selection + Tile::TileState tstate = mTiles[row][col]->GetTileState(); + if (tstate != Tile::EStart && tstate != Tile::EBase) + { + // Deselect previous one + if (mSelectedTile) + { + mSelectedTile->ToggleSelect(); + } + mSelectedTile = mTiles[row][col]; + mSelectedTile->ToggleSelect(); + } +} + +void Grid::ProcessClick(int x, int y) +{ + y -= static_cast(StartY - TileSize / 2); + if (y >= 0) + { + x /= static_cast(TileSize); + y /= static_cast(TileSize); + if (x >= 0 && x < static_cast(NumCols) && y >= 0 && y < static_cast(NumRows)) + { + SelectTile(y, x); + } + } +} + +// Implements A* pathfinding +bool Grid::FindPath(Tile* start, Tile* goal) +{ + for (size_t i = 0; i < NumRows; i++) + { + for (size_t j = 0; j < NumCols; j++) + { + mTiles[i][j]->g = 0.0f; + mTiles[i][j]->mInOpenSet = false; + mTiles[i][j]->mInClosedSet = false; + } + } + + std::vector openSet; + + // Set current node to start, and add to closed set + Tile* current = start; + current->mInClosedSet = true; + + do + { + // Add adjacent nodes to open set + for (Tile* neighbor : current->mAdjacent) + { + if (neighbor->mBlocked) + { + continue; + } + + // Only check nodes that aren't in the closed set + if (!neighbor->mInClosedSet) + { + if (!neighbor->mInOpenSet) + { + // Not in the open set, so set parent + neighbor->mParent = current; + neighbor->h = (neighbor->GetPosition() - goal->GetPosition()).Length(); + // g(x) is the parent's g plus cost of traversing edge + neighbor->g = current->g + TileSize; + neighbor->f = neighbor->g + neighbor->h; + openSet.emplace_back(neighbor); + neighbor->mInOpenSet = true; + } + else + { + // Compute g(x) cost if current becomes the parent + float newG = current->g + TileSize; + if (newG < neighbor->g) + { + // Adopt this node + neighbor->mParent = current; + neighbor->g = newG; + // f(x) changes because g(x) changes + neighbor->f = neighbor->g + neighbor->h; + } + } + } + } + + // If open set is empty, all possible paths are exhausted + if (openSet.empty()) + { + break; + } + + // Find lowest cost node in open set + auto iter = std::min_element(openSet.begin(), openSet.end(), + [](Tile* a, Tile* b) { + return a->f < b->f; + }); + // Set to current and move from open to closed + current = *iter; + openSet.erase(iter); + current->mInOpenSet = false; + current->mInClosedSet = true; + } + while (current != goal); + + // Did we find a path? + return (current == goal) ? true : false; +} + +void Grid::UpdatePathTiles(class Tile* start) +{ + // Reset all tiles to normal (except for start/end) + for (size_t i = 0; i < NumRows; i++) + { + for (size_t j = 0; j < NumCols; j++) + { + if (!(i == 3 && j == 0) && !(i == 3 && j == 15)) + { + mTiles[i][j]->SetTileState(Tile::EDefault); + } + } + } + + Tile* t = start->mParent; + while (t != GetEndTile()) + { + t->SetTileState(Tile::EPath); + t = t->mParent; + } +} + +void Grid::BuildTower() +{ + if (mSelectedTile && !mSelectedTile->mBlocked) + { + mSelectedTile->mBlocked = true; + if (FindPath(GetEndTile(), GetStartTile())) + { + Tower* t = new Tower(GetGame()); + t->SetPosition(mSelectedTile->GetPosition()); + } + else + { + // This tower would block the path, so don't allow build + mSelectedTile->mBlocked = false; + FindPath(GetEndTile(), GetStartTile()); + } + UpdatePathTiles(GetStartTile()); + } +} + +Tile* Grid::GetStartTile() +{ + return mTiles[3][0]; +} + +Tile* Grid::GetEndTile() +{ + return mTiles[3][15]; +} + +void Grid::UpdateActor(float deltaTime) +{ + Actor::UpdateActor(deltaTime); + + // Is it time to spawn a new enemy? + mNextEnemy -= deltaTime; + if (mNextEnemy <= 0.0f) + { + new Enemy(GetGame()); + mNextEnemy += EnemyTime; + } +} diff --git a/Chapter04/Grid.h b/Chapter04/Grid.h new file mode 100644 index 00000000..a69ac893 --- /dev/null +++ b/Chapter04/Grid.h @@ -0,0 +1,57 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +#include + +class Grid : public Actor +{ +public: + Grid(class Game* game); + + // Handle a mouse click at the x/y screen locations + void ProcessClick(int x, int y); + + // Use A* to find a path + bool FindPath(class Tile* start, class Tile* goal); + + // Try to build a tower + void BuildTower(); + + // Get start/end tile + class Tile* GetStartTile(); + class Tile* GetEndTile(); + + void UpdateActor(float deltaTime) override; +private: + // Select a specific tile + void SelectTile(size_t row, size_t col); + + // Update textures for tiles on path + void UpdatePathTiles(class Tile* start); + + // Currently selected tile + class Tile* mSelectedTile; + + // 2D vector of tiles in grid + std::vector> mTiles; + + // Time until next enemy + float mNextEnemy; + + // Rows/columns in grid + const size_t NumRows = 7; + const size_t NumCols = 16; + // Start y position of top left corner + const float StartY = 192.0f; + // Width/height of each tile + const float TileSize = 64.0f; + // Time between enemies + const float EnemyTime = 1.5f; +}; diff --git a/Chapter04/Main.cpp b/Chapter04/Main.cpp new file mode 100644 index 00000000..22ea0c69 --- /dev/null +++ b/Chapter04/Main.cpp @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" + +int main(int argc, char** argv) +{ + Game game; + bool success = game.Initialize(); + if (success) + { + game.RunLoop(); + } + game.Shutdown(); + return 0; +} diff --git a/Chapter04/Math.cpp b/Chapter04/Math.cpp new file mode 100644 index 00000000..a16e7261 --- /dev/null +++ b/Chapter04/Math.cpp @@ -0,0 +1,239 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Math.h" + +const Vector2 Vector2::Zero(0.0f, 0.0f); +const Vector2 Vector2::UnitX(1.0f, 0.0f); +const Vector2 Vector2::UnitY(0.0f, 1.0f); +const Vector2 Vector2::NegUnitX(-1.0f, 0.0f); +const Vector2 Vector2::NegUnitY(0.0f, -1.0f); + +const Vector3 Vector3::Zero(0.0f, 0.0f, 0.f); +const Vector3 Vector3::UnitX(1.0f, 0.0f, 0.0f); +const Vector3 Vector3::UnitY(0.0f, 1.0f, 0.0f); +const Vector3 Vector3::UnitZ(0.0f, 0.0f, 1.0f); +const Vector3 Vector3::NegUnitX(-1.0f, 0.0f, 0.0f); +const Vector3 Vector3::NegUnitY(0.0f, -1.0f, 0.0f); +const Vector3 Vector3::NegUnitZ(0.0f, 0.0f, -1.0f); +const Vector3 Vector3::Infinity(Math::Infinity, Math::Infinity, Math::Infinity); +const Vector3 Vector3::NegInfinity(Math::NegInfinity, Math::NegInfinity, Math::NegInfinity); + +static float m3Ident[3][3] = +{ + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f } +}; +const Matrix3 Matrix3::Identity(m3Ident); + +static float m4Ident[4][4] = +{ + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; + +const Matrix4 Matrix4::Identity(m4Ident); + +const Quaternion Quaternion::Identity(0.0f, 0.0f, 0.0f, 1.0f); + +Vector2 Vector2::Transform(const Vector2& vec, const Matrix3& mat, float w /*= 1.0f*/) +{ + Vector2 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + w * mat.mat[2][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + w * mat.mat[2][1]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +Vector3 Vector3::Transform(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +// This will transform the vector and renormalize the w component +Vector3 Vector3::TransformWithPerspDiv(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + float transformedW = vec.x * mat.mat[0][3] + vec.y * mat.mat[1][3] + + vec.z * mat.mat[2][3] + w * mat.mat[3][3]; + if (!Math::NearZero(Math::Abs(transformedW))) + { + transformedW = 1.0f / transformedW; + retVal *= transformedW; + } + return retVal; +} + +// Transform a Vector3 by a quaternion +Vector3 Vector3::Transform(const Vector3& v, const Quaternion& q) +{ + // v + 2.0*cross(q.xyz, cross(q.xyz,v) + q.w*v); + Vector3 qv(q.x, q.y, q.z); + Vector3 retVal = v; + retVal += 2.0f * Vector3::Cross(qv, Vector3::Cross(qv, v) + q.w * v); + return retVal; +} + +void Matrix4::Invert() +{ + // Thanks slow math + // This is a really janky way to unroll everything... + float tmp[12]; + float src[16]; + float dst[16]; + float det; + + // Transpose matrix + // row 1 to col 1 + src[0] = mat[0][0]; + src[4] = mat[0][1]; + src[8] = mat[0][2]; + src[12] = mat[0][3]; + + // row 2 to col 2 + src[1] = mat[1][0]; + src[5] = mat[1][1]; + src[9] = mat[1][2]; + src[13] = mat[1][3]; + + // row 3 to col 3 + src[2] = mat[2][0]; + src[6] = mat[2][1]; + src[10] = mat[2][2]; + src[14] = mat[2][3]; + + // row 4 to col 4 + src[3] = mat[3][0]; + src[7] = mat[3][1]; + src[11] = mat[3][2]; + src[15] = mat[3][3]; + + // Calculate cofactors + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + dst[0] = tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]; + dst[0] -= tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]; + dst[1] = tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]; + dst[1] -= tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]; + dst[2] = tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]; + dst[2] -= tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]; + dst[3] = tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]; + dst[3] -= tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]; + dst[4] = tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]; + dst[4] -= tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]; + dst[5] = tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]; + dst[5] -= tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]; + dst[6] = tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]; + dst[6] -= tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]; + dst[7] = tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]; + dst[7] -= tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]; + + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + dst[8] = tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]; + dst[8] -= tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]; + dst[9] = tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]; + dst[9] -= tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]; + dst[10] = tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]; + dst[10] -= tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]; + dst[11] = tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]; + dst[11] -= tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]; + dst[12] = tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]; + dst[12] -= tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]; + dst[13] = tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]; + dst[13] -= tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]; + dst[14] = tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]; + dst[14] -= tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]; + dst[15] = tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]; + dst[15] -= tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]; + + // Calculate determinant + det = src[0] * dst[0] + src[1] * dst[1] + src[2] * dst[2] + src[3] * dst[3]; + + // Inverse of matrix is divided by determinant + det = 1 / det; + for (int j = 0; j < 16; j++) + { + dst[j] *= det; + } + + // Set it back + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + mat[i][j] = dst[i * 4 + j]; + } + } +} + +Matrix4 Matrix4::CreateFromQuaternion(const class Quaternion& q) +{ + float mat[4][4]; + + mat[0][0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z; + mat[0][1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z; + mat[0][2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y; + mat[0][3] = 0.0f; + + mat[1][0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z; + mat[1][1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z; + mat[1][2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x; + mat[1][3] = 0.0f; + + mat[2][0] = 2.0f * q.x * q.z + 2.0f * q.w * q.y; + mat[2][1] = 2.0f * q.y * q.z - 2.0f * q.w * q.x; + mat[2][2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y; + mat[2][3] = 0.0f; + + mat[3][0] = 0.0f; + mat[3][1] = 0.0f; + mat[3][2] = 0.0f; + mat[3][3] = 1.0f; + + return Matrix4(mat); +} diff --git a/Chapter04/Math.h b/Chapter04/Math.h new file mode 100644 index 00000000..752963f1 --- /dev/null +++ b/Chapter04/Math.h @@ -0,0 +1,1033 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +namespace Math +{ + const float Pi = 3.1415926535f; + const float TwoPi = Pi * 2.0f; + const float PiOver2 = Pi / 2.0f; + const float Infinity = std::numeric_limits::infinity(); + const float NegInfinity = -std::numeric_limits::infinity(); + + inline float ToRadians(float degrees) + { + return degrees * Pi / 180.0f; + } + + inline float ToDegrees(float radians) + { + return radians * 180.0f / Pi; + } + + inline bool NearZero(float val, float epsilon = 0.001f) + { + if (fabs(val) <= epsilon) + { + return true; + } + else + { + return false; + } + } + + template + T Max(const T& a, const T& b) + { + return (a < b ? b : a); + } + + template + T Min(const T& a, const T& b) + { + return (a < b ? a : b); + } + + template + T Clamp(const T& value, const T& lower, const T& upper) + { + return Min(upper, Max(lower, value)); + } + + inline float Abs(float value) + { + return fabs(value); + } + + inline float Cos(float angle) + { + return cosf(angle); + } + + inline float Sin(float angle) + { + return sinf(angle); + } + + inline float Tan(float angle) + { + return tanf(angle); + } + + inline float Acos(float value) + { + return acosf(value); + } + + inline float Atan2(float y, float x) + { + return atan2f(y, x); + } + + inline float Cot(float angle) + { + return 1.0f / Tan(angle); + } + + inline float Lerp(float a, float b, float f) + { + return a + f * (b - a); + } + + inline float Sqrt(float value) + { + return sqrtf(value); + } + + inline float Fmod(float numer, float denom) + { + return fmod(numer, denom); + } +} + +// 2D Vector +class Vector2 +{ +public: + float x; + float y; + + Vector2() + :x(0.0f) + ,y(0.0f) + {} + + explicit Vector2(float inX, float inY) + :x(inX) + ,y(inY) + {} + + // Set both components in one line + void Set(float inX, float inY) + { + x = inX; + y = inY; + } + + // Vector addition (a + b) + friend Vector2 operator+(const Vector2& a, const Vector2& b) + { + return Vector2(a.x + b.x, a.y + b.y); + } + + // Vector subtraction (a - b) + friend Vector2 operator-(const Vector2& a, const Vector2& b) + { + return Vector2(a.x - b.x, a.y - b.y); + } + + // Component-wise multiplication + // (a.x * b.x, ...) + friend Vector2 operator*(const Vector2& a, const Vector2& b) + { + return Vector2(a.x * b.x, a.y * b.y); + } + + // Scalar multiplication + friend Vector2 operator*(const Vector2& vec, float scalar) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar multiplication + friend Vector2 operator*(float scalar, const Vector2& vec) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar *= + Vector2& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + return *this; + } + + // Vector += + Vector2& operator+=(const Vector2& right) + { + x += right.x; + y += right.y; + return *this; + } + + // Vector -= + Vector2& operator-=(const Vector2& right) + { + x -= right.x; + y -= right.y; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + } + + // Normalize the provided vector + static Vector2 Normalize(const Vector2& vec) + { + Vector2 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector2& a, const Vector2& b) + { + return (a.x * b.x + a.y * b.y); + } + + // Lerp from A to B by f + static Vector2 Lerp(const Vector2& a, const Vector2& b, float f) + { + return Vector2(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector2 Reflect(const Vector2& v, const Vector2& n) + { + return v - 2.0f * Vector2::Dot(v, n) * n; + } + + // Transform vector by matrix + static Vector2 Transform(const Vector2& vec, const class Matrix3& mat, float w = 1.0f); + + static const Vector2 Zero; + static const Vector2 UnitX; + static const Vector2 UnitY; + static const Vector2 NegUnitX; + static const Vector2 NegUnitY; +}; + +// 3D Vector +class Vector3 +{ +public: + float x; + float y; + float z; + + Vector3() + :x(0.0f) + ,y(0.0f) + ,z(0.0f) + {} + + explicit Vector3(float inX, float inY, float inZ) + :x(inX) + ,y(inY) + ,z(inZ) + {} + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&x); + } + + // Set all three components in one line + void Set(float inX, float inY, float inZ) + { + x = inX; + y = inY; + z = inZ; + } + + // Vector addition (a + b) + friend Vector3 operator+(const Vector3& a, const Vector3& b) + { + return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); + } + + // Vector subtraction (a - b) + friend Vector3 operator-(const Vector3& a, const Vector3& b) + { + return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); + } + + // Component-wise multiplication + friend Vector3 operator*(const Vector3& left, const Vector3& right) + { + return Vector3(left.x * right.x, left.y * right.y, left.z * right.z); + } + + // Scalar multiplication + friend Vector3 operator*(const Vector3& vec, float scalar) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar multiplication + friend Vector3 operator*(float scalar, const Vector3& vec) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar *= + Vector3& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + z *= scalar; + return *this; + } + + // Vector += + Vector3& operator+=(const Vector3& right) + { + x += right.x; + y += right.y; + z += right.z; + return *this; + } + + // Vector -= + Vector3& operator-=(const Vector3& right) + { + x -= right.x; + y -= right.y; + z -= right.z; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y + z*z); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + } + + // Normalize the provided vector + static Vector3 Normalize(const Vector3& vec) + { + Vector3 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector3& a, const Vector3& b) + { + return (a.x * b.x + a.y * b.y + a.z * b.z); + } + + // Cross product between two vectors (a cross b) + static Vector3 Cross(const Vector3& a, const Vector3& b) + { + Vector3 temp; + temp.x = a.y * b.z - a.z * b.y; + temp.y = a.z * b.x - a.x * b.z; + temp.z = a.x * b.y - a.y * b.x; + return temp; + } + + // Lerp from A to B by f + static Vector3 Lerp(const Vector3& a, const Vector3& b, float f) + { + return Vector3(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector3 Reflect(const Vector3& v, const Vector3& n) + { + return v - 2.0f * Vector3::Dot(v, n) * n; + } + + static Vector3 Transform(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + // This will transform the vector and renormalize the w component + static Vector3 TransformWithPerspDiv(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + + // Transform a Vector3 by a quaternion + static Vector3 Transform(const Vector3& v, const class Quaternion& q); + + static const Vector3 Zero; + static const Vector3 UnitX; + static const Vector3 UnitY; + static const Vector3 UnitZ; + static const Vector3 NegUnitX; + static const Vector3 NegUnitY; + static const Vector3 NegUnitZ; + static const Vector3 Infinity; + static const Vector3 NegInfinity; +}; + +// 3x3 Matrix +class Matrix3 +{ +public: + float mat[3][3]; + + Matrix3() + { + *this = Matrix3::Identity; + } + + explicit Matrix3(float inMat[3][3]) + { + memcpy(mat, inMat, 9 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication + friend Matrix3 operator*(const Matrix3& left, const Matrix3& right) + { + Matrix3 retVal; + // row 0 + retVal.mat[0][0] = + left.mat[0][0] * right.mat[0][0] + + left.mat[0][1] * right.mat[1][0] + + left.mat[0][2] * right.mat[2][0]; + + retVal.mat[0][1] = + left.mat[0][0] * right.mat[0][1] + + left.mat[0][1] * right.mat[1][1] + + left.mat[0][2] * right.mat[2][1]; + + retVal.mat[0][2] = + left.mat[0][0] * right.mat[0][2] + + left.mat[0][1] * right.mat[1][2] + + left.mat[0][2] * right.mat[2][2]; + + // row 1 + retVal.mat[1][0] = + left.mat[1][0] * right.mat[0][0] + + left.mat[1][1] * right.mat[1][0] + + left.mat[1][2] * right.mat[2][0]; + + retVal.mat[1][1] = + left.mat[1][0] * right.mat[0][1] + + left.mat[1][1] * right.mat[1][1] + + left.mat[1][2] * right.mat[2][1]; + + retVal.mat[1][2] = + left.mat[1][0] * right.mat[0][2] + + left.mat[1][1] * right.mat[1][2] + + left.mat[1][2] * right.mat[2][2]; + + // row 2 + retVal.mat[2][0] = + left.mat[2][0] * right.mat[0][0] + + left.mat[2][1] * right.mat[1][0] + + left.mat[2][2] * right.mat[2][0]; + + retVal.mat[2][1] = + left.mat[2][0] * right.mat[0][1] + + left.mat[2][1] * right.mat[1][1] + + left.mat[2][2] * right.mat[2][1]; + + retVal.mat[2][2] = + left.mat[2][0] * right.mat[0][2] + + left.mat[2][1] * right.mat[1][2] + + left.mat[2][2] * right.mat[2][2]; + + return retVal; + } + + Matrix3& operator*=(const Matrix3& right) + { + *this = *this * right; + return *this; + } + + // Create a scale matrix with x and y scales + static Matrix3 CreateScale(float xScale, float yScale) + { + float temp[3][3] = + { + { xScale, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + static Matrix3 CreateScale(const Vector2& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y); + } + + // Create a scale matrix with a uniform factor + static Matrix3 CreateScale(float scale) + { + return CreateScale(scale, scale); + } + + // Create a rotation matrix about the Z axis + // theta is in radians + static Matrix3 CreateRotation(float theta) + { + float temp[3][3] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + // Create a translation matrix (on the xy-plane) + static Matrix3 CreateTranslation(const Vector2& trans) + { + float temp[3][3] = + { + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, 1.0f }, + }; + return Matrix3(temp); + } + + static const Matrix3 Identity; +}; + +// 4x4 Matrix +class Matrix4 +{ +public: + float mat[4][4]; + + Matrix4() + { + *this = Matrix4::Identity; + } + + explicit Matrix4(float inMat[4][4]) + { + memcpy(mat, inMat, 16 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication (a * b) + friend Matrix4 operator*(const Matrix4& a, const Matrix4& b) + { + Matrix4 retVal; + // row 0 + retVal.mat[0][0] = + a.mat[0][0] * b.mat[0][0] + + a.mat[0][1] * b.mat[1][0] + + a.mat[0][2] * b.mat[2][0] + + a.mat[0][3] * b.mat[3][0]; + + retVal.mat[0][1] = + a.mat[0][0] * b.mat[0][1] + + a.mat[0][1] * b.mat[1][1] + + a.mat[0][2] * b.mat[2][1] + + a.mat[0][3] * b.mat[3][1]; + + retVal.mat[0][2] = + a.mat[0][0] * b.mat[0][2] + + a.mat[0][1] * b.mat[1][2] + + a.mat[0][2] * b.mat[2][2] + + a.mat[0][3] * b.mat[3][2]; + + retVal.mat[0][3] = + a.mat[0][0] * b.mat[0][3] + + a.mat[0][1] * b.mat[1][3] + + a.mat[0][2] * b.mat[2][3] + + a.mat[0][3] * b.mat[3][3]; + + // row 1 + retVal.mat[1][0] = + a.mat[1][0] * b.mat[0][0] + + a.mat[1][1] * b.mat[1][0] + + a.mat[1][2] * b.mat[2][0] + + a.mat[1][3] * b.mat[3][0]; + + retVal.mat[1][1] = + a.mat[1][0] * b.mat[0][1] + + a.mat[1][1] * b.mat[1][1] + + a.mat[1][2] * b.mat[2][1] + + a.mat[1][3] * b.mat[3][1]; + + retVal.mat[1][2] = + a.mat[1][0] * b.mat[0][2] + + a.mat[1][1] * b.mat[1][2] + + a.mat[1][2] * b.mat[2][2] + + a.mat[1][3] * b.mat[3][2]; + + retVal.mat[1][3] = + a.mat[1][0] * b.mat[0][3] + + a.mat[1][1] * b.mat[1][3] + + a.mat[1][2] * b.mat[2][3] + + a.mat[1][3] * b.mat[3][3]; + + // row 2 + retVal.mat[2][0] = + a.mat[2][0] * b.mat[0][0] + + a.mat[2][1] * b.mat[1][0] + + a.mat[2][2] * b.mat[2][0] + + a.mat[2][3] * b.mat[3][0]; + + retVal.mat[2][1] = + a.mat[2][0] * b.mat[0][1] + + a.mat[2][1] * b.mat[1][1] + + a.mat[2][2] * b.mat[2][1] + + a.mat[2][3] * b.mat[3][1]; + + retVal.mat[2][2] = + a.mat[2][0] * b.mat[0][2] + + a.mat[2][1] * b.mat[1][2] + + a.mat[2][2] * b.mat[2][2] + + a.mat[2][3] * b.mat[3][2]; + + retVal.mat[2][3] = + a.mat[2][0] * b.mat[0][3] + + a.mat[2][1] * b.mat[1][3] + + a.mat[2][2] * b.mat[2][3] + + a.mat[2][3] * b.mat[3][3]; + + // row 3 + retVal.mat[3][0] = + a.mat[3][0] * b.mat[0][0] + + a.mat[3][1] * b.mat[1][0] + + a.mat[3][2] * b.mat[2][0] + + a.mat[3][3] * b.mat[3][0]; + + retVal.mat[3][1] = + a.mat[3][0] * b.mat[0][1] + + a.mat[3][1] * b.mat[1][1] + + a.mat[3][2] * b.mat[2][1] + + a.mat[3][3] * b.mat[3][1]; + + retVal.mat[3][2] = + a.mat[3][0] * b.mat[0][2] + + a.mat[3][1] * b.mat[1][2] + + a.mat[3][2] * b.mat[2][2] + + a.mat[3][3] * b.mat[3][2]; + + retVal.mat[3][3] = + a.mat[3][0] * b.mat[0][3] + + a.mat[3][1] * b.mat[1][3] + + a.mat[3][2] * b.mat[2][3] + + a.mat[3][3] * b.mat[3][3]; + + return retVal; + } + + Matrix4& operator*=(const Matrix4& right) + { + *this = *this * right; + return *this; + } + + // Invert the matrix - super slow + void Invert(); + + // Get the translation component of the matrix + Vector3 GetTranslation() const + { + return Vector3(mat[3][0], mat[3][1], mat[3][2]); + } + + // Get the X axis of the matrix (forward) + Vector3 GetXAxis() const + { + return Vector3::Normalize(Vector3(mat[0][0], mat[0][1], mat[0][2])); + } + + // Get the Y axis of the matrix (left) + Vector3 GetYAxis() const + { + return Vector3::Normalize(Vector3(mat[1][0], mat[1][1], mat[1][2])); + } + + // Get the Z axis of the matrix (up) + Vector3 GetZAxis() const + { + return Vector3::Normalize(Vector3(mat[2][0], mat[2][1], mat[2][2])); + } + + // Extract the scale component from the matrix + Vector3 GetScale() const + { + Vector3 retVal; + retVal.x = Vector3(mat[0][0], mat[0][1], mat[0][2]).Length(); + retVal.y = Vector3(mat[1][0], mat[1][1], mat[1][2]).Length(); + retVal.z = Vector3(mat[2][0], mat[2][1], mat[2][2]).Length(); + return retVal; + } + + // Create a scale matrix with x, y, and z scales + static Matrix4 CreateScale(float xScale, float yScale, float zScale) + { + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, zScale, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateScale(const Vector3& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y, scaleVector.z); + } + + // Create a scale matrix with a uniform factor + static Matrix4 CreateScale(float scale) + { + return CreateScale(scale, scale, scale); + } + + // Rotation about x-axis + static Matrix4 CreateRotationX(float theta) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f , 0.0f }, + { 0.0f, Math::Cos(theta), Math::Sin(theta), 0.0f }, + { 0.0f, -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about y-axis + static Matrix4 CreateRotationY(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), 0.0f, -Math::Sin(theta), 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { Math::Sin(theta), 0.0f, Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about z-axis + static Matrix4 CreateRotationZ(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f, 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Create a rotation matrix from a quaternion + static Matrix4 CreateFromQuaternion(const class Quaternion& q); + + static Matrix4 CreateTranslation(const Vector3& trans) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateLookAt(const Vector3& eye, const Vector3& target, const Vector3& up) + { + Vector3 zaxis = Vector3::Normalize(target - eye); + Vector3 xaxis = Vector3::Normalize(Vector3::Cross(up, zaxis)); + Vector3 yaxis = Vector3::Normalize(Vector3::Cross(zaxis, xaxis)); + Vector3 trans; + trans.x = -Vector3::Dot(xaxis, eye); + trans.y = -Vector3::Dot(yaxis, eye); + trans.z = -Vector3::Dot(zaxis, eye); + + float temp[4][4] = + { + { xaxis.x, yaxis.x, zaxis.x, 0.0f }, + { xaxis.y, yaxis.y, zaxis.y, 0.0f }, + { xaxis.z, yaxis.z, zaxis.z, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateOrtho(float width, float height, float near, float far) + { + float temp[4][4] = + { + { 2.0f / width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f / height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f / (far - near), 0.0f }, + { 0.0f, 0.0f, near / (near - far), 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreatePerspectiveFOV(float fovY, float width, float height, float near, float far) + { + float yScale = Math::Cot(fovY / 2.0f); + float xScale = yScale * height / width; + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, far / (far - near), 1.0f }, + { 0.0f, 0.0f, -near * far / (far - near), 0.0f } + }; + return Matrix4(temp); + } + + // Create "Simple" View-Projection Matrix from Chapter 6 + static Matrix4 CreateSimpleViewProj(float width, float height) + { + float temp[4][4] = + { + { 2.0f/width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f } + }; + return Matrix4(temp); + } + + static const Matrix4 Identity; +}; + +// (Unit) Quaternion +class Quaternion +{ +public: + float x; + float y; + float z; + float w; + + Quaternion() + { + *this = Quaternion::Identity; + } + + // This directly sets the quaternion components -- + // don't use for axis/angle + explicit Quaternion(float inX, float inY, float inZ, float inW) + { + Set(inX, inY, inZ, inW); + } + + // Construct the quaternion from an axis and angle + // It is assumed that axis is already normalized, + // and the angle is in radians + explicit Quaternion(const Vector3& axis, float angle) + { + float scalar = Math::Sin(angle / 2.0f); + x = axis.x * scalar; + y = axis.y * scalar; + z = axis.z * scalar; + w = Math::Cos(angle / 2.0f); + } + + // Directly set the internal components + void Set(float inX, float inY, float inZ, float inW) + { + x = inX; + y = inY; + z = inZ; + w = inW; + } + + void Conjugate() + { + x *= -1.0f; + y *= -1.0f; + z *= -1.0f; + } + + float LengthSq() const + { + return (x*x + y*y + z*z + w*w); + } + + float Length() const + { + return Math::Sqrt(LengthSq()); + } + + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + w /= length; + } + + // Normalize the provided quaternion + static Quaternion Normalize(const Quaternion& q) + { + Quaternion retVal = q; + retVal.Normalize(); + return retVal; + } + + // Linear interpolation + static Quaternion Lerp(const Quaternion& a, const Quaternion& b, float f) + { + Quaternion retVal; + retVal.x = Math::Lerp(a.x, b.x, f); + retVal.y = Math::Lerp(a.y, b.y, f); + retVal.z = Math::Lerp(a.z, b.z, f); + retVal.w = Math::Lerp(a.w, b.w, f); + retVal.Normalize(); + return retVal; + } + + static float Dot(const Quaternion& a, const Quaternion& b) + { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + // Spherical Linear Interpolation + static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float f) + { + float rawCosm = Quaternion::Dot(a, b); + + float cosom = -rawCosm; + if (rawCosm >= 0.0f) + { + cosom = rawCosm; + } + + float scale0, scale1; + + if (cosom < 0.9999f) + { + const float omega = Math::Acos(cosom); + const float invSin = 1.f / Math::Sin(omega); + scale0 = Math::Sin((1.f - f) * omega) * invSin; + scale1 = Math::Sin(f * omega) * invSin; + } + else + { + // Use linear interpolation if the quaternions + // are collinear + scale0 = 1.0f - f; + scale1 = f; + } + + if (rawCosm < 0.0f) + { + scale1 = -scale1; + } + + Quaternion retVal; + retVal.x = scale0 * a.x + scale1 * b.x; + retVal.y = scale0 * a.y + scale1 * b.y; + retVal.z = scale0 * a.z + scale1 * b.z; + retVal.w = scale0 * a.w + scale1 * b.w; + retVal.Normalize(); + return retVal; + } + + // Concatenate + // Rotate by q FOLLOWED BY p + static Quaternion Concatenate(const Quaternion& q, const Quaternion& p) + { + Quaternion retVal; + + // Vector component is: + // ps * qv + qs * pv + pv x qv + Vector3 qv(q.x, q.y, q.z); + Vector3 pv(p.x, p.y, p.z); + Vector3 newVec = p.w * qv + q.w * pv + Vector3::Cross(pv, qv); + retVal.x = newVec.x; + retVal.y = newVec.y; + retVal.z = newVec.z; + + // Scalar component is: + // ps * qs - pv . qv + retVal.w = p.w * q.w - Vector3::Dot(pv, qv); + + return retVal; + } + + static const Quaternion Identity; +}; + +namespace Color +{ + static const Vector3 Black(0.0f, 0.0f, 0.0f); + static const Vector3 White(1.0f, 1.0f, 1.0f); + static const Vector3 Red(1.0f, 0.0f, 0.0f); + static const Vector3 Green(0.0f, 1.0f, 0.0f); + static const Vector3 Blue(0.0f, 0.0f, 1.0f); + static const Vector3 Yellow(1.0f, 1.0f, 0.0f); + static const Vector3 LightYellow(1.0f, 1.0f, 0.88f); + static const Vector3 LightBlue(0.68f, 0.85f, 0.9f); + static const Vector3 LightPink(1.0f, 0.71f, 0.76f); + static const Vector3 LightGreen(0.56f, 0.93f, 0.56f); +} diff --git a/Chapter04/MoveComponent.cpp b/Chapter04/MoveComponent.cpp new file mode 100644 index 00000000..cdf1fdf9 --- /dev/null +++ b/Chapter04/MoveComponent.cpp @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "MoveComponent.h" +#include "Actor.h" + +MoveComponent::MoveComponent(class Actor* owner, int updateOrder) +:Component(owner, updateOrder) +,mAngularSpeed(0.0f) +,mForwardSpeed(0.0f) +{ + +} + +void MoveComponent::Update(float deltaTime) +{ + if (!Math::NearZero(mAngularSpeed)) + { + float rot = mOwner->GetRotation(); + rot += mAngularSpeed * deltaTime; + mOwner->SetRotation(rot); + } + + if (!Math::NearZero(mForwardSpeed)) + { + Vector2 pos = mOwner->GetPosition(); + pos += mOwner->GetForward() * mForwardSpeed * deltaTime; + mOwner->SetPosition(pos); + } +} diff --git a/Chapter04/MoveComponent.h b/Chapter04/MoveComponent.h new file mode 100644 index 00000000..def7d389 --- /dev/null +++ b/Chapter04/MoveComponent.h @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" + +class MoveComponent : public Component +{ +public: + // Lower update order to update first + MoveComponent(class Actor* owner, int updateOrder = 10); + void Update(float deltaTime) override; + + float GetAngularSpeed() const { return mAngularSpeed; } + float GetForwardSpeed() const { return mForwardSpeed; } + void SetAngularSpeed(float speed) { mAngularSpeed = speed; } + void SetForwardSpeed(float speed) { mForwardSpeed = speed; } +private: + float mAngularSpeed; + float mForwardSpeed; +}; diff --git a/Chapter04/NavComponent.cpp b/Chapter04/NavComponent.cpp new file mode 100644 index 00000000..a80d38ce --- /dev/null +++ b/Chapter04/NavComponent.cpp @@ -0,0 +1,49 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "NavComponent.h" +#include "Tile.h" + +NavComponent::NavComponent(class Actor* owner, int updateOrder) +:MoveComponent(owner, updateOrder) +,mNextNode(nullptr) +{ + +} + +void NavComponent::Update(float deltaTime) +{ + if (mNextNode) + { + // If we're at the next node, advance along path + Vector2 diff = mOwner->GetPosition() - mNextNode->GetPosition(); + if (Math::NearZero(diff.Length(), 2.0f)) + { + mNextNode = mNextNode->GetParent(); + TurnTo(mNextNode->GetPosition()); + } + } + + MoveComponent::Update(deltaTime); +} + +void NavComponent::StartPath(const Tile* start) +{ + mNextNode = start->GetParent(); + TurnTo(mNextNode->GetPosition()); +} + +void NavComponent::TurnTo(const Vector2& pos) +{ + // Vector from me to pos + Vector2 dir = pos - mOwner->GetPosition(); + // New angle is just atan2 of this dir vector + // (Negate y because +y is down on screen) + float angle = Math::Atan2(-dir.y, dir.x); + mOwner->SetRotation(angle); +} diff --git a/Chapter04/NavComponent.h b/Chapter04/NavComponent.h new file mode 100644 index 00000000..b79c721f --- /dev/null +++ b/Chapter04/NavComponent.h @@ -0,0 +1,23 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "MoveComponent.h" +#include "Math.h" + +class NavComponent : public MoveComponent +{ +public: + // Lower update order to update first + NavComponent(class Actor* owner, int updateOrder = 10); + void Update(float deltaTime) override; + void StartPath(const class Tile* start); + void TurnTo(const Vector2& pos); +private: + const class Tile* mNextNode; +}; diff --git a/Chapter04/Search.cpp b/Chapter04/Search.cpp new file mode 100644 index 00000000..56a83598 --- /dev/null +++ b/Chapter04/Search.cpp @@ -0,0 +1,575 @@ +#include +#include +#include +#include + +struct GraphNode +{ + // Adjacency list + std::vector mAdjacent; +}; + +struct Graph +{ + // A graph contains nodes + std::vector mNodes; +}; + +struct WeightedEdge +{ + // Which nodes are connected by this edge? + struct WeightedGraphNode* mFrom; + struct WeightedGraphNode* mTo; + // Weight of this edge + float mWeight; +}; + +struct WeightedGraphNode +{ + std::vector mEdges; +}; + +struct WeightedGraph +{ + std::vector mNodes; +}; + +struct GBFSScratch +{ + const WeightedEdge* mParentEdge = nullptr; + float mHeuristic = 0.0f; + bool mInOpenSet = false; + bool mInClosedSet = false; +}; + +using GBFSMap = +std::unordered_map; + +struct AStarScratch +{ + const WeightedEdge* mParentEdge = nullptr; + float mHeuristic = 0.0f; + float mActualFromStart = 0.0f; + bool mInOpenSet = false; + bool mInClosedSet = false; +}; + +using AStarMap = +std::unordered_map; + +float ComputeHeuristic(const WeightedGraphNode* a, const WeightedGraphNode* b) +{ + return 0.0f; +} + +bool AStar(const WeightedGraph& g, const WeightedGraphNode* start, + const WeightedGraphNode* goal, AStarMap& outMap) +{ + std::vector openSet; + + // Set current node to start, and mark in closed set + const WeightedGraphNode* current = start; + outMap[current].mInClosedSet = true; + + do + { + // Add adjacent nodes to open set + for (const WeightedEdge* edge : current->mEdges) + { + const WeightedGraphNode* neighbor = edge->mTo; + // Get scratch data for this node + AStarScratch& data = outMap[neighbor]; + // Only check nodes that aren't in the closed set + if (!data.mInClosedSet) + { + if (!data.mInOpenSet) + { + // Not in the open set, so parent must be current + data.mParentEdge = edge; + data.mHeuristic = ComputeHeuristic(neighbor, goal); + // Actual cost is the parent's plus cost of traversing edge + data.mActualFromStart = outMap[current].mActualFromStart + + edge->mWeight; + data.mInOpenSet = true; + openSet.emplace_back(neighbor); + } + else + { + // Compute what new actual cost is if current becomes parent + float newG = outMap[current].mActualFromStart + edge->mWeight; + if (newG < data.mActualFromStart) + { + // Current should adopt this node + data.mParentEdge = edge; + data.mActualFromStart = newG; + } + } + } + } + + // If open set is empty, all possible paths are exhausted + if (openSet.empty()) + { + break; + } + + // Find lowest cost node in open set + auto iter = std::min_element(openSet.begin(), openSet.end(), + [&outMap](const WeightedGraphNode* a, const WeightedGraphNode* b) { + // Calculate f(x) for nodes a/b + float fOfA = outMap[a].mHeuristic + outMap[a].mActualFromStart; + float fOfB = outMap[b].mHeuristic + outMap[b].mActualFromStart; + return fOfA < fOfB; + }); + // Set to current and move from open to closed + current = *iter; + openSet.erase(iter); + outMap[current].mInOpenSet = true; + outMap[current].mInClosedSet = true; + } while (current != goal); + + // Did we find a path? + return (current == goal) ? true : false; +} + +bool GBFS(const WeightedGraph& g, const WeightedGraphNode* start, + const WeightedGraphNode* goal, GBFSMap& outMap) +{ + std::vector openSet; + + // Set current node to start, and mark in closed set + const WeightedGraphNode* current = start; + outMap[current].mInClosedSet = true; + + do + { + // Add adjacent nodes to open set + for (const WeightedEdge* edge : current->mEdges) + { + // Get scratch data for this node + GBFSScratch& data = outMap[edge->mTo]; + // Add it only if it's not in the closed set + if (!data.mInClosedSet) + { + // Set the adjacent node's parent edge + data.mParentEdge = edge; + if (!data.mInOpenSet) + { + // Compute the heuristic for this node, and add to open set + data.mHeuristic = ComputeHeuristic(edge->mTo, goal); + data.mInOpenSet = true; + openSet.emplace_back(edge->mTo); + } + } + } + + // If open set is empty, all possible paths are exhausted + if (openSet.empty()) + { + break; + } + + // Find lowest cost node in open set + auto iter = std::min_element(openSet.begin(), openSet.end(), + [&outMap](const WeightedGraphNode* a, const WeightedGraphNode* b) { + return outMap[a].mHeuristic < outMap[b].mHeuristic; + }); + + // Set to current and move from open to closed + current = *iter; + openSet.erase(iter); + outMap[current].mInOpenSet = false; + outMap[current].mInClosedSet = true; + } while (current != goal); + + // Did we find a path? + return (current == goal) ? true : false; +} + +using NodeToParentMap = +std::unordered_map; + +bool BFS(const Graph& graph, const GraphNode* start, const GraphNode* goal, NodeToParentMap& outMap) +{ + // Whether we found a path + bool pathFound = false; + // Nodes to consider + std::queue q; + // Enqueue the first node + q.emplace(start); + + while (!q.empty()) + { + // Dequeue a node + const GraphNode* current = q.front(); + q.pop(); + if (current == goal) + { + pathFound = true; + break; + } + + // Enqueue adjacent nodes that aren't already in the queue + for (const GraphNode* node : current->mAdjacent) + { + // If the parent is null, it hasn't been enqueued + // (except for the start node) + const GraphNode* parent = outMap[node]; + if (parent == nullptr && node != start) + { + // Enqueue this node, setting its parent + outMap[node] = current; + q.emplace(node); + } + } + } + + return pathFound; +} + +void testBFS() +{ + Graph g; + for (int i = 0; i < 5; i++) + { + for (int j = 0; j < 5; j++) + { + GraphNode* node = new GraphNode; + g.mNodes.emplace_back(node); + } + } + + for (int i = 0; i < 5; i++) + { + for (int j = 0; j < 5; j++) + { + GraphNode* node = g.mNodes[i * 5 + j]; + if (i > 0) + { + node->mAdjacent.emplace_back(g.mNodes[(i - 1) * 5 + j]); + } + if (i < 4) + { + node->mAdjacent.emplace_back(g.mNodes[(i + 1) * 5 + j]); + } + if (j > 0) + { + node->mAdjacent.emplace_back(g.mNodes[i * 5 + j - 1]); + } + if (j < 4) + { + node->mAdjacent.emplace_back(g.mNodes[i * 5 + j + 1]); + } + } + } + + NodeToParentMap map; + bool found = BFS(g, g.mNodes[0], g.mNodes[9], map); + std::cout << found << '\n'; +} + +void testHeuristic(bool useAStar) +{ + WeightedGraph g; + for (int i = 0; i < 5; i++) + { + for (int j = 0; j < 5; j++) + { + WeightedGraphNode* node = new WeightedGraphNode; + g.mNodes.emplace_back(node); + } + } + + for (int i = 0; i < 5; i++) + { + for (int j = 0; j < 5; j++) + { + WeightedGraphNode* node = g.mNodes[i * 5 + j]; + if (i > 0) + { + WeightedEdge* e = new WeightedEdge; + e->mFrom = node; + e->mTo = g.mNodes[(i - 1) * 5 + j]; + e->mWeight = 1.0f; + node->mEdges.emplace_back(e); + } + if (i < 4) + { + WeightedEdge* e = new WeightedEdge; + e->mFrom = node; + e->mTo = g.mNodes[(i + 1) * 5 + j]; + e->mWeight = 1.0f; + node->mEdges.emplace_back(e); + } + if (j > 0) + { + WeightedEdge* e = new WeightedEdge; + e->mFrom = node; + e->mTo = g.mNodes[i * 5 + j - 1]; + e->mWeight = 1.0f; + node->mEdges.emplace_back(e); + } + if (j < 4) + { + WeightedEdge* e = new WeightedEdge; + e->mFrom = node; + e->mTo = g.mNodes[i * 5 + j + 1]; + e->mWeight = 1.0f; + node->mEdges.emplace_back(e); + } + } + } + bool found = false; + if (useAStar) + { + AStarMap map; + found = AStar(g, g.mNodes[0], g.mNodes[9], map); + } + else + { + GBFSMap map; + found = GBFS(g, g.mNodes[0], g.mNodes[9], map); + } + std::cout << found << '\n'; +} + +struct GameState +{ + // (For tic-tac-toe, array of board) + enum SquareState { Empty, X, O }; + SquareState mBoard[3][3]; +}; + +struct GTNode +{ + // Children nodes + std::vector mChildren; + // State of game + GameState mState; +}; + +void GenStates(GTNode* root, bool xPlayer) +{ + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + if (root->mState.mBoard[i][j] == GameState::Empty) + { + GTNode* node = new GTNode; + root->mChildren.emplace_back(node); + node->mState = root->mState; + node->mState.mBoard[i][j] = xPlayer ? GameState::X : GameState::O; + GenStates(node, !xPlayer); + } + } + } +} + +float GetScore(const GameState& state) +{ + // Are any of the rows the same? + for (int i = 0; i < 3; i++) + { + bool same = true; + GameState::SquareState v = state.mBoard[i][0]; + for (int j = 1; j < 3; j++) + { + if (state.mBoard[i][j] != v) + { + same = false; + } + } + + if (same) + { + if (v == GameState::X) + { + return 1.0f; + } + else + { + return -1.0f; + } + } + } + + // Are any of the columns the same? + for (int j = 0; j < 3; j++) + { + bool same = true; + GameState::SquareState v = state.mBoard[0][j]; + for (int i = 1; i < 3; i++) + { + if (state.mBoard[i][j] != v) + { + same = false; + } + } + + if (same) + { + if (v == GameState::X) + { + return 1.0f; + } + else + { + return -1.0f; + } + } + } + + // What about diagonals? + if (((state.mBoard[0][0] == state.mBoard[1][1]) && + (state.mBoard[1][1] == state.mBoard[2][2])) || + ((state.mBoard[2][0] == state.mBoard[1][1]) && + (state.mBoard[1][1] == state.mBoard[0][2]))) + { + if (state.mBoard[1][1] == GameState::X) + { + return 1.0f; + } + else + { + return -1.0f; + } + } + // We tied + return 0.0f; +} + +float MinPlayer(const GTNode* node); + +float MaxPlayer(const GTNode* node) +{ + // If this is a leaf, return score + if (node->mChildren.empty()) + { + return GetScore(node->mState); + } + + float maxValue = -std::numeric_limits::infinity(); + // Find the subtree with the maximum value + for (const GTNode* child : node->mChildren) + { + maxValue = std::max(maxValue, MinPlayer(child)); + } + return maxValue; +} + +float MinPlayer(const GTNode* node) +{ + // If this is a leaf, return score + if (node->mChildren.empty()) + { + return GetScore(node->mState); + } + + float minValue = std::numeric_limits::infinity(); + // Find the subtree with the minimum value + for (const GTNode* child : node->mChildren) + { + minValue = std::min(minValue, MaxPlayer(child)); + } + return minValue; +} + +const GTNode* MinimaxDecide(const GTNode* root) +{ + // Find the subtree with the maximum value, and save the choice + const GTNode* choice = nullptr; + float maxValue = -std::numeric_limits::infinity(); + for (const GTNode* child : root->mChildren) + { + float v = MinPlayer(child); + if (v > maxValue) + { + maxValue = v; + choice = child; + } + } + return choice; +} + +float AlphaBetaMin(const GTNode* node, float alpha, float beta); + +float AlphaBetaMax(const GTNode* node, float alpha, float beta) +{ + // If this is a leaf, return score + if (node->mChildren.empty()) + { + return GetScore(node->mState); + } + + float maxValue = -std::numeric_limits::infinity(); + // Find the subtree with the maximum value + for (const GTNode* child : node->mChildren) + { + maxValue = std::max(maxValue, AlphaBetaMin(child, alpha, beta)); + if (maxValue >= beta) + { + return maxValue; // Beta prune + } + alpha = std::max(maxValue, alpha); + } + return maxValue; +} + +float AlphaBetaMin(const GTNode* node, float alpha, float beta) +{ + // If this is a leaf, return score + if (node->mChildren.empty()) + { + return GetScore(node->mState); + } + + float minValue = std::numeric_limits::infinity(); + // Find the subtree with the minimum value + for (const GTNode* child : node->mChildren) + { + minValue = std::min(minValue, AlphaBetaMax(child, alpha, beta)); + if (minValue <= alpha) + { + return minValue; // Alpha prune + } + beta = std::min(minValue, beta); + } + return minValue; +} + +const GTNode* AlphaBetaDecide(const GTNode* root) +{ + // Find the subtree with the maximum value, and save the choice + const GTNode* choice = nullptr; + float maxValue = -std::numeric_limits::infinity(); + float beta = std::numeric_limits::infinity(); + for (const GTNode* child : root->mChildren) + { + float v = AlphaBetaMin(child, maxValue, beta); + if (v > maxValue) + { + maxValue = v; + choice = child; + } + } + return choice; +} + +void testTicTac() +{ + GTNode* root = new GTNode; + root->mState.mBoard[0][0] = GameState::O; + root->mState.mBoard[0][1] = GameState::Empty; + root->mState.mBoard[0][2] = GameState::X; + root->mState.mBoard[1][0] = GameState::X; + root->mState.mBoard[1][1] = GameState::O; + root->mState.mBoard[1][2] = GameState::O; + root->mState.mBoard[2][0] = GameState::X; + root->mState.mBoard[2][1] = GameState::Empty; + root->mState.mBoard[2][2] = GameState::Empty; + + GenStates(root, true); + const GTNode* choice = AlphaBetaDecide(root); + std::cout << choice->mChildren.size(); +} diff --git a/Chapter04/SpriteComponent.cpp b/Chapter04/SpriteComponent.cpp new file mode 100644 index 00000000..56884fbc --- /dev/null +++ b/Chapter04/SpriteComponent.cpp @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "SpriteComponent.h" +#include "Actor.h" +#include "Game.h" + +SpriteComponent::SpriteComponent(Actor* owner, int drawOrder) + :Component(owner) + ,mTexture(nullptr) + ,mDrawOrder(drawOrder) + ,mTexWidth(0) + ,mTexHeight(0) +{ + mOwner->GetGame()->AddSprite(this); +} + +SpriteComponent::~SpriteComponent() +{ + mOwner->GetGame()->RemoveSprite(this); +} + +void SpriteComponent::Draw(SDL_Renderer* renderer) +{ + if (mTexture) + { + SDL_Rect r; + // Scale the width/height by owner's scale + r.w = static_cast(mTexWidth * mOwner->GetScale()); + r.h = static_cast(mTexHeight * mOwner->GetScale()); + // Center the rectangle around the position of the owner + r.x = static_cast(mOwner->GetPosition().x - r.w / 2); + r.y = static_cast(mOwner->GetPosition().y - r.h / 2); + + // Draw (have to convert angle from radians to degrees, and clockwise to counter) + SDL_RenderCopyEx(renderer, + mTexture, + nullptr, + &r, + -Math::ToDegrees(mOwner->GetRotation()), + nullptr, + SDL_FLIP_NONE); + } +} + +void SpriteComponent::SetTexture(SDL_Texture* texture) +{ + mTexture = texture; + // Set width/height + SDL_QueryTexture(texture, nullptr, nullptr, &mTexWidth, &mTexHeight); +} diff --git a/Chapter04/SpriteComponent.h b/Chapter04/SpriteComponent.h new file mode 100644 index 00000000..c430e888 --- /dev/null +++ b/Chapter04/SpriteComponent.h @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "SDL/SDL.h" +class SpriteComponent : public Component +{ +public: + // (Lower draw order corresponds with further back) + SpriteComponent(class Actor* owner, int drawOrder = 100); + ~SpriteComponent(); + + virtual void Draw(SDL_Renderer* renderer); + virtual void SetTexture(SDL_Texture* texture); + + int GetDrawOrder() const { return mDrawOrder; } + int GetTexHeight() const { return mTexHeight; } + int GetTexWidth() const { return mTexWidth; } +protected: + SDL_Texture* mTexture; + int mDrawOrder; + int mTexWidth; + int mTexHeight; +}; diff --git a/Chapter04/Tile.cpp b/Chapter04/Tile.cpp new file mode 100644 index 00000000..90e5c781 --- /dev/null +++ b/Chapter04/Tile.cpp @@ -0,0 +1,66 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Tile.h" +#include "SpriteComponent.h" +#include "Game.h" + +Tile::Tile(class Game* game) +:Actor(game) +,mParent(nullptr) +,f(0.0f) +,g(0.0f) +,h(0.0f) +,mBlocked(false) +,mSprite(nullptr) +,mTileState(EDefault) +,mSelected(false) +{ + mSprite = new SpriteComponent(this); + UpdateTexture(); +} + +void Tile::SetTileState(TileState state) +{ + mTileState = state; + UpdateTexture(); +} + +void Tile::ToggleSelect() +{ + mSelected = !mSelected; + UpdateTexture(); +} + +void Tile::UpdateTexture() +{ + std::string text; + switch (mTileState) + { + case EStart: + text = "Assets/TileTan.png"; + break; + case EBase: + text = "Assets/TileGreen.png"; + break; + case EPath: + if (mSelected) + text = "Assets/TileGreySelected.png"; + else + text = "Assets/TileGrey.png"; + break; + case EDefault: + default: + if (mSelected) + text = "Assets/TileBrownSelected.png"; + else + text = "Assets/TileBrown.png"; + break; + } + mSprite->SetTexture(GetGame()->GetTexture(text)); +} diff --git a/Chapter04/Tile.h b/Chapter04/Tile.h new file mode 100644 index 00000000..f3e90d12 --- /dev/null +++ b/Chapter04/Tile.h @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +#include + +class Tile : public Actor +{ +public: + friend class Grid; + enum TileState + { + EDefault, + EPath, + EStart, + EBase + }; + + Tile(class Game* game); + + void SetTileState(TileState state); + TileState GetTileState() const { return mTileState; } + void ToggleSelect(); + const Tile* GetParent() const { return mParent; } +private: + // For pathfinding + std::vector mAdjacent; + Tile* mParent; + float f; + float g; + float h; + bool mInOpenSet; + bool mInClosedSet; + bool mBlocked; + + void UpdateTexture(); + class SpriteComponent* mSprite; + TileState mTileState; + bool mSelected; +}; diff --git a/Chapter04/Tower.cpp b/Chapter04/Tower.cpp new file mode 100644 index 00000000..20d60dea --- /dev/null +++ b/Chapter04/Tower.cpp @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Tower.h" +#include "SpriteComponent.h" +#include "MoveComponent.h" +#include "Game.h" +#include "Enemy.h" +#include "Bullet.h" + +Tower::Tower(class Game* game) +:Actor(game) +{ + SpriteComponent* sc = new SpriteComponent(this, 200); + sc->SetTexture(game->GetTexture("Assets/Tower.png")); + + mMove = new MoveComponent(this); + //mMove->SetAngularSpeed(Math::Pi); + + mNextAttack = AttackTime; +} + +void Tower::UpdateActor(float deltaTime) +{ + Actor::UpdateActor(deltaTime); + + mNextAttack -= deltaTime; + if (mNextAttack <= 0.0f) + { + Enemy* e = GetGame()->GetNearestEnemy(GetPosition()); + if (e != nullptr) + { + // Vector from me to enemy + Vector2 dir = e->GetPosition() - GetPosition(); + float dist = dir.Length(); + if (dist < AttackRange) + { + // Rotate to face enemy + SetRotation(Math::Atan2(-dir.y, dir.x)); + // Spawn bullet at tower position facing enemy + Bullet* b = new Bullet(GetGame()); + b->SetPosition(GetPosition()); + b->SetRotation(GetRotation()); + } + } + mNextAttack += AttackTime; + } +} diff --git a/Chapter04/Tower.h b/Chapter04/Tower.h new file mode 100644 index 00000000..bfbf12ff --- /dev/null +++ b/Chapter04/Tower.h @@ -0,0 +1,22 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" + +class Tower : public Actor +{ +public: + Tower(class Game* game); + void UpdateActor(float deltaTime) override; +private: + class MoveComponent* mMove; + float mNextAttack; + const float AttackTime = 2.5f; + const float AttackRange = 100.0f; +}; diff --git a/Chapter05/Actor.cpp b/Chapter05/Actor.cpp new file mode 100644 index 00000000..738d508f --- /dev/null +++ b/Chapter05/Actor.cpp @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Actor.h" +#include "Game.h" +#include "Component.h" +#include + +Actor::Actor(Game* game) + :mState(EActive) + ,mPosition(Vector2::Zero) + ,mScale(1.0f) + ,mRotation(0.0f) + ,mGame(game) + ,mRecomputeWorldTransform(true) +{ + mGame->AddActor(this); +} + +Actor::~Actor() +{ + mGame->RemoveActor(this); + // Need to delete components + // Because ~Component calls RemoveComponent, need a different style loop + while (!mComponents.empty()) + { + delete mComponents.back(); + } +} + +void Actor::Update(float deltaTime) +{ + if (mState == EActive) + { + ComputeWorldTransform(); + + UpdateComponents(deltaTime); + UpdateActor(deltaTime); + + ComputeWorldTransform(); + } +} + +void Actor::UpdateComponents(float deltaTime) +{ + for (auto comp : mComponents) + { + comp->Update(deltaTime); + } +} + +void Actor::UpdateActor(float deltaTime) +{ +} + +void Actor::ProcessInput(const uint8_t* keyState) +{ + if (mState == EActive) + { + // First process input for components + for (auto comp : mComponents) + { + comp->ProcessInput(keyState); + } + + ActorInput(keyState); + } +} + +void Actor::ActorInput(const uint8_t* keyState) +{ +} + +void Actor::ComputeWorldTransform() +{ + if (mRecomputeWorldTransform) + { + mRecomputeWorldTransform = false; + // Scale, then rotate, then translate + mWorldTransform = Matrix4::CreateScale(mScale); + mWorldTransform *= Matrix4::CreateRotationZ(mRotation); + mWorldTransform *= Matrix4::CreateTranslation(Vector3(mPosition.x, mPosition.y, 0.0f)); + + // Inform components world transform updated + for (auto comp : mComponents) + { + comp->OnUpdateWorldTransform(); + } + } +} + +void Actor::AddComponent(Component* component) +{ + // Find the insertion point in the sorted vector + // (The first element with a order higher than me) + int myOrder = component->GetUpdateOrder(); + auto iter = mComponents.begin(); + for (; + iter != mComponents.end(); + ++iter) + { + if (myOrder < (*iter)->GetUpdateOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mComponents.insert(iter, component); +} + +void Actor::RemoveComponent(Component* component) +{ + auto iter = std::find(mComponents.begin(), mComponents.end(), component); + if (iter != mComponents.end()) + { + mComponents.erase(iter); + } +} diff --git a/Chapter05/Actor.h b/Chapter05/Actor.h new file mode 100644 index 00000000..7a41bd47 --- /dev/null +++ b/Chapter05/Actor.h @@ -0,0 +1,74 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include "Math.h" +#include + +class Actor +{ +public: + enum State + { + EActive, + EPaused, + EDead + }; + + Actor(class Game* game); + virtual ~Actor(); + + // Update function called from Game (not overridable) + void Update(float deltaTime); + // Updates all the components attached to the actor (not overridable) + void UpdateComponents(float deltaTime); + // Any actor-specific update code (overridable) + virtual void UpdateActor(float deltaTime); + + // ProcessInput function called from Game (not overridable) + void ProcessInput(const uint8_t* keyState); + // Any actor-specific input code (overridable) + virtual void ActorInput(const uint8_t* keyState); + + // Getters/setters + const Vector2& GetPosition() const { return mPosition; } + void SetPosition(const Vector2& pos) { mPosition = pos; mRecomputeWorldTransform = true; } + float GetScale() const { return mScale; } + void SetScale(float scale) { mScale = scale; mRecomputeWorldTransform = true; } + float GetRotation() const { return mRotation; } + void SetRotation(float rotation) { mRotation = rotation; mRecomputeWorldTransform = true; } + + void ComputeWorldTransform(); + const Matrix4& GetWorldTransform() const { return mWorldTransform; } + + Vector2 GetForward() const { return Vector2(Math::Cos(mRotation), Math::Sin(mRotation)); } + + State GetState() const { return mState; } + void SetState(State state) { mState = state; } + + class Game* GetGame() { return mGame; } + + + // Add/remove components + void AddComponent(class Component* component); + void RemoveComponent(class Component* component); +private: + // Actor's state + State mState; + + // Transform + Matrix4 mWorldTransform; + Vector2 mPosition; + float mScale; + float mRotation; + bool mRecomputeWorldTransform; + + std::vector mComponents; + class Game* mGame; +}; diff --git a/Chapter05/Assets/Asteroid.png b/Chapter05/Assets/Asteroid.png new file mode 100644 index 00000000..98e831e9 Binary files /dev/null and b/Chapter05/Assets/Asteroid.png differ diff --git a/Chapter05/Assets/Laser.png b/Chapter05/Assets/Laser.png new file mode 100644 index 00000000..95352d27 Binary files /dev/null and b/Chapter05/Assets/Laser.png differ diff --git a/Chapter05/Assets/Ship.png b/Chapter05/Assets/Ship.png new file mode 100644 index 00000000..96514436 Binary files /dev/null and b/Chapter05/Assets/Ship.png differ diff --git a/Chapter05/Assets/ShipWithThrust.png b/Chapter05/Assets/ShipWithThrust.png new file mode 100644 index 00000000..ed9a43e2 Binary files /dev/null and b/Chapter05/Assets/ShipWithThrust.png differ diff --git a/Chapter05/Asteroid.cpp b/Chapter05/Asteroid.cpp new file mode 100644 index 00000000..9bcaa1e1 --- /dev/null +++ b/Chapter05/Asteroid.cpp @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Asteroid.h" +#include "SpriteComponent.h" +#include "MoveComponent.h" +#include "Game.h" +#include "Random.h" +#include "CircleComponent.h" + +Asteroid::Asteroid(Game* game) + :Actor(game) + ,mCircle(nullptr) +{ + // Initialize to random position/orientation + Vector2 randPos = Random::GetVector(Vector2(-512.0f, -384.0f), + Vector2(512.0f, 384.0f)); + SetPosition(randPos); + + SetRotation(Random::GetFloatRange(0.0f, Math::TwoPi)); + + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this); + sc->SetTexture(game->GetTexture("Assets/Asteroid.png")); + + // Create a move component, and set a forward speed + MoveComponent* mc = new MoveComponent(this); + mc->SetForwardSpeed(150.0f); + + // Create a circle component (for collision) + mCircle = new CircleComponent(this); + mCircle->SetRadius(40.0f); + + // Add to mAsteroids in game + game->AddAsteroid(this); +} + +Asteroid::~Asteroid() +{ + GetGame()->RemoveAsteroid(this); +} diff --git a/Chapter05/Asteroid.h b/Chapter05/Asteroid.h new file mode 100644 index 00000000..45305770 --- /dev/null +++ b/Chapter05/Asteroid.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Asteroid : public Actor +{ +public: + Asteroid(class Game* game); + ~Asteroid(); + + class CircleComponent* GetCircle() { return mCircle; } +private: + class CircleComponent* mCircle; +}; diff --git a/Chapter05/Chapter05-mac.xcodeproj/project.pbxproj b/Chapter05/Chapter05-mac.xcodeproj/project.pbxproj new file mode 100644 index 00000000..0d5e462f --- /dev/null +++ b/Chapter05/Chapter05-mac.xcodeproj/project.pbxproj @@ -0,0 +1,402 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9206FDC61F140707005078A2 /* Texture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9206FDC41F140707005078A2 /* Texture.cpp */; }; + 9206FDC91F140D40005078A2 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9206FDC71F140D40005078A2 /* Shader.cpp */; }; + 9223C4781F009428009A94D7 /* Game.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4671F009428009A94D7 /* Game.cpp */; }; + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4681F009428009A94D7 /* Actor.cpp */; }; + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C46E1F009428009A94D7 /* Component.cpp */; }; + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4711F009428009A94D7 /* Main.cpp */; }; + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4721F009428009A94D7 /* Math.cpp */; }; + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4761F009428009A94D7 /* SpriteComponent.cpp */; }; + 9223C48B1F0CA3CE009A94D7 /* MoveComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */; }; + 9223C4951F0CA766009A94D7 /* CircleComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */; }; + 92CF0D791F3BBF140086A0F3 /* VertexArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92CF0D771F3BBF140086A0F3 /* VertexArray.cpp */; }; + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92D324FA1B697389005A86C7 /* CoreFoundation.framework */; }; + 92E391981FE87F4800D8C362 /* InputComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E3918F1FE87F4700D8C362 /* InputComponent.cpp */; }; + 92E391991FE87F4800D8C362 /* Ship.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391911FE87F4700D8C362 /* Ship.cpp */; }; + 92E3919A1FE87F4800D8C362 /* Asteroid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391921FE87F4700D8C362 /* Asteroid.cpp */; }; + 92E3919B1FE87F4800D8C362 /* Laser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391931FE87F4700D8C362 /* Laser.cpp */; }; + 92E3919C1FE87F4800D8C362 /* Random.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92E391941FE87F4800D8C362 /* Random.cpp */; }; + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92E46E931B6353E50035CD21 /* OpenGL.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 9206FDC31F13F7E8005078A2 /* Shaders */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Shaders; sourceTree = ""; }; + 9206FDC41F140707005078A2 /* Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Texture.cpp; sourceTree = ""; }; + 9206FDC51F140707005078A2 /* Texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Texture.h; sourceTree = ""; }; + 9206FDC71F140D40005078A2 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; + 9206FDC81F140D40005078A2 /* Shader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Shader.h; sourceTree = ""; }; + 9223C4671F009428009A94D7 /* Game.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Game.cpp; sourceTree = ""; }; + 9223C4681F009428009A94D7 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 9223C4691F009428009A94D7 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 9223C46E1F009428009A94D7 /* Component.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Component.cpp; sourceTree = ""; }; + 9223C46F1F009428009A94D7 /* Component.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Component.h; sourceTree = ""; }; + 9223C4701F009428009A94D7 /* Game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Game.h; sourceTree = ""; }; + 9223C4711F009428009A94D7 /* Main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Main.cpp; sourceTree = ""; }; + 9223C4721F009428009A94D7 /* Math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Math.cpp; sourceTree = ""; }; + 9223C4731F009428009A94D7 /* Math.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Math.h; sourceTree = ""; }; + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpriteComponent.cpp; sourceTree = ""; }; + 9223C4771F009428009A94D7 /* SpriteComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpriteComponent.h; sourceTree = ""; }; + 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MoveComponent.cpp; sourceTree = ""; }; + 9223C48C1F0CA3D4009A94D7 /* MoveComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MoveComponent.h; sourceTree = ""; }; + 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CircleComponent.cpp; sourceTree = ""; }; + 9223C4931F0CA766009A94D7 /* CircleComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleComponent.h; sourceTree = ""; }; + 92CF0D771F3BBF140086A0F3 /* VertexArray.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VertexArray.cpp; sourceTree = ""; }; + 92CF0D781F3BBF140086A0F3 /* VertexArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VertexArray.h; sourceTree = ""; }; + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 92E3918E1FE87F4700D8C362 /* Asteroid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Asteroid.h; sourceTree = ""; }; + 92E3918F1FE87F4700D8C362 /* InputComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InputComponent.cpp; sourceTree = ""; }; + 92E391901FE87F4700D8C362 /* Random.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Random.h; sourceTree = ""; }; + 92E391911FE87F4700D8C362 /* Ship.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Ship.cpp; sourceTree = ""; }; + 92E391921FE87F4700D8C362 /* Asteroid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Asteroid.cpp; sourceTree = ""; }; + 92E391931FE87F4700D8C362 /* Laser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Laser.cpp; sourceTree = ""; }; + 92E391941FE87F4800D8C362 /* Random.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Random.cpp; sourceTree = ""; }; + 92E391951FE87F4800D8C362 /* Laser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Laser.h; sourceTree = ""; }; + 92E391961FE87F4800D8C362 /* InputComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputComponent.h; sourceTree = ""; }; + 92E391971FE87F4800D8C362 /* Ship.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ship.h; sourceTree = ""; }; + 92E46DF71B634EA30035CD21 /* Game-mac */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Game-mac"; sourceTree = BUILT_PRODUCTS_DIR; }; + 92E46E931B6353E50035CD21 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 92E46DF41B634EA30035CD21 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */, + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 92E46DEE1B634EA30035CD21 = { + isa = PBXGroup; + children = ( + 9223C4681F009428009A94D7 /* Actor.cpp */, + 9223C4691F009428009A94D7 /* Actor.h */, + 92E391921FE87F4700D8C362 /* Asteroid.cpp */, + 92E3918E1FE87F4700D8C362 /* Asteroid.h */, + 9223C46E1F009428009A94D7 /* Component.cpp */, + 9223C46F1F009428009A94D7 /* Component.h */, + 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */, + 9223C4931F0CA766009A94D7 /* CircleComponent.h */, + 9223C4671F009428009A94D7 /* Game.cpp */, + 9223C4701F009428009A94D7 /* Game.h */, + 92E3918F1FE87F4700D8C362 /* InputComponent.cpp */, + 92E391961FE87F4800D8C362 /* InputComponent.h */, + 92E391931FE87F4700D8C362 /* Laser.cpp */, + 92E391951FE87F4800D8C362 /* Laser.h */, + 9223C4711F009428009A94D7 /* Main.cpp */, + 9223C4721F009428009A94D7 /* Math.cpp */, + 9223C4731F009428009A94D7 /* Math.h */, + 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */, + 9223C48C1F0CA3D4009A94D7 /* MoveComponent.h */, + 92E391941FE87F4800D8C362 /* Random.cpp */, + 92E391901FE87F4700D8C362 /* Random.h */, + 9206FDC71F140D40005078A2 /* Shader.cpp */, + 9206FDC81F140D40005078A2 /* Shader.h */, + 92E391911FE87F4700D8C362 /* Ship.cpp */, + 92E391971FE87F4800D8C362 /* Ship.h */, + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */, + 9223C4771F009428009A94D7 /* SpriteComponent.h */, + 9206FDC41F140707005078A2 /* Texture.cpp */, + 9206FDC51F140707005078A2 /* Texture.h */, + 92CF0D771F3BBF140086A0F3 /* VertexArray.cpp */, + 92CF0D781F3BBF140086A0F3 /* VertexArray.h */, + 9206FDC31F13F7E8005078A2 /* Shaders */, + 92E46DF81B634EA30035CD21 /* Products */, + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */, + 92E46E931B6353E50035CD21 /* OpenGL.framework */, + ); + sourceTree = ""; + }; + 92E46DF81B634EA30035CD21 /* Products */ = { + isa = PBXGroup; + children = ( + 92E46DF71B634EA30035CD21 /* Game-mac */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 92E46DF61B634EA30035CD21 /* Game-mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */; + buildPhases = ( + 92E46DF31B634EA30035CD21 /* Sources */, + 92E46DF41B634EA30035CD21 /* Frameworks */, + 92E46EA11B63615B0035CD21 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Game-mac"; + productName = "Game-mac"; + productReference = 92E46DF71B634EA30035CD21 /* Game-mac */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 92E46DEF1B634EA30035CD21 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Sanjay Madhav"; + TargetAttributes = { + 92E46DF61B634EA30035CD21 = { + CreatedOnToolsVersion = 6.4; + }; + }; + }; + buildConfigurationList = 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter05-mac" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 92E46DEE1B634EA30035CD21; + productRefGroup = 92E46DF81B634EA30035CD21 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 92E46DF61B634EA30035CD21 /* Game-mac */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 92E46EA11B63615B0035CD21 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -d \"$BUILD_DIR/Debug\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Debug\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Debug\nfi\n\nif [ -d \"$BUILD_DIR/Release\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Release\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Release\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 92E46DF31B634EA30035CD21 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 92E391991FE87F4800D8C362 /* Ship.cpp in Sources */, + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */, + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */, + 9223C4781F009428009A94D7 /* Game.cpp in Sources */, + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */, + 92CF0D791F3BBF140086A0F3 /* VertexArray.cpp in Sources */, + 9223C48B1F0CA3CE009A94D7 /* MoveComponent.cpp in Sources */, + 9206FDC91F140D40005078A2 /* Shader.cpp in Sources */, + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */, + 92E3919B1FE87F4800D8C362 /* Laser.cpp in Sources */, + 9223C4951F0CA766009A94D7 /* CircleComponent.cpp in Sources */, + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */, + 92E3919C1FE87F4800D8C362 /* Random.cpp in Sources */, + 92E391981FE87F4800D8C362 /* InputComponent.cpp in Sources */, + 92E3919A1FE87F4800D8C362 /* Asteroid.cpp in Sources */, + 9206FDC61F140707005078A2 /* Texture.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 92E46DFC1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 92E46DFD1B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 92E46DFF1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 92E46E001B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter05-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFC1B634EA40035CD21 /* Debug */, + 92E46DFD1B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFF1B634EA40035CD21 /* Debug */, + 92E46E001B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 92E46DEF1B634EA30035CD21 /* Project object */; +} diff --git a/Chapter05/Chapter05-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Chapter05/Chapter05-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..da50ea13 --- /dev/null +++ b/Chapter05/Chapter05-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Chapter05/Chapter05-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme b/Chapter05/Chapter05-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme new file mode 100644 index 00000000..39791b89 --- /dev/null +++ b/Chapter05/Chapter05-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter05/Chapter05-windows.sln b/Chapter05/Chapter05-windows.sln new file mode 100644 index 00000000..f3e55a49 --- /dev/null +++ b/Chapter05/Chapter05-windows.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2008 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "Game.vcxproj", "{BC508D87-495F-4554-932D-DD68388B63CC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.Build.0 = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.ActiveCfg = Release|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {84365DE3-728A-494A-A8A1-96281CA6772F} + EndGlobalSection +EndGlobal diff --git a/Chapter05/CircleComponent.cpp b/Chapter05/CircleComponent.cpp new file mode 100644 index 00000000..d41aab2c --- /dev/null +++ b/Chapter05/CircleComponent.cpp @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "CircleComponent.h" +#include "Actor.h" + +CircleComponent::CircleComponent(class Actor* owner) +:Component(owner) +,mRadius(0.0f) +{ + +} + +const Vector2& CircleComponent::GetCenter() const +{ + return mOwner->GetPosition(); +} + +float CircleComponent::GetRadius() const +{ + return mOwner->GetScale() * mRadius; +} + +bool Intersect(const CircleComponent& a, const CircleComponent& b) +{ + // Calculate distance squared + Vector2 diff = a.GetCenter() - b.GetCenter(); + float distSq = diff.LengthSq(); + + // Calculate sum of radii squared + float radiiSq = a.GetRadius() + b.GetRadius(); + radiiSq *= radiiSq; + + return distSq <= radiiSq; +} diff --git a/Chapter05/CircleComponent.h b/Chapter05/CircleComponent.h new file mode 100644 index 00000000..4eecb2a4 --- /dev/null +++ b/Chapter05/CircleComponent.h @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "Math.h" + +class CircleComponent : public Component +{ +public: + CircleComponent(class Actor* owner); + + void SetRadius(float radius) { mRadius = radius; } + float GetRadius() const; + + const Vector2& GetCenter() const; +private: + float mRadius; +}; + +bool Intersect(const CircleComponent& a, const CircleComponent& b); diff --git a/Chapter05/Component.cpp b/Chapter05/Component.cpp new file mode 100644 index 00000000..c4ed432d --- /dev/null +++ b/Chapter05/Component.cpp @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Component.h" +#include "Actor.h" + +Component::Component(Actor* owner, int updateOrder) + :mOwner(owner) + ,mUpdateOrder(updateOrder) +{ + // Add to actor's vector of components + mOwner->AddComponent(this); +} + +Component::~Component() +{ + mOwner->RemoveComponent(this); +} + +void Component::Update(float deltaTime) +{ +} diff --git a/Chapter05/Component.h b/Chapter05/Component.h new file mode 100644 index 00000000..e2be424b --- /dev/null +++ b/Chapter05/Component.h @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include + +class Component +{ +public: + // Constructor + // (the lower the update order, the earlier the component updates) + Component(class Actor* owner, int updateOrder = 100); + // Destructor + virtual ~Component(); + // Update this component by delta time + virtual void Update(float deltaTime); + // Process input for this component + virtual void ProcessInput(const uint8_t* keyState) {} + // Called when world transform changes + virtual void OnUpdateWorldTransform() { } + + int GetUpdateOrder() const { return mUpdateOrder; } +protected: + // Owning actor + class Actor* mOwner; + // Update order of component + int mUpdateOrder; +}; diff --git a/Chapter05/Game.cpp b/Chapter05/Game.cpp new file mode 100644 index 00000000..e18e90a3 --- /dev/null +++ b/Chapter05/Game.cpp @@ -0,0 +1,375 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" +#include +#include "Texture.h" +#include "VertexArray.h" +#include "Shader.h" +#include +#include "Actor.h" +#include "SpriteComponent.h" +#include "Actor.h" +#include "Ship.h" +#include "Asteroid.h" +#include "Random.h" + +Game::Game() +:mWindow(nullptr) +,mSpriteShader(nullptr) +,mIsRunning(true) +,mUpdatingActors(false) +{ + +} + +bool Game::Initialize() +{ + if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) + { + SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); + return false; + } + + // Set OpenGL attributes + // Use the core OpenGL profile + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + // Specify version 3.3 + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + // Request a color buffer with 8-bits per RGBA channel + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + // Enable double buffering + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + // Force OpenGL to use hardware acceleration + SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); + + mWindow = SDL_CreateWindow("Game Programming in C++ (Chapter 5)", 100, 100, + 1024, 768, SDL_WINDOW_OPENGL); + if (!mWindow) + { + SDL_Log("Failed to create window: %s", SDL_GetError()); + return false; + } + + // Create an OpenGL context + mContext = SDL_GL_CreateContext(mWindow); + + // Initialize GLEW + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) + { + SDL_Log("Failed to initialize GLEW."); + return false; + } + + // On some platforms, GLEW will emit a benign error code, + // so clear it + glGetError(); + + // Make sure we can create/compile shaders + if (!LoadShaders()) + { + SDL_Log("Failed to load shaders."); + return false; + } + + // Create quad for drawing sprites + CreateSpriteVerts(); + + LoadData(); + + mTicksCount = SDL_GetTicks(); + + return true; +} + +void Game::RunLoop() +{ + while (mIsRunning) + { + ProcessInput(); + UpdateGame(); + GenerateOutput(); + } +} + +void Game::ProcessInput() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + mIsRunning = false; + break; + } + } + + const Uint8* keyState = SDL_GetKeyboardState(NULL); + if (keyState[SDL_SCANCODE_ESCAPE]) + { + mIsRunning = false; + } + + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->ProcessInput(keyState); + } + mUpdatingActors = false; +} + +void Game::UpdateGame() +{ + // Compute delta time + // Wait until 16ms has elapsed since last frame + while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16)) + ; + + float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f; + if (deltaTime > 0.05f) + { + deltaTime = 0.05f; + } + mTicksCount = SDL_GetTicks(); + + // Update all actors + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->Update(deltaTime); + } + mUpdatingActors = false; + + // Move any pending actors to mActors + for (auto pending : mPendingActors) + { + pending->ComputeWorldTransform(); + mActors.emplace_back(pending); + } + mPendingActors.clear(); + + // Add any dead actors to a temp vector + std::vector deadActors; + for (auto actor : mActors) + { + if (actor->GetState() == Actor::EDead) + { + deadActors.emplace_back(actor); + } + } + + // Delete dead actors (which removes them from mActors) + for (auto actor : deadActors) + { + delete actor; + } +} + +void Game::GenerateOutput() +{ + // Set the clear color to grey + glClearColor(0.86f, 0.86f, 0.86f, 1.0f); + // Clear the color buffer + glClear(GL_COLOR_BUFFER_BIT); + + // Draw all sprite components + // Enable alpha blending on the color buffer + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Set shader/vao as active + mSpriteShader->SetActive(); + mSpriteVerts->SetActive(); + for (auto sprite : mSprites) + { + sprite->Draw(mSpriteShader); + } + + // Swap the buffers + SDL_GL_SwapWindow(mWindow); +} + +bool Game::LoadShaders() +{ + mSpriteShader = new Shader(); + if (!mSpriteShader->Load("Shaders/Sprite.vert", "Shaders/Sprite.frag")) + { + return false; + } + + mSpriteShader->SetActive(); + // Set the view-projection matrix + Matrix4 viewProj = Matrix4::CreateSimpleViewProj(1024.f, 768.f); + mSpriteShader->SetMatrixUniform("uViewProj", viewProj); + return true; +} + +void Game::CreateSpriteVerts() +{ + float vertices[] = { + -0.5f, 0.5f, 0.f, 0.f, 0.f, // top left + 0.5f, 0.5f, 0.f, 1.f, 0.f, // top right + 0.5f, -0.5f, 0.f, 1.f, 1.f, // bottom right + -0.5f, -0.5f, 0.f, 0.f, 1.f // bottom left + }; + + unsigned int indices[] = { + 0, 1, 2, + 2, 3, 0 + }; + + mSpriteVerts = new VertexArray(vertices, 4, indices, 6); +} + +void Game::LoadData() +{ + // Create player's ship + mShip = new Ship(this); + mShip->SetRotation(Math::PiOver2); + + // Create asteroids + const int numAsteroids = 20; + for (int i = 0; i < numAsteroids; i++) + { + new Asteroid(this); + } +} + +void Game::UnloadData() +{ + // Delete actors + // Because ~Actor calls RemoveActor, have to use a different style loop + while (!mActors.empty()) + { + delete mActors.back(); + } + + // Destroy textures + for (auto i : mTextures) + { + i.second->Unload(); + delete i.second; + } + mTextures.clear(); +} + +Texture* Game::GetTexture(const std::string& fileName) +{ + Texture* tex = nullptr; + auto iter = mTextures.find(fileName); + if (iter != mTextures.end()) + { + tex = iter->second; + } + else + { + tex = new Texture(); + if (tex->Load(fileName)) + { + mTextures.emplace(fileName, tex); + } + else + { + delete tex; + tex = nullptr; + } + } + return tex; +} + +void Game::AddAsteroid(Asteroid* ast) +{ + mAsteroids.emplace_back(ast); +} + +void Game::RemoveAsteroid(Asteroid* ast) +{ + auto iter = std::find(mAsteroids.begin(), + mAsteroids.end(), ast); + if (iter != mAsteroids.end()) + { + mAsteroids.erase(iter); + } +} + +void Game::Shutdown() +{ + UnloadData(); + delete mSpriteVerts; + mSpriteShader->Unload(); + delete mSpriteShader; + SDL_GL_DeleteContext(mContext); + SDL_DestroyWindow(mWindow); + SDL_Quit(); +} + +void Game::AddActor(Actor* actor) +{ + // If we're updating actors, need to add to pending + if (mUpdatingActors) + { + mPendingActors.emplace_back(actor); + } + else + { + mActors.emplace_back(actor); + } +} + +void Game::RemoveActor(Actor* actor) +{ + // Is it in pending actors? + auto iter = std::find(mPendingActors.begin(), mPendingActors.end(), actor); + if (iter != mPendingActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mPendingActors.end() - 1); + mPendingActors.pop_back(); + } + + // Is it in actors? + iter = std::find(mActors.begin(), mActors.end(), actor); + if (iter != mActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mActors.end() - 1); + mActors.pop_back(); + } +} + +void Game::AddSprite(SpriteComponent* sprite) +{ + // Find the insertion point in the sorted vector + // (The first element with a higher draw order than me) + int myDrawOrder = sprite->GetDrawOrder(); + auto iter = mSprites.begin(); + for (; + iter != mSprites.end(); + ++iter) + { + if (myDrawOrder < (*iter)->GetDrawOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mSprites.insert(iter, sprite); +} + +void Game::RemoveSprite(SpriteComponent* sprite) +{ + auto iter = std::find(mSprites.begin(), mSprites.end(), sprite); + mSprites.erase(iter); +} diff --git a/Chapter05/Game.h b/Chapter05/Game.h new file mode 100644 index 00000000..d5c22637 --- /dev/null +++ b/Chapter05/Game.h @@ -0,0 +1,71 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "SDL/SDL.h" +#include +#include +#include +#include "Math.h" + +class Game +{ +public: + Game(); + bool Initialize(); + void RunLoop(); + void Shutdown(); + + void AddActor(class Actor* actor); + void RemoveActor(class Actor* actor); + + void AddSprite(class SpriteComponent* sprite); + void RemoveSprite(class SpriteComponent* sprite); + + class Texture* GetTexture(const std::string& fileName); + + // Game-specific (add/remove asteroid) + void AddAsteroid(class Asteroid* ast); + void RemoveAsteroid(class Asteroid* ast); + std::vector& GetAsteroids() { return mAsteroids; } +private: + void ProcessInput(); + void UpdateGame(); + void GenerateOutput(); + bool LoadShaders(); + void CreateSpriteVerts(); + void LoadData(); + void UnloadData(); + + // Map of textures loaded + std::unordered_map mTextures; + + // All the actors in the game + std::vector mActors; + // Any pending actors + std::vector mPendingActors; + + // All the sprite components drawn + std::vector mSprites; + + // Sprite shader + class Shader* mSpriteShader; + // Sprite vertex array + class VertexArray* mSpriteVerts; + + SDL_Window* mWindow; + SDL_GLContext mContext; + Uint32 mTicksCount; + bool mIsRunning; + // Track if we're updating actors right now + bool mUpdatingActors; + + // Game-specific + class Ship* mShip; + std::vector mAsteroids; +}; diff --git a/Chapter05/Game.vcxproj b/Chapter05/Game.vcxproj new file mode 100644 index 00000000..70015946 --- /dev/null +++ b/Chapter05/Game.vcxproj @@ -0,0 +1,145 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {BC508D87-495F-4554-932D-DD68388B63CC} + Win32Proj + Game + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + /NODEFAULTLIB:msvcrt.lib %(AdditionalOptions) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + true + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + + \ No newline at end of file diff --git a/Chapter05/Game.vcxproj.filters b/Chapter05/Game.vcxproj.filters new file mode 100644 index 00000000..aca6a6bb --- /dev/null +++ b/Chapter05/Game.vcxproj.filters @@ -0,0 +1,126 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {12a47348-9a6e-404a-8d7e-2ffa91eb59f6} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + \ No newline at end of file diff --git a/Chapter05/InputComponent.cpp b/Chapter05/InputComponent.cpp new file mode 100644 index 00000000..148ffc95 --- /dev/null +++ b/Chapter05/InputComponent.cpp @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "InputComponent.h" +#include "Actor.h" + +InputComponent::InputComponent(class Actor* owner) +:MoveComponent(owner) +,mForwardKey(0) +,mBackKey(0) +,mClockwiseKey(0) +,mCounterClockwiseKey(0) +{ + +} + +void InputComponent::ProcessInput(const uint8_t* keyState) +{ + // Calculate forward speed for MoveComponent + float forwardSpeed = 0.0f; + if (keyState[mForwardKey]) + { + forwardSpeed += mMaxForwardSpeed; + } + if (keyState[mBackKey]) + { + forwardSpeed -= mMaxForwardSpeed; + } + SetForwardSpeed(forwardSpeed); + + // Calculate angular speed for MoveComponent + float angularSpeed = 0.0f; + if (keyState[mClockwiseKey]) + { + angularSpeed += mMaxAngularSpeed; + } + if (keyState[mCounterClockwiseKey]) + { + angularSpeed -= mMaxAngularSpeed; + } + SetAngularSpeed(angularSpeed); +} diff --git a/Chapter05/InputComponent.h b/Chapter05/InputComponent.h new file mode 100644 index 00000000..57b32ccb --- /dev/null +++ b/Chapter05/InputComponent.h @@ -0,0 +1,45 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "MoveComponent.h" +#include + +class InputComponent : public MoveComponent +{ +public: + // Lower update order to update first + InputComponent(class Actor* owner); + + void ProcessInput(const uint8_t* keyState) override; + + // Getters/setters for private variables + float GetMaxForward() const { return mMaxForwardSpeed; } + float GetMaxAngular() const { return mMaxAngularSpeed; } + int GetForwardKey() const { return mForwardKey; } + int GetBackKey() const { return mBackKey; } + int GetClockwiseKey() const { return mClockwiseKey; } + int GetCounterClockwiseKey() const { return mCounterClockwiseKey; } + + void SetMaxForwardSpeed(float speed) { mMaxForwardSpeed = speed; } + void SetMaxAngularSpeed(float speed) { mMaxAngularSpeed = speed; } + void SetForwardKey(int key) { mForwardKey = key; } + void SetBackKey(int key) { mBackKey = key; } + void SetClockwiseKey(int key) { mClockwiseKey = key; } + void SetCounterClockwiseKey(int key) { mCounterClockwiseKey = key; } +private: + // The maximum forward/angular speeds + float mMaxForwardSpeed; + float mMaxAngularSpeed; + // Keys for forward/back movement + int mForwardKey; + int mBackKey; + // Keys for angular movement + int mClockwiseKey; + int mCounterClockwiseKey; +}; diff --git a/Chapter05/Laser.cpp b/Chapter05/Laser.cpp new file mode 100644 index 00000000..a03c1981 --- /dev/null +++ b/Chapter05/Laser.cpp @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Laser.h" +#include "SpriteComponent.h" +#include "MoveComponent.h" +#include "Game.h" +#include "CircleComponent.h" +#include "Asteroid.h" + +Laser::Laser(Game* game) + :Actor(game) + ,mDeathTimer(1.0f) +{ + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this); + sc->SetTexture(game->GetTexture("Assets/Laser.png")); + + // Create a move component, and set a forward speed + MoveComponent* mc = new MoveComponent(this); + mc->SetForwardSpeed(800.0f); + + // Create a circle component (for collision) + mCircle = new CircleComponent(this); + mCircle->SetRadius(11.0f); +} + +void Laser::UpdateActor(float deltaTime) +{ + // If we run out of time, laser is dead + mDeathTimer -= deltaTime; + if (mDeathTimer <= 0.0f) + { + SetState(EDead); + } + else + { + // Do we intersect with an asteroid? + for (auto ast : GetGame()->GetAsteroids()) + { + if (Intersect(*mCircle, *(ast->GetCircle()))) + { + // The first asteroid we intersect with, + // set ourselves and the asteroid to dead + SetState(EDead); + ast->SetState(EDead); + break; + } + } + } +} diff --git a/Chapter05/Laser.h b/Chapter05/Laser.h new file mode 100644 index 00000000..f1afd638 --- /dev/null +++ b/Chapter05/Laser.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Laser : public Actor +{ +public: + Laser(class Game* game); + + void UpdateActor(float deltaTime) override; +private: + class CircleComponent* mCircle; + float mDeathTimer; +}; diff --git a/Chapter05/Main.cpp b/Chapter05/Main.cpp new file mode 100644 index 00000000..22ea0c69 --- /dev/null +++ b/Chapter05/Main.cpp @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" + +int main(int argc, char** argv) +{ + Game game; + bool success = game.Initialize(); + if (success) + { + game.RunLoop(); + } + game.Shutdown(); + return 0; +} diff --git a/Chapter05/Math.cpp b/Chapter05/Math.cpp new file mode 100644 index 00000000..a16e7261 --- /dev/null +++ b/Chapter05/Math.cpp @@ -0,0 +1,239 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Math.h" + +const Vector2 Vector2::Zero(0.0f, 0.0f); +const Vector2 Vector2::UnitX(1.0f, 0.0f); +const Vector2 Vector2::UnitY(0.0f, 1.0f); +const Vector2 Vector2::NegUnitX(-1.0f, 0.0f); +const Vector2 Vector2::NegUnitY(0.0f, -1.0f); + +const Vector3 Vector3::Zero(0.0f, 0.0f, 0.f); +const Vector3 Vector3::UnitX(1.0f, 0.0f, 0.0f); +const Vector3 Vector3::UnitY(0.0f, 1.0f, 0.0f); +const Vector3 Vector3::UnitZ(0.0f, 0.0f, 1.0f); +const Vector3 Vector3::NegUnitX(-1.0f, 0.0f, 0.0f); +const Vector3 Vector3::NegUnitY(0.0f, -1.0f, 0.0f); +const Vector3 Vector3::NegUnitZ(0.0f, 0.0f, -1.0f); +const Vector3 Vector3::Infinity(Math::Infinity, Math::Infinity, Math::Infinity); +const Vector3 Vector3::NegInfinity(Math::NegInfinity, Math::NegInfinity, Math::NegInfinity); + +static float m3Ident[3][3] = +{ + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f } +}; +const Matrix3 Matrix3::Identity(m3Ident); + +static float m4Ident[4][4] = +{ + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; + +const Matrix4 Matrix4::Identity(m4Ident); + +const Quaternion Quaternion::Identity(0.0f, 0.0f, 0.0f, 1.0f); + +Vector2 Vector2::Transform(const Vector2& vec, const Matrix3& mat, float w /*= 1.0f*/) +{ + Vector2 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + w * mat.mat[2][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + w * mat.mat[2][1]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +Vector3 Vector3::Transform(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +// This will transform the vector and renormalize the w component +Vector3 Vector3::TransformWithPerspDiv(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + float transformedW = vec.x * mat.mat[0][3] + vec.y * mat.mat[1][3] + + vec.z * mat.mat[2][3] + w * mat.mat[3][3]; + if (!Math::NearZero(Math::Abs(transformedW))) + { + transformedW = 1.0f / transformedW; + retVal *= transformedW; + } + return retVal; +} + +// Transform a Vector3 by a quaternion +Vector3 Vector3::Transform(const Vector3& v, const Quaternion& q) +{ + // v + 2.0*cross(q.xyz, cross(q.xyz,v) + q.w*v); + Vector3 qv(q.x, q.y, q.z); + Vector3 retVal = v; + retVal += 2.0f * Vector3::Cross(qv, Vector3::Cross(qv, v) + q.w * v); + return retVal; +} + +void Matrix4::Invert() +{ + // Thanks slow math + // This is a really janky way to unroll everything... + float tmp[12]; + float src[16]; + float dst[16]; + float det; + + // Transpose matrix + // row 1 to col 1 + src[0] = mat[0][0]; + src[4] = mat[0][1]; + src[8] = mat[0][2]; + src[12] = mat[0][3]; + + // row 2 to col 2 + src[1] = mat[1][0]; + src[5] = mat[1][1]; + src[9] = mat[1][2]; + src[13] = mat[1][3]; + + // row 3 to col 3 + src[2] = mat[2][0]; + src[6] = mat[2][1]; + src[10] = mat[2][2]; + src[14] = mat[2][3]; + + // row 4 to col 4 + src[3] = mat[3][0]; + src[7] = mat[3][1]; + src[11] = mat[3][2]; + src[15] = mat[3][3]; + + // Calculate cofactors + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + dst[0] = tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]; + dst[0] -= tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]; + dst[1] = tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]; + dst[1] -= tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]; + dst[2] = tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]; + dst[2] -= tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]; + dst[3] = tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]; + dst[3] -= tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]; + dst[4] = tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]; + dst[4] -= tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]; + dst[5] = tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]; + dst[5] -= tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]; + dst[6] = tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]; + dst[6] -= tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]; + dst[7] = tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]; + dst[7] -= tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]; + + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + dst[8] = tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]; + dst[8] -= tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]; + dst[9] = tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]; + dst[9] -= tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]; + dst[10] = tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]; + dst[10] -= tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]; + dst[11] = tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]; + dst[11] -= tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]; + dst[12] = tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]; + dst[12] -= tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]; + dst[13] = tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]; + dst[13] -= tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]; + dst[14] = tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]; + dst[14] -= tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]; + dst[15] = tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]; + dst[15] -= tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]; + + // Calculate determinant + det = src[0] * dst[0] + src[1] * dst[1] + src[2] * dst[2] + src[3] * dst[3]; + + // Inverse of matrix is divided by determinant + det = 1 / det; + for (int j = 0; j < 16; j++) + { + dst[j] *= det; + } + + // Set it back + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + mat[i][j] = dst[i * 4 + j]; + } + } +} + +Matrix4 Matrix4::CreateFromQuaternion(const class Quaternion& q) +{ + float mat[4][4]; + + mat[0][0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z; + mat[0][1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z; + mat[0][2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y; + mat[0][3] = 0.0f; + + mat[1][0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z; + mat[1][1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z; + mat[1][2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x; + mat[1][3] = 0.0f; + + mat[2][0] = 2.0f * q.x * q.z + 2.0f * q.w * q.y; + mat[2][1] = 2.0f * q.y * q.z - 2.0f * q.w * q.x; + mat[2][2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y; + mat[2][3] = 0.0f; + + mat[3][0] = 0.0f; + mat[3][1] = 0.0f; + mat[3][2] = 0.0f; + mat[3][3] = 1.0f; + + return Matrix4(mat); +} diff --git a/Chapter05/Math.h b/Chapter05/Math.h new file mode 100644 index 00000000..752963f1 --- /dev/null +++ b/Chapter05/Math.h @@ -0,0 +1,1033 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +namespace Math +{ + const float Pi = 3.1415926535f; + const float TwoPi = Pi * 2.0f; + const float PiOver2 = Pi / 2.0f; + const float Infinity = std::numeric_limits::infinity(); + const float NegInfinity = -std::numeric_limits::infinity(); + + inline float ToRadians(float degrees) + { + return degrees * Pi / 180.0f; + } + + inline float ToDegrees(float radians) + { + return radians * 180.0f / Pi; + } + + inline bool NearZero(float val, float epsilon = 0.001f) + { + if (fabs(val) <= epsilon) + { + return true; + } + else + { + return false; + } + } + + template + T Max(const T& a, const T& b) + { + return (a < b ? b : a); + } + + template + T Min(const T& a, const T& b) + { + return (a < b ? a : b); + } + + template + T Clamp(const T& value, const T& lower, const T& upper) + { + return Min(upper, Max(lower, value)); + } + + inline float Abs(float value) + { + return fabs(value); + } + + inline float Cos(float angle) + { + return cosf(angle); + } + + inline float Sin(float angle) + { + return sinf(angle); + } + + inline float Tan(float angle) + { + return tanf(angle); + } + + inline float Acos(float value) + { + return acosf(value); + } + + inline float Atan2(float y, float x) + { + return atan2f(y, x); + } + + inline float Cot(float angle) + { + return 1.0f / Tan(angle); + } + + inline float Lerp(float a, float b, float f) + { + return a + f * (b - a); + } + + inline float Sqrt(float value) + { + return sqrtf(value); + } + + inline float Fmod(float numer, float denom) + { + return fmod(numer, denom); + } +} + +// 2D Vector +class Vector2 +{ +public: + float x; + float y; + + Vector2() + :x(0.0f) + ,y(0.0f) + {} + + explicit Vector2(float inX, float inY) + :x(inX) + ,y(inY) + {} + + // Set both components in one line + void Set(float inX, float inY) + { + x = inX; + y = inY; + } + + // Vector addition (a + b) + friend Vector2 operator+(const Vector2& a, const Vector2& b) + { + return Vector2(a.x + b.x, a.y + b.y); + } + + // Vector subtraction (a - b) + friend Vector2 operator-(const Vector2& a, const Vector2& b) + { + return Vector2(a.x - b.x, a.y - b.y); + } + + // Component-wise multiplication + // (a.x * b.x, ...) + friend Vector2 operator*(const Vector2& a, const Vector2& b) + { + return Vector2(a.x * b.x, a.y * b.y); + } + + // Scalar multiplication + friend Vector2 operator*(const Vector2& vec, float scalar) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar multiplication + friend Vector2 operator*(float scalar, const Vector2& vec) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar *= + Vector2& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + return *this; + } + + // Vector += + Vector2& operator+=(const Vector2& right) + { + x += right.x; + y += right.y; + return *this; + } + + // Vector -= + Vector2& operator-=(const Vector2& right) + { + x -= right.x; + y -= right.y; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + } + + // Normalize the provided vector + static Vector2 Normalize(const Vector2& vec) + { + Vector2 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector2& a, const Vector2& b) + { + return (a.x * b.x + a.y * b.y); + } + + // Lerp from A to B by f + static Vector2 Lerp(const Vector2& a, const Vector2& b, float f) + { + return Vector2(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector2 Reflect(const Vector2& v, const Vector2& n) + { + return v - 2.0f * Vector2::Dot(v, n) * n; + } + + // Transform vector by matrix + static Vector2 Transform(const Vector2& vec, const class Matrix3& mat, float w = 1.0f); + + static const Vector2 Zero; + static const Vector2 UnitX; + static const Vector2 UnitY; + static const Vector2 NegUnitX; + static const Vector2 NegUnitY; +}; + +// 3D Vector +class Vector3 +{ +public: + float x; + float y; + float z; + + Vector3() + :x(0.0f) + ,y(0.0f) + ,z(0.0f) + {} + + explicit Vector3(float inX, float inY, float inZ) + :x(inX) + ,y(inY) + ,z(inZ) + {} + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&x); + } + + // Set all three components in one line + void Set(float inX, float inY, float inZ) + { + x = inX; + y = inY; + z = inZ; + } + + // Vector addition (a + b) + friend Vector3 operator+(const Vector3& a, const Vector3& b) + { + return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); + } + + // Vector subtraction (a - b) + friend Vector3 operator-(const Vector3& a, const Vector3& b) + { + return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); + } + + // Component-wise multiplication + friend Vector3 operator*(const Vector3& left, const Vector3& right) + { + return Vector3(left.x * right.x, left.y * right.y, left.z * right.z); + } + + // Scalar multiplication + friend Vector3 operator*(const Vector3& vec, float scalar) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar multiplication + friend Vector3 operator*(float scalar, const Vector3& vec) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar *= + Vector3& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + z *= scalar; + return *this; + } + + // Vector += + Vector3& operator+=(const Vector3& right) + { + x += right.x; + y += right.y; + z += right.z; + return *this; + } + + // Vector -= + Vector3& operator-=(const Vector3& right) + { + x -= right.x; + y -= right.y; + z -= right.z; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y + z*z); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + } + + // Normalize the provided vector + static Vector3 Normalize(const Vector3& vec) + { + Vector3 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector3& a, const Vector3& b) + { + return (a.x * b.x + a.y * b.y + a.z * b.z); + } + + // Cross product between two vectors (a cross b) + static Vector3 Cross(const Vector3& a, const Vector3& b) + { + Vector3 temp; + temp.x = a.y * b.z - a.z * b.y; + temp.y = a.z * b.x - a.x * b.z; + temp.z = a.x * b.y - a.y * b.x; + return temp; + } + + // Lerp from A to B by f + static Vector3 Lerp(const Vector3& a, const Vector3& b, float f) + { + return Vector3(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector3 Reflect(const Vector3& v, const Vector3& n) + { + return v - 2.0f * Vector3::Dot(v, n) * n; + } + + static Vector3 Transform(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + // This will transform the vector and renormalize the w component + static Vector3 TransformWithPerspDiv(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + + // Transform a Vector3 by a quaternion + static Vector3 Transform(const Vector3& v, const class Quaternion& q); + + static const Vector3 Zero; + static const Vector3 UnitX; + static const Vector3 UnitY; + static const Vector3 UnitZ; + static const Vector3 NegUnitX; + static const Vector3 NegUnitY; + static const Vector3 NegUnitZ; + static const Vector3 Infinity; + static const Vector3 NegInfinity; +}; + +// 3x3 Matrix +class Matrix3 +{ +public: + float mat[3][3]; + + Matrix3() + { + *this = Matrix3::Identity; + } + + explicit Matrix3(float inMat[3][3]) + { + memcpy(mat, inMat, 9 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication + friend Matrix3 operator*(const Matrix3& left, const Matrix3& right) + { + Matrix3 retVal; + // row 0 + retVal.mat[0][0] = + left.mat[0][0] * right.mat[0][0] + + left.mat[0][1] * right.mat[1][0] + + left.mat[0][2] * right.mat[2][0]; + + retVal.mat[0][1] = + left.mat[0][0] * right.mat[0][1] + + left.mat[0][1] * right.mat[1][1] + + left.mat[0][2] * right.mat[2][1]; + + retVal.mat[0][2] = + left.mat[0][0] * right.mat[0][2] + + left.mat[0][1] * right.mat[1][2] + + left.mat[0][2] * right.mat[2][2]; + + // row 1 + retVal.mat[1][0] = + left.mat[1][0] * right.mat[0][0] + + left.mat[1][1] * right.mat[1][0] + + left.mat[1][2] * right.mat[2][0]; + + retVal.mat[1][1] = + left.mat[1][0] * right.mat[0][1] + + left.mat[1][1] * right.mat[1][1] + + left.mat[1][2] * right.mat[2][1]; + + retVal.mat[1][2] = + left.mat[1][0] * right.mat[0][2] + + left.mat[1][1] * right.mat[1][2] + + left.mat[1][2] * right.mat[2][2]; + + // row 2 + retVal.mat[2][0] = + left.mat[2][0] * right.mat[0][0] + + left.mat[2][1] * right.mat[1][0] + + left.mat[2][2] * right.mat[2][0]; + + retVal.mat[2][1] = + left.mat[2][0] * right.mat[0][1] + + left.mat[2][1] * right.mat[1][1] + + left.mat[2][2] * right.mat[2][1]; + + retVal.mat[2][2] = + left.mat[2][0] * right.mat[0][2] + + left.mat[2][1] * right.mat[1][2] + + left.mat[2][2] * right.mat[2][2]; + + return retVal; + } + + Matrix3& operator*=(const Matrix3& right) + { + *this = *this * right; + return *this; + } + + // Create a scale matrix with x and y scales + static Matrix3 CreateScale(float xScale, float yScale) + { + float temp[3][3] = + { + { xScale, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + static Matrix3 CreateScale(const Vector2& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y); + } + + // Create a scale matrix with a uniform factor + static Matrix3 CreateScale(float scale) + { + return CreateScale(scale, scale); + } + + // Create a rotation matrix about the Z axis + // theta is in radians + static Matrix3 CreateRotation(float theta) + { + float temp[3][3] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + // Create a translation matrix (on the xy-plane) + static Matrix3 CreateTranslation(const Vector2& trans) + { + float temp[3][3] = + { + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, 1.0f }, + }; + return Matrix3(temp); + } + + static const Matrix3 Identity; +}; + +// 4x4 Matrix +class Matrix4 +{ +public: + float mat[4][4]; + + Matrix4() + { + *this = Matrix4::Identity; + } + + explicit Matrix4(float inMat[4][4]) + { + memcpy(mat, inMat, 16 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication (a * b) + friend Matrix4 operator*(const Matrix4& a, const Matrix4& b) + { + Matrix4 retVal; + // row 0 + retVal.mat[0][0] = + a.mat[0][0] * b.mat[0][0] + + a.mat[0][1] * b.mat[1][0] + + a.mat[0][2] * b.mat[2][0] + + a.mat[0][3] * b.mat[3][0]; + + retVal.mat[0][1] = + a.mat[0][0] * b.mat[0][1] + + a.mat[0][1] * b.mat[1][1] + + a.mat[0][2] * b.mat[2][1] + + a.mat[0][3] * b.mat[3][1]; + + retVal.mat[0][2] = + a.mat[0][0] * b.mat[0][2] + + a.mat[0][1] * b.mat[1][2] + + a.mat[0][2] * b.mat[2][2] + + a.mat[0][3] * b.mat[3][2]; + + retVal.mat[0][3] = + a.mat[0][0] * b.mat[0][3] + + a.mat[0][1] * b.mat[1][3] + + a.mat[0][2] * b.mat[2][3] + + a.mat[0][3] * b.mat[3][3]; + + // row 1 + retVal.mat[1][0] = + a.mat[1][0] * b.mat[0][0] + + a.mat[1][1] * b.mat[1][0] + + a.mat[1][2] * b.mat[2][0] + + a.mat[1][3] * b.mat[3][0]; + + retVal.mat[1][1] = + a.mat[1][0] * b.mat[0][1] + + a.mat[1][1] * b.mat[1][1] + + a.mat[1][2] * b.mat[2][1] + + a.mat[1][3] * b.mat[3][1]; + + retVal.mat[1][2] = + a.mat[1][0] * b.mat[0][2] + + a.mat[1][1] * b.mat[1][2] + + a.mat[1][2] * b.mat[2][2] + + a.mat[1][3] * b.mat[3][2]; + + retVal.mat[1][3] = + a.mat[1][0] * b.mat[0][3] + + a.mat[1][1] * b.mat[1][3] + + a.mat[1][2] * b.mat[2][3] + + a.mat[1][3] * b.mat[3][3]; + + // row 2 + retVal.mat[2][0] = + a.mat[2][0] * b.mat[0][0] + + a.mat[2][1] * b.mat[1][0] + + a.mat[2][2] * b.mat[2][0] + + a.mat[2][3] * b.mat[3][0]; + + retVal.mat[2][1] = + a.mat[2][0] * b.mat[0][1] + + a.mat[2][1] * b.mat[1][1] + + a.mat[2][2] * b.mat[2][1] + + a.mat[2][3] * b.mat[3][1]; + + retVal.mat[2][2] = + a.mat[2][0] * b.mat[0][2] + + a.mat[2][1] * b.mat[1][2] + + a.mat[2][2] * b.mat[2][2] + + a.mat[2][3] * b.mat[3][2]; + + retVal.mat[2][3] = + a.mat[2][0] * b.mat[0][3] + + a.mat[2][1] * b.mat[1][3] + + a.mat[2][2] * b.mat[2][3] + + a.mat[2][3] * b.mat[3][3]; + + // row 3 + retVal.mat[3][0] = + a.mat[3][0] * b.mat[0][0] + + a.mat[3][1] * b.mat[1][0] + + a.mat[3][2] * b.mat[2][0] + + a.mat[3][3] * b.mat[3][0]; + + retVal.mat[3][1] = + a.mat[3][0] * b.mat[0][1] + + a.mat[3][1] * b.mat[1][1] + + a.mat[3][2] * b.mat[2][1] + + a.mat[3][3] * b.mat[3][1]; + + retVal.mat[3][2] = + a.mat[3][0] * b.mat[0][2] + + a.mat[3][1] * b.mat[1][2] + + a.mat[3][2] * b.mat[2][2] + + a.mat[3][3] * b.mat[3][2]; + + retVal.mat[3][3] = + a.mat[3][0] * b.mat[0][3] + + a.mat[3][1] * b.mat[1][3] + + a.mat[3][2] * b.mat[2][3] + + a.mat[3][3] * b.mat[3][3]; + + return retVal; + } + + Matrix4& operator*=(const Matrix4& right) + { + *this = *this * right; + return *this; + } + + // Invert the matrix - super slow + void Invert(); + + // Get the translation component of the matrix + Vector3 GetTranslation() const + { + return Vector3(mat[3][0], mat[3][1], mat[3][2]); + } + + // Get the X axis of the matrix (forward) + Vector3 GetXAxis() const + { + return Vector3::Normalize(Vector3(mat[0][0], mat[0][1], mat[0][2])); + } + + // Get the Y axis of the matrix (left) + Vector3 GetYAxis() const + { + return Vector3::Normalize(Vector3(mat[1][0], mat[1][1], mat[1][2])); + } + + // Get the Z axis of the matrix (up) + Vector3 GetZAxis() const + { + return Vector3::Normalize(Vector3(mat[2][0], mat[2][1], mat[2][2])); + } + + // Extract the scale component from the matrix + Vector3 GetScale() const + { + Vector3 retVal; + retVal.x = Vector3(mat[0][0], mat[0][1], mat[0][2]).Length(); + retVal.y = Vector3(mat[1][0], mat[1][1], mat[1][2]).Length(); + retVal.z = Vector3(mat[2][0], mat[2][1], mat[2][2]).Length(); + return retVal; + } + + // Create a scale matrix with x, y, and z scales + static Matrix4 CreateScale(float xScale, float yScale, float zScale) + { + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, zScale, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateScale(const Vector3& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y, scaleVector.z); + } + + // Create a scale matrix with a uniform factor + static Matrix4 CreateScale(float scale) + { + return CreateScale(scale, scale, scale); + } + + // Rotation about x-axis + static Matrix4 CreateRotationX(float theta) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f , 0.0f }, + { 0.0f, Math::Cos(theta), Math::Sin(theta), 0.0f }, + { 0.0f, -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about y-axis + static Matrix4 CreateRotationY(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), 0.0f, -Math::Sin(theta), 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { Math::Sin(theta), 0.0f, Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about z-axis + static Matrix4 CreateRotationZ(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f, 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Create a rotation matrix from a quaternion + static Matrix4 CreateFromQuaternion(const class Quaternion& q); + + static Matrix4 CreateTranslation(const Vector3& trans) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateLookAt(const Vector3& eye, const Vector3& target, const Vector3& up) + { + Vector3 zaxis = Vector3::Normalize(target - eye); + Vector3 xaxis = Vector3::Normalize(Vector3::Cross(up, zaxis)); + Vector3 yaxis = Vector3::Normalize(Vector3::Cross(zaxis, xaxis)); + Vector3 trans; + trans.x = -Vector3::Dot(xaxis, eye); + trans.y = -Vector3::Dot(yaxis, eye); + trans.z = -Vector3::Dot(zaxis, eye); + + float temp[4][4] = + { + { xaxis.x, yaxis.x, zaxis.x, 0.0f }, + { xaxis.y, yaxis.y, zaxis.y, 0.0f }, + { xaxis.z, yaxis.z, zaxis.z, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateOrtho(float width, float height, float near, float far) + { + float temp[4][4] = + { + { 2.0f / width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f / height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f / (far - near), 0.0f }, + { 0.0f, 0.0f, near / (near - far), 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreatePerspectiveFOV(float fovY, float width, float height, float near, float far) + { + float yScale = Math::Cot(fovY / 2.0f); + float xScale = yScale * height / width; + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, far / (far - near), 1.0f }, + { 0.0f, 0.0f, -near * far / (far - near), 0.0f } + }; + return Matrix4(temp); + } + + // Create "Simple" View-Projection Matrix from Chapter 6 + static Matrix4 CreateSimpleViewProj(float width, float height) + { + float temp[4][4] = + { + { 2.0f/width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f } + }; + return Matrix4(temp); + } + + static const Matrix4 Identity; +}; + +// (Unit) Quaternion +class Quaternion +{ +public: + float x; + float y; + float z; + float w; + + Quaternion() + { + *this = Quaternion::Identity; + } + + // This directly sets the quaternion components -- + // don't use for axis/angle + explicit Quaternion(float inX, float inY, float inZ, float inW) + { + Set(inX, inY, inZ, inW); + } + + // Construct the quaternion from an axis and angle + // It is assumed that axis is already normalized, + // and the angle is in radians + explicit Quaternion(const Vector3& axis, float angle) + { + float scalar = Math::Sin(angle / 2.0f); + x = axis.x * scalar; + y = axis.y * scalar; + z = axis.z * scalar; + w = Math::Cos(angle / 2.0f); + } + + // Directly set the internal components + void Set(float inX, float inY, float inZ, float inW) + { + x = inX; + y = inY; + z = inZ; + w = inW; + } + + void Conjugate() + { + x *= -1.0f; + y *= -1.0f; + z *= -1.0f; + } + + float LengthSq() const + { + return (x*x + y*y + z*z + w*w); + } + + float Length() const + { + return Math::Sqrt(LengthSq()); + } + + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + w /= length; + } + + // Normalize the provided quaternion + static Quaternion Normalize(const Quaternion& q) + { + Quaternion retVal = q; + retVal.Normalize(); + return retVal; + } + + // Linear interpolation + static Quaternion Lerp(const Quaternion& a, const Quaternion& b, float f) + { + Quaternion retVal; + retVal.x = Math::Lerp(a.x, b.x, f); + retVal.y = Math::Lerp(a.y, b.y, f); + retVal.z = Math::Lerp(a.z, b.z, f); + retVal.w = Math::Lerp(a.w, b.w, f); + retVal.Normalize(); + return retVal; + } + + static float Dot(const Quaternion& a, const Quaternion& b) + { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + // Spherical Linear Interpolation + static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float f) + { + float rawCosm = Quaternion::Dot(a, b); + + float cosom = -rawCosm; + if (rawCosm >= 0.0f) + { + cosom = rawCosm; + } + + float scale0, scale1; + + if (cosom < 0.9999f) + { + const float omega = Math::Acos(cosom); + const float invSin = 1.f / Math::Sin(omega); + scale0 = Math::Sin((1.f - f) * omega) * invSin; + scale1 = Math::Sin(f * omega) * invSin; + } + else + { + // Use linear interpolation if the quaternions + // are collinear + scale0 = 1.0f - f; + scale1 = f; + } + + if (rawCosm < 0.0f) + { + scale1 = -scale1; + } + + Quaternion retVal; + retVal.x = scale0 * a.x + scale1 * b.x; + retVal.y = scale0 * a.y + scale1 * b.y; + retVal.z = scale0 * a.z + scale1 * b.z; + retVal.w = scale0 * a.w + scale1 * b.w; + retVal.Normalize(); + return retVal; + } + + // Concatenate + // Rotate by q FOLLOWED BY p + static Quaternion Concatenate(const Quaternion& q, const Quaternion& p) + { + Quaternion retVal; + + // Vector component is: + // ps * qv + qs * pv + pv x qv + Vector3 qv(q.x, q.y, q.z); + Vector3 pv(p.x, p.y, p.z); + Vector3 newVec = p.w * qv + q.w * pv + Vector3::Cross(pv, qv); + retVal.x = newVec.x; + retVal.y = newVec.y; + retVal.z = newVec.z; + + // Scalar component is: + // ps * qs - pv . qv + retVal.w = p.w * q.w - Vector3::Dot(pv, qv); + + return retVal; + } + + static const Quaternion Identity; +}; + +namespace Color +{ + static const Vector3 Black(0.0f, 0.0f, 0.0f); + static const Vector3 White(1.0f, 1.0f, 1.0f); + static const Vector3 Red(1.0f, 0.0f, 0.0f); + static const Vector3 Green(0.0f, 1.0f, 0.0f); + static const Vector3 Blue(0.0f, 0.0f, 1.0f); + static const Vector3 Yellow(1.0f, 1.0f, 0.0f); + static const Vector3 LightYellow(1.0f, 1.0f, 0.88f); + static const Vector3 LightBlue(0.68f, 0.85f, 0.9f); + static const Vector3 LightPink(1.0f, 0.71f, 0.76f); + static const Vector3 LightGreen(0.56f, 0.93f, 0.56f); +} diff --git a/Chapter05/MoveComponent.cpp b/Chapter05/MoveComponent.cpp new file mode 100644 index 00000000..c1a5d5b2 --- /dev/null +++ b/Chapter05/MoveComponent.cpp @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "MoveComponent.h" +#include "Actor.h" + +MoveComponent::MoveComponent(class Actor* owner, int updateOrder) +:Component(owner, updateOrder) +,mAngularSpeed(0.0f) +,mForwardSpeed(0.0f) +{ + +} + +void MoveComponent::Update(float deltaTime) +{ + if (!Math::NearZero(mAngularSpeed)) + { + float rot = mOwner->GetRotation(); + rot += mAngularSpeed * deltaTime; + mOwner->SetRotation(rot); + } + + if (!Math::NearZero(mForwardSpeed)) + { + Vector2 pos = mOwner->GetPosition(); + pos += mOwner->GetForward() * mForwardSpeed * deltaTime; + + // Screen wrapping (for asteroids) + if (pos.x < -512.0f) { pos.x = 510.0f; } + else if (pos.x > 512.0f) { pos.x = -510.0f; } + if (pos.y < -384.0f) { pos.y = 382.0f; } + else if (pos.y > 384.0f) { pos.y = -382.0f; } + mOwner->SetPosition(pos); + } +} diff --git a/Chapter05/MoveComponent.h b/Chapter05/MoveComponent.h new file mode 100644 index 00000000..def7d389 --- /dev/null +++ b/Chapter05/MoveComponent.h @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" + +class MoveComponent : public Component +{ +public: + // Lower update order to update first + MoveComponent(class Actor* owner, int updateOrder = 10); + void Update(float deltaTime) override; + + float GetAngularSpeed() const { return mAngularSpeed; } + float GetForwardSpeed() const { return mForwardSpeed; } + void SetAngularSpeed(float speed) { mAngularSpeed = speed; } + void SetForwardSpeed(float speed) { mForwardSpeed = speed; } +private: + float mAngularSpeed; + float mForwardSpeed; +}; diff --git a/Chapter05/Random.cpp b/Chapter05/Random.cpp new file mode 100644 index 00000000..05a3a32a --- /dev/null +++ b/Chapter05/Random.cpp @@ -0,0 +1,51 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Random.h" + +void Random::Init() +{ + std::random_device rd; + Random::Seed(rd()); +} + +void Random::Seed(unsigned int seed) +{ + sGenerator.seed(seed); +} + +float Random::GetFloat() +{ + return GetFloatRange(0.0f, 1.0f); +} + +float Random::GetFloatRange(float min, float max) +{ + std::uniform_real_distribution dist(min, max); + return dist(sGenerator); +} + +int Random::GetIntRange(int min, int max) +{ + std::uniform_int_distribution dist(min, max); + return dist(sGenerator); +} + +Vector2 Random::GetVector(const Vector2& min, const Vector2& max) +{ + Vector2 r = Vector2(GetFloat(), GetFloat()); + return min + (max - min) * r; +} + +Vector3 Random::GetVector(const Vector3& min, const Vector3& max) +{ + Vector3 r = Vector3(GetFloat(), GetFloat(), GetFloat()); + return min + (max - min) * r; +} + +std::mt19937 Random::sGenerator; diff --git a/Chapter05/Random.h b/Chapter05/Random.h new file mode 100644 index 00000000..3ae92fe5 --- /dev/null +++ b/Chapter05/Random.h @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include "Math.h" + +class Random +{ +public: + static void Init(); + + // Seed the generator with the specified int + // NOTE: You should generally not need to manually use this + static void Seed(unsigned int seed); + + // Get a float between 0.0f and 1.0f + static float GetFloat(); + + // Get a float from the specified range + static float GetFloatRange(float min, float max); + + // Get an int from the specified range + static int GetIntRange(int min, int max); + + // Get a random vector given the min/max bounds + static Vector2 GetVector(const Vector2& min, const Vector2& max); + static Vector3 GetVector(const Vector3& min, const Vector3& max); +private: + static std::mt19937 sGenerator; +}; diff --git a/Chapter05/Shader.cpp b/Chapter05/Shader.cpp new file mode 100644 index 00000000..e32c07a2 --- /dev/null +++ b/Chapter05/Shader.cpp @@ -0,0 +1,148 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Shader.h" +#include "Texture.h" +#include +#include +#include + +Shader::Shader() + : mShaderProgram(0) + , mVertexShader(0) + , mFragShader(0) +{ + +} + +Shader::~Shader() +{ + +} + +bool Shader::Load(const std::string& vertName, const std::string& fragName) +{ + // Compile vertex and pixel shaders + if (!CompileShader(vertName, + GL_VERTEX_SHADER, + mVertexShader) || + !CompileShader(fragName, + GL_FRAGMENT_SHADER, + mFragShader)) + { + return false; + } + + // Now create a shader program that + // links together the vertex/frag shaders + mShaderProgram = glCreateProgram(); + glAttachShader(mShaderProgram, mVertexShader); + glAttachShader(mShaderProgram, mFragShader); + glLinkProgram(mShaderProgram); + + // Verify that the program linked successfully + if (!IsValidProgram()) + { + return false; + } + + return true; +} + +void Shader::Unload() +{ + // Delete the program/shaders + glDeleteProgram(mShaderProgram); + glDeleteShader(mVertexShader); + glDeleteShader(mFragShader); +} + +void Shader::SetActive() +{ + // Set this program as the active one + glUseProgram(mShaderProgram); +} + +void Shader::SetMatrixUniform(const char* name, const Matrix4& matrix) +{ + // Find the uniform by this name + GLuint loc = glGetUniformLocation(mShaderProgram, name); + // Send the matrix data to the uniform + glUniformMatrix4fv(loc, 1, GL_TRUE, matrix.GetAsFloatPtr()); +} + +bool Shader::CompileShader(const std::string& fileName, + GLenum shaderType, + GLuint& outShader) +{ + // Open file + std::ifstream shaderFile(fileName); + if (shaderFile.is_open()) + { + // Read all the text into a string + std::stringstream sstream; + sstream << shaderFile.rdbuf(); + std::string contents = sstream.str(); + const char* contentsChar = contents.c_str(); + + // Create a shader of the specified type + outShader = glCreateShader(shaderType); + // Set the source characters and try to compile + glShaderSource(outShader, 1, &(contentsChar), nullptr); + glCompileShader(outShader); + + if (!IsCompiled(outShader)) + { + SDL_Log("Failed to compile shader %s", fileName.c_str()); + return false; + } + } + else + { + SDL_Log("Shader file not found: %s", fileName.c_str()); + return false; + } + + return true; +} + +bool Shader::IsCompiled(GLuint shader) +{ + GLint status; + // Query the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) + { + char buffer[512]; + memset(buffer, 0, 512); + glGetShaderInfoLog(shader, 511, nullptr, buffer); + SDL_Log("GLSL Compile Failed:\n%s", buffer); + return false; + } + + return true; +} + +bool Shader::IsValidProgram() +{ + + GLint status; + // Query the link status + glGetProgramiv(mShaderProgram, GL_LINK_STATUS, &status); + if (status != GL_TRUE) + { + char buffer[512]; + memset(buffer, 0, 512); + glGetProgramInfoLog(mShaderProgram, 511, nullptr, buffer); + SDL_Log("GLSL Link Status:\n%s", buffer); + return false; + } + + return true; +} diff --git a/Chapter05/Shader.h b/Chapter05/Shader.h new file mode 100644 index 00000000..2b0161c0 --- /dev/null +++ b/Chapter05/Shader.h @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include +#include "Math.h" + +class Shader +{ +public: + Shader(); + ~Shader(); + // Load the vertex/fragment shaders with the given names + bool Load(const std::string& vertName, const std::string& fragName); + void Unload(); + // Set this as the active shader program + void SetActive(); + // Sets a Matrix uniform + void SetMatrixUniform(const char* name, const Matrix4& matrix); +private: + // Tries to compile the specified shader + bool CompileShader(const std::string& fileName, + GLenum shaderType, + GLuint& outShader); + + // Tests whether shader compiled successfully + bool IsCompiled(GLuint shader); + // Tests whether vertex/fragment programs link + bool IsValidProgram(); +private: + // Store the shader object IDs + GLuint mVertexShader; + GLuint mFragShader; + GLuint mShaderProgram; +}; diff --git a/Chapter05/Shaders/Basic.frag b/Chapter05/Shaders/Basic.frag new file mode 100644 index 00000000..af9a33e5 --- /dev/null +++ b/Chapter05/Shaders/Basic.frag @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// This corresponds to the output color +// to the color buffer +out vec4 outColor; + +void main() +{ + // RGBA of 100% blue, 100% opaque + outColor = vec4(0.0, 0.0, 1.0, 1.0); +} diff --git a/Chapter05/Shaders/Basic.vert b/Chapter05/Shaders/Basic.vert new file mode 100644 index 00000000..345b3c05 --- /dev/null +++ b/Chapter05/Shaders/Basic.vert @@ -0,0 +1,23 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// This should correspond to the data stored +// for each vertex in the vertex buffer. +// For now, just a position. +in vec3 inPosition; + +void main() +{ + // The vertex shader needs to output a 4D + // coordinate. + // For now set the 4th coordinate to 1.0 + gl_Position = vec4(inPosition, 1.0); +} diff --git a/Chapter05/Shaders/Sprite.frag b/Chapter05/Shaders/Sprite.frag new file mode 100644 index 00000000..f48caf3d --- /dev/null +++ b/Chapter05/Shaders/Sprite.frag @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// Tex coord input from vertex shader +in vec2 fragTexCoord; + +// This corresponds to the output color to the color buffer +out vec4 outColor; + +// This is used for the texture sampling +uniform sampler2D uTexture; + +void main() +{ + // Sample color from texture + outColor = texture(uTexture, fragTexCoord); +} diff --git a/Chapter05/Shaders/Sprite.vert b/Chapter05/Shaders/Sprite.vert new file mode 100644 index 00000000..ea0f396f --- /dev/null +++ b/Chapter05/Shaders/Sprite.vert @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// Uniforms for world transform and view-proj +uniform mat4 uWorldTransform; +uniform mat4 uViewProj; + +// Attribute 0 is position, 1 is tex coords. +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec2 inTexCoord; + +// Add texture coordinate as output +out vec2 fragTexCoord; + +void main() +{ + // Convert position to homogeneous coordinates + vec4 pos = vec4(inPosition, 1.0); + // Transform position to world space, then clip space + gl_Position = pos * uWorldTransform * uViewProj; + + // Transform + // Pass along the texture coordinate to frag shader + fragTexCoord = inTexCoord; +} diff --git a/Chapter05/Shaders/Transform.vert b/Chapter05/Shaders/Transform.vert new file mode 100644 index 00000000..fc59d32e --- /dev/null +++ b/Chapter05/Shaders/Transform.vert @@ -0,0 +1,23 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// Uniforms for world transform and view-proj +uniform mat4 uWorldTransform; +uniform mat4 uViewProj; + +// Vertex attributes +in vec3 inPosition; + +void main() +{ + vec4 pos = vec4(inPosition, 1.0); + gl_Position = pos * uWorldTransform * uViewProj; +} diff --git a/Chapter05/Ship.cpp b/Chapter05/Ship.cpp new file mode 100644 index 00000000..e5b236db --- /dev/null +++ b/Chapter05/Ship.cpp @@ -0,0 +1,50 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Ship.h" +#include "SpriteComponent.h" +#include "InputComponent.h" +#include "Game.h" +#include "Laser.h" + +Ship::Ship(Game* game) + :Actor(game) + ,mLaserCooldown(0.0f) +{ + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this, 150); + sc->SetTexture(game->GetTexture("Assets/Ship.png")); + + // Create an input component and set keys/speed + InputComponent* ic = new InputComponent(this); + ic->SetForwardKey(SDL_SCANCODE_W); + ic->SetBackKey(SDL_SCANCODE_S); + ic->SetClockwiseKey(SDL_SCANCODE_A); + ic->SetCounterClockwiseKey(SDL_SCANCODE_D); + ic->SetMaxForwardSpeed(300.0f); + ic->SetMaxAngularSpeed(Math::TwoPi); +} + +void Ship::UpdateActor(float deltaTime) +{ + mLaserCooldown -= deltaTime; +} + +void Ship::ActorInput(const uint8_t* keyState) +{ + if (keyState[SDL_SCANCODE_SPACE] && mLaserCooldown <= 0.0f) + { + // Create a laser and set its position/rotation to mine + Laser* laser = new Laser(GetGame()); + laser->SetPosition(GetPosition()); + laser->SetRotation(GetRotation()); + + // Reset laser cooldown (half second) + mLaserCooldown = 0.5f; + } +} diff --git a/Chapter05/Ship.h b/Chapter05/Ship.h new file mode 100644 index 00000000..808639ff --- /dev/null +++ b/Chapter05/Ship.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Ship : public Actor +{ +public: + Ship(class Game* game); + + void UpdateActor(float deltaTime) override; + void ActorInput(const uint8_t* keyState) override; +private: + float mLaserCooldown; +}; \ No newline at end of file diff --git a/Chapter05/SpriteComponent.cpp b/Chapter05/SpriteComponent.cpp new file mode 100644 index 00000000..d62b9840 --- /dev/null +++ b/Chapter05/SpriteComponent.cpp @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "SpriteComponent.h" +#include "Texture.h" +#include "Shader.h" +#include "Actor.h" +#include "Game.h" + +SpriteComponent::SpriteComponent(Actor* owner, int drawOrder) + :Component(owner) + ,mTexture(nullptr) + ,mDrawOrder(drawOrder) + ,mTexWidth(0) + ,mTexHeight(0) +{ + mOwner->GetGame()->AddSprite(this); +} + +SpriteComponent::~SpriteComponent() +{ + mOwner->GetGame()->RemoveSprite(this); +} + +void SpriteComponent::Draw(Shader* shader) +{ + if (mTexture) + { + // Scale the quad by the width/height of texture + Matrix4 scaleMat = Matrix4::CreateScale( + static_cast(mTexWidth), + static_cast(mTexHeight), + 1.0f); + + Matrix4 world = scaleMat * mOwner->GetWorldTransform(); + + // Since all sprites use the same shader/vertices, + // the game first sets them active before any sprite draws + + // Set world transform + shader->SetMatrixUniform("uWorldTransform", world); + // Set current texture + mTexture->SetActive(); + // Draw quad + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); + } +} + +void SpriteComponent::SetTexture(Texture* texture) +{ + mTexture = texture; + // Set width/height + mTexWidth = texture->GetWidth(); + mTexHeight = texture->GetHeight(); +} diff --git a/Chapter05/SpriteComponent.h b/Chapter05/SpriteComponent.h new file mode 100644 index 00000000..6c5642f2 --- /dev/null +++ b/Chapter05/SpriteComponent.h @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "SDL/SDL.h" +class SpriteComponent : public Component +{ +public: + // (Lower draw order corresponds with further back) + SpriteComponent(class Actor* owner, int drawOrder = 100); + ~SpriteComponent(); + + virtual void Draw(class Shader* shader); + virtual void SetTexture(class Texture* texture); + + int GetDrawOrder() const { return mDrawOrder; } + int GetTexHeight() const { return mTexHeight; } + int GetTexWidth() const { return mTexWidth; } +protected: + class Texture* mTexture; + int mDrawOrder; + int mTexWidth; + int mTexHeight; +}; diff --git a/Chapter05/Texture.cpp b/Chapter05/Texture.cpp new file mode 100644 index 00000000..7d0dbd3e --- /dev/null +++ b/Chapter05/Texture.cpp @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Texture.h" +#include +#include +#include + +Texture::Texture() +:mTextureID(0) +,mWidth(0) +,mHeight(0) +{ + +} + +Texture::~Texture() +{ + +} + +bool Texture::Load(const std::string& fileName) +{ + int channels = 0; + + unsigned char* image = SOIL_load_image(fileName.c_str(), + &mWidth, &mHeight, &channels, SOIL_LOAD_AUTO); + + if (image == nullptr) + { + SDL_Log("SOIL failed to load image %s: %s", fileName.c_str(), SOIL_last_result()); + return false; + } + + int format = GL_RGB; + if (channels == 4) + { + format = GL_RGBA; + } + + glGenTextures(1, &mTextureID); + glBindTexture(GL_TEXTURE_2D, mTextureID); + + glTexImage2D(GL_TEXTURE_2D, 0, format, mWidth, mHeight, 0, format, + GL_UNSIGNED_BYTE, image); + + SOIL_free_image_data(image); + + // Enable bilinear filtering + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + return true; +} + +void Texture::Unload() +{ + glDeleteTextures(1, &mTextureID); +} + +void Texture::SetActive() +{ + glBindTexture(GL_TEXTURE_2D, mTextureID); +} diff --git a/Chapter05/Texture.h b/Chapter05/Texture.h new file mode 100644 index 00000000..ed12b0dc --- /dev/null +++ b/Chapter05/Texture.h @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include + +class Texture +{ +public: + Texture(); + ~Texture(); + + bool Load(const std::string& fileName); + void Unload(); + + void SetActive(); + + int GetWidth() const { return mWidth; } + int GetHeight() const { return mHeight; } +private: + // OpenGL ID of this texture + unsigned int mTextureID; + // Width/height of the texture + int mWidth; + int mHeight; +}; diff --git a/Chapter05/VertexArray.cpp b/Chapter05/VertexArray.cpp new file mode 100644 index 00000000..c7bff446 --- /dev/null +++ b/Chapter05/VertexArray.cpp @@ -0,0 +1,51 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "VertexArray.h" +#include + +VertexArray::VertexArray(const float* verts, unsigned int numVerts, + const unsigned int* indices, unsigned int numIndices) + :mNumVerts(numVerts) + ,mNumIndices(numIndices) +{ + // Create vertex array + glGenVertexArrays(1, &mVertexArray); + glBindVertexArray(mVertexArray); + + // Create vertex buffer + glGenBuffers(1, &mVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, numVerts * 5 * sizeof(float), verts, GL_STATIC_DRAW); + + // Create index buffer + glGenBuffers(1, &mIndexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices * sizeof(unsigned int), indices, GL_STATIC_DRAW); + + // Specify the vertex attributes + // (For now, assume one vertex format) + // Position is 3 floats starting at offset 0 + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 5, 0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 5, + reinterpret_cast(sizeof(float) * 3)); +} + +VertexArray::~VertexArray() +{ + glDeleteBuffers(1, &mVertexBuffer); + glDeleteBuffers(1, &mIndexBuffer); + glDeleteVertexArrays(1, &mVertexArray); +} + +void VertexArray::SetActive() +{ + glBindVertexArray(mVertexArray); +} diff --git a/Chapter05/VertexArray.h b/Chapter05/VertexArray.h new file mode 100644 index 00000000..9f2c3e9b --- /dev/null +++ b/Chapter05/VertexArray.h @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +class VertexArray +{ +public: + VertexArray(const float* verts, unsigned int numVerts, + const unsigned int* indices, unsigned int numIndices); + ~VertexArray(); + + // Activate this vertex array (so we can draw it) + void SetActive(); + + unsigned int GetNumIndices() const { return mNumIndices; } + unsigned int GetNumVerts() const { return mNumVerts; } +private: + // How many vertices in the vertex buffer? + unsigned int mNumVerts; + // How many indices in the index buffer + unsigned int mNumIndices; + // OpenGL ID of the vertex buffer + unsigned int mVertexBuffer; + // OpenGL ID of the index buffer + unsigned int mIndexBuffer; + // OpenGL ID of the vertex array object + unsigned int mVertexArray; +}; \ No newline at end of file diff --git a/Chapter08/Actor.cpp b/Chapter08/Actor.cpp new file mode 100644 index 00000000..511c97ec --- /dev/null +++ b/Chapter08/Actor.cpp @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Actor.h" +#include "Game.h" +#include "Component.h" +#include + +Actor::Actor(Game* game) + :mState(EActive) + ,mPosition(Vector2::Zero) + ,mScale(1.0f) + ,mRotation(0.0f) + ,mGame(game) + ,mRecomputeWorldTransform(true) +{ + mGame->AddActor(this); +} + +Actor::~Actor() +{ + mGame->RemoveActor(this); + // Need to delete components + // Because ~Component calls RemoveComponent, need a different style loop + while (!mComponents.empty()) + { + delete mComponents.back(); + } +} + +void Actor::Update(float deltaTime) +{ + if (mState == EActive) + { + ComputeWorldTransform(); + + UpdateComponents(deltaTime); + UpdateActor(deltaTime); + + ComputeWorldTransform(); + } +} + +void Actor::UpdateComponents(float deltaTime) +{ + for (auto comp : mComponents) + { + comp->Update(deltaTime); + } +} + +void Actor::UpdateActor(float deltaTime) +{ +} + +void Actor::ProcessInput(const struct InputState& state) +{ + if (mState == EActive) + { + // First process input for components + for (auto comp : mComponents) + { + comp->ProcessInput(state); + } + + ActorInput(state); + } +} + +void Actor::ActorInput(const struct InputState& state) +{ +} + +void Actor::ComputeWorldTransform() +{ + if (mRecomputeWorldTransform) + { + mRecomputeWorldTransform = false; + // Scale, then rotate, then translate + mWorldTransform = Matrix4::CreateScale(mScale); + mWorldTransform *= Matrix4::CreateRotationZ(mRotation); + mWorldTransform *= Matrix4::CreateTranslation(Vector3(mPosition.x, mPosition.y, 0.0f)); + + // Inform components world transform updated + for (auto comp : mComponents) + { + comp->OnUpdateWorldTransform(); + } + } +} + +void Actor::AddComponent(Component* component) +{ + // Find the insertion point in the sorted vector + // (The first element with a order higher than me) + int myOrder = component->GetUpdateOrder(); + auto iter = mComponents.begin(); + for (; + iter != mComponents.end(); + ++iter) + { + if (myOrder < (*iter)->GetUpdateOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mComponents.insert(iter, component); +} + +void Actor::RemoveComponent(Component* component) +{ + auto iter = std::find(mComponents.begin(), mComponents.end(), component); + if (iter != mComponents.end()) + { + mComponents.erase(iter); + } +} diff --git a/Chapter08/Actor.h b/Chapter08/Actor.h new file mode 100644 index 00000000..38e28db3 --- /dev/null +++ b/Chapter08/Actor.h @@ -0,0 +1,74 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include "Math.h" +#include + +class Actor +{ +public: + enum State + { + EActive, + EPaused, + EDead + }; + + Actor(class Game* game); + virtual ~Actor(); + + // Update function called from Game (not overridable) + void Update(float deltaTime); + // Updates all the components attached to the actor (not overridable) + void UpdateComponents(float deltaTime); + // Any actor-specific update code (overridable) + virtual void UpdateActor(float deltaTime); + + // ProcessInput function called from Game (not overridable) + void ProcessInput(const struct InputState& state); + // Any actor-specific input code (overridable) + virtual void ActorInput(const struct InputState& state); + + // Getters/setters + const Vector2& GetPosition() const { return mPosition; } + void SetPosition(const Vector2& pos) { mPosition = pos; mRecomputeWorldTransform = true; } + float GetScale() const { return mScale; } + void SetScale(float scale) { mScale = scale; mRecomputeWorldTransform = true; } + float GetRotation() const { return mRotation; } + void SetRotation(float rotation) { mRotation = rotation; mRecomputeWorldTransform = true; } + + void ComputeWorldTransform(); + const Matrix4& GetWorldTransform() const { return mWorldTransform; } + + Vector2 GetForward() const { return Vector2(Math::Cos(mRotation), Math::Sin(mRotation)); } + + State GetState() const { return mState; } + void SetState(State state) { mState = state; } + + class Game* GetGame() { return mGame; } + + + // Add/remove components + void AddComponent(class Component* component); + void RemoveComponent(class Component* component); +private: + // Actor's state + State mState; + + // Transform + Matrix4 mWorldTransform; + Vector2 mPosition; + float mScale; + float mRotation; + bool mRecomputeWorldTransform; + + std::vector mComponents; + class Game* mGame; +}; diff --git a/Chapter08/Assets/Asteroid.png b/Chapter08/Assets/Asteroid.png new file mode 100644 index 00000000..98e831e9 Binary files /dev/null and b/Chapter08/Assets/Asteroid.png differ diff --git a/Chapter08/Assets/Laser.png b/Chapter08/Assets/Laser.png new file mode 100644 index 00000000..95352d27 Binary files /dev/null and b/Chapter08/Assets/Laser.png differ diff --git a/Chapter08/Assets/Ship.png b/Chapter08/Assets/Ship.png new file mode 100644 index 00000000..96514436 Binary files /dev/null and b/Chapter08/Assets/Ship.png differ diff --git a/Chapter08/Assets/ShipWithThrust.png b/Chapter08/Assets/ShipWithThrust.png new file mode 100644 index 00000000..ed9a43e2 Binary files /dev/null and b/Chapter08/Assets/ShipWithThrust.png differ diff --git a/Chapter08/Asteroid.cpp b/Chapter08/Asteroid.cpp new file mode 100644 index 00000000..9bcaa1e1 --- /dev/null +++ b/Chapter08/Asteroid.cpp @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Asteroid.h" +#include "SpriteComponent.h" +#include "MoveComponent.h" +#include "Game.h" +#include "Random.h" +#include "CircleComponent.h" + +Asteroid::Asteroid(Game* game) + :Actor(game) + ,mCircle(nullptr) +{ + // Initialize to random position/orientation + Vector2 randPos = Random::GetVector(Vector2(-512.0f, -384.0f), + Vector2(512.0f, 384.0f)); + SetPosition(randPos); + + SetRotation(Random::GetFloatRange(0.0f, Math::TwoPi)); + + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this); + sc->SetTexture(game->GetTexture("Assets/Asteroid.png")); + + // Create a move component, and set a forward speed + MoveComponent* mc = new MoveComponent(this); + mc->SetForwardSpeed(150.0f); + + // Create a circle component (for collision) + mCircle = new CircleComponent(this); + mCircle->SetRadius(40.0f); + + // Add to mAsteroids in game + game->AddAsteroid(this); +} + +Asteroid::~Asteroid() +{ + GetGame()->RemoveAsteroid(this); +} diff --git a/Chapter08/Asteroid.h b/Chapter08/Asteroid.h new file mode 100644 index 00000000..45305770 --- /dev/null +++ b/Chapter08/Asteroid.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Asteroid : public Actor +{ +public: + Asteroid(class Game* game); + ~Asteroid(); + + class CircleComponent* GetCircle() { return mCircle; } +private: + class CircleComponent* mCircle; +}; diff --git a/Chapter08/Chapter08-mac.xcodeproj/project.pbxproj b/Chapter08/Chapter08-mac.xcodeproj/project.pbxproj new file mode 100644 index 00000000..10dd8c93 --- /dev/null +++ b/Chapter08/Chapter08-mac.xcodeproj/project.pbxproj @@ -0,0 +1,408 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9206FDC61F140707005078A2 /* Texture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9206FDC41F140707005078A2 /* Texture.cpp */; }; + 9206FDC91F140D40005078A2 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9206FDC71F140D40005078A2 /* Shader.cpp */; }; + 9216C5431FCFFDA400F72B29 /* Asteroid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9216C5371FCFFDA200F72B29 /* Asteroid.cpp */; }; + 9216C5441FCFFDA400F72B29 /* Random.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9216C53A1FCFFDA300F72B29 /* Random.cpp */; }; + 9216C5451FCFFDA400F72B29 /* Laser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9216C53D1FCFFDA300F72B29 /* Laser.cpp */; }; + 9216C5461FCFFDA400F72B29 /* InputSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9216C5401FCFFDA300F72B29 /* InputSystem.cpp */; }; + 9216C5471FCFFDA400F72B29 /* Ship.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9216C5411FCFFDA400F72B29 /* Ship.cpp */; }; + 9216C5481FCFFDA400F72B29 /* InputComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9216C5421FCFFDA400F72B29 /* InputComponent.cpp */; }; + 9223C4781F009428009A94D7 /* Game.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4671F009428009A94D7 /* Game.cpp */; }; + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4681F009428009A94D7 /* Actor.cpp */; }; + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C46E1F009428009A94D7 /* Component.cpp */; }; + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4711F009428009A94D7 /* Main.cpp */; }; + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4721F009428009A94D7 /* Math.cpp */; }; + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4761F009428009A94D7 /* SpriteComponent.cpp */; }; + 9223C48B1F0CA3CE009A94D7 /* MoveComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */; }; + 9223C4951F0CA766009A94D7 /* CircleComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */; }; + 92CF0D791F3BBF140086A0F3 /* VertexArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 92CF0D771F3BBF140086A0F3 /* VertexArray.cpp */; }; + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92D324FA1B697389005A86C7 /* CoreFoundation.framework */; }; + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92E46E931B6353E50035CD21 /* OpenGL.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 9206FDC31F13F7E8005078A2 /* Shaders */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Shaders; sourceTree = ""; }; + 9206FDC41F140707005078A2 /* Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Texture.cpp; sourceTree = ""; }; + 9206FDC51F140707005078A2 /* Texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Texture.h; sourceTree = ""; }; + 9206FDC71F140D40005078A2 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; + 9206FDC81F140D40005078A2 /* Shader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Shader.h; sourceTree = ""; }; + 9216C5371FCFFDA200F72B29 /* Asteroid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Asteroid.cpp; sourceTree = ""; }; + 9216C5381FCFFDA200F72B29 /* InputComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputComponent.h; sourceTree = ""; }; + 9216C5391FCFFDA200F72B29 /* Random.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Random.h; sourceTree = ""; }; + 9216C53A1FCFFDA300F72B29 /* Random.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Random.cpp; sourceTree = ""; }; + 9216C53B1FCFFDA300F72B29 /* Ship.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ship.h; sourceTree = ""; }; + 9216C53C1FCFFDA300F72B29 /* Laser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Laser.h; sourceTree = ""; }; + 9216C53D1FCFFDA300F72B29 /* Laser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Laser.cpp; sourceTree = ""; }; + 9216C53E1FCFFDA300F72B29 /* InputSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputSystem.h; sourceTree = ""; }; + 9216C53F1FCFFDA300F72B29 /* Asteroid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Asteroid.h; sourceTree = ""; }; + 9216C5401FCFFDA300F72B29 /* InputSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InputSystem.cpp; sourceTree = ""; }; + 9216C5411FCFFDA400F72B29 /* Ship.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Ship.cpp; sourceTree = ""; }; + 9216C5421FCFFDA400F72B29 /* InputComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InputComponent.cpp; sourceTree = ""; }; + 9223C4671F009428009A94D7 /* Game.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Game.cpp; sourceTree = ""; }; + 9223C4681F009428009A94D7 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 9223C4691F009428009A94D7 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 9223C46E1F009428009A94D7 /* Component.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Component.cpp; sourceTree = ""; }; + 9223C46F1F009428009A94D7 /* Component.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Component.h; sourceTree = ""; }; + 9223C4701F009428009A94D7 /* Game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Game.h; sourceTree = ""; }; + 9223C4711F009428009A94D7 /* Main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Main.cpp; sourceTree = ""; }; + 9223C4721F009428009A94D7 /* Math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Math.cpp; sourceTree = ""; }; + 9223C4731F009428009A94D7 /* Math.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Math.h; sourceTree = ""; }; + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpriteComponent.cpp; sourceTree = ""; }; + 9223C4771F009428009A94D7 /* SpriteComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpriteComponent.h; sourceTree = ""; }; + 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MoveComponent.cpp; sourceTree = ""; }; + 9223C48C1F0CA3D4009A94D7 /* MoveComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MoveComponent.h; sourceTree = ""; }; + 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CircleComponent.cpp; sourceTree = ""; }; + 9223C4931F0CA766009A94D7 /* CircleComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleComponent.h; sourceTree = ""; }; + 92CF0D771F3BBF140086A0F3 /* VertexArray.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VertexArray.cpp; sourceTree = ""; }; + 92CF0D781F3BBF140086A0F3 /* VertexArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VertexArray.h; sourceTree = ""; }; + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 92E46DF71B634EA30035CD21 /* Game-mac */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Game-mac"; sourceTree = BUILT_PRODUCTS_DIR; }; + 92E46E931B6353E50035CD21 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 92E46DF41B634EA30035CD21 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 92D324FB1B697389005A86C7 /* CoreFoundation.framework in Frameworks */, + 92E46E941B6353E50035CD21 /* OpenGL.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 92E46DEE1B634EA30035CD21 = { + isa = PBXGroup; + children = ( + 9223C4681F009428009A94D7 /* Actor.cpp */, + 9223C4691F009428009A94D7 /* Actor.h */, + 9216C5371FCFFDA200F72B29 /* Asteroid.cpp */, + 9216C53F1FCFFDA300F72B29 /* Asteroid.h */, + 9223C46E1F009428009A94D7 /* Component.cpp */, + 9223C46F1F009428009A94D7 /* Component.h */, + 9223C4911F0CA766009A94D7 /* CircleComponent.cpp */, + 9223C4931F0CA766009A94D7 /* CircleComponent.h */, + 9216C5421FCFFDA400F72B29 /* InputComponent.cpp */, + 9216C5381FCFFDA200F72B29 /* InputComponent.h */, + 9216C5401FCFFDA300F72B29 /* InputSystem.cpp */, + 9216C53E1FCFFDA300F72B29 /* InputSystem.h */, + 9223C4671F009428009A94D7 /* Game.cpp */, + 9223C4701F009428009A94D7 /* Game.h */, + 9216C53D1FCFFDA300F72B29 /* Laser.cpp */, + 9216C53C1FCFFDA300F72B29 /* Laser.h */, + 9223C4711F009428009A94D7 /* Main.cpp */, + 9223C4721F009428009A94D7 /* Math.cpp */, + 9223C4731F009428009A94D7 /* Math.h */, + 9223C48A1F0CA3CE009A94D7 /* MoveComponent.cpp */, + 9223C48C1F0CA3D4009A94D7 /* MoveComponent.h */, + 9216C53A1FCFFDA300F72B29 /* Random.cpp */, + 9216C5391FCFFDA200F72B29 /* Random.h */, + 9206FDC71F140D40005078A2 /* Shader.cpp */, + 9206FDC81F140D40005078A2 /* Shader.h */, + 9216C5411FCFFDA400F72B29 /* Ship.cpp */, + 9216C53B1FCFFDA300F72B29 /* Ship.h */, + 9223C4761F009428009A94D7 /* SpriteComponent.cpp */, + 9223C4771F009428009A94D7 /* SpriteComponent.h */, + 9206FDC41F140707005078A2 /* Texture.cpp */, + 9206FDC51F140707005078A2 /* Texture.h */, + 92CF0D771F3BBF140086A0F3 /* VertexArray.cpp */, + 92CF0D781F3BBF140086A0F3 /* VertexArray.h */, + 9206FDC31F13F7E8005078A2 /* Shaders */, + 92E46DF81B634EA30035CD21 /* Products */, + 92D324FA1B697389005A86C7 /* CoreFoundation.framework */, + 92E46E931B6353E50035CD21 /* OpenGL.framework */, + ); + sourceTree = ""; + }; + 92E46DF81B634EA30035CD21 /* Products */ = { + isa = PBXGroup; + children = ( + 92E46DF71B634EA30035CD21 /* Game-mac */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 92E46DF61B634EA30035CD21 /* Game-mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */; + buildPhases = ( + 92E46DF31B634EA30035CD21 /* Sources */, + 92E46DF41B634EA30035CD21 /* Frameworks */, + 92E46EA11B63615B0035CD21 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Game-mac"; + productName = "Game-mac"; + productReference = 92E46DF71B634EA30035CD21 /* Game-mac */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 92E46DEF1B634EA30035CD21 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Sanjay Madhav"; + TargetAttributes = { + 92E46DF61B634EA30035CD21 = { + CreatedOnToolsVersion = 6.4; + }; + }; + }; + buildConfigurationList = 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter08-mac" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 92E46DEE1B634EA30035CD21; + productRefGroup = 92E46DF81B634EA30035CD21 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 92E46DF61B634EA30035CD21 /* Game-mac */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 92E46EA11B63615B0035CD21 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -d \"$BUILD_DIR/Debug\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Debug\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Debug\nfi\n\nif [ -d \"$BUILD_DIR/Release\" ]; then\n cp \"$SRCROOT\"/../external/GLEW/lib/mac/*.dylib $BUILD_DIR/Release\n cp \"$SRCROOT\"/../external/SDL/lib/mac/*.dylib $BUILD_DIR/Release\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 92E46DF31B634EA30035CD21 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9223C47D1F009428009A94D7 /* Main.cpp in Sources */, + 9223C47E1F009428009A94D7 /* Math.cpp in Sources */, + 9223C4781F009428009A94D7 /* Game.cpp in Sources */, + 9216C5451FCFFDA400F72B29 /* Laser.cpp in Sources */, + 9216C5481FCFFDA400F72B29 /* InputComponent.cpp in Sources */, + 9216C5461FCFFDA400F72B29 /* InputSystem.cpp in Sources */, + 9223C4801F009428009A94D7 /* SpriteComponent.cpp in Sources */, + 9216C5431FCFFDA400F72B29 /* Asteroid.cpp in Sources */, + 92CF0D791F3BBF140086A0F3 /* VertexArray.cpp in Sources */, + 9223C48B1F0CA3CE009A94D7 /* MoveComponent.cpp in Sources */, + 9206FDC91F140D40005078A2 /* Shader.cpp in Sources */, + 9223C4791F009428009A94D7 /* Actor.cpp in Sources */, + 9223C4951F0CA766009A94D7 /* CircleComponent.cpp in Sources */, + 9216C5441FCFFDA400F72B29 /* Random.cpp in Sources */, + 9216C5471FCFFDA400F72B29 /* Ship.cpp in Sources */, + 9223C47C1F009428009A94D7 /* Component.cpp in Sources */, + 9206FDC61F140707005078A2 /* Texture.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 92E46DFC1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 92E46DFD1B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 92E46DFF1B634EA40035CD21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 92E46E001B634EA40035CD21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_CPP_RTTI = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../external/SDL/include", + "$(SRCROOT)/../external/GLEW/include", + "$(SRCROOT)/../external/SOIL/include", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SRCROOT)/../external/GLEW/lib/mac", + "$(SRCROOT)/../external/SDL/lib/mac", + "$(SRCROOT)/../external/SOIL/lib/mac", + ); + OTHER_LDFLAGS = ( + "-lGLEW.2.1.0", + "-lSDL2-2.0.0", + "-lSDL2_mixer-2.0.0", + "-lSDL2_ttf-2.0.0", + "-lSOIL", + "-lSDL2_image-2.0.0", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 92E46DF21B634EA30035CD21 /* Build configuration list for PBXProject "Chapter08-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFC1B634EA40035CD21 /* Debug */, + 92E46DFD1B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 92E46DFE1B634EA40035CD21 /* Build configuration list for PBXNativeTarget "Game-mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92E46DFF1B634EA40035CD21 /* Debug */, + 92E46E001B634EA40035CD21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 92E46DEF1B634EA30035CD21 /* Project object */; +} diff --git a/Chapter08/Chapter08-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Chapter08/Chapter08-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..bd66929c --- /dev/null +++ b/Chapter08/Chapter08-mac.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Chapter08/Chapter08-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme b/Chapter08/Chapter08-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme new file mode 100644 index 00000000..57c6b89e --- /dev/null +++ b/Chapter08/Chapter08-mac.xcodeproj/xcshareddata/xcschemes/Game-mac.xcscheme @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter08/Chapter08-windows.sln b/Chapter08/Chapter08-windows.sln new file mode 100644 index 00000000..f3e55a49 --- /dev/null +++ b/Chapter08/Chapter08-windows.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2008 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "Game.vcxproj", "{BC508D87-495F-4554-932D-DD68388B63CC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Debug|Win32.Build.0 = Debug|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.ActiveCfg = Release|Win32 + {BC508D87-495F-4554-932D-DD68388B63CC}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {84365DE3-728A-494A-A8A1-96281CA6772F} + EndGlobalSection +EndGlobal diff --git a/Chapter08/CircleComponent.cpp b/Chapter08/CircleComponent.cpp new file mode 100644 index 00000000..d41aab2c --- /dev/null +++ b/Chapter08/CircleComponent.cpp @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "CircleComponent.h" +#include "Actor.h" + +CircleComponent::CircleComponent(class Actor* owner) +:Component(owner) +,mRadius(0.0f) +{ + +} + +const Vector2& CircleComponent::GetCenter() const +{ + return mOwner->GetPosition(); +} + +float CircleComponent::GetRadius() const +{ + return mOwner->GetScale() * mRadius; +} + +bool Intersect(const CircleComponent& a, const CircleComponent& b) +{ + // Calculate distance squared + Vector2 diff = a.GetCenter() - b.GetCenter(); + float distSq = diff.LengthSq(); + + // Calculate sum of radii squared + float radiiSq = a.GetRadius() + b.GetRadius(); + radiiSq *= radiiSq; + + return distSq <= radiiSq; +} diff --git a/Chapter08/CircleComponent.h b/Chapter08/CircleComponent.h new file mode 100644 index 00000000..4eecb2a4 --- /dev/null +++ b/Chapter08/CircleComponent.h @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "Math.h" + +class CircleComponent : public Component +{ +public: + CircleComponent(class Actor* owner); + + void SetRadius(float radius) { mRadius = radius; } + float GetRadius() const; + + const Vector2& GetCenter() const; +private: + float mRadius; +}; + +bool Intersect(const CircleComponent& a, const CircleComponent& b); diff --git a/Chapter08/Component.cpp b/Chapter08/Component.cpp new file mode 100644 index 00000000..c4ed432d --- /dev/null +++ b/Chapter08/Component.cpp @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Component.h" +#include "Actor.h" + +Component::Component(Actor* owner, int updateOrder) + :mOwner(owner) + ,mUpdateOrder(updateOrder) +{ + // Add to actor's vector of components + mOwner->AddComponent(this); +} + +Component::~Component() +{ + mOwner->RemoveComponent(this); +} + +void Component::Update(float deltaTime) +{ +} diff --git a/Chapter08/Component.h b/Chapter08/Component.h new file mode 100644 index 00000000..a8174728 --- /dev/null +++ b/Chapter08/Component.h @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include + +class Component +{ +public: + // Constructor + // (the lower the update order, the earlier the component updates) + Component(class Actor* owner, int updateOrder = 100); + // Destructor + virtual ~Component(); + // Update this component by delta time + virtual void Update(float deltaTime); + // Process input for this component + virtual void ProcessInput(const struct InputState& state) {} + // Called when world transform changes + virtual void OnUpdateWorldTransform() { } + + int GetUpdateOrder() const { return mUpdateOrder; } +protected: + // Owning actor + class Actor* mOwner; + // Update order of component + int mUpdateOrder; +}; diff --git a/Chapter08/Game.cpp b/Chapter08/Game.cpp new file mode 100644 index 00000000..e3fff3a7 --- /dev/null +++ b/Chapter08/Game.cpp @@ -0,0 +1,397 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" +#include +#include "Texture.h" +#include "VertexArray.h" +#include "Shader.h" +#include +#include "Actor.h" +#include "SpriteComponent.h" +#include "Actor.h" +#include "Ship.h" +#include "Asteroid.h" +#include "Random.h" +#include "InputSystem.h" + +Game::Game() +:mWindow(nullptr) +,mSpriteShader(nullptr) +,mIsRunning(true) +,mUpdatingActors(false) +{ +} + +bool Game::Initialize() +{ + if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_GAMECONTROLLER) != 0) + { + SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); + return false; + } + + // Set OpenGL attributes + // Use the core OpenGL profile + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + // Specify version 3.3 + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + // Request a color buffer with 8-bits per RGBA channel + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + // Enable double buffering + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + // Force OpenGL to use hardware acceleration + SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); + + mWindow = SDL_CreateWindow("Game Programming in C++ (Chapter 5)", 100, 100, + 1024, 768, SDL_WINDOW_OPENGL); + if (!mWindow) + { + SDL_Log("Failed to create window: %s", SDL_GetError()); + return false; + } + + // Initialize input system + mInputSystem = new InputSystem(); + if (!mInputSystem->Initialize()) + { + SDL_Log("Failed to initialize input system"); + return false; + } + + // Create an OpenGL context + mContext = SDL_GL_CreateContext(mWindow); + + // Initialize GLEW + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) + { + SDL_Log("Failed to initialize GLEW."); + return false; + } + + // On some platforms, GLEW will emit a benign error code, + // so clear it + glGetError(); + + // Make sure we can create/compile shaders + if (!LoadShaders()) + { + SDL_Log("Failed to load shaders."); + return false; + } + + // Create quad for drawing sprites + CreateSpriteVerts(); + + LoadData(); + + mTicksCount = SDL_GetTicks(); + + return true; +} + +void Game::RunLoop() +{ + while (mIsRunning) + { + ProcessInput(); + UpdateGame(); + GenerateOutput(); + } +} + +void Game::ProcessInput() +{ + mInputSystem->PrepareForUpdate(); + + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + mIsRunning = false; + break; + case SDL_MOUSEWHEEL: + mInputSystem->ProcessEvent(event); + break; + default: + break; + } + } + + mInputSystem->Update(); + const InputState& state = mInputSystem->GetState(); + + if (state.Keyboard.GetKeyState(SDL_SCANCODE_ESCAPE) + == EReleased) + { + mIsRunning = false; + } + + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->ProcessInput(state); + } + mUpdatingActors = false; +} + +void Game::UpdateGame() +{ + // Compute delta time + // Wait until 16ms has elapsed since last frame + while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount + 16)) + ; + + float deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f; + if (deltaTime > 0.05f) + { + deltaTime = 0.05f; + } + mTicksCount = SDL_GetTicks(); + + // Update all actors + mUpdatingActors = true; + for (auto actor : mActors) + { + actor->Update(deltaTime); + } + mUpdatingActors = false; + + // Move any pending actors to mActors + for (auto pending : mPendingActors) + { + pending->ComputeWorldTransform(); + mActors.emplace_back(pending); + } + mPendingActors.clear(); + + // Add any dead actors to a temp vector + std::vector deadActors; + for (auto actor : mActors) + { + if (actor->GetState() == Actor::EDead) + { + deadActors.emplace_back(actor); + } + } + + // Delete dead actors (which removes them from mActors) + for (auto actor : deadActors) + { + delete actor; + } +} + +void Game::GenerateOutput() +{ + // Set the clear color to grey + glClearColor(0.86f, 0.86f, 0.86f, 1.0f); + // Clear the color buffer + glClear(GL_COLOR_BUFFER_BIT); + + // Draw all sprite components + // Enable alpha blending on the color buffer + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Set shader/vao as active + mSpriteShader->SetActive(); + mSpriteVerts->SetActive(); + for (auto sprite : mSprites) + { + sprite->Draw(mSpriteShader); + } + + // Swap the buffers + SDL_GL_SwapWindow(mWindow); +} + +bool Game::LoadShaders() +{ + mSpriteShader = new Shader(); + if (!mSpriteShader->Load("Shaders/Sprite.vert", "Shaders/Sprite.frag")) + { + return false; + } + + mSpriteShader->SetActive(); + // Set the view-projection matrix + Matrix4 viewProj = Matrix4::CreateSimpleViewProj(1024.f, 768.f); + mSpriteShader->SetMatrixUniform("uViewProj", viewProj); + return true; +} + +void Game::CreateSpriteVerts() +{ + float vertices[] = { + -0.5f, 0.5f, 0.f, 0.f, 0.f, // top left + 0.5f, 0.5f, 0.f, 1.f, 0.f, // top right + 0.5f, -0.5f, 0.f, 1.f, 1.f, // bottom right + -0.5f, -0.5f, 0.f, 0.f, 1.f // bottom left + }; + + unsigned int indices[] = { + 0, 1, 2, + 2, 3, 0 + }; + + mSpriteVerts = new VertexArray(vertices, 4, indices, 6); +} + +void Game::LoadData() +{ + // Create player's ship + mShip = new Ship(this); + mShip->SetRotation(Math::PiOver2); + + // Create asteroids + const int numAsteroids = 20; + for (int i = 0; i < numAsteroids; i++) + { + new Asteroid(this); + } +} + +void Game::UnloadData() +{ + // Delete actors + // Because ~Actor calls RemoveActor, have to use a different style loop + while (!mActors.empty()) + { + delete mActors.back(); + } + + // Destroy textures + for (auto i : mTextures) + { + i.second->Unload(); + delete i.second; + } + mTextures.clear(); +} + +Texture* Game::GetTexture(const std::string& fileName) +{ + Texture* tex = nullptr; + auto iter = mTextures.find(fileName); + if (iter != mTextures.end()) + { + tex = iter->second; + } + else + { + tex = new Texture(); + if (tex->Load(fileName)) + { + mTextures.emplace(fileName, tex); + } + else + { + delete tex; + tex = nullptr; + } + } + return tex; +} + +void Game::AddAsteroid(Asteroid* ast) +{ + mAsteroids.emplace_back(ast); +} + +void Game::RemoveAsteroid(Asteroid* ast) +{ + auto iter = std::find(mAsteroids.begin(), + mAsteroids.end(), ast); + if (iter != mAsteroids.end()) + { + mAsteroids.erase(iter); + } +} + +void Game::Shutdown() +{ + UnloadData(); + + mInputSystem->Shutdown(); + delete mInputSystem; + + delete mSpriteVerts; + mSpriteShader->Unload(); + delete mSpriteShader; + SDL_GL_DeleteContext(mContext); + SDL_DestroyWindow(mWindow); + SDL_Quit(); +} + +void Game::AddActor(Actor* actor) +{ + // If we're updating actors, need to add to pending + if (mUpdatingActors) + { + mPendingActors.emplace_back(actor); + } + else + { + mActors.emplace_back(actor); + } +} + +void Game::RemoveActor(Actor* actor) +{ + // Is it in pending actors? + auto iter = std::find(mPendingActors.begin(), mPendingActors.end(), actor); + if (iter != mPendingActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mPendingActors.end() - 1); + mPendingActors.pop_back(); + } + + // Is it in actors? + iter = std::find(mActors.begin(), mActors.end(), actor); + if (iter != mActors.end()) + { + // Swap to end of vector and pop off (avoid erase copies) + std::iter_swap(iter, mActors.end() - 1); + mActors.pop_back(); + } +} + +void Game::AddSprite(SpriteComponent* sprite) +{ + // Find the insertion point in the sorted vector + // (The first element with a higher draw order than me) + int myDrawOrder = sprite->GetDrawOrder(); + auto iter = mSprites.begin(); + for (; + iter != mSprites.end(); + ++iter) + { + if (myDrawOrder < (*iter)->GetDrawOrder()) + { + break; + } + } + + // Inserts element before position of iterator + mSprites.insert(iter, sprite); +} + +void Game::RemoveSprite(SpriteComponent* sprite) +{ + auto iter = std::find(mSprites.begin(), mSprites.end(), sprite); + mSprites.erase(iter); +} diff --git a/Chapter08/Game.h b/Chapter08/Game.h new file mode 100644 index 00000000..6c9595b5 --- /dev/null +++ b/Chapter08/Game.h @@ -0,0 +1,74 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "SDL/SDL.h" +#include +#include +#include +#include "Math.h" + +class Game +{ +public: + Game(); + bool Initialize(); + void RunLoop(); + void Shutdown(); + + void AddActor(class Actor* actor); + void RemoveActor(class Actor* actor); + + void AddSprite(class SpriteComponent* sprite); + void RemoveSprite(class SpriteComponent* sprite); + + class Texture* GetTexture(const std::string& fileName); + + // Game-specific (add/remove asteroid) + void AddAsteroid(class Asteroid* ast); + void RemoveAsteroid(class Asteroid* ast); + std::vector& GetAsteroids() { return mAsteroids; } +private: + void ProcessInput(); + void UpdateGame(); + void GenerateOutput(); + bool LoadShaders(); + void CreateSpriteVerts(); + void LoadData(); + void UnloadData(); + + // Map of textures loaded + std::unordered_map mTextures; + + // All the actors in the game + std::vector mActors; + + class InputSystem* mInputSystem; + + // Any pending actors + std::vector mPendingActors; + + // All the sprite components drawn + std::vector mSprites; + + // Sprite shader + class Shader* mSpriteShader; + // Sprite vertex array + class VertexArray* mSpriteVerts; + + SDL_Window* mWindow; + SDL_GLContext mContext; + Uint32 mTicksCount; + bool mIsRunning; + // Track if we're updating actors right now + bool mUpdatingActors; + + // Game-specific + class Ship* mShip; + std::vector mAsteroids; +}; diff --git a/Chapter08/Game.vcxproj b/Chapter08/Game.vcxproj new file mode 100644 index 00000000..af5adf12 --- /dev/null +++ b/Chapter08/Game.vcxproj @@ -0,0 +1,147 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {BC508D87-495F-4554-932D-DD68388B63CC} + Win32Proj + Game + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + /NODEFAULTLIB:msvcrt.lib %(AdditionalOptions) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ..\external\SDL\include;..\external\GLEW\include;..\external\SOIL\include;%(AdditionalIncludeDirectories) + false + Sync + + + Console + true + true + true + ..\external\SDL\lib\win\x86;..\external\GLEW\lib\win\x86;..\external\SOIL\lib\win\x86;%(AdditionalLibraryDirectories) + opengl32.lib;SDL2.lib;SDL2main.lib;SDL2_ttf.lib;SDL2_mixer.lib;SDL2_image.lib;glew32.lib;SOIL.lib;%(AdditionalDependencies) + + + xcopy "$(ProjectDir)\..\external\SDL\lib\win\x86\*.dll" "$(OutDir)" /i /s /y +xcopy "$(ProjectDir)\..\external\GLEW\lib\win\x86\*.dll" "$(OutDir)" /i /s /y + + + + + + \ No newline at end of file diff --git a/Chapter08/Game.vcxproj.filters b/Chapter08/Game.vcxproj.filters new file mode 100644 index 00000000..e78491fe --- /dev/null +++ b/Chapter08/Game.vcxproj.filters @@ -0,0 +1,132 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {12a47348-9a6e-404a-8d7e-2ffa91eb59f6} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + \ No newline at end of file diff --git a/Chapter08/InputComponent.cpp b/Chapter08/InputComponent.cpp new file mode 100644 index 00000000..56ede348 --- /dev/null +++ b/Chapter08/InputComponent.cpp @@ -0,0 +1,48 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "InputComponent.h" +#include "Actor.h" +#include "InputSystem.h" + +InputComponent::InputComponent(class Actor* owner) +:MoveComponent(owner) +,mForwardKey(0) +,mBackKey(0) +,mClockwiseKey(0) +,mCounterClockwiseKey(0) +{ + +} + +void InputComponent::ProcessInput(const InputState& state) +{ + // Calculate forward speed for MoveComponent + float forwardSpeed = 0.0f; + if (state.Keyboard.GetKeyValue(SDL_Scancode(mForwardKey))) + { + forwardSpeed += mMaxForwardSpeed; + } + if (state.Keyboard.GetKeyValue(SDL_Scancode(mBackKey))) + { + forwardSpeed -= mMaxForwardSpeed; + } + SetForwardSpeed(forwardSpeed); + + // Calculate angular speed for MoveComponent + float angularSpeed = 0.0f; + if (state.Keyboard.GetKeyValue(SDL_Scancode(mClockwiseKey))) + { + angularSpeed += mMaxAngularSpeed; + } + if (state.Keyboard.GetKeyValue(SDL_Scancode(mCounterClockwiseKey))) + { + angularSpeed -= mMaxAngularSpeed; + } + SetAngularSpeed(angularSpeed); +} diff --git a/Chapter08/InputComponent.h b/Chapter08/InputComponent.h new file mode 100644 index 00000000..dc5de17e --- /dev/null +++ b/Chapter08/InputComponent.h @@ -0,0 +1,45 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "MoveComponent.h" +#include + +class InputComponent : public MoveComponent +{ +public: + // Lower update order to update first + InputComponent(class Actor* owner); + + void ProcessInput(const struct InputState& state) override; + + // Getters/setters for private variables + float GetMaxForward() const { return mMaxForwardSpeed; } + float GetMaxAngular() const { return mMaxAngularSpeed; } + int GetForwardKey() const { return mForwardKey; } + int GetBackKey() const { return mBackKey; } + int GetClockwiseKey() const { return mClockwiseKey; } + int GetCounterClockwiseKey() const { return mCounterClockwiseKey; } + + void SetMaxForwardSpeed(float speed) { mMaxForwardSpeed = speed; } + void SetMaxAngularSpeed(float speed) { mMaxAngularSpeed = speed; } + void SetForwardKey(int key) { mForwardKey = key; } + void SetBackKey(int key) { mBackKey = key; } + void SetClockwiseKey(int key) { mClockwiseKey = key; } + void SetCounterClockwiseKey(int key) { mCounterClockwiseKey = key; } +private: + // The maximum forward/angular speeds + float mMaxForwardSpeed; + float mMaxAngularSpeed; + // Keys for forward/back movement + int mForwardKey; + int mBackKey; + // Keys for angular movement + int mClockwiseKey; + int mCounterClockwiseKey; +}; diff --git a/Chapter08/InputSystem.cpp b/Chapter08/InputSystem.cpp new file mode 100644 index 00000000..da787cf9 --- /dev/null +++ b/Chapter08/InputSystem.cpp @@ -0,0 +1,282 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "InputSystem.h" +#include +#include + +bool KeyboardState::GetKeyValue(SDL_Scancode keyCode) const +{ + return mCurrState[keyCode] == 1; +} + +ButtonState KeyboardState::GetKeyState(SDL_Scancode keyCode) const +{ + if (mPrevState[keyCode] == 0) + { + if (mCurrState[keyCode] == 0) + { + return ENone; + } + else + { + return EPressed; + } + } + else // Prev state must be 1 + { + if (mCurrState[keyCode] == 0) + { + return EReleased; + } + else + { + return EHeld; + } + } +} + +bool MouseState::GetButtonValue(int button) const +{ + return (SDL_BUTTON(button) & mCurrButtons) == 1; +} + +ButtonState MouseState::GetButtonState(int button) const +{ + int mask = SDL_BUTTON(button); + if ((mask & mPrevButtons) == 0) + { + if ((mask & mCurrButtons) == 0) + { + return ENone; + } + else + { + return EPressed; + } + } + else + { + if ((mask & mCurrButtons) == 0) + { + return EReleased; + } + else + { + return EHeld; + } + } +} + +bool ControllerState::GetButtonValue(SDL_GameControllerButton button) const +{ + return mCurrButtons[button] == 1; +} + +ButtonState ControllerState::GetButtonState(SDL_GameControllerButton button) const +{ + if (mPrevButtons[button] == 0) + { + if (mCurrButtons[button] == 0) + { + return ENone; + } + else + { + return EPressed; + } + } + else // Prev state must be 1 + { + if (mCurrButtons[button] == 0) + { + return EReleased; + } + else + { + return EHeld; + } + } +} + +bool InputSystem::Initialize() +{ + // Keyboard + // Assign current state pointer + mState.Keyboard.mCurrState = SDL_GetKeyboardState(NULL); + // Clear previous state memory + memset(mState.Keyboard.mPrevState, 0, + SDL_NUM_SCANCODES); + + // Mouse (just set everything to 0) + mState.Mouse.mCurrButtons = 0; + mState.Mouse.mPrevButtons = 0; + + // Get the connected controller, if it exists + mController = SDL_GameControllerOpen(0); + // Initialize controller state + mState.Controller.mIsConnected = (mController != nullptr); + memset(mState.Controller.mCurrButtons, 0, + SDL_CONTROLLER_BUTTON_MAX); + memset(mState.Controller.mPrevButtons, 0, + SDL_CONTROLLER_BUTTON_MAX); + + return true; +} + +void InputSystem::Shutdown() +{ +} + +void InputSystem::PrepareForUpdate() +{ + // Copy current state to previous + // Keyboard + memcpy(mState.Keyboard.mPrevState, + mState.Keyboard.mCurrState, + SDL_NUM_SCANCODES); + + // Mouse + mState.Mouse.mPrevButtons = mState.Mouse.mCurrButtons; + mState.Mouse.mIsRelative = false; + mState.Mouse.mScrollWheel = Vector2::Zero; + + // Controller + memcpy(mState.Controller.mPrevButtons, + mState.Controller.mCurrButtons, + SDL_CONTROLLER_BUTTON_MAX); +} + +void InputSystem::Update() +{ + // Mouse + int x = 0, y = 0; + if (mState.Mouse.mIsRelative) + { + mState.Mouse.mCurrButtons = + SDL_GetRelativeMouseState(&x, &y); + } + else + { + mState.Mouse.mCurrButtons = + SDL_GetMouseState(&x, &y); + } + + mState.Mouse.mMousePos.x = static_cast(x); + mState.Mouse.mMousePos.y = static_cast(y); + + // Controller + // Buttons + for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) + { + mState.Controller.mCurrButtons[i] = + SDL_GameControllerGetButton(mController, + SDL_GameControllerButton(i)); + } + + // Triggers + mState.Controller.mLeftTrigger = + Filter1D(SDL_GameControllerGetAxis(mController, + SDL_CONTROLLER_AXIS_TRIGGERLEFT)); + mState.Controller.mRightTrigger = + Filter1D(SDL_GameControllerGetAxis(mController, + SDL_CONTROLLER_AXIS_TRIGGERRIGHT)); + + // Sticks + x = SDL_GameControllerGetAxis(mController, + SDL_CONTROLLER_AXIS_LEFTX); + y = -SDL_GameControllerGetAxis(mController, + SDL_CONTROLLER_AXIS_LEFTY); + mState.Controller.mLeftStick = Filter2D(x, y); + + x = SDL_GameControllerGetAxis(mController, + SDL_CONTROLLER_AXIS_RIGHTX); + y = -SDL_GameControllerGetAxis(mController, + SDL_CONTROLLER_AXIS_RIGHTY); + mState.Controller.mRightStick = Filter2D(x, y); +} + +void InputSystem::ProcessEvent(SDL_Event& event) +{ + switch (event.type) + { + case SDL_MOUSEWHEEL: + mState.Mouse.mScrollWheel = Vector2( + static_cast(event.wheel.x), + static_cast(event.wheel.y)); + break; + default: + break; + } +} + +void InputSystem::SetRelativeMouseMode(bool value) +{ + SDL_bool set = value ? SDL_TRUE : SDL_FALSE; + SDL_SetRelativeMouseMode(set); + + mState.Mouse.mIsRelative = value; +} + +float InputSystem::Filter1D(int input) +{ + // A value < dead zone is interpreted as 0% + const int deadZone = 250; + // A value > max value is interpreted as 100% + const int maxValue = 30000; + + float retVal = 0.0f; + + // Take absolute value of input + int absValue = input > 0 ? input : -input; + // Ignore input within dead zone + if (absValue > deadZone) + { + // Compute fractional value between dead zone and max value + retVal = static_cast(absValue - deadZone) / + (maxValue - deadZone); + // Make sure sign matches original value + retVal = input > 0 ? retVal : -1.0f * retVal; + // Clamp between -1.0f and 1.0f + retVal = Math::Clamp(retVal, -1.0f, 1.0f); + } + + return retVal; +} + +Vector2 InputSystem::Filter2D(int inputX, int inputY) +{ + const float deadZone = 8000.0f; + const float maxValue = 30000.0f; + + // Make into 2D vector + Vector2 dir; + dir.x = static_cast(inputX); + dir.y = static_cast(inputY); + + float length = dir.Length(); + + // If length < deadZone, should be no input + if (length < deadZone) + { + dir = Vector2::Zero; + } + else + { + // Calculate fractional value between + // dead zone and max value circles + float f = (length - deadZone) / (maxValue - deadZone); + // Clamp f between 0.0f and 1.0f + f = Math::Clamp(f, 0.0f, 1.0f); + // Normalize the vector, and then scale it to the + // fractional value + dir *= f / length; + } + + return dir; +} diff --git a/Chapter08/InputSystem.h b/Chapter08/InputSystem.h new file mode 100644 index 00000000..a1eccc04 --- /dev/null +++ b/Chapter08/InputSystem.h @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include +#include +#include "Math.h" + +// The different button states +enum ButtonState +{ + ENone, + EPressed, + EReleased, + EHeld +}; + +// Helper for keyboard input +class KeyboardState +{ +public: + // Friend so InputSystem can easily update it + friend class InputSystem; + // Get just the boolean true/false value of key + bool GetKeyValue(SDL_Scancode keyCode) const; + // Get a state based on current and previous frame + ButtonState GetKeyState(SDL_Scancode keyCode) const; +private: + const Uint8* mCurrState; + Uint8 mPrevState[SDL_NUM_SCANCODES]; +}; + +// Helper for mouse input +class MouseState +{ +public: + friend class InputSystem; + + // For mouse position + const Vector2& GetPosition() const { return mMousePos; } + const Vector2& GetScrollWheel() const { return mScrollWheel; } + bool IsRelative() const { return mIsRelative; } + + // For buttons + bool GetButtonValue(int button) const; + ButtonState GetButtonState(int button) const; +private: + // Store current mouse position + Vector2 mMousePos; + // Motion of scroll wheel + Vector2 mScrollWheel; + // Store button data + Uint32 mCurrButtons; + Uint32 mPrevButtons; + // Are we in relative mouse mode + bool mIsRelative; +}; + +// Helper for controller input +class ControllerState +{ +public: + friend class InputSystem; + + // For buttons + bool GetButtonValue(SDL_GameControllerButton button) const; + ButtonState GetButtonState(SDL_GameControllerButton button) const; + + const Vector2& GetLeftStick() const { return mLeftStick; } + const Vector2& GetRightStick() const { return mRightStick; } + float GetLeftTrigger() const { return mLeftTrigger; } + float GetRightTrigger() const { return mRightTrigger; } + + bool GetIsConnected() const { return mIsConnected; } +private: + // Current/previous buttons + Uint8 mCurrButtons[SDL_CONTROLLER_BUTTON_MAX]; + Uint8 mPrevButtons[SDL_CONTROLLER_BUTTON_MAX]; + // Left/right sticks + Vector2 mLeftStick; + Vector2 mRightStick; + // Left/right trigger + float mLeftTrigger; + float mRightTrigger; + // Is this controller connected? + bool mIsConnected; +}; + +// Wrapper that contains current state of input +struct InputState +{ + KeyboardState Keyboard; + MouseState Mouse; + ControllerState Controller; +}; + +class InputSystem +{ +public: + bool Initialize(); + void Shutdown(); + + // Called right before SDL_PollEvents loop + void PrepareForUpdate(); + // Called after SDL_PollEvents loop + void Update(); + // Called to process an SDL event in input system + void ProcessEvent(union SDL_Event& event); + + const InputState& GetState() const { return mState; } + + void SetRelativeMouseMode(bool value); +private: + float Filter1D(int input); + Vector2 Filter2D(int inputX, int inputY); + InputState mState; + SDL_GameController* mController; +}; diff --git a/Chapter08/Laser.cpp b/Chapter08/Laser.cpp new file mode 100644 index 00000000..a03c1981 --- /dev/null +++ b/Chapter08/Laser.cpp @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Laser.h" +#include "SpriteComponent.h" +#include "MoveComponent.h" +#include "Game.h" +#include "CircleComponent.h" +#include "Asteroid.h" + +Laser::Laser(Game* game) + :Actor(game) + ,mDeathTimer(1.0f) +{ + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this); + sc->SetTexture(game->GetTexture("Assets/Laser.png")); + + // Create a move component, and set a forward speed + MoveComponent* mc = new MoveComponent(this); + mc->SetForwardSpeed(800.0f); + + // Create a circle component (for collision) + mCircle = new CircleComponent(this); + mCircle->SetRadius(11.0f); +} + +void Laser::UpdateActor(float deltaTime) +{ + // If we run out of time, laser is dead + mDeathTimer -= deltaTime; + if (mDeathTimer <= 0.0f) + { + SetState(EDead); + } + else + { + // Do we intersect with an asteroid? + for (auto ast : GetGame()->GetAsteroids()) + { + if (Intersect(*mCircle, *(ast->GetCircle()))) + { + // The first asteroid we intersect with, + // set ourselves and the asteroid to dead + SetState(EDead); + ast->SetState(EDead); + break; + } + } + } +} diff --git a/Chapter08/Laser.h b/Chapter08/Laser.h new file mode 100644 index 00000000..f1afd638 --- /dev/null +++ b/Chapter08/Laser.h @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Laser : public Actor +{ +public: + Laser(class Game* game); + + void UpdateActor(float deltaTime) override; +private: + class CircleComponent* mCircle; + float mDeathTimer; +}; diff --git a/Chapter08/Main.cpp b/Chapter08/Main.cpp new file mode 100644 index 00000000..22ea0c69 --- /dev/null +++ b/Chapter08/Main.cpp @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Game.h" + +int main(int argc, char** argv) +{ + Game game; + bool success = game.Initialize(); + if (success) + { + game.RunLoop(); + } + game.Shutdown(); + return 0; +} diff --git a/Chapter08/Math.cpp b/Chapter08/Math.cpp new file mode 100644 index 00000000..a16e7261 --- /dev/null +++ b/Chapter08/Math.cpp @@ -0,0 +1,239 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Math.h" + +const Vector2 Vector2::Zero(0.0f, 0.0f); +const Vector2 Vector2::UnitX(1.0f, 0.0f); +const Vector2 Vector2::UnitY(0.0f, 1.0f); +const Vector2 Vector2::NegUnitX(-1.0f, 0.0f); +const Vector2 Vector2::NegUnitY(0.0f, -1.0f); + +const Vector3 Vector3::Zero(0.0f, 0.0f, 0.f); +const Vector3 Vector3::UnitX(1.0f, 0.0f, 0.0f); +const Vector3 Vector3::UnitY(0.0f, 1.0f, 0.0f); +const Vector3 Vector3::UnitZ(0.0f, 0.0f, 1.0f); +const Vector3 Vector3::NegUnitX(-1.0f, 0.0f, 0.0f); +const Vector3 Vector3::NegUnitY(0.0f, -1.0f, 0.0f); +const Vector3 Vector3::NegUnitZ(0.0f, 0.0f, -1.0f); +const Vector3 Vector3::Infinity(Math::Infinity, Math::Infinity, Math::Infinity); +const Vector3 Vector3::NegInfinity(Math::NegInfinity, Math::NegInfinity, Math::NegInfinity); + +static float m3Ident[3][3] = +{ + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f } +}; +const Matrix3 Matrix3::Identity(m3Ident); + +static float m4Ident[4][4] = +{ + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } +}; + +const Matrix4 Matrix4::Identity(m4Ident); + +const Quaternion Quaternion::Identity(0.0f, 0.0f, 0.0f, 1.0f); + +Vector2 Vector2::Transform(const Vector2& vec, const Matrix3& mat, float w /*= 1.0f*/) +{ + Vector2 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + w * mat.mat[2][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + w * mat.mat[2][1]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +Vector3 Vector3::Transform(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + //ignore w since we aren't returning a new value for it... + return retVal; +} + +// This will transform the vector and renormalize the w component +Vector3 Vector3::TransformWithPerspDiv(const Vector3& vec, const Matrix4& mat, float w /*= 1.0f*/) +{ + Vector3 retVal; + retVal.x = vec.x * mat.mat[0][0] + vec.y * mat.mat[1][0] + + vec.z * mat.mat[2][0] + w * mat.mat[3][0]; + retVal.y = vec.x * mat.mat[0][1] + vec.y * mat.mat[1][1] + + vec.z * mat.mat[2][1] + w * mat.mat[3][1]; + retVal.z = vec.x * mat.mat[0][2] + vec.y * mat.mat[1][2] + + vec.z * mat.mat[2][2] + w * mat.mat[3][2]; + float transformedW = vec.x * mat.mat[0][3] + vec.y * mat.mat[1][3] + + vec.z * mat.mat[2][3] + w * mat.mat[3][3]; + if (!Math::NearZero(Math::Abs(transformedW))) + { + transformedW = 1.0f / transformedW; + retVal *= transformedW; + } + return retVal; +} + +// Transform a Vector3 by a quaternion +Vector3 Vector3::Transform(const Vector3& v, const Quaternion& q) +{ + // v + 2.0*cross(q.xyz, cross(q.xyz,v) + q.w*v); + Vector3 qv(q.x, q.y, q.z); + Vector3 retVal = v; + retVal += 2.0f * Vector3::Cross(qv, Vector3::Cross(qv, v) + q.w * v); + return retVal; +} + +void Matrix4::Invert() +{ + // Thanks slow math + // This is a really janky way to unroll everything... + float tmp[12]; + float src[16]; + float dst[16]; + float det; + + // Transpose matrix + // row 1 to col 1 + src[0] = mat[0][0]; + src[4] = mat[0][1]; + src[8] = mat[0][2]; + src[12] = mat[0][3]; + + // row 2 to col 2 + src[1] = mat[1][0]; + src[5] = mat[1][1]; + src[9] = mat[1][2]; + src[13] = mat[1][3]; + + // row 3 to col 3 + src[2] = mat[2][0]; + src[6] = mat[2][1]; + src[10] = mat[2][2]; + src[14] = mat[2][3]; + + // row 4 to col 4 + src[3] = mat[3][0]; + src[7] = mat[3][1]; + src[11] = mat[3][2]; + src[15] = mat[3][3]; + + // Calculate cofactors + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + dst[0] = tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]; + dst[0] -= tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]; + dst[1] = tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]; + dst[1] -= tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]; + dst[2] = tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]; + dst[2] -= tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]; + dst[3] = tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]; + dst[3] -= tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]; + dst[4] = tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]; + dst[4] -= tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]; + dst[5] = tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]; + dst[5] -= tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]; + dst[6] = tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]; + dst[6] -= tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]; + dst[7] = tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]; + dst[7] -= tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]; + + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + dst[8] = tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]; + dst[8] -= tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]; + dst[9] = tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]; + dst[9] -= tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]; + dst[10] = tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]; + dst[10] -= tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]; + dst[11] = tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]; + dst[11] -= tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]; + dst[12] = tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]; + dst[12] -= tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]; + dst[13] = tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]; + dst[13] -= tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]; + dst[14] = tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]; + dst[14] -= tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]; + dst[15] = tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]; + dst[15] -= tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]; + + // Calculate determinant + det = src[0] * dst[0] + src[1] * dst[1] + src[2] * dst[2] + src[3] * dst[3]; + + // Inverse of matrix is divided by determinant + det = 1 / det; + for (int j = 0; j < 16; j++) + { + dst[j] *= det; + } + + // Set it back + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + mat[i][j] = dst[i * 4 + j]; + } + } +} + +Matrix4 Matrix4::CreateFromQuaternion(const class Quaternion& q) +{ + float mat[4][4]; + + mat[0][0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z; + mat[0][1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z; + mat[0][2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y; + mat[0][3] = 0.0f; + + mat[1][0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z; + mat[1][1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z; + mat[1][2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x; + mat[1][3] = 0.0f; + + mat[2][0] = 2.0f * q.x * q.z + 2.0f * q.w * q.y; + mat[2][1] = 2.0f * q.y * q.z - 2.0f * q.w * q.x; + mat[2][2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y; + mat[2][3] = 0.0f; + + mat[3][0] = 0.0f; + mat[3][1] = 0.0f; + mat[3][2] = 0.0f; + mat[3][3] = 1.0f; + + return Matrix4(mat); +} diff --git a/Chapter08/Math.h b/Chapter08/Math.h new file mode 100644 index 00000000..752963f1 --- /dev/null +++ b/Chapter08/Math.h @@ -0,0 +1,1033 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +namespace Math +{ + const float Pi = 3.1415926535f; + const float TwoPi = Pi * 2.0f; + const float PiOver2 = Pi / 2.0f; + const float Infinity = std::numeric_limits::infinity(); + const float NegInfinity = -std::numeric_limits::infinity(); + + inline float ToRadians(float degrees) + { + return degrees * Pi / 180.0f; + } + + inline float ToDegrees(float radians) + { + return radians * 180.0f / Pi; + } + + inline bool NearZero(float val, float epsilon = 0.001f) + { + if (fabs(val) <= epsilon) + { + return true; + } + else + { + return false; + } + } + + template + T Max(const T& a, const T& b) + { + return (a < b ? b : a); + } + + template + T Min(const T& a, const T& b) + { + return (a < b ? a : b); + } + + template + T Clamp(const T& value, const T& lower, const T& upper) + { + return Min(upper, Max(lower, value)); + } + + inline float Abs(float value) + { + return fabs(value); + } + + inline float Cos(float angle) + { + return cosf(angle); + } + + inline float Sin(float angle) + { + return sinf(angle); + } + + inline float Tan(float angle) + { + return tanf(angle); + } + + inline float Acos(float value) + { + return acosf(value); + } + + inline float Atan2(float y, float x) + { + return atan2f(y, x); + } + + inline float Cot(float angle) + { + return 1.0f / Tan(angle); + } + + inline float Lerp(float a, float b, float f) + { + return a + f * (b - a); + } + + inline float Sqrt(float value) + { + return sqrtf(value); + } + + inline float Fmod(float numer, float denom) + { + return fmod(numer, denom); + } +} + +// 2D Vector +class Vector2 +{ +public: + float x; + float y; + + Vector2() + :x(0.0f) + ,y(0.0f) + {} + + explicit Vector2(float inX, float inY) + :x(inX) + ,y(inY) + {} + + // Set both components in one line + void Set(float inX, float inY) + { + x = inX; + y = inY; + } + + // Vector addition (a + b) + friend Vector2 operator+(const Vector2& a, const Vector2& b) + { + return Vector2(a.x + b.x, a.y + b.y); + } + + // Vector subtraction (a - b) + friend Vector2 operator-(const Vector2& a, const Vector2& b) + { + return Vector2(a.x - b.x, a.y - b.y); + } + + // Component-wise multiplication + // (a.x * b.x, ...) + friend Vector2 operator*(const Vector2& a, const Vector2& b) + { + return Vector2(a.x * b.x, a.y * b.y); + } + + // Scalar multiplication + friend Vector2 operator*(const Vector2& vec, float scalar) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar multiplication + friend Vector2 operator*(float scalar, const Vector2& vec) + { + return Vector2(vec.x * scalar, vec.y * scalar); + } + + // Scalar *= + Vector2& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + return *this; + } + + // Vector += + Vector2& operator+=(const Vector2& right) + { + x += right.x; + y += right.y; + return *this; + } + + // Vector -= + Vector2& operator-=(const Vector2& right) + { + x -= right.x; + y -= right.y; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + } + + // Normalize the provided vector + static Vector2 Normalize(const Vector2& vec) + { + Vector2 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector2& a, const Vector2& b) + { + return (a.x * b.x + a.y * b.y); + } + + // Lerp from A to B by f + static Vector2 Lerp(const Vector2& a, const Vector2& b, float f) + { + return Vector2(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector2 Reflect(const Vector2& v, const Vector2& n) + { + return v - 2.0f * Vector2::Dot(v, n) * n; + } + + // Transform vector by matrix + static Vector2 Transform(const Vector2& vec, const class Matrix3& mat, float w = 1.0f); + + static const Vector2 Zero; + static const Vector2 UnitX; + static const Vector2 UnitY; + static const Vector2 NegUnitX; + static const Vector2 NegUnitY; +}; + +// 3D Vector +class Vector3 +{ +public: + float x; + float y; + float z; + + Vector3() + :x(0.0f) + ,y(0.0f) + ,z(0.0f) + {} + + explicit Vector3(float inX, float inY, float inZ) + :x(inX) + ,y(inY) + ,z(inZ) + {} + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&x); + } + + // Set all three components in one line + void Set(float inX, float inY, float inZ) + { + x = inX; + y = inY; + z = inZ; + } + + // Vector addition (a + b) + friend Vector3 operator+(const Vector3& a, const Vector3& b) + { + return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); + } + + // Vector subtraction (a - b) + friend Vector3 operator-(const Vector3& a, const Vector3& b) + { + return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); + } + + // Component-wise multiplication + friend Vector3 operator*(const Vector3& left, const Vector3& right) + { + return Vector3(left.x * right.x, left.y * right.y, left.z * right.z); + } + + // Scalar multiplication + friend Vector3 operator*(const Vector3& vec, float scalar) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar multiplication + friend Vector3 operator*(float scalar, const Vector3& vec) + { + return Vector3(vec.x * scalar, vec.y * scalar, vec.z * scalar); + } + + // Scalar *= + Vector3& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + z *= scalar; + return *this; + } + + // Vector += + Vector3& operator+=(const Vector3& right) + { + x += right.x; + y += right.y; + z += right.z; + return *this; + } + + // Vector -= + Vector3& operator-=(const Vector3& right) + { + x -= right.x; + y -= right.y; + z -= right.z; + return *this; + } + + // Length squared of vector + float LengthSq() const + { + return (x*x + y*y + z*z); + } + + // Length of vector + float Length() const + { + return (Math::Sqrt(LengthSq())); + } + + // Normalize this vector + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + } + + // Normalize the provided vector + static Vector3 Normalize(const Vector3& vec) + { + Vector3 temp = vec; + temp.Normalize(); + return temp; + } + + // Dot product between two vectors (a dot b) + static float Dot(const Vector3& a, const Vector3& b) + { + return (a.x * b.x + a.y * b.y + a.z * b.z); + } + + // Cross product between two vectors (a cross b) + static Vector3 Cross(const Vector3& a, const Vector3& b) + { + Vector3 temp; + temp.x = a.y * b.z - a.z * b.y; + temp.y = a.z * b.x - a.x * b.z; + temp.z = a.x * b.y - a.y * b.x; + return temp; + } + + // Lerp from A to B by f + static Vector3 Lerp(const Vector3& a, const Vector3& b, float f) + { + return Vector3(a + f * (b - a)); + } + + // Reflect V about (normalized) N + static Vector3 Reflect(const Vector3& v, const Vector3& n) + { + return v - 2.0f * Vector3::Dot(v, n) * n; + } + + static Vector3 Transform(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + // This will transform the vector and renormalize the w component + static Vector3 TransformWithPerspDiv(const Vector3& vec, const class Matrix4& mat, float w = 1.0f); + + // Transform a Vector3 by a quaternion + static Vector3 Transform(const Vector3& v, const class Quaternion& q); + + static const Vector3 Zero; + static const Vector3 UnitX; + static const Vector3 UnitY; + static const Vector3 UnitZ; + static const Vector3 NegUnitX; + static const Vector3 NegUnitY; + static const Vector3 NegUnitZ; + static const Vector3 Infinity; + static const Vector3 NegInfinity; +}; + +// 3x3 Matrix +class Matrix3 +{ +public: + float mat[3][3]; + + Matrix3() + { + *this = Matrix3::Identity; + } + + explicit Matrix3(float inMat[3][3]) + { + memcpy(mat, inMat, 9 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication + friend Matrix3 operator*(const Matrix3& left, const Matrix3& right) + { + Matrix3 retVal; + // row 0 + retVal.mat[0][0] = + left.mat[0][0] * right.mat[0][0] + + left.mat[0][1] * right.mat[1][0] + + left.mat[0][2] * right.mat[2][0]; + + retVal.mat[0][1] = + left.mat[0][0] * right.mat[0][1] + + left.mat[0][1] * right.mat[1][1] + + left.mat[0][2] * right.mat[2][1]; + + retVal.mat[0][2] = + left.mat[0][0] * right.mat[0][2] + + left.mat[0][1] * right.mat[1][2] + + left.mat[0][2] * right.mat[2][2]; + + // row 1 + retVal.mat[1][0] = + left.mat[1][0] * right.mat[0][0] + + left.mat[1][1] * right.mat[1][0] + + left.mat[1][2] * right.mat[2][0]; + + retVal.mat[1][1] = + left.mat[1][0] * right.mat[0][1] + + left.mat[1][1] * right.mat[1][1] + + left.mat[1][2] * right.mat[2][1]; + + retVal.mat[1][2] = + left.mat[1][0] * right.mat[0][2] + + left.mat[1][1] * right.mat[1][2] + + left.mat[1][2] * right.mat[2][2]; + + // row 2 + retVal.mat[2][0] = + left.mat[2][0] * right.mat[0][0] + + left.mat[2][1] * right.mat[1][0] + + left.mat[2][2] * right.mat[2][0]; + + retVal.mat[2][1] = + left.mat[2][0] * right.mat[0][1] + + left.mat[2][1] * right.mat[1][1] + + left.mat[2][2] * right.mat[2][1]; + + retVal.mat[2][2] = + left.mat[2][0] * right.mat[0][2] + + left.mat[2][1] * right.mat[1][2] + + left.mat[2][2] * right.mat[2][2]; + + return retVal; + } + + Matrix3& operator*=(const Matrix3& right) + { + *this = *this * right; + return *this; + } + + // Create a scale matrix with x and y scales + static Matrix3 CreateScale(float xScale, float yScale) + { + float temp[3][3] = + { + { xScale, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + static Matrix3 CreateScale(const Vector2& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y); + } + + // Create a scale matrix with a uniform factor + static Matrix3 CreateScale(float scale) + { + return CreateScale(scale, scale); + } + + // Create a rotation matrix about the Z axis + // theta is in radians + static Matrix3 CreateRotation(float theta) + { + float temp[3][3] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 1.0f }, + }; + return Matrix3(temp); + } + + // Create a translation matrix (on the xy-plane) + static Matrix3 CreateTranslation(const Vector2& trans) + { + float temp[3][3] = + { + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, 1.0f }, + }; + return Matrix3(temp); + } + + static const Matrix3 Identity; +}; + +// 4x4 Matrix +class Matrix4 +{ +public: + float mat[4][4]; + + Matrix4() + { + *this = Matrix4::Identity; + } + + explicit Matrix4(float inMat[4][4]) + { + memcpy(mat, inMat, 16 * sizeof(float)); + } + + // Cast to a const float pointer + const float* GetAsFloatPtr() const + { + return reinterpret_cast(&mat[0][0]); + } + + // Matrix multiplication (a * b) + friend Matrix4 operator*(const Matrix4& a, const Matrix4& b) + { + Matrix4 retVal; + // row 0 + retVal.mat[0][0] = + a.mat[0][0] * b.mat[0][0] + + a.mat[0][1] * b.mat[1][0] + + a.mat[0][2] * b.mat[2][0] + + a.mat[0][3] * b.mat[3][0]; + + retVal.mat[0][1] = + a.mat[0][0] * b.mat[0][1] + + a.mat[0][1] * b.mat[1][1] + + a.mat[0][2] * b.mat[2][1] + + a.mat[0][3] * b.mat[3][1]; + + retVal.mat[0][2] = + a.mat[0][0] * b.mat[0][2] + + a.mat[0][1] * b.mat[1][2] + + a.mat[0][2] * b.mat[2][2] + + a.mat[0][3] * b.mat[3][2]; + + retVal.mat[0][3] = + a.mat[0][0] * b.mat[0][3] + + a.mat[0][1] * b.mat[1][3] + + a.mat[0][2] * b.mat[2][3] + + a.mat[0][3] * b.mat[3][3]; + + // row 1 + retVal.mat[1][0] = + a.mat[1][0] * b.mat[0][0] + + a.mat[1][1] * b.mat[1][0] + + a.mat[1][2] * b.mat[2][0] + + a.mat[1][3] * b.mat[3][0]; + + retVal.mat[1][1] = + a.mat[1][0] * b.mat[0][1] + + a.mat[1][1] * b.mat[1][1] + + a.mat[1][2] * b.mat[2][1] + + a.mat[1][3] * b.mat[3][1]; + + retVal.mat[1][2] = + a.mat[1][0] * b.mat[0][2] + + a.mat[1][1] * b.mat[1][2] + + a.mat[1][2] * b.mat[2][2] + + a.mat[1][3] * b.mat[3][2]; + + retVal.mat[1][3] = + a.mat[1][0] * b.mat[0][3] + + a.mat[1][1] * b.mat[1][3] + + a.mat[1][2] * b.mat[2][3] + + a.mat[1][3] * b.mat[3][3]; + + // row 2 + retVal.mat[2][0] = + a.mat[2][0] * b.mat[0][0] + + a.mat[2][1] * b.mat[1][0] + + a.mat[2][2] * b.mat[2][0] + + a.mat[2][3] * b.mat[3][0]; + + retVal.mat[2][1] = + a.mat[2][0] * b.mat[0][1] + + a.mat[2][1] * b.mat[1][1] + + a.mat[2][2] * b.mat[2][1] + + a.mat[2][3] * b.mat[3][1]; + + retVal.mat[2][2] = + a.mat[2][0] * b.mat[0][2] + + a.mat[2][1] * b.mat[1][2] + + a.mat[2][2] * b.mat[2][2] + + a.mat[2][3] * b.mat[3][2]; + + retVal.mat[2][3] = + a.mat[2][0] * b.mat[0][3] + + a.mat[2][1] * b.mat[1][3] + + a.mat[2][2] * b.mat[2][3] + + a.mat[2][3] * b.mat[3][3]; + + // row 3 + retVal.mat[3][0] = + a.mat[3][0] * b.mat[0][0] + + a.mat[3][1] * b.mat[1][0] + + a.mat[3][2] * b.mat[2][0] + + a.mat[3][3] * b.mat[3][0]; + + retVal.mat[3][1] = + a.mat[3][0] * b.mat[0][1] + + a.mat[3][1] * b.mat[1][1] + + a.mat[3][2] * b.mat[2][1] + + a.mat[3][3] * b.mat[3][1]; + + retVal.mat[3][2] = + a.mat[3][0] * b.mat[0][2] + + a.mat[3][1] * b.mat[1][2] + + a.mat[3][2] * b.mat[2][2] + + a.mat[3][3] * b.mat[3][2]; + + retVal.mat[3][3] = + a.mat[3][0] * b.mat[0][3] + + a.mat[3][1] * b.mat[1][3] + + a.mat[3][2] * b.mat[2][3] + + a.mat[3][3] * b.mat[3][3]; + + return retVal; + } + + Matrix4& operator*=(const Matrix4& right) + { + *this = *this * right; + return *this; + } + + // Invert the matrix - super slow + void Invert(); + + // Get the translation component of the matrix + Vector3 GetTranslation() const + { + return Vector3(mat[3][0], mat[3][1], mat[3][2]); + } + + // Get the X axis of the matrix (forward) + Vector3 GetXAxis() const + { + return Vector3::Normalize(Vector3(mat[0][0], mat[0][1], mat[0][2])); + } + + // Get the Y axis of the matrix (left) + Vector3 GetYAxis() const + { + return Vector3::Normalize(Vector3(mat[1][0], mat[1][1], mat[1][2])); + } + + // Get the Z axis of the matrix (up) + Vector3 GetZAxis() const + { + return Vector3::Normalize(Vector3(mat[2][0], mat[2][1], mat[2][2])); + } + + // Extract the scale component from the matrix + Vector3 GetScale() const + { + Vector3 retVal; + retVal.x = Vector3(mat[0][0], mat[0][1], mat[0][2]).Length(); + retVal.y = Vector3(mat[1][0], mat[1][1], mat[1][2]).Length(); + retVal.z = Vector3(mat[2][0], mat[2][1], mat[2][2]).Length(); + return retVal; + } + + // Create a scale matrix with x, y, and z scales + static Matrix4 CreateScale(float xScale, float yScale, float zScale) + { + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, zScale, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateScale(const Vector3& scaleVector) + { + return CreateScale(scaleVector.x, scaleVector.y, scaleVector.z); + } + + // Create a scale matrix with a uniform factor + static Matrix4 CreateScale(float scale) + { + return CreateScale(scale, scale, scale); + } + + // Rotation about x-axis + static Matrix4 CreateRotationX(float theta) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f , 0.0f }, + { 0.0f, Math::Cos(theta), Math::Sin(theta), 0.0f }, + { 0.0f, -Math::Sin(theta), Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about y-axis + static Matrix4 CreateRotationY(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), 0.0f, -Math::Sin(theta), 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { Math::Sin(theta), 0.0f, Math::Cos(theta), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Rotation about z-axis + static Matrix4 CreateRotationZ(float theta) + { + float temp[4][4] = + { + { Math::Cos(theta), Math::Sin(theta), 0.0f, 0.0f }, + { -Math::Sin(theta), Math::Cos(theta), 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f }, + }; + return Matrix4(temp); + } + + // Create a rotation matrix from a quaternion + static Matrix4 CreateFromQuaternion(const class Quaternion& q); + + static Matrix4 CreateTranslation(const Vector3& trans) + { + float temp[4][4] = + { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateLookAt(const Vector3& eye, const Vector3& target, const Vector3& up) + { + Vector3 zaxis = Vector3::Normalize(target - eye); + Vector3 xaxis = Vector3::Normalize(Vector3::Cross(up, zaxis)); + Vector3 yaxis = Vector3::Normalize(Vector3::Cross(zaxis, xaxis)); + Vector3 trans; + trans.x = -Vector3::Dot(xaxis, eye); + trans.y = -Vector3::Dot(yaxis, eye); + trans.z = -Vector3::Dot(zaxis, eye); + + float temp[4][4] = + { + { xaxis.x, yaxis.x, zaxis.x, 0.0f }, + { xaxis.y, yaxis.y, zaxis.y, 0.0f }, + { xaxis.z, yaxis.z, zaxis.z, 0.0f }, + { trans.x, trans.y, trans.z, 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreateOrtho(float width, float height, float near, float far) + { + float temp[4][4] = + { + { 2.0f / width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f / height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f / (far - near), 0.0f }, + { 0.0f, 0.0f, near / (near - far), 1.0f } + }; + return Matrix4(temp); + } + + static Matrix4 CreatePerspectiveFOV(float fovY, float width, float height, float near, float far) + { + float yScale = Math::Cot(fovY / 2.0f); + float xScale = yScale * height / width; + float temp[4][4] = + { + { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, far / (far - near), 1.0f }, + { 0.0f, 0.0f, -near * far / (far - near), 0.0f } + }; + return Matrix4(temp); + } + + // Create "Simple" View-Projection Matrix from Chapter 6 + static Matrix4 CreateSimpleViewProj(float width, float height) + { + float temp[4][4] = + { + { 2.0f/width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/height, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f } + }; + return Matrix4(temp); + } + + static const Matrix4 Identity; +}; + +// (Unit) Quaternion +class Quaternion +{ +public: + float x; + float y; + float z; + float w; + + Quaternion() + { + *this = Quaternion::Identity; + } + + // This directly sets the quaternion components -- + // don't use for axis/angle + explicit Quaternion(float inX, float inY, float inZ, float inW) + { + Set(inX, inY, inZ, inW); + } + + // Construct the quaternion from an axis and angle + // It is assumed that axis is already normalized, + // and the angle is in radians + explicit Quaternion(const Vector3& axis, float angle) + { + float scalar = Math::Sin(angle / 2.0f); + x = axis.x * scalar; + y = axis.y * scalar; + z = axis.z * scalar; + w = Math::Cos(angle / 2.0f); + } + + // Directly set the internal components + void Set(float inX, float inY, float inZ, float inW) + { + x = inX; + y = inY; + z = inZ; + w = inW; + } + + void Conjugate() + { + x *= -1.0f; + y *= -1.0f; + z *= -1.0f; + } + + float LengthSq() const + { + return (x*x + y*y + z*z + w*w); + } + + float Length() const + { + return Math::Sqrt(LengthSq()); + } + + void Normalize() + { + float length = Length(); + x /= length; + y /= length; + z /= length; + w /= length; + } + + // Normalize the provided quaternion + static Quaternion Normalize(const Quaternion& q) + { + Quaternion retVal = q; + retVal.Normalize(); + return retVal; + } + + // Linear interpolation + static Quaternion Lerp(const Quaternion& a, const Quaternion& b, float f) + { + Quaternion retVal; + retVal.x = Math::Lerp(a.x, b.x, f); + retVal.y = Math::Lerp(a.y, b.y, f); + retVal.z = Math::Lerp(a.z, b.z, f); + retVal.w = Math::Lerp(a.w, b.w, f); + retVal.Normalize(); + return retVal; + } + + static float Dot(const Quaternion& a, const Quaternion& b) + { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + // Spherical Linear Interpolation + static Quaternion Slerp(const Quaternion& a, const Quaternion& b, float f) + { + float rawCosm = Quaternion::Dot(a, b); + + float cosom = -rawCosm; + if (rawCosm >= 0.0f) + { + cosom = rawCosm; + } + + float scale0, scale1; + + if (cosom < 0.9999f) + { + const float omega = Math::Acos(cosom); + const float invSin = 1.f / Math::Sin(omega); + scale0 = Math::Sin((1.f - f) * omega) * invSin; + scale1 = Math::Sin(f * omega) * invSin; + } + else + { + // Use linear interpolation if the quaternions + // are collinear + scale0 = 1.0f - f; + scale1 = f; + } + + if (rawCosm < 0.0f) + { + scale1 = -scale1; + } + + Quaternion retVal; + retVal.x = scale0 * a.x + scale1 * b.x; + retVal.y = scale0 * a.y + scale1 * b.y; + retVal.z = scale0 * a.z + scale1 * b.z; + retVal.w = scale0 * a.w + scale1 * b.w; + retVal.Normalize(); + return retVal; + } + + // Concatenate + // Rotate by q FOLLOWED BY p + static Quaternion Concatenate(const Quaternion& q, const Quaternion& p) + { + Quaternion retVal; + + // Vector component is: + // ps * qv + qs * pv + pv x qv + Vector3 qv(q.x, q.y, q.z); + Vector3 pv(p.x, p.y, p.z); + Vector3 newVec = p.w * qv + q.w * pv + Vector3::Cross(pv, qv); + retVal.x = newVec.x; + retVal.y = newVec.y; + retVal.z = newVec.z; + + // Scalar component is: + // ps * qs - pv . qv + retVal.w = p.w * q.w - Vector3::Dot(pv, qv); + + return retVal; + } + + static const Quaternion Identity; +}; + +namespace Color +{ + static const Vector3 Black(0.0f, 0.0f, 0.0f); + static const Vector3 White(1.0f, 1.0f, 1.0f); + static const Vector3 Red(1.0f, 0.0f, 0.0f); + static const Vector3 Green(0.0f, 1.0f, 0.0f); + static const Vector3 Blue(0.0f, 0.0f, 1.0f); + static const Vector3 Yellow(1.0f, 1.0f, 0.0f); + static const Vector3 LightYellow(1.0f, 1.0f, 0.88f); + static const Vector3 LightBlue(0.68f, 0.85f, 0.9f); + static const Vector3 LightPink(1.0f, 0.71f, 0.76f); + static const Vector3 LightGreen(0.56f, 0.93f, 0.56f); +} diff --git a/Chapter08/MoveComponent.cpp b/Chapter08/MoveComponent.cpp new file mode 100644 index 00000000..c1a5d5b2 --- /dev/null +++ b/Chapter08/MoveComponent.cpp @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "MoveComponent.h" +#include "Actor.h" + +MoveComponent::MoveComponent(class Actor* owner, int updateOrder) +:Component(owner, updateOrder) +,mAngularSpeed(0.0f) +,mForwardSpeed(0.0f) +{ + +} + +void MoveComponent::Update(float deltaTime) +{ + if (!Math::NearZero(mAngularSpeed)) + { + float rot = mOwner->GetRotation(); + rot += mAngularSpeed * deltaTime; + mOwner->SetRotation(rot); + } + + if (!Math::NearZero(mForwardSpeed)) + { + Vector2 pos = mOwner->GetPosition(); + pos += mOwner->GetForward() * mForwardSpeed * deltaTime; + + // Screen wrapping (for asteroids) + if (pos.x < -512.0f) { pos.x = 510.0f; } + else if (pos.x > 512.0f) { pos.x = -510.0f; } + if (pos.y < -384.0f) { pos.y = 382.0f; } + else if (pos.y > 384.0f) { pos.y = -382.0f; } + mOwner->SetPosition(pos); + } +} diff --git a/Chapter08/MoveComponent.h b/Chapter08/MoveComponent.h new file mode 100644 index 00000000..def7d389 --- /dev/null +++ b/Chapter08/MoveComponent.h @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" + +class MoveComponent : public Component +{ +public: + // Lower update order to update first + MoveComponent(class Actor* owner, int updateOrder = 10); + void Update(float deltaTime) override; + + float GetAngularSpeed() const { return mAngularSpeed; } + float GetForwardSpeed() const { return mForwardSpeed; } + void SetAngularSpeed(float speed) { mAngularSpeed = speed; } + void SetForwardSpeed(float speed) { mForwardSpeed = speed; } +private: + float mAngularSpeed; + float mForwardSpeed; +}; diff --git a/Chapter08/Random.cpp b/Chapter08/Random.cpp new file mode 100644 index 00000000..05a3a32a --- /dev/null +++ b/Chapter08/Random.cpp @@ -0,0 +1,51 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Random.h" + +void Random::Init() +{ + std::random_device rd; + Random::Seed(rd()); +} + +void Random::Seed(unsigned int seed) +{ + sGenerator.seed(seed); +} + +float Random::GetFloat() +{ + return GetFloatRange(0.0f, 1.0f); +} + +float Random::GetFloatRange(float min, float max) +{ + std::uniform_real_distribution dist(min, max); + return dist(sGenerator); +} + +int Random::GetIntRange(int min, int max) +{ + std::uniform_int_distribution dist(min, max); + return dist(sGenerator); +} + +Vector2 Random::GetVector(const Vector2& min, const Vector2& max) +{ + Vector2 r = Vector2(GetFloat(), GetFloat()); + return min + (max - min) * r; +} + +Vector3 Random::GetVector(const Vector3& min, const Vector3& max) +{ + Vector3 r = Vector3(GetFloat(), GetFloat(), GetFloat()); + return min + (max - min) * r; +} + +std::mt19937 Random::sGenerator; diff --git a/Chapter08/Random.h b/Chapter08/Random.h new file mode 100644 index 00000000..3ae92fe5 --- /dev/null +++ b/Chapter08/Random.h @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include "Math.h" + +class Random +{ +public: + static void Init(); + + // Seed the generator with the specified int + // NOTE: You should generally not need to manually use this + static void Seed(unsigned int seed); + + // Get a float between 0.0f and 1.0f + static float GetFloat(); + + // Get a float from the specified range + static float GetFloatRange(float min, float max); + + // Get an int from the specified range + static int GetIntRange(int min, int max); + + // Get a random vector given the min/max bounds + static Vector2 GetVector(const Vector2& min, const Vector2& max); + static Vector3 GetVector(const Vector3& min, const Vector3& max); +private: + static std::mt19937 sGenerator; +}; diff --git a/Chapter08/Shader.cpp b/Chapter08/Shader.cpp new file mode 100644 index 00000000..e32c07a2 --- /dev/null +++ b/Chapter08/Shader.cpp @@ -0,0 +1,148 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Shader.h" +#include "Texture.h" +#include +#include +#include + +Shader::Shader() + : mShaderProgram(0) + , mVertexShader(0) + , mFragShader(0) +{ + +} + +Shader::~Shader() +{ + +} + +bool Shader::Load(const std::string& vertName, const std::string& fragName) +{ + // Compile vertex and pixel shaders + if (!CompileShader(vertName, + GL_VERTEX_SHADER, + mVertexShader) || + !CompileShader(fragName, + GL_FRAGMENT_SHADER, + mFragShader)) + { + return false; + } + + // Now create a shader program that + // links together the vertex/frag shaders + mShaderProgram = glCreateProgram(); + glAttachShader(mShaderProgram, mVertexShader); + glAttachShader(mShaderProgram, mFragShader); + glLinkProgram(mShaderProgram); + + // Verify that the program linked successfully + if (!IsValidProgram()) + { + return false; + } + + return true; +} + +void Shader::Unload() +{ + // Delete the program/shaders + glDeleteProgram(mShaderProgram); + glDeleteShader(mVertexShader); + glDeleteShader(mFragShader); +} + +void Shader::SetActive() +{ + // Set this program as the active one + glUseProgram(mShaderProgram); +} + +void Shader::SetMatrixUniform(const char* name, const Matrix4& matrix) +{ + // Find the uniform by this name + GLuint loc = glGetUniformLocation(mShaderProgram, name); + // Send the matrix data to the uniform + glUniformMatrix4fv(loc, 1, GL_TRUE, matrix.GetAsFloatPtr()); +} + +bool Shader::CompileShader(const std::string& fileName, + GLenum shaderType, + GLuint& outShader) +{ + // Open file + std::ifstream shaderFile(fileName); + if (shaderFile.is_open()) + { + // Read all the text into a string + std::stringstream sstream; + sstream << shaderFile.rdbuf(); + std::string contents = sstream.str(); + const char* contentsChar = contents.c_str(); + + // Create a shader of the specified type + outShader = glCreateShader(shaderType); + // Set the source characters and try to compile + glShaderSource(outShader, 1, &(contentsChar), nullptr); + glCompileShader(outShader); + + if (!IsCompiled(outShader)) + { + SDL_Log("Failed to compile shader %s", fileName.c_str()); + return false; + } + } + else + { + SDL_Log("Shader file not found: %s", fileName.c_str()); + return false; + } + + return true; +} + +bool Shader::IsCompiled(GLuint shader) +{ + GLint status; + // Query the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) + { + char buffer[512]; + memset(buffer, 0, 512); + glGetShaderInfoLog(shader, 511, nullptr, buffer); + SDL_Log("GLSL Compile Failed:\n%s", buffer); + return false; + } + + return true; +} + +bool Shader::IsValidProgram() +{ + + GLint status; + // Query the link status + glGetProgramiv(mShaderProgram, GL_LINK_STATUS, &status); + if (status != GL_TRUE) + { + char buffer[512]; + memset(buffer, 0, 512); + glGetProgramInfoLog(mShaderProgram, 511, nullptr, buffer); + SDL_Log("GLSL Link Status:\n%s", buffer); + return false; + } + + return true; +} diff --git a/Chapter08/Shader.h b/Chapter08/Shader.h new file mode 100644 index 00000000..2b0161c0 --- /dev/null +++ b/Chapter08/Shader.h @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include +#include +#include "Math.h" + +class Shader +{ +public: + Shader(); + ~Shader(); + // Load the vertex/fragment shaders with the given names + bool Load(const std::string& vertName, const std::string& fragName); + void Unload(); + // Set this as the active shader program + void SetActive(); + // Sets a Matrix uniform + void SetMatrixUniform(const char* name, const Matrix4& matrix); +private: + // Tries to compile the specified shader + bool CompileShader(const std::string& fileName, + GLenum shaderType, + GLuint& outShader); + + // Tests whether shader compiled successfully + bool IsCompiled(GLuint shader); + // Tests whether vertex/fragment programs link + bool IsValidProgram(); +private: + // Store the shader object IDs + GLuint mVertexShader; + GLuint mFragShader; + GLuint mShaderProgram; +}; diff --git a/Chapter08/Shaders/Basic.frag b/Chapter08/Shaders/Basic.frag new file mode 100644 index 00000000..af9a33e5 --- /dev/null +++ b/Chapter08/Shaders/Basic.frag @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// This corresponds to the output color +// to the color buffer +out vec4 outColor; + +void main() +{ + // RGBA of 100% blue, 100% opaque + outColor = vec4(0.0, 0.0, 1.0, 1.0); +} diff --git a/Chapter08/Shaders/Basic.vert b/Chapter08/Shaders/Basic.vert new file mode 100644 index 00000000..345b3c05 --- /dev/null +++ b/Chapter08/Shaders/Basic.vert @@ -0,0 +1,23 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// This should correspond to the data stored +// for each vertex in the vertex buffer. +// For now, just a position. +in vec3 inPosition; + +void main() +{ + // The vertex shader needs to output a 4D + // coordinate. + // For now set the 4th coordinate to 1.0 + gl_Position = vec4(inPosition, 1.0); +} diff --git a/Chapter08/Shaders/Sprite.frag b/Chapter08/Shaders/Sprite.frag new file mode 100644 index 00000000..f48caf3d --- /dev/null +++ b/Chapter08/Shaders/Sprite.frag @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// Tex coord input from vertex shader +in vec2 fragTexCoord; + +// This corresponds to the output color to the color buffer +out vec4 outColor; + +// This is used for the texture sampling +uniform sampler2D uTexture; + +void main() +{ + // Sample color from texture + outColor = texture(uTexture, fragTexCoord); +} diff --git a/Chapter08/Shaders/Sprite.vert b/Chapter08/Shaders/Sprite.vert new file mode 100644 index 00000000..ea0f396f --- /dev/null +++ b/Chapter08/Shaders/Sprite.vert @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// Uniforms for world transform and view-proj +uniform mat4 uWorldTransform; +uniform mat4 uViewProj; + +// Attribute 0 is position, 1 is tex coords. +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec2 inTexCoord; + +// Add texture coordinate as output +out vec2 fragTexCoord; + +void main() +{ + // Convert position to homogeneous coordinates + vec4 pos = vec4(inPosition, 1.0); + // Transform position to world space, then clip space + gl_Position = pos * uWorldTransform * uViewProj; + + // Transform + // Pass along the texture coordinate to frag shader + fragTexCoord = inTexCoord; +} diff --git a/Chapter08/Shaders/Transform.vert b/Chapter08/Shaders/Transform.vert new file mode 100644 index 00000000..fc59d32e --- /dev/null +++ b/Chapter08/Shaders/Transform.vert @@ -0,0 +1,23 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +// Request GLSL 3.3 +#version 330 + +// Uniforms for world transform and view-proj +uniform mat4 uWorldTransform; +uniform mat4 uViewProj; + +// Vertex attributes +in vec3 inPosition; + +void main() +{ + vec4 pos = vec4(inPosition, 1.0); + gl_Position = pos * uWorldTransform * uViewProj; +} diff --git a/Chapter08/Ship.cpp b/Chapter08/Ship.cpp new file mode 100644 index 00000000..86bb7b3d --- /dev/null +++ b/Chapter08/Ship.cpp @@ -0,0 +1,61 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Ship.h" +#include "SpriteComponent.h" +#include "Game.h" +#include "Laser.h" +#include "InputSystem.h" + +Ship::Ship(Game* game) + :Actor(game) + ,mSpeed(400.0f) + ,mLaserCooldown(0.0f) +{ + // Create a sprite component + SpriteComponent* sc = new SpriteComponent(this, 150); + sc->SetTexture(game->GetTexture("Assets/Ship.png")); +} + +void Ship::UpdateActor(float deltaTime) +{ + mLaserCooldown -= deltaTime; + + // Update position based on velocity + Vector2 pos = GetPosition(); + pos += mVelocityDir * mSpeed * deltaTime; + SetPosition(pos); + + // Update rotation + float angle = Math::Atan2(mRotationDir.y, mRotationDir.x); + SetRotation(angle); +} + +void Ship::ActorInput(const InputState& state) +{ + if (state.Controller.GetRightTrigger() > 0.25f + && mLaserCooldown <= 0.0f) + { + // Create a laser and set its position/rotation to mine + Laser* laser = new Laser(GetGame()); + laser->SetPosition(GetPosition()); + laser->SetRotation(GetRotation()); + + // Reset laser cooldown (quarter second) + mLaserCooldown = 0.25f; + } + + if (state.Controller.GetIsConnected()) + { + mVelocityDir = state.Controller.GetLeftStick(); + if (!Math::NearZero(state.Controller.GetRightStick().Length())) + { + mRotationDir = state.Controller.GetRightStick(); + } + } +} diff --git a/Chapter08/Ship.h b/Chapter08/Ship.h new file mode 100644 index 00000000..bafac3de --- /dev/null +++ b/Chapter08/Ship.h @@ -0,0 +1,23 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Actor.h" +class Ship : public Actor +{ +public: + Ship(class Game* game); + + void UpdateActor(float deltaTime) override; + void ActorInput(const struct InputState& state) override; +private: + Vector2 mVelocityDir; + Vector2 mRotationDir; + float mSpeed; + float mLaserCooldown; +}; \ No newline at end of file diff --git a/Chapter08/SpriteComponent.cpp b/Chapter08/SpriteComponent.cpp new file mode 100644 index 00000000..d62b9840 --- /dev/null +++ b/Chapter08/SpriteComponent.cpp @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "SpriteComponent.h" +#include "Texture.h" +#include "Shader.h" +#include "Actor.h" +#include "Game.h" + +SpriteComponent::SpriteComponent(Actor* owner, int drawOrder) + :Component(owner) + ,mTexture(nullptr) + ,mDrawOrder(drawOrder) + ,mTexWidth(0) + ,mTexHeight(0) +{ + mOwner->GetGame()->AddSprite(this); +} + +SpriteComponent::~SpriteComponent() +{ + mOwner->GetGame()->RemoveSprite(this); +} + +void SpriteComponent::Draw(Shader* shader) +{ + if (mTexture) + { + // Scale the quad by the width/height of texture + Matrix4 scaleMat = Matrix4::CreateScale( + static_cast(mTexWidth), + static_cast(mTexHeight), + 1.0f); + + Matrix4 world = scaleMat * mOwner->GetWorldTransform(); + + // Since all sprites use the same shader/vertices, + // the game first sets them active before any sprite draws + + // Set world transform + shader->SetMatrixUniform("uWorldTransform", world); + // Set current texture + mTexture->SetActive(); + // Draw quad + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); + } +} + +void SpriteComponent::SetTexture(Texture* texture) +{ + mTexture = texture; + // Set width/height + mTexWidth = texture->GetWidth(); + mTexHeight = texture->GetHeight(); +} diff --git a/Chapter08/SpriteComponent.h b/Chapter08/SpriteComponent.h new file mode 100644 index 00000000..6c5642f2 --- /dev/null +++ b/Chapter08/SpriteComponent.h @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +#include "Component.h" +#include "SDL/SDL.h" +class SpriteComponent : public Component +{ +public: + // (Lower draw order corresponds with further back) + SpriteComponent(class Actor* owner, int drawOrder = 100); + ~SpriteComponent(); + + virtual void Draw(class Shader* shader); + virtual void SetTexture(class Texture* texture); + + int GetDrawOrder() const { return mDrawOrder; } + int GetTexHeight() const { return mTexHeight; } + int GetTexWidth() const { return mTexWidth; } +protected: + class Texture* mTexture; + int mDrawOrder; + int mTexWidth; + int mTexHeight; +}; diff --git a/Chapter08/Texture.cpp b/Chapter08/Texture.cpp new file mode 100644 index 00000000..7d0dbd3e --- /dev/null +++ b/Chapter08/Texture.cpp @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "Texture.h" +#include +#include +#include + +Texture::Texture() +:mTextureID(0) +,mWidth(0) +,mHeight(0) +{ + +} + +Texture::~Texture() +{ + +} + +bool Texture::Load(const std::string& fileName) +{ + int channels = 0; + + unsigned char* image = SOIL_load_image(fileName.c_str(), + &mWidth, &mHeight, &channels, SOIL_LOAD_AUTO); + + if (image == nullptr) + { + SDL_Log("SOIL failed to load image %s: %s", fileName.c_str(), SOIL_last_result()); + return false; + } + + int format = GL_RGB; + if (channels == 4) + { + format = GL_RGBA; + } + + glGenTextures(1, &mTextureID); + glBindTexture(GL_TEXTURE_2D, mTextureID); + + glTexImage2D(GL_TEXTURE_2D, 0, format, mWidth, mHeight, 0, format, + GL_UNSIGNED_BYTE, image); + + SOIL_free_image_data(image); + + // Enable bilinear filtering + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + return true; +} + +void Texture::Unload() +{ + glDeleteTextures(1, &mTextureID); +} + +void Texture::SetActive() +{ + glBindTexture(GL_TEXTURE_2D, mTextureID); +} diff --git a/Chapter08/Texture.h b/Chapter08/Texture.h new file mode 100644 index 00000000..ed12b0dc --- /dev/null +++ b/Chapter08/Texture.h @@ -0,0 +1,30 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include + +class Texture +{ +public: + Texture(); + ~Texture(); + + bool Load(const std::string& fileName); + void Unload(); + + void SetActive(); + + int GetWidth() const { return mWidth; } + int GetHeight() const { return mHeight; } +private: + // OpenGL ID of this texture + unsigned int mTextureID; + // Width/height of the texture + int mWidth; + int mHeight; +}; diff --git a/Chapter08/VertexArray.cpp b/Chapter08/VertexArray.cpp new file mode 100644 index 00000000..c7bff446 --- /dev/null +++ b/Chapter08/VertexArray.cpp @@ -0,0 +1,51 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#include "VertexArray.h" +#include + +VertexArray::VertexArray(const float* verts, unsigned int numVerts, + const unsigned int* indices, unsigned int numIndices) + :mNumVerts(numVerts) + ,mNumIndices(numIndices) +{ + // Create vertex array + glGenVertexArrays(1, &mVertexArray); + glBindVertexArray(mVertexArray); + + // Create vertex buffer + glGenBuffers(1, &mVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, numVerts * 5 * sizeof(float), verts, GL_STATIC_DRAW); + + // Create index buffer + glGenBuffers(1, &mIndexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices * sizeof(unsigned int), indices, GL_STATIC_DRAW); + + // Specify the vertex attributes + // (For now, assume one vertex format) + // Position is 3 floats starting at offset 0 + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 5, 0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 5, + reinterpret_cast(sizeof(float) * 3)); +} + +VertexArray::~VertexArray() +{ + glDeleteBuffers(1, &mVertexBuffer); + glDeleteBuffers(1, &mIndexBuffer); + glDeleteVertexArrays(1, &mVertexArray); +} + +void VertexArray::SetActive() +{ + glBindVertexArray(mVertexArray); +} diff --git a/Chapter08/VertexArray.h b/Chapter08/VertexArray.h new file mode 100644 index 00000000..9f2c3e9b --- /dev/null +++ b/Chapter08/VertexArray.h @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------- +// From Game Programming in C++ by Sanjay Madhav +// Copyright (C) 2017 Sanjay Madhav. All rights reserved. +// +// Released under the BSD License +// See LICENSE in root directory for full details. +// ---------------------------------------------------------------- + +#pragma once +class VertexArray +{ +public: + VertexArray(const float* verts, unsigned int numVerts, + const unsigned int* indices, unsigned int numIndices); + ~VertexArray(); + + // Activate this vertex array (so we can draw it) + void SetActive(); + + unsigned int GetNumIndices() const { return mNumIndices; } + unsigned int GetNumVerts() const { return mNumVerts; } +private: + // How many vertices in the vertex buffer? + unsigned int mNumVerts; + // How many indices in the index buffer + unsigned int mNumIndices; + // OpenGL ID of the vertex buffer + unsigned int mVertexBuffer; + // OpenGL ID of the index buffer + unsigned int mIndexBuffer; + // OpenGL ID of the vertex array object + unsigned int mVertexArray; +}; \ No newline at end of file diff --git a/External/FMOD/README.md b/External/FMOD/README.md new file mode 100644 index 00000000..00a032f2 --- /dev/null +++ b/External/FMOD/README.md @@ -0,0 +1,5 @@ +Code for Chapter 7 and beyond uses the FMOD API for audio. This requires +a separate installation from (https://www.fmod.com/download). Download +and install version 1.09.x of the FMOD Studio API (newer versions are untested). +On Windows, install FMOD to the default directory. On Mac, copy the contents +of the FMOD package into External/FMOD. diff --git a/README.md b/README.md index a8b0f953..41b4a0db 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ license. See LICENSE for more detail. Note that this license does not apply to the code in the External directory. Each External project is licensed separately. # Building the Code -All chapters projects are tested on both Microsoft Windows and Apple macOS. +Each chapter's code is tested and works on both Microsoft Windows and Apple macOS. To compile on Windows, install Microsoft Visual Studio 2017 Community (https://www.visualstudio.com/downloads/). During installation, select the @@ -18,6 +18,6 @@ a corresponding ChapterXX-mac.xcodeproj file. Code for Chapter 7 and beyond uses the FMOD API for audio. This requires a separate installation from (https://www.fmod.com/download). Download -and install version 1.10.x of the FMOD Studio API. On Windows, install -FMOD to the default directory. On Mac, copy the contents of the FMOD package -into External/FMOD. +and install version 1.09.x of the FMOD Studio API (newer versions are untested). +On Windows, install FMOD to the default directory. On Mac, copy the contents +of the FMOD package into External/FMOD.