shithub: sce

Download patch

ref: a1c6005292984c6c2c6327cf4f6dfa1b5b2e72cb
parent: f6b64cc388c12bb270d6804e0f712026be366f6c
author: qwx <qwx@sciops.net>
date: Thu Aug 27 18:08:00 EDT 2020

fs: improve image files format, storage and handling

individual images were stores as individual files as compressed
plan 9 images.
this results in a combinatorial explosion of files any time a unit is added.
one unit typically has 8 team colors, 32 rotations, shadows for each
rotation, and potentially multiple frames, meaning even more rotations
and shadows.
so, for command center, hatchery, scv and drones, with just idle and
movement sprites, we exceed 1700 files.
an obvious additional consequence is ridiculous loading times at startup.

units may have 32 rotations internally, but they actually only have 17
sprites: 8 images [0°,180°[ duplicated once, a single 180° sprite,
7 mirrored images ]180°, and another unique sprite for the 32nd rotation.
in addition, images for each of the 8 teams all have the same dimensions
and offsets.
it would still be nice to have to avoid having special formats or more
metadata, so to avoid dealing with offsets, we don't bundle shadow
sprites.
shadows also may have different dimensions.
however, we can bundle team colors and only store 17 rotations,
selecting the correct one in the code.
we can also concatenate tileset images, all 32x32.
this reduces the current file count to about 210.
bundling images together in compressed bitmaps reduces space somewhat.

it would still be nice to find something better since we'll reach the
same problem when we add more and more units and frames.

--- a/dat.h
+++ b/dat.h
@@ -63,9 +63,9 @@
 };
 
 enum{
-	PFterrain = 0,
-	PFidle = 1<<0,
-	PFmove = 2<<0,
+	PFterrain = 1<<0,
+	PFidle = 1<<1,
+	PFmove = 1<<2,
 	PFshadow = 1<<15,
 };
 struct Pic{
@@ -76,7 +76,7 @@
 	int dy;
 };
 struct Pics{
-	Pic **p;
+	Pic **pic;
 	Pic **shadow;
 	int nf;
 	int nr;
--- a/drw.c
+++ b/drw.c
@@ -261,16 +261,26 @@
 static Pic *
 frm(Mobj *mo, int notshadow)
 {
+	static int rot17[Nrot] = {
+		0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,
+		9,9,10,10,11,11,12,12,13,13,14,14,15,15,16
+	};
+	int θ;
 	Pics *pp;
 	Pic *p;
 
 	pp = mo->pics;
+	switch(pp->nr){
+	case 17: θ = rot17[mo->θ]; break;
+	default: θ = 0; break;
+	}
 	if(notshadow){
-		p = pp->p[tc % pp->nf];
-		p += pp->nr * (mo->team-1);
-	}else
+		p = pp->pic[tc % pp->nf];
+		p += nteam * θ + mo->team - 1;
+	}else{
 		p = pp->shadow[tc % pp->nf];
-	p += mo->θ / (Nrot / pp->nr);
+		p += θ;
+	}
 	return p;
 }
 
--- a/fs.c
+++ b/fs.c
@@ -26,7 +26,7 @@
 	int row;
 };
 struct Picl{
-	int id;
+	int frm;
 	int type;
 	char *name;
 	char iname[64];
@@ -40,6 +40,7 @@
 	Terrainl *l;
 };
 static Terrainl terrainl0 = {.l = &terrainl0}, *terrainl = &terrainl0;
+static Pic tilesetpic;
 static Picl pic0 = {.l = &pic0}, *pic = &pic0;
 static Objp *objp;
 static Attack *attack;
@@ -47,6 +48,9 @@
 static char *tileset;
 static int nattack, nobj, nresource, nobjp;
 static u32int bgcol = 0x00ffff;
+static int rot17idx[17] = {
+	0,2,4,6,8,10,12,14,16,17,19,21,23,25,27,29,31
+};
 
 static void
 loadpic(char *name, Pic *pic)
@@ -87,48 +91,99 @@
 	free(b);
 }
 
