code:
#define WITHD3D
#include "common.h"

#ifdef RW_D3D9
#include "main.h"
#include "RwHelper.h"
#include "Lights.h"
#include "Timecycle.h"
#include "FileMgr.h"
#include "Clock.h"
#include "Weather.h"
#include "TxdStore.h"
#include "Renderer.h"
#include "World.h"
#include "custompipes.h"

#ifdef EXTENDED_PIPELINES

#ifndef LIBRW
#error "Need librw for EXTENDED_PIPELINES"
#endif

extern RwTexture *gpWhiteTexture;	// from vehicle model info

namespace CustomPipes {

enum {
	// rim pipe
	VSLOC_boneMatrices = rw::d3d::VSLOC_afterLights,
	VSLOC_viewVec =	VSLOC_boneMatrices + 64*3,
	VSLOC_rampStart,
	VSLOC_rampEnd,
	VSLOC_rimData,

	// gloss pipe
	VSLOC_eye = rw::d3d::VSLOC_afterLights,

	VSLOC_reflProps,
	VSLOC_specLights
};

/*
 * Neo Vehicle pipe
 */

static void *neoVehicle_VS;
static void *neoVehicle_PS;

void
uploadSpecLights(void)
{
	struct VsLight {
		rw::RGBAf color;
		float pos[4];	// unused
		rw::V3d dir;
		float power;
	} specLights[1 + NUMEXTRADIRECTIONALS];
	memset(specLights, 0, sizeof(specLights));
	for(int i = 0; i < 1+NUMEXTRADIRECTIONALS; i++)
		specLights[i].power = 1.0f;
	float power = Power.Get();
	Color speccol = SpecColor.Get();
	specLights[0].color.red = speccol.r;
	specLights[0].color.green = speccol.g;
	specLights[0].color.blue = speccol.b;
	specLights[0].dir = pDirect->getFrame()->getLTM()->at;
	specLights[0].power = power;
	for(int i = 0; i < NUMEXTRADIRECTIONALS; i++){
		if(pExtraDirectionals[i]->getFlags() & rw::Light::LIGHTATOMICS){
			specLights[1+i].color = pExtraDirectionals[i]->color;
			specLights[1+i].dir = pExtraDirectionals[i]->getFrame()->getLTM()->at;
			specLights[1+i].power = power*2.0f;
		}
	}
	rw::d3d::d3ddevice->SetVertexShaderConstantF(VSLOC_specLights, (float*)&specLights, 3*(1 + NUMEXTRADIRECTIONALS));
}

void
vehicleRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::d3d;
	using namespace rw::d3d9;

	// TODO: make this less of a kludge
	if(VehiclePipeSwitch == VEHICLEPIPE_MATFX){
		matFXGlobals.pipelines[rw::platform]->render(atomic);
		return;
	}

	int vsBits;
	rw::uint32 flags = atomic->geometry->flags;
	setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride);
	setIndices(header->indexBuffer);
	setVertexDeclaration(header->vertexDeclaration);

	vsBits = lightingCB_Shader(atomic);
	uploadSpecLights();
	uploadMatrices(atomic->getFrame()->getLTM());

	setVertexShader(neoVehicle_VS);

	V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos;
	d3ddevice->SetVertexShaderConstantF(VSLOC_eye, (float*)&eyePos, 1);

	float reflProps[4];
	reflProps[0] = Fresnel.Get();
	reflProps[1] = SpecColor.Get().a;

	d3d::setTexture(1, EnvMapTex);

	SetRenderState(SRCBLEND, BLENDONE);

	InstanceData *inst = header->inst;
	for(rw::uint32 i = 0; i < header->numMeshes; i++){
		Material *m = inst->material;

		SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255);

		reflProps[2] = m->surfaceProps.specular * VehicleShininess;
		reflProps[3] = m->surfaceProps.specular == 0.0f ? 0.0f : VehicleSpecularity;
		d3ddevice->SetVertexShaderConstantF(VSLOC_reflProps, reflProps, 1);

		setMaterial(flags, m->color, m->surfaceProps);

		if(m->texture)
			d3d::setTexture(0, m->texture);
		else
			d3d::setTexture(0, gpWhiteTexture);
		setPixelShader(neoVehicle_PS);

		drawInst(header, inst);
		inst++;
	}
	d3d::setTexture(1, nil);

	SetRenderState(SRCBLEND, BLENDSRCALPHA);
}

void
CreateVehiclePipe(void)
{
	if(CFileMgr::LoadFile("neo/carTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0)
		printf("Error: couldn't open 'neo/carTweakingTable.dat'\n");
	else{
		char *fp = (char*)work_buff;
		fp = ReadTweakValueTable(fp, Fresnel);
		fp = ReadTweakValueTable(fp, Power);
		fp = ReadTweakValueTable(fp, DiffColor);
		fp = ReadTweakValueTable(fp, SpecColor);
	}

#include "shaders/obj/neoVehicle_VS.inc"
	neoVehicle_VS = rw::d3d::createVertexShader(neoVehicle_VS_cso);
	assert(neoVehicle_VS);

#include "shaders/obj/neoVehicle_PS.inc"
	neoVehicle_PS = rw::d3d::createPixelShader(neoVehicle_PS_cso);
	assert(neoVehicle_PS);

	rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create();
	pipe->instanceCB = rw::d3d9::defaultInstanceCB;
	pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB;
	pipe->renderCB = vehicleRenderCB;
	vehiclePipe = pipe;
}

void
DestroyVehiclePipe(void)
{
	rw::d3d::destroyVertexShader(neoVehicle_VS);
	neoVehicle_VS = nil;

	rw::d3d::destroyPixelShader(neoVehicle_PS);
	neoVehicle_PS = nil;

	((rw::d3d9::ObjPipeline*)vehiclePipe)->destroy();
	vehiclePipe = nil;
}

/*
 * Neo World pipe
 */

static void *neoWorld_VS;
static void *neoWorldIII_PS;

static void
worldRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::d3d;
	using namespace rw::d3d9;

	if(!LightmapEnable){
		defaultRenderCB_Shader(atomic, header);
		return;
	}

	int vsBits;
	setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride);
	setIndices(header->indexBuffer);
	setVertexDeclaration(header->vertexDeclaration);

	vsBits = lightingCB_Shader(atomic);
	uploadMatrices(atomic->getFrame()->getLTM());

	float lightfactor[4];

	InstanceData *inst = header->inst;
	for(rw::uint32 i = 0; i < header->numMeshes; i++){
		Material *m = inst->material;

		if(MatFX::getEffects(m) == MatFX::DUAL){
			setVertexShader(neoWorld_VS);

			MatFX *matfx = MatFX::get(m);
			Texture *dualtex = matfx->getDualTexture();
			if(dualtex == nil)
				goto notex;
			d3d::setTexture(1, dualtex);
			lightfactor[0] = lightfactor[1] = lightfactor[2] = WorldLightmapBlend.Get()*LightmapMult;
		}else{
		notex:
			setVertexShader(default_amb_VS);

			d3d::setTexture(1, nil);
			lightfactor[0] = lightfactor[1] = lightfactor[2] = 0.0f;
		}
		lightfactor[3] = m->color.alpha/255.0f;
		d3d::setTexture(0, m->texture);
		d3ddevice->SetPixelShaderConstantF(1, lightfactor, 1);

		SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255);

		RGBA color = { 255, 255, 255, m->color.alpha };
		setMaterial(color, m->surfaceProps);

		if(m->texture)
			d3d::setTexture(0, m->texture);
		else
			d3d::setTexture(0, gpWhiteTexture);
		setPixelShader(neoWorldIII_PS);

		drawInst(header, inst);
		inst++;
	}
	d3d::setTexture(1, nil);
}

void
CreateWorldPipe(void)
{
	if(CFileMgr::LoadFile("neo/worldTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0)
		printf("Error: couldn't open 'neo/worldTweakingTable.dat'\n");
	else
		ReadTweakValueTable((char*)work_buff, WorldLightmapBlend);

#include "shaders/obj/default_UV2_VS.inc"
	neoWorld_VS = rw::d3d::createVertexShader(default_UV2_VS_cso);
	assert(neoWorld_VS);

#include "shaders/obj/neoWorldIII_PS.inc"
	neoWorldIII_PS = rw::d3d::createPixelShader(neoWorldIII_PS_cso);
	assert(neoWorldIII_PS);

	rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create();
	pipe->instanceCB = rw::d3d9::defaultInstanceCB;
	pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB;
	pipe->renderCB = worldRenderCB;
	worldPipe = pipe;
}

void
DestroyWorldPipe(void)
{
	rw::d3d::destroyVertexShader(neoWorld_VS);
	neoWorld_VS = nil;
	rw::d3d::destroyPixelShader(neoWorldIII_PS);
	neoWorldIII_PS = nil;

	((rw::d3d9::ObjPipeline*)worldPipe)->destroy();
	worldPipe = nil;
}

/*
 * Neo Gloss pipe
 */

static void *neoGloss_VS;
static void *neoGloss_PS;

static void
glossRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header)
{
	worldRenderCB(atomic, header);

	using namespace rw;
	using namespace rw::d3d;
	using namespace rw::d3d9;

	if(!GlossEnable)
		return;

	setVertexShader(neoGloss_VS);
	setPixelShader(neoGloss_PS);

	V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos;
	d3ddevice->SetVertexShaderConstantF(VSLOC_eye, (float*)&eyePos, 1);
	d3ddevice->SetPixelShaderConstantF(1, (float*)&GlossMult, 1);

	SetRenderState(VERTEXALPHA, TRUE);
	SetRenderState(SRCBLEND, BLENDONE);
	SetRenderState(DESTBLEND, BLENDONE);
	SetRenderState(ZWRITEENABLE, FALSE);
	SetRenderState(ALPHATESTFUNC, ALPHAALWAYS);

	InstanceData *inst = header->inst;
	for(rw::uint32 i = 0; i < header->numMeshes; i++){
		Material *m = inst->material;

		if(m->texture){
			Texture *tex = GetGlossTex(m);
			if(tex){
				d3d::setTexture(0, tex);
				drawInst(header, inst);
			}
		}
		inst++;
	}

	SetRenderState(ZWRITEENABLE, TRUE);
	SetRenderState(ALPHATESTFUNC, ALPHAGREATEREQUAL);
	SetRenderState(SRCBLEND, BLENDSRCALPHA);
	SetRenderState(DESTBLEND, BLENDINVSRCALPHA);
}

void
CreateGlossPipe(void)
{
#include "shaders/obj/neoGloss_VS.inc"
	neoGloss_VS = rw::d3d::createVertexShader(neoGloss_VS_cso);
	assert(neoGloss_VS);

#include "shaders/obj/neoGloss_PS.inc"
	neoGloss_PS = rw::d3d::createPixelShader(neoGloss_PS_cso);
	assert(neoGloss_PS);

	rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create();
	pipe->instanceCB = rw::d3d9::defaultInstanceCB;
	pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB;
	pipe->renderCB = glossRenderCB;
	glossPipe = pipe;
}

void
DestroyGlossPipe(void)
{
	rw::d3d::destroyVertexShader(neoGloss_VS);
	neoGloss_VS = nil;

	rw::d3d::destroyPixelShader(neoGloss_PS);
	neoGloss_PS = nil;

	((rw::d3d9::ObjPipeline*)glossPipe)->destroy();
	glossPipe = nil;
}

/*
 * Neo Rim pipes
 */

static void *neoRim_VS;
static void *neoRimSkin_VS;

static void
uploadRimData(bool enable)
{
	using namespace rw;
	using namespace rw::d3d;

	V3d viewVec = rw::engine->currentCamera->getFrame()->getLTM()->at;
	d3ddevice->SetVertexShaderConstantF(VSLOC_viewVec, (float*)&viewVec, 1);
	float rimData[4];
	rimData[0] = Offset.Get();
	rimData[1] = Scale.Get();
	if(enable)
		rimData[2] = Scaling.Get()*RimlightMult;
	else
		rimData[2] = 0.0f;
	rimData[3] = 0.0f;
	d3ddevice->SetVertexShaderConstantF(VSLOC_rimData, rimData, 1);
	Color col = RampStart.Get();
	d3ddevice->SetVertexShaderConstantF(VSLOC_rampStart, (float*)&col, 1);
	col = RampEnd.Get();
	d3ddevice->SetVertexShaderConstantF(VSLOC_rampEnd, (float*)&col, 1);
}

static void
rimRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::d3d;
	using namespace rw::d3d9;

	if(!RimlightEnable){
		defaultRenderCB_Shader(atomic, header);
		return;
	}

	int vsBits;
	rw::uint32 flags = atomic->geometry->flags;
	setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride);
	setIndices(header->indexBuffer);
	setVertexDeclaration(header->vertexDeclaration);

	vsBits = lightingCB_Shader(atomic);
	uploadMatrices(atomic->getFrame()->getLTM());

	setVertexShader(neoRim_VS);

	uploadRimData(atomic->geometry->flags & Geometry::LIGHT);

	InstanceData *inst = header->inst;
	for(rw::uint32 i = 0; i < header->numMeshes; i++){
		Material *m = inst->material;

		SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255);

		setMaterial(flags, m->color, m->surfaceProps);

		if(m->texture){
			d3d::setTexture(0, m->texture);
			setPixelShader(default_tex_PS);
		}else
			setPixelShader(default_PS);

		drawInst(header, inst);
		inst++;
	}
}

