picking and brute force ray intersections
authorJohn Tsiombikas <nuclear@member.fsf.org>
Mon, 12 Jun 2023 16:41:33 +0000 (19:41 +0300)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Mon, 12 Jun 2023 16:41:33 +0000 (19:41 +0300)
16 files changed:
.gitignore
libs/cgmath/cgmath.h
libs/cgmath/cgmmat.inl
libs/cgmath/cgmmisc.inl
libs/cgmath/cgmquat.inl
libs/cgmath/cgmray.inl
libs/cgmath/cgmvec3.inl
libs/cgmath/cgmvec4.inl
src/gaw/gaw.h
src/gaw/gaw_gl.c
src/gaw/gawswtnl.c
src/rt.c [new file with mode: 0644]
src/rt.h [new file with mode: 0644]
src/scene.c
src/scene.h
src/scr_mod.c

index 614b0ad..01e8736 100644 (file)
@@ -7,3 +7,4 @@ retroray
 *.cfg
 data/
 *.exe
+config.mk
index cd3de51..9f99361 100644 (file)
@@ -1,5 +1,5 @@
 /* gph-cmath - C graphics math library
- * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ * Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
  *
  * This program is free software. Feel free to use, modify, and/or redistribute
  * it under the terms of the MIT/X11 license. See LICENSE for details.
 #ifndef CGMATH_H_
 #define CGMATH_H_
 
-#if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES)
-#define _USE_MATH_DEFINES
-#endif
-
 #include <math.h>
 #include <string.h>
 
-#ifndef M_PI
-#define M_PI   3.141592653589793
-#endif
+#define CGM_PI 3.141592653589793
 
 typedef struct {
        float x, y;
@@ -66,6 +60,8 @@ typedef enum cgm_euler_mode {
 } cgm_euler_mode;
 
 #ifdef __cplusplus
+#define CGM_INLINE inline
+
 extern "C" {
 #else
 
@@ -270,10 +266,6 @@ static CGM_INLINE void cgm_bary(cgm_vec3 *bary, const cgm_vec3 *a,
 static CGM_INLINE void cgm_uvec_to_sph(float *theta, float *phi, const cgm_vec3 *v);
 static CGM_INLINE void cgm_sph_to_uvec(cgm_vec3 *v, float theta, float phi);
 
-#ifdef _MSC_VER
-#pragma warning (disable: 4244)
-#endif
-
 #include "cgmvec3.inl"
 #include "cgmvec4.inl"
 #include "cgmquat.inl"
index b1a19f0..f8168b4 100644 (file)
@@ -1,5 +1,5 @@
 /* gph-cmath - C graphics math library
- * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ * Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
  *
  * This program is free software. Feel free to use, modify, and/or redistribute
  * it under the terms of the MIT/X11 license. See LICENSE for details.
@@ -486,22 +486,6 @@ static CGM_INLINE void cgm_mget_scaling(const float *m, cgm_vec3 *res)
 
 static CGM_INLINE void cgm_mget_frustum_plane(const float *m, int p, cgm_vec4 *res)
 {
-       /*
-       int row = p >> 1;
-       const float *rowptr = m + row * 4;
-
-       if((p & 1) == 0) {
-               res->x = m[12] + rowptr[0];
-               res->y = m[13] + rowptr[1];
-               res->z = m[14] + rowptr[2];
-               res->w = m[15] + rowptr[3];
-       } else {
-               res->x = m[12] - rowptr[0];
-               res->y = m[13] - rowptr[1];
-               res->z = m[14] - rowptr[2];
-               res->w = m[15] - rowptr[3];
-       }
-       */
        switch(p) {
        case 0:
                res->x = m[3] + m[0];
index 2ef8a11..a3cdfb5 100644 (file)
@@ -1,5 +1,5 @@
 /* gph-cmath - C graphics math library
- * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ * Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
  *
  * This program is free software. Feel free to use, modify, and/or redistribute
  * it under the terms of the MIT/X11 license. See LICENSE for details.
@@ -133,8 +133,9 @@ static CGM_INLINE void cgm_glu_unproject(float winx, float winy, float winz,
        cgm_vec3 npos, res;
        float inv_pv[16];
 
-       cgm_mcopy(inv_pv, proj);
-       cgm_mmul(inv_pv, view);
+       cgm_mcopy(inv_pv, view);
+       cgm_mmul(inv_pv, proj);
+       cgm_minverse(inv_pv);
 
        npos.x = (winx - vp[0]) / vp[2];
        npos.y = (winy - vp[1]) / vp[4];
@@ -153,8 +154,9 @@ static CGM_INLINE void cgm_pick_ray(cgm_ray *ray, float nx, float ny,
        cgm_vec3 npos, farpt;
        float inv_pv[16];
 
-       cgm_mcopy(inv_pv, projmat);
-       cgm_mmul(inv_pv, viewmat);
+       cgm_mcopy(inv_pv, viewmat);
+       cgm_mmul(inv_pv, projmat);
+       cgm_minverse(inv_pv);
 
        cgm_vcons(&npos, nx, ny, 0.0f);
        cgm_unproject(&ray->origin, &npos, inv_pv);
index 4351a1f..72b18a3 100644 (file)
@@ -1,5 +1,5 @@
 /* gph-cmath - C graphics math library
- * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ * Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
  *
  * This program is free software. Feel free to use, modify, and/or redistribute
  * it under the terms of the MIT/X11 license. See LICENSE for details.
index c396a55..b9a7dfa 100644 (file)
@@ -1,5 +1,5 @@
 /* gph-cmath - C graphics math library
- * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ * Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
  *
  * This program is free software. Feel free to use, modify, and/or redistribute
  * it under the terms of the MIT/X11 license. See LICENSE for details.
index e03cc32..874d0fc 100644 (file)
@@ -1,5 +1,5 @@
 /* gph-cmath - C graphics math library
- * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ * Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
  *
  * This program is free software. Feel free to use, modify, and/or redistribute
  * it under the terms of the MIT/X11 license. See LICENSE for details.
index 1ee8533..1d66496 100644 (file)
@@ -1,5 +1,5 @@
 /* gph-cmath - C graphics math library
- * Copyright (C) 2018 John Tsiombikas <nuclear@member.fsf.org>
+ * Copyright (C) 2018-2023 John Tsiombikas <nuclear@member.fsf.org>
  *
  * This program is free software. Feel free to use, modify, and/or redistribute
  * it under the terms of the MIT/X11 license. See LICENSE for details.
index fcb19e9..e80c623 100644 (file)
@@ -58,7 +58,8 @@ enum {
        GAW_LIGHT2,
        GAW_LIGHT3,
        GAW_TEXTURE_1D,
-       GAW_TEXTURE_2D
+       GAW_TEXTURE_2D,
+       GAW_POLYGON_OFFSET
 };
 
 enum {
@@ -97,6 +98,7 @@ enum {
 };
 
 void gaw_viewport(int x, int y, int w, int h);
+void gaw_get_viewport(int *vp);
 
 void gaw_matrix_mode(int mode);
 void gaw_load_identity(void);
index 40f870c..1868d26 100644 (file)
@@ -44,6 +44,11 @@ void gaw_viewport(int x, int y, int w, int h)
        glViewport(x, y, w, h);
 }
 
+void gaw_get_viewport(int *vp)
+{
+       glGetIntegerv(GL_VIEWPORT, vp);
+}
+
 void gaw_matrix_mode(int mode)
 {
        mode += GL_MODELVIEW;
@@ -167,6 +172,9 @@ void gaw_enable(int st)
        case GAW_TEXTURE_2D:
                glEnable(GL_TEXTURE_2D);
                break;
+       case GAW_POLYGON_OFFSET:
+               glEnable(GL_POLYGON_OFFSET_FILL);
+               break;
        default:
                break;
        }
@@ -214,6 +222,9 @@ void gaw_disable(int st)
        case GAW_TEXTURE_2D:
                glDisable(GL_TEXTURE_2D);
                break;
+       case GAW_POLYGON_OFFSET:
+               glDisable(GL_POLYGON_OFFSET_FILL);
+               break;
        default:
                break;
        }
@@ -235,6 +246,11 @@ void gaw_alpha_func(int func, float ref)
        glAlphaFunc(func + GL_NEVER, ref);
 }
 
+void gaw_zoffset(float offs)
+{
+       glPolygonOffset(1, offs);
+}
+
 void gaw_clear_color(float r, float g, float b, float a)
 {
        glClearColor(r, g, b, a);
index 4930e09..a30d56c 100644 (file)
@@ -100,6 +100,11 @@ void gaw_viewport(int x, int y, int w, int h)
        st.vport[3] = h;
 }
 
+void gaw_get_viewport(int *vp)
+{
+       memcpy(vp, st.vport, sizeof st.vport);
+}
+
 void gaw_matrix_mode(int mode)
 {
        st.mmode = mode;
@@ -318,7 +323,7 @@ void gaw_alpha_func(int func, float ref)
 
 void gaw_zoffset(float offs)
 {
-       st.zoffs = offs;
+       st.zoffs = offs * 0.1;
 }
 
 #define CLAMP(x, a, b)         ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x)))
@@ -540,7 +545,9 @@ void gaw_draw_indexed(int prim, const unsigned int *idxarr, int nidx)
                        float oow = 1.0f / v[i].w;
                        v[i].x *= oow;
                        v[i].y *= oow;
-                       v[i].z += st.zoffs;
+                       if(st.opt & (1 << GAW_POLYGON_OFFSET)) {
+                               v[i].z += st.zoffs;
+                       }
                        if(st.opt & (1 << GAW_DEPTH_TEST)) {
                                v[i].z *= oow;
                        }
diff --git a/src/rt.c b/src/rt.c
new file mode 100644 (file)
index 0000000..e94ac19
--- /dev/null
+++ b/src/rt.c
@@ -0,0 +1,106 @@
+/*
+RetroRay - integrated standalone vintage modeller/renderer
+Copyright (C) 2023  John Tsiombikas <nuclear@mutantstargoat.com>
+
+This program 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 3 of the License, or
+(at your option) any later version.
+
+This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "rt.h"
+
+
+int ray_object(const cgm_ray *ray, const struct object *obj, struct csghit *hit)
+{
+       int i, res;
+       cgm_ray localray = *ray;
+
+       cgm_rmul_mr(&localray, obj->inv_xform);
+
+       switch(obj->type) {
+       case OBJ_SPHERE:
+               res = ray_sphere(ray, (const struct sphere*)obj, hit);
+               break;
+
+       default:
+               res = 0;
+       }
+
+       if(res && hit) {
+               for(i=0; i<hit->ivcount; i++) {
+                       cgm_vmul_m4v3(&hit->ivlist[i].a.pos, obj->xform);
+                       cgm_vmul_m3v3(&hit->ivlist[i].a.norm, obj->xform);
+                       cgm_vmul_m4v3(&hit->ivlist[i].b.pos, obj->xform);
+                       cgm_vmul_m3v3(&hit->ivlist[i].b.norm, obj->xform);
+               }
+       }
+       return res;
+}
+
+int ray_sphere(const cgm_ray *ray, const struct sphere *sph, struct csghit *hit)
+{
+       int i;
+       float a, b, c, d, sqrt_d, t1, t2, invrad;
+       struct rayhit *rhptr;
+
+       a = cgm_vdot(&ray->dir, &ray->dir);
+       b = 2.0f * ray->dir.x * (ray->origin.x - sph->pos.x) +
+               2.0f * ray->dir.y * (ray->origin.y - sph->pos.y) +
+               2.0f * ray->dir.z * (ray->origin.z - sph->pos.z);
+       c = cgm_vdot(&sph->pos, &sph->pos) + cgm_vdot(&ray->origin, &ray->origin) +
+               cgm_vdot(&sph->pos, &ray->origin) * -2.0f - sph->rad * sph->rad;
+
+       if((d = b * b - 4.0 * a * c) < 0.0) return 0;
+
+       sqrt_d = sqrt(d);
+       t1 = (-b + sqrt_d) / (2.0 * a);
+       t2 = (-b - sqrt_d) / (2.0 * a);
+
+       if((t1 < 1e-6f && t2 < 1e-6f) || (t1 > 1.0f && t2 > 1.0f)) {
+               return 0;
+       }
+
+       if(hit) {
+               if(t1 < 1e-6f || t1 > 1.0f) {
+                       t1 = t2;
+               } else if(t2 < 1e-6f || t2 > 1.0f) {
+                       t2 = t1;
+               }
+               if(t2 < t1) {
+                       float tmp = t1;
+                       t1 = t2;
+                       t2 = tmp;
+               }
+
+               hit->ivcount = 1;
+               hit->ivlist[0].a.t = t1;
+               hit->ivlist[0].b.t = t2;
+               invrad = 1.0f / sph->rad;
+
+               rhptr = &hit->ivlist[0].a;
+               for(i=0; i<2; i++) {
+                       cgm_raypos(&rhptr->pos, ray, rhptr->t);
+                       rhptr->norm.x = rhptr->pos.x * invrad;
+                       rhptr->norm.y = rhptr->pos.y * invrad;
+                       rhptr->norm.z = rhptr->pos.z * invrad;
+                       rhptr->uv.x = (atan2(rhptr->norm.z, rhptr->norm.x) + CGM_PI) / (2.0 * CGM_PI);
+                       rhptr->uv.y = acos(rhptr->norm.y) / CGM_PI;
+                       rhptr->obj = (struct object*)sph;
+                       rhptr = &hit->ivlist[0].b;
+               }
+       }
+       return 1;
+}
+
+int ray_csg(const cgm_ray *ray, const struct csgnode *csg, struct csghit *hit)
+{
+       return 0;
+}
diff --git a/src/rt.h b/src/rt.h
new file mode 100644 (file)
index 0000000..4252281
--- /dev/null
+++ b/src/rt.h
@@ -0,0 +1,46 @@
+/*
+RetroRay - integrated standalone vintage modeller/renderer
+Copyright (C) 2023  John Tsiombikas <nuclear@mutantstargoat.com>
+
+This program 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 3 of the License, or
+(at your option) any later version.
+
+This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef RT_H_
+#define RT_H_
+
+#include "cgmath/cgmath.h"
+#include "scene.h"
+
+struct rayhit {
+       float t;
+       cgm_vec3 pos;
+       cgm_vec3 norm;
+       cgm_vec2 uv;
+       struct object *obj;
+};
+
+struct interval {
+       struct rayhit a, b;
+};
+
+#define MAX_INTERV     32
+struct csghit {
+       struct interval ivlist[MAX_INTERV];
+       int ivcount;
+};
+
+int ray_object(const cgm_ray *ray, const struct object *obj, struct csghit *hit);
+int ray_sphere(const cgm_ray *ray, const struct sphere *sph, struct csghit *hit);
+int ray_csg(const cgm_ray *ray, const struct csgnode *csg, struct csghit *hit);
+
+#endif /* RT_H_ */
index 4f78861..4994257 100644 (file)
@@ -16,7 +16,9 @@ You should have received a copy of the GNU General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 #include <stdlib.h>
+#include <float.h>
 #include "scene.h"
+#include "rt.h"
 #include "darray.h"
 #include "logger.h"
 
@@ -52,11 +54,62 @@ int scn_add_object(struct scene *scn, struct object *obj)
        return 0;
 }
 
