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

interleaving wip #1566

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions addons/io_scene_gltf2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ def __init__(self):
default=False
)

export_attributes_interleaving: EnumProperty(
name='Interleaving',
items=(('PACKED', 'Packed',
'Vertex attributes are tightly packed and each has its own buffer view'),
('INTERLEAVED', 'Interleaved',
'Vertex attributes are stored as a Array-Of-Structures using a single buffer view')),
description='Specify how the vertex attributes should be packed',
default='PACKED'
)

export_materials: EnumProperty(
name='Materials',
items=(('EXPORT', 'Export',
Expand Down Expand Up @@ -561,6 +571,8 @@ def execute(self, context):
else:
export_settings['gltf_draco_mesh_compression'] = False

export_settings['gltf_attributes_interleaving'] = self.export_attributes_interleaving

export_settings['gltf_materials'] = self.export_materials
export_settings['gltf_colors'] = self.export_colors
export_settings['gltf_cameras'] = self.export_cameras
Expand Down Expand Up @@ -779,6 +791,8 @@ def draw(self, context):
col.prop(operator, 'use_mesh_edges')
col.prop(operator, 'use_mesh_vertices')

layout.prop(operator, 'export_attributes_interleaving')

layout.prop(operator, 'export_materials')
col = layout.column()
col.active = operator.export_materials == "EXPORT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from io_scene_gltf2.io.com import gltf2_io_constants
from io_scene_gltf2.io.com import gltf2_io_debug
from io_scene_gltf2.io.exp import gltf2_io_binary_data
from io_scene_gltf2.io.com.gltf2_io_debug import print_console


def gather_primitive_attributes(blender_primitive, export_settings):
Expand All @@ -27,17 +28,26 @@ def gather_primitive_attributes(blender_primitive, export_settings):

:return: a dictionary of attributes
"""

interleaving_info = None
if export_settings['gltf_attributes_interleaving'] == "INTERLEAVED":
interleaving_info = {"names": [], "data": []}

attributes = {}
attributes.update(__gather_position(blender_primitive, export_settings))
attributes.update(__gather_normal(blender_primitive, export_settings))
attributes.update(__gather_tangent(blender_primitive, export_settings))
attributes.update(__gather_texcoord(blender_primitive, export_settings))
attributes.update(__gather_colors(blender_primitive, export_settings))
attributes.update(__gather_skins(blender_primitive, export_settings))
attributes.update(__gather_position(blender_primitive, interleaving_info, export_settings))
attributes.update(__gather_normal(blender_primitive, interleaving_info, export_settings))
attributes.update(__gather_tangent(blender_primitive, interleaving_info, export_settings))
attributes.update(__gather_texcoord(blender_primitive, interleaving_info, export_settings))
attributes.update(__gather_colors(blender_primitive, interleaving_info, export_settings))
attributes.update(__gather_skins(blender_primitive, interleaving_info, export_settings))

if interleaving_info:
__interleave_data(attributes, interleaving_info)

return attributes


def array_to_accessor(array, component_type, data_type, include_max_and_min=False):
def array_to_accessor(array, component_type, data_type, include_max_and_min=False, interleaving_info=None):
dtype = gltf2_io_constants.ComponentType.to_numpy_dtype(component_type)
num_elems = gltf2_io_constants.DataType.num_elements(data_type)

Expand All @@ -54,8 +64,14 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals
amax = np.amax(array, axis=0).tolist()
amin = np.amin(array, axis=0).tolist()

buffer_view = None
if interleaving_info is None:
buffer_view = gltf2_io_binary_data.BinaryData(array.tobytes())
else:
interleaving_info["data"].append(array)

return gltf2_io.Accessor(
buffer_view=gltf2_io_binary_data.BinaryData(array.tobytes()),
buffer_view=buffer_view,
byte_offset=None,
component_type=component_type,
count=len(array),
Expand All @@ -70,66 +86,86 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals
)


def __gather_position(blender_primitive, export_settings):
def __gather_position(blender_primitive, interleaving_info, export_settings):
position = blender_primitive["attributes"]["POSITION"]

if interleaving_info:
interleaving_info["names"].append("POSITION")

return {
"POSITION": array_to_accessor(
position,
component_type=gltf2_io_constants.ComponentType.Float,
data_type=gltf2_io_constants.DataType.Vec3,
include_max_and_min=True
include_max_and_min=True,
interleaving_info=interleaving_info,
)
}


def __gather_normal(blender_primitive, export_settings):
def __gather_normal(blender_primitive, interleaving_info, export_settings):
if not export_settings[gltf2_blender_export_keys.NORMALS]:
return {}
if 'NORMAL' not in blender_primitive["attributes"]:
return {}
normal = blender_primitive["attributes"]['NORMAL']

if interleaving_info:
interleaving_info["names"].append("NORMAL")

return {
"NORMAL": array_to_accessor(
normal,
component_type=gltf2_io_constants.ComponentType.Float,
data_type=gltf2_io_constants.DataType.Vec3,
interleaving_info=interleaving_info,
)
}


def __gather_tangent(blender_primitive, export_settings):
def __gather_tangent(blender_primitive, interleaving_info, export_settings):
if not export_settings[gltf2_blender_export_keys.TANGENTS]:
return {}
if 'TANGENT' not in blender_primitive["attributes"]:
return {}
tangent = blender_primitive["attributes"]['TANGENT']

if interleaving_info:
interleaving_info["names"].append("TANGENT")

return {
"TANGENT": array_to_accessor(
tangent,
component_type=gltf2_io_constants.ComponentType.Float,
data_type=gltf2_io_constants.DataType.Vec4,
interleaving_info=interleaving_info,
)
}


def __gather_texcoord(blender_primitive, export_settings):
def __gather_texcoord(blender_primitive, interleaving_info, export_settings):
attributes = {}
if export_settings[gltf2_blender_export_keys.TEX_COORDS]:
tex_coord_index = 0
tex_coord_id = 'TEXCOORD_' + str(tex_coord_index)
while blender_primitive["attributes"].get(tex_coord_id) is not None:
tex_coord = blender_primitive["attributes"][tex_coord_id]

if interleaving_info:
interleaving_info["names"].append(tex_coord_id)

attributes[tex_coord_id] = array_to_accessor(
tex_coord,
component_type=gltf2_io_constants.ComponentType.Float,
data_type=gltf2_io_constants.DataType.Vec2,
interleaving_info=interleaving_info,
)
tex_coord_index += 1
tex_coord_id = 'TEXCOORD_' + str(tex_coord_index)
return attributes


def __gather_colors(blender_primitive, export_settings):
def __gather_colors(blender_primitive, interleaving_info, export_settings):
attributes = {}
if export_settings[gltf2_blender_export_keys.COLORS]:
color_index = 0
Expand All @@ -146,27 +182,22 @@ def __gather_colors(blender_primitive, export_settings):
colors += 0.5 # bias for rounding
colors = colors.astype(np.uint16)

attributes[color_id] = gltf2_io.Accessor(
buffer_view=gltf2_io_binary_data.BinaryData(colors.tobytes()),
byte_offset=None,
component_type=gltf2_io_constants.ComponentType.UnsignedShort,
count=len(colors),
extensions=None,
extras=None,
max=None,
min=None,
name=None,
normalized=True,
sparse=None,
type=gltf2_io_constants.DataType.Vec4,
if interleaving_info:
interleaving_info["names"].append(color_id)

attributes[color_id] = array_to_accessor(
colors,
gltf2_io_constants.ComponentType.UnsignedShort,
gltf2_io_constants.DataType.Vec4,
interleaving_info=interleaving_info,
)

color_index += 1
color_id = 'COLOR_' + str(color_index)
return attributes


def __gather_skins(blender_primitive, export_settings):
def __gather_skins(blender_primitive, interleaving_info, export_settings):
attributes = {}
if export_settings[gltf2_blender_export_keys.SKINS]:
bone_set_index = 0
Expand All @@ -178,8 +209,12 @@ def __gather_skins(blender_primitive, export_settings):
gltf2_io_debug.print_console("WARNING", "There are more than 4 joint vertex influences."
"The 4 with highest weight will be used (and normalized).")
break


# joints
if interleaving_info:
interleaving_info["names"].append(joint_id)

internal_joint = blender_primitive["attributes"][joint_id]
component_type = gltf2_io_constants.ComponentType.UnsignedShort
if max(internal_joint) < 256:
Expand All @@ -188,10 +223,14 @@ def __gather_skins(blender_primitive, export_settings):
internal_joint,
component_type,
data_type=gltf2_io_constants.DataType.Vec4,
interleaving_info=interleaving_info,
)
attributes[joint_id] = joint

# weights
if interleaving_info:
interleaving_info["names"].append(weight_id)

internal_weight = blender_primitive["attributes"][weight_id]
# normalize first 4 weights, when not exporting all influences
if not export_settings['gltf_all_vertex_influences']:
Expand All @@ -206,10 +245,32 @@ def __gather_skins(blender_primitive, export_settings):
internal_weight,
component_type=gltf2_io_constants.ComponentType.Float,
data_type=gltf2_io_constants.DataType.Vec4,
interleaving_info=interleaving_info,
)
attributes[weight_id] = weight

bone_set_index += 1
joint_id = 'JOINTS_' + str(bone_set_index)
weight_id = 'WEIGHTS_' + str(bone_set_index)
return attributes


def __interleave_data(attributes, interleaving_info):

# Compute the view byte_stride
view_stride = 0
for name in interleaving_info["names"]:
attr = attributes[name]
attr.byte_offset = view_stride
view_stride += gltf2_io_constants.ComponentType.get_size(attr.component_type) * gltf2_io_constants.DataType.num_elements(attr.type)

# Build the view bytearray
view_bytearray = bytearray()
for idx in range(0, attributes["POSITION"].count):
for data in interleaving_info["data"]:
view_bytearray.extend(data[idx].tobytes())

# Store the view
view = gltf2_io_binary_data.BinaryData(bytes(view_bytearray), view_stride)
for name in interleaving_info["names"]:
attributes[name].buffer_view = view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from io_scene_gltf2.io.exp import gltf2_io_image_data
from io_scene_gltf2.blender.exp import gltf2_blender_export_keys
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
from io_scene_gltf2.io.com.gltf2_io_debug import print_console


class GlTF2Exporter:
Expand Down Expand Up @@ -306,8 +307,9 @@ def __traverse_property(node):

# binary data needs to be moved to a buffer and referenced with a buffer view
if isinstance(node, gltf2_io_binary_data.BinaryData):
buffer_view = self.__buffer.add_and_get_view(node)
return self.__to_reference(buffer_view)
if node.buffer_view is None:
node.buffer_view = self.__buffer.add_and_get_view(node)
return self.__to_reference(node.buffer_view)

# image data needs to be saved to file
if isinstance(node, gltf2_io_image_data.ImageData):
Expand Down
9 changes: 8 additions & 1 deletion addons/io_scene_gltf2/io/exp/gltf2_io_binary_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,18 @@
class BinaryData:
"""Store for gltf binary data that can later be stored in a buffer."""

def __init__(self, data: bytes):
def __init__(self, data: bytes, byte_stride=None):
if not isinstance(data, bytes):
raise TypeError("Data is not a bytes array")
self.data = data

# Byte stride to set when the data is converted to a BufferView
self.byte_stride = byte_stride

# Set to the generated buffer view if the data is shared between multiple source
# Ex: When accessor data is interleaved
self.buffer_view = None

def __eq__(self, other):
return self.data == other.data

Expand Down
2 changes: 1 addition & 1 deletion addons/io_scene_gltf2/io/exp/gltf2_io_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def add_and_get_view(self, binary_data: gltf2_io_binary_data.BinaryData) -> gltf
buffer=self.__buffer_index,
byte_length=length,
byte_offset=offset,
byte_stride=None,
byte_stride=binary_data.byte_stride,
extensions=None,
extras=None,
name=None,
Expand Down