bullet trail particles
This commit is contained in:
parent
2f1a146c20
commit
53bcacb044
@ -2182,78 +2182,59 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
|
||||
// TODO: Not like this
|
||||
|
||||
// for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
|
||||
// {
|
||||
// if (bullet->is_bullet)
|
||||
// {
|
||||
// Vec2 start = bullet->bullet_start;
|
||||
// Vec2 end = bullet->bullet_end;
|
||||
for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
|
||||
{
|
||||
if (bullet->is_bullet)
|
||||
{
|
||||
Vec2 start = bullet->bullet_start;
|
||||
Vec2 end = bullet->bullet_end;
|
||||
|
||||
// b32 skip = 0;
|
||||
// if (bullet->has_hit)
|
||||
// {
|
||||
// Vec2 hit_pos = bullet->hit_entry;
|
||||
// if (DotVec2(SubVec2(hit_pos, start), SubVec2(end, start)) < 0)
|
||||
// {
|
||||
// skip = 1;
|
||||
// }
|
||||
// // V_DrawPoint(MulAffineVec2(frame->af.world_to_screen, start), Color_Red);
|
||||
// // V_DrawPoint(MulAffineVec2(frame->af.world_to_screen, end), Color_Purple);
|
||||
// end = hit_pos;
|
||||
// }
|
||||
b32 skip = 0;
|
||||
if (bullet->has_hit)
|
||||
{
|
||||
Vec2 hit_pos = bullet->hit_entry;
|
||||
if (DotVec2(SubVec2(hit_pos, start), SubVec2(end, start)) < 0)
|
||||
{
|
||||
skip = 1;
|
||||
}
|
||||
// V_DrawPoint(MulAffineVec2(frame->af.world_to_screen, start), Color_Red);
|
||||
// V_DrawPoint(MulAffineVec2(frame->af.world_to_screen, end), Color_Purple);
|
||||
end = hit_pos;
|
||||
}
|
||||
|
||||
// if (!skip)
|
||||
// {
|
||||
// f32 trail_len = Vec2Len(SubVec2(end, start));
|
||||
// f32 particles_count = trail_len * frame->dt * Kibi(8); // Particles per meter per second
|
||||
// particles_count = MaxF32(particles_count, 1);
|
||||
if (!skip)
|
||||
{
|
||||
f32 trail_len = Vec2Len(SubVec2(end, start));
|
||||
f32 particles_count = trail_len * frame->dt * Kibi(8); // Particles per meter per second
|
||||
particles_count = MaxF32(particles_count, 1);
|
||||
|
||||
// f32 angle = AngleFromVec2(PerpVec2(SubVec2(end, start)));
|
||||
// // f32 angle = AngleFromVec2(NegVec2(SubVec2(end, start)));
|
||||
f32 angle = AngleFromVec2(PerpVec2(SubVec2(end, start)));
|
||||
// f32 angle = AngleFromVec2(NegVec2(SubVec2(end, start)));
|
||||
|
||||
// V_Emitter emitter = Zi;
|
||||
// {
|
||||
// // emitter.flags |= V_ParticleFlag_StainOnPrune;
|
||||
// // emitter.flags |= V_ParticleFlag_StainTrail;
|
||||
V_Emitter emitter = Zi;
|
||||
{
|
||||
emitter.kind = V_ParticleKind_BulletTrail;
|
||||
emitter.count = particles_count;
|
||||
|
||||
// // emitter.lifetime = 1;
|
||||
// // emitter.lifetime_spread = 2;
|
||||
f32 angle_spread = Tau / 4.0;
|
||||
|
||||
// emitter.count = particles_count;
|
||||
emitter.angle.min = angle - angle_spread / 2;
|
||||
emitter.angle.max = angle + angle_spread / 2;
|
||||
|
||||
// // emitter.lifetime = 1;
|
||||
// // emitter.lifetime = 0.15;
|
||||
// emitter.lifetime = 0.075;
|
||||
// // emitter.lifetime = 0.05;
|
||||
// // emitter.lifetime = 0.04;
|
||||
// emitter.lifetime_spread = emitter.lifetime * 2;
|
||||
emitter.pos.p0 = start;
|
||||
emitter.pos.p1 = end;
|
||||
|
||||
// emitter.angle = angle;
|
||||
// // emitter.angle_spread = Tau / 4;
|
||||
// emitter.angle_spread = Tau / 4;
|
||||
// emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1));
|
||||
|
||||
// emitter.start = start;
|
||||
// emitter.end = end;
|
||||
// emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1));
|
||||
|
||||
// // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1));
|
||||
|
||||
// // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.8, 0.8, 0.25));
|
||||
// emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1));
|
||||
// // emitter.color_spread = LinearFromSrgb(VEC4(0, 0, 0, 0.2));
|
||||
|
||||
// emitter.speed = 0;
|
||||
// emitter.speed_spread = 1;
|
||||
|
||||
// // emitter.speed = 1;
|
||||
// // emitter.speed_spread = 1;
|
||||
|
||||
// // emitter.velocity_falloff = 1;
|
||||
// // emitter.velocity_falloff_spread = 0;
|
||||
// }
|
||||
// V_PushParticles(emitter);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
emitter.speed.min = 0;
|
||||
emitter.speed.max = 1;
|
||||
}
|
||||
V_PushParticles(emitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -2385,7 +2366,7 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
emitter.speed.max = speed + speed_spread * 0.5;
|
||||
emitter.angle.min = angle - angle_spread * 0.5;
|
||||
emitter.angle.max = angle + angle_spread * 0.5;
|
||||
emitter.count = TweakFloat("Emitter count", 1, 0, 100) * (Kibi(32) * frame->dt);
|
||||
emitter.count = TweakFloat("Emitter count", 1, 0, 100) * (Kibi(1) * frame->dt);
|
||||
V_PushParticles(emitter);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ Vec4 V_ColorFromParticle(V_ParticleDesc desc, u32 particle_idx, u32 density)
|
||||
|
||||
// Apply density
|
||||
{
|
||||
if (desc.kind == V_ParticleKind_Smoke)
|
||||
if (desc.layer == V_ParticleLayer_Air)
|
||||
{
|
||||
// f32 t = saturate(density / 10.0);
|
||||
f32 t = smoothstep(-10, 32, density);
|
||||
@ -297,17 +297,6 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
particle.life = 0;
|
||||
particle.pos = lerp(emitter.pos.p0, emitter.pos.p1, rand_offset);
|
||||
particle.velocity = Vec2(cos(initial_angle), sin(initial_angle)) * initial_speed;
|
||||
|
||||
Vec2 cell_pos = mul(frame.af.world_to_cell, Vec3(particle.pos, 1));
|
||||
if (IsInside(cell_pos, P_WorldCellsDims))
|
||||
{
|
||||
particle.origin_occluder = occluders[cell_pos];
|
||||
particle.prev_occluder = particle.origin_occluder;
|
||||
}
|
||||
else
|
||||
{
|
||||
prune = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (particle.kind > V_ParticleKind_None && particle.kind < V_ParticleKind_COUNT && !prune)
|
||||
@ -322,17 +311,23 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
StaticAssert(V_ParticlesCap <= (1 << 24)); // particle idx must fit in 24 bits
|
||||
StaticAssert(V_ParticleKind_COUNT <= 0x7F); // particle kind must fit in 7 bits
|
||||
|
||||
u32 start_occluder = 0;
|
||||
if (particle.life == 0)
|
||||
{
|
||||
Vec2 cell_pos = mul(frame.af.world_to_cell, Vec3(particle.pos, 1));
|
||||
if (IsInside(cell_pos, P_WorldCellsDims))
|
||||
{
|
||||
start_occluder = occluders[cell_pos];
|
||||
if (particle.life == 0)
|
||||
u32 occluder = occluders[cell_pos];
|
||||
b32 occluder_is_wall = occluder == 0xFFFFFFFF;
|
||||
if (!(AnyBit(desc.flags, V_ParticleFlag_OnlyCollideWithWalls) && !occluder_is_wall))
|
||||
{
|
||||
particle.origin_occluder = start_occluder;
|
||||
particle.origin_occluder = occluders[cell_pos];
|
||||
particle.prev_occluder = particle.origin_occluder;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prune = 1;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
@ -341,6 +336,7 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
b32 collision = 0;
|
||||
|
||||
// TODO: Clip to avoid unnecessary iterations outside of world bounds
|
||||
if (!prune)
|
||||
{
|
||||
Vec2 p0 = particle.pos;
|
||||
Vec2 p1 = particle.pos + particle.velocity * frame.dt;
|
||||
@ -415,11 +411,16 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
//- Handle collision
|
||||
{
|
||||
u32 occluder = occluders[cell_pos];
|
||||
b32 occluder_is_wall = occluder == 0xFFFFFFFF;
|
||||
if (occluder != particle.origin_occluder)
|
||||
{
|
||||
particle.origin_occluder = 0;
|
||||
}
|
||||
if (occluder != 0 && occluder != particle.origin_occluder)
|
||||
if (
|
||||
occluder != 0 &&
|
||||
!(AnyBit(desc.flags, V_ParticleFlag_OnlyCollideWithWalls) && !occluder_is_wall) &&
|
||||
occluder != particle.origin_occluder
|
||||
)
|
||||
{
|
||||
u64 collision_seed = MixU64(V_ParticleCellBasis ^ seed0 ^ particle.cells_count);
|
||||
f32 rand_collision_angle = Norm16(collision_seed >> 0);
|
||||
@ -481,7 +482,7 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
}
|
||||
}
|
||||
|
||||
if (!collision)
|
||||
if (!collision && particle.origin_occluder != 0xFFFFFFFF)
|
||||
{
|
||||
u32 stain_count = floor(particle.stain_accum);
|
||||
u32 density = 1 + stain_count;
|
||||
@ -702,7 +703,8 @@ ComputeShader2D(V_CompositeCS, 8, 8)
|
||||
stain_color = orig_stain;
|
||||
}
|
||||
|
||||
Vec4 particle_color = 0;
|
||||
Vec4 ground_particle_color = 0;
|
||||
Vec4 air_particle_color = 0;
|
||||
|
||||
|
||||
for (V_ParticleLayer layer = (V_ParticleLayer)0; layer < V_ParticleLayer_COUNT; layer += (V_ParticleLayer)1)
|
||||
@ -718,70 +720,26 @@ ComputeShader2D(V_CompositeCS, 8, 8)
|
||||
u32 particle_idx = packed & ((1 << 24) - 1);
|
||||
Vec4 cell_color = V_ColorFromParticle(desc, particle_idx, density);
|
||||
cell_color.rgb *= cell_color.a;
|
||||
particle_color = BlendPremul(cell_color, particle_color);
|
||||
|
||||
if (layer == V_ParticleLayer_Ground)
|
||||
{
|
||||
ground_particle_color = BlendPremul(cell_color, ground_particle_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
air_particle_color = BlendPremul(cell_color, air_particle_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Darken wall particles / stains
|
||||
if (tile == P_TileKind_Wall)
|
||||
{
|
||||
particle_color *= 0.25;
|
||||
ground_particle_color *= 0.25;
|
||||
air_particle_color *= 0.25;
|
||||
stain_color *= 0.25;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Vec4 stain_particle_color = 0;
|
||||
// Vec4 ground_particle_color = 0;
|
||||
// Vec4 air_particle_color = 0;
|
||||
// {
|
||||
// //- Stain
|
||||
// {
|
||||
// {
|
||||
// u32 packed = stain_cells[cell_pos];
|
||||
// V_ParticleKind particle_kind = (V_ParticleKind)((packed >> 24) & 0x7F);
|
||||
// if (particle_kind != V_ParticleKind_None)
|
||||
// {
|
||||
// u32 particle_idx = packed & ((1 << 24) - 1);
|
||||
// u32 density = stain_densities[cell_pos];
|
||||
// f32 dryness = drynesses[cell_pos];
|
||||
// stain_particle_color = V_ColorFromParticle(particle_kind, particle_idx, density, dryness);
|
||||
// }
|
||||
// }
|
||||
// stain_particle_color.rgb *= 1.0 - (0.30 * tile_is_wall); // Darken wall stains
|
||||
// stain_particle_color.rgb *= stain_particle_color.a;
|
||||
// }
|
||||
// //- Ground
|
||||
// {
|
||||
// {
|
||||
// u32 packed = ground_cells[cell_pos];
|
||||
// V_ParticleKind particle_kind = (V_ParticleKind)((packed >> 24) & 0x7F);
|
||||
// if (particle_kind != V_ParticleKind_None)
|
||||
// {
|
||||
// u32 particle_idx = packed & ((1 << 24) - 1);
|
||||
// u32 density = ground_densities[cell_pos];
|
||||
// ground_particle_color = V_ColorFromParticle(particle_kind, particle_idx, density, 0);
|
||||
// }
|
||||
// }
|
||||
// ground_particle_color.rgb *= ground_particle_color.a;
|
||||
// }
|
||||
// //- Air
|
||||
// {
|
||||
// {
|
||||
// u32 packed = air_cells[cell_pos];
|
||||
// V_ParticleKind particle_kind = (V_ParticleKind)((packed >> 24) & 0x7F);
|
||||
// if (particle_kind != V_ParticleKind_None)
|
||||
// {
|
||||
// u32 particle_idx = packed & ((1 << 24) - 1);
|
||||
// u32 density = air_densities[cell_pos];
|
||||
// air_particle_color = V_ColorFromParticle(particle_kind, particle_idx, density, 0);
|
||||
// }
|
||||
// }
|
||||
// air_particle_color.rgb *= air_particle_color.a;
|
||||
// }
|
||||
// }
|
||||
|
||||
//////////////////////////////
|
||||
//- Compose world
|
||||
|
||||
@ -792,15 +750,16 @@ ComputeShader2D(V_CompositeCS, 8, 8)
|
||||
{
|
||||
world_color = BlendPremul(tile_color, world_color); // Blend ground tile
|
||||
world_color = BlendPremul(stain_color, world_color); // Blend ground stain
|
||||
world_color = BlendPremul(particle_color, world_color); // Blend ground particle
|
||||
world_color = BlendPremul(ground_particle_color, world_color); // Blend ground particle
|
||||
}
|
||||
world_color = BlendPremul(albedo_tex_color, world_color);
|
||||
if (tile_is_wall)
|
||||
{
|
||||
world_color = BlendPremul(tile_color, world_color); // Blend wall tile
|
||||
world_color = BlendPremul(stain_color, world_color); // Blend wall stain
|
||||
world_color = BlendPremul(particle_color, world_color); // Blend wall particle
|
||||
world_color = BlendPremul(ground_particle_color, world_color); // Blend wall particle
|
||||
}
|
||||
world_color = BlendPremul(air_particle_color, world_color); // Blend air particle
|
||||
|
||||
|
||||
|
||||
|
||||
@ -11,42 +11,47 @@ V_ParticleDesc V_DescFromParticleKind(V_ParticleKind kind)
|
||||
V_ParticleDesc result;
|
||||
{
|
||||
PERSIST Readonly V_ParticleFlag flags[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) flags,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) flags,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly V_ParticleLayer layers[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) layer,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) layer,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 stain_rates[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) stain_rate,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) stain_rate,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 pen_rates[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) pen_rate,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) pen_rate,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 lifetimes[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) lifetime,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 r[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) r,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) r,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 g[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) g,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) g,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 b[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) b,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) b,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 a[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) a,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, lifetime, r, g, b, a) a,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
@ -55,6 +60,7 @@ V_ParticleDesc V_DescFromParticleKind(V_ParticleKind kind)
|
||||
result.layer = layers[kind];
|
||||
result.stain_rate = stain_rates[kind];
|
||||
result.pen_rate = pen_rates[kind];
|
||||
result.lifetime = lifetimes[kind];
|
||||
result.color = LinearFromSrgb(VEC4(r[kind], g[kind], b[kind], a[kind]));
|
||||
}
|
||||
return result;
|
||||
|
||||
@ -32,6 +32,7 @@ Enum(V_ParticleFlag)
|
||||
V_ParticleFlag_NoPruneWhenStill = (1 << 0),
|
||||
V_ParticleFlag_StainWhenPruned = (1 << 1),
|
||||
V_ParticleFlag_NoReflect = (1 << 2),
|
||||
V_ParticleFlag_OnlyCollideWithWalls = (1 << 3),
|
||||
};
|
||||
|
||||
Enum(V_ParticleLayer)
|
||||
@ -50,6 +51,7 @@ Enum(V_ParticleLayer)
|
||||
/* Flags */ V_ParticleFlag_None, \
|
||||
/* Layer */ V_ParticleLayer_Ground, \
|
||||
/* Stain rate, pen chance */ 30, 0, \
|
||||
/* Lifetime */ Inf, \
|
||||
/* Base color */ 0, 0, 0, 0 \
|
||||
) \
|
||||
\
|
||||
@ -59,6 +61,7 @@ Enum(V_ParticleLayer)
|
||||
/* Flags */ V_ParticleFlag_NoReflect | V_ParticleFlag_StainWhenPruned, \
|
||||
/* Layer */ V_ParticleLayer_Ground, \
|
||||
/* Stain rate, pen chance */ 100, 0.25, \
|
||||
/* Lifetime */ Inf, \
|
||||
/* Base color */ 0.5, 0.1, 0.1, 0.05 \
|
||||
) \
|
||||
X( \
|
||||
@ -66,6 +69,7 @@ Enum(V_ParticleLayer)
|
||||
/* Flags */ V_ParticleFlag_StainWhenPruned, \
|
||||
/* Layer */ V_ParticleLayer_Mid, \
|
||||
/* Stain rate, pen chance */ 30, 0, \
|
||||
/* Lifetime */ Inf, \
|
||||
/* Base color */ 0.5, 0.1, 0.1, 0.8 \
|
||||
) \
|
||||
X( \
|
||||
@ -73,23 +77,26 @@ Enum(V_ParticleLayer)
|
||||
/* Flags */ V_ParticleFlag_StainWhenPruned, \
|
||||
/* Layer */ V_ParticleLayer_Mid, \
|
||||
/* Stain rate, pen chance */ 0, 0, \
|
||||
/* Lifetime */ Inf, \
|
||||
/* Base color */ 2, 0.5, 0, 1 \
|
||||
) \
|
||||
\
|
||||
/* Air particles */ \
|
||||
X( \
|
||||
/* Name */ Smoke, \
|
||||
/* Flags */ V_ParticleFlag_None, \
|
||||
/* Flags */ V_ParticleFlag_OnlyCollideWithWalls, \
|
||||
/* Layer */ V_ParticleLayer_Air, \
|
||||
/* Stain rate, pen chance */ 0, 0, \
|
||||
/* Lifetime */ Inf, \
|
||||
/* Base color */ 0.15, 0.15, 0.15, 0.5 \
|
||||
) \
|
||||
X( \
|
||||
/* Name */ BulletTrail, \
|
||||
/* Flags */ V_ParticleFlag_None, \
|
||||
/* Flags */ V_ParticleFlag_OnlyCollideWithWalls, \
|
||||
/* Layer */ V_ParticleLayer_Air, \
|
||||
/* Stain rate, pen chance */ 0, 0, \
|
||||
/* Base color */ 1, 0, 1, 1 \
|
||||
/* Lifetime */ 0.075, \
|
||||
/* Base color */ 0.8, 0.6, 0.2, 0.25 \
|
||||
) \
|
||||
\
|
||||
/* Test particles */ \
|
||||
@ -98,6 +105,7 @@ Enum(V_ParticleLayer)
|
||||
/* Flags */ V_ParticleFlag_None, \
|
||||
/* Layer */ V_ParticleLayer_Mid, \
|
||||
/* Stain rate, pen chance */ 0, 0, \
|
||||
/* Lifetime */ Inf, \
|
||||
/* Base color */ 1, 1, 0, 1 \
|
||||
) \
|
||||
/* ----------------------------------------------------------------------------------------------------------------------------------- */
|
||||
@ -142,6 +150,7 @@ Struct(V_ParticleDesc)
|
||||
V_ParticleLayer layer;
|
||||
f32 stain_rate;
|
||||
f32 pen_rate;
|
||||
f32 lifetime;
|
||||
Vec4 color;
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user