shithub: libgraphics

ref: cdfd05b4e5b4ffedae1a4fffa46f1217a88d09ee
dir: /texture.c/

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
#include "graphics.h"
#include "internal.h"

enum {
	CUBEMAP_FACE_LEFT,	/* -x */
	CUBEMAP_FACE_RIGHT,	/* +x */
	CUBEMAP_FACE_BOTTOM,	/* -y */
	CUBEMAP_FACE_TOP,	/* +y */
	CUBEMAP_FACE_FRONT,	/* -z */
	CUBEMAP_FACE_BACK,	/* +z */
};

/*
 * uv-coords belong to the 1st quadrant (v grows bottom-up),
 * hence the need to reverse the v coord.
 */
static Point
uv2tp(Point2 uv, Texture *t)
{
	uv.x = fclamp(uv.x, 0, 1);
	uv.y = fclamp(uv.y, 0, 1);
	return Pt(uv.x*Dx(t->image->r), (1 - uv.y)*Dy(t->image->r));
}

static Color
cbuf2col(uchar b[4])
{
	Color c;

	c.a = b[0] / 255.0;
	c.b = b[1] / 255.0;
	c.g = b[2] / 255.0;
	c.r = b[3] / 255.0;
	return c;
}

static Color
_memreadcolor(Texture *t, Point sp)
{
	Color c;
	uchar cbuf[4];

	switch(t->image->chan){
	default: sysfatal("unsupported texture format");
	case RGB24:
		unloadmemimage(t->image, rectaddpt(UR, sp), cbuf+1, sizeof cbuf - 1);
		cbuf[0] = 0xFF;
		break;
	case RGBA32:
		unloadmemimage(t->image, rectaddpt(UR, sp), cbuf, sizeof cbuf);
		break;
	case XRGB32:
		unloadmemimage(t->image, rectaddpt(UR, sp), cbuf, sizeof cbuf);
		memmove(cbuf+1, cbuf, 3);
		cbuf[0] = 0xFF;
		break;
	}

	c = cbuf2col(cbuf);
	/* remove pre-multiplied alpha */
	if(hasalpha(t->image->chan) && c.a > 0 && c.a < 1){
		c.r /= c.a;
		c.g /= c.a;
		c.b /= c.a;
	}
	switch(t->type){
	case sRGBTexture: c = srgb2linear(c); break;
	}
	return c;
}

/*
 * nearest-neighbour sampler
 */
Color
neartexsampler(Texture *t, Point2 uv)
{
	return _memreadcolor(t, uv2tp(uv, t));
}

/*
 * bilinear sampler
 */
Color
bilitexsampler(Texture *t, Point2 uv)
{
	Rectangle r;
	Color c1, c2;

	r = rectaddpt(UR, uv2tp(uv, t));
	if(r.min.x < t->image->r.min.x){
		r.min.x++;
		r.max.x++;
	}if(r.min.y < t->image->r.min.y){
		r.min.y++;
		r.max.y++;
	}if(r.max.x >= t->image->r.max.x){
		r.min.x--;
		r.max.x--;
	}if(r.max.y >= t->image->r.max.y){
		r.min.y--;
		r.max.y--;
	}
	c1 = lerp3(_memreadcolor(t, r.min), _memreadcolor(t, Pt(r.max.x, r.min.y)), 0.5);
	c2 = lerp3(_memreadcolor(t, Pt(r.min.x, r.max.y)), _memreadcolor(t, r.max), 0.5);
	return lerp3(c1, c2, 0.5);
}

Color
sampletexture(Texture *t, Point2 uv, Color(*sampler)(Texture*,Point2))
{
	return sampler(t, uv);
}

Texture *
alloctexture(int type, Memimage *i)
{
	Texture *t;

	t = emalloc(sizeof *t);
	memset(t, 0, sizeof *t);
	t->image = i;
	t->type = type;
	return t;
}

Texture *
duptexture(Texture *t)
{
	if(t == nil)
		return nil;
	return alloctexture(t->type, dupmemimage(t->image));
}

void
freetexture(Texture *t)
{
	if(t == nil)
		return;

	freememimage(t->image);
	free(t);
}

/* cubemap sampling */

Cubemap *
readcubemap(char *paths[6])
{
	Cubemap *cm;
	Memimage *i;
	char **p;
	int fd;

	cm = emalloc(sizeof *cm);
	memset(cm, 0, sizeof *cm);
	
	for(p = paths; p < paths+6; p++){
		assert(*p != nil);
		fd = open(*p, OREAD);
		if(fd < 0)
			sysfatal("open: %r");
		i = readmemimage(fd);
		if(i == nil)
			sysfatal("readmemimage: %r");
		cm->faces[p-paths] = alloctexture(sRGBTexture, i);
		close(fd);
	}
	return cm;
}

Cubemap *
dupcubemap(Cubemap *cm)
{
	Cubemap *ncm;
	int i;

	if(cm == nil)
		return nil;

	ncm = emalloc(sizeof *ncm);
	memset(ncm, 0, sizeof *ncm);
	if(cm->name != nil)
		ncm->name = strdup(cm->name);
	for(i = 0; i < 6; i++)
		ncm->faces[i] = duptexture(cm->faces[i]);
	return ncm;
}

void
freecubemap(Cubemap *cm)
{
	int i;

	if(cm == nil)
		return;

	for(i = 0; i < 6; i++)
		freetexture(cm->faces[i]);
	free(cm->name);
	free(cm);
}

/*
 * references:
 * 	- https://github.com/zauonlok/renderer/blob/9ed5082f0eda453f0b2a0d5ec37cf5a60f0207f6/renderer/core/texture.c#L206
 * 	- “Cubemap Texture Selection”, OpenGL ES 2.0 § 3.7.5, November 2010
 */
Color
samplecubemap(Cubemap *cm, Point3 d, Color(*sampler)(Texture*,Point2))
{
	Point2 uv;
	double ax, ay, az, ma, sc, tc;
	int face;

	ax = fabs(d.x);
	ay = fabs(d.y);
	az = fabs(d.z);

	if(ax > ay && ax > az){
		ma = ax;
		if(d.x > 0){
			face = CUBEMAP_FACE_RIGHT;
			sc = -d.z;
			tc = -d.y;
		}else{
			face = CUBEMAP_FACE_LEFT;
			sc =  d.z;
			tc = -d.y;
		}
	}else if(ay > az){
		ma = ay;
		if(d.y > 0){
			face = CUBEMAP_FACE_TOP;
			sc = d.x;
			tc = d.z;
		}else{
			face = CUBEMAP_FACE_BOTTOM;
			sc =  d.x;
			tc = -d.z;
		}
	}else{
		ma = az;
		if(d.z > 0){
			face = CUBEMAP_FACE_BACK;
			sc =  d.x;
			tc = -d.y;
		}else{
			face = CUBEMAP_FACE_FRONT;
			sc = -d.x;
			tc = -d.y;
		}
	}

	uv.x = (sc/ma + 1)/2;
	uv.y = 1 - (tc/ma + 1)/2;
	uv.w = 1;
	return sampler(cm->faces[face], uv);
}