/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / 3D rendering plugin
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */


#include "render3d.h"
#include "visual_surface.h"
#include "render3d_nodes.h"

void effect3d_reset(RenderEffect3D *eff)
{
	Chain *sbck = eff->sensors;
	Chain *dbck = eff->local_lights;
	memset(eff, 0, sizeof(RenderEffect3D));
	m4_mx_init(eff->model_matrix);
	m4_cmx_init(&eff->color_mat);
	eff->sensors = sbck;
	eff->local_lights = dbck;
	ChainReset(eff->sensors);

	while (ChainGetCount(eff->local_lights)) {
		DLightContext *dl = ChainGetEntry(eff->local_lights, 0);
		ChainDeleteEntry(eff->local_lights, 0);
		free(dl);
	}
}

RenderEffect3D *effect3d_new()
{
	RenderEffect3D *eff;
	SAFEALLOC(eff, sizeof(RenderEffect3D));
	eff->sensors = NewChain();
	eff->local_lights = NewChain();
	return eff;
}

void effect3d_delete(RenderEffect3D *eff)
{
	effect3d_reset(eff);
	DeleteChain(eff->sensors);
	DeleteChain(eff->local_lights);
	free(eff);
}


M4Rect R3D_UpdateClipper(RenderEffect3D *eff, M4Rect this_clip, Bool *need_restore, M4Rect *original, Bool for_layer)
{
	M4Rect clip, orig;
	if (for_layer) {
		orig = eff->layer_clipper;
		*need_restore = eff->has_layer_clip;
	} else {
		orig = eff->clipper;
		*need_restore = eff->has_clip;
	}
	*original = orig;

	clip = this_clip;
	if (*need_restore) {
		M4Matrix mx;
		m4_mx_copy(mx, eff->model_matrix);
		m4_mx_inverse(&mx);
		m4_mx_apply_rect(&mx, &orig);
		if (clip.x < orig.x) {
			clip.width -= (orig.x - clip.x);
			clip.x = orig.x;
		}
		if (clip.x + clip.width > orig.x + orig.width) {
			clip.width = orig.x + orig.width - clip.x;
		}
		if (clip.y > orig.y) {
			clip.height -= (clip.y - orig.y);
			clip.y = orig.y;
		}
		if (clip.y - clip.height < orig.y - orig.height) {
			clip.height = clip.y - orig.y + orig.height;
		}
	}
	if (for_layer) {
		eff->layer_clipper = clip;
		eff->has_layer_clip = 1;
	} else {
		eff->clipper = clip;
		/*retranslate to world coords*/
		m4_mx_apply_rect(&eff->model_matrix, &eff->clipper);
		eff->has_clip = 1;
	}
	return clip;
}


Bool R3D_GetSurfaceSizeInfo(RenderEffect3D *eff, Fixed *surf_width, Fixed *surf_height)
{
	u32 w, h;
	w = eff->surface->width;
	h = eff->surface->height;
	/*no size info, use main render output size*/
	if (!w || !h) {
		w = eff->surface->render->out_width;
		h = eff->surface->render->out_height;
	}
	if (eff->is_pixel_metrics) {
		*surf_width = INT2FIX(w);
		*surf_height = INT2FIX(h);
		return 1;
	}
	if (h > w) {
		*surf_width = 2*FIX_ONE;
		*surf_height = m4_divfix(2*INT2FIX(h), INT2FIX(w));
	} else {
		*surf_width = m4_divfix(2*INT2FIX(w), INT2FIX(h));
		*surf_height = 2*FIX_ONE;
	}
	return 0;
}

SensorHandler *r3d_get_sensor_handler(SFNode *n)
{
	SensorHandler *hs;
	if (!n) return NULL;

	switch (Node_GetTag(n)) {
	case TAG_MPEG4_DiscSensor: hs = r3d_ds_get_handler(n); break;
	case TAG_MPEG4_PlaneSensor2D: hs = r3d_ps2D_get_handler(n); break;
	case TAG_MPEG4_ProximitySensor2D: hs = r3d_prox2D_get_handler(n); break;

	case TAG_MPEG4_Anchor: case TAG_X3D_Anchor: hs = r3d_anchor_get_handler(n); break;
	case TAG_MPEG4_TouchSensor: case TAG_X3D_TouchSensor: hs = r3d_touch_sensor_get_handler(n); break;
	case TAG_MPEG4_PlaneSensor: case TAG_X3D_PlaneSensor: hs = r3d_ps_get_handler(n); break;
	case TAG_MPEG4_CylinderSensor: case TAG_X3D_CylinderSensor: hs = r3d_cs_get_handler(n); break;
	case TAG_MPEG4_SphereSensor: case TAG_X3D_SphereSensor: hs = r3d_sphere_get_handler(n); break;
	default: return NULL;
	}
	if (hs && hs->IsEnabled(n)) return hs;
	return NULL;
}


