diff --git a/model/dummies/tracking_backend_dummy.gd b/model/dummies/tracking_backend_dummy.gd index 51ff6ced..35a271a2 100644 --- a/model/dummies/tracking_backend_dummy.gd +++ b/model/dummies/tracking_backend_dummy.gd @@ -1,22 +1,27 @@ -class_name TrackingBackendDummy -extends TrackingBackendInterface +extends TrackingBackendTrait -## Non-functional TrackingBackendInterface loaded by default in the RunnerTrait +## Non-functional TrackingBackendTrait ## ## @see: `RunnerTrait` +var logger := Logger.new("TrackingBackendDummy") + func get_name() -> String: - AM.logger.error("get_name not yet implemented") + logger.error("get_name not yet implemented") return "" func start_receiver() -> void: - AM.logger.error("start_receiver not yet implemented") + logger.error("start_receiver not yet implemented") func stop_receiver() -> void: - AM.logger.error("stop_reciever not yet implemented") + logger.error("stop_reciever not yet implemented") + +func set_offsets() -> void: + logger.error("set_offsets not yet implemented") -func set_offsets(_offsets: StoredOffsets) -> void: - AM.logger.error("set_offsets not yet implemented") +func has_data() -> bool: + logger.error("has_data not yet implemented") + return false -func apply(_model: PuppetTrait, _interpolation_data: InterpolationData, _extra: Dictionary) -> void: - AM.logger.error("apply not yet implemented") +func apply(_interpolation_data: InterpolationData, _model: PuppetTrait) -> void: + logger.error("apply not yet implemented") diff --git a/model/error.gd b/model/error.gd index 401e2517..b0eaaa68 100644 --- a/model/error.gd +++ b/model/error.gd @@ -128,7 +128,7 @@ enum Code { RUNNER_NO_LOADERS_FOUND, RUNNER_FILE_NOT_FOUND, RUNNER_LOAD_FILE_FAILED, - RUNNER_UNHANDLED_FILE_FORMAT + RUNNER_UNHANDLED_FILE_FORMAT, RUNNER_NO_PREVIEW_IMAGE_FOUND, #endregion diff --git a/model/extensions/runner_trait.gd b/model/extensions/runner_trait.gd index f4641035..c905d884 100644 --- a/model/extensions/runner_trait.gd +++ b/model/extensions/runner_trait.gd @@ -7,9 +7,9 @@ var logger: Logger # TODO this should be stored on the model var current_model_path := "" -## Array of TrackingBackendInterfaces -var trackers := [] -var main_tracker: TrackingBackendInterface +## @type: Dictionary +var trackers := {} +var main_tracker: TrackingBackendTrait #-----------------------------------------------------------------------------# # Builtin functions # @@ -72,8 +72,9 @@ func _teardown() -> void: _generate_preview() main_tracker = null - for tracker in trackers: - if not tracker is TrackingBackendInterface: + for tracker in trackers.values(): + if not tracker is TrackingBackendTrait: + logger.error("Tracker %s does not inherit from TrackingBackendTrait" % str(tracker)) continue tracker.stop_receiver() trackers.clear() @@ -226,22 +227,6 @@ func _try_load_model(path: String) -> Result: func load_model(_path: String) -> Result: return Result.err(Error.Code.NOT_YET_IMPLEMENTED, "load_model") -## Uses the built-in gltf loader to load a `glb` model -## -## @param: path: String - The absolute path to a model -## -## @return: Result - The loaded model -func load_glb(path: String) -> Result: - logger.info("Using glb loader") - - var gltf_loader := PackedSceneGLTF.new() - - var model = gltf_loader.import_gltf_scene(path) - if model == null: - return Safely.err(Error.Code.RUNNER_LOAD_FILE_FAILED) - - return Safely.ok(model) - ## Uses the built-in scene loader to load a PackedScene ## ## @param: path: String - The absolute path to a PackedScene diff --git a/model/extensions/tracking_backend_interface.gd b/model/extensions/tracking_backend_interface.gd deleted file mode 100644 index b0b04963..00000000 --- a/model/extensions/tracking_backend_interface.gd +++ /dev/null @@ -1,22 +0,0 @@ -class_name TrackingBackendInterface -extends Reference - -## Interface for defining tracking backends - -func get_name() -> String: - printerr("get_name not yet implemented") - return "" - -## Start the receiver and thus start listening for data -func start_receiver() -> void: - printerr("start_receiver not yet implemented") - -## Stop the receiver and thus stop listening for data -func stop_receiver() -> void: - printerr("stop_reciever not yet implemented") - -func set_offsets(_offsets: StoredOffsets) -> void: - printerr("set_offsets not yet implemented") - -func apply(_model: PuppetTrait, _interpolation_data: InterpolationData, _extra: Dictionary) -> void: - printerr("apply not yet implemented") diff --git a/model/extensions/tracking_backend_trait.gd b/model/extensions/tracking_backend_trait.gd new file mode 100644 index 00000000..f8e9d7d0 --- /dev/null +++ b/model/extensions/tracking_backend_trait.gd @@ -0,0 +1,32 @@ +class_name TrackingBackendTrait +extends Reference + +var stored_offsets := StoredOffsets.new() + +## Interface for defining tracking backends + +func get_name() -> String: + printerr("get_name not yet implemented") + return "" + +## Start the receiver and thus start listening for data +func start_receiver() -> void: + printerr("start_receiver not yet implemented") + +## Stop the receiver and thus stop listening for data +func stop_receiver() -> void: + printerr("stop_reciever not yet implemented") + +## Sets the stored offsets based off of the current data +func set_offsets() -> void: + printerr("set_offsets not yet implemented") + +## Determines if there is tracking data +func has_data() -> bool: + printerr("has_data not yet implemented") + return false + +## Called by the Runner to apply tracking data. The model is also passed to allow for +## blend shapes to be directly applied, if applicable +func apply(_interpolation_data: InterpolationData, _model: PuppetTrait) -> void: + printerr("apply not yet implemented") diff --git a/project.godot b/project.godot index 7f65b5c3..6c717bec 100644 --- a/project.godot +++ b/project.godot @@ -194,15 +194,10 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://utils/temp_cache_manager.gd" }, { -"base": "TrackingBackendInterface", -"class": "TrackingBackendDummy", -"language": "GDScript", -"path": "res://model/dummies/tracking_backend_dummy.gd" -}, { "base": "Reference", -"class": "TrackingBackendInterface", +"class": "TrackingBackendTrait", "language": "GDScript", -"path": "res://model/extensions/tracking_backend_interface.gd" +"path": "res://model/extensions/tracking_backend_trait.gd" }, { "base": "AbstractManager", "class": "TranslationManager", @@ -252,8 +247,7 @@ _global_script_class_icons={ "StoredOffsets": "", "Sun": "", "TempCacheManager": "", -"TrackingBackendDummy": "", -"TrackingBackendInterface": "", +"TrackingBackendTrait": "", "TranslationManager": "", "VRMTopLevel": "" } diff --git a/resources/extensions/i_facial_mocap/gui.gd b/resources/extensions/i_facial_mocap/gui.gd index ba7fad06..ae8eb5ad 100644 --- a/resources/extensions/i_facial_mocap/gui.gd +++ b/resources/extensions/i_facial_mocap/gui.gd @@ -108,24 +108,28 @@ func _toggle_tracking() -> Button: func _on_toggle_tracking(button: Button) -> void: var trackers = get_tree().current_scene.get("trackers") - if typeof(trackers) != TYPE_ARRAY: + if typeof(trackers) != TYPE_DICTIONARY: logger.error(tr("I_FACIAL_MOCAP_INCOMPATIBLE_RUNNER_ERROR")) return - var tracker: TrackingBackendInterface + var tracker: TrackingBackendTrait var found := false - for i in trackers: - if i.get_name() == "iFacialMocap" and i is TrackingBackendInterface: + for i in trackers.values(): + if i is TrackingBackendTrait and i.get_name() == "iFacialMocap": tracker = i found = true break if found: + logger.debug("Stopping ifm tracker") + tracker.stop_receiver() - trackers.erase(tracker) + trackers.erase(tracker.get_name()) button.text = tr("I_FACIAL_MOCAP_TOGGLE_TRACKING_BUTTON_START") else: + logger.debug("Starting ifm tracker") + var res: Result = Safely.wrap(AM.em.load_resource("iFacialMocap", "ifm.gd")) if res.is_err(): logger.error(res) @@ -133,7 +137,7 @@ func _on_toggle_tracking(button: Button) -> void: var ifm = res.unwrap().new() - trackers.append(ifm) + trackers[ifm.get_name()] = ifm button.text = tr("I_FACIAL_MOCAP_TOGGLE_TRACKING_BUTTON_STOP") diff --git a/resources/extensions/i_facial_mocap/ifm.gd b/resources/extensions/i_facial_mocap/ifm.gd index bfb9610f..e9558ac7 100644 --- a/resources/extensions/i_facial_mocap/ifm.gd +++ b/resources/extensions/i_facial_mocap/ifm.gd @@ -1,4 +1,4 @@ -extends TrackingBackendInterface +extends TrackingBackendTrait ## Reference: https://www.ifacialmocap.com/for-developer/ ## @@ -20,6 +20,8 @@ extends TrackingBackendInterface # TODO Check if these can be ints or not ## Data from an ifm packet. Variables are defined and named in the order they are encounted in the packet class IFacialMocapData: + var has_data := false + var blend_shapes := {} var head_rotation := Vector3.ZERO @@ -105,8 +107,15 @@ func _receive() -> void: server.poll() if connection != null: var packet := connection.get_packet() + if connection.get_packet_error() != OK: + logger.error("Last packet had an error: %d" % connection.get_packet_error()) + connection.close() + connection = null + return if packet.size() < 1: return + + ifm_data.has_data = true var split := packet.get_string_from_utf8().split("|") for pair in split: @@ -193,13 +202,16 @@ func stop_receiver() -> void: server.stop() server = null -func set_offsets(offsets: StoredOffsets) -> void: - offsets.translation_offset = ifm_data.head_position - offsets.rotation_offset = ifm_data.head_rotation - offsets.left_eye_gaze_offset = ifm_data.left_eye_rotation - offsets.right_eye_gaze_offset = ifm_data.right_eye_rotation +func set_offsets() -> void: + stored_offsets.translation_offset = ifm_data.head_position + stored_offsets.rotation_offset = ifm_data.head_rotation + stored_offsets.left_eye_gaze_offset = ifm_data.left_eye_rotation + stored_offsets.right_eye_gaze_offset = ifm_data.right_eye_rotation + +func has_data() -> bool: + return ifm_data.has_data -func apply(_model: PuppetTrait, interpolation_data: InterpolationData, _extra: Dictionary) -> void: +func apply(interpolation_data: InterpolationData, _model: PuppetTrait) -> void: interpolation_data.bone_translation.target_value = ifm_data.head_position interpolation_data.bone_rotation.target_value = ifm_data.head_rotation diff --git a/resources/extensions/open_see_face/data/tracking_gui_descriptor.gd b/resources/extensions/open_see_face/data/tracking_gui_descriptor.gd index 38297b85..40d14958 100644 --- a/resources/extensions/open_see_face/data/tracking_gui_descriptor.gd +++ b/resources/extensions/open_see_face/data/tracking_gui_descriptor.gd @@ -11,11 +11,13 @@ const ConfigKeys := { "MODEL": "open_see_face_model" } +var logger := Logger.new("OpenSeeFaceGUI") + func _init() -> void: for val in ConfigKeys.values(): var res: Result = Safely.wrap(AM.cm.runtime_subscribe_to_signal(val)) if res.is_err() and res.unwrap_err().code != Error.Code.PUB_SUB_ALREADY_CONNECTED: - AM.logger.error(res) + logger.error(res) return _hv_fill_expand(self) @@ -155,7 +157,7 @@ func _on_camera_selected(idx: int, ob: OptionButton) -> void: var current_index: int = popup.get_current_index() if current_index < 0: - AM.logger.error(tr("CAMERA_SELECTED_NO_PREVIOUSLY_SELECTED_ITEM")) + logger.error(tr("CAMERA_SELECTED_NO_PREVIOUSLY_SELECTED_ITEM")) else: popup.set_item_checked(current_index, false) @@ -271,14 +273,14 @@ func _toggle_tracking() -> Button: func _on_toggle_tracking(button: Button) -> void: var trackers = get_tree().current_scene.get("trackers") - if typeof(trackers) != TYPE_ARRAY: - AM.logger.error(tr("TOGGLE_TRACKING_INCOMPATIBLE_RUNNER_ERROR")) + if typeof(trackers) != TYPE_DICTIONARY: + logger.error(tr("TOGGLE_TRACKING_INCOMPATIBLE_RUNNER_ERROR")) return - var tracker: TrackingBackendInterface + var tracker: TrackingBackendTrait var found := false - for i in trackers: - if i.get_name() == "OpenSeeFace" and i is TrackingBackendInterface: + for i in trackers.values(): + if i is TrackingBackendTrait and i.get_name() == "OpenSeeFace": tracker = i found = true break @@ -287,19 +289,23 @@ func _on_toggle_tracking(button: Button) -> void: # is being turned off. Thus if the button were to be pressed again, it would be for # starting the tracker if found: + logger.debug("Stopping osf tracker") + tracker.stop_receiver() - trackers.erase(tracker) + trackers.erase(tracker.get_name()) button.text = tr("TOGGLE_TRACKING_BUTTON_TEXT_START") else: + logger.debug("Starting osf tracker") + var osf_res: Result = AM.em.load_resource("OpenSeeFace", "open_see_face.gd") if not osf_res or osf_res.is_err(): - AM.logger.error(tr("TOGGLE_TRACKING_LOAD_TRACKER_ERROR")) + logger.error(tr("TOGGLE_TRACKING_LOAD_TRACKER_ERROR")) return var osf = osf_res.unwrap().new() - trackers.append(osf) + trackers[osf.get_name()] = osf button.text = tr("TOGGLE_TRACKING_BUTTON_TEXT_STOP") @@ -372,7 +378,7 @@ func _on_model_selected(idx: int, ob: OptionButton) -> void: var current_index: int = popup.get_current_index() if current_index < 0: - AM.logger.error(tr("ML_MODEL_SELECT_NO_PREVIOUS_MODEL_SELECTED_ERROR")) + logger.error(tr("ML_MODEL_SELECT_NO_PREVIOUS_MODEL_SELECTED_ERROR")) else: popup.set_item_checked(current_index, false) @@ -380,11 +386,11 @@ func _on_model_selected(idx: int, ob: OptionButton) -> void: var split: PoolStringArray = popup.get_item_text(idx).split(":") if split.size() < 2: - AM.logger.error(tr("ML_MODEL_SELECT_INVALID_MODEL_SELECTED") % str(split)) + logger.error(tr("ML_MODEL_SELECT_INVALID_MODEL_SELECTED") % str(split)) return if not split[0].is_valid_integer(): - AM.logger.error(tr("ML_MODEL_SELECT_NO_PRECEDING_INTEGER")) + logger.error(tr("ML_MODEL_SELECT_NO_PRECEDING_INTEGER")) return AM.ps.publish(ConfigKeys.MODEL, split[0].to_int()) diff --git a/resources/extensions/open_see_face/open_see_face.gd b/resources/extensions/open_see_face/open_see_face.gd index 265ea01c..29967666 100644 --- a/resources/extensions/open_see_face/open_see_face.gd +++ b/resources/extensions/open_see_face/open_see_face.gd @@ -1,4 +1,4 @@ -extends TrackingBackendInterface +extends TrackingBackendTrait class OSFData: const NUMBER_OF_POINTS: int = 68 @@ -192,15 +192,12 @@ var connection: PacketPeerUDP # Must be taken when running the server var receive_thread: Thread # Must be created when starting tracking var reception_counter: float = 0.0 - var stop_reception := false var face_tracker_pid: int = -1 var data_map := {} # Face id: int -> OpenSeeFaceData -# var open_see_face_data: GDScript - var updated_time: float = 0.0 #-----------------------------------------------------------------------------# @@ -388,50 +385,32 @@ func stop_receiver() -> void: server.stop() server = null -func set_offsets(offsets: StoredOffsets) -> void: +func set_offsets() -> void: var data: OSFData = data_map.get(0, null) if data == null: return - offsets.translation_offset = data.translation - offsets.rotation_offset = data.rotation - offsets.left_eye_gaze_offset = data.left_gaze.get_euler() - offsets.right_eye_gaze_offset = data.right_gaze.get_euler() + stored_offsets.translation_offset = data.translation + stored_offsets.rotation_offset = data.rotation + stored_offsets.left_eye_gaze_offset = data.left_gaze.get_euler() + stored_offsets.right_eye_gaze_offset = data.right_gaze.get_euler() + +func has_data() -> bool: + return not data_map.empty() -func apply(_model: PuppetTrait, interpolation_data: InterpolationData, extra: Dictionary) -> void: +func apply(data: InterpolationData, _model: PuppetTrait) -> void: var osf_data: OSFData = data_map.get(0, null) if osf_data == null or osf_data.fit_3d_error > 100.0: return - - var features := osf_data.features - - # TODO rethink this, blindly grabbing data is bad - var stored_offsets: StoredOffsets = extra.stored_offsets - - if osf_data.time > updated_time: - updated_time = osf_data.time - - interpolation_data.update_values( - updated_time, - - stored_offsets.translation_offset - osf_data.translation, - stored_offsets.rotation_offset - osf_data.rotation, - - stored_offsets.left_eye_gaze_offset - osf_data.left_gaze.get_euler(), - stored_offsets.right_eye_gaze_offset - osf_data.right_gaze.get_euler(), - - osf_data.left_eye_open, - osf_data.right_eye_open, - features.mouth_open, - features.mouth_wide, + data.bone_translation.target_value = stored_offsets.translation_offset - osf_data.translation + data.bone_rotation.target_value = stored_offsets.rotation_offset - osf_data.rotation - features.eyebrow_steepness_left, - features.eyebrow_steepness_right, + data.left_gaze.target_value = stored_offsets.left_eye_gaze_offset - osf_data.left_gaze.get_euler() + data.right_gaze.target_value = stored_offsets.right_eye_gaze_offset - osf_data.right_gaze.get_euler() - features.eyebrow_up_down_left, - features.eyebrow_up_down_right, + data.left_blink.target_value = osf_data.left_eye_open + data.right_blink.target_value = osf_data.right_eye_open - features.eyebrow_quirk_left, - features.eyebrow_quirk_right - ) + data.mouth_open.target_value = osf_data.features.mouth_open + data.mouth_wide.target_value = osf_data.features.mouth_wide diff --git a/screens/default_runner.gd b/screens/default_runner.gd index a2fc0faf..22b4d799 100644 --- a/screens/default_runner.gd +++ b/screens/default_runner.gd @@ -206,11 +206,8 @@ func _physics_step(_delta: float) -> void: if trackers.empty(): return - for tracker in trackers: - tracker.apply(model, interpolation_data, { - "stored_offsets": stored_offsets, - "runner": self - }) + for tracker in trackers.values(): + tracker.apply(interpolation_data, model) model.custom_update(interpolation_data) @@ -296,8 +293,20 @@ func _on_event_published(payload: SignalPayload) -> void: Globals.POSE_BONE: should_pose_model = payload.data bone_to_pose = model.skeleton.find_bone(payload.id) + Globals.TRACKER_TOGGLED: + if payload.data == false: + return + if not trackers.has(payload.id): + logger.error("Tried to set offsets on non-existent tracker %s" % payload.id) + return + + var tracker: TrackingBackendTrait = trackers[payload.id] + while not tracker.has_data(): + yield(get_tree(), "idle_frame") + + tracker.set_offsets() Globals.TRACKER_USE_AS_MAIN_TRACKER: - for tracker in trackers: + for tracker in trackers.values(): if tracker.get_name() == payload.data: main_tracker = tracker return @@ -332,11 +341,8 @@ func _on_event_published(payload: SignalPayload) -> void: #-----------------------------------------------------------------------------# func _save_offsets() -> void: - if main_tracker == null: - logger.error("No main tracker defined") - return - - main_tracker.set_offsets(stored_offsets) + for tracker in trackers.values(): + tracker.set_offsets() #-----------------------------------------------------------------------------# # Public functions # @@ -380,21 +386,30 @@ func load_model(path: String) -> Result: return Safely.ok() +## Uses the built-in gltf loader to load a `glb` model +## +## @param: path: String - The absolute path to a model +## +## @return: Result - The loaded model func load_glb(path: String) -> Result: - var res := .load_glb(path) - if res.is_err(): - return res + logger.info("Using glb loader") + + var gltf_loader := PackedSceneGLTF.new() + + var node: Node = gltf_loader.import_gltf_scene(path) + if node == null: + return Safely.err(Error.Code.RUNNER_LOAD_FILE_FAILED) var script: GDScript = load(PUPPET_TRAIT_SCRIPT_PATH) if script == null: return Safely.err(Error.Code.RUNNER_LOAD_FILE_FAILED, "Unable to load puppet trait script") - res.unwrap().set_script(script) + node.set_script(script) translation_adjustment = Vector3.ONE rotation_adjustment = Vector3(-1, -1, 1) - return res + return Safely.ok(node) func load_scn(path: String) -> Result: var res := .load_scn(path) diff --git a/tests/test_resources/extension_resources/good_extensions/test_extension/tracking_backend_dummy.gd b/tests/test_resources/extension_resources/good_extensions/test_extension/tracking_backend_dummy.gd index 97342ff0..6c67090f 100644 --- a/tests/test_resources/extension_resources/good_extensions/test_extension/tracking_backend_dummy.gd +++ b/tests/test_resources/extension_resources/good_extensions/test_extension/tracking_backend_dummy.gd @@ -1,4 +1,4 @@ -extends TrackingBackendInterface +extends TrackingBackendTrait func is_listening() -> bool: return true diff --git a/tests/test_resources/extension_resources/good_extensions/test_extension/tracking_entrypoint.gd b/tests/test_resources/extension_resources/good_extensions/test_extension/tracking_entrypoint.gd index ad8fc9cf..e497f512 100644 --- a/tests/test_resources/extension_resources/good_extensions/test_extension/tracking_entrypoint.gd +++ b/tests/test_resources/extension_resources/good_extensions/test_extension/tracking_entrypoint.gd @@ -1,4 +1,4 @@ extends Reference -func get_tracking_backend() -> TrackingBackendInterface: +func get_tracking_backend() -> TrackingBackendTrait: return AM.em.load_resource("TestExtension", "tracking_backend_dummy.gd").expect("Unable to load").new()