static void
rimSkinRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::d3d;
	using namespace rw::d3d9;

	if(!RimlightEnable){
		skinRenderCB(atomic, header);
		return;
	}

	int vsBits;
	rw::uint32 flags = atomic->geometry->flags;
	setStreamSource(0, (IDirect3DVertexBuffer9*)header->vertexStream[0].vertexBuffer,
	                           0, header->vertexStream[0].stride);
	setIndices((IDirect3DIndexBuffer9*)header->indexBuffer);
	setVertexDeclaration((IDirect3DVertexDeclaration9*)header->vertexDeclaration);

	vsBits = lightingCB_Shader(atomic);
	uploadMatrices(atomic->getFrame()->getLTM());

	uploadSkinMatrices(atomic);

	setVertexShader(neoRimSkin_VS);

	uploadRimData(atomic->geometry->flags & Geometry::LIGHT);

	InstanceData *inst = header->inst;
	for(rw::uint32 i = 0; i < header->numMeshes; i++){
		Material *m = inst->material;

		SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255);

		setMaterial(flags, m->color, m->surfaceProps);

		if(inst->material->texture){
			d3d::setTexture(0, m->texture);
			setPixelShader(default_tex_PS);
		}else
			setPixelShader(default_PS);

		drawInst(header, inst);
		inst++;
	}
}

void
CreateRimLightPipes(void)
{
	if(CFileMgr::LoadFile("neo/rimTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0)
		printf("Error: couldn't open 'neo/rimTweakingTable.dat'\n");
	else{
		char *fp = (char*)work_buff;
		fp = ReadTweakValueTable(fp, RampStart);
		fp = ReadTweakValueTable(fp, RampEnd);
		fp = ReadTweakValueTable(fp, Offset);
		fp = ReadTweakValueTable(fp, Scale);
		fp = ReadTweakValueTable(fp, Scaling);
	}

#include "shaders/obj/neoRim_VS.inc"
	neoRim_VS = rw::d3d::createVertexShader(neoRim_VS_cso);
	assert(neoRim_VS);

#include "shaders/obj/neoRimSkin_VS.inc"
	neoRimSkin_VS = rw::d3d::createVertexShader(neoRimSkin_VS_cso);
	assert(neoRimSkin_VS);

	rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create();
	pipe->instanceCB = rw::d3d9::defaultInstanceCB;
	pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB;
	pipe->renderCB = rimRenderCB;
	rimPipe = pipe;

	pipe = rw::d3d9::ObjPipeline::create();
	pipe->instanceCB = rw::d3d9::skinInstanceCB;
	pipe->uninstanceCB = nil;
	pipe->renderCB = rimSkinRenderCB;
	rimSkinPipe = pipe;
}

void
DestroyRimLightPipes(void)
{
	rw::d3d::destroyVertexShader(neoRim_VS);
	neoRim_VS = nil;

	rw::d3d::destroyVertexShader(neoRimSkin_VS);
	neoRimSkin_VS = nil;

	((rw::d3d9::ObjPipeline*)rimPipe)->destroy();
	rimPipe = nil;

	((rw::d3d9::ObjPipeline*)rimSkinPipe)->destroy();
	rimSkinPipe = nil;
}

}

#endif

#ifdef NEW_RENDERER
#ifndef LIBRW
#error "Need librw for NEW_PIPELINES"
#endif

namespace WorldRender
{

struct BuildingInst
{
	rw::RawMatrix combinedMat;
	rw::d3d9::InstanceDataHeader *instHeader;
	uint32 cullMode;
	uint8 fadeAlpha;
	bool lighting;
};
BuildingInst blendInsts[3][2000];
int numBlendInsts[3];

static RwRGBAReal black;

static void
SetMatrix(BuildingInst *building, rw::Matrix *worldMat)
{
	using namespace rw;
	RawMatrix world, worldview;
	Camera *cam = engine->currentCamera;
	convMatrix(&world, worldMat);
	RawMatrix::mult(&worldview, &world, &cam->devView);
	RawMatrix::mult(&building->combinedMat, &worldview, &cam->devProj);
}

static bool
IsTextureTransparent(RwTexture *tex)
{
	if(tex == nil || tex->raster == nil)
		return false;
	return PLUGINOFFSET(rw::d3d::D3dRaster, tex->raster, rw::d3d::nativeRasterOffset)->hasAlpha;
}

// Render all opaque meshes and put atomics that needs blending
// into the deferred list.
void
AtomicFirstPass(RpAtomic *atomic, int pass)
{
	using namespace rw;
	using namespace rw::d3d;
	using namespace rw::d3d9;

	BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]];

	atomic->getPipeline()->instance(atomic);
	building->instHeader = (d3d9::InstanceDataHeader*)atomic->geometry->instData;
	assert(building->instHeader != nil);
	assert(building->instHeader->platform == PLATFORM_D3D9);
	building->fadeAlpha = 255;
	building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT);
	building->cullMode = rw::GetRenderState(rw::CULLMODE);
	rw::uint32 flags = atomic->geometry->flags;

	bool setupDone = false;
	bool defer = false;
	SetMatrix(building, atomic->getFrame()->getLTM());

	InstanceData *inst = building->instHeader->inst;
	for(rw::uint32 i = 0; i < building->instHeader->numMeshes; i++, inst++){
		Material *m = inst->material;

		if(inst->vertexAlpha || m->color.alpha != 255 ||
		   IsTextureTransparent(m->texture)){
			defer = true;
			continue;
		}

		// alright we're rendering this atomic
		if(!setupDone){
			rw::SetRenderState(rw::CULLMODE, building->cullMode);
			setStreamSource(0, building->instHeader->vertexStream[0].vertexBuffer, 0, building->instHeader->vertexStream[0].stride);
			setIndices(building->instHeader->indexBuffer);
			setVertexDeclaration(building->instHeader->vertexDeclaration);
			setVertexShader(default_amb_VS);
			d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&building->combinedMat, 4);
			if(building->lighting)
				setAmbient(pAmbient->color);
			else
				setAmbient(black);
			setupDone = true;
		}

		setMaterial(flags, m->color, m->surfaceProps);

		if(m->texture){
			d3d::setTexture(0, m->texture);
			setPixelShader(default_tex_PS);
		}else
			setPixelShader(default_PS);

		drawInst(building->instHeader, inst);
	}
	if(defer)
		numBlendInsts[pass]++;
}

void
AtomicFullyTransparent(RpAtomic *atomic, int pass, int fadeAlpha)
{
	using namespace rw;
	using namespace rw::d3d;
	using namespace rw::d3d9;

	BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]];

	atomic->getPipeline()->instance(atomic);
	building->instHeader = (d3d9::InstanceDataHeader*)atomic->geometry->instData;
	assert(building->instHeader != nil);
	assert(building->instHeader->platform == PLATFORM_D3D9);
	building->fadeAlpha = fadeAlpha;
	building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT);
	building->cullMode = rw::GetRenderState(rw::CULLMODE);
	SetMatrix(building, atomic->getFrame()->getLTM());
	numBlendInsts[pass]++;
}

void
RenderBlendPass(int pass)
{
	using namespace rw;
	using namespace rw::d3d;
	using namespace rw::d3d9;

	setVertexShader(default_amb_VS);

	int i;
	for(i = 0; i < numBlendInsts[pass]; i++){
		BuildingInst *building = &blendInsts[pass][i];

		rw::SetRenderState(rw::CULLMODE, building->cullMode);
		setStreamSource(0, building->instHeader->vertexStream[0].vertexBuffer, 0, building->instHeader->vertexStream[0].stride);
		setIndices(building->instHeader->indexBuffer);
		setVertexDeclaration(building->instHeader->vertexDeclaration);
		d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&building->combinedMat, 4);
		if(building->lighting)
			setAmbient(pAmbient->color);
		else
			setAmbient(black);

		InstanceData *inst = building->instHeader->inst;
		for(rw::uint32 j = 0; j < building->instHeader->numMeshes; j++, inst++){
			Material *m = inst->material;
			if(!inst->vertexAlpha && m->color.alpha == 255 && !IsTextureTransparent(m->texture) && building->fadeAlpha == 255)
				continue;	// already done this one

			rw::RGBA color = m->color;
			color.alpha = (color.alpha * building->fadeAlpha)/255;
			setMaterial(color, m->surfaceProps);	// always modulate here

			if(m->texture){
				d3d::setTexture(0, m->texture);
				setPixelShader(default_tex_PS);
			}else
				setPixelShader(default_PS);

			drawInst(building->instHeader, inst);
		}
	}
}
}
#endif

#endif


code:
#include "common.h"

#ifdef RW_OPENGL
#include "main.h"
#include "RwHelper.h"
#include "Lights.h"
#include "Timecycle.h"
#include "FileMgr.h"
#include "Clock.h"
#include "Weather.h"
#include "TxdStore.h"
#include "Renderer.h"
#include "World.h"
#include "custompipes.h"

#ifdef EXTENDED_PIPELINES

#ifndef LIBRW
#error "Need librw for EXTENDED_PIPELINES"
#endif

namespace CustomPipes {

static int32 u_viewVec;
static int32 u_rampStart;
static int32 u_rampEnd;
static int32 u_rimData;

static int32 u_lightMap;

static int32 u_eye;
static int32 u_reflProps;
static int32 u_specDir;
static int32 u_specColor;

#define U(i) currentShader->uniformLocations[i]

/*
 * Neo Vehicle pipe
 */

rw::gl3::Shader *neoVehicleShader;

static void
uploadSpecLights(void)
{
	using namespace rw::gl3;

	rw::RGBAf colors[1 + NUMEXTRADIRECTIONALS];
	struct {
		rw::V3d dir;
		float power;
	} dirs[1 + NUMEXTRADIRECTIONALS];
	memset(colors, 0, sizeof(colors));
	memset(dirs, 0, sizeof(dirs));
	for(int i = 0; i < 1+NUMEXTRADIRECTIONALS; i++)
		dirs[i].power = 1.0f;
	float power = Power.Get();
	Color speccol = SpecColor.Get();
	colors[0].red = speccol.r;
	colors[0].green = speccol.g;
	colors[0].blue = speccol.b;
	dirs[0].dir = pDirect->getFrame()->getLTM()->at;
	dirs[0].power = power;
	for(int i = 0; i < NUMEXTRADIRECTIONALS; i++){
		if(pExtraDirectionals[i]->getFlags() & rw::Light::LIGHTATOMICS){
			colors[1+i] = pExtraDirectionals[i]->color;
			dirs[1+i].dir = pExtraDirectionals[i]->getFrame()->getLTM()->at;
			dirs[1+i].power = power*2.0f;
		}
	}
	glUniform4fv(U(u_specDir), 1 + NUMEXTRADIRECTIONALS, (float*)&dirs);
	glUniform4fv(U(u_specColor), 1 + NUMEXTRADIRECTIONALS, (float*)&colors);
}

static void
vehicleRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::gl3;

	// TODO: make this less of a kludge
	if(VehiclePipeSwitch == VEHICLEPIPE_MATFX){
		matFXGlobals.pipelines[rw::platform]->render(atomic);
		return;
	}

	Material *m;

	rw::uint32 flags = atomic->geometry->flags;
	setWorldMatrix(atomic->getFrame()->getLTM());
	lightingCB(atomic);

	setupVertexInput(header);

	InstanceData *inst = header->inst;
	rw::int32 n = header->numMeshes;

	neoVehicleShader->use();

	V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos;
	glUniform3fv(U(u_eye), 1, (float*)&eyePos);

	uploadSpecLights();

	float reflProps[4];
	reflProps[0] = Fresnel.Get();
	reflProps[1] = SpecColor.Get().a;

	setTexture(1, EnvMapTex);

	SetRenderState(SRCBLEND, BLENDONE);

	while(n--){
		m = inst->material;

		setMaterial(flags, m->color, m->surfaceProps);

		setTexture(0, m->texture);

		rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF);

		reflProps[2] = m->surfaceProps.specular * VehicleShininess;
		reflProps[3] = m->surfaceProps.specular == 0.0f ? 0.0f : VehicleSpecularity;
		glUniform4fv(U(u_reflProps), 1, reflProps);

