Add support for menu background scenes (but no default scene yet).

This commit is contained in:
IntegratedQuantum 2023-04-04 11:30:02 +02:00
parent 3b5b9fec8a
commit 60f0c5d3af
9 changed files with 1922 additions and 15 deletions

View File

@ -0,0 +1,11 @@
#version 330
layout (location=0) out vec4 fragColor;
in vec2 outTexCoords;
uniform sampler2D image;
void main() {
fragColor = texture(image, outTexCoords);
}

View File

@ -0,0 +1,15 @@
#version 330
layout (location=0) in vec3 vertexPos;
layout (location=1) in vec2 texCoords;
out vec2 outTexCoords;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
void main() {
gl_Position = projectionMatrix*viewMatrix*vec4(vertexPos, 1);
outTexCoords = texCoords*vec2(1, -1);
}

View File

@ -43,7 +43,7 @@ pub fn build(b: *std.build.Builder) !void {
std.log.err("Unsupported target: {}\n", .{ target.getOsTag() });
}
}
exe.addCSourceFiles(&[_][]const u8{"lib/glad.c", "lib/stb_image.c"}, &[_][]const u8{"-g"});
exe.addCSourceFiles(&[_][]const u8{"lib/glad.c", "lib/stb_image.c", "lib/stb_image_write.c"}, &[_][]const u8{"-g", "-O3"});
exe.addAnonymousModule("gui", .{.source_file = .{.path = "src/gui/gui.zig"}});
const harfbuzzModule = freetype.harfbuzzModule(b);
const freetypeModule = harfbuzzModule.dependencies.get("freetype").?;

File diff suppressed because it is too large Load Diff

2
lib/stb_image_write.c Normal file
View File

@ -0,0 +1,2 @@
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb/stb_image_write.h"

View File

@ -36,11 +36,10 @@ pub const camera = struct {
}
// Mouse movement along the y-axis rotates the image along the x-axis.
rotation[1] += mouseX;
direction = vec.rotateY(vec.rotateX(Vec3f{0, 0, -1}, -rotation[0]), -rotation[1]);
}
pub fn updateViewMatrix() void {
direction = vec.rotateY(vec.rotateX(Vec3f{0, 0, -1}, -rotation[0]), -rotation[1]);
viewMatrix = Mat4f.rotationX(rotation[0]).mul(Mat4f.rotationY(rotation[1]));
}
};

View File

