Przeglądaj źródła

Merge pull request #6533 from tschw/Blender_BufferGeometry_index

Blender export of indexed BufferGeometry objects.
Ricardo Cabello 10 lat temu
rodzic
commit
afd72f5d06

+ 19 - 1
utils/exporters/blender/addons/io_three/__init__.py

@@ -38,7 +38,7 @@ logging.basicConfig(
 
 bl_info = {
     'name': "Three.js Format",
-    'author': "repsac, mrdoob, yomotsu, mpk, jpweeks, rkusa",
+    'author': "repsac, mrdoob, yomotsu, mpk, jpweeks, rkusa, tschw",
     'version': (1, 4, 0),
     'blender': (2, 7, 3),
     'location': "File > Export",
@@ -308,6 +308,10 @@ def restore_export_settings(properties, settings):
     properties.option_geometry_type = settings.get(
         constants.GEOMETRY_TYPE,
         constants.EXPORT_OPTIONS[constants.GEOMETRY_TYPE])
+
+    properties.option_index_type = settings.get(
+        constants.INDEX_TYPE,
+        constants.EXPORT_OPTIONS[constants.INDEX_TYPE])
     ## }
 
     ## Materials {
@@ -430,6 +434,7 @@ def set_settings(properties):
         constants.BONES: properties.option_bones,
         constants.APPLY_MODIFIERS: properties.option_apply_modifiers,
         constants.GEOMETRY_TYPE: properties.option_geometry_type,
+        constants.INDEX_TYPE: properties.option_index_type,
 
         constants.MATERIALS: properties.option_materials,
         constants.UVS: properties.option_uv_coords,
@@ -566,6 +571,17 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         default=constants.EXPORT_OPTIONS[constants.APPLY_MODIFIERS]
     )
 
+    index_buffer_types = [
+        (constants.NONE,) * 3,
+        (constants.UINT_16,) * 3,
+        (constants.UINT_32,) * 3]
+
+    option_index_type = EnumProperty(
+        name="Index Buffer",
+        description="Index buffer type that will be used for BufferGeometry objects.",
+        items=index_buffer_types,
+        default=constants.EXPORT_OPTIONS[constants.INDEX_TYPE])
+
     option_scale = FloatProperty(
         name="Scale",
         description="Scale vertices",
@@ -768,6 +784,8 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         row = layout.row()
         row.prop(self.properties, 'option_geometry_type')
 
+        row = layout.row()
+        row.prop(self.properties, 'option_index_type')
         ## }
 
         layout.separator()

+ 11 - 2
utils/exporters/blender/addons/io_three/constants.py

@@ -47,6 +47,11 @@ UVS = 'uvs'
 APPLY_MODIFIERS = 'applyModifiers'
 COLORS = 'colors'
 MIX_COLORS = 'mixColors'
+INDEX = 'index'
+DRAW_CALLS = 'offsets'
+DC_START = 'start'
+DC_COUNT = 'count'
+DC_INDEX = 'index'
 SCALE = 'scale'
 COMPRESSION = 'compression'
 MAPS = 'maps'
@@ -77,6 +82,7 @@ GLOBAL = 'global'
 BUFFER_GEOMETRY = 'BufferGeometry'
 GEOMETRY = 'geometry'
 GEOMETRY_TYPE = 'geometryType'
+INDEX_TYPE = 'indexType'
 
 CRITICAL = 'critical'
 ERROR = 'error'
@@ -89,6 +95,10 @@ MSGPACK = 'msgpack'
 
 PACK = 'pack'
 
+FLOAT_32 = 'Float32Array'
+UINT_16 = 'Uint16Array'
+UINT_32 = 'Uint32Array'
+
 INFLUENCES_PER_VERTEX = 'influencesPerVertex'
 
 EXPORT_OPTIONS = {
@@ -98,6 +108,7 @@ EXPORT_OPTIONS = {
     UVS: True,
     APPLY_MODIFIERS: True,
     COLORS: False,
+    INDEX_TYPE: UINT_16,
     MATERIALS: False,
     FACE_MATERIALS: False,
     SCALE: 1,
@@ -187,8 +198,6 @@ NORMAL = 'normal'
 ITEM_SIZE = 'itemSize'
 ARRAY = 'array'
 
-FLOAT_32 = 'Float32Array'
-
 VISIBLE = 'visible'
 CAST_SHADOW = 'castShadow'
 RECEIVE_SHADOW = 'receiveShadow'

+ 97 - 6
utils/exporters/blender/addons/io_three/exporter/geometry.py

@@ -241,7 +241,8 @@ class Geometry(base_classes.BaseNode):
                       constants.NORMALS, constants.BONES,
                       constants.SKIN_WEIGHTS,
                       constants.SKIN_INDICES, constants.NAME,
-                      constants.INFLUENCES_PER_VERTEX]
+                      constants.INFLUENCES_PER_VERTEX,
+                      constants.INDEX]
 
         data = {}
         anim_components = [constants.MORPH_TARGETS, constants.ANIMATION]
@@ -285,6 +286,10 @@ class Geometry(base_classes.BaseNode):
 
         data[constants.METADATA].update(self.metadata)
 
+        draw_calls = self.get(constants.DRAW_CALLS)
+        if draw_calls is not None:
+            data[constants.DRAW_CALLS] = draw_calls
+
         return data
 
     def _buffer_geometry_metadata(self, metadata):
@@ -345,12 +350,15 @@ class Geometry(base_classes.BaseNode):
                 constants.METADATA: self.metadata
             })
         else:
