Compare commits

...

52 Commits

Author SHA1 Message Date
830daec408 Bump engine submodule and merge vertex and param formats. 2023-01-06 18:45:34 +01:00
4783f0ba2d Bump engine submodule and fix signed integers normalisation. 2023-01-04 15:26:05 +01:00
ccc168c896 Bump engine submodule and add branch name. 2023-01-04 12:57:38 +01:00
cbae4ec0e7 Cleanup game script. 2023-01-04 09:38:27 +01:00
efdbbb5815 Bump engine submodules and rework vertices data for the new mesh struct. 2023-01-04 09:38:04 +01:00
f487a4fcb7 Bump engine submodule. 2023-01-03 14:10:43 +01:00
4cb67c0d79 Fix perf node division by zero. 2023-01-03 14:10:04 +01:00
78440cce16 Bump engine submodule and rework resouces. 2023-01-03 13:02:22 +01:00
84fc41e014 Update screenshort with sRGB enabled. 2023-01-03 05:46:16 +01:00
e90f198fa0 Bump engine submodule and freeze batchs. 2023-01-03 05:32:45 +01:00
a96ea8bb6b Bump engine submodule and add static batch. 2023-01-02 17:20:34 +01:00
7219039980 Split scene groups and nodes. 2023-01-02 12:43:25 +01:00
3344ac7d6c Rename samplers in sea shader. 2023-01-02 12:39:23 +01:00
89e0b99d3a Automatic uniforms with binding. 2023-01-02 12:33:34 +01:00
9131523ae7 Bump engine submodule. 2023-01-01 08:11:38 +01:00
3621a340f1 Add perf node. 2023-01-01 07:45:01 +01:00
8e49c16929 Fix camera yaw and pitch. 2022-12-31 22:30:57 +01:00
7c2d3d81ae Better input node. 2022-12-31 22:08:06 +01:00
dd786ef2b4 Better texture node. 2022-12-31 21:45:29 +01:00
acc2e4cd28 Add entity class. 2022-12-31 21:02:46 +01:00
af9365c6e0 Rework resources and split main loop. 2022-12-31 20:24:08 +01:00
513d79a67c Fix scene node declaration. 2022-12-31 20:23:27 +01:00
f66c6ea6e9 Cleanup sea triangles creation. 2022-12-31 19:32:39 +01:00
9e77e6680b Rename sea shader. 2022-12-31 19:27:23 +01:00
f4bfb7fee0 Rename tiles shader. 2022-12-31 19:17:32 +01:00
6485da7d67 Basic scene graph. 2022-12-31 19:16:44 +01:00
9fff666ebf Automatic shader inputs. 2022-12-31 15:22:30 +01:00
09dcaae95b Add support for includes when loading shaders. 2022-12-31 14:38:53 +01:00
2efdcdca64 Bump engine submodule and move samplers bindings to shaders. 2022-12-31 13:42:22 +01:00
6701c8dbb3 Bump engine submodule. 2022-12-31 12:28:20 +01:00
f05a4b64a2 Bump engine submodule. 2022-12-31 09:39:49 +01:00
2f3508ed3a Improve mesh indexing. 2022-12-31 06:20:30 +01:00
c3e50b7d60 Bump engine submodule and update timings. 2022-12-31 05:38:31 +01:00
b6553db09e Bump engine submodule and update timings. 2022-12-30 14:58:40 +01:00
bd445575fd Bump engine submodule and update timings. 2022-12-30 14:22:26 +01:00
dc75859d95 Update timings. 2022-12-30 12:38:27 +01:00
2e905a80a8 Use kwargs in Batch construction to provide parameters names. 2022-12-30 12:29:33 +01:00
8e020915a6 Bump engine submodule and switch back to SoA parameters. 2022-12-30 10:53:37 +01:00
66a099e51f Bump engine submodule. 2022-12-29 19:19:17 +01:00
1649bb6dc9 Run forever. 2022-12-29 14:36:30 +01:00
81f0745feb Pimp forest texture. 2022-12-29 14:36:16 +01:00
758ed8c7f4 Bump engine submodule and add interactive orbit camera. 2022-12-29 08:15:38 +01:00
850a254abc Add events manager and mouse keyboard clients. 2022-12-29 08:13:53 +01:00
1ac12ea9ea Bump engine submodule and update timings. 2022-12-27 06:30:14 +01:00
108976bdff Bump engine submodule and switch to mat3 orientations. 2022-12-26 14:14:33 +01:00
c4309ef7c7 Bump engine submodule. 2022-12-25 04:35:09 +01:00
bff54342dc Bump engine submodule. 2022-12-24 11:35:14 +01:00
119cd76156 Bump engine submodule and quit with a click or by pressing q. 2022-12-24 06:41:02 +01:00
7ed23a5d5f Terminate on key release. 2022-12-23 10:25:37 +01:00
34277274a3 Use the new buffer method to create ctypes arrays. 2022-12-23 10:24:21 +01:00
858dcd95bc Bump engine submodule. 2022-12-23 10:23:32 +01:00
3e83c636ee A little bit of clenaup. 2022-12-23 06:36:24 +01:00
32 changed files with 872 additions and 544 deletions

1
.gitmodules vendored
View File

@ -1,3 +1,4 @@
[submodule "engine"] [submodule "engine"]
path = engine path = engine
url = https://code.rozk.net/RK/rk_engine.git url = https://code.rozk.net/RK/rk_engine.git
branch = master

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 800 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

2
engine

Submodule engine updated: ccce6c5d83...7384a014ff

View File

@ -13,36 +13,72 @@
# 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 ctypes import c_ubyte, c_uint, POINTER from ctypes import c_ubyte, c_ushort, c_void_p, POINTER, addressof
from array import array
from engine import ( from engine import (
INSTANCE_FLAG_SPAWNED, BATCH_MAX_SIZE, create_batch, draw_batch, destroy_batch) buffer, INSTANCE_FLAG_SPAWNED, BATCH_MAX_SIZE, vertex_type, vertex_format,
create_batch, fill_batch, draw_batch, destroy_batch)
_flags_t = c_ubyte
_flags_p = POINTER(_flags_t)
_mesh_t = c_ushort
_mesh_p = POINTER(_mesh_t)
class Batch: class Batch:
__slots__ = '_batch', '_paramsp', 'size', 'max_size', 'flags', 'meshes', 'params' __slots__ = ('_batch', 'vertices', 'max_size', 'size',
'flags', '_flags', 'mesh', '_meshes', 'param', '_params', '_name', '__dict__')
def __init__(self, vertices, max_size, params_format, params_type): def __init__(self, vertices, max_size, **params_decls):
assert max_size <= BATCH_MAX_SIZE assert max_size <= BATCH_MAX_SIZE
self._batch = create_batch(vertices, max_size, params_format) nparams = len(params_decls)
self._paramsp = POINTER(params_type) if nparams:
self.size = 0 self._batch = create_batch(vertices._vertices, max_size, vertex_format(*params_decls.values()))
else:
self._batch = create_batch(vertices._vertices, max_size, None)
self.vertices = vertices
self.max_size = max_size self.max_size = max_size
self.flags = (c_ubyte * max_size)() self.size = 0
self.meshes = (c_uint * max_size)() self.flags = buffer(_flags_t, max_size)
self.params = (params_type * max_size)() self._flags = _flags_p(self.flags)
self.mesh = buffer(_mesh_t, max_size)
self._meshes = _mesh_p(self.mesh)
if nparams:
self.param = tuple(map(lambda f: buffer(vertex_type(f), max_size), params_decls.values()))
self._params = (c_void_p * nparams)(*map(addressof, self.param))
self._name = dict(zip(params_decls.keys(), range(nparams)))
for name, value in zip(params_decls.keys(), self.param):
setattr(self, name, value)
else:
self.param = None
self._params = None
self._name = None
def __del__(self): def __del__(self):
destroy_batch(self._batch) destroy_batch(self._batch)
def append(self, flags, mesh, params): def spawn(self, flags, mesh, **params):
index = self.size index = self.size
assert index < self.max_size assert index < self.max_size
self.flags[index] = flags | INSTANCE_FLAG_SPAWNED self.flags[index] = flags | INSTANCE_FLAG_SPAWNED
self.meshes[index] = mesh self.mesh[index] = mesh
self.params[index] = params for name, value in params.items():
getattr(self, name)[index] = value
self.size += 1 self.size += 1
return index return index
def freeze(self, flags = None, mesh = None, **params):
if flags is not None:
self._flags = None if flags else _flags_p(self.flags)
if mesh is not None:
self._meshes = None if mesh else _mesh_p(self.mesh)
for name, value in params.items():
if value is not None:
index = self._name[name]
self._params[index] = None if value else addressof(self.param[index])
def fill(self):
fill_batch(self._batch, self.size, self._flags, self._meshes, self._params)
def draw(self): def draw(self):
draw_batch(self._batch, self.size, self.flags, self.meshes, self._paramsp(self.params)) fill_batch(self._batch, self.size, self._flags, self._meshes, self._params)
draw_batch(self._batch)