void R3D_SensorDeleted(LPSCENERENDER rend, SensorHandler *hdl)
{
	Render3D *sr = (Render3D *)rend->visual_renderer->user_priv;
	ChainDeleteItem(sr->prev_sensors, hdl);
	if (rend->interaction_sensors) rend->interaction_sensors--;
}

void R3D_SetGrabbed(LPSCENERENDER rend, Bool bOn) 
{
	((Render3D *)rend->visual_renderer->user_priv)->is_grabbed = bOn; 
}

SFNode *R3D_PickNode(VisualRenderer *vr, s32 X, s32 Y)
{
	return NULL;
}

static void DestroyLineProps(SFNode *n)
{
	u32 i;
	LinePropStack *st = Node_GetPrivate(n);
	Render3D *sr = (Render3D *)st->sr;
	
	for (i=0; i<ChainGetCount(sr->strike_bank); i++) {
		StrikeInfo *si = ChainGetEntry(sr->strike_bank, i);
		if (si->lineProps == n) {
			/*remove from node*/
			if (si->node2D) {
				stack2D *st = (stack2D *) Node_GetPrivate(si->node2D);
				ChainDeleteItem(st->strike_list, si);
			}
			ChainDeleteEntry(sr->strike_bank, i);
			delete_strikeinfo(si);
			i--;
		}
	}
	free(st);
}

void R3D_InitLineProps(Render3D *sr, SFNode *node)
{
	LinePropStack *st = malloc(sizeof(LinePropStack));
	st->sr = sr;
	st->last_mod_time = 1;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyLineProps);
}

u32 R3D_LP_GetLastUpdateTime(SFNode *node)
{
	LinePropStack *st = Node_GetPrivate(node);
	if (!st) return 0;
	if (Node_GetDirty(node) & SG_NODE_DIRTY) {
		st->last_mod_time ++;
		Node_ClearDirty(node);
	}
	return st->last_mod_time;
}


void R3D_DrawScene(VisualRenderer *vr)
{
	u32 i, tag;
	RenderEffect3D static_eff;
	Render3D *sr = (Render3D *)vr->user_priv;
	SFNode *top_node = NULL;
	
	if (sr->compositor->scene) top_node = SG_GetRootNode(sr->compositor->scene);

	/*setup GL*/
	VS3D_Setup(sr->surface);

	memcpy(&static_eff, sr->top_effect, sizeof(RenderEffect3D));

	if (top_node) {
		if (!sr->main_surface_setup) {
			tag = Node_GetTag(top_node);
			sr->surface->width = sr->compositor->scene_width;
			sr->surface->height = sr->compositor->scene_height;
			if ((tag>=TAG_RANGE_FIRST_X3D) && (tag<=TAG_RANGE_LAST_X3D)) {
				sr->surface->camera.is_3D = sr->root_is_3D = 1;
			} else {
				sr->surface->camera.is_3D = sr->root_is_3D = ((tag==TAG_MPEG4_Group) || (tag==TAG_MPEG4_Layer3D) ) ? 1 : 0;
			}
			camera_invalidate(&sr->surface->camera);
			sr->main_surface_setup = 1;
		}
		sr->top_effect->is_pixel_metrics = SG_UsePixelMetrics(sr->compositor->scene);
		/*setup our effects*/
		VS_SetupEffects(sr->surface, sr->top_effect);
		VS_NodeRender(sr->top_effect, top_node);

		sr->top_effect->surface = NULL;
	}

	for (i=0; i<ChainGetCount(sr->compositor->extra_scenes); i++) {
		LPSCENEGRAPH sg = ChainGetEntry(sr->compositor->extra_scenes, i);
		SFNode *n = SG_GetRootNode(sg);
		if (!n) continue;
		
		tag = Node_GetTag(n);
		if (!sr->main_surface_setup) {
			sr->surface->width = sr->compositor->scene_width;
			sr->surface->height = sr->compositor->scene_height;
		}
/*		if ((tag>=TAG_RANGE_FIRST_X3D) && (tag<=TAG_RANGE_LAST_X3D)) {
			sr->surface->camera.is_3D = 1;
		} else {
			sr->surface->camera.is_3D = ((tag==TAG_MPEG4_Group) || (tag==TAG_MPEG4_Layer3D) ) ? 1 : 0;
		}
		sr->surface->camera.flags |= CAM_IS_DIRTY;
*/		sr->top_effect->is_pixel_metrics = SG_UsePixelMetrics(sg);
		VS_SetupEffects(sr->surface, sr->top_effect);
		//VS_NodeRender(sr->top_effect, n);
		sr->top_effect->traversing_mode = TRAVERSE_SORT;
		Node_Render(n, sr->top_effect);
	}
	//if (i) sr->main_surface_setup = 0;
	memcpy(sr->top_effect, &static_eff, sizeof(RenderEffect3D));
	
	if (!i && !top_node) {
		/*default color is VRML default background color*/
		SFColor c;
		c.red = c.green = c.blue = 0;
		VS3D_ClearSurface(sr->surface, c, FIX_ONE);
	}

	sr->compositor->video_out->FlushVideo(sr->compositor->video_out, NULL);
}