+            geometry_data = data
             if self.options.get(constants.EMBED_GEOMETRY, True):
-                data[constants.DATA] = {
-                    constants.ATTRIBUTES: component_data
-                }
-            else:
-                data[constants.ATTRIBUTES] = component_data
+                data[constants.DATA] = geometry_data = {}
+
+            geometry_data[constants.ATTRIBUTES] = component_data
+            draw_calls = self.get(constants.DRAW_CALLS)
+            if draw_calls is not None:
+                geometry_data[constants.DRAW_CALLS] = draw_calls
+
             data[constants.METADATA] = self.metadata
             data[constants.NAME] = self[constants.NAME]
 
@@ -363,6 +371,7 @@ class Geometry(base_classes.BaseNode):
         options_vertices = self.options.get(constants.VERTICES)
         option_normals = self.options.get(constants.NORMALS)
         option_uvs = self.options.get(constants.UVS)
+        option_index_type = self.options.get(constants.INDEX_TYPE)
 
         pos_tuple = (constants.POSITION, options_vertices,
                      api.mesh.buffer_position, 3)
@@ -388,6 +397,88 @@ class Geometry(base_classes.BaseNode):
                 constants.ARRAY: array
             }
 
+        if option_index_type != constants.NONE:
+
+            assert(not (self.get(constants.INDEX) or
+                        self.get(constants.DRAW_CALLS)))
+
+            indices_per_face = 3
+            index_threshold  = 0xffff - indices_per_face
+            if option_index_type == constants.UINT_32:
+                index_threshold = 0x7fffffff - indices_per_face
+
+            attrib_data_in, attrib_data_out, attrib_keys = [], [], []
+
+            i = 0
+            for key, entry in self[constants.ATTRIBUTES].items():
+
+                item_size = entry[constants.ITEM_SIZE]
+
+                attrib_keys.append(key)
+                attrib_data_in.append( (entry[constants.ARRAY], item_size) )
+                attrib_data_out.append( ([], i, i + item_size) )
+                i += item_size
+
+            index_data, draw_calls = [], []
+            indexed, flush_req, base_vertex = {}, False, 0
+
+            assert(len(attrib_data_in) > 0)
+            array, item_size = attrib_data_in[0]
+            i, n = 0, len(array) / item_size
+            while i < n:
+
+                vertex_data = ()
+                for array, item_size in attrib_data_in:
+                    vertex_data += tuple(
+                            array[i * item_size : (i + 1) * item_size])
+
+                vertex_index = indexed.get(vertex_data)
+
+                if vertex_index is None:
+
+                    vertex_index = len(indexed)
+                    flush_req = vertex_index >= index_threshold
+
+                    indexed[vertex_data] = vertex_index
+                    for array, i_from, i_to in attrib_data_out:
+                        array.extend(vertex_data[i_from : i_to])
+
+                index_data.append(vertex_index)
+
+                i += 1
+                if i == n:
+                    flush_req = len(draw_calls) > 0
+                    assert(i % indices_per_face == 0)
+
+                if flush_req and i % indices_per_face == 0:
+                    start, count = 0, len(index_data)
+                    if draw_calls:
+                        prev = draw_calls[-1]
+                        start = prev[constants.DC_START] + prev[constants.DC_COUNT]
+                        count -= start
+                    draw_calls.append({
+                        constants.DC_START: start,
+                        constants.DC_COUNT: count,
+                        constants.DC_INDEX: base_vertex
+                    })
+                    base_vertex += len(indexed)
+                    indexed.clear()
+                    flush_req = False
+
+            for i, key in enumerate(attrib_keys):
+                array = attrib_data_out[i][0]
+                self[constants.ATTRIBUTES][key][constants.ARRAY] = array
+
+            self[constants.ATTRIBUTES][constants.INDEX] = {
+                constants.ITEM_SIZE: 3,
+                constants.TYPE: option_index_type,
+                constants.ARRAY: index_data
+            }
+            if (draw_calls):
+                logger.info("draw_calls = %s", repr(draw_calls))
+                self[constants.DRAW_CALLS] = draw_calls
+
+
     def _parse_geometry(self):
         """Parse the geometry to Three.Geometry specs"""
         if self.options.get(constants.VERTICES):