1 | // This file is a part of Framsticks SDK. http://www.framsticks.com/ |
---|
2 | // Copyright (C) 1999-2025 Maciej Komosinski and Szymon Ulatowski. |
---|
3 | // See LICENSE.txt for details. |
---|
4 | |
---|
5 | #include "f9_conv.h" |
---|
6 | #include <frams/model/model.h> |
---|
7 | #include <string.h> |
---|
8 | |
---|
9 | #define APPLY_DETERMINISTIC_BODY_NOISE //this genetic representation easily produces perfectly vertical sticks that would stay upright forever in simulation. In most cases such infinite perfection is not desired, so we make the construct less perfect by perturbing its coordinates. |
---|
10 | |
---|
11 | GenoConv_f90::GenoConv_f90() |
---|
12 | { |
---|
13 | name = "Turtle3D-ortho encoding"; |
---|
14 | in_format = '9'; |
---|
15 | out_format = '0'; |
---|
16 | mapsupport = 1; |
---|
17 | } |
---|
18 | |
---|
19 | |
---|
20 | const char* turtle_commands_f9 = "LRBFDU"; |
---|
21 | int turtle_commands_f9_count = 6; //keep in sync, must equal strlen(turtle_commands_f9) |
---|
22 | |
---|
23 | //const char* turtle_commandsX_f9="-+0000"; |
---|
24 | //const char* turtle_commandsY_f9="00-+00"; |
---|
25 | //const char* turtle_commandsZ_f9="0000-+"; |
---|
26 | |
---|
27 | SString GenoConv_f90::convert(SString &in, MultiMap *map, bool using_checkpoints) |
---|
28 | { |
---|
29 | vector<XYZ_LOC> vertices; |
---|
30 | XYZ_LOC current; |
---|
31 | Model m; |
---|
32 | m.open(using_checkpoints); |
---|
33 | int recently_added = addSegment(m, 0, vertices, current, 0xDead); |
---|
34 | for (int i = 0; i < in.length(); i++) |
---|
35 | { |
---|
36 | char command = in[i]; |
---|
37 | char *ptr = strchr((char*)turtle_commands_f9, command); |
---|
38 | if (ptr) |
---|
39 | { |
---|
40 | int delta[] = { 0, 0, 0 }; |
---|
41 | int pos = ptr - turtle_commands_f9; |
---|
42 | int axis = pos / 2; |
---|
43 | int dir = pos % 2; |
---|
44 | (*(delta + axis)) += dir * 2 - 1; //+1 or -1 in the given axis |
---|
45 | current.add(delta); |
---|
46 | recently_added = addSegment(m, i, vertices, current, recently_added); |
---|
47 | m.checkpoint(); |
---|
48 | } |
---|
49 | } |
---|
50 | #ifdef APPLY_DETERMINISTIC_BODY_NOISE |
---|
51 | perturbPartLocations(m); |
---|
52 | #endif |
---|
53 | setColors(m, recently_added); |
---|
54 | m.close(); |
---|
55 | if (m.getPartCount() < 2) //only one part <=> there were no valid turtle commands in the input genotype |
---|
56 | return ""; //so we return an invalid f0 genotype |
---|
57 | if (map != NULL) |
---|
58 | m.getCurrentToF0Map(*map); |
---|
59 | return m.getF0Geno().getGenes(); |
---|
60 | } |
---|
61 | |
---|
62 | int GenoConv_f90::addSegment(Model &m, int genenr, vector<XYZ_LOC> &vertices, const XYZ_LOC &new_vertex, int recently_added) |
---|
63 | { |
---|
64 | if (vertices.size() < 1) //empty model? |
---|
65 | { |
---|
66 | return addNewVertex(m, vertices, new_vertex); |
---|
67 | } |
---|
68 | else |
---|
69 | { |
---|
70 | int vertex_here = findVertexAt(vertices, new_vertex); |
---|
71 | if (vertex_here < 0) //need to create a new Part |
---|
72 | { |
---|
73 | vertex_here = addNewVertex(m, vertices, new_vertex); |
---|
74 | } //else there already exists a Part in new_vertex; new Joint may or may not be needed |
---|
75 | Part *p1 = m.getPart(recently_added); |
---|
76 | Part *p2 = m.getPart(vertex_here); |
---|
77 | p1->addMapping(MultiRange(genenr, genenr)); |
---|
78 | p2->addMapping(MultiRange(genenr, genenr)); |
---|
79 | |
---|
80 | int j12 = m.findJoint(p1, p2); |
---|
81 | int j21 = m.findJoint(p2, p1); |
---|
82 | if (j12 >= 0) |
---|
83 | m.getJoint(j12)->addMapping(MultiRange(genenr, genenr)); |
---|
84 | else if (j21 >= 0) |
---|
85 | m.getJoint(j21)->addMapping(MultiRange(genenr, genenr)); |
---|
86 | else //both j12<0 and j21<0. New Joint needed. Should always happen if we just created a new Part (vertex_here was <0) |
---|
87 | m.addNewJoint(p1, p2)->addMapping(MultiRange(genenr, genenr)); |
---|
88 | return vertex_here; |
---|
89 | } |
---|
90 | } |
---|
91 | |
---|
92 | int GenoConv_f90::findVertexAt(vector<XYZ_LOC> &vertices, const XYZ_LOC &vertex) |
---|
93 | { |
---|
94 | for (int i = 0; i < (int)vertices.size(); i++) |
---|
95 | if (vertices[i].same_coordinates(vertex)) return i; |
---|
96 | return -1; |
---|
97 | } |
---|
98 | |
---|
99 | |
---|
100 | int GenoConv_f90::addNewVertex(Model &m, vector<XYZ_LOC> &vertices, const XYZ_LOC &new_vertex) |
---|
101 | { |
---|
102 | Part *p = new Part; |
---|
103 | p->p.x = new_vertex.x; |
---|
104 | p->p.y = new_vertex.y; |
---|
105 | p->p.z = new_vertex.z; |
---|
106 | m.addPart(p); |
---|
107 | |
---|
108 | vertices.push_back(new_vertex); |
---|
109 | return int(vertices.size()) - 1; |
---|
110 | } |
---|
111 | |
---|
112 | double mix(int *colortab, int maxind, double ind) |
---|
113 | { |
---|
114 | int indpre = (int)ind; |
---|
115 | int indpost = indpre + 1; |
---|
116 | if (indpost > maxind) indpost = maxind; |
---|
117 | int v1 = colortab[indpre]; |
---|
118 | int v2 = colortab[indpost]; |
---|
119 | double d1 = ind - indpre; |
---|
120 | double d2 = indpost - ind; |
---|
121 | double v = indpre == indpost ? v1 : d2 * v1 + d1 * v2; //d1+d2==1 |
---|
122 | return v; |
---|
123 | } |
---|
124 | |
---|
125 | void GenoConv_f90::setColors(Model &m, int last_added_part) //sets fixed (independent from genes) colors and widths on a model, purely for aesthetic purposes |
---|
126 | { |
---|
127 | static const bool OLD = false; //old "rainbow" hue gradient |
---|
128 | if (OLD) |
---|
129 | { |
---|
130 | //a rainbow on Joints: from the first one red, through middle green, to blue or violet - last |
---|
131 | static int r[] = { 1, 1, 0, 0, 0, 1 }; |
---|
132 | static int g[] = { 0, 1, 1, 1, 0, 0 }; |
---|
133 | static int b[] = { 0, 0, 0, 1, 1, 1 }; |
---|
134 | int maxind = int(std::size(r)) - 1; |
---|
135 | |
---|
136 | int joints_count = m.getJointCount(); |
---|
137 | for (int i = 0; i < joints_count; i++) |
---|
138 | { |
---|
139 | Joint *j = m.getJoint(i); |
---|
140 | double x = joints_count < 2 ? 0 : (double)i / (joints_count - 1); //0..1, position in the rainbow |
---|
141 | double ind = x * maxind; |
---|
142 | j->vcolor.x = mix(r, maxind, ind); |
---|
143 | j->vcolor.y = mix(g, maxind, ind); |
---|
144 | j->vcolor.z = mix(b, maxind, ind); |
---|
145 | } |
---|
146 | } |
---|
147 | else |
---|
148 | { |
---|
149 | int joints_count = m.getJointCount(); |
---|
150 | for (int i = 0; i < joints_count; i++) |
---|
151 | { |
---|
152 | Joint *j = m.getJoint(i); |
---|
153 | Pt3D d = j->part2->p - j->part1->p; //dx,dy,dz |
---|
154 | double ax = fabs(d.x), ay = fabs(d.y), az = fabs(d.z); |
---|
155 | // Pairs of colors should be easy to associate as "one family" at a glance, but different so that we use all main six parts of the spectrum. |
---|
156 | // Colors are slightly brightened to make them more pastel/plastic; extreme saturation pure red=1,0,0 or blue 0,0,1 do not look good. |
---|
157 | // Find the longest axis (i.e., recover the information from the genotype: was this joint created by LR, BF, or DU?) |
---|
158 | if ((ax > ay) && (ax > az)) // x |
---|
159 | { |
---|
160 | j->vcolor = d.x < 0 ? Pt3D(1, 0.2, 0.2) : Pt3D(1, 0.2, 0.6); // red, purple red |
---|
161 | } |
---|
162 | else |
---|
163 | if ((ay > ax) && (ay > az)) // y |
---|
164 | { |
---|
165 | j->vcolor = d.y < 0 ? Pt3D(0.2, 1, 0.2) : Pt3D(0.7, 1, 0.2); //green, yellowish green |
---|
166 | } |
---|
167 | else // z |
---|
168 | { |
---|
169 | j->vcolor = d.z < 0 ? Pt3D(0.2, 0.2, 1) : Pt3D(0.4, 0.9, 1); //blue, cyanish blue |
---|
170 | } |
---|
171 | } |
---|
172 | } |
---|
173 | |
---|
174 | int parts_count = m.getPartCount(); |
---|
175 | if (OLD) |
---|
176 | { |
---|
177 | SList jlist; |
---|
178 | for (int i = 0; i < parts_count; i++) |
---|
179 | { |
---|
180 | Part *p = m.getPart(i); |
---|
181 | jlist.clear(); |
---|
182 | int count = m.findJoints(jlist, p); |
---|
183 | Pt3D averagecolor(0, 0, 0); //Parts will get averaged colors from all attached Joints |
---|
184 | FOREACH(Joint*, j, jlist) |
---|
185 | averagecolor += j->vcolor; |
---|
186 | p->vcolor = averagecolor / count; |
---|
187 | } |
---|
188 | } |
---|
189 | else |
---|
190 | { |
---|
191 | //Parts will get gray colors from darker to brighter, according to their order of creation. |
---|
192 | for (int i = 0; i < parts_count; i++) |
---|
193 | { |
---|
194 | Part *p = m.getPart(i); |
---|
195 | p->vcolor.x = p->vcolor.y = p->vcolor.z = 0.3 + 0.4 * i / (parts_count - 1); //0.3..0.7, so first (black) and last (white) stand out more |
---|
196 | } |
---|
197 | } |
---|
198 | //The first Part will be black, the last Part will be white - a visual aid for easier matching of the genotype and the corresponding phenotype. |
---|
199 | if (!OLD) |
---|
200 | m.getPart(0)->vcolor = Pt3D(0, 0, 0); //mark first Part black - not attractive in OLD sweet and positive color scheme |
---|
201 | m.getPart(last_added_part)->vcolor = Pt3D(1, 1, 1); //mark last Part white |
---|
202 | } |
---|
203 | |
---|
204 | void GenoConv_f90::perturbPartLocations(Model &m) //deterministic "body noise", see APPLY_DETERMINISTIC_BODY_NOISE |
---|
205 | { |
---|
206 | for (int i = 0; i < m.getPartCount(); i++) |
---|
207 | { |
---|
208 | Part *p = m.getPart(i); |
---|
209 | Pt3D noise( |
---|
210 | ((i + 1) % 10) - 4.5, |
---|
211 | ((3 * i + 5) % 10) - 4.5, |
---|
212 | ((7 * i + 2) % 10) - 4.5 |
---|
213 | ); //-4.5 .. 4.5 in each axis |
---|
214 | p->p += noise / 1000; |
---|
215 | } |
---|
216 | } |
---|