diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..ee3a0897 --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +pyglet = "*" +cython = "*" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000..66457090 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,69 @@ +{ + "_meta": { + "hash": { + "sha256": "29440d5a0a43c9b0e351da23ed46d150d8d21fd43fd4c6f2c7283cd2b9d286f1" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "cython": { + "hashes": [ + "sha256:0ce8f6c789c907472c9084a44b625eba76a85d0189513de1497ab102a9d39ef8", + "sha256:0d67964b747ac09758ba31fe25da2f66f575437df5f121ff481889a7a4485f56", + "sha256:1630823619a87a814e5c1fa9f96544272ce4f94a037a34093fbec74989342328", + "sha256:1a4c634bb049c8482b7a4f3121330de1f1c1f66eac3570e1e885b0c392b6a451", + "sha256:1ec91cc09e9f9a2c3173606232adccc68f3d14be1a15a8c5dc6ab97b47b31528", + "sha256:237a8fdd8333f7248718875d930d1e963ffa519fefeb0756d01d91cbfadab0bc", + "sha256:28a308cbfdf9b7bb44def918ad4a26b2d25a0095fa2f123addda33a32f308d00", + "sha256:2fe3dde34fa125abf29996580d0182c18b8a240d7fa46d10984cc28d27808731", + "sha256:30bda294346afa78c49a343e26f3ab2ad701e09f6a6373f579593f0cfcb1235a", + "sha256:33d27ea23e12bf0d420e40c20308c03ef192d312e187c1f72f385edd9bd6d570", + "sha256:34d24d9370a6089cdd5afe56aa3c4af456e6400f8b4abb030491710ee765bafc", + "sha256:4e4877c2b96fae90f26ee528a87b9347872472b71c6913715ca15c8fe86a68c9", + "sha256:50d6f1f26702e5f2a19890c7bc3de00f9b8a0ec131b52edccd56a60d02519649", + "sha256:55d081162191b7c11c7bfcb7c68e913827dfd5de6ecdbab1b99dab190586c1e8", + "sha256:59d339c7f99920ff7e1d9d162ea309b35775172e4bab9553f1b968cd43b21d6d", + "sha256:6cf4d10df9edc040c955fca708bbd65234920e44c30fccd057ecf3128efb31ad", + "sha256:6ec362539e2a6cf2329cd9820dec64868d8f0babe0d8dc5deff6c87a84d13f68", + "sha256:7edc61a17c14b6e54d5317b0300d2da23d94a719c466f93cafa3b666b058c43b", + "sha256:8e37fc4db3f2c4e7e1ed98fe4fe313f1b7202df985de4ee1451d2e331332afae", + "sha256:b8c996bde5852545507bff45af44328fa48a7b22b5bec2f43083f0b8d1024fd9", + "sha256:bf9c16f3d46af82f89fdefc0d64b2fb02f899c20da64548a8ea336beefcf8d23", + "sha256:c1038aba898bed34ab1b5ddb0d3f9c9ae33b0649387ab9ffe6d0af677f66bfc1", + "sha256:d405649c1bfc42e20d86178257658a859a3217b6e6d950ee8cb76353fcea9c39", + "sha256:db6eeb20a3bd60e1cdcf6ce9a784bc82aec6ab891c800dc5d7824d5cfbfe77f2", + "sha256:e382f8cb40dca45c3b439359028a4b60e74e22d391dc2deb360c0b8239d6ddc0", + "sha256:f3f6c09e2c76f2537d61f907702dd921b04d1c3972f01d5530ef1f748f22bd89", + "sha256:f749287087f67957c020e1de26906e88b8b0c4ea588facb7349c115a63346f67", + "sha256:f86b96e014732c0d1ded2c1f51444c80176a98c21856d0da533db4e4aef54070" + ], + "index": "pypi", + "version": "==0.29.7" + }, + "future": { + "hashes": [ + "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8" + ], + "version": "==0.17.1" + }, + "pyglet": { + "hashes": [ + "sha256:8b07aea16f34ac861cffd06a0c17723ca944d172e577b57b21859b7990709a66", + "sha256:b00570e7cdf6971af8953b6ece50d83d13272afa5d1f1197c58c0f478dd17743" + ], + "index": "pypi", + "version": "==1.3.2" + } + }, + "develop": {} +} diff --git a/blocks.py b/blocks.py index 5f3980f1..13c3652b 100644 --- a/blocks.py +++ b/blocks.py @@ -4,7 +4,7 @@ # Future imports -from math import floor +from math import floor, ceil, sqrt # Python packages # Nothing for now... @@ -41,15 +41,15 @@ def get_texture_coordinates(x, y, tileset_size=G.TILESET_SIZE): #To enable, extract a texture pack's blocks folder to resources/texturepacks/textures/blocks/ #For MC 1.5 Texture Packs class TextureGroupIndividual(pyglet.graphics.Group): - def __init__(self, names, height=1.0, width=1.0): + def __init__(self, names, height=1.0, width=1.0, background_color=None): super(TextureGroupIndividual, self).__init__() atlas = None # self.texture = atlas.texture self.texture_data = [] i=0 texture_pack = G.texture_pack_list.selected_texture_pack + for name in names: - if not name in BLOCK_TEXTURE_DIR: BLOCK_TEXTURE_DIR[name] = texture_pack.load_texture(['textures', 'blocks', name + '.png']) @@ -58,14 +58,25 @@ def __init__(self, names, height=1.0, width=1.0): texture_size = BLOCK_TEXTURE_DIR[name].width + # remove background color for crack textures + if background_color is not None: + data = bytearray(BLOCK_TEXTURE_DIR[name].get_image_data().get_data('RGBA', texture_size * 4)) + for i in range(len(data)): + if data [i] == background_color: + data[i] = 0 + BLOCK_TEXTURE_DIR[name].get_image_data().set_data('RGBA', texture_size * 4, bytes(data)) + if atlas == None: - atlas = TextureAtlas(texture_size * len(names), texture_size) + # prevent pyglet from using rectangle texture + atlas = TextureAtlas(texture_size * ceil(sqrt(len(names))), texture_size * ceil(sqrt(len(names)))) self.texture = atlas.texture subtex = atlas.add(BLOCK_TEXTURE_DIR[name].get_region(0,0,texture_size,texture_size)) + for val in subtex.tex_coords: i += 1 - if i % 3 != 0: self.texture_data.append(val) #tex_coords has a z component we don't utilize + if i % 3 != 0: self.texture_data.append(val) # tex_coords has a z component we don't utilize + if atlas == None: atlas = TextureAtlas(1, 1) self.texture = atlas.texture @@ -96,13 +107,17 @@ def __init__(self, names, height=1.0, width=1.0): def set_state(self): if self.texture: + glActiveTexture(GL_TEXTURE0) glBindTexture(self.texture.target, self.texture.id) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glEnable(self.texture.target) def unset_state(self): if self.texture: + glActiveTexture(GL_TEXTURE0) glDisable(self.texture.target) @@ -326,6 +341,30 @@ def get_vertices(self, x, y, z): ) return vertices + def get_normal(self): + if self.vertex_mode == G.VERTEX_GRID or self.vertex_mode == G.VERTEX_CROSS: + return None + + normal = () + + if len(self.top_texture) > 0 or len(self.side_texture) == 0: + normal += ( + 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0 # top + ) + if len(self.bottom_texture) > 0 or len(self.side_texture) == 0: + normal += ( + 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0 # bottom + ) + + normal += ( + -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, # left + 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, # right + 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, # front + 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, # back + ) + + return normal + def get_metadata(self): if self.sub_id_as_metadata: return self.id.sub @@ -347,10 +386,10 @@ def update_texture(self): if self.group: self.texture_data = self.group.texture_data - + if self.top_texture == (): return - + if self.front_texture is None: self.front_texture = self.side_texture if not self.texture_data: @@ -412,7 +451,7 @@ class StoneBlock(HardBlock): def __init__(self): super(StoneBlock, self).__init__() self.drop_id = BlockID(CobbleBlock.id) - + colorizer = BlockColorizer('stonecolor.png') def get_color(self, temperature, humidity): @@ -1401,7 +1440,7 @@ def fertilize(self): return False G.CLIENT.update_tile_entity(self.entity.position, make_nbt_from_dict({'action': 'fertilize'})) return True - + def update_tile_entity(self, value): nbt = extract_nbt(value) # client side @@ -1738,7 +1777,7 @@ def __init__(self): texture_name = [] for i in range(self.crack_level): texture_name.append('destroy_' + str(i)) - self.group = TextureGroupIndividual(texture_name) + self.group = TextureGroupIndividual(texture_name, background_color=0x7f) for i in range(self.crack_level): self.texture_data.append(self.group.texture_data[i * 8:(i + 1) * 8] * 6) diff --git a/controllers.py b/controllers.py index 17fc27f7..2414b1c2 100644 --- a/controllers.py +++ b/controllers.py @@ -11,7 +11,6 @@ import os import random - # Third-party packages import threading from pyglet.gl import * @@ -26,6 +25,7 @@ from items import Tool from player import Player from model import PlayerModel +from shaders import create_block_shader from skydome import Skydome import utils from utils import vec, sectorize, normalize, load_image, image_sprite @@ -51,7 +51,7 @@ def setup(self): def update(self, dt): if self.current_view: self.current_view.update(dt) - + def switch_view(self, new_view): if self.current_view: self.current_view.pop_handlers() @@ -118,11 +118,8 @@ def __init__(self, window): self.sector, self.highlighted_block, self.crack, self.last_key = (None,) * 4 self.bg_red, self.bg_green, self.bg_blue = (0.0,) * 3 self.mouse_pressed, self.sorted = (False,) * 2 - self.count, self.block_damage = (0,) * 2 - self.light_y, self.light_z = (1.0,) * 2 - self.time_of_day = 0.0 - self.hour_deg = 15.0 - self.clock = 6 + self.block_damage = 0 + self.time_of_day = 6.0 self.back_to_main_menu = threading.Event() @@ -133,7 +130,7 @@ def update(self, dt): self.update_sector(dt) self.update_player(dt) self.update_mouse(dt) - self.update_time() + self.update_time(dt) self.camera.update(dt) def update_sector(self, dt): @@ -199,7 +196,7 @@ def update_block_remove(self, dt, hit_block): if not self.player.add_item(item, quantity=hit_block.drop_quantity[index]): return elif not self.player.add_item(hit_block.drop_id, quantity=hit_block.drop_quantity): - return + return self.item_list.update_items() self.inventory_list.update_items() @@ -208,7 +205,7 @@ def init_gl(self): glAlphaFunc(GL_GREATER, 0.1) glEnable(GL_COLOR_MATERIAL) glEnable(GL_BLEND) - + glEnable(GL_LINE_SMOOTH) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glEnable(GL_POLYGON_SMOOTH) @@ -217,10 +214,14 @@ def init_gl(self): #glClampColorARB(GL_CLAMP_VERTEX_COLOR_ARB, GL_FALSE) #glClampColorARB(GL_CLAMP_FRAGMENT_COLOR_ARB, GL_FALSE) #glClampColorARB(GL_CLAMP_READ_COLOR_ARB, GL_FALSE) - + #glClearColor(0, 0, 0, 0) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + self.block_shader = create_block_shader() + self.block_shader.uniformi('uDiffuse', 0) + + def setup(self): if G.SINGLEPLAYER: try: @@ -255,7 +256,7 @@ def setup(self): sky_rotation = -20.0 # -20.0 - # TERRAIN_CHOICE = self.biome_generator.get_biome_type(sector[0], sector[2]) + # TERRAIN_CHOICE = self.biome_generator.get_biome_type(sector[0], sector[2]) default_skybox = 'skydome.jpg' #if TERRAIN_CHOICE == G.NETHER: # default_skybox = 'skydome_nether.jpg' @@ -277,7 +278,6 @@ def setup(self): self.focus_block = Block(width=1.05, height=1.05) self.earth = vec(0.8, 0.8, 0.8, 1.0) self.white = vec(1.0, 1.0, 1.0, 1.0) - self.ambient = vec(1.0, 1.0, 1.0, 1.0) self.polished = GLfloat(100.0) self.crack_batch = pyglet.graphics.Batch() @@ -323,7 +323,7 @@ def setup(self): self.label = pyglet.text.Label( '', font_name='Arial', font_size=8, x=10, y=self.window.height - 10, anchor_x='left', anchor_y='top', color=(255, 255, 255, 255)) - + #if G.DEBUG_TEXT_ENABLED: # self.debug_text = TextWidget(self.window, '', # 0, self.window.height - 300, @@ -337,56 +337,17 @@ def setup(self): pyglet.clock.schedule_interval_soft(self.world.hide_sectors, 10.0, self.player) return True - def update_time(self): - """ - The idle function advances the time of day. - The day has 24 hours, from sunrise to sunset and from sunrise to - second sunset. - The time of day is converted to degrees and then to radians. - """ - - if not self.window.exclusive: - return - - time_of_day = self.time_of_day if self.time_of_day < 12.0 \ - else 24.0 - self.time_of_day - - if time_of_day <= 2.5: - self.time_of_day += 1.0 / G.TIME_RATE - time_of_day += 1.0 / G.TIME_RATE - self.count += 1 - else: - self.time_of_day += 20.0 / G.TIME_RATE - time_of_day += 20.0 / G.TIME_RATE - self.count += 1.0 / 20.0 + def update_time(self, dt: float): + self.time_of_day += dt * 24.0 / G.TIME_RATE if self.time_of_day > 24.0: self.time_of_day = 0.0 - time_of_day = 0.0 - - side = len(self.world.sectors) * 2.0 - - self.light_y = 2.0 * side * sin(time_of_day * self.hour_deg - * G.DEG_RAD) - self.light_z = 2.0 * side * cos(time_of_day * self.hour_deg - * G.DEG_RAD) - if time_of_day <= 2.5: - ambient_value = 1.0 - else: - ambient_value = 1 - (time_of_day - 2.25) / 9.5 - self.ambient = vec(ambient_value, ambient_value, ambient_value, 1.0) # Calculate sky colour according to time of day. - sin_t = sin(pi * time_of_day / 12.0) + sin_t = sin(pi * (((self.time_of_day / 12.0) + 1) % 2 - 1)) self.bg_red = 0.1 * (1.0 - sin_t) self.bg_green = 0.9 * sin_t self.bg_blue = min(sin_t + 0.4, 0.8) - if fmod(self.count / 2, G.TIME_RATE) == 0: - if self.clock == 18: - self.clock = 6 - else: - self.clock += 1 - self.skydome.update_time_of_day(self.time_of_day) def set_highlighted_block(self, block): @@ -425,7 +386,7 @@ def on_mouse_press_right(self, block, previous, x, y, button, modifiers): self.inventory_list.set_furnace(hit_block) self.inventory_list.toggle(False) elif hit_block.density >= 1: - self.put_block(previous) + self.put_block(previous) elif self.item_list.get_current_block() and getattr(self.item_list.get_current_block(), 'regenerated_health', 0) != 0 and self.player.health < self.player.max_health: self.eat_item() @@ -454,7 +415,7 @@ def on_mouse_release(self, x, y, button, modifiers): self.set_highlighted_block(None) self.mouse_pressed = False - def on_mouse_motion(self, x, y, dx, dy): + def on_mouse_motion(self, x, y, dx, dy): if self.window.exclusive: m = 0.15 x, y = self.player.rotation @@ -544,10 +505,14 @@ def on_draw(self): #self.window.clear() self.set_3d() #glColor3d(1, 1, 1) + self.block_shader.bind() + sun_position = self.skydome.get_sun_position() + self.block_shader.uniformf('uLightPos', *sun_position) self.world.batch.draw() + self.block_shader.unbind() self.world.transparency_batch.draw() glEnable(GL_BLEND) - glBlendFunc(GL_ZERO, GL_DST_ALPHA) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) self.crack_batch.draw() glDisable(GL_BLEND) self.draw_focused_block() @@ -613,8 +578,7 @@ def draw_focused_block(self): def draw_label(self): x, y, z = self.player.position self.label.text = 'Time:%.1f Inaccurate FPS:%02d (%.2f, %.2f, %.2f) Blocks Shown: %d / %d sector_packets:%d'\ - % (self.time_of_day if (self.time_of_day < 12.0) - else (24.0 - self.time_of_day), + % (self.time_of_day, pyglet.clock.get_fps(), x, y, z, len(self.world._shown), len(self.world), len(self.world.sector_packets)) self.label.draw() @@ -624,8 +588,7 @@ def update_label(self): self.debug_text.clear() self.debug_text.write_line(' '.join((G.APP_NAME, str(G.APP_VERSION)))) self.debug_text.write_line('Time:%.1f Inaccurate FPS:%02d Blocks Shown: %d / %d sector_packets:%d'\ - % (self.time_of_day if (self.time_of_day < 12.0) - else (24.0 - self.time_of_day), + % (self.time_of_day, pyglet.clock.get_fps(), len(self.world._shown), len(self.world), len(self.world.sector_packets))) self.debug_text.write_line('x: %.2f, sector: %d' %(x, x // G.SECTOR_SIZE)) @@ -690,7 +653,7 @@ def on_close(self): def show_map(self): print("map called...") - # taken from Nebual's biome_explorer, this is ment to be a full screen map that uses mini tiles to make a full 2d map. + # taken from Nebual's biome_explorer, this is ment to be a full screen map that uses mini tiles to make a full 2d map. with open(os.path.join(G.game_dir, "world", "seed"), "r") as f: SEED = f.read() b = BiomeGenerator(SEED) @@ -711,27 +674,27 @@ def show_map(self): #sprite = pyglet.sprite.Sprite(image) #sprite.image(image) #sprite.visible = True - # map_frame.draw() - # map_frame.visible = True + # map_frame.draw() + # map_frame.visible = True for y in range(int(cury),int(cury+ysize)): - for x in range(int(curx),int(curx+xsize)): - #string += letters[b.get_biome_type(x,y)] - tmap = letters[b.get_biome_type(x,y)] - tile_map = load_image('resources', 'textures', tmap +'.png') - tile_map.anchor_x = x * 8 - tile_map.anchor_y = y * 8 - sprite = pyglet.sprite.Sprite(tile_map, x=x * 8, y=y * 8, batch=pbatch) - game_map = image_sprite(tile_map, pbatch, pgroup, x * 8, y * 8, 8, 8) - game_map = pyglet.sprite.Sprite(image,x=G.WINDOW_WIDTH, y=G.WINDOW_HEIGHT,batch=pbatch, group=pgroup) - game_map = pyglet.sprite.Sprite(tile_map,x=x*8, y=y*8,batch=pbatch, group=pgroup) - - tile_map.blit(x *8, y * 8) - - #tile_map.draw() - #map.append(tmap) - game_map.draw() - pbatch.draw() - ## Save to file, did not work... + for x in range(int(curx),int(curx+xsize)): + #string += letters[b.get_biome_type(x,y)] + tmap = letters[b.get_biome_type(x,y)] + tile_map = load_image('resources', 'textures', tmap +'.png') + tile_map.anchor_x = x * 8 + tile_map.anchor_y = y * 8 + sprite = pyglet.sprite.Sprite(tile_map, x=x * 8, y=y * 8, batch=pbatch) + game_map = image_sprite(tile_map, pbatch, pgroup, x * 8, y * 8, 8, 8) + game_map = pyglet.sprite.Sprite(image,x=G.WINDOW_WIDTH, y=G.WINDOW_HEIGHT,batch=pbatch, group=pgroup) + game_map = pyglet.sprite.Sprite(tile_map,x=x*8, y=y*8,batch=pbatch, group=pgroup) + + tile_map.blit(x *8, y * 8) + + #tile_map.draw() + #map.append(tmap) + game_map.draw() + pbatch.draw() + ## Save to file, did not work... #map.image.save('mapX.png') # map_streamX = open('mapX2.png', 'wb') # tile_map.save('mapX2.png', file=map_streamX) diff --git a/globals.py b/globals.py index 7744868d..43a5d06e 100644 --- a/globals.py +++ b/globals.py @@ -109,7 +109,7 @@ # ids of items should be >= ITEM_ID_MIN ITEM_ID_MIN = 256 -TIME_RATE = 240 * 10 # Rate of change (steps per hour). +TIME_RATE = (20 * 60) # Rate of change (seconds per game day). SPREADING_MUTATION_DELAY = 4 # in seconds diff --git a/main.py b/main.py index 1c6c16b2..61ef5834 100755 --- a/main.py +++ b/main.py @@ -34,6 +34,7 @@ def __init__(self, **kwargs): ) super(Window, self).__init__( G.WINDOW_WIDTH, G.WINDOW_HEIGHT, **kwargs) + self.exclusive = False self.reticle = None self.controller = None @@ -136,7 +137,7 @@ def main(options): if G.CLIENT: G.CLIENT.stop() - + if G.SERVER: print('Saving...') save_world(G.SERVER, "world") diff --git a/shader.py b/shader.py new file mode 100644 index 00000000..e2aafee7 --- /dev/null +++ b/shader.py @@ -0,0 +1,131 @@ +# +# Copyright Tristam Macdonald 2008. +# +# Distributed under the Boost Software License, Version 1.0 +# (see http://www.boost.org/LICENSE_1_0.txt) +# + +from pyglet.gl import * +from ctypes import c_char_p, cast, pointer, POINTER, c_char, c_int, byref, create_string_buffer, c_float + + +class Shader: + # vert, frag and geom take arrays of source strings + # the arrays will be concattenated into one string by OpenGL + def __init__(self, vert=[], frag=[], geom=[]): + # create the program handle + self.handle = glCreateProgram() + # we are not linked yet + self.linked = False + + # create the vertex shader + self.createShader(vert, GL_VERTEX_SHADER) + # create the fragment shader + self.createShader(frag, GL_FRAGMENT_SHADER) + # the geometry shader will be the same, once pyglet supports the extension + # self.createShader(frag, GL_GEOMETRY_SHADER_EXT) + + # attempt to link the program + self.link() + + def createShader(self, strings, type): + count = len(strings) + # if we have no source code, ignore this shader + if count < 1: + return + + # create the shader handle + shader = glCreateShader(type) + + # convert the source strings into a ctypes pointer-to-char array, and upload them + # this is deep, dark, dangerous black magick - don't try stuff like this at home! + src = (c_char_p * count)(*[x.encode() for x in strings]) + glShaderSource(shader, count, + cast(pointer(src), POINTER(POINTER(c_char))), None) + + # compile the shader + glCompileShader(shader) + + temp = c_int(0) + # retrieve the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, byref(temp)) + + # if compilation failed, print the log + if not temp: + # retrieve the log length + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, byref(temp)) + # create a buffer for the log + buffer = create_string_buffer(temp.value) + # retrieve the log text + glGetShaderInfoLog(shader, temp, None, buffer) + print(buffer.value) + else: + # all is well, so attach the shader to the program + glAttachShader(self.handle, shader) + + def link(self): + # link the program + glLinkProgram(self.handle) + + temp = c_int(0) + # retrieve the link status + glGetProgramiv(self.handle, GL_LINK_STATUS, byref(temp)) + + # if linking failed, print the log + if not temp: + # retrieve the log length + glGetProgramiv(self.handle, GL_INFO_LOG_LENGTH, byref(temp)) + # create a buffer for the log + buffer = create_string_buffer(temp.value) + # retrieve the log text + glGetProgramInfoLog(self.handle, temp, None, buffer) + print(buffer.value) + else: + # all is well, so we are linked + self.linked = True + + def bind(self): + # bind the program + glUseProgram(self.handle) + + def unbind(self): + # unbind whatever program is currently bound - not necessarily this program, + # so this should probably be a class method instead + glUseProgram(0) + + # upload a floating point uniform + # this program must be currently bound + def uniformf(self, name, *vals): + # check there are 1-4 values + if len(vals) in range(1, 5): + # select the correct function + { + 1: glUniform1f, + 2: glUniform2f, + 3: glUniform3f, + 4: glUniform4f + # retrieve the uniform location, and set + }[len(vals)](glGetUniformLocation(self.handle, create_string_buffer(name.encode())), *vals) + + # upload an integer uniform + # this program must be currently bound + def uniformi(self, name, *vals): + # check there are 1-4 values + if len(vals) in range(1, 5): + # select the correct function + { + 1: glUniform1i, + 2: glUniform2i, + 3: glUniform3i, + 4: glUniform4i + # retrieve the uniform location, and set + }[len(vals)](glGetUniformLocation(self.handle, create_string_buffer(name.encode())), *vals) + + # upload a uniform matrix + # works with matrices stored as lists, + # as well as euclid matrices + def uniform_matrixf(self, name, mat): + # obtian the uniform location + loc = glGetUniformLocation(self.handle, name) + # uplaod the 4x4 floating point matrix + glUniformMatrix4fv(loc, 1, False, (c_float * 16)(*mat)) diff --git a/shaders.py b/shaders.py new file mode 100644 index 00000000..3f7c47c5 --- /dev/null +++ b/shaders.py @@ -0,0 +1,55 @@ +# Imports, sorted alphabetically. + +# Python packages + +# Third-party packages + +# Modules from this project +from shader import Shader + + +__all__ = ( + 'create_block_shader' +) + + +def create_block_shader(): + return Shader([ + ''' +#version 130 +out vec3 vWorldPos; +out vec2 vTexCoord; +out vec3 vNormal; +out vec3 vColor; + +void main() +{ + gl_Position = ftransform(); + vWorldPos = vec3(gl_ModelViewMatrix * gl_Vertex); + vTexCoord = gl_MultiTexCoord0.st; + vNormal = gl_Normal.xyz; + vColor = gl_Color.rgb; +} +''' + ], [ + ''' +#version 130 + +in vec3 vWorldPos; +in vec2 vTexCoord; +in vec3 vNormal; +in vec3 vColor; + +uniform sampler2D uDiffuse; +uniform vec3 uLightPos; + +void main (void) +{ + vec3 light_dir = normalize(uLightPos - vWorldPos); + vec3 diffuse = texture2D(uDiffuse, vTexCoord).rgb; + float lambert = max(0.4, dot(vNormal, light_dir)); + vec4 final_color = vec4(lambert * diffuse * vColor, 1.0); + gl_FragColor = final_color; +} +''' + ]) diff --git a/skydome.py b/skydome.py index 7d706ba6..77018a84 100644 --- a/skydome.py +++ b/skydome.py @@ -58,7 +58,7 @@ def sphere_vert(i, j): v_length = vend - vstart v = (v_length-i * v_length) + vstart return (x, y, z), (u, v) - + for j in range(40): v, uv = sphere_vert(0, j) vertex.extend(v) @@ -70,7 +70,7 @@ def sphere_vert(i, j): vertex.extend(v) uvs.extend(uv) count += 3 - + for i in range(1, 10): for j in range(40): v, uv = sphere_vert(i, j) @@ -82,7 +82,7 @@ def sphere_vert(i, j): v, uv = sphere_vert(i+1, j+1) vertex.extend(v) uvs.extend(uv) - + v, uv = sphere_vert(i, j) vertex.extend(v) uvs.extend(uv) @@ -129,6 +129,9 @@ def sun_vertex(self, sun_angle): ('t2f/static', uv_list), ) + def get_sun_position(self): + return (0.0, self.size * sin(self.sun_angle), self.size * -cos(self.sun_angle)) + def draw(self): glPushMatrix() # draw skydome diff --git a/utils.py b/utils.py index 95391914..22574033 100644 --- a/utils.py +++ b/utils.py @@ -4,6 +4,7 @@ import os import struct from typing import Tuple, List +from ctypes import byref # Third-party packages import pyglet @@ -72,7 +73,12 @@ def init_font(filename, fontname): pyglet.font.load(fontname) + +_block_icon_fbo = None + def get_block_icon(block, icon_size, world): + global _block_icon_fbo + print(block.id.filename()) block_icon = G.texture_pack_list.selected_texture_pack.load_texture(block.id.filename()) \ or (block.group or world.group).texture.get_region( @@ -80,7 +86,65 @@ def get_block_icon(block, icon_size, world): int(block.texture_data[2 * 8 + 1] * G.TILESET_SIZE) * icon_size, icon_size, icon_size) - return block_icon + + if block.id.is_item(): + return block_icon + + # create 3d icon for blocks + if _block_icon_fbo is None: + _block_icon_fbo = GLuint(0) + glGenFramebuffers(1, byref(_block_icon_fbo)) + + glBindFramebuffer(GL_FRAMEBUFFER, _block_icon_fbo) + + icon_texture = pyglet.image.Texture.create(icon_size, icon_size, GL_RGBA) + glBindTexture(GL_TEXTURE_2D, icon_texture.id) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, icon_size, icon_size, 0, GL_RGBA, GL_FLOAT, None) + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, icon_texture.id, 0) + + viewport = (GLint * 4)() + glGetIntegerv(GL_VIEWPORT, viewport) + glViewport(0, 0, icon_size, icon_size) + + glClearColor(1.0, 1.0, 1.0, 0.0) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + glOrtho(-1.5, 1.5, -1.5, 1.5, -10, 10) + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + glColor4f(1.0, 1.0, 1.0, 1.0) + glRotatef(-45.0, 0.0, 1.0, 0.0) + glRotatef(-30.0, -1.0, 0.0, 1.0) + glScalef(1.5, 1.5, 1.5) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + + vertex_data = block.get_vertices(0, 0, 0) + texture_data = block.texture_data + count = len(texture_data) // 2 + batch = pyglet.graphics.Batch() + batch.add(count, GL_QUADS, (block.group or world.group), + ('v3f/static', vertex_data), + ('t2f/static', texture_data)) + batch.draw() + + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) + glPopMatrix() + + glBindFramebuffer(GL_FRAMEBUFFER, 0) + + glViewport(*viewport) + glClearColor(0.0, 0.0, 0.0, 1.0) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + + return icon_texture.get_image_data() FACES: Tuple[iVector, ...] = ( @@ -154,12 +218,14 @@ def __init__(self, path): self.texture = pyglet.image.load(path).get_texture() def set_state(self): + glActiveTexture(GL_TEXTURE0) glBindTexture(self.texture.target, self.texture.id) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glEnable(self.texture.target) def unset_state(self): + glActiveTexture(GL_TEXTURE0) glDisable(self.texture.target) # Named Binary Tag diff --git a/world.py b/world.py index e2b4ff17..ebc245d6 100644 --- a/world.py +++ b/world.py @@ -203,6 +203,8 @@ def _show_block(self, position: iVector, block): # only show exposed faces vertex_data = list(block.get_vertices(*position)) texture_data = list(block.texture_data) + normal_data = block.get_normal() + color_data = None if hasattr(block, 'get_color') and self.biome_generator is not None: temp = self.biome_generator.get_temperature(position[0], position[-1]) @@ -224,15 +226,21 @@ def _show_block(self, position: iVector, block): count = len(texture_data) // 2 # create vertex list batch = self.transparency_batch if block.transparent else self.batch - if color_data is not None: + if color_data is None: + color_data = [1.0] * (count * 3) + + if normal_data is not None: self._shown[position] = batch.add(count, GL_QUADS, block.group or self.group, - ('v3f/static', vertex_data), - ('t2f/static', texture_data), - ('c3f/static', color_data)) + ('v3f/static', vertex_data), + ('t2f/static', texture_data), + ('c3f/static', color_data), + ('n3f/static', normal_data)) else: self._shown[position] = batch.add(count, GL_QUADS, block.group or self.group, ('v3f/static', vertex_data), - ('t2f/static', texture_data)) + ('t2f/static', texture_data), + ('c3f/static', color_data)) + def show_sector(self, sector: iVector): if sector in self.sectors: @@ -321,4 +329,4 @@ def hide_sectors(self, dt, player): for sector in self.sectors: x,y,z = sector if abs(px-x) > deload or abs(py-y) > deload or abs(pz-z) > deload: - self.enqueue_sector(False, sector) \ No newline at end of file + self.enqueue_sector(False, sector)