View File

@ -13,50 +13,30 @@
# 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 ( from engine import vec3_origin, mat4, mat4_projection, mat4_orbit, set_input_mat4
vec3, vec3_mul_vec3,
mat3, mat3_rotation, mat3_mul_vec3,
mat4, mat4_projection, mat4_lookat,
resolve_input, set_input_mat4)
_m2km = vec3(0.001, 0.001, 0.001)
class _Inputs:
__slots__ = 'projection', 'view'
def __init__(self, shader, projection, view):
self.projection = resolve_input(shader, projection)
self.view = resolve_input(shader, view)
class Camera: class Camera:
__slots__ = 'origin', 'lookat', 'rotation', 'projection', 'view' __slots__ = 'yaw', 'pitch', 'distance', 'projection', 'view'
def __init__(self): def __init__(self):
self.origin = vec3() self.yaw = 0.0
self.lookat = vec3() self.pitch = 0.0
self.rotation = mat3() self.distance = 0.0
self.projection = mat4() self.projection = mat4()
self.view = mat4() self.view = mat4()
def set_projection(self, half_fov, ratio, near_z, far_z): def set_projection(self, half_fov, ratio, near_z, far_z):
mat4_projection(self.projection, half_fov, ratio, near_z, far_z) mat4_projection(self.projection, half_fov, ratio, near_z, far_z)
def set_view(self, origin, lookat, axis, angle): def set_view(self, yaw, pitch, distance):
self.origin.set(*origin) self.yaw = yaw
self.lookat.set(*lookat) self.pitch = pitch
mat3_rotation(self.rotation, axis, angle) self.distance = distance
mat3_mul_vec3(self.origin, self.rotation, self.origin) mat4_orbit(self.view, vec3_origin, yaw, pitch, distance)
mat3_mul_vec3(self.lookat, self.rotation, self.lookat)
mat4_lookat(self.view, self.origin, self.lookat)
def to_km(self): def to_km(self):
vec3_mul_vec3(self.origin, _m2km, self.origin) mat4_orbit(self.view, vec3_origin, self.yaw, self.pitch, self.distance * 0.001)
vec3_mul_vec3(self.lookat, _m2km, self.lookat)
mat4_lookat(self.view, self.origin, self.lookat)
def resolve_inputs(self, shader, projection = b'u_projection', view = b'u_view'): def set_inputs(self, shader):
return _Inputs(shader, projection, view) set_input_mat4(shader.u_projection, self.projection)
set_input_mat4(shader.u_view, self.view)
def update_inputs(self, inputs):
set_input_mat4(inputs.projection, self.projection)
set_input_mat4(inputs.view, self.view)

20
game/entity.py Normal file
View File

@ -0,0 +1,20 @@
# 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, flags, mesh, **params):
self.index = batch.spawn(flags, mesh, **params)

View File

@ -14,7 +14,7 @@
# 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 math import radians, cos from math import radians, cos
from engine import vec3, mat4_mul_vec3, resolve_input, set_input_vec3 from engine import vec3, mat4_mul_vec3, set_input_vec3
from game.math import vec3_up, vec3_add, vec3_sub, vec3_scale, vec3_mul, vec3_dot from game.math import vec3_up, vec3_add, vec3_sub, vec3_scale, vec3_mul, vec3_dot
def _angles(start, end): def _angles(start, end):
@ -62,16 +62,6 @@ def _resolve_color(ranges, c):
w, (a, b) = _resolve(ranges, c) w, (a, b) = _resolve(ranges, c)
return vec3_add(a, vec3_scale(b, w)) return vec3_add(a, vec3_scale(b, w))
class _Inputs:
__slots__ = 'light_direction', 'light_color', 'horizon_color', 'sky_color', 'sun_color'
def __init__(self, shader):
self.light_direction = resolve_input(shader, b'u_light_direction')
self.light_color = resolve_input(shader, b'u_light_color')
self.horizon_color = resolve_input(shader, b'u_horizon_color')
self.sky_color = resolve_input(shader, b'u_sky_color')
self.sun_color = resolve_input(shader, b'u_sun_color')
class Environment: class Environment:
__slots__ = 'light_direction', 'light_color', 'horizon_color', 'sky_color', 'sun_color' __slots__ = 'light_direction', 'light_color', 'horizon_color', 'sky_color', 'sun_color'
@ -91,12 +81,9 @@ class Environment:
self.sky_color.set(*vec3_scale(_resolve_color(_sky_color, c), light_power)) self.sky_color.set(*vec3_scale(_resolve_color(_sky_color, c), light_power))
self.sun_color.set(*_resolve_color(_sun_color, c)) self.sun_color.set(*_resolve_color(_sun_color, c))
def resolve_inputs(self, shader): def set_inputs(self, shader):
return _Inputs(shader) set_input_vec3(shader.u_light_direction, self.light_direction)
set_input_vec3(shader.u_light_color, self.light_color)
def update_inputs(self, inputs): set_input_vec3(shader.u_horizon_color, self.horizon_color)
set_input_vec3(inputs.light_direction, self.light_direction) set_input_vec3(shader.u_sky_color, self.sky_color)
set_input_vec3(inputs.light_color, self.light_color) set_input_vec3(shader.u_sun_color, self.sun_color)
set_input_vec3(inputs.horizon_color, self.horizon_color)
set_input_vec3(inputs.sky_color, self.sky_color)
set_input_vec3(inputs.sun_color, self.sun_color)

54
game/events.py Normal file
View File

@ -0,0 +1,54 @@
# 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 (
buffer,
EVENT_FOCUS_IN, EVENT_FOCUS_OUT,
EVENT_KEY_PRESS, EVENT_KEY_RELEASE,
EVENT_BUTTON_PRESS, EVENT_BUTTON_RELEASE,
EVENT_MOTION,
Event, create_events, destroy_events, consume_events)
_max_type = max(
EVENT_FOCUS_IN, EVENT_FOCUS_OUT,
EVENT_KEY_PRESS, EVENT_KEY_RELEASE,
EVENT_BUTTON_PRESS, EVENT_BUTTON_RELEASE,
EVENT_MOTION)
_max_events = 64
class Events:
__slots__ = '_display', '_events', '_buffer', '_handlers'
def __init__(self, display):
self._display = display
self._events = create_events(display)
self._buffer = buffer(Event, _max_events)
self._handlers = [[] for _ in range(_max_type + 1)]
def __del__(self):
destroy_events(self._display, self._events)
def register(self, type, handler):
assert type <= _max_type
self._handlers[type].append(handler)
def update(self):
nevents = consume_events(self._events, self._buffer, _max_events)
if nevents:
for event in self._buffer[:nevents]:
data = event.data
for handler in self._handlers[event.type]:
handler(data)

