]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/point_layer.c
Implement chop_point_layer
[nothing.git] / src / game / level / level_editor / point_layer.c
1 #include <stdio.h>
2
3 #include <SDL.h>
4
5 #include "dynarray.h"
6 #include "game/camera.h"
7 #include "system/line_stream.h"
8 #include "system/log.h"
9 #include "system/lt.h"
10 #include "system/nth_alloc.h"
11 #include "system/stacktrace.h"
12 #include "system/str.h"
13 #include "ui/edit_field.h"
14 #include "./point_layer.h"
15 #include "math/extrema.h"
16 #include "math/mat3x3.h"
17 #include "./color_picker.h"
18 #include "undo_history.h"
19
20 #define POINT_LAYER_ELEMENT_RADIUS 10.0f
21 #define POINT_LAYER_ID_TEXT_SIZE vec(2.0f, 2.0f)
22 #define POINT_LAYER_ID_TEXT_COLOR COLOR_BLACK
23
24 static int point_clipboard = 0;
25 static Color point_clipboard_color;
26
27 // TODO(#1140): PointLayer does not support snapping
28
29 typedef enum {
30     POINT_LAYER_IDLE = 0,
31     POINT_LAYER_EDIT_ID,
32     POINT_LAYER_MOVE,
33     POINT_LAYER_RECOLOR
34 } PointLayerState;
35
36 struct PointLayer
37 {
38     Lt *lt;
39     PointLayerState state;
40     Dynarray/*<Vec2f>*/ positions;
41     Dynarray/*<Color>*/ colors;
42     Dynarray/*<char[ID_MAX_SIZE]>*/ ids;
43     int selection;
44     ColorPicker color_picker;
45
46     Vec2f inter_position;
47     Color inter_color;
48     Edit_field *edit_field;
49
50     int id_name_counter;
51     const char *id_name_prefix;
52 };
53
54 typedef enum {
55     POINT_UNDO_ADD,
56     POINT_UNDO_DELETE,
57     POINT_UNDO_UPDATE,
58     POINT_UNDO_SWAP
59 } PointUndoType;
60
61 typedef struct {
62     PointUndoType type;
63     PointLayer *layer;
64     Vec2f position;
65     Color color;
66     char id[ID_MAX_SIZE];
67     size_t index;
68     size_t index2;
69 } PointUndoContext;
70
71 static
72 PointUndoContext create_point_undo_swap_context(PointLayer *point_layer,
73                                      size_t index, size_t index2)
74 {
75     trace_assert(point_layer);
76     trace_assert(index < point_layer->positions.count);
77     trace_assert(index2 < point_layer->positions.count);
78
79     PointUndoContext undo_context;
80     undo_context.type = POINT_UNDO_SWAP;
81     undo_context.layer = point_layer;
82     undo_context.index = index;
83     undo_context.index2 = index2;
84     return undo_context;
85 }
86
87 static
88 PointUndoContext create_point_undo_context(PointLayer *point_layer,
89                                 PointUndoType type)
90 {
91     trace_assert(type != POINT_UNDO_SWAP);
92
93     (void) create_point_undo_swap_context;
94
95     PointUndoContext undo_context;
96
97     size_t index =
98         type == POINT_UNDO_ADD
99         ? point_layer->positions.count - 1
100         : (size_t) point_layer->selection;
101
102     undo_context.type = type;
103     undo_context.layer = point_layer;
104     dynarray_copy_to(&point_layer->positions, &undo_context.position, index);
105     dynarray_copy_to(&point_layer->colors, &undo_context.color, index);
106     dynarray_copy_to(&point_layer->ids, &undo_context.id, index);
107     undo_context.index = index;
108
109     return undo_context;
110 }
111
112 static
113 void point_layer_undo(void *context, size_t context_size)
114 {
115     trace_assert(context);
116     trace_assert(sizeof(PointUndoContext) == context_size);
117
118     PointUndoContext *undo_context = context;
119     PointLayer *point_layer = undo_context->layer;
120
121     switch (undo_context->type) {
122     case POINT_UNDO_ADD: {
123         dynarray_pop(&point_layer->positions, NULL);
124         dynarray_pop(&point_layer->colors, NULL);
125         dynarray_pop(&point_layer->ids, NULL);
126         point_layer->selection = -1;
127     } break;
128
129     case POINT_UNDO_DELETE: {
130         dynarray_insert_before(&point_layer->positions, undo_context->index, &undo_context->position);
131         dynarray_insert_before(&point_layer->colors, undo_context->index, &undo_context->color);
132         dynarray_insert_before(&point_layer->ids, undo_context->index, &undo_context->id);
133         point_layer->selection = -1;
134     } break;
135
136     case POINT_UNDO_UPDATE: {
137         dynarray_replace_at(&point_layer->positions, undo_context->index, &undo_context->position);
138         dynarray_replace_at(&point_layer->colors, undo_context->index, &undo_context->color);
139         dynarray_replace_at(&point_layer->ids, undo_context->index, &undo_context->id);
140     } break;
141
142     case POINT_UNDO_SWAP: {
143         dynarray_swap(&point_layer->positions, undo_context->index, undo_context->index2);
144         dynarray_swap(&point_layer->colors, undo_context->index, undo_context->index2);
145         dynarray_swap(&point_layer->ids, undo_context->index, undo_context->index2);
146     } break;
147     }
148 }
149
150 #define POINT_UNDO_PUSH(HISTORY, CONTEXT)                                     \
151     do {                                                                \
152         PointUndoContext context = (CONTEXT);                                \
153         undo_history_push(                                              \
154             HISTORY,                                                    \
155             point_layer_undo,                                           \
156             &context,                                                   \
157             sizeof(context));                                           \
158     } while(0)
159
160 LayerPtr point_layer_as_layer(PointLayer *point_layer)
161 {
162     LayerPtr layer = {
163         .type = LAYER_POINT,
164         .ptr = point_layer
165     };
166     return layer;
167 }
168
169 PointLayer *create_point_layer(const char *id_name_prefix)
170 {
171     Lt *lt = create_lt();
172
173     PointLayer *point_layer = PUSH_LT(lt, nth_calloc(1, sizeof(PointLayer)), free);
174     if (point_layer == NULL) {
175         RETURN_LT(lt, NULL);
176     }
177     point_layer->lt = lt;
178
179     point_layer->state = POINT_LAYER_IDLE;
180
181     point_layer->positions = create_dynarray(sizeof(Vec2f));
182     point_layer->colors = create_dynarray(sizeof(Color));
183     point_layer->ids = create_dynarray(sizeof(char) * ID_MAX_SIZE);
184
185     point_layer->edit_field = PUSH_LT(
186         lt,
187         create_edit_field(
188             POINT_LAYER_ID_TEXT_SIZE,
189             POINT_LAYER_ID_TEXT_COLOR),
190         destroy_edit_field);
191     if (point_layer->edit_field == NULL) {
192         RETURN_LT(lt, NULL);
193     }
194
195     point_layer->id_name_prefix = id_name_prefix;
196
197     return point_layer;
198 }
199
200 PointLayer *create_point_layer_from_line_stream(LineStream *line_stream,
201                                                 const char *id_name_prefix)
202 {
203     trace_assert(line_stream);
204
205     PointLayer *point_layer = create_point_layer(id_name_prefix);
206
207     size_t count = 0;
208     if (sscanf(
209             line_stream_next(line_stream),
210             "%zu",
211             &count) == EOF) {
212         log_fail("Could not read amount of points");
213         RETURN_LT(point_layer->lt, NULL);
214     }
215
216     char color_name[7];
217     char id[ID_MAX_SIZE];
218     float x, y;
219     for (size_t i = 0; i < count; ++i) {
220         if (sscanf(
221                 line_stream_next(line_stream),
222                 "%"STRINGIFY(ID_MAX_SIZE)"s%f%f%6s",
223                 id, &x, &y, color_name) < 0) {
224             log_fail("Could not read %dth goal\n", i);
225             RETURN_LT(point_layer->lt, NULL);
226         }
227         const Color color = hexstr(color_name);
228         const Vec2f point = vec(x, y);
229
230         dynarray_push(&point_layer->colors, &color);
231         dynarray_push(&point_layer->positions, &point);
232         dynarray_push(&point_layer->ids, id);
233     }
234
235     point_layer->selection = -1;
236
237     point_layer->color_picker = create_color_picker_from_rgba(COLOR_RED);
238
239     return point_layer;
240 }
241
242 PointLayer *chop_point_layer(Memory *memory,
243                              String *input,
244                              const char *id_name_prefix)
245 {
246     trace_assert(memory);
247     trace_assert(input);
248     trace_assert(id_name_prefix);
249
250     PointLayer *point_layer = create_point_layer(id_name_prefix);
251
252     int n = atoi(string_to_cstr(memory, trim(chop_by_delim(input, '\n'))));
253     char id[ENTITY_MAX_ID_SIZE];
254     for (int i = 0; i < n; ++i) {
255         String line = trim(chop_by_delim(input, '\n'));
256         String string_id = trim(chop_word(&line));
257         Vec2f point;
258         point.x = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL);
259         point.y = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL);
260         Color color = hexs(trim(chop_word(&line)));
261
262         memset(id, 0, ENTITY_MAX_ID_SIZE);
263         memcpy(
264             id,
265             string_id.data,
266             min_size_t(ENTITY_MAX_ID_SIZE - 1, string_id.count));
267
268         dynarray_push(&point_layer->positions, &point);
269         dynarray_push(&point_layer->colors, &color);
270         dynarray_push(&point_layer->ids, id);
271     }
272
273     point_layer->selection = -1;
274     point_layer->color_picker = create_color_picker_from_rgba(COLOR_RED);
275
276     return point_layer;
277 }
278
279 void destroy_point_layer(PointLayer *point_layer)
280 {
281     trace_assert(point_layer);
282
283     free(point_layer->positions.data);
284     free(point_layer->colors.data);
285     free(point_layer->ids.data);
286
287     RETURN_LT0(point_layer->lt);
288 }
289
290 static inline
291 Triangle element_shape(Vec2f position, float scale)
292 {
293     return triangle_mat3x3_product(
294         equilateral_triangle(),
295         mat3x3_product(
296             trans_mat_vec(position),
297             scale_mat(scale)));
298 }
299
300 int point_layer_render(const PointLayer *point_layer,
301                        const Camera *camera,
302                        int active)
303 {
304     trace_assert(point_layer);
305     trace_assert(camera);
306
307     const int n = (int)point_layer->positions.count;
308     Vec2f *positions = (Vec2f *)point_layer->positions.data;
309     Color *colors = (Color *)point_layer->colors.data;
310     char *ids = (char *)point_layer->ids.data;
311
312     for (int i = 0; i < n; ++i) {
313         const Color color = color_scale(
314             point_layer->state == POINT_LAYER_RECOLOR && i == point_layer->selection
315             ? point_layer->inter_color
316             : colors[i],
317             rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f));
318
319         const Vec2f position =
320             point_layer->state == POINT_LAYER_MOVE && i == point_layer->selection
321             ? point_layer->inter_position
322             : positions[i];
323
324         // Selection Layer
325         if (active && i == point_layer->selection) {
326             if (camera_fill_triangle(
327                     camera,
328                     element_shape(
329                         position,
330                         POINT_LAYER_ELEMENT_RADIUS + 5.0f),
331                     color_invert(color)) < 0) {
332                 return -1;
333             }
334
335             if (point_layer->state != POINT_LAYER_EDIT_ID &&
336                 camera_render_text(
337                     camera,
338                     ids + ID_MAX_SIZE * i,
339                     POINT_LAYER_ID_TEXT_SIZE,
340                     POINT_LAYER_ID_TEXT_COLOR,
341                     position) < 0) {
342                 return -1;
343             }
344         }
345
346         if (camera_fill_triangle(
347                 camera,
348                 element_shape(
349                     position,
350                     POINT_LAYER_ELEMENT_RADIUS),
351                 color) < 0) {
352             return -1;
353         }
354     }
355
356     if (point_layer->state == POINT_LAYER_EDIT_ID) {
357         if (edit_field_render_world(
358                 point_layer->edit_field,
359                 camera,
360                 positions[point_layer->selection]) < 0) {
361             return -1;
362         }
363     }
364
365     if (active && color_picker_render(&point_layer->color_picker, camera) < 0) {
366         return -1;
367     }
368
369
370     return 0;
371 }
372
373 static
374 int point_layer_element_at(const PointLayer *point_layer,
375                            Vec2f position)
376 {
377     trace_assert(point_layer);
378
379     int n = (int) point_layer->positions.count;
380     Vec2f *positions = (Vec2f *)point_layer->positions.data;
381
382     for (int i = n - 1; i >= 0; --i) {
383         if (vec_length(vec_sub(positions[i], position)) < POINT_LAYER_ELEMENT_RADIUS) {
384             return i;
385         }
386     }
387
388     return -1;
389 }
390
391 static
392 int point_layer_add_element(PointLayer *point_layer,
393                             Vec2f position,
394                             Color color,
395                             UndoHistory *undo_history)
396 {
397     trace_assert(point_layer);
398     trace_assert(undo_history);
399
400     char id[ID_MAX_SIZE];
401     snprintf(id, ID_MAX_SIZE, "%s_%d",
402              point_layer->id_name_prefix,
403              point_layer->id_name_counter++);
404
405     dynarray_push(&point_layer->positions, &position);
406     dynarray_push(&point_layer->colors, &color);
407     dynarray_push(&point_layer->ids, id);
408
409     POINT_UNDO_PUSH(
410         undo_history,
411         create_point_undo_context(point_layer, POINT_UNDO_ADD));
412
413     return 0;
414 }
415
416 static
417 void point_layer_swap_elements(PointLayer *point_layer,
418                                size_t a, size_t b,
419                                UndoHistory *undo_history)
420 {
421     trace_assert(point_layer);
422     trace_assert(undo_history);
423     trace_assert(a < point_layer->positions.count);
424     trace_assert(b < point_layer->positions.count);
425
426     dynarray_swap(&point_layer->positions, a, b);
427     dynarray_swap(&point_layer->colors, a, b);
428     dynarray_swap(&point_layer->ids, a, b);
429
430     POINT_UNDO_PUSH(
431         undo_history,
432         create_point_undo_swap_context(point_layer, a, b));
433 }
434
435 static
436 void point_layer_delete_nth_element(PointLayer *point_layer,
437                                     size_t i,
438                                     UndoHistory *undo_history)
439 {
440     trace_assert(point_layer);
441
442     POINT_UNDO_PUSH(
443         undo_history,
444         create_point_undo_context(
445             point_layer,
446             POINT_UNDO_DELETE));
447
448     dynarray_delete_at(&point_layer->positions, i);
449     dynarray_delete_at(&point_layer->colors, i);
450     dynarray_delete_at(&point_layer->ids, i);
451 }
452
453 static
454 int point_layer_idle_event(PointLayer *point_layer,
455                            const SDL_Event *event,
456                            const Camera *camera,
457                            UndoHistory *undo_history)
458 {
459     trace_assert(point_layer);
460     trace_assert(event);
461     trace_assert(camera);
462
463     int selected = 0;
464     if (color_picker_event(
465             &point_layer->color_picker,
466             event,
467             camera,
468             &selected) < 0) {
469         return -1;
470     }
471
472     if (selected) {
473         if (point_layer->selection >= 0) {
474             point_layer->inter_color = color_picker_rgba(&point_layer->color_picker);
475             point_layer->state = POINT_LAYER_RECOLOR;
476         }
477         return 0;
478     }
479
480     switch (event->type) {
481     case SDL_MOUSEBUTTONDOWN: {
482         switch (event->button.button) {
483         case SDL_BUTTON_LEFT: {
484             const Vec2f position = camera_map_screen(camera, event->button.x, event->button.y);
485
486             point_layer->selection = point_layer_element_at(
487                 point_layer, position);
488
489             if (point_layer->selection < 0) {
490                 point_layer_add_element(
491                     point_layer,
492                     position,
493                     color_picker_rgba(&point_layer->color_picker),
494                     undo_history);
495             } else {
496                 Color *colors = (Color*)point_layer->colors.data;
497                 Vec2f *positions = (Vec2f*)point_layer->positions.data;
498
499                 point_layer->state = POINT_LAYER_MOVE;
500                 point_layer->color_picker =
501                     create_color_picker_from_rgba(colors[point_layer->selection]);
502                 point_layer->inter_position = positions[point_layer->selection];
503             }
504         } break;
505         }
506     } break;
507
508     case SDL_KEYDOWN: {
509         switch (event->key.keysym.sym) {
510         case SDLK_UP: {
511             if ((event->key.keysym.mod & KMOD_SHIFT)
512                 && (point_layer->selection >= 0)
513                 && ((size_t)(point_layer->selection + 1) < point_layer->positions.count)) {
514                 point_layer_swap_elements(
515                     point_layer,
516                     (size_t) point_layer->selection,
517                     (size_t) point_layer->selection + 1,
518                     undo_history);
519                 point_layer->selection++;
520             }
521         } break;
522
523         case SDLK_DOWN: {
524             if ((event->key.keysym.mod & KMOD_SHIFT)
525                 && (point_layer->selection > 0)
526                 && ((size_t) point_layer->selection < point_layer->positions.count)) {
527                 point_layer_swap_elements(
528                     point_layer,
529                     (size_t) point_layer->selection,
530                     (size_t) point_layer->selection - 1,
531                     undo_history);
532                 point_layer->selection--;
533             }
534         } break;
535
536         case SDLK_DELETE: {
537             if (0 <= point_layer->selection && point_layer->selection < (int) point_layer->positions.count) {
538                 point_layer_delete_nth_element(
539                     point_layer,
540                     (size_t)point_layer->selection,
541                     undo_history);
542                 point_layer->selection = -1;
543             }
544         } break;
545
546         case SDLK_F2: {
547             if (point_layer->selection >= 0) {
548                 char *ids = (char*)point_layer->ids.data;
549                 point_layer->state = POINT_LAYER_EDIT_ID;
550                 edit_field_replace(
551                     point_layer->edit_field,
552                     ids + ID_MAX_SIZE * point_layer->selection);
553                 SDL_StartTextInput();
554             }
555         } break;
556
557         case SDLK_c: {
558             if ((event->key.keysym.mod & KMOD_LCTRL) && point_layer->selection >= 0) {
559                 point_clipboard = 1;
560                 dynarray_copy_to(&point_layer->colors, &point_clipboard_color, (size_t)point_layer->selection);
561             }
562         } break;
563
564         case SDLK_v: {
565             if ((event->key.keysym.mod & KMOD_LCTRL) && point_clipboard) {
566                 int x, y;
567                 SDL_GetMouseState(&x, &y);
568                 Vec2f position = camera_map_screen(camera, x, y);
569
570                 point_layer_add_element(
571                     point_layer,
572                     position,
573                     point_clipboard_color,
574                     undo_history);
575             }
576         } break;
577         }
578     } break;
579     }
580
581     return 0;
582 }
583
584 static
585 int point_layer_edit_id_event(PointLayer *point_layer,
586                               const SDL_Event *event,
587                               const Camera *camera,
588                               UndoHistory *undo_history)
589 {
590     trace_assert(point_layer);
591     trace_assert(event);
592     trace_assert(camera);
593
594     switch (event->type) {
595     case SDL_KEYDOWN: {
596         switch(event->key.keysym.sym) {
597         case SDLK_RETURN: {
598             POINT_UNDO_PUSH(
599                 undo_history,
600                 create_point_undo_context(
601                     point_layer,
602                     POINT_UNDO_UPDATE));
603
604             char *id = dynarray_pointer_at(&point_layer->ids, (size_t) point_layer->selection);
605             const char *text = edit_field_as_text(point_layer->edit_field);
606             size_t n = min_size_t(strlen(text), ID_MAX_SIZE - 1);
607             memcpy(id, text, n);
608             memset(id + n, 0, ID_MAX_SIZE - n);
609
610             point_layer->state = POINT_LAYER_IDLE;
611             SDL_StopTextInput();
612             return 0;
613         } break;
614
615         case SDLK_ESCAPE: {
616             point_layer->state = POINT_LAYER_IDLE;
617             SDL_StopTextInput();
618             return 0;
619         } break;
620         }
621     } break;
622     }
623
624     return edit_field_event(point_layer->edit_field, event);
625 }
626
627 static
628 int point_layer_move_event(PointLayer *point_layer,
629                            const SDL_Event *event,
630                            const Camera *camera,
631                            UndoHistory *undo_history)
632 {
633     trace_assert(point_layer);
634     trace_assert(event);
635     trace_assert(camera);
636     trace_assert(point_layer->selection >= 0);
637
638     Vec2f *positions = (Vec2f*)point_layer->positions.data;
639
640     switch (event->type) {
641     case SDL_MOUSEBUTTONUP: {
642         switch (event->button.button) {
643         case SDL_BUTTON_LEFT: {
644             point_layer->state = POINT_LAYER_IDLE;
645
646             const float distance = vec_length(
647                 vec_sub(point_layer->inter_position,
648                         positions[point_layer->selection]));
649
650             if (distance > 1e-6) {
651                 POINT_UNDO_PUSH(
652                     undo_history,
653                     create_point_undo_context(
654                         point_layer,
655                         POINT_UNDO_UPDATE));
656
657                 dynarray_replace_at(
658                     &point_layer->positions,
659                     (size_t) point_layer->selection,
660                     &point_layer->inter_position);
661             }
662         } break;
663         }
664     } break;
665
666     case SDL_MOUSEMOTION: {
667         const Uint8 *state = SDL_GetKeyboardState(NULL);
668         const Vec2f mouse_pos = camera_map_screen(camera, event->motion.x, event->motion.y);
669         const Vec2f point_pos = positions[point_layer->selection];
670
671         if (!(state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL])) {
672             point_layer->inter_position = mouse_pos;
673         } else {
674             const float dx = fabsf(point_pos.x - mouse_pos.x);
675             const float dy = fabsf(point_pos.y - mouse_pos.y);
676
677             if (dx > dy) {
678                 point_layer->inter_position = vec(mouse_pos.x, point_pos.y);
679             } else {
680                 point_layer->inter_position = vec(point_pos.x, mouse_pos.y);
681             }
682         }
683     } break;
684     }
685
686     return 0;
687 }
688
689 static
690 int point_layer_recolor_event(PointLayer *point_layer,
691                               const SDL_Event *event,
692                               const Camera *camera,
693                               UndoHistory *undo_history)
694 {
695     trace_assert(point_layer);
696     trace_assert(event);
697     trace_assert(camera);
698     trace_assert(undo_history);
699     trace_assert(point_layer->selection >= 0);
700
701     int selected = 0;
702     if (color_picker_event(
703             &point_layer->color_picker,
704             event,
705             camera,
706             &selected) < 0) {
707         return -1;
708     }
709
710     if (selected) {
711         point_layer->inter_color = color_picker_rgba(&point_layer->color_picker);
712
713         if (!color_picker_drag(&point_layer->color_picker)) {
714             POINT_UNDO_PUSH(
715                 undo_history,
716                 create_point_undo_context(
717                     point_layer,
718                     POINT_UNDO_UPDATE));
719
720             dynarray_replace_at(
721                 &point_layer->colors,
722                 (size_t) point_layer->selection,
723                 &point_layer->inter_color);
724
725             point_layer->state = POINT_LAYER_IDLE;
726         }
727     }
728
729
730     return 0;
731 }
732
733 int point_layer_event(PointLayer *point_layer,
734                       const SDL_Event *event,
735                       const Camera *camera,
736                       UndoHistory *undo_history)
737 {
738     trace_assert(point_layer);
739     trace_assert(event);
740     trace_assert(camera);
741     trace_assert(undo_history);
742
743     switch (point_layer->state) {
744     case POINT_LAYER_IDLE:
745         return point_layer_idle_event(point_layer, event, camera, undo_history);
746
747     case POINT_LAYER_EDIT_ID:
748         return point_layer_edit_id_event(point_layer, event, camera, undo_history);
749
750     case POINT_LAYER_MOVE:
751         return point_layer_move_event(point_layer, event, camera, undo_history);
752
753     case POINT_LAYER_RECOLOR:
754         return point_layer_recolor_event(point_layer, event, camera, undo_history);
755     }
756
757     return 0;
758 }
759
760 size_t point_layer_count(const PointLayer *point_layer)
761 {
762     trace_assert(point_layer);
763     return point_layer->positions.count;
764 }
765
766 const Vec2f *point_layer_positions(const PointLayer *point_layer)
767 {
768     trace_assert(point_layer);
769     return (const Vec2f *)point_layer->positions.data;
770 }
771
772 const Color *point_layer_colors(const PointLayer *point_layer)
773 {
774     trace_assert(point_layer);
775     return (const Color *)point_layer->colors.data;
776 }
777
778 const char *point_layer_ids(const PointLayer *point_layer)
779 {
780     trace_assert(point_layer);
781     return (const char *)point_layer->ids.data;
782 }
783
784 int point_layer_dump_stream(const PointLayer *point_layer,
785                             FILE *filedump)
786 {
787     trace_assert(point_layer);
788     trace_assert(filedump);
789
790     size_t n = point_layer->ids.count;
791     char *ids = (char *) point_layer->ids.data;
792     Vec2f *positions = (Vec2f *) point_layer->positions.data;
793     Color *colors = (Color *) point_layer->colors.data;
794
795     fprintf(filedump, "%zd\n", n);
796     for (size_t i = 0; i < n; ++i) {
797         fprintf(filedump, "%s %f %f ",
798                 ids + ID_MAX_SIZE * i,
799                 positions[i].x, positions[i].y);
800         color_hex_to_stream(colors[i], filedump);
801         fprintf(filedump, "\n");
802     }
803
804     return 0;
805 }