		drawInst(header, inst);
		inst++;
	}

	SetRenderState(SRCBLEND, BLENDSRCALPHA);
	setTexture(1, nil);

	teardownVertexInput(header);
}

void
CreateVehiclePipe(void)
{
	using namespace rw;
	using namespace rw::gl3;

	if(CFileMgr::LoadFile("neo/carTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0)
		printf("Error: couldn't open 'neo/carTweakingTable.dat'\n");
	else{
		char *fp = (char*)work_buff;
		fp = ReadTweakValueTable(fp, Fresnel);
		fp = ReadTweakValueTable(fp, Power);
		fp = ReadTweakValueTable(fp, DiffColor);
		fp = ReadTweakValueTable(fp, SpecColor);
	}

	{
#include "shaders/obj/neoVehicle_frag.inc"
#include "shaders/obj/neoVehicle_vert.inc"
	const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoVehicle_vert_src, nil };
	const char *fs[] = { shaderDecl, header_frag_src, neoVehicle_frag_src, nil };
	neoVehicleShader = Shader::create(vs, fs);
	assert(neoVehicleShader);
	}

	rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create();
	pipe->instanceCB = rw::gl3::defaultInstanceCB;
	pipe->uninstanceCB = nil;
	pipe->renderCB = vehicleRenderCB;
	vehiclePipe = pipe;
}

void
DestroyVehiclePipe(void)
{
	neoVehicleShader->destroy();
	neoVehicleShader = nil;

	((rw::gl3::ObjPipeline*)vehiclePipe)->destroy();
	vehiclePipe = nil;
}

/*
 * Neo World pipe
 */

rw::gl3::Shader *neoWorldShader;

static void
worldRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::gl3;

	if(!LightmapEnable){
		gl3::defaultRenderCB(atomic, header);
		return;
	}

	Material *m;

	setWorldMatrix(atomic->getFrame()->getLTM());
	lightingCB(atomic);

	setupVertexInput(header);

	InstanceData *inst = header->inst;
	rw::int32 n = header->numMeshes;

	neoWorldShader->use();

	float lightfactor[4];

	while(n--){
		m = inst->material;

		if(MatFX::getEffects(m) == MatFX::DUAL){
			MatFX *matfx = MatFX::get(m);
			Texture *dualtex = matfx->getDualTexture();
			if(dualtex == nil)
				goto notex;
			setTexture(1, dualtex);
			lightfactor[0] = lightfactor[1] = lightfactor[2] = WorldLightmapBlend.Get()*LightmapMult;
		}else{
		notex:
			setTexture(1, nil);
			lightfactor[0] = lightfactor[1] = lightfactor[2] = 0.0f;
		}
		lightfactor[3] = m->color.alpha/255.0f;
		glUniform4fv(U(u_lightMap), 1, lightfactor);

		RGBA color = { 255, 255, 255, m->color.alpha };
		setMaterial(color, m->surfaceProps);

		setTexture(0, m->texture);

		rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF);

		drawInst(header, inst);
		inst++;
	}
	setTexture(1, nil);
	teardownVertexInput(header);
}

void
CreateWorldPipe(void)
{
	using namespace rw;
	using namespace rw::gl3;

	if(CFileMgr::LoadFile("neo/worldTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0)
		printf("Error: couldn't open 'neo/worldTweakingTable.dat'\n");
	else
		ReadTweakValueTable((char*)work_buff, WorldLightmapBlend);

	{
#include "shaders/obj/neoWorldIII_frag.inc"
#include "shaders/obj/default_UV2_vert.inc"
	const char *vs[] = { shaderDecl, header_vert_src, default_UV2_vert_src, nil };
	const char *fs[] = { shaderDecl, header_frag_src, neoWorldIII_frag_src, nil };
	neoWorldShader = Shader::create(vs, fs);
	assert(neoWorldShader);
	}

	rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create();
	pipe->instanceCB = rw::gl3::defaultInstanceCB;
	pipe->uninstanceCB = nil;
	pipe->renderCB = worldRenderCB;
	worldPipe = pipe;
}

void
DestroyWorldPipe(void)
{
	neoWorldShader->destroy();
	neoWorldShader = nil;

	((rw::gl3::ObjPipeline*)worldPipe)->destroy();
	worldPipe = nil;
}

/*
 * Neo Gloss pipe
 */

rw::gl3::Shader *neoGlossShader;

static void
glossRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::gl3;

	worldRenderCB(atomic, header);
	if(!GlossEnable)
		return;

	Material *m;

	setupVertexInput(header);

	InstanceData *inst = header->inst;
	rw::int32 n = header->numMeshes;

	neoGlossShader->use();

	V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos;
	glUniform3fv(U(u_eye), 1, (float*)&eyePos);
	float reflProps[4];
	reflProps[0] = GlossMult;
	reflProps[1] = 0.0f;
	reflProps[2] = 0.0f;
	reflProps[3] = 0.0f;
	glUniform4fv(U(u_reflProps), 1, reflProps);

	SetRenderState(VERTEXALPHA, TRUE);
	SetRenderState(SRCBLEND, BLENDONE);
	SetRenderState(DESTBLEND, BLENDONE);
	SetRenderState(ZWRITEENABLE, FALSE);
	SetRenderState(ALPHATESTFUNC, ALPHAALWAYS);

	while(n--){
		m = inst->material;

		RGBA color = { 255, 255, 255, m->color.alpha };
		setMaterial(color, m->surfaceProps);

		if(m->texture){
			Texture *tex = GetGlossTex(m);
			if(tex){
				setTexture(0, tex);
				drawInst(header, inst);
			}
		}
		inst++;
	}

	SetRenderState(ZWRITEENABLE, TRUE);
	SetRenderState(ALPHATESTFUNC, ALPHAGREATEREQUAL);
	SetRenderState(SRCBLEND, BLENDSRCALPHA);
	SetRenderState(DESTBLEND, BLENDINVSRCALPHA);

	teardownVertexInput(header);
}

void
CreateGlossPipe(void)
{
	using namespace rw;
	using namespace rw::gl3;

	{
#include "shaders/obj/neoGloss_frag.inc"
#include "shaders/obj/neoGloss_vert.inc"
	const char *vs[] = { shaderDecl, header_vert_src, neoGloss_vert_src, nil };
	const char *fs[] = { shaderDecl, header_frag_src, neoGloss_frag_src, nil };
	neoGlossShader = Shader::create(vs, fs);
	assert(neoGlossShader);
	}

	rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create();
	pipe->instanceCB = rw::gl3::defaultInstanceCB;
	pipe->uninstanceCB = nil;
	pipe->renderCB = glossRenderCB;
	glossPipe = pipe;
}

void
DestroyGlossPipe(void)
{
	neoGlossShader->destroy();
	neoGlossShader = nil;

	((rw::gl3::ObjPipeline*)glossPipe)->destroy();
	glossPipe = nil;
}

/*
 * Neo Rim pipes
 */

rw::gl3::Shader *neoRimShader;
rw::gl3::Shader *neoRimSkinShader;

static void
uploadRimData(bool enable)
{
	using namespace rw;
	using namespace rw::gl3;

	V3d viewVec = rw::engine->currentCamera->getFrame()->getLTM()->at;
	glUniform3fv(U(u_viewVec), 1, (float*)&viewVec);
	float rimData[4];
	rimData[0] = Offset.Get();
	rimData[1] = Scale.Get();
	if(enable)
		rimData[2] = Scaling.Get()*RimlightMult;
	else
		rimData[2] = 0.0f;
	rimData[3] = 0.0f;
	glUniform3fv(U(u_rimData), 1, rimData);
	Color col = RampStart.Get();
	glUniform4fv(U(u_rampStart), 1, (float*)&col);
	col = RampEnd.Get();
	glUniform4fv(U(u_rampEnd), 1, (float*)&col);
}

static void
rimSkinRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::gl3;

	if(!RimlightEnable){
		gl3::skinRenderCB(atomic, header);
		return;
	}

	Material *m;

	rw::uint32 flags = atomic->geometry->flags;
	setWorldMatrix(atomic->getFrame()->getLTM());
	lightingCB(atomic);

	setupVertexInput(header);

	InstanceData *inst = header->inst;
	rw::int32 n = header->numMeshes;

	neoRimSkinShader->use();

	uploadRimData(atomic->geometry->flags & Geometry::LIGHT);

	uploadSkinMatrices(atomic);

	while(n--){
		m = inst->material;

		setMaterial(flags, m->color, m->surfaceProps);

		setTexture(0, m->texture);

		rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF);

		drawInst(header, inst);
		inst++;
	}
	teardownVertexInput(header);
}

static void
rimRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header)
{
	using namespace rw;
	using namespace rw::gl3;

	if(!RimlightEnable){
		gl3::defaultRenderCB(atomic, header);
		return;
	}

	Material *m;

	rw::uint32 flags = atomic->geometry->flags;
	setWorldMatrix(atomic->getFrame()->getLTM());
	lightingCB(atomic);

	setupVertexInput(header);

	InstanceData *inst = header->inst;
	rw::int32 n = header->numMeshes;

	neoRimShader->use();

	uploadRimData(atomic->geometry->flags & Geometry::LIGHT);

	while(n--){
		m = inst->material;

		setMaterial(flags, m->color, m->surfaceProps);

		setTexture(0, m->texture);

		rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF);

		drawInst(header, inst);
		inst++;
	}
	teardownVertexInput(header);
}

void
CreateRimLightPipes(void)
{
	using namespace rw::gl3;

	if(CFileMgr::LoadFile("neo/rimTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0)
		printf("Error: couldn't open 'neo/rimTweakingTable.dat'\n");
	else{
		char *fp = (char*)work_buff;
		fp = ReadTweakValueTable(fp, RampStart);
		fp = ReadTweakValueTable(fp, RampEnd);
		fp = ReadTweakValueTable(fp, Offset);
		fp = ReadTweakValueTable(fp, Scale);
		fp = ReadTweakValueTable(fp, Scaling);
	}

	{
#include "shaders/obj/simple_frag.inc"
#include "shaders/obj/neoRimSkin_vert.inc"
	const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoRimSkin_vert_src, nil };
	const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil };
	neoRimSkinShader = Shader::create(vs, fs);
	assert(neoRimSkinShader);
	}

	{
#include "shaders/obj/simple_frag.inc"
#include "shaders/obj/neoRim_vert.inc"
	const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoRim_vert_src, nil };
	const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil };
	neoRimShader = Shader::create(vs, fs);
	assert(neoRimShader);
	}

	rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create();
	pipe->instanceCB = rw::gl3::defaultInstanceCB;
	pipe->uninstanceCB = nil;
	pipe->renderCB = rimRenderCB;
	rimPipe = pipe;

	pipe = rw::gl3::ObjPipeline::create();
	pipe->instanceCB = rw::gl3::skinInstanceCB;
	pipe->uninstanceCB = nil;
	pipe->renderCB = rimSkinRenderCB;
	rimSkinPipe = pipe;
}

void
DestroyRimLightPipes(void)
{
	neoRimShader->destroy();
	neoRimShader = nil;

	neoRimSkinShader->destroy();
	neoRimSkinShader = nil;

	((rw::gl3::ObjPipeline*)rimPipe)->destroy();
	rimPipe = nil;

	((rw::gl3::ObjPipeline*)rimSkinPipe)->destroy();
	rimSkinPipe = nil;
}

void
CustomPipeRegisterGL(void)
{
	u_viewVec = rw::gl3::registerUniform("u_viewVec");
	u_rampStart = rw::gl3::registerUniform("u_rampStart");
	u_rampEnd = rw::gl3::registerUniform("u_rampEnd");
	u_rimData = rw::gl3::registerUniform("u_rimData");

	u_lightMap = rw::gl3::registerUniform("u_lightMap");

	u_eye = rw::gl3::registerUniform("u_eye");
	u_reflProps = rw::gl3::registerUniform("u_reflProps");
	u_specDir = rw::gl3::registerUniform("u_specDir");
	u_specColor = rw::gl3::registerUniform("u_specColor");
}

}

#endif

#ifdef NEW_RENDERER
#ifndef LIBRW
#error "Need librw for NEW_PIPELINES"
#endif

