# 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 . from time import thread_time from math import pi, tau, dist from engine import * from game import math 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 sun_direction = math.vec3_normalize((1.0, 0.0, 0.5)) sun_power = 1.0 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...") generated = Generator(256) heightmap = Texture( TEXTURE_FORMAT_FLOAT_32, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR, generated.packed_heights) normalmap = Texture( TEXTURE_FORMAT_RGB10_A2, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR, generated.packed_normals) print("Loading resources...") archive = Archive.load('data/rk_island.rkar') print("Building tiles...") tiles_texture = Texture(*archive.get_texture('tiles')) tiles_vertices = archive.get_vertices('tiles') 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 vr = generated.volcano_r for my, mx in generated.map_coords: vd = dist((mx + 0.5, my + 0.5), vc) nx, ny, nz, h = generated.unpack(my, mx) r = generated.rivers[my * generated.size + mx] if h == 0.0: continue if r > 0.0: mesh = water_mesh elif h < 2.0: mesh = sand_mesh elif h < 180: if nz > 0.9: if ny < -0.01 and nz > 0.93: mesh = forest_mesh else: mesh = grass_mesh else: mesh = rock_mesh elif vd < vr - 3.0 and nz > 0.999: mesh = lava_mesh elif vd < vr + 2.0: mesh = mud_mesh elif vd < vr + 6.0 and nz < 0.67: mesh = mud_mesh else: 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) tests_texture = Texture(*archive.get_texture('tests')) tests_vertices = archive.get_vertices('tests') 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_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) assert tiles_shader.u_height_sampler == tests_shader.u_height_sampler assert tiles_shader.u_normal_sampler == tests_shader.u_normal_sampler 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)))))) 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: time = Time() while not keyboard.quit: events.update() clear_buffer(True, True, True) scene.draw(time) swap_buffers(display) time.update() except KeyboardInterrupt: pass 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...") render_terminate() destroy_display(display)