103 lines
5.9 KiB
Plaintext
103 lines
5.9 KiB
Plaintext
#version 450
|
|
#extension GL_GOOGLE_include_directive : require
|
|
layout(local_size_x = 64) in;
|
|
#include "fx_common.glsl"
|
|
|
|
// The player's right arm and the item in its hand, drawn into the entity layer every frame. The arm (player_arm.xml) hangs at the body's
|
|
// right_arm_root hotspot (the host follows the stand / walk frames, so it breathes with the body), turns toward the aim point (the cursor, or
|
|
// ahead of the player) and is mirrored when aiming left; the item is held at the arm's "hand" hotspot, turned the same way about its grip.
|
|
// The arm header is in the first words of the hand texture region (hand1.x - its offset is absolute): [0] arm on, [1] arm pixels (absolute),
|
|
// [2..3] arm size, [4..5] arm root relative to the body (floats), [6..7] the arm sprite's pivot (offset_x, offset_y), [8..9] its hand hotspot;
|
|
// [48] cape on, [49..50] the corner of its 32 x 32 image (words 64..1087, rasterized by the host) relative to the body: drawn behind the body (empty pixels only).
|
|
const uint HAND_BASE = (1u << 20) - 32768u;
|
|
|
|
uint hw(uint i) { return fxtex[HAND_BASE + i]; }
|
|
|
|
// draws a w x h image whose pixel `pivot` sits at `org`, turned by (c, s), mirrored vertically when `mirror`
|
|
void draw_turned(vec2 org, vec2 pivot, float c, float s, bool mirror, int w, int h, uint off) {
|
|
float r = length(vec2(float(w), float(h))) + 2.0;
|
|
int n = int(2.0 * r) + 2;
|
|
ivec2 lo = ivec2(floor(org - vec2(r)));
|
|
for (int i = int(gl_LocalInvocationID.x); i < n * n; i += 64) {
|
|
ivec2 dst = lo + ivec2(i % n, i / n);
|
|
vec2 d = vec2(dst) + 0.5 - org;
|
|
vec2 l = vec2(c * d.x + s * d.y, -s * d.x + c * d.y); // the image frame, relative to the pivot
|
|
if (mirror) l.y = -l.y;
|
|
ivec2 sp = ivec2(floor(pivot + l));
|
|
if (sp.x < 0 || sp.y < 0 || sp.x >= w || sp.y >= h) continue;
|
|
ivec2 q = dst - ivec2(pc.view_x, pc.view_y);
|
|
if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) continue;
|
|
uint t = fxtex[off + uint(sp.y * w + sp.x)];
|
|
const uint ta = t >> 24;
|
|
if (ta == 0u) continue;
|
|
const uint di = uint(q.y * pc.gw + q.x);
|
|
const uint under = entlayer[di];
|
|
if (ta < 255u && (under >> 24) != 0u) { // a translucent texel over the body: mixed with it (source over), not cut through it
|
|
uint mixd = max(ta, under >> 24) << 24;
|
|
for (uint sh = 0u; sh < 24u; sh += 8u) mixd |= (((t >> sh) & 0xFFu) * ta + ((under >> sh) & 0xFFu) * (255u - ta)) / 255u << sh;
|
|
entlayer[di] = mixd;
|
|
} else entlayer[di] = t;
|
|
}
|
|
}
|
|
|
|
// The items NPCs hold (ItemPickUpperComponent is_in_npc): at word 32640 of the hand region the count, then 12 words per entry: [0] the slot + 1, [1] the image
|
|
// (absolute), [2..3] its size, [4..5] the grip, [6..7] the hand relative to the body, [8..9] the aim point (floats); drawn like the player's item, turned toward the aim
|
|
void draw_npc_items() {
|
|
const uint n = min(hw(32640u), 10u);
|
|
for (uint k = 0u; k < n; ++k) {
|
|
const uint b = 32641u + k * 12u;
|
|
const int sl = int(hw(b)) - 1;
|
|
if (sl <= 0 || sl >= int(ents.length()) || (ents[sl].st.w & 8) != 0) continue;
|
|
vec2 cur = ents[sl].a.xy, prv = prevpos[sl].xy;
|
|
vec2 body = distance(prv, cur) < 10.0 ? mix(prv, cur, ctrl.view.z) : cur;
|
|
vec2 hand = body + vec2(uintBitsToFloat(hw(b + 6u)), uintBitsToFloat(hw(b + 7u)));
|
|
vec2 aim = vec2(uintBitsToFloat(hw(b + 8u)), uintBitsToFloat(hw(b + 9u)));
|
|
float ang = length(aim - hand) > 0.5 ? atan(aim.y - hand.y, aim.x - hand.x) : 0.0;
|
|
float c = cos(ang), s = sin(ang);
|
|
draw_turned(hand, vec2(uintBitsToFloat(hw(b + 4u)), uintBitsToFloat(hw(b + 5u))), c, s, c < 0.0, int(hw(b + 2u)), int(hw(b + 3u)), hw(b + 1u));
|
|
}
|
|
memoryBarrierBuffer();
|
|
barrier();
|
|
}
|
|
|
|
void main() {
|
|
draw_npc_items();
|
|
if (ctrl.inp.w < 1 || ents[0].st.x != 3 || (ents[0].st.w & 8) != 0 || (ents[0].ex8.x & 8192) != 0) return;
|
|
vec2 cur = ents[0].a.xy, prv = prevpos[0].xy;
|
|
vec2 body = distance(prv, cur) < 10.0 ? mix(prv, cur, ctrl.view.z) : cur;
|
|
if (hw(48u) == 1u) { // the cape, behind the body
|
|
ivec2 corner = ivec2(floor(body + vec2(uintBitsToFloat(hw(49u)), uintBitsToFloat(hw(50u))) + 0.5));
|
|
for (int i = int(gl_LocalInvocationID.x); i < 1024; i += 64) {
|
|
uint t = hw(64u + uint(i));
|
|
if ((t >> 24) == 0u) continue;
|
|
ivec2 q = corner + ivec2(i % 32, i / 32) - ivec2(pc.view_x, pc.view_y);
|
|
if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) continue;
|
|
if ((entlayer[q.y * pc.gw + q.x] >> 24) == 0u) entlayer[q.y * pc.gw + q.x] = t;
|
|
}
|
|
memoryBarrierBuffer();
|
|
barrier();
|
|
}
|
|
if (ctrl.hand0.z < 0.5) return;
|
|
const bool arm = hw(0u) == 1u;
|
|
vec2 root = arm ? body + vec2(uintBitsToFloat(hw(4u)), uintBitsToFloat(hw(5u))) : body + vec2(0.0, -5.0);
|
|
vec2 aim = ctrl.hand0.xy;
|
|
float ang = length(aim - root) > 0.5 ? atan(aim.y - root.y, aim.x - root.x) : 0.0;
|
|
float c = cos(ang), s = sin(ang);
|
|
bool mirror = c < 0.0;
|
|
vec2 hand = root;
|
|
if (arm) { // the hand: the arm's hand hotspot relative to its pivot, turned and mirrored with the arm
|
|
vec2 piv = vec2(uintBitsToFloat(hw(6u)), uintBitsToFloat(hw(7u)));
|
|
vec2 hl = vec2(uintBitsToFloat(hw(8u)), uintBitsToFloat(hw(9u))) + 0.5 - piv;
|
|
if (mirror) hl.y = -hl.y;
|
|
hand = root + vec2(c * hl.x - s * hl.y, s * hl.x + c * hl.y);
|
|
}
|
|
{ // the item's recoil (00b51670): hand2.z px back along the aim, turned muzzle-up by hand0.w radians (the arm keeps the aim)
|
|
const float ia = ang + (mirror ? ctrl.hand0.w : -ctrl.hand0.w);
|
|
const float ic = cos(ia), is = sin(ia);
|
|
draw_turned(hand - vec2(c, s) * ctrl.hand2.z, ctrl.hand2.xy, ic, is, mirror, ctrl.hand1.y, ctrl.hand1.z, uint(ctrl.hand1.x));
|
|
}
|
|
memoryBarrierBuffer();
|
|
barrier();
|
|
if (arm) draw_turned(root, vec2(uintBitsToFloat(hw(6u)), uintBitsToFloat(hw(7u))), c, s, mirror, int(hw(2u)), int(hw(3u)), hw(1u)); // the hand over the grip
|
|
}
|