namespace WorldRender
{

struct BuildingInst
{
	rw::Matrix matrix;
	rw::gl3::InstanceDataHeader *instHeader;
	uint32 cullMode;
	uint8 fadeAlpha;
	bool lighting;
};
BuildingInst blendInsts[3][2000];
int numBlendInsts[3];

static RwRGBAReal black;

static bool
IsTextureTransparent(RwTexture *tex)
{
	if(tex == nil || tex->raster == nil)
		return false;
	return PLUGINOFFSET(rw::gl3::Gl3Raster, tex->raster, rw::gl3::nativeRasterOffset)->hasAlpha;
}

// Render all opaque meshes and put atomics that needs blending
// into the deferred list.
void
AtomicFirstPass(RpAtomic *atomic, int pass)
{
	using namespace rw;
	using namespace rw::gl3;

	BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]];

	atomic->getPipeline()->instance(atomic);
	building->instHeader = (gl3::InstanceDataHeader*)atomic->geometry->instData;
	assert(building->instHeader != nil);
	assert(building->instHeader->platform == PLATFORM_GL3);
	building->fadeAlpha = 255;
	building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT);
	building->cullMode = rw::GetRenderState(rw::CULLMODE);
	rw::uint32 flags = atomic->geometry->flags;

	WorldLights lights;
	lights.numAmbients = 1;
	lights.numDirectionals = 0;
	lights.numLocals = 0;
	if(building->lighting)
		lights.ambient = pAmbient->color;
	else
		lights.ambient = black;

	bool setupDone = false;
	bool defer = false;
	building->matrix = *atomic->getFrame()->getLTM();

	InstanceData *inst = building->instHeader->inst;
	for(rw::uint32 i = 0; i < building->instHeader->numMeshes; i++, inst++){
		Material *m = inst->material;

		if(inst->vertexAlpha || m->color.alpha != 255 ||
		   IsTextureTransparent(m->texture)){
			defer = true;
			continue;
		}

		// alright we're rendering this atomic
		if(!setupDone){
			rw::SetRenderState(rw::CULLMODE, building->cullMode);
			defaultShader->use();
			setWorldMatrix(&building->matrix);
			setupVertexInput(building->instHeader);
			setLights(&lights);
			setupDone = true;
		}

		setMaterial(flags, m->color, m->surfaceProps);

		setTexture(0, m->texture);

		drawInst(building->instHeader, inst);
	}
	teardownVertexInput(building->instHeader);
	if(defer)
		numBlendInsts[pass]++;
}

void
AtomicFullyTransparent(RpAtomic *atomic, int pass, int fadeAlpha)
{
	using namespace rw;
	using namespace rw::gl3;

	BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]];

	atomic->getPipeline()->instance(atomic);
	building->instHeader = (gl3::InstanceDataHeader*)atomic->geometry->instData;
	assert(building->instHeader != nil);
	assert(building->instHeader->platform == PLATFORM_GL3);
	building->fadeAlpha = fadeAlpha;
	building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT);
	building->cullMode = rw::GetRenderState(rw::CULLMODE);
	building->matrix = *atomic->getFrame()->getLTM();
	numBlendInsts[pass]++;
}

void
RenderBlendPass(int pass)
{
	using namespace rw;
	using namespace rw::gl3;

	defaultShader->use();
	WorldLights lights;
	lights.numAmbients = 1;
	lights.numDirectionals = 0;
	lights.numLocals = 0;

	int i;
	for(i = 0; i < numBlendInsts[pass]; i++){
		BuildingInst *building = &blendInsts[pass][i];

		rw::SetRenderState(rw::CULLMODE, building->cullMode);
		setupVertexInput(building->instHeader);
		setWorldMatrix(&building->matrix);
		if(building->lighting)
			lights.ambient = pAmbient->color;
		else
			lights.ambient = black;
		setLights(&lights);

		InstanceData *inst = building->instHeader->inst;
		for(rw::uint32 j = 0; j < building->instHeader->numMeshes; j++, inst++){
			Material *m = inst->material;
			if(!inst->vertexAlpha && m->color.alpha == 255 && !IsTextureTransparent(m->texture) && building->fadeAlpha == 255)
				continue;	// already done this one

			rw::RGBA color = m->color;
			color.alpha = (color.alpha * building->fadeAlpha)/255;
			setMaterial(color, m->surfaceProps);	// always modulate here

			setTexture(0, m->texture);

			drawInst(building->instHeader, inst);
		}
		teardownVertexInput(building->instHeader);
	}
}
}
#endif

#endif

license: mit


code:
#include "common.h"
#ifdef DEBUGMENU
#include "RwHelper.h"
#include "Pad.h"
#include "ControllerConfig.h"
#include "Timer.h"
#include "rtcharse.h"
#include "re3_inttypes.h"
#include "debugmenu.h"
#include <new>

#ifdef _WIN32
#define snprintf _snprintf

#define strdup _strdup
#endif

// Font stuff
struct Pt
{
	int x, y;
};

enum MenuFontStyle
{
	MENUFONT_NORMAL,
	MENUFONT_SEL_ACTIVE,
	MENUFONT_SEL_INACTIVE,
	MENUFONT_MOUSE
};

RtCharset *fontStyles[4];
RtCharsetDesc fontDesc;
int fontscale = 1;	// not supported right now

Pt
fontGetStringSize(const char *s)
{
	Pt sz = { 0, 0 };
	int x;
	char c;
	sz.y = fontDesc.height*fontscale;	// always assume one line;
	x = 0;
	while(c = *s++){
		if(c == '\n'){
			sz.y += fontDesc.height*fontscale;
			if(x > sz.x)
				sz.x = x;
			x = 0;
		}else
			x += fontDesc.width*fontscale;
	}
	if(x > sz.x)
		sz.x = x;
	return sz;
}

Pt
fontPrint(const char *s, float x, float y, int style)
{
	RtCharsetPrintBuffered(fontStyles[style], s, x, y, false);
	return fontGetStringSize(s);
}

int
fontGetLen(int len)
{
	return len*fontDesc.width*fontscale;
}

void
createMenuFont(void)
{
	OpenCharsetSafe();

	RwRGBA fg_normal = { 255, 255, 255, 255 };
	RwRGBA bg_normal = { 255, 255, 255, 0 };
	fontStyles[MENUFONT_NORMAL] = RtCharsetCreate(&fg_normal, &bg_normal);
	assert(fontStyles[MENUFONT_NORMAL]);

	RwRGBA fg_sel_active = { 200, 200, 200, 255 };
	RwRGBA bg_sel_active = { 132, 132, 132, 255 };
	fontStyles[MENUFONT_SEL_ACTIVE] = RtCharsetCreate(&fg_sel_active, &bg_sel_active);
	assert(fontStyles[MENUFONT_SEL_ACTIVE]);

	RwRGBA fg_sel_inactive = { 200, 200, 200, 255 };
	RwRGBA bg_sel_inactive = { 200, 200, 200, 0 };
	fontStyles[MENUFONT_SEL_INACTIVE] = RtCharsetCreate(&fg_sel_inactive, &bg_sel_inactive);
	assert(fontStyles[MENUFONT_SEL_INACTIVE]);

	RwRGBA fg_mouse = { 255, 255, 255, 255 };
	RwRGBA bg_mouse = { 132, 132, 132, 255 };
	fontStyles[MENUFONT_MOUSE] = RtCharsetCreate(&fg_mouse, &bg_mouse);
	assert(fontStyles[MENUFONT_MOUSE]);

	RtCharsetGetDesc(fontStyles[MENUFONT_NORMAL], &fontDesc);
}

void
destroyMenuFont(void)
{
	RtCharsetDestroy(fontStyles[MENUFONT_NORMAL]);
	fontStyles[MENUFONT_NORMAL] = nil;
	RtCharsetDestroy(fontStyles[MENUFONT_SEL_ACTIVE]);
	fontStyles[MENUFONT_SEL_ACTIVE] = nil;
	RtCharsetDestroy(fontStyles[MENUFONT_SEL_INACTIVE]);
	fontStyles[MENUFONT_SEL_INACTIVE] = nil;
	RtCharsetDestroy(fontStyles[MENUFONT_MOUSE]);
	fontStyles[MENUFONT_MOUSE] = nil;
}

enum EntryType
{
	MENUEMPTY = 0,
	MENUSUB,
	MENUVAR,

	MENUVAR_INT,
	MENUVAR_FLOAT,
	MENUVAR_CMD,

	MENUSCROLL	// dummy
};

struct Menu
{
	Menu *parent;
	RwRect r;
	MenuEntry *entries;
	int numEntries;
	int maxNameWidth, maxValWidth;

	MenuEntry *findEntry(const char *entryname);
	void insertEntrySorted(MenuEntry *entry);
	void appendEntry(MenuEntry *entry);

	bool isScrollingUp, isScrollingDown;
	int scrollStart;
	int numVisible;
	RwRect scrollUpR, scrollDownR;
	void scroll(int off);

	int selection;
	MenuEntry *selectedEntry;	// updated by update
	void changeSelection(int newsel);
	void changeSelection(MenuEntry *e);

	void update(void);
	void draw(void);
	Menu(void){ memset(this, 0, sizeof(Menu)); }
	~Menu(void);
};
extern Menu toplevel;

struct MenuEntry_Sub : MenuEntry
{
	Menu *submenu;

	MenuEntry_Sub(const char *name, Menu *menu);
	~MenuEntry_Sub(void) { delete submenu; }
};

struct MenuEntry_Var : MenuEntry
{
	int maxvallen;
	int vartype;
	bool wrapAround;

	virtual void processInput(bool mouseOver, bool selected) = 0;
	int getValWidth(void) { return maxvallen; }
	virtual void getValStr(char *str, int len) = 0;
	MenuEntry_Var(const char *name, int type);
};

struct MenuEntry_Int : MenuEntry_Var
{
	virtual void setStrings(const char **strings) = 0;
	virtual int findStringLen(void) = 0;
	MenuEntry_Int(const char *name);
};

#define INTTYPES \
	X(Int8, int8, 4, "%4" PRId8) \
	X(Int16, int16, 6, "%6" PRId16) \
	X(Int32, int32, 11, "%11" PRId32) \
	X(Int64, int64, 21, "%21" PRId64) \
	X(UInt8, uint8, 4, "%4" PRIu8) \
	X(UInt16, uint16, 6, "%6" PRIu16) \
	X(UInt32, uint32, 11, "%11" PRIu32) \
	X(UInt64, uint64, 21, "%21" PRIu64)
#define FLOATTYPES \
	X(Float32, float, 11, "%11.3f") \
	X(Float64, double, 11, "%11.3lf")

#define X(NAME, TYPE, MAXLEN, FMT) \
struct MenuEntry_##NAME : MenuEntry_Int														       \
{																		       \
	TYPE *variable;																       \
	TYPE lowerBound, upperBound;														       \
	TYPE step;																       \
	TriggerFunc triggerFunc;														       \
	const char *fmt;															       \
	const char **strings;															       \
																		       \
	void processInput(bool mouseOver, bool selected);														       \
	void getValStr(char *str, int len);													       \
																		       \
	void setStrings(const char **strings);													       \
	int findStringLen(void);														       \
	MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings);     \
};
INTTYPES
#undef X

#define X(NAME, TYPE, MAXLEN, FMT) \
struct MenuEntry_##NAME : MenuEntry_Var												 \
{																 \
	TYPE *variable;														 \
	TYPE lowerBound, upperBound;												 \
	TYPE step;														 \
	TriggerFunc triggerFunc;												 \
	const char *fmt;													 \
																 \
	void processInput(bool mouseOver, bool selected);												 \
	void getValStr(char *str, int len);											 \
																 \
	MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound);	 \
};
FLOATTYPES
#undef X

struct MenuEntry_Cmd : MenuEntry_Var
{
	TriggerFunc triggerFunc;

	void processInput(bool mouseOver, bool selected);
	void getValStr(char *str, int len);

	MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc);
};

Menu *findMenu(const char *name);

#define MUHKEYS \
	X(leftjustdown, rsLEFT) \
	X(rightjustdown, rsRIGHT) \
	X(upjustdown, rsUP) \
	X(downjustdown, rsDOWN) \
	X(pgupjustdown, rsPGUP) \
	X(pgdnjustdown, rsPGDN)

#define MUHBUTTONS \
	X(button1justdown, 1) \
	X(button2justdown, 2) \
	X(button3justdown, 3)

#define REPEATDELAY 700
#define REPEATINTERVAL 50
#define X(var, keycode) static int var;
MUHKEYS
#undef X
static int downtime;
static int repeattime;
static int lastkeydown;
static int *keyptr;

static int buttondown[3];
static int lastbuttondown;
static int *buttonptr;
static int button1justdown, button2justdown, button3justdown;
static float mouseX, mouseY;

static int menuOn;
static int menuInitialized;
static int screenWidth, screenHeight;
static RwRaster *cursor, *arrow;

static int firstBorder = 10;
static int topBorder = 40;	//10;
static int leading = 4;
static int gap = 10;
static int minwidth = 100;

void drawMouse(void);
void drawArrow(RwRect r, int direction, int style);

Menu toplevel;
Menu *activeMenu = &toplevel;
Menu *deepestMenu = &toplevel;
Menu *mouseOverMenu;
MenuEntry *mouseOverEntry;
MenuEntry scrollUpEntry("SCROLLUP"), scrollDownEntry("SCROLLDOWN");	// dummies