+static void
+loadshadpic(Pic *pic, Picl *pl)
+{
+	int i;
+	char path[128];
+
+	for(i=0; i<pl->nr; i++){
+		snprint(path, sizeof path, "%s.%02d.%02d.s.bit",
+			pl->name, pl->frm, rot17idx[i]);
+		loadpic(path, pic++);
+	}
+}
+
+static void
+loadobjpic(Pic *pic, Picl *pl)
+{
+	int n, i, j;
+	char path[128];
+	u32int *p0;
+	Pic pic0;
+
+	for(i=0; i<pl->nr; i++){
+		snprint(path, sizeof path, "%s.%02d.%02d.bit",
+			pl->name, pl->frm, rot17idx[i]);
+		loadpic(path, &pic0);
+		if(pic0.h % Nteam != 0)
+			sysfatal("loadobjpic: obj %s sprite sheet %d,%d: height not multiple of %d\n",
+				pl->name, pic0.w, pic0.h, Nteam);
+		pic0.h /= Nteam;
+		n = pic0.w * pic0.h;
+		/* nteam has been set by now, no point in retaining sprites
+		 * for additional teams */
+		for(j=0, p0=pic0.p; j<nteam; j++, p0+=n, pic++){
+			memcpy(pic, &pic0, sizeof *pic);
+			pic->p = emalloc(n * sizeof *pic->p);
+			memcpy(pic->p, p0, n * sizeof *pic->p);
+		}
+		free(pic0.p);
+	}
+}
+
+static void
+loadterpic(Pic *pic, Picl *pl)
+{
+	int id, size;
+	char path[128];
+
+	if(tilesetpic.p == nil){
+		snprint(path, sizeof path, "%s.bit", tileset);
+		loadpic(path, &tilesetpic);
+		if(tilesetpic.h % tilesetpic.w != 0)
+			sysfatal("loadterpic: tiles not squares: tilepic %d,%d\n",
+				tilesetpic.w, tilesetpic.h);
+	}
+	id = pl->frm;
+	size = tilesetpic.w;
+	if(size * id >= tilesetpic.h)
+		sysfatal("loadterpic: terrain tile index %d out of bounds", id);
+	pic->w = size;
+	pic->h = size;
+	size *= size;
+	pic->p = emalloc(size * sizeof *pic->p);
+	memcpy(pic->p, tilesetpic.p + size * id, size * sizeof *pic->p);
+}
+
 void
 initimg(void)
 {
-	int i, r;
-	char path[128];
 	Pic *p;
 	Picl *pl;
 
 	for(pl=pic->l; pl!=pic; pl=pic->l){
 		p = pl->p;
-		if(pl->type == PFterrain){
-			snprint(path, sizeof path, "%s.%05d.bit", tileset, pl->id);
-			loadpic(path, p);
-		}else if(pl->type & PFshadow){
-			for(r=0; r<pl->nr; r++){
-				snprint(path, sizeof path,
-					"%ss.%02d.%02d.bit",
-					pl->name, pl->id, r);
-				loadpic(path, p++);
-			}
-		}else{
-			for(i=0; i<nteam; i++)
-				for(r=0; r<pl->nr; r++){
-					snprint(path, sizeof path,
-						"%s%d.%02d.%02d.bit",
-						pl->name, i+1, pl->id, r);
-					loadpic(path, p++);
-				}
-		}
+		if(pl->type & PFterrain)
+			loadterpic(p, pl);
+		else if(pl->type & PFshadow)
+			loadshadpic(p, pl);
+		else
+			loadobjpic(p, pl);
 		pic->l = pl->l;
 		free(pl);
 	}
+	free(tilesetpic.p);
 }
 
 static Pic *