View File

@ -13,73 +13,131 @@
# 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/>.
import time from time import thread_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 import generator from game.time import Time
from game import shader from game.events import Events
from game import resources from game.mouse import Mouse
from game import batch from game.keyboard import Keyboard
from game import triangles from game.generator import Generator
from game import sea 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.camera import Camera
from game.environment import Environment from game.environment import Environment
from game.inputs import InputFloat
from game.batch import Batch
from game.entity import Entity
from game.scene import Group, SceneGroup, TextureGroup, ShaderGroup, 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
proj_near_z = 8.0 proj_near_z = 8.0
proj_far_z = 3000.0 proj_far_z = 3000.0
camera_origin = (0.0, -1200.0, 500.0) sun_direction = math.vec3_normalize((1.0, 0.0, 0.5))
camera_lookat = (0.0, 500.0, -500.0)
sun_direction = math.vec3_normalize((1.0, 0.0, 1.0))
sun_power = 1.0 sun_power = 1.0
def main(): class PerfGroup(Group):
__slots__ = '_count', '_min', '_max', '_total', '_name'
def __init__(self, name, *subnodes):
Group.__init__(self, *subnodes)
self._count = None
self._min = 10000.0
self._max = 0.0
self._total = 0.0
self._name = name
def __del__(self):
if self._count:
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()
Group.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, mesh, translation, orientation):
Entity.__init__(self, batch, INSTANCE_FLAG_VISIBLE, mesh,
translation = translation,
orientation = 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.process_time() generated = Generator(256)
generated = generator.Generator(256)
gen_end = time.process_time()
print("Done: ", round(gen_end - gen_begin, 2), "seconds")
print("Initializing...") heightmap = Texture(
window = initialize(b'RK Island', 1600, 900) TEXTURE_FORMAT_FLOAT_32, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
terrain_shader = shader.load('terrain', 'common')
tests_shader = shader.load('tests', 'common')
sky_shader = shader.load('sky')
heightmap = create_texture(
TEXTURE_FORMAT_32F, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
generated.packed_heights) generated.packed_heights)
terrain_heightmap_sampler = resolve_input(terrain_shader, b'u_height_sampler') normalmap = Texture(
tests_heightmap_sampler = resolve_input(tests_shader, b'u_height_sampler')
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)
terrain_normalmap_sampler = resolve_input(terrain_shader, b'u_normal_sampler')
tests_normalmap_sampler = resolve_input(tests_shader, b'u_normal_sampler')
print("Loading resources...") print("Loading resources...")
archive = resources.RuntimeArchive.load('data/rk_island.rkar') archive = Archive.load('data/rk_island.rkar')
print("Building tiles...") print("Building tiles...")
tiles_texture = archive.get_texture('tiles') tiles_texture = Texture(*archive.get_texture('tiles'))
tiles_sampler = resolve_input(terrain_shader, b'u_texture_sampler')
tiles_vertices = archive.get_vertices('tiles') tiles_vertices = archive.get_vertices('tiles')
water_model = archive.get_model('water') water_mesh = tiles_vertices.get_mesh('water')
sand_model = archive.get_model('sand') sand_mesh = tiles_vertices.get_mesh('sand')
grass_model = archive.get_model('grass') grass_mesh = tiles_vertices.get_mesh('grass')
forest_model = archive.get_model('forest') forest_mesh = tiles_vertices.get_mesh('forest')
rock_model = archive.get_model('rock') rock_mesh = tiles_vertices.get_mesh('rock')
mud_model = archive.get_model('mud') mud_mesh = tiles_vertices.get_mesh('mud')
lava_model = archive.get_model('lava') lava_mesh = tiles_vertices.get_mesh('lava')
terrain_batch = batch.Batch(tiles_vertices, generated.size ** 2, params_format(PARAM_FORMAT_VEC3_SHORT), vec3) tiles_batch = Batch(Vertices(*tiles_vertices), generated.size ** 2, translation = VERTEX_FORMAT_VEC3_SHORT)
#TODO: generator & for real #TODO: generator & for real
vc = generated.volcano_c vc = generated.volcano_c
@ -91,188 +149,98 @@ def main():
if h == 0.0: if h == 0.0:
continue continue
if r > 0.0: if r > 0.0:
model = water_model mesh = water_mesh
elif h < 2.0: elif h < 2.0:
model = sand_model mesh = sand_mesh
elif h < 180: elif h < 180:
if nz > 0.9: if nz > 0.9:
if ny < -0.01 and nz > 0.93: if ny < -0.01 and nz > 0.93:
model = forest_model mesh = forest_mesh
else: else:
model = grass_model mesh = grass_mesh
else: else:
model = rock_model mesh = rock_mesh
elif vd < vr - 3.0 and nz > 0.999: elif vd < vr - 3.0 and nz > 0.999:
model = lava_model mesh = lava_mesh
elif vd < vr + 2.0: elif vd < vr + 2.0:
model = mud_model mesh = mud_mesh
elif vd < vr + 6.0 and nz < 0.67: elif vd < vr + 6.0 and nz < 0.67:
model = mud_model mesh = mud_mesh
else: else:
model = rock_model mesh = rock_mesh
model.spawn(terrain_batch, vec3(float(((mx - 128) * 8) + 4), float(((127 - my) * 8) + 4), 0.0)) tiles_batch.spawn(INSTANCE_FLAG_VISIBLE, mesh,
translation = vec3(float(((mx - 128) * 8) + 4), float(((127 - my) * 8) + 4), 0.0))
tiles_batch.fill()
tiles_batch.freeze(flags = True, mesh = True, translation = True)
class testsparams(Structure): tests_texture = Texture(*archive.get_texture('tests'))
_fields_ = ('translation', vec3), ('orientation', vec3)
tests_texture = archive.get_texture('tests')
tests_sampler = resolve_input(tests_shader, b'u_texture_sampler')
tests_vertices = archive.get_vertices('tests') tests_vertices = archive.get_vertices('tests')
blob_model = archive.get_model('blob') blob_mesh = tests_vertices.get_mesh('blob')
cube_model = archive.get_model('cube') cube_mesh = tests_vertices.get_mesh('cube')
clouds_model = archive.get_model('clouds') clouds_mesh = tests_vertices.get_mesh('clouds')
tests_batch = batch.Batch(tests_vertices, 3, tests_batch = Batch(Vertices(*tests_vertices), 3,
params_format(PARAM_FORMAT_VEC3_FLOAT, PARAM_FORMAT_VEC3_INT10 | PARAM_FORMAT_NORMALIZE), testsparams) translation = VERTEX_FORMAT_VEC3_FLOAT,
blob_spawn_translation = vec3(-100.0, -500.0, 0.0) orientation = VERTEX_FORMAT_MAT3_INT10 | VERTEX_FORMAT_NORMALIZE)
cube_spawn_translation = vec3(100.0, -500.0, 0.0) blob_forward = math.vec3_normalize((sun_direction[0], sun_direction[1], 0.0))
blob_id = blob_model.spawn(tests_batch, blob_right = math.vec3_cross(blob_forward, math.vec3_up)
testsparams(blob_spawn_translation, vec3(*math.vec3_normalize((sun_direction[0], sun_direction[1], 0.0))))) blob_orientation = mat3(vec3(*blob_right), vec3(*blob_forward), vec3_up)
cube_id = cube_model.spawn(tests_batch, testsparams(cube_spawn_translation, vec3_forward)) blob = TestEntity(tests_batch, blob_mesh, translation = vec3(-100.0, -500.0, 0.0), orientation = blob_orientation)
clouds_id = clouds_model.spawn(tests_batch, testsparams(vec3(0.0, 0.0, 32.0), vec3_forward)) cube = TestEntity(tests_batch, cube_mesh, translation = vec3(100.0, -500.0, 0.0), orientation = mat3_identity)
clouds = TestEntity(tests_batch, clouds_mesh, translation = vec3(0.0, 0.0, 32.0), orientation = mat3_identity)
tests_batch.fill()
tests_batch.freeze(flags = True, mesh = True)
sea_phase = resolve_input(sky_shader, b'u_sea_phase') sea_polar = 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 = sea.load_detail_texture('data/sea_bump.png')
sea_polar_sampler = resolve_input(sky_shader, b'u_sea_polar_sampler') sea_triangles = sea.sea_triangles(64, proj_far_z - 0.1, proj_ratio)
sea_detail_texture = sea.load_detail_texture('data/sea_bump.png')
sea_detail_sampler = resolve_input(sky_shader, b'u_sea_detail_sampler')
sky_triangles = create_triangles(triangles.sky_triangles(64, proj_far_z - 0.1, proj_ratio))
camera = Camera() assert tiles_shader.u_height_sampler == tests_shader.u_height_sampler
camera.set_projection(proj_hfov, proj_ratio, proj_near_z, proj_far_z) assert tiles_shader.u_normal_sampler == tests_shader.u_normal_sampler
terrain_camera_inputs = camera.resolve_inputs(terrain_shader)
tests_camera_inputs = camera.resolve_inputs(tests_shader)
sky_camera_inputs = camera.resolve_inputs(sky_shader)
environment = Environment() return SceneGroup(
terrain_environment_inputs = environment.resolve_inputs(terrain_shader) PerfGroup('frame',
tests_environment_inputs = environment.resolve_inputs(tests_shader) TextureGroup({tiles_shader.u_height_sampler: heightmap, tiles_shader.u_normal_sampler: normalmap},
sky_environment_inputs = environment.resolve_inputs(sky_shader) FuncNode(update_camera, (mouse, camera, environment)),
TextureGroup({tiles_shader.u_texture_sampler: tiles_texture},
ShaderGroup(tiles_shader,
InputNode(tiles_shader, camera, environment),
PerfGroup('tiles_batch',
DrawNode(tiles_batch)))),
FuncNode(update_tests, (blob, cube, clouds)),
TextureGroup({tests_shader.u_texture_sampler: tests_texture},
ShaderGroup(tests_shader,
InputNode(tests_shader, camera, environment),
PerfGroup('tests_batch',
DrawNode(tests_batch))))),
FuncNode(update_sea, (camera, sea_phase)),
TextureGroup({sea_shader.u_polar_sampler: sea_polar, sea_shader.u_detail_sampler: sea_detail},
ShaderGroup(sea_shader,
InputNode(sea_shader, camera, environment, sea_phase),
PerfGroup('sea_triangles',
DrawNode(sea_triangles))))))
blob_translation = tests_batch.params[blob_id].translation def loop(display):
cube_translation = tests_batch.params[cube_id].translation events = Events(display)
cube_orientation = tests_batch.params[cube_id].orientation keyboard = Keyboard(events)
clouds_orientation = tests_batch.params[clouds_id].orientation mouse = Mouse(events, wheel = 60, wheel_min = 20)
scene = create_scene(keyboard, mouse)
print("Running...") print("Running... Ctrl+c to quit")
start_time = time.monotonic()
current_time = 0.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:
for frame in range(10000): time = Time()
current_time = time.monotonic() - start_time while not keyboard.quit:
events.update()
begin_frame() clear_buffer(True, True, True)
frame_begin = time.thread_time() scene.draw(time)
swap_buffers(display)
rotation = mat3() time.update()
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)
vec3_rotate(cube_orientation, vec3_forward, vec3_up, (current_time * 0.43) % tau)
vec3_rotate(clouds_orientation, vec3_forward, vec3_up, (current_time * -0.037) % tau)
camera.set_view(camera_origin, camera_lookat, vec3_up, (current_time * 0.05) % tau)
environment.from_sun(camera.view, sun_direction, sun_power)
select_shader(terrain_shader)
camera.update_inputs(terrain_camera_inputs)
environment.update_inputs(terrain_environment_inputs)
select_texture(0, tiles_texture, tiles_sampler)
select_texture(1, heightmap, terrain_heightmap_sampler)
select_texture(2, normalmap, terrain_normalmap_sampler)
select_vertices(tiles_vertices)
draw_begin = time.thread_time()
terrain_batch.draw()
draw_end = time.thread_time()
unselect_vertices(tiles_vertices)
unselect_texture(0, tiles_texture)
unselect_texture(1, heightmap)
unselect_texture(2, normalmap)
unselect_shader(terrain_shader)
select_shader(tests_shader)
camera.update_inputs(tests_camera_inputs)
environment.update_inputs(tests_environment_inputs)
select_texture(0, tests_texture, tests_sampler)
select_texture(1, heightmap, tests_heightmap_sampler)
select_texture(2, normalmap, tests_normalmap_sampler)
select_vertices(tests_vertices)
tests_batch.draw()
unselect_vertices(tests_vertices)
unselect_texture(0, tests_texture)
unselect_texture(1, heightmap)
unselect_texture(2, normalmap)
unselect_shader(tests_shader)
camera.to_km()
select_shader(sky_shader)
camera.update_inputs(sky_camera_inputs)
environment.update_inputs(sky_environment_inputs)
set_input_float(sea_phase, (current_time * 0.023) % 1.0)
select_texture(0, sea_polar_textures, sea_polar_sampler)
select_texture(1, sea_detail_texture, sea_detail_sampler)
draw_triangles(sky_triangles)
unselect_texture(0, sea_polar_textures)
unselect_texture(1, sea_detail_texture)
unselect_shader(sky_shader)
frame_end = time.thread_time()
end_frame()
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
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
print("\rDraw *", perf_count, def main():
": min =", round(draw_min * 1000.0, 2), print("Initializing...")
", max =", round(draw_max * 1000.0, 2), display = create_display(b'RK Island - Drag to rotate, wheel to zoom, q to quit', 1600, 900)
", avg =", round((draw_avg / perf_count) * 1000.0, 2), "ms") render_initialize(__debug__)
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
# camera_origin = vec3((0.0, -1200.0, 500.0))
# camera_lookat = vec3((0.0, 500.0, -500.0))
# for x in range(10000)
# current_time = 0
# Draw * 9999 : min = 0.2 , max = 2.31 , avg = 0.27 ms
# Draw * 9999 : min = 0.2 , max = 2.41 , avg = 0.27 ms
# Draw * 9999 : min = 0.2 , max = 2.73 , avg = 0.27 ms
# Frame * 9999 : min = 0.26 , max = 2.44 , avg = 0.38 ms
# Frame * 9999 : min = 0.26 , max = 2.54 , avg = 0.37 ms
# Frame * 9999 : min = 0.26 , max = 2.90 , avg = 0.38 ms
print("Quitting...") print("Quitting...")
del tests_batch render_terminate()
del terrain_batch destroy_display(display)
destroy_texture(sea_polar_textures)
destroy_texture(sea_detail_texture)
destroy_texture(heightmap)
destroy_texture(normalmap)
destroy_triangles(sky_triangles)
archive.destroy()
destroy_shader(terrain_shader)
destroy_shader(tests_shader)
destroy_shader(sky_shader)
terminate()