#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k)
#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k)
#define CTRLJUSTDOWN(key) \
	       ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \
	        (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key))
#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key))

bool
isMouseInRect(RwRect r)
{
	return (mouseX >= r.x && mouseX < r.x+r.w &&
		mouseY >= r.y && mouseY < r.y+r.h);
}

/*
 * MenuEntry
 */

MenuEntry::MenuEntry(const char *name)
{
	this->type = MENUEMPTY;
	this->name = strdup(name);
	this->next = nil;
	this->menu = nil;
}

MenuEntry_Sub::MenuEntry_Sub(const char *name, Menu *menu)
: MenuEntry(name)
{
	this->type = MENUSUB;
	this->submenu = menu;
}

MenuEntry_Var::MenuEntry_Var(const char *name, int vartype)
: MenuEntry(name)
{
	this->type = MENUVAR;
	this->vartype = vartype;
	this->maxvallen = 0;
	this->wrapAround = false;
}

/*
 * *****************************
 * MenuEntry_Int
 * *****************************
 */

MenuEntry_Int::MenuEntry_Int(const char *name)
: MenuEntry_Var(name, MENUVAR_INT)
{
}

#define X(NAME, TYPE, MAXLEN, FMT) \
int								     \
MenuEntry_##NAME::findStringLen(void){				     \
	TYPE i;							     \
	int len, maxlen = 0;					     \
	for(i = this->lowerBound; i <= this->upperBound; i++){	     \
		len = strlen(this->strings[i-this->lowerBound]);     \
		if(len > maxlen)				     \
			maxlen = len;				     \
	}							     \
	return maxlen;						     \
}								     \
void								     \
MenuEntry_##NAME::processInput(bool mouseOver, bool selected)				     \
{								     \
	TYPE v, oldv;						     \
	int overflow = 0;					     \
	int underflow = 0;					     \
								     \
	v = *this->variable;					     \
	oldv = v;						     \
								     \
	if((selected && leftjustdown) || (mouseOver && button3justdown)){					     \
		v -= this->step;				     \
		if(v > oldv)					     \
			underflow = 1;				     \
	}							     \
	if((selected && rightjustdown) || (mouseOver && button1justdown)){					     \
		v += this->step;				     \
		if(v < oldv)					     \
			overflow = 1;				     \
	}							     \
	if(this->wrapAround){					     \
		if(v > this->upperBound || overflow) v = this->lowerBound;	     \
		if(v < this->lowerBound || underflow) v = this->upperBound;	     \
	}else{									     \
		if(v > this->upperBound || overflow) v = this->upperBound;	     \
		if(v < this->lowerBound || underflow) v = this->lowerBound;	     \
	}							     \
								     \
	*this->variable = v;					     \
	if(oldv != v && this->triggerFunc)			     \
		this->triggerFunc();				     \
}								     \
void											       \
MenuEntry_##NAME::getValStr(char *str, int len)						       \
{											       \
	static char tmp[20];								       \
	if(this->strings){								       \
		snprintf(tmp, 20, "%%%ds", this->maxvallen);				       \
		if(*this->variable < this->lowerBound || *this->variable > this->upperBound){  \
			snprintf(str, len, "ERROR");					       \
			return;								       \
		}									       \
		snprintf(str, len, tmp, this->strings[*this->variable-this->lowerBound]);      \
	}else										       \
		snprintf(str, len, this->fmt, *this->variable);				       \
}											       \
void																			   \
MenuEntry_##NAME::setStrings(const char **strings)													   \
{																			   \
	this->strings = strings;															   \
	if(this->strings)																   \
		this->maxvallen = findStringLen();													   \
}																			   \
MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings)   \
: MenuEntry_Int(name)																	   \
{																			   \
	this->variable = ptr;																   \
	this->step = step;																   \
	this->lowerBound = lowerBound;															   \
	this->upperBound = upperBound;															   \
	this->triggerFunc = triggerFunc;														   \
	this->maxvallen = MAXLEN;															   \
	this->fmt = FMT;																   \
	this->setStrings(strings);																   \
}
INTTYPES
#undef X

/*
 * *****************************
 * MenuEntry_Float
 * *****************************
 */

#define X(NAME, TYPE, MAXLEN, FMT) \
MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound)	     \
: MenuEntry_Var(name, MENUVAR_FLOAT)															     \
{																	     \
	this->variable = ptr;														     \
	this->step = step;														     \
	this->lowerBound = lowerBound;													     \
	this->upperBound = upperBound;													     \
	this->triggerFunc = triggerFunc;												     \
	this->maxvallen = MAXLEN;													     \
	this->fmt = FMT;														     \
}																	     \
void																	     \
MenuEntry_##NAME::getValStr(char *str, int len)												     \
{																	     \
	snprintf(str, len, this->fmt, *this->variable);											     \
}																	     \
void																	     \
MenuEntry_##NAME::processInput(bool mouseOver, bool selected)													     \
{																	     \
	float v, oldv;															     \
	int overflow = 0;					     \
	int underflow = 0;					     \
																	     \
	v = *this->variable;														     \
	oldv = v;															     \
																	     \
	if((selected && leftjustdown) || (mouseOver && button3justdown)){					     \
		v -= this->step;				     \
		if(v > oldv)					     \
			underflow = 1;				     \
	}							     \
	if((selected && rightjustdown) || (mouseOver && button1justdown)){					     \
		v += this->step;				     \
		if(v < oldv)					     \
			overflow = 1;				     \
	}							     \
	if(this->wrapAround){					     \
		if(v > this->upperBound || overflow) v = this->lowerBound;	     \
		if(v < this->lowerBound || underflow) v = this->upperBound;	     \
	}else{									     \
		if(v > this->upperBound || overflow) v = this->upperBound;	     \
		if(v < this->lowerBound || underflow) v = this->lowerBound;	     \
	}							     \
																	     \
	*this->variable = v;														     \
	if(oldv != v && this->triggerFunc)												     \
		this->triggerFunc();													     \
}

FLOATTYPES
#undef X

/*
 * *****************************
 * MenuEntry_Cmd
 * *****************************
 */

void
MenuEntry_Cmd::processInput(bool mouseOver, bool selected)
{
	// Don't execute on button3
	if(this->triggerFunc && (selected && (leftjustdown || rightjustdown) || (mouseOver && button1justdown)))
		this->triggerFunc();
}

void
MenuEntry_Cmd::getValStr(char *str, int len)
{
	strncpy(str, "<", len);
}

MenuEntry_Cmd::MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc)
: MenuEntry_Var(name, MENUVAR_CMD)
{
	this->maxvallen = 1;
	this->triggerFunc = triggerFunc;
}

/*
 * *****************************
 * Menu
 * *****************************
 */

void
Menu::scroll(int off) {
	if(isScrollingUp && off < 0)
		scrollStart += off;
	if(isScrollingDown && off > 0)
		scrollStart += off;
	if(scrollStart < 0) scrollStart = 0;
	if(scrollStart > numEntries-numVisible) scrollStart = numEntries-numVisible;
}

void
Menu::changeSelection(int newsel){
	selection = newsel;
	if(selection < 0) selection = 0;
	if(selection >= numEntries) selection = numEntries-1;
	if(selection < scrollStart) scrollStart = selection;
	if(selection >= scrollStart+numVisible) scrollStart = selection-numVisible+1;
}

void
Menu::changeSelection(MenuEntry *sel)
{
	MenuEntry *e;
	int i = 0;
	for(e = this->entries; e; e = e->next){
		if(e == sel){
			this->selection = i;
			this->selectedEntry = sel;
			break;
		}
		i++;
	}
}

MenuEntry*
Menu::findEntry(const char *entryname)
{
	MenuEntry *m;
	for(m = this->entries; m; m = m->next)
		if(strcmp(entryname, m->name) == 0)
			return m;
	return nil;
}

void
Menu::insertEntrySorted(MenuEntry *entry)
{
	MenuEntry **mp;
	int cmp;
	for(mp = &this->entries; *mp; mp = &(*mp)->next){
		cmp = strcmp(entry->name, (*mp)->name);
		if(cmp == 0)
			return;
		if(cmp < 0)
			break;
	}
	entry->next = *mp;
	*mp = entry;
	entry->menu = this;
	this->numEntries++;
}

void
Menu::appendEntry(MenuEntry *entry)
{
	MenuEntry **mp;
	for(mp = &this->entries; *mp; mp = &(*mp)->next);
	entry->next = *mp;
	*mp = entry;
	entry->menu = this;
	this->numEntries++;
}

void
Menu::update(void)
{
	int i;
	int x, y;
	Pt sz;
	MenuEntry *e;
	int onscreen;
	x = this->r.x;
	y = this->r.y + 18;
	int end = this->r.y+this->r.h - 18;
	this->numVisible = 0;

	deepestMenu = this;

	int bottomy = end;
	onscreen = 1;
	i = 0;
	this->maxNameWidth = 0;
	this->maxValWidth = 0;
	this->isScrollingUp = this->scrollStart > 0;
	this->isScrollingDown = false;
	this->selectedEntry = nil;
	for(e = this->entries; e; e = e->next){
		sz = fontGetStringSize(e->name);
		e->r.x = x;
		e->r.y = y;
		e->r.w = sz.x;
		e->r.h = sz.y;

		if(i == this->selection)
			this->selectedEntry = e;

		if(i >= this->scrollStart)
			y += sz.y + leading*fontscale;
		if(y >= end){
			this->isScrollingDown = true;
			onscreen = 0;
		}else
			bottomy = y;
		if(i >= this->scrollStart && onscreen)
			this->numVisible++;

		if(e->type == MENUVAR){
			int valwidth = fontGetLen(((MenuEntry_Var*)e)->getValWidth());
			if(valwidth > maxValWidth)
				maxValWidth = valwidth;
		}
		if(e->r.w > maxNameWidth)
			maxNameWidth = e->r.w;
		i++;
	}
	if(this->r.w < maxNameWidth + maxValWidth + gap*fontscale)
		this->r.w = maxNameWidth + maxValWidth + gap*fontscale;

	this->scrollUpR = this->r;
	this->scrollUpR.h = 16;
	this->scrollDownR = this->scrollUpR;
	this->scrollDownR.y = bottomy;

	// Update active submenu
	if(this->selectedEntry && this->selectedEntry->type == MENUSUB){
		Menu *submenu = ((MenuEntry_Sub*)this->selectedEntry)->submenu;
		submenu->r.x = this->r.x+this->r.w + 10;
		submenu->r.y = this->r.y;
		submenu->r.w = minwidth;	// update menu will expand
		submenu->r.h = this->r.h;
		submenu->update();
	}
}

void
Menu::draw(void)
{
	static char val[100];
	int i;
	MenuEntry *e;
	i = 0;
	for(e = this->entries; e; e = e->next){
		if(i >= this->scrollStart+this->numVisible)
			break;
		if(i >= this->scrollStart){
			int style = MENUFONT_NORMAL;
			if(i == this->selection)
				style = this == activeMenu ? MENUFONT_SEL_ACTIVE : MENUFONT_SEL_INACTIVE;
			if(style != MENUFONT_SEL_ACTIVE && e == mouseOverEntry)
				style = MENUFONT_MOUSE;
			fontPrint(e->name, e->r.x, e->r.y, style);
			if(e->type == MENUVAR){
				int valw = fontGetLen(((MenuEntry_Var*)e)->getValWidth());
				((MenuEntry_Var*)e)->getValStr(val, 100);
				fontPrint(val, e->r.x+this->r.w-valw, e->r.y, style);
			}
		}
		i++;
	}

	if(this->isScrollingUp)
		drawArrow(this->scrollUpR, -1, isMouseInRect(this->scrollUpR));
	if(this->isScrollingDown)
		drawArrow(this->scrollDownR, 1, isMouseInRect(this->scrollDownR));

	if(this->selectedEntry && this->selectedEntry->type == MENUSUB)
		((MenuEntry_Sub*)this->selectedEntry)->submenu->draw();
}

Menu::~Menu(void)
{
	MenuEntry *e, *next;
	for(e = entries; e; e = next){
		next = e->next;
		delete e;
	}
	memset(this, 0, sizeof(Menu));
}

