diff --git a/apps/tools/twndel/tool.c b/apps/tools/twndel/tool.c
index 6c4df04..1d36ae1 100644
--- a/apps/tools/twndel/tool.c
+++ b/apps/tools/twndel/tool.c
@@ -222,6 +222,23 @@ static bool find_closest_point(uint8_t* object_result, uint16_t *point_result) {
 }
 
 
+/* o = vector origin */
+/* v = vector direction, normalized */
+/* p = any point on plane */
+/* n = normal of a plane */
+static bool vector_plane_intersection(Vec3 o, Vec3 v, Vec3 p, Vec3 n, Vec3 *out) {
+    float dot = vec3_dot(n, v);
+    if (dot > FLT_EPSILON) {
+        Vec3 w = vec3_sub(o, p);
+        float fac = -vec3_dot(n, w) / dot;
+        *out = vec3_add(o, vec3_scale(v, fac));
+        return true;
+    }
+    /* vector and plane are perpendicular, assume that it lies exactly on it */
+    return false;
+}
+
+
 static void process_operation_move_point(Operation *op) {
     /* finish dragging around */
     /* TODO: dont keep empty ops on stack? */
@@ -238,33 +255,46 @@ static void process_operation_move_point(Operation *op) {
                    (Color){255,255,255,255},
                    false);
 
-    DrawCameraUnprojectResult pos_and_ray = unproject_point(ctx.mouse_position);
-
+    DrawCameraUnprojectResult cam = unproject_point(ctx.mouse_position);
     Vec3 p = point_to_vec3(op->data.move_point.object, op->data.move_point.point);
-    Vec3 d = vec3_sub(pos_and_ray.position, p);
-    Vec3 b = vec3_cross(d, pos_and_ray.direction);
+    bool point_moved = false;
 
     /* TODO: show thresholds */
+    /* TODO: planes used should depend on looking angle */
+    /*       for example, editing from the side makes better use of x/z facing planes */
 
-    log_vec3(b, "cross");
+    Vec3 s;
+    if (!state.axis_mask[0]) {
+        if (vector_plane_intersection(cam.position, cam.direction, p, (Vec3){0,copysignf(1,cam.direction.y),0}, &s)) {
+            int16_t xch = (int16_t)(floorf(vec3_dot(vec3_sub(s, p), (Vec3){1,0,0}) * (float)POINTS_PER_METER));
+            xch -= xch % state.grid_snap_granularity;
+            state.points[op->data.move_point.point].x += xch;
+            op->data.move_point.delta_x += xch;
+            if (xch != 0) point_moved = true;
+        }
+    }
 
-    /* change along axes, delta is accumulated when threshold is met*/
-    int16_t xch = (int16_t)(floorf(vec3_dot(b, (Vec3){0,0,1}) * (float)POINTS_PER_METER)) * !state.axis_mask[0];
-    xch -= xch % state.grid_snap_granularity;
-    state.points[op->data.move_point.point].x += xch;
-    op->data.move_point.delta_x += xch;
+    if (!state.axis_mask[1]) {
+        if (vector_plane_intersection(cam.position, cam.direction, p, (Vec3){0,0,copysignf(1,cam.direction.z)}, &s)) {
+            int16_t ych = (int16_t)(floorf(vec3_dot(vec3_sub(s, p), (Vec3){0,1,0}) * (float)POINTS_PER_METER));
+            ych -= ych % state.grid_snap_granularity;
+            state.points[op->data.move_point.point].y += ych;
+            op->data.move_point.delta_y += ych;
+            if (ych != 0) point_moved = true;
+        }
+    }
 
-    int16_t ych = (int16_t)(floorf(vec3_dot(vec3_cross(b, pos_and_ray.direction), (Vec3){0,-1,0}) * (float)POINTS_PER_METER)) * !state.axis_mask[1];
-    ych -= ych % state.grid_snap_granularity;
-    state.points[op->data.move_point.point].y += ych;
-    op->data.move_point.delta_y += ych;
+    if (!state.axis_mask[2]) {
+        if (vector_plane_intersection(cam.position, cam.direction, p, (Vec3){0,copysignf(1,cam.direction.y),0}, &s)) {
+            int16_t zch = (int16_t)(floorf(vec3_dot(vec3_sub(s, p), (Vec3){0,0,1}) * (float)POINTS_PER_METER));
+            zch -= zch % state.grid_snap_granularity;
+            state.points[op->data.move_point.point].z += zch;
+            op->data.move_point.delta_z += zch;
+            if (zch != 0) point_moved = true;
+        }
+    }
 
-    int16_t zch = (int16_t)(floorf(vec3_dot(b, (Vec3){-1,0,0}) * (float)POINTS_PER_METER)) * !state.axis_mask[2];
-    zch -= zch % state.grid_snap_granularity;
-    state.points[op->data.move_point.point].z += zch;
-    op->data.move_point.delta_z += zch;
-
-    if (xch != 0 || ych != 0 || zch != 0)
+    if (point_moved)
         audio_play("/data/bong.ogg", NULL, false, 0.12f, 0.0f);
 }