diff --git a/src/game.cpp b/src/game.cpp index a09d7b6..d5c6ec0 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -242,28 +242,15 @@ void Game::setUpdateRate(double frequency) void Game::setup() { - // Load settings loadSettings(); - - // Setup localization (load strings, select language, ...) + setupDebugging(); setupLocalization(); - setupWindow(); - setupGraphics(); - setupUI(); - setupControls(); - setupGameplay(); - setupDebugging(); - - - - - screenshotQueued = false; @@ -281,943 +268,923 @@ void Game::setup() close(EXIT_FAILURE); } - try - { - splashTexture = resourceManager->load("epigraph.png"); - hudSpriteSheetTexture = resourceManager->load("hud.png"); - - // Read texture atlas file - CSVTable* atlasTable = resourceManager->load("hud-atlas.csv"); - // Build texture atlas - for (int row = 0; row < atlasTable->size(); ++row) - { - std::stringstream ss; - float x; - float y; - float w; - float h; - - ss << (*atlasTable)[row][1]; - ss >> x; - ss.str(std::string()); - ss.clear(); - ss << (*atlasTable)[row][2]; - ss >> y; - ss.str(std::string()); - ss.clear(); - ss << (*atlasTable)[row][3]; - ss >> w; - ss.str(std::string()); - ss.clear(); - ss << (*atlasTable)[row][4]; - ss >> h; - ss.str(std::string()); - - y = static_cast(hudSpriteSheetTexture->getHeight()) - y - h; - x = (int)(x + 0.5f); - y = (int)(y + 0.5f); - w = (int)(w + 0.5f); - h = (int)(h + 0.5f); - - hudTextureAtlas.insert((*atlasTable)[row][0], Rect(Vector2(x, y), Vector2(x + w, y + h))); - } - } - catch (const std::exception& e) - { - std::cerr << "Failed to load one or more textures: \"" << e.what() << "\"" << std::endl; - close(EXIT_FAILURE); - } - // Load font resources - try - { - //labelTypeface = resourceManager->load("open-sans-regular.ttf"); - labelTypeface = resourceManager->load("caveat-bold.ttf"); - labelFont = labelTypeface->createFont(fontSizePX); + time = 0.0f; + - debugTypeface = resourceManager->load("inconsolata-bold.ttf"); - debugFont = debugTypeface->createFont(fontSizePX); - debugTypeface->loadCharset(debugFont, UnicodeRange::BASIC_LATIN); + // Tools + currentTool = nullptr; + lens = new Lens(lensModel, &animator); + lens->setOrbitCam(orbitCam); + worldScene->addObject(lens->getModelInstance()); + worldScene->addObject(lens->getSpotlight()); + lens->setSunDirection(-sunlightCamera.getForward()); - std::set charset; - charset.emplace(U'方'); - charset.emplace(U'蕴'); - labelTypeface->loadCharset(labelFont, UnicodeRange::BASIC_LATIN); - labelTypeface->loadCharset(labelFont, charset); - } - catch (const std::exception& e) + ModelInstance* modelInstance = lens->getModelInstance(); + for (std::size_t i = 0; i < modelInstance->getModel()->getGroupCount(); ++i) { - std::cerr << "Failed to load one or more fonts: \"" << e.what() << "\"" << std::endl; - close(EXIT_FAILURE); + Material* material = modelInstance->getModel()->getGroup(i)->material->clone(); + material->setFlags(material->getFlags() | 256); + modelInstance->setMaterialSlot(i, material); } + // Forceps + forceps = new Forceps(forcepsModel, &animator); + forceps->setOrbitCam(orbitCam); + worldScene->addObject(forceps->getModelInstance()); - Shader* shader = resourceManager->load("depth-pass.glsl"); + // Brush + brush = new Brush(brushModel, &animator); + brush->setOrbitCam(orbitCam); + worldScene->addObject(brush->getModelInstance()); - cameraRig = nullptr; - orbitCam = new OrbitCam(); - orbitCam->attachCamera(&camera); - freeCam = new FreeCam(); - freeCam->attachCamera(&camera); + // Initialize component manager + componentManager = new ComponentManager(); - silhouetteRenderTarget.width = w; - silhouetteRenderTarget.height = h; + // Initialize entity manager + entityManager = new EntityManager(componentManager); - // Silhouette framebuffer texture - glGenTextures(1, &silhouetteRenderTarget.texture); - glBindTexture(GL_TEXTURE_2D, silhouetteRenderTarget.texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - // Generate framebuffer - glGenFramebuffers(1, &silhouetteRenderTarget.framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, silhouetteRenderTarget.framebuffer); + // Initialize systems + soundSystem = new SoundSystem(componentManager); + collisionSystem = new CollisionSystem(componentManager); + cameraSystem = new CameraSystem(componentManager); + renderSystem = new RenderSystem(componentManager, worldScene); + toolSystem = new ToolSystem(componentManager); + toolSystem->setPickingCamera(&camera); + toolSystem->setPickingViewport(Vector4(0, 0, w, h)); + eventDispatcher.subscribe(toolSystem); + behaviorSystem = new BehaviorSystem(componentManager); + steeringSystem = new SteeringSystem(componentManager); + locomotionSystem = new LocomotionSystem(componentManager); + particleSystem = new ParticleSystem(componentManager); + particleSystem->resize(1000); + particleSystem->setMaterial(smokeMaterial); + particleSystem->setDirection(Vector3(0, 1, 0)); + lens->setParticleSystem(particleSystem); + particleSystem->getBillboardBatch()->setAlignment(&camera, BillboardAlignmentMode::SPHERICAL); + worldScene->addObject(particleSystem->getBillboardBatch()); - // Attach textures to framebuffer - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, silhouetteRenderTarget.texture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - glReadBuffer(GL_NONE); - - // Unbind framebuffer and texture - glBindTexture(GL_TEXTURE_2D, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - // Setup rendering - defaultRenderTarget.width = w; - defaultRenderTarget.height = h; - defaultRenderTarget.framebuffer = 0; - clearPass = new ClearRenderPass(); - clearPass->setRenderTarget(&defaultRenderTarget); - clearPass->setClear(true, true, false); - clearPass->setClearColor(Vector4(0.0f)); - clearPass->setClearDepth(1.0f); - skyPass = new SkyRenderPass(resourceManager); - skyPass->setRenderTarget(&defaultRenderTarget); - uiPass = new UIRenderPass(resourceManager); - uiPass->setRenderTarget(&defaultRenderTarget); - uiCompositor.addPass(uiPass); - uiCompositor.load(nullptr); + // Initialize system manager + systemManager = new SystemManager(); + systemManager->addSystem(soundSystem); + systemManager->addSystem(behaviorSystem); + systemManager->addSystem(steeringSystem); + systemManager->addSystem(locomotionSystem); + systemManager->addSystem(collisionSystem); + systemManager->addSystem(toolSystem); + systemManager->addSystem(particleSystem); + systemManager->addSystem(cameraSystem); + systemManager->addSystem(renderSystem); - // Setup UI batching - uiBatch = new BillboardBatch(); - uiBatch->resize(1024); - uiBatcher = new UIBatcher(); + EntityID sidewalkPanel; + sidewalkPanel = createInstanceOf("sidewalk-panel"); - // Setup root UI element - uiRootElement = new UIContainer(); - eventDispatcher.subscribe(uiRootElement); - eventDispatcher.subscribe(uiRootElement); - eventDispatcher.subscribe(uiRootElement); + EntityID antHill = createInstanceOf("ant-hill"); + setTranslation(antHill, Vector3(20, 0, 40)); - // Create splash screen background element - splashBackgroundImage = new UIImage(); - splashBackgroundImage->setLayerOffset(-1); - splashBackgroundImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); - splashBackgroundImage->setVisible(false); - uiRootElement->addChild(splashBackgroundImage); + EntityID antNest = createInstanceOf("ant-nest"); + setTranslation(antNest, Vector3(20, 0, 40)); - // Create splash screen element - splashImage = new UIImage(); - splashImage->setTexture(splashTexture); - splashImage->setVisible(false); - uiRootElement->addChild(splashImage); + EntityID lollipop = createInstanceOf("lollipop"); + setTranslation(lollipop, Vector3(30.0f, 3.5f * 0.5f, -30.0f)); + setRotation(lollipop, glm::angleAxis(glm::radians(8.85f), Vector3(1.0f, 0.0f, 0.0f))); - Rect hudTextureAtlasBounds(Vector2(0), Vector2(hudSpriteSheetTexture->getWidth(), hudSpriteSheetTexture->getHeight())); - auto normalizeTextureBounds = [](const Rect& texture, const Rect& atlas) + // Load navmesh + TriangleMesh* navmesh = resourceManager->load("sidewalk.mesh"); + + // Find surface + TriangleMesh::Triangle* surface = nullptr; + Vector3 barycentricPosition; + Ray ray; + ray.origin = Vector3(0, 100, 0); + ray.direction = Vector3(0, -1, 0); + auto intersection = ray.intersects(*navmesh); + if (std::get<0>(intersection)) { - Vector2 atlasDimensions = Vector2(atlas.getWidth(), atlas.getHeight()); - return Rect(texture.getMin() / atlasDimensions, texture.getMax() / atlasDimensions); - }; + surface = (*navmesh->getTriangles())[std::get<3>(intersection)]; - // Create HUD elements - hudContainer = new UIContainer(); - hudContainer->setVisible(false); - uiRootElement->addChild(hudContainer); - + Vector3 position = ray.extrapolate(std::get<1>(intersection)); + Vector3 a = surface->edge->vertex->position; + Vector3 b = surface->edge->next->vertex->position; + Vector3 c = surface->edge->previous->vertex->position; - toolIndicatorBGImage = new UIImage(); - toolIndicatorBGImage->setTexture(hudSpriteSheetTexture); - toolIndicatorBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds)); - hudContainer->addChild(toolIndicatorBGImage); + barycentricPosition = barycentric(position, a, b, c); + } - toolIndicatorsBounds = new Rect[8]; - toolIndicatorsBounds[0] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-brush"), hudTextureAtlasBounds); - toolIndicatorsBounds[1] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-spade"), hudTextureAtlasBounds); - toolIndicatorsBounds[2] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-lens"), hudTextureAtlasBounds); - toolIndicatorsBounds[3] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-test-tube"), hudTextureAtlasBounds); - toolIndicatorsBounds[4] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-forceps"), hudTextureAtlasBounds); - toolIndicatorsBounds[5] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); - toolIndicatorsBounds[6] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); - toolIndicatorsBounds[7] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); + for (int i = 0; i < 0; ++i) + { + EntityID ant = createInstanceOf("worker-ant"); + setTranslation(ant, Vector3(0.0f, 0, 0.0f)); - toolIndicatorIconImage = new UIImage(); - toolIndicatorIconImage->setTexture(hudSpriteSheetTexture); - toolIndicatorIconImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-brush"), hudTextureAtlasBounds)); - toolIndicatorBGImage->addChild(toolIndicatorIconImage); + BehaviorComponent* behavior = new BehaviorComponent(); + SteeringComponent* steering = new SteeringComponent(); + LeggedLocomotionComponent* locomotion = new LeggedLocomotionComponent(); + componentManager->addComponent(ant, behavior); + componentManager->addComponent(ant, steering); + componentManager->addComponent(ant, locomotion); - buttonContainer = new UIContainer(); - hudContainer->addChild(buttonContainer); + locomotion->surface = surface; + behavior->wanderTriangle = surface; + locomotion->barycentricPosition = barycentricPosition; + } - playButtonBGImage = new UIImage(); - playButtonBGImage->setTexture(hudSpriteSheetTexture); - playButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); - //buttonContainer->addChild(playButtonBGImage); + + EntityID tool0 = createInstanceOf("lens"); - pauseButtonBGImage = new UIImage(); - pauseButtonBGImage->setTexture(hudSpriteSheetTexture); - pauseButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); - //buttonContainer->addChild(pauseButtonBGImage); + changeState(splashState); +} - fastForwardButtonBGImage = new UIImage(); - fastForwardButtonBGImage->setTexture(hudSpriteSheetTexture); - fastForwardButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); - //buttonContainer->addChild(fastForwardButtonBGImage); +void Game::update(float t, float dt) +{ + this->time = t; - playButtonImage = new UIImage(); - playButtonImage->setTexture(hudSpriteSheetTexture); - playButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-play"), hudTextureAtlasBounds)); - //buttonContainer->addChild(playButtonImage); + // Dispatch scheduled events + eventDispatcher.update(t); - fastForwardButtonImage = new UIImage(); - fastForwardButtonImage->setTexture(hudSpriteSheetTexture); - fastForwardButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-fast-forward-2x"), hudTextureAtlasBounds)); - //buttonContainer->addChild(fastForwardButtonImage); + // Execute current state + if (currentState != nullptr) + { + currentState->execute(); + } - pauseButtonImage = new UIImage(); - pauseButtonImage->setTexture(hudSpriteSheetTexture); - pauseButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-pause"), hudTextureAtlasBounds)); - //buttonContainer->addChild(pauseButtonImage); + // Update systems + systemManager->update(t, dt); - radialMenuContainer = new UIContainer(); - radialMenuContainer->setVisible(false); - uiRootElement->addChild(radialMenuContainer); + // Update animations + animator.animate(dt); - radialMenuBackgroundImage = new UIImage(); - radialMenuBackgroundImage->setTintColor(Vector4(Vector3(0.0f), 0.25f)); - radialMenuContainer->addChild(radialMenuBackgroundImage); + if (fpsLabel->isVisible()) + { + std::stringstream stream; + stream.precision(2); + stream << std::fixed << (performanceSampler.getMeanFrameDuration() * 1000.0f); + fpsLabel->setText(stream.str()); + } - radialMenuImage = new UIImage(); - radialMenuImage->setTexture(hudSpriteSheetTexture); - radialMenuImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("radial-menu"), hudTextureAtlasBounds)); - radialMenuContainer->addChild(radialMenuImage); + uiRootElement->update(); +} - radialMenuSelectorImage = new UIImage(); - radialMenuSelectorImage->setTexture(hudSpriteSheetTexture); - radialMenuSelectorImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("radial-menu-selector"), hudTextureAtlasBounds)); - radialMenuContainer->addChild(radialMenuSelectorImage); +void Game::input() +{ + controls.update(); +} - toolIconBrushImage = new UIImage(); - toolIconBrushImage->setTexture(hudSpriteSheetTexture); - toolIconBrushImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-brush"), hudTextureAtlasBounds)); - radialMenuImage->addChild(toolIconBrushImage); +void Game::render() +{ + // Perform sub-frame interpolation on UI elements + uiRootElement->interpolate(stepScheduler.getScheduledSubsteps()); - toolIconLensImage = new UIImage(); - toolIconLensImage->setTexture(hudSpriteSheetTexture); - toolIconLensImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-lens"), hudTextureAtlasBounds)); - radialMenuImage->addChild(toolIconLensImage); + // Update and batch UI elements + uiBatcher->batch(uiBatch, uiRootElement); - toolIconForcepsImage = new UIImage(); - toolIconForcepsImage->setTexture(hudSpriteSheetTexture); - toolIconForcepsImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-forceps"), hudTextureAtlasBounds)); - radialMenuImage->addChild(toolIconForcepsImage); + // Perform sub-frame interpolation particles + particleSystem->getBillboardBatch()->interpolate(stepScheduler.getScheduledSubsteps()); + particleSystem->getBillboardBatch()->batch(); - toolIconSpadeImage = new UIImage(); - toolIconSpadeImage->setTexture(hudSpriteSheetTexture); - toolIconSpadeImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-spade"), hudTextureAtlasBounds)); - //radialMenuImage->addChild(toolIconSpadeImage); + // Render scene + renderer.render(*worldScene); + renderer.render(*uiScene); - toolIconCameraImage = new UIImage(); - toolIconCameraImage->setTexture(hudSpriteSheetTexture); - toolIconCameraImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-camera"), hudTextureAtlasBounds)); - radialMenuImage->addChild(toolIconCameraImage); + // Swap window framebuffers + window->swapBuffers(); - toolIconTestTubeImage = new UIImage(); - toolIconTestTubeImage->setTexture(hudSpriteSheetTexture); - toolIconTestTubeImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-test-tube"), hudTextureAtlasBounds)); - //radialMenuImage->addChild(toolIconTestTubeImage); + if (screenshotQueued) + { + screenshot(); + screenshotQueued = false; + } +} +void Game::exit() +{ - antTag = new UIContainer(); - antTag->setLayerOffset(-10); - antTag->setVisible(false); +} - uiRootElement->addChild(antTag); +void Game::handleEvent(const WindowResizedEvent& event) +{ + w = event.width; + h = event.height; - antLabelContainer = new UIContainer(); - antTag->addChild(antLabelContainer); + defaultRenderTarget.width = event.width; + defaultRenderTarget.height = event.height; + glViewport(0, 0, event.width, event.height); - antLabelTL = new UIImage(); - antLabelTR = new UIImage(); - antLabelBL = new UIImage(); - antLabelBR = new UIImage(); - antLabelCC = new UIImage(); - antLabelCT = new UIImage(); - antLabelCB = new UIImage(); - antLabelCL = new UIImage(); - antLabelCR = new UIImage(); - antLabelTL->setTexture(hudSpriteSheetTexture); - antLabelTR->setTexture(hudSpriteSheetTexture); - antLabelBL->setTexture(hudSpriteSheetTexture); - antLabelBR->setTexture(hudSpriteSheetTexture); - antLabelCC->setTexture(hudSpriteSheetTexture); - antLabelCT->setTexture(hudSpriteSheetTexture); - antLabelCB->setTexture(hudSpriteSheetTexture); - antLabelCL->setTexture(hudSpriteSheetTexture); - antLabelCR->setTexture(hudSpriteSheetTexture); + camera.setPerspective(glm::radians(40.0f), static_cast(w) / static_cast(h), 0.1, 100.0f); - Rect labelTLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-tl"), hudTextureAtlasBounds); - Rect labelTRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-tr"), hudTextureAtlasBounds); - Rect labelBLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-bl"), hudTextureAtlasBounds); - Rect labelBRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-br"), hudTextureAtlasBounds); - Rect labelCCBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cc"), hudTextureAtlasBounds); - Rect labelCTBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-ct"), hudTextureAtlasBounds); - Rect labelCBBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cb"), hudTextureAtlasBounds); - Rect labelCLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cl"), hudTextureAtlasBounds); - Rect labelCRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cr"), hudTextureAtlasBounds); - Vector2 labelTLMin = labelTLBounds.getMin(); - Vector2 labelTRMin = labelTRBounds.getMin(); - Vector2 labelBLMin = labelBLBounds.getMin(); - Vector2 labelBRMin = labelBRBounds.getMin(); - Vector2 labelCCMin = labelCCBounds.getMin(); - Vector2 labelCTMin = labelCTBounds.getMin(); - Vector2 labelCBMin = labelCBBounds.getMin(); - Vector2 labelCLMin = labelCLBounds.getMin(); - Vector2 labelCRMin = labelCRBounds.getMin(); - Vector2 labelTLMax = labelTLBounds.getMax(); - Vector2 labelTRMax = labelTRBounds.getMax(); - Vector2 labelBLMax = labelBLBounds.getMax(); - Vector2 labelBRMax = labelBRBounds.getMax(); - Vector2 labelCCMax = labelCCBounds.getMax(); - Vector2 labelCTMax = labelCTBounds.getMax(); - Vector2 labelCBMax = labelCBBounds.getMax(); - Vector2 labelCLMax = labelCLBounds.getMax(); - Vector2 labelCRMax = labelCRBounds.getMax(); + toolSystem->setPickingViewport(Vector4(0, 0, w, h)); - antLabelTL->setTextureBounds(labelTLBounds); - antLabelTR->setTextureBounds(labelTRBounds); - antLabelBL->setTextureBounds(labelBLBounds); - antLabelBR->setTextureBounds(labelBRBounds); - antLabelCC->setTextureBounds(labelCCBounds); - antLabelCT->setTextureBounds(labelCTBounds); - antLabelCB->setTextureBounds(labelCBBounds); - antLabelCL->setTextureBounds(labelCLBounds); - antLabelCR->setTextureBounds(labelCRBounds); + resizeUI(event.width, event.height); +} - antLabelContainer->addChild(antLabelTL); - antLabelContainer->addChild(antLabelTR); - antLabelContainer->addChild(antLabelBL); - antLabelContainer->addChild(antLabelBR); - antLabelContainer->addChild(antLabelCC); - antLabelContainer->addChild(antLabelCT); - antLabelContainer->addChild(antLabelCB); - antLabelContainer->addChild(antLabelCL); - antLabelContainer->addChild(antLabelCR); +void Game::handleEvent(const GamepadConnectedEvent& event) +{ + // Unmap all controls + inputMapper->reset(); - antLabel = new UILabel(); - antLabel->setFont(labelFont); - antLabel->setText("Boggy B."); - antLabel->setTintColor(Vector4(Vector3(0.0f), 1.0f)); - antLabel->setLayerOffset(1); - antLabelContainer->addChild(antLabel); + // Reload control profile + loadControlProfile(); +} - fpsLabel = new UILabel(); - fpsLabel->setFont(debugFont); - fpsLabel->setTintColor(Vector4(1, 1, 0, 1)); - fpsLabel->setLayerOffset(50); - fpsLabel->setAnchor(Anchor::TOP_LEFT); - uiRootElement->addChild(fpsLabel); +void Game::handleEvent(const GamepadDisconnectedEvent& event) +{} + +void Game::setupDebugging() +{ + // Setup performance sampling + performanceSampler.setSampleSize(30); + // Disable wireframe drawing + wireframe = false; +} - antPin = new UIImage(); - antPin->setTexture(hudSpriteSheetTexture); - antPin->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("label-pin"), hudTextureAtlasBounds)); - antTag->addChild(antPin); +void Game::setupLocalization() +{ + // Load strings + loadStrings(); - antLabelPinHole = new UIImage(); - antLabelPinHole->setTexture(hudSpriteSheetTexture); - antLabelPinHole->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("label-pin-hole"), hudTextureAtlasBounds)); - antLabelContainer->addChild(antLabelPinHole); + // Determine number of available languages + languageCount = (*stringTable)[0].size() - 1; - // Construct box selection - boxSelectionImageBackground = new UIImage(); - boxSelectionImageBackground->setAnchor(Anchor::CENTER); - boxSelectionImageTop = new UIImage(); - boxSelectionImageTop->setAnchor(Anchor::TOP_LEFT); - boxSelectionImageBottom = new UIImage(); - boxSelectionImageBottom->setAnchor(Anchor::BOTTOM_LEFT); - boxSelectionImageLeft = new UIImage(); - boxSelectionImageLeft->setAnchor(Anchor::TOP_LEFT); - boxSelectionImageRight = new UIImage(); - boxSelectionImageRight->setAnchor(Anchor::TOP_RIGHT); - boxSelectionContainer = new UIContainer(); - boxSelectionContainer->setLayerOffset(80); - boxSelectionContainer->addChild(boxSelectionImageBackground); - boxSelectionContainer->addChild(boxSelectionImageTop); - boxSelectionContainer->addChild(boxSelectionImageBottom); - boxSelectionContainer->addChild(boxSelectionImageLeft); - boxSelectionContainer->addChild(boxSelectionImageRight); - boxSelectionContainer->setVisible(false); - uiRootElement->addChild(boxSelectionContainer); - boxSelectionImageBackground->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.5f)); - boxSelectionContainer->setTintColor(Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - boxSelectionBorderWidth = 2.0f; + // Match language code with language index + languageIndex = 0; + CSVRow* languageCodes = &(*stringTable)[0]; + for (std::size_t i = 1; i < languageCodes->size(); ++i) + { + if (language == (*languageCodes)[i]) + { + languageIndex = i - 1; + break; + } + } +} - cameraGridColor = Vector4(1, 1, 1, 0.5f); - cameraReticleColor = Vector4(1, 1, 1, 0.75f); - cameraGridY0Image = new UIImage(); - cameraGridY0Image->setAnchor(Vector2(0.5f, (1.0f / 3.0f))); - cameraGridY0Image->setTintColor(cameraGridColor); - cameraGridY1Image = new UIImage(); - cameraGridY1Image->setAnchor(Vector2(0.5f, (2.0f / 3.0f))); - cameraGridY1Image->setTintColor(cameraGridColor); - cameraGridX0Image = new UIImage(); - cameraGridX0Image->setAnchor(Vector2((1.0f / 3.0f), 0.5f)); - cameraGridX0Image->setTintColor(cameraGridColor); - cameraGridX1Image = new UIImage(); - cameraGridX1Image->setAnchor(Vector2((2.0f / 3.0f), 0.5f)); - cameraGridX1Image->setTintColor(cameraGridColor); - cameraReticleImage = new UIImage(); - cameraReticleImage->setAnchor(Anchor::CENTER); - cameraReticleImage->setTintColor(cameraReticleColor); - cameraReticleImage->setTexture(hudSpriteSheetTexture); - cameraReticleImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("camera-reticle"), hudTextureAtlasBounds)); - cameraGridContainer = new UIContainer(); - cameraGridContainer->addChild(cameraGridY0Image); - cameraGridContainer->addChild(cameraGridY1Image); - cameraGridContainer->addChild(cameraGridX0Image); - cameraGridContainer->addChild(cameraGridX1Image); - cameraGridContainer->addChild(cameraReticleImage); - cameraGridContainer->setVisible(true); - uiRootElement->addChild(cameraGridContainer); +void Game::setupWindow() +{ + // Get display resolution + const Display* display = deviceManager->getDisplays()->front(); + int displayWidth = std::get<0>(display->getDimensions()); + int displayHeight = std::get<1>(display->getDimensions()); - cameraFlashImage = new UIImage(); - cameraFlashImage->setLayerOffset(99); - cameraFlashImage->setTintColor(Vector4(1.0f)); - cameraFlashImage->setVisible(false); - uiRootElement->addChild(cameraFlashImage); + if (fullscreen) + { + w = static_cast(fullscreenResolution.x); + h = static_cast(fullscreenResolution.y); + } + else + { + w = static_cast(windowedResolution.x); + h = static_cast(windowedResolution.y); + } - blackoutImage = new UIImage(); - blackoutImage->setLayerOffset(98); - blackoutImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); - blackoutImage->setVisible(false); - uiRootElement->addChild(blackoutImage); + // Determine window position + int x = std::get<0>(display->getPosition()) + displayWidth / 2 - w / 2; + int y = std::get<1>(display->getPosition()) + displayHeight / 2 - h / 2; - // Construct fade-in animation clip - fadeInClip.setInterpolator(easeOutCubic); - AnimationChannel* channel; - channel = fadeInClip.addChannel(0); - channel->insertKeyframe(0.0f, 1.0f); - channel->insertKeyframe(1.0f, 0.0f); + // Read title string + std::string title = getString(getLanguageIndex(), "title"); - // Construct fade-out animation clip - fadeOutClip.setInterpolator(easeOutCubic); - channel = fadeOutClip.addChannel(0); - channel->insertKeyframe(0.0f, 0.0f); - channel->insertKeyframe(1.0f, 1.0f); + // Create window + window = windowManager->createWindow(title.c_str(), x, y, w, h, fullscreen, WindowFlag::RESIZABLE); + if (!window) + { + throw std::runtime_error("Game::Game(): Failed to create window."); + } - // Setup fade-in animation callbacks - fadeInAnimation.setAnimateCallback - ( - [this](std::size_t id, float opacity) - { - Vector3 color = Vector3(blackoutImage->getTintColor()); - blackoutImage->setTintColor(Vector4(color, opacity)); - } - ); - fadeInAnimation.setEndCallback - ( - [this]() - { - blackoutImage->setVisible(false); - if (fadeInEndCallback != nullptr) - { - fadeInEndCallback(); - } - } - ); + // Set v-sync mode + window->setVSync(vsync); +} - // Setup fade-out animation callbacks - fadeOutAnimation.setAnimateCallback - ( - [this](std::size_t id, float opacity) - { - Vector3 color = Vector3(blackoutImage->getTintColor()); - blackoutImage->setTintColor(Vector4(color, opacity)); - } - ); - fadeOutAnimation.setEndCallback - ( - [this]() - { - blackoutImage->setVisible(false); - if (fadeOutEndCallback != nullptr) - { - fadeOutEndCallback(); - } - } - ); +void Game::setupGraphics() +{ + // Setup OpenGL + glEnable(GL_MULTISAMPLE); - animator.addAnimation(&fadeInAnimation); - animator.addAnimation(&fadeOutAnimation); + // Setup default render target + defaultRenderTarget.width = w; + defaultRenderTarget.height = h; + defaultRenderTarget.framebuffer = 0; + + // Set shadow map resolution + shadowMapResolution = 4096; + + // Setup shadow map framebuffer + glGenFramebuffers(1, &shadowMapFramebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFramebuffer); + glGenTextures(1, &shadowMapDepthTextureID); + glBindTexture(GL_TEXTURE_2D, shadowMapDepthTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, shadowMapResolution, shadowMapResolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMapDepthTextureID, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Setup shadow map render target + shadowMapRenderTarget.width = shadowMapResolution; + shadowMapRenderTarget.height = shadowMapResolution; + shadowMapRenderTarget.framebuffer = shadowMapFramebuffer; + + // Setup shadow map depth texture + shadowMapDepthTexture.setTextureID(shadowMapDepthTextureID); + shadowMapDepthTexture.setWidth(shadowMapResolution); + shadowMapDepthTexture.setHeight(shadowMapResolution); + + // Setup silhouette framebuffer + glGenTextures(1, &silhouetteRenderTarget.texture); + glBindTexture(GL_TEXTURE_2D, silhouetteRenderTarget.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glGenFramebuffers(1, &silhouetteRenderTarget.framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, silhouetteRenderTarget.framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, silhouetteRenderTarget.texture, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + glReadBuffer(GL_NONE); + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Setup silhouette render target + silhouetteRenderTarget.width = w; + silhouetteRenderTarget.height = h; + + // Setup shadow map render pass + shadowMapPass = new ShadowMapRenderPass(resourceManager); + shadowMapPass->setRenderTarget(&shadowMapRenderTarget); + shadowMapPass->setViewCamera(&camera); + shadowMapPass->setLightCamera(&sunlightCamera); + + // Setup shadow map compositor + shadowMapCompositor.addPass(shadowMapPass); + shadowMapCompositor.load(nullptr); + + // Setup clear render pass + clearPass = new ClearRenderPass(); + clearPass->setRenderTarget(&defaultRenderTarget); + clearPass->setClear(true, true, false); + clearPass->setClearColor(Vector4(0.0f)); + clearPass->setClearDepth(1.0f); + + // Setup sky render pass + skyPass = new SkyRenderPass(resourceManager); + skyPass->setRenderTarget(&defaultRenderTarget); + // Setup lighting pass + lightingPass = new LightingRenderPass(resourceManager); + lightingPass->setRenderTarget(&defaultRenderTarget); + lightingPass->setShadowMapPass(shadowMapPass); + lightingPass->setShadowMap(&shadowMapDepthTexture); + + // Setup clear silhouette pass + clearSilhouettePass = new ClearRenderPass(); + clearSilhouettePass->setRenderTarget(&silhouetteRenderTarget); + clearSilhouettePass->setClear(true, false, false); + clearSilhouettePass->setClearColor(Vector4(0.0f)); + + // Setup silhouette pass + silhouettePass = new SilhouetteRenderPass(resourceManager); + silhouettePass->setRenderTarget(&silhouetteRenderTarget); + + // Setup final pass + finalPass = new FinalRenderPass(resourceManager); + finalPass->setRenderTarget(&defaultRenderTarget); + finalPass->setSilhouetteRenderTarget(&silhouetteRenderTarget); + + // Setup default compositor + defaultCompositor.addPass(clearPass); + defaultCompositor.addPass(skyPass); + defaultCompositor.addPass(lightingPass); + defaultCompositor.addPass(clearSilhouettePass); + defaultCompositor.addPass(silhouettePass); + defaultCompositor.addPass(finalPass); + defaultCompositor.load(nullptr); + + // Setup UI render pass + uiPass = new UIRenderPass(resourceManager); + uiPass->setRenderTarget(&defaultRenderTarget); - // Construct camera flash animation clip - cameraFlashClip.setInterpolator(easeOutQuad); - channel = cameraFlashClip.addChannel(0); - channel->insertKeyframe(0.0f, 1.0f); - channel->insertKeyframe(1.0f, 0.0f); + // Setup UI compositor + uiCompositor.addPass(uiPass); + uiCompositor.load(nullptr); - // Setup camera flash animation - float flashDuration = 0.5f; - cameraFlashAnimation.setSpeed(1.0f / flashDuration); - cameraFlashAnimation.setLoop(false); - cameraFlashAnimation.setClip(&cameraFlashClip); - cameraFlashAnimation.setTimeFrame(cameraFlashClip.getTimeFrame()); - cameraFlashAnimation.setAnimateCallback - ( - [this](std::size_t id, float opacity) - { - cameraFlashImage->setTintColor(Vector4(Vector3(1.0f), opacity)); - } - ); - cameraFlashAnimation.setStartCallback - ( - [this]() - { - cameraFlashImage->setVisible(true); - cameraFlashImage->setTintColor(Vector4(1.0f)); - cameraFlashImage->resetTweens(); - } - ); - cameraFlashAnimation.setEndCallback - ( - [this]() - { - cameraFlashImage->setVisible(false); - } - ); - animator.addAnimation(&cameraFlashAnimation); - + // Create scenes + worldScene = new Scene(&stepInterpolator); + uiScene = new Scene(&stepInterpolator); - // Setup shadow map pass and compositor - { - // Set shadow map resolution - shadowMapResolution = 4096; - - // Generate shadow map framebuffer - glGenFramebuffers(1, &shadowMapFramebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFramebuffer); - - // Generate shadow map depth texture - glGenTextures(1, &shadowMapDepthTextureID); - glBindTexture(GL_TEXTURE_2D, shadowMapDepthTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, shadowMapResolution, shadowMapResolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); - - // Attach depth texture to framebuffer - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMapDepthTextureID, 0); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - - // Unbind shadow map depth texture - glBindTexture(GL_TEXTURE_2D, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Setup shadow map render target - shadowMapRenderTarget.width = shadowMapResolution; - shadowMapRenderTarget.height = shadowMapResolution; - shadowMapRenderTarget.framebuffer = shadowMapFramebuffer; - - // Setup texture class - shadowMapDepthTexture.setTextureID(shadowMapDepthTextureID); - shadowMapDepthTexture.setWidth(shadowMapResolution); - shadowMapDepthTexture.setHeight(shadowMapResolution); - - // Setup shadow map render pass - shadowMapPass = new ShadowMapRenderPass(resourceManager); - shadowMapPass->setRenderTarget(&shadowMapRenderTarget); - shadowMapPass->setViewCamera(&camera); - shadowMapPass->setLightCamera(&sunlightCamera); - - // Setup shadow map compositor - shadowMapCompositor.addPass(shadowMapPass); - shadowMapCompositor.load(nullptr); - } + // Setup camera + camera.setPerspective(glm::radians(40.0f), static_cast(w) / static_cast(h), 0.1, 100.0f); + camera.lookAt(Vector3(0.0f, 4.0f, 2.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 1.0f, 0.0f)); + camera.setCompositor(&defaultCompositor); + camera.setCompositeIndex(1); + worldScene->addObject(&camera); + + // Setup sun + sunlight.setDirection(Vector3(0, -1, 0)); + setTimeOfDay(11.0f); + worldScene->addObject(&sunlight); + + // Setup sunlight camera + sunlightCamera.setOrthographic(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); + sunlightCamera.setCompositor(&shadowMapCompositor); + sunlightCamera.setCompositeIndex(0); + sunlightCamera.setCullingEnabled(true); + sunlightCamera.setCullingMask(&camera.getViewFrustum()); + worldScene->addObject(&sunlightCamera); +} - // Setup scene - { - // Setup lighting pass - lightingPass = new LightingRenderPass(resourceManager); - lightingPass->setRenderTarget(&defaultRenderTarget); - lightingPass->setShadowMapPass(shadowMapPass); - lightingPass->setShadowMap(&shadowMapDepthTexture); - - // Setup clear silhouette pass - clearSilhouettePass = new ClearRenderPass(); - clearSilhouettePass->setRenderTarget(&silhouetteRenderTarget); - clearSilhouettePass->setClear(true, false, false); - clearSilhouettePass->setClearColor(Vector4(0.0f)); - - // Setup silhouette pass - silhouettePass = new SilhouetteRenderPass(resourceManager); - silhouettePass->setRenderTarget(&silhouetteRenderTarget); - - // Setup final pass - finalPass = new FinalRenderPass(resourceManager); - finalPass->setRenderTarget(&defaultRenderTarget); - finalPass->setSilhouetteRenderTarget(&silhouetteRenderTarget); - - // Setup default compositor - defaultCompositor.addPass(clearPass); - defaultCompositor.addPass(skyPass); - defaultCompositor.addPass(lightingPass); - defaultCompositor.addPass(clearSilhouettePass); - defaultCompositor.addPass(silhouettePass); - defaultCompositor.addPass(finalPass); - defaultCompositor.load(nullptr); - - // Setup camera - camera.setPerspective(glm::radians(40.0f), static_cast(w) / static_cast(h), 0.1, 100.0f); - camera.lookAt(Vector3(0.0f, 4.0f, 2.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 1.0f, 0.0f)); - camera.setCompositor(&defaultCompositor); - camera.setCompositeIndex(1); - worldScene->addObject(&camera); - - // Setup sun - sunlight.setDirection(Vector3(0, -1, 0)); - setTimeOfDay(11.0f); - worldScene->addObject(&sunlight); - - // Setup sunlight camera - sunlightCamera.setOrthographic(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); - sunlightCamera.setCompositor(&shadowMapCompositor); - sunlightCamera.setCompositeIndex(0); - sunlightCamera.setCullingEnabled(true); - sunlightCamera.setCullingMask(&camera.getViewFrustum()); - worldScene->addObject(&sunlightCamera); - } - - // Setup UI scene - uiScene->addObject(uiBatch); - uiScene->addObject(&uiCamera); +void Game::setupUI() +{ + // Get DPI and convert font size to pixels + const Display* display = deviceManager->getDisplays()->front(); + dpi = display->getDPI(); + fontSizePX = fontSizePT * (1.0f / 72.0f) * dpi; - // Setup UI camera - uiCamera.lookAt(Vector3(0), Vector3(0, 0, -1), Vector3(0, 1, 0)); - uiCamera.resetTweens(); - uiCamera.setCompositor(&uiCompositor); - uiCamera.setCompositeIndex(0); - uiCamera.setCullingEnabled(false); - - restringUI(); - resizeUI(w, h); + // Load label typeface + labelTypeface = resourceManager->load("caveat-bold.ttf"); + labelFont = labelTypeface->createFont(fontSizePX); + // Load debugging typeface + debugTypeface = resourceManager->load("inconsolata-bold.ttf"); + debugFont = debugTypeface->createFont(fontSizePX); + debugTypeface->loadCharset(debugFont, UnicodeRange::BASIC_LATIN); - time = 0.0f; - + // Character set test + std::set charset; + charset.emplace(U'方'); + charset.emplace(U'蕴'); + labelTypeface->loadCharset(labelFont, UnicodeRange::BASIC_LATIN); + labelTypeface->loadCharset(labelFont, charset); - // Tools - currentTool = nullptr; + // Load splash screen texture + splashTexture = resourceManager->load("epigraph.png"); - lens = new Lens(lensModel, &animator); - lens->setOrbitCam(orbitCam); - worldScene->addObject(lens->getModelInstance()); - worldScene->addObject(lens->getSpotlight()); - lens->setSunDirection(-sunlightCamera.getForward()); + // Load HUD texture + hudSpriteSheetTexture = resourceManager->load("hud.png"); - ModelInstance* modelInstance = lens->getModelInstance(); - for (std::size_t i = 0; i < modelInstance->getModel()->getGroupCount(); ++i) + // Read texture atlas file + CSVTable* atlasTable = resourceManager->load("hud-atlas.csv"); + + // Build texture atlas + for (int row = 0; row < atlasTable->size(); ++row) { - Material* material = modelInstance->getModel()->getGroup(i)->material->clone(); - material->setFlags(material->getFlags() | 256); - modelInstance->setMaterialSlot(i, material); + std::stringstream ss; + float x; + float y; + float w; + float h; + + ss << (*atlasTable)[row][1]; + ss >> x; + ss.str(std::string()); + ss.clear(); + ss << (*atlasTable)[row][2]; + ss >> y; + ss.str(std::string()); + ss.clear(); + ss << (*atlasTable)[row][3]; + ss >> w; + ss.str(std::string()); + ss.clear(); + ss << (*atlasTable)[row][4]; + ss >> h; + ss.str(std::string()); + + y = static_cast(hudSpriteSheetTexture->getHeight()) - y - h; + x = (int)(x + 0.5f); + y = (int)(y + 0.5f); + w = (int)(w + 0.5f); + h = (int)(h + 0.5f); + + hudTextureAtlas.insert((*atlasTable)[row][0], Rect(Vector2(x, y), Vector2(x + w, y + h))); } - // Forceps - forceps = new Forceps(forcepsModel, &animator); - forceps->setOrbitCam(orbitCam); - worldScene->addObject(forceps->getModelInstance()); - - // Brush - brush = new Brush(brushModel, &animator); - brush->setOrbitCam(orbitCam); - worldScene->addObject(brush->getModelInstance()); + // Setup UI batching + uiBatch = new BillboardBatch(); + uiBatch->resize(1024); + uiBatcher = new UIBatcher(); - // - - performanceSampler.setSampleSize(30); + // Setup root UI element + uiRootElement = new UIContainer(); + eventDispatcher.subscribe(uiRootElement); + eventDispatcher.subscribe(uiRootElement); + eventDispatcher.subscribe(uiRootElement); - // Initialize component manager - componentManager = new ComponentManager(); + // Create splash screen background element + splashBackgroundImage = new UIImage(); + splashBackgroundImage->setLayerOffset(-1); + splashBackgroundImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + splashBackgroundImage->setVisible(false); + uiRootElement->addChild(splashBackgroundImage); - // Initialize entity manager - entityManager = new EntityManager(componentManager); + // Create splash screen element + splashImage = new UIImage(); + splashImage->setTexture(splashTexture); + splashImage->setVisible(false); + uiRootElement->addChild(splashImage); - // Initialize systems - soundSystem = new SoundSystem(componentManager); - collisionSystem = new CollisionSystem(componentManager); - cameraSystem = new CameraSystem(componentManager); - renderSystem = new RenderSystem(componentManager, worldScene); - toolSystem = new ToolSystem(componentManager); - toolSystem->setPickingCamera(&camera); - toolSystem->setPickingViewport(Vector4(0, 0, w, h)); - eventDispatcher.subscribe(toolSystem); - behaviorSystem = new BehaviorSystem(componentManager); - steeringSystem = new SteeringSystem(componentManager); - locomotionSystem = new LocomotionSystem(componentManager); - particleSystem = new ParticleSystem(componentManager); - particleSystem->resize(1000); - particleSystem->setMaterial(smokeMaterial); - particleSystem->setDirection(Vector3(0, 1, 0)); - lens->setParticleSystem(particleSystem); - particleSystem->getBillboardBatch()->setAlignment(&camera, BillboardAlignmentMode::SPHERICAL); - worldScene->addObject(particleSystem->getBillboardBatch()); + Rect hudTextureAtlasBounds(Vector2(0), Vector2(hudSpriteSheetTexture->getWidth(), hudSpriteSheetTexture->getHeight())); + auto normalizeTextureBounds = [](const Rect& texture, const Rect& atlas) + { + Vector2 atlasDimensions = Vector2(atlas.getWidth(), atlas.getHeight()); + return Rect(texture.getMin() / atlasDimensions, texture.getMax() / atlasDimensions); + }; + // Create HUD elements + hudContainer = new UIContainer(); + hudContainer->setVisible(false); + uiRootElement->addChild(hudContainer); + - // Initialize system manager - systemManager = new SystemManager(); - systemManager->addSystem(soundSystem); - systemManager->addSystem(behaviorSystem); - systemManager->addSystem(steeringSystem); - systemManager->addSystem(locomotionSystem); - systemManager->addSystem(collisionSystem); - systemManager->addSystem(toolSystem); - systemManager->addSystem(particleSystem); - systemManager->addSystem(cameraSystem); - systemManager->addSystem(renderSystem); + toolIndicatorBGImage = new UIImage(); + toolIndicatorBGImage->setTexture(hudSpriteSheetTexture); + toolIndicatorBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds)); + hudContainer->addChild(toolIndicatorBGImage); - EntityID sidewalkPanel; - sidewalkPanel = createInstanceOf("sidewalk-panel"); + toolIndicatorsBounds = new Rect[8]; + toolIndicatorsBounds[0] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-brush"), hudTextureAtlasBounds); + toolIndicatorsBounds[1] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-spade"), hudTextureAtlasBounds); + toolIndicatorsBounds[2] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-lens"), hudTextureAtlasBounds); + toolIndicatorsBounds[3] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-test-tube"), hudTextureAtlasBounds); + toolIndicatorsBounds[4] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-forceps"), hudTextureAtlasBounds); + toolIndicatorsBounds[5] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); + toolIndicatorsBounds[6] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); + toolIndicatorsBounds[7] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); - EntityID antHill = createInstanceOf("ant-hill"); - setTranslation(antHill, Vector3(20, 0, 40)); + toolIndicatorIconImage = new UIImage(); + toolIndicatorIconImage->setTexture(hudSpriteSheetTexture); + toolIndicatorIconImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-brush"), hudTextureAtlasBounds)); + toolIndicatorBGImage->addChild(toolIndicatorIconImage); - EntityID antNest = createInstanceOf("ant-nest"); - setTranslation(antNest, Vector3(20, 0, 40)); + buttonContainer = new UIContainer(); + hudContainer->addChild(buttonContainer); - EntityID lollipop = createInstanceOf("lollipop"); - setTranslation(lollipop, Vector3(30.0f, 3.5f * 0.5f, -30.0f)); - setRotation(lollipop, glm::angleAxis(glm::radians(8.85f), Vector3(1.0f, 0.0f, 0.0f))); + playButtonBGImage = new UIImage(); + playButtonBGImage->setTexture(hudSpriteSheetTexture); + playButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); + //buttonContainer->addChild(playButtonBGImage); - // Load navmesh - TriangleMesh* navmesh = resourceManager->load("sidewalk.mesh"); + pauseButtonBGImage = new UIImage(); + pauseButtonBGImage->setTexture(hudSpriteSheetTexture); + pauseButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); + //buttonContainer->addChild(pauseButtonBGImage); - // Find surface - TriangleMesh::Triangle* surface = nullptr; - Vector3 barycentricPosition; - Ray ray; - ray.origin = Vector3(0, 100, 0); - ray.direction = Vector3(0, -1, 0); - auto intersection = ray.intersects(*navmesh); - if (std::get<0>(intersection)) - { - surface = (*navmesh->getTriangles())[std::get<3>(intersection)]; + fastForwardButtonBGImage = new UIImage(); + fastForwardButtonBGImage->setTexture(hudSpriteSheetTexture); + fastForwardButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); + //buttonContainer->addChild(fastForwardButtonBGImage); - Vector3 position = ray.extrapolate(std::get<1>(intersection)); - Vector3 a = surface->edge->vertex->position; - Vector3 b = surface->edge->next->vertex->position; - Vector3 c = surface->edge->previous->vertex->position; + playButtonImage = new UIImage(); + playButtonImage->setTexture(hudSpriteSheetTexture); + playButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-play"), hudTextureAtlasBounds)); + //buttonContainer->addChild(playButtonImage); - barycentricPosition = barycentric(position, a, b, c); - } + fastForwardButtonImage = new UIImage(); + fastForwardButtonImage->setTexture(hudSpriteSheetTexture); + fastForwardButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-fast-forward-2x"), hudTextureAtlasBounds)); + //buttonContainer->addChild(fastForwardButtonImage); - for (int i = 0; i < 0; ++i) - { - EntityID ant = createInstanceOf("worker-ant"); - setTranslation(ant, Vector3(0.0f, 0, 0.0f)); + pauseButtonImage = new UIImage(); + pauseButtonImage->setTexture(hudSpriteSheetTexture); + pauseButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-pause"), hudTextureAtlasBounds)); + //buttonContainer->addChild(pauseButtonImage); - BehaviorComponent* behavior = new BehaviorComponent(); - SteeringComponent* steering = new SteeringComponent(); - LeggedLocomotionComponent* locomotion = new LeggedLocomotionComponent(); - componentManager->addComponent(ant, behavior); - componentManager->addComponent(ant, steering); - componentManager->addComponent(ant, locomotion); + radialMenuContainer = new UIContainer(); + radialMenuContainer->setVisible(false); + uiRootElement->addChild(radialMenuContainer); - locomotion->surface = surface; - behavior->wanderTriangle = surface; - locomotion->barycentricPosition = barycentricPosition; - } + radialMenuBackgroundImage = new UIImage(); + radialMenuBackgroundImage->setTintColor(Vector4(Vector3(0.0f), 0.25f)); + radialMenuContainer->addChild(radialMenuBackgroundImage); - - EntityID tool0 = createInstanceOf("lens"); + radialMenuImage = new UIImage(); + radialMenuImage->setTexture(hudSpriteSheetTexture); + radialMenuImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("radial-menu"), hudTextureAtlasBounds)); + radialMenuContainer->addChild(radialMenuImage); - changeState(splashState); -} + radialMenuSelectorImage = new UIImage(); + radialMenuSelectorImage->setTexture(hudSpriteSheetTexture); + radialMenuSelectorImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("radial-menu-selector"), hudTextureAtlasBounds)); + radialMenuContainer->addChild(radialMenuSelectorImage); -void Game::update(float t, float dt) -{ - this->time = t; + toolIconBrushImage = new UIImage(); + toolIconBrushImage->setTexture(hudSpriteSheetTexture); + toolIconBrushImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-brush"), hudTextureAtlasBounds)); + radialMenuImage->addChild(toolIconBrushImage); - // Dispatch scheduled events - eventDispatcher.update(t); + toolIconLensImage = new UIImage(); + toolIconLensImage->setTexture(hudSpriteSheetTexture); + toolIconLensImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-lens"), hudTextureAtlasBounds)); + radialMenuImage->addChild(toolIconLensImage); - // Execute current state - if (currentState != nullptr) - { - currentState->execute(); - } + toolIconForcepsImage = new UIImage(); + toolIconForcepsImage->setTexture(hudSpriteSheetTexture); + toolIconForcepsImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-forceps"), hudTextureAtlasBounds)); + radialMenuImage->addChild(toolIconForcepsImage); - // Update systems - systemManager->update(t, dt); + toolIconSpadeImage = new UIImage(); + toolIconSpadeImage->setTexture(hudSpriteSheetTexture); + toolIconSpadeImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-spade"), hudTextureAtlasBounds)); + //radialMenuImage->addChild(toolIconSpadeImage); - // Update animations - animator.animate(dt); + toolIconCameraImage = new UIImage(); + toolIconCameraImage->setTexture(hudSpriteSheetTexture); + toolIconCameraImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-camera"), hudTextureAtlasBounds)); + radialMenuImage->addChild(toolIconCameraImage); - if (fpsLabel->isVisible()) - { - std::stringstream stream; - stream.precision(2); - stream << std::fixed << (performanceSampler.getMeanFrameDuration() * 1000.0f); - fpsLabel->setText(stream.str()); - } + toolIconTestTubeImage = new UIImage(); + toolIconTestTubeImage->setTexture(hudSpriteSheetTexture); + toolIconTestTubeImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-test-tube"), hudTextureAtlasBounds)); + //radialMenuImage->addChild(toolIconTestTubeImage); - uiRootElement->update(); -} -void Game::input() -{ - /* - if (useToolControl.isActive() && !useToolControl.wasActive()) - { - intentSystem.queueIntent(Intent::USE_TOOL); - } - */ + antTag = new UIContainer(); + antTag->setLayerOffset(-10); + antTag->setVisible(false); - controls.update(); -} + uiRootElement->addChild(antTag); -void Game::render() -{ - // Perform sub-frame interpolation on UI elements - uiRootElement->interpolate(stepScheduler.getScheduledSubsteps()); + antLabelContainer = new UIContainer(); + antTag->addChild(antLabelContainer); - // Update and batch UI elements - uiBatcher->batch(uiBatch, uiRootElement); + antLabelTL = new UIImage(); + antLabelTR = new UIImage(); + antLabelBL = new UIImage(); + antLabelBR = new UIImage(); + antLabelCC = new UIImage(); + antLabelCT = new UIImage(); + antLabelCB = new UIImage(); + antLabelCL = new UIImage(); + antLabelCR = new UIImage(); - // Perform sub-frame interpolation particles - particleSystem->getBillboardBatch()->interpolate(stepScheduler.getScheduledSubsteps()); - particleSystem->getBillboardBatch()->batch(); + antLabelTL->setTexture(hudSpriteSheetTexture); + antLabelTR->setTexture(hudSpriteSheetTexture); + antLabelBL->setTexture(hudSpriteSheetTexture); + antLabelBR->setTexture(hudSpriteSheetTexture); + antLabelCC->setTexture(hudSpriteSheetTexture); + antLabelCT->setTexture(hudSpriteSheetTexture); + antLabelCB->setTexture(hudSpriteSheetTexture); + antLabelCL->setTexture(hudSpriteSheetTexture); + antLabelCR->setTexture(hudSpriteSheetTexture); - // Render scene - renderer.render(*worldScene); - renderer.render(*uiScene); + Rect labelTLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-tl"), hudTextureAtlasBounds); + Rect labelTRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-tr"), hudTextureAtlasBounds); + Rect labelBLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-bl"), hudTextureAtlasBounds); + Rect labelBRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-br"), hudTextureAtlasBounds); + Rect labelCCBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cc"), hudTextureAtlasBounds); + Rect labelCTBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-ct"), hudTextureAtlasBounds); + Rect labelCBBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cb"), hudTextureAtlasBounds); + Rect labelCLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cl"), hudTextureAtlasBounds); + Rect labelCRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cr"), hudTextureAtlasBounds); - // Swap window framebuffers - window->swapBuffers(); + Vector2 labelTLMin = labelTLBounds.getMin(); + Vector2 labelTRMin = labelTRBounds.getMin(); + Vector2 labelBLMin = labelBLBounds.getMin(); + Vector2 labelBRMin = labelBRBounds.getMin(); + Vector2 labelCCMin = labelCCBounds.getMin(); + Vector2 labelCTMin = labelCTBounds.getMin(); + Vector2 labelCBMin = labelCBBounds.getMin(); + Vector2 labelCLMin = labelCLBounds.getMin(); + Vector2 labelCRMin = labelCRBounds.getMin(); + Vector2 labelTLMax = labelTLBounds.getMax(); + Vector2 labelTRMax = labelTRBounds.getMax(); + Vector2 labelBLMax = labelBLBounds.getMax(); + Vector2 labelBRMax = labelBRBounds.getMax(); + Vector2 labelCCMax = labelCCBounds.getMax(); + Vector2 labelCTMax = labelCTBounds.getMax(); + Vector2 labelCBMax = labelCBBounds.getMax(); + Vector2 labelCLMax = labelCLBounds.getMax(); + Vector2 labelCRMax = labelCRBounds.getMax(); - if (screenshotQueued) - { - screenshot(); - screenshotQueued = false; - } -} + antLabelTL->setTextureBounds(labelTLBounds); + antLabelTR->setTextureBounds(labelTRBounds); + antLabelBL->setTextureBounds(labelBLBounds); + antLabelBR->setTextureBounds(labelBRBounds); + antLabelCC->setTextureBounds(labelCCBounds); + antLabelCT->setTextureBounds(labelCTBounds); + antLabelCB->setTextureBounds(labelCBBounds); + antLabelCL->setTextureBounds(labelCLBounds); + antLabelCR->setTextureBounds(labelCRBounds); -void Game::exit() -{ + antLabelContainer->addChild(antLabelTL); + antLabelContainer->addChild(antLabelTR); + antLabelContainer->addChild(antLabelBL); + antLabelContainer->addChild(antLabelBR); + antLabelContainer->addChild(antLabelCC); + antLabelContainer->addChild(antLabelCT); + antLabelContainer->addChild(antLabelCB); + antLabelContainer->addChild(antLabelCL); + antLabelContainer->addChild(antLabelCR); -} + antLabel = new UILabel(); + antLabel->setFont(labelFont); + antLabel->setText("Boggy B."); + antLabel->setTintColor(Vector4(Vector3(0.0f), 1.0f)); + antLabel->setLayerOffset(1); + antLabelContainer->addChild(antLabel); -void Game::handleEvent(const WindowResizedEvent& event) -{ - w = event.width; - h = event.height; + fpsLabel = new UILabel(); + fpsLabel->setFont(debugFont); + fpsLabel->setTintColor(Vector4(1, 1, 0, 1)); + fpsLabel->setLayerOffset(50); + fpsLabel->setAnchor(Anchor::TOP_LEFT); + uiRootElement->addChild(fpsLabel); - defaultRenderTarget.width = event.width; - defaultRenderTarget.height = event.height; - glViewport(0, 0, event.width, event.height); + antPin = new UIImage(); + antPin->setTexture(hudSpriteSheetTexture); + antPin->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("label-pin"), hudTextureAtlasBounds)); + antTag->addChild(antPin); - camera.setPerspective(glm::radians(40.0f), static_cast(w) / static_cast(h), 0.1, 100.0f); + antLabelPinHole = new UIImage(); + antLabelPinHole->setTexture(hudSpriteSheetTexture); + antLabelPinHole->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("label-pin-hole"), hudTextureAtlasBounds)); + antLabelContainer->addChild(antLabelPinHole); + + // Construct box selection + boxSelectionImageBackground = new UIImage(); + boxSelectionImageBackground->setAnchor(Anchor::CENTER); + boxSelectionImageTop = new UIImage(); + boxSelectionImageTop->setAnchor(Anchor::TOP_LEFT); + boxSelectionImageBottom = new UIImage(); + boxSelectionImageBottom->setAnchor(Anchor::BOTTOM_LEFT); + boxSelectionImageLeft = new UIImage(); + boxSelectionImageLeft->setAnchor(Anchor::TOP_LEFT); + boxSelectionImageRight = new UIImage(); + boxSelectionImageRight->setAnchor(Anchor::TOP_RIGHT); + boxSelectionContainer = new UIContainer(); + boxSelectionContainer->setLayerOffset(80); + boxSelectionContainer->addChild(boxSelectionImageBackground); + boxSelectionContainer->addChild(boxSelectionImageTop); + boxSelectionContainer->addChild(boxSelectionImageBottom); + boxSelectionContainer->addChild(boxSelectionImageLeft); + boxSelectionContainer->addChild(boxSelectionImageRight); + boxSelectionContainer->setVisible(false); + uiRootElement->addChild(boxSelectionContainer); + boxSelectionImageBackground->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.5f)); + boxSelectionContainer->setTintColor(Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + boxSelectionBorderWidth = 2.0f; + + cameraGridColor = Vector4(1, 1, 1, 0.5f); + cameraReticleColor = Vector4(1, 1, 1, 0.75f); + cameraGridY0Image = new UIImage(); + cameraGridY0Image->setAnchor(Vector2(0.5f, (1.0f / 3.0f))); + cameraGridY0Image->setTintColor(cameraGridColor); + cameraGridY1Image = new UIImage(); + cameraGridY1Image->setAnchor(Vector2(0.5f, (2.0f / 3.0f))); + cameraGridY1Image->setTintColor(cameraGridColor); + cameraGridX0Image = new UIImage(); + cameraGridX0Image->setAnchor(Vector2((1.0f / 3.0f), 0.5f)); + cameraGridX0Image->setTintColor(cameraGridColor); + cameraGridX1Image = new UIImage(); + cameraGridX1Image->setAnchor(Vector2((2.0f / 3.0f), 0.5f)); + cameraGridX1Image->setTintColor(cameraGridColor); + cameraReticleImage = new UIImage(); + cameraReticleImage->setAnchor(Anchor::CENTER); + cameraReticleImage->setTintColor(cameraReticleColor); + cameraReticleImage->setTexture(hudSpriteSheetTexture); + cameraReticleImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("camera-reticle"), hudTextureAtlasBounds)); + cameraGridContainer = new UIContainer(); + cameraGridContainer->addChild(cameraGridY0Image); + cameraGridContainer->addChild(cameraGridY1Image); + cameraGridContainer->addChild(cameraGridX0Image); + cameraGridContainer->addChild(cameraGridX1Image); + cameraGridContainer->addChild(cameraReticleImage); + cameraGridContainer->setVisible(true); + uiRootElement->addChild(cameraGridContainer); + cameraFlashImage = new UIImage(); + cameraFlashImage->setLayerOffset(99); + cameraFlashImage->setTintColor(Vector4(1.0f)); + cameraFlashImage->setVisible(false); + uiRootElement->addChild(cameraFlashImage); - toolSystem->setPickingViewport(Vector4(0, 0, w, h)); + blackoutImage = new UIImage(); + blackoutImage->setLayerOffset(98); + blackoutImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + blackoutImage->setVisible(false); + uiRootElement->addChild(blackoutImage); - resizeUI(event.width, event.height); -} + // Construct fade-in animation clip + fadeInClip.setInterpolator(easeOutCubic); + AnimationChannel* channel; + channel = fadeInClip.addChannel(0); + channel->insertKeyframe(0.0f, 1.0f); + channel->insertKeyframe(1.0f, 0.0f); -void Game::setupLocalization() -{ - // Load strings - loadStrings(); + // Construct fade-out animation clip + fadeOutClip.setInterpolator(easeOutCubic); + channel = fadeOutClip.addChannel(0); + channel->insertKeyframe(0.0f, 0.0f); + channel->insertKeyframe(1.0f, 1.0f); - // Determine number of available languages - languageCount = (*stringTable)[0].size() - 1; - - // Match language code with language index - languageIndex = 0; - CSVRow* languageCodes = &(*stringTable)[0]; - for (std::size_t i = 1; i < languageCodes->size(); ++i) - { - if (language == (*languageCodes)[i]) + // Setup fade-in animation callbacks + fadeInAnimation.setAnimateCallback + ( + [this](std::size_t id, float opacity) { - languageIndex = i - 1; - break; + Vector3 color = Vector3(blackoutImage->getTintColor()); + blackoutImage->setTintColor(Vector4(color, opacity)); } - } -} - -void Game::setupWindow() -{ - // Get display resolution - const Display* display = deviceManager->getDisplays()->front(); - int displayWidth = std::get<0>(display->getDimensions()); - int displayHeight = std::get<1>(display->getDimensions()); - - if (fullscreen) - { - w = static_cast(fullscreenResolution.x); - h = static_cast(fullscreenResolution.y); - } - else - { - w = static_cast(windowedResolution.x); - h = static_cast(windowedResolution.y); - } + ); + fadeInAnimation.setEndCallback + ( + [this]() + { + blackoutImage->setVisible(false); + if (fadeInEndCallback != nullptr) + { + fadeInEndCallback(); + } + } + ); - // Determine window position - int x = std::get<0>(display->getPosition()) + displayWidth / 2 - w / 2; - int y = std::get<1>(display->getPosition()) + displayHeight / 2 - h / 2; + // Setup fade-out animation callbacks + fadeOutAnimation.setAnimateCallback + ( + [this](std::size_t id, float opacity) + { + Vector3 color = Vector3(blackoutImage->getTintColor()); + blackoutImage->setTintColor(Vector4(color, opacity)); + } + ); + fadeOutAnimation.setEndCallback + ( + [this]() + { + blackoutImage->setVisible(false); + if (fadeOutEndCallback != nullptr) + { + fadeOutEndCallback(); + } + } + ); - // Read title string - std::string title = getString(getLanguageIndex(), "title"); + animator.addAnimation(&fadeInAnimation); + animator.addAnimation(&fadeOutAnimation); - // Create window - window = windowManager->createWindow(title.c_str(), x, y, w, h, fullscreen, WindowFlag::RESIZABLE); - if (!window) - { - throw std::runtime_error("Game::Game(): Failed to create window."); - } - // Set v-sync mode - window->setVSync(vsync); -} + // Construct camera flash animation clip + cameraFlashClip.setInterpolator(easeOutQuad); + channel = cameraFlashClip.addChannel(0); + channel->insertKeyframe(0.0f, 1.0f); + channel->insertKeyframe(1.0f, 0.0f); -void Game::setupGraphics() -{ - glEnable(GL_MULTISAMPLE); + // Setup camera flash animation + float flashDuration = 0.5f; + cameraFlashAnimation.setSpeed(1.0f / flashDuration); + cameraFlashAnimation.setLoop(false); + cameraFlashAnimation.setClip(&cameraFlashClip); + cameraFlashAnimation.setTimeFrame(cameraFlashClip.getTimeFrame()); + cameraFlashAnimation.setAnimateCallback + ( + [this](std::size_t id, float opacity) + { + cameraFlashImage->setTintColor(Vector4(Vector3(1.0f), opacity)); + } + ); + cameraFlashAnimation.setStartCallback + ( + [this]() + { + cameraFlashImage->setVisible(true); + cameraFlashImage->setTintColor(Vector4(1.0f)); + cameraFlashImage->resetTweens(); + } + ); + cameraFlashAnimation.setEndCallback + ( + [this]() + { + cameraFlashImage->setVisible(false); + } + ); + animator.addAnimation(&cameraFlashAnimation); - // Create scenes - worldScene = new Scene(&stepInterpolator); - uiScene = new Scene(&stepInterpolator); -} + // Setup UI scene + uiScene->addObject(uiBatch); + uiScene->addObject(&uiCamera); -void Game::setupUI() -{ - // Get DPI and convert font size to pixels - const Display* display = deviceManager->getDisplays()->front(); - dpi = display->getDPI(); - fontSizePX = fontSizePT * (1.0f / 72.0f) * dpi; + // Setup UI camera + uiCamera.lookAt(Vector3(0), Vector3(0, 0, -1), Vector3(0, 1, 0)); + uiCamera.resetTweens(); + uiCamera.setCompositor(&uiCompositor); + uiCamera.setCompositeIndex(0); + uiCamera.setCullingEnabled(false); + + restringUI(); + resizeUI(w, h); } void Game::setupControls() @@ -1227,9 +1194,9 @@ void Game::setupControls() mouse = deviceManager->getMice()->front(); // Build the master control set + controls.addControl(&exitControl); controls.addControl(&toggleFullscreenControl); controls.addControl(&screenshotControl); - controls.addControl(&exitControl); controls.addControl(&menuUpControl); controls.addControl(&menuDownControl); controls.addControl(&menuLeftControl); @@ -1288,26 +1255,33 @@ void Game::setupControls() screenshotControl.setActivatedCallback(std::bind(&Game::queueScreenshot, this)); toggleWireframeControl.setActivatedCallback(std::bind(&Game::toggleWireframe, this)); - // Map controls - inputMapper->map(&exitControl, keyboard, Scancode::ESCAPE); - inputMapper->map(&toggleFullscreenControl, keyboard, Scancode::F11); - inputMapper->map(&screenshotControl, keyboard, Scancode::F12); - inputMapper->map(&openToolMenuControl, keyboard, Scancode::LSHIFT); - inputMapper->map(&moveForwardControl, keyboard, Scancode::W); - inputMapper->map(&moveBackControl, keyboard, Scancode::S); - inputMapper->map(&moveLeftControl, keyboard, Scancode::A); - inputMapper->map(&moveRightControl, keyboard, Scancode::D); - inputMapper->map(&orbitCCWControl, keyboard, Scancode::Q); - inputMapper->map(&orbitCWControl, keyboard, Scancode::E); - inputMapper->map(&zoomInControl, mouse, MouseWheelAxis::POSITIVE_Y); - inputMapper->map(&zoomInControl, keyboard, Scancode::EQUALS); - inputMapper->map(&zoomOutControl, mouse, MouseWheelAxis::NEGATIVE_Y); - inputMapper->map(&zoomOutControl, keyboard, Scancode::MINUS); - inputMapper->map(&adjustCameraControl, mouse, 2); - inputMapper->map(&dragCameraControl, mouse, 3); - inputMapper->map(&toggleWireframeControl, keyboard, Scancode::V); - inputMapper->map(&toggleEditModeControl, keyboard, Scancode::TAB); - + // Build map of control names + controlNameMap["exit"] = &exitControl; + controlNameMap["toggle-fullscreen"] = &toggleFullscreenControl; + controlNameMap["screenshot"] = &screenshotControl; + controlNameMap["menu-up"] = &menuUpControl; + controlNameMap["menu-down"] = &menuDownControl; + controlNameMap["menu-left"] = &menuLeftControl; + controlNameMap["menu-right"] = &menuRightControl; + controlNameMap["menu-back"] = &menuBackControl; + controlNameMap["move-forward"] = &moveForwardControl; + controlNameMap["move-back"] = &moveBackControl; + controlNameMap["move-left"] = &moveLeftControl; + controlNameMap["move-right"] = &moveRightControl; + controlNameMap["zoom-in"] = &zoomInControl; + controlNameMap["zoom-out"] = &zoomOutControl; + controlNameMap["orbit-ccw"] = &orbitCCWControl; + controlNameMap["orbit-cw"] = &orbitCWControl; + controlNameMap["adjust-camera"] = &adjustCameraControl; + controlNameMap["drag-camera"] = &dragCameraControl; + controlNameMap["open-tool-menu"] = &openToolMenuControl; + controlNameMap["use-tool"] = &useToolControl; + controlNameMap["toggle-edit-mode"] = &toggleEditModeControl; + controlNameMap["toggle-wireframe"] = &toggleWireframeControl; + + // Load control profile + loadControlProfile(); + /* controlProfile.registerControl("exit", &exitControl); controlProfile.registerControl("toggle-fullscreen", &toggleFullscreenControl); @@ -1351,15 +1325,14 @@ void Game::setupGameplay() stepScheduler.setMaxFrameDuration(maxFrameDuration); stepScheduler.setStepFrequency(stepFrequency); timestep = stepScheduler.getStepPeriod(); -} -void Game::setupDebugging() -{ - // Setup performance sampling - performanceSampler.setSampleSize(15); + // Setup camera rigs + cameraRig = nullptr; + orbitCam = new OrbitCam(); + orbitCam->attachCamera(&camera); + freeCam = new FreeCam(); + freeCam->attachCamera(&camera); - // Disable wireframe drawing - wireframe = false; } void Game::resetSettings() @@ -1437,6 +1410,218 @@ void Game::loadStrings() } } +void Game::loadControlProfile() +{ + // Create control directory if it doesn't exist + std::string controlsPath = getConfigPath() + "/controls/"; + if (!pathExists(controlsPath)) + { + createDirectory(controlsPath); + } + + // Load control profile + std::string controlProfilePath = "/controls/" + controlProfileName + ".csv"; + CSVTable* controlProfile = resourceManager->load(controlProfilePath); + + for (const CSVRow& row: *controlProfile) + { + // Skip empty rows and comments + if (row.empty() || row[0].empty() || row[0][0] == '#') + { + continue; + } + + // Get control name + const std::string& controlName = row[0]; + + // Lookup control in control name map + auto it = controlNameMap.find(controlName); + if (it == controlNameMap.end()) + { + std::cerr << "Game::loadControlProfile(): Unknown control name \"" << controlName << "\"" << std::endl; + continue; + } + + // Get pointer to the control + Control* control = it->second; + + // Determine type of input mapping + const std::string& deviceType = row[1]; + if (deviceType == "keyboard") + { + const std::string& eventType = row[2]; + const std::string& scancodeString = row[3]; + + // Get scancode from string + std::stringstream stream; + int scancodeIndex; + stream << row[3]; + stream >> scancodeIndex; + Scancode scancode = static_cast(scancodeIndex); + + // Map control + inputMapper->map(control, keyboard, scancode); + } + else if (deviceType == "mouse") + { + const std::string& eventType = row[2]; + + if (eventType == "motion") + { + const std::string& axisName = row[3]; + + // Get axis from string + MouseMotionAxis axis; + bool negative = (axisName.find('-') != std::string::npos); + if (axisName.find('x') != std::string::npos) + { + axis = (negative) ? MouseMotionAxis::NEGATIVE_X : MouseMotionAxis::POSITIVE_X; + } + else if (axisName.find('y') != std::string::npos) + { + axis = (negative) ? MouseMotionAxis::NEGATIVE_Y : MouseMotionAxis::POSITIVE_Y; + } + else + { + std::cerr << "Game::loadControlProfile(): Unknown mouse motion axis \"" << axisName << "\"" << std::endl; + continue; + } + + // Map control + inputMapper->map(control, mouse, axis); + } + else if (eventType == "wheel") + { + const std::string& axisName = row[3]; + + // Get axis from string + MouseWheelAxis axis; + bool negative = (axisName.find('-') != std::string::npos); + if (axisName.find('x') != std::string::npos) + { + axis = (negative) ? MouseWheelAxis::NEGATIVE_X : MouseWheelAxis::POSITIVE_X; + } + else if (axisName.find('y') != std::string::npos) + { + axis = (negative) ? MouseWheelAxis::NEGATIVE_Y : MouseWheelAxis::POSITIVE_Y; + } + else + { + std::cerr << "Game::loadControlProfile(): Unknown mouse wheel axis \"" << axisName << "\"" << std::endl; + continue; + } + + // Map control + inputMapper->map(control, mouse, axis); + } + else if (eventType == "button") + { + const std::string& buttonName = row[3]; + + // Get button from string + int button; + std::stringstream stream; + stream << buttonName; + stream >> button; + + // Map control + inputMapper->map(control, mouse, button); + } + else + { + std::cerr << "Game::loadControlProfile(): Unknown mouse event type \"" << eventType << "\"" << std::endl; + continue; + } + } + else if (deviceType == "gamepad") + { + const std::string& eventType = row[2]; + if (eventType == "axis") + { + std::string axisName = row[3]; + + // Determine whether axis is negative or positive + bool negative = (axisName.find('-') != std::string::npos); + + // Remove sign from axis name + std::size_t plusPosition = axisName.find('+'); + std::size_t minusPosition = axisName.find('-'); + if (plusPosition != std::string::npos) + { + axisName.erase(plusPosition); + } + else if (minusPosition != std::string::npos) + { + axisName.erase(minusPosition); + } + + // Get axis from string + int axis; + std::stringstream stream; + stream << axisName; + stream >> axis; + + // Map control to each gamepad + const std::list* gamepads = deviceManager->getGamepads(); + for (Gamepad* gamepad: *gamepads) + { + inputMapper->map(control, gamepad, axis, negative); + } + } + else if (eventType == "button") + { + const std::string& buttonName = row[3]; + + // Get button from string + int button; + std::stringstream stream; + stream << buttonName; + stream >> button; + + // Map control to each gamepad + const std::list* gamepads = deviceManager->getGamepads(); + for (Gamepad* gamepad: *gamepads) + { + inputMapper->map(control, gamepad, button); + } + } + else + { + std::cerr << "Game::loadControlProfile(): Unknown gamepad event type \"" << eventType << "\"" << std::endl; + continue; + } + } + else + { + std::cerr << "Game::loadControlProfile(): Unknown input device type \"" << deviceType << "\"" << std::endl; + continue; + } + } + + // Map controls + inputMapper->map(&exitControl, keyboard, Scancode::ESCAPE); + inputMapper->map(&toggleFullscreenControl, keyboard, Scancode::F11); + inputMapper->map(&screenshotControl, keyboard, Scancode::F12); + inputMapper->map(&openToolMenuControl, keyboard, Scancode::LSHIFT); + inputMapper->map(&moveForwardControl, keyboard, Scancode::W); + inputMapper->map(&moveBackControl, keyboard, Scancode::S); + inputMapper->map(&moveLeftControl, keyboard, Scancode::A); + inputMapper->map(&moveRightControl, keyboard, Scancode::D); + inputMapper->map(&orbitCCWControl, keyboard, Scancode::Q); + inputMapper->map(&orbitCWControl, keyboard, Scancode::E); + inputMapper->map(&zoomInControl, mouse, MouseWheelAxis::POSITIVE_Y); + inputMapper->map(&zoomInControl, keyboard, Scancode::EQUALS); + inputMapper->map(&zoomOutControl, mouse, MouseWheelAxis::NEGATIVE_Y); + inputMapper->map(&zoomOutControl, keyboard, Scancode::MINUS); + inputMapper->map(&adjustCameraControl, mouse, 2); + inputMapper->map(&dragCameraControl, mouse, 3); + inputMapper->map(&toggleWireframeControl, keyboard, Scancode::V); + inputMapper->map(&toggleEditModeControl, keyboard, Scancode::TAB); +} + +void Game::saveControlProfile() +{} + void Game::resizeUI(int w, int h) { // Adjust root element dimensions @@ -1744,6 +1929,11 @@ void Game::screenshot() // Restore camera UI visibility cameraGridContainer->setVisible(true); fpsLabel->setVisible(true); + + // Whiteout screen immediately + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + window->swapBuffers(); } void Game::boxSelect(float x, float y, float w, float h) diff --git a/src/game.hpp b/src/game.hpp index d89ffab..d074bb6 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -131,19 +131,23 @@ private: virtual void render(); virtual void exit(); virtual void handleEvent(const WindowResizedEvent& event); + virtual void handleEvent(const GamepadConnectedEvent& event); + virtual void handleEvent(const GamepadDisconnectedEvent& event); + void setupDebugging(); void setupLocalization(); void setupWindow(); void setupGraphics(); void setupUI(); void setupControls(); void setupGameplay(); - void setupDebugging(); void resetSettings(); void loadSettings(); void saveSettings(); void loadStrings(); + void loadControlProfile(); + void saveControlProfile(); void resizeUI(int w, int h); void restringUI(); @@ -245,6 +249,9 @@ public: ControlSet debugControls; Control toggleWireframeControl; + // Map of control names + std::map controlNameMap; + // Logic float time; float timestep; diff --git a/src/states/sandbox-state.cpp b/src/states/sandbox-state.cpp index bd86812..9f76c3a 100755 --- a/src/states/sandbox-state.cpp +++ b/src/states/sandbox-state.cpp @@ -73,7 +73,7 @@ void SandboxState::enter() toolIndex = 0; game->selectTool(toolIndex); //game->currentTool->setActive(false); - game->mouse->warp(game->window, game->w / 2, game->h / 2); + //game->mouse->warp(game->window, game->w / 2, game->h / 2); zoom = 0.5f; noPick = false;