Menu*
findMenu(const char *name)
{
	Menu *m;
	MenuEntry *e;
	char *tmppath = strdup(name);
	char *next, *curname;

	curname = tmppath;
	next = curname;

	m = &toplevel;
	while(*next){
		curname = next;
		while(*next){
			if(*next == '|'){
				*next++ = '\0';
				break;
			}
			next++;
		}
		e = m->findEntry(curname);
		if(e){
			// return an error if the entry exists but isn't a menu
			if(e->type != MENUSUB){
				free(tmppath);
				return nil;
			}
			m = ((MenuEntry_Sub*)e)->submenu;
		}else{
			// Create submenus that don't exist yet
			Menu *submenu = new Menu();
			submenu->parent = m;
			MenuEntry *me = new MenuEntry_Sub(curname, submenu);
			// Don't sort submenus outside the toplevel menu
			if(m == &toplevel)
				m->insertEntrySorted(me);
			else
				m->appendEntry(me);
			m = submenu;
		}
	}

	free(tmppath);
	return m;
}

/*
 * ****************
 * debug menu
 * ****************
 */

static uint8 cursorPx[] = {
#include "cursor.inc"
};

static uint8 arrowPx[] = {
#include "arrow.inc"
};

void
DebugMenuInit(void)
{
	createMenuFont();

	RwInt32 w, h, d, flags;
	RwImage *img = RwImageCreate(16, 16, 32);
	assert(img);
	RwImageSetPixels(img, cursorPx);
	RwImageSetStride(img, RwImageGetWidth(img)*4);
	RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags);
	cursor = RwRasterCreate(w, h, d, flags);
	cursor = RwRasterSetFromImage(cursor, img);
	assert(cursor);
	RwImageDestroy(img);

	img = RwImageCreate(32, 16, 32);
	assert(img);
	RwImageSetPixels(img, arrowPx);
	RwImageSetStride(img, RwImageGetWidth(img)*4);
	RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags);
	arrow = RwRasterCreate(w, h, d, flags);
	arrow = RwRasterSetFromImage(arrow, img);
	assert(arrow);
	RwImageDestroy(img);

	menuInitialized = true;
}

void
DebugMenuShutdown(void)
{
	if(menuInitialized){
		destroyMenuFont();
		RwRasterDestroy(cursor);
		cursor = nil;
		RwRasterDestroy(arrow);
		arrow = nil;

		toplevel.~Menu();
		new (&toplevel) Menu();

		activeMenu = &toplevel;
		deepestMenu = &toplevel;
		mouseOverMenu = nil;
		mouseOverEntry = nil;
	}
	menuInitialized = false;
}

void
processInput(void)
{
	int shift = KEYDOWN(rsRSHIFT) || KEYDOWN(rsLSHIFT);
#define X(var, keycode) var = KEYJUSTDOWN(keycode);
	MUHKEYS
#undef X

	// Implement auto-repeat
#define X(var, keycode) \
	if(var){ \
		repeattime = downtime = CTimer::GetTimeInMilliseconds(); \
		lastkeydown = keycode; \
		keyptr = &var; \
	}
	MUHKEYS
#undef X
	if(lastkeydown){
		if(KEYDOWN(lastkeydown)){
			int curtime = CTimer::GetTimeInMilliseconds();
			if(curtime - downtime > REPEATDELAY){
				if(curtime - repeattime > REPEATINTERVAL){
					repeattime = curtime;
					*keyptr = 1;
				}
			}
		}else{
			lastkeydown = 0;
		}
	}

	// Also for mouse buttons
#define X(var, num)							  \
	if(var){							  \
		repeattime = downtime = CTimer::GetTimeInMilliseconds();	  \
		lastbuttondown = num;					  \
		buttonptr = &var;					  \
	}
	MUHBUTTONS
#undef X
	if(lastbuttondown){
		if(buttondown[lastbuttondown-1]){
			int curtime = CTimer::GetTimeInMilliseconds();
			if(curtime - downtime > REPEATDELAY){
				if(curtime - repeattime > REPEATINTERVAL){
					repeattime = curtime;
					*buttonptr = 1;
				}
			}
		}else{
			lastbuttondown = 0;
		}
	}

	// Walk through all visible menus and figure out which one the mouse is over
	mouseOverMenu = nil;
	mouseOverEntry = nil;
	Menu *menu;
	for(menu = deepestMenu; menu; menu = menu->parent)
		if(isMouseInRect(menu->r)){
			mouseOverMenu = menu;
			break;
		}
	if(mouseOverMenu){
		// Walk all visibile entries and figure out which one the mouse is over
		MenuEntry *e;
		int i = 0;
		for(e = mouseOverMenu->entries; e; e = e->next){
			if(i >= mouseOverMenu->scrollStart+mouseOverMenu->numVisible)
				break;
			if(i >= mouseOverMenu->scrollStart){
				RwRect r = e->r;
				r.w = mouseOverMenu->r.w;	// span the whole menu
				if(isMouseInRect(r)){
					mouseOverEntry = e;
					break;
				}
			}
			i++;
		}
		if(mouseOverMenu->isScrollingUp && isMouseInRect(mouseOverMenu->scrollUpR)){
			mouseOverEntry = &scrollUpEntry;
			mouseOverEntry->r = mouseOverMenu->scrollUpR;
			mouseOverEntry->menu = mouseOverMenu;
			mouseOverEntry->type = MENUSCROLL;
		}
		if(mouseOverMenu->isScrollingDown && isMouseInRect(mouseOverMenu->scrollDownR)){
			mouseOverEntry = &scrollDownEntry;
			mouseOverEntry->r = mouseOverMenu->scrollDownR;
			mouseOverEntry->menu = mouseOverMenu;
			mouseOverEntry->type = MENUSCROLL;
		}
	}

	if(pgupjustdown)
		activeMenu->scroll(shift ? -5 : -1);
	if(pgdnjustdown)
		activeMenu->scroll(shift ? 5 : 1);
	if(downjustdown)
		activeMenu->changeSelection(activeMenu->selection + (shift ? 5 : 1));
	if(upjustdown)
		activeMenu->changeSelection(activeMenu->selection - (shift ? 5 : 1));

	if(CPad::NewMouseControllerState.WHEELUP){
		if(mouseOverMenu)
			activeMenu = mouseOverMenu;
		activeMenu->scroll(shift ? -5 : -1);
	}
	if(CPad::NewMouseControllerState.WHEELDN){
		if(mouseOverMenu)
			activeMenu = mouseOverMenu;
		activeMenu->scroll(shift ? 5 : 1);
	}

	if(mouseOverEntry == &scrollUpEntry){
		if(button1justdown){
			activeMenu = mouseOverEntry->menu;
			activeMenu->scroll(shift ? -5 : -1);
		}
	}
	if(mouseOverEntry == &scrollDownEntry){
		if(button1justdown){
			activeMenu = mouseOverEntry->menu;
			activeMenu->scroll(shift ? 5 : 1);
		}
	}

	// Have to call this before processInput below because menu entry can change
	if((button1justdown || button3justdown) && mouseOverEntry){
		activeMenu = mouseOverEntry->menu;
		activeMenu->changeSelection(mouseOverEntry);
	}
	if(KEYJUSTDOWN(rsENTER)){
		if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUSUB)
			activeMenu = ((MenuEntry_Sub*)activeMenu->selectedEntry)->submenu;
	}else if(KEYJUSTDOWN(rsBACKSP)){
		if(activeMenu->parent)
			activeMenu = activeMenu->parent;
	}else{
		if(mouseOverEntry && mouseOverEntry->type == MENUVAR)
			((MenuEntry_Var*)mouseOverEntry)->processInput(true, mouseOverEntry == activeMenu->selectedEntry);
		if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUVAR &&
		   mouseOverEntry != activeMenu->selectedEntry)
			((MenuEntry_Var*)activeMenu->selectedEntry)->processInput(false, true);
	}
}

void
updateMouse(void)
{
	CPad *pad = CPad::GetPad(0);
	int dirX = 1;
	int dirY = 1;

	if(MousePointerStateHelper.bInvertHorizontally) dirX = -1;
	if(MousePointerStateHelper.bInvertVertically) dirY = -1;

	mouseX += pad->NewMouseControllerState.x*dirX;
	mouseY += pad->NewMouseControllerState.y*dirY;

	if(mouseX < 0.0f) mouseX = 0.0f;
	if(mouseY < 0.0f) mouseY = 0.0f;
	if(mouseX >= screenWidth) mouseX = screenWidth;
	if(mouseY >= screenHeight) mouseY = screenHeight;

	button1justdown = pad->NewMouseControllerState.LMB && !pad->OldMouseControllerState.LMB;
	button2justdown = pad->NewMouseControllerState.MMB && !pad->OldMouseControllerState.MMB;
	button3justdown = pad->NewMouseControllerState.RMB && !pad->OldMouseControllerState.RMB;
	buttondown[0] = pad->NewMouseControllerState.LMB;
	buttondown[1] = pad->NewMouseControllerState.MMB;
	buttondown[2] = pad->NewMouseControllerState.RMB;

	// Zero the mouse position so the camera won't move
	pad->NewMouseControllerState.x = 0.0f;
	pad->NewMouseControllerState.y = 0.0f;
}

void
DebugMenuProcess(void)
{
	// We only process some input here

	CPad *pad = CPad::GetPad(0);
	if(CTRLJUSTDOWN('M'))
		menuOn = !menuOn;
	if(!menuOn)
		return;

	pad->DisablePlayerControls = 1;
	// TODO: this could happen earlier
	if(!menuInitialized)
		DebugMenuInit();
	updateMouse();

}

void
DebugMenuRender(void)
{
	if(!menuOn)
		return;

	RwRenderStateSet(rwRENDERSTATEZTESTENABLE, 0);
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, 0);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEFOGENABLE, 0);
	RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE);

	RwCamera *cam = RwCameraGetCurrentCamera();
	screenWidth = RwRasterGetWidth(RwCameraGetRaster(cam));
	screenHeight = RwRasterGetHeight(RwCameraGetRaster(cam));

//	if(screenHeight > 1080)
//		fontscale = 2;
//	else
		fontscale = 1;

	Pt sz;
	sz = fontPrint("Debug Menu", firstBorder*fontscale+30, topBorder, 0);

	toplevel.r.x = firstBorder*fontscale;
	toplevel.r.y = topBorder + sz.y + 10;
	toplevel.r.w = minwidth;	// update menu will expand
	toplevel.r.h = screenHeight - 10 - toplevel.r.y;
	toplevel.update();
	toplevel.draw();
	processInput();
	RtCharsetBufferFlush();

	drawMouse();
}

void
drawArrow(RwRect r, int direction, int style)
{
	static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 };
	static RwIm2DVertex arrowVerts[4];

	RwCamera *cam = RwCameraGetCurrentCamera();
	float recipz = 1.0f/RwCameraGetNearClipPlane(cam);

	int width = RwRasterGetWidth(arrow);
	int height = RwRasterGetHeight(arrow);

	int left = r.x + (r.w - width)/2;
	int right = left + width;
	int top = r.y;
	int bottom = r.y+r.h;

	float umin = HALFPX / width;
	float vmin = HALFPX / height;
	float umax = (width + HALFPX) / width;
	float vmax = (height + HALFPX) / height;
	if(direction < 0){
		vmin = (height - HALFPX) / height;
		vmax = -HALFPX / height;
	}

	if(style){
		RwIm2DVertexSetScreenX(&arrowVerts[0], r.x);
		RwIm2DVertexSetScreenY(&arrowVerts[0], r.y-1);
		RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ());
		RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam));
		RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz);
		RwIm2DVertexSetIntRGBA(&arrowVerts[0], 132, 132, 132, 255);

		RwIm2DVertexSetScreenX(&arrowVerts[1], r.x+r.w);
		RwIm2DVertexSetScreenY(&arrowVerts[1], r.y-1);
		RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ());
		RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam));
		RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz);
		RwIm2DVertexSetIntRGBA(&arrowVerts[1], 132, 132, 132, 255);

		RwIm2DVertexSetScreenX(&arrowVerts[2], r.x);
		RwIm2DVertexSetScreenY(&arrowVerts[2], r.y+r.h+1);
		RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ());
		RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam));
		RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz);
		RwIm2DVertexSetIntRGBA(&arrowVerts[2], 132, 132, 132, 255);

		RwIm2DVertexSetScreenX(&arrowVerts[3], r.x+r.w);
		RwIm2DVertexSetScreenY(&arrowVerts[3], r.y+r.h+1);
		RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ());
		RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam));
		RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz);
		RwIm2DVertexSetIntRGBA(&arrowVerts[3], 132, 132, 132, 255);

		RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
		RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
		RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6);
	}

	RwIm2DVertexSetScreenX(&arrowVerts[0], left);
	RwIm2DVertexSetScreenY(&arrowVerts[0], top);
	RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz);
	RwIm2DVertexSetIntRGBA(&arrowVerts[0], 255, 255, 255, 255);
	RwIm2DVertexSetU(&arrowVerts[0], umin, recipz);
	RwIm2DVertexSetV(&arrowVerts[0], vmin, recipz);

	RwIm2DVertexSetScreenX(&arrowVerts[1], right);
	RwIm2DVertexSetScreenY(&arrowVerts[1], top);
	RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz);
	RwIm2DVertexSetIntRGBA(&arrowVerts[1], 255, 255, 255, 255);
	RwIm2DVertexSetU(&arrowVerts[1], umax, recipz);
	RwIm2DVertexSetV(&arrowVerts[1], vmin, recipz);

	RwIm2DVertexSetScreenX(&arrowVerts[2], left);
	RwIm2DVertexSetScreenY(&arrowVerts[2], bottom);
	RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz);
	RwIm2DVertexSetIntRGBA(&arrowVerts[2], 255, 255, 255, 255);
	RwIm2DVertexSetU(&arrowVerts[2], umin, recipz);
	RwIm2DVertexSetV(&arrowVerts[2], vmax, recipz);

	RwIm2DVertexSetScreenX(&arrowVerts[3], right);
	RwIm2DVertexSetScreenY(&arrowVerts[3], bottom);
	RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz);
	RwIm2DVertexSetIntRGBA(&arrowVerts[3], 255, 255, 255, 255);
	RwIm2DVertexSetU(&arrowVerts[3], umax, recipz);
	RwIm2DVertexSetV(&arrowVerts[3], vmax, recipz);

	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, arrow);
	RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
	RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6);
}