29
game/inputs.py Normal file
View File

@ -0,0 +1,29 @@
# 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)

27
game/keyboard.py Normal file
View File

@ -0,0 +1,27 @@
# 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 EVENT_KEY_PRESS
class Keyboard:
__slots__ = 'quit'
def __init__(self, events):
self.quit = False
events.register(EVENT_KEY_PRESS, self.key_press_handler)
def key_press_handler(self, data):
if data.key.character == 'q':
self.quit = True

52
game/mouse.py Normal file
View File

@ -0,0 +1,52 @@
# 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 (
EVENT_BUTTON_PRESS, EVENT_BUTTON_RELEASE, EVENT_MOTION, BUTTON_LEFT, BUTTON_WHEEL_UP, BUTTON_WHEEL_DOWN)
class Mouse:
__slots__ = 'buttons', 'wheel', 'wheel_min', 'position', 'drag'
def __init__(self, events, wheel = 0, wheel_min = None):
self.buttons = 0
self.wheel = wheel
self.wheel_min = wheel_min
self.position = None
self.drag = (0, 0)
events.register(EVENT_BUTTON_PRESS, self.button_press_handler)
events.register(EVENT_BUTTON_RELEASE, self.button_release_handler)
events.register(EVENT_MOTION, self.motion_handler)
def button_press_handler(self, data):
button = data.button.index
self.buttons |= 1 << button
if button == BUTTON_WHEEL_UP:
self.wheel -= 1
if self.wheel_min is not None and self.wheel < self.wheel_min:
self.wheel = self.wheel_min
elif button == BUTTON_WHEEL_DOWN:
self.wheel += 1
def button_release_handler(self, data):
self.buttons &= ~(1 << data.button.index)
def motion_handler(self, data):
new_x = data.motion.x
new_y = data.motion.y
if self.position and (self.buttons & (1 << BUTTON_LEFT)):
prev_x, prev_y = self.position
prev_dx, prev_dy = self.drag
self.drag = (prev_dx + (new_x - prev_x), prev_dy + (new_y - prev_y))
self.position = (new_x, new_y)

