Code

AXONOMETRIC (3D) GRID! OK, still alot of work to be done. Snapping, diagonal guides...
[inkscape.git] / src / display / canvas-axonomgrid.cpp
1 #define SP_CANVAS_AXONOMGRID_C\r
2 \r
3 /*\r
4  * SPCAxonomGrid\r
5  *\r
6  * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>\r
7  * Copyright (C) 2000 Lauris Kaplinski\r
8  *\r
9  */                         \r
10  \r
11  /* \r
12   * Current limits are: one axis (y-axis) is always vertical. The other two\r
13   * axes are bound to a certain range of angles. The z-axis always has an angle \r
14   * smaller than 90 degrees (measured from horizontal, 0 degrees being a line extending\r
15   * to the right). The x-axis will always have an angle between 0 and 90 degrees.\r
16   * When I quickly think about it: all possibilities are probably covered this way. Eg.\r
17   * a z-axis with negative angle can be replaced with an x-axis, etc.\r
18   */              \r
19   \r
20  /*\r
21   *  TODO:  LOTS LOTS LOTS. Optimization etc.\r
22   *\r
23   */\r
24 \r
25 #include "sp-canvas-util.h"\r
26 #include "canvas-axonomgrid.h"\r
27 #include "display-forward.h"\r
28 #include <libnr/nr-pixops.h>\r
29 \r
30 #define SAFE_SETPIXEL   //undefine this when it is certain that setpixel is never called with invalid params\r
31 \r
32 enum {\r
33     ARG_0,\r
34     ARG_ORIGINX,\r
35     ARG_ORIGINY,\r
36     ARG_ANGLEX,\r
37     ARG_SPACINGY,\r
38     ARG_ANGLEZ,\r
39     ARG_COLOR,\r
40     ARG_EMPCOLOR,\r
41     ARG_EMPSPACING\r
42 };\r
43 \r
44 enum Dim3 { X=0, Y, Z };\r
45 \r
46 #ifndef M_PI\r
47 #define M_PI 3.14159265358979323846\r
48 #endif\r
49 \r
50 static double deg_to_rad(double deg) { return deg*M_PI/180.0;}\r
51 \r
52 \r
53 static void sp_caxonomgrid_class_init (SPCAxonomGridClass *klass);\r
54 static void sp_caxonomgrid_init (SPCAxonomGrid *grid);\r
55 static void sp_caxonomgrid_destroy (GtkObject *object);\r
56 static void sp_caxonomgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);\r
57 \r
58 static void sp_caxonomgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);\r
59 static void sp_caxonomgrid_render (SPCanvasItem *item, SPCanvasBuf *buf);\r
60 \r
61 static SPCanvasItemClass * parent_class;\r
62 \r
63 GtkType\r
64 sp_caxonomgrid_get_type (void)\r
65 {\r
66     static GtkType caxonomgrid_type = 0;\r
67 \r
68     if (!caxonomgrid_type) {\r
69         GtkTypeInfo caxonomgrid_info = {\r
70             "SPCAxonomGrid",\r
71             sizeof (SPCAxonomGrid),\r
72             sizeof (SPCAxonomGridClass),\r
73             (GtkClassInitFunc) sp_caxonomgrid_class_init,\r
74             (GtkObjectInitFunc) sp_caxonomgrid_init,\r
75             NULL, NULL,\r
76             (GtkClassInitFunc) NULL\r
77         };\r
78         caxonomgrid_type = gtk_type_unique (sp_canvas_item_get_type (), &caxonomgrid_info);\r
79     }\r
80     return caxonomgrid_type;\r
81 }\r
82 \r
83 static void\r
84 sp_caxonomgrid_class_init (SPCAxonomGridClass *klass)\r
85 {\r
86 \r
87     GtkObjectClass *object_class;\r
88     SPCanvasItemClass *item_class;\r
89 \r
90     object_class = (GtkObjectClass *) klass;\r
91     item_class = (SPCanvasItemClass *) klass;\r
92 \r
93     parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());\r
94 \r
95     gtk_object_add_arg_type ("SPCAxonomGrid::originx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINX);\r
96     gtk_object_add_arg_type ("SPCAxonomGrid::originy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINY);\r
97     gtk_object_add_arg_type ("SPCAxonomGrid::anglex", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ANGLEX);\r
98     gtk_object_add_arg_type ("SPCAxonomGrid::spacingy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGY);\r
99     gtk_object_add_arg_type ("SPCAxonomGrid::anglez", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ANGLEZ);\r
100     gtk_object_add_arg_type ("SPCAxonomGrid::color", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_COLOR);\r
101     gtk_object_add_arg_type ("SPCAxonomGrid::empcolor", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPCOLOR);\r
102     gtk_object_add_arg_type ("SPCAxonomGrid::empspacing", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPSPACING);\r
103 \r
104     object_class->destroy = sp_caxonomgrid_destroy;\r
105     object_class->set_arg = sp_caxonomgrid_set_arg;\r
106 \r
107     item_class->update = sp_caxonomgrid_update;\r
108     item_class->render = sp_caxonomgrid_render;\r
109   \r
110 }\r
111 \r
112 static void\r
113 sp_caxonomgrid_init (SPCAxonomGrid *grid)\r
114 {\r
115     grid->origin[NR::X] = grid->origin[NR::Y] = 0.0;\r
116 //    grid->spacing[X] = grid->spacing[Y] = grid->spacing[Z] = 8.0;\r
117     grid->color = 0x0000ff7f;\r
118     grid->empcolor = 0x3F3FFF40;\r
119     grid->empspacing = 5;\r
120 }\r
121 \r
122 static void\r
123 sp_caxonomgrid_destroy (GtkObject *object)\r
124 {\r
125     g_return_if_fail (object != NULL);\r
126     g_return_if_fail (SP_IS_CAXONOMGRID (object));\r
127 \r
128     if (GTK_OBJECT_CLASS (parent_class)->destroy)\r
129         (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);\r
130 }\r
131 \r
132 static void\r
133 sp_caxonomgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)\r
134 {\r
135     SPCanvasItem *item = SP_CANVAS_ITEM (object);\r
136     SPCAxonomGrid *grid = SP_CAXONOMGRID (object);\r
137     \r
138     g_message("arg");\r
139     \r
140     switch (arg_id) {\r
141     case ARG_ORIGINX:\r
142         grid->origin[NR::X] = GTK_VALUE_DOUBLE (* arg);\r
143         sp_canvas_item_request_update (item);\r
144         break;\r
145     case ARG_ORIGINY:\r
146         grid->origin[NR::Y] = GTK_VALUE_DOUBLE (* arg);\r
147         sp_canvas_item_request_update (item);\r
148         break;\r
149     case ARG_ANGLEX:\r
150         grid->angle_deg[X] = GTK_VALUE_DOUBLE (* arg);\r
151         if (grid->angle_deg[X] < 0.0) grid->angle_deg[X] = 0.0;\r
152         grid->angle_rad[X] = deg_to_rad(grid->angle_deg[X]);\r
153         grid->tan_angle[X] = tan(grid->angle_rad[X]);\r
154         sp_canvas_item_request_update (item);\r
155         break;\r
156     case ARG_SPACINGY:\r
157         grid->lengthy = GTK_VALUE_DOUBLE (* arg);\r
158         if (grid->lengthy < 0.01) grid->lengthy = 0.01;\r
159         sp_canvas_item_request_update (item);\r
160         break;\r
161     case ARG_ANGLEZ:\r
162         grid->angle_deg[Z] = GTK_VALUE_DOUBLE (* arg);\r
163         if (grid->angle_deg[Z] < 0.0) grid->angle_deg[Z] = 0.0;\r
164         grid->angle_rad[Z] = deg_to_rad(grid->angle_deg[Z]);\r
165         grid->tan_angle[Z] = tan(grid->angle_rad[Z]);\r
166         sp_canvas_item_request_update (item);\r
167         break;\r
168     case ARG_COLOR:\r
169         grid->color = GTK_VALUE_INT (* arg);\r
170         sp_canvas_item_request_update (item);\r
171         break;\r
172     case ARG_EMPCOLOR:\r
173         grid->empcolor = GTK_VALUE_INT (* arg);\r
174         sp_canvas_item_request_update (item);\r
175         break;\r
176     case ARG_EMPSPACING:\r
177         grid->empspacing = GTK_VALUE_INT (* arg);\r
178         // std::cout << "Emphasis Spacing: " << grid->empspacing << std::endl;\r
179         sp_canvas_item_request_update (item);\r
180         break;\r
181     default:\r
182         break;\r
183     }\r
184 }\r
185 \r
186 \r
187 \r
188 /**\r
189     \brief  This function renders a pixel on a particular buffer.\r
190                 \r
191     The topleft of the buffer equals\r
192                         ( rect.x0 , rect.y0 )  in screen coordinates\r
193                         ( 0 , 0 )  in setpixel coordinates\r
194     The bottomright of the buffer equals\r
195                         ( rect.x1 , rect,y1 )  in screen coordinates\r
196                         ( rect.x1 - rect.x0 , rect.y1 - rect.y0 )  in setpixel coordinates\r
197 */\r
198 static void \r
199 sp_caxonomgrid_setpixel (SPCanvasBuf *buf, gint x, gint y, guint32 rgba) {\r
200 #ifdef SAFE_SETPIXEL\r
201     if ( (x >= buf->rect.x0) && (x < buf->rect.x1) && (y >= buf->rect.y0) && (y < buf->rect.y1) ) {\r
202 #endif        \r
203         guint r, g, b, a;          \r
204         r = NR_RGBA32_R (rgba);\r
205         g = NR_RGBA32_G (rgba);\r
206         b = NR_RGBA32_B (rgba);\r
207         a = NR_RGBA32_A (rgba);  \r
208         guchar * p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;\r
209         p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);\r
210         p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);\r
211         p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);\r
212 #ifdef SAFE_SETPIXEL\r
213     }\r
214 #endif    \r
215 }\r
216 \r
217 /**\r
218     \brief  This function renders a line on a particular canvas buffer,\r
219             using Bresenham's line drawing function.\r
220             http://www.cs.unc.edu/~mcmillan/comp136/Lecture6/Lines.html \r
221             Coordinates are interpreted as SCREENcoordinates\r
222 */\r
223 static void \r
224 sp_caxonomgrid_drawline (SPCanvasBuf *buf, gint x0, gint y0, gint x1, gint y1, guint32 rgba) {\r
225     int dy = y1 - y0;\r
226     int dx = x1 - x0;\r
227     int stepx, stepy;\r
228 \r
229     if (dy < 0) { dy = -dy;  stepy = -1; } else { stepy = 1; }\r
230     if (dx < 0) { dx = -dx;  stepx = -1; } else { stepx = 1; }\r
231     dy <<= 1;                                                  // dy is now 2*dy\r
232     dx <<= 1;                                                  // dx is now 2*dx\r
233 \r
234     sp_caxonomgrid_setpixel(buf, x0, y0, rgba);\r
235     if (dx > dy) {\r
236         int fraction = dy - (dx >> 1);                         // same as 2*dy - dx\r
237         while (x0 != x1) {\r
238             if (fraction >= 0) {\r
239                 y0 += stepy;\r
240                 fraction -= dx;                                // same as fraction -= 2*dx\r
241             }\r
242             x0 += stepx;\r
243             fraction += dy;                                    // same as fraction -= 2*dy\r
244             sp_caxonomgrid_setpixel(buf, x0, y0, rgba);\r
245         }\r
246     } else {\r
247         int fraction = dx - (dy >> 1);\r
248         while (y0 != y1) {\r
249             if (fraction >= 0) {\r
250                 x0 += stepx;\r
251                 fraction -= dy;\r
252             }\r
253             y0 += stepy;\r
254             fraction += dx;\r
255             sp_caxonomgrid_setpixel(buf, x0, y0, rgba);\r
256         }\r
257     }\r
258     \r
259 }\r
260 \r
261 static void\r
262 sp_grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba)\r
263 {\r
264     if ((x >= buf->rect.x0) && (x < buf->rect.x1)) {\r
265         guint r, g, b, a;\r
266         gint y0, y1, y;\r
267         guchar *p;\r
268         r = NR_RGBA32_R(rgba);\r
269         g = NR_RGBA32_G (rgba);\r
270         b = NR_RGBA32_B (rgba);\r
271         a = NR_RGBA32_A (rgba);\r
272         y0 = MAX (buf->rect.y0, ys);\r
273         y1 = MIN (buf->rect.y1, ye + 1);\r
274         p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;\r
275         for (y = y0; y < y1; y++) {\r
276             p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);\r
277             p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);\r
278             p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);\r
279             p += buf->buf_rowstride;\r
280         }\r
281     }\r
282 }\r
283 \r
284 /**\r
285     \brief  This function renders the grid on a particular canvas buffer\r
286     \param  item  The grid to render on the buffer\r
287     \param  buf   The buffer to render the grid on\r
288     \r
289     This function gets called a touch more than you might believe,\r
290     about once per tile.  This means that it could probably be optimized\r
291     and help things out.\r
292 \r
293     Basically this function has to determine where in the canvas it is,\r
294     and how that associates with the grid.  It does this first by looking\r
295     at the bounding box of the buffer, and then calculates where the grid\r
296     starts in that buffer.  It will then step through grid lines until\r
297     it is outside of the buffer.\r
298 \r
299     For each grid line it is drawn using the function \c sp_grid_hline\r
300     or \c sp_grid_vline.  These are convience functions for the sake\r
301     of making the function easier to read.\r
302 \r
303     Also, there are emphasized lines on the grid.  While the \c syg and\r
304     \c sxg variable track grid positioning, the \c xlinestart and \c\r
305     ylinestart variables track the 'count' of what lines they are.  If\r
306     that count is a multiple of the line seperation between emphasis\r
307     lines, then that line is drawn in the emphasis color.\r
308 */\r
309 static void\r
310 sp_caxonomgrid_render (SPCanvasItem * item, SPCanvasBuf * buf)\r
311 {\r
312     SPCAxonomGrid *grid = SP_CAXONOMGRID (item);\r
313 \r
314     sp_canvas_prepare_buffer (buf);\r
315               \r
316      // gc = gridcoordinates (the coordinates calculated from the grids origin 'grid->ow'.\r
317      // sc = screencoordinates ( for example "buf->rect.x0" is in screencoordinates )\r
318      // bc = buffer patch coordinates \r
319      \r
320      // tl = topleft ; br = bottomright\r
321     NR::Point buf_tl_gc;\r
322     NR::Point buf_br_gc;\r
323     buf_tl_gc[NR::X] = buf->rect.x0 - grid->ow[NR::X];\r
324     buf_tl_gc[NR::Y] = buf->rect.y0 - grid->ow[NR::Y];\r
325     buf_br_gc[NR::X] = buf->rect.x1 - grid->ow[NR::X];\r
326     buf_br_gc[NR::Y] = buf->rect.y1 - grid->ow[NR::Y];\r
327 \r
328 \r
329     gdouble x;\r
330     gdouble y;\r
331 \r
332     // render the three separate line groups representing the main-axes:\r
333     // x-axis always goes from topleft to bottomright. (0,0) - (1,1)  \r
334     const gdouble xintercept_y_bc = (buf_tl_gc[NR::X] * grid->tan_angle[X]) - buf_tl_gc[NR::Y] ;\r
335     const gdouble xstart_y_sc = ( xintercept_y_bc - floor(xintercept_y_bc/grid->lyw)*grid->lyw ) + buf->rect.y0;\r
336     const gint  xlinestart = (gint) Inkscape::round( (xstart_y_sc - grid->ow[NR::Y]) / grid->lyw );\r
337     gint xlinenum;\r
338     // lijnen vanaf linker zijkant.\r
339     for (y = xstart_y_sc, xlinenum = xlinestart; y < buf->rect.y1; y += grid->lyw, xlinenum++) {\r
340         const gint x0 = buf->rect.x0;\r
341         const gint y0 = (gint) Inkscape::round(y);\r
342         const gint x1 = x0 + (gint) Inkscape::round( (buf->rect.y1 - y) / grid->tan_angle[X] );\r
343         const gint y1 = buf->rect.y1;\r
344             \r
345         if (!grid->scaled && (xlinenum % grid->empspacing) == 0) {\r
346             sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->empcolor);\r
347         } else {\r
348             sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->color);\r
349         }\r
350     }\r
351     // lijnen vanaf bovenkant.\r
352     const gdouble xstart_x_sc = buf->rect.x0 + (grid->lxw_x - (xstart_y_sc - buf->rect.y0) / grid->tan_angle[X]) ;\r
353     for (x = xstart_x_sc, xlinenum = xlinestart; x < buf->rect.x1; x += grid->lxw_x, xlinenum--) {\r
354         const gint y0 = buf->rect.y0;\r
355         const gint y1 = buf->rect.y1;\r
356         const gint x0 = (gint) Inkscape::round(x);\r
357         const gint x1 = x0 + (gint) Inkscape::round( (y1 - y0) / grid->tan_angle[X] );\r
358             \r
359         if (!grid->scaled && (xlinenum % grid->empspacing) == 0) {\r
360             sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->empcolor);\r
361         } else {\r
362             sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->color);\r
363         }\r
364     }\r
365     \r
366 \r
367     // y-axis lines (vertical)\r
368     const gdouble ystart_x_sc = floor (buf_tl_gc[NR::X] / grid->spacing_ylines) * grid->spacing_ylines + grid->ow[NR::X];\r
369     const gint  ylinestart = (gint) Inkscape::round((ystart_x_sc - grid->ow[NR::X]) / grid->spacing_ylines);\r
370     gint ylinenum;\r
371     for (x = ystart_x_sc, ylinenum = ylinestart; x < buf->rect.x1; x += grid->spacing_ylines, ylinenum++) {\r
372         const gint x0 = (gint) Inkscape::round(x);\r
373 \r
374         if (!grid->scaled && (ylinenum % grid->empspacing) == 0) {\r
375             sp_grid_vline (buf, x0, buf->rect.y0, buf->rect.y1 - 1, grid->empcolor);\r
376         } else {\r
377             sp_grid_vline (buf, x0, buf->rect.y0, buf->rect.y1 - 1, grid->color);\r
378         }\r
379     }\r
380 \r
381     // z-axis always goes from bottomleft to topright. (0,1) - (1,0)  \r
382     const gdouble zintercept_y_bc = (buf_tl_gc[NR::X] * -grid->tan_angle[Z]) - buf_tl_gc[NR::Y] ;\r
383     const gdouble zstart_y_sc = ( zintercept_y_bc - floor(zintercept_y_bc/grid->lyw)*grid->lyw ) + buf->rect.y0;\r
384     const gint  zlinestart = (gint) Inkscape::round( (zstart_y_sc - grid->ow[NR::Y]) / grid->lyw );\r
385     gint zlinenum;\r
386     // lijnen vanaf linker zijkant.\r
387     for (y = zstart_y_sc, zlinenum = zlinestart; y < buf->rect.y1; y += grid->lyw, zlinenum++) {\r
388         const gint x0 = buf->rect.x0;\r
389         const gint y0 = (gint) Inkscape::round(y);\r
390         const gint x1 = x0 + (gint) Inkscape::round( (y - buf->rect.y0 ) / grid->tan_angle[Z] );\r
391         const gint y1 = buf->rect.y0;\r
392             \r
393         if (!grid->scaled && (zlinenum % grid->empspacing) == 0) {\r
394             sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->empcolor);\r
395         } else {\r
396             sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->color);\r
397         }\r
398     }\r
399     // lijnen vanaf onderkant.\r
400     const gdouble zstart_x_sc = buf->rect.x0 + (y - buf->rect.y1) / grid->tan_angle[Z] ;\r
401     for (x = zstart_x_sc; x < buf->rect.x1; x += grid->lxw_z, zlinenum--) {\r
402         const gint y0 = buf->rect.y1;\r
403         const gint y1 = buf->rect.y0;\r
404         const gint x0 = (gint) Inkscape::round(x);\r
405         const gint x1 = x0 + (gint) Inkscape::round( (buf->rect.y1 - buf->rect.y0) / grid->tan_angle[Z] );\r
406             \r
407         if (!grid->scaled && (zlinenum % grid->empspacing) == 0) {\r
408             sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->empcolor);\r
409         } else {\r
410             sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->color);\r
411         }\r
412     }\r
413     \r
414 }\r
415 \r
416 static void\r
417 sp_caxonomgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)\r
418 {\r
419     SPCAxonomGrid *grid = SP_CAXONOMGRID (item);\r
420 \r
421     if (parent_class->update)\r
422         (* parent_class->update) (item, affine, flags);\r
423 \r
424     grid->ow = grid->origin * affine;\r
425     grid->sw = NR::Point(fabs(affine[0]),fabs(affine[3]));\r
426     \r
427     for(int dim = 0; dim < 2; dim++) {\r
428         gint scaling_factor = grid->empspacing;\r
429 \r
430         if (scaling_factor <= 1)\r
431             scaling_factor = 5;\r
432 \r
433         grid->scaled = FALSE;\r
434         while (grid->sw[dim] < 8.0) {\r
435             grid->scaled = TRUE;\r
436             grid->sw[dim] *= scaling_factor;\r
437             // First pass, go up to the major line spacing, then\r
438             // keep increasing by two.\r
439             scaling_factor = 2;\r
440         }\r
441     }\r
442 \r
443     grid->spacing_ylines = grid->sw[NR::X] * grid->lengthy  /(grid->tan_angle[X] + grid->tan_angle[Z]);\r
444     grid->lyw            = grid->lengthy * grid->sw[NR::Y];\r
445     grid->lxw_x          = (grid->lengthy / grid->tan_angle[X]) * grid->sw[NR::X];\r
446     grid->lxw_z          = (grid->lengthy / grid->tan_angle[Z]) * grid->sw[NR::X];\r
447 \r
448     if (grid->empspacing == 0) {\r
449         grid->scaled = TRUE;\r
450     }\r
451 \r
452     sp_canvas_request_redraw (item->canvas,\r
453                      -1000000, -1000000,\r
454                      1000000, 1000000);\r
455                      \r
456     item->x1 = item->y1 = -1000000;\r
457     item->x2 = item->y2 = 1000000;\r
458 }\r
459 \r
460 /*\r
461   Local Variables:\r
462   mode:c++\r
463   c-file-style:"stroustrup"\r
464   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
465   indent-tabs-mode:nil\r
466   fill-column:99\r
467   End:\r
468 */\r
469 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :\r