static Bool R3D_ExecuteEvent(VisualRenderer *vr, M4UserEvent *event)
{
	Render3D *sr = (Render3D *)vr->user_priv;
	sr->top_effect->is_pixel_metrics = SG_UsePixelMetrics(sr->compositor->scene);
	/*process regular events*/
	if ((sr->compositor->interaction_level & M4_InteractNormal) && VS_ExecuteEvent(sr->surface, sr->top_effect, event, NULL)) 
		return 1;
	/*remember active layer on mouse click - may be NULL*/
	if (event->event_type==M4E_LEFTDOWN) sr->active_layer = sr->top_effect->collect_layer;
	/*process navigation events*/
	if (sr->compositor->interaction_level & M4_InteractNavigation) return R3D_HandleUserEvent(sr, event);
	return 0;
}

static void R3D_SetScaling(Render3D *sr, Fixed sx, Fixed sy)
{
	sr->scale_x = sx;
	sr->scale_y = sy;
}

M4Err R3D_RecomputeAR(VisualRenderer *vr)
{
	Double ratio;
	Fixed scaleX, scaleY;
	Render3D *sr = (Render3D *)vr->user_priv;

	sr->surface->camera.flags |= CAM_IS_DIRTY;

	if (!sr->compositor->height || !sr->compositor->width) return M4OK;

	sr->out_width = sr->compositor->width;
	sr->out_height = sr->compositor->height;
	sr->out_x = 0;
	sr->out_y = 0;

	if (!sr->compositor->has_size_info) {
		R3D_SetScaling(sr, FIX_ONE, FIX_ONE);
		sr->out_width = sr->surface->width = sr->compositor->width;
		sr->out_height = sr->surface->height = sr->compositor->height;
		return M4OK;
	}

	switch (sr->compositor->aspect_ratio) {
	case M4_AR_None:
		break;
	case M4_AR_16_9:
		sr->out_width = sr->compositor->width;
		sr->out_height = 9 * sr->compositor->width / 16;
		break;
	case M4_AR_4_3:
		sr->out_width = sr->compositor->width;
		sr->out_height = 3 * sr->compositor->width / 4;
		break;
	default:
		ratio = sr->compositor->scene_height;
		ratio /= sr->compositor->scene_width;
		if (sr->out_width * ratio > sr->out_height) {
			sr->out_width = sr->out_height * sr->compositor->scene_width;
			sr->out_width /= sr->compositor->scene_height;
		}
		else {
			sr->out_height = sr->out_width * sr->compositor->scene_height;
			sr->out_height /= sr->compositor->scene_width;
		}
		break;
	}
	sr->out_x = (sr->compositor->width - sr->out_width) / 2;
	sr->out_y = (sr->compositor->height - sr->out_height) / 2;
	/*update size info for main surface*/
	if (sr->surface) {
		sr->surface->width = sr->compositor->scene_width;
		sr->surface->height = sr->compositor->scene_height;
	}
	/*scaling is still needed for bitmap*/
	scaleX = m4_divfix(INT2FIX(sr->out_width), INT2FIX(sr->compositor->scene_width));
	scaleY = m4_divfix(INT2FIX(sr->out_height), INT2FIX(sr->compositor->scene_height));
	R3D_SetScaling(sr, scaleX, scaleY);
	return M4OK;
}


