source: cpp/frams/genetics/fH/fH_general.cpp @ 1300

Last change on this file since 1300 was 1299, checked in by Maciej Komosinski, 9 months ago

More reasonable usage of size_t, int, and unsigned int, and their conversions

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