Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scene grabbables support #282

Merged
merged 13 commits into from
May 8, 2024
1 change: 0 additions & 1 deletion addons/io_hubs_addon/components/definitions/ammo_shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ class AmmoShape(HubsComponent):
_definition = {
'name': 'ammo-shape',
'display_name': 'Ammo Shape',
'category': Category.OBJECT,
'node_type': NodeType.NODE,
'panel_type': [PanelType.OBJECT, PanelType.BONE],
'icon': 'SCENE_DATA',
Expand Down
26 changes: 26 additions & 0 deletions addons/io_hubs_addon/components/definitions/grabbable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from bpy.props import BoolProperty
from ..hubs_component import HubsComponent
from ..types import NodeType, PanelType, Category


class Grabbable(HubsComponent):
_definition = {
'name': 'grabbable',
'display_name': 'Grabbable',
'category': Category.OBJECT,
'node_type': NodeType.NODE,
'panel_type': [PanelType.OBJECT],
'icon': 'VIEW_PAN',
'deps': ['rigidbody', 'networked-transform'],
'version': (1, 0, 0)
}

cursor: BoolProperty(
name="By Cursor", description="Can be grabbed by a cursor", default=True)

hand: BoolProperty(
name="By Hand", description="Can be grabbed by VR hands", default=True)

@classmethod
def init(cls, obj):
obj.hubs_component_list.items.get('rigidbody').isDependency = True
14 changes: 14 additions & 0 deletions addons/io_hubs_addon/components/definitions/networked_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from io_hubs_addon.components.hubs_component import HubsComponent
from io_hubs_addon.components.types import NodeType, PanelType


class NetworkedTransform(HubsComponent):
_definition = {
'name': 'networked-transform',
'display_name': 'Networked Transform',
'node_type': NodeType.NODE,
'panel_type': [PanelType.OBJECT],
'icon': 'EMPTY_AXIS',
'deps': ['networked'],
'version': (1, 0, 0)
}
118 changes: 118 additions & 0 deletions addons/io_hubs_addon/components/definitions/physics_shape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from bpy.props import BoolProperty, FloatProperty, EnumProperty, FloatVectorProperty
from ..hubs_component import HubsComponent
from ..types import NodeType, PanelType, Category
from mathutils import Vector


class PhysicsShape(HubsComponent):
_definition = {
'name': 'physics-shape',
'display_name': 'Physics Shape',
'category': Category.OBJECT,
'node_type': NodeType.NODE,
'panel_type': [PanelType.OBJECT, PanelType.BONE],
'icon': 'SCENE_DATA',
'version': (1, 0, 1)
}

type: EnumProperty(
name="Type", description="Type",
items=[("box", "Box Collider", "A box-shaped primitive collision shape"),
("sphere", "Sphere Collider", "A primitive collision shape which represents a sphere"),
("hull", "Convex Hull",
"A convex hull wrapped around the object's vertices. A good analogy for a convex hull is an elastic membrane or balloon under pressure which is placed around a given set of vertices. When released the membrane will assume the shape of the convex hull"),
("mesh", "Mesh Collider",
"A shape made of the actual vertices of the object. This can be expensive for large meshes")],
default="hull")

fit: EnumProperty(
name="Fit Mode",
description="Shape fitting mode",
items=[("all", "Automatic fit all", "Automatically match the shape to fit the object's vertices"),
("manual", "Manual", "Use the manually specified dimensions to define the shape, ignoring the object's vertices")],
default="all")

halfExtents: FloatVectorProperty(
name="Half Extents",
description="Half dimensions of the collider. (Only used when fit is set to \"manual\" and type is set to \"box\")",
unit='LENGTH',
subtype="XYZ",
default=(0.5, 0.5, 0.5))

minHalfExtent: FloatProperty(
name="Min Half Extent",
description="The minimum size to use when automatically generating half extents. (Only used when fit is set to \"all\" and type is set to \"box\")",
unit="LENGTH",
default=0.0)

maxHalfExtent: FloatProperty(
name="Max Half Extent",
description="The maximum size to use when automatically generating half extents. (Only used when fit is set to \"all\" and type is set to \"box\")",
unit="LENGTH",
default=1000.0)

sphereRadius: FloatProperty(
name="Sphere Radius",
description="Radius of the sphere collider. (Only used when fit is set to \"manual\" and type is set to \"sphere\")",
unit="LENGTH", default=0.5)

offset: FloatVectorProperty(
name="Offset", description="An offset to apply to the collider relative to the object's origin",
unit='LENGTH',
subtype="XYZ",
default=(0.0, 0.0, 0.0))

includeInvisible: BoolProperty(
name="Include Invisible",
description="Include invisible objects when generating a collider. (Only used if \"fit\" is set to \"all\")",
default=False)

def draw(self, context, layout, panel):
layout.prop(self, "type")
layout.prop(self, "fit")
if self.fit == "manual":
if self.type == "box":
layout.prop(self, "halfExtents")
elif self.type == "sphere":
layout.prop(self, "sphereRadius")
else:
if self.type == "box":
layout.prop(self, "minHalfExtent")
layout.prop(self, "maxHalfExtent")
layout.prop(self, "includeInvisible")
layout.prop(self, "offset")

if self.fit == "manual" and (self.type == "mesh" or self.type == "hull"):
col = layout.column()
col.alert = True
col.label(
text="'Hull' and 'Mesh' do not support 'manual' fit mode", icon='ERROR')

def gather(self, export_settings, object):
props = super().gather(export_settings, object)
props['offset'] = {
'x': self.offset[0],
'y': self.offset[2] if export_settings['gltf_yup'] else self.offset[1],
'z': self.offset[1] if export_settings['gltf_yup'] else self.offset[2],
}
props['halfExtents'] = {
'x': self.halfExtents[0],
'y': self.halfExtents[2] if export_settings['gltf_yup'] else self.halfExtents[1],
'z': self.halfExtents[1] if export_settings['gltf_yup'] else self.halfExtents[2],
keianhzo marked this conversation as resolved.
Show resolved Hide resolved
}
return props

def migrate(self, migration_type, panel_type, instance_version, host, migration_report, ob=None):
migration_occurred = False
if instance_version <= (1, 0, 0):
migration_occurred = True

offset = self.offset.copy()
offset = Vector((offset.x, offset.z, offset.y))
self.offset = offset

halfExtents = self.halfExtents.copy()
halfExtents = Vector((halfExtents.x, halfExtents.z, halfExtents.y))
self.halfExtents = halfExtents

return migration_occurred
168 changes: 168 additions & 0 deletions addons/io_hubs_addon/components/definitions/rigid_body.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
from bpy.props import BoolProperty, FloatProperty, EnumProperty, BoolVectorProperty, FloatVectorProperty
from ..hubs_component import HubsComponent
from ..types import NodeType, PanelType, Category
from mathutils import Vector


collision_masks = [
("objects", "Objects", "Interactive objects"),
("triggers", "Triggers", "Trigger Colliders"),
("environment", "Environment", "Environment geometry"),
("avatars", "Avatars", "Player Avatars"),
("media-frames", "Media Frames", "Media Frames"),
]


class RigidBody(HubsComponent):
_definition = {
'name': 'rigidbody',
'display_name': 'RigidBody',
'category': Category.OBJECT,
'node_type': NodeType.NODE,
'panel_type': [PanelType.OBJECT],
'icon': 'PHYSICS',
'deps': ['physics-shape'],
'version': (1, 0, 1)
}

type: EnumProperty(
name="Body Type",
description="RigidBody Type",
items=[("static", "Static", "Will not ever move."),
("dynamic", "Dynamic", "Effected by physics and gravity"),
("kinematic", "Kinematic", "Not effected by gravity or collisions, but can be moved.")],
default="dynamic")

disableCollision: BoolProperty(
name="Is Trigger",
description="Disable collision response, act as a trigger only",
default=False)

collisionGroup: EnumProperty(
name="Collision Group",
description="What collision group this object belongs to. This effects what objects will collide with it.",
items=[g for g in collision_masks if g[0] != "avatars"],
default="objects")

collisionMask: BoolVectorProperty(
name="Collision Mask",
description="What collision groups this object will collide with. Note: the other object must also be set to collide with this object's group.",
size=5, subtype='LAYER', options={'ANIMATABLE'},
default=[value in ["objects", "triggers", "environment"] for (value, _label, _desc) in collision_masks])

mass: FloatProperty(
name="Mass",
description="Object's Mass",
default=1)

linearDamping: FloatProperty(
name="Linear Damping",
description="Amount of linear damping",
default=0,
min=0.0,
soft_max=1.0,
)

angularDamping: FloatProperty(
name="Angular Damping",
description="Amount of angular damping",
default=0,
min=0.0,
soft_max=1.0,
)

linearSleepingThreshold: FloatProperty(
name="Linear Sleeping Threshold",
description="Linear velocity threshold below which the object starts to sleep",
default=0.8,
min=0.0,
soft_max=10.0,
)

angularSleepingThreshold: FloatProperty(
name="Angular Sleeping Threshold",
description="Angular velocity threshold below which the object starts to sleep",
default=1.0,
min=0.0,
soft_max=10.0,
)

angularFactor: FloatVectorProperty(
name="Angular Factor",
description="Influence of the object's rotation along the X, Y, and Z axes",
size=3,
subtype="XYZ",
default=(1.0, 1.0, 1.0),
min=0.0,
soft_max=10.0,
)

gravity: FloatVectorProperty(
name="Gravity", description="Object's Gravity",
unit="ACCELERATION",
subtype="ACCELERATION",
default=(0.0, 0.0, -9.8))

def gather(self, export_settings, object):
props = super().gather(export_settings, object)
props['collisionMask'] = [value for i, (value, _label, _desc) in enumerate(
collision_masks) if self.collisionMask[i]]

# prefer to store as an array for new components
props['angularFactor'] = [
self.angularFactor[0],
self.angularFactor[2] if export_settings['gltf_yup'] else self.angularFactor[1],
self.angularFactor[1] if export_settings['gltf_yup'] else self.angularFactor[2],
]
props['gravity'] = [
self.gravity[0],
self.gravity[2] if export_settings['gltf_yup'] else self.gravity[1],
self.gravity[1] if export_settings['gltf_yup'] else self.gravity[2],
]
keianhzo marked this conversation as resolved.
Show resolved Hide resolved
return props

def draw(self, context, layout, panel):
layout.prop(self, "type")

if (self.disableCollision and self.collisionGroup != "triggers") or (self.collisionGroup == "triggers" and not self.disableCollision):
col = layout.column()
# col.alert = True
col.label(
text="When making triggers you likely want 'Is Trigger' checked and collision group set to 'Triggers'",
icon='INFO')
layout.prop(self, "collisionGroup")
layout.label(text="Collision Mask:")
col = layout.column(align=True)
for i, (_value, label, _desc) in enumerate(collision_masks):
col.prop(self, "collisionMask", text=label, index=i, toggle=True)
layout.prop(self, "disableCollision")

layout.prop(self, "mass")
layout.prop(self, "linearDamping")
layout.prop(self, "angularDamping")
layout.prop(self, "linearSleepingThreshold")
layout.prop(self, "angularSleepingThreshold")
layout.prop(self, "angularFactor")
layout.prop(self, "gravity")

@classmethod
def init(cls, obj):
obj.hubs_component_list.items.get('physics-shape').isDependency = True

def migrate(self, migration_type, panel_type, instance_version, host, migration_report, ob=None):
migration_occurred = False
if instance_version <= (1, 0, 0):
migration_occurred = True

angularFactor = self.angularFactor.copy()
angularFactor = Vector((angularFactor.x, angularFactor.z, angularFactor.y))
self.angularFactor = angularFactor

# If the gravity property has not changed, it will take the default value so we don't swizzle
# in that case. Is there any way of checking if the property has been modified?
if self.gravity != Vector((0.0, 0.0, -9.8)):
keianhzo marked this conversation as resolved.
Show resolved Hide resolved
gravity = self.gravity.copy()
gravity = Vector((gravity.x, gravity.z, gravity.y))
self.gravity = gravity

return migration_occurred
2 changes: 1 addition & 1 deletion addons/io_hubs_addon/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ class HubsSceneDebuggerRoomExportPrefs(bpy.types.PropertyGroup):
avatar_to_viewport: bpy.props.BoolProperty(
name='Spawn using viewport transform',
description='Spawn the avatar in the current viewport camera position/rotation',
default=True, options=set())
default=False, options=set())
keianhzo marked this conversation as resolved.
Show resolved Hide resolved


class HubsSceneProject(bpy.types.PropertyGroup):
Expand Down
Binary file added tests/scenes/rigid-body.blend
Binary file not shown.
Loading
Loading