Skip to content

Commit

Permalink
Improve OMI_seat, add new node type for Area3D-derived seats
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronfranke committed Aug 8, 2024
1 parent 4bb762e commit 1828e42
Show file tree
Hide file tree
Showing 31 changed files with 917 additions and 88 deletions.
13 changes: 11 additions & 2 deletions addons/omi_extensions/omi_extensions_plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@
extends EditorPlugin


var seat_gizmo_plugin = EditorSeat3DGizmoPlugin.new()


func _enter_tree() -> void:
# NOTE: Be sure to also instance and register these at runtime if you want
# the extensions at runtime. This editor plugin script won't run in games.
var ext = GLTFDocumentExtensionOMISeat.new()
GLTFDocument.register_gltf_document_extension(ext)
var ext: GLTFDocumentExtension
ext = GLTFDocumentExtensionOMISeat.new()
GLTFDocument.register_gltf_document_extension(ext, true)
ext = GLTFDocumentExtensionOMISpawnPoint.new()
GLTFDocument.register_gltf_document_extension(ext)
ext = GLTFDocumentExtensionOMIPhysicsJoint.new()
GLTFDocument.register_gltf_document_extension(ext)
add_node_3d_gizmo_plugin(seat_gizmo_plugin)


func _exit_tree():
remove_node_3d_gizmo_plugin(seat_gizmo_plugin)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class_name GLTFDocumentExtensionOMISeat
extends GLTFDocumentExtension


# Import process.
func _import_preflight(_state: GLTFState, extensions: PackedStringArray) -> Error:
if extensions.has("OMI_seat"):
return OK
Expand All @@ -13,27 +14,46 @@ func _get_supported_extensions() -> PackedStringArray:
return PackedStringArray(["OMI_seat"])


func _import_node(_state: GLTFState, _gltf_node: GLTFNode, json: Dictionary, node: Node) -> Error:
if not json.has("extensions"):
return OK
var extensions = json.get("extensions")
if not extensions is Dictionary:
printerr("Error: GLTF file is invalid, extensions should be a Dictionary.")
return ERR_FILE_CORRUPT
func _parse_node_extensions(state: GLTFState, gltf_node: GLTFNode, extensions: Dictionary) -> Error:
if not extensions.has("OMI_seat"):
return OK
var seat_dict = extensions.get("OMI_seat")
var seat_dict = extensions["OMI_seat"]
if not seat_dict is Dictionary:
printerr("Error: OMI_seat extension should be a Dictionary.")
return ERR_FILE_CORRUPT
if convert_seat(seat_dict):
node.set_meta(&"OMI_seat", seat_dict)
else:
if not _convert_seat_dict(seat_dict):
return ERR_FILE_CORRUPT
gltf_node.set_additional_data(&"OMI_seat", seat_dict)
return OK


static func convert_seat(seat_dict: Dictionary) -> bool:
func _generate_scene_node(state: GLTFState, gltf_node: GLTFNode, scene_parent: Node) -> Node3D:
var seat_dict = gltf_node.get_additional_data(&"OMI_seat")
if seat_dict == null:
return null
# If this node is both a seat and a glTF trigger, generate the Area3D-derived Seat3D node.
# Else, if this is not any kind of trigger, don't generate a Seat3D/Area3D, just set node metadata later.
var trigger = gltf_node.get_additional_data(&"GLTFPhysicsTrigger")
if trigger == null:
trigger = gltf_node.get_additional_data(&"GLTFPhysicsCompoundTriggerNodes")
if trigger == null:
trigger = gltf_node.get_additional_data(&"GLTFPhysicsBody")
if trigger == null:
return null
if trigger.body_type != "trigger":
return null
return Seat3D.from_points(seat_dict["back"], seat_dict["foot"], seat_dict["knee"], seat_dict.get("angle", TAU * 0.25))


func _import_node(_state: GLTFState, gltf_node: GLTFNode, json: Dictionary, node: Node) -> Error:
var seat_dict = gltf_node.get_additional_data(&"OMI_seat")
if seat_dict != null:
node.set_meta(&"OMI_seat", seat_dict)
return OK


static func _convert_seat_dict(seat_dict: Dictionary) -> bool:
# The JSON data has Vector3s represented as arrays, let's convert them to Vector3.
if not _convert_seat_vector(seat_dict, "foot"):
return false
if not _convert_seat_vector(seat_dict, "knee"):
Expand Down Expand Up @@ -80,29 +100,45 @@ static func _calculate_helper_vectors(seat_dict: Dictionary) -> void:
seat_dict["lower_leg_norm"] = lower_leg_norm


# Export process.
func _export_node(state: GLTFState, _gltf_node: GLTFNode, json: Dictionary, node: Node) -> Error:
if node.has_meta(&"OMI_seat"):
var omi_seat_ext: Dictionary = _export_omi_seat(node.get_meta(&"OMI_seat"))
# Write to the GLTF node JSON.
var extensions: Dictionary
if "extensions" in json:
extensions = json["extensions"]
else:
extensions = {}
json["extensions"] = extensions
extensions["OMI_seat"] = omi_seat_ext
state.add_used_extension("OMI_seat", false)
var omi_seat_ext: Dictionary = _export_omi_seat_from_node(node)
if omi_seat_ext.is_empty():
return OK
# Write to the GLTF node JSON.
var extensions: Dictionary
if "extensions" in json:
extensions = json["extensions"]
else:
extensions = {}
json["extensions"] = extensions
extensions["OMI_seat"] = omi_seat_ext
state.add_used_extension("OMI_seat", false)
return OK


