feat: Enhance JSON configuration with scene and physics settings

This commit is contained in:
2026-01-08 17:33:58 +00:00
parent 7af2c4e392
commit 40740782d0
5 changed files with 161 additions and 49 deletions

View File

@@ -1,5 +1,6 @@
{
"schema_version": 2,
"configVersion": 2,
"launcher": {
"name": "Cube Demo",
"description": "3D cube room with first-person controls, lanterns, and physics interactions",
@@ -70,6 +71,43 @@
"gamepad_axis_action_threshold": 0.5
}
},
"scene": {
"rotation_speed": 0.9,
"room": {
"half_size": 15.0,
"wall_thickness": 0.5,
"wall_height": 4.0,
"floor_half_thickness": 0.3,
"floor_top": 0.0,
"floor_subdivisions": 20,
"floor_color": [1.0, 1.0, 1.0],
"wall_color": [1.0, 1.0, 1.0],
"ceiling_color": [1.0, 1.0, 1.0]
},
"physics_cube": {
"enabled": true,
"half_extents": [1.5, 1.5, 1.5],
"mass": 1.0,
"color": [0.92, 0.34, 0.28],
"kick_strength": 6.0,
"gravity": [0.0, -9.8, 0.0],
"max_sub_steps": 10
},
"spinning_cube": {
"enabled": true,
"scale": 1.5,
"height": 5.0,
"color": [0.75, 0.45, 0.25]
},
"lanterns": {
"enabled": true,
"height": 8.0,
"size": 0.2,
"color": [1.0, 0.9, 0.6],
"corner_offset": 2.0,
"wall_offset": 2.0
}
},
"rendering": {
"bgfx": {
"renderer": "vulkan"

View File

@@ -82,4 +82,12 @@ function resolver.resolve_gui_opacity(config)
return nil
end
function resolver.resolve_scene(config)
local root = as_table(config)
if not root then
return nil
end
return as_table(root.scene)
end
return resolver

View File

@@ -105,10 +105,40 @@ end
start_music()
local math3d = require("math3d")
local Gui = require("gui")
local string_format = string.format
local resolve_number = scene_framework.resolve_number
local resolve_boolean = scene_framework.resolve_boolean
local resolve_table = scene_framework.resolve_table
local resolve_vec3 = scene_framework.resolve_vec3
local function resolve_positive_int(value, fallback)
local candidate = resolve_number(value, fallback)
local rounded = math.floor(candidate)
if rounded < 1 then
return fallback
end
return rounded
end
local function resolve_window_size()
local default_width = 1024
local default_height = 768
if type(config) ~= "table" then
return default_width, default_height
end
local window = resolve_table(config.window)
local size = resolve_table(window.size)
local width = resolve_positive_int(size.width, default_width)
local height = resolve_positive_int(size.height, default_height)
width = resolve_positive_int(config.window_width, width)
height = resolve_positive_int(config.window_height, height)
return width, height
end
local InputState = {}
InputState.__index = InputState
@@ -201,9 +231,10 @@ end
gui_input = InputState:new()
local gui_context = Gui.newContext()
local window_width, window_height = resolve_window_size()
local ui_layout = {
width = 1024,
height = 768,
width = window_width,
height = window_height,
margin = 16,
}
local compass_layout = {
@@ -279,12 +310,22 @@ local function get_time_seconds()
end
local movement_log_cooldown = 0.0
local world_up = {0.0, 1.0, 0.0}
local scene_config = resolve_table(config_resolver.resolve_scene(config))
local room_config = resolve_table(scene_config.room)
local lantern_config = resolve_table(scene_config.lanterns)
local physics_config = resolve_table(scene_config.physics_cube)
local spinning_config = resolve_table(scene_config.spinning_cube)
local rotation_speed = resolve_number(scene_config.rotation_speed, 0.9)
local room = {
half_size = 15.0,
wall_thickness = 0.5,
wall_height = 4.0,
floor_half_thickness = 0.3,
floor_top = 0.0,
half_size = resolve_number(room_config.half_size, 15.0),
wall_thickness = resolve_number(room_config.wall_thickness, 0.5),
wall_height = resolve_number(room_config.wall_height, 4.0),
floor_half_thickness = resolve_number(room_config.floor_half_thickness, 0.3),
floor_top = resolve_number(room_config.floor_top, 0.0),
floor_subdivisions = resolve_positive_int(room_config.floor_subdivisions, 20),
floor_color = resolve_vec3(room_config.floor_color, {1.0, 1.0, 1.0}),
wall_color = resolve_vec3(room_config.wall_color, {1.0, 1.0, 1.0}),
ceiling_color = resolve_vec3(room_config.ceiling_color, {1.0, 1.0, 1.0}),
}
local player_state = {
eye_height = 1.6,
@@ -303,27 +344,45 @@ local function physics_is_available()
and type(math3d.from_transform) == "function"
end
local physics_cube_half_extents = {1.5, 1.5, 1.5}
local physics_enabled = resolve_boolean(physics_config.enabled, true)
local physics_cube_half_extents = resolve_vec3(physics_config.half_extents, {1.5, 1.5, 1.5})
local physics_cube_scale = {physics_cube_half_extents[1], physics_cube_half_extents[2], physics_cube_half_extents[3]}
local physics_cube_spawn = {
local physics_cube_spawn = resolve_vec3(physics_config.spawn, {
0.0,
room.floor_top + room.wall_height + physics_cube_half_extents[2] + 0.5,
0.0,
}
})
local physics_cube_color = resolve_vec3(physics_config.color, {0.92, 0.34, 0.28})
local physics_kick_strength = resolve_number(physics_config.kick_strength, 6.0)
local physics_cube_mass = resolve_number(physics_config.mass, 1.0)
local physics_gravity = resolve_vec3(physics_config.gravity, {0.0, -9.8, 0.0})
local physics_max_sub_steps = resolve_positive_int(physics_config.max_sub_steps, 10)
local physics_spin_speed = resolve_number(physics_config.rotation_speed, rotation_speed)
local spinning_cube_enabled = resolve_boolean(spinning_config.enabled, true)
local spinning_cube_scale = resolve_number(spinning_config.scale, 1.5)
local spinning_cube_height = resolve_number(spinning_config.height, 5.0)
local spinning_cube_color = resolve_vec3(spinning_config.color, {0.75, 0.45, 0.25})
local spinning_cube_rotation_speed = resolve_number(spinning_config.rotation_speed, rotation_speed)
local lantern_enabled = resolve_boolean(lantern_config.enabled, true)
local lantern_height = resolve_number(lantern_config.height, 8.0)
local lantern_size = resolve_number(lantern_config.size, 0.2)
local lantern_color = resolve_vec3(lantern_config.color, {1.0, 0.9, 0.6})
local lantern_corner_offset = resolve_number(lantern_config.corner_offset, 2.0)
local lantern_wall_offset = resolve_number(lantern_config.wall_offset, lantern_corner_offset)
local physics_state = {
enabled = physics_is_available(),
enabled = physics_is_available() and physics_enabled,
ready = false,
last_step_time = nil,
max_sub_steps = 10,
max_sub_steps = physics_max_sub_steps,
cube_name = "demo_cube",
cube_half_extents = physics_cube_half_extents,
cube_scale = physics_cube_scale,
cube_mass = 1.0,
cube_color = {0.92, 0.34, 0.28},
cube_mass = physics_cube_mass,
cube_color = physics_cube_color,
cube_spawn = physics_cube_spawn,
kick_strength = 6.0,
gravity = {0.0, -9.8, 0.0},
kick_strength = physics_kick_strength,
gravity = physics_gravity,
}
camera.position[1] = 0.0
@@ -705,7 +764,6 @@ local function update_audio_controls()
music_state.togglePressed = toggle_pressed and true or false
end
local rotation_speed = 0.9
local function resolve_material_shader()
if type(config) ~= "table" then
error("Missing config table for MaterialX shader selection")
@@ -777,7 +835,7 @@ local function create_physics_cube()
end
-- Add rotation to physics cube so it spins while falling
local spin_angle = base_rotation_offset + (time * rotation_speed)
local spin_angle = base_rotation_offset + (time * physics_spin_speed)
local spin_rotation = math3d.rotation_y(spin_angle)
local physics_matrix = math3d.from_transform(transform.position, transform.rotation)
local scale = scale_matrix(
@@ -801,17 +859,20 @@ local function create_physics_cube()
end
local function create_spinning_cube()
if not spinning_cube_enabled then
return nil
end
local shader_key = resolve_material_shader()
log_debug("Spinning cube shader=%s", shader_key)
local function compute_model_matrix(time)
local rotation = math3d.rotation_y(time * rotation_speed)
local scale = scale_matrix(1.5, 1.5, 1.5) -- Make cube 3x3x3 units
local position = math3d.translation(0.0, 5.0, 0.0) -- Center of the room
local rotation = math3d.rotation_y(time * spinning_cube_rotation_speed)
local scale = scale_matrix(spinning_cube_scale, spinning_cube_scale, spinning_cube_scale)
local position = math3d.translation(0.0, spinning_cube_height, 0.0)
return math3d.multiply(position, math3d.multiply(rotation, scale))
end
return {
vertices = apply_color_to_vertices({0.75, 0.45, 0.25}),
vertices = apply_color_to_vertices(spinning_cube_color),
indices = cube_indices,
compute_model_matrix = compute_model_matrix,
shader_keys = {shader_key},
@@ -828,10 +889,8 @@ local function create_dynamic_cube()
end
local function create_lantern(x, z)
local lantern_height = 8
local lantern_size = 0.2
return create_static_cube({x, lantern_height, z},
{lantern_size, lantern_size, lantern_size}, {1.0, 0.9, 0.6}, "solid", "lantern")
{lantern_size, lantern_size, lantern_size}, lantern_color, "solid", "lantern")
end
local function create_room_objects()
@@ -843,18 +902,18 @@ local function create_room_objects()
local wall_outer_edge = wall_offset + room.wall_thickness
log_debug("Room walls: inner=%.2f outer=%.2f", wall_inner_edge, wall_outer_edge)
local floor_color = {1.0, 1.0, 1.0}
local wall_color = {1.0, 1.0, 1.0}
local ceiling_color = {1.0, 1.0, 1.0}
local floor_color = room.floor_color
local wall_color = room.wall_color
local ceiling_color = room.ceiling_color
-- Generate proper floor and ceiling planes with tessellation (20x20 = 400 triangles, 441 vertices)
local floor_vertices, floor_indices = generate_plane_mesh(room.half_size * 2, room.half_size * 2, 20, floor_color)
local ceiling_vertices, ceiling_indices = generate_plane_mesh(room.half_size * 2, room.half_size * 2, 20, ceiling_color)
-- Generate proper floor and ceiling planes with tessellation
local floor_vertices, floor_indices = generate_plane_mesh(
room.half_size * 2, room.half_size * 2, room.floor_subdivisions, floor_color)
local ceiling_vertices, ceiling_indices = generate_plane_mesh(
room.half_size * 2, room.half_size * 2, room.floor_subdivisions, ceiling_color)
-- Flip ceiling normals to face down
for i = 1, #ceiling_vertices do
ceiling_vertices[i].normal = {0.0, -1.0, 0.0}
end
scene_framework.flip_normals(ceiling_vertices)
local function create_floor()
local function compute_model_matrix()
@@ -895,18 +954,19 @@ local function create_room_objects()
{room.wall_thickness, room.wall_height, room.half_size}, wall_color, "wall", "wall"),
}
-- Add lanterns in the four corners (adjusted for bigger room)
local lantern_offset = room.half_size - 2.0 -- 2 units from wall
objects[#objects + 1] = create_lantern(lantern_offset, lantern_offset)
objects[#objects + 1] = create_lantern(-lantern_offset, lantern_offset)
objects[#objects + 1] = create_lantern(lantern_offset, -lantern_offset)
objects[#objects + 1] = create_lantern(-lantern_offset, -lantern_offset)
if lantern_enabled then
local corner_offset = room.half_size - lantern_corner_offset
local wall_offset = room.half_size - lantern_wall_offset
objects[#objects + 1] = create_lantern(corner_offset, corner_offset)
objects[#objects + 1] = create_lantern(-corner_offset, corner_offset)
objects[#objects + 1] = create_lantern(corner_offset, -corner_offset)
objects[#objects + 1] = create_lantern(-corner_offset, -corner_offset)
-- Add lanterns on the walls (midpoints)
objects[#objects + 1] = create_lantern(0.0, lantern_offset)
objects[#objects + 1] = create_lantern(0.0, -lantern_offset)
objects[#objects + 1] = create_lantern(lantern_offset, 0.0)
objects[#objects + 1] = create_lantern(-lantern_offset, 0.0)
objects[#objects + 1] = create_lantern(0.0, wall_offset)
objects[#objects + 1] = create_lantern(0.0, -wall_offset)
objects[#objects + 1] = create_lantern(wall_offset, 0.0)
objects[#objects + 1] = create_lantern(-wall_offset, 0.0)
end
return objects
end
@@ -1074,7 +1134,10 @@ function get_scene_objects()
for i = 1, #room_objects do
objects[#objects + 1] = room_objects[i]
end
objects[#objects + 1] = create_dynamic_cube()
local dynamic_cube = create_dynamic_cube()
if dynamic_cube then
objects[#objects + 1] = dynamic_cube
end
return objects
end

View File

@@ -974,7 +974,8 @@ std::string JsonConfigService::BuildConfigJson(const RuntimeConfig& config,
target.AddMember(nameValue, stringValue, allocator);
};
document.AddMember("schema_version", 2, allocator);
document.AddMember("schema_version", kExpectedSchemaVersion, allocator);
document.AddMember("configVersion", kExpectedSchemaVersion, allocator);
rapidjson::Value scriptsObject(rapidjson::kObjectType);
addStringMember(scriptsObject, "entry", config.scriptPath.string());

View File

@@ -41,7 +41,9 @@ void JsonConfigWriterService::WriteConfig(const RuntimeConfig& config, const std
target.AddMember(nameValue, stringValue, allocator);
};
document.AddMember("schema_version", 2, allocator);
constexpr int kSchemaVersion = 2;
document.AddMember("schema_version", kSchemaVersion, allocator);
document.AddMember("configVersion", kSchemaVersion, allocator);
rapidjson::Value scriptsObject(rapidjson::kObjectType);
addStringMember(scriptsObject, "entry", config.scriptPath.string());