void R3D_SceneReset(VisualRenderer *vr)
{
	Render3D *sr = (Render3D*)vr->user_priv;
	sr->main_surface_setup = 0;
}

void R3D_ReloadConfig(VisualRenderer *vr)
{
	const char *sOpt;
	Render3D *sr = (Render3D *)vr->user_priv;

	SR_Lock(sr->compositor, 1);

	sOpt = PMI_GetOpt(vr, "Render3D", "RasterOutlines");
	sr->raster_outlines = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0;
	sOpt = PMI_GetOpt(vr, "Render3D", "EmulatePOW2");
	sr->emul_pow2 = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0;
	sOpt = PMI_GetOpt(vr, "Render3D", "PolygonAA");
	sr->poly_aa = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0;
	sOpt = PMI_GetOpt(vr, "Render3D", "DisableBackFaceCulling");
	sr->no_backcull = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0;
	sOpt = PMI_GetOpt(vr, "Render3D", "Wireframe");
	if (sOpt && !stricmp(sOpt, "WireOnly")) sr->wiremode = M4_WireOnly;
	else if (sOpt && !stricmp(sOpt, "WireOnSolid")) sr->wiremode = M4_WireOnSolid;
	else sr->wiremode = M4_WireNone;

	sOpt = PMI_GetOpt(vr, "Render3D", "BitmapCopyPixels");
	sr->bitmap_use_pixels = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0;

	sOpt = PMI_GetOpt(vr, "Render3D", "DisableRectExt");
	sr->disable_rect_ext = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0;
	/*RECT texture support - we must reload HW*/
	SR_ResetGraphics(sr->compositor);

	SR_Lock(sr->compositor, 0);
}

M4Err R3D_LoadRenderer(VisualRenderer *vr, SceneRenderer *compositor)
{
	Render3D *sr;
	if (vr->user_priv) return M4BadParam;

	sr = malloc(sizeof(Render3D));
	if (!sr) return M4OutOfMem;
	memset(sr, 0, sizeof(Render3D));

	sr->compositor = compositor;
	sr->strike_bank = NewChain();
	/*create default unit sphere and box for bounds*/
	sr->unit_bbox = new_mesh();
	mesh_new_unit_bbox(sr->unit_bbox);

	sr->top_effect = effect3d_new();
	sr->sensors = NewChain();
	sr->prev_sensors = NewChain();

	/*note we create main surface only when attached to scene*/
	sr->surface = VS_New();
	sr->surface->render = sr;
	sr->main_surface_setup = 0;
	/*default collision mode*/
	sr->collide_mode = M4_CollideDisp;
//	sr->collide_mode = M4_Collide;
	sr->gravity_on = 1;
	vr->user_priv = sr;

	R3D_ReloadConfig(vr);
	return M4OK;
}

void R3D_UnloadRenderer(VisualRenderer *vr)
{
	Render3D *sr = (Render3D *)vr->user_priv;
	if (!sr) return;

	assert(!ChainGetCount(sr->strike_bank));
	DeleteChain(sr->strike_bank);

	if (sr->unit_bbox) mesh_free(sr->unit_bbox);
	effect3d_delete(sr->top_effect);
	DeleteChain(sr->sensors);
	DeleteChain(sr->prev_sensors);

	VS_Delete(sr->surface);

	free(sr);
	vr->user_priv = NULL;
}

void R3D_GraphicsReset(VisualRenderer *vr)
{
	Render3D *sr = (Render3D *)vr->user_priv;
	if (!sr) return;
	R3D_LoadExtensions(sr);
}

M4Camera *R3D_GetCamera(Render3D *sr) 
{
	if (sr->active_layer) {
		return l3d_get_camera(sr->active_layer);
	} else {
		return &sr->surface->camera;
	}
}