View File

@ -22,7 +22,7 @@ from itertools import starmap, chain, repeat
from engine import * from engine import *
from game.math import float_s8, vec3_srgb8a8 from game.math import float_s8, vec3_srgb8a8
from game.resources import load_png, TextureData, VerticesData, ModelData, Archive from game.resources import load_png, TextureData, VerticesData, Archive
_texture_flags = TEXTURE_FLAG_MIPMAPS | TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_NEAREST _texture_flags = TEXTURE_FLAG_MIPMAPS | TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_NEAREST
@ -192,42 +192,47 @@ class ObjArchive(Archive):
if name: if name:
assert mesh assert mesh
objects[name] = mesh objects[name] = mesh
vertices = set()
for mesh in objects.values(): vertices = []
vertices |= frozenset(chain.from_iterable(mesh))
vertices = tuple(vertices)
indices = [] indices = []
models = {} meshes = {}
for name, mesh in sorted(objects.items()): for name, mesh in sorted(objects.items()):
offset = len(indices) offset = len(indices)
assert offset < 65536 assert offset < 65536
indices.extend(map(vertices.index, chain.from_iterable(mesh))) mesh_vertices = []
count = len(indices) - offset for vertex in chain.from_iterable(mesh):
if vertex not in mesh_vertices:
mesh_vertices.append(vertex)
base_vertex = len(vertices)
mesh_indices = list(map(lambda v: mesh_vertices.index(v) + base_vertex, chain.from_iterable(mesh)))
assert max(mesh_indices) < 65536
count = len(mesh_indices)
print(name, ": vertices =", count, "packed =", len(mesh_vertices))
assert count % 3 == 0 assert count % 3 == 0
count //= 3 count //= 3
assert count < 65536 assert count < 65536
print(name, ": offset =", offset, "triangles =", count) vertices.extend(mesh_vertices)
models[name] = (offset, count) indices.extend(mesh_indices)
meshes[name] = (offset, count)
name = str(objpath.stem) name = str(objpath.stem)
assert name not in self.vertices_db.keys() assert name not in self.vertices_db.keys()
#TODO: move to math #TODO: move to math
def pack_10(_x): def pack_10(_x):
return round(_x * (512.0 if _x < 0.0 else 511.0)) & 1023 assert _x >= -1.0 and _x <= 1.0
return round(_x * 511.0) & 1023
# return ((round(_x * 1023.0) - 1) // 2) & 1023
def pack_u10(_x): def pack_u10(_x):
return round(_x * 1023.0) & 1023 assert _x >= 0.0 and _x <= 1.0
return round(_x * 1023.0)
def pack_vertex(_px, _py, _pz, _nx, _ny, _nz, _s, _t, _tl): def pack_vertex(_px, _py, _pz, _nx, _ny, _nz, _s, _t, _tl):
n = (pack_10(_nz) << 20) | (pack_10(_ny) << 10) | pack_10(_nx) n = (pack_10(_nz) << 20) | (pack_10(_ny) << 10) | pack_10(_nx)
t = ((_tl & 1023) << 20) | (pack_u10(_t) << 10) | pack_u10(_s) t = ((_tl & 1023) << 20) | (pack_u10(_t) << 10) | pack_u10(_s)
return struct.pack('fffII', _px, _py, _pz, n, t) return struct.pack('fffII', _px, _py, _pz, n, t)
self.vertices_db[name] = VerticesData(name, self.vertices_db[name] = VerticesData(name,
array('B', b''.join(starmap(pack_vertex, vertices))), array('B', b''.join(starmap(pack_vertex, vertices))),
array('H', indices)) array('H', indices),
for name, (offset, count) in sorted(models.items()): meshes)
if name[0] != '_':
print("Storing", name)
assert name not in self.models_db.keys()
self.models_db[name] = ModelData(
name, INSTANCE_FLAG_VISIBLE, offset | count << 16)
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 3: if len(sys.argv) < 3:

View File

