This source file includes following definitions.
- DestroyLayer3D
- l3d_CheckBindables
- layer3d_setup_offscreen
- layer3d_draw_2d
- layer3d_setup_clip
- TraverseLayer3D
- compositor_init_layer3d
- compositor_layer3d_get_camera
- compositor_layer3d_bind_camera
#include "nodes_stacks.h"
#include "mpeg4_grouping.h"
#include "texturing.h"
#include "visual_manager.h"
#ifndef GPAC_DISABLE_VRML
#ifndef GPAC_DISABLE_3D
#ifdef GPAC_USE_TINYGL
#include <GL/oscontext.h>
#endif
typedef struct
{
GROUPING_NODE_STACK_3D
Bool first;
GF_Rect clip, vp;
GF_VisualManager *visual;
Bool unsupported;
GF_TextureHandler txh;
Drawable *drawable;
#ifdef GPAC_USE_TINYGL
ostgl_context *tgl_ctx;
#endif
Fixed scale_x, scale_y;
} Layer3DStack;
static void DestroyLayer3D(GF_Node *node)
{
Layer3DStack *st = (Layer3DStack *) gf_node_get_private(node);
GF_Compositor *compositor = st->visual->compositor;
drawable_del(st->drawable);
#ifdef GPAC_USE_TINYGL
if (st->tgl_ctx) ostgl_delete_context(st->tgl_ctx);
#endif
if (st->txh.data) gf_free(st->txh.data);
gf_sc_texture_destroy(&st->txh);
BindableStackDelete(st->visual->back_stack);
st->visual->back_stack = NULL;
BindableStackDelete(st->visual->view_stack);
st->visual->view_stack = NULL;
BindableStackDelete(st->visual->navigation_stack);
st->visual->navigation_stack = NULL;
BindableStackDelete(st->visual->fog_stack);
st->visual->fog_stack = NULL;
visual_del(st->visual);
if (compositor && compositor->active_layer == node) compositor->active_layer = NULL;
gf_free(st);
}
static void l3d_CheckBindables(GF_Node *n, GF_TraverseState *tr_state, Bool force_traverse)
{
GF_Node *btop;
u32 mode;
M_Layer3D *l3d;
l3d = (M_Layer3D *)n;
mode = tr_state->traversing_mode;
tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
if (force_traverse) gf_node_traverse(l3d->background, tr_state);
btop = (GF_Node*)gf_list_get(tr_state->backgrounds, 0);
if (btop != l3d->background) {
gf_node_unregister(l3d->background, n);
gf_node_register(btop, n);
l3d->background = btop;
gf_node_event_out(n, 4);
}
if (force_traverse) gf_node_traverse(l3d->viewpoint, tr_state);
btop = (GF_Node*)gf_list_get(tr_state->viewpoints, 0);
if (btop != l3d->viewpoint) {
gf_node_unregister(l3d->viewpoint, n);
gf_node_register(btop, n);
l3d->viewpoint = btop;
gf_node_event_out(n, 7);
}
if (force_traverse) gf_node_traverse(l3d->navigationInfo, tr_state);
btop = (GF_Node*)gf_list_get(tr_state->navigations, 0);
if (btop != l3d->navigationInfo) {
gf_node_unregister(l3d->navigationInfo, n);
gf_node_register(btop, n);
l3d->navigationInfo = btop;
gf_node_event_out(n, 6);
}
if (force_traverse) gf_node_traverse(l3d->fog, tr_state);
btop = (GF_Node*)gf_list_get(tr_state->fogs, 0);
if (btop != l3d->fog) {
gf_node_unregister(l3d->fog, n);
gf_node_register(btop, n);
l3d->fog = btop;
gf_node_event_out(n, 5);
}
tr_state->traversing_mode = mode;
}
u32 layer3d_setup_offscreen(GF_Node *node, Layer3DStack *st, GF_TraverseState *tr_state, Fixed width, Fixed height)
{
GF_STENCIL stencil;
u32 new_pixel_format, w, h;
GF_Compositor *compositor = (GF_Compositor *)st->visual->compositor;
if (st->unsupported) return 0;
#ifndef GPAC_USE_TINYGL
if (!(compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN)) {
st->unsupported = 1;
return 0;
}
#endif
new_pixel_format = GF_PIXEL_RGBA;
#ifndef GPAC_USE_TINYGL
#ifdef GPAC_USE_GLES1X
new_pixel_format = GF_PIXEL_RGBA;
#else
if (!(compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA))
new_pixel_format = GF_PIXEL_RGB_24;
#endif
#endif
#if defined(GF_SR_USE_DEPTH) && !defined(GPAC_DISABLE_3D)
if (st->visual->type_3d && (compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_DEPTH) ) new_pixel_format = GF_PIXEL_RGBDS;
#endif
w = (u32) FIX2INT(gf_ceil(width));
h = (u32) FIX2INT(gf_ceil(height));
w = gf_get_next_pow2(w);
if (w>1024) w = 1024;
h = gf_get_next_pow2(h);
if (h>1024) h = 1024;
if (!w || !h) return 0;
if (st->txh.tx_io
&& (new_pixel_format == st->txh.pixelformat)
&& (w == st->txh.width)
&& (h == st->txh.height)
&& (compositor->offscreen_width >= w)
&& (compositor->offscreen_height >= h)
)
return 2;
if (st->txh.tx_io) {
#ifdef GPAC_USE_TINYGL
if (st->tgl_ctx) ostgl_delete_context(st->tgl_ctx);
#endif
gf_sc_texture_release(&st->txh);
if (st->txh.data) gf_free(st->txh.data);
st->txh.data = NULL;
}
st->vp = gf_rect_center(INT2FIX(w), INT2FIX(h) );
st->txh.width = w;
st->txh.height = h;
gf_sc_texture_allocate(&st->txh);
st->txh.pixelformat = new_pixel_format;
if (new_pixel_format==GF_PIXEL_RGBA) {
st->txh.stride = w * 4;
st->txh.transparent = 1;
}
else if (new_pixel_format==GF_PIXEL_RGBDS) {
st->txh.stride = w * 4;
st->txh.transparent = 1;
}
else {
st->txh.stride = w * 3;
st->txh.transparent = 0;
}
st->visual->width = w;
st->visual->height = h;
stencil = compositor->rasterizer->stencil_new(compositor->rasterizer, GF_STENCIL_TEXTURE);
#ifndef GPAC_USE_TINYGL
if ((compositor->offscreen_width < w) || (compositor->offscreen_height < h)) {
GF_Err e;
GF_Event evt;
compositor->offscreen_width = MAX(compositor->offscreen_width, w);
compositor->offscreen_height = MAX(compositor->offscreen_height, h);
evt.type = GF_EVENT_VIDEO_SETUP;
evt.setup.width = tr_state->visual->compositor->offscreen_width;
evt.setup.height = tr_state->visual->compositor->offscreen_height;
evt.setup.back_buffer = 0;
evt.setup.opengl_mode = 2;
e = compositor->video_out->ProcessEvent(compositor->video_out, &evt);
if (e) {
gf_sc_texture_release(&st->txh);
st->unsupported = 1;
return 0;
}
visual_3d_reset_graphics(compositor->visual);
gf_sc_load_opengl_extensions(compositor, 1);
visual_3d_init_shaders(compositor->visual);
}
#endif
st->txh.data = (char*)gf_malloc(sizeof(unsigned char) * st->txh.stride * st->txh.height);
memset(st->txh.data, 0, sizeof(unsigned char) * st->txh.stride * st->txh.height);
compositor->rasterizer->stencil_set_texture(stencil, st->txh.data, st->txh.width, st->txh.height, st->txh.stride, st->txh.pixelformat, st->txh.pixelformat, 0);
#ifdef GPAC_USE_TINYGL
st->tgl_ctx = ostgl_create_context(st->txh.width, st->txh.height, st->txh.transparent ? 32 : 24, &st->txh.data, 1);
st->scale_x = st->scale_y = FIX_ONE;
#else
st->scale_x = INT2FIX(w) / tr_state->visual->compositor->offscreen_width;
st->scale_y = INT2FIX(h) / tr_state->visual->compositor->offscreen_height;
#endif
gf_sc_texture_set_stencil(&st->txh, stencil);
drawable_reset_path(st->drawable);
gf_path_add_rect_center(st->drawable->path, 0, 0, st->clip.width, st->clip.height);
return 1;
}
static void layer3d_draw_2d(GF_Node *node, GF_TraverseState *tr_state)
{
DrawableContext *ctx = tr_state->ctx;
if (tr_state->visual->DrawBitmap(tr_state->visual, tr_state, ctx))
return;
visual_2d_texture_path(tr_state->visual, ctx->drawable->path, ctx, tr_state);
}
static void layer3d_setup_clip(Layer3DStack *st, GF_TraverseState *tr_state, Bool prev_cam, GF_Rect rc)
{
Fixed sw, sh;
if (tr_state->visual->compositor->visual==tr_state->visual) {
sw = INT2FIX(tr_state->visual->compositor->display_width);
sh = INT2FIX(tr_state->visual->compositor->display_height);
} else {
sw = INT2FIX(tr_state->visual->width);
sh = INT2FIX(tr_state->visual->height);
}
if ((tr_state->traversing_mode==TRAVERSE_SORT) && !prev_cam) {
rc.x = rc.y = 0;
st->visual->camera.vp = rc;
} else {
st->visual->camera.vp = rc;
st->visual->camera.vp.x += sw/2;
st->visual->camera.vp.y -= st->visual->camera.vp.height;
st->visual->camera.vp.y += sh/2;
}
tr_state->camera->width = tr_state->camera->vp.width;
tr_state->camera->height = tr_state->camera->vp.height;
if (!tr_state->pixel_metrics) {
if (tr_state->camera->height > tr_state->camera->width) {
tr_state->camera->height = 2 * gf_divfix(tr_state->camera->height, tr_state->camera->width);
tr_state->camera->width = 2*FIX_ONE;
} else {
tr_state->camera->width = 2 * gf_divfix(tr_state->camera->width, tr_state->camera->height);
tr_state->camera->height = 2*FIX_ONE;
}
}
tr_state->bbox.max_edge.x = tr_state->camera->width/2;
tr_state->bbox.min_edge.x = -tr_state->bbox.max_edge.x;
tr_state->bbox.max_edge.y = tr_state->camera->height/2;
tr_state->bbox.min_edge.y = -tr_state->bbox.max_edge.y;
tr_state->bbox.max_edge.z = tr_state->bbox.min_edge.z = 0;
tr_state->bbox.is_set = 1;
}
static void TraverseLayer3D(GF_Node *node, void *rs, Bool is_destroy)
{
Bool prev_layer, changed = 0;
GF_List *oldb, *oldv, *oldf, *oldn;
GF_Rect rc;
u32 cur_lights;
GF_List *node_list_backup;
GF_BBox bbox_backup;
GF_Matrix model_backup;
GF_Matrix2D mx2d_backup;
GF_Camera *prev_cam;
GF_VisualManager *old_visual;
GF_Matrix2D *transform;
M_Layer3D *l = (M_Layer3D *)node;
Layer3DStack *st = (Layer3DStack *) gf_node_get_private(node);
GF_TraverseState *tr_state = (GF_TraverseState *) rs;
GF_TraverseState a_tr;
u32 old_type_3d;
if (is_destroy) {
DestroyLayer3D(node);
return;
}
if (st->unsupported) return;
memcpy(&a_tr, tr_state, sizeof(GF_TraverseState));
if (gf_node_dirty_get(node)) {
if (tr_state->pixel_metrics && (tr_state->visual->compositor->visual==tr_state->visual)) {
st->clip.width = INT2FIX(tr_state->visual->compositor->vp_width);
st->clip.height = INT2FIX(tr_state->visual->compositor->vp_height);
} else {
visual_get_size_info(tr_state, &st->clip.width, &st->clip.height);
}
if (l->size.x>=0) st->clip.width = l->size.x;
if (l->size.y>=0) st->clip.height = l->size.y;
st->clip = gf_rect_center(st->clip.width, st->clip.height);
changed = 1;
}
transform = &tr_state->transform;
oldb = tr_state->backgrounds;
oldv = tr_state->viewpoints;
oldf = tr_state->fogs;
oldn = tr_state->navigations;
old_visual = tr_state->visual;
prev_layer = tr_state->is_layer;
prev_cam = tr_state->camera;
bbox_backup = tr_state->bbox;
switch (tr_state->traversing_mode) {
case TRAVERSE_GET_BOUNDS:
if (!tr_state->for_node) {
tr_state->bounds = st->clip;
gf_bbox_from_rect(&tr_state->bbox, &st->clip);
return;
}
case TRAVERSE_PICK:
if (tr_state->camera && tr_state->camera->is_3D)
return;
break;
case TRAVERSE_SORT:
if (tr_state->camera && tr_state->camera->is_3D)
return;
if (tr_state->visual->compositor->hybrid_opengl) {
DrawableContext *ctx = drawable_init_context_mpeg4(st->drawable, tr_state);
if (!ctx) goto l3d_exit;
ctx->aspect.fill_texture = &st->txh;
ctx->flags |= CTX_APP_DIRTY | CTX_IS_TRANSPARENT;
drawable_finalize_sort(ctx, tr_state, &st->clip);
return;
}
break;
case TRAVERSE_DRAW_2D:
if (tr_state->visual->compositor->hybrid_opengl) {
transform = &tr_state->ctx->transform;
break;
}
layer3d_draw_2d(node, tr_state);
return;
case TRAVERSE_DRAW_3D:
default:
return;
}
tr_state->backgrounds = st->visual->back_stack;
tr_state->viewpoints = st->visual->view_stack;
tr_state->navigations = st->visual->navigation_stack;
tr_state->fogs = st->visual->fog_stack;
tr_state->is_layer = 1;
tr_state->camera = &st->visual->camera;
gf_mx_copy(model_backup, tr_state->model_matrix);
gf_mx2d_copy(mx2d_backup, tr_state->transform);
rc = st->clip;
if (prev_cam) {
gf_mx_apply_rect(&tr_state->model_matrix, &rc);
if (tr_state->visual->compositor->hybrid_opengl) {
gf_mx2d_apply_rect(transform, &rc);
} else {
gf_mx_apply_rect(&prev_cam->modelview, &rc);
}
if (tr_state->camera->flags & CAM_HAS_VIEWPORT)
gf_mx_apply_rect(&prev_cam->viewport, &rc);
} else {
gf_mx2d_apply_rect(&tr_state->transform, &rc);
tr_state->visual = st->visual;
}
gf_mx_init(tr_state->model_matrix);
l3d_CheckBindables(node, tr_state, st->first);
if (prev_cam) gf_mx_copy(tr_state->model_matrix, model_backup);
if ((tr_state->traversing_mode==TRAVERSE_SORT) || (tr_state->traversing_mode==TRAVERSE_DRAW_2D)) {
u32 trav_mode = tr_state->traversing_mode;
if (gf_node_dirty_get(node)) changed = 1;
gf_node_dirty_clear(node, GF_SG_NODE_DIRTY|GF_SG_VRML_BINDABLE_DIRTY);
if (!prev_cam) {
switch (layer3d_setup_offscreen(node, st, tr_state, rc.width, rc.height)) {
case 0:
goto l3d_exit;
case 2:
if (!changed && !(st->visual->camera.flags & CAM_IS_DIRTY) && !st->visual->camera.anim_len) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Layer3D] No changes found , skipping 3D draw\n"));
goto layer3d_unchanged_2d;
}
default:
break;
}
#ifdef GPAC_USE_TINYGL
if (st->tgl_ctx) ostgl_make_current(st->tgl_ctx, 0);
#endif
rc = st->vp;
visual_3d_setup(tr_state->visual);
}
tr_state->layer3d = node;
GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Layer3D] Redrawing\n"));
layer3d_setup_clip(st, tr_state, prev_cam ? 1 : 0, rc);
if (trav_mode==TRAVERSE_DRAW_2D) {
visual_2d_flush_hybgl_canvas(tr_state->visual, NULL, tr_state->ctx, tr_state);
}
old_type_3d = tr_state->visual->type_3d;
tr_state->visual->type_3d = 2;
visual_3d_clear_all_lights(tr_state->visual);
cur_lights = tr_state->visual->num_lights;
visual_3d_init_draw(tr_state, prev_cam ? 1 : 2);
visual_3d_check_collisions(tr_state, NULL, l->children);
tr_state->traversing_mode = TRAVERSE_SORT;
node_list_backup = tr_state->visual->alpha_nodes_to_draw;
tr_state->visual->alpha_nodes_to_draw = gf_list_new();
tr_state->cull_flag = 0;
group_3d_traverse(node, (GroupingNode *)st, tr_state);
visual_3d_flush_contexts(tr_state->visual, tr_state);
gf_list_del(tr_state->visual->alpha_nodes_to_draw);
tr_state->visual->alpha_nodes_to_draw = node_list_backup;
while (cur_lights < tr_state->visual->num_lights) {
visual_3d_remove_last_light(tr_state->visual);
}
tr_state->traversing_mode = trav_mode ;
tr_state->visual->type_3d = old_type_3d;
tr_state->layer3d = NULL;
if (!prev_cam ) {
DrawableContext *ctx;
#ifndef GPAC_USE_TINYGL
gf_sc_copy_to_stencil(&st->txh);
#else
if (st->txh.pixelformat==GF_PIXEL_RGBDS)
gf_get_tinygl_depth(&st->txh);
#endif
if (tr_state->visual->compositor->rasterizer->stencil_texture_modified)
tr_state->visual->compositor->rasterizer->stencil_texture_modified(gf_sc_texture_get_stencil(&st->txh) );
gf_sc_texture_set_stencil(&st->txh, gf_sc_texture_get_stencil(&st->txh) );
changed = 1;
layer3d_unchanged_2d:
tr_state->visual = old_visual;
tr_state->appear = NULL;
ctx = drawable_init_context_mpeg4(st->drawable, tr_state);
if (!ctx) goto l3d_exit;
ctx->aspect.fill_texture = &st->txh;
ctx->flags |= CTX_NO_ANTIALIAS;
if (changed) ctx->flags |= CTX_APP_DIRTY;
if (st->txh.transparent) ctx->flags |= CTX_IS_TRANSPARENT;
drawable_finalize_sort(ctx, tr_state, NULL);
}
}
else if (!gf_node_dirty_get(node) && (tr_state->traversing_mode==TRAVERSE_PICK)) {
GF_Ray prev_r;
SFVec3f start, end;
SFVec4f res;
Fixed in_x, in_y;
Bool do_pick = 0;
if (!prev_cam) rc = st->vp;
layer3d_setup_clip(st, tr_state, prev_cam ? 1 : 0, rc);
old_type_3d = tr_state->visual->type_3d;
tr_state->visual->type_3d = 2;
if (tr_state->visual->compositor->active_layer==node) {
do_pick = (tr_state->visual->compositor->grabbed_sensor || tr_state->visual->compositor->navigation_state) ? 1 : 0;
}
if (!prev_cam || tr_state->visual->compositor->hybrid_opengl) gf_mx_from_mx2d(&tr_state->model_matrix, &tr_state->transform);
if (!do_pick && !gf_list_count(tr_state->visual->compositor->sensors))
do_pick = gf_sc_pick_in_clipper(tr_state, &st->clip);
if (!do_pick) {
tr_state->visual->type_3d = old_type_3d;
goto l3d_exit;
}
prev_r = tr_state->ray;
compositor_get_2d_plane_intersection(&tr_state->ray, &start);
gf_mx_inverse(&tr_state->model_matrix);
gf_mx_apply_vec(&tr_state->model_matrix, &start);
if (tr_state->visual->compositor->visual==tr_state->visual) {
start.x = gf_mulfix(start.x, tr_state->visual->compositor->scale_x);
start.y = gf_mulfix(start.y, tr_state->visual->compositor->scale_y);
} else if (!prev_cam) {
start.x = gf_muldiv(start.x, st->visual->camera.width, st->clip.width);
start.y = gf_muldiv(start.y, st->visual->camera.height, st->clip.height);
}
visual_3d_setup_projection(tr_state, 1);
in_x = 2 * gf_divfix(start.x, st->visual->camera.width);
in_y = 2 * gf_divfix(start.y, st->visual->camera.height);
res.x = in_x;
res.y = in_y;
res.z = -FIX_ONE;
res.q = FIX_ONE;
gf_mx_apply_vec_4x4(&st->visual->camera.unprojection, &res);
if (!res.q) {
tr_state->visual->type_3d = old_type_3d;
goto l3d_exit;
}
start.x = gf_divfix(res.x, res.q);
start.y = gf_divfix(res.y, res.q);
start.z = gf_divfix(res.z, res.q);
res.x = in_x;
res.y = in_y;
res.z = FIX_ONE;
res.q = FIX_ONE;
gf_mx_apply_vec_4x4(&st->visual->camera.unprojection, &res);
if (!res.q) {
tr_state->visual->type_3d = old_type_3d;
goto l3d_exit;
}
end.x = gf_divfix(res.x, res.q);
end.y = gf_divfix(res.y, res.q);
end.z = gf_divfix(res.z, res.q);
tr_state->ray = gf_ray(start, end);
GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Layer3D] Picking: cast ray\n\tOrigin %.4f %.4f %.4f - End %.4f %.4f %.4f\n\tDir %.4f %.4f %.4f\n",
FIX2FLT(tr_state->ray.orig.x), FIX2FLT(tr_state->ray.orig.y), FIX2FLT(tr_state->ray.orig.z),
FIX2FLT(end.x), FIX2FLT(end.y), FIX2FLT(end.z),
FIX2FLT(tr_state->ray.dir.x), FIX2FLT(tr_state->ray.dir.y), FIX2FLT(tr_state->ray.dir.z)));
group_3d_traverse(node, (GroupingNode *)st, tr_state);
tr_state->ray = prev_r;
tr_state->visual->type_3d = old_type_3d;
if (tr_state->camera->navigate_mode || (tr_state->camera->navigation_flags & NAV_ANY))
tr_state->layer3d = node;
tr_state->traversing_mode = TRAVERSE_PICK;
}
l3d_exit:
tr_state->camera = prev_cam;
if (prev_cam) {
visual_3d_projection_matrix_modified(tr_state->visual);
if (tr_state->visual == old_visual) {
visual_3d_set_viewport(tr_state->visual, tr_state->camera->vp);
}
}
tr_state->visual = old_visual;
tr_state->backgrounds = oldb;
tr_state->viewpoints = oldv;
tr_state->fogs = oldf;
tr_state->navigations = oldn;
tr_state->bbox = bbox_backup;
tr_state->is_layer = prev_layer;
gf_mx_copy(tr_state->model_matrix, model_backup);
gf_mx2d_copy(tr_state->transform, mx2d_backup);
if (st->first) {
st->first = 0;
gf_node_dirty_set(node, 0, 0);
gf_sc_invalidate(tr_state->visual->compositor, NULL);
}
}
void compositor_init_layer3d(GF_Compositor *compositor, GF_Node *node)
{
Layer3DStack *stack;
GF_SAFEALLOC(stack, Layer3DStack);
if (!stack) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate layer 3d stack\n"));
return;
}
stack->visual = visual_new(compositor);
stack->visual->type_3d = 2;
stack->visual->camera.is_3D = 1;
stack->visual->camera.visibility = 0;
stack->visual->camera.speed = FIX_ONE;
camera_invalidate(&stack->visual->camera);
stack->first = 1;
stack->txh.compositor = compositor;
stack->drawable = drawable_new();
stack->drawable->node = node;
stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, TraverseLayer3D);
}
GF_Camera *compositor_layer3d_get_camera(GF_Node *node)
{
Layer3DStack *st = (Layer3DStack *) gf_node_get_private(node);
return &st->visual->camera;
}
void compositor_layer3d_bind_camera(GF_Node *node, Bool do_bind, u32 nav_value)
{
Layer3DStack *st = (Layer3DStack *) gf_node_get_private(node);
GF_Node *n = (GF_Node*)gf_list_get(st->visual->navigation_stack, 0);
if (n) Bindable_SetSetBind(n, do_bind);
else st->visual->camera.navigate_mode = nav_value;
}
#endif
#endif