Compare commits

..

No commits in common. "9131523ae7ac3a3875de5c9ab0261da5b61b3859" and "9fff666ebf3f0474817ec51d7ff2bddc8926baac" have entirely different histories.

16 changed files with 280 additions and 433 deletions

2
engine

@ -1 +1 @@
Subproject commit beca8798bf91edba954d31eb4e1a619ec2140c83 Subproject commit ae3333e22e36667a0c02817fafb16ab7b8d1341b

View File

@ -19,15 +19,14 @@ from engine import (vec3, mat3, buffer,
INSTANCE_FLAG_SPAWNED, BATCH_MAX_SIZE, param_type, params_format, create_batch, draw_batch, destroy_batch) INSTANCE_FLAG_SPAWNED, BATCH_MAX_SIZE, param_type, params_format, create_batch, draw_batch, destroy_batch)
class Batch: class Batch:
__slots__ = '_batch', 'vertices', 'size', 'max_size', 'flags', 'meshes', 'params', '_params', '__dict__' __slots__ = '_batch', 'size', 'max_size', 'flags', 'meshes', 'params', '_params', '__dict__'
def __init__(self, vertices, max_size, max_meshes, **params_decls): def __init__(self, vertices, max_size, max_meshes, **params_decls):
assert max_size <= BATCH_MAX_SIZE assert max_size <= BATCH_MAX_SIZE
nparams = len(params_decls) nparams = len(params_decls)
names = params_decls.keys() names = params_decls.keys()
formats = params_decls.values() formats = params_decls.values()
self._batch = create_batch(vertices._vertices, max_size, max_meshes, params_format(*formats)) self._batch = create_batch(vertices, max_size, max_meshes, params_format(*formats))
self.vertices = vertices
self.size = 0 self.size = 0
self.max_size = max_size self.max_size = max_size
self.flags = buffer(c_ubyte, max_size) self.flags = buffer(c_ubyte, max_size)
@ -40,12 +39,12 @@ class Batch:
def __del__(self): def __del__(self):
destroy_batch(self._batch) destroy_batch(self._batch)
def spawn(self, model, *params): def append(self, flags, mesh, params):
assert len(params) == len(self.params) assert len(params) == len(self.params)
index = self.size index = self.size
assert index < self.max_size assert index < self.max_size
self.flags[index] = model.flags | INSTANCE_FLAG_SPAWNED self.flags[index] = flags | INSTANCE_FLAG_SPAWNED
self.meshes[index] = model.mesh self.meshes[index] = mesh
for self_params, param in zip(self.params, params): for self_params, param in zip(self.params, params):
self_params[index] = param self_params[index] = param
self.size += 1 self.size += 1

View File

@ -1,20 +0,0 @@
# 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/>.
class Entity:
__slots__ = 'index'
def __init__(self, batch, model, *params):
self.index = batch.spawn(model, *params)

View File