@ -13,12 +13,11 @@
# 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
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
@ -50,7 +49,7 @@ def _read_struct(file, format):
_read_magic(file, b'RK') _read_magic(file, b'RK')
return struct.unpack(format, data) return struct.unpack(format, data)
def _write_struct(file, format, elems): def _write_struct(file, format, *elems):
assert format assert format
data = struct.pack(format, *elems) data = struct.pack(format, *elems)
size = file.write(data) size = file.write(data)
@ -58,7 +57,7 @@ def _write_struct(file, format, elems):
_write_magic(file, b'RK') _write_magic(file, b'RK')
def _read_string(file): def _read_string(file):
length, = _read_struct(file, 'B') length, = _read_struct(file, 'I')
assert length assert length
data = file.read(length) data = file.read(length)
assert len(data) == length assert len(data) == length
@ -68,7 +67,7 @@ def _read_string(file):
def _write_string(file, string): def _write_string(file, string):
data = bytes(string, encoding='ascii') data = bytes(string, encoding='ascii')
assert data and len(data) < 256 assert data and len(data) < 256
_write_struct(file, 'B', (len(data),)) _write_struct(file, 'I', len(data))
size = file.write(data) size = file.write(data)
assert size == len(data) assert size == len(data)
_write_magic(file, b'RK') _write_magic(file, b'RK')
@ -98,7 +97,7 @@ def _read_blob(file):
def _write_blob(file, array): def _write_blob(file, array):
assert array assert array
_write_struct(file, 'II', (ord(array.typecode), len(array))) _write_struct(file, 'II', ord(array.typecode), len(array))
array.tofile(file) array.tofile(file)
_write_magic(file, b'RK') _write_magic(file, b'RK')
@ -116,9 +115,17 @@ 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'TXTR')
name = _read_string(file) name = _read_string(file)
format, width, height, nlevels, flags = _read_struct(file, 'IIIII') format, width, height, nlevels, flags = _read_struct(file, 'IIIII')
pixels = _read_blob(file) pixels = _read_blob(file)
@ -127,92 +134,61 @@ class TextureData:
return cls(name, format, width, height, nlevels, flags, pixels) return cls(name, format, width, height, nlevels, flags, pixels)
def to_archive(self, file): def to_archive(self, file):
_write_magic(file, b'TX') _write_magic(file, b'TXTR')
_write_string(file, self.name) _write_string(file, self.name)
_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', 'meshes', 'meshes_db'
def __init__(self, name, vertices, indices): def __init__(self, name, vertices, indices, meshes):
if len(vertices) % VERTEX_SIZE != 0: if len(vertices) % VERTEX_SIZE != 0:
raise RuntimeError("Vertex format mismatch!") raise RuntimeError("Vertex format mismatch!")
self.name = name self.name = name
self.vertices = vertices self.vertices = vertices
self.indices = indices self.indices = indices
self.meshes = meshes.values()
self.meshes_db = dict(zip(meshes.keys(), range(len(meshes))))
def __iter__(self):
yield VERTEX_FORMAT
yield len(self.vertices) // VERTEX_SIZE
yield self.vertices
yield self.indices
yield array('I', chain.from_iterable(self.meshes))
def get_mesh(self, name):
return self.meshes_db[name]
@classmethod @classmethod
def from_archive(cls, file): def from_archive(cls, file):
_read_magic(file, b'VT') _read_magic(file, b'VERT')
name = _read_string(file) name = _read_string(file)
nvertices, nindices = _read_struct(file, 'II') nvertices, nindices, nmeshes = _read_struct(file, 'III')
vertices = _read_array(file, 'B', nvertices * VERTEX_SIZE) vertices = _read_array(file, 'B', nvertices * VERTEX_SIZE)
indices = _read_array(file, 'H', nindices) indices = _read_array(file, 'H', nindices)
return cls(name, vertices, indices) names = [_read_string(file) for _ in range(nmeshes)]
meshes = [_read_struct(file, 'II') for _ in range(nmeshes)]
return cls(name, vertices, indices, dict(zip(names, meshes)))
def to_archive(self, file): def to_archive(self, file):
_write_magic(file, b'VT') _write_magic(file, b'VERT')
_write_string(file, self.name) _write_string(file, self.name)
_write_struct(file, 'II', (len(self.vertices) // VERTEX_SIZE, len(self.indices))) _write_struct(file, 'III', len(self.vertices) // VERTEX_SIZE, len(self.indices), len(self.meshes_db))
_write_array(file, self.vertices) _write_array(file, self.vertices)
_write_array(file, self.indices) _write_array(file, self.indices)
for name in self.meshes_db.keys():
def create_vertices(data): _write_string(file, name)
return engine.create_vertices(VERTEX_FORMAT, len(data.vertices) // VERTEX_SIZE, data.vertices, data.indices) for mesh in self.meshes:
_write_struct(file, 'II', *mesh)
class ModelData:
__slots__ = 'name', 'flags', 'mesh'
def __init__(self, name, flags, mesh):
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)
def to_archive(self, file):
_write_magic(file, b'MD')
_write_string(file, self.name)
_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'
def __init__(self, textures_db = None, vertices_db = None, models_db = None): def __init__(self, textures_db = None, vertices_db = None):
self.textures_db = textures_db or {} self.textures_db = textures_db or {}
self.vertices_db = vertices_db or {} self.vertices_db = vertices_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]
@ -220,38 +196,27 @@ class Archive:
def get_vertices(self, name): def get_vertices(self, name):
return self.vertices_db[name] return self.vertices_db[name]
def get_model(self, 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 = {}
vertices_db = {} vertices_db = {}
models_db = {}
_read_magic(file, b'RKAR') _read_magic(file, b'RKAR')
ntextures, nvertices, nmodels = _read_struct(file, 'III') ntextures, nvertices = _read_struct(file, 'II')
for _ in range(ntextures): for _ in range(ntextures):
data = TextureData.from_archive(file) data = TextureData.from_archive(file)
textures_db[data.name] = cls._new_texture(data) textures_db[data.name] = data
for _ in range(nvertices): for _ in range(nvertices):
data = VerticesData.from_archive(file) data = VerticesData.from_archive(file)
vertices_db[data.name] = cls._new_vertices(data) vertices_db[data.name] = data
for _ in range(nmodels): return cls(textures_db, vertices_db)
data = ModelData.from_archive(file)
models_db[data.name] = cls._new_model(data) def to_archive(self, file):
return cls(textures_db, vertices_db, models_db) _write_magic(file, b'RKAR')
_write_struct(file, 'II', len(self.textures_db), len(self.vertices_db))
for _, data in self.textures_db.items():
data.to_archive(file)
for _, data in self.vertices_db.items():
data.to_archive(file)
@classmethod @classmethod
def load(cls, filename): def load(cls, filename):
@ -260,30 +225,7 @@ class Archive:
file.close() file.close()
return archive return archive
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)
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)

85
game/scene.py Normal file
View File

@ -0,0 +1,85 @@
# 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 Group:
__slots__ = '_subnodes'
def __init__(self, *subnodes):
self._subnodes = subnodes
def draw(self, time):
for subnode in self._subnodes:
subnode.draw(time)
class SceneGroup(Group):
pass
class TextureGroup(Group):
__slots__ = '_textures'
def __init__(self, textures, *subnodes):
Group.__init__(self, *subnodes)
self._textures = textures
def draw(self, time):
items = self._textures.items()
for slot, texture in items:
texture.select(slot)
Group.draw(self, time)
for slot, texture in items:
texture.unselect(slot)
class ShaderGroup(Group):
__slots__ = '_shader'
def __init__(self, shader, *subnodes):
Group.__init__(self, *subnodes)
self._shader = shader
def draw(self, time):
self._shader.select()
Group.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,14 +13,15 @@
# 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 product from itertools import chain, 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 (create_texture, from engine import TEXTURE_FORMAT_RGB10_A2, TEXTURE_FORMAT_TYPECODE, TEXTURE_FLAG_MIN_LINEAR, TEXTURE_FLAG_MAG_LINEAR
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
@ -51,7 +52,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 create_texture(_format, width, height, len(paths), _flags, data) return 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)
@ -68,4 +69,36 @@ 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 create_texture(_format, width, height, 0, _flags, data) return 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,33 +15,80 @@
from pathlib import Path from pathlib import Path
from engine import load_shader from engine import create_shader, resolve_input, select_shader, unselect_shader, destroy_shader
#TODO: preprocessing (at least includes) def _cleanup(line):
return line.partition('//')[0].strip()
def load(vert_name, frag_name = ''): def _filter(line):
path = Path('.') / 'game' / 'shaders' return line
if not frag_name:
frag_name = vert_name def _subst(line):
def _cleanup(_line): if line.startswith('#include'):
return _line.partition('//')[0].strip() path = Path('.') / 'game' / 'shaders'
def _filter(_line): lines = []
return _line for name in line.split()[1:]:
def _convert(_line): lines.extend(_load_source(path / name.strip('"')))
return bytes(_line, 'utf-8') + b'\n' return lines
def load_source(_path): return [line]
if not _path.exists():
print("Cannot find", _path) def _load_source(path):
return None assert path.exists()
return list(map(_convert, filter(_filter, map(_cleanup, open(_path, 'rt'))))) lines = filter(_filter, map(_cleanup, open(path, 'rt')))
print("Loading vertex shader", vert_name) source = []
vert_lines = load_source(path / (vert_name + '_opengles.vert')) for line in lines:
if not vert_lines: source.extend(_subst(line))
print("Error: Vertex shader is empty.") return source
return None
print("Loading fragment shader", frag_name) def _convert(line):
frag_lines = load_source(path / (frag_name + '_opengles.frag')) return bytes(line, 'utf-8') + b'\n'
if not frag_lines:
print("Error: Fragment shader is empty.") def _parse(shader, vert_lines, frag_lines):
return None uniforms = []
return load_shader(vert_lines, frag_lines) bindings = {}
def collect(line):
if line.startswith('uniform'):
name = line.split()[-1].strip(';')
if name not in uniforms:
uniforms.append(name)
elif line.startswith('layout(binding='):
name = line.split()[-1].strip(';')
value = int(line[line.index('=') + 1 : line.index(')')].strip())
if name in bindings:
assert value == bindings[name]
else:
bindings[name] = value
for line in vert_lines:
collect(line)
for line in frag_lines:
collect(line)
for name in uniforms:
setattr(shader, name, resolve_input(shader._shader, bytes(name, 'utf-8')))
for name, value in bindings.items():
setattr(shader, name, value)
class Shader:
__slots__ = '_shader', '__dict__'
def __init__(self, vert_name, frag_name = ''):
path = Path('.') / 'game' / 'shaders'
assert path.exists()
if not frag_name:
frag_name = vert_name
print("Loading vertex shader", vert_name)
vert_lines = _load_source(path / (vert_name + '_opengles.vert'))
assert vert_lines
print("Loading fragment shader", frag_name)
frag_lines = _load_source(path / (frag_name + '_opengles.frag'))
assert frag_lines
self._shader = create_shader(list(map(_convert, vert_lines)), list(map(_convert, frag_lines)))
_parse(self, vert_lines, frag_lines)
def __del__(self):
destroy_shader(self._shader)
def select(self):
select_shader(self._shader)
def unselect(self):
unselect_shader(self._shader)