M4Err R3D_SetOption(VisualRenderer *vr, u32 option, u32 value)
{
	SFNode *n;
	M4Camera *cam;
	Render3D *sr = (Render3D *)vr->user_priv;
	switch (option) {
	case M4O_RasterOutlines: sr->raster_outlines = value; return M4OK;
	case M4O_EmulatePOW2: sr->emul_pow2 = value; return M4OK;
	case M4O_PolygonAA: sr->poly_aa = value; return M4OK;
	case M4O_NoBackCull: sr->no_backcull = value; return M4OK;
	case M4O_Wireframe: sr->wiremode = value; return M4OK;
	case M4O_ReloadConfig:
		R3D_ReloadConfig(vr);
		return M4OK;
	case M4O_NoRectExt:
		if (value != sr->disable_rect_ext) {
			sr->disable_rect_ext = value;
			/*RECT texture support - we must reload HW*/
			SR_ResetGraphics(sr->compositor);
		}
		return M4OK;
	case M4O_BitmapCopyPixels: sr->bitmap_use_pixels = value; return M4OK;
	case M4O_OriginalView: 
		R3D_ResetCamera(sr);
		return M4OK;

	case M4O_NavigationType:
		if (!sr->surface) return M4BadParam;
		R3D_ResetCamera(sr);
		return M4OK;
	case M4O_NavigationMode: 
		cam = R3D_GetCamera(sr);
		if (cam->navigation_flags & NAV_ANY) {
			/*if not specifying mode, try to (un)bind top*/
			if (!value) {
				if (sr->active_layer) {
					l3d_bind_camera(sr->active_layer, 0, value);
				} else {
					n = ChainGetEntry(sr->surface->navigation_stack, 0);
					if (n) Bindable_SetSetBind(n, 0);
					else cam->navigate_mode = value;
				}
			} else {
				cam->navigate_mode = value;
			}
			return M4OK;
		}
		return M4NotSupported;

	case M4O_Headlight:
		cam = R3D_GetCamera(sr);
		if (cam->navigation_flags & NAV_ANY) {
			if (value) 
				cam->navigation_flags |= NAV_HEADLIGHT;
			else
				cam->navigation_flags &= ~NAV_HEADLIGHT;
			return M4OK;
		}
		return M4NotSupported;
	case M4O_Collision: 
		sr->collide_mode = value;
		return M4OK;
	case M4O_Gravity: 
		cam = R3D_GetCamera(sr);
		sr->gravity_on = value;
		/*force collision pass*/
		cam->last_pos.z -= 1;
		SR_Invalidate(sr->compositor, NULL);
		return M4OK;
	default:
		return M4BadParam;
	}
}

u32 R3D_GetOption(VisualRenderer *vr, u32 option)
{
	M4Camera *cam;
	Render3D *sr = (Render3D *)vr->user_priv;
	switch (option) {
	case M4O_RasterOutlines: return sr->raster_outlines;
	case M4O_EmulatePOW2: return sr->emul_pow2;
	case M4O_PolygonAA: return sr->poly_aa;
	case M4O_NoBackCull: return sr->no_backcull;
	case M4O_Wireframe: return sr->wiremode;
	case M4O_NoRectExt: return sr->disable_rect_ext;
	case M4O_BitmapCopyPixels: return sr->bitmap_use_pixels;
	case M4O_NavigationType: 
		cam = R3D_GetCamera(sr);
		if (!(cam->navigation_flags & NAV_ANY)) return M4_NoNavigation;
		return ((sr->root_is_3D || sr->active_layer) ? M4_Navigation3D : M4_Navigation2D);
	case M4O_NavigationMode: 
		cam = R3D_GetCamera(sr);
		return cam->navigate_mode;
	case M4O_Headlight: 
		cam = R3D_GetCamera(sr);
		return (cam->navigation_flags & NAV_HEADLIGHT) ? 1 : 0;
	case M4O_Collision: return sr->collide_mode;
	case M4O_Gravity: return sr->gravity_on;
	default: return 0;
	}
}


M4Err R3D_GetScreenBuffer(VisualRenderer *vr, M4VideoSurface *framebuffer);
M4Err R3D_ReleaseScreenBuffer(VisualRenderer *vr, M4VideoSurface *framebuffer);
M4Err R3D_GetViewpoint(VisualRenderer *vr, u32 viewpoint_idx, const char **outName, Bool *is_bound);
M4Err R3D_SetViewpoint(VisualRenderer *vr, u32 viewpoint_idx, const char *viewpoint_name);


