Skip to content

Creating a New App

Marcus Hudritsch edited this page Jul 1, 2024 · 14 revisions

SLProject provides an framework to develop cross-platform apps that run on Windows, macOS, Linux, Android, iOS, and on the Web. This guide explains how to create a small example app.

Setting Things Up

To get started, create a subdirectory inside the apps directory called app_example with the following structure:

app_example/
├── AppExample.cpp
└── CMakeLists.txt

Next, we have to tell CMake about our new app. Append the following line to apps/CMakeLists.txt:

add_subdirectory(app_example)

To create the CMake target for the app, paste the following snippet into apps/app_example/CMakeLists.txt:

sl_add_app(
    TARGET "app-example"
    PLATFORMS "GLFW;EMSCRIPTEN"
    SOURCES "AppExample.cpp"
)

This creates a new CMake target called app-example that runs on GLFW (Windows, macOS, Linux) and Emscripten (the Web). We'll add support for mobile platforms later on. In our example, we only have one source file to add: AppExample.cpp, which will contain our main function.

We're now ready to write some C++ code. Paste the following snippet into apps/app_example/AppExample.cpp:

#include <App.h>
#include <SLScene.h>

static SLScene* createScene(SLSceneID sceneID)
{
    return new SLScene("Example Scene");
}

int SL_MAIN_FUNCTION(int argc, char* argv[])
{
    return App::run({.windowTitle = "Example App",
                     .onNewScene  = createScene});
}

In this snippet, we include the App.h header that contains everything required to get a cross-platform app running. Our main function is defined using a macro (SL_MAIN_FUNCTION) that expands to the platform-dependent main function name. The main function calls App::run with two arguments: the name of the app and a callback function to create a scene. Currently, we just create an empty default scene that shows a gray background.

Building and Running

Building and running the app involves some platform-specific steps.

Windows (Using Visual C++)

Open a Developer PowerShell in the SLProject root directory and run the following commands:

cmake -Bbuild .
cmake --build build --target app-example --config Debug --parallel
.\build\Debug\app-example-debug

macOS and Linux

Open a terminal in the SLProject root directory and run the following commands:

cmake -Bbuild -DCMAKE_BUILD_TYPE=Debug .
cmake --build build --target app-example --parallel
./build/app-example-debug

For the Web

Read the build page for Emscripten on how to setup emscripten. After installation, open a terminal in the SLProject root directory, activate your Emscripten environment, and run the following commands:

emcmake cmake -Bbuild-emscripten -DCMAKE_BUILD_TYPE=Debug .
cmake --build build-emscripten --target app-example --parallel

For every app, SLProject generates a Python script that serves its content over HTTP.
Serving on Windows: cd build-emscripten; python .\serve-app-example.py
Serving on macOS and Linux: cd build-emscripten && python3 ./serve-app-example.py

To view your app, open a browser and go to http://localhost:8080.

Adding Content to the Scene

Now that we have set up a basic app, we can start adding some objects into our scene.

Add the following includes at the top of AppExample.cpp:

#include <SLSceneView.h>
#include <SLCamera.h>
#include <SLLightDirect.h>
#include <SLMaterial.h>
#include <SLNode.h>
#include <SLBox.h>

Next, add the following class below the includes:

class AppExampleScene : public SLScene
{
public:
    AppExampleScene() : SLScene("Example Scene") {}

    void assemble(SLAssetManager* am, SLSceneView* sv) override
    {
        SLCamera* camera = new SLCamera();
        camera->translation(2, 1, 3);
        camera->lookAt(0, 0, 0);
        camera->focalDist(camera->translationWS().distance(SLVec3f::ZERO));

        SLLightDirect* light = new SLLightDirect(am, this);
        light->translate(-0.5f, 1.0f, 0.75f);
        light->lookAt(0, 0, 0);

        SLMaterial* material = new SLMaterial(am, "Red Material", SLCol4f::RED);
        SLNode*     box      = new SLNode("Box Node");
        box->addMesh(new SLBox(am, -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f, "Box", material));

        SLNode* scene = new SLNode();
        scene->addChild(light);
        scene->addChild(camera);
        scene->addChild(box);
        root3D(scene);

        sv->camera(camera);
    }
};

AppExampleScene defines the contents of our example scene. It inherits from SLScene and overrides the method assemble. This method takes care of instantiating our scene content such as cameras, lights, materials, etc.

The last change is to update our createScene function such that it creates an instance of AppExampleScene instead of the generic SLScene:

static SLScene* createScene(SLSceneID sceneID)
{
    return new AppExampleScene();
}

If you run the app now, you should see a red cube illuminated by a directional light.

Loading Assets

Assets such as textures or models are usually stored in files and can be loaded by SLProject.

First, we need some more includes:

#include <SLAssetLoader.h>
#include <AppDemo.h>

Now update the AppExampleScene so it looks like this:

