/*
 * This file is part of Afterwarp Framework (http://afterwarp.io).
 * Copyright (c) 2015 - 2025 Dr. Yuriy Kotsarenko. All rights reserved.
 *
 * This example source code is licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

// This example illustrates the following features:
//  * Object hierarchial structure, where smaller objects are parented to bigger objects. Therefore, when
//    a bigger object is moved, it also moves all its children with it.
//  * Bone joint connections for rotating and moving portions of robotic arm, and forklift.
//  * Movement waypoints for moving boxes along two pre-defined paths on the bands.
//  * Object selection, which uses object's bounding boxes and accompanying voxel representation to
//    determine accurately what object is being pointed at, including approximate 3D location that mouse
//    is pointing on the object's surface.
//  * Object manipulation, where objects can be moved and/or rotated with mouse and keyboard.
//  * "Auto-draw" functionality for rendering objects using automatic instancing, while applying correctly
//    their materials and colors.
//  * Selection highlight effect, where a colored glow outline can be applied to one or more objects.
//  * Signed Distance Field (SDF) text rendering in 3D: a technique to draw good looking text in 3D with
//    any zoom factor and an optional outline.
//  * Volumetric text in 3D, which is represented as a selectable object with the corresponding mesh.
//  * HDR lighting, ambient occlusion and bloom effect.
//  * Third-person view camera with location-based zoom (zooms in or out where the mouse is pointing at).

#ifdef _WIN32
  #include "Resource.h"
  #include <SDKDDKVer.h>

  #define WIN32_LEAN_AND_MEAN
  #include <windows.h>
  // Remove conflicting Windows macros.
  #undef KEY_EXECUTE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "Afterwarp.h"
#include "Afterwarp.Vectors.h"

#define AFTERWARP_VECTORS_IMPLEMENTATION
#include "Afterwarp.Vectors.h"

// Global variables.

// Display characteristics.
static Point displaySize = {1280, 720};
static float displayScale = 1.0f;

// Base Afterwarp interfaces.
static Application* application = NULL;
static Device* device = NULL;
static SwapChain* swapChain = NULL;
static Canvas* canvas = NULL;
static TextRenderer* textRenderer = NULL;
static SelectionHighlight* selectionHighlight = NULL;
static Timer* timer = NULL;

static SceneMeshes* meshes = NULL;
static ObjectModels* objects = NULL;
static SceneLights* lights = NULL;
static ObjectMaterials* materials = NULL;

static TextureCabinet* textureCabinet = NULL;
static ShadowCastingAtlas* shadowCastingAtlas = NULL;
static ObjectModelView* shadowCasterView = NULL;

static Scene* sceneDepthNormals = NULL;
static Scene* sceneModeling = NULL;

static ObjectModelView* viewport = NULL;
static ActorCamera* camera = NULL;
static Matrix projection = {0};

static Vector rayOrigin = {0};
static Vector rayDirection = {0};
static Vector mousePos = {0};

static ObjectModel* selected = NULL;
static Vector selectionPoint = {0};

static Vector mousePoint = {0};
static Vector mouseDragAxis = {0};
static uint8_t mouseRotation = 0;

static bool keyShift = false, keyCtrl = false, keyAlt = false;
static bool rotatingView = false;
static bool applicationActive = true;
static uint64_t timerTicks = 0;
static uint8_t directionPlaneToggle = 0;
static float directionPlaneAngle = 0.0f;
static int32_t drawCalls;
static uint64_t prngBoxes;

static VertexElement const vertexElements[] = {
  {"vertexPosition", ELEMENT_FORMAT_FLOAT, 3, 0, 0},
  {"vertexNormal", ELEMENT_FORMAT_FLOAT, 3, 0, 0},
  {"vertexColor", ELEMENT_FORMAT_UNSIGNED_BYTE, 4, 0 | VERTEX_CHANNEL_NORMALIZED, 0}};

static int32_t const vertexElementsCount = sizeof(vertexElements) / sizeof(VertexElement);
static Vector const lightPosition = {.x = 300.f, .y = 800.0f, .z = -300.0f};

typedef struct ObjectMovementTracking
{
  float distance;
  uint8_t group;
  float angle;
} ObjectMovementTracking;

// Forward declarations.

static void renderObjectHighlight(Scene* scene, ObjectModel* selected);
static void renderTextLabels(Matrix const* viewProjection);
static void updateMovement(void);
static float randomizeBoxAngle(void);
static void createObjects(void);
static bool createAndLoadMeshes(void);
static void createLightsAndMaterials(void);
static void releaseObjectMovementTracking(void);
static void outputError(char const* message);

// Implementation.

LibraryBool applicationCreate(Application* application, void* user)
{
  device = DeviceCreate(application, DEVICE_TECHNOLOGY_UNKNOWN, 0);
  if (!device)
  {
    outputError("Could not create graphics device.");
    return false;
  }
  swapChain = SwapChainCreate(device, ApplicationGetWindowHandle(application), &displaySize,
    PIXEL_FORMAT_RGBA8, PIXEL_FORMAT_UNKNOWN, 0, true);
  if (!swapChain)
  {
    outputError("Could not create rendering swap-chain.");
    return false;
  }
#ifndef _WIN32
  {
    char const* fileNames[] = {"../../../../Media/afterwarp.ico"};

    if (!ApplicationSetIconsFromFiles(application, fileNames, sizeof(fileNames) / sizeof(fileNames[0])))
    {
      outputError("Could not load application icon.");
      return false;
    }
  }
#endif
  canvas = CanvasCreate(device);
  if (!canvas)
  {
    outputError("Could not create rendering canvas.");
    return false;
  }
  textRenderer = TextRendererCreate(canvas, &(Point){512, 512}, PIXEL_FORMAT_LA8, false);
  if (!textRenderer)
  {
    outputError("Could not create text rendering module.");
    return false;
  }
  selectionHighlight = SelectionHighlightCreate(device, &displaySize, PIXEL_FORMAT_D32F, 8, false);
  if (!selectionHighlight)
  {
    outputError("Could not create selection highlight module.");
    return false;
  }
  meshes = SceneMeshesCreate(device);
  if (!meshes)
  {
    outputError("Could not create meshes container.");
    return false;
  }
  objects = ObjectModelsCreate(meshes);
  if (!objects)
  {
    outputError("Could not create objects container.");
    return false;
  }
  lights = SceneLightsCreate(device);
  if (!lights)
  {
    outputError("Could not create 3D scene lights module.");
    return false;
  }
  materials = ObjectMaterialsCreate();
  if (!materials)
  {
    outputError("Could not create object materials container.");
    return false;
  }

#if (defined(_MSC_VER) && defined(_M_ARM64)) || defined(__aarch64__)
  // Optimize for WoA devices
  if (ApplicationGetWindowScale(application) >= 2.0f)
    textureCabinet = TextureCabinetCreate(device, &displaySize, TEXTURE_FIDELITY_LOW, 0,
      PIXEL_FORMAT_UNKNOWN);
  else
    textureCabinet = TextureCabinetCreate(device, &displaySize, TEXTURE_FIDELITY_MEDIUM, 4,
      PIXEL_FORMAT_UNKNOWN);
#else
  textureCabinet = TextureCabinetCreate(device, &displaySize, TEXTURE_FIDELITY_HIGH, 8,
    PIXEL_FORMAT_UNKNOWN);
#endif

  if (!textureCabinet)
  {
    outputError("Could not create texture cabinet container.");
    return false;
  }
  shadowCastingAtlas = ShadowCastingAtlasCreate(device, &(Point){256, 256}, TECHNIQUE_SHADOWS_EVSM, 8, 1);
  if (!shadowCastingAtlas)
  {
    outputError("Could not create shadow casting atlas container.");
    return false;
  }
  sceneDepthNormals = SceneCreateDepthsNormals(device);
  if (!sceneDepthNormals || !SceneSetVertexElements(sceneDepthNormals, 0, vertexElements,
    vertexElementsCount))
  {
    outputError("Could not create depths/normals rendering scene.");
    return false;
  }
  sceneModeling = SceneCreateModeling(device);
  if (!sceneModeling || !SceneSetVertexElements(sceneModeling, 0, vertexElements,
    sizeof(vertexElements) / sizeof(VertexElement)))
  {
    outputError("Could not create 3D rendering scene.");
    return false;
  }
  viewport = ObjectModelsCreateView(objects);
  if (!viewport)
  {
    outputError("Could not create main viewport.");
    return false;
  }
  { // Create and configure a shadow caster.
    bool const depthClipNegative = DeviceGetBehavior(device) & DEVICE_BEHAVIOR_DEPTH_CLIP_NEGATIVE;

    ShadowCaster* const caster = ShadowCastingAtlasAdd(shadowCastingAtlas, &(Point){256, 256}, true);
    if (!caster)
    {
      outputError("Could not create a shadow caster.");
      return false;
    }
    shadowCasterView = ObjectModelsCreateView(objects);
    if (!shadowCasterView)
    {
      outputError("Could not create a view for shadow caster.");
      return false;
    }
    Matrix const view = matrixLookAt(lightPosition, vectorZero, vectorAxisY);
    Matrix const projection = matrixPerspectiveFOVY((float)M_PI * 0.3f, 1.0f, 200.0f, 1400.0f,
      depthClipNegative);

    ObjectModelViewSetView(shadowCasterView, &view);
    ObjectModelViewSetProjection(shadowCasterView, &projection, depthClipNegative);
  }
  camera = ActorCameraCreate(&(Vector){-80.0f, 0.0f, 0.0f}, &(Vector){0.906f, -0.0192f, 0.0f}, 700.0f);
  if (!camera)
  {
    outputError("Could not create actor camera.");
    return false;
  }
  // Limit camera position and rotation to reasonable values.
  ActorCameraSetConstraints(camera, &(CameraConstraints){
    .positionMin.x = -400.0f, .positionMax.x = 400.0f,
    .positionMin.y = -400.0f,
    .positionMin.z = -400.0f, .positionMax.z = 400.0f,
    .positionMin.w =  100.0f, .positionMax.w = 1500.0f,
    .rotationMin.x = (float)M_PI * 0.01f,
    .rotationMax.x = (float)M_PI * 0.5f});

  timer = TimerCreate();

  if (!createAndLoadMeshes())
  {
    outputError("Could not create and/or load 3D meshes.");
    return false;
  }
  createObjects();
  createLightsAndMaterials();
  return true;
}

void applicationDestroy(Application* application, void* user)
{
  TimerDestroy(timer);
  ActorCameraDestroy(camera);
  SceneDestroy(sceneModeling);
  SceneDestroy(sceneDepthNormals);
  ShadowCastingAtlasDestroy(shadowCastingAtlas);
  TextureCabinetDestroy(textureCabinet);
  ObjectMaterialsDestroy(materials);
  SceneLightsDestroy(lights);
  releaseObjectMovementTracking();
  ObjectModelsDestroy(objects);
  SceneMeshesDestroy(meshes);
  SelectionHighlightDestroy(selectionHighlight);
  TextRendererDestroy(textRenderer);
  CanvasDestroy(canvas);
  SwapChainDestroy(swapChain);
  DeviceDestroy(device);
}

void applicationResize(Application* application, void* user)
{
  Rect clientRect = {0};
  ApplicationGetClientRect(application, &clientRect);

  displaySize = rectGetSize(clientRect);

  SwapChainResize(swapChain, &displaySize);
  TextureCabinetSetSize(textureCabinet, &displaySize);
  SelectionHighlightSetSize(selectionHighlight, &displaySize);

  ApplicationInvalidate(application);
  TimerReset(timer);
}

void applicationStatus(Application* application, ApplicationEvent event, void* user)
{
  switch (event)
  {
  case APPLICATION_EVENT_RESTORE:
    applicationActive = true;
    break;

  case APPLICATION_EVENT_MINIMIZE:
    applicationActive = false;
    break;

  case APPLICATION_EVENT_APPEARANCE:
    displayScale = ApplicationGetWindowScale(application);
    break;

  default:
    break;
  }
  TimerReset(timer);
}

LibraryBool applicationIdle(Application* application, void* user)
{
  if (applicationActive)
  {
    ApplicationInvalidate(application);
    return false;
  }
  else
  { // Move objects in the background.
    updateMovement();
    return true;
  }
}

void applicationRender(Application* application, void* user)
{
  if (applicationActive)
    updateMovement();

  bool const depthClipNegative = DeviceGetBehavior(device) & DEVICE_BEHAVIOR_DEPTH_CLIP_NEGATIVE;
  drawCalls = 0;

  // Configure view and projection matrices.
  Matrix view = {0};
  ActorCameraGetView(camera, &view);

  projection = matrixPerspectiveFOVY((float)M_PI * 0.25f, (float)displaySize.x / displaySize.y,
    10.0f, 2000.0, depthClipNegative);

  ObjectModelViewSetView(viewport, &view);
  ObjectModelViewSetProjection(viewport, &projection, depthClipNegative);

  // Calculate mouse pointing ray.
  PointF const targetSize = pointF(displaySize);
  Matrix const viewInverse = matrixInverse(view);

  RayCreate(&rayOrigin, &rayDirection, (PointF*)&mousePos, &targetSize, &viewInverse, &projection);

  // Perform object selection.
  if (ObjectModelViewUpdate(viewport) && mouseRotation == 0 && vectorEmpty(mouseDragAxis))
  {
    if (!rotatingView)
    {
      float distance = 0.0f;

      selected = ObjectModelViewSelect(viewport, &rayOrigin, &rayDirection, &distance);
      if (selected)
        selectionPoint = vectorAdd(rayOrigin, vectorRescale(rayDirection, distance));
    }
    else
      selected = NULL;
  }

  { // Update texture cabinet's attributes.
    TextureCabinetAttributes attributes =
      TEXTURE_CABINET_ATTRIBUTE_BLOOM |
    #if (!defined(_MSC_VER) || !defined(_M_ARM64)) && !defined(__aarch64__)
      TEXTURE_CABINET_ATTRIBUTE_BLOOM_CUBIC |
    #endif
      TEXTURE_CABINET_ATTRIBUTE_AMBIENT_OCCLUSION;

    if (displayScale >= 2.0f)
      attributes |=
        TEXTURE_CABINET_ATTRIBUTE_BLOOM_DOWNSCALE |
        TEXTURE_CABINET_ATTRIBUTE_AMBIENT_OCCLUSION_DOWNSCALE;

    TextureCabinetSetAttributes(textureCabinet, attributes);

    // Adjust ambient occlusion parameters.
    AmbientOcclusionParameters parameters;
    TextureCabinetGetAmbientOcclusionParameters(textureCabinet, &parameters);

    parameters.kernelBias = 0.075f;
    parameters.strength = 1.0f;

    TextureCabinetSetAmbientOcclusionParameters(textureCabinet, &parameters);
  }

  // Perform light assignment for 3D scene.
  SceneLightsSetViewSize(lights, &displaySize);

  // Only one light is used for the entire scene, so only minimal light culling is required.
  SceneLightsSetCullingMode(lights, CLUSTERS_CULLING_MODE_PERFORMANCE);
  SceneLightsSetClusterSize(lights, 512);
  SceneLightsSetDepthSlices(lights, 1);

  SceneLightsExecute(lights, &view, &projection);

  // Render shadow map.
  ShadowCaster* const shadowCaster = ShadowCastingAtlasGetCaster(shadowCastingAtlas, 0);

  if (shadowCaster && shadowCasterView)
  {
    Matrix viewProjection;
    ObjectModelViewGetViewProjection(shadowCasterView, &viewProjection);
    ShadowCasterSetViewProjection(shadowCaster, &viewProjection);

    if (ObjectModelViewUpdate(shadowCasterView) && ShadowCasterClear(shadowCaster) &&
      ShadowCasterBegin(shadowCaster))
    {
      Matrix view, projection;
      ObjectModelViewGetView(shadowCasterView, &view);
      ObjectModelViewGetProjection(shadowCasterView, &projection, NULL);

      SceneSetAttributes(sceneDepthNormals, 0);
      SceneSetView(sceneDepthNormals, &view);
      SceneSetProjection(sceneDepthNormals, &projection);

      if (SceneBegin(sceneDepthNormals))
      {
        // Use "auto-draw" helper to automatically draw all objects visible within the view frustum of
        // shadow caster.
        ObjectModelViewAutoDraw(shadowCasterView, sceneDepthNormals, NULL, 0, &drawCalls);

        SceneEnd(sceneDepthNormals);
      }
      ShadowCasterEnd(shadowCaster);
      ShadowCasterFilter(shadowCaster);
    }
  }

  // Prepare scene's attributes.
  SceneAttributes const attributes =
    SCENE_ATTRIBUTE_NORMALS |
    SCENE_ATTRIBUTE_COLORING |
    SCENE_ATTRIBUTE_SHADOWS_CUBIC |
    SCENE_ATTRIBUTE_SINGLE_PASS;

  // Render main scene.
  FloatColor const clearColor = floatColor(0xFFE0E0E0u);
  TextureCabinetClear(textureCabinet, TEXTURE_CABINET_PASS_COLOR, &clearColor);

  if (TextureCabinetBegin(textureCabinet, TEXTURE_CABINET_PASS_COLOR))
  {
    SceneSetAttributes(sceneModeling, attributes);
    SceneSetView(sceneModeling, &view);
    SceneSetProjection(sceneModeling, &projection);

    SceneSetLights(sceneModeling, lights);
    SceneSetShadowCastingAtlas(sceneModeling, shadowCastingAtlas);
    SceneSetTextureCabinet(sceneModeling, textureCabinet);

    if (ScenePrepare(sceneModeling) && SceneBegin(sceneModeling))
    {
      // Use "auto-draw" helper to automatically draw all objects visible within the camera's view frustum.
      ObjectModelViewAutoDraw(viewport, sceneModeling, materials, 0, &drawCalls);

      SceneEnd(sceneModeling);
    }
    TextureCabinetEnd(textureCabinet);
  }
  TextureCabinetFilter(textureCabinet, TEXTURE_CABINET_FILTER_TYPE_OCCLUSION, &projection);

  // Render text labels.
  if (TextureCabinetBegin(textureCabinet, TEXTURE_CABINET_PASS_COLOR))
  {
    if (CanvasBegin(canvas))
    {
      Matrix const viewProjection = matrixMultiply(view, projection);

      renderTextLabels(&viewProjection);
      CanvasEnd(canvas);
    }
    TextureCabinetEnd(textureCabinet);
  }
  TextureCabinetFilter(textureCabinet, TEXTURE_CABINET_FILTER_TYPE_BLOOM, NULL);

  // Render selection highlight effect.
  if (selected)
  {
    SelectionHighlightClear(selectionHighlight);

    if (SelectionHighlightBegin(selectionHighlight))
    {
      SceneSetAttributes(sceneModeling, SCENE_ATTRIBUTE_NORMALS);
      SceneSetView(sceneModeling, &view);
      SceneSetProjection(sceneModeling, &projection);

      SceneSetLights(sceneModeling, NULL);
      SceneSetShadowCastingAtlas(sceneModeling, NULL);
      SceneSetTextureCabinet(sceneModeling, NULL);

      if (ScenePrepare(sceneModeling) && SceneBegin(sceneModeling))
      {
        renderObjectHighlight(sceneModeling, selected);
        SceneEnd(sceneModeling);
      }
      SelectionHighlightEnd(selectionHighlight);
    }
    SelectionHighlightFilter(selectionHighlight);
  }

  // Render on the main form.
  if (SwapChainBegin(swapChain))
  {
    FloatColor const clearColor = floatColor(0xFF404040u);
    DeviceClear(device, DEVICE_CLEAR_LAYER_COLOR, &clearColor, 1.0f, 0);

    TextureCabinetPresent(textureCabinet);

    if (CanvasBegin(canvas))
    {
      CanvasSetAttributes(canvas, 0);
      CanvasSetContextState(canvas, CANVAS_CONTEXT_STATE_FLAT_SCENE);
      CanvasSetSamplerState(canvas, &(CanvasSamplerState){TEXTURE_FILTER_NEAREST, TEXTURE_FILTER_NEAREST});

      if (selected)
      { // Render selection highlight on top of the scene.
        Quad const vertices = makeQuad(0.0f, 0.0f, displaySize.x, displaySize.y);
        Quad texCoords;
        SelectionHighlightGetTextureCoordinates(selectionHighlight, &texCoords);

        CanvasTexturedQuad(canvas, SelectionHighlightGetTexture(selectionHighlight,
          SELECTION_HIGHLIGHT_TEXTURE_HIGHLIGHT), &vertices, &texCoords, NULL, BLENDING_EFFECT_NORMAL);

        CanvasTexturedQuad(canvas, SelectionHighlightGetTexture(selectionHighlight,
          SELECTION_HIGHLIGHT_TEXTURE_HIGHLIGHT_MASK), &vertices, &texCoords, &COLOR_RECT(0x80FFFFFFu),
          BLENDING_EFFECT_NORMAL);
      }
      CanvasSetContextState(canvas, CANVAS_CONTEXT_STATE_FLAT_TEXT);
      { // Draw text hints.
        FontParameters parameters = {
          .family = "Segoe UI", .size = 14.0f * displayScale, .weight = FONT_WEIGHT_SEMI_BOLD,
          .stretch = FONT_STRETCH_NORMAL, .slant = FONT_SLANT_NONE, .effect = fontEffectDefault};
        parameters.effect.fillOpacity = 1.0f;
        parameters.effect.fillBrightness = 0.0f;
        parameters.effect.borderBrightness = 1.0f;
        parameters.effect.borderOpacity = 0.75f;
        parameters.effect.borderThickness = 1.0f;
        parameters.effect.borderType = FONT_BORDER_SEMI_HEAVY;
        parameters.effect.shadowBrightness = 0.85f;
        parameters.effect.shadowOpacity = 0.4f;
        parameters.effect.shadowDistance = (PointF){2.0f, 2.0f};
        parameters.effect.shadowSmoothness = 2;
        TextRendererSetFontParameters(textRenderer, &parameters);

        TextRendererDrawAligned(textRenderer, &(PointF){displaySize.x - 8.0f * displayScale,
          4.0f * displayScale},
          "Shift/Ctrl + Left/Right mouse buttons: move/rotate objects.",
          &(ColorPair){0xFFA5E7B8u, 0xFF77FFCEu}, TEXT_ALIGNMENT_END, TEXT_ALIGNMENT_START, 1.0f, true, NULL);

        TextRendererDrawAligned(textRenderer, &(PointF){displaySize.x - 8.0f * displayScale,
          displaySize.y - 4.0f * displayScale},
          "Left mouse button: rotate scene, right button: move scene, wheel: zoom.",
          &(ColorPair){0xFF00E0FFu, 0xFF0040FFu}, TEXT_ALIGNMENT_END, TEXT_ALIGNMENT_END, 1.0f, true, NULL);
      }
      { // Draw technology string.
        FontParameters parameters = {
          .family = "Segoe UI", .size = 14.0f * displayScale, .weight = FONT_WEIGHT_SEMI_BOLD,
          .stretch = FONT_STRETCH_NORMAL, .slant = FONT_SLANT_NONE, .effect = fontEffectDefault};
        parameters.effect.borderType = FONT_BORDER_SEMI_HEAVY;
        parameters.effect.borderBrightness = 1.0f;
        parameters.effect.fillBrightness = 0.0f;
        parameters.effect.shadowBrightness = 0.75f;
        parameters.effect.shadowOpacity = 1.0f;
        TextRendererSetFontParameters(textRenderer, &parameters);

        char const* const textDirect3D = "Direct3D (%d.%d), features: %d.%d";
        char const* const textOpenGL = "OpenGL (%d.%d+), features: %d.%d";

        char const* const formatText = DeviceGetTechnology(device) == DEVICE_TECHNOLOGY_DIRECT3D ?
          textDirect3D : textOpenGL;

        char text[128];

        snprintf(text, sizeof(text), formatText,
          DeviceGetTechVersion(device) >> 8, (DeviceGetTechVersion(device) >> 4) & 0xF,
          DeviceGetTechFeatureVersion(device) >> 8, (DeviceGetTechFeatureVersion(device) >> 4) & 0xF);

        TextRendererDrawAligned(textRenderer, &(PointF){8.0f * displayScale, 4.0f * displayScale}, text,
          &COLOR_PAIR(0xA0FFFFFFu), TEXT_ALIGNMENT_START, TEXT_ALIGNMENT_START, 1.0f, true, NULL);

        snprintf(text, sizeof(text), "Frame rate: %1.0f, draw calls: %d", TimerGetFrameRate(timer),
          drawCalls);

        TextRendererDrawAligned(textRenderer, &(PointF){8.0f * displayScale, displaySize.y -
          4.0f * displayScale}, text, &(ColorPair){0xA0FFE0E0u, 0xA0FF80A0u}, TEXT_ALIGNMENT_START,
          TEXT_ALIGNMENT_END, 1.0f, true, NULL);
      }
      CanvasEnd(canvas);
    }
    SwapChainEnd(swapChain);
  }
}

void applicationMouse(Application* application, MouseEvent event, MouseButton button, Point const* position,
  void* user)
{
  PointF const screenSize = pointF(displaySize);
  Vector const prevMousePos = mousePos;

  mousePos = (Vector){.x = position->x, .y = position->y, .z = 0.0f};
  bool const positionChanged = !vectorEquals(prevMousePos, mousePos);

  switch (event)
  {
  case MOUSE_EVENT_MOUSE_DOWN:
    if (keyShift && selected)
    { // Object movement.
      if (button == MOUSE_BUTTON_LEFT)
        mouseDragAxis = vectorAxisY; // Move horizontally.
      else if (fabsf(vectorDot(rayDirection, vectorAxisX)) > fabsf(vectorDot(rayDirection, vectorAxisZ)))
        mouseDragAxis = vectorAxisX;
      else
        mouseDragAxis = vectorAxisZ;

      if (RayIntersectPlane(&rayOrigin, &rayDirection, &selectionPoint, &mouseDragAxis, &mousePoint, NULL))
        selectionPoint = vectorZero;
      else
        mouseDragAxis = vectorZero;
    }
    else if (keyCtrl && selected)
    {
      selectionPoint = vectorZero;

      if (button == MOUSE_BUTTON_LEFT)
        mouseRotation = 2;
      else if (fabsf(vectorDot(rayDirection, vectorAxisX)) > fabsf(vectorDot(rayDirection, vectorAxisZ)))
        mouseRotation = 1;
      else
        mouseRotation = 3;
    }
    else
    { // Camera movement.
      if (button == MOUSE_BUTTON_LEFT)
        ActorCameraCommand(camera, CAMERA_COMMAND_ROTATION, &mousePos, &screenSize, NULL, NULL);
      else if (button == MOUSE_BUTTON_RIGHT)
        ActorCameraCommand(camera, CAMERA_COMMAND_DRAGGING, &mousePos, &screenSize, NULL, NULL);

      rotatingView = button == MOUSE_BUTTON_LEFT || button == MOUSE_BUTTON_RIGHT;
    }
    break;

  case MOUSE_EVENT_MOUSE_MOVE:
    if (!vectorEmpty(mouseDragAxis))
    {
      Vector movePoint;

      if (RayIntersectPlane(&rayOrigin, &rayDirection, &mousePoint, &mouseDragAxis, &movePoint, NULL))
      { // Move selected object by mouse-pointed displacement.
        Vector offset = vectorSubtract(movePoint, mousePoint);

        // Adjust offset to ensure movement respective viewer's direction.
        ObjectModel* const parent = ObjectModelGetParent(selected);
        if (parent)
        {
          Matrix transform;
          ObjectModelGetTransform(parent, MODEL_TRANSFORM_GLOBAL, &transform);
          offset = vectorTransform(offset, 0.0f, matrixInverse(transform));
        }

        // Apply the displacement.
        Matrix transform;

        ObjectModelGetTransform(selected, MODEL_TRANSFORM_LOCAL, &transform);
        transform = matrixMultiply(transform, matrixTranslate(offset));

        ObjectModelSetTransform(selected, MODEL_TRANSFORM_LOCAL, &transform);
        mousePoint = movePoint; // Reset mouse movement.

        ApplicationInvalidate(application);
      }
    }
    else if (mouseRotation)
    {
      float const angle = (prevMousePos.x - mousePos.x) * 2.5f / screenSize.x;
      Matrix transform, rotation;

      switch (mouseRotation)
      {
      case 1:
        rotation = matrixRotateX(angle);
        break;

      case 3:
        rotation = matrixRotateZ(angle);
        break;

      default:
        rotation = matrixRotateY(angle);
        break;
      }
      ObjectModelGetTransform(selected, MODEL_TRANSFORM_LOCAL, &transform);
      transform = matrixMultiply(rotation, transform);
      ObjectModelSetTransform(selected, MODEL_TRANSFORM_LOCAL, &transform);

      ApplicationInvalidate(application);
    }
    else if (ActorCameraCommand(camera, CAMERA_COMMAND_UPDATE, &mousePos, &screenSize, NULL, NULL))
      ApplicationInvalidate(application);
    break;

  case MOUSE_EVENT_MOUSE_UP:
    ActorCameraCommand(camera, CAMERA_COMMAND_STOP, &mousePos, &screenSize, NULL, NULL);
    mouseDragAxis = vectorZero;
    mouseRotation = 0;
    rotatingView = false;
    ApplicationInvalidate(application);
    break;

  case MOUSE_EVENT_WHEEL_UP:
    ActorCameraZoom(camera, (PointF*)&mousePos, &screenSize, &projection, -20.0f);
    ApplicationInvalidate(application);
    break;

  case MOUSE_EVENT_WHEEL_DOWN:
    ActorCameraZoom(camera, (PointF*)&mousePos, &screenSize, &projection, 20.0f);
    ApplicationInvalidate(application);
    break;
  }
  if (positionChanged)
    ApplicationInvalidate(application); // Update and show object selection.
}

void applicationKey(Application* application, KeyEvent event, int32_t virtualKey, uint16_t keyCode,
  void* user)
{
  Key const key = ApplicationTranslateVirtualKey(application, virtualKey);

  if (key == KEY_SHIFT_LEFT || key == KEY_SHIFT_RIGHT)
  {
    keyShift = event == KEY_EVENT_PRESSED;
    ApplicationInvalidate(application);
  }
  else if (key == KEY_CTRL_LEFT || key == KEY_CTRL_RIGHT)
  {
    keyCtrl = event == KEY_EVENT_PRESSED;
    ApplicationInvalidate(application);
  }
  else if (key == KEY_ALT_LEFT || key == KEY_ALT_RIGHT)
  {
    keyAlt = event == KEY_EVENT_PRESSED;
    ApplicationInvalidate(application);
  }
  else if (key == KEY_ESCAPE && event == KEY_EVENT_PRESSED)
    ApplicationTerminate(application);
  else if (key == KEY_RETURN && event == KEY_EVENT_PRESSED && keyAlt)
  {
    ApplicationWindowState state = ApplicationGetWindowState(application);

    if (state != APPLICATION_WINDOW_STATE_FULLSCREEN)
      state = APPLICATION_WINDOW_STATE_FULLSCREEN;
    else
      state = APPLICATION_WINDOW_STATE_WINDOWED;

    ApplicationSetWindowState(application, state);
  }
}

void renderObjectHighlight(Scene* scene, ObjectModel* selected)
{
  Matrix transform = matrixIdentity;
  ObjectModelGetTransform(selected, MODEL_TRANSFORM_GLOBAL_MODEL, &transform);
  SceneSetWorld(scene, &transform);

  char meshName[128] = {0};
  int32_t meshNameLength = sizeof(meshName);

  ObjectModelGetMeshName(selected, meshName, &meshNameLength);

  uint32_t color = 0xFFFFFFFFu;

  if (!strcmp(meshName, "Box"))
    color = 0xFF00FF00u;
  else if (strstr(meshName, "RoboticArm"))
    color = 0xFF0080FFu;
  else if (strstr(meshName, "Forklift"))
    color = 0xFF10E0A0u;
  else if (!strcmp(meshName, "Text3D"))
    color = 0xFFFF0000u;
  else if (!strcmp(meshName, "Pallet"))
    color = 0xFF4080A0u;
  else if (!strcmp(meshName, "MultipleBands") || !strcmp(meshName, "DirectionPlane"))
    color = 0xFF0000FFu;
  else if (!strcmp(meshName, "Fence") || !strcmp(meshName, "FenceThick"))
    color = 0xFFFF4040u;

  FloatColor highlight = floatColor(color);

  SceneMeshAutoDrawSliced(ObjectModelGetMesh(selected), scene, NULL, &highlight,
    PRIMITIVE_TOPOLOGY_TRIANGLES, 0, AUTO_DRAW_OPTION_OBJECT_HIGHLIGHTED, &drawCalls);
}

void renderTextLabels(Matrix const* viewProjection)
{
  RenderingState renderState;
  DeviceGetRenderingState(device, &renderState);

  // Configure rendering state to avoid depth fighting and disable depth writes.
  renderState.states &= ~DEVICE_STATE_DEPTH_WRITE;
  renderState.depthBias = 5.0f;
  renderState.slopeDepthBias = 5.0f;
  DeviceSetRenderingState(device, &renderState);

  // Configure canvas for 3D projected SDF rendering.
  CanvasSetSignedDistanceField(canvas, &(SignedDistanceField){
    SUPER_SAMPLE_SDF_SUPER_SAMPLE_16X, 1.0f, {0.0f, 1.0f, 0.0f}, 0.15f, 0.45f});

  CanvasSetAttributes(canvas, CANVAS_ATTRIBUTE_PROJECTED | CANVAS_ATTRIBUTE_SDF | CANVAS_ATTRIBUTE_CUBIC);
  CanvasSetContextState(canvas, CANVAS_CONTEXT_STATE_PROJECTED);
  CanvasSetSamplerState(canvas, &(CanvasSamplerState){TEXTURE_FILTER_LINEAR, TEXTURE_FILTER_LINEAR});

  CanvasSetViewProjection(canvas, viewProjection);

  { // Prepare SDF font.
    FontParameters parameters = {
      .family = "Cambria", .size = 32.0f, .weight = FONT_WEIGHT_SEMI_BOLD,
      .stretch = FONT_STRETCH_NORMAL, .slant = FONT_SLANT_NONE, .effect = fontEffectDefault};
    parameters.effect.signedFieldDistance = 1.0f;
    TextRendererSetFontParameters(textRenderer, &parameters);
  }

  // Prepare SDF font modifiers.
  TextRenderModifiers modifiers = {0};
  modifiers.scale = 0.4f;

  { // Label describing robotic arm.
    ObjectModel* const roboticArm = ObjectModelsGetObjectByName(objects, "RoboticArm:Piece0");

    Matrix transform;
    ObjectModelGetTransform(roboticArm, MODEL_TRANSFORM_GLOBAL, &transform);

    transform = matrixMultiply(matrixTranslate((Vector){40.0f, 0.0f, 0.0f}), transform);
    transform = matrixMultiply(matrixRotateY(-(float)M_PI * 0.5f), transform);
    transform = matrixMultiply(matrixRotateX((float)M_PI * 0.5f), transform);

    CanvasSetWorld(canvas, &transform);

    TextRendererDrawAligned(textRenderer, NULL, "Robotic Arm", &COLOR_PAIR(0xFF000000u),
      TEXT_ALIGNMENT_MIDDLE, TEXT_ALIGNMENT_MIDDLE, 1.0, false, &modifiers);
  }
  { // Labels describing the bands.
    ObjectModel* const multipleBands = ObjectModelsGetObjectByName(objects, "MultipleBands");

    Matrix transform;
    ObjectModelGetTransform(multipleBands, MODEL_TRANSFORM_GLOBAL, &transform);

    transform = matrixMultiply(matrixTranslate((Vector){70.0f, 0.0f, 20.0f}), transform);
    transform = matrixMultiply(matrixRotateX((float)M_PI * 0.5f), transform);

    CanvasSetWorld(canvas, &transform);

    TextRendererDrawAligned(textRenderer, NULL, "Band for outcoming packages", &COLOR_PAIR(0xFF000000u),
      TEXT_ALIGNMENT_MIDDLE, TEXT_ALIGNMENT_MIDDLE, 1.0, false, &modifiers);

    // Band 2 label
    ObjectModelGetTransform(multipleBands, MODEL_TRANSFORM_GLOBAL, &transform);

    transform = matrixMultiply(matrixTranslate((Vector){-200.0f, 0.0f, -140.0f}), transform);
    transform = matrixMultiply(matrixRotateY(-(float)M_PI * 0.5f), transform);
    transform = matrixMultiply(matrixRotateX((float)M_PI * 0.5f), transform);

    CanvasSetWorld(canvas, &transform);

    TextRendererDrawAligned(textRenderer, NULL, "Bands for incoming packages", &COLOR_PAIR(0xFF000000u),
      TEXT_ALIGNMENT_MIDDLE, TEXT_ALIGNMENT_MIDDLE, 1.0, false, &modifiers);
  }
}

static void objectMove(char const* objectName, Matrix transform)
{
  ObjectModelSetTransform(ObjectModelsGetObjectByName(objects, objectName), MODEL_TRANSFORM_LOCAL,
    &transform);
}

void updateMovement(void)
{
  TimerUpdate(timer);

  // Retrieve current latency and update timer ticks.
  double latency = TimerGetLatency(timer) / 1000000.0;
  timerTicks += TimerExtractTokens(timer);

  // Move boxes over the bands.
  for (ObjectModel* object = ObjectModelsGetFirst(objects); object; object = ObjectModelGetNext(object))
  {
    ObjectMovementTracking* const tracking = ObjectModelGetPayload(object);
    ObjectModel* const parent = ObjectModelGetParent(object);

    if (tracking && parent)
    {
      float const speed = 70.0f;
      float const distance = ObjectModelGetWaypointDistance(parent, tracking->group);

      tracking->distance += latency * speed;
      if (tracking->distance >= distance)
      {
        tracking->distance = 0.0f;
        tracking->group ^= 1;
        tracking->angle = randomizeBoxAngle();

        directionPlaneToggle = tracking->group;
        // Note: distance is different between tracking groups, but object is at zero size anyway.
      }

      float scale = 1.0f;
      if (tracking->distance < 10.0f)
        scale = tracking->distance / 10.0f;
      else if (tracking->distance > distance - 10.0f)
        scale = (distance - tracking->distance) / 10.0f;

      Matrix transform = {0};
      ObjectModelGetLatchWaypointCoupleMatrix(parent, tracking->group, tracking->distance, &transform);

      transform = matrixMultiply(matrixMultiply(matrixScale((Vector){scale, scale, scale}),
        matrixRotateY(tracking->angle)), transform);

      ObjectModelSetTransform(object, MODEL_TRANSFORM_LOCAL, &transform);
    }
  }
  // Animate the angle of direction plane.
  if (directionPlaneToggle == 1)
    directionPlaneAngle = MIN(directionPlaneAngle + latency * 0.5, 1.0);
  else
    directionPlaneAngle = MAX(directionPlaneAngle - latency * 0.5, 0.0);

  { // Move direction plane and all pieces of robotic arm.
    double const timing = timerTicks / 60.0;
    double gripperTheta = sin(timing * 1.2816) * 0.7;

    objectMove("RoboticArm:Piece1", matrixRotateY(sin(timing * 0.5823) * M_PI * 0.25));
    objectMove("RoboticArm:Piece2", matrixRotateZ((sin(timing * 0.7921) - 0.5) * M_PI * 0.1));
    objectMove("RoboticArm:Piece3", matrixRotateZ((sin(timing * 1.184) + 0.25) * M_PI * 0.1));
    objectMove("RoboticArm:Piece4", matrixRotateZ((cos(timing * 1.037) + 0.75) * M_PI * 0.15));
    objectMove("RoboticArm:Piece5", matrixRotateZ(sin(timing * 0.8) * M_PI * 0.15));
    objectMove("RoboticArm:Piece6", matrixRotateY(cos(timing * 0.681) * M_PI * 0.5));

    objectMove("RoboticArm:Gripper0", matrixTranslate((Vector){.z = -9.8 + gripperTheta}));
    objectMove("RoboticArm:Gripper1", matrixTranslate((Vector){.z = 9.8 - gripperTheta}));

    objectMove("DirectionPlane", matrixRotateY((SineTransform(directionPlaneAngle) - 0.5) * M_PI * 0.2));
  }
  { // Move lifter and grippers of the forklift.
    double const timing = timerTicks / 60.0;
    double gripperTheta = cos(timing * 0.773) * 9.0;

    objectMove("Forklift:Forklift", matrixMultiply(matrixRotateY(M_PI * 0.5), matrixTranslate((Vector){
      -320.0f, 0.0f, -160.0f + sin(timing * 1.6102) * 24.0})));

    objectMove("Forklift:Lifter", matrixTranslate((Vector){.y = sin(timing * 0.6102) * 12.0 + 12.0}));
    objectMove("Forklift:GripperL", matrixTranslate((Vector){.z = -12.0 + gripperTheta}));
    objectMove("Forklift:GripperR", matrixTranslate((Vector){.z = 12.0 - gripperTheta}));
  }
}

float randomizeBoxAngle(void)
{
  return RandomSequenceGenerateRanged(&prngBoxes, 4) * M_PI * 0.5 +
    (RandomSequenceGenerateFloat(&prngBoxes) - 0.5f) * M_PI * 0.12;
}

// Creates an object at the given position and rotation angle around Y axis.
static ObjectModel* objectCreate(char const* objectName, char const* meshName, ObjectModel* parent,
  Vector position, float angle, int32_t material, uint32_t attributes)
{
  ObjectModel* const object = ObjectModelsAdd(objects, objectName, NULL, attributes);

  ObjectModelSetParent(object, parent);
  ObjectModelSetMeshName(object, meshName);
  ObjectModelSetMaterial(object, material);

  Matrix transform = matrixTranslate(position);

  if (angle != 0.0f)
    transform = matrixMultiply(matrixRotateY(angle), transform);

  ObjectModelSetTransform(object, MODEL_TRANSFORM_LOCAL, &transform);

  return object;
}

void createObjects(void)
{
  { // Floor
    ObjectModel* floor = ObjectModelsAdd(objects, "Floor", NULL, OBJECT_MODEL_ATTRIBUTE_VISIBLE |
      OBJECT_MODEL_ATTRIBUTE_HIERARCHYLESS);

    ObjectModelSetMeshName(floor, "Plane");
    ObjectModelSetMaterial(floor, 0);
    ObjectModelSetColor(floor, &(FloatColor){0.875f, 0.875f, 0.875f, 1.0f});

    Matrix const transform = matrixTranslate((Vector){0.0f, -0.01f, 0.0f});
    ObjectModelSetTransform(floor, MODEL_TRANSFORM_LOCAL, &transform);
  }
  uint32_t const attributes = OBJECT_MODEL_ATTRIBUTE_VISIBLE | OBJECT_MODEL_ATTRIBUTE_SELECTABLE;
  uint32_t const attributesMoving = attributes | OBJECT_MODEL_ATTRIBUTE_HIERARCHYLESS;

  { // Robotic arm pieces.
    int32_t const materialPieces = 3;

    ObjectModel* piece0 = objectCreate("RoboticArm:Piece0", "RoboticArm_Piece0", NULL,
      (Vector){-60.0f, 0.0f, -160.0f}, M_PI * 0.25, materialPieces, attributesMoving);

    ObjectModel* piece1 = objectCreate("RoboticArm:Piece1", "RoboticArm_Piece1", piece0,
      (Vector){0.0f, 0.0f, 0.0f}, 0, materialPieces, attributesMoving);

    ObjectModel* piece2 = objectCreate("RoboticArm:Piece2", "RoboticArm_Piece2", piece1,
      (Vector){0.0f, 0.0f, 0.0f}, 0, materialPieces, attributesMoving);

    ObjectModel* piece3 = objectCreate("RoboticArm:Piece3", "RoboticArm_Piece3", piece2,
      (Vector){0.0f, 0.0f, 0.0f}, 0, materialPieces, attributesMoving);

    ObjectModel* piece4 = objectCreate("RoboticArm:Piece4", "RoboticArm_Piece4", piece3,
      (Vector){0.0f, 0.0f, 0.0f}, 0, materialPieces, attributesMoving);

    ObjectModel* piece5 = objectCreate("RoboticArm:Piece5", "RoboticArm_Piece5", piece4,
      (Vector){0.0f, 0.0f, 0.0f}, 0, materialPieces, attributesMoving);

    ObjectModel* piece6 = objectCreate("RoboticArm:Piece6", "RoboticArm_Piece6", piece5,
      (Vector){0.0f, 0.0f, 0.0f}, 0, materialPieces, attributesMoving);

    ObjectModel* gripper0 = objectCreate("RoboticArm:Gripper0", "RoboticArm_Gripper0", piece6,
      (Vector){0.0f, 0.0f, 0.0f}, 0, materialPieces, attributesMoving);

    ObjectModel* gripper1 = objectCreate("RoboticArm:Gripper1", "RoboticArm_Gripper1", piece6,
      (Vector){0.0f, 0.0f, 0.0f}, 0, materialPieces, attributesMoving);

    // Connect each pieces of robotic arm together by using "latches". An object attached to its parent by
    // latch mechanism places and orients the object so that its and parent latches coincide. This enables
    // precise placement and orientation of pieces together. The positions and orientations are calculated
    // according to parent dependency chain. Therefore, moving the base piece (which has no parent) will
    // also move accordingly all its codependent child pieces.
    ObjectModelConnectLatchesByName(piece1, "Out", "In");
    ObjectModelConnectLatchesByName(piece2, "Out", "In");
    ObjectModelConnectLatchesByName(piece3, "Out", "In");
    ObjectModelConnectLatchesByName(piece4, "Out", "In");
    ObjectModelConnectLatchesByName(piece5, "Out", "In");
    ObjectModelConnectLatchesByName(piece6, "Out", "In");
    ObjectModelConnectLatchesByName(gripper0, "Out", "In");
    ObjectModelConnectLatchesByName(gripper1, "Out", "In");
  }

  { // Multiple bands with direction plane
    ObjectModel* const multipleBands = objectCreate("MultipleBands", "MultipleBands", NULL,
      (Vector){0.0f, 0.0f, 40.0f}, 0.0f, 1, attributes);

    ObjectModel* const directionPlane = objectCreate("DirectionPlane", "DirectionPlane", multipleBands,
      (Vector){0.0f, -2.0f, 0.0f}, M_PI * 0.17, 1, attributesMoving);

    ObjectModelConnectLatchesByName(directionPlane, "DirectionPlane", "Base");

    // Create boxes that will be moved over the bands.
    float const distance = ObjectModelGetWaypointDistance(multipleBands, 0);
    RandomSequenceInit(&prngBoxes);

    for (int32_t i = 0; i < 20; ++i)
    {
      ObjectMovementTracking* tracking = malloc(sizeof(ObjectMovementTracking));
      tracking->distance = distance * i / 32.0f;
      tracking->group = 0;
      tracking->angle = randomizeBoxAngle();

      char name[64] = {0};
      snprintf(name, sizeof(name), "MovingBox_%d", i);

      ObjectModel* const box = ObjectModelsAdd(objects, name, tracking, attributesMoving);
      ObjectModelSetParent(box, multipleBands);
      ObjectModelSetMeshName(box, "Box");
      ObjectModelSetMaterial(box, 2);
    }
  }
  { // Forklift with separate lifter and grippers
    ObjectModel* const forklift = objectCreate("Forklift:Forklift", "Forklift_Forklift", NULL,
      (Vector){-320.0f, 0.0f, -160.0f}, M_PI * 0.5, 1, attributesMoving);

    ObjectModel* const lifter = objectCreate("Forklift:Lifter", "Forklift_Lifter", forklift,
      (Vector){0.0f, 0.0f, 0.0f}, 0, 3, attributesMoving);

    ObjectModelConnectLatchesByName(lifter, "Lifter", "Base");

    ObjectModel* const gripper0 = objectCreate("Forklift:GripperL", "Forklift_GripperL", lifter,
      (Vector){0.0f, 0.0f, -10.0f}, 0, 3, attributesMoving);

    ObjectModelConnectLatchesByName(gripper0, "Gripper", "Lifter");

    ObjectModel* const gripper1 = objectCreate("Forklift:GripperR", "Forklift_GripperR", lifter,
      (Vector){0.0f, 0.0f, 10.0f}, 0, 3, attributesMoving);

    ObjectModelConnectLatchesByName(gripper1, "Gripper", "Lifter");
  }

  // Fences
  objectCreate("Fence", "Fence", NULL, (Vector){20.0f, 0.0f, 220.0f}, 0.0f, 3, attributes);
  objectCreate("FenceThick", "FenceThick", NULL, (Vector){-280.0f, 0.0f, -60.0f}, M_PI * 0.5, 3, attributes);

  { // Stack of pallets behind the fence.
    float height = 0.0f;

    for (int32_t i = 0; i < 7; ++i)
    {
      ObjectModel* const pallet = objectCreate(NULL, "Pallet", NULL, (Vector){-340.0f, height, -28.0f},
        0.0f, 2, attributes);

      Vector size;
      ObjectModelGetSize(pallet, &size);

      height += size.y;
    }
  }
  // Stack of boxes at the backyard.
  objectCreate(NULL, "Box", NULL, (Vector){-320.0f, 0.0f, 40.0f}, 0.0f, 2, attributes);
  objectCreate(NULL, "Box", NULL, (Vector){-315.0f, 0.0f, 72.0f}, M_PI * 0.1, 2, attributes);
  objectCreate(NULL, "Box", NULL, (Vector){-345.0f, 0.0f, 65.0f}, -M_PI * 0.1, 2, attributes);

  // Text describing the boxes.
  objectCreate("Text3D", "Text3D", NULL, (Vector){135.0f, 0.0f, -135.0f}, M_PI * -0.24, 3, attributes);

  { // Stack of boxes near the arm.
    ObjectModel* const dummy = objectCreate(NULL, NULL, NULL, (Vector){105.0f, 0.0f, -105.0f},
      -M_PI * 0.25, 2, attributes);

    // Use a dummy object thas has no mesh as parent for the boxes.
    uint64_t prng;
    RandomSequenceInitBySeed(&prng, 4007);
    bool boxes[3][3][7] = {0};

    for (int k = 0; k < 3; k++)
      for (int j = 0; j < 3; j++)
        for (int i = 0; i < 7; ++i)
          if (RandomSequenceGenerateRanged(&prng, 4) != 1 && (k == 0 || boxes[k - 1][j][i]))
          {
            ObjectModel* const box = ObjectModelsAdd(objects, NULL, NULL, OBJECT_MODEL_ATTRIBUTE_VISIBLE |
              OBJECT_MODEL_ATTRIBUTE_SELECTABLE);

            ObjectModelSetParent(box, dummy);
            ObjectModelSetMeshName(box, "Box");
            ObjectModelSetMaterial(box, 2);

            Vector size = {0};
            ObjectModelGetSize(box, &size);

            Matrix transform = matrixMultiply(
              matrixRotateY((float)(RandomSequenceGenerateRanged(&prng, 3) * M_PI * 0.5)),
              matrixTranslate((Vector){(i - 3) * (size.x + 1.0f),
                (size.y + 0.5f) * k, (j - 1) * (size.z + 1.0f)}));

            ObjectModelSetTransform(box, MODEL_TRANSFORM_LOCAL, &transform);
            boxes[k][j][i] = true;
          }
  }
  { // Stand with pallets and boxes.
    ObjectModel* const stand = objectCreate(NULL, "Stand", NULL,
      (Vector){60.0f, 0.0f, 200.0f}, 0.0f, 1, attributes);

    uint64_t prng;
    RandomSequenceInitBySeed(&prng, 5004);

    for (int j = 0; j < 3; ++j)
      for (int i = 0; i < 5; ++i)
      {
        ObjectModel* const pallet = ObjectModelsAdd(objects, NULL, NULL, OBJECT_MODEL_ATTRIBUTE_VISIBLE |
          OBJECT_MODEL_ATTRIBUTE_SELECTABLE);

        float height = j * 46.0f;
        if (j == 2)
          height = 86.5f;

        ObjectModelSetParent(pallet, stand);
        ObjectModelSetMeshName(pallet, "Pallet");
        ObjectModelSetMaterial(pallet, 2);

        Vector palletSize = {0};
        ObjectModelGetSize(pallet, &palletSize);

        Matrix const transform = matrixTranslate((Vector){(i - 2) * (palletSize.x + 7.0f), height, 0.0f});
        ObjectModelSetTransform(pallet, MODEL_TRANSFORM_LOCAL, &transform);

        for (int kz = 0; kz < 3; ++kz)
          for (int kx = 0; kx < 3; ++kx)
            if (RandomSequenceGenerateRanged(&prng, 2) == 1)
            {
              ObjectModel* const box = ObjectModelsAdd(objects, NULL, NULL, OBJECT_MODEL_ATTRIBUTE_VISIBLE |
                OBJECT_MODEL_ATTRIBUTE_SELECTABLE);

              ObjectModelSetParent(box, pallet);
              ObjectModelSetMeshName(box, "Box");
              ObjectModelSetMaterial(box, 2);

              Vector boxSize = {0};
              ObjectModelGetSize(box, &boxSize);

              Matrix transform = matrixMultiply(
                matrixRotateY((float)(RandomSequenceGenerateRanged(&prng, 3) * M_PI * 0.5)),
                matrixTranslate((Vector){
                  (kx - 1) * (boxSize.x + 5.0f), palletSize.y, (kz - 1) * (boxSize.z + 2.0f)}));

              ObjectModelSetTransform(box, MODEL_TRANSFORM_LOCAL, &transform);
            }
      }
  }
}

static bool createMeshText3D(MeshBuffer* meshBuffer)
{
  MeshBufferClear(meshBuffer);

  TextModeller* textModeller = TextModellerCreate(device);
  if (!textModeller)
    return false;

  TextModellerSetFontParameters(textModeller, &(FontParameters){
    .family = "Cambria", .size = 16.0f, .weight = FONT_WEIGHT_BOLD,
    .stretch = FONT_STRETCH_NORMAL, .slant = FONT_SLANT_NONE});

  TextModellerDrawCurved(textModeller, NULL, "Boxes waiting to be dispatched",
    -150.0f, NAN, 3.0f, &COLOR_PAIR(0xFFFFFFFFu), TEXT_ALIGNMENT_START, NULL);

  bool created = TextModellerCopyToMeshBuffer(textModeller, meshBuffer);
  if (created)
  {
    SceneMesh* mesh = SceneMeshesAddFromBuffer(meshes, "Text3D", meshBuffer, NULL, vertexElements,
      vertexElementsCount, NULL, NULL, 0, 0, 1.0f);

    if (mesh)
    { // Create a rough voxel representation of 3D text mesh to improve mouse selection accuracy.
      MeshVoxel* voxel = MeshBufferVoxelize(meshBuffer, 4, false);
      if (voxel)
      {
        MeshVoxelTakeAway(SceneMeshGetVoxel(mesh), voxel);
        MeshVoxelDestroy(voxel);
      }
    }
    else
      created = false;
  }
  TextModellerDestroy(textModeller);
  return created;
}

bool createAndLoadMeshes(void)
{
  bool created = false;
  { // Create geometric meshes.
    MeshBuffer* meshBuffer = MeshBufferCreate();
    if (!meshBuffer)
      return false;

    MeshBufferPlane(meshBuffer, 1, 1, NULL, &(Vector){1200.0f, 0.0f, 0.0f}, &(Vector){0.0f, 0.0f, 1200.0f},
      &vectorAxisY, NULL, NULL, NULL, true);

    created = SceneMeshesAddFromBuffer(meshes, "Plane", meshBuffer, NULL, vertexElements,
      vertexElementsCount, NULL, NULL, 0, 0, 1.0f);

    created = createMeshText3D(meshBuffer) && created;

    MeshBufferDestroy(meshBuffer);
  }
  static char const* const basePath = "../../../../Media/Warehouse/";

  if (created)
  { // Load meshes from disk.
    static char const* const meshNames[] = {"Box", "DirectionPlane", "Fence", "FenceThick", "Forklift",
      "MultipleBands", "Pallet", "RoboticArm", "Stand"};

    static bool const meshVertexColored[] = {true, true, true, true, false, false, true, false, true};

    for (size_t i = 0; i < sizeof(meshNames) / sizeof(char const*); ++i)
    {
      char fileName[128] = {0};

      strcpy(fileName, basePath);
      strcat(fileName, meshNames[i]);
      strcat(fileName, ".obj");

      uint32_t options = meshVertexColored[i] ? MESH_LOADING_OPTION_MATERIAL_VERTEX_COLOR : 0;

      if (!SceneMeshesAddFromFile(meshes, meshNames[i], fileName, NULL, vertexElements, vertexElementsCount,
        0, 0, &options, 1.5f, NULL, NULL, NULL, NULL))
      {
        created = false;
        break;
      }
    }

    // For meshes such as "RoboticArm" and "ForkLift", a split is performed by creating mesh instances
    // that refer to a specific portion of a parent mesh. This is done according to meta-tags that are
    // present in the mesh. An object that refers to one of the sub-meshes will still use original mesh
    // for rendering, while using each sub-mesh latch and voxel mechanisms.
    static char const* const meshesToSplit[] = {"RoboticArm", "Forklift"};

    for (size_t i = 0; i < sizeof(meshesToSplit) / sizeof(char const*); ++i)
    {
      SceneMesh* const mesh = SceneMeshesGetMeshByName(meshes, meshesToSplit[i]);
      if (!mesh)
        continue; // Mesh does not exist.

      if (!SceneMeshesSlice(meshes, mesh, MESH_META_TAG_TYPE_OBJECT, NULL))
        return false;
    }

    for (int32_t i = 0; i < SceneMeshesGetCount(meshes); ++i)
    {
      SceneMesh* const mesh = SceneMeshesGetMeshByIndex(meshes, i);
      if (!mesh)
        continue; // Non-existant mesh?

      SceneMesh* parent = NULL;
      SceneMeshGetSlice(mesh, &parent, NULL);

      if (!parent)
        continue; // Mesh is not a slice.

      // Retrieve the name of the slice and its parent.
      char parentMeshName[128] = {0}, meshName[128] = {0};
      int32_t parentMeshNameLength = sizeof(parentMeshName), meshNameLength = sizeof(meshName);

      SceneMeshGetName(parent, parentMeshName, &parentMeshNameLength);
      SceneMeshGetName(mesh, meshName, &meshNameLength);

      { // Load voxel for a particular segment, if voxel file is present.
        char voxelName[256] = {0};

        strcpy(voxelName, basePath);
        strcat(voxelName, "/");
        strcat(voxelName, parentMeshName);
        strcat(voxelName, "/");
        strcat(voxelName, meshName);
        strcat(voxelName, ".voxel");

        MeshVoxelLoadFromFile(SceneMeshGetVoxel(mesh), voxelName, NULL, NULL, NULL);
      }
      { // Load latches for a particular segment, if latches file is present.
        char latchesName[256] = {0};

        strcpy(latchesName, basePath);
        strcat(latchesName, "/");
        strcat(latchesName, parentMeshName);
        strcat(latchesName, "/");
        strcat(latchesName, meshName);
        strcat(latchesName, ".latch");

        SceneMeshLatchesLoadFromFile(SceneMeshGetLatches(mesh), latchesName);
      }
    }
  }
  return created;
}

void createLightsAndMaterials(void)
{
  SceneLight* const light = SceneLightsAdd(lights);
  if (light)
  { // Default light
    light->ambientColor = floatColorRGB(0xFFFFFFu);
    light->albedoColor = floatColorRGB(0xFFFFFFu);
    light->specularColor = floatColorRGB(0xFFFFFFu);
    light->specularExponent = 4.0f;
    light->position = lightPosition;
    light->shadowCaster = 0;
  }

  ObjectMaterial* material = ObjectMaterialsAdd(materials);
  if (material)
  { // (0) Floor
    material->ambientColor = (FloatColorRGB){1.0f, 1.0f, 1.0f};
    material->albedoColor = (FloatColor){0.0f, 0.0f, 0.0f, 0.0f};
    material->specularColor = (FloatColorRGB){0.0f, 0.0f, 0.0f};
    material->shadows = 0.5f;
    material->occlusion = 0.5f;
  }

  material = ObjectMaterialsAdd(materials);
  if (material)
  { // (1) Generic
    material->ambientColor = (FloatColorRGB){0.25f, 0.25f, 0.25f};
    material->albedoColor = (FloatColor){1.0f, 1.0f, 1.0f, 0.5f};
    material->specularColor = (FloatColorRGB){1.0f, 1.0f, 1.0f};
    material->specularExponent = 4.0f;
    material->shadows = 0.5f;
    material->occlusion = 0.5f;
  }

  material = ObjectMaterialsAdd(materials);
  if (material)
  { // (2) Carton
    material->ambientColor = (FloatColorRGB){0.25f, 0.25f, 0.25f};
    material->albedoColor = (FloatColor){1.0f, 1.0f, 1.0f, 0.5f};
    material->specularColor = (FloatColorRGB){0.5f, 0.5f, 0.5f};
    material->specularExponent = 16.0f;
    material->shadows = 0.5f;
    material->occlusion = 0.5f;
    material->roughness = (Vector){0.3f, 1.2f, 0.4f};
    material->technique = TECHNIQUE_LIGHTING_MINNAERT;
  }

  material = ObjectMaterialsAdd(materials);
  if (material)
  { // (3) Metal
    material->ambientColor = (FloatColorRGB){0.25f, 0.25f, 0.25f};
    material->albedoColor = (FloatColor){1.0f, 1.0f, 1.0f, 0.5f};
    material->specularColor = (FloatColorRGB){1.0f, 1.0f, 1.0f};
    material->specularExponent = 4.0f;
    material->shadows = 0.5f;
    material->occlusion = 0.5f;
    material->roughness = (Vector){1.8f, 1.2f, 0.7f};
    material->technique = TECHNIQUE_LIGHTING_COOK_TORRANCE;
  }
}

void releaseObjectMovementTracking(void)
{
  for (ObjectModel* object = ObjectModelsGetFirst(objects); object; object = ObjectModelGetNext(object))
  {
    ObjectMovementTracking* tracking = ObjectModelGetPayload(object);
    if (tracking)
      free(tracking);
  }
}

void outputError(char const* message)
{
#ifdef _WIN32
  static char const* initErrorTitle = "Initialization Error";
  MessageBoxA(NULL, message, initErrorTitle, MB_ICONERROR | MB_OK | MB_DEFAULT_DESKTOP_ONLY);
#else
  printf("%s\n", message);
#endif
}

// Main application entry.

#ifdef _WIN32

int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine,
  _In_ int showCmd)
{
  UNREFERENCED_PARAMETER(prevInstance);
  UNREFERENCED_PARAMETER(cmdLine);
  UNREFERENCED_PARAMETER(showCmd);

  char const* windowClassName = "AfterwarpWarehouseObjectsDemo";

#else

int main(int argc, char **argv)
{

#endif

  ApplicationConfiguration configuration = {0};
  configuration.size = displaySize;
  configuration.position = (Point){.x = INT32_MAX, .y = INT32_MAX}; // default position
#ifdef _WIN32
  configuration.startup.win.handleInstance = instance;
  configuration.startup.win.handleIconSmall = LoadIcon(instance, MAKEINTRESOURCE(IDI_AFTERWARP));
  configuration.startup.win.handleIconBig = LoadIcon(instance, MAKEINTRESOURCE(IDI_AFTERWARP));

  strncpy(configuration.startup.win.windowClassName, windowClassName,
    sizeof(configuration.startup.win.windowClassName));
#else
  configuration.startup.nx.parameterCount = argc;
  configuration.startup.nx.parameterStrings = argv;
#endif

  application = ApplicationCreate(&configuration);
  if (!application)
  {
    outputError("Could not create Application object.");
    return -1;
  }

  ApplicationEvents events = {0};
  events.eventCreate = applicationCreate;
  events.eventDestroy = applicationDestroy;
  events.eventResize = applicationResize;
  events.eventStatus = applicationStatus;
  events.eventIdle = applicationIdle;
  events.eventMouse = applicationMouse;
  events.eventKey = applicationKey;
  events.eventRender = applicationRender;
  ApplicationSetEvents(application, &events, NULL);

  displayScale = ApplicationGetWindowScale(application);

  ApplicationSetTitle(application, "Afterwarp: WarehouseObjects example");
  ApplicationSetIconTitle(application, "WarehouseObjects");

  { // Retrieve client size back in case it has changed since startup.
    Rect clientRect = {0};
    ApplicationGetClientRect(application, &clientRect);

    displaySize = rectGetSize(clientRect);
  }

  bool const executed = ApplicationExecute(application);
  ApplicationDestroy(application);

  return executed ? 0 : -1;
}