shithub: city

ref: 07469015a833ea56db430aed28f004e205549360
dir: /drw.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include "dat.h"
#include "fns.h"

Point EP = {-1,-1};

int scale = 1;
Point pan;
void (*mapdrawfn)(void);
QLock drwlock;
int repaint;

enum{
	Cbg,
	Ctext,
	Cend,
};
static Image *cols[Cend];

Rectangle scrwin;
static Rectangle fbr, hudr;
static Point scrofs, tlwin, fbbufr;
static Image *fb;
static u32int *fbbuf;

static Image *
eallocimage(Rectangle r, ulong chan, int repl, ulong col)
{
	Image *i;

	if((i = allocimage(display, r, chan, repl, col)) == nil)
		sysfatal("allocimage: %r");
	return i;
}

Point
s2p(Point p)
{
	if(!ptinrect(p, scrwin))
		return EP;
	return divpt(subpt(p, scrwin.min), scale);
}

Tile *
p2t(Point p)
{
	p = divpt(p, Tilesz);
	assert(p.x >= 0 && p.x < mapwidth && p.y >= 0 && p.y < mapheight);
	return map + p.y * mapwidth + p.x;
}

Tile *
s2t(Point p)
{
	if(eqpt(p, EP))
		return nil;
	return p2t(p);
}

static int
clippic(Point pp, Pic *pic, Point *scpp, Point *ofs, Point *sz)
{
	int fbbufunsx;
	Point minp, maxp;

	fbbufunsx = fbbufr.x / scale;
	minp = subpt(pp, pan);
	maxp = addpt(minp, pic->picsz);
	if(maxp.x < 0 || maxp.y < 0 || minp.x > fbbufunsx || minp.y > fbbufr.y)
		return -1;
	ofs->x = minp.x < 0 ? -minp.x : 0;
	ofs->y = minp.y < 0 ? -minp.y : 0;
	*sz = subpt(pic->picsz, *ofs);
	if(maxp.x > fbbufunsx)
		sz->x -= maxp.x - fbbufunsx;
	if(maxp.y > fbbufr.y)
		sz->y -= maxp.y - fbbufr.y;
	if(sz->x <= 0 || sz->y <= 0)
		return -1;
	scpp->x = minp.x + ofs->x;
	scpp->y = minp.y + ofs->y;
	return 0;
}

static void
drawpic(Point pp, Pic *pic)
{
	int Δpx, Δx;
	Point scpp, ofs, sz;
	u32int v, *p, *fbp, *fbe;

	if(pic->pic == nil)
		sysfatal("drawpic: empty pic");
	if(clippic(pp, pic, &scpp, &ofs, &sz) < 0)
		return;
	p = pic->pic + ofs.y * pic->picsz.x + ofs.x;
	assert(p < pic->pic + pic->picsz.y * pic->picsz.x);
	Δpx = pic->picsz.x - sz.x;
	sz.x *= scale;
	fbp = fbbuf + scpp.y * fbbufr.x + scpp.x * scale;
	assert(fbp < fbbuf + fbbufr.x * fbbufr.y);
	Δx = fbbufr.x - sz.x;
	while(sz.y-- > 0){
		fbe = fbp + sz.x;
		while(fbp < fbe){
			v = *p++;
			switch(scale){
			case 16: fbp[15] = v;
			case 15: fbp[14] = v;
			case 14: fbp[13] = v;
			case 13: fbp[12] = v;
			case 12: fbp[11] = v;
			case 11: fbp[10] = v;
			case 10: fbp[9] = v;
			case 9: fbp[8] = v;
			case 8: fbp[7] = v;
			case 7: fbp[6] = v;
			case 6: fbp[5] = v;
			case 5: fbp[4] = v;
			case 4: fbp[3] = v;
			case 3: fbp[2] = v;
			case 2: fbp[1] = v;
			default: fbp[0] = v;
			}
			fbp += scale;
		}
		p += Δpx;
		fbp += Δx;
	}
}

void
composeat(Tile *m, u32int c)
{
	int k, n, x, w;
	u32int v, *pp;

	w = fbr.max.x * fbr.max.y / scale;
	pp = fbbuf + (m-map) / mapwidth * w
		+ (m-map) % mapwidth * Tilesz * scale;
	n = Tilesz;
	w = (fbr.max.x / scale - Tilesz) * scale;
	while(n-- > 0){
		x = Tilesz;
		while(x-- > 0){
			v = *pp;
			v = (v & 0xff0000) + (c & 0xff0000) >> 1 & 0xff0000
				| (v & 0xff00) + (c & 0xff00) >> 1 & 0xff00
				| (v & 0xff) + (c & 0xff) >> 1 & 0xff;
			k = scale;
			while(k-- > 0)
				*pp++ = v;
		}
		pp += w;
	}
}

static void
drawhud(void)
{
	char *name;

	draw(screen, hudr, cols[Cbg], nil, ZP);
	if(selected == nil)
		return;
	name = selected->b != nil ? selected->b->name : selected->t->name;
	string(screen, hudr.min, cols[Ctext], ZP, font, name);
}

