Example scene loading

This commit is contained in:
2024-12-02 14:53:41 -06:00
parent ac0f0e86c5
commit 2af55041c8
48 changed files with 698 additions and 38 deletions

View File

@@ -27,14 +27,10 @@ add_subdirectory(component)
add_subdirectory(display)
add_subdirectory(environment)
add_subdirectory(game)
# add_subdirectory(games)
# add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(prefab)
add_subdirectory(physics)
add_subdirectory(save)
add_subdirectory(scene)
# add_subdirectory(state)
add_subdirectory(time)
add_subdirectory(util)
add_subdirectory(ui)

View File

@@ -84,6 +84,17 @@ void assertTrueImplement(
map.find(key) != map.end(), __VA_ARGS__ \
)
/**
* Asserts that a given map does not have a specific key.
* @param map Map to check.
* @param key Key to check for.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertMapNotHasKey(map, key, ...) assertTrue( \
map.find(key) == map.end(), __VA_ARGS__ \
)
/**
* Asserts that a given value has a specific flag turned off.
*

View File

@@ -4,11 +4,13 @@
// https://opensource.org/licenses/MIT
#include "AssetManager.hpp"
#include "assert/assert.hpp"
using namespace Dawn;
void AssetManager::init() {
void AssetManager::init(const std::shared_ptr<Game> &game) {
assertNotNull(game, "Game is NULL?");
this->game = game;
}
void AssetManager::update() {
@@ -27,6 +29,22 @@ void AssetManager::update() {
}
}
void AssetManager::remove(const std::shared_ptr<AssetLoader> loader) {
auto existing = std::find(
pendingAssetLoaders.begin(), pendingAssetLoaders.end(), loader
);
if(existing != pendingAssetLoaders.end()) {
pendingAssetLoaders.erase(existing);
}
existing = std::find(
finishedAssetLoaders.begin(), finishedAssetLoaders.end(), loader
);
if(existing != finishedAssetLoaders.end()) {
finishedAssetLoaders.erase(existing);
}
}
// Disabled because it does not respect scopes
// void AssetManager::removeExisting(const std::string &filename) {
// auto existing = std::find_if(
@@ -54,6 +72,12 @@ bool_t AssetManager::isEverythingLoaded() {
return pendingAssetLoaders.size() == 0;
}
std::shared_ptr<Game> AssetManager::getGame() {
auto g = game.lock();
assertNotNull(g, "Game is NULL?");
return g;
}
AssetManager::~AssetManager() {
}

View File

@@ -8,8 +8,11 @@
#include "asset/AssetLoader.hpp"
namespace Dawn {
class Game;
class AssetManager final : public std::enable_shared_from_this<AssetManager> {
private:
std::weak_ptr<Game> game;
std::vector<std::shared_ptr<AssetLoader>> pendingAssetLoaders;
std::vector<std::shared_ptr<AssetLoader>> finishedAssetLoaders;
@@ -59,8 +62,10 @@ namespace Dawn {
public:
/**
* Initializes this asset manager so it can begin accepting assets.
*
* @param game Game context that this asset manager is attached to.
*/
void init();
void init(const std::shared_ptr<Game> &game);
/**
* Updates the asset manager.
@@ -96,6 +101,22 @@ namespace Dawn {
return loader;
}
/**
* Returns the game context that this asset manager is attached to.
*
* @return The game context.
*/
std::shared_ptr<Game> getGame();
/**
* Removes the given asset loader from the asset manager, assumes that
* nothing else needs to access it and any dangling shared_ptrs will have
* to remain in memory.
*
* @param loader The asset loader to remove.
*/
void remove(const std::shared_ptr<AssetLoader> loader);
/**
* Dispose the asset manager, and all attached assets.
*/

View File

