Difficult-Rocket/pyglet/model/codecs/gltf.py
2021-03-24 23:37:10 +08:00

283 lines
8.9 KiB
Python

# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# Copyright (c) 2008-2020 pyglet contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of pyglet nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
import json
import struct
import pyglet
from pyglet.gl import GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_FLOAT
from pyglet.gl import GL_UNSIGNED_INT, GL_ELEMENT_ARRAY_BUFFER, GL_ARRAY_BUFFER, GL_TRIANGLES
from .. import Model, Material, MaterialGroup, TexturedMaterialGroup
from . import ModelDecodeException, ModelDecoder
# pyglet.graphics types
_pyglet_types = {
GL_BYTE: 'b',
GL_UNSIGNED_BYTE: 'B',
GL_SHORT: 's',
GL_UNSIGNED_SHORT: 'S',
GL_UNSIGNED_INT: 'I',
GL_FLOAT: 'f',
}
# struct module types
_struct_types = {
GL_BYTE: 'b',
GL_UNSIGNED_BYTE: 'B',
GL_SHORT: 'h',
GL_UNSIGNED_SHORT: 'H',
GL_UNSIGNED_INT: 'I',
GL_FLOAT: 'f',
}
# OpenGL type sizes
_component_sizes = {
GL_BYTE: 1,
GL_UNSIGNED_BYTE: 1,
GL_SHORT: 2,
GL_UNSIGNED_SHORT: 2,
GL_UNSIGNED_INT: 4,
GL_FLOAT: 4
}
_accessor_type_sizes = {
"SCALAR": 1,
"VEC2": 2,
"VEC3": 3,
"VEC4": 4,
"MAT2": 4,
"MAT3": 9,
"MAT4": 16
}
_targets = {
GL_ELEMENT_ARRAY_BUFFER: "element_array",
GL_ARRAY_BUFFER: "array",
}
# GLTF to pyglet shorthand types:
_attributes = {
'POSITION': 'v',
'NORMAL': 'n',
'TANGENT': None,
'TEXCOORD_0': '0t',
'TEXCOORD_1': '1t',
'COLOR_0': 'c',
'JOINTS_0': None,
'WEIGHTS_0': None
}
class Buffer:
# TODO: support GLB format
# TODO: support data uris
def __init__(self, length, uri):
self._length = length
self._uri = uri
def read(self, offset, length):
file = pyglet.resource.file(self._uri, 'rb')
file.seek(offset)
data = file.read(length)
file.close()
return data
class BufferView:
def __init__(self, buffer, offset, length, target, stride):
self.buffer = buffer
self.offset = offset
self.length = length
self.target = target
self.stride = stride
class Accessor:
# TODO: support sparse accessors
def __init__(self, buffer_view, offset, comp_type, count,
maximum, minimum, accessor_type, sparse):
self.buffer_view = buffer_view
self.offset = offset
self.component_type = comp_type
self.count = count
self.maximum = maximum
self.minimum = minimum
self.type = accessor_type
self.sparse = sparse
self.size = _component_sizes[comp_type] * _accessor_type_sizes[accessor_type]
def read(self):
offset = self.offset + self.buffer_view.offset
length = self.size * self.count
stride = self.buffer_view.stride or 1
# TODO: handle stride
data = self.buffer_view.buffer.read(offset, length)
return data
def parse_gltf_file(file, filename, batch):
if file is None:
file = pyglet.resource.file(filename, 'r')
elif file.mode != 'r':
file.close()
file = pyglet.resource.file(filename, 'r')
try:
data = json.load(file)
except json.JSONDecodeError:
raise ModelDecodeException('Json error. Does not appear to be a valid glTF file.')
finally:
file.close()
if 'asset' not in data:
raise ModelDecodeException('Not a valid glTF file. Asset property not found.')
else:
if float(data['asset']['version']) < 2.0:
raise ModelDecodeException('Only glTF 2.0+ models are supported')
buffers = dict()
buffer_views = dict()
accessors = dict()
materials = dict()
for i, item in enumerate(data.get('buffers', [])):
buffers[i] = Buffer(item['byteLength'], item['uri'])
for i, item in enumerate(data.get('bufferViews', [])):
buffer_index = item['buffer']
buffer = buffers[buffer_index]
offset = item.get('byteOffset', 0)
length = item.get('byteLength')
target = item.get('target')
stride = item.get('byteStride', 1)
buffer_views[i] = BufferView(buffer, offset, length, target, stride)
for i, item in enumerate(data.get('accessors', [])):
buf_view_index = item.get('bufferView')
buf_view = buffer_views[buf_view_index]
offset = item.get('byteOffset', 0)
comp_type = item.get('componentType')
count = item.get('count')
maxi = item.get('max')
mini = item.get('min')
acc_type = item.get('type')
sparse = item.get('sparse', None)
accessors[i] = Accessor(buf_view, offset, comp_type, count, maxi, mini, acc_type, sparse)
vertex_lists = []
for mesh_data in data.get('meshes'):
for primitive in mesh_data.get('primitives', []):
indices = None
attribute_list = []
count = 0
for attribute_type, i in primitive['attributes'].items():
accessor = accessors[i]
attrib = _attributes[attribute_type]
if not attrib:
# TODO: Add support for these attribute types to pyglet
continue
attrib_size = _accessor_type_sizes[accessor.type]
pyglet_type = _pyglet_types[accessor.component_type]
pyglet_fmt = "{0}{1}{2}".format(attrib, attrib_size, pyglet_type)
count = accessor.count
struct_fmt = str(count * attrib_size) + _struct_types[accessor.component_type]
array = struct.unpack('<' + struct_fmt, accessor.read())
attribute_list.append((pyglet_fmt, array))
if 'indices' in primitive:
indices_index = primitive.get('indices')
accessor = accessors[indices_index]
attrib_size = _accessor_type_sizes[accessor.type]
fmt = str(accessor.count * attrib_size) + _struct_types[accessor.component_type]
indices = struct.unpack('<' + fmt, accessor.read())
# if 'material' in primitive:
# material_index = primitive.get('material')
# color = materials[material_index]
# attribute_list.append(('c4f', color * count))
diffuse = [1.0, 1.0, 1.0]
ambient = [1.0, 1.0, 1.0]
specular = [1.0, 1.0, 1.0]
emission = [0.0, 0.0, 0.0]
shininess = 100.0
opacity = 1.0
material = Material("Default", diffuse, ambient, specular, emission, shininess, opacity)
group = MaterialGroup(material=material)
if indices:
vlist = batch.add_indexed(count, GL_TRIANGLES, group, indices, *attribute_list)
else:
vlist = batch.add(count, GL_TRIANGLES, group, *attribute_list)
vertex_lists.append(vlist)
return vertex_lists
###################################################
# Decoder definitions start here:
###################################################
class GLTFModelDecoder(ModelDecoder):
def get_file_extensions(self):
return ['.gltf']
def decode(self, file, filename, batch):
if not batch:
batch = pyglet.graphics.Batch()
vertex_lists = parse_gltf_file(file=file, filename=filename, batch=batch)
textures = {}
return Model(vertex_lists, textures, batch=batch)
def get_decoders():
return [GLTFModelDecoder()]
def get_encoders():
return []