nx_sprite_forge.nx source
↩ module page · 245 lines · 13220 B
1// nx_sprite_forge.nx -- THE SPRITE BUILDER (not a sprite): a sovereign, data-driven procedural character forge.
2// A reusable library of detailed, directionally-shaded armour/body PARTS (helm, breastplate, pauldrons, plated
3// legs, arms, sword, cape, skull, ribcage, staff, robe), plus a COMPOSER that builds a full character from a
4// SPEC = an ordered list of [part_id, colourA, colourB] records (back-to-front). New characters are new spec
5// DATA, not new code -- so the ecosystem (and team) emits knights, skeletons, mages, beasts at will. Integer /
6// no-float; bakes form-shading (light from upper-left); the engine's dynamic torch lighting is applied on top.
7// Built on nx_gfx_iso primitives. license_tier: ORIGINAL
8import "nx_gfx_iso.nx"
9
10func sf_white() -> i64 { return gx_rgb(255,255,255) }
11func sf_hi(c: i64) -> i64 { return gx_mix(c, gx_rgb(255,255,255), 95) } // bright highlight
12func sf_lt(c: i64) -> i64 { return gx_mix(c, gx_rgb(255,255,255), 55) } // lit
13func sf_sh(c: i64) -> i64 { return gx_shade(c, 120) } // shadow
14func sf_ed(c: i64) -> i64 { return gx_shade(c, 64) } // dark edge / crevice
15
16// ---- PART: cape (flowing cloth behind the body) ----
17func sf_cape(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
18 var yy: i64 = 0
19 while yy < 46 {
20 let hw: i64 = 8 + (yy*6)/46 // flares 8 -> 14
21 let prog: i64 = (yy*256)/46
22 var dx: i64 = 0 - hw
23 while dx <= hw {
24 var c: i64 = gx_mix(ca, gx_shade(ca, 55), prog) // darker toward the hem
25 if dx < 0 - hw + 2 { c = gx_mix(c, gx_rgb(255,255,255), 45) } // left edge catches light
26 if ((dx + (yy/3)) % 7) == 0 { c = gx_shade(c, 72) } // vertical folds
27 if dx > hw - 1 { c = sf_ed(c) }
28 gx_px(fb, W, H, fx + dx, fy - 52 + yy, c)
29 dx = dx + 1
30 }
31 yy = yy + 1
32 }
33 return 0
34}
35
36// ---- PART: plated legs + boots (two legs) ----
37func sf_leg1(fb: *i64, W: i64, H: i64, lx: i64, fy: i64, ca: i64) -> i64 {
38 var yy: i64 = fy - 30
39 while yy < fy - 2 {
40 var dx: i64 = 0 - 4
41 while dx <= 4 {
42 var c: i64 = ca
43 if dx < 0 - 2 { c = sf_lt(ca) } else { if dx > 2 { c = sf_sh(ca) } }
44 if dx == 0 - 4 { c = sf_ed(ca) } if dx == 4 { c = sf_ed(ca) }
45 gx_px(fb, W, H, lx + dx, yy, c); dx = dx + 1
46 }
47 yy = yy + 1
48 }
49 gx_ellipse(fb, W, H, lx - 1, fy - 18, 3, 3, sf_hi(ca)) // knee plate
50 gx_hspan(fb, W, H, lx - 5, lx + 5, fy - 2, sf_ed(ca)) // boot sole
51 gx_hspan(fb, W, H, lx - 5, lx + 6, fy - 1, sf_ed(ca)) // boot toe
52 gx_px(fb, W, H, lx - 4, fy - 8, sf_hi(ca)) // boot cuff glint
53 return 0
54}
55func sf_legs(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
56 sf_leg1(fb, W, H, fx - 5, fy, ca); sf_leg1(fb, W, H, fx + 5, fy, ca)
57 gx_hspan(fb, W, H, fx - 9, fx + 9, fy - 30, cb) // belt/tasset trim
58 gx_hspan(fb, W, H, fx - 9, fx + 9, fy - 29, gx_shade(cb, 150))
59 gx_ellipse(fb, W, H, fx, fy - 29, 3, 2, gx_mix(cb, gx_rgb(255,255,255), 80)) // buckle
60 return 0
61}
62
63// ---- PART: breastplate (the detail centrepiece) ----
64func sf_torso(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
65 var yy: i64 = fy - 52
66 while yy < fy - 30 {
67 let prog: i64 = ((yy - (fy-52))*256)/22
68 let hw: i64 = 11 - (prog*3)/256 // shoulders 11 -> waist 8
69 var dx: i64 = 0 - hw
70 while dx <= hw {
71 var c: i64 = ca
72 if dx >= 0 - 2 { if dx <= 1 { c = sf_hi(ca) } } // central ridge highlight
73 if dx < 0 - hw + 3 { c = sf_lt(ca) } // lit left
74 if dx > hw - 3 { c = sf_sh(ca) } // shadow right
75 if dx == 0 - hw { c = sf_ed(ca) } if dx == hw { c = sf_ed(ca) }
76 gx_px(fb, W, H, fx + dx, yy, c); dx = dx + 1
77 }
78 yy = yy + 1
79 }
80 gx_hspan(fb, W, H, fx - 9, fx + 9, fy - 40, sf_ed(ca)) // ab plate divisions
81 gx_hspan(fb, W, H, fx - 8, fx + 8, fy - 35, sf_ed(ca))
82 gx_px(fb, W, H, fx - 1, fy - 52, sf_ed(ca)); gx_px(fb, W, H, fx, fy - 51, sf_ed(ca)); gx_px(fb, W, H, fx + 1, fy - 50, sf_ed(ca)) // neckline V
83 gx_px(fb, W, H, fx - 9, fy - 50, sf_ed(ca)); gx_px(fb, W, H, fx + 9, fy - 50, sf_ed(ca)) // rivets
84 gx_px(fb, W, H, fx - 8, fy - 44, sf_ed(ca)); gx_px(fb, W, H, fx + 8, fy - 44, sf_ed(ca))
85 gx_hspan(fb, W, H, fx - 8, fx + 8, fy - 31, cb) // gold belt-line trim
86 return 0
87}
88
89// ---- PART: arms (two; the right grips a weapon) ----
90func sf_arms(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
91 var yy: i64 = fy - 49
92 while yy < fy - 30 {
93 gx_px(fb, W, H, fx - 12, yy, sf_lt(ca)); gx_px(fb, W, H, fx - 11, yy, ca); gx_px(fb, W, H, fx - 10, yy, sf_sh(ca))
94 gx_px(fb, W, H, fx + 10, yy, sf_lt(ca)); gx_px(fb, W, H, fx + 11, yy, ca); gx_px(fb, W, H, fx + 12, yy, sf_sh(ca))
95 yy = yy + 1
96 }
97 gx_ellipse(fb, W, H, fx - 11, fy - 30, 2, 2, cb) // gauntlets
98 gx_ellipse(fb, W, H, fx + 11, fy - 30, 2, 2, cb)
99 gx_ellipse(fb, W, H, fx - 11, fy - 48, 3, 2, sf_hi(ca)) // shoulder highlight
100 return 0
101}
102
103// ---- PART: sword (held at the right hand, blade up) ----
104func sf_sword(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
105 let bx: i64 = fx + 13
106 gx_vgrad(fb, W, H, bx - 1, fy - 34, bx + 1, fy - 30, gx_shade(cb,150), sf_ed(cb)) // grip (leather/dark)
107 gx_hspan(fb, W, H, bx - 5, bx + 5, fy - 35, cb) // crossguard
108 gx_hspan(fb, W, H, bx - 5, bx + 5, fy - 34, gx_shade(cb, 160))
109 gx_ellipse(fb, W, H, bx, fy - 29, 2, 2, gx_mix(cb, gx_rgb(255,255,255), 90)) // pommel
110 var yy: i64 = fy - 62
111 while yy < fy - 35 {
112 gx_px(fb, W, H, bx - 2, yy, sf_sh(ca)); gx_px(fb, W, H, bx - 1, yy, ca)
113 gx_px(fb, W, H, bx, yy, sf_hi(ca)); gx_px(fb, W, H, bx + 1, yy, sf_lt(ca)); gx_px(fb, W, H, bx + 2, yy, sf_sh(ca)) // fuller highlight
114 yy = yy + 1
115 }
116 gx_px(fb, W, H, bx, fy - 63, gx_rgb(255,255,255)); gx_px(fb, W, H, bx, fy - 62, sf_hi(ca)) // tip glint
117 return 0
118}
119
120// ---- PART: pauldrons (two rounded shoulder plates) ----
121func sf_pauldrons(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
122 gx_ellipse(fb, W, H, fx - 11, fy - 48, 6, 5, ca)
123 gx_ellipse(fb, W, H, fx - 12, fy - 50, 4, 3, sf_hi(ca)) // top catches light
124 gx_ellipse(fb, W, H, fx - 9, fy - 45, 3, 2, sf_sh(ca)) // underside shadow
125 gx_ellipse(fb, W, H, fx + 11, fy - 48, 6, 5, ca)
126 gx_ellipse(fb, W, H, fx + 10, fy - 50, 4, 3, sf_lt(ca))
127 gx_ellipse(fb, W, H, fx + 13, fy - 45, 3, 2, sf_sh(ca))
128 gx_px(fb, W, H, fx - 11, fy - 52, cb); gx_px(fb, W, H, fx + 11, fy - 52, cb) // rivet studs
129 return 0
130}
131
132// ---- PART: helm (domed, visor, plume) ----
133func sf_helm(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
134 gx_ellipse(fb, W, H, fx, fy - 58, 8, 9, ca) // dome
135 gx_ellipse(fb, W, H, fx - 2, fy - 61, 4, 4, sf_hi(ca)) // top-left highlight
136 gx_ellipse(fb, W, H, fx + 4, fy - 56, 3, 5, sf_sh(ca)) // right shadow
137 gx_hspan(fb, W, H, fx - 7, fx + 7, fy - 55, sf_ed(ca)) // visor slit
138 gx_hspan(fb, W, H, fx - 7, fx + 7, fy - 54, gx_rgb(20,16,14))
139 gx_px(fb, W, H, fx - 3, fy - 54, gx_rgb(150,70,40)); gx_px(fb, W, H, fx + 2, fy - 54, gx_rgb(150,70,40)) // eye glints in the visor
140 gx_hspan(fb, W, H, fx - 8, fx + 8, fy - 51, sf_sh(ca)) // jaw guard
141 // plume / crest
142 var yy: i64 = fy - 68
143 while yy < fy - 58 {
144 gx_px(fb, W, H, fx, yy, cb); gx_px(fb, W, H, fx - 1, yy, gx_shade(cb, 140)); gx_px(fb, W, H, fx + 1, yy, sf_lt(cb))
145 yy = yy + 1
146 }
147 return 0
148}
149
150// ---- PART: skull (skeleton head) ----
151func sf_skull(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
152 gx_ellipse(fb, W, H, fx, fy - 58, 7, 8, ca) // cranium (bone)
153 gx_ellipse(fb, W, H, fx - 2, fy - 61, 3, 3, sf_hi(ca))
154 gx_ellipse(fb, W, H, fx - 3, fy - 56, 2, 3, gx_rgb(14,12,10)) // eye sockets
155 gx_ellipse(fb, W, H, fx + 3, fy - 56, 2, 3, gx_rgb(14,12,10))
156 gx_px(fb, W, H, fx - 3, fy - 56, cb); gx_px(fb, W, H, fx + 3, fy - 56, cb) // eye-glints
157 gx_px(fb, W, H, fx, fy - 53, gx_rgb(14,12,10)) // nasal cavity
158 gx_px(fb, W, H, fx, fy - 52, gx_rgb(14,12,10))
159 var t: i64 = 0 - 4 // teeth
160 while t <= 4 { gx_px(fb, W, H, fx + t, fy - 50, sf_sh(ca)); if (t % 2) == 0 { gx_px(fb, W, H, fx + t, fy - 49, gx_rgb(14,12,10)) } t = t + 1 }
161 return 0
162}
163
164// ---- PART: ribcage (skeleton torso) ----
165func sf_ribs(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
166 var yy: i64 = fy - 50
167 while yy < fy - 30 { gx_px(fb, W, H, fx, yy, ca); gx_px(fb, W, H, fx - 1, yy, sf_sh(ca)); yy = yy + 1 } // spine
168 var r: i64 = 0
169 while r < 4 {
170 let ry: i64 = fy - 48 + r*4
171 var dx: i64 = 0 - 7
172 while dx <= 7 {
173 let yo: i64 = (dx*dx)/12 // curved rib
174 gx_px(fb, W, H, fx + dx, ry + yo, ca)
175 dx = dx + 1
176 }
177 r = r + 1
178 }
179 return 0
180}
181
182// ---- PART: staff (mage) ----
183func sf_staff(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, ca: i64, cb: i64) -> i64 {
184 let bx: i64 = fx + 13
185 var yy: i64 = fy - 62
186 while yy < fy - 26 { gx_px(fb, W, H, bx, yy, ca); gx_px(fb, W, H, bx - 1, yy, sf_sh(ca)); gx_px(fb, W, H, bx + 1, yy, sf_lt(ca)); yy = yy + 1 } // shaft
187 gx_ellipse(fb, W, H, bx, fy - 64, 4, 4, cb) // orb
188 gx_ellipse(fb, W, H, bx - 1, fy - 65, 2, 2, gx_mix(cb, gx_rgb(255,255,255), 120))
189 return 0
190}
191
192// ---- the dispatcher + composer ----
193func sf_draw_part(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, pid: i64, ca: i64, cb: i64) -> i64 {
194 if pid == 1 { return sf_cape(fb, W, H, fx, fy, ca, cb) }
195 if pid == 2 { return sf_legs(fb, W, H, fx, fy, ca, cb) }
196 if pid == 3 { return sf_torso(fb, W, H, fx, fy, ca, cb) }
197 if pid == 4 { return sf_arms(fb, W, H, fx, fy, ca, cb) }
198 if pid == 5 { return sf_sword(fb, W, H, fx, fy, ca, cb) }
199 if pid == 6 { return sf_pauldrons(fb, W, H, fx, fy, ca, cb) }
200 if pid == 7 { return sf_helm(fb, W, H, fx, fy, ca, cb) }
201 if pid == 8 { return sf_skull(fb, W, H, fx, fy, ca, cb) }
202 if pid == 9 { return sf_ribs(fb, W, H, fx, fy, ca, cb) }
203 if pid == 10 { return sf_staff(fb, W, H, fx, fy, ca, cb) }
204 return 0
205}
206// compose a character from a SPEC = nrec records of [pid, ca, cb] (drawn in order, back-to-front)
207func sf_forge(fb: *i64, W: i64, H: i64, fx: i64, fy: i64, spec: *i64, nrec: i64) -> i64 {
208 gx_ellipse_blend(fb, W, H, fx, fy, 13, 5, gx_rgb(0,0,0), 150) // grounded drop shadow
209 var r: i64 = 0
210 while r < nrec { sf_draw_part(fb, W, H, fx, fy, spec[r*3], spec[r*3+1], spec[r*3+2]); r = r + 1 }
211 return 0
212}
213
214// ---- SPEC DATA (archetypes as data, not code paths) -- fill a caller buffer, return record count ----
215func sf_rec(buf: *i64, i: i64, pid: i64, ca: i64, cb: i64) -> i64 { buf[i*3] = pid; buf[i*3+1] = ca; buf[i*3+2] = cb; return 0 }
216func sf_spec_knight(buf: *i64) -> i64 {
217 let steel: i64 = gx_rgb(120,126,140); let gold: i64 = gx_rgb(214,176,72); let red: i64 = gx_rgb(150,38,42)
218 sf_rec(buf, 0, 1, red, red) // cape
219 sf_rec(buf, 1, 2, steel, gold) // legs
220 sf_rec(buf, 2, 3, steel, gold) // breastplate
221 sf_rec(buf, 3, 4, steel, gold) // arms
222 sf_rec(buf, 4, 6, steel, gold) // pauldrons
223 sf_rec(buf, 5, 5, gx_rgb(190,196,210), gold) // sword
224 sf_rec(buf, 6, 7, steel, red) // helm + red plume
225 return 7
226}
227func sf_spec_skeleton(buf: *i64) -> i64 {
228 let bone: i64 = gx_rgb(206,200,176); let glow: i64 = gx_rgb(120,200,90); let rust: i64 = gx_rgb(120,96,70)
229 sf_rec(buf, 0, 2, bone, rust) // bony legs
230 sf_rec(buf, 1, 9, bone, bone) // ribcage
231 sf_rec(buf, 2, 4, bone, bone) // bony arms
232 sf_rec(buf, 3, 5, gx_rgb(120,110,96), rust) // rusty sword
233 sf_rec(buf, 4, 8, bone, glow) // skull + glowing eyes
234 return 5
235}
236func sf_spec_mage(buf: *i64) -> i64 {
237 let robe: i64 = gx_rgb(54,72,150); let gold: i64 = gx_rgb(214,176,72); let orb: i64 = gx_rgb(90,200,230)
238 sf_rec(buf, 0, 1, robe, robe) // robe back
239 sf_rec(buf, 1, 2, robe, gold) // robe skirt
240 sf_rec(buf, 2, 3, robe, gold) // robe torso
241 sf_rec(buf, 3, 4, robe, gold) // sleeves
242 sf_rec(buf, 4, 10, gold, orb) // staff + orb
243 sf_rec(buf, 5, 7, robe, gold) // hood
244 return 6
245}