View File

@ -16,6 +16,8 @@
#version 320 es #version 320 es
precision highp float; precision highp float;
#include "include/environment.glsl"
in vec4 v_position; // view space in vec4 v_position; // view space
in vec4 v_normal; // view space in vec4 v_normal; // view space
in vec4 v_terrain_normal; // view space (x, y, z, weight) in vec4 v_terrain_normal; // view space (x, y, z, weight)
@ -23,10 +25,7 @@ in vec4 v_texcoord; // texture space (s, t, pixel_level, material_level)
#define v_weight v_terrain_normal.w #define v_weight v_terrain_normal.w
uniform vec3 u_light_direction; // view space (-direction_x, -direction_y, -direction_z) layout(binding=0) uniform highp sampler2DArray u_texture_sampler;
uniform vec3 u_light_color; // (color.r * power, color.g * power, color.b * power)
uniform highp sampler2DArray u_texture_sampler;
layout(location = 0) out vec4 o_color; layout(location = 0) out vec4 o_color;

View File

@ -0,0 +1,7 @@
#ifndef CAMERA_INCLUDED
#define CAMERA_INCLUDED
uniform mat4 u_view; // world space -> view space
uniform mat4 u_projection; // view space -> screen space
#endif // CAMERA_INCLUDED

View File

@ -0,0 +1,20 @@
#ifndef COMMON_INCLUDED
#define COMMON_INCLUDED
layout(binding=1) uniform highp sampler2D u_height_sampler;
layout(binding=2) uniform highp sampler2D u_normal_sampler;
const vec3 c_normal_scale = vec3(2.0, 2.0, 1.0);
const vec3 c_normal_shift = vec3(-1.0, -1.0, 0.0);
const vec2 c_st_scale = vec2(1.0 / 1023.0, 1.0 / 1023.0);
const vec2 c_terrain_scale = vec2(1.0 / 2048.0, -1.0 / 2048.0);
const vec2 c_terrain_shift = vec2(0.5, 0.5);
const float c_weight_scale = 1.0 / 64.f;
const vec3 c_world_forward = vec3(0.0, 1.0, 0.0);
out vec4 v_position; // view space
out vec4 v_normal; // view space
out vec4 v_terrain_normal; // view space (x, y, z, weigth)
out vec4 v_texcoord; // texture space (s, t, pixel_level, material_level)
#endif // COMMON_INCLUDED

View File

@ -0,0 +1,10 @@
#ifndef ENVIRONMENT_INCLUDED
#define ENVIRONMENT_INCLUDED
uniform vec3 u_light_direction; // view space
uniform vec3 u_light_color;
uniform vec3 u_horizon_color;
uniform vec3 u_sky_color;
uniform vec3 u_sun_color;
#endif // ENVIRONMENT_INCLUDED

View File

@ -0,0 +1,8 @@
#ifndef VERTEX_FORMAT_INCLUDED
#define VERTEX_FORMAT_INCLUDED
layout(location = 0) in vec3 a_position; // model space
layout(location = 1) in vec3 a_normal; // model space
layout(location = 2) in vec3 a_texcoord; // texture space
#endif // VERTEX_FORMAT_INCLUDED

View File

@ -16,14 +16,11 @@
#version 320 es #version 320 es
precision highp float; precision highp float;
#include "include/camera.glsl"
#include "include/environment.glsl"
in vec3 v_position; in vec3 v_position;
uniform mat4 u_view; // world space -> view space, unit = km
uniform vec3 u_light_direction; // view space (-direction_x, -direction_y, -direction_z)
uniform vec3 u_light_color;
uniform vec3 u_horizon_color;
uniform vec3 u_sky_color;
uniform vec3 u_sun_color;
uniform float u_sea_phase; uniform float u_sea_phase;
#define u_right u_view[0].xyz #define u_right u_view[0].xyz
@ -31,8 +28,8 @@ uniform float u_sea_phase;
#define u_up u_view[2].xyz #define u_up u_view[2].xyz
#define u_origin u_view[3].xyz #define u_origin u_view[3].xyz
uniform highp sampler2DArray u_sea_polar_sampler; layout(binding=0) uniform highp sampler2DArray u_polar_sampler;
uniform highp sampler2D u_sea_detail_sampler; layout(binding=1) uniform highp sampler2D u_detail_sampler;
const float c_sea_radius = 637.1; const float c_sea_radius = 637.1;
const float c_sea_radius_sq = c_sea_radius * c_sea_radius; const float c_sea_radius_sq = c_sea_radius * c_sea_radius;
@ -71,13 +68,13 @@ void main(void) {
} }
float t = sqrt(length(sea_position)); //TODO: more accurate float t = sqrt(length(sea_position)); //TODO: more accurate
vec3 sea_polar1 = normalize( vec3 sea_polar1 = normalize(
c_normal_shift + texture(u_sea_polar_sampler, vec3(s, t + u_sea_phase, 0.0)).xyz * c_normal_scale); c_normal_shift + texture(u_polar_sampler, vec3(s, t + u_sea_phase, 0.0)).xyz * c_normal_scale);
vec3 sea_polar2 = normalize( vec3 sea_polar2 = normalize(
c_normal_shift + texture(u_sea_polar_sampler, vec3(s, t - u_sea_phase, 1.0)).xyz * c_normal_scale); c_normal_shift + texture(u_polar_sampler, vec3(s, t - u_sea_phase, 1.0)).xyz * c_normal_scale);
//TODO: vec2 //TODO: vec2
s = (u_sea_phase + dot(sea_position, u_right)) * c_detail_scale; s = (u_sea_phase + dot(sea_position, u_right)) * c_detail_scale;
t = (u_sea_phase + dot(sea_position, u_forward)) * c_detail_scale; t = (u_sea_phase + dot(sea_position, u_forward)) * c_detail_scale;
vec3 sea_detail = normalize(c_normal_shift + texture(u_sea_detail_sampler, vec2(s, t)).xyz * c_normal_scale); vec3 sea_detail = normalize(c_normal_shift + texture(u_detail_sampler, vec2(s, t)).xyz * c_normal_scale);
//TODO: better blending, with earth normal //TODO: better blending, with earth normal
vec4 normal = u_view * vec4(normalize(sea_polar1 + sea_polar2 + sea_detail), 0.0); vec4 normal = u_view * vec4(normalize(sea_polar1 + sea_polar2 + sea_detail), 0.0);
float d = max(0.0, dot(normal.xyz, u_light_direction)); float d = max(0.0, dot(normal.xyz, u_light_direction));

