working splatter with particle layers
This commit is contained in:
parent
a9ca711892
commit
ed40711781
@ -2265,6 +2265,143 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
|
||||
|
||||
|
||||
// if (0)
|
||||
{
|
||||
for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
|
||||
{
|
||||
if (bullet->is_bullet && bullet->has_hit)
|
||||
{
|
||||
P_MaterialKind material = bullet->hit_material;
|
||||
|
||||
Vec2 hit_entry = bullet->hit_entry;
|
||||
Vec2 hit_entry_normal = bullet->hit_entry_normal;
|
||||
Vec2 bullet_vel = SubVec2(bullet->bullet_end, bullet->bullet_start);
|
||||
|
||||
V_DrawLine(bullet->bullet_start, bullet->bullet_end, Color_Cyan);
|
||||
|
||||
//////////////////////////////
|
||||
//- Wall particles
|
||||
|
||||
// if (material != P_MaterialKind_Flesh)
|
||||
// {
|
||||
// //- Wall debris
|
||||
// {
|
||||
// V_Emitter emitter = Zi;
|
||||
// {
|
||||
// // emitter.flags |= V_ParticleFlag_PruneWhenStill;
|
||||
// // emitter.flags |= V_ParticleFlag_StainOnPrune;
|
||||
|
||||
|
||||
// emitter.count = 4;
|
||||
|
||||
// emitter.start = hit_entry;
|
||||
// emitter.end = emitter.start;
|
||||
|
||||
// emitter.color_lin = LinearFromSrgb(VEC4(0.4, 0.3, 0.2, 0.75));
|
||||
// emitter.color_spread = VEC4(0, 0, 0, 0.25);
|
||||
|
||||
// emitter.speed = 2;
|
||||
// // emitter.speed_spread = emitter.speed * 2;
|
||||
// emitter.speed_spread = emitter.speed * 2.5;
|
||||
|
||||
// emitter.velocity_falloff = 5;
|
||||
// emitter.velocity_falloff_spread = emitter.velocity_falloff_spread * 1.5;
|
||||
|
||||
// Vec2 dir = hit_entry_normal;
|
||||
|
||||
// emitter.angle = AngleFromVec2(dir);
|
||||
// emitter.angle_spread = Tau * 0.5;
|
||||
|
||||
// // emitter.lifetime = 0.25;
|
||||
// // emitter.lifetime = 0.05;
|
||||
// // emitter.lifetime = 0.04;
|
||||
// emitter.lifetime_spread = emitter.lifetime * 2;
|
||||
// }
|
||||
// V_PushParticles(emitter);
|
||||
// }
|
||||
|
||||
// //- Wall dust
|
||||
// {
|
||||
// V_Emitter emitter = Zi;
|
||||
// {
|
||||
// // emitter.flags |= V_ParticleFlag_PruneWhenStill;
|
||||
|
||||
// emitter.count = 32;
|
||||
|
||||
// emitter.start = hit_entry;
|
||||
// emitter.end = emitter.start;
|
||||
|
||||
// emitter.color_lin = LinearFromSrgb(VEC4(0.5, 0.5, 0.5, 0.75));
|
||||
|
||||
// emitter.speed = 4;
|
||||
// // emitter.speed_spread = emitter.speed * 2;
|
||||
// emitter.speed_spread = emitter.speed * 2;
|
||||
|
||||
// emitter.velocity_falloff = 12;
|
||||
// emitter.velocity_falloff_spread = emitter.velocity_falloff_spread * 1.5;
|
||||
|
||||
// Vec2 dir = hit_entry_normal;
|
||||
|
||||
// emitter.angle = AngleFromVec2(dir);
|
||||
// emitter.angle_spread = Tau * 0.1;
|
||||
|
||||
// emitter.lifetime = 1;
|
||||
// // emitter.lifetime = 0.05;
|
||||
// // emitter.lifetime = 0.04;
|
||||
// emitter.lifetime_spread = emitter.lifetime * 2;
|
||||
// }
|
||||
// V_PushParticles(emitter);
|
||||
// }
|
||||
// }
|
||||
|
||||
//////////////////////////////
|
||||
//- Blood particles
|
||||
|
||||
if (material == P_MaterialKind_Flesh)
|
||||
{
|
||||
{
|
||||
V_Emitter emitter = Zi;
|
||||
|
||||
emitter.kind = V_ParticleKind_BloodTrail;
|
||||
// emitter.kind = V_ParticleKind_BloodDebris;
|
||||
|
||||
Vec2 dir = NormVec2(NegVec2(bullet_vel));
|
||||
|
||||
f32 angle = AngleFromVec2(dir);
|
||||
// f32 angle = 0;
|
||||
// f32 angle_spread = Tau * 0.25;
|
||||
f32 angle_spread = TweakFloat("Emitter angle spread", 0.1, 0, 1) * Tau;
|
||||
// f32 angle_spread = Tau;
|
||||
// f32 angle_spread = 0;
|
||||
|
||||
// f32 speed = 5;
|
||||
// f32 speed = 25;
|
||||
f32 speed = 50;
|
||||
// f32 speed = 100;
|
||||
f32 speed_spread = speed * 2;
|
||||
|
||||
emitter.pos.p0 = emitter.pos.p1 = hit_entry;
|
||||
emitter.speed.min = speed - speed_spread * 0.5;
|
||||
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);
|
||||
V_PushParticles(emitter);
|
||||
}
|
||||
}
|
||||
|
||||
// V_DrawPoint(victim_raycast.p, Color_Green);
|
||||
// V_DrawLine(victim_raycast.p, AddVec2(victim_raycast.p, MulVec2(victim_raycast.normal, 0.5)), Color_White);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// if (0)
|
||||
// {
|
||||
// for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
|
||||
@ -2556,32 +2693,32 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
V_PushParticles(emitter);
|
||||
}
|
||||
|
||||
{
|
||||
V_Emitter emitter = Zi;
|
||||
// {
|
||||
// V_Emitter emitter = Zi;
|
||||
|
||||
// emitter.kind = V_ParticleKind_BloodTrail;
|
||||
emitter.kind = V_ParticleKind_BloodDebris;
|
||||
// // emitter.kind = V_ParticleKind_BloodTrail;
|
||||
// emitter.kind = V_ParticleKind_BloodDebris;
|
||||
|
||||
f32 angle = AngleFromVec2(frame->look);
|
||||
// f32 angle = 0;
|
||||
f32 angle_spread = Tau * 0.25;
|
||||
// f32 angle_spread = Tau;
|
||||
// f32 angle_spread = 0;
|
||||
// f32 angle = AngleFromVec2(frame->look);
|
||||
// // f32 angle = 0;
|
||||
// f32 angle_spread = Tau * 0.25;
|
||||
// // f32 angle_spread = Tau;
|
||||
// // f32 angle_spread = 0;
|
||||
|
||||
// f32 speed = 5;
|
||||
f32 speed = 10;
|
||||
// f32 speed = 50;
|
||||
// f32 speed = 100;
|
||||
f32 speed_spread = speed * 2;
|
||||
// // f32 speed = 5;
|
||||
// f32 speed = 10;
|
||||
// // f32 speed = 50;
|
||||
// // f32 speed = 100;
|
||||
// f32 speed_spread = speed * 2;
|
||||
|
||||
emitter.pos.p0 = emitter.pos.p1 = frame->world_cursor;
|
||||
emitter.speed.min = speed - speed_spread * 0.5;
|
||||
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 = Kibi(32) * frame->dt;
|
||||
V_PushParticles(emitter);
|
||||
}
|
||||
// emitter.pos.p0 = emitter.pos.p1 = frame->world_cursor;
|
||||
// emitter.speed.min = speed - speed_spread * 0.5;
|
||||
// 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 = Kibi(32) * frame->dt;
|
||||
// V_PushParticles(emitter);
|
||||
// }
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
@ -18,21 +18,6 @@ Vec4 V_ColorFromParticle(V_ParticleDesc desc, u32 particle_idx, u32 density)
|
||||
|
||||
result = desc.color;
|
||||
|
||||
// // FIXME: Base color on particle desc
|
||||
// if (desc.kind == V_ParticleKind_Test)
|
||||
// {
|
||||
// // result.rgb = Vec3(0, 0, 0);
|
||||
// result = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 0.5));
|
||||
// }
|
||||
// else if (desc.kind == V_ParticleKind_Debris)
|
||||
// {
|
||||
// result = Color_Orange;
|
||||
// }
|
||||
// else if (desc.kind == V_ParticleKind_Smoke)
|
||||
// {
|
||||
// result = Vec4(0.15, 0.15, 0.15, 1);
|
||||
// }
|
||||
|
||||
// Apply density
|
||||
{
|
||||
if (desc.kind == V_ParticleKind_Smoke)
|
||||
@ -49,10 +34,10 @@ Vec4 V_ColorFromParticle(V_ParticleDesc desc, u32 particle_idx, u32 density)
|
||||
// t = saturate(t);
|
||||
// result.rgb *= 1.0 - (t * 0.9);
|
||||
|
||||
f32 t = (f32)density / 5;
|
||||
f32 t = (f32)density / 10;
|
||||
// t = smoothstep(-10, 10, t);
|
||||
// t = smoothstep(-5, 5, t);
|
||||
t = smoothstep(0, 50, t);
|
||||
t = smoothstep(-50, 50, t);
|
||||
// result.rgb *= 1.0 - (t * 0.9);
|
||||
|
||||
// result.a = t;
|
||||
@ -296,9 +281,6 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
f32 rand_speed = Norm16(seed0 >> 32);
|
||||
f32 rand_falloff = Norm16(seed0 >> 48);
|
||||
|
||||
u64 seed1 = MixU64(seed0);
|
||||
f32 rand_density = Norm16(seed1 >> 0);
|
||||
|
||||
//////////////////////////////
|
||||
//- Init
|
||||
|
||||
@ -335,11 +317,6 @@ 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
|
||||
|
||||
if (AnyBit(desc.flags, V_ParticleFlag_StainTrail))
|
||||
{
|
||||
packed |= 1 << 31;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
//- Move
|
||||
|
||||
@ -414,8 +391,8 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
|
||||
if (is_in_world)
|
||||
{
|
||||
f32 commit_delta = abs(t_diff) * desc.commit_rate * frame.dt;
|
||||
particle.commit_accum += commit_delta;
|
||||
f32 stain_delta = abs(t_diff) * desc.stain_rate * frame.dt;
|
||||
particle.stain_accum += stain_delta;
|
||||
|
||||
//- Handle collision
|
||||
V_OccluderKind occluder = (V_OccluderKind)occluders[cell_pos];
|
||||
@ -449,9 +426,10 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
{
|
||||
f32 collision_angle = lerp(-0.05 * Tau, 0.05 * Tau, rand_collision_angle);
|
||||
|
||||
f32 collision_velocity_falloff = lerp(50, 100, rand_collision_velocity);
|
||||
// f32 collision_velocity_falloff = lerp(50, 100, rand_collision_velocity);
|
||||
// f32 collision_velocity_falloff = lerp(5000, 10000, rand_collision_velocity);
|
||||
// f32 collision_velocity_falloff = lerp(500, 10000, rand_collision_velocity);
|
||||
f32 collision_velocity_falloff = lerp(50, 100, rand_collision_velocity);
|
||||
// f32 collision_velocity_falloff = 0;
|
||||
|
||||
particle.velocity = RotateVec2Angle(particle.velocity, collision_angle);
|
||||
@ -472,22 +450,26 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
done = 1;
|
||||
if (AnyBit(desc.flags, V_ParticleFlag_StainWhenPruned))
|
||||
{
|
||||
// particle.commit_accum = max(particle.commit_accum, 1);
|
||||
particle.commit_accum += 1;
|
||||
// particle.stain_accum = max(particle.stain_accum, 1);
|
||||
particle.stain_accum += 1;
|
||||
packed |= 1 << 31;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collision)
|
||||
{
|
||||
u32 commit_count = floor(particle.commit_accum);
|
||||
u32 density = commit_count;
|
||||
u32 stain_count = floor(particle.stain_accum);
|
||||
u32 density = 1;
|
||||
|
||||
u32 commit = packed;
|
||||
if (stain_count > 0)
|
||||
{
|
||||
InterlockedMax(cells[cell_pos], packed);
|
||||
InterlockedAdd(densities[cell_pos], density);
|
||||
particle.commit_accum -= commit_count;
|
||||
commit |= (1 << 31);
|
||||
}
|
||||
|
||||
InterlockedMax(cells[cell_pos], commit);
|
||||
InterlockedAdd(densities[cell_pos], density);
|
||||
particle.stain_accum -= stain_count;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -609,8 +591,8 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
|
||||
// if (is_in_world)
|
||||
// {
|
||||
// f32 commit_delta = abs(t_diff) * desc.commit_rate * frame.dt;
|
||||
// particle.commit_accum += commit_delta;
|
||||
// f32 stain_delta = abs(t_diff) * desc.stain_rate * frame.dt;
|
||||
// particle.stain_accum += stain_delta;
|
||||
|
||||
// //- Handle collision
|
||||
// V_OccluderKind occluder = (V_OccluderKind)occluders[cell_pos];
|
||||
@ -667,13 +649,13 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
|
||||
// if (prune && AnyBit(desc.flags, V_ParticleFlag_StainWhenPruned))
|
||||
// {
|
||||
// particle.commit_accum += 1;
|
||||
// particle.stain_accum += 1;
|
||||
// }
|
||||
|
||||
// if (!collision)
|
||||
// {
|
||||
// //- Stain
|
||||
// u32 stains_count = floor(particle.commit_accum);
|
||||
// u32 stains_count = floor(particle.stain_accum);
|
||||
// if (stains_count > 0)
|
||||
// {
|
||||
// // TODO: Fixed point
|
||||
@ -681,7 +663,7 @@ ComputeShader(V_SimParticlesCS, 64)
|
||||
// InterlockedMax(stain_cells[cell_pos], packed);
|
||||
// InterlockedAdd(stain_densities[cell_pos], density);
|
||||
// drynesses[cell_pos] = 0;
|
||||
// particle.commit_accum -= stains_count;
|
||||
// particle.stain_accum -= stains_count;
|
||||
// }
|
||||
|
||||
// //- Draw
|
||||
|
||||
@ -11,49 +11,49 @@ V_ParticleDesc V_DescFromParticleKind(V_ParticleKind kind)
|
||||
V_ParticleDesc result;
|
||||
{
|
||||
PERSIST Readonly V_ParticleFlag flags[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) flags,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) flags,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly V_ParticleLayer layers[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) layer,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) layer,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 commit_rates[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) commit_rate,
|
||||
PERSIST Readonly f32 stain_rates[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) stain_rate,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 pen_rates[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) pen_rate,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) pen_rate,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 r[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) r,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) r,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 g[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) g,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) g,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 b[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) b,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) b,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
PERSIST Readonly f32 a[V_ParticleKind_COUNT] = {
|
||||
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) a,
|
||||
#define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) a,
|
||||
V_ParticlesXList(X)
|
||||
#undef X
|
||||
};
|
||||
result.kind = kind;
|
||||
result.flags = flags[kind];
|
||||
result.layer = layers[kind];
|
||||
result.commit_rate = commit_rates[kind];
|
||||
result.stain_rate = stain_rates[kind];
|
||||
result.pen_rate = pen_rates[kind];
|
||||
result.color = LinearFromSrgb(VEC4(r[kind], g[kind], b[kind], a[kind]));
|
||||
}
|
||||
|
||||
@ -42,7 +42,6 @@ Enum(V_ParticleFlag)
|
||||
V_ParticleFlag_NoPruneWhenStill = (1 << 0),
|
||||
V_ParticleFlag_StainWhenPruned = (1 << 1),
|
||||
V_ParticleFlag_NoReflect = (1 << 2),
|
||||
V_ParticleFlag_StainTrail = (1 << 3),
|
||||
};
|
||||
|
||||
Enum(V_ParticleLayer)
|
||||
@ -60,30 +59,30 @@ Enum(V_ParticleLayer)
|
||||
/* Name */ None, \
|
||||
/* Flags */ V_ParticleFlag_None, \
|
||||
/* Layer */ V_ParticleLayer_Ground, \
|
||||
/* Commit rate, pen chance */ 30, 0, \
|
||||
/* Stain rate, pen chance */ 30, 0, \
|
||||
/* Base color */ 0, 0, 0, 0 \
|
||||
) \
|
||||
\
|
||||
/* Ground particles */ \
|
||||
X( \
|
||||
/* Name */ BloodTrail, \
|
||||
/* Flags */ V_ParticleFlag_NoReflect | V_ParticleFlag_StainTrail, \
|
||||
/* Flags */ V_ParticleFlag_NoReflect | V_ParticleFlag_StainWhenPruned, \
|
||||
/* Layer */ V_ParticleLayer_Ground, \
|
||||
/* Commit rate, pen chance */ 500, 0.25, \
|
||||
/* Base color */ 0.5, 0.1, 0.1, 0.1 \
|
||||
/* Stain rate, pen chance */ 30, 0.25, \
|
||||
/* Base color */ 0.5, 0.1, 0.1, 0.05 \
|
||||
) \
|
||||
X( \
|
||||
/* Name */ BloodDebris, \
|
||||
/* Flags */ V_ParticleFlag_StainWhenPruned, \
|
||||
/* Layer */ V_ParticleLayer_Mid, \
|
||||
/* Commit rate, pen chance */ 30, 0, \
|
||||
/* Stain rate, pen chance */ 30, 0, \
|
||||
/* Base color */ 0.5, 0.1, 0.1, 0.8 \
|
||||
) \
|
||||
X( \
|
||||
/* Name */ Debris, \
|
||||
/* Flags */ V_ParticleFlag_StainWhenPruned, \
|
||||
/* Layer */ V_ParticleLayer_Mid, \
|
||||
/* Commit rate, pen chance */ 10000, 0, \
|
||||
/* Stain rate, pen chance */ 0, 0, \
|
||||
/* Base color */ 2, 0.5, 0, 1 \
|
||||
) \
|
||||
\
|
||||
@ -92,14 +91,14 @@ Enum(V_ParticleLayer)
|
||||
/* Name */ Smoke, \
|
||||
/* Flags */ V_ParticleFlag_None, \
|
||||
/* Layer */ V_ParticleLayer_Air, \
|
||||
/* Commit rate, pen chance */ 30, 0, \
|
||||
/* Stain rate, pen chance */ 0, 0, \
|
||||
/* Base color */ 0.15, 0.15, 0.15, 0.5 \
|
||||
) \
|
||||
X( \
|
||||
/* Name */ BulletTrail, \
|
||||
/* Flags */ V_ParticleFlag_None, \
|
||||
/* Layer */ V_ParticleLayer_Air, \
|
||||
/* Commit rate, pen chance */ 30, 0, \
|
||||
/* Stain rate, pen chance */ 0, 0, \
|
||||
/* Base color */ 1, 0, 1, 1 \
|
||||
) \
|
||||
\
|
||||
@ -108,7 +107,7 @@ Enum(V_ParticleLayer)
|
||||
/* Name */ Test, \
|
||||
/* Flags */ V_ParticleFlag_None, \
|
||||
/* Layer */ V_ParticleLayer_Mid, \
|
||||
/* Commit rate, pen chance */ 30, 0, \
|
||||
/* Stain rate, pen chance */ 0, 0, \
|
||||
/* Base color */ 1, 1, 0, 1 \
|
||||
) \
|
||||
/* ----------------------------------------------------------------------------------------------------------------------------------- */
|
||||
@ -138,7 +137,7 @@ Struct(V_Particle)
|
||||
{
|
||||
i32 kind; // If >= 0, then map to V_ParticleKind. Otherwize initialize particle using emitter at index [abs(kind) - 1]
|
||||
f32 life;
|
||||
f32 commit_accum;
|
||||
f32 stain_accum;
|
||||
u32 cells_count;
|
||||
Vec2 pos;
|
||||
Vec2 velocity;
|
||||
@ -149,7 +148,7 @@ Struct(V_ParticleDesc)
|
||||
V_ParticleKind kind;
|
||||
V_ParticleFlag flags;
|
||||
V_ParticleLayer layer;
|
||||
f32 commit_rate;
|
||||
f32 stain_rate;
|
||||
f32 pen_rate;
|
||||
Vec4 color;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user