void
drawMouse(void)
{
	static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 };
	static RwIm2DVertex vertices[4];
	RwIm2DVertex *vert;
	RwCamera *cam;
	cam = RwCameraGetCurrentCamera();
	float x = mouseX;
	float y = mouseY;
	float w = RwRasterGetWidth(cursor);
	float h = RwRasterGetHeight(cursor);
	float recipz = 1.0f/RwCameraGetNearClipPlane(cam);

	float umin = HALFPX / w;
	float vmin = HALFPX / h;
	float umax = (w + HALFPX) / w;
	float vmax = (h + HALFPX) / h;

	vert = vertices;
	RwIm2DVertexSetScreenX(vert, x);
	RwIm2DVertexSetScreenY(vert, y);
	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(vert, recipz);
	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
	RwIm2DVertexSetU(vert, umin, recipz);
	RwIm2DVertexSetV(vert, vmin, recipz);
	vert++;

	RwIm2DVertexSetScreenX(vert, x+w);
	RwIm2DVertexSetScreenY(vert, y);
	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(vert, recipz);
	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
	RwIm2DVertexSetU(vert, umax, recipz);
	RwIm2DVertexSetV(vert, vmin, recipz);
	vert++;

	RwIm2DVertexSetScreenX(vert, x);
	RwIm2DVertexSetScreenY(vert, y+h);
	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(vert, recipz);
	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
	RwIm2DVertexSetU(vert, umin, recipz);
	RwIm2DVertexSetV(vert, vmax, recipz);
	vert++;

	RwIm2DVertexSetScreenX(vert, x+w);
	RwIm2DVertexSetScreenY(vert, y+h);
	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
	RwIm2DVertexSetRecipCameraZ(vert, recipz);
	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
	RwIm2DVertexSetU(vert, umax, recipz);
	RwIm2DVertexSetV(vert, vmax, recipz);
	vert++;

	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, cursor);
	RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
	RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, vertices, 4, indices, 6);
}

/*
 * Generate interfaces
 */

#define X(NAME, TYPE, unused1, unused2) \
MenuEntry*																			\
DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings)	\
{																				\
	Menu *m = findMenu(path);																\
	if(m == nil)																		\
		return nil;																		\
	MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound, strings);							\
	m->appendEntry(e);							\
	return e;																		\
}
INTTYPES
#undef X

#define X(NAME, TYPE, unused1, unused2) \
MenuEntry*																			\
DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound)	\
{																				\
	Menu *m = findMenu(path);																\
	if(m == nil)																		\
		return nil;																		\
	MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound);								\
	m->appendEntry(e);							\
	return e;																		\
}
FLOATTYPES
#undef X

MenuEntry*																			\
DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc)
{
	Menu *m = findMenu(path);
	if(m == nil)
		return nil;
	MenuEntry *e = new MenuEntry_Cmd(name, triggerFunc);
	m->appendEntry(e);
	return e;
}

void
DebugMenuEntrySetWrap(MenuEntry *e, bool wrap)
{
	if(e && e->type == MENUVAR)
		((MenuEntry_Var*)e)->wrapAround = wrap;
}

void
DebugMenuEntrySetStrings(MenuEntry *e, const char **strings)
{
	if(e && e->type == MENUVAR_INT)
		((MenuEntry_Int*)e)->setStrings(strings);
}

void
DebugMenuEntrySetAddress(MenuEntry *e, void *addr)
{
	if(e && e->type == MENUVAR){
		MenuEntry_Var *ev = (MenuEntry_Var*)e;
		// HACK - we know the variable field is at the same address
		// for all int/float classes. let's hope it stays that way.
		if(ev->vartype = MENUVAR_INT)
			((MenuEntry_Int32*)e)->variable = (int32*)addr;
		else if(ev->vartype = MENUVAR_FLOAT)
			((MenuEntry_Float32*)e)->variable = (float*)addr;
	}
}
#endif


code:
#pragma once

#ifdef DEBUGMENU

// Tweaking stuff for debugmenu
#define TWEAKPATH                                   ___tw___TWEAKPATH
#define SETTWEAKPATH(path)	                        static const char *___tw___TWEAKPATH = path;
#define TWEAKFUNC(v)                                static CTweakFunc   CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), TWEAKPATH);
#define TWEAKFUNCN(v, name)                         static CTweakFunc   CONCAT(___tw___tweak, __COUNTER__)(&v, name, TWEAKPATH);
#define TWEAKBOOL(v)                                static CTweakBool   CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), TWEAKPATH);
#define TWEAKBOOLN(v, name)                         static CTweakBool   CONCAT(___tw___tweak, __COUNTER__)(&v, name, TWEAKPATH);
#define TWEAKINT32(v, lower, upper, step)           static CTweakInt32  CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH);
#define TWEAKINT32N(v, lower, upper, step, name)    static CTweakInt32  CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH);
#define TWEAKUINT32(v, lower, upper, step)          static CTweakUInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH);
#define TWEAKUINT32N(v, lower, upper, step, name)   static CTweakUInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH);
#define TWEAKINT16(v, lower, upper, step)           static CTweakInt16  CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH);
#define TWEAKINT16N(v, lower, upper, step, name)    static CTweakInt16  CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH);
#define TWEAKUINT16(v, lower, upper, step)          static CTweakUInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH);
#define TWEAKUINT16N(v, lower, upper, step, name)   static CTweakUInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH);
#define TWEAKINT8(v, lower, upper, step)            static CTweakInt8   CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH);
#define TWEAKINT8N(v, lower, upper, step, name)     static CTweakInt8   CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH);
#define TWEAKUINT8(v, lower, upper, step)           static CTweakUInt8  CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH);
#define TWEAKUINT8N(v, lower, upper, step, name)    static CTweakUInt8  CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH);
#define TWEAKFLOAT(v, lower, upper, step)           static CTweakFloat  CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH);
#define TWEAKFLOATN(v, lower, upper, step, name)    static CTweakFloat  CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH);
#define TWEAKSWITCH(v, lower, upper, str, f)        static CTweakSwitch CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, str, f, TWEAKPATH);
#define TWEAKSWITCHN(v, lower, upper, str, f, name) static CTweakSwitch CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, str, f, TWEAKPATH);

// interface
class CTweakVar
{
public:
	virtual void AddDBG(const char* path) = 0;
};

class CTweakVars
{
public:
	static void Add(CTweakVar* var);
	static void AddDBG(const char* path);
};

class CTweakFunc : public CTweakVar
{
	const char* m_pPath, * m_pVarName;
	void (*m_pFunc)();
public:
	CTweakFunc(void (*pFunc)(), const char* strName, const char* strPath) :
		m_pPath(strPath), m_pVarName(strName), m_pFunc(pFunc)
	{
		CTweakVars::Add(this);
	}

	void AddDBG(const char* path);
};

class CTweakBool : public CTweakVar
{
	const char* m_pPath, * m_pVarName;
	bool* m_pBoolVar;
public:
	CTweakBool(bool* pBool, const char* strName, const char* strPath) :
		m_pPath(strPath), m_pVarName(strName), m_pBoolVar(pBool)
	{
		CTweakVars::Add(this);
	}

	void AddDBG(const char* path);
};

class CTweakSwitch : public CTweakVar
{
	const char* m_pPath, * m_pVarName;
	void* m_pIntVar;
	int32 m_nMin, m_nMax;
	const char** m_aStr;
	void (*m_pFunc)();
public:
	CTweakSwitch(void* pInt, const char* strName, int32 nMin, int32 nMax, const char** aStr,
		void (*pFunc)(), const char* strPath)
		: m_pPath(strPath), m_pVarName(strName), m_pIntVar(pInt), m_nMin(nMin), m_nMax(nMax),
		m_aStr(aStr)
	{
		CTweakVars::Add(this);
	}

	void AddDBG(const char* path);
};

#define _TWEEKCLASS(name, type)                                                                    \
	class name : public CTweakVar                                                              \
	{                                                                                \
	public:                                                                                \
		const char *m_pPath, *m_pVarName;                                                  \
		type *m_pIntVar, m_nLoawerBound, m_nUpperBound, m_nStep;                           \
                                                                                \
		name(type *pInt, const char *strName, type nLower, type nUpper, type nStep,        \
		     const char *strPath)                                                          \
		    : m_pPath(strPath), m_pVarName(strName), m_pIntVar(pInt),                      \
		      m_nLoawerBound(nLower), m_nUpperBound(nUpper), m_nStep(nStep)                \
                                                                                \
		{                                                                                \
			CTweakVars::Add(this);                                                     \
		}                                                                                \
                                                                                \
		void AddDBG(const char *path);                                                     \
	};

_TWEEKCLASS(CTweakInt8, int8);
_TWEEKCLASS(CTweakUInt8, uint8);
_TWEEKCLASS(CTweakInt16, int16);
_TWEEKCLASS(CTweakUInt16, uint16);
_TWEEKCLASS(CTweakInt32, int32);
_TWEEKCLASS(CTweakUInt32, uint32);
_TWEEKCLASS(CTweakFloat, float);

#undef _TWEEKCLASS

typedef void (*TriggerFunc)(void);

struct Menu;

struct MenuEntry
{
	int type;
	const char *name;
	MenuEntry *next;
	RwRect r;
	Menu *menu;

	MenuEntry(const char *name);
	virtual ~MenuEntry(void) { free((void*)name); }
};

typedef MenuEntry DebugMenuEntry;

MenuEntry *DebugMenuAddInt8(const char *path, const char *name, int8 *ptr, TriggerFunc triggerFunc, int8 step, int8 lowerBound, int8 upperBound, const char **strings);
MenuEntry *DebugMenuAddInt16(const char *path, const char *name, int16 *ptr, TriggerFunc triggerFunc, int16 step, int16 lowerBound, int16 upperBound, const char **strings);
MenuEntry *DebugMenuAddInt32(const char *path, const char *name, int32 *ptr, TriggerFunc triggerFunc, int32 step, int32 lowerBound, int32 upperBound, const char **strings);
MenuEntry *DebugMenuAddInt64(const char *path, const char *name, int64 *ptr, TriggerFunc triggerFunc, int64 step, int64 lowerBound, int64 upperBound, const char **strings);
MenuEntry *DebugMenuAddUInt8(const char *path, const char *name, uint8 *ptr, TriggerFunc triggerFunc, uint8 step, uint8 lowerBound, uint8 upperBound, const char **strings);
MenuEntry *DebugMenuAddUInt16(const char *path, const char *name, uint16 *ptr, TriggerFunc triggerFunc, uint16 step, uint16 lowerBound, uint16 upperBound, const char **strings);
MenuEntry *DebugMenuAddUInt32(const char *path, const char *name, uint32 *ptr, TriggerFunc triggerFunc, uint32 step, uint32 lowerBound, uint32 upperBound, const char **strings);
MenuEntry *DebugMenuAddUInt64(const char *path, const char *name, uint64 *ptr, TriggerFunc triggerFunc, uint64 step, uint64 lowerBound, uint64 upperBound, const char **strings);
MenuEntry *DebugMenuAddFloat32(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound);
MenuEntry *DebugMenuAddFloat64(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound);
MenuEntry *DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc);
void DebugMenuEntrySetWrap(MenuEntry *e, bool wrap);
void DebugMenuEntrySetStrings(MenuEntry *e, const char **strings);
void DebugMenuEntrySetAddress(MenuEntry *e, void *addr);
void DebugMenuInit(void);
void DebugMenuShutdown(void);
void DebugMenuProcess(void);
void DebugMenuRender(void);