View File

@ -16,9 +16,9 @@
#version 320 es #version 320 es
precision highp float; precision highp float;
layout(location = 0) in vec3 a_position; // view space #include "include/camera.glsl"
uniform mat4 u_projection; // view space -> screen space layout(location = 0) in vec3 a_position; // view space
out vec3 v_position; // view space out vec3 v_position; // view space

View File

@ -16,39 +16,17 @@
#version 320 es #version 320 es
precision highp float; precision highp float;
layout(location = 0) in vec3 a_position; // model space #include "include/vertex_format.glsl"
layout(location = 1) in vec3 a_normal; // model space #include "include/camera.glsl"
layout(location = 2) in vec3 a_texcoord; // texture space #include "include/common.glsl"
layout(location = 3) in vec3 i_translation; // per mesh, model space -> world space layout(location = 3) in vec3 i_translation; // per mesh, model space -> world space
layout(location = 4) in vec3 i_orientation; // per mesh, model space -> world space layout(location = 4) in mat3 i_orientation; // per mesh, model space -> world space
uniform mat4 u_view; // world space -> view space
uniform mat4 u_projection; // view space -> screen space
uniform highp sampler2D u_height_sampler;
uniform highp sampler2D u_normal_sampler;
const vec3 c_normal_scale = vec3(2.0, 2.0, 1.0);
const vec3 c_normal_shift = vec3(-1.0, -1.0, 0.0);
const vec2 c_st_scale = vec2(1.0 / 1023.0, 1.0 / 1023.0);
const vec2 c_terrain_scale = vec2(1.0 / 2048.0, -1.0 / 2048.0);
const vec2 c_terrain_shift = vec2(0.5, 0.5);
const float c_weight_scale = 1.0 / 64.f;
const vec3 c_world_forward = vec3(0.0, 1.0, 0.0);
const vec3 c_world_up = vec3(0.0, 0.0, 1.0);
out vec4 v_position; // view space
out vec4 v_normal; // view space
out vec4 v_terrain_normal; // view space (x, y, z, weigth)
out vec4 v_texcoord; // texture space (s, t, pixel_level, material_level)
void main(void) { void main(void) {
vec3 orientation = normalize(i_orientation); vec4 world_position = vec4(i_translation + i_orientation * a_position, 1.0);
mat3 rotation = mat3(cross(orientation, c_world_up), orientation, c_world_up);
vec4 world_position = vec4(i_translation + rotation * a_position, 1.0);
float weight = max(0.0, 1.0 - world_position.z * c_weight_scale); float weight = max(0.0, 1.0 - world_position.z * c_weight_scale);
vec3 world_normal = rotation * normalize(a_normal); vec3 world_normal = i_orientation * a_normal;
vec2 terrain_coords = c_terrain_shift + world_position.xy * c_terrain_scale; vec2 terrain_coords = c_terrain_shift + world_position.xy * c_terrain_scale;
world_position.z += texture(u_height_sampler, terrain_coords).r; world_position.z += texture(u_height_sampler, terrain_coords).r;
vec4 view_position = u_view * world_position; vec4 view_position = u_view * world_position;

View File

@ -16,39 +16,19 @@
#version 320 es #version 320 es
precision highp float; precision highp float;
layout(location = 0) in vec3 a_position; // model space #include "include/vertex_format.glsl"
layout(location = 1) in vec3 a_normal; // model space #include "include/camera.glsl"
layout(location = 2) in vec3 a_texcoord; // texture space #include "include/common.glsl"
layout(location = 3) in vec3 i_translation; // per mesh, model space -> world space layout(location = 3) in vec3 i_translation; // per mesh, model space -> world space
uniform mat4 u_view; // world space -> view space
uniform mat4 u_projection; // view space -> screen space
uniform highp sampler2D u_height_sampler;
uniform highp sampler2D u_normal_sampler;
const vec3 c_normal_scale = vec3(2.0, 2.0, 1.0);
const vec3 c_normal_shift = vec3(-1.0, -1.0, 0.0);
const vec2 c_st_scale = vec2(1.0 / 1023.0, 1.0 / 1023.0);
const vec2 c_terrain_scale = vec2(1.0 / 2048.0, -1.0 / 2048.0);
const vec2 c_terrain_shift = vec2(0.5, 0.5);
const float c_weight_scale = 1.0 / 64.f;
const vec3 c_world_forward = vec3(0.0, 1.0, 0.0);
const vec3 c_world_up = vec3(0.0, 0.0, 1.0);
out vec4 v_position; // view space
out vec4 v_normal; // view space
out vec4 v_terrain_normal; // view space (x, y, z, weigth)
out vec4 v_texcoord; // texture space (s, t, pixel_level, material_level)
void main(void) { void main(void) {
vec4 world_position = vec4(i_translation + a_position, 1.0); vec4 world_position = vec4(i_translation + a_position, 1.0);
vec2 terrain_coords = c_terrain_shift + world_position.xy * c_terrain_scale; vec2 terrain_coords = c_terrain_shift + world_position.xy * c_terrain_scale;
world_position.z += texture(u_height_sampler, terrain_coords).r; world_position.z += texture(u_height_sampler, terrain_coords).r;
vec4 view_position = u_view * world_position; vec4 view_position = u_view * world_position;
vec3 terrain_normal = normalize(c_normal_shift + texture(u_normal_sampler, terrain_coords).rgb * c_normal_scale); vec3 terrain_normal = normalize(c_normal_shift + texture(u_normal_sampler, terrain_coords).rgb * c_normal_scale);
vec3 world_normal = mat3(cross(c_world_forward, terrain_normal), c_world_forward, terrain_normal) * normalize(a_normal); vec3 world_normal = mat3(cross(c_world_forward, terrain_normal), c_world_forward, terrain_normal) * a_normal;
v_position = view_position; v_position = view_position;
v_normal = u_view * vec4(world_normal, 0.0); v_normal = u_view * vec4(world_normal, 0.0);
v_terrain_normal = vec4((u_view * vec4(terrain_normal, 0.0)).xyz, 1.0); v_terrain_normal = vec4((u_view * vec4(terrain_normal, 0.0)).xyz, 1.0);

31
game/texture.py Normal file
View File

@ -0,0 +1,31 @@
# 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)

29
game/time.py Normal file
View File

@ -0,0 +1,29 @@
# 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,38 +13,16 @@
# 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 from engine import create_triangles, draw_triangles, destroy_triangles
from array import array
from math import cos, sin
# TODO: with FOV class Triangles:
def sky_triangles(vsubdivs, distance, projection_ratio): __slots__ = '_triangles'
assert vsubdivs > 0
vertices = [] def __init__(self, triangles):
hsubdivs = round(vsubdivs * projection_ratio) self._triangles = create_triangles(triangles)
z = -distance
width = distance * projection_ratio def __del__(self):
height = distance destroy_triangles(self._triangles)
startx = width * -0.5
starty = height * -0.5 def draw(self):
stepx = width / hsubdivs draw_triangles(self._triangles)
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))

25
game/vertices.py Normal file
View File

@ -0,0 +1,25 @@
# 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, meshes):
self._vertices = create_vertices(format, nvertices, vertices, indices, meshes)
def __del__(self):
destroy_vertices(self._vertices)