-pushpic(char *name, int id, int type, int nr)
+pushpic(char *name, int frm, int type, int nr)
 {
 	int n;
 	char iname[64];
 	Picl *pl;
 
-	snprint(iname, sizeof iname, "%s%d%02ux", name, id, type);
+	snprint(iname, sizeof iname, "%s%02d%02ux", name, frm, type);
 	for(pl=pic->l; pl!=pic; pl=pl->l)
 		if(strcmp(iname, pl->iname) == 0)
 			break;
@@ -135,12 +190,15 @@
 	if(pl == pic){
 		pl = emalloc(sizeof *pl);
 		memcpy(pl->iname, iname, nelem(pl->iname));
-		pl->id = id;
+		pl->frm = frm;
 		pl->type = type;
 		pl->name = name;
 		pl->nr = nr;
+		if(nr != 17 && nr != 1)
+			sysfatal("pushpic %s: invalid number of rotations", iname);
 		n = nr;
-		if((type & PFshadow) == 0)
+		/* nteam isn't guaranteed to be set correctly by now */
+		if((type & (PFshadow|PFterrain)) == 0)
 			n *= Nteam;
 		pl->p = emalloc(n * sizeof *pl->p);
 		pl->l = pic->l;
@@ -161,7 +219,7 @@
 		tl = emalloc(sizeof *tl);
 		tl->id = id;
 		tl->t = emalloc(sizeof *tl->t);
-		tl->t->p = pushpic(",", id, PFterrain, 1);
+		tl->t->p = pushpic("/tile/", id - 1, PFterrain, 1);
 		tl->l = terrainl->l;
 		terrainl->l = tl;
 	}
@@ -342,34 +400,34 @@
 static void
 readspr(char **fld, int n, Table *)
 {
-	int type, id, nr;
+	int type, frm, nr;
 	Obj *o;
 	Pics *ps;
 	Pic ***ppp, **p, **pe;
 
 	if(n < 4)
-		sysfatal("readspr: %d fields < 4 mandatory columns", n);
+		sysfatal("readspr %s: %d fields < 4 mandatory columns", o->name, n);
 	unpack(fld, "odd", &o, &type, &nr);
 	fld += 3;
 	n -= 3;
 	ps = nil;
-	switch(type & 0x7f){
+	switch(type & 0x7e){
 	case PFidle: ps = &o->pidle; break;
 	case PFmove: ps = &o->pmove; break;
-	default: sysfatal("readspr: invalid type %#02ux", type & 0x7f);
+	default: sysfatal("readspr %s: invalid type %#02ux", o->name, type & 0x7e);
 	}
-	ppp = type & PFshadow ? &ps->shadow : &ps->p;
+	ppp = type & PFshadow ? &ps->shadow : &ps->pic;
 	if(*ppp != nil)
-		sysfatal("readspr: %s pic type %#ux already allocated", o->name, type);
+		sysfatal("readspr %s: pic type %#ux already allocated", o->name, type);
 	if(ps->nf != 0 && ps->nf != n || ps->nr != 0 && ps->nr != nr)
-		sysfatal("readspr: %s spriteset phase error", o->name);
+		sysfatal("readspr %s: spriteset phase error", o->name);
 	ps->nf = n;
 	ps->nr = nr;
 	p = emalloc(n * sizeof *ppp);
 	*ppp = p;
 	for(pe=p+n; p<pe; p++){
-		unpack(fld++, "d", &id);
-		*p = pushpic(o->name, id, type, nr);
+		unpack(fld++, "d", &frm);
+		*p = pushpic(o->name, frm, type, nr);
 	}
 }
 
--- a/sce/sce.db
+++ b/sce/sce.db
@@ -13,13 +13,13 @@
 # spawn: objname, [obj..]
 spawn,control,scv
 # spr: objname, flags (PF enum), rotations, [frame..]
-spr,scv,1,32,0
-spr,scv,0x8001,32,0
-spr,control,1,1,0
-spr,control,0x8001,1,0
-spr,drone,1,32,0
-spr,drone,2,32,0,1,2,3,4
-spr,drone,0x8001,32,0
-spr,drone,0x8002,32,0,1,2,3,4
-spr,hatchery,1,1,0,1,2,3,3,2,1,0
-spr,hatchery,0x8001,1,0,0,0,0,0,0,0,0
+spr,scv,2,17,0
+spr,scv,0x8002,17,0
+spr,control,2,1,0
+spr,control,0x8002,1,0
+spr,drone,2,17,0
+spr,drone,4,17,0,1,2,3,4
+spr,drone,0x8002,17,0
+spr,drone,0x8004,17,0,1,2,3,4
+spr,hatchery,2,1,0,1,2,3,3,2,1,0
+spr,hatchery,0x8002,1,0,0,0,0,0,0,0,0
--- a/sim.c
+++ b/sim.c
@@ -108,7 +108,7 @@
 	}
 	mo->movingp = linkmobj(moving, mo, mo->movingp);
 	mo->pathp = mo->paths;
-	mo->pics = mo->o->pmove.p != nil ? &mo->o->pmove : &mo->o->pidle;
+	mo->pics = mo->o->pmove.pic != nil ? &mo->o->pmove : &mo->o->pidle;
 	nextmove(mo);
 	return 0;
 }