/*render inline scene*/
void R3D_RenderInline(VisualRenderer *vr, SFNode *inline_root, void *rs)
{
	Bool had_scale, use_pm;
	u32 h, w;
	Fixed prev_scale;
	M4Matrix m4_mx_bck, mx;
	LPSCENEGRAPH in_scene;
	RenderEffect3D *eff = (RenderEffect3D *)rs;

	in_scene = Node_GetParentGraph(inline_root);
	use_pm = SG_UsePixelMetrics(in_scene);
	if (use_pm == eff->is_pixel_metrics) {
		Node_Render(inline_root, rs);
		return;
	}
	m4_mx_copy(m4_mx_bck, eff->model_matrix);
	prev_scale = eff->min_hsize;
	had_scale = eff->has_scale;
	/*override aspect ratio if any size info is given in the scene*/
	if (SG_GetSizeInfo(in_scene, &w, &h)) {
		Fixed scale = INT2FIX(MIN(w, h)) / 2;
		if (scale) eff->min_hsize = scale;
	}
	m4_mx_init(mx);
	/*apply meterMetrics<->pixelMetrics scale*/
	if (!use_pm) {
		m4_mx_add_scale(&mx, eff->min_hsize, eff->min_hsize, eff->min_hsize);
	} else {
		Fixed inv_scale = m4_divfix(FIX_ONE, eff->min_hsize);
		m4_mx_add_scale(&mx, inv_scale, inv_scale, inv_scale);
	}
	eff->has_scale = 1;
	eff->is_pixel_metrics = use_pm;
	m4_mx_add_matrix(&eff->model_matrix, &mx);
	if (eff->traversing_mode==TRAVERSE_SORT) {
		VS3D_PushMatrix(eff->surface);
		VS3D_MultMatrix(eff->surface, mx.m);
		Node_Render(inline_root, rs);
		VS3D_PopMatrix(eff->surface);
	} else {
		Node_Render(inline_root, rs);
	}
	eff->is_pixel_metrics = !use_pm;
	m4_mx_copy(eff->model_matrix, m4_mx_bck);
	eff->has_scale = had_scale;
}

/*interface create*/
void *LoadInterface(u32 InterfaceType)
{
	VisualRenderer *sr;
	if (InterfaceType != M4_RENDERER_INTERFACE) return NULL;
	
	sr = malloc(sizeof(VisualRenderer));
	if (!sr) return NULL;
	memset(sr, 0, sizeof(VisualRenderer));
	M4_REG_PLUG(sr, M4_RENDERER_INTERFACE, "GPAC 3D Renderer", "gpac distribution", 0);

	sr->LoadRenderer = R3D_LoadRenderer;
	sr->UnloadRenderer = R3D_UnloadRenderer;
	sr->GraphicsReset = R3D_GraphicsReset;
	sr->NodeChanged = R3D_NodeChanged;
	sr->NodeInit = R3D_NodeInit;
	sr->DrawScene = R3D_DrawScene;
	sr->RenderInline = R3D_RenderInline;
	sr->ExecuteEvent = R3D_ExecuteEvent;
	sr->RecomputeAR = R3D_RecomputeAR;
	sr->SceneReset = R3D_SceneReset;
	sr->AllocTexture = tx_allocate;
	sr->ReleaseTexture = tx_delete;
	sr->SetTextureData = R3D_SetTextureData;
	sr->TextureHWReset = R3D_TextureHWReset;

	sr->SetViewpoint = R3D_SetViewpoint;
	sr->GetViewpoint = R3D_GetViewpoint;

	sr->SetOption = R3D_SetOption;
	sr->GetOption = R3D_GetOption;
	sr->GetScreenBuffer = R3D_GetScreenBuffer;
	sr->ReleaseScreenBuffer = R3D_ReleaseScreenBuffer;

	/*signal we need openGL*/
	sr->bNeedsGL = 1;
	sr->user_priv = NULL;
	return sr;
}


/*interface destroy*/
void ShutdownInterface(void *ifce)
{
	VisualRenderer *rend = (VisualRenderer *)ifce;
	if (rend->InterfaceType != M4_RENDERER_INTERFACE) return;
	assert(rend->user_priv==NULL);
	free(rend);
}

/*interface query*/
Bool QueryInterface(u32 InterfaceType)
{
	if (InterfaceType == M4_RENDERER_INTERFACE) return 1;
	return 0;
}