class AppExampleScene : public SLScene
{
public:
    AppExampleScene() : SLScene("Example Scene") {}

    void registerAssetsToLoad(SLAssetLoader& al) override
    {
        al.addTextureToLoad(_woodTexture, AppDemo::texturePath + "wood0_0512_C.jpg");
    }

    void assemble(SLAssetManager* am, SLSceneView* sv) override
    {
        // ...
    }

private:
    SLGLTexture* _woodTexture;
};

In SLProject, all file I/O has to be performed before scene assembly. For this purpose, we override the registerAssetsToLoad method of SLScene. In there, we tell the asset loader which files need to be loaded. In this example, we load a texture asset and store it in a class member called _woodTexture.

Now that we have a wood texture, we can replace the red color on our material with that texture:

void assemble(SLAssetManager* am, SLSceneView* sv) override
{
    // ...
    
    SLMaterial* material = new SLMaterial(am, "Wood Material", _woodTexture);
    
    // ...
}

If you run the app now, you should see a wooden cube instead of the previous red cube.

Adding a GUI

SLProject uses Dear ImGui for its GUI. The Dear ImGui setup is done behind the scenes, in our app we only have to define a function that uses Dear ImGui to build the controls.

First, we need to add another include:

#include <SLGLImGui.h>

Now define a function to create the GUI:

static void buildGui(SLScene* s, SLSceneView* sv)
{
    if (ImGui::BeginMainMenuBar())
    {
        if (ImGui::BeginMenu("View"))
        {
            if (ImGui::MenuItem("Reset Camera"))
            {
                sv->camera()->translation(2, 1, 3);
                sv->camera()->lookAt(0, 0, 0);
            }

            ImGui::EndMenu();
        }

        ImGui::EndMainMenuBar();
    }
}

This function creates a menu bar with a menu View that contains a button Reset Camera that resets the camera to its initial state.

To tell SLProject about this function, pass it to App::run:

int SL_MAIN_FUNCTION(int argc, char* argv[])
{
    return App::run({.windowTitle = "Example App",
                     .onNewScene  = createScene,
                     .onGuiBuild  = buildGui});
}

Adding Support for Mobile Platforms

To build our app for mobile platforms, we have to specify that our app runs on Android and iOS. Open apps/app_example/CMakeLists.txt and add ANDROID and IOS to the list of supported platforms:

sl_add_app(
    TARGET "app-example"
    PLATFORMS "GLFW;EMSCRIPTEN;ANDROID;IOS"
    SOURCES "AppExample.cpp"
)

Next, we set some platform-specific configuration options. On Android, we need to specify the directory of our Android app module. We use the app module in SLProject's example Android project at apps/source/platforms/android/example_project. Add the following configuration option:

sl_add_app(
    ...
    
    ANDROID_APP_DIR "${SL_PROJECT_ROOT}/apps/source/platforms/android/example_project/app"
)

On iOS, we specify an Information Property List (Info.plist) template and values for its variables. We use the Info.plist template from SLProject's example iOS project at apps/source/platforms/ios/example_project.

sl_add_app(
    ...
    
    IOS_INFO_PLIST "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/plist.in"
    IOS_DISPLAY_NAME "SL Example"
    IOS_COPYRIGHT "Copyright [Your Name]"
    IOS_ICON_NAME "AppIcon"
)

We also have to add some additional resources for iOS like asset catalogs or storyboards:

sl_add_app(
    ...
    
    IOS_RESOURCES
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/Base.lproj/ViewController_iPad.xib"
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/Base.lproj/ViewController_iPhone.xib"
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/LaunchScreenSLDemo.storyboard"
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/Images/Images.xcassets"
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/Images/LaunchImage_1024x768.png"
)

This is how the final CMake configuration for your example app should look like in order to run on Windows, macOS, Linux, Android, iOS, and the Web:

sl_add_app(
    TARGET "app-example"
    PLATFORMS "GLFW;EMSCRIPTEN;ANDROID;IOS"
    SOURCES "AppExample.cpp"

    ANDROID_APP_DIR "${SL_PROJECT_ROOT}/apps/source/platforms/android/example_project/app"

    IOS_INFO_PLIST "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/plist.in"
    IOS_DISPLAY_NAME "app-example"
    IOS_COPYRIGHT "Copyright [Your Name]"
    IOS_ICON_NAME "AppIcon"

    IOS_RESOURCES
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/Base.lproj/ViewController_iPad.xib"
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/Base.lproj/ViewController_iPhone.xib"
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/LaunchScreenSLDemo.storyboard"
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/Images/Images.xcassets"
    "${SL_PROJECT_ROOT}/apps/source/platforms/ios/example_project/Images/LaunchImage_1024x768.png"
)

To actually build and run the app on your mobile device, follow the build guides for Android and iOS.

Next Steps

Now that you have created a basic example app, you can take a look at the online documentation or at the demo application to learn more.