#include #include #include "fL_general.h" const char *fL_part_names[PART_PROPS_COUNT] = { "dn", "fr", "ing", "as" }; const char *fL_part_fullnames[PART_PROPS_COUNT] = { "details", "friction", "ingestion", "assimilation" }; const char *fL_joint_names[JOINT_PROPS_COUNT] = { "stif", "rotstif", "stam" }; const char *fL_joint_fullnames[JOINT_PROPS_COUNT] = { "stiffness", "rotation stiffness", "stamina" }; #define FIELDSTRUCT fL_Word ParamEntry fL_word_paramtab[] = { { "Word", 1, 2, "w" }, { "name", 0, PARAM_CANOMITNAME, "word name", "s", FIELD(name), }, { "npar", 0, PARAM_CANOMITNAME, "number of parameters", "d 0 3 0", FIELD(npar), }, { 0, 0, 0, } }; #undef FIELDSTRUCT #define FIELDSTRUCT fL_Rule ParamEntry fL_rule_paramtab[] = { { "Rule", 1, 3, "r" }, { "pred", 0, PARAM_CANOMITNAME, "predecessor", "s", FIELD(predecessor), }, { "cond", 0, PARAM_CANOMITNAME, "parameter condition", "s", FIELD(condition), }, { "succ", 0, PARAM_CANOMITNAME, "successor", "s", FIELD(successor), }, { 0, 0, 0, } }; #undef FIELDSTRUCT #define FIELDSTRUCT fL_Builder ParamEntry fL_builder_paramtab[] = { { "LSystemInfo", 1, 3, "i" }, { "axiom", 0, PARAM_CANOMITNAME, "starting sequence of L-System", "s", FIELD(axiom), }, { "time", 0, PARAM_CANOMITNAME, "development time", "f 1.0 100.0 1.0", FIELD(time), }, { "numckp", 0, PARAM_CANOMITNAME, "number of checkpoints", "d 1 50 1", FIELD(numckp), }, { 0, 0, 0, } }; #undef FIELDSTRUCT fL_Builder::~fL_Builder() { // first remove words from builder for (fL_Word *word : genotype) { delete word; } genotype.clear(); // remove rules from builder for (fL_Rule *rule : rules) { delete rule; } rules.clear(); // remove words definitions with their ParamTabs std::unordered_map::iterator it; for (it = words.begin(); it != words.end(); it++) { ParamObject::freeParamTab(it->second->tab); delete it->second; } words.clear(); } bool fL_Builder::getNextObject(int &pos, SString src, SString &token) { // if position exceeds length then return false if (pos >= src.len()) return false; int opencount = -1; int i = pos; for (; i < src.len(); i++) { // token cannot contain branching parenthesis if (src[i] == '[' || src[i] == ']') { // if token started - return parenthesis mismatch if (opencount != -1) { pos = -1; return false; } // otherwise [ and ] are interpreted as tokens and they do not have parenthesis token = src.substr(pos, i + 1 - pos); pos = i + 1; return true; } // every word, except [ and ], has () parenthesis // every open parenthesis increment opencount counter; if (src[i] == '(') { if (opencount == -1) opencount = 1; else opencount++; } // every close parenthesis decrement opencount counter else if (src[i] == ')') { // if there were no open parenthesis, return parenthesis mismatch if (opencount == -1) { pos = -1; return false; } else opencount--; } // if counter reaches 0, the token extraction is finished if (opencount == 0) { break; } } if (opencount == 0) { token = src.substr(pos, i + 1 - pos); pos = i + 1; return true; } // if there was no closing parenthesis, then return parenthesis mismatch pos = -1; return false; } std::string fL_Builder::trimSpaces(std::string data) { size_t first = data.find_first_not_of(' '); if (std::string::npos == first) { return data; } size_t last = data.find_last_not_of(' '); return data.substr(first, (last - first + 1)); } int fL_Builder::tokenize(SString sequence, std::list &result, int numparams) { int pos = 0; SString token; int branchcount = 0; if (result.size() > 0) { for (fL_Word *word : result) { delete word; } result.clear(); } // iterate through available tokens while (getNextObject(pos, sequence, token)) { // if token is of open branch type, then add start of branch if (token.indexOf("[", 0) != -1) { fL_Branch *word = new fL_Branch(fL_Branch::BranchType::OPEN); result.push_back(word); branchcount++; continue; } // if token is of closed branch type, then add end of branch if (token.indexOf("]", 0) != -1) { if (branchcount == 0) { SString message = "Branch parenthesis mismatch at: "; message += sequence; logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str()); return 1; } fL_Branch *word = new fL_Branch(fL_Branch::BranchType::CLOSE); result.push_back(word); branchcount--; continue; } SString wordn; int tokpos = 0; // if word name cannot be extracted, then return error if (!token.getNextToken(tokpos, wordn, '(')) { SString message = "Error during parsing words sequence: "; message += sequence; logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str()); return 1; } std::string wordname = fL_Builder::trimSpaces(wordn.c_str()); // if word cannot be found in available words, then return error if (words.find(wordname) == words.end()) { SString message = "Word '"; message += wordname.c_str(); message += "' in sequence does not exist"; logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str()); return 1; } // create new word and assign parameters fL_Word *word = new fL_Word(); *word = *words[wordname]; // if word has parameters if (word->npar > 0) { // create ParamObject that will hold parameter data word->data = ParamObject::makeObject(word->tab); Param par(word->tab); par.select(word->data); par.setDefault(); ParamInterface::LoadOptions opts; SString temp; temp = token.substr(tokpos); temp = temp.substr(0, temp.len() - 1); // load parameters from string par.load(ParamInterface::FormatSingleLine, temp, &opts); for (int i = 0; i < par.getPropCount(); i++) { // create MathEvaluation object to check if string contained by // parameter is valid double tmp; MathEvaluation *eval = new MathEvaluation(numparams); SString seq = par.getString(i); // if string is empty, then evaluate this with 0 if (seq.len() == 0) { eval->evaluate("0", tmp); } // if sequence could not be evaluated, then return error else if (eval->evaluate(seq.c_str(), tmp) != 0) { SString message = "Word in sequence has invalid parameter: "; message += temp; logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str()); delete eval; delete word; return 1; } word->parevals.push_back(eval); } } result.push_back(word); } // check if there were no parenthesis errors in genotype if (pos == -1) { SString message = "Parenthesis mismatch at: "; message += sequence; logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str()); return 1; } if (branchcount != 0) { SString message = "Branching mismatch at: "; message += sequence; logMessage("fL_Builder", "tokenize", LOG_ERROR, message.c_str()); return 1; } return 0; } void fL_Word::operator=(const fL_Word& src) { if (&src != this) { name = src.name; npar = src.npar; //mut = src.mut; tab = src.tab; parevals = src.parevals; data = NULL; // properties cannot be copied } } int fL_Word::processDefinition(fL_Builder *builder) { // if word already exist, then return error if (this->name.len() == 0) { logMessage("fL_Word", "processDefinition", LOG_ERROR, "Axiom name is empty"); return 1; } if (builder->words.find(this->name.c_str()) != builder->words.end()) { std::string message = "Word redefinition: "; message += this->name.c_str(); logMessage("fL_Word", "processDefinition", LOG_ERROR, message.c_str()); return 1; } // create ParamTab for word for (int i = 0; i < npar; i++) { std::string n = "n"; n += std::to_string(i); mut.addProperty(NULL, n.c_str(), LSYSTEM_PARAM_TYPE, n.c_str(), "", PARAM_CANOMITNAME, 0, -1); } tab = ParamObject::makeParamTab((ParamInterface *)&mut, 0, 0, mut.firstMutableIndex()); builder->words[this->name.c_str()] = this; return 0; } int fL_Rule::processDefinition(fL_Builder *builder) { // if there is no word among words that matches predecessor, then return error if (builder->words.find(predecessor.c_str()) == builder->words.end()) { logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Word in Rule condition does not exist"); return 1; } objpred = new fL_Word(); *objpred = *builder->words[predecessor.c_str()]; // parse condition if (condition != "") { std::string cond = condition.c_str(); condeval = new MathEvaluation(objpred->npar); double tmp; if (condeval->evaluate(condition.c_str(), tmp) != 0) { SString message = "Parametric condition of rule invalid: "; message += condition; logMessage("fL_Rule", "processDefinition", LOG_ERROR, message.c_str()); return 1; } } // parse successor if (successor == "") { logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Successor cannot be empty"); return 1; } if (builder->tokenize(successor, objsucc, objpred->npar) != 0) { logMessage("fL_Rule", "processDefinition", LOG_ERROR, "Unable to process successor sequence"); return 1; } builder->rules.push_back(this); return 0; } int fL_Builder::processDefinition(fL_Builder *builder) { // tokenize axiom if (tokenize(axiom, genotype, 0) != 0) { logMessage("fL_Builder", "processDefinition", LOG_ERROR, "Unable to process axiom sequence"); return 1; } else if (genotype.size() == 0) { logMessage("fL_Builder", "processDefinition", LOG_ERROR, "Axiom sequence is empty"); return 1; } return 0; } int fL_Builder::processLine(fLElementType type, SString line, fL_Element *&obj, int linenumber, int begin, int end) { ParamEntry *tab; // choose proper ParamTab and construct proper object switch (type) { case fLElementType::TERM: { tab = fL_word_paramtab; obj = new fL_Word(); break; } case fLElementType::INFO: { tab = fL_builder_paramtab; obj = this; break; } case fLElementType::RULE: { tab = fL_rule_paramtab; obj = new fL_Rule(begin, end); break; } default: break; } Param par(tab); par.select(obj); par.setDefault(); ParamInterface::LoadOptions opts; par.load(ParamInterface::FormatSingleLine, line, &opts); if (opts.parse_failed) { std::string message = "Error in parsing parameters at line: " + std::to_string(linenumber); logMessage("fL_Builder", "processLine", LOG_ERROR, message.c_str()); delete obj; return begin + 1; } return 0; } void fL_Builder::addModelWords() { // stick S fL_Word *stick = new fL_Word(true); stick->name = "S"; stick->npar = 8; for (int i = 0; i < PART_PROPS_COUNT; i++) { stick->mut.addProperty(NULL, fL_part_names[i], "s", fL_part_fullnames[i], fL_part_fullnames[i], PARAM_CANOMITNAME, 0, -1); } for (int i = 0; i < JOINT_PROPS_COUNT; i++) { stick->mut.addProperty(NULL, fL_joint_names[i], "s", fL_joint_fullnames[i], fL_joint_fullnames[i], PARAM_CANOMITNAME, 0, -1); } stick->mut.addProperty(NULL, "l", "s", "length", "length", PARAM_CANOMITNAME, 0, -1); stick->tab = ParamObject::makeParamTab((ParamInterface *)&stick->mut, 0, 0, stick->mut.firstMutableIndex()); words["S"] = stick; // neuron N fL_Word *neuron = new fL_Word(true); neuron->name = "N"; neuron->npar = 1; neuron->mut.addProperty(NULL, "d", "s", "details", "details", PARAM_CANOMITNAME, 0, -1); neuron->tab = ParamObject::makeParamTab((ParamInterface *)&neuron->mut, 0, 0, neuron->mut.firstMutableIndex()); words["N"] = neuron; // connection C fL_Word *connection = new fL_Word(true); connection->name = "C"; connection->npar = 2; connection->mut.addProperty(NULL, "w", "s", "weight", "weight", PARAM_CANOMITNAME, 0, -1); connection->mut.addProperty(NULL, "attr", "s", "attractor", "connection attractor", PARAM_CANOMITNAME, 0, -1); connection->tab = ParamObject::makeParamTab((ParamInterface *)&connection->mut, 0, 0, connection->mut.firstMutableIndex()); words["C"] = connection; // rotation objects fL_Word *rotx = new fL_Word(true); rotx->name = "rotX"; rotx->npar = 1; rotx->processDefinition(this); fL_Word *roty = new fL_Word(true); roty->name = "rotY"; roty->npar = 1; roty->processDefinition(this); fL_Word *rotz = new fL_Word(true); rotz->name = "rotZ"; rotz->npar = 1; rotz->processDefinition(this); } int fL_Builder::parseGenotype(SString genotype) { int pos = 0; int lastpos = 0; SString line; int linenumber = 0; fLElementType type = fLElementType::TERM; // add default words first to prevent redefinitions addModelWords(); while (genotype.getNextToken(pos, line, '\n')) { if (line.len() > 0) { // words can be defined in the beginning of genotype if (line.startsWith("w:") && type != fLElementType::TERM) { logMessage("fL_Builder", "parseGenotype", LOG_ERROR, "All words should be defined in the beginning of genotype"); return lastpos + 1; } else if (line.startsWith("i:")) { // after all words are defined, next definition should be information if (type == fLElementType::TERM) { type = fLElementType::INFO; } else { logMessage("fL_Builder", "parseGenotype", LOG_ERROR, "Axioms and iteration number should be defined after word definitions"); return lastpos + 1; } } else if (line.startsWith("r:")) { // after information definition, the last thing is rule definitions if (type == fLElementType::TERM) { logMessage("fL_Builder", "parseGenotype", LOG_ERROR, "Axiom is not defined - define it after words definition"); return lastpos + 1; } else if (type == fLElementType::INFO) { type = fLElementType::RULE; } } // create object fL_Element *obj = NULL; int res = processLine(type, line.substr(2), obj, linenumber, lastpos, pos - 1); if (res != 0) { if (obj && obj != this) delete obj; return res; } res = obj->processDefinition(this); if (res != 0) { if (obj && obj != this) delete obj; return res; } } lastpos = pos; } if (type == fLElementType::TERM) { logMessage("fL_Builder", "parseGenotype", LOG_ERROR, "Info line was not declared"); return 1; } return 0; } int fL_Word::saveEvals(bool keepformulas) { if (npar > 0) { Param par(tab); par.select(data); for (int i = 0; i < npar; i++) { if (parevals[i] != NULL) { double val; if (parevals[i]->evaluateRPN(val) != 0) { logMessage("fL_Word", "saveEvals", LOG_ERROR, "Could not stringify mathematical expression in Word"); return 1; } if (val == 0) { par.setString(i, ""); } else { if (keepformulas) { std::string res; if (parevals[i]->RPNToInfix(res) != 0) { logMessage("fL_Word", "saveEvals", LOG_ERROR, "Could not stringify mathematical expression in Word"); return 1; } par.setString(i, res.c_str()); } else { SString r = SString::valueOf(val); par.setString(i, r); } } } } } return 0; } // Methods for converting L-System objects to string SString fL_Word::toString() { Param par(fL_word_paramtab); fL_Word *obj = new fL_Word(); par.select(this); SString res; par.saveSingleLine(res, obj, true, false); res = SString("w:") + res; delete obj; return res; } SString fL_Word::stringify(bool keepformulas) { SString res = name; SString params = ""; if (npar > 0) { saveEvals(keepformulas); Param par(tab); void *obj = ParamObject::makeObject(tab); par.select(obj); par.setDefault(); par.select(data); par.saveSingleLine(params, obj, false, false); ParamObject::freeObject(obj); } res += "("; res += params + ")"; return res; } SString fL_Rule::toString() { predecessor = objpred->name; std::string tmp; if (condeval) { condeval->RPNToInfix(tmp); condition = tmp.c_str(); } else { condition = ""; } successor = ""; std::list::iterator i; for (i = objsucc.begin(); i != objsucc.end(); i++) { successor += (*i)->stringify(); } Param par(fL_rule_paramtab); fL_Rule *obj = new fL_Rule(0, 0); par.select(this); SString res; par.saveSingleLine(res, obj, true, false); res = SString("r:") + res; delete obj; return res; } SString fL_Builder::getStringifiedProducts() { axiom = ""; std::list::iterator i; for (i = genotype.begin(); i != genotype.end(); i++) { axiom += (*i)->stringify(false); } return axiom; } SString fL_Builder::toString() { SString res; for (std::unordered_map::iterator it = words.begin(); it != words.end(); it++) { if (!it->second->builtin) { res += it->second->toString(); } } getStringifiedProducts(); Param par(fL_builder_paramtab); fL_Builder *obj = new fL_Builder(); par.select(this); SString tmp; par.saveSingleLine(tmp, obj, true, false); res += SString("i:") + tmp; delete obj; for (fL_Rule * rule : rules) { res += rule->toString(); } return res; } int fL_Rule::deploy(fL_Word *in, std::list::iterator &it, std::list &genotype, double currtime) { // if predecessor and given word differ, then rule is not applicable if (in->name != objpred->name || in->npar != objpred->npar) { return 1; } // store predecessor values in separate array double *inwordvalues = new double[in->npar]; for (int i = 0; i < in->npar; i++) { if (in->parevals[i] != NULL) { in->parevals[i]->modifyVariable(-1, currtime == in->creationiter + 1.0 ? 1.0 : currtime - floor(currtime)); in->parevals[i]->evaluateRPN(inwordvalues[i]); } else { inwordvalues[i] = 0; } } // if condition exists if (condeval) { // check if condition is satisfied. If not, rule is not applicable for (int i = 0; i < in->npar; i++) { condeval->modifyVariable(i, inwordvalues[i]); } double condvalue; condeval->evaluateRPN(condvalue); if (condvalue == 0) { delete[] inwordvalues; return 1; } } // remove predecessor word from genotype and replace it with successor it = genotype.erase(it); for (std::list::iterator word = objsucc.begin(); word != objsucc.end(); word++) { // create new word and copy properties from word definition fL_Word *nword = new fL_Word(); *nword = **word; // store information about when word has been created nword->creationiter = currtime; nword->parevals.clear(); if (nword->npar > 0) { nword->data = ParamObject::makeObject(nword->tab); } // calculate word parameters and store MathEvaluation objects for further // time manipulations. for (int q = 0; q < nword->npar; q++) { if ((*word)->parevals[q] == NULL) { MathEvaluation *ev = new MathEvaluation(0); ev->convertString("0"); nword->parevals.push_back(ev); } else { std::string tmp; (*word)->parevals[q]->RPNToInfix(tmp); MathEvaluation *ev = new MathEvaluation(in->npar); for (int i = 0; i < in->npar; i++) { ev->modifyVariable(i, inwordvalues[i]); } ev->modifyVariable(-1, currtime == (*word)->creationiter + 1.0 ? 1.0 : currtime - floor(currtime)); ev->convertString(tmp); nword->parevals.push_back(ev); } } genotype.insert(it, nword); } delete[] inwordvalues; delete in; return 0; } int fL_Builder::iterate(double currtime) { // deploy proper rules for all words in current genotype std::list::iterator word = genotype.begin(); while (word != genotype.end()) { bool deployed = false; for (fL_Rule * rule : rules) { if (rule->deploy((*word), word, genotype, currtime) == 0) { deployed = true; break; } } if (!deployed) word++; } return 0; } int fL_Builder::alterTimedProperties(double currtime) { // alter parameters of all words, if they are time-dependent std::list::iterator word = genotype.begin(); while (word != genotype.end()) { if (currtime - (*word)->creationiter <= 1.0) { for (MathEvaluation *ev : (*word)->parevals) { if (ev) ev->modifyVariable(-1, currtime == (*word)->creationiter + 1.0 ? 1.0 : currtime - floor(currtime)); } } word++; } return 0; } int fL_Builder::alterPartProperties(Part *part, fL_Word *stickword, double &alterationcount) { Param par(stickword->tab, stickword->data); Param ppar = part->properties(); for (int i = 0; i < PART_PROPS_COUNT; i++) { double partprop; double currval; if (!ExtValue::parseDouble(par.getStringById(fL_part_names[i]).c_str(), currval, false)) { logMessage("fL_Builder", "alterPartProperties", LOG_ERROR, "Error parsing word parameter"); return 1; } partprop = (ppar.getDoubleById(fL_part_names[i]) * alterationcount + currval) / (alterationcount + 1.0); ppar.setDoubleById(fL_part_names[i], partprop); } return 0; } bool fL_Word::operator==(const fL_Word& other) const { if (name == other.name) return false; if (npar == other.npar) return false; for (int i = 0; i < npar; i++) { if (parevals[i] && other.parevals[i]) { double first; double second; parevals[i]->evaluateRPN(first); other.parevals[i]->evaluateRPN(second); if (first != second) { return false; } } else { return false; } } return true; } void fL_Builder::findNext(fL_Word *word, std::list genotype, std::list::iterator &it) { int rightdist = 0; std::list::iterator iter(it); for (; iter != genotype.end(); iter++) { if (*word == **iter) { break; } rightdist++; } int leftdist = 0; std::list::reverse_iterator riter(it); for (; riter != genotype.rend(); riter++) { if (*word == **riter) { break; } leftdist++; } if (iter == genotype.end()) { it = riter.base(); } else if (riter == genotype.rend()) { it = iter; } else if (leftdist < rightdist) { it = riter.base(); } else { it = iter; } } Neuro *fL_Builder::findInputNeuron(std::list genotype, std::list::iterator it, fL_Word *attractor) { if (!attractor) { attractor = new fL_Word(); *attractor = *(*it); attractor->data = NULL; // TODO implement } return NULL; } int fL_Builder::developModel(Model &model) { // TODO implement fL_State currstate; std::unordered_map counters; std::stack statestack; std::vector::iterator, Neuro *>> connsbuffer; Part *firstpart = NULL; for (std::list::iterator w = genotype.begin(); w != genotype.end(); w++) { fL_Word *word = (*w); if (word->builtin) { if (word->name == "S") { if (!currstate.currpart) { if (!firstpart) { firstpart = new Part(); firstpart->p = Pt3D_0; counters[firstpart] = 0; model.addPart(firstpart); } currstate.currpart = firstpart; } if (alterPartProperties(currstate.currpart, word, counters[currstate.currpart]) != 0) { return 1; } counters[currstate.currpart] += 1; Part *newpart = new Part(); counters[newpart] = 0; if (alterPartProperties(newpart, word, counters[newpart]) != 0) { delete newpart; return 1; } Param par(word->tab, word->data); double length; if (!ExtValue::parseDouble(par.getStringById("l").c_str(), length, false)) { delete newpart; logMessage("fL_Builder", "alterPartProperties", LOG_ERROR, "Error parsing word parameter"); return 1; } newpart->p = currstate.currpart->p + currstate.direction * length; counters[newpart] += 1; model.addPart(newpart); Joint *newjoint = new Joint(); newjoint->attachToParts(currstate.currpart, newpart); Param jpar = newjoint->properties(); for (int i = 0; i < JOINT_PROPS_COUNT; i++) { double jointprop; if (!ExtValue::parseDouble(par.getStringById(fL_joint_names[i]).c_str(), jointprop, false)) { logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing word parameter"); delete newjoint; return 1; } jpar.setDoubleById(fL_joint_names[i], jointprop); } model.addJoint(newjoint); currstate.currpart = newpart; } else if (word->name == "N") { Param npar(word->tab, word->data); Neuro *neu = new Neuro(); neu->setDetails(npar.getStringById("d")); if (!neu->getClass()) { logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing neuron class"); delete neu; return 1; } model.addNeuro(neu); currstate.currneuron = neu; } else if (word->name == "C") { connsbuffer.push_back({ w, currstate.currneuron }); } else if (word->name.startsWith("rot")) { Orient rotmatrix; double rot; if (word->parevals[0]->evaluateRPN(rot) != 0) { logMessage("fL_Builder", "developModel", LOG_ERROR, "Error parsing neuron class"); return 1; } if (word->name == "rotX") { rotmatrix.rotate(Pt3D(rot, 0.0, 0.0)); } else if (word->name == "rotY") { rotmatrix.rotate(Pt3D(0.0, rot, 0.0)); } else if (word->name == "rotZ") { rotmatrix.rotate(Pt3D(0.0, 0.0, rot)); } currstate.direction = rotmatrix.transform(currstate.direction); } else if (word->name == "[") { statestack.push(currstate); } else if (word->name == "]") { currstate = statestack.top(); statestack.pop(); } } } // connections need // for (std::pair::iterator, Neuro *> conndata : connsbuffer) // { // // } return 0; } int fL_Builder::develop() { Model m; double curriter = 0; double timestamp = 1.0 / numckp; double t = 0; for (; t <= time; t += timestamp) { alterTimedProperties(t); // always alter timed properties in the beginning // if iteration exceeds integer value, then deploy rules if (floor(t) > curriter) { iterate(t); curriter += 1.0; } } // if exact time of development was not reached due to floating point errors, // then alter timed properties if (time < t) { alterTimedProperties(time); } return 0; }