extends Node3D ## ## StudioMain - root bootstrap for MonkeyStudio. ## ## Builds the runtime scene tree in code so the whole app is one auditable ## script rather than a fragile binary .tscn. Scene tree created: ## ## StudioMain (Node3D) ## ├── WorldEnvironment (mobile renderer settings) ## ├── Sun (DirectionalLight3D) ## ├── Camera3D (touch_camera.gd) ## ├── Geometry (Node3D) <- user-created meshes live here ## ├── Rig (Node3D) <- Skeleton3D lives here ## ├── Features (Node) <- lazily-instantiated feature modules ## └── UI (CanvasLayer) ## └── (built by studio_ui.gd) ## const TouchCameraScript := preload("res://scripts/studio/touch_camera.gd") const StudioUIScript := preload("res://scripts/studio/studio_ui.gd") var camera: Camera3D var geometry: Node3D var rig: Node3D var feature_host: Node var ui: CanvasLayer func _ready() -> void: _build_environment() _build_lighting() _build_camera() geometry = _make_node("Geometry") rig = _make_node("Rig") feature_host = Node.new() feature_host.name = "Features" add_child(feature_host) FeatureRegistry.host = feature_host _build_ui() _spawn_starter_scene() _connect_signals() print("[StudioMain] ready. %d features available." % FeatureRegistry.feature_count()) func _build_environment() -> void: var env := Environment.new() env.background_mode = Environment.BG_COLOR env.background_color = Color(0.08, 0.09, 0.11) env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR env.ambient_light_color = Color(0.35, 0.38, 0.45) env.ambient_light_energy = 0.6 # Mobile-friendly tonemap + no heavy SSAO/GI by default. env.tonemap_mode = Environment.TONE_MAPPER_FILMIC env.ssao_enabled = false env.glow_enabled = false var we := WorldEnvironment.new() we.name = "WorldEnvironment" we.environment = env add_child(we) func _build_lighting() -> void: var sun := DirectionalLight3D.new() sun.name = "Sun" sun.rotation_degrees = Vector3(-50, -40, 0) sun.light_energy = 1.1 sun.shadow_enabled = true # Lower shadow resolution on mobile to save bandwidth. sun.directional_shadow_max_distance = 40.0 add_child(sun) func _build_camera() -> void: camera = TouchCameraScript.new() camera.name = "Camera3D" camera.fov = 60.0 camera.position = Vector3(4, 4, 8) add_child(camera) camera.focus = Vector3.ZERO func _make_node(n: String) -> Node3D: var node := Node3D.new() node.name = n add_child(node) return node func _build_ui() -> void: ui = StudioUIScript.new() ui.name = "UI" add_child(ui) ## Spawn a cube so the app is immediately interactive on first launch. func _spawn_starter_scene() -> void: FeatureRegistry.activate("mesh.cube") var gen: Node = feature_host.get_node_or_null("Feature_mesh.cube") if gen != null and gen.has_method("spawn"): var mesh: MeshInstance3D = gen.call("spawn", "cube", {"size": 1.5}) _ensure_collision(mesh) StudioBus.object_selected.emit(mesh) if camera != null: camera.frame_aabb(mesh.mesh.get_aabb()) ## Give a mesh a static trimesh collider so sculpting raycasts hit it. func _ensure_collision(mi: MeshInstance3D) -> void: if mi == null or mi.mesh == null: return var body := StaticBody3D.new() body.name = mi.name + "_Body" var shape := CollisionShape3D.new() var trimesh := mi.mesh.create_trimesh_shape() if trimesh != null: shape.shape = trimesh else: # Fallback to a box for exotic primitives without trimesh support. var box := BoxShape3D.new() box.size = mi.mesh.get_aabb().size shape.shape = box body.add_child(shape) mi.add_child(body) func _connect_signals() -> void: StudioBus.fps_throttled.connect(_on_fps_throttled) MemoryManager.memory_warning.connect(_on_memory_warning) func _on_fps_throttled(fps: int) -> void: # Shed load: disable shadows first, then reduce the sim substep budget. var sun := get_node_or_null("Sun") as DirectionalLight3D if sun != null and sun.shadow_enabled: sun.shadow_enabled = false print("[Perf] shadows off at %d fps" % fps) func _on_memory_warning(used_mb: float, budget_mb: float) -> void: print("[Mem] warning: %.0f/%.0f MB" % [used_mb, budget_mb])