@ -22,6 +22,7 @@ pub const c = @cImport ({
pub const stb_image = @cImport ({
@cInclude("stb/stb_image.h");
@cInclude("stb/stb_image_write.h");
});
fn fileToString(allocator: Allocator, path: []const u8) ![]u8 {
@ -1467,6 +1468,11 @@ pub const Image = struct {
stb_image.stbi_image_free(data);
return result;
}
pub fn exportToFile(self: Image, path: []const u8) !void {
const nullTerminated = try main.threadAllocator.dupeZ(u8, path);
defer main.threadAllocator.free(nullTerminated);
_ = stb_image.stbi_write_png(nullTerminated.ptr, self.width, self.height, 4, self.imageData.ptr, self.width*4);
}
pub fn getRGB(self: Image, x: usize, y: usize) Color {
std.debug.assert(x < self.width);
std.debug.assert(y < self.height);

View File

@ -182,6 +182,12 @@ fn openCreativeInventory() void {
std.log.err("Got error while opening the inventory: {s}", .{@errorName(err)});
};
}
fn takeBackgroundImageFn() void {
if(game.world == null) return;
renderer.MenuBackGround.takeBackgroundImage() catch |err| {
std.log.err("Got error while recording the background image: {s}", .{@errorName(err)});
};
}
pub var keyboard: struct {
// Gameplay:
forward: Key = Key{.key = c.GLFW_KEY_W},
@ -193,6 +199,8 @@ pub var keyboard: struct {
fall: Key = Key{.key = c.GLFW_KEY_LEFT_SHIFT},
fullscreen: Key = Key{.key = c.GLFW_KEY_F11, .releaseAction = &Window.toggleFullscreen},
takeBackgroundImage: Key = Key{.key = c.GLFW_KEY_PRINT_SCREEN, .releaseAction = &takeBackgroundImageFn},
// Gui:
escape: Key = Key{.key = c.GLFW_KEY_ESCAPE, .releaseAction = &ungrabMouse},
openInventory: Key = Key{.key = c.GLFW_KEY_I, .releaseAction = &openInventory},
@ -457,6 +465,7 @@ pub const Window = struct {
};
pub fn main() !void {
seed = @bitCast(u64, std.time.milliTimestamp());
var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){};
threadAllocator = gpa.allocator();
defer if(gpa.deinit()) {
@ -546,12 +555,12 @@ pub fn main() !void {
var newTime = std.time.milliTimestamp();
var deltaTime = @intToFloat(f64, newTime -% lastTime)/1000.0;
lastTime = newTime;
if(game.world != null) { // Render the game
if(game.world != null) { // Update the game
try game.update(deltaTime);
c.glEnable(c.GL_CULL_FACE);
c.glEnable(c.GL_DEPTH_TEST);
try renderer.render(game.Player.getPosBlocking());
}
c.glEnable(c.GL_CULL_FACE);
c.glEnable(c.GL_DEPTH_TEST);
try renderer.render(game.Player.getPosBlocking());
{ // Render the GUI
c.glDisable(c.GL_CULL_FACE);

View File

@ -44,12 +44,15 @@ var deferredUniforms: struct {
color: c_int,
} = undefined;
pub var activeFrameBuffer: c_uint = 0;
pub fn init() !void {
fogShader = try Shader.initAndGetUniforms("assets/cubyz/shaders/fog_vertex.vs", "assets/cubyz/shaders/fog_fragment.fs", &fogUniforms);
deferredRenderPassShader = try Shader.initAndGetUniforms("assets/cubyz/shaders/deferred_render_pass.vs", "assets/cubyz/shaders/deferred_render_pass.fs", &deferredUniforms);
buffers.init();
try Bloom.init();
try MeshSelection.init();
try MenuBackGround.init();
}
pub fn deinit() void {
@ -58,6 +61,7 @@ pub fn deinit() void {
buffers.deinit();
Bloom.deinit();
MeshSelection.deinit();
MenuBackGround.deinit();
}
const buffers = struct {
@ -139,7 +143,13 @@ const buffers = struct {
}
};
var lastWidth: u31 = 0;
var lastHeight: u31 = 0;
var lastFov: f32 = 0;
pub fn updateViewport(width: u31, height: u31, fov: f32) void {
lastWidth = width;
lastHeight = height;
lastFov = fov;
c.glViewport(0, 0, width, height);
game.projectionMatrix = Mat4f.perspective(std.math.degreesToRadians(f32, fov), @intToFloat(f32, width)/@intToFloat(f32, height), zNear, zFar);
game.lodProjectionMatrix = Mat4f.perspective(std.math.degreesToRadians(f32, fov), @intToFloat(f32, width)/@intToFloat(f32, height), zNearLOD, zFarLOD);
@ -197,8 +207,7 @@ pub fn render(playerPosition: Vec3d) !void {
// clearColor.x = 0.1f;@import("main.zig")
//
// Window.setClearColor(clearColor);
//
// BackgroundScene.renderBackground();
MenuBackGround.render();
}
// Cubyz.gameUI.render();
// Keyboard.release(); // TODO: Why is this called in the render thread???
@ -212,7 +221,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
game.camera.updateViewMatrix();
// Uses FrustumCulling on the chunks.
var frustum = Frustum.init(Vec3f{0, 0, 0}, game.camera.viewMatrix, settings.fov, zFarLOD, main.Window.width, main.Window.height);
var frustum = Frustum.init(Vec3f{0, 0, 0}, game.camera.viewMatrix, lastFov, zFarLOD, lastWidth, lastHeight);
const time = @intCast(u32, std.time.milliTimestamp() & std.math.maxInt(u32));
var waterFog = Fog{.active=true, .color=.{0.0, 0.1, 0.2}, .density=0.1};
@ -314,7 +323,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
// }
// }
if(settings.bloom) {
Bloom.render(main.Window.width, main.Window.height);
Bloom.render(lastWidth, lastHeight);
}
buffers.unbind();
buffers.bindTextures();
@ -322,16 +331,14 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
c.glUniform1i(deferredUniforms.color, 3);
c.glUniform1i(deferredUniforms.position, 4);
// if(Window.getRenderTarget() != null)
// Window.getRenderTarget().bind();
c.glBindFramebuffer(c.GL_FRAMEBUFFER, activeFrameBuffer);
c.glBindVertexArray(graphics.draw.rectVAO);
c.glDisable(c.GL_DEPTH_TEST);
c.glDisable(c.GL_CULL_FACE);
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
// if(Window.getRenderTarget() != null)
// Window.getRenderTarget().unbind();
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
try entity.ClientEntityManager.renderNames(game.projectionMatrix, playerPos);
}
@ -470,6 +477,177 @@ const Bloom = struct {
}
};
pub const MenuBackGround = struct {
var shader: Shader = undefined;
var uniforms: struct {
image: c_int,
viewMatrix: c_int,
projectionMatrix: c_int,
} = undefined;
var vao: c_uint = undefined;
var vbos: [2]c_uint = undefined;
var texture: graphics.Texture = undefined;
var angle: f32 = 0;
var lastTime: i128 = undefined;
fn init() !void {
lastTime = std.time.nanoTimestamp();
shader = try Shader.initAndGetUniforms("assets/cubyz/shaders/background/vertex.vs", "assets/cubyz/shaders/background/fragment.fs", &uniforms);
shader.bind();
c.glUniform1i(uniforms.image, 0);
// 4 sides of a simple cube with some panorama texture on it.
const rawData = [_]f32 {
-1, -1, -1, 1, 1,
-1, 1, -1, 1, 0,
-1, -1, 1, 0.75, 1,
-1, 1, 1, 0.75, 0,
1, -1, 1, 0.5, 1,
1, 1, 1, 0.5, 0,
1, -1, -1, 0.25, 1,
1, 1, -1, 0.25, 0,
-1, -1, -1, 0, 1,
-1, 1, -1, 0, 0,
};
const indices = [_]c_int {
0, 1, 2,
2, 3, 1,
2, 3, 4,
4, 5, 3,
4, 5, 6,
6, 7, 5,
6, 7, 8,
8, 9, 7,
};
c.glGenVertexArrays(1, &vao);
c.glBindVertexArray(vao);
c.glGenBuffers(2, &vbos);
c.glBindBuffer(c.GL_ARRAY_BUFFER, vbos[0]);
c.glBufferData(c.GL_ARRAY_BUFFER, @intCast(isize, rawData.len*@sizeOf(f32)), &rawData, c.GL_STATIC_DRAW);
c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, 5*@sizeOf(f32), null);
c.glVertexAttribPointer(1, 2, c.GL_FLOAT, c.GL_FALSE, 5*@sizeOf(f32), @intToPtr(?*const anyopaque, 3*@sizeOf(f32)));
c.glEnableVertexAttribArray(0);
c.glEnableVertexAttribArray(1);
c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, @intCast(isize, indices.len*@sizeOf(c_int)), &indices, c.GL_STATIC_DRAW);
// Load a random texture from the backgrounds folder. The player may make their own pictures which can be chosen as well.
var dir: std.fs.IterableDir = try std.fs.cwd().makeOpenPathIterable("assets/backgrounds", .{});
defer dir.close();
var walker = try dir.walk(main.threadAllocator);
defer walker.deinit();
var fileList = std.ArrayList([]const u8).init(main.threadAllocator);
defer {
for(fileList.items) |fileName| {
main.threadAllocator.free(fileName);
}
fileList.deinit();
}
while(try walker.next()) |entry| {
if(entry.kind == .File and std.ascii.endsWithIgnoreCase(entry.basename, ".png")) {
try fileList.append(try main.threadAllocator.dupe(u8, entry.path));
}
}
if(fileList.items.len == 0) {
std.log.err("Couldn't find any background scene images in \"assets/backgrounds\".", .{});
texture = .{.textureID = 0};
return;
}
const theChosenOne = main.random.nextIntBounded(u32, &main.seed, @intCast(u32, fileList.items.len));
const theChosenPath = try std.fmt.allocPrint(main.threadAllocator, "assets/backgrounds/{s}", .{fileList.items[theChosenOne]});
defer main.threadAllocator.free(theChosenPath);
texture = try graphics.Texture.initFromFile(theChosenPath);
}
pub fn deinit() void {
shader.deinit();
c.glDeleteVertexArrays(1, &vao);
c.glDeleteBuffers(2, &vbos);
}
pub fn render() void {
if(texture.textureID == 0) return;
c.glDisable(c.GL_CULL_FACE); // I'm not sure if my triangles are rotated correctly, and there are no triangles facing away from the player anyways.
// Use a simple rotation around the y axis, with a steadily increasing angle.
const newTime = std.time.nanoTimestamp();
angle += @intToFloat(f32, newTime - lastTime)/2e10;
lastTime = newTime;
var viewMatrix = Mat4f.rotationY(angle);
shader.bind();
c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_FALSE, @ptrCast([*c]const f32, &viewMatrix));
c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast([*c]const f32, &game.projectionMatrix));
texture.bindTo(0);
c.glBindVertexArray(vao);
c.glDrawElements(c.GL_TRIANGLES, 24, c.GL_UNSIGNED_INT, null);
}
pub fn takeBackgroundImage() !void {
const size: usize = 1024; // Use a power of 2 here, to reduce video memory waste.
var pixels: []u32 = try main.threadAllocator.alloc(u32, size*size);
defer main.threadAllocator.free(pixels);
// Change the viewport and the matrices to render 4 cube faces:
updateViewport(size, size, 90.0);
defer updateViewport(Window.width, Window.height, settings.fov);
var buffer: graphics.FrameBuffer = undefined;
buffer.init(true);
defer buffer.deinit();
buffer.updateSize(size, size, c.GL_NEAREST, c.GL_REPEAT);
activeFrameBuffer = buffer.frameBuffer;
defer activeFrameBuffer = 0;
const oldRotation = game.camera.rotation;
defer game.camera.rotation = oldRotation;
const angles = [_]f32 {std.math.pi/2.0, std.math.pi, std.math.pi*3/2.0, std.math.pi*2};
// All 4 sides are stored in a single image.
var image = try graphics.Image.init(main.threadAllocator, 4*size, size);
defer image.deinit(main.threadAllocator);
for(0..4) |i| {
c.glEnable(c.GL_CULL_FACE);
c.glEnable(c.GL_DEPTH_TEST);
game.camera.rotation = .{0, angles[i], 0};
// Draw to frame buffer.
buffer.bind();
c.glClear(c.GL_DEPTH_BUFFER_BIT | c.GL_STENCIL_BUFFER_BIT | c.GL_COLOR_BUFFER_BIT);
try main.renderer.render(game.Player.getPosBlocking());
// Copy the pixels directly from OpenGL
buffer.bind();
c.glReadPixels(0, 0, size, size, c.GL_RGBA, c.GL_UNSIGNED_BYTE, pixels.ptr);
for(0..size) |y| {
for(0..size) |x| {
const index = x + y*size;
// Needs to flip the image in y-direction.
image.setRGB(x + size*i, size - 1 - y, @bitCast(graphics.Color, pixels[index]));
}
}
}
c.glDisable(c.GL_CULL_FACE);
c.glDisable(c.GL_DEPTH_TEST);
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
const fileName = try std.fmt.allocPrint(main.threadAllocator, "assets/backgrounds/{s}_{}.png", .{game.world.?.name, game.world.?.gameTime.load(.Monotonic)});
defer main.threadAllocator.free(fileName);
try image.exportToFile(fileName);
// TODO: Performance is terrible even with -O3. Consider using qoi instead.
}
};
pub const Frustum = struct {
const Plane = struct {
pos: Vec3f,