diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Decals.h | 20 | ||||
-rw-r--r-- | src/Decals.zig | 214 | ||||
-rw-r--r-- | src/Game.h | 3 | ||||
-rw-r--r-- | src/GameDraw.cpp | 23 | ||||
-rw-r--r-- | src/GameInitDispose.cpp | 24 | ||||
-rw-r--r-- | src/GameTick.cpp | 28 | ||||
-rw-r--r-- | src/Globals.cpp | 1 | ||||
-rw-r--r-- | src/Sprites.cpp | 6 | ||||
-rw-r--r-- | src/Textures.zig | 85 | ||||
-rw-r--r-- | src/cimport.zig | 1 | ||||
-rw-r--r-- | src/decal.zig | 247 | ||||
-rw-r--r-- | src/main.zig | 11 | ||||
-rw-r--r-- | src/misc.zig | 38 |
13 files changed, 326 insertions, 375 deletions
diff --git a/src/Decals.h b/src/Decals.h index 4eb6cf7..743344e 100644 --- a/src/Decals.h +++ b/src/Decals.h @@ -25,32 +25,14 @@ #include "Models.h" #include "Quaternions.h" -#define MAX_DECALS 120 - enum decal { BULLET_HOLE, CRATER, BLOOD_POOL }; -struct Decals { - GLuint hole_textures[2]; - GLuint blood_textures[11]; - - GLuint len; - enum decal kind[MAX_DECALS]; - XYZ points[MAX_DECALS * 8]; - GLuint numpoints[MAX_DECALS]; - GLfloat texcoordsx[MAX_DECALS * 8]; - GLfloat texcoordsy[MAX_DECALS * 8]; - GLfloat alive[MAX_DECALS]; -}; - #ifdef __cplusplus extern "C" { #endif // __cplusplus -void addDecal(struct Decals *d, enum decal kind, XYZ location, float size, +void addDecal(void *decals, enum decal kind, XYZ location, float size, XYZ normal, int poly, const struct Model *model, XYZ move, float rotation); -void updateDecals(struct Decals *d); -void drawDecals(struct Decals *d); -void destroyDecals(struct Decals *d); #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/src/Decals.zig b/src/Decals.zig new file mode 100644 index 0000000..ecbc71c --- /dev/null +++ b/src/Decals.zig @@ -0,0 +1,214 @@ +// Decal construction and drawing +// Copyright (C) 2002 David Rosen +// Copyright (C) 2003 Steven Fuller +// Copyright (C) 2023 Nguyễn Gia Phong +// +// This file is part of Black Shades. +// +// Black Shades is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Black Shades 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Black Shades. If not, see <https://www.gnu.org/licenses/>. + +const Allocator = std.mem.Allocator; +const Child = std.meta.Child; +const Model = model.Model; +const MultiArrayList = std.MultiArrayList; +const Textures = @import("Textures.zig"); +const XYZ = geom.XYZ; +const c = @import("cimport.zig"); +const geom = @import("geom.zig"); +const maxInt = std.math.maxInt; +const model = @import("model.zig"); +const norm = geom.norm; +const segCrossModelTrans = model.segCrossModelTrans; +const segCrossTrigon = geom.segCrossTrigon; +const splat = geom.splat; +const std = @import("std"); + +const ones: @Vector(3, f32) = @splat(1.0); + +fn cross(u: @Vector(3, f32), v: @Vector(3, f32)) @Vector(3, f32) { + return .{ + u[1] * v[2] - u[2] * v[1], + u[2] * v[0] - u[0] * v[2], + u[0] * v[1] - u[1] * v[0], + }; +} + +fn normalize(vector: anytype, unit: Child(@TypeOf(vector))) @TypeOf(vector) { + const d = norm(vector) / unit; + if (d == 0) return vector; + return vector / @as(@TypeOf(vector), @splat(d)); +} + +// TODO: use smaller int +const Kind = enum(c_int) { bullet_hole, crater, blood_pool }; +const Mapping = struct { + vert: XYZ, + tex: struct { x: f32, y: f32 }, +}; +const Decal = struct { + kind: Kind, + corners: [4]Mapping, + time: f32, +}; + +const Decals = @This(); +allocator: Allocator, +textures: *const Textures, +list: MultiArrayList(Decal), + +pub fn init(allocator: Allocator, textures: *const Textures) Decals { + return .{ + .allocator = allocator, + .textures = textures, + .list = .{}, + }; +} + +pub fn deinit(self: *Decals) void { + self.list.deinit(self.allocator); +} + +export fn addDecal(d: *Decals, kind: Kind, location: XYZ, + size: f32, normal: XYZ, poly: c_int, + m: *const Model, move: XYZ, rot: f32) void { + const n: @Vector(3, f32) = @bitCast(normal); + const abs_n = @fabs(n); + var major: u2 = 0; + if (abs_n[1] > abs_n[major]) + major = 1; + if (abs_n[2] > abs_n[major]) + major = 2; + + const r: @Vector(3, f32) = if (@reduce(.And, abs_n != ones)) + cross(switch (major) { + 0 => .{ 1.0, 0.0, 0.0 }, + 1 => .{ 0.0, 1.0, 0.0 }, + 2 => .{ 0.0, 0.0, 1.0 }, + else => unreachable, + }, n) + else if (major == 0 and normal.x > 0 or major == 1) + .{ 0.0, 0.0, -1.0 } + else if (major == 0) + .{ 0.0, 0.0, 1.0 } + else + .{ normal.z, 0.0, 0.0 }; + const up = normalize(cross(n, r), size / 3.0); + const right = normalize(r, size / 3.0); + const loc: @Vector(3, f32) = @bitCast(location); + const face = m.faces[@intCast(poly)]; + const n_eps = n * splat(3, @as(f32, 0.02)); + const n_eps2 = n * splat(3, @as(f32, 0.04)); + + var decal = Decal{ + .kind = kind, + .corners = undefined, + .time = switch (kind) { + .bullet_hole, .crater => 10.0, + .blood_pool => 20.0, + }, + }; + outer: for ([_]f32{ -1, 1, 1, -1 }, [_]f32{ -1, -1, 1, 1 }, + &decal.corners) |x, y, *corner| { + var p = loc + right * splat(3, x) + up * splat(3, y); + var temp: XYZ = undefined; + if (move.x == 0 and move.y == 0 and move.z == 0 and rot == 0 + or segCrossTrigon(@bitCast(p + n_eps2), @bitCast(p - n_eps2), + &face[0].position, &face[1].position, + &face[2].position, &normal, &temp)) { + corner.vert = @bitCast(p + n_eps); + corner.tex = .{ .x = x * 0.5 + 0.5, .y = y * 0.5 + 0.5 }; + continue :outer; + } + + const delta = @max(0.01, @min(1.0 / size, 0.2)); + var kx: f32 = 1.0; + while (kx > 0) : (kx -= delta) { + var ky: f32 = 1.0; + while (ky > 0) : (ky -= delta) { + p = loc + right * splat(3, x * kx) + + up * splat(3, y * ky); + if (segCrossModelTrans(@bitCast(p + n_eps2), + @bitCast(p - n_eps2), + m, move, rot, &temp) == poly) { + corner.vert = @bitCast(p + n_eps); + corner.tex = .{ + .x = x * kx * 0.5 + 0.5, + .y = y * ky * 0.5 + 0.5, + }; + continue :outer; + } + } + } + return; + } + d.list.append(d.allocator, decal) catch unreachable; +} + +pub fn draw(self: Decals) void { + c.glDepthFunc(c.GL_LEQUAL); + c.glEnable(c.GL_CULL_FACE); + c.glEnable(c.GL_TEXTURE_2D); + c.glEnable(c.GL_LIGHTING); + c.glDepthMask(0); + c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); + c.glEnable(c.GL_POLYGON_OFFSET_FILL); + c.glEnable(c.GL_COLOR_MATERIAL); + const s = self.list.slice(); + for (s.items(.kind), s.items(.corners), s.items(.time)) |k, corners, t| { + switch (k) { + .bullet_hole => { + c.glColor4f(1.0, 1.0, 1.0, t / 10.0); + c.glBindTexture(c.GL_TEXTURE_2D, self.textures.bullet_hole); + }, + .crater => { + c.glColor4f(1.0, 1.0, 1.0, t / 10.0); + c.glBindTexture(c.GL_TEXTURE_2D, self.textures.crater); + }, + .blood_pool => { + const alpha = if (t > 18.0) + @mod(20.0 - t, 0.2) + 0.8 + else + t / 18.0; + c.glColor4f(1.0, 1.0, 1.0, alpha); + const i: usize = @intFromFloat(100.0 - t * 5.0); + c.glBindTexture(c.GL_TEXTURE_2D, + self.textures.blood_pool[@min(i, 10)]); + }, + } + + c.glPushMatrix(); + defer c.glPopMatrix(); + c.glBegin(c.GL_TRIANGLE_FAN); + defer c.glEnd(); + for (corners) |corner| { + c.glTexCoord2f(corner.tex.x, corner.tex.y); + c.glVertex3f(corner.vert.x, corner.vert.y, corner.vert.z); + } + } +} + +pub fn update(self: *Decals) void { + const times = self.list.items(.time); + self.list.sort(struct { + times: []f32, + pub fn lessThan(ctx: @This(), i: usize, j: usize) bool { + return ctx.times[i] > ctx.times[j]; // descending time + } + }{ .times = times }); + self.list.shrinkAndFree(self.allocator, for (times, 0..) |*t, i| { + t.* -= c.multiplier; + if (t.* <= 0.0) + break i; + } else self.list.len); +} diff --git a/src/Game.h b/src/Game.h index 5a0d831..363f4f6 100644 --- a/src/Game.h +++ b/src/Game.h @@ -109,6 +109,7 @@ struct Game { GLuint deadpersonspritetextureptr; GLuint scopetextureptr; GLuint flaretextureptr; + void *decals; bool gameinprogress; bool beatgame; @@ -136,7 +137,7 @@ typedef struct Game Game; #ifdef __cplusplus extern "C" { #endif // __cplusplus - Game* makeGame(struct Config config, struct Scores scores); + Game* makeGame(void *decals, struct Config config, struct Scores scores); void initGl(Game* game); void initGame(Game* game); void resizeWindow(Game* game, int width, int height); diff --git a/src/GameDraw.cpp b/src/GameDraw.cpp index 94e7e3b..5451adb 100644 --- a/src/GameDraw.cpp +++ b/src/GameDraw.cpp @@ -40,7 +40,6 @@ extern float fogcolorb; extern float sinefluct; extern float sinefluctprog; extern int environment; -extern Decals decals; GLvoid ReSizeGLScene(Game* game, float fov, float near) { @@ -88,7 +87,6 @@ void Game::DrawGLScene(void) glRotatef(sinefluctprog*50,0,0,1); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, sprites.smoketextureptr); - glEnable(GL_BLEND); glColor4f(1,1,1,.4+sinefluct/8); glBegin(GL_TRIANGLES); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1, 1, 0.0f); @@ -108,7 +106,6 @@ void Game::DrawGLScene(void) glRotatef(-sinefluctprog*50,0,0,1); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, sprites.smoketextureptr); - glEnable(GL_BLEND); glColor4f(1,1,1,.4-sinefluct/8); glBegin(GL_TRIANGLES); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1, 1, 0.0f); @@ -154,7 +151,6 @@ void Game::DrawGLScene(void) glEnable(GL_TEXTURE_2D); if(mouseoverbutton!=1)glBindTexture(GL_TEXTURE_2D, sprites.smoketextureptr); if(mouseoverbutton==1)glBindTexture(GL_TEXTURE_2D, flaretextureptr); - glEnable(GL_BLEND); glColor4f(1,0,0,.5+sinefluct/6); glBegin(GL_TRIANGLES); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1, 1, 0.0f); @@ -175,7 +171,6 @@ void Game::DrawGLScene(void) glEnable(GL_TEXTURE_2D); if(mouseoverbutton!=1)glBindTexture(GL_TEXTURE_2D, sprites.smoketextureptr); if(mouseoverbutton==1)glBindTexture(GL_TEXTURE_2D, flaretextureptr); - glEnable(GL_BLEND); glColor4f(1,0,0,.5-sinefluct/6); glBegin(GL_TRIANGLES); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1, 1, 0.0f); @@ -224,7 +219,6 @@ void Game::DrawGLScene(void) if(mouseoverbutton!=2)glBindTexture(GL_TEXTURE_2D, sprites.smoketextureptr); if(mouseoverbutton==2)glBindTexture(GL_TEXTURE_2D, flaretextureptr); - glEnable(GL_BLEND); glColor4f(1,0,0,.5+sinefluct/6); glBegin(GL_TRIANGLES); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1, 1, 0.0f); @@ -245,7 +239,6 @@ void Game::DrawGLScene(void) glEnable(GL_TEXTURE_2D); if(mouseoverbutton!=2)glBindTexture(GL_TEXTURE_2D, sprites.smoketextureptr); if(mouseoverbutton==2)glBindTexture(GL_TEXTURE_2D, flaretextureptr); - glEnable(GL_BLEND); glColor4f(1,0,0,.5-sinefluct/6); glBegin(GL_TRIANGLES); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1, 1, 0.0f); @@ -292,7 +285,6 @@ void Game::DrawGLScene(void) glLoadIdentity(); // Reset The Modelview Matrix glScalef(screenwidth,screenheight,1); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); glColor4f(flashr,flashg,flashb,flashamount); glBegin(GL_QUADS); glVertex3f(0, 0, 0.0f); @@ -306,10 +298,10 @@ void Game::DrawGLScene(void) glPopMatrix(); // Restore The Old Projection Matrix glEnable(GL_DEPTH_TEST); // Enables Depth Testing glEnable(GL_CULL_FACE); - glDisable(GL_BLEND); glDepthMask(1); } } else { // in-game + glDepthMask(1); glLoadIdentity(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); @@ -464,7 +456,6 @@ void Game::DrawGLScene(void) glEnable(GL_LIGHTING); //Draw blocks - glEnable(GL_BLEND); XYZ move; int beginx,endx; int beginz,endz; @@ -515,8 +506,6 @@ void Game::DrawGLScene(void) } } - drawDecals(&decals); - // Occluding blocks beginx=(camera.position.x+block_spacing/2)/block_spacing-2; if(beginx<0)beginx=0; @@ -545,7 +534,6 @@ void Game::DrawGLScene(void) glColor4f(1 , 1, 1, 1); glEnable(GL_COLOR_MATERIAL); - glEnable(GL_BLEND); for(int i=0;i<numpeople;i++){ bool draw = true; if (((!person[i].skeleton.free @@ -609,7 +597,6 @@ void Game::DrawGLScene(void) glPushMatrix(); if(person[i].skeleton.free<1)person[i].DoAnimationslite(i); glColor4f(1,1,1,1); - glEnable(GL_BLEND); glDisable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); @@ -651,7 +638,6 @@ void Game::DrawGLScene(void) person[i].existing = 1; } glDisable(GL_COLOR_MATERIAL); - glDisable(GL_BLEND); //Attacker psychicness @@ -707,14 +693,12 @@ void Game::DrawGLScene(void) glTexCoord2f(0,1); glVertex3f(0, 1, 0.0f); glEnd(); - glDisable(GL_TEXTURE_2D); glMatrixMode(GL_PROJECTION); // Select The Projection Matrix glPopMatrix(); // Restore The Old Projection Matrix glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix glPopMatrix(); // Restore The Old Projection Matrix glEnable(GL_DEPTH_TEST); // Enables Depth Testing glEnable(GL_CULL_FACE); - glDisable(GL_BLEND); glDepthMask(1); } @@ -754,7 +738,6 @@ void Game::DrawGLScene(void) glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); glColor4f(flashr,flashg,flashb,flashamount); @@ -782,7 +765,6 @@ void Game::DrawGLScene(void) glEnable(GL_CULL_FACE); - glDisable(GL_BLEND); glDepthMask(1); @@ -816,7 +798,6 @@ void Game::DrawGLScene(void) glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); glColor4f(0,0,0,1-person[0].longdead); @@ -844,8 +825,6 @@ void Game::DrawGLScene(void) glEnable(GL_CULL_FACE); - glDisable(GL_BLEND); - glDepthMask(1); } diff --git a/src/GameInitDispose.cpp b/src/GameInitDispose.cpp index b560b48..c2e7a2f 100644 --- a/src/GameInitDispose.cpp +++ b/src/GameInitDispose.cpp @@ -23,7 +23,6 @@ #include <initializer_list> #include <AL/alc.h> -#include <GL/glu.h> #include "Game.h" #include "misc.h" @@ -33,7 +32,6 @@ extern unsigned int gSampleSet[37]; extern Camera camera; extern Skeleton testskeleton; extern Sprites sprites; -extern Decals decals; extern Model skeletonmodels[10]; extern Model gunmodels[11]; @@ -213,9 +211,10 @@ void loadModels(Game* game) gunmodels[shotgunmodel] = loadModel("guns/shotgun.off"); } -Game* makeGame(Config config, Scores scores) +Game* makeGame(void *decals, Config config, Scores scores) { auto game = new Game(); + game->decals = decals; loadModels(game); // FIXME: free models game->screenwidth = config.width; game->screenheight = config.height; @@ -718,7 +717,6 @@ void initGame(Game* game) glClearColor(fogcolorr,fogcolorg,fogcolorb,1); game->initialized = true; - decals.len = 0; sprites.howmanysprites=0; game->losedelay = 1; } @@ -729,6 +727,9 @@ void initGl(Game* game) glDepthFunc(GL_LESS); glPolygonOffset(-8,0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glEnable(GL_BLEND); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); game->text.texture = loadTexture("font.qoi"); buildFont(&game->text); @@ -743,20 +744,6 @@ void initGl(Game* game) sprites.bloodtextureptr = loadTexture("sprites/blood.qoi"); sprites.raintextureptr = loadTexture("sprites/white.qoi"); sprites.snowtextureptr = loadTexture("sprites/snowflake.qoi"); - - decals.hole_textures[0] = loadTexture("bullet-hole.qoi"); - decals.hole_textures[1] = loadTexture("black.qoi"); - decals.blood_textures[0u] = loadTexture("blood/00.qoi"); - decals.blood_textures[1u] = loadTexture("blood/01.qoi"); - decals.blood_textures[2u] = loadTexture("blood/02.qoi"); - decals.blood_textures[3u] = loadTexture("blood/03.qoi"); - decals.blood_textures[4u] = loadTexture("blood/04.qoi"); - decals.blood_textures[5u] = loadTexture("blood/05.qoi"); - decals.blood_textures[6u] = loadTexture("blood/06.qoi"); - decals.blood_textures[7u] = loadTexture("blood/07.qoi"); - decals.blood_textures[8u] = loadTexture("blood/08.qoi"); - decals.blood_textures[9u] = loadTexture("blood/09.qoi"); - decals.blood_textures[10] = loadTexture("blood/10.qoi"); } struct Scores getScores(Game* game) @@ -800,5 +787,4 @@ void closeGame(Game* game) alDeleteSources(34 + game->musictoggle * 3, gSourceID); alDeleteBuffers(34 + game->musictoggle * 3, gSampleSet); destroyModels(game); - destroyDecals(&decals); } diff --git a/src/GameTick.cpp b/src/GameTick.cpp index 311ba9a..68fe508 100644 --- a/src/GameTick.cpp +++ b/src/GameTick.cpp @@ -23,6 +23,7 @@ // along with Black Shades. If not, see <https://www.gnu.org/licenses/>. #include <algorithm> +#include <cassert> #include "Game.h" #include "misc.h" @@ -41,7 +42,6 @@ extern float snowdelay; extern float precipitationdensity; extern float soundscalefactor; extern int slomo; -extern Decals decals; #define maxfallvel 40 @@ -722,18 +722,9 @@ void bleed(Game* game, size_t i) XYZ move {(float) x * block_spacing, 0.0f, (float) y * block_spacing, }; auto whichtri = segCrossModelTrans(overpoint, underpoint, &game->sidewalkcollide, move, rot, &loc); - - XYZ normish {0.0f, 1.0f, 0.0f}; - if (whichtri >= 0) { - addDecal(&decals, BLOOD_POOL, loc, 12, normish, - whichtri, &game->sidewalkcollide, move, rot); - } else { - loc = person.skeleton.joints[abdomen].position; - loc.y = -0.5f; - move = {0.0f}; - addDecal(&decals, BLOOD_POOL, loc, 12, normish, - 0, &game->sidewalkcollide, move, 0); - } + assert(whichtri >= 0); + addDecal(game->decals, BLOOD_POOL, loc, 12, {0.0f, 1.0f, 0.0f}, + whichtri, &game->sidewalkcollide, move, rot); person.firstlongdead = true; return; } @@ -974,7 +965,6 @@ void Game::Tick() spawnNpc(this); sprites.DoStuff(); - updateDecals(&decals); // Facing XYZ facing {0, 0, -1}; @@ -1739,7 +1729,7 @@ void Game::Tick() hitNorm = {0.0f, 1.0f, 0.0f}; hit_terrain: whichhit = -1; - addDecal(&decals, BULLET_HOLE, wallhit, 0.7f, + addDecal(this->decals, BULLET_HOLE, wallhit, 0.7f, hitNorm, whichtri, model, move, hitRot); const auto& velocity = hitNorm * 3; switch (person[j].whichgun) { @@ -2096,7 +2086,7 @@ hit_terrain: const auto& normalrotated = rotate(blocks[citytype[wherex][wherey]].faces[whichtri][0].normal, 0, cityrotation[wherex][wherey] * 90, 0); if (sprites.size[i] > 1) - addDecal(&decals, CRATER, wallhit, 9.0f, + addDecal(this->decals, CRATER, wallhit, 9.0f, normalrotated, whichtri, &blocks[citytype[wherex][wherey]], move, cityrotation[wherex][wherey] * 90); @@ -2132,7 +2122,7 @@ hit_terrain: move = {}; sprites.location[i].y=-.5; XYZ normish = {0.0f, 1.0f, 0.0f}; - addDecal(&decals, CRATER, sprites.location[i], 9.0f, + addDecal(this->decals, CRATER, sprites.location[i], 9.0f, normish, 0, blocks + citytype[wherex][wherey], move, 0); } @@ -2257,14 +2247,14 @@ hit_terrain: move, cityrotation[wherex][wherey] * 90, &temp); XYZ normish = {0.0f, 1.0f, 0.0f}; if (whichtri > -1) { - addDecal(&decals, CRATER, sprites.location[i], 9.0f, + addDecal(this->decals, CRATER, sprites.location[i], 9.0f, normish, 0, &sidewalkcollide, move, cityrotation[wherex][wherey]*90); } else { temp = sprites.location[i]; temp.y = -0.5f; move = {0.0f}; - addDecal(&decals, CRATER, sprites.location[i], 9.0f, + addDecal(this->decals, CRATER, sprites.location[i], 9.0f, normish, 0, &sidewalkcollide, move, 0); } diff --git a/src/Globals.cpp b/src/Globals.cpp index e94a7fc..53aa3fd 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -27,7 +27,6 @@ Model skeletonmodels[10]; Model gunmodels[11]; Costume costume[10]; Sprites sprites; -Decals decals; int thirdperson; bool visions; Camera camera; diff --git a/src/Sprites.cpp b/src/Sprites.cpp index 12bb8b7..f5da5f7 100644 --- a/src/Sprites.cpp +++ b/src/Sprites.cpp @@ -142,9 +142,6 @@ void Sprites::draw() XYZ point; glAlphaFunc(GL_GREATER, 0.01); - //glEnable(GL_POLYGON_OFFSET_FILL); - - glEnable(GL_BLEND); glDisable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); @@ -245,7 +242,6 @@ void Sprites::draw() if(type[i]==grenadesprite||type[i]==spoonsprite||type[i]==pinsprite){ glTranslatef(location[i].x,location[i].y+.2,location[i].z); - glDisable(GL_BLEND); glEnable(GL_FOG); glEnable(GL_CULL_FACE); glDisable(GL_TEXTURE_2D); @@ -264,7 +260,6 @@ void Sprites::draw() drawModel(gunmodels + grenadepinmodel, visions ? BLACK : NULL); - glEnable(GL_BLEND); glDisable(GL_FOG); glDisable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); @@ -320,7 +315,6 @@ void Sprites::draw() glDepthMask(1); glDisable(GL_TEXTURE_2D); glEnable(GL_CULL_FACE); - //glDisable(GL_POLYGON_OFFSET_FILL); } Sprites::~Sprites() diff --git a/src/Textures.zig b/src/Textures.zig new file mode 100644 index 0000000..068db80 --- /dev/null +++ b/src/Textures.zig @@ -0,0 +1,85 @@ +// Texture store +// Copyright (C) 2002 David Rosen +// Copyright (C) 2021-2023 Nguyễn Gia Phong +// +// This file is part of Black Shades. +// +// Black Shades is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Black Shades 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Black Shades. If not, see <https://www.gnu.org/licenses/>. + +const allocator = std.heap.c_allocator; +const c = @import("cimport.zig"); +const cwd = std.fs.cwd; +const data_dir = misc.data_dir; +const formatIntBuf = std.fmt.formatIntBuf; +const misc = @import("misc.zig"); +const qoi = @import("qoi"); +const readFile = misc.readFile; +const sep = std.fs.path.sep; +const span = std.mem.span; +const std = @import("std"); + +/// Load QOI file into an OpenGL buffer bound to the given texture. +fn load(texture: u32, path: [*:0]const u8) !void { + const file = try readFile(cwd(), data_dir ++ "textures{c}{s}", .{ + sep, path, + }); + defer allocator.free(file); + + var image = try qoi.decodeBuffer(allocator, file); + defer image.deinit(allocator); + const data: [*c]const u8 = @ptrCast(image.pixels.ptr); + + c.glBindTexture(c.GL_TEXTURE_2D, texture); + defer c.glBindTexture(c.GL_TEXTURE_2D, 0); + c.glTexEnvi(c.GL_TEXTURE_ENV, c.GL_TEXTURE_ENV_MODE, c.GL_MODULATE); + c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_LINEAR); + c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_LINEAR); + c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_GENERATE_MIPMAP, c.GL_TRUE); + + const width: i32 = @intCast(image.width); + const height: i32 = @intCast(image.height); + c.glPixelStorei(c.GL_UNPACK_ALIGNMENT, 1); + c.glTexImage2D(c.GL_TEXTURE_2D, 0, 4, width, height, + 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, data); +} + +export fn loadTexture(path: [*:0]const u8) u32 { + var texture: u32 = undefined; + c.glGenTextures(1, &texture); + load(texture, path) catch unreachable; + return texture; +} + +const Textures = @This(); +const size = @sizeOf(Textures) / @sizeOf(u32); +bullet_hole: u32, +crater: u32, +blood_pool: [11]u32, + +pub fn init() !Textures { + var self: Textures = undefined; + c.glGenTextures(size, @ptrCast(&self)); + try load(self.bullet_hole, "bullet-hole.qoi"); + try load(self.crater, "black.qoi"); + for (0..11) |i| { + var buf: [2]u8 = undefined; + _ = formatIntBuf(&buf, i, 10, .lower, .{ .width = 2, .fill = '0' }); + try load(self.blood_pool[i], "blood/" ++ buf ++ ".qoi"); + } + return self; +} + +pub fn deinit(self: Textures) void { + c.glDeleteTextures(size, @ptrCast(&self)); +} diff --git a/src/cimport.zig b/src/cimport.zig index ea29276..c55d916 100644 --- a/src/cimport.zig +++ b/src/cimport.zig @@ -1,7 +1,6 @@ pub usingnamespace @cImport({ @cInclude("AL/al.h"); @cInclude("GL/gl.h"); - @cInclude("GL/glu.h"); @cInclude("Game.h"); @cInclude("Constants.h"); diff --git a/src/decal.zig b/src/decal.zig deleted file mode 100644 index 7633880..0000000 --- a/src/decal.zig +++ /dev/null @@ -1,247 +0,0 @@ -// Decal construction and drawing -// Copyright (C) 2002 David Rosen -// Copyright (C) 2003 Steven Fuller -// Copyright (C) 2023 Nguyễn Gia Phong -// -// This file is part of Black Shades. -// -// Black Shades is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Black Shades 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Black Shades. If not, see <https://www.gnu.org/licenses/>. - -const Child = std.meta.Child; -const Model = model.Model; -const XYZ = geom.XYZ; -const assert = std.debug.assert; -const c = @import("cimport.zig"); -const geom = @import("geom.zig"); -const model = @import("model.zig"); -const norm = geom.norm; -const segCrossModelTrans = model.segCrossModelTrans; -const segCrossTrigon = geom.segCrossTrigon; -const splat = geom.splat; -const std = @import("std"); - -const ones: @Vector(3, f32) = @splat(1.0); -const max_len = 120; - -const Kind = enum(c_int) { bullet_hole, crater, blood_pool }; - -const Decals = extern struct { - // GLuint is always 32-bit. - hole_textures: [2]u32, - blood_textures: [11]u32, - len: u32, - kind: [max_len]Kind, - points: [max_len * 8]XYZ, - numpoints: [max_len]u32, - texcoordsx: [max_len * 8]f32, - texcoordsy: [max_len * 8]f32, - alive: [max_len]f32, -}; - -fn cross(u: @Vector(3, f32), v: @Vector(3, f32)) @Vector(3, f32) { - return .{ - u[1] * v[2] - u[2] * v[1], - u[2] * v[0] - u[0] * v[2], - u[0] * v[1] - u[1] * v[0], - }; -} - -fn normalize(vector: anytype, unit: Child(@TypeOf(vector))) @TypeOf(vector) { - const d = norm(vector) / unit; - if (d == 0) return vector; - return vector / @as(@TypeOf(vector), @splat(d)); -} - -fn pointTouchModel(p: @Vector(3, f32), n: @Vector(3, f32), - m: *const Model, move: XYZ, rot: f32) bool { - var temp: XYZ = undefined; - return segCrossModelTrans(@bitCast(p + n), @bitCast(p - n), - m, move, rot, &temp) > -1; -} - -export fn addDecal(d: *Decals, kind: Kind, location: XYZ, - size: f32, normal: XYZ, poly: c_int, - m: *const Model, move: XYZ, rot: f32) void { - if (d.len >= max_len) return; - const n: @Vector(3, f32) = @bitCast(normal); - const abs_n = @fabs(n); - var major: u2 = 0; - if (abs_n[1] > abs_n[major]) - major = 1; - if (abs_n[2] > abs_n[major]) - major = 2; - - const r: @Vector(3, f32) = if (@reduce(.And, abs_n != ones)) - cross(switch (major) { - 0 => .{ 1.0, 0.0, 0.0 }, - 1 => .{ 0.0, 1.0, 0.0 }, - 2 => .{ 0.0, 0.0, 1.0 }, - else => unreachable, - }, n) - else if (major == 0 and normal.x > 0 or major == 1) - .{ 0.0, 0.0, -1.0 } - else if (major == 0) - .{ 0.0, 0.0, 1.0 } - else - .{ normal.z, 0.0, 0.0 }; - const up = normalize(cross(n, r), size / 3.0); - const right = normalize(r, size / 3.0); - const loc: @Vector(3, f32) = @bitCast(location); - const face = m.faces[@intCast(poly)]; - const n_eps = n * splat(3, @as(f32, 0.02)); - const n_eps2 = n * splat(3, @as(f32, 0.04)); - - d.kind[d.len] = kind; - d.numpoints[d.len] = 0; - d.alive[d.len] = 0; - for ([_]f32{ -1, 1, 1, -1 }, [_]f32{ -1, -1, 1, 1 }) |x, y| { - var p = loc + right * splat(3, x) + up * splat(3, y); - var i = d.len * 8 + d.numpoints[d.len]; - var temp: XYZ = undefined; - if (move.x == 0 and move.y == 0 and move.z == 0 and rot == 0 - or segCrossTrigon(@bitCast(p + n_eps2), @bitCast(p - n_eps2), - &face[0].position, &face[1].position, - &face[2].position, &normal, &temp)) { - d.texcoordsx[i] = x * 0.5 + 0.5; - d.texcoordsy[i] = y * 0.5 + 0.5; - d.points[i] = @bitCast(p + n_eps); - d.numpoints[d.len] += 1; - continue; - } - - const count_inc = @max(0.01, @min(1.0 / size, 0.2)); - var good: bool = false; - var count = 1.0 - count_inc; - while (!good and count > -1.0) : (count -= count_inc) { - d.texcoordsx[i] = x * 0.5 + 0.5; - d.texcoordsy[i] = y * count * 0.5 + 0.5; - p = loc + right * splat(3, x) + up * splat(3, y * count); - good = pointTouchModel(p, n_eps2, m, move, rot); - } - if (good) { - d.points[i] = @bitCast(p + n_eps); - d.numpoints[d.len] += 1; - i += 1; - } - - good = false; - count = 1.0 - count_inc; - while (!good and count > -1.0) : (count -= count_inc) { - d.texcoordsx[i] = x * count * 0.5 + 0.5; - d.texcoordsy[i] = y * 0.5 + 0.5; - p = loc + right * splat(3, x * count) + up * splat(3, y); - good = pointTouchModel(p, n_eps2, m, move, rot); - } - if (good) { - d.points[i] = @bitCast(p + n_eps); - d.numpoints[d.len] += 1; - continue; - } - - var count2 = 1.0 - count_inc; - while (!good and count2 > -1.0) : (count2 -= count_inc) { - count = 1.0 - count_inc; - while (!good and count > -1.0) : (count -= count_inc) { - d.texcoordsx[i] = x * count2 * 0.5 + 0.5; - d.texcoordsy[i] = y * count * 0.5 + 0.5; - p = loc + right * splat(3, x * count2) - + up * splat(3, y * count); - good = pointTouchModel(p, n_eps2, m, move, rot); - } - } - if (good) { - d.points[i] = @bitCast(p + n_eps); - d.numpoints[d.len] += 1; - } - } - d.len += 1; -} - -export fn drawDecals(d: *const Decals) void { - c.glAlphaFunc(c.GL_GREATER, 0.01); - c.glDepthFunc(c.GL_LEQUAL); - c.glEnable(c.GL_BLEND); - c.glEnable(c.GL_CULL_FACE); - c.glEnable(c.GL_TEXTURE_2D); - c.glEnable(c.GL_LIGHTING); - c.glDepthMask(0); - c.glAlphaFunc(c.GL_GREATER, 0.01); - c.glTexParameterf(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, c.GL_CLAMP); - c.glTexParameterf(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, c.GL_CLAMP); - c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); - c.glEnable(c.GL_POLYGON_OFFSET_FILL); - for (0..d.len) |i| { - switch (d.kind[i]) { - .bullet_hole, .crater => |k| { - c.glColor4f(1.0, 1.0, 1.0, 1.0 - d.alive[i] / 10.0); - c.glBindTexture(c.GL_TEXTURE_2D, d.hole_textures[switch (k) { - .bullet_hole => 0, - .crater => 1, - // https://github.com/ziglang/zig/issues/12863 - else => unreachable, // TODO: remove - }]); - }, - .blood_pool => { - const alpha = if (d.alive[i] < 2.0) - @mod(d.alive[i], 0.2) + 0.8 - else - (20.0 - d.alive[i]) / 18.0; - c.glColor4f(1.0, 1.0, 1.0, alpha); - const j: usize = @intFromFloat(d.alive[i] * 5.0); - c.glBindTexture(c.GL_TEXTURE_2D, d.blood_textures[@min(j, 10)]); - }, - } - - c.glPushMatrix(); - c.glBegin(c.GL_TRIANGLE_FAN); - for (0..d.numpoints[i]) |j| { - c.glTexCoord2f(d.texcoordsx[i * 8 + j], d.texcoordsy[i * 8 + j]); - c.glVertex3f(d.points[i * 8 + j].x, - d.points[i * 8 + j].y, d.points[i * 8 + j].z); - } - c.glEnd(); - c.glPopMatrix(); - } - c.glDepthMask(1); - c.glDisable(c.GL_TEXTURE_2D); - c.glEnable(c.GL_CULL_FACE); - c.glDisable(c.GL_POLYGON_OFFSET_FILL); - c.glDepthFunc(c.GL_LEQUAL); -} - -export fn updateDecals(d: *Decals) void { - for (0..d.len) |i| { - d.alive[i] += c.multiplier; - if (d.alive[i] < @as(f32, switch (d.kind[i]) { - .bullet_hole, .crater => 10.0, - .blood_pool => 20.0, - })) continue; - - d.len -= 1; - const last = d.len; - d.numpoints[i] = d.numpoints[last]; - d.alive[i] = d.alive[last]; - d.kind[i] = d.kind[last]; - for (0..d.numpoints[i]) |j| { - d.points[i * 8 + j] = d.points[last * 8 + j]; - d.texcoordsx[i * 8 + j] = d.texcoordsx[last * 8 + j]; - d.texcoordsy[i * 8 + j] = d.texcoordsy[last * 8 + j]; - } - } -} - -export fn destroyDecals(d: *const Decals) void { - c.glDeleteTextures(2, &d.hole_textures); - c.glDeleteTextures(11, &d.blood_textures); -} diff --git a/src/main.zig b/src/main.zig index 7a7d21e..9accfc8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -26,7 +26,9 @@ const al = @import("zeal"); const gf = @import("gfz"); const gl = @import("zgl"); +const Decals = @import("Decals.zig"); const Scores = misc.Scores; +const Textures = @import("Textures.zig"); const c = @import("cimport.zig"); const configuration = @import("config.zig"); const misc = @import("misc.zig"); @@ -37,7 +39,6 @@ var game: *c.Game = undefined; var prng: DefaultPrng = undefined; comptime { - _ = @import("decal.zig"); _ = @import("geom.zig"); _ = @import("model.zig"); } // export functions in C ABI @@ -95,7 +96,11 @@ pub fn main() !void { try window.makeCurrent(); prng = DefaultPrng.init(@bitCast(try gf.getTime())); - game = c.makeGame(@bitCast(config), + const textures = try Textures.init(); + defer textures.deinit(); + var decals = Decals.init(allocator, &textures); + defer decals.deinit(); + game = c.makeGame(&decals, @bitCast(config), @bitCast(try loadScores(loca.user_data))).?; defer saveScores(loca.user_data, @bitCast(c.getScores(game))) catch unreachable; @@ -122,6 +127,8 @@ pub fn main() !void { defer c.closeGame(game); while (!try window.shouldClose()) { c.eventLoop(game); + decals.update(); + decals.draw(); try window.swapBuffers(); try gf.pollEvents(); } diff --git a/src/misc.zig b/src/misc.zig index ab11ca3..46237cb 100644 --- a/src/misc.zig +++ b/src/misc.zig @@ -36,7 +36,6 @@ const tokenizeScalar = std.mem.tokenizeScalar; const al = @import("zeal"); const gf = @import("gfz"); const ini = @import("ini"); -const qoi = @import("qoi"); const c = @import("cimport.zig"); pub const data_dir = @import("build_options").data_dir ++ [_]u8{ sep }; @@ -221,42 +220,6 @@ export fn loadSound(filename: [*:0]const u8) u32 { return buffer.reference; } -fn check(comptime errorString: fn (c_uint) callconv(.C) [*c]const u8, - status: anytype) void { - if (status != 0) - @panic(span(errorString(@intCast(status)))); -} - -/// Load PNG file into an OpenGL buffer and return it. -export fn loadTexture(filename: [*:0]const u8) u32 { - const file = readFile(cwd(), data_dir ++ "textures{c}{s}", .{ - sep, filename, - }) catch unreachable; - defer allocator.free(file); - - var image = qoi.decodeBuffer(allocator, file) catch unreachable; - defer image.deinit(allocator); - const data: [*c]const u8 = @ptrCast(image.pixels.ptr); - - var texture: u32 = undefined; - c.glGenTextures(1, &texture); - c.glBindTexture(c.GL_TEXTURE_2D, texture); - defer c.glBindTexture(c.GL_TEXTURE_2D, 0); - c.glTexEnvi(c.GL_TEXTURE_ENV, c.GL_TEXTURE_ENV_MODE, c.GL_MODULATE); - c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_LINEAR); - c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_LINEAR); - - const width: i32 = @intCast(image.width); - const height: i32 = @intCast(image.height); - c.glPixelStorei(c.GL_UNPACK_ALIGNMENT, 1); - c.glTexImage2D(c.GL_TEXTURE_2D, 0, 4, width, height, - 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, data); - check(c.gluErrorString, - c.gluBuild2DMipmaps(c.GL_TEXTURE_2D, 4, width, height, - c.GL_RGBA, c.GL_UNSIGNED_BYTE, data)); - return texture; -} - /// Move sound source to given position and play it. export fn playSound(source: u32, x: f32, y: f32, z: f32) void { const src = al.Source{ .reference = source }; @@ -321,7 +284,6 @@ export fn glPrint(text: *const Text, x: f64, y: f64, str: [*:0]const u8, c.glBindTexture(c.GL_TEXTURE_2D, text.texture); c.glDisable(c.GL_DEPTH_TEST); c.glDisable(c.GL_LIGHTING); - c.glEnable(c.GL_BLEND); c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); c.glMatrixMode(c.GL_PROJECTION); c.glPushMatrix(); |