@ -13,28 +13,24 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from time import thread_time import time
from math import pi, tau, dist from math import pi, tau, dist
from ctypes import Structure
from engine import * from engine import *
from game import math from game import math
from game.time import Time from game import triangles
from game import sea
from game.shader import Shader
from game.generator import Generator
from game.resources import RuntimeArchive
from game.batch import Batch
from game.camera import Camera
from game.environment import Environment
from game.events import Events from game.events import Events
from game.mouse import Mouse from game.mouse import Mouse
from game.keyboard import Keyboard from game.keyboard import Keyboard
from game.generator import Generator
from game.resources import Archive
from game.texture import Texture
from game.shader import Shader
from game.vertices import Vertices
from game.camera import Camera
from game.environment import Environment
from game.inputs import InputFloat
from game.batch import Batch
from game.entity import Entity
from game.scene import Node, SceneNode, TextureNode, ShaderNode, InputNode, DrawNode, FuncNode
from game import sea
proj_hfov = pi * 0.25 proj_hfov = pi * 0.25
proj_ratio = 16.0 / 9.0 proj_ratio = 16.0 / 9.0
@ -44,89 +40,36 @@ proj_far_z = 3000.0
sun_direction = math.vec3_normalize((1.0, 0.0, 0.5)) sun_direction = math.vec3_normalize((1.0, 0.0, 0.5))
sun_power = 1.0 sun_power = 1.0
class PerfNode(Node): def main():
__slots__ = '_count', '_min', '_max', '_total', '_name'
def __init__(self, name, *subnodes):
Node.__init__(self, *subnodes)
self._count = None
self._min = 10000.0
self._max = 0.0
self._total = 0.0
self._name = name
def __del__(self):
avg = round(self._total / self._count, 2)
print(self._name, "*", self._count,
": min =", round(self._min, 2), ", max =", round(self._max, 2), ", avg =", avg, "(ms)")
def draw(self, time):
begin = thread_time()
Node.draw(self, time)
elapsed = (thread_time() - begin) * 1000.0
if self._count is None:
self._count = 0
else:
self._count += 1
self._min = min(self._min, elapsed)
self._max = max(self._max, elapsed)
self._total += elapsed
class TestEntity(Entity):
__slots__ = 'translation', 'orientation', 'spawn_translation', 'spawn_orientation'
def __init__(self, batch, model, translation, orientation):
Entity.__init__(self, batch, model, translation, orientation)
self.translation = batch.translation[self.index]
self.orientation = batch.orientation[self.index]
self.spawn_translation = translation
self.spawn_orientation = orientation
def update_camera(time, mouse, camera, environment):
camera_yaw = mouse.drag[0] * 0.001
camera_pitch = mouse.drag[1] * 0.001 + pi * 0.25
camera_distance = mouse.wheel * 20.0
camera.set_view(camera_yaw % tau, camera_pitch % tau, camera_distance)
environment.from_sun(camera.view, sun_direction, sun_power)
def update_tests(time, blob, cube, clouds):
rotation = mat3()
mat3_rotation(rotation, vec3_up, (time.current * 0.21) % tau)
mat3_mul_vec3(blob.translation, rotation, blob.spawn_translation)
mat3_mul_vec3(cube.translation, rotation, cube.spawn_translation)
mat3_rotation(cube.orientation, vec3_up, (time.current * 0.43) % tau)
mat3_rotation(clouds.orientation, vec3_up, (time.current * -0.037) % tau)
def update_sea(time, camera, sea_phase):
camera.to_km()
sea_phase.update((time.current * 0.023) % 1.0)
def create_scene(keyboard, mouse):
tiles_shader = Shader('tiles', 'common')
tests_shader = Shader('tests', 'common')
sea_shader = Shader('sea')
camera = Camera()
camera.set_projection(proj_hfov, proj_ratio, proj_near_z, proj_far_z)
environment = Environment()
sea_phase = InputFloat(sea_shader.u_sea_phase, 0.0)
print("Generating terrain...") print("Generating terrain...")
gen_begin = time.thread_time()
generated = Generator(256) generated = Generator(256)
gen_end = time.thread_time()
print("Done: ", round(gen_end - gen_begin, 2), "seconds")
heightmap = Texture( print("Initializing...")
display = create_display(b'RK Island - Drag to rotate, wheel to zoom, q to quit', 1600, 900)
events = Events(display)
mouse = Mouse(events, wheel = 60, wheel_min = 20)
keyboard = Keyboard(events)
render_initialize(__debug__)
terrain_shader = Shader('terrain', 'common')
tests_shader = Shader('tests', 'common')
sky_shader = Shader('sky')
heightmap = create_texture(
TEXTURE_FORMAT_FLOAT_32, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR, TEXTURE_FORMAT_FLOAT_32, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
generated.packed_heights) generated.packed_heights)
normalmap = Texture( normalmap = create_texture(
TEXTURE_FORMAT_RGB10_A2, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR, TEXTURE_FORMAT_RGB10_A2, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
generated.packed_normals) generated.packed_normals)
print("Loading resources...") print("Loading resources...")
archive = Archive.load('data/rk_island.rkar') archive = RuntimeArchive.load('data/rk_island.rkar')
print("Building tiles...") print("Building tiles...")
tiles_texture = Texture(*archive.get_texture('tiles')) tiles_texture = archive.get_texture('tiles')
tiles_vertices = Vertices(*archive.get_vertices('tiles')) tiles_vertices = archive.get_vertices('tiles')
water_model = archive.get_model('water') water_model = archive.get_model('water')
sand_model = archive.get_model('sand') sand_model = archive.get_model('sand')
grass_model = archive.get_model('grass') grass_model = archive.get_model('grass')
@ -134,7 +77,7 @@ def create_scene(keyboard, mouse):
rock_model = archive.get_model('rock') rock_model = archive.get_model('rock')
mud_model = archive.get_model('mud') mud_model = archive.get_model('mud')
lava_model = archive.get_model('lava') lava_model = archive.get_model('lava')
tiles_batch = Batch(tiles_vertices, generated.size ** 2, 8, translation = PARAM_FORMAT_VEC3_SHORT) terrain_batch = Batch(tiles_vertices, generated.size ** 2, 8, translation = PARAM_FORMAT_VEC3_SHORT)
#TODO: generator & for real #TODO: generator & for real
vc = generated.volcano_c vc = generated.volcano_c
@ -165,71 +108,165 @@ def create_scene(keyboard, mouse):
model = mud_model model = mud_model
else: else:
model = rock_model model = rock_model
tiles_batch.spawn(model, vec3(float(((mx - 128) * 8) + 4), float(((127 - my) * 8) + 4), 0.0)) model.spawn(terrain_batch, vec3(float(((mx - 128) * 8) + 4), float(((127 - my) * 8) + 4), 0.0))
tests_texture = Texture(*archive.get_texture('tests')) tests_texture = archive.get_texture('tests')
tests_vertices = Vertices(*archive.get_vertices('tests')) tests_vertices = archive.get_vertices('tests')
blob_model = archive.get_model('blob') blob_model = archive.get_model('blob')
cube_model = archive.get_model('cube') cube_model = archive.get_model('cube')
clouds_model = archive.get_model('clouds') clouds_model = archive.get_model('clouds')
tests_batch = Batch(tests_vertices, 3, 3, tests_batch = Batch(tests_vertices, 3, 3,
translation = PARAM_FORMAT_VEC3_FLOAT, translation = PARAM_FORMAT_VEC3_FLOAT,
orientation = PARAM_FORMAT_MAT3_INT10 | PARAM_FORMAT_NORMALIZE) orientation = PARAM_FORMAT_MAT3_INT10 | PARAM_FORMAT_NORMALIZE)
blob_spawn_translation = vec3(-100.0, -500.0, 0.0)
cube_spawn_translation = vec3(100.0, -500.0, 0.0)
blob_forward = math.vec3_normalize((sun_direction[0], sun_direction[1], 0.0)) blob_forward = math.vec3_normalize((sun_direction[0], sun_direction[1], 0.0))
blob_right = math.vec3_cross(blob_forward, math.vec3_up) blob_right = math.vec3_cross(blob_forward, math.vec3_up)
blob_orientation = mat3(vec3(*blob_right), vec3(*blob_forward), vec3_up) blob_id = blob_model.spawn(tests_batch,
blob = TestEntity(tests_batch, blob_model, vec3(-100.0, -500.0, 0.0), blob_orientation) blob_spawn_translation, mat3(vec3(*blob_right), vec3(*blob_forward), vec3_up))
cube = TestEntity(tests_batch, cube_model, vec3(100.0, -500.0, 0.0), mat3_identity) cube_id = cube_model.spawn(tests_batch, cube_spawn_translation, mat3_identity)
clouds = TestEntity(tests_batch, clouds_model, vec3(0.0, 0.0, 32.0), mat3_identity) clouds_id = clouds_model.spawn(tests_batch, vec3(0.0, 0.0, 32.0), mat3_identity)
sea_polar_textures = sea.load_polar_textures(('data/sea_bump1.png', 'data/sea_bump2.png')) sea_polar_textures = sea.load_polar_textures(('data/sea_bump1.png', 'data/sea_bump2.png'))
sea_detail_texture = sea.load_detail_texture('data/sea_bump.png') sea_detail_texture = sea.load_detail_texture('data/sea_bump.png')
sea_triangles = sea.sea_triangles(64, proj_far_z - 0.1, proj_ratio) sky_triangles = create_triangles(triangles.sky_triangles(64, proj_far_z - 0.1, proj_ratio))
return SceneNode( camera = Camera()
PerfNode('frame', camera.set_projection(proj_hfov, proj_ratio, proj_near_z, proj_far_z)
TextureNode({1: heightmap, 2: normalmap}, environment = Environment()
FuncNode(update_camera, (mouse, camera, environment)),
TextureNode({0: tiles_texture}, blob_translation = tests_batch.translation[blob_id]
ShaderNode(tiles_shader, cube_translation = tests_batch.translation[cube_id]
InputNode(tiles_shader, camera, environment), cube_orientation = tests_batch.orientation[cube_id]
PerfNode('tiles_batch', clouds_orientation = tests_batch.orientation[clouds_id]
DrawNode(tiles_batch)))),
FuncNode(update_tests, (blob, cube, clouds)),
TextureNode({0: tests_texture},
ShaderNode(tests_shader,
InputNode(tests_shader, camera, environment),
PerfNode('tests_batch',
DrawNode(tests_batch))))),
FuncNode(update_sea, (camera, sea_phase)),
TextureNode({0: sea_polar_textures, 1: sea_detail_texture},
ShaderNode(sea_shader,
InputNode(sea_shader, camera, environment, sea_phase),
PerfNode('sea_triangles',
DrawNode(sea_triangles))))))
def loop(display):
events = Events(display)
keyboard = Keyboard(events)
mouse = Mouse(events, wheel = 60, wheel_min = 20)
scene = create_scene(keyboard, mouse)
print("Running... Ctrl+c to quit") print("Running... Ctrl+c to quit")
time = Time() start_time = time.monotonic()
current_time = 0.0
frame = 0
frame_min = 10000.0
frame_max = 0.0
frame_avg = 0.0
draw_min = 10000.0
draw_max = 0.0
draw_avg = 0.0
perf_count = 0
try: try:
while not keyboard.quit: while True:
current_time = time.monotonic() - start_time
frame_begin = time.thread_time()
events.update() events.update()
time.update() if keyboard.quit:
clear_buffer(True, True, True) break
scene.draw(time)
begin_frame()
camera_distance = mouse.wheel * 20.0
camera_yaw = mouse.drag[0] * 0.001
camera_pitch = mouse.drag[1] * 0.001 + pi * 0.25
camera.set_view(camera_yaw, camera_pitch, camera_distance)
environment.from_sun(camera.view, sun_direction, sun_power)
terrain_shader.select()
camera.set_inputs(terrain_shader)
environment.set_inputs(terrain_shader)
select_texture(0, tiles_texture)
select_texture(1, heightmap)
select_texture(2, normalmap)
draw_begin = time.thread_time()
terrain_batch.draw()
draw_end = time.thread_time()
unselect_texture(0, tiles_texture)
unselect_texture(1, heightmap)
unselect_texture(2, normalmap)
terrain_shader.unselect()
rotation = mat3()
mat3_rotation(rotation, vec3_up, (current_time * 0.21) % tau)
mat3_mul_vec3(blob_translation, rotation, blob_spawn_translation)
mat3_mul_vec3(cube_translation, rotation, cube_spawn_translation)
mat3_rotation(cube_orientation, vec3_up, (current_time * 0.43) % tau)
mat3_rotation(clouds_orientation, vec3_up, (current_time * -0.037) % tau)
tests_shader.select()
camera.set_inputs(tests_shader)
environment.set_inputs(tests_shader)
select_texture(0, tests_texture)
select_texture(1, heightmap)
select_texture(2, normalmap)
tests_batch.draw()
unselect_texture(0, tests_texture)
unselect_texture(1, heightmap)
unselect_texture(2, normalmap)
tests_shader.unselect()
camera.to_km()
sky_shader.select()
camera.set_inputs(sky_shader)
environment.set_inputs(sky_shader)
set_input_float(sky_shader.u_sea_phase, (current_time * 0.023) % 1.0)
select_texture(0, sea_polar_textures)
select_texture(1, sea_detail_texture)
draw_triangles(sky_triangles)
unselect_texture(0, sea_polar_textures)
unselect_texture(1, sea_detail_texture)
sky_shader.unselect()
frame_end = time.thread_time()
end_frame()
swap_buffers(display) swap_buffers(display)
if frame > 0:
draw_ms = draw_end - draw_begin
draw_min = min(draw_min, draw_ms)
draw_max = max(draw_max, draw_ms)
draw_avg += draw_ms
frame_ms = frame_end - frame_begin
frame_min = min(frame_min, frame_ms)
frame_max = max(frame_max, frame_ms)
frame_avg += frame_ms
perf_count += 1
frame += 1
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
def main(): print("\rDraw *", perf_count,
print("Initializing...") ": min =", round(draw_min * 1000.0, 2),
display = create_display(b'RK Island - Drag to rotate, wheel to zoom, q to quit', 1600, 900) ", max =", round(draw_max * 1000.0, 2),
render_initialize(__debug__) ", avg =", round((draw_avg / perf_count) * 1000.0, 2), "ms")
loop(display)
print("\rFrame *", perf_count,
": min =", round(frame_min * 1000.0, 2),
", max =", round(frame_max * 1000.0, 2),
", avg =", round((frame_avg / perf_count) * 1000.0, 2), "ms")
# seed 666
# for _ in range(10000):
# current_time = 0 # time.monotonic() - start_time
# Draw * 9999 : min = 0.14 , max = 0.43 , avg = 0.19 ms
# Draw * 9999 : min = 0.14 , max = 0.35 , avg = 0.19 ms
# Draw * 9999 : min = 0.13 , max = 0.44 , avg = 0.18 ms
# Frame * 9999 : min = 0.21 , max = 0.7 , avg = 0.33 ms
# Frame * 9999 : min = 0.2 , max = 0.54 , avg = 0.31 ms
# Frame * 9999 : min = 0.19 , max = 0.6 , avg = 0.29 ms
print("Quitting...") print("Quitting...")
del tests_batch
del terrain_batch
destroy_texture(sea_polar_textures)
destroy_texture(sea_detail_texture)
destroy_texture(heightmap)
destroy_texture(normalmap)
destroy_triangles(sky_triangles)
archive.destroy()
del terrain_shader
del tests_shader
del sky_shader
render_terminate() render_terminate()
del events
destroy_display(display) destroy_display(display)

