2021-04-16 23:21:06 +08:00
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# pyglet
|
|
|
|
# Copyright (c) 2006-2008 Alex Holkner
|
2021-04-17 01:14:38 +08:00
|
|
|
# Copyright (c) 2008-2021 pyglet contributors
|
2021-04-16 23:21:06 +08:00
|
|
|
# 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
|
|
|
|
|
2021-09-23 06:34:23 +08:00
|
|
|
from .. import Model, Material, MaterialGroup
|
2021-04-16 23:21:06 +08:00
|
|
|
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 []
|