BabylonLearningLab

Babylon Learning Lab: Beginner Guide

This document explains the project from zero C# and .NET knowledge. Read it from top to bottom once, then use the links and exercises while changing the code.

1. What This Project Does

This is a small furniture-fitting application:

  1. The user enters room width and length in a 2D panel.
  2. The browser draws a simple top-down floor plan.
  3. The browser asks the Babylon.js engine to create a 3D floor and four walls.
  4. The browser requests furniture metadata from a C# API.
  5. The user chooses a bookshelf and a wall.
  6. Babylon.js downloads the selected .glb model and places it against that wall.

The application is deliberately small. A commercial application would add users, projects, saved layouts, authentication, permissions, more furniture, doors, windows, measurements, collision detection, and a model storage service.

2. The Important Files

BabylonLearningLab/
├── BabylonLearningLab.csproj       .NET project and NuGet packages
├── Program.cs                      C# web server, API, models, database access
├── appsettings.json                Application configuration
├── docker-compose.yml              Local PostgreSQL container
├── database/001-furniture.sql      Database table and seed records
├── wwwroot/index.html              Browser page structure
├── wwwroot/styles.css              Browser appearance and layout
├── wwwroot/app.js                  Room editor and Babylon.js logic
└── wwwroot/models/*.glb            3D model files served by the app

wwwroot is a special ASP.NET Core folder. Files inside it are public static files. For example:

wwwroot/models/bookshelf-60.glb

is available in a browser as:

/models/bookshelf-60.glb

3. What .NET Is

.NET is the platform that runs the C# application. C# is the programming language; .NET provides the runtime, libraries, web server integration, and command-line tools.

The project file, BabylonLearningLab.csproj, says:

<TargetFramework>net10.0</TargetFramework>

This project targets .NET 10. It also references the Npgsql NuGet package:

<PackageReference Include="Npgsql" Version="10.0.3" />

Npgsql is the C# library that opens connections to PostgreSQL and executes SQL.

Useful commands:

dotnet restore   # download NuGet packages
dotnet build     # compile the C# project
dotnet run       # build and start the web server

The dotnet command is similar to a combination of a compiler, package manager, and application runner.

4. What Program.cs Does

Program.cs is the startup file. It creates the web application, configures middleware, defines API endpoints, and starts the server.

4.1 Imports

using Npgsql;
using Microsoft.AspNetCore.StaticFiles;

using makes types from a library available without writing their full namespace every time.

4.2 Create the application

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

builder prepares the application. app is the web application that will receive HTTP requests.

var means the compiler infers the type from the value. It is still strongly typed; C# is not turning the value into an untyped JavaScript-style variable.

4.3 Static files and GLB files

app.UseDefaultFiles();
var contentTypeProvider = new FileExtensionContentTypeProvider();
contentTypeProvider.Mappings[".glb"] = "model/gltf-binary";
app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = contentTypeProvider });

This lets the browser request index.html, CSS, JavaScript, and model files. The .glb mapping is important because ASP.NET Core does not automatically know the MIME type of every file extension.

Without this mapping, the files can exist on disk but the browser receives a 404 response.

4.4 API endpoints

app.MapGet("/api/furniture", async (...) =>
{
    var catalog = CreateCatalog(configuration, fallbackCatalog);
    return Results.Ok(await catalog.GetAllAsync(cancellationToken));
});

This defines an HTTP GET endpoint:

GET http://localhost:5090/api/furniture

The endpoint returns JSON. The browser does not call C# functions directly. It sends an HTTP request, and the server sends an HTTP response.

The other endpoints are:

Endpoint Purpose
GET /api/scene Returns the page title, message, and background color
GET /api/furniture Returns every catalog item
GET /api/furniture/1 Returns one catalog item by ID

Results.Ok(...) creates a successful HTTP response. If the requested furniture ID does not exist, the code returns Results.NotFound(), which becomes HTTP 404.

4.5 C# records

record FurnitureItem(
    int Id,
    string Name,
    string Category,
    string ModelUrl,
    decimal Width,
    decimal Height,
    decimal Depth,
    string Color);

A record is a compact way to define a data object. This record describes the JSON contract shared by the API and frontend.

The same object is represented in the browser as JSON:

{
  "id": 1,
  "name": "Bookshelf 60",
  "modelUrl": "/models/bookshelf-60.glb",
  "width": 0.6,
  "height": 2.0,
  "depth": 0.35
}

Important C# types here:

4.6 Interfaces and implementations

interface IFurnitureCatalog
{
    Task<IReadOnlyList<FurnitureItem>> GetAllAsync(CancellationToken cancellationToken);
}

An interface describes what an object can do without specifying how it does it.

This project has two implementations:

The API uses IFurnitureCatalog, so the endpoint does not need to know which implementation supplied the data. This is an important pattern in company applications: the API depends on a capability, not on one concrete database implementation.

4.7 Choosing PostgreSQL or fallback data

var connectionString = configuration.GetConnectionString("FurnitureDb")
    ?? configuration["DATABASE_URL"];

return string.IsNullOrWhiteSpace(connectionString)
    ? fallbackCatalog
    : new PostgresFurnitureCatalog(connectionString);

The application checks configuration. If no connection string exists, it uses the in-memory catalog. If a connection string exists, it creates the PostgreSQL catalog.

The ?? operator means “use the value on the left unless it is null; otherwise use the value on the right.”

4.8 Database access

The PostgreSQL implementation follows this sequence:

  1. Create an NpgsqlConnection.
  2. Open the connection asynchronously.
  3. Create an NpgsqlCommand containing SQL.
  4. Execute the command.
  5. Read rows from the data reader.
  6. Convert each row into a FurnitureItem.
  7. Close and dispose database objects.

This syntax is important:

await using var connection = new NpgsqlConnection(connectionString);

await using ensures the connection is cleaned up even if an error occurs. Database connections must not be left open.

The single-item query uses a parameter:

command.Parameters.AddWithValue("id", id);

Parameters prevent user input from being concatenated into SQL and are an essential protection against SQL injection.

5. PostgreSQL Structure

database/001-furniture.sql creates this table:

furniture_items
├── id
├── name
├── category
├── model_url
├── width
├── height
├── depth
└── color

The database stores metadata, not necessarily the binary model itself. In a production system, model_url could point to:

For this lab the files are local static files, so /models/bookshelf-60.glb is enough.

docker-compose.yml starts PostgreSQL locally. The API and the database are separate processes:

ASP.NET Core process  --SQL/TCP-->  PostgreSQL container
Browser              --HTTP------>  ASP.NET Core process
Browser              --HTTP------>  GLB static file

6. What index.html Does

index.html defines the controls and canvas:

These scripts are loaded in order:

<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
<script src="/app.js"></script>

The first script provides Babylon.js. The second provides model-loading support. The third contains this application’s code.

7. What app.js Does

7.1 Browser state

const state = {
  room: { width: 5, length: 4, height: 2.6 },
  scene: null,
  furniture: [],
  placedModels: []
};

This is the current client-side state. It is not the database. It is the browser’s working copy while the user edits the room.

7.2 Create the Babylon scene

const scene = new BABYLON.Scene(engine);

A Babylon scene contains 3D objects, cameras, lights, materials, and the render loop.

This project creates:

7.3 Build the room from dimensions

rebuildRoom() reads the current width, length, and height. It creates:

The room uses a simple coordinate system:

       north (-Z)
 west              east
       south (+Z)

The floor is at Y = 0. Wall height extends upward on the Y axis. Width uses X; length uses Z.

When the user changes dimensions, old room meshes are disposed and new meshes are created. This is a simple and understandable approach for the lab.

7.4 Draw the 2D floor plan

drawFloorPlan() uses the normal HTML canvas 2D API. It calculates a scale that fits the room inside the small canvas, then draws:

The 2D plan and the 3D room use the same state.room values. That shared data is what keeps them synchronized.

7.5 Request catalog data

const response = await fetch('/api/furniture');
const furniture = await response.json();

fetch sends an HTTP request. await pauses this function until the response arrives. response.json() converts the JSON response into JavaScript objects.

The UI then creates one dropdown option for each returned item. Notice that the frontend does not contain the bookshelf URLs as its source of truth. It receives them from the API.

7.6 Load a GLB model

When the user clicks Place selected model, placeFurniture() does this:

  1. Reads the selected furniture object.
  2. Splits /models/bookshelf-60.glb into a folder and filename.
  3. Calls BABYLON.SceneLoader.ImportMeshAsync.
  4. Creates a parent transform node for the imported meshes.
  5. Measures the imported model’s bounds.
  6. Scales it to the database dimensions.
  7. Rotates it based on the selected wall.
  8. Positions it against that wall.
  9. Adjusts its vertical position so it sits on the floor.

The important boundary is:

API modelUrl: "/models/bookshelf-60.glb"
       |
       v
SceneLoader.ImportMeshAsync(...)
       |
       v
Babylon meshes in the scene

The C# API does not render the model. The browser downloads the model and Babylon.js renders it.

8. Complete Request Flow

When the page loads:

1. Browser requests GET /
2. ASP.NET Core serves wwwroot/index.html
3. Browser loads Babylon.js, the loader, app.js, and styles.css
4. app.js requests GET /api/scene
5. app.js requests GET /api/furniture
6. C# selects the in-memory or PostgreSQL catalog
7. C# returns JSON
8. Browser fills the furniture dropdown
9. Babylon.js renders the empty room

When the user places a bookshelf:

1. User chooses a bookshelf and wall
2. Browser reads the selected FurnitureItem
3. Browser requests the modelUrl
4. ASP.NET Core serves the GLB bytes
5. Babylon.js parses the GLB
6. Browser scales and positions the imported meshes
7. Babylon.js renders the model every frame

There is no database request when the user clicks the placement button in this lab because the catalog data was already loaded. A production design might use GET /api/furniture/{id} at that point if the catalog response were intentionally small or if placement required fresh permission checks.

9. How to Run and Test It

Without PostgreSQL, the fallback catalog is enough:

cd BabylonLearningLab
dotnet run

With PostgreSQL:

docker compose up -d
export ConnectionStrings__FurnitureDb='Host=localhost;Port=5432;Database=furniture_lab;Username=furniture_app;Password=furniture_dev'
dotnet run

Useful browser/API checks:

curl http://localhost:5090/api/scene
curl http://localhost:5090/api/furniture
curl -I http://localhost:5090/models/bookshelf-60.glb

A successful model response should have status 200 and content type model/gltf-binary.

10. Beginner Exercises

Do these in order:

  1. Change the default room from 5 x 4 to 6 x 3 in app.js.
  2. Change the wall height from 2.6 to 3.0.
  3. Add a third furniture record to the in-memory catalog.
  4. Add a new column such as price to the database and C# record.
  5. Display the selected item’s name and dimensions in the UI.
  6. Add a “clear placed models” button.
  7. Store placed furniture in browser state with its wall and position.
  8. Create a POST /api/projects endpoint to save a room layout.
  9. Replace local model URLs with a protected model-download endpoint.
  10. Add collision checks so two furniture items cannot overlap.

11. C# and .NET Vocabulary

12. What to Learn Next

For this company-style architecture, the most useful next topics are:

  1. C# classes, records, interfaces, generics, and nullable reference types.
  2. ASP.NET Core dependency injection instead of creating the catalog inside each endpoint.
  3. Entity Framework Core or a repository/query pattern for larger database access.
  4. PostgreSQL indexes, foreign keys, migrations, and transactions.
  5. HTTP status codes, authentication, authorization, and validation.
  6. Babylon.js transforms, parenting, bounding boxes, picking, and collision detection.
  7. Saving a project as JSON with room dimensions and placed furniture transforms.