--- /dev/null
+++ b/utils/scefix
@@ -1,0 +1,93 @@
+#!/bin/rc
+rfork n
+
+fn terrain{
+	name=$1
+	shift
+	echo $name `{ls $name^.*.bit | wc -l} |\
+		awk '
+{
+	n = int($2)
+	print "!s", 32, 32*n
+	print "r = z == 0 ? 0 : Z"
+	for(i=0; i<n; i++){
+		printf "!r %s.%05d.bit a\n", $1, i+1
+		print "r = y >= 32*" i " && y < 32*" i+1 " ? a[x,y-32*" i "] : r"
+	}
+	print "!w r /tmp/a.bit"
+}
+' >/env/fuckrc
+	$home/p/pico/pico </env/fuckrc
+	iconv -c r8g8b8 /tmp/a.bit >$name^.bit
+	rm /tmp/a.bit $name^.*.bit
+}
+
+fn sprsheet{
+	name=$1
+	frm=$2
+	rot=$3
+	dim=`{read -c 72 $name^1.$frm.$rot.bit | awk 'NR>1{print $2, $3, $4, $5}'}
+	x1=$dim(1)
+	y1=$dim(2)
+	x2=$dim(3)
+	y2=$dim(4)
+	dy=`{echo $y2-$y1 | pc -n}
+	crop -b 0 255 255 -r $x1 $y1 $x2 \
+		`{echo $y1^'+'^$dy^'*8' | pc -n} \
+		$name^1.$frm.$rot.bit >/tmp/a.bit
+	sed 's/NAME/'^$name^'/g;s/FRM/'^$frm^'/g;s/ROT/'^$rot^'/g;s/DY/'^$dy^'/g' \
+		/env/fuckrc >/env/forever
+	</env/forever $home/p/pico/pico
+	iconv -c r8g8b8 /tmp/b.bit |\
+		crop -t $x1 $y1 \
+		>$name.$frm.$rot.bit
+	mv $name^s.$frm.$rot.bit $name.$frm.$rot.s.bit
+	rm /tmp/b.bit
+}
+
+fn gen32{
+	name=$1
+	shift
+	for(frm in $*){
+		for(rot in 00 02 04 06 08 10 12 14 16 17 19 21 23 25 27 29 31)
+			sprsheet $name $frm $rot
+		rm $name^?.$frm.*.bit
+	}
+	rm /tmp/a.bit
+}
+
+fn gen1{
+	name=$1
+	shift
+	for(frm in $*){
+		sprsheet $name $frm 00
+		rm $name^?.$frm.*.bit
+	}
+	rm /tmp/a.bit
+}
+
+cat <<! >/env/fuckrc
+!r /tmp/a.bit a
+r = a
+!r NAME2.FRM.ROT.bit b
+r = y >= DY*1 && y < DY*2 ? b[x,y-DY*1] : r
+!r NAME3.FRM.ROT.bit b
+r = y >= DY*2 && y < DY*3 ? b[x,y-DY*2] : r
+!r NAME4.FRM.ROT.bit b
+r = y >= DY*3 && y < DY*4 ? b[x,y-DY*3] : r
+!r NAME5.FRM.ROT.bit b
+r = y >= DY*4 && y < DY*5 ? b[x,y-DY*4] : r
+!r NAME6.FRM.ROT.bit b
+r = y >= DY*5 && y < DY*6 ? b[x,y-DY*5] : r
+!r NAME7.FRM.ROT.bit b
+r = y >= DY*6 && y < DY*7 ? b[x,y-DY*6] : r
+!r NAME8.FRM.ROT.bit b
+r = y >= DY*7 && y < DY*8 ? b[x,y-DY*7] : r
+!w r /tmp/b.bit
+!
+
+gen32 scv 00
+gen32 drone 00 01 02 03 04
+gen1 control 00
+gen1 hatchery 00 01 02 03
+terrain badlands