rk_island/game/resources.py

252 lines
7.8 KiB
Python
Raw Normal View History

2022-08-28 05:09:52 +02:00
# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import struct
from array import array
from pathlib import Path
import png
import engine
VERTEX_SIZE = 20
VERTEX_FORMAT = engine.vertex_format(
engine.VERTEX_FORMAT_VEC3_FLOAT, # position
engine.VERTEX_FORMAT_VEC3_INT10 | engine.VERTEX_FORMAT_NORMALIZE, # normal
engine.VERTEX_FORMAT_VEC3_UINT10) # texcoords
2022-08-28 05:09:52 +02:00
def load_png(path):
width, height, data, _ = png.Reader(filename = path).read_flat()
return (width, height, data)
def _read_magic(file, magic):
assert magic
if file.read(len(magic)) != magic:
raise RuntimeError("Archive magic mismatch!", magic)
def _write_magic(file, magic):
assert magic
size = file.write(magic)
assert size == len(magic)
def _read_struct(file, format):
assert format
size = struct.calcsize(format)
assert size
data = file.read(size)
assert len(data) == size
_read_magic(file, b'RK')
return struct.unpack(format, data)
def _write_struct(file, format, elems):
assert format
data = struct.pack(format, *elems)
size = file.write(data)
assert size == len(data)
_write_magic(file, b'RK')
def _read_string(file):
length, = _read_struct(file, 'B')
assert length
data = file.read(length)
assert len(data) == length
_read_magic(file, b'RK')
return str(data, encoding='ascii')
def _write_string(file, string):
data = bytes(string, encoding='ascii')
assert data and len(data) < 256
_write_struct(file, 'B', (len(data),))
size = file.write(data)
assert size == len(data)
_write_magic(file, b'RK')
def _read_array(file, format, length):
assert format
data = array(format)
data.fromfile(file, length)
assert len(data) == length
_read_magic(file, b'RK')
return data
def _write_array(file, array):
assert array
array.tofile(file)
_write_magic(file, b'RK')
def _read_blob(file):
typecode, length = _read_struct(file, 'II')
typecode = chr(typecode)
assert typecode, length
data = array(typecode)
data.fromfile(file, length)
assert len(data) == length
_read_magic(file, b'RK')
return data
def _write_blob(file, array):
assert array
_write_struct(file, 'II', (ord(array.typecode), len(array)))
array.tofile(file)
_write_magic(file, b'RK')
class TextureData:
__slots__ = 'name', 'format', 'width', 'height', 'nlevels', 'flags', 'pixels'
def __init__(self, name, format, width, height, nlevels, flags, pixels):
assert pixels.typecode == engine.TEXTURE_FORMAT_TYPECODE[format]
assert len(pixels) == width * height * max(1, nlevels) * engine.TEXTURE_FORMAT_NELEMS[format]
self.name = name
self.format = format
self.width = width
self.height = height
self.nlevels = nlevels
self.flags = flags
self.pixels = pixels
2022-12-31 20:24:08 +01:00
def __iter__(self):
yield self.format
yield self.width
yield self.height
yield self.nlevels
yield self.flags
yield self.pixels
2022-08-28 05:09:52 +02:00
@classmethod
def from_archive(cls, file):
_read_magic(file, b'TX')
name = _read_string(file)
format, width, height, nlevels, flags = _read_struct(file, 'IIIII')
pixels = _read_blob(file)
assert pixels.typecode == engine.TEXTURE_FORMAT_TYPECODE[format]
assert len(pixels) == width * height * max(1, nlevels) * engine.TEXTURE_FORMAT_NELEMS[format]
return cls(name, format, width, height, nlevels, flags, pixels)
def to_archive(self, file):
_write_magic(file, b'TX')
_write_string(file, self.name)
_write_struct(file, 'IIIII', (self.format, self.width, self.height, self.nlevels, self.flags))
_write_blob(file, self.pixels)
class VerticesData:
__slots__ = 'name', 'vertices', 'indices'
def __init__(self, name, vertices, indices):
if len(vertices) % VERTEX_SIZE != 0:
raise RuntimeError("Vertex format mismatch!")
self.name = name
self.vertices = vertices
self.indices = indices
2022-12-31 20:24:08 +01:00
def __iter__(self):
yield VERTEX_FORMAT
yield len(self.vertices) // VERTEX_SIZE
yield self.vertices
yield self.indices
2022-08-28 05:09:52 +02:00
@classmethod
def from_archive(cls, file):
_read_magic(file, b'VT')
name = _read_string(file)
nvertices, nindices = _read_struct(file, 'II')
vertices = _read_array(file, 'B', nvertices * VERTEX_SIZE)
indices = _read_array(file, 'H', nindices)
return cls(name, vertices, indices)
def to_archive(self, file):
_write_magic(file, b'VT')
_write_string(file, self.name)
_write_struct(file, 'II', (len(self.vertices) // VERTEX_SIZE, len(self.indices)))
_write_array(file, self.vertices)
_write_array(file, self.indices)
class ModelData:
__slots__ = 'name', 'flags', 'mesh'
2022-08-28 05:09:52 +02:00
def __init__(self, name, flags, mesh):
2022-08-28 05:09:52 +02:00
self.name = name
self.flags = flags
self.mesh = mesh
@classmethod
def from_archive(cls, file):
_read_magic(file, b'MD')
name = _read_string(file)
flags, mesh = _read_struct(file, 'II')
return ModelData(name, flags, mesh)
2022-08-28 05:09:52 +02:00
def to_archive(self, file):
_write_magic(file, b'MD')
_write_string(file, self.name)
_write_struct(file, 'II', (self.flags, self.mesh))
2022-08-28 05:09:52 +02:00
def spawn(self, batch, *params):
return batch.append(self.flags, self.mesh, params)
2022-08-28 05:09:52 +02:00
class Archive:
__slots__ = 'textures_db', 'vertices_db', 'models_db'
def __init__(self, textures_db = None, vertices_db = None, models_db = None):
self.textures_db = textures_db or {}
self.vertices_db = vertices_db or {}
self.models_db = models_db or {}
def get_texture(self, name):
return self.textures_db[name]
def get_vertices(self, name):
return self.vertices_db[name]
def get_model(self, name):
return self.models_db[name]
@classmethod
def from_archive(cls, file):
textures_db = {}
vertices_db = {}
models_db = {}
_read_magic(file, b'RKAR')
ntextures, nvertices, nmodels = _read_struct(file, 'III')
for _ in range(ntextures):
data = TextureData.from_archive(file)
2022-12-31 20:24:08 +01:00
textures_db[data.name] = data
2022-08-28 05:09:52 +02:00
for _ in range(nvertices):
data = VerticesData.from_archive(file)
2022-12-31 20:24:08 +01:00
vertices_db[data.name] = data
2022-08-28 05:09:52 +02:00
for _ in range(nmodels):
data = ModelData.from_archive(file)
2022-12-31 20:24:08 +01:00
models_db[data.name] = data
2022-08-28 05:09:52 +02:00
return cls(textures_db, vertices_db, models_db)
def to_archive(self, file):
_write_magic(file, b'RKAR')
_write_struct(file, 'III', (len(self.textures_db), len(self.vertices_db), len(self.models_db)))
for _, data in self.textures_db.items():
data.to_archive(file)
for _, data in self.vertices_db.items():
data.to_archive(file)
for _, data in self.models_db.items():
data.to_archive(file)
2022-12-31 20:24:08 +01:00
@classmethod
def load(cls, filename):
file = open(Path(filename), 'rb')
archive = cls.from_archive(file)
file.close()
return archive
2022-08-28 05:09:52 +02:00
def save(self, filename):
file = open(Path(filename), 'wb')
self.to_archive(file)
file.close()