Compare commits
52 Commits
643c9a1bae
...
master
Author | SHA1 | Date | |
---|---|---|---|
830daec408
|
|||
4783f0ba2d
|
|||
ccc168c896
|
|||
cbae4ec0e7
|
|||
efdbbb5815
|
|||
f487a4fcb7
|
|||
4cb67c0d79
|
|||
78440cce16
|
|||
84fc41e014
|
|||
e90f198fa0
|
|||
a96ea8bb6b
|
|||
7219039980
|
|||
3344ac7d6c
|
|||
89e0b99d3a
|
|||
9131523ae7
|
|||
3621a340f1
|
|||
8e49c16929
|
|||
7c2d3d81ae
|
|||
dd786ef2b4
|
|||
acc2e4cd28
|
|||
af9365c6e0
|
|||
513d79a67c
|
|||
f66c6ea6e9
|
|||
9e77e6680b
|
|||
f4bfb7fee0
|
|||
6485da7d67
|
|||
9fff666ebf
|
|||
09dcaae95b
|
|||
2efdcdca64
|
|||
6701c8dbb3
|
|||
f05a4b64a2
|
|||
2f3508ed3a
|
|||
c3e50b7d60
|
|||
b6553db09e
|
|||
bd445575fd
|
|||
dc75859d95
|
|||
2e905a80a8
|
|||
8e020915a6
|
|||
66a099e51f
|
|||
1649bb6dc9
|
|||
81f0745feb
|
|||
758ed8c7f4
|
|||
850a254abc
|
|||
1ac12ea9ea
|
|||
108976bdff
|
|||
c4309ef7c7
|
|||
bff54342dc
|
|||
119cd76156
|
|||
7ed23a5d5f
|
|||
34277274a3
|
|||
858dcd95bc
|
|||
3e83c636ee
|
1
.gitmodules
vendored
1
.gitmodules
vendored
@ -1,3 +1,4 @@
|
||||
[submodule "engine"]
|
||||
path = engine
|
||||
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
2
engine
Submodule engine updated: ccce6c5d83...7384a014ff
@ -13,36 +13,72 @@
|
||||
# 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 ctypes import c_ubyte, c_uint, POINTER
|
||||
from array import array
|
||||
from ctypes import c_ubyte, c_ushort, c_void_p, POINTER, addressof
|
||||
|
||||
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:
|
||||
__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
|
||||
self._batch = create_batch(vertices, max_size, params_format)
|
||||
self._paramsp = POINTER(params_type)
|
||||
self.size = 0
|
||||
nparams = len(params_decls)
|
||||
if nparams:
|
||||
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.flags = (c_ubyte * max_size)()
|
||||
self.meshes = (c_uint * max_size)()
|
||||
self.params = (params_type * max_size)()
|
||||
self.size = 0
|
||||
self.flags = buffer(_flags_t, 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):
|
||||
destroy_batch(self._batch)
|
||||
|
||||
def append(self, flags, mesh, params):
|
||||
def spawn(self, flags, mesh, **params):
|
||||
index = self.size
|
||||
assert index < self.max_size
|
||||
self.flags[index] = flags | INSTANCE_FLAG_SPAWNED
|
||||
self.meshes[index] = mesh
|
||||
self.params[index] = params
|
||||
self.mesh[index] = mesh
|
||||
for name, value in params.items():
|
||||
getattr(self, name)[index] = value
|
||||
self.size += 1
|
||||
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):
|
||||
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)
|
||||
|
@ -13,50 +13,30 @@
|
||||
# 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 (
|
||||
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)
|
||||
from engine import vec3_origin, mat4, mat4_projection, mat4_orbit, set_input_mat4
|
||||
|
||||
class Camera:
|
||||
__slots__ = 'origin', 'lookat', 'rotation', 'projection', 'view'
|
||||
__slots__ = 'yaw', 'pitch', 'distance', 'projection', 'view'
|
||||
|
||||
def __init__(self):
|
||||
self.origin = vec3()
|
||||
self.lookat = vec3()
|
||||
self.rotation = mat3()
|
||||
self.yaw = 0.0
|
||||
self.pitch = 0.0
|
||||
self.distance = 0.0
|
||||
self.projection = mat4()
|
||||
self.view = mat4()
|
||||
|
||||
def set_projection(self, 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):
|
||||
self.origin.set(*origin)
|
||||
self.lookat.set(*lookat)
|
||||
mat3_rotation(self.rotation, axis, angle)
|
||||
mat3_mul_vec3(self.origin, self.rotation, self.origin)
|
||||
mat3_mul_vec3(self.lookat, self.rotation, self.lookat)
|
||||
mat4_lookat(self.view, self.origin, self.lookat)
|
||||
def set_view(self, yaw, pitch, distance):
|
||||
self.yaw = yaw
|
||||
self.pitch = pitch
|
||||
self.distance = distance
|
||||
mat4_orbit(self.view, vec3_origin, yaw, pitch, distance)
|
||||
|
||||
def to_km(self):
|
||||
vec3_mul_vec3(self.origin, _m2km, self.origin)
|
||||
vec3_mul_vec3(self.lookat, _m2km, self.lookat)
|
||||
mat4_lookat(self.view, self.origin, self.lookat)
|
||||
mat4_orbit(self.view, vec3_origin, self.yaw, self.pitch, self.distance * 0.001)
|
||||
|
||||
def resolve_inputs(self, shader, projection = b'u_projection', view = b'u_view'):
|
||||
return _Inputs(shader, projection, view)
|
||||
|
||||
def update_inputs(self, inputs):
|
||||
set_input_mat4(inputs.projection, self.projection)
|
||||
set_input_mat4(inputs.view, self.view)
|
||||
def set_inputs(self, shader):
|
||||
set_input_mat4(shader.u_projection, self.projection)
|
||||
set_input_mat4(shader.u_view, self.view)
|
||||
|
20
game/entity.py
Normal file
20
game/entity.py
Normal 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)
|
@ -14,7 +14,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
||||
def _angles(start, end):
|
||||
@ -62,16 +62,6 @@ def _resolve_color(ranges, c):
|
||||
w, (a, b) = _resolve(ranges, c)
|
||||
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:
|
||||
__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.sun_color.set(*_resolve_color(_sun_color, c))
|
||||
|
||||
def resolve_inputs(self, shader):
|
||||
return _Inputs(shader)
|
||||
|
||||
def update_inputs(self, inputs):
|
||||
set_input_vec3(inputs.light_direction, self.light_direction)
|
||||
set_input_vec3(inputs.light_color, self.light_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)
|
||||
def set_inputs(self, shader):
|
||||
set_input_vec3(shader.u_light_direction, self.light_direction)
|
||||
set_input_vec3(shader.u_light_color, self.light_color)
|
||||
set_input_vec3(shader.u_horizon_color, self.horizon_color)
|
||||
set_input_vec3(shader.u_sky_color, self.sky_color)
|
||||
set_input_vec3(shader.u_sun_color, self.sun_color)
|
||||
|
54
game/events.py
Normal file
54
game/events.py
Normal 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)
|
380
game/game.py
380
game/game.py
@ -13,73 +13,131 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
from time import thread_time
|
||||
from math import pi, tau, dist
|
||||
from ctypes import Structure
|
||||
|
||||
from engine import *
|
||||
|
||||
from game import math
|
||||
from game import generator
|
||||
from game import shader
|
||||
from game import resources
|
||||
from game import batch
|
||||
from game import triangles
|
||||
from game import sea
|
||||
from game.time import Time
|
||||
from game.events import Events
|
||||
from game.mouse import Mouse
|
||||
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 Group, SceneGroup, TextureGroup, ShaderGroup, InputNode, DrawNode, FuncNode
|
||||
from game import sea
|
||||
|
||||
proj_hfov = pi * 0.25
|
||||
proj_ratio = 16.0 / 9.0
|
||||
proj_near_z = 8.0
|
||||
proj_far_z = 3000.0
|
||||
|
||||
camera_origin = (0.0, -1200.0, 500.0)
|
||||
camera_lookat = (0.0, 500.0, -500.0)
|
||||
|
||||
sun_direction = math.vec3_normalize((1.0, 0.0, 1.0))
|
||||
sun_direction = math.vec3_normalize((1.0, 0.0, 0.5))
|
||||
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...")
|
||||
gen_begin = time.process_time()
|
||||
generated = generator.Generator(256)
|
||||
gen_end = time.process_time()
|
||||
print("Done: ", round(gen_end - gen_begin, 2), "seconds")
|
||||
generated = Generator(256)
|
||||
|
||||
print("Initializing...")
|
||||
window = initialize(b'RK Island', 1600, 900)
|
||||
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,
|
||||
heightmap = Texture(
|
||||
TEXTURE_FORMAT_FLOAT_32, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
|
||||
generated.packed_heights)
|
||||
terrain_heightmap_sampler = resolve_input(terrain_shader, b'u_height_sampler')
|
||||
tests_heightmap_sampler = resolve_input(tests_shader, b'u_height_sampler')
|
||||
|
||||
normalmap = create_texture(
|
||||
normalmap = Texture(
|
||||
TEXTURE_FORMAT_RGB10_A2, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
|
||||
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...")
|
||||
archive = resources.RuntimeArchive.load('data/rk_island.rkar')
|
||||
archive = Archive.load('data/rk_island.rkar')
|
||||
|
||||
print("Building tiles...")
|
||||
tiles_texture = archive.get_texture('tiles')
|
||||
tiles_sampler = resolve_input(terrain_shader, b'u_texture_sampler')
|
||||
tiles_texture = Texture(*archive.get_texture('tiles'))
|
||||
tiles_vertices = archive.get_vertices('tiles')
|
||||
water_model = archive.get_model('water')
|
||||
sand_model = archive.get_model('sand')
|
||||
grass_model = archive.get_model('grass')
|
||||
forest_model = archive.get_model('forest')
|
||||
rock_model = archive.get_model('rock')
|
||||
mud_model = archive.get_model('mud')
|
||||
lava_model = archive.get_model('lava')
|
||||
terrain_batch = batch.Batch(tiles_vertices, generated.size ** 2, params_format(PARAM_FORMAT_VEC3_SHORT), vec3)
|
||||
water_mesh = tiles_vertices.get_mesh('water')
|
||||
sand_mesh = tiles_vertices.get_mesh('sand')
|
||||
grass_mesh = tiles_vertices.get_mesh('grass')
|
||||
forest_mesh = tiles_vertices.get_mesh('forest')
|
||||
rock_mesh = tiles_vertices.get_mesh('rock')
|
||||
mud_mesh = tiles_vertices.get_mesh('mud')
|
||||
lava_mesh = tiles_vertices.get_mesh('lava')
|
||||
tiles_batch = Batch(Vertices(*tiles_vertices), generated.size ** 2, translation = VERTEX_FORMAT_VEC3_SHORT)
|
||||
|
||||
#TODO: generator & for real
|
||||
vc = generated.volcano_c
|
||||
@ -91,188 +149,98 @@ def main():
|
||||
if h == 0.0:
|
||||
continue
|
||||
if r > 0.0:
|
||||
model = water_model
|
||||
mesh = water_mesh
|
||||
elif h < 2.0:
|
||||
model = sand_model
|
||||
mesh = sand_mesh
|
||||
elif h < 180:
|
||||
if nz > 0.9:
|
||||
if ny < -0.01 and nz > 0.93:
|
||||
model = forest_model
|
||||
mesh = forest_mesh
|
||||
else:
|
||||
model = grass_model
|
||||
mesh = grass_mesh
|
||||
else:
|
||||
model = rock_model
|
||||
mesh = rock_mesh
|
||||
elif vd < vr - 3.0 and nz > 0.999:
|
||||
model = lava_model
|
||||
mesh = lava_mesh
|
||||
elif vd < vr + 2.0:
|
||||
model = mud_model
|
||||
mesh = mud_mesh
|
||||
elif vd < vr + 6.0 and nz < 0.67:
|
||||
model = mud_model
|
||||
mesh = mud_mesh
|
||||
else:
|
||||
model = rock_model
|
||||
model.spawn(terrain_batch, vec3(float(((mx - 128) * 8) + 4), float(((127 - my) * 8) + 4), 0.0))
|
||||
mesh = rock_mesh
|
||||
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):
|
||||
_fields_ = ('translation', vec3), ('orientation', vec3)
|
||||
|
||||
tests_texture = archive.get_texture('tests')
|
||||
tests_sampler = resolve_input(tests_shader, b'u_texture_sampler')
|
||||
tests_texture = Texture(*archive.get_texture('tests'))
|
||||
tests_vertices = archive.get_vertices('tests')
|
||||
blob_model = archive.get_model('blob')
|
||||
cube_model = archive.get_model('cube')
|
||||
clouds_model = archive.get_model('clouds')
|
||||
tests_batch = batch.Batch(tests_vertices, 3,
|
||||
params_format(PARAM_FORMAT_VEC3_FLOAT, PARAM_FORMAT_VEC3_INT10 | PARAM_FORMAT_NORMALIZE), testsparams)
|
||||
blob_spawn_translation = vec3(-100.0, -500.0, 0.0)
|
||||
cube_spawn_translation = vec3(100.0, -500.0, 0.0)
|
||||
blob_id = blob_model.spawn(tests_batch,
|
||||
testsparams(blob_spawn_translation, vec3(*math.vec3_normalize((sun_direction[0], sun_direction[1], 0.0)))))
|
||||
cube_id = cube_model.spawn(tests_batch, testsparams(cube_spawn_translation, vec3_forward))
|
||||
clouds_id = clouds_model.spawn(tests_batch, testsparams(vec3(0.0, 0.0, 32.0), vec3_forward))
|
||||
blob_mesh = tests_vertices.get_mesh('blob')
|
||||
cube_mesh = tests_vertices.get_mesh('cube')
|
||||
clouds_mesh = tests_vertices.get_mesh('clouds')
|
||||
tests_batch = Batch(Vertices(*tests_vertices), 3,
|
||||
translation = VERTEX_FORMAT_VEC3_FLOAT,
|
||||
orientation = VERTEX_FORMAT_MAT3_INT10 | VERTEX_FORMAT_NORMALIZE)
|
||||
blob_forward = math.vec3_normalize((sun_direction[0], sun_direction[1], 0.0))
|
||||
blob_right = math.vec3_cross(blob_forward, math.vec3_up)
|
||||
blob_orientation = mat3(vec3(*blob_right), vec3(*blob_forward), vec3_up)
|
||||
blob = TestEntity(tests_batch, blob_mesh, translation = vec3(-100.0, -500.0, 0.0), orientation = blob_orientation)
|
||||
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_textures = sea.load_polar_textures(('data/sea_bump1.png', 'data/sea_bump2.png'))
|
||||
sea_polar_sampler = resolve_input(sky_shader, b'u_sea_polar_sampler')
|
||||
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))
|
||||
sea_polar = sea.load_polar_textures(('data/sea_bump1.png', 'data/sea_bump2.png'))
|
||||
sea_detail = sea.load_detail_texture('data/sea_bump.png')
|
||||
sea_triangles = sea.sea_triangles(64, proj_far_z - 0.1, proj_ratio)
|
||||
|
||||
camera = Camera()
|
||||
camera.set_projection(proj_hfov, proj_ratio, proj_near_z, proj_far_z)
|
||||
terrain_camera_inputs = camera.resolve_inputs(terrain_shader)
|
||||
tests_camera_inputs = camera.resolve_inputs(tests_shader)
|
||||
sky_camera_inputs = camera.resolve_inputs(sky_shader)
|
||||
assert tiles_shader.u_height_sampler == tests_shader.u_height_sampler
|
||||
assert tiles_shader.u_normal_sampler == tests_shader.u_normal_sampler
|
||||
|
||||
environment = Environment()
|
||||
terrain_environment_inputs = environment.resolve_inputs(terrain_shader)
|
||||
tests_environment_inputs = environment.resolve_inputs(tests_shader)
|
||||
sky_environment_inputs = environment.resolve_inputs(sky_shader)
|
||||
return SceneGroup(
|
||||
PerfGroup('frame',
|
||||
TextureGroup({tiles_shader.u_height_sampler: heightmap, tiles_shader.u_normal_sampler: normalmap},
|
||||
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
|
||||
cube_translation = tests_batch.params[cube_id].translation
|
||||
cube_orientation = tests_batch.params[cube_id].orientation
|
||||
clouds_orientation = tests_batch.params[clouds_id].orientation
|
||||
|
||||
print("Running...")
|
||||
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
|
||||
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")
|
||||
try:
|
||||
for frame in range(10000):
|
||||
current_time = time.monotonic() - start_time
|
||||
|
||||
begin_frame()
|
||||
frame_begin = time.thread_time()
|
||||
|
||||
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)
|
||||
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
|
||||
time = Time()
|
||||
while not keyboard.quit:
|
||||
events.update()
|
||||
clear_buffer(True, True, True)
|
||||
scene.draw(time)
|
||||
swap_buffers(display)
|
||||
time.update()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
print("\rDraw *", perf_count,
|
||||
": min =", round(draw_min * 1000.0, 2),
|
||||
", max =", round(draw_max * 1000.0, 2),
|
||||
", avg =", round((draw_avg / perf_count) * 1000.0, 2), "ms")
|
||||
|
||||
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
|
||||
|
||||
def main():
|
||||
print("Initializing...")
|
||||
display = create_display(b'RK Island - Drag to rotate, wheel to zoom, q to quit', 1600, 900)
|
||||
render_initialize(__debug__)
|
||||
loop(display)
|
||||
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()
|
||||
destroy_shader(terrain_shader)
|
||||
destroy_shader(tests_shader)
|
||||
destroy_shader(sky_shader)
|
||||
terminate()
|
||||
render_terminate()
|
||||
destroy_display(display)
|
||||
|
29
game/inputs.py
Normal file
29
game/inputs.py
Normal 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
27
game/keyboard.py
Normal 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
52
game/mouse.py
Normal 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)
|
@ -22,7 +22,7 @@ from itertools import starmap, chain, repeat
|
||||
from engine import *
|
||||
|
||||
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
|
||||
|
||||
@ -192,42 +192,47 @@ class ObjArchive(Archive):
|
||||
if name:
|
||||
assert mesh
|
||||
objects[name] = mesh
|
||||
vertices = set()
|
||||
for mesh in objects.values():
|
||||
vertices |= frozenset(chain.from_iterable(mesh))
|
||||
vertices = tuple(vertices)
|
||||
|
||||
vertices = []
|
||||
indices = []
|
||||
models = {}
|
||||
meshes = {}
|
||||
for name, mesh in sorted(objects.items()):
|
||||
offset = len(indices)
|
||||
assert offset < 65536
|
||||
indices.extend(map(vertices.index, chain.from_iterable(mesh)))
|
||||
count = len(indices) - offset
|
||||
mesh_vertices = []
|
||||
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
|
||||
count //= 3
|
||||
assert count < 65536
|
||||
print(name, ": offset =", offset, "triangles =", count)
|
||||
models[name] = (offset, count)
|
||||
vertices.extend(mesh_vertices)
|
||||
indices.extend(mesh_indices)
|
||||
meshes[name] = (offset, count)
|
||||
|
||||
name = str(objpath.stem)
|
||||
assert name not in self.vertices_db.keys()
|
||||
#TODO: move to math
|
||||
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):
|
||||
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):
|
||||
n = (pack_10(_nz) << 20) | (pack_10(_ny) << 10) | pack_10(_nx)
|
||||
t = ((_tl & 1023) << 20) | (pack_u10(_t) << 10) | pack_u10(_s)
|
||||
return struct.pack('fffII', _px, _py, _pz, n, t)
|
||||
self.vertices_db[name] = VerticesData(name,
|
||||
array('B', b''.join(starmap(pack_vertex, vertices))),
|
||||
array('H', indices))
|
||||
for name, (offset, count) in sorted(models.items()):
|
||||
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)
|
||||
array('H', indices),
|
||||
meshes)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 3:
|
||||
|
@ -13,12 +13,11 @@
|
||||
# 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 itertools import chain
|
||||
import struct
|
||||
from array import array
|
||||
from pathlib import Path
|
||||
|
||||
import png
|
||||
|
||||
import engine
|
||||
|
||||
VERTEX_SIZE = 20
|
||||
@ -50,7 +49,7 @@ def _read_struct(file, format):
|
||||
_read_magic(file, b'RK')
|
||||
return struct.unpack(format, data)
|
||||
|
||||
def _write_struct(file, format, elems):
|
||||
def _write_struct(file, format, *elems):
|
||||
assert format
|
||||
data = struct.pack(format, *elems)
|
||||
size = file.write(data)
|
||||
@ -58,7 +57,7 @@ def _write_struct(file, format, elems):
|
||||
_write_magic(file, b'RK')
|
||||
|
||||
def _read_string(file):
|
||||
length, = _read_struct(file, 'B')
|
||||
length, = _read_struct(file, 'I')
|
||||
assert length
|
||||
data = file.read(length)
|
||||
assert len(data) == length
|
||||
@ -68,7 +67,7 @@ def _read_string(file):
|
||||
def _write_string(file, string):
|
||||
data = bytes(string, encoding='ascii')
|
||||
assert data and len(data) < 256
|
||||
_write_struct(file, 'B', (len(data),))
|
||||
_write_struct(file, 'I', len(data))
|
||||
size = file.write(data)
|
||||
assert size == len(data)
|
||||
_write_magic(file, b'RK')
|
||||
@ -98,7 +97,7 @@ def _read_blob(file):
|
||||
|
||||
def _write_blob(file, array):
|
||||
assert array
|
||||
_write_struct(file, 'II', (ord(array.typecode), len(array)))
|
||||
_write_struct(file, 'II', ord(array.typecode), len(array))
|
||||
array.tofile(file)
|
||||
_write_magic(file, b'RK')
|
||||
|
||||
@ -116,9 +115,17 @@ class TextureData:
|
||||
self.flags = flags
|
||||
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
|
||||
def from_archive(cls, file):
|
||||
_read_magic(file, b'TX')
|
||||
_read_magic(file, b'TXTR')
|
||||
name = _read_string(file)
|
||||
format, width, height, nlevels, flags = _read_struct(file, 'IIIII')
|
||||
pixels = _read_blob(file)
|
||||
@ -127,92 +134,61 @@ class TextureData:
|
||||
return cls(name, format, width, height, nlevels, flags, pixels)
|
||||
|
||||
def to_archive(self, file):
|
||||
_write_magic(file, b'TX')
|
||||
_write_magic(file, b'TXTR')
|
||||
_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)
|
||||
|
||||
def create_texture(data):
|
||||
return engine.create_texture(data.format, data.width, data.height, data.nlevels, data.flags, data.pixels)
|
||||
|
||||
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:
|
||||
raise RuntimeError("Vertex format mismatch!")
|
||||
self.name = name
|
||||
self.vertices = vertices
|
||||
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
|
||||
def from_archive(cls, file):
|
||||
_read_magic(file, b'VT')
|
||||
_read_magic(file, b'VERT')
|
||||
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)
|
||||
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):
|
||||
_write_magic(file, b'VT')
|
||||
_write_magic(file, b'VERT')
|
||||
_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.indices)
|
||||
|
||||
def create_vertices(data):
|
||||
return engine.create_vertices(VERTEX_FORMAT, len(data.vertices) // VERTEX_SIZE, data.vertices, data.indices)
|
||||
|
||||
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)
|
||||
for name in self.meshes_db.keys():
|
||||
_write_string(file, name)
|
||||
for mesh in self.meshes:
|
||||
_write_struct(file, 'II', *mesh)
|
||||
|
||||
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.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):
|
||||
return self.textures_db[name]
|
||||
@ -220,38 +196,27 @@ class Archive:
|
||||
def get_vertices(self, 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
|
||||
def from_archive(cls, file):
|
||||
textures_db = {}
|
||||
vertices_db = {}
|
||||
models_db = {}
|
||||
_read_magic(file, b'RKAR')
|
||||
ntextures, nvertices, nmodels = _read_struct(file, 'III')
|
||||
ntextures, nvertices = _read_struct(file, 'II')
|
||||
for _ in range(ntextures):
|
||||
data = TextureData.from_archive(file)
|
||||
textures_db[data.name] = cls._new_texture(data)
|
||||
textures_db[data.name] = data
|
||||
for _ in range(nvertices):
|
||||
data = VerticesData.from_archive(file)
|
||||
vertices_db[data.name] = cls._new_vertices(data)
|
||||
for _ in range(nmodels):
|
||||
data = ModelData.from_archive(file)
|
||||
models_db[data.name] = cls._new_model(data)
|
||||
return cls(textures_db, vertices_db, models_db)
|
||||
vertices_db[data.name] = data
|
||||
return cls(textures_db, vertices_db)
|
||||
|
||||
def to_archive(self, file):
|
||||
_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
|
||||
def load(cls, filename):
|
||||
@ -260,30 +225,7 @@ class Archive:
|
||||
file.close()
|
||||
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):
|
||||
file = open(Path(filename), 'wb')
|
||||
self.to_archive(file)
|
||||
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
85
game/scene.py
Normal 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)
|
43
game/sea.py
43
game/sea.py
@ -13,14 +13,15 @@
|
||||
# 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 itertools import product
|
||||
from itertools import chain, product
|
||||
from math import tau, cos, sin, copysign
|
||||
from array import array
|
||||
|
||||
from engine import (create_texture,
|
||||
TEXTURE_FORMAT_RGB10_A2, TEXTURE_FORMAT_TYPECODE, TEXTURE_FLAG_MIN_LINEAR, TEXTURE_FLAG_MAG_LINEAR)
|
||||
from engine import 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.texture import Texture
|
||||
from game.triangles import Triangles
|
||||
from game.resources import load_png
|
||||
|
||||
_format = TEXTURE_FORMAT_RGB10_A2
|
||||
@ -51,7 +52,7 @@ def load_polar_textures(paths, waves_height = 0.008):
|
||||
_width, _height, normals = load_texture(path)
|
||||
assert _width == width and _height == height
|
||||
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):
|
||||
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]))
|
||||
normals = list(map(normal, product(range(height), range(width)), data))
|
||||
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)))
|
||||
|
103
game/shader.py
103
game/shader.py
@ -15,33 +15,80 @@
|
||||
|
||||
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 = ''):
|
||||
path = Path('.') / 'game' / 'shaders'
|
||||
if not frag_name:
|
||||
frag_name = vert_name
|
||||
def _cleanup(_line):
|
||||
return _line.partition('//')[0].strip()
|
||||
def _filter(_line):
|
||||
return _line
|
||||
def _convert(_line):
|
||||
return bytes(_line, 'utf-8') + b'\n'
|
||||
def load_source(_path):
|
||||
if not _path.exists():
|
||||
print("Cannot find", _path)
|
||||
return None
|
||||
return list(map(_convert, filter(_filter, map(_cleanup, open(_path, 'rt')))))
|
||||
print("Loading vertex shader", vert_name)
|
||||
vert_lines = load_source(path / (vert_name + '_opengles.vert'))
|
||||
if not vert_lines:
|
||||
print("Error: Vertex shader is empty.")
|
||||
return None
|
||||
print("Loading fragment shader", frag_name)
|
||||
frag_lines = load_source(path / (frag_name + '_opengles.frag'))
|
||||
if not frag_lines:
|
||||
print("Error: Fragment shader is empty.")
|
||||
return None
|
||||
return load_shader(vert_lines, frag_lines)
|
||||
def _filter(line):
|
||||
return line
|
||||
|
||||
def _subst(line):
|
||||
if line.startswith('#include'):
|
||||
path = Path('.') / 'game' / 'shaders'
|
||||
lines = []
|
||||
for name in line.split()[1:]:
|
||||
lines.extend(_load_source(path / name.strip('"')))
|
||||
return lines
|
||||
return [line]
|
||||
|
||||
def _load_source(path):
|
||||
assert path.exists()
|
||||
lines = filter(_filter, map(_cleanup, open(path, 'rt')))
|
||||
source = []
|
||||
for line in lines:
|
||||
source.extend(_subst(line))
|
||||
return source
|
||||
|
||||
def _convert(line):
|
||||
return bytes(line, 'utf-8') + b'\n'
|
||||
|
||||
def _parse(shader, vert_lines, frag_lines):
|
||||
uniforms = []
|
||||
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)
|
||||
|
@ -16,6 +16,8 @@
|
||||
#version 320 es
|
||||
precision highp float;
|
||||
|
||||
#include "include/environment.glsl"
|
||||
|
||||
in vec4 v_position; // view space
|
||||
in vec4 v_normal; // view space
|
||||
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
|
||||
|
||||
uniform vec3 u_light_direction; // view space (-direction_x, -direction_y, -direction_z)
|
||||
uniform vec3 u_light_color; // (color.r * power, color.g * power, color.b * power)
|
||||
|
||||
uniform highp sampler2DArray u_texture_sampler;
|
||||
layout(binding=0) uniform highp sampler2DArray u_texture_sampler;
|
||||
|
||||
layout(location = 0) out vec4 o_color;
|
||||
|
||||
|
7
game/shaders/include/camera.glsl
Normal file
7
game/shaders/include/camera.glsl
Normal 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
|
20
game/shaders/include/common.glsl
Normal file
20
game/shaders/include/common.glsl
Normal 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
|
10
game/shaders/include/environment.glsl
Normal file
10
game/shaders/include/environment.glsl
Normal 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
|
8
game/shaders/include/vertex_format.glsl
Normal file
8
game/shaders/include/vertex_format.glsl
Normal 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
|
@ -16,14 +16,11 @@
|
||||
#version 320 es
|
||||
precision highp float;
|
||||
|
||||
#include "include/camera.glsl"
|
||||
#include "include/environment.glsl"
|
||||
|
||||
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;
|
||||
|
||||
#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_origin u_view[3].xyz
|
||||
|
||||
uniform highp sampler2DArray u_sea_polar_sampler;
|
||||
uniform highp sampler2D u_sea_detail_sampler;
|
||||
layout(binding=0) uniform highp sampler2DArray u_polar_sampler;
|
||||
layout(binding=1) uniform highp sampler2D u_detail_sampler;
|
||||
|
||||
const float c_sea_radius = 637.1;
|
||||
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
|
||||
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(
|
||||
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
|
||||
s = (u_sea_phase + dot(sea_position, u_right)) * 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
|
||||
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));
|
@ -16,9 +16,9 @@
|
||||
#version 320 es
|
||||
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
|
||||
|
@ -16,39 +16,17 @@
|
||||
#version 320 es
|
||||
precision highp float;
|
||||
|
||||
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
|
||||
#include "include/vertex_format.glsl"
|
||||
#include "include/camera.glsl"
|
||||
#include "include/common.glsl"
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
layout(location = 4) in mat3 i_orientation; // per mesh, model space -> world space
|
||||
|
||||
void main(void) {
|
||||
vec3 orientation = normalize(i_orientation);
|
||||
mat3 rotation = mat3(cross(orientation, c_world_up), orientation, c_world_up);
|
||||
vec4 world_position = vec4(i_translation + rotation * a_position, 1.0);
|
||||
vec4 world_position = vec4(i_translation + i_orientation * a_position, 1.0);
|
||||
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;
|
||||
world_position.z += texture(u_height_sampler, terrain_coords).r;
|
||||
vec4 view_position = u_view * world_position;
|
||||
|
@ -16,39 +16,19 @@
|
||||
#version 320 es
|
||||
precision highp float;
|
||||
|
||||
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
|
||||
#include "include/vertex_format.glsl"
|
||||
#include "include/camera.glsl"
|
||||
#include "include/common.glsl"
|
||||
|
||||
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) {
|
||||
vec4 world_position = vec4(i_translation + a_position, 1.0);
|
||||
vec2 terrain_coords = c_terrain_shift + world_position.xy * c_terrain_scale;
|
||||
world_position.z += texture(u_height_sampler, terrain_coords).r;
|
||||
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 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_normal = u_view * vec4(world_normal, 0.0);
|
||||
v_terrain_normal = vec4((u_view * vec4(terrain_normal, 0.0)).xyz, 1.0);
|
31
game/texture.py
Normal file
31
game/texture.py
Normal 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
29
game/time.py
Normal 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
|
@ -13,38 +13,16 @@
|
||||
# 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 itertools import chain
|
||||
from array import array
|
||||
from math import cos, sin
|
||||
from engine import create_triangles, draw_triangles, destroy_triangles
|
||||
|
||||
# TODO: with FOV
|
||||
def sky_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 array('f', chain.from_iterable(vertices))
|
||||
class Triangles:
|
||||
__slots__ = '_triangles'
|
||||
|
||||
def __init__(self, triangles):
|
||||
self._triangles = create_triangles(triangles)
|
||||
|
||||
def __del__(self):
|
||||
destroy_triangles(self._triangles)
|
||||
|
||||
def draw(self):
|
||||
draw_triangles(self._triangles)
|
||||
|
25
game/vertices.py
Normal file
25
game/vertices.py
Normal 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)
|
Reference in New Issue
Block a user