/* * Copyright (c) 2005-2023 Julien Nadeau Carriere * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Visualization widget for SG(3). */ #include #include #include M_Real sgEyeSeparation = 0.01; /* Create a new SG_View widget. */ SG_View * SG_ViewNew(void *parent, SG *sg, Uint flags) { SG_View *sv; sv = Malloc(sizeof(SG_View)); AG_ObjectInit(sv, &sgViewClass); if (flags & SG_VIEW_HFILL) { WIDGET(sv)->flags |= AG_WIDGET_HFILL; } if (flags & SG_VIEW_VFILL) { WIDGET(sv)->flags |= AG_WIDGET_VFILL; } sv->flags |= flags; sv->sg = sg; if (sg != NULL) { AG_OBJECT_ISA(sg, "SG:*"); AG_ObjectLock(sg); if (sg->def.cam != NULL) SG_ViewSetCamera(sv, sg->def.cam); AG_ObjectUnlock(sg); } AG_ObjectAttach(parent, sv); return (sv); } static void ChangeScene(SG_View *_Nonnull sv, SG *_Nullable sg, SG_Camera *_Nullable cam) { SG_Node *node; #ifdef AG_DEBUG Debug(sv, "Change to: %s(%s)\n", OBJECT(sg)->name, (cam != NULL) ? OBJECT(cam)->name : "NULL"); #endif if (sv->sg == sg) { SG_ViewSetCamera(sv, cam); return; } if (sv->sg) { AG_ObjectLock(sv->sg); SG_FOREACH_NODE(node, sv->sg) { AG_PostEvent(node, "view-detach", "%p", sv); } AG_ObjectUnlock(sv->sg); } sv->sg = sg; SG_ViewSetCamera(sv, cam); if (sg) { AG_ObjectLock(sg); SG_FOREACH_NODE(node, sg) { AG_PostEvent(node, "view-attach", "%p", sv); } AG_ObjectUnlock(sg); } } static Uint32 TransitionFade(AG_Timer *_Nonnull to, AG_Event *_Nonnull event) { SG_View *sv = SG_VIEW_SELF(); AG_ObjectLock(sv); /* (10ms timer and range -1.0 to 1.0) */ sv->transProgress += (10.0*2.0/sv->transDuration); if (sv->camTrans != NULL && Fabs(sv->transProgress) < 0.01) { /* Mid */ ChangeScene(sv, sv->sgTrans, sv->camTrans); sv->sgTrans = NULL; sv->camTrans = NULL; } else if (sv->transProgress >= 1.0) { /* End */ sv->transProgress = 1.0; goto stop; } AG_Redraw(sv); AG_ObjectUnlock(sv); return (to->ival); stop: sv->transFlags &= ~(SG_VIEW_TRANSFADE); AG_Redraw(sv); AG_ObjectUnlock(sv); return (0); } /* Initiate transition to a different scene. */ int SG_ViewTransition(SG_View *sv, SG *sg, SG_Camera *cam, Uint flags) { AG_ObjectLock(sv); #ifdef AG_DEBUG Debug(sv, "Transition to: %s(%s)\n", OBJECT(sg)->name, (cam != NULL) ? OBJECT(cam)->name : "NULL"); #endif if (cam == NULL) { AG_ObjectLock(sg); if ((cam = sg->def.cam) == NULL) { AG_SetError("No target camera"); AG_ObjectUnlock(sg); return (-1); } AG_ObjectUnlock(sg); } if (sv->sgTrans != NULL) { AG_SetError("Transition already in progress"); goto fail; } sv->transFlags = flags; if (flags & SG_VIEW_TRANSFADE) { sv->sgTrans = sg; sv->camTrans = cam; sv->transProgress = -1.0; AG_AddTimer(sv, &sv->toTransFade, 10, TransitionFade, NULL); } else { ChangeScene(sv, sg, cam); } AG_ObjectUnlock(sv); return (0); fail: AG_ObjectUnlock(sv); return (-1); } void SG_ViewSetFadeColor(SG_View *sv, const AG_Color *c) { AG_ObjectLock(sv); sv->transFadeColor = *c; AG_ObjectUnlock(sv); } /* Configure fade duration. */ void SG_ViewSetFadeDuration(SG_View *sv, Uint ms) { AG_ObjectLock(sv); sv->transDuration = ms; AG_ObjectUnlock(sv); } /* * Convert widget coordinates [x,y] to a vector vOut in world coordinates. * The output vector is on the active camera's near plane. */ void SG_ViewUnProject(SG_View *sv, int x, int y, M_Vector4 *vNear, M_Vector4 *vFar) { M_Rectangle3 rNear, rFar; M_Real w, h; /* * Convert widget coordinates to normalized device coordinates, * and project the point onto the camera's near plane. */ SG_CameraFrustum(sv->cam, &rNear, &rFar); w = M_VecDistance3(rNear.b, rNear.c); h = M_VecDistance3(rNear.a, rNear.b); vNear->x = (-0.5 + (M_Real)x/(M_Real)WIDTH(sv))*w; vNear->y = (+0.5 - (M_Real)y/(M_Real)HEIGHT(sv))*h; vNear->z = rNear.a.z; vNear->w = 1.0; w = M_VecDistance3(rFar.b, rFar.c); h = M_VecDistance3(rFar.a, rFar.b); vFar->x = (-0.5 + (M_Real)x/(M_Real)WIDTH(sv))*w; vFar->y = (+0.5 - (M_Real)y/(M_Real)HEIGHT(sv))*h; vFar->z = rFar.a.z; vFar->w = 1.0; } static void SizeRequest(void *_Nonnull obj, AG_SizeReq *_Nonnull r) { /* SG_View *sv = obj; */ r->w = 100; r->h = 100; } static int SizeAllocate(void *_Nonnull obj, const AG_SizeAlloc *_Nonnull a) { if (a->w < 4 || a->h < 4) { return (-1); } return (0); } static void OnOverlayCameraStatus(SG_View *_Nonnull sv) { char text[128]; AG_Surface *su; M_Vector3 v; if (sv->cam != NULL) { v = SG_NodePos(sv->cam); AG_Snprintf(text, sizeof(text), "%s (%f,%f,%f)", OBJECT(sv->cam)->name, v.x, v.y, v.z); } else { Strlcpy(text, _("No camera"), sizeof(text)); } AG_TextBGColor(&WCOLOR(sv, FG_COLOR)); AG_TextColor(&WCOLOR(sv, TEXT_COLOR)); su = AG_TextRender(text); AG_WidgetBlit(sv, su, 0, HEIGHT(sv) - su->h); AG_SurfaceFree(su); } static void OnOverlay(AG_Event *_Nonnull event) { SG_View *sv = SG_VIEW_SELF(); AG_Surface *su; if (sv->transProgress < 1.0) { /* Fade in progress */ AG_Rect r; AG_Color c; r.x = 0; r.y = 0; r.w = WIDTH(sv); r.h = HEIGHT(sv); c = sv->transFadeColor; c.a = AG_COLOR_LAST - (AG_Component) (Fabs(sv->transProgress) * (AG_COLOR_LASTD-1.0)); AG_DrawRectBlended(sv, &r, &c, AG_ALPHA_SRC, AG_ALPHA_ONE_MINUS_SRC); } if (sv->flags & SG_VIEW_CAMERA_STATUS) { OnOverlayCameraStatus(sv); } if (sv->flags & SG_VIEW_EDIT_STATUS && sv->editStatus[0] != '\0') { AG_TextBGColor(&WCOLOR(sv, FG_COLOR)); AG_TextColor(&WCOLOR(sv, TEXT_COLOR)); su = AG_TextRender(sv->editStatus); AG_WidgetBlit(sv, su, 0, HEIGHT(sv) - su->h); AG_SurfaceFree(su); } } static void SetupLights(SG_View *_Nonnull sv, SG_Node *_Nonnull root) { SG_Light *lt; /* XXX TODO map into array */ OBJECT_FOREACH_CLASS(lt, root, sg_light, "SG_Node:SG_Light:*") { if (lt->light != GL_INVALID_ENUM) SG_LightSetup(lt); } } static void Draw(void *_Nonnull obj) { SG_View *sv = obj; SG *sg = sv->sg; if (sg == NULL || sv->cam == NULL) return; #ifdef AG_DEBUG /* Debug(sv, "Draw %s(%s)\n", sg ? OBJECT(sg)->name : "NULL", sv->cam ? OBJECT(sv->cam)->name : ""); */ #endif GL_PushAttrib(GL_ENABLE_BIT | GL_POLYGON_BIT); AG_ObjectLock(sg); AG_ObjectLock(sv->cam); if (sv->flags & SG_VIEW_UPDATE_PROJ) { M_Matrix44 P; int m, n; SG_CameraGetProjection(sv->cam, &P); #ifdef AG_DEBUG if (WIDGET(sv)->gl == NULL) { AG_FatalError("NULL gl"); } #endif for (m = 0; m < 4; m++) { for (n = 0; n < 4; n++) WIDGET(sv)->gl->mProjection[(m<<2)+n] = (float)P.m[n][m]; } sv->flags &= ~(SG_VIEW_UPDATE_PROJ); AG_WidgetUpdate(sv); } if ((sv->flags & SG_VIEW_NO_DEPTH_TEST) == 0) GL_Enable(GL_DEPTH_TEST); /* Set the modelview matrix and rendering modes the current camera. */ GL_LoadIdentity(); SG_CameraSetup(sv->cam); /* Enable the light sources. */ if ((sv->flags & SG_VIEW_NO_LIGHTING) == 0) { GL_PushAttrib(GL_LIGHTING_BIT); GL_Enable(GL_LIGHTING); GL_Enable(GL_LIGHT0); /* XXX TODO array */ SetupLights(sv, sg->root); glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1.0); } /* Render the scene. */ if (agStereo) { glDrawBuffer(GL_BACK_LEFT); glClear(GL_DEPTH_BUFFER_BIT); GL_MatrixMode(GL_PROJECTION); GL_PushMatrix(); GL_LoadIdentity(); SG_CameraProjectLeft(sv->cam); GL_MatrixMode(GL_MODELVIEW); GL_PushMatrix(); SG_NodeDraw(sg, sg->root, sv); GL_PopMatrix(); glDrawBuffer(GL_BACK_RIGHT); glClear(GL_DEPTH_BUFFER_BIT); GL_MatrixMode(GL_PROJECTION); GL_LoadIdentity(); SG_CameraProjectRight(sv->cam); GL_MatrixMode(GL_MODELVIEW); GL_PushMatrix(); SG_NodeDraw(sg, sg->root, sv); GL_PopMatrix(); GL_MatrixMode(GL_PROJECTION); GL_PopMatrix(); GL_MatrixMode(GL_MODELVIEW); glDrawBuffer(GL_BACK); } else { glClear(GL_DEPTH_BUFFER_BIT); GL_MatrixMode(GL_MODELVIEW); GL_PushMatrix(); SG_NodeDraw(sg, sg->root, sv); GL_PopMatrix(); } AG_ObjectUnlock(sv->cam); AG_ObjectUnlock(sg); if ((sv->flags & SG_VIEW_NO_LIGHTING) == 0) { GL_PopAttrib(); } GL_PopAttrib(); } static void OnReshape(AG_Event *_Nonnull event) { SG_View *sv = SG_VIEW_SELF(); if (sv->cam == NULL) return; Debug(sv, "OnReshape (%d x %d)\n", WIDTH(sv), HEIGHT(sv)); GL_MatrixMode(GL_PROJECTION); SG_CameraProject(sv->cam); GL_MatrixMode(GL_MODELVIEW); glLoadIdentity(); } static void MouseMotion(void *obj, int x, int y, int dx, int dy) { SG_View *sv = obj; if ((sv->flags & SG_VIEW_GRABBED_MOUSE) == 0) { return; } if (AG_GetModState(sv) & AG_KEYMOD_CTRL) { SG_CameraRotMouse(sv->cam, sv, dx,dy); } else { SG_CameraMoveMouse(sv->cam, sv, dx,dy, 0); } } static void ViewSwitchCamera(AG_Event *_Nonnull event) { SG_View *sv = SG_VIEW_PTR(1); SG_Camera *cam = SG_CAMERA_PTR(2); AG_ObjectLock(sv); sv->cam = cam; sv->flags |= SG_VIEW_UPDATE_PROJ; AG_ObjectUnlock(sv); } static void PopupMenu(SG_View *_Nonnull sv, int x, int y) { SG *sg = sv->sg; AG_PopupMenu *pm; AG_MenuItem *mOvl, *mCam; if (sv->pmView != NULL) { AG_PopupShowAt(sv->pmView, x, y); return; } if ((pm = sv->pmView = AG_PopupNew(sv)) == NULL) return; AG_MenuUintFlags(pm->root, _("Lighting"), sgIconLighting.s, &sv->flags, SG_VIEW_NO_LIGHTING, 1); AG_MenuUintFlags(pm->root, _("Z-Buffer"), sgIconZBuffer.s, &sv->flags, SG_VIEW_NO_DEPTH_TEST, 1); mOvl = AG_MenuNode(pm->root, _("Overlay"), NULL); { AG_MenuUintFlags(mOvl, _("Wireframe"), sgIconWireframe.s, &sg->flags, SG_OVERLAY_WIREFRAME, 0); AG_MenuUintFlags(mOvl, _("Vertices"), sgIconVertices.s, &sg->flags, SG_OVERLAY_VERTICES, 0); AG_MenuUintFlags(mOvl, _("Facet normals"), sgIconFacetNormals.s, &sg->flags, SG_OVERLAY_FNORMALS, 0); AG_MenuUintFlags(mOvl, _("Vertex normals"), sgIconVertexNormals.s, &sg->flags, SG_OVERLAY_VNORMALS, 0); } mCam = AG_MenuNode(pm->root, _("Switch to camera"), NULL); { AG_MenuItem *mi; SG_Camera *cam; OBJECT_FOREACH_CLASS(cam, sg->root, sg_camera, "SG_Node:SG_Camera:*") { mi = AG_MenuAction(mCam, OBJECT(cam)->name, sgIconCamera.s, ViewSwitchCamera, "%p,%p", sv, cam); mi->state = (cam != sv->cam); } } AG_PopupShowAt(pm, x,y); } /* Select node by mouse and initiate move instruction. */ static void SelectByMouse(SG_View *_Nonnull sv, int x, int y) { M_Vector4 pNear, pFar; M_Line3 ray; SG_Node *node, *nodeNearest = NULL; M_Real dNearest = M_INFINITY; M_Matrix44 T; /* Find point corresponding to x,y on the Near and Far planes */ SG_ViewUnProject(sv, x, y, &pNear, &pFar); SG_GetNodeTransform(sv->cam, &T); M_MatMultVector44v(&pNear, &T); M_MatMultVector44v(&pFar, &T); /* Intersect ray with nodes in the scene. */ SG_FOREACH_NODE(node, sv->sg) { M_Vector3 pRayNear, pRayFar; M_GeomSet3 S = M_GEOM_SET_EMPTY; M_Matrix44 T; M_Geom3 gt; M_Real d; M_Line3 ln; SG_GetNodeTransformInverse(node, &T); pRayNear = M_VecFromProj3(M_MatMultVector44(T,pNear)); pRayFar = M_VecFromProj3(M_MatMultVector44(T,pFar)); ray = M_LineFromPts3(pRayNear, pRayFar); gt.type = M_LINE; gt.g.line = ray; node->flags &= ~(SG_NODE_SELECTED); if (SG_Intersect(node, gt, &S) != 1) { continue; } switch (S.g[0].type) { case M_POINT: d = M_VecDistance3(S.g[0].g.point, pRayNear); if (d < dNearest) { dNearest = d; nodeNearest = node; } break; case M_LINE: ln = S.g[0].g.line; d = M_VecDistance3(M_LineInitPt3(ln), pRayNear); if (d < dNearest) { dNearest = d; nodeNearest = node; } d = M_VecDistance3(M_LineTermPt3(ln), pRayNear); if (d < dNearest) { dNearest = d; nodeNearest = node; } break; default: break; } } if (nodeNearest != NULL) nodeNearest->flags |= SG_NODE_SELECTED; } /* Timer for keyboard controlled camera rotation. */ static Uint32 CamRotateTimeout(AG_Timer *_Nonnull to, AG_Event *event) { SG_View *sv = SG_VIEW_SELF(); const SG_ViewCamAction *act = AG_PTR(1); const M_Real dir = (M_Real)AG_FLOAT(2); SG *sg = sv->sg; Uint32 rv = 0; AG_ObjectLock(sv); if (sv->cam == NULL) { goto out; } AG_ObjectLock(sg); SG_Rotatev(sv->cam, dir*act->incr, act->v); AG_ObjectUnlock(sg); rv = (to->ival > act->vMax) ? (to->ival - act->vAccel) : to->ival; AG_Redraw(sv); out: AG_ObjectUnlock(sv); return (rv); } /* Timer for keyboard controlled camera translation. */ static Uint32 CamMoveTimeout(AG_Timer *_Nonnull to, AG_Event *event) { SG_View *sv = SG_VIEW_SELF(); const SG_ViewCamAction *act = AG_PTR(1); const M_Real dir = (M_Real)AG_FLOAT(2); SG *sg = sv->sg; Uint32 rv = 0; AG_ObjectLock(sv); if (sv->cam == NULL) { goto out; } AG_ObjectLock(sg); SG_Translatev(sv->cam, M_VecScale3p(&act->v, dir*act->incr)); AG_ObjectUnlock(sg); rv = (to->ival > act->vMax) ? (to->ival - act->vAccel) : to->ival; AG_Redraw(sv); out: AG_ObjectUnlock(sv); return (rv); } static void KeyDown(void *obj, AG_KeySym ks, AG_KeyMod kmod, AG_Char ch) { SG_View *sv = obj; switch (ks) { case AG_KEY_LEFT: if (kmod & AG_KEYMOD_CTRL) { AG_AddTimer(sv, &sv->toRot, sv->rot[0].vMin, CamRotateTimeout, "%p,%f", &sv->rot[0], -1.0f); } else { AG_AddTimer(sv, &sv->toMove, sv->move[0].vMin, CamMoveTimeout, "%p,%f", &sv->move[0], -1.0f); } break; case AG_KEY_RIGHT: if (kmod & AG_KEYMOD_CTRL) { AG_AddTimer(sv, &sv->toRot, sv->rot[0].vMin, CamRotateTimeout, "%p,%f", &sv->rot[0], +1.0f); } else { AG_AddTimer(sv, &sv->toMove, sv->move[0].vMin, CamMoveTimeout, "%p,%f", &sv->move[0], +1.0f); } break; case AG_KEY_UP: if (kmod & AG_KEYMOD_CTRL) { AG_AddTimer(sv, &sv->toRot, sv->rot[1].vMin, CamRotateTimeout, "%p,%f", &sv->rot[1], -1.0f); } else { AG_AddTimer(sv, &sv->toMove, sv->move[1].vMin, CamMoveTimeout, "%p,%f", &sv->move[1], +1.0f); } break; case AG_KEY_DOWN: if (kmod & AG_KEYMOD_CTRL) { AG_AddTimer(sv, &sv->toRot, sv->rot[1].vMin, CamRotateTimeout, "%p,%f", &sv->rot[1], +1.0f); } else { AG_AddTimer(sv, &sv->toMove, sv->move[1].vMin, CamMoveTimeout, "%p,%f", &sv->move[1], -1.0f); } break; case AG_KEY_PAGEUP: AG_AddTimer(sv, &sv->toMove, sv->move[2].vMin, CamMoveTimeout, "%p,%f", &sv->move[2], -1.0f); break; case AG_KEY_PAGEDOWN: AG_AddTimer(sv, &sv->toMove, sv->move[2].vMin, CamMoveTimeout, "%p,%f", &sv->move[2], +1.0f); break; case AG_KEY_DELETE: AG_AddTimer(sv, &sv->toRot, sv->rot[2].vMin, CamRotateTimeout, "%p,%f", &sv->rot[2], -1.0f); break; case AG_KEY_END: AG_AddTimer(sv, &sv->toRot, sv->rot[2].vMin, CamRotateTimeout, "%p,%f", &sv->rot[2], +1.0f); break; default: break; } sv->lastKeyDown = ks; } static void KeyUp(void *obj, AG_KeySym ks, AG_KeyMod kmod, AG_Char ch) { SG_View *sv = obj; switch (ks) { case AG_KEY_LEFT: case AG_KEY_RIGHT: case AG_KEY_UP: case AG_KEY_DOWN: case AG_KEY_PAGEUP: case AG_KEY_PAGEDOWN: case AG_KEY_DELETE: case AG_KEY_END: if (ks == sv->lastKeyDown) { AG_DelTimer(sv, &sv->toRot); AG_DelTimer(sv, &sv->toMove); } break; default: break; } } static void MouseButtonDown(void *obj, AG_MouseButton button, int x, int y) { SG_View *sv = obj; if (!AG_WidgetIsFocused(sv)) { AG_WidgetFocus(sv); } switch (button) { case AG_MOUSE_WHEELUP: SG_CameraMoveMouse(sv->cam, sv, 0, 0, 1); break; case AG_MOUSE_WHEELDOWN: SG_CameraMoveMouse(sv->cam, sv, 0, 0, -1); break; case AG_MOUSE_RIGHT: PopupMenu(sv, x, y); break; case AG_MOUSE_LEFT: sv->flags |= SG_VIEW_GRABBED_MOUSE; if ((AG_GetModState(sv) & AG_KEYMOD_CTRL) == 0) { SelectByMouse(sv, x,y); } break; default: break; } } static void MouseButtonUp(void *obj, AG_MouseButton button, int x, int y) { SG_View *sv = obj; switch (button) { case AG_MOUSE_LEFT: sv->flags &= ~(SG_VIEW_GRABBED_MOUSE); break; default: break; } } /* Configure the active camera. */ void SG_ViewSetCamera(SG_View *sv, SG_Camera *cam) { AG_ObjectLock(sv); sv->cam = cam; sv->flags |= SG_VIEW_UPDATE_PROJ; AG_Redraw(sv); AG_ObjectUnlock(sv); } static void OnShow(AG_Event *_Nonnull event) { SG_View *sv = SG_VIEW_SELF(); SG *sg = sv->sg; SG_Node *node; if (sg == NULL) return; #ifdef AG_DEBUG Debug(sv, "Show: %s (%d x %d)\n", OBJECT(sg)->name, WIDTH(sv), HEIGHT(sv)); #endif AG_ObjectLock(sg); SG_FOREACH_NODE(node, sg) { AG_PostEvent(node, "view-shown", "%p", sv); } AG_ObjectUnlock(sg); } static void OnHide(AG_Event *_Nonnull event) { SG_View *sv = SG_VIEW_SELF(); SG *sg = sv->sg; SG_Node *node; if (sg == NULL) return; #ifdef AG_DEBUG Debug(sv, "Hide: %s\n", OBJECT(sg)->name); #endif AG_ObjectLock(sg); SG_FOREACH_NODE(node, sg) { AG_PostEvent(node, "view-hidden", "%p", sv); } AG_ObjectUnlock(sg); } static void Init(void *_Nonnull obj) { SG_View *sv = obj; int i; WIDGET(sv)->flags |= (AG_WIDGET_FOCUSABLE | AG_WIDGET_USE_OPENGL | AG_WIDGET_USE_TEXT); sv->flags = 0; sv->transFlags = 0; sv->sg = NULL; sv->sgTrans = NULL; sv->transProgress = 1.0; sv->cam = NULL; sv->camTrans = NULL; sv->editArea = NULL; sv->editNode = NULL; AG_ColorBlack(&sv->transFadeColor); sv->transDuration = 500; sv->pmView = NULL; sv->pmNode = NULL; AG_InitTimer(&sv->toTransFade, "transFade", 0); AG_InitTimer(&sv->toRefresh, "refresh", 0); sv->rSens = M_VECTOR3(0.01, 0.01, 0.01); sv->tSens = M_VECTOR3(0.01, 0.01, 0.1); sv->rot[0].v = M_VecJ3(); sv->rot[1].v = M_VecI3(); sv->rot[2].v = M_VecK3(); sv->move[0].v = M_VecI3(); sv->move[1].v = M_VecJ3(); sv->move[2].v = M_VecK3(); for (i = 0; i < 3; i++) { sv->rot[i].vMin = 30; sv->rot[i].vAccel = 1; sv->rot[i].vMax = 10; sv->rot[i].incr = 0.02; } for (i = 0; i < 3; i++) { sv->move[i].vMin = 20; sv->move[i].vAccel = 1; sv->move[i].vMax = 5; sv->move[i].incr = 0.02; } sv->editStatus[0] = '\0'; AG_AddEvent(sv, "widget-shown", OnShow, NULL); AG_AddEvent(sv, "widget-hidden", OnHide, NULL); AG_SetEvent(sv, "widget-reshape", OnReshape, NULL); AG_SetEvent(sv, "widget-overlay", OnOverlay, NULL); /* AG_RedrawOnTick(sv, 1000); */ } static void Destroy(void *_Nonnull obj) { SG_View *sv = obj; if (sv->pmView != NULL) AG_PopupDestroy(sv->pmView); if (sv->pmNode != NULL) AG_PopupDestroy(sv->pmNode); } AG_WidgetClass sgViewClass = { { "AG_Widget:SG_View", sizeof(SG_View), { 0,0, AGC_SG_VIEW, 0xE02D }, Init, NULL, /* reset */ Destroy, NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate, MouseButtonDown, MouseButtonUp, MouseMotion, KeyDown, KeyUp, NULL, /* touch */ NULL, /* ctrl */ NULL /* joy */ };