-int scn_num_objects(struct scene *scn)
+int scn_rm_object(struct scene *scn, int idx)
+{
+       int numobj = darr_size(scn->objects);
+
+       if(idx < 0 || idx >= numobj) {
+               return -1;
+       }
+
+       free_object(scn->objects[idx]);
+
+       if(idx < numobj - 1) {
+               scn->objects[idx] = scn->objects[numobj - 1];
+       }
+       darr_pop(scn->objects);
+       return 0;
+}
+
+int scn_num_objects(const struct scene *scn)
 {
        return darr_size(scn->objects);
 }
 
+int scn_object_index(const struct scene *scn, const struct object *obj)
+{
+       int i, num = darr_size(scn->objects);
+       for(i=0; i<num; i++) {
+               if(scn->objects[i] == obj) {
+                       return i;
+               }
+       }
+       return -1;
+}
+
+int scn_intersect(const struct scene *scn, const cgm_ray *ray, struct rayhit *hit)
+{
+       int i, numobj;
+       struct rayhit hit0;
+       struct csghit chit;
+
+       hit0.t = FLT_MAX;
+       hit0.obj = 0;
+
+       numobj = darr_size(scn->objects);
+       for(i=0; i<numobj; i++) {
+               if(ray_object(ray, scn->objects[i], &chit) && chit.ivlist[0].a.t < hit0.t) {
+                       hit0 = chit.ivlist[0].a;
+               }
+       }
+
+       if(hit0.obj) {
+               if(hit) *hit = hit0;
+               return 1;
+       }
+       return 0;
+}
+
 struct object *create_object(int type)
 {
        struct object *obj;
@@ -64,7 +117,7 @@ struct object *create_object(int type)
        char buf[32];
        static int objid;
 
-       if(!(obj = malloc(sizeof *obj))) {
+       if(!(obj = calloc(1, sizeof *obj))) {
                errormsg("failed to allocate object\n");
                return 0;
        }
@@ -75,6 +128,7 @@ struct object *create_object(int type)
        cgm_vcons(&obj->scale, 1, 1, 1);
        cgm_vcons(&obj->pivot, 0, 0, 0);
        cgm_midentity(obj->xform);
+       cgm_midentity(obj->inv_xform);
 
        switch(type) {
        case OBJ_SPHERE:
@@ -131,6 +185,8 @@ void calc_object_matrix(struct object *obj)
        mat[2] *= obj->scale.x; mat[6] *= obj->scale.y; mat[10] *= obj->scale.z; mat[14] += obj->pos.z;
 
        cgm_mpretranslate(mat, -obj->pivot.x, -obj->pivot.y, -obj->pivot.z);
-
        /* that's basically: pivot * rotation * translation * scaling * -pivot */
+
+       cgm_mcopy(obj->inv_xform, mat);
+       cgm_minverse(obj->inv_xform);
 }
index a1e81c2..d12dae6 100644 (file)
@@ -22,7 +22,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 enum {
        OBJ_NULL,
-       OBJ_SPHERE
+       OBJ_SPHERE,
+       OBJ_CSG
 };
 
 #define OBJ_COMMON_ATTR \
@@ -30,7 +31,7 @@ enum {
        char *name; \
        cgm_vec3 pos, scale, pivot; \
        cgm_quat rot; \
-       float xform[16]
+       float xform[16], inv_xform[16]
 
 struct object {
        OBJ_COMMON_ATTR;
@@ -41,15 +42,27 @@ struct sphere {
        float rad;
 };
 
+struct csgnode {
+       OBJ_COMMON_ATTR;
+       int op;
+       struct object *subobj;  /* darr */
+};
+
 struct scene {
        struct object **objects;        /* darr */
 };
 
+struct rayhit; /* declared in rt.h */
+
 struct scene *create_scene(void);
 void free_scene(struct scene *scn);
 
 int scn_add_object(struct scene *scn, struct object *obj);
-int scn_num_objects(struct scene *scn);
+int scn_rm_object(struct scene *scn, int idx);
+int scn_num_objects(const struct scene *scn);
+int scn_object_index(const struct scene *scn, const struct object *obj);
+
+int scn_intersect(const struct scene *scn, const cgm_ray *ray, struct rayhit *hit);
 
 struct object *create_object(int type);
 void free_object(struct object *obj);
index 2153663..0a17745 100644 (file)
@@ -15,10 +15,12 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
+#include <assert.h>
 #include "gaw/gaw.h"
 #include "app.h"
 #include "rtk.h"
 #include "scene.h"
+#include "rt.h"
 #include "cmesh.h"
 #include "meshgen.h"
 
@@ -26,7 +28,8 @@ enum {
        TBN_NEW, TBN_OPEN, TBN_SAVE, TBN_SEP1,
        TBN_SEL, TBN_MOVE, TBN_ROT, TBN_SCALE, TBN_SEP2,
        TBN_ADD, TBN_RM, TBN_SEP3,
-       TBN_MTL, TBN_REND, TBN_VIEWREND, TBN_SEP4, TBN_CFG,
+       TBN_UNION, TBN_ISECT, TBN_DIFF, TBN_SEP4,
+       TBN_MTL, TBN_REND, TBN_VIEWREND, TBN_SEP5, TBN_CFG,
 
        NUM_TOOL_BUTTONS
 };
@@ -34,20 +37,34 @@ static const char *tbn_icon_name[] = {
        "new", "open", "save", 0,
        "sel", "move", "rot", "scale", 0,
        "add", "remove", 0,
+       "union", "isect", "diff", 0,
        "mtl", "rend", "viewrend", 0, "cfg"
 };
 static int tbn_icon_pos[][2] = {
        {0,0}, {16,0}, {32,0}, {-1,-1},
        {48,0}, {64,0}, {80,0}, {96,0}, {-1,-1},
        {112,0}, {112,16}, {-1,-1},
+       {0,16}, {16,16}, {32,16}, {-1,-1},
        {48,16}, {64,16}, {80,16}, {-1,-1}, {96,16}
 };
+static int tbn_istool[] = {
+       0, 0, 0, 0,
+       1, 1, 1, 1, 0,
+       0, 0, 0,
+       1, 1, 1, 0,
+       0, 0, 0, 0, 0
+};
 static rtk_icon *tbn_icons[NUM_TOOL_BUTTONS];
 static rtk_widget *tbn_buttons[NUM_TOOL_BUTTONS];
 
 #define TOOLBAR_HEIGHT 26
 
-enum {TOOL_SEL, TOOL_MOVE, TOOL_ROT, TOOL_SCALE, NUM_TOOLS};
+enum {
+       TOOL_SEL, TOOL_MOVE, TOOL_ROT, TOOL_SCALE,
+       TOOL_UNION, TOOL_ISECT, TOOL_DIFF,
+       NUM_TOOLS
+};
+static rtk_widget *tools[NUM_TOOLS];
 
 
 static int mdl_init(void);
@@ -60,9 +77,14 @@ static void mdl_keyb(int key, int press);
 static void mdl_mouse(int bn, int press, int x, int y);
 static void mdl_motion(int x, int y);
 
+static void draw_object(struct object *obj);
 static void draw_grid(void);
 static void tbn_callback(rtk_widget *w, void *cls);
 
+static void draw_rband(void);
+static void primray(cgm_ray *ray, int x, int y);
+
+
 struct app_screen scr_model = {
        "modeller",
        mdl_init, mdl_destroy,
@@ -77,14 +99,20 @@ static rtk_iconsheet *icons;
 static struct cmesh *mesh_sph;
 
 static float cam_theta, cam_phi = 20, cam_dist = 8;
+static float view_matrix[16], proj_matrix[16];
+static float view_matrix_inv[16], proj_matrix_inv[16];
+static int viewport[4];
+static cgm_ray pickray;
 
-static int tool;
+static int cur_tool;
 static int selobj = -1;
 
+static rtk_rect rband;
+static int rband_valid;
 
 static int mdl_init(void)
 {
-       int i;
+       int i, toolidx;
        rtk_widget *w;
 
        if(!(icons = rtk_load_iconsheet("data/icons.png"))) {
@@ -105,6 +133,7 @@ static int mdl_init(void)
        }
        rtk_win_layout(toolbar, RTK_HBOX);
 
+       toolidx = 0;
        for(i=0; i<NUM_TOOL_BUTTONS; i++) {
                if(!tbn_icons[i]) {
                        rtk_create_separator(toolbar);
@@ -113,15 +142,17 @@ static int mdl_init(void)
                                return -1;
                        }
                        tbn_buttons[i] = w;
-                       rtk_set_callback(w, tbn_callback, (void*)i);
-                       if(i >= TBN_SEL && i <= TBN_SCALE) {
+                       rtk_set_callback(w, tbn_callback, (void*)(intptr_t)i);
+                       if(tbn_istool[i]) {
                                rtk_bn_mode(w, RTK_TOGGLEBN);
+                               tools[toolidx++] = w;
                        }
                        if(i == TBN_SEL) {
                                rtk_set_value(w, 1);
                        }
                }
        }
+       assert(toolidx == NUM_TOOLS);
 
        if(!(mesh_sph = cmesh_alloc())) {
                errormsg("failed to allocate sphere vis mesh\n");
@@ -160,50 +191,64 @@ static void mdl_display(void)
 
        rtk_draw_widget(toolbar);
 
-       gaw_viewport(0, TOOLBAR_HEIGHT, win_width, win_height - TOOLBAR_HEIGHT);
-
        gaw_matrix_mode(GAW_MODELVIEW);
        gaw_load_identity();
        gaw_translate(0, 0, -cam_dist);
        gaw_rotate(cam_phi, 1, 0, 0);
        gaw_rotate(cam_theta, 0, 1, 0);
+       gaw_get_modelview(view_matrix);
+       cgm_mcopy(view_matrix_inv, view_matrix);
+       cgm_minverse(view_matrix_inv);
 
        draw_grid();
 
+       gaw_mtl_diffuse(0.5, 0.5, 0.5, 1);
+
        num = scn_num_objects(scn);
        for(i=0; i<num; i++) {
-               struct object *obj = scn->objects[i];
-               struct sphere *sph;
-
-               calc_object_matrix(obj);
-               gaw_push_matrix();
-               gaw_mult_matrix(obj->xform);
-
-               switch(obj->type) {
-               case OBJ_SPHERE:
-                       sph = (struct sphere*)obj;
-                       gaw_scale(sph->rad, sph->rad, sph->rad);
-                       gaw_zoffset(0.1);
-                       cmesh_draw(mesh_sph);
-                       gaw_zoffset(0);
+               if(i == selobj) {
+                       gaw_zoffset(1);
+                       gaw_enable(GAW_POLYGON_OFFSET);
+                       draw_object(scn->objects[i]);
+                       gaw_disable(GAW_POLYGON_OFFSET);
 
                        gaw_save();
                        gaw_disable(GAW_LIGHTING);
                        gaw_poly_wire();
                        gaw_color3f(0, 1, 0);
-                       cmesh_draw(mesh_sph);
+                       draw_object(scn->objects[i]);
                        gaw_poly_gouraud();
                        gaw_restore();
-                       break;
-
-               default:
-                       break;
+               } else {
+                       draw_object(scn->objects[i]);
                }
+       }
+
+       if(rband_valid) {
+               draw_rband();
+       }
+}
 
-               gaw_pop_matrix();
+static void draw_object(struct object *obj)
+{
+       struct sphere *sph;
+
+       calc_object_matrix(obj);
+       gaw_push_matrix();
+       gaw_mult_matrix(obj->xform);
+
+       switch(obj->type) {
+       case OBJ_SPHERE:
+               sph = (struct sphere*)obj;
+               gaw_scale(sph->rad, sph->rad, sph->rad);
+               cmesh_draw(mesh_sph);
+               break;
+
+       default:
+               break;
        }
 
-       gaw_viewport(0, 0, win_width, win_height);
+       gaw_pop_matrix();
 }
 
 static void draw_grid(void)
@@ -231,9 +276,18 @@ static void mdl_reshape(int x, int y)
 {
        float aspect = (float)x / (float)(y - TOOLBAR_HEIGHT);
 
+       viewport[0] = 0;
+       viewport[1] = TOOLBAR_HEIGHT;
+       viewport[2] = x;
+       viewport[3] = y - TOOLBAR_HEIGHT;
+       gaw_viewport(viewport[0], viewport[1], viewport[2], viewport[3]);
+
        gaw_matrix_mode(GAW_PROJECTION);
        gaw_load_identity();
        gaw_perspective(50, aspect, 0.5, 100.0);
+       gaw_get_projection(proj_matrix);
+       cgm_mcopy(proj_matrix_inv, proj_matrix);
+       cgm_minverse(proj_matrix_inv);
 
        rtk_resize(toolbar, win_width, TOOLBAR_HEIGHT);
 }
@@ -250,15 +304,32 @@ static int vpdrag;
 
 static void mdl_mouse(int bn, int press, int x, int y)
 {
+       struct rayhit hit;
        if(!vpdrag && rtk_input_mbutton(toolbar, bn, press, x, y)) {
                app_redisplay();
                return;
        }
 
        if(press) {
+               rband.x = x;
+               rband.y = y;
                vpdrag |= (1 << bn);
        } else {
                vpdrag &= ~(1 << bn);
+
+               if(rband_valid) {
+                       printf("rubber band: %d,%d %dx%d\n", rband.x, rband.y, rband.width, rband.height);
+                       rband_valid = 0;
+
+               } else if(bn == 0 && x == rband.x && y == rband.y) {
+                       primray(&pickray, x, y);
+                       if(scn_intersect(scn, &pickray, &hit)) {
+                               selobj = scn_object_index(scn, hit.obj);
+                       } else {
+                               selobj = -1;
+                       }
+               }
+               app_redisplay();
        }
 }
 
@@ -274,19 +345,29 @@ static void mdl_motion(int x, int y)
        dx = x - mouse_x;
        dy = y - mouse_y;
 
-       if((dx | dy) == 0) return;
-
-       if(mouse_state[0]) {
-               cam_theta += dx * 0.5f;
-               cam_phi += dy * 0.5f;
-               if(cam_phi < -90) cam_phi = -90;
-               if(cam_phi > 90) cam_phi = 90;
-               app_redisplay();
-       }
+       if(modkeys) {
+               /* navigation */
+               if(mouse_state[0]) {
+                       cam_theta += dx * 0.5f;
+                       cam_phi += dy * 0.5f;
+                       if(cam_phi < -90) cam_phi = -90;
+                       if(cam_phi > 90) cam_phi = 90;
+                       app_redisplay();
+               }
 
-       if(mouse_state[2]) {
-               cam_dist += dy * 0.1f;
-               if(cam_dist < 0) cam_dist = 0;
+               if(mouse_state[2]) {
+                       cam_dist += dy * 0.1f;
+                       if(cam_dist < 0) cam_dist = 0;
+                       app_redisplay();
+               }
+       } else {
+               if(mouse_state[0]) {
+                       if(rband.x != x || rband.y != y) {
+                               rband.width = x - rband.x;
+                               rband.height = y - rband.y;
+                               rband_valid = 1;
+                       }
+               }
                app_redisplay();
        }
 }
@@ -311,10 +392,17 @@ static void tbn_callback(rtk_widget *w, void *cls)
        case TBN_MOVE:
        case TBN_ROT:
        case TBN_SCALE:
-               tool = id - TBN_SEL;
+               idx = id - TBN_SEL;
+               if(0) {
+       case TBN_UNION:
+       case TBN_ISECT:
+       case TBN_DIFF:
+                       idx = id - TBN_UNION + TOOL_UNION;
+               }
+               cur_tool = idx;
                for(i=0; i<NUM_TOOLS; i++) {
-                       if(i != tool) {
-                               rtk_set_value(tbn_buttons[i + TBN_SEL], 0);
+                       if(i != cur_tool) {
+                               rtk_set_value(tools[i], 0);
                        }
                }
                break;
@@ -325,7 +413,74 @@ static void tbn_callback(rtk_widget *w, void *cls)
                selobj = idx;
                break;
 
+       case TBN_RM:
+               if(selobj >= 0) {
+                       scn_rm_object(scn, selobj);
+                       selobj = -1;
+                       app_redisplay();
+               }
+               break;
+
        default:
                break;
        }
 }
+
+static void draw_rband(void)
+{
+       int i, x, y, w, h;
+       uint32_t *fbptr, *bptr;
+
+       x = rband.x;
+       y = rband.y;
+
+       if(rband.width < 0) {
+               w = -rband.width;
+               x += rband.width;
+       } else {
+               w = rband.width;
+       }
+       if(rband.height < 0) {
+               h = -rband.height;
+               y += rband.height;
+       } else {
+               h = rband.height;
+       }
+
+       fbptr = framebuf + y * win_width + x;
+       bptr = fbptr + win_width * (h - 1);
+
+       for(i=0; i<w; i++) {
+               fbptr[i] ^= 0xffffff;
+               bptr[i] ^= 0xffffff;
+       }
+       fbptr += win_width;
+       for(i=0; i<h-2; i++) {
+               fbptr[0] ^= 0xffffff;
+               fbptr[w - 1] ^= 0xffffff;
+               fbptr += win_width;
+       }
+}
+
+static void primray(cgm_ray *ray, int x, int y)
+{
+       float nx, ny;
+       cgm_vec3 npos, farpt;
+       float inv_pv[16];
+
+       y = win_height - y;
+       nx = (float)(x - viewport[0]) / (float)viewport[2];
+       ny = (float)(y - viewport[1]) / (float)viewport[3];
+
+       cgm_mcopy(inv_pv, proj_matrix_inv);
+       cgm_mmul(inv_pv, view_matrix_inv);
+
+       cgm_vcons(&npos, nx, ny, 0.0f);
+       cgm_unproject(&ray->origin, &npos, inv_pv);
+       npos.z = 1.0f;
+       cgm_unproject(&farpt, &npos, inv_pv);
+
+       ray->dir.x = farpt.x - ray->origin.x;
+       ray->dir.y = farpt.y - ray->origin.y;
+       ray->dir.z = farpt.z - ray->origin.z;
+}