View File

@ -1,29 +0,0 @@
# 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/>.
from engine import set_input_float
class InputFloat:
__slots__ = '_input', 'value'
def __init__(self, input, value):
self._input = input
self.value = value
def update(self, value):
self.value = value
def set_inputs(self, shader):
set_input_float(self._input, self.value)

View File

@ -16,7 +16,9 @@
import struct import struct
from array import array from array import array
from pathlib import Path from pathlib import Path
import png import png
import engine import engine
VERTEX_SIZE = 20 VERTEX_SIZE = 20
@ -114,14 +116,6 @@ class TextureData:
self.flags = flags self.flags = flags
self.pixels = pixels self.pixels = pixels
def __iter__(self):
yield self.format
yield self.width
yield self.height
yield self.nlevels
yield self.flags
yield self.pixels
@classmethod @classmethod
def from_archive(cls, file): def from_archive(cls, file):
_read_magic(file, b'TX') _read_magic(file, b'TX')
@ -138,6 +132,9 @@ class TextureData:
_write_struct(file, 'IIIII', (self.format, self.width, self.height, self.nlevels, self.flags)) _write_struct(file, 'IIIII', (self.format, self.width, self.height, self.nlevels, self.flags))
_write_blob(file, self.pixels) _write_blob(file, self.pixels)
def create_texture(data):
return engine.create_texture(data.format, data.width, data.height, data.nlevels, data.flags, data.pixels)
class VerticesData: class VerticesData:
__slots__ = 'name', 'vertices', 'indices' __slots__ = 'name', 'vertices', 'indices'
@ -148,12 +145,6 @@ class VerticesData:
self.vertices = vertices self.vertices = vertices
self.indices = indices self.indices = indices
def __iter__(self):
yield VERTEX_FORMAT
yield len(self.vertices) // VERTEX_SIZE
yield self.vertices
yield self.indices
@classmethod @classmethod
def from_archive(cls, file): def from_archive(cls, file):
_read_magic(file, b'VT') _read_magic(file, b'VT')
@ -170,6 +161,9 @@ class VerticesData:
_write_array(file, self.vertices) _write_array(file, self.vertices)
_write_array(file, self.indices) _write_array(file, self.indices)
def create_vertices(data):
return engine.create_vertices(VERTEX_FORMAT, len(data.vertices) // VERTEX_SIZE, data.vertices, data.indices)
class ModelData: class ModelData:
__slots__ = 'name', 'flags', 'mesh' __slots__ = 'name', 'flags', 'mesh'
@ -190,6 +184,19 @@ class ModelData:
_write_string(file, self.name) _write_string(file, self.name)
_write_struct(file, 'II', (self.flags, self.mesh)) _write_struct(file, 'II', (self.flags, self.mesh))
class Model:
__slots__ = 'flags', 'mesh'
def __init__(self, flags, mesh):
self.flags = flags
self.mesh = mesh
def spawn(self, batch, *params):
return batch.append(self.flags, self.mesh, params)
def create_model(data):
return Model(data.flags, data.mesh)
class Archive: class Archive:
__slots__ = 'textures_db', 'vertices_db', 'models_db' __slots__ = 'textures_db', 'vertices_db', 'models_db'
@ -198,6 +205,15 @@ class Archive:
self.vertices_db = vertices_db or {} self.vertices_db = vertices_db or {}
self.models_db = models_db or {} self.models_db = models_db or {}
def destroy(self):
for vertices in self.vertices_db.values():
engine.destroy_vertices(vertices)
for texture in self.textures_db.values():
engine.destroy_texture(texture)
self.textures_db.clear()
self.vertices_db.clear()
self.models_db.clear()
def get_texture(self, name): def get_texture(self, name):
return self.textures_db[name] return self.textures_db[name]
@ -207,6 +223,18 @@ class Archive:
def get_model(self, name): def get_model(self, name):
return self.models_db[name] return self.models_db[name]
@classmethod
def _new_texture(cls, data):
return data
@classmethod
def _new_vertices(cls, data):
return data
@classmethod
def _new_model(cls, data):
return data
@classmethod @classmethod
def from_archive(cls, file): def from_archive(cls, file):
textures_db = {} textures_db = {}
@ -216,15 +244,22 @@ class Archive:
ntextures, nvertices, nmodels = _read_struct(file, 'III') ntextures, nvertices, nmodels = _read_struct(file, 'III')
for _ in range(ntextures): for _ in range(ntextures):
data = TextureData.from_archive(file) data = TextureData.from_archive(file)
textures_db[data.name] = data textures_db[data.name] = cls._new_texture(data)
for _ in range(nvertices): for _ in range(nvertices):
data = VerticesData.from_archive(file) data = VerticesData.from_archive(file)
vertices_db[data.name] = data vertices_db[data.name] = cls._new_vertices(data)
for _ in range(nmodels): for _ in range(nmodels):
data = ModelData.from_archive(file) data = ModelData.from_archive(file)
models_db[data.name] = data models_db[data.name] = cls._new_model(data)
return cls(textures_db, vertices_db, models_db) return cls(textures_db, vertices_db, models_db)
@classmethod
def load(cls, filename):
file = open(Path(filename), 'rb')
archive = cls.from_archive(file)
file.close()
return archive
def to_archive(self, file): def to_archive(self, file):
_write_magic(file, b'RKAR') _write_magic(file, b'RKAR')
_write_struct(file, 'III', (len(self.textures_db), len(self.vertices_db), len(self.models_db))) _write_struct(file, 'III', (len(self.textures_db), len(self.vertices_db), len(self.models_db)))
@ -235,14 +270,20 @@ class Archive:
for _, data in self.models_db.items(): for _, data in self.models_db.items():
data.to_archive(file) data.to_archive(file)
@classmethod
def load(cls, filename):
file = open(Path(filename), 'rb')
archive = cls.from_archive(file)
file.close()
return archive
def save(self, filename): def save(self, filename):
file = open(Path(filename), 'wb') file = open(Path(filename), 'wb')
self.to_archive(file) self.to_archive(file)
file.close() file.close()
class RuntimeArchive(Archive):
@classmethod
def _new_texture(cls, data):
return create_texture(data)
@classmethod
def _new_vertices(cls, data):
return create_vertices(data)
@classmethod
def _new_model(cls, data):
return create_model(data)

View File

@ -1,85 +0,0 @@
# 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/>.
class Node:
__slots__ = '_subnodes'
def __init__(self, *subnodes):
self._subnodes = subnodes
def draw(self, time):
for subnode in self._subnodes:
subnode.draw(time)
class SceneNode(Node):
pass
class TextureNode(Node):
__slots__ = '_textures'
def __init__(self, textures, *subnodes):
Node.__init__(self, *subnodes)
self._textures = textures
def draw(self, time):
items = self._textures.items()
for slot, texture in items:
texture.select(slot)
Node.draw(self, time)
for slot, texture in items:
texture.unselect(slot)
class ShaderNode(Node):
__slots__ = '_shader'
def __init__(self, shader, *subnodes):
Node.__init__(self, *subnodes)
self._shader = shader
def draw(self, time):
self._shader.select()
Node.draw(self, time)
self._shader.unselect()
class InputNode:
__slots__ = '_shader', '_inputs'
def __init__(self, shader, *inputs):
self._shader = shader
self._inputs = inputs
def draw(self, time):
shader = self._shader
for input in self._inputs:
input.set_inputs(shader)
class DrawNode:
__slots__ = '_drawable'
def __init__(self, drawable):
self._drawable = drawable
def draw(self, time):
self._drawable.draw()
class FuncNode:
__slots__ = '_func', '_params'
def __init__(self, func, params):
self._func = func
self._params = params
def draw(self, time):
self._func(time, *self._params)

View File

@ -13,15 +13,14 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from itertools import chain, product from itertools import product
from math import tau, cos, sin, copysign from math import tau, cos, sin, copysign
from array import array from array import array
from engine import TEXTURE_FORMAT_RGB10_A2, TEXTURE_FORMAT_TYPECODE, TEXTURE_FLAG_MIN_LINEAR, TEXTURE_FLAG_MAG_LINEAR from engine import (create_texture,
TEXTURE_FORMAT_RGB10_A2, TEXTURE_FORMAT_TYPECODE, TEXTURE_FLAG_MIN_LINEAR, TEXTURE_FLAG_MAG_LINEAR)
from game.math import vec3_scale, vec3_direction, vec3_cross, vec3_normal_rgb10a2 from game.math import vec3_scale, vec3_direction, vec3_cross, vec3_normal_rgb10a2
from game.texture import Texture
from game.triangles import Triangles
from game.resources import load_png from game.resources import load_png
_format = TEXTURE_FORMAT_RGB10_A2 _format = TEXTURE_FORMAT_RGB10_A2
@ -52,7 +51,7 @@ def load_polar_textures(paths, waves_height = 0.008):
_width, _height, normals = load_texture(path) _width, _height, normals = load_texture(path)
assert _width == width and _height == height assert _width == width and _height == height
data.extend(list(map(_conv, normals))) data.extend(list(map(_conv, normals)))
return Texture(_format, width, height, len(paths), _flags, data) return create_texture(_format, width, height, len(paths), _flags, data)
def load_detail_texture(path, scale = 0.5, waves_height = 0.002): def load_detail_texture(path, scale = 0.5, waves_height = 0.002):
width, height, data = load_png(path) width, height, data = load_png(path)
@ -69,36 +68,4 @@ def load_detail_texture(path, scale = 0.5, waves_height = 0.002):
return vec3_scale(n, copysign(1.0, n[2])) return vec3_scale(n, copysign(1.0, n[2]))
normals = list(map(normal, product(range(height), range(width)), data)) normals = list(map(normal, product(range(height), range(width)), data))
data = array(_typecode, list(map(_conv, normals))) data = array(_typecode, list(map(_conv, normals)))
return Texture(_format, width, height, 0, _flags, data) return create_texture(_format, width, height, 0, _flags, data)
# TODO: with FOV
def sea_triangles(vsubdivs, distance, projection_ratio):
assert vsubdivs > 0
vertices = []
hsubdivs = round(vsubdivs * projection_ratio)
z = -distance
width = distance * projection_ratio
height = distance
startx = width * -0.5
starty = height * -0.5
stepx = width / hsubdivs
stepy = height / vsubdivs
y1 = starty
y2 = y1 + stepy
for sy in range(vsubdivs):
x1 = startx
x2 = x1 + stepx
for sx in range(hsubdivs):
a = (x1, y2, z)
b = (x2, y1, z)
vertices.append((x1, y1, z))
vertices.append(b)
vertices.append(a)
vertices.append((x2, y2, z))
vertices.append(a)
vertices.append(b)
x1 = x2
x2 += stepx
y1 = y2
y2 += stepy
return Triangles(array('f', chain.from_iterable(vertices)))

View File

@ -15,7 +15,7 @@
from pathlib import Path from pathlib import Path
from engine import create_shader, resolve_input, select_shader, unselect_shader, destroy_shader from engine import load_shader, resolve_input, select_shader, unselect_shader, destroy_shader
def _cleanup(line): def _cleanup(line):
return line.partition('//')[0].strip() return line.partition('//')[0].strip()
@ -70,7 +70,7 @@ class Shader:
print("Loading fragment shader", frag_name) print("Loading fragment shader", frag_name)
frag_lines = _load_source(path / (frag_name + '_opengles.frag')) frag_lines = _load_source(path / (frag_name + '_opengles.frag'))
assert frag_lines assert frag_lines
self._shader = create_shader(list(map(_convert, vert_lines)), list(map(_convert, frag_lines))) self._shader = load_shader(list(map(_convert, vert_lines)), list(map(_convert, frag_lines)))
_parse(self, vert_lines, frag_lines) _parse(self, vert_lines, frag_lines)
def __del__(self): def __del__(self):

View File

@ -1,31 +0,0 @@
# 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/>.
from engine import create_texture, select_texture, unselect_texture, destroy_texture
class Texture:
__slots__ = '_texture'
def __init__(self, format, width, height, nlevels, flags, pixels):
self._texture = create_texture(format, width, height, nlevels, flags, pixels)
def __del__(self):
destroy_texture(self._texture)
def select(self, slot):
select_texture(slot, self._texture)
def unselect(self, slot):
unselect_texture(slot, self._texture)

View File

@ -1,29 +0,0 @@
# 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/>.
from time import monotonic
class Time:
__slots__ = '_start', 'current', 'delta'
def __init__(self):
self._start = monotonic()
self.current = 0.0
self.delta = 0.0
def update(self):
current = monotonic() - self._start
self.delta = current - self.current
self.current = current

View File

@ -13,16 +13,38 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from engine import create_triangles, draw_triangles, destroy_triangles from itertools import chain
from array import array
from math import cos, sin
class Triangles: # TODO: with FOV
__slots__ = '_triangles' def sky_triangles(vsubdivs, distance, projection_ratio):
assert vsubdivs > 0
def __init__(self, triangles): vertices = []
self._triangles = create_triangles(triangles) hsubdivs = round(vsubdivs * projection_ratio)
z = -distance
def __del__(self): width = distance * projection_ratio
destroy_triangles(self._triangles) height = distance
startx = width * -0.5
def draw(self): starty = height * -0.5
draw_triangles(self._triangles) stepx = width / hsubdivs
stepy = height / vsubdivs
y1 = starty
y2 = y1 + stepy
for sy in range(vsubdivs):
x1 = startx
x2 = x1 + stepx
for sx in range(hsubdivs):
a = (x1, y2, z)
b = (x2, y1, z)
vertices.append((x1, y1, z))
vertices.append(b)
vertices.append(a)
vertices.append((x2, y2, z))
vertices.append(a)
vertices.append(b)
x1 = x2
x2 += stepx
y1 = y2
y2 += stepy
return array('f', chain.from_iterable(vertices))

View File

@ -1,25 +0,0 @@
# 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/>.
from engine import create_vertices, destroy_vertices
class Vertices:
__slots__ = '_vertices'
def __init__(self, format, nvertices, vertices, indices):
self._vertices = create_vertices(format, nvertices, vertices, indices)
def __del__(self):
destroy_vertices(self._vertices)