1 | // This file is a part of Framsticks SDK. http://www.framsticks.com/ |
---|
2 | // Copyright (C) 1999-2018 Maciej Komosinski and Szymon Ulatowski. |
---|
3 | // See LICENSE.txt for details. |
---|
4 | |
---|
5 | #include <string> |
---|
6 | #include <limits> |
---|
7 | #include <algorithm> |
---|
8 | #include <frams/util/multirange.h> |
---|
9 | #include <utility> |
---|
10 | #include "fH_general.h" |
---|
11 | |
---|
12 | using namespace std; |
---|
13 | #undef max //this macro is not needed here and it clashes with numeric_limits<>::max() |
---|
14 | |
---|
15 | |
---|
16 | |
---|
17 | // Methods for loading handles |
---|
18 | |
---|
19 | const char *fH_part_names[FH_PART_PROPS_COUNT] = { "dn", "fr", "ing", "as" }; |
---|
20 | |
---|
21 | const char *fH_joint_names[FH_JOINT_PROPS_COUNT] = { "stif", "rotstif", "stam" }; |
---|
22 | |
---|
23 | void fH_Handle::loadProperties(Param par) |
---|
24 | { |
---|
25 | // loading values for vectors |
---|
26 | for (int i = 0; i < dimensions; i++) |
---|
27 | { |
---|
28 | first[i] = par.getDouble(i); |
---|
29 | second[i] = par.getDouble(dimensions + i); |
---|
30 | } |
---|
31 | obj = par.getSelected(); |
---|
32 | } |
---|
33 | |
---|
34 | void fH_Builder::addHandle(fH_Handle *handle) |
---|
35 | { |
---|
36 | switch (handle->type) |
---|
37 | { |
---|
38 | case fHBodyType::JOINT: |
---|
39 | sticks.push_back((fH_StickHandle*)handle); |
---|
40 | break; |
---|
41 | case fHBodyType::NEURON: |
---|
42 | neurons.push_back((fH_NeuronHandle*)handle); |
---|
43 | break; |
---|
44 | case fHBodyType::CONNECTION: |
---|
45 | connections.push_back((fH_ConnectionHandle*)handle); |
---|
46 | break; |
---|
47 | } |
---|
48 | } |
---|
49 | |
---|
50 | // Methods for saving properties of handles in params |
---|
51 | |
---|
52 | void fH_Handle::saveProperties(Param &par) |
---|
53 | { |
---|
54 | par.select(obj); |
---|
55 | for (int i = 0; i < dimensions; i++) |
---|
56 | { |
---|
57 | par.setDouble(i, first[i]); |
---|
58 | par.setDouble(dimensions + i, second[i]); |
---|
59 | } |
---|
60 | } |
---|
61 | |
---|
62 | // Destructor of Builder |
---|
63 | |
---|
64 | fH_Builder::~fH_Builder() |
---|
65 | { |
---|
66 | for (fH_StickHandle *obj : sticks) |
---|
67 | { |
---|
68 | delete obj; |
---|
69 | } |
---|
70 | sticks.clear(); |
---|
71 | for (fH_NeuronHandle *obj : neurons) |
---|
72 | { |
---|
73 | delete obj; |
---|
74 | } |
---|
75 | neurons.clear(); |
---|
76 | for (fH_ConnectionHandle *obj : connections) |
---|
77 | { |
---|
78 | delete obj; |
---|
79 | } |
---|
80 | connections.clear(); |
---|
81 | |
---|
82 | if (stickparamtab) ParamObject::freeParamTab(stickparamtab); |
---|
83 | if (neuronparamtab) ParamObject::freeParamTab(neuronparamtab); |
---|
84 | if (connectionparamtab) ParamObject::freeParamTab(connectionparamtab); |
---|
85 | |
---|
86 | } |
---|
87 | |
---|
88 | // Methods for parsing genotype |
---|
89 | |
---|
90 | void fH_Builder::prepareParams() |
---|
91 | { |
---|
92 | for (int i = 0; i < dimensions; i++) // preparing first vector fields |
---|
93 | { |
---|
94 | string x = "x"; |
---|
95 | x += to_string(i); |
---|
96 | stickmut.addProperty(NULL, x.c_str(), HANDLE_VECTOR_TYPE, x.c_str(), "", PARAM_CANOMITNAME, 0, -1); |
---|
97 | neuronmut.addProperty(NULL, x.c_str(), HANDLE_VECTOR_TYPE, x.c_str(), "", PARAM_CANOMITNAME, 0, -1); |
---|
98 | connectionmut.addProperty(NULL, x.c_str(), HANDLE_VECTOR_TYPE, x.c_str(), "", PARAM_CANOMITNAME, 0, -1); |
---|
99 | |
---|
100 | } |
---|
101 | for (int i = 0; i < dimensions; i++) // preparing second vector fields |
---|
102 | { |
---|
103 | string y = "y"; |
---|
104 | y += to_string(i); |
---|
105 | stickmut.addProperty(NULL, y.c_str(), HANDLE_VECTOR_TYPE, y.c_str(), "", PARAM_CANOMITNAME, 0, -1); |
---|
106 | neuronmut.addProperty(NULL, y.c_str(), HANDLE_VECTOR_TYPE, y.c_str(), "", PARAM_CANOMITNAME, 0, -1); |
---|
107 | connectionmut.addProperty(NULL, y.c_str(), HANDLE_VECTOR_TYPE, y.c_str(), "", PARAM_CANOMITNAME, 0, -1); |
---|
108 | |
---|
109 | } |
---|
110 | |
---|
111 | Part p; |
---|
112 | for (int i = 0; i < FH_PART_PROPS_COUNT; i++) |
---|
113 | { |
---|
114 | stickmut.addProperty(&p.properties().getParamTab()[p.properties().findId(fH_part_names[i]) + p.properties().getGroupCount()], -1); |
---|
115 | } |
---|
116 | |
---|
117 | Joint j; |
---|
118 | for (int i = 0; i < FH_JOINT_PROPS_COUNT; i++) |
---|
119 | { |
---|
120 | stickmut.addProperty(&j.properties().getParamTab()[j.properties().findId(fH_joint_names[i]) + j.properties().getGroupCount()], -1); |
---|
121 | } |
---|
122 | stickmut.addProperty(NULL, "l", STICKH_LENGTH_TYPE, "length", "", 0, 0, -1); |
---|
123 | |
---|
124 | Neuro n; |
---|
125 | neuronmut.addProperty(&n.properties().getParamTab()[n.properties().findId(FH_PE_NEURO_DET) + n.properties().getGroupCount()], -1); |
---|
126 | |
---|
127 | Param tmp(f0_neuroconn_paramtab, NULL); |
---|
128 | connectionmut.addProperty(&tmp.getParamTab()[tmp.findId(FH_PE_CONN_WEIGHT) + tmp.getGroupCount()], -1); |
---|
129 | |
---|
130 | stickparamtab = ParamObject::makeParamTab((ParamInterface *)&stickmut, 0, 0, stickmut.firstMutableIndex()); |
---|
131 | neuronparamtab = ParamObject::makeParamTab((ParamInterface *)&neuronmut, 0, 0, neuronmut.firstMutableIndex()); |
---|
132 | connectionparamtab = ParamObject::makeParamTab((ParamInterface *)&connectionmut, 0, 0, connectionmut.firstMutableIndex()); |
---|
133 | } |
---|
134 | |
---|
135 | int fH_Builder::processLine(SString line, int linenumber, int begin, int end) |
---|
136 | { |
---|
137 | // Firstly, method determines if line describes joint, neuron or neural connection |
---|
138 | // and prepares corresponding ParamTab |
---|
139 | fH_Handle *handle = NULL; |
---|
140 | ParamEntry *tab = NULL; |
---|
141 | if (line.startsWith("j:")) //joint |
---|
142 | { |
---|
143 | handle = new fH_StickHandle(dimensions, begin, end); |
---|
144 | tab = stickparamtab; |
---|
145 | } |
---|
146 | else if (line.startsWith("n:")) //neuron |
---|
147 | { |
---|
148 | handle = new fH_NeuronHandle(dimensions, begin, end); |
---|
149 | tab = neuronparamtab; |
---|
150 | } |
---|
151 | else if (line.startsWith("c:")) //connection |
---|
152 | { |
---|
153 | handle = new fH_ConnectionHandle(dimensions, begin, end); |
---|
154 | tab = connectionparamtab; |
---|
155 | } |
---|
156 | else // could not determine type of a handle |
---|
157 | { |
---|
158 | string message = "Cannot determine handle type at line: " + to_string(linenumber); |
---|
159 | logMessage("fH_Builder", "processLine", LOG_ERROR, message.c_str()); |
---|
160 | return begin; |
---|
161 | } |
---|
162 | line = line.substr(2); // skip of "j:", "c:" or "n:" |
---|
163 | |
---|
164 | // Secondly, ParamObject for holding handle properties is created |
---|
165 | void *obj = ParamObject::makeObject(tab); |
---|
166 | Param par(tab, obj); |
---|
167 | par.setDefault(); |
---|
168 | ParamInterface::LoadOptions opts; |
---|
169 | |
---|
170 | // After preparing Param objects, vector values and body properties are parsed |
---|
171 | par.load(ParamInterface::FormatSingleLine, line, &opts); |
---|
172 | |
---|
173 | // If parsing failed, method writes error message and ends processing |
---|
174 | if (opts.parse_failed) |
---|
175 | { |
---|
176 | string message = "Error in parsing handle parameters at line: " + to_string(linenumber); |
---|
177 | logMessage("fH_Builder", "processLine", LOG_ERROR, message.c_str()); |
---|
178 | delete handle; |
---|
179 | ParamObject::freeObject(obj); |
---|
180 | return begin; |
---|
181 | } |
---|
182 | |
---|
183 | // If parsing ended successfully, parsed properties are loaded into handle fields |
---|
184 | handle->loadProperties(par); |
---|
185 | |
---|
186 | // In the end, ready handle is stored in an appropriate vector |
---|
187 | addHandle(handle); |
---|
188 | return 0; |
---|
189 | } |
---|
190 | |
---|
191 | int fH_Builder::parseGenotype(const SString &genotype) |
---|
192 | { |
---|
193 | // Firstly, number of dimensions is parsed |
---|
194 | int pos = 0; |
---|
195 | SString numdimensions; |
---|
196 | genotype.getNextToken(pos, numdimensions, '\n'); |
---|
197 | if (!ExtValue::parseInt(numdimensions.c_str(), dimensions, true, false)) |
---|
198 | { |
---|
199 | logMessage("fH_Builder", "parseGenotype", LOG_ERROR, "Could not parse number of dimensions"); |
---|
200 | return 1; |
---|
201 | } |
---|
202 | if (dimensions < 1) |
---|
203 | { |
---|
204 | logMessage("fH_Builder", "parseGenotype", LOG_ERROR, "Number of dimensions cannot be lower than 1"); |
---|
205 | return 1; |
---|
206 | } |
---|
207 | SString line; |
---|
208 | int linenumber = 2; |
---|
209 | |
---|
210 | // With known number of dimensions ParamTabs for handles are prepared |
---|
211 | prepareParams(); |
---|
212 | |
---|
213 | // After preparing Builder for parsing, each line is processed with processLine |
---|
214 | int lastpos = pos; |
---|
215 | while (genotype.getNextToken(pos, line, '\n')) |
---|
216 | { |
---|
217 | if (line.len() > 0) |
---|
218 | { |
---|
219 | int res = processLine(line, linenumber, lastpos, pos - 1); |
---|
220 | if (res != 0) |
---|
221 | { |
---|
222 | return res; |
---|
223 | } |
---|
224 | } |
---|
225 | lastpos = pos; |
---|
226 | linenumber++; |
---|
227 | } |
---|
228 | if (sticks.size() == 0) |
---|
229 | { |
---|
230 | logMessage("fH_Builder", "parseGenotype", LOG_ERROR, "Genotype does not contain any stick"); |
---|
231 | return 1; |
---|
232 | } |
---|
233 | return 0; |
---|
234 | } |
---|
235 | |
---|
236 | // Distance calculations |
---|
237 | |
---|
238 | double fH_Handle::dist(vector<double> left, vector<double> right) |
---|
239 | { |
---|
240 | double sum = 0; |
---|
241 | for (unsigned int i = 0; i < left.size(); i++) |
---|
242 | { |
---|
243 | sum += (left[i] - right[i]) * (left[i] - right[i]); |
---|
244 | } |
---|
245 | return sqrt(sum); |
---|
246 | } |
---|
247 | |
---|
248 | vector<double> fH_Handle::getVectorsAverage() |
---|
249 | { |
---|
250 | vector<double> result(dimensions, 0); |
---|
251 | for (int i = 0; i < dimensions; i++) |
---|
252 | { |
---|
253 | result[i] = (first[i] + second[i]) / 2; |
---|
254 | } |
---|
255 | return result; |
---|
256 | } |
---|
257 | |
---|
258 | double fH_StickHandle::distance(fH_Handle *right) |
---|
259 | { |
---|
260 | double distance = 0; |
---|
261 | switch (right->type) |
---|
262 | { |
---|
263 | case fHBodyType::JOINT: |
---|
264 | // distance is computed between second vector of current handle and first |
---|
265 | // vector of second handle |
---|
266 | distance = dist(second, right->first); |
---|
267 | break; |
---|
268 | case fHBodyType::NEURON: |
---|
269 | { |
---|
270 | // if neuron has to be connected to joint, then distance is calculated |
---|
271 | // between averages of both handles |
---|
272 | vector<double> avgs = getVectorsAverage(); |
---|
273 | vector<double> avgn = right->getVectorsAverage(); |
---|
274 | distance = dist(avgs, avgn); |
---|
275 | break; |
---|
276 | } |
---|
277 | case fHBodyType::CONNECTION: |
---|
278 | // it is impossible to calculate distance between Joint and Connection |
---|
279 | return numeric_limits<double>::quiet_NaN(); |
---|
280 | } |
---|
281 | return distance; |
---|
282 | } |
---|
283 | |
---|
284 | double fH_NeuronHandle::distance(fH_Handle *right) |
---|
285 | { |
---|
286 | double distance = 0; |
---|
287 | switch (right->type) |
---|
288 | { |
---|
289 | case fHBodyType::JOINT: |
---|
290 | { |
---|
291 | // if neuron has to be connected to joint, then distance is calculated |
---|
292 | // between averages of both handles |
---|
293 | vector<double> avgs = right->getVectorsAverage(); |
---|
294 | vector<double> avgn = getVectorsAverage(); |
---|
295 | distance = dist(avgs, avgn); |
---|
296 | break; |
---|
297 | } |
---|
298 | case fHBodyType::CONNECTION: |
---|
299 | // this calculation is meant for input neuron - it compares second vector |
---|
300 | // of neuron and first vector of connection |
---|
301 | distance = dist(second, right->first); |
---|
302 | break; |
---|
303 | case fHBodyType::NEURON: |
---|
304 | // it is impossible to calculate distance between two Neurons |
---|
305 | return numeric_limits<double>::quiet_NaN(); |
---|
306 | } |
---|
307 | return distance; |
---|
308 | } |
---|
309 | |
---|
310 | double fH_NeuronHandle::distance(fH_StickHandle *right, bool first) |
---|
311 | { |
---|
312 | vector<double> avgn = getVectorsAverage(); |
---|
313 | double distance = 0; |
---|
314 | if (first) |
---|
315 | { |
---|
316 | distance = dist(avgn, right->firstparthandle); |
---|
317 | } |
---|
318 | else |
---|
319 | { |
---|
320 | distance = dist(avgn, right->secondparthandle); |
---|
321 | } |
---|
322 | return distance; |
---|
323 | } |
---|
324 | |
---|
325 | double fH_ConnectionHandle::distance(fH_Handle *right) |
---|
326 | { |
---|
327 | double distance = 0; |
---|
328 | switch (right->type) |
---|
329 | { |
---|
330 | case fHBodyType::NEURON: |
---|
331 | // this calculation is meant for output neuron - it compares second vector |
---|
332 | // of connection and first vector of neuron |
---|
333 | distance = dist(second, right->first); |
---|
334 | break; |
---|
335 | case fHBodyType::JOINT: |
---|
336 | case fHBodyType::CONNECTION: |
---|
337 | // it is impossible to calculate distance between Connection and other |
---|
338 | // Connection or Joint |
---|
339 | return numeric_limits<double>::quiet_NaN(); |
---|
340 | } |
---|
341 | return distance; |
---|
342 | } |
---|
343 | |
---|
344 | // Creature build functions |
---|
345 | |
---|
346 | Part * fH_StickHandle::createPart(ParamEntry *tab, std::vector<fH_StickHandle *> *children, Model *model, bool createmapping) |
---|
347 | { |
---|
348 | Param par(tab, obj); |
---|
349 | double partprops[FH_PART_PROPS_COUNT]; |
---|
350 | for (int i = 0; i < FH_PART_PROPS_COUNT; i++) |
---|
351 | { |
---|
352 | partprops[i] = par.getDouble(2 * getDimensions() + i); |
---|
353 | } |
---|
354 | |
---|
355 | unsigned int stickscount = children->size() + 1; |
---|
356 | |
---|
357 | MultiRange ranges; |
---|
358 | ranges.add(begin, end); |
---|
359 | |
---|
360 | for (fH_StickHandle *child : (*children)) |
---|
361 | { |
---|
362 | par.select(child->obj); |
---|
363 | for (int i = 0; i < FH_PART_PROPS_COUNT; i++) |
---|
364 | { |
---|
365 | partprops[i] += par.getDouble(2 * getDimensions() + i); |
---|
366 | } |
---|
367 | ranges.add(child->begin, child->end); |
---|
368 | } |
---|
369 | |
---|
370 | for (int i = 0; i < FH_PART_PROPS_COUNT; i++) |
---|
371 | { |
---|
372 | partprops[i] /= stickscount; |
---|
373 | } |
---|
374 | |
---|
375 | Part *newpart = new Part(); |
---|
376 | |
---|
377 | model->addPart(newpart); |
---|
378 | |
---|
379 | newpart->density = partprops[0]; |
---|
380 | newpart->friction = partprops[1]; |
---|
381 | newpart->ingest = partprops[2]; |
---|
382 | newpart->assim = partprops[3]; |
---|
383 | |
---|
384 | if (createmapping) newpart->addMapping(ranges); |
---|
385 | |
---|
386 | return newpart; |
---|
387 | } |
---|
388 | |
---|
389 | Joint* fH_StickHandle::createJoint(ParamEntry *tab, Model *model, bool createmapping) |
---|
390 | { |
---|
391 | Param par(tab, obj); |
---|
392 | if (firstpart == NULL || secondpart == NULL) |
---|
393 | { |
---|
394 | return NULL; |
---|
395 | } |
---|
396 | Joint *newjoint = new Joint(); |
---|
397 | |
---|
398 | model->addJoint(newjoint); |
---|
399 | |
---|
400 | newjoint->stif = par.getDoubleById("stif"); |
---|
401 | newjoint->rotstif = par.getDoubleById("rotstif"); |
---|
402 | newjoint->stamina = par.getDoubleById("stam"); |
---|
403 | newjoint->attachToParts(firstpart, secondpart); |
---|
404 | if (createmapping) newjoint->addMapping(IRange(begin, end)); |
---|
405 | return newjoint; |
---|
406 | } |
---|
407 | |
---|
408 | void fH_Builder::buildBody() |
---|
409 | { |
---|
410 | // stickconnections vector holds information about connections between sticks. |
---|
411 | // Left side of pair should hold pointer to stick that is connected with second |
---|
412 | // vector, and right side of pair should hold pointer to stick that is connected |
---|
413 | // with first vector |
---|
414 | stickconnections.clear(); |
---|
415 | |
---|
416 | // if body consists of single stick, just add it to body |
---|
417 | if (sticks.size() == 1) |
---|
418 | { |
---|
419 | stickconnections.push_back(pair<fH_StickHandle *, fH_StickHandle *>(NULL, sticks[0])); |
---|
420 | sticksorder.push_back(0); |
---|
421 | return; |
---|
422 | } |
---|
423 | |
---|
424 | vector<bool> remainingsticks(sticks.size(), true); |
---|
425 | |
---|
426 | // first we find two handles that have minimal distances between their second |
---|
427 | // and first vector |
---|
428 | fH_StickHandle *left = sticks[0]; |
---|
429 | fH_StickHandle *right = sticks[1]; |
---|
430 | double mindist = left->distance(right); |
---|
431 | int leftid = 0; |
---|
432 | int rightid = 1; |
---|
433 | for (unsigned int i = 0; i < sticks.size(); i++) |
---|
434 | { |
---|
435 | for (unsigned int j = i + 1; j < sticks.size(); j++) |
---|
436 | { |
---|
437 | double distance = sticks[i]->distance(sticks[j]); |
---|
438 | if (distance < mindist) |
---|
439 | { |
---|
440 | mindist = distance; |
---|
441 | left = sticks[i]; |
---|
442 | right = sticks[j]; |
---|
443 | leftid = i; |
---|
444 | rightid = j; |
---|
445 | } |
---|
446 | distance = sticks[j]->distance(sticks[i]); |
---|
447 | if (distance < mindist) |
---|
448 | { |
---|
449 | mindist = distance; |
---|
450 | left = sticks[j]; |
---|
451 | right = sticks[i]; |
---|
452 | leftid = j; |
---|
453 | rightid = i; |
---|
454 | } |
---|
455 | } |
---|
456 | } |
---|
457 | |
---|
458 | // two found handles are the beginning of creature body |
---|
459 | stickconnections.push_back(pair<fH_StickHandle *, fH_StickHandle *>(NULL, left)); |
---|
460 | stickconnections.push_back(pair<fH_StickHandle *, fH_StickHandle *>(left, right)); |
---|
461 | |
---|
462 | // after selecting two handles as beginning of body, they are marked as used |
---|
463 | // in the list of remaining sticks |
---|
464 | remainingsticks[leftid] = false; |
---|
465 | remainingsticks[rightid] = false; |
---|
466 | |
---|
467 | sticksorder.push_back(leftid); |
---|
468 | sticksorder.push_back(rightid); |
---|
469 | |
---|
470 | // next stick is selected by minimum distance between first vector of its handle |
---|
471 | // and second vector of any existing StickHandle in body |
---|
472 | int remaining = sticks.size() - 2; |
---|
473 | while (remaining > 0) |
---|
474 | { |
---|
475 | leftid = -1; |
---|
476 | rightid = -1; |
---|
477 | mindist = numeric_limits<double>::max(); |
---|
478 | for (unsigned int i = 0; i < sticks.size(); i++) |
---|
479 | { |
---|
480 | // if stick is not already in |
---|
481 | if (remainingsticks[i]) |
---|
482 | { |
---|
483 | for (int stickid : sticksorder) |
---|
484 | { |
---|
485 | double distance = sticks[stickid]->distance(sticks[i]); |
---|
486 | if (distance < mindist) |
---|
487 | { |
---|
488 | mindist = distance; |
---|
489 | leftid = stickid; |
---|
490 | rightid = i; |
---|
491 | } |
---|
492 | } |
---|
493 | } |
---|
494 | } |
---|
495 | stickconnections.push_back(pair<fH_StickHandle *, fH_StickHandle *>(sticks[leftid], sticks[rightid])); |
---|
496 | remainingsticks[rightid] = false; |
---|
497 | sticksorder.push_back(rightid); |
---|
498 | remaining--; |
---|
499 | } |
---|
500 | } |
---|
501 | |
---|
502 | int fH_Builder::developBrain(Model *model, bool createmapping) |
---|
503 | { |
---|
504 | Param par(neuronparamtab, NULL); |
---|
505 | // First of all, neurons are attached to body |
---|
506 | for (fH_NeuronHandle *currneu : neurons) |
---|
507 | { |
---|
508 | par.select(currneu->obj); |
---|
509 | // create Neuro object and set details |
---|
510 | currneu->neuron = new Neuro(); |
---|
511 | SString det = par.getStringById("d"); |
---|
512 | if (det != "") |
---|
513 | { |
---|
514 | currneu->neuron->setDetails(det); |
---|
515 | } |
---|
516 | else |
---|
517 | { |
---|
518 | currneu->neuron->setDetails("N"); |
---|
519 | } |
---|
520 | |
---|
521 | // get class of neuron. If class with given name does not exist - return error |
---|
522 | NeuroClass *nclass = currneu->neuron->getClass(); |
---|
523 | if (!nclass) |
---|
524 | { |
---|
525 | SString msg = "NeuroClass given in details \""; |
---|
526 | msg += det + "\" does not exist"; |
---|
527 | logMessage("fH_Builder", "developBrain", LOG_ERROR, msg.c_str()); |
---|
528 | delete currneu->neuron; |
---|
529 | return -1; |
---|
530 | } |
---|
531 | // add neuron to model -> required before attaching to body part |
---|
532 | model->addNeuro(currneu->neuron); |
---|
533 | if (nclass->getPreferredLocation() == 2) // attach to Joint |
---|
534 | { |
---|
535 | // find stick that has closest average handle to average handle of |
---|
536 | // neuron |
---|
537 | double mindist = currneu->distance(sticks[0]); |
---|
538 | fH_StickHandle *minstick = sticks[0]; |
---|
539 | for (unsigned int i = 1; i < sticks.size(); i++) |
---|
540 | { |
---|
541 | double distance = currneu->distance(sticks[i]); |
---|
542 | if (distance < mindist) |
---|
543 | { |
---|
544 | mindist = distance; |
---|
545 | minstick = sticks[i]; |
---|
546 | } |
---|
547 | } |
---|
548 | currneu->neuron->attachToJoint(minstick->joint); |
---|
549 | } |
---|
550 | else if (nclass->getPreferredLocation() == 1) // attach to Part |
---|
551 | { |
---|
552 | // in the beginning we take first part of first stick to calculate |
---|
553 | // distance between them as initial minimal distance |
---|
554 | double mindist = currneu->distance(sticks[0], true); |
---|
555 | Part *minpart = sticks[0]->firstpart; |
---|
556 | for (unsigned int i = 0; i < sticks.size(); i++) |
---|
557 | { |
---|
558 | // after this we take only second parts of following sticks to |
---|
559 | // avoid repetition (thats why we start from i = 0) |
---|
560 | double distance = currneu->distance(sticks[i], false); |
---|
561 | if (distance < mindist) |
---|
562 | { |
---|
563 | mindist = distance; |
---|
564 | minpart = sticks[i]->secondpart; |
---|
565 | } |
---|
566 | } |
---|
567 | currneu->neuron->attachToPart(minpart); |
---|
568 | } |
---|
569 | if (createmapping) currneu->neuron->addMapping(IRange(currneu->begin, currneu->end)); |
---|
570 | model->checkpoint(); |
---|
571 | } |
---|
572 | |
---|
573 | par.setParamTab(connectionparamtab); |
---|
574 | // Secondly, connections are created |
---|
575 | for (fH_ConnectionHandle *currcon : connections) |
---|
576 | { |
---|
577 | par.select(currcon->obj); |
---|
578 | // Connection is created as follows: |
---|
579 | // beginneu ---> endneu |
---|
580 | // distance between beginneu and connection is calculated as distance |
---|
581 | // between second handle of beginneu and first handle of connection. |
---|
582 | // This is why calculation is written as beginneu->distance(currcon). |
---|
583 | // In case of connection and endneu distance between them is calculated |
---|
584 | // as distance between second handle of connection and first handle of |
---|
585 | // endneu. This is why calculation is written as currcon->distance(endneu). |
---|
586 | |
---|
587 | fH_NeuronHandle *beginneu = NULL; |
---|
588 | double mindist = numeric_limits<double>::max(); |
---|
589 | // find beginning of connection |
---|
590 | for (fH_NeuronHandle *neuron : neurons) |
---|
591 | { |
---|
592 | // These method checked earlier if all neurons have valid classes. |
---|
593 | // If a neuron does not have output, then it's skipped from comparison. |
---|
594 | // Otherwise: |
---|
595 | if (neuron->neuron->getClass()->getPreferredOutput() > 0) |
---|
596 | { |
---|
597 | double distance = neuron->distance(currcon); |
---|
598 | if (distance < mindist) |
---|
599 | { |
---|
600 | mindist = distance; |
---|
601 | beginneu = neuron; |
---|
602 | } |
---|
603 | } |
---|
604 | } |
---|
605 | // if there was no neuron that could begin connection, then return warning |
---|
606 | if (!beginneu) |
---|
607 | { |
---|
608 | // due to often appearance of connection genes in fB encoding, this |
---|
609 | // log message is commented |
---|
610 | // logMessage("fH_Builder", "developBrain", LOG_DEBUG, "There are no available neurons with outputs, connection could not be established"); |
---|
611 | continue; |
---|
612 | } |
---|
613 | |
---|
614 | fH_NeuronHandle *endneu = NULL; |
---|
615 | mindist = numeric_limits<double>::max(); |
---|
616 | // find ending of connection |
---|
617 | for (fH_NeuronHandle *neuron : neurons) |
---|
618 | { |
---|
619 | // Method checked earlier if all neurons have valid classes. |
---|
620 | // If neuron does not accept input or all inputs are already connected, |
---|
621 | // then it's skipped from comparison. |
---|
622 | // Otherwise: |
---|
623 | if (neuron->neuron->getClass()->getPreferredInputs() == -1 || |
---|
624 | neuron->neuron->getClass()->getPreferredInputs() > neuron->neuron->getInputCount()) |
---|
625 | { |
---|
626 | double distance = currcon->distance(neuron); |
---|
627 | if (distance < mindist) |
---|
628 | { |
---|
629 | mindist = distance; |
---|
630 | endneu = neuron; |
---|
631 | } |
---|
632 | } |
---|
633 | } |
---|
634 | // if there was no neuron that could end connection, then return warning |
---|
635 | if (!endneu) |
---|
636 | { |
---|
637 | // due to often appearance of connection genes in fB encoding, this |
---|
638 | // log message is commented |
---|
639 | // logMessage("fH_Builder", "developBrain", LOG_DEBUG, "There are no available neurons with free inputs, connection could not be established"); |
---|
640 | continue; |
---|
641 | } |
---|
642 | endneu->neuron->addInput(beginneu->neuron, par.getDoubleById("w")); |
---|
643 | if (createmapping) endneu->neuron->addMapping(IRange(currcon->begin, currcon->end)); |
---|
644 | model->checkpoint(); |
---|
645 | } |
---|
646 | return 0; |
---|
647 | } |
---|
648 | |
---|
649 | Pt3D fH_Builder::getNextDirection(int count, int number) |
---|
650 | { |
---|
651 | // In order to get evenly distributed sticks coming from the same Part method |
---|
652 | // uses algorithm for even distribution of points on a sphere. There are several |
---|
653 | // methods to perform this, usually they are iterative. This method introduced |
---|
654 | // below offers not fully accurate, yet quite satisfying results. This is |
---|
655 | // RSZ method (Rakhmanov, Saff and Zhou method), with use of the golden angle. |
---|
656 | // This method is based on distribution of points along spiral that covers sphere |
---|
657 | // surface. |
---|
658 | |
---|
659 | // Following method works partially on spherical coordinates (r and theta is used). |
---|
660 | // The Z coordinate is from Cartesian coordinate system. The golden angle is used |
---|
661 | // to "iterate" along spiral, while Z coordinate is used to move down the |
---|
662 | // sphere. |
---|
663 | |
---|
664 | double golden_angle = M_PI * (3.0 - sqrt(5)); |
---|
665 | double dz = 2.0 / (double)count; |
---|
666 | double z = 1 - ((double)number + 0.5) * dz; |
---|
667 | double r = sqrt(1 - z * z); |
---|
668 | double theta = golden_angle * number; |
---|
669 | Pt3D vec; |
---|
670 | // In the end X and Y coordinates are calculated with current values of |
---|
671 | // r and theta. Value z is already calculated |
---|
672 | vec.x = r * cos(theta); |
---|
673 | vec.y = r * sin(theta); |
---|
674 | vec.z = z; |
---|
675 | vec.normalize(); |
---|
676 | return vec; |
---|
677 | } |
---|
678 | |
---|
679 | Orient fH_Builder::getRotationMatrixToFitVector(Pt3D currdir, Pt3D expecteddir) |
---|
680 | { |
---|
681 | Orient res; |
---|
682 | // first method normalizes vectors for easy calculations |
---|
683 | currdir.normalize(); |
---|
684 | expecteddir.normalize(); |
---|
685 | double c = currdir.dotProduct(expecteddir); // dot product of both vectors |
---|
686 | // if the dot product of both vectors equals 0 |
---|
687 | if (c == 0) |
---|
688 | { |
---|
689 | res.x.x = -1; |
---|
690 | res.x.y = 0; |
---|
691 | res.x.z = 0; |
---|
692 | |
---|
693 | res.y.x = 0; |
---|
694 | res.y.y = -1; |
---|
695 | res.y.z = 0; |
---|
696 | |
---|
697 | res.z.x = 0; |
---|
698 | res.z.y = 0; |
---|
699 | res.z.z = -1; |
---|
700 | } |
---|
701 | Pt3D v = Pt3D(0); // cross product of both vectors |
---|
702 | v.x = currdir.y * expecteddir.z - currdir.z * expecteddir.y; |
---|
703 | v.y = currdir.z * expecteddir.x - currdir.x * expecteddir.z; |
---|
704 | v.z = currdir.x * expecteddir.y - currdir.y * expecteddir.x; |
---|
705 | |
---|
706 | // Rotation matrix that enables aligning currdir to expecteddir comes from |
---|
707 | // following calculation |
---|
708 | // R = I + [v]_x + ([v]_x)^2 / (1+c) |
---|
709 | // where [v]_x is the skew-symmetric cross-product matrix of v |
---|
710 | res.x.x = 1 - (v.y * v.y + v.z * v.z) / (1 + c); |
---|
711 | res.x.y = v.z + (v.x * v.y) / (1 + c); |
---|
712 | res.x.z = -v.y + (v.x * v.z) / (1 + c); |
---|
713 | res.y.x = -v.z + (v.x * v.y) / (1 + c); |
---|
714 | res.y.y = 1 - (v.x * v.x + v.z * v.z) / (1 + c); |
---|
715 | res.y.z = v.x + (v.y * v.z) / (1 + c); |
---|
716 | res.z.x = v.y + (v.x * v.z) / (1 + c); |
---|
717 | res.z.y = -v.x + (v.y * v.z) / (1 + c); |
---|
718 | res.z.z = 1 - (v.x * v.x + v.y * v.y) / (1 + c); |
---|
719 | |
---|
720 | return res; |
---|
721 | } |
---|
722 | |
---|
723 | Model* fH_Builder::buildModel(bool using_checkpoints) |
---|
724 | { |
---|
725 | Model *model = new Model(); |
---|
726 | |
---|
727 | // At first, floating sticks are connected |
---|
728 | buildBody(); |
---|
729 | |
---|
730 | model->open(using_checkpoints); |
---|
731 | |
---|
732 | // Secondly, parts and joints are created |
---|
733 | // For every stick in body, starting with initial |
---|
734 | Param par(stickparamtab, NULL); |
---|
735 | for (int currid : sticksorder) |
---|
736 | { |
---|
737 | fH_StickHandle *currstick = sticks[currid]; |
---|
738 | fH_StickHandle *parent = NULL; |
---|
739 | // find parent of current stick - it is first element of pair, in which |
---|
740 | // current stick is second |
---|
741 | for (pair<fH_StickHandle *, fH_StickHandle *> conn : stickconnections) |
---|
742 | { |
---|
743 | if (conn.second == currstick) |
---|
744 | { |
---|
745 | parent = conn.first; |
---|
746 | break; |
---|
747 | } |
---|
748 | } |
---|
749 | |
---|
750 | // if parent is NULL, then create Part with current stick properties and |
---|
751 | // location at (0,0,0) |
---|
752 | if (!parent) |
---|
753 | { |
---|
754 | vector<fH_StickHandle *> emptylist; |
---|
755 | Part *firstpart = currstick->createPart(stickparamtab, &emptylist, model, createmapping); |
---|
756 | firstpart->p = Pt3D(0); |
---|
757 | currstick->firstpart = firstpart; |
---|
758 | currstick->firstparthandle = currstick->first; // this is used to calculate later distance between |
---|
759 | model->checkpoint(); |
---|
760 | } |
---|
761 | else //otherwise first part of current stick is the second part of previous stick |
---|
762 | { |
---|
763 | currstick->firstpart = parent->secondpart; |
---|
764 | currstick->firstparthandle = parent->secondparthandle; |
---|
765 | } |
---|
766 | // position of second part depends on two things |
---|
767 | // 1. direction of previous joint |
---|
768 | // 2. how many sticks are connected to the same parent |
---|
769 | // default direction of growth (without parent) is (1,0,0) |
---|
770 | Pt3D direction(1, 0, 0); |
---|
771 | Pt3D secondposition(currstick->firstpart->p); |
---|
772 | // if parent does exist, then determine how many sticks are connected to |
---|
773 | // parent and distribute them evenly on a sphere surrounding second part |
---|
774 | if (parent) |
---|
775 | { |
---|
776 | // improved RSZ method creates vectors that starts in |
---|
777 | // center of sphere (which will act as shared part), so direction |
---|
778 | // calculated below should point from shared part to previous part |
---|
779 | // in order to perform proper aligning |
---|
780 | direction = parent->secondpart->p - parent->firstpart->p; |
---|
781 | direction.normalize(); |
---|
782 | // determine how many sticks are connected to parent and when connection |
---|
783 | // between parent and current stick appear |
---|
784 | int count = 0; |
---|
785 | int id = -1; |
---|
786 | for (unsigned int i = 0; i < stickconnections.size(); i++) |
---|
787 | { |
---|
788 | if (stickconnections[i].first == parent) |
---|
789 | { |
---|
790 | if (stickconnections[i].second == currstick) |
---|
791 | { |
---|
792 | id = count; |
---|
793 | } |
---|
794 | count++; |
---|
795 | } |
---|
796 | } |
---|
797 | if (id == -1) |
---|
798 | { |
---|
799 | logMessage("fH_Builder", "buildModel", LOG_ERROR, "Invalid behaviour"); |
---|
800 | delete model; |
---|
801 | return NULL; |
---|
802 | } |
---|
803 | |
---|
804 | // if there is only one child, then don't change direction - continue |
---|
805 | // along axis of parent. Otherwise calculate direction of id-th stick |
---|
806 | // (that is currstick) with use of RSZ/Vogel method of distributing points |
---|
807 | // evenly on a sphere |
---|
808 | if (count > 1) |
---|
809 | { |
---|
810 | direction = parent->firstpart->p - parent->secondpart->p; |
---|
811 | direction.normalize(); |
---|
812 | // there has to be count+1 directions, so method needs to generate |
---|
813 | // count+1 evenly distributed points on a sphere to make vectors |
---|
814 | // from point (0,0,0) to those points. First generated vector |
---|
815 | // will act as parent joint direction vector |
---|
816 | Pt3D sphere0direction = getNextDirection(count + 1, 0); |
---|
817 | |
---|
818 | // First generated vector needs to be aligned to parent vector |
---|
819 | Orient rotmatrix = getRotationMatrixToFitVector(sphere0direction, direction); |
---|
820 | |
---|
821 | // Calculation of direction from sphere for currstick |
---|
822 | direction = getNextDirection(count + 1, id + 1); |
---|
823 | // Rotation matrix aligning |
---|
824 | direction = rotmatrix.transform(direction); |
---|
825 | direction.normalize(); |
---|
826 | } |
---|
827 | } |
---|
828 | |
---|
829 | // calculate second position |
---|
830 | par.select(currstick->obj); |
---|
831 | secondposition += direction * par.getDoubleById("l"); |
---|
832 | |
---|
833 | // find every stick connected to current stick in order to calculate second |
---|
834 | // part properties |
---|
835 | vector<fH_StickHandle *> children; |
---|
836 | currstick->secondparthandle = currstick->second; |
---|
837 | for (pair<fH_StickHandle *, fH_StickHandle *> conn : stickconnections) |
---|
838 | { |
---|
839 | if (conn.first == currstick) |
---|
840 | { |
---|
841 | children.push_back(conn.second); |
---|
842 | for (int i = 0; i < dimensions; i++) |
---|
843 | { |
---|
844 | currstick->secondparthandle[i] += conn.second->first[i]; |
---|
845 | } |
---|
846 | } |
---|
847 | } |
---|
848 | // create part from current stick and other sticks connected to this part |
---|
849 | Part *secondpart = currstick->createPart(stickparamtab, &children, model, createmapping); |
---|
850 | secondpart->p = secondposition; |
---|
851 | currstick->secondpart = secondpart; |
---|
852 | double count = (double)children.size() + 1; |
---|
853 | for (int i = 0; i < dimensions; i++) |
---|
854 | { |
---|
855 | currstick->secondparthandle[i] /= count; |
---|
856 | } |
---|
857 | |
---|
858 | //after creating second part connect two parts with joint |
---|
859 | Joint * joint = currstick->createJoint(stickparamtab, model, createmapping); |
---|
860 | if (!joint) |
---|
861 | { |
---|
862 | logMessage("fH_Builder", "buildModel", LOG_ERROR, "Joint cannot be created"); |
---|
863 | delete model; |
---|
864 | return NULL; |
---|
865 | |
---|
866 | } |
---|
867 | currstick->joint = joint; |
---|
868 | model->checkpoint(); |
---|
869 | } |
---|
870 | // after creating a body, attach neurons to body and link them according to |
---|
871 | // connections |
---|
872 | if (developBrain(model, createmapping) == -1) |
---|
873 | { |
---|
874 | delete model; |
---|
875 | return NULL; |
---|
876 | } |
---|
877 | model->close(); |
---|
878 | return model; |
---|
879 | } |
---|
880 | |
---|
881 | int fH_Builder::removeNeuronsWithInvalidClasses() |
---|
882 | { |
---|
883 | int count = neurons.size(); |
---|
884 | if (count == 0) |
---|
885 | { |
---|
886 | return 0; |
---|
887 | } |
---|
888 | vector<fH_NeuronHandle *>::iterator it = neurons.begin(); |
---|
889 | Param par(neuronparamtab, NULL); |
---|
890 | while (it != neurons.end()) |
---|
891 | { |
---|
892 | par.select((*it)->obj); |
---|
893 | SString det = par.getStringById("d"); |
---|
894 | if (det == "") |
---|
895 | { |
---|
896 | it++; |
---|
897 | } |
---|
898 | else |
---|
899 | { |
---|
900 | Neuro *neu = new Neuro(); |
---|
901 | neu->setDetails(det); |
---|
902 | if (neu->getClass()) |
---|
903 | { |
---|
904 | it++; |
---|
905 | } |
---|
906 | else |
---|
907 | { |
---|
908 | fH_NeuronHandle *tmp = (*it); |
---|
909 | it = neurons.erase(it); |
---|
910 | delete tmp; |
---|
911 | } |
---|
912 | delete neu; |
---|
913 | } |
---|
914 | |
---|
915 | } |
---|
916 | return count - neurons.size(); |
---|
917 | } |
---|
918 | |
---|
919 | SString fH_Builder::toString() |
---|
920 | { |
---|
921 | SString result = ""; |
---|
922 | result += to_string(dimensions).c_str(); |
---|
923 | result += "\n"; |
---|
924 | // first method stringifies parts |
---|
925 | Param par(stickparamtab, NULL); |
---|
926 | void *def = ParamObject::makeObject(stickparamtab); |
---|
927 | par.select(def); |
---|
928 | par.setDefault(); |
---|
929 | for (fH_StickHandle *currstick : sticks) |
---|
930 | { |
---|
931 | currstick->saveProperties(par); |
---|
932 | SString props; |
---|
933 | par.saveSingleLine(props, def, true, false); |
---|
934 | result += "j:"; |
---|
935 | result += props; |
---|
936 | } |
---|
937 | ParamObject::freeObject(def); |
---|
938 | par.setParamTab(neuronparamtab); |
---|
939 | def = ParamObject::makeObject(neuronparamtab); |
---|
940 | par.select(def); |
---|
941 | par.setDefault(); |
---|
942 | for (fH_NeuronHandle *currneuron : neurons) |
---|
943 | { |
---|
944 | currneuron->saveProperties(par); |
---|
945 | SString props; |
---|
946 | par.saveSingleLine(props, def, true, false); |
---|
947 | result += "n:"; |
---|
948 | result += props; |
---|
949 | } |
---|
950 | ParamObject::freeObject(def); |
---|
951 | par.setParamTab(connectionparamtab); |
---|
952 | def = ParamObject::makeObject(connectionparamtab); |
---|
953 | par.select(def); |
---|
954 | par.setDefault(); |
---|
955 | for (fH_ConnectionHandle *currconnection : connections) |
---|
956 | { |
---|
957 | currconnection->saveProperties(par); |
---|
958 | SString props; |
---|
959 | par.saveSingleLine(props, def, true, false); |
---|
960 | result += "c:"; |
---|
961 | result += props; |
---|
962 | } |
---|
963 | ParamObject::freeObject(def); |
---|
964 | return result; |
---|
965 | } |
---|
966 | |
---|
967 | ParamEntry* fH_Builder::getParamTab(fHBodyType type) |
---|
968 | { |
---|
969 | switch (type) |
---|
970 | { |
---|
971 | case fHBodyType::JOINT: |
---|
972 | return stickparamtab; |
---|
973 | break; |
---|
974 | case fHBodyType::NEURON: |
---|
975 | return neuronparamtab; |
---|
976 | break; |
---|
977 | default: |
---|
978 | return connectionparamtab; |
---|
979 | break; |
---|
980 | } |
---|
981 | } |
---|