shithub: sce

Download patch

ref: 70b5711e8aca15868a92b193111365f81b1f6fab
parent: 785ecb8bfe4bb0bdce98b43aa2c57037150c74f4
author: qwx <qwx@sciops.net>
date: Thu Apr 15 13:49:42 EDT 2021

add utils/vspr: extracted sprite viewer

so far, i have not been able to understand how to reconcile
r.min offsets from extracted grp's, L/R/U/D and w·h dimensions
from specs in an automated manner.

the current strategy is to create a large map in staredit,
paste units aligned to the 32x32 grid (upper left corners),
take screenshots, and compare.  it's tedious, but i see no
better way to get reference sprite offsets.
vspr loads given unit sprite frames and all their rotations
once they have been extracted and processed, and shows them all,
allowing cycling through animations and rotating, which should
catch many common errors previously found.  hopefully, a pattern
will emerge.  there currently are errors in drone/scv sprites
since they were based on ingame screenshots with building grids,
which is not at all reliable due to the map's visualization.

--- a/utils/mkfile
+++ b/utils/mkfile
@@ -2,6 +2,7 @@
 TARG=\
 	grp\
 	sctile\
+	vspr\
 
 HFILES=
 </sys/src/cmd/mkmany
--- /dev/null
+++ b/utils/vspr.c
@@ -1,0 +1,256 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <mouse.h>
+
+enum{
+	Nodesz = 32,
+	Nspr = 8,
+	Nrot = 17,
+	Ndt = 50,
+};
+
+Keyboardctl *kc;
+Mousectl *mc;
+Channel *tmc;
+int pause;
+QLock pauselck;
+int Δt = 1000;
+Point pan, center, shadofs;
+int frm, nfrm, rot, nrot, cansz;
+char *name;
+Image *canvas, *gridcol, *selcol, *bgcol, **imtab, **shtab;
+
+int rot17[Nrot] = {
+	0, 2, 4, 6, 8, 10, 12, 14, 16, 17, 19, 21, 23, 25, 27, 29, 31
+};
+
+void
+resetdraw(void)
+{
+	freeimage(canvas);
+	if((canvas = allocimage(display, Rect(0,0,Dx(screen->r),Dy(screen->r)), screen->chan, 0, DNofill)) == nil)
+		sysfatal("allocimage: %r");
+	cansz = canvas->r.max.x > canvas->r.max.y ? canvas->r.max.x : canvas->r.max.y;
+	center = (Point){Nodesz * (Dx(canvas->r)/Nodesz / 2), Nodesz * (Dy(canvas->r)/Nodesz / 2)};
+}
+
+void
+redraw(void)
+{
+	int n;
+	char s[128];
+	Image *ui, *us;
+	Point p, o;
+	Rectangle r;
+
+	o = addpt(center, pan);
+	draw(canvas, canvas->r, bgcol, nil, ZP);
+	ui = imtab[nrot * frm + rot];
+	us = nil;
+	if(shtab != nil){
+		us = shtab[nrot * frm + rot];
+		draw(canvas, rectaddpt(us->r, addpt(o, shadofs)), us, us, us->r.min);
+	}
+	r = ui->r;
+	r.max.y = r.min.y + Dy(r) / Nspr;
+	draw(canvas, rectaddpt(r, o), ui, ui, ui->r.min);
+	for(n=Nodesz; n<cansz; n+=Nodesz){
+		line(canvas, Pt(n,0), Pt(n,canvas->r.max.y), 0, 0, 0, gridcol, ZP);
+		line(canvas, Pt(0,n), Pt(canvas->r.max.x,n), 0, 0, 0, gridcol, ZP);
+	}
+	p = addpt(ui->r.min, o);
+	line(canvas, p, Pt(p.x+Nodesz, p.y), 0, 0, 0, selcol, ZP);
+	line(canvas, p, Pt(p.x, p.y+Nodesz), 0, 0, 0, selcol, ZP);
+	line(canvas, Pt(p.x, p.y+Nodesz), Pt(p.x+Nodesz, p.y+Nodesz), 0, 0, 0, selcol, ZP);
+	line(canvas, Pt(p.x+Nodesz, p.y), Pt(p.x+Nodesz, p.y+Nodesz), 0, 0, 0, selcol, ZP);
+	if(us != nil)
+		snprint(s, sizeof s, "%s frm %02d size %R sha %R", name, frm, r, us->r);
+	else
+		snprint(s, sizeof s, "%s frm %02d size %R", name, frm, r);
+	string(canvas, Pt(8,0), gridcol, ZP, font, s);
+	snprint(s, sizeof s, "ofs %P shofs %P Δt %d", pan, shadofs, Δt);
+	string(canvas, Pt(8,font->height), gridcol, ZP, font, s);
+	draw(screen, screen->r, canvas, nil, ZP);
+	flushimage(display, 1);
+}
+
+Image *
+loadframe(char *frm, int rot, int shad)
+{
+	int n, fd;
+	char *path;
+	uchar *buf, *p;
+	Image *i, *im;
+
+	if((path = smprint("/sys/games/lib/sce/%s.%s.%02ud%s.bit", name, frm, rot, shad?".s":"")) == nil)
+		sysfatal("mprint: %r");
+	if((fd = open(path, OREAD)) < 0)
+		sysfatal("open: %r");
+	if((i = readimage(display, fd, 0)) == nil)
+		sysfatal("readimage: %r");
+	close(fd);
+	if(i->depth == 32)
+		goto end;
+	im = i;
+	if((i = allocimage(display, im->r, ARGB32, 0, DTransparent)) == nil)
+		sysfatal("allocimage: %r");
+	draw(i, i->r, im, nil, i->r.min);
+	freeimage(im);
+	n = Dy(i->r) * bytesperline(i->r, i->depth) * 2;
+	if((buf = malloc(n)) == nil)
+		sysfatal("malloc: %r");
+	unloadimage(i, i->r, buf, n);
+	for(p=buf; p<buf+n; p+=4)
+		if((p[2] << 16 | p[1] << 8 | p[0]) == 0x00ffff)
+			p[3] = 0;
+	loadimage(i, i->r, buf, n);
+	free(buf);
+end:
+	free(path);
+	return i;
+}
+
+void
+setpause(void)
+{
+	if(pause ^= 1)
+		qlock(&pauselck);
+	else
+		qunlock(&pauselck);
+}
+
+void
+timeproc(void *)
+{
+	for(;;){
+		qlock(&pauselck);
+		nbsendul(tmc, 0);
+		sleep(Δt);
+		qunlock(&pauselck);
+	}
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-rs] name frame..\n", argv0);
+	threadexits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	int n, nr, shad;
+	Image **i, **s;
+	Mouse m;
+	Rune r;
+
+	shad = 1;
+	nrot = Nrot;
+	ARGBEGIN{
+	case 'r': nrot = 1; break;
+	case 's': shad = 0; break;
+	default: usage();
+	}ARGEND
+	if(argc < 2)
+		usage();
+	if(initdraw(nil, nil, "vspr") < 0)
+		sysfatal("initdraw: %r");
+	name = *argv++;
+	argc--;
+	nfrm = argc;
+	if((imtab = malloc(nfrm * nrot * sizeof *imtab)) == nil)
+		sysfatal("malloc: %r");
+	if(shad && (shtab = malloc(nfrm * nrot * sizeof *shtab)) == nil)
+		sysfatal("malloc: %r");
+	i = imtab;
+	s = shtab;
+	while(*argv != nil){
+		for(n=0; n<nrot; n++){
+			nr = nrot == Nrot ? rot17[n] : n;
+			*i++ = loadframe(*argv, nr, 0);
+			if(shad)
+				*s++ = loadframe(*argv, nr, 1);
+		}
+		argv++;
+	}
+	fmtinstall('P', Pfmt);
+	fmtinstall('R', Rfmt);
+	resetdraw();
+	gridcol = display->black;
+	if((bgcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x00ffffff)) == nil
+	|| (selcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DGreen)) == nil)
+		sysfatal("allocimage: %r");
+	redraw();
+	if((kc = initkeyboard(nil)) == nil)
+		sysfatal("initkeyboard: %r");
+	if((mc = initmouse(nil, screen)) == nil)
+		sysfatal("initmouse: %r");
+	m.xy = ZP;
+	if((tmc = chancreate(sizeof(ulong), 0)) == nil)
+		sysfatal("chancreate: %r");
+	if(proccreate(timeproc, nil, 8192) < 0)
+		sysfatal("proccreate: %r");
+	Alt a[] = {
+		{mc->resizec, nil, CHANRCV},
+		{mc->c, &mc->Mouse, CHANRCV},
+		{kc->c, &r, CHANRCV},
+		{tmc, nil, CHANRCV},
+		{nil, nil, CHANEND}
+	};
+	for(;;){
+		switch(alt(a)){
+		case 0:
+			if(getwindow(display, Refnone) < 0)
+				sysfatal("getwindow: %r");
+			m = mc->Mouse;
+			resetdraw();
+			redraw();
+			break;
+		case 1:
+			if(eqpt(m.xy, ZP))
+				m = mc->Mouse;
+			if((mc->buttons & 1) == 1 && !eqpt(m.xy, ZP)){
+				pan.x += mc->xy.x - m.xy.x;
+				pan.y += mc->xy.y - m.xy.y;
+				redraw();
+			}
+			m = mc->Mouse;
+			break;
+		case 2:
+			switch(r){
+			case '0': case '1': case '2': case '3': case '4':
+			case '5': case '6': case '7': case '8': case '9':
+				r -= '0';
+				if(r < nfrm){
+					if(!pause)
+						setpause();
+					frm = r;
+					redraw();
+				}
+				break;
+			case '=':
+			case '+': Δt += Ndt; break;
+			case '-': if(Δt > Ndt) Δt -= Ndt; break;
+			case 'r':
+			case Kesc: pan = ZP; redraw(); break;
+			case ' ': setpause(); break;
+			case Kup: shadofs.y -= 1; redraw(); break;
+			case Kdown: shadofs.y += 1; redraw(); break;
+			case Kright: rot = (rot + 1) % nrot; break;
+			case Kleft: if(--rot < 0) rot = nrot - 1; break;
+			case Kdel: case 'q': threadexitsall(nil);
+			}
+			break;
+		case 3:
+			frm = (frm + 1) % nfrm;
+			redraw();
+			break;
+		default:
+			threadexitsall("alt: %r");
+		}
+	}
+}