static void
drawmenuselected(void)
{
	int i, n;
	char s[256], *sp;
	Point p;
	Building *b;

	if(selected == nil)
		return;
	assert(selected >= map && selected < map + mapwidth * mapheight);
	b = buildings + (selected - map);
	if(b >= buildings + nelem(buildings)){
		fprint(2, "nope\n");
		return;
	}
	p = addpt(hudr.min, Pt(0, font->height));
	sp = s;
	sp = seprint(sp, s+sizeof s, "%s time %d cost ",
		b->name, b->buildtime);
	for(i=0, n=0; i<nelem(b->product); i++)
		if(b->buildcost[i] > 0){
			sp = seprint(sp, s+sizeof s, "%s%d %s",
				n > 0 ? "," : "", b->buildcost[i], goods[i].name);
			n++;
		}
	sp = seprint(sp, s+sizeof s, " product ");
	for(i=0, n=0; i<nelem(b->product); i++)
		if(b->product[i] > 0){
			sp = seprint(sp, s+sizeof s, "%s%d %s",
				n > 0 ? "," : "", b->product[i], goods[i].name);
			n++;
		}
	string(screen, p, cols[Ctext], ZP, font, s);
}

static void
drawtile(Tile *m)
{
	Pic *pic;

	pic = m->b != nil ? &m->b->Pic : &m->t->Pic;
	drawpic(tile2xy(m), pic);
}

void
drawbuildings(void)
{
	int x, y;
	Tile *m;
	Building *b;

	for(y=0, m=map, b=buildings; y<tlwin.y; y++){
		for(x=0; x<tlwin.x; x++, m++, b++){
			if(b >= buildings + nelem(buildings))
				return;
			drawpic(tile2xy(m), &b->Pic);
			if(!couldbuild(b))
				composeat(m, 0x555555);
			if(m->stale){
				m->stale = 0;
				repaint = 1;
			}
		}
		m += mapwidth - tlwin.x;
	}
}

static void
drawmap(void)
{
	int x, y;
	Tile *m;

	for(y=0, m=map; y<tlwin.y; y++){
		for(x=0; x<tlwin.x; x++, m++)
			if(m->stale){
				drawtile(m);
				m->stale = 0;
				repaint = 1;
			}
		m += mapwidth - tlwin.x;
	}
}

void
drawbuildmenu(void)
{
	drawbuildings();
	drawmenuselected();
}

static void
redrawcanvas(void)
{
	int x, y;
	Tile *m;

	memset(fbbuf, 0, fbbufr.x * fbbufr.y * sizeof *fbbuf);
	for(y=0, m=map; y<tlwin.y; y++){
		for(x=0; x<tlwin.x; x++, m++)
			m->stale = 1;
		m += mapwidth - tlwin.x;
	}
}

void
updatedraw(int all)
{
	qlock(&drwlock);
	if(all || repaint)
		redrawcanvas();
	(mapdrawfn != nil ? mapdrawfn : drawmap)();
	qunlock(&drwlock);
	flushfb();
	repaint = 0;
}

void
flushfb(void)
{
	uchar *p;
	Rectangle r, r2;

	lockdisplay(display);
	p = (uchar *)fbbuf;
	if(scale == 1){
		loadimage(fb, fb->r, p, fbbufr.x * fbbufr.y * sizeof *fbbuf);
		draw(screen, screen->r, fb, nil, scrofs);
	}else{
		r = rectaddpt(fbr, subpt(screen->r.min, scrofs));
		r2 = r;
		while(r.min.y < r2.max.y){
			r.max.y = r.min.y + scale;
			p += loadimage(fb, fb->r, p, fbbufr.x * sizeof *fbbuf);
			draw(screen, r, fb, nil, ZP);
			r.min.y = r.max.y;
		}
	}
	drawhud();
	flushimage(display, 1);
	unlockdisplay(display);
}

void
resetdraw(void)
{
	int sw, sh;

	sw = Dx(screen->r);
	sh = Dy(screen->r);
	/* fb rect in pixels cropped to fit window */
	fbr.min = ZP;
	fbr.max.x = min(sw, mapwidth * Tilesz * scale);
	fbr.max.y = min(sh, mapheight * Tilesz * scale);
	/* actual drawing fb size, scaled horizontally only */
	fbbufr.x = fbr.max.x;
	fbbufr.y = fbr.max.y / scale;
	/* negative offset to translate the framebuffer
	 * towards the lower right corner (see draw(2)) */
	scrofs.x = -(sw > fbr.max.x ? (sw - fbr.max.x) / 2 : 0);
	scrofs.y = -(sh > fbr.max.y ? (sh - fbr.max.y) / 2 : 0);
	/* tile window in tiles cropped to fit window */
	tlwin.x = min(mapwidth, fbr.max.x / scale / Tilesz + 1);
	tlwin.y = min(mapheight, fbr.max.y / scale / Tilesz + 1);
	/* absolute tile window rect in pixels */
	scrwin.min = subpt(screen->r.min, scrofs);
	scrwin.max = addpt(scrwin.min, fbr.max);
	/* absolute hud rect in pixels */
	hudr.min = addpt(scrwin.max, Pt(-fbr.max.x, font->height));
	hudr.max = addpt(hudr.min, Pt(fbr.max.x, screen->r.max.y - hudr.min.y));
	freeimage(fb);
	fb = eallocimage(Rect(0,0,fbr.max.x,scale==1 ? fbr.max.y : 1),
		XRGB32, scale>1, DNofill);
	free(fbbuf);
	fbbuf = emalloc(fbbufr.x * fbbufr.y * sizeof *fbbuf);
	if(!eqpt(scrofs, ZP))
		draw(screen, screen->r, cols[Cbg], nil, ZP);
	updatedraw(1);
}

void
initdrw(void)
{
	if(initdraw(nil, nil, "city") < 0)
		sysfatal("initdraw: %r");
	display->locking = 1;
	unlockdisplay(display);
	cols[Cbg] = eallocimage(Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
	cols[Ctext] = display->black;
	fmtinstall('P', Pfmt);
	fmtinstall('R', Rfmt);
}