@@ -6,9 +6,7 @@
#pragma once
#include "asset/AssetLoader.hpp"
#include "asset/AssetDataLoader.hpp"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace Dawn {
enum class JSONLoaderState {

View File

@@ -4,6 +4,11 @@
// https://opensource.org/licenses/MIT
#include "SceneLoader.hpp"
#include "game/Game.hpp"
#include "assert/assert.hpp"
#include "asset/loader/TextureLoader.hpp"
#include "scene/Scene.hpp"
#include "component/SceneComponentRegistry.hpp"
using namespace Dawn;
@@ -18,6 +23,83 @@ SceneLoader::SceneLoader(
{
}
void SceneLoader::setupDependencies() {
std::cout << "Setting up dependencies" << std::endl;
// Begin loading dependencies.
auto &data = this->jsonLoader->data;
if(data.contains("assets")) {
for(auto &asset : data["assets"].items()) {
auto &assetName = asset.key();
auto &assetData = asset.value();
assertTrue(assetData.contains("type"), "Asset missing type");
assertTrue(assetData.contains("path"), "Asset missing path");
auto type = assetData["type"].get<std::string>();
auto path = assetData["path"].get<std::string>();
std::shared_ptr<AssetLoader> loader;
if(type == "texture") {
loader = getAssetManager()->get<TextureLoader>(path);
} else if(type == "json") {
loader = getAssetManager()->get<JSONLoader>(path);
} else {
assertUnreachable("Unknown asset type: %s", type.c_str());
}
assertMapNotHasKey(
ctx.assets,
assetName,
"Asset already exists: %s", assetName.c_str()
);
ctx.assets[assetName] = loader;
}
}
this->state = SceneLoaderState::LOADING_DEPENDENCIES;
}
void SceneLoader::sceneInitializer(Scene &scene) {
std::cout << "Initializing scene" << std::endl;
auto &data = this->jsonLoader->data;
if(data.contains("items")) {
// Create the scene items
for(auto &item : data["items"].items()) {
auto &itemName = item.key();
auto &itemData = item.value();
auto sceneItem = scene.createSceneItem();
ctx.items[itemName] = sceneItem;
}
// Add components to each scene item
for(auto &item : data["items"].items()) {
auto &itemName = item.key();
auto &itemData = item.value();
auto sceneItem = ctx.items[itemName];
ctx.data = itemData;
sceneItem->load(ctx);
if(itemData.contains("components")) {
for(auto &cmpItem : itemData["components"].items()) {
auto &cmpName = cmpItem.key();
auto &cmpData = cmpItem.value();
assertTrue(cmpData.contains("type"), "Component missing type in %s", itemName.c_str());
auto cmpType = cmpData["type"].get<std::string>();
auto cmp = SceneComponentRegistry::createComponent(cmpType, sceneItem);
ctx.data = cmpData;
ctx.components[cmpName] = cmp;
cmp->load(ctx);
}
}
}
}
this->jsonLoader = nullptr;
this->state = SceneLoaderState::DONE;
getAssetManager()->remove(shared_from_this());
}
void SceneLoader::updateAsync() {
switch(this->state) {
case SceneLoaderState::INITIAL:
@@ -26,6 +108,31 @@ void SceneLoader::updateAsync() {
break;
case SceneLoaderState::LOADING_JSON:
assertNotNull(this->jsonLoader, "JSON Loader is NULL?");
if(!this->jsonLoader->loaded) return;
this->setupDependencies();
break;
case SceneLoaderState::LOADING_DEPENDENCIES:
// Check if all dependencies are loaded.
for(auto &asset : ctx.assets) {
if(!asset.second->loaded) return;
}
this->state = SceneLoaderState::DEPENDENCIES_LOADED;
break;
case SceneLoaderState::DEPENDENCIES_LOADED:
std::cout << "Deps Loaded" << std::endl;
this->loaded = true;
ctx.scene = std::make_shared<Scene>(
this->getAssetManager()->getGame(),
[this](Scene &scene) -> void {
this->sceneInitializer(scene);
}
);
this->state = SceneLoaderState::PENDING_STAGE;
this->loaded = true;
break;
default:
@@ -42,9 +149,11 @@ std::string SceneLoader::getAssetType() const {
}
std::shared_ptr<Scene> SceneLoader::getScene() {
return scene;
return ctx.scene;
}
SceneLoader::~SceneLoader() {
ctx = {};
jsonLoader = nullptr;
std::cout << "Scene Loader removed" << std::endl;
}

View File

@@ -11,14 +11,32 @@ namespace Dawn {
enum class SceneLoaderState {
INITIAL,
LOADING_JSON,
LOADING_DEPENDENCIES,
DEPENDENCIES_LOADED,
PENDING_STAGE,
DONE
};
class SceneLoader : public AssetLoader {
class SceneLoader :
public AssetLoader,
public std::enable_shared_from_this<SceneLoader>
{
protected:
SceneLoaderState state;
std::shared_ptr<JSONLoader> jsonLoader;
std::shared_ptr<Scene> scene;
struct SceneComponentLoadContext ctx;
/**
* Loads the dependencies of the scene.
*/
void setupDependencies();
/**
* Scene intializer function to stage the loaded scene.
*
* @param scene Scene that is being staged.
*/
void sceneInitializer(Scene &scene);
public:
const static std::string ASSET_TYPE;

View File

@@ -6,9 +6,9 @@
target_sources(${DAWN_TARGET_NAME}
PRIVATE
SimpleComponent.cpp
SceneComponentRegistry.cpp
)
# Subdirs
add_subdirectory(display)
add_subdirectory(ui)
add_subdirectory(physics)
add_subdirectory(ui)

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "SceneComponentRegistry.hpp"
#include "component/display/Camera.hpp"
#include "component/display/material/SimpleTexturedMaterial.hpp"
#include "component/display/MeshRenderer.hpp"
#include "component/display/mesh/CubeMeshComponent.hpp"
using namespace Dawn;
std::shared_ptr<SceneComponent> SceneComponentRegistry::createComponent(
const std::string &type,
const std::shared_ptr<SceneItem> &item
) {
if(type.length() == 0) {
assertUnreachable("Component type is empty!");
} else if(type == "Camera") {
return item->addComponent<Camera>();
} else if(type == "SimpleTexturedMaterial") {
return item->addComponent<SimpleTexturedMaterial>();
} else if(type == "MeshRenderer") {
return item->addComponent<MeshRenderer>();
} else if(type == "CubeMesh" || type == "CubeMeshComponent") {
return item->addComponent<CubeMeshComponent>();
} else {
assertUnreachable("Unknown component type: %s", type.c_str());
}
return nullptr;
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "scene/Scene.hpp"
namespace Dawn {
class SceneComponentRegistry final {
public:
/**
* Creates a scene component by its type name. Type names are unique and
* must be registered in order to be constructed by their string name.
*
* @param type Type name of the scene component to create.
* @param item Scene item that the component belongs to.
* @return The created scene component.
*/
static std::shared_ptr<SceneComponent> createComponent(
const std::string &type,
const std::shared_ptr<SceneItem> &item
);
};
}

View File

@@ -10,4 +10,5 @@ target_sources(${DAWN_TARGET_NAME}
)
# Subdirs
add_subdirectory(material)
add_subdirectory(material)
add_subdirectory(mesh)

View File

@@ -21,6 +21,68 @@ void Camera::onDispose() {
renderTarget = nullptr;
}
void Camera::load(const SceneComponentLoadContext &ctx) {
SceneComponent::load(ctx);
if(ctx.data.contains("fov")) {
this->fov = Math::deg2rad(ctx.data["fov"].get<float_t>());
}
if(ctx.data.contains("cameraType")) {
if(
ctx.data["cameraType"] == "orthogonal" ||
ctx.data["cameraType"] == "orthographic" ||
ctx.data["cameraType"] == "ortho"
) {
this->type = CameraType::ORTHOGONAL;
} else if(ctx.data["cameraType"] == "perspective") {
this->type = CameraType::PERSPECTIVE;
} else {
assertUnreachable("Invalid Camera Type!");
}
}
if(ctx.data.contains("orthoLeft")) {
this->orthoLeft = ctx.data["orthoLeft"].get<float_t>();
} else if(ctx.data.contains("left")) {
this->orthoLeft = ctx.data["left"].get<float_t>();
}
if(ctx.data.contains("orthoRight")) {
this->orthoRight = ctx.data["orthoRight"].get<float_t>();
} else if(ctx.data.contains("right")) {
this->orthoRight = ctx.data["right"].get<float_t>();
}
if(ctx.data.contains("orthoBottom")) {
this->orthoBottom = ctx.data["orthoBottom"].get<float_t>();
} else if(ctx.data.contains("bottom")) {
this->orthoBottom = ctx.data["bottom"].get<float_t>();
}
if(ctx.data.contains("orthoTop")) {
this->orthoTop = ctx.data["orthoTop"].get<float_t>();
} else if(ctx.data.contains("top")) {
this->orthoTop = ctx.data["top"].get<float_t>();
}
if(ctx.data.contains("clipNear")) {
this->clipNear = ctx.data["clipNear"].get<float_t>();
} else if(ctx.data.contains("near")) {
this->clipNear = ctx.data["near"].get<float_t>();
} else if(ctx.data.contains("zNear")) {
this->clipNear = ctx.data["zNear"].get<float_t>();
}
if(ctx.data.contains("clipFar")) {
this->clipFar = ctx.data["clipFar"].get<float_t>();
} else if(ctx.data.contains("far")) {
this->clipFar = ctx.data["far"].get<float_t>();
} else if(ctx.data.contains("zFar")) {
this->clipFar = ctx.data["zFar"].get<float_t>();
}
}
std::shared_ptr<RenderTarget> Camera::getRenderTarget() {
if(this->renderTarget) return this->renderTarget;
return getGame()->renderHost->getBackBufferRenderTarget();

View File

@@ -33,6 +33,7 @@ namespace Dawn {
void onInit() override;
void onDispose() override;
void load(const SceneComponentLoadContext &ctx) override;
/**
* Returns the aspect ratio that the camera is using. In future I may

View File

@@ -4,9 +4,24 @@
// https://opensource.org/licenses/MIT
#include "SimpleTexturedMaterial.hpp"
#include "util/JSON.hpp"
#include "asset/loader/TextureLoader.hpp"
using namespace Dawn;
void SimpleTexturedMaterial::load(const SceneComponentLoadContext &ctx) {
if(ctx.data.contains("color")) {
this->setColor(JSON::color(ctx.data["color"]));
}
if(ctx.data.contains("texture")) {
auto asset = ctx.getAsset<TextureLoader>(
ctx.data["texture"].get<std::string>()
);
this->setTexture(asset->getTexture());
}
}
struct Color SimpleTexturedMaterial::getColor() {
return this->data.color;
}

View File

@@ -15,6 +15,8 @@ namespace Dawn {
std::shared_ptr<Texture> texture;
public:
void load(const SceneComponentLoadContext &ctx) override;
/**
* Returns the color of this material.
*/

View File

@@ -5,5 +5,5 @@
target_sources(${DAWN_TARGET_NAME}
PRIVATE
PhysicsManager.cpp
CubeMeshComponent.cpp
)

View File

@@ -0,0 +1,33 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "CubeMeshComponent.hpp"
#include "display/mesh/CubeMesh.hpp"
#include "component/display/MeshRenderer.hpp"
using namespace Dawn;
void CubeMeshComponent::onInit() {
if(!mesh) {
mesh = std::make_shared<Mesh>();
}
mesh->createBuffers(CUBE_VERTICE_COUNT, CUBE_INDICE_COUNT);
CubeMesh::buffer(
mesh, glm::vec3(-0.5f, -0.5f, -0.5f), glm::vec3(1.0f, 1.0f, 1.0f), 0, 0
);
auto renderer = getItem()->getComponent<MeshRenderer>();
if(renderer) renderer->mesh = mesh;
}
void CubeMeshComponent::onDispose() {
mesh = nullptr;
}
void CubeMeshComponent::load(const SceneComponentLoadContext &ctx) {
if(!mesh) mesh = std::make_shared<Mesh>();
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "scene/SceneItem.hpp"
#include "display/mesh/Mesh.hpp"
namespace Dawn {
class CubeMeshComponent final : public SceneComponent {
public:
std::shared_ptr<Mesh> mesh;
void onInit() override;
void onDispose() override;
void load(const SceneComponentLoadContext &ctx) override;
};
}

View File

@@ -1,11 +0,0 @@
# Copyright (c) 2023 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DAWN_TARGET_NAME}
PRIVATE
Collider.cpp
CubeCollider.cpp
SphereCollider.cpp
)

View File

@@ -1,137 +0,0 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "Collider.hpp"
#include "assert/assert.hpp"
#include "game/Game.hpp"
using namespace Dawn;
using namespace JPH;
using namespace JPH::literals;
EMotionType Collider::getMotionType(const ColliderType colliderType) {
EMotionType motionType;
switch(colliderType) {
case ColliderType::DYNAMIC:
return EMotionType::Dynamic;
break;
case ColliderType::STATIC:
return EMotionType::Static;
break;
case ColliderType::KINEMATIC:
return EMotionType::Kinematic;
break;
}
assertUnreachable("Invalid ColliderType");
return EMotionType::Kinematic;
}
//
void Collider::onInit() {
assertNull(this->body, "Body is not NULL?");
auto settings = this->getShapeSettings();
auto shapeResult = settings->Create();
auto shape = shapeResult.Get();
auto pos = getItem()->getLocalPosition();
BodyCreationSettings bodySettings(
shape,
RVec3(pos.x, pos.y, pos.z),
Quat::sIdentity(),
Collider::getMotionType(this->colliderType),
layer
);
this->body = getBodyInterface().CreateBody(bodySettings);
assertNotNull(this->body, "Body failed to create?");
this->bodyId = this->body->GetID();
getBodyInterface().AddBody(this->bodyId, EActivation::Activate);
}
void Collider::onDispose() {
getBodyInterface().RemoveBody(this->bodyId);
getBodyInterface().DestroyBody(this->bodyId);
}
void Collider::notifyShapeChanged() {
if(!this->isColliderReady()) return;
auto settings = this->getShapeSettings();
auto shapeResult = settings->Create();
auto shape = shapeResult.Get();
getBodyInterface().SetShape(
this->bodyId,
shape,
// TODO: I may not always need to re-activate the body here.
true,
EActivation::Activate
);
}
bool_t Collider::isColliderReady() {
return this->body != nullptr;
}
BodyInterface & Collider::getBodyInterface() {
return getGame()->physicsManager->getBodyInterface();
}
ColliderType Collider::getColliderType() {
return colliderType;
}
BodyID Collider::getBodyId() {
return bodyId;
}
glm::vec3 Collider::getVelocity() {
if(!this->isColliderReady()) return glm::vec3(0.0f, 0.0f, 0.0f);
auto vel = getBodyInterface().GetLinearVelocity(this->bodyId);
return glm::vec3(vel.GetX(), vel.GetY(), vel.GetZ());
}
void Collider::setVelocity(const glm::vec3 velocity) {
if(!this->isColliderReady()) {
assertUnreachable("Collider is not ready.");
}
getBodyInterface().SetLinearVelocity(
this->bodyId, RVec3(velocity.x, velocity.y, velocity.z)
);
}
void Collider::setColliderType(const ColliderType type) {
this->colliderType = type;
if(!this->isColliderReady()) return;
getBodyInterface().SetMotionType(
this->bodyId,
Collider::getMotionType(type),
EActivation::Activate// TODO: Should be false on kinematics
);
}
void Collider::addForce(
const glm::vec3 force,
const glm::vec3 inPoint
) {
if(!this->isColliderReady()) assertUnreachable("Collider is not ready.");
getBodyInterface().AddForce(
this->bodyId,
RVec3(force.x, force.y, force.z),
RVec3(0, 0, 0)
);
}

View File

@@ -1,109 +0,0 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "scene/SceneItem.hpp"
namespace Dawn {
enum class ColliderType {
DYNAMIC,
STATIC,
KINEMATIC
};
class Collider : public SceneComponent {
private:
JPH::Body *body = nullptr;
ColliderType colliderType = ColliderType::DYNAMIC;
/**
* Returns the JoltPhysics motion type for the collider type.
*
* @param colliderType The collider type.
* @return The JoltPhysics motion type.
*/
static JPH::EMotionType getMotionType(const ColliderType colliderType);
protected:
JPH::BodyID bodyId;
JPH::ObjectLayer layer = 1;
/**
* Returns the shape settings for the collider.
*
* @return The shape settings for the collider.
*/
virtual std::shared_ptr<JPH::ShapeSettings> getShapeSettings() = 0;
/**
* Returns the JoltPhysics body interface system.
*
* @return The JoltPhysics body interface system.
*/
JPH::BodyInterface & getBodyInterface();
/**
* Callable by subclasses to notify that the shape has changed.
*/
void notifyShapeChanged();
public:
void onInit() override;
void onDispose() override;
/**
* Returns whether the collider is ready.
*
* @return Whether the collider is ready.
*/
bool_t isColliderReady();
/**
* Returns the collider type.
*
* @return The collider type.
*/
ColliderType getColliderType();
/**
* Returns the JoltPhysics body ID of the collider.
*
* @return The body ID of the collider.
*/
JPH::BodyID getBodyId();
/**
* Returns the velocity of the collider.
*
* @return The velocity of the collider.
*/
glm::vec3 getVelocity();
/**
* Sets the collider type.
*
* @param colliderType The collider type.
*/
void setColliderType(ColliderType colliderType);
/**
* Sets the velocity of the collider.
*
* @param velocity The velocity.
*/
void setVelocity(const glm::vec3 velocity);
/**
* Adds a force to the collider.
*
* @param force Force to add.
* @param inPoint Application point of the force.
*/
void addForce(
const glm::vec3 force,
const glm::vec3 inPoint = glm::vec3(0, 0, 0)
);
};
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "CubeCollider.hpp"
using namespace Dawn;
std::shared_ptr<JPH::ShapeSettings> CubeCollider::getShapeSettings() {
return std::make_shared<JPH::BoxShapeSettings>(
JPH::Vec3(shape.x, shape.y, shape.z)
);
}
glm::vec3 CubeCollider::getShape() {
return shape;
}
void CubeCollider::setShape(const glm::vec3 &shape) {
this->shape = shape;
this->notifyShapeChanged();
}

View File

@@ -1,32 +0,0 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "Collider.hpp"
namespace Dawn {
class CubeCollider : public Collider {
private:
glm::vec3 shape = glm::vec3(1, 1, 1);
protected:
std::shared_ptr<JPH::ShapeSettings> getShapeSettings() override;
public:
/**
* Returns the shape of the cube collider.
*
* @return The shape of the cube collider.
*/
glm::vec3 getShape();
/**
* Sets the shape of the cube collider.
*
* @param shape The shape of the cube collider.
*/
void setShape(const glm::vec3 &shape);
};
}

View File

@@ -1,12 +0,0 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "SphereCollider.hpp"
using namespace Dawn;
std::shared_ptr<JPH::ShapeSettings> SphereCollider::getShapeSettings() {
return std::make_shared<JPH::SphereShapeSettings>(radius);
}

View File

@@ -1,17 +0,0 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "Collider.hpp"
namespace Dawn {
class SphereCollider : public Collider {
protected:
std::shared_ptr<JPH::ShapeSettings> getShapeSettings() override;
public:
float radius = 1.0f;
};
}

View File

@@ -60,4 +60,7 @@ extern "C" {
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyActivationListener.h>
#include <Jolt/Physics/Character/Character.h>
#include <Jolt/Physics/Character/Character.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;

View File

@@ -25,21 +25,30 @@ void IGame::init() {
renderHost->init(selfAsGame);
assetManager = std::make_shared<AssetManager>();
assetManager->init();
assetManager->init(selfAsGame);
localeManager = std::make_shared<LocaleManager>();
localeManager->init(selfAsGame);
physicsManager = std::make_shared<PhysicsManager>();
physicsManager->init(selfAsGame);
#ifdef DAWN_ENABLE_PHYSICS
physicsManager = std::make_shared<PhysicsManager>();
physicsManager->init(selfAsGame);
#endif
inputManager.init(selfAsGame);
saveManager.init(selfAsGame);
this->initManagers();
auto initialScene = this->getInitialScene();
nextFrameScene = std::make_shared<Scene>(selfAsGame, initialScene);
// TEST
auto scene = this->assetManager->get<SceneLoader>("test_scene.json");
while(!this->assetManager->isEverythingLoaded()) {
this->assetManager->update();
}
// auto initialScene = this->getInitialScene();
// nextFrameScene = std::make_shared<Scene>(selfAsGame, initialScene);
nextFrameScene = scene->getScene();
}
void IGame::deinit() {
@@ -51,7 +60,10 @@ void IGame::deinit() {
if(nextFrameScene) nextFrameScene->deinit();
nextFrameScene = nullptr;
physicsManager = nullptr;
#ifdef DAWN_ENABLE_PHYSICS
physicsManager = nullptr;
#endif
assetManager = nullptr;
renderHost = nullptr;
@@ -71,7 +83,11 @@ void IGame::update() {
timeManager.update();
if(currentScene) currentScene->update();
physicsManager->update();
#ifdef DAWN_ENABLE_PHYSICS
physicsManager->update();
#endif
renderHost->update(this->getSelfAsGame());
}

View File

@@ -50,7 +50,11 @@ namespace Dawn {
std::shared_ptr<RenderHost> renderHost;
std::shared_ptr<AssetManager> assetManager;
std::shared_ptr<LocaleManager> localeManager;
std::shared_ptr<PhysicsManager> physicsManager;
#ifdef DAWN_ENABLE_PHYSICS
std::shared_ptr<PhysicsManager> physicsManager;
#endif
InputManager inputManager;
TimeManager timeManager;
SaveManager saveManager;

View File

@@ -1,294 +0,0 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "PhysicsManager.hpp"
#include "game/Game.hpp"
#include "scene/Scene.hpp"
#include "component/physics/Collider.hpp"
using namespace Dawn;
using namespace JPH;
using namespace JPH::literals;
static void TraceImpl(const char *inFMT, ...) {
// Format the message
va_list list;
va_start(list, inFMT);
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), inFMT, list);
va_end(list);
// Print to the TTY
// std::cout << buffer << std::endl;
}
#ifdef JPH_ENABLE_ASSERTS
static bool AssertFailedImpl(
const char *inExpression,
const char *inMessage,
const char *inFile,
uint inLine
) {
// Print to the TTY
std::cout << inFile << ":" << inLine << ": (" << inExpression << ") " << (inMessage != nullptr? inMessage : "") << std::endl;
// Breakpoint
return true;
};
#endif
namespace Layers {
static constexpr ObjectLayer NON_MOVING = 0;
static constexpr ObjectLayer MOVING = 1;
static constexpr ObjectLayer NUM_LAYERS = 2;
};
/// Class that determines if two object layers can collide
class ObjectLayerPairFilterImpl : public ObjectLayerPairFilter {
public:
virtual bool ShouldCollide(
ObjectLayer inObject1,
ObjectLayer inObject2
) const override {
switch (inObject1) {
case Layers::NON_MOVING:
return inObject2 == Layers::MOVING;
case Layers::MOVING:
return true;
default:
JPH_ASSERT(false);
return false;
}
}
};
namespace BroadPhaseLayers {
static constexpr BroadPhaseLayer NON_MOVING(0);
static constexpr BroadPhaseLayer MOVING(1);
static constexpr uint NUM_LAYERS(2);
};
class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface {
public:
BPLayerInterfaceImpl() {
mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
}
virtual uint GetNumBroadPhaseLayers() const override {
return BroadPhaseLayers::NUM_LAYERS;
}
virtual BroadPhaseLayer GetBroadPhaseLayer(
ObjectLayer inLayer
) const override {
JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
return mObjectToBroadPhase[inLayer];
}
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
virtual const char * GetBroadPhaseLayerName(
BroadPhaseLayer inLayer
) const override {
switch ((BroadPhaseLayer::Type)inLayer) {
case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING:
return "NON_MOVING";
case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING:
return "MOVING";
default:
JPH_ASSERT(false); return "INVALID";
}
}
#endif
private:
BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS];
};
class ObjectVsBroadPhaseLayerFilterImpl : public ObjectVsBroadPhaseLayerFilter {
public:
virtual bool ShouldCollide(
ObjectLayer inLayer1,
BroadPhaseLayer inLayer2
) const override {
switch (inLayer1) {
case Layers::NON_MOVING:
return inLayer2 == BroadPhaseLayers::MOVING;
case Layers::MOVING:
return true;
default:
JPH_ASSERT(false);
return false;
}
}
};
// An example contact listener
class MyContactListener : public ContactListener {
public:
// See: ContactListener
virtual ValidateResult OnContactValidate(
const Body &inBody1,
const Body &inBody2,
RVec3Arg inBaseOffset,
const CollideShapeResult &inCollisionResult
) override {
// std::cout << "Contact validate callback" << std::endl;
return ValidateResult::AcceptAllContactsForThisBodyPair;
}
virtual void OnContactAdded(
const Body &inBody1,
const Body &inBody2,
const ContactManifold &inManifold,
ContactSettings &ioSettings
) override {
// std::cout << "A contact was added" << std::endl;
}
virtual void OnContactPersisted(
const Body &inBody1,
const Body &inBody2,
const ContactManifold &inManifold,
ContactSettings &ioSettings
) override {
// std::cout << "A contact was persisted" << std::endl;
}
virtual void OnContactRemoved(
const SubShapeIDPair &inSubShapePair
) override {
// std::cout << "A contact was removed" << std::endl;
}
};
// An example activation listener
class MyBodyActivationListener : public BodyActivationListener {
public:
virtual void OnBodyActivated(
const BodyID &inBodyID,
uint64 inBodyUserData
) override {
// std::cout << "A body got activated" << std::endl;
}
virtual void OnBodyDeactivated(
const BodyID &inBodyID,
uint64 inBodyUserData
) override {
// std::cout << "A body went to sleep" << std::endl;
}
};
const uint cMaxBodies = 1024;
const uint cNumBodyMutexes = 0;
const uint cMaxBodyPairs = 1024;
const uint cMaxContactConstraints = 1024;
BPLayerInterfaceImpl broadPhaseLayerInterface;
ObjectVsBroadPhaseLayerFilterImpl objectVsBroadPhaseLayerFilter;
ObjectLayerPairFilterImpl objectVsObjectLayerFilter;
MyBodyActivationListener bodyActivationListener;
MyContactListener contactListener;
std::shared_ptr<Game> PhysicsManager::getGame() {
auto g = game.lock();
assertNotNull(g, "Game is null");
return g;
}
BodyInterface & PhysicsManager::getBodyInterface() {
return physicsSystem.GetBodyInterface();
}
void PhysicsManager::init(const std::shared_ptr<Game> &game) {
this->game = game;
RegisterDefaultAllocator();
Trace = TraceImpl;
JPH_IF_ENABLE_ASSERTS(AssertFailed = AssertFailedImpl;)
Factory::sInstance = new Factory();
RegisterTypes();
tempAllocator = std::make_shared<TempAllocatorImpl>(10 * 1024 * 1024);
jobSystem = std::make_shared<JobSystemThreadPool>(
cMaxPhysicsJobs,
cMaxPhysicsBarriers,
thread::hardware_concurrency() - 1
);
physicsSystem.Init(
cMaxBodies,
cNumBodyMutexes,
cMaxBodyPairs,
cMaxContactConstraints,
broadPhaseLayerInterface,
objectVsBroadPhaseLayerFilter,
objectVsObjectLayerFilter
);
physicsSystem.SetBodyActivationListener(&bodyActivationListener);
physicsSystem.SetContactListener(&contactListener);
}
void PhysicsManager::update() {
BodyInterface &bodyInterface = physicsSystem.GetBodyInterface();
const float cDeltaTime = getGame()->timeManager.delta;
const int cCollisionSteps = 1;
auto scene = getGame()->getCurrentScene();
if(scene == nullptr) return;
// // Now we're ready to simulate the body, keep simulating until it goes to sleep
// uint step = 0;
// if(!bodyInterface.IsActive(sphereId)) return;
// // ++step;
// // Output current position and velocity of the sphere
// RVec3 position = bodyInterface.GetCenterOfMassPosition(sphereId);
// Vec3 velocity = bodyInterface.GetLinearVelocity(sphereId);
// Step the world
physicsSystem.Update(
cDeltaTime,
cCollisionSteps,
tempAllocator.get(),
jobSystem.get()
);
auto colliders = scene->findComponents<Collider>();
for(auto &collider : colliders) {
auto bodyId = collider->getBodyId();
if(!bodyInterface.IsActive(bodyId)) continue;
auto pos = bodyInterface.GetCenterOfMassPosition(bodyId);
auto rot = bodyInterface.GetRotation(bodyId);
collider->getItem()->setLocalPosition(glm::vec3(pos.GetX(), pos.GetY(), pos.GetZ()));
collider->getItem()->setLocalRotation(glm::quat(rot.GetW(), rot.GetX(), rot.GetY(), rot.GetZ()));
}
}
PhysicsManager::~PhysicsManager() {
BodyInterface &bodyInterface = physicsSystem.GetBodyInterface();
// bodyInterface.RemoveBody(sphereId);
// bodyInterface.DestroyBody(sphereId);
// Remove and destroy the floor
// bodyInterface.RemoveBody(floor->GetID());
// bodyInterface.DestroyBody(floor->GetID());
tempAllocator = nullptr;
jobSystem = nullptr;
UnregisterTypes();
delete Factory::sInstance;
Factory::sInstance = nullptr;
game.reset();
}

View File

@@ -1,54 +0,0 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawn.hpp"
namespace Dawn {
class Game;
class PhysicsManager final {
private:
std::weak_ptr<Game> game;
protected:
JPH::PhysicsSystem physicsSystem;
std::shared_ptr<JPH::TempAllocatorImpl> tempAllocator;
std::shared_ptr<JPH::JobSystemThreadPool> jobSystem;
public:
/**
* Gets the game associated with the PhysicsManager.
*
* @return The game associated with the PhysicsManager.
*/
std::shared_ptr<Game> getGame();
/**
* Gets the JoltPhysics body interface system.
*
* @return The JoltPhysics body interface system.
*/
JPH::BodyInterface & getBodyInterface();
/**
* Initializes the PhysicsManager.
*
* @param game The game to initialize the PhysicsManager with.
*/
void init(const std::shared_ptr<Game> &game);
/**
* Updates the PhysicsManager.
*/
void update();
/**
* Deconstructs the PhysicsManager.
*/
~PhysicsManager();
};
}

View File

@@ -19,6 +19,9 @@ Scene::Scene(
void Scene::init() {
Scene &selfReference = *this;
sceneInitializer(selfReference);
sceneInitializer = [](Scene &scene) -> void {
};
}
void Scene::deinit() {

View File

@@ -19,7 +19,7 @@ namespace Dawn {
class Scene final : public std::enable_shared_from_this<Scene> {
private:
std::weak_ptr<Game> game;
const std::function<void(Scene&)> sceneInitializer;
std::function<void(Scene&)> sceneInitializer;
std::vector<std::shared_ptr<SceneItem>> sceneItems;
std::vector<std::shared_ptr<SceneItem>> sceneItemsToRemove;
bool_t paused = false;

View File

@@ -70,6 +70,10 @@ std::shared_ptr<Game> SceneComponent::getGame() {
return this->getScene()->getGame();
}
void SceneComponent::load(const SceneComponentLoadContext &context) {
// Override this method to load data from a JSON object.
}
SceneComponent::~SceneComponent() {
if(Flag::isOn<uint_fast8_t>(
sceneComponentState,

View File

@@ -5,6 +5,7 @@
#pragma once
#include "dawn.hpp"
#include "assert/assert.hpp"
#define SCENE_COMPONENT_STATE_INIT 0x01
#define SCENE_COMPONENT_STATE_DISPOSED 0x02
@@ -13,6 +14,25 @@ namespace Dawn {
class Game;
class Scene;
class SceneItem;
class SceneComponent;
class AssetLoader;
struct SceneComponentLoadContext {
json data;
std::shared_ptr<Scene> scene;
std::unordered_map<std::string, std::shared_ptr<SceneItem>> items;
std::unordered_map<std::string, std::shared_ptr<SceneComponent>> components;
std::unordered_map<std::string, std::shared_ptr<AssetLoader>> assets;
template<class T>
std::shared_ptr<T> getAsset(const std::string &j) const {
auto it = assets.find(j);
assertTrue(it != assets.end(), "Asset not found.");
auto asset = std::dynamic_pointer_cast<T>(it->second);
assertNotNull(asset, "Asset is not of the correct type.");
return asset;
}
};
class SceneComponent : std::enable_shared_from_this<SceneComponent> {
private:
@@ -75,6 +95,14 @@ namespace Dawn {
*/
std::shared_ptr<Game> getGame();
/**
* Load data from a JSON object. This is typically done during a scene
* load.
*
* @param json JSON Data that this object needs to load.
*/
virtual void load(const SceneComponentLoadContext &context);
/**
* Disposes this scene component.
*/

View File

@@ -5,6 +5,7 @@
#include "scene/SceneItem.hpp"
#include "scene/Scene.hpp"
#include "util/JSON.hpp"
using namespace Dawn;
@@ -65,6 +66,29 @@ void SceneItem::deinit() {
this->components.clear();
}
void SceneItem::load(const SceneComponentLoadContext &ctx) {
// Transforms
if(ctx.data.contains("position")) {
this->setLocalPosition(JSON::vec3(ctx.data["position"]));
}
if(ctx.data.contains("lookAt")) {
auto &lookAt = ctx.data["lookAt"];
glm::vec3 pos = glm::vec3(3, 3, 3);
glm::vec3 look = glm::vec3(0, 0, 0);
glm::vec3 up = glm::vec3(0, 1, 0);
if(lookAt.contains("position")) pos = JSON::vec3(lookAt["position"]);
if(lookAt.contains("look")) look = JSON::vec3(lookAt["look"]);
if(lookAt.contains("up")) up = JSON::vec3(lookAt["up"]);
this->lookAt(pos, look, up);
}
if(ctx.data.contains("scale")) {
this->setLocalScale(JSON::vec3(ctx.data["scale"]));
}
}
void SceneItem::remove() {
auto scene = getScene();
if(!scene) return;

View File

@@ -41,6 +41,15 @@ namespace Dawn {
*/
void deinit();
/**
* Loads this scene item from the given context. Scene items are not
* responsible for loading their components, this is handled by the scene
* loader.
*
* @param ctx Context to load this scene item from.
*/
void load(const SceneComponentLoadContext &ctx);
/**
* Returns the scene that this scene item belongs to.
*

View File

@@ -6,4 +6,5 @@
target_sources(${DAWN_TARGET_NAME}
PRIVATE
String.cpp
JSON.cpp
)

77
src/dawn/util/JSON.cpp Normal file
View File

@@ -0,0 +1,77 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "JSON.hpp"
#include "assert/assert.hpp"
using namespace Dawn;
glm::vec3 JSON::vec3(const json &j) {
if(j.type() == json::value_t::array) {
return glm::vec3(j[0].get<float_t>(), j[1].get<float_t>(), j[2].get<float_t>());
} else if(j.type() == json::value_t::object) {
assertTrue(j.contains("x"), "Missing x in vec3");
assertTrue(j.contains("y"), "Missing y in vec3");
assertTrue(j.contains("z"), "Missing z in vec3");
return glm::vec3(
j["x"].get<float_t>(),
j["y"].get<float_t>(),
j["z"].get<float_t>()
);
} else {
assertUnreachable("Invalid JSON type for vec3");
}
}
struct Color JSON::color(const json &j) {
if(j.type() == json::value_t::array) {
return {
j[0].get<float_t>(),
j[1].get<float_t>(),
j[2].get<float_t>(),
j[3].get<float_t>()
};
} else if(j.type() == json::value_t::object) {
float_t r, g, b, a = 1.0f;
if(j.contains("r")) {
r = j["r"].get<float_t>();
} else if(j.contains("red")) {
r = j["red"].get<float_t>();
} else {
assertTrue(j.contains("red"), "Missing red in color");
}
if(j.contains("g")) {
g = j["g"].get<float_t>();
} else if(j.contains("green")) {
g = j["green"].get<float_t>();
} else {
assertTrue(j.contains("green"), "Missing green in color");
}
if(j.contains("b")) {
b = j["b"].get<float_t>();
} else if(j.contains("blue")) {
b = j["blue"].get<float_t>();
} else {
assertTrue(j.contains("blue"), "Missing blue in color");
}
if(j.contains("a")) {
a = j["a"].get<float_t>();
} else if(j.contains("alpha")) {
a = j["alpha"].get<float_t>();
}
return { r, g, b, a };
} else if(j.type() == json::value_t::string) {
return Color::fromString(j.get<std::string>());
} else {
assertUnreachable("Invalid JSON type for color");
}
}

29
src/dawn/util/JSON.hpp Normal file
View File

@@ -0,0 +1,29 @@
// Copyright (c) 2024 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawn.hpp"
#include "display/Color.hpp"
namespace Dawn {
class JSON final {
public:
/**
* Parses the given JSON string as a vec3.
*
* @param j JSON obj to parse.
* @return Parsed vec3.
*/
static glm::vec3 vec3(const json &j);
/**
* Parses the given JSON string as a color.
*
* @param j JSON obj to parse.
* @return Parsed color.
*/
static struct Color color(const json &j);
};
}