File size: 4,200 Bytes
84e8204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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])