func _export_omi_seat(omi_seat_meta: Dictionary) -> Dictionary:
var back: Vector3 = omi_seat_meta["back"]
var foot: Vector3 = omi_seat_meta["foot"]
var knee: Vector3 = omi_seat_meta["knee"]
func _export_omi_seat_from_node(node: Node) -> Dictionary:
var omi_seat_ext: Dictionary = {}
var back: Vector3
var foot: Vector3
var knee: Vector3
var angle: float
if node is Seat3D:
back = node.back
foot = node.foot
knee = node.knee
angle = node.angle
elif node.has_meta(&"OMI_seat"):
var omi_seat_meta = node.get_meta(&"OMI_seat")
back = omi_seat_meta["back"]
foot = omi_seat_meta["foot"]
knee = omi_seat_meta["knee"]
angle = omi_seat_meta["angle"]
else:
return omi_seat_ext
omi_seat_ext["back"] = [back.x, back.y, back.z]
omi_seat_ext["foot"] = [foot.x, foot.y, foot.z]
omi_seat_ext["knee"] = [knee.x, knee.y, knee.z]
if not is_equal_approx(omi_seat_meta["angle"], TAU * 0.25):
omi_seat_ext["angle"] = omi_seat_meta["angle"]
if not is_equal_approx(angle, TAU * 0.25):
omi_seat_ext["angle"] = angle
return omi_seat_ext
115 changes: 115 additions & 0 deletions addons/omi_extensions/seat/seat_3d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
## Defines a seat using a set of control points. Characters can sit on this seat by interacting with it.
@tool
class_name Seat3D
extends Area3D


## The character occupying the seat, if any. This is expected to be assigned at runtime.
## Change the type if your characters do not inherit CharacterBody3D.
var occupant: CharacterBody3D

var _back := Vector3(0.0, 0.0, -0.25)
## The seat position control point corresponding to the character's back position.
@export_custom(PROPERTY_HINT_NONE, "suffix:m")
var back := Vector3(0.0, 0.0, -0.25):
get:
return _back
set(value):
_back = value
_recalculate_helper_vectors()

var _foot := Vector3(0.0, -0.5, 0.25)
## The seat position control point corresponding to the character's foot position.
@export_custom(PROPERTY_HINT_NONE, "suffix:m")
var foot := Vector3(0.0, -0.5, 0.25):
get:
return _foot
set(value):
_foot = value
_recalculate_helper_vectors()

var _knee := Vector3(0.0, 0.0, 0.25)
## The seat position control point corresponding to the character's knee position.
@export_custom(PROPERTY_HINT_NONE, "suffix:m")
var knee := Vector3(0.0, 0.0, 0.25):
get:
return _knee
set(value):
_knee = value
_recalculate_helper_vectors()

var _angle: float = TAU * 0.25
## The seat angle between the spine and the back-knee line. Recommended values are close to 90 degrees, and going over is better than going under.
@export_range(10.0, 180.0, 0.1, "radians_as_degrees")
var angle: float = TAU * 0.25:
get:
return _angle
set(value):
_angle = value
_recalculate_helper_vectors()

var _right := Vector3.ZERO
var _spine_dir := Vector3.ZERO
var _spine_norm := Vector3.ZERO
var _upper_leg_dir := Vector3.ZERO
var _upper_leg_norm := Vector3.ZERO
var _lower_leg_dir := Vector3.ZERO
var _lower_leg_norm := Vector3.ZERO


func _init() -> void:
_recalculate_helper_vectors()


func get_right() -> Vector3:
return _right


func get_spine_dir() -> Vector3:
return _spine_dir


func get_spine_norm() -> Vector3:
return _spine_norm


func get_upper_leg_dir() -> Vector3:
return _upper_leg_dir


func get_upper_leg_norm() -> Vector3:
return _upper_leg_norm


func get_lower_leg_dir() -> Vector3:
return _lower_leg_dir


func get_lower_leg_norm() -> Vector3:
return _lower_leg_norm


func set_points(p_back: Vector3, p_foot: Vector3, p_knee: Vector3, p_angle: float = TAU * 0.25) -> void:
_back = p_back
_foot = p_foot
_knee = p_knee
_angle = p_angle
_recalculate_helper_vectors()


func _recalculate_helper_vectors() -> void:
_upper_leg_dir = back.direction_to(knee)
_lower_leg_dir = knee.direction_to(foot)
_right = _lower_leg_dir.cross(_upper_leg_dir).normalized()
if _right == Vector3.ZERO:
return
_spine_dir = _upper_leg_dir.rotated(_right, angle)
_spine_norm = _spine_dir.cross(_right)
_upper_leg_norm = _right.cross(_upper_leg_dir)
_lower_leg_norm = _right.cross(_lower_leg_dir)


static func from_points(p_back: Vector3, p_foot: Vector3, p_knee: Vector3, p_angle: float = TAU * 0.25) -> Seat3D:
var seat = Seat3D.new()
seat.set_points(p_back, p_foot, p_knee, p_angle)
return seat
Loading

0 comments on commit 1828e42

Please sign in to comment.