// Some overloads for simplicity
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings)
{ return DebugMenuAddInt8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings)
{ return DebugMenuAddInt16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings)
{ return DebugMenuAddInt32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings)
{ return DebugMenuAddInt64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings)
{ return DebugMenuAddUInt8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings)
{ return DebugMenuAddUInt16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings)
{ return DebugMenuAddUInt32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings)
{ return DebugMenuAddUInt64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound)
{ return DebugMenuAddFloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound)
{ return DebugMenuAddFloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }

inline DebugMenuEntry *DebugMenuAddVarBool32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc)
{
	static const char *boolstr[] = { "Off", "On" };
	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
	DebugMenuEntrySetWrap(e, true);
	return e;
}
inline DebugMenuEntry *DebugMenuAddVarBool16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc)
{
	static const char *boolstr[] = { "Off", "On" };
	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
	DebugMenuEntrySetWrap(e, true);
	return e;
}
inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc)
{
	static const char *boolstr[] = { "Off", "On" };
	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
	DebugMenuEntrySetWrap(e, true);
	return e;
}
inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, bool *ptr, TriggerFunc triggerFunc)
{
	return DebugMenuAddVarBool8(path, name, (int8_t*)ptr, triggerFunc);
}
#endif


code:
#include "common.h"

#ifdef CUSTOM_FRONTEND_OPTIONS
#include "Frontend.h"
#include "Text.h"

int lastOgScreen = MENUPAGES; // means no new pages

int numCustomFrontendOptions = 0;
int numCustomFrontendScreens = 0;

int optionCursor = -2;
int currentMenu;
bool optionOverwrite = false;

void ChangeScreen(int screen, int option, bool fadeIn)
{
	FrontEndMenuManager.m_nPrevScreen = FrontEndMenuManager.m_nCurrScreen;
	FrontEndMenuManager.m_nCurrScreen = screen;
	FrontEndMenuManager.m_nCurrOption = option;
	if (fadeIn)
		FrontEndMenuManager.m_nMenuFadeAlpha = 0;
}

void GoBack(bool fadeIn)
{
	int screen = !FrontEndMenuManager.m_bGameNotLoaded ?
		aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[1] : aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[0];
	int option = FrontEndMenuManager.GetPreviousPageOption();

	FrontEndMenuManager.ThingsToDoBeforeGoingBack();

	ChangeScreen(screen, option, fadeIn);
}

uint8
GetNumberOfMenuOptions(int screen)
{
	uint8 Rows = 0;
	for (int i = 0; i < NUM_MENUROWS; i++) {
		if (aScreens[screen].m_aEntries[i].m_Action == MENUACTION_NOTHING)
			break;

		++Rows;
	}
	return Rows;
}

uint8
GetLastMenuScreen()
{
	int8 page = -1;
	for (int i = 0; i < MENUPAGES; i++) {
		if (strcmp(aScreens[i].m_ScreenName, "") == 0 && aScreens[i].m_PreviousPage[0] == MENUPAGE_NONE)
			break;

		++page;
	}
	return page;
}

int8 RegisterNewScreen(const char *name, int prevPage, ReturnPrevPageFunc returnPrevPageFunc)
{
	if (lastOgScreen == MENUPAGES)
		lastOgScreen = GetLastMenuScreen();

	numCustomFrontendScreens++;
	int id = lastOgScreen + numCustomFrontendScreens;
	assert(id < MENUPAGES && "No room for new custom frontend screens! Increase MENUPAGES");
	strncpy(aScreens[id].m_ScreenName, name, 8);
	aScreens[id].m_PreviousPage[0] = aScreens[id].m_PreviousPage[1] = prevPage;
	aScreens[id].returnPrevPageFunc = returnPrevPageFunc;
	return id;
}

int8 RegisterNewOption()
{
	numCustomFrontendOptions++;
	uint8 numOptions = GetNumberOfMenuOptions(currentMenu);
	uint8 curIdx;
	if (optionCursor < 0) {
		optionCursor = curIdx = numOptions + optionCursor + 1;
	} else
		curIdx = optionCursor;

	if (!optionOverwrite) {
		if (aScreens[currentMenu].m_aEntries[curIdx].m_Action != MENUACTION_NOTHING) {
			for (int i = numOptions - 1; i >= curIdx; i--) {
				memcpy(&aScreens[currentMenu].m_aEntries[i + 1], &aScreens[currentMenu].m_aEntries[i], sizeof(CMenuScreenCustom::CMenuEntry));
			}
		}
	}
	optionCursor++;
	return curIdx;
}

void FrontendOptionSetCursor(int screen, int8 option, bool overwrite)
{
	currentMenu = screen;
	optionCursor = option;
	optionOverwrite = overwrite;
}

void FrontendOptionAddBuiltinAction(const char* gxtKey, int action, int targetMenu, int saveSlot) {
	int8 screenOptionOrder = RegisterNewOption();

	CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder];

	// We can't use custom text on those :shrug:
	switch (action) {
		case MENUACTION_SCREENRES:
			strcpy(option.m_EntryName, "FED_RES");
			break;
		case MENUACTION_AUDIOHW:
			strcpy(option.m_EntryName, "FEA_3DH");
			break;
		default:
			strncpy(option.m_EntryName, gxtKey, 8);
			break;
	}
	option.m_Action = action;
	option.m_SaveSlot = saveSlot;
	option.m_TargetMenu = targetMenu;
}

void FrontendOptionAddSelect(const char* gxtKey, const char** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, const char* saveCat, const char* saveKey, bool disableIfGameLoaded)
{
	int8 screenOptionOrder = RegisterNewOption();

	CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder];
	option.m_Action = MENUACTION_CFO_SELECT;
	strncpy(option.m_EntryName, gxtKey, 8);
	option.m_CFOSelect = new CCFOSelect();
	option.m_CFOSelect->rightTexts = (char**)malloc(numRightTexts * sizeof(char*));
	memcpy(option.m_CFOSelect->rightTexts, rightTexts, numRightTexts * sizeof(char*));
	option.m_CFOSelect->numRightTexts = numRightTexts;
	option.m_CFOSelect->value = var;
	if (var) {
		option.m_CFOSelect->displayedValue = *var;
		option.m_CFOSelect->lastSavedValue = *var;
	}
	option.m_CFOSelect->saveCat = saveCat;
	option.m_CFOSelect->save = saveKey;
	option.m_CFOSelect->onlyApplyOnEnter = onlyApplyOnEnter;
	option.m_CFOSelect->changeFunc = changeFunc;
	option.m_CFOSelect->disableIfGameLoaded = disableIfGameLoaded;
}

void FrontendOptionAddDynamic(const char* gxtKey, DrawFunc drawFunc, int8 *var, ButtonPressFunc buttonPressFunc, const char* saveCat, const char* saveKey)
{
	int8 screenOptionOrder = RegisterNewOption();

	CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder];
	option.m_Action = MENUACTION_CFO_DYNAMIC;
	strncpy(option.m_EntryName, gxtKey, 8);
	option.m_CFODynamic = new CCFODynamic();
	option.m_CFODynamic->drawFunc = drawFunc;
	option.m_CFODynamic->buttonPressFunc = buttonPressFunc;
	option.m_CFODynamic->value = var;
	option.m_CFODynamic->saveCat = saveCat;
	option.m_CFODynamic->save = saveKey;
}

uint8 FrontendScreenAdd(const char* gxtKey, eMenuSprites sprite, int prevPage, int columnWidth, int headerHeight, int lineHeight,
	int8 font, float fontScaleX, float fontScaleY, int8 alignment, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc) {

	uint8 screenOrder = RegisterNewScreen(gxtKey, prevPage, returnPrevPageFunc);

	CCustomScreenLayout *screen = new CCustomScreenLayout();
	aScreens[screenOrder].layout = screen;
	screen->sprite = sprite;
	screen->columnWidth = columnWidth;
	screen->headerHeight = headerHeight;
	screen->lineHeight = lineHeight;
	screen->font = font;
	screen->fontScaleX = fontScaleX;
	screen->fontScaleY = fontScaleY;
	screen->alignment = alignment;

	return screenOrder;
}
#endif


code:
#pragma once
#include "common.h"

#ifdef CUSTOM_FRONTEND_OPTIONS

// ! There are 2 ways to use CFO,
//		1st; by adding a new option to the array in MenuScreensCustom.cpp and passing attributes/CBs to it
//		2nd; by calling the functions listed at the bottom of this file.

// -- Option types
//
// Static/select:	You allocate the variable, pass it to function and game sets it from user input among the strings given to function,
//					optionally you can add post-change event via ChangeFunc(only called on enter if onlyApplyOnEnter set, or set immediately)
//					You can store the option in an INI file if you pass the key(as a char array) to corresponding parameter.
//
// Dynamic:			Passing variable to function is only needed if you want to store it, otherwise you should do
//					all the operations with ButtonPressFunc, this includes allocating the variable.
//					Left-side text is passed while creating and static, but ofc right-side text is dynamic -
//					you should return it in DrawFunc, which is called on every draw.
//
// Built-in action:	As the name suggests, any action that game has built-in. But as an extra you can set the option text,

// -- Returned via ButtonPressFunc() action param.
#define FEOPTION_ACTION_LEFT 0
#define FEOPTION_ACTION_RIGHT 1
#define FEOPTION_ACTION_SELECT 2
#define FEOPTION_ACTION_FOCUSLOSS 3

// -- Passed via FrontendScreenAdd()
#define FESCREEN_CENTER 0
#define FESCREEN_LEFT_ALIGN 1
#define FESCREEN_RIGHT_ALIGN 2

// -- Callbacks

// pretty much in everything I guess, and optional in all of them
typedef void (*ReturnPrevPageFunc)();

// for static options
typedef void (*ChangeFunc)(int8 before, int8 after); // called after updating the value.
												// only called on enter if onlyApplyOnEnter set, otherwise called on every value change

typedef void (*ChangeFuncFloat)(float before, float after); // called after updating the value.

// for dynamic options
typedef wchar* (*DrawFunc)(bool* disabled, bool userHovering); // you must return a pointer for right text.
															// you can also set *disabled if you want to gray it out.
typedef void (*ButtonPressFunc)(int8 action); // see FEOPTION_ACTIONs above

// -- Internal things
void CustomFrontendOptionsPopulate();
extern int lastOgScreen; // for reloading
extern int numCustomFrontendOptions;
extern int numCustomFrontendScreens;

// -- To be used in ButtonPressFunc / ChangeFunc(this one would be weird):
void ChangeScreen(int screen, int option = 0, bool fadeIn = true);
void GoBack(bool fadeIn = true);

uint8 GetNumberOfMenuOptions(int screen);

// !!! We're now moved to MenuScreensCustom.cpp, which houses an array that keeps all original+custom options.
//		But you can still use the APIs below, and manipulate aScreens while in game.

// Limits:
//		The code relies on that you won't use more then NUM_MENUROWS(18) options on one page, and won't exceed the MENUPAGES of pages.
//		Also congrats if you can make 18 options visible at once.

// Texts:
//		All text parameters accept char[8] GXT key.

// Execute direction:
//		All of the calls below eventually manipulate the aScreens array, so keep in mind to add/replace options in order,
//		i.e. don't set cursor to 8 first and then 3.

// -- Placing the cursor to append/overwrite option
//
// Done via FrontendOptionSetCursor(screen, position, overwrite = false), parameters explained below:
// Screen:			as the name suggests. Also accepts the screen IDs returned from FrontendScreenAdd.
// Option:			if positive, next AddOption call will put the option to there and progress the cursor.
//					if negative, cursor will be placed on bottom-(pos+1), so -1 means the very bottom, -2 means before the back button etc.
// Overwrite:		Use to overwrite the options, not appending a new one. AddOption calls will still progress the cursor.

void FrontendOptionSetCursor(int screen, int8 option, bool overwrite = false);

// var is optional in AddDynamic, enables you to save them in an INI file(also needs passing char array to and saveCat saveKey param. obv), otherwise pass nil/0
void FrontendOptionAddBuiltinAction(const char* gxtKey, int action, int targetMenu = MENUPAGE_NONE, int saveSlot = SAVESLOT_NONE);
void FrontendOptionAddSelect(const char* gxtKey, const char** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, const char* saveCat = nil, const char* saveKey = nil, bool disableIfGameLoaded = false);
void FrontendOptionAddDynamic(const char* gxtKey, DrawFunc rightTextDrawFunc, int8 *var, ButtonPressFunc buttonPressFunc, const char* saveCat = nil, const char* saveKey = nil);

uint8 FrontendScreenAdd(const char* gxtKey, eMenuSprites sprite, int prevPage, int columnWidth, int headerHeight, int lineHeight, int8 font, float fontScaleX, float fontScaleY, int8 alignment, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc = nil);
#endif