-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.gd
413 lines (341 loc) · 11.5 KB
/
main.gd
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
extends Node3D
const SETTINGS_FILE = "user://settings.json"
const PERSIST_NODES = [
'ImageSizeX',
'ImageSizeY',
'CaptureCount',
'EncodeVideo',
'FileDialog',
'BgFileDialog',
'SaveFileDialog',
'CamDist',
'CamTilt',
'CamHeight'
]
const PERSIST_KEYS = [
'value',
'pressed',
'current_path'
]
const VoxelImport = preload('res://addons/MagicaVoxelImporter/voxel_import.gd')
# class member variables go here, for example:
# var a = 2
# var b = "textvar"
var files := {}
var _loading := false
var _cam_orig := {}
func _ready():
# Called when the node is added to the scene for the first time.
# Initialization here
var fd = $FileDialog
if fd:
var container: Node = get_container()
_loading = true
container.get_node('Scale').edit_value = $Model.transform.basis.get_scale().z
container.get_node('Zoom').edit_value = %Camera3D.transform.origin.z
container.get_node('CamTilt').edit_value = %ZoomPlane.rotation_degrees.x
container.get_node('CamHeight').edit_value = %Marker3D.transform.origin.y
_loading = false
for name in ['Zoom', 'CamTilt', 'CamHeight']:
var n := container.get_node(name)
n.reset_value = n.edit_value
if get_node_or_null('MultiMeshinstance'):
_on_BenchmarkButton_toggled(false)
_load_settings()
func _enter_tree():
var err := get_tree().root.get_window().connect('files_dropped', _on_files_dropped)
assert(err == OK)
func _exit_tree():
get_tree().root.get_window().disconnect('files_dropped', _on_files_dropped)
func _on_Control_changed():
_save_settings()
func _on_CaptureCount_value_changed(value):
_on_Control_changed()
func _load_settings():
var f := FileAccess.open(SETTINGS_FILE, FileAccess.READ)
if !f:
var err := FileAccess.get_open_error()
if err == ERR_FILE_NOT_FOUND:
msg('No saved settings file found')
return
else:
msg('Error loading settings file %s: %s' % [SETTINGS_FILE, FileAccess.get_open_error()])
msg('Loading settings from previous session')
var test_json_conv := JSON.new()
var err = test_json_conv.parse(f.get_as_text())
var json = test_json_conv.get_data()
f.close()
assert(err == OK, 'Unable to parse %s: %s (%s on line %s)' % [SETTINGS_FILE, err, test_json_conv.get_error_message(), test_json_conv.get_error_line()])
# no returns or asserts between setting and unsetting this flag
_loading = true
for name in PERSIST_NODES:
if name in json:
var n = get_node_or_null('%%%s' % [name])
if !n:
msgerr('unable to find node to load setting: %s' % [name])
else:
for k in PERSIST_KEYS:
if k in n && typeof(n[k]) == typeof(json[name]):
n[k] = json[name]
_loading = false
if 'files' in json:
files = json.files
if get_container():
reload_vox()
func _on_files_dropped(files: PackedStringArray, screen: int) -> void:
for path in files:
if path.get_extension() == 'vox':
_on_FileDialog_file_load(0, path)
return
func _save_settings():
if _loading:
return
var settings := {}
for name in PERSIST_NODES:
var n = get_node_or_null('%%%s' % [name])
if !n:
msgerr('unable to find node to save settings: %s' % [name])
else:
for k in PERSIST_KEYS:
if k in n:
settings[name] = n[k]
settings.files = files
var f = FileAccess.open(SETTINGS_FILE, FileAccess.WRITE)
assert(f, 'Unable to open %s for writing: %s' % [SETTINGS_FILE, FileAccess.get_open_error()])
f.store_string(JSON.stringify(settings))
f.close()
func _process(delta):
# Called every frame. Delta is time since last frame.
# Update game logic here.
var vpsz := get_tree().root.size
var verts = Performance.get_monitor(Performance.RENDER_TOTAL_PRIMITIVES_IN_FRAME)
var meshes = Performance.get_monitor(Performance.RENDER_TOTAL_OBJECTS_IN_FRAME)
if get_node_or_null('MultiMeshInstance3D'):
var multimesh = ' (%sx multimesh)' % [$MultiMeshInstance3D.multimesh.instance_count] if $MultiMeshInstance3D.visible else ''
$Bench/Label.text = '%s fps %s verts %sx%s (~%s voxels %s mesh%s%s)' % [
Engine.get_frames_per_second(),
verts,
vpsz.x,
vpsz.y,
round(verts * 0.5),
meshes,
'es' if meshes != 1 else '',
multimesh
]
func get_container():
return %Grid
func _on_Scale_value_changed(value):
$Model.transform = Transform3D().scaled(Vector3(value, value, value))
func _on_PointSize_value_changed(value):
$Model.point_size = value
func _on_Zoom_value_changed(value):
$'%Camera3D'.transform.origin = Vector3(0, 0, value)
_save_settings()
func _on_CamTilt_value_changed(value):
$'%ZoomPlane'.rotation_degrees.x = value
_save_settings()
func _on_CamHeight_value_changed(value):
$'%Marker3D'.transform.origin = Vector3(0, value, 0)
_save_settings()
func _on_CheckBox_toggled(button_pressed):
$Characters.visible = button_pressed
func _on_CheckButton_toggled(button_pressed):
# don't use proper fullscreen because its harder for recording
get_window().borderless = button_pressed
get_window().mode = Window.MODE_MAXIMIZED if (button_pressed) else Window.MODE_WINDOWED
$Button2.visible = button_pressed
func _on_Button2_pressed():
get_tree().quit()
func _on_CheckButton2_toggled(button_pressed):
$AnimationPlayer.playback_active = button_pressed
func _on_FileDialog_file_load(index, path):
files[index] = path
$Model.mesh = await load_vox(path)
_save_settings()
func load_vox(path: String):
var container = get_container()
if container:
var smoothing_edit = container.get_node('NormalSmoothing') if container else null
var smoothing = smoothing_edit.edit_value if smoothing_edit else 1.0
var vi = VoxelImport.new()
%Loading.visible = true
await get_tree().process_frame
await get_tree().process_frame
await get_tree().process_frame
var mesh = vi.load_vox(path, {bone_map='', smoothing=smoothing})
if container:
set_show_normals(container.get_node('NormalDebug').edit_value, mesh)
%Loading.visible = false
await get_tree().process_frame
return mesh
func _on_Enable_toggled(button_pressed):
var container = get_container()
var edit = container.get_node('NormalDebug/Edit')
var nd = container.get_node('NormalDebug')
edit.visible = button_pressed
nd.edit_value = 1 if button_pressed else 0
func _on_Button_pressed():
reload_vox()
func _on_Background_pressed():
$BgFileDialog.show_modal()
func _on_BgFileDialog_file_load(index, path):
var tex = ImageTexture.new()
tex.load(path)
$'%Background'.texture = tex
_save_settings()
func reload_vox():
for index in files:
if int(index) == 0:
$Model.mesh = await load_vox(files[index])
func _on_Edit_value_changed(value):
if $Model:
set_show_normals(value, $Model.mesh)
func set_show_normals(value, mesh):
if mesh:
var mat = mesh.surface_get_material(0)
mat.set_shader_parameter('show_normals', value)
func _on_BenchmarkButton_toggled(button_pressed):
if !get_node_or_null('MultiMeshInstance3D'):
return
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if (false) else DisplayServer.VSYNC_DISABLED)
var mm := $MultiMeshInstance3D.multimesh as MultiMesh
$BenchmarkPos/Camera3D.current = button_pressed && !$Bench/Camera2CheckButton.pressed
$BenchmarkPos/Camera2.current = button_pressed && $Bench/Camera2CheckButton.pressed
$BenchmarkPos/Light3D.visible = button_pressed
$'%Camera3D'.current = !button_pressed
for c in get_children():
if c is Node3D:
c.visible = !button_pressed
$BenchmarkPos.visible = true
$MultiMeshInstance3D.visible = button_pressed
for c in $Bench.get_children():
c.visible = button_pressed || c in [$Bench/BenchmarkButton, $Bench/Label, $Bench/Spacer]
var spacing = 2.0 if $Bench/FarSpacingCheckButton.pressed else 0.5
if button_pressed:
# probably best if this is a power of 2
var count := 64
var side = ceil(sqrt(count))
mm.instance_count = side * side
var tfm = $MeshInstance4.global_transform
$AnimationPlayer.seek(0.0, true)
$AnimationPlayer2.seek(0.0, true)
await get_tree().process_frame
await get_tree().process_frame
$AnimationPlayer.stop()
$AnimationPlayer2.stop()
for x in range(side):
for y in range(side):
var i = x + (side * y)
var itfm = Transform3D().translated(spacing * Vector3(x - side * 0.5, 0, y - side * 0.5)) * tfm
mm.set_instance_transform(i, itfm)
else:
mm.instance_count = 0
$AnimationPlayer.play()
$AnimationPlayer2.play()
func _on_FastCheckButton_toggled(button_pressed):
var mat: ShaderMaterial = $MultiMeshInstance3D.multimesh.mesh.surface_get_material(0)
mat.set_shader_parameter('fast', button_pressed)
func _on_Camera2CheckButton_toggled(button_pressed):
$BenchmarkPos/Camera3D.current = !button_pressed
$BenchmarkPos/Camera2.current = button_pressed
func _on_FarSpacingCheckButton_toggled(button_pressed):
_on_BenchmarkButton_toggled(true)
func _on_LODBiasSlider_value_changed(value):
var mat: ShaderMaterial = $MultiMeshInstance3D.multimesh.mesh.surface_get_material(0)
mat.set_shader_parameter('lod_bias', value)
$Bench/LODBias.text = 'LOD Bias %s' % [value]
func _ease_in_out_quad(x: float):
return 2 * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 2) / 2;
func _on_SaveFileDialog_file_load(index, path: String):
var cb := $'%CaptureProgressBar' as ProgressBar
var cc := $'%CaptureCount' as SpinBox
var ap := $AnimationPlayer as AnimationPlayer
var p := $'%Marker3D'
var vp := $'%SubViewport' as SubViewport
var size := vp.size
vp.size = Vector2($'%ImageSizeX'.value, $'%ImageSizeY'.value)
var file_template := path
var file_base := file_template.get_basename().trim_suffix('0000')
var file_ext := file_template.get_extension()
if !file_ext:
file_ext = 'png'
ap.playback_active = false
var tex := ImageTexture.new()
cb.visible = true
cb.value = 0
cb.max_value = cc.value
for i in cc.value:
var raw_y := (float(i) / float(cc.value))
p.rotation_degrees.y = 360 * _ease_in_out_quad(raw_y)
var filename := '%s%04d.%s' % [file_base, i, file_ext]
await RenderingServer.frame_post_draw
var img := vp.get_texture().get_image()
img.convert(Image.FORMAT_RGBA8)
img.save_png(filename)
cb.value = i
ap.playback_active = true
vp.size = size
if %EncodeVideo.pressed:
cb.value = cb.max_value
var cmd := _find_ffmpeg()
var args := PackedStringArray([
'-framerate', '30',
'-pattern_type', 'sequence', '-i', '%s%%04d.%s' % [file_base, file_ext],
'-c:v', 'libvpx-vp9',
'-pix_fmt', 'yuv420p',
'-b:v', '1M',
'-y',
'%s.webm' % [file_base]
])
var args_str := ' '.join(args)
msg('Running Command:\n%s %s\n...' % [cmd, args_str])
var output = []
var result := OS.execute(cmd, args, output, true, true)
if !result == OK:
msgerr('Error: %s exited with %s' % [cmd, result])
msg("\n".join(output).strip_edges())
cb.visible = false
_save_settings()
func _find_ffmpeg() -> String:
var cmd := 'ffmpeg'
var app_path := OS.get_executable_path().get_base_dir()
var macos_path := '/Contents/MacOS'
if app_path.ends_with(macos_path):
app_path = "%s/.." % [app_path.trim_suffix(macos_path)]
var try_cmds := [
'/opt/local/bin/ffmpeg',
'/usr/local/bin/ffmpeg',
'/usr/bin/ffmpeg',
'%s/ffmpeg' % [app_path],
]
var result := OS.execute(cmd, ['-version'])
var tried := PackedStringArray()
if result != OK:
for ext in ['', '.exe']:
for tc in try_cmds:
var fc := "%s%s" % [tc, ext]
if FileAccess.file_exists(fc):
msg('Found ffmpeg at %s' % [fc])
cmd = fc
break
else:
tried.append(fc)
if cmd != 'ffmpeg':
break
if cmd == 'ffmpeg':
msgerr('Unable to find ffmpeg, checked these paths:\n%s' % ['\n'.join(tried)])
return cmd
func _on_CloseButton_pressed():
for n in get_tree().get_nodes_in_group('Menu'):
n.visible = true
queue_free()
func msg(value: String, error := false):
var m := get_node_or_null('%Messages')
if m:
m.text += "%s\n" % [value]
if error:
printerr(value)
else:
print(value)
func msgerr(value):
msg(value, true)