Categories
dev robots simulation

Catkin

ROS build tool. These are the patterns of use:

In order to help automate the merged build process, Catkin was distributed with a command-line tool called catkin_make. This command automated the above CMake work flow while setting some variables according to standard conventions. These defaults would result in the execution of the following commands:

$ mkdir build
$ cd build
$ cmake ../src -DCATKIN_DEVEL_SPACE=../devel -DCMAKE_INSTALL_PREFIX=../install
$ make -j<number of cores> -l<number of cores> [optional target, e.g. install]

To get DSO (Direct Sparse Odometry) working.

I followed these instructions: https://github.com/JakobEngel/dso_ros/issues/32

I made /opt/catkin_ws

git clone –single-branch –branch cmake https://github.com/NikolausDemmel/dso.git
git clone –single-branch –branch catkin https://github.com/NikolausDemmel/dso_ros.git

catkin init

catkin config -DCMAKE_BUILD_TYPE=Release

catkin build

Categories
AI/ML dev evolution

ONS variant of HyperNEAT

https://open.uct.ac.za/bitstream/handle/11427/27910/thesis_sci_2018_didi_sabre_z.pdf?sequence=1&isAllowed=y

The TD methods, were further compared to variants of NEAT and HyperNEAT (that is, OS, ONS, NS, OGN and GNS) with and without behavior transfer. The results demonstrated that the ONS variant of HyperNEAT performs much better (with respect to effectiveness and efficiency) than both TD methods and all variants of NEAT. Specific
evolutionary search methods to direct NE such as behavior diversity maintenance and the hybrid approach, work most effectively at balancing exploration versus exploitation in the search space, more so than TD methods.

Evolutionary search approaches investigated were objective-based search (OS), novelty search (NS), genotypic diversity search (GNS), hybrid of objective and novelty search
and hybrid of objective based and genotypic diversity maintenance search (ONS and OGN, respectively). In this thesis, three methodological features were explored to
ascertain an appropriate combination that enables the evolution of high quality solutions based on effectiveness (task performance) and efficiency (speed of adaptation)
of evolved behaviors. These features are as follows: First, direct versus indirect encoding neuro-evolution methods for collective behavior evolution (that is, NEAT and
HyperNEAT, respectively). Second, non-objective evolutionary search versus objective based search approach for guiding collective behavior evolution. Third, neuro-evolution
with collective behavior transfer.

“with behavior transfer” referring more or less to crossover/mutate rather than starting from scratch with new individuals. Something like that.

https://github.com/sdidi/KeepawaySim

Categories
AI/ML dev envs neuro simulation

OpenAI Gym MultiNEAT

ok also, just saw this: https://gym.openai.com/evaluations/eval_a0YXWDc4SKeJjyTH7IrHBg/

it doesn’t work apparrently, but could be salvaged into something,

possibly written by this guy https://blog.otoro.net/

https://attentionagent.github.io/ there is no conscious perception of the visual world without attention to it

# Using ES-HyperNEAT to try to solve the Bipedal walker.
# This attempt was not successful. Adjustment of hyperparameters is likely needed.

# A neural network is trained using NeuroEvolution of Augmenting Topologies
# The idea is from the paper: "Evolving Neural Networks through Augmenting Topologies"
# This gist is using MultiNEAT (http://multineat.com/)

import logging
import numpy as np
import pickle

import gym

import MultiNEAT as NEAT

# NEAT setup
params = NEAT.Parameters()
params.PopulationSize = 200;

params.DynamicCompatibility = True;
params.CompatTreshold = 2.0;
params.YoungAgeTreshold = 15;
params.SpeciesMaxStagnation = 100;
params.OldAgeTreshold = 35;
params.MinSpecies = 5;
params.MaxSpecies = 10;
params.RouletteWheelSelection = False;

params.MutateRemLinkProb = 0.02;
params.RecurrentProb = 0;
params.OverallMutationRate = 0.15;
params.MutateAddLinkProb = 0.08;
params.MutateAddNeuronProb = 0.01;
params.MutateWeightsProb = 0.90;
params.MaxWeight = 8.0;
params.WeightMutationMaxPower = 0.2;
params.WeightReplacementMaxPower = 1.0;

params.MutateActivationAProb = 0.0;
params.ActivationAMutationMaxPower = 0.5;
params.MinActivationA = 0.05;
params.MaxActivationA = 6.0;

params.MutateNeuronActivationTypeProb = 0.03;

params.ActivationFunction_SignedSigmoid_Prob = 0.0;
params.ActivationFunction_UnsignedSigmoid_Prob = 0.0;
params.ActivationFunction_Tanh_Prob = 1.0;
params.ActivationFunction_TanhCubic_Prob = 0.0;
params.ActivationFunction_SignedStep_Prob = 1.0;
params.ActivationFunction_UnsignedStep_Prob = 0.0;
params.ActivationFunction_SignedGauss_Prob = 1.0;
params.ActivationFunction_UnsignedGauss_Prob = 0.0;
params.ActivationFunction_Abs_Prob = 0.0;
params.ActivationFunction_SignedSine_Prob = 1.0;
params.ActivationFunction_UnsignedSine_Prob = 0.0;
params.ActivationFunction_Linear_Prob = 1.0;

params.DivisionThreshold = 0.5;
params.VarianceThreshold = 0.03;
params.BandThreshold = 0.3;
params.InitialDepth = 2;
params.MaxDepth = 3;
params.IterationLevel = 1;
params.Leo = False;
params.GeometrySeed = False;
params.LeoSeed = False;
params.LeoThreshold = 0.3;
params.CPPN_Bias = -1.0;
params.Qtree_X = 0.0;
params.Qtree_Y = 0.0;
params.Width = 1.;
params.Height = 1.;
params.Elitism = 0.1;

rng = NEAT.RNG()
rng.TimeSeed()

list = []

for i in range(0,14):
	list.append((-1. +(2.*i/13.), -1., 0.))

for i in range(0,10):
	list.append((-1. +(2.*i/9), -0.5, 0))


substrate = NEAT.Substrate(list,
                           [],
                           [(-1., 1., 0.), (-0.5, 1., 0.), (0.5, 1., 0.), (1., 1., 0.)])

substrate.m_allow_input_hidden_links = False;
substrate.m_allow_input_output_links = False;
substrate.m_allow_hidden_hidden_links = False;
substrate.m_allow_hidden_output_links = False;
substrate.m_allow_output_hidden_links = False;
substrate.m_allow_output_output_links = False;
substrate.m_allow_looped_hidden_links = True;
substrate.m_allow_looped_output_links = False;

substrate.m_allow_input_hidden_links = True;
substrate.m_allow_input_output_links = False;
substrate.m_allow_hidden_output_links = True;
substrate.m_allow_hidden_hidden_links = True;

substrate.m_hidden_nodes_activation = NEAT.ActivationFunction.SIGNED_SIGMOID;
substrate.m_output_nodes_activation = NEAT.ActivationFunction.UNSIGNED_SIGMOID;

substrate.m_with_distance = False;

substrate.m_max_weight_and_bias = 8.0;


def trainNetwork(env, seed):
    # Training parameters
    generationSize = 50
    episode_count = 10
    max_steps = 1000
    # Max reward for environments that reward 1 for each succesfull step (e.g. CartPole-v0)
    max_reward = episode_count * max_steps

    def evaluate(genome):
        net = NEAT.NeuralNetwork()
        genome.BuildESHyperNEATPhenotype(net, substrate, params)

        cum_reward = 0

        for i in xrange(episode_count):
            ob = env.reset()
            net.Flush()

            for j in xrange(max_steps):
                # get next action
                net.Input(ob)
                net.Activate()
                o = net.Output()
                action = np.clip(o,-1,1)
                ob, reward, done, _ = env.step(action)
                cum_reward += reward
                if done:
                    break

        return cum_reward

    # Create initial genome
    g = NEAT.Genome(0, 24, 0, 4, False, 
                    NEAT.ActivationFunction.TANH, NEAT.ActivationFunction.TANH, 0, params)
    pop = NEAT.Population(g, params, True, 1.0, seed)

    current_best = None

    for generation in range(generationSize):
        for i_episode, genome in enumerate(NEAT.GetGenomeList(pop)):
            reward = evaluate(genome)

            if reward == max_reward:
                return pickle.dumps(genome)

            genome.SetFitness(reward)

        print('Generation: {}, max fitness: {}'.format(generation,
                            max((x.GetFitness() for x in NEAT.GetGenomeList(pop)))))
        current_best = pickle.dumps(pop.GetBestGenome())
        pop.Epoch()


    return current_best

env_name = "BipedalWalker"

if __name__ == '__main__':
    # Test the algorithm multiple times
    for test_case in xrange(0, 1):
        # setup logger, environment and monitor
        logger = logging.getLogger()
        logger.setLevel(logging.INFO)
        env = gym.make("%s-v2" % env_name)
        outdir = "/tmp/neat-%s-results-%d" % (env_name, test_case)
        env.monitor.start(outdir, force=True)

        # Train network
        learned = trainNetwork(env, test_case)

        # Test trained network on 1000 episodes
        learned_genome = pickle.loads(learned)
        net = NEAT.NeuralNetwork()
        learned_genome.BuildESHyperNEATPhenotype( net,substrate, params)

        episode_count = 1000
        max_steps = 1000

        for i in xrange(episode_count):
            ob = env.reset()
            net.Flush()

            for j in xrange(max_steps):
                # get next action
                net.Input(ob)
                net.Activate()
                o = net.Output()
                action = np.clip(o,-1,1)
                ob, reward, done, _ = env.step(action)
                if done:
                    break


        # Dump result info to disk
        env.monitor.close()
Categories
dev

Installing MultiNEAT

import MultiNEAT as NEAT

need to install MultiNEAT.

Tried to install
using these instructions
https://github.com/peter-ch/MultiNEAT

it said success despite this:

byte-compiling build/bdist.linux-x86_64/egg/MultiNEAT/init.py to init.pyc
File "build/bdist.linux-x86_64/egg/MultiNEAT/init.py", line 45
t = {**traits, 'w':w}
^
SyntaxError: invalid syntax


Installed /usr/local/lib/python2.7/dist-packages/multineat-0.5-py2.7-linux-x86_64.egg
Processing dependencies for multineat==0.5
Finished processing dependencies for multineat==0.5


then tried
pip install .
https://stackoverflow.com/questions/1471994/what-is-setup-py
It gets these errors:

/usr/bin/ld: cannot find -lboost_python36
/usr/bin/ld: cannot find -lboost_numpy36

boost libs missing. Apparently it has a conda install, but I don’t entirely understand whether conda and pip will play nicely together. conda also a big install and the Chromebook is not big on space. Ok Miniconda is smaller.

https://anaconda.org/conda-forge/multineat

To install this package with conda run one of the following:
conda install -c conda-forge multineat
conda install -c conda-forge/label/cf201901 multineat
conda install -c conda-forge/label/cf202003 multineat

apt-get install libboost-all-dev
...already installed

https://stackoverflow.com/questions/36881958/c-program-cannot-find-boost possible solution.

ah…

/usr/local/lib/python2.7/dist-packages

root@chrx:/opt/MultiNEAT# ls -l /usr/local/lib/python2.7/dist-packages
total 7312
-rw-r–r– 1 root staff 39 Apr 8 21:50 easy-install.pth
-rw-r–r– 1 root staff 7482106 Apr 8 21:50 multineat-0.5-py2.7-linux-x86_64.egg

so it’s compiling with python2.

I’m gonna go with conda instead.

conda install MultiNEAT

https://anaconda.org/conda-forge/multineat

https://docs.conda.io/en/latest/miniconda.html#linux-installers

you need to chmod 755 the install file, and don’t forget to add to $PATH.

echo “PATH=$PATH:/opt/miniconda3/bin” >> ~/.bashrc

then close window and open new bash window, to refresh envs. (envs are environment variables, if someone is actually reading this)

conda install -c conda-forge multineat

(base) root@chrx:~# conda install -c conda-forge multineat
Collecting package metadata (current_repodata.json): done
Solving environment: failed with initial frozen solve. Retrying with flexible solve.
Solving environment: failed with repodata from current_repodata.json, will retry with next repodata source.
Collecting package metadata (repodata.json): done
Solving environment: failed with initial frozen solve. Retrying with flexible solve.
Solving environment: | 
Found conflicts! Looking for incompatible packages.
This can take several minutes.  Press CTRL-C to abort.
failed                                                                          

UnsatisfiableError: The following specifications were found
to be incompatible with the existing python installation in your environment:

Specifications:

  - multineat -> python[version='>=2.7,<2.8.0a0|>=3.6,<3.7.0a0|>=3.5,<3.6.0a0']

Your python: python=3.7

If python is on the left-most side of the chain, that's the version you've asked for.
When python appears to the right, that indicates that the thing on the left is somehow
not available for the python version you are constrained to. Note that conda will not
change your python version to a different minor version unless you explicitly specify
that.

ok…

root@chrx:~# conda info

     active environment : base
    active env location : /opt/miniconda3
            shell level : 1
       user config file : /root/.condarc
 populated config files : /root/.condarc
          conda version : 4.8.2
    conda-build version : not installed
         python version : 3.7.6.final.0
       virtual packages : __glibc=2.27
       base environment : /opt/miniconda3  (writable)
           channel URLs : https://repo.anaconda.com/pkgs/main/linux-64
                          https://repo.anaconda.com/pkgs/main/noarch
                          https://repo.anaconda.com/pkgs/r/linux-64
                          https://repo.anaconda.com/pkgs/r/noarch
          package cache : /opt/miniconda3/pkgs
                          /root/.conda/pkgs
       envs directories : /opt/miniconda3/envs
                          /root/.conda/envs
               platform : linux-64
             user-agent : conda/4.8.2 requests/2.22.0 CPython/3.7.6 Linux/4.16.18-galliumos galliumos/3.1 glibc/2.27
                UID:GID : 0:0
             netrc file : None
           offline mode : False


ok let’s go back to compiling boost from source cause conda don’t like 3.7. Which seems like I should do a –force or something, cause wtf. So here’s the theory of C++ linking (fml):

https://stackoverflow.com/questions/16710047/usr-bin-ld-cannot-find-lnameofthelibrary

here’s more relevant

https://askubuntu.com/questions/944035/installing-libboost-python-dev-for-python3-without-installing-python2-7

https://stackoverflow.com/questions/12578499/how-to-install-boost-on-ubuntu

Here’s some Dockerfile on it:

RUN cd /usr/src && \
 wget --no-verbose https://dl.bintray.com/boostorg/release/1.65.1/source/boost_1_65_1.tar.gz && \
 tar xzf boost_1_65_1.tar.gz && \
 cd boost_1_65_1 && \
 ln -s /usr/local/include/python3.6m /usr/local/include/python3.6 && \
 ./bootstrap.sh --with-python=$(which python3) && \
 ./b2 install && \
 rm /usr/local/include/python3.6 && \
 ldconfig && \
 cd / && rm -rf /usr/src/*

I’m doing the steps one by one, ./b2 made it compile. it’s taking like a good few mins now.

The Boost C++ Libraries were successfully built!

The following directory should be added to compiler include paths:

    /opt/boost_1_65_1

The following directory should be added to linker library paths:

    /opt/boost_1_65_1/stage/lib

ok let’s try again…

/usr/bin/ld: cannot find -lboost_python36
/usr/bin/ld: cannot find -lboost_numpy36

ldconfig

nope ok where is the library?

# find | grep boost_python

(there's all the boost libs i built under /opt, and also these:)

./usr/lib/x86_64-linux-gnu/libboost_python-py36.so
./usr/lib/x86_64-linux-gnu/libboost_python3-py36.so.1.65.1
./usr/lib/x86_64-linux-gnu/libboost_python-py27.so.1.65.1
./usr/lib/x86_64-linux-gnu/libboost_python3-py36.so
./usr/lib/x86_64-linux-gnu/libboost_python-py27.a
./usr/lib/x86_64-linux-gnu/libboost_python-py36.a
./usr/lib/x86_64-linux-gnu/libboost_python.so
./usr/lib/x86_64-linux-gnu/libboost_python3.a
./usr/lib/x86_64-linux-gnu/libboost_python-py27.so
./usr/lib/x86_64-linux-gnu/libboost_python3.so
./usr/lib/x86_64-linux-gnu/libboost_python3-py36.a
./usr/lib/x86_64-linux-gnu/libboost_python.a

Ok ./usr/lib/x86_64-linux-gnu/ is where the .so is.

https://stackoverflow.com/questions/3808775/cmake-doesnt-find-boost

https://askubuntu.com/questions/449348/why-are-boost-package-libs-installed-to-usr-lib-x86-64-linux-gnu

https://stackoverflow.com/questions/36881958/c-program-cannot-find-boost

the last one had the sauce that worked for someone for something similar

So I’m changing this bit of setup.py

        include_dirs = ['/opt/boost_1_65_1']
        library_dirs = ['/opt/boost_1_65_1/stage/lib']
        extra.extend(['-DUSE_BOOST_PYTHON', '-DUSE_BOOST_RANDOM', #'-O0',
                      #'-DVDEBUG',
                      ])
        exx = Extension('MultiNEAT._MultiNEAT',
                        sources,
                        libraries=libs,
                        library_dirs=library_dirs,
                        include_dirs=include_dirs,
                        extra_compile_args=extra)

nope

https://unix.stackexchange.com/questions/423821/gcc-usr-bin-ld-cannot-find-lglut32-lopengl32-lglu32-lfreegut-but-these

So we need to use LD somehow.

You need:

  1. To actually have the library in your computer
  2. Help gcc/the linker to find the library by providing the path to the library
    • You can add -Ldir-name to the gcc command
    • You can the library location to the LD_LIBRARY_PATH environment variable
  3. Update the “Dynamic Linker“:sudo ldconfig

Let’s see what it’s running…

x86_64-linux-gnu-g++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.6/src/PythonBindings.o build/temp.linux-x86_64-3.6/src/Genome.o build/temp.linux-x86_64-3.6/src/Innovation.o build/temp.linux-x86_64-3.6/src/NeuralNetwork.o build/temp.linux-x86_64-3.6/src/Parameters.o build/temp.linux-x86_64-3.6/src/PhenotypeBehavior.o build/temp.linux-x86_64-3.6/src/Population.o build/temp.linux-x86_64-3.6/src/Random.o build/temp.linux-x86_64-3.6/src/Species.o build/temp.linux-x86_64-3.6/src/Substrate.o build/temp.linux-x86_64-3.6/src/Utils.o -L/opt/boost_1_65_1/stage/lib -lboost_system -lboost_serialization -lboost_python36 -lboost_numpy36 -o build/lib.linux-x86_64-3.6/MultiNEAT/_MultiNEAT.cpython-36m-x86_64-linux-gnu.so

ok and indeed there doesn’t seem to be anything under /opt/boost_1_65_1/stage/lib with python in the name. We found it in /usr/lib/x86_64-linux-gnu/ earlier.

nope nope nope

https://stackoverflow.com/questions/24173330/cmake-is-not-able-to-find-boost-libraries

SET (BOOST_ROOT "/opt/boost_1_65_1")
SET (BOOST_INCLUDEDIR "/opt/boost_1_65_1/boost")
SET (BOOST_LIBRARYDIR "/opt/boost_1_65_1/libs")

ok so I think it’s just that the Boost.Python package isn’t here. It’s in usr/lib … hmm more later

Here’s something promising: https://github.com/andrewssobral/bgslibrary/issues/96

#:/usr/lib/x86_64-linux-gnu# find | grep boost_python
./libboost_python-py36.so
./libboost_python3-py36.so.1.65.1
./libboost_python-py27.so.1.65.1
./libboost_python3-py36.so
./libboost_python-py27.a
./libboost_python-py36.a
./libboost_python.so
./libboost_python3.a
./libboost_python-py27.so
./libboost_python3.so
./libboost_python3-py36.a
./libboost_python.a

Hmm the answer doesn’t make sense here. Half of those are already symlinks. Let’s go back and make sure we compiled correctly.

./bootstrap.sh --with-libraries=python --with-python=python3.6

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

export LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH

nope. ok what is cmake doing?

It calls Find.Boost,

dies on :

No header defined for python36; skipping header check

No header defined for numpy36; skipping header check

so, ok i worked it out

I changed the CMakeList.txt line to -lboost_python3 -lboost_numpy3

Installed /usr/local/lib/python3.6/dist-packages/multineat-0.5-py3.6-linux-x86_64.egg
Processing dependencies for multineat==0.5
Finished processing dependencies for multineat==0.5

ok but when i try run a python boi in the examples folder,

Boost.Python.ArgumentError: Python argument types in
Genome.init(Genome, int, int, int, int, bool, ActivationFunction, ActivationFunction, int, Parameters, int)
did not match C++ signature:
init(_object*, unsigned int, unsigned int, unsigned int, unsigned int, bool, NEAT::ActivationFunction, NEAT::ActivationFunction, int, NEAT::Parameters, unsigned int, unsigned int)

ok fuck it, github, help me.

https://github.com/peter-ch/MultiNEAT/issues

Ok so the guy pointed out that the constructor changed. It works fine now, after adding a field.

Categories
dev Hardware hardware_ Linux

RPi without keyboard and mouse

https://sendgrid.com/blog/complete-guide-set-raspberry-pi-without-keyboard-mouse/

https://github.com/motdotla/ansible-pi

First thing is you need a file called ‘ssh’ on the raspbian to enable it:.

https://www.raspberrypi.org/forums/viewtopic.php?t=144839

ok so I found the IP address of the PI

root@chrx:~# nmap -sP 192.168.101.0/24

Starting Nmap 7.60 ( https://nmap.org ) at 2020-04-05 17:06 UTC
Nmap scan report for _gateway (192.168.101.1)
Host is up (0.0026s latency).
MAC Address: B8:69:F4:1B:D5:0F (Unknown)
Nmap scan report for 192.168.101.43
Host is up (0.042s latency).
MAC Address: 28:0D:FC:76:BB:3E (Sony Interactive Entertainment)
Nmap scan report for 192.168.101.100
Host is up (0.049s latency).
MAC Address: 18:F0:E4:E9:AF:E3 (Unknown)
Nmap scan report for 192.168.101.101
Host is up (0.015s latency).
MAC Address: DC:85:DE:22:AC:5D (AzureWave Technology)
Nmap scan report for 192.168.101.103
Host is up (-0.057s latency).
MAC Address: 74:C1:4F:31:47:61 (Unknown)
Nmap scan report for 192.168.101.105
Host is up (-0.097s latency).
MAC Address: B8:27:EB:03:24:B0 (Raspberry Pi Foundation)

Nmap scan report for 192.168.101.111
Host is up (-0.087s latency).
MAC Address: 00:24:D7:87:78:EC (Intel Corporate)
Nmap scan report for 192.168.101.121
Host is up (-0.068s latency).
MAC Address: AC:E0:10:C0:84:26 (Liteon Technology)
Nmap scan report for 192.168.101.130
Host is up (-0.097s latency).
MAC Address: 80:5E:C0:52:7A:27 (Yealink(xiamen) Network Technology)
Nmap scan report for 192.168.101.247
Host is up (0.15s latency).
MAC Address: DC:4F:22:FB:0B:27 (Unknown)
Nmap scan report for chrx (192.168.101.127)
Host is up.
Nmap done: 256 IP addresses (11 hosts up) scanned in 2.45 seconds

if nmap is not installed,

apt-get install nmap

Connect to whatever IP it is

ssh -vvvv pi@192.168.101.105

Are you sure you want to continue connecting (yes/no)? yes

Cool, and to set up wifi, let’s check out this ansible script https://github.com/motdotla/ansible-pi

$ sudo apt update
$ sudo apt install software-properties-common
$ sudo apt-add-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible

ok 58MB install…

# ansible-playbook playbook.yml -i hosts –ask-pass –become -c paramiko

PLAY [Ansible Playbook for configuring brand new Raspberry Pi]

TASK [Gathering Facts]

TASK [pi : set_fact]
ok: [192.168.101.105]

TASK [pi : Configure WIFI] **
changed: [192.168.101.105]

TASK [pi : Update APT package cache]
[WARNING]: Updating cache and auto-installing missing dependency: python-apt
ok: [192.168.101.105]

TASK [pi : Upgrade APT to the lastest packages] *
changed: [192.168.101.105]

TASK [pi : Reboot] **
changed: [192.168.101.105]

TASK [pi : Wait for Raspberry PI to come back] **
ok: [192.168.101.105 -> localhost]

PLAY RECAP ****
192.168.101.105 : ok=7 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

And I’ll unplug the ethernet and try connect by ssh again

Ah, but it’s moved up to 192.168.1.106 now

nmap -sP 192.168.101.0/24 (I checked again) and now it was ‘Unknown’, but ssh pi@192.168.101.106 worked

(If you can connect to your router, eg. 192.168.0.1 for most D-Link routers, you can go to something like Status -> Wireless, to see connected devices too, and skip the nmap stuff.)

I log in, then to configure some stuff:

sudo raspi-config

Under the interfaces peripheral section, Enable the camera and I2C

sudo apt-get install python-smbus
sudo apt-get install i2c-tools

ok tested with

raspistill -o out.jpg

Then copied across from my computer with

scp pi@192.168.101.106:/home/pi/out.jpg out.jpg

and then make it smaller (because trying to upload the 4MB version no)

convert out.jpg -resize 800×600 new.jpg

Cool and it looks like we also need to expand the partition

sudo raspi-config again, (Advanced Options, and first option)


Upon configuring the latest pi, I needed to first use the ethernet cable,

and then once logged in, use

sudo rfkill unblock 0

to turn on the wifi. The SSID and wifi password could be configured in raspi-config.


At Bitwäsherei, the ethernet cable to the router trick didn’t work.

Instead, as per the resident Gandalf’s advice, the instructions here

https://raspberrypi.stackexchange.com/questions/10251/prepare-sd-card-for-wifi-on-headless-pi

worked for setting up wireless access on the sd card.

“Since May 2016, Raspbian has been able to copy wifi details from /boot/wpa_supplicant.conf into /etc/wpa_supplicant/wpa_supplicant.conf to automatically configure wireless network access”

The file contains

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=«your_ISO-3166-1_two-letter_country_code»

network={
    ssid="«your_SSID»"
    psk="«your_PSK»"
    key_mgmt=WPA-PSK
}

Save, and put sd card in RPi. Wireless working and can ssh in again!

2022 News flash:

Incredibly, some more issues.

New issue, user guide not updated yet

https://stackoverflow.com/questions/71804429/raspberry-pi-ssh-access-denied

In essence, the default pi user no longer exists, so you have to create it and set its password using either the official Imager tool or by creating a userconf file in the boot partition of your microSD card, which should contain a single line of text: username:hashed-password

Default pi and raspberry

pi:$6$/4.VdYgDm7RJ0qM1$FwXCeQgDKkqrOU3RIRuDSKpauAbBvP11msq9X58c8Que2l1Dwq3vdJMgiZlQSbEXGaY5esVHGBNbCxKLVNqZW1

Categories
dev envs simulation

OpenAI Gym

pip3 install gym

git clone https://github.com/openai/gym.git

cd gym/examples/agents/

python3 random_agent.py

root@root:/opt/gym/examples/agents# python3 random_agent.py
INFO: Making new env: CartPole-v0
INFO: Creating monitor directory /tmp/random-agent-results
INFO: Starting new video recorder writing to /tmp/random-agent-results/openaigym.video.0.4726.video000000.mp4
INFO: Starting new video recorder writing to /tmp/random-agent-results/openaigym.video.0.4726.video000001.mp4
INFO: Starting new video recorder writing to /tmp/random-agent-results/openaigym.video.0.4726.video000008.mp4
INFO: Starting new video recorder writing to /tmp/random-agent-results/openaigym.video.0.4726.video000027.mp4
INFO: Starting new video recorder writing to /tmp/random-agent-results/openaigym.video.0.4726.video000064.mp4
INFO: Finished writing results. You can upload them to the scoreboard via gym.upload(‘/tmp/random-agent-results’)
root@chrx:/opt/gym/examples/agents#

https://github.com/openai/gym/blob/master/docs/environments.md

https://gym.openai.com/envs/#mujoco of course, we’re using Bullet instead of mujoco for a physics engine, as it’s free.

Categories
dev evolution Locomotion simulation

Evolution (Part 1)

Ok next we’re going to be looking at https://github.com/bulletphysics/bullet3/tree/master/examples/Evolution

According to some blog bit: https://gitmemory.com/issue/bulletphysics/bullet3/2118/468943185 there might be an issue: “The problem with this demo is the following: It can not optimize since the robots are not properly restored. Their physical settings are not properly restored and thus their performance varies. I think it should only serve as a bullet example that does some simulation. If you can improve it to restore the physics correctly, that is fine, but this might be hard to do if you are new to bullet. It would be best to reimplement it in pybullet, because it features deterministic restoring of robots.”

So will have to keep that in mind.

So starting in the NN3DWalkersTimeWarp code, we quickly run into ERP and CFM http://www.ode.org/ode-latest-userguide.html#sec_3_7_0 – These are error reduction parameter and constraint force mixing and help fix errors caused by joints that aren’t set up perfectly.

The code also loads a number of Constraint Solvers:

btSequentialImpulseConstraintSolver.h
btNNCGConstraintSolver.h
btMultiBodyConstraintSolver.h
btMultiBodyDynamicsWorld.h
btDantzigSolver.h
btSolveProjectedGaussSeidel.h
btLemkeSolver.h
btMLCPSolver.h

Hmm. Not sure what they do, but they enforce the joint constraints in the physics we’ve set up.

The TimeWarp class mostly deals with configuration settings for running the simulation at different speeds, and with different solvers, etc.

The setupBasicParamInterface() sets up the UI sliders. Then,

m_guiHelper->setUpAxis(1); // Set Y axis as Up axis

createEmptyDynamicsWorld(); // create an empty dynamic world

m_guiHelper->autogenerateGraphicsObjects(m_dynamicsWorld);

The Dynamics world is configured with

btDefaultCollisionConfiguration in a btCollisionDispatcher

a default btDbvtBroadphase (a sweeping style collision detection)

and whichever solver is being used.

The Dynamics world is initiated as a btDiscreteDynamicsWorld

There’s timeWarpSimulation which we override, and the stepSimulation which is in a loop called the ‘canonical game loop’. It updates any variables changed by the UI parameters, then calls our code, and then updates and time variables that need refreshing, and gives you a chance to update some graphics. It gets to the end of the loop, checks the time, to decide whether to render the graphics, or to do the loop again.

That’s sufficient for now, to move on with the CPP file code.

https://github.com/bulletphysics/bullet3/blob/master/examples/Evolution/NN3DWalkers.cpp

Another interesting project I saw now, using OGRE, which is some other physics engine, I think, is https://github.com/benelot/minemonics – has some evolved life too.

Jumper 1

But we are evolving something that looks more like these creepy rainbow spider bombs, that ‘we made’ in MotorDemo.cpp.

class NNWalker
{
	btDynamicsWorld* m_ownerWorld;
	btCollisionShape* m_shapes[BODYPART_COUNT];
	btRigidBody* m_bodies[BODYPART_COUNT];
	btTransform m_bodyRelativeTransforms[BODYPART_COUNT];
	btTypedConstraint* m_joints[JOINT_COUNT];
	btHashMap<btHashPtr, int> m_bodyTouchSensorIndexMap;
	bool m_touchSensors[BODYPART_COUNT];
	btScalar m_sensoryMotorWeights[BODYPART_COUNT * JOINT_COUNT];

	bool m_inEvaluation;
	btScalar m_evaluationTime;
	bool m_reaped;
	btVector3 m_startPosition;
	int m_index;

These above are the variables for the walker, which is similar to the MotorDemo critter. The code has been upgraded here and there since MotorDemo.cpp. It’s pretty great that people share their hard work so that we can just waltz on in and hack a quick robot together. This guy https://github.com/erwincoumans founded the Bullet project, and now works for Google Brain. (on Google Brain?)

So these are some of the public methods for the WalkersExample

	// Evaluation

	void update(const btScalar timeSinceLastTick);

	void updateEvaluations(const btScalar timeSinceLastTick);

	void scheduleEvaluations();

	void drawMarkings();



	// Reaper

	void rateEvaluations();

	void reap();

	void sow();

	void crossover(NNWalker* mother, NNWalker* father, NNWalker* offspring);

	void mutate(NNWalker* mutant, btScalar mutationRate);

	NNWalker* getRandomElite();

	NNWalker* getRandomNonElite();

	NNWalker* getNextReaped();

	void printWalkerConfigs();

Let’s just go through these for now.

We want to evolve behaviours based on stimulus, and the NNWalkers even have m_touchSensors, which we’ll have a look at later, to wee what they let us do.

Interlude: http://news.mit.edu/2015/algorithm-helps-robots-handle-uncertainty-0602 here says the following, and also has a video with sick drone rock music. This is a good part to remember, that either the robot is pausing, evaluating, pausing, evaluating, to accomplish the behaviours using ‘online’ feedback and offline policy processing.

There’s an offline planning phase where the agents can figure out a policy together that says, ‘If I take this set of actions, given that I’ve made these observations during online execution, and you take these other sets of actions, given that you’ve made these observations, then we can all agree that the whole set of actions that we take is pretty close to optimal,’” says Shayegan Omidshafiei, an MIT graduate student in aeronautics and astronautics and first author on the new paper. “There’s no point during the online phase where the agents stop and say, ‘This is my belief. This is your belief. Let’s come up with a consensus on the best overall belief and replan.’ Each one just does its own thing.

So the parts to do with Evaluation are:

// Evaluation

void update(const btScalar timeSinceLastTick);

void updateEvaluations(const btScalar timeSinceLastTick);

void scheduleEvaluations();

void drawMarkings();

And here’s the code for all the evolution stuff:

bool fitnessComparator(const NNWalker* a, const NNWalker* b)
{
return a->getFitness() > b->getFitness(); // sort walkers descending
}

So we are maximising fitness, and this is the Comparator function

The ratingEvaluations function orders by fitness, and prints the square root of the best performing individual’s distance fitness. Then we iterate through the walkers, update the time series canvas tick (next generation). Zero the times, and counters.

void NN3DWalkersExample::rateEvaluations()
 {
     m_walkersInPopulation.quickSort(fitnessComparator);  // Sort walkers by fitness
b3Printf("Best performing walker: %f meters", btSqrt(m_walkersInPopulation[0]->getDistanceFitness()));

for (int i = 0; i < NUM_WALKERS; i++)
{
    m_timeSeriesCanvas->insertDataAtCurrentTime(btSqrt(m_walkersInPopulation[i]->getDistanceFitness()), 0, true);
}
m_timeSeriesCanvas->nextTick();

for (int i = 0; i < NUM_WALKERS; i++)
{
    m_walkersInPopulation[i]->setEvaluationTime(0);
}
m_nextReaped = 0;

}

The reap function uses the REAP_QTY (0.3) to iterate backwards through the worst 30%, set them to reaped.

void NN3DWalkersExample::reap()
{
int reaped = 0;
for (int i = NUM_WALKERS - 1; i >= (NUM_WALKERS - 1) * (1 - REAP_QTY); i--)
{ // reap a certain percentage
m_walkersInPopulation[i]->setReaped(true);
reaped++;
b3Printf("%i Walker(s) reaped.", reaped);
}
}

getRandomElite and getRandomNonElite use SOW_ELITE_QTY (set to 0.2). The functions return one in the first 20% and the last 80%.

NNWalker* NN3DWalkersExample::getRandomElite()
{
return m_walkersInPopulation[((NUM_WALKERS - 1) * SOW_ELITE_QTY) * (rand() / RAND_MAX)];
}
NNWalker* NN3DWalkersExample::getRandomNonElite()
{
return m_walkersInPopulation[(NUM_WALKERS - 1) * SOW_ELITE_QTY + (NUM_WALKERS - 1) * (1.0f - SOW_ELITE_QTY) * (rand() / RAND_MAX)];
}

The getNextReaped() function checks if we’ve reaped the REAP_QTY percentage yet. If not, increment counter and return the next reaped individual.

NNWalker* NN3DWalkersExample::getNextReaped()
 {
     if ((NUM_WALKERS - 1) - m_nextReaped >= (NUM_WALKERS - 1) * (1 - REAP_QTY))
     {
         m_nextReaped++;
     }

if (m_walkersInPopulation[(NUM_WALKERS - 1) - m_nextReaped + 1]->isReaped())
 {
     return m_walkersInPopulation[(NUM_WALKERS - 1) - m_nextReaped + 1];
 }
 else
 {
     return NULL;  // we asked for too many
 }
}

Next is sow()…

SOW_CROSSOVER_QTY is 0.2,
SOW_ELITE_PARTNER is 0.8.
SOW_MUTATION_QTY is 0.5.
MUTATION_RATE is 0.5.
SOW_ELITE_QTY is 0.2.
So we iterate over 20% of the population, increase sow count, get random elite mother, and 80% of the time, a random elite father (20% non-elite). Grab a reaped individual, and make it into a crossover of the parents.

Then mutations are performed on the population from 20% to 70%, passing in some scalar to the mutate().

Finally, the REAP_QTY – SOW_CROSSOVER_QTY is 10%, who are sown as randomized motor weight individuals.

Ok…

void NN3DWalkersExample::sow()

{

 int sow = 0;

 for (int i = 0; i < NUM_WALKERS * (SOW_CROSSOVER_QTY); i++)

 {  // create number of new crossover creatures

 sow++;

 b3Printf("%i Walker(s) sown.", sow);

 NNWalker* mother = getRandomElite();                                                                  // Get elite partner (mother)

 NNWalker* father = (SOW_ELITE_PARTNER < rand() / RAND_MAX) ? getRandomElite() : getRandomNonElite();  //Get elite or random partner (father)

 NNWalker* offspring = getNextReaped();

 crossover(mother, father, offspring);

 }

 

 for (int i = NUM_WALKERS * SOW_ELITE_QTY; i < NUM_WALKERS * (SOW_ELITE_QTY + SOW_MUTATION_QTY); i++)

 {  // create mutants

 mutate(m_walkersInPopulation[i], btScalar(MUTATION_RATE / (NUM_WALKERS * SOW_MUTATION_QTY) * (i - NUM_WALKERS * SOW_ELITE_QTY)));

 }

 

 for (int i = 0; i < (NUM_WALKERS - 1) * (REAP_QTY - SOW_CROSSOVER_QTY); i++)

 {

 sow++;

 b3Printf("%i Walker(s) sown.", sow);

 NNWalker* reaped = getNextReaped();

 reaped->setReaped(false);

 reaped->randomizeSensoryMotorWeights();

 }

}


Crossover goes through the joints, and sets half of the motor weights to the mom and half to the dad, randomly.

void NN3DWalkersExample::crossover(NNWalker* mother, NNWalker* father, NNWalker* child)
{
	for (int i = 0; i < BODYPART_COUNT * JOINT_COUNT; i++)
	{
		btScalar random = ((double)rand() / (RAND_MAX));

		if (random >= 0.5f)
		{
			child->getSensoryMotorWeights()[i] = mother->getSensoryMotorWeights()[i];
		}
		else
		{
			child->getSensoryMotorWeights()[i] = father->getSensoryMotorWeights()[i];
		}
	}
}

mutate() takes a mutation rate, and randomizes motor weights at that rate.


void NN3DWalkersExample::mutate(NNWalker* mutant, btScalar mutationRate)
{
	for (int i = 0; i < BODYPART_COUNT * JOINT_COUNT; i++)
	{
		btScalar random = ((double)rand() / (RAND_MAX));

		if (random >= mutationRate)
		{
			mutant->getSensoryMotorWeights()[i] = ((double)rand() / (RAND_MAX)) * 2.0f - 1.0f;
		}
	}
}

Ok good enough.

Let’s move on to the evaluation code…

Categories
AI/ML chicken research chicken_research dev ears

Chicken Song Detection

We might get into some goal modification or behaviour triggering, when the robot detects specific chicken sounds.

It seems to be a thing people have been doing for a while: https://www.scientificamerican.com/article/fowl-language-ai-decodes-the-nuances-of-chicken-ldquo-speech-rdquo/

“Over the past five years, engineers and poultry scientists at The University of Georgia and Georgia Institute of Technology have been collaborating to help farmers like Mitchell make better use of the information latent in chicken chatter. In a series of studies published between 2014 and 2016, Georgia Tech research engineer Wayne Daley and his colleagues exposed groups of six to 12 broiler chickens to moderately stressful situations—such as high temperatures, increased ammonia levels in the air and mild viral infections—and recorded their vocalizations with standard USB microphones. They then fed the audio into a machine-learning program, training it to recognize the difference between the sounds of contented and distressed birds.”

For now, just pasting links to libraries and stuff.

librosa: https://librosa.github.io/librosa/
(python package for music and audio analysis)

Pandas: https://pandas.pydata.org/
(python data analysis in general)

If I’m going to do data science, I’ll probably need to install anaconda, https://www.anaconda.com/distribution/#download-section

Let’s see how far we can get without installing Anaconda. It’s like 500MB, and we might not use any of it. Miniconda seems to be the light weight version.

Chickens make these calls for air and ground predators:

https://static.scientificamerican.com/sciam/assets/File/Aerial%20alarm%20calls.wav

https://static.scientificamerican.com/sciam/assets/File/Ground%20alarm.wav

Will get back to this another time…

Categories
control dev simulation

Understanding MotorDemo.cpp (Part 2)

Since this is the meaty bits where the robot is configured, we need to understand links and joints first. Here is the bit from the docs:

Ok so that’s like the basics, which is pretty confusingly worded here. But basically, because the body isn’t counted as a link, you have the same number of links as joints, since you add a joint and link, each time you add a new body part.

Maybe good to recall the 6 degrees of freedom:

Image result for pitch yaw roll

So the one function is localCreateRigidBody,

you can follow along in the code (We’re looking at TestRig) here:

https://github.com/bulletphysics/bullet3/blob/master/examples/DynamicControlDemo/MotorDemo.cpp

Takes mass, startTransform, and shape.

If mass is not 0, it is ‘dynamic’. If it’s dynamic, calculate the local inertia on the shape, using the mass and local inertia.

The default motion state uses the startTransform

The rigid body construction info takes mass, motion state, shape and local inertia. body is then pointing to the rigid body created using the construction info.

The rigid body is added to the world, and returned.

So the Test Rig takes the world, a vector position offset and a boolean of whether it is fixed or not.

The comment says // Setup geometry

Body size is 0.25, legs length are 0.45, foreleg lengths are 0.75 long.

shapes[0] is a capsule shape of ‘body size’ radius, and 0.1 scalar height. Then it iterates through the number of legs, adding in shapes[i] as leg capsules with .1 radius and leg length, and shapes[i+1] as foreleg capsules with .08 radius and foreleg length.

Next is // Setup rigid bodies

It just sets up the offset origin.

Next is // root

Root’s vector is set as 0.5 height, and a transform set up with origin at root. If the ‘fixed’ variable is set, the root body is set up with (mass, startTransform, and shape) of 0, offset*transform, and the capsule shape set up just now. If ‘fixed’ variable is not set, the mass is 1, same place, same shape.

Next is // legs

This bit is complicated. Angle is 2*PI*i/NUM_LEGS, to evenly distribute the legs around the root body. fSin is the sin (opposite over hypotenuse), and fCos is the cos (adjacent over hypotenuse).

So, set transform to identity. That just means like 1,1,1. Multiply a vector by identity and you get the vector, I think.

vBoneOrigin is a vector:
X: cos (bodysize + 0.5*leg length),
Y: 0.5,
Z: sin (bodysize + 0.5*leg length)

then set the transform to this vector. So, this is to find where the joint is, between the body, and the thigh. Same starting height for all legs, but X and Z plane goes around in circle.

// thigh

vToBone = (vBoneOrigin – vRoot).normalize()

Normalizing makes a unit vector (changes length to 1). So Bone Origin minus Root, normalized, means a unit vector in the direction of root, from the new thigh joint.

vAxis = vToBone.cross(vUp)
where vUp is a unit vector on the y axis.

transform.setRotation(btQuaternion(vAxis, M_PI_2));

Ok dunno. So… cross product is like a normal vector, perpendicular to both vToBone, and vUp, using a right hand rule. Both are unit vectors. So it’s like a weird sideways vector. Ok but that is called vAxis. AH. ok, so the axis is the freaking axis to rotate around. Duh. And it rotates on that axis, by PI/2. By 90 degrees.

The quaternion is just the mathematical system rotating it by 90 degrees. It must have rotated the transform to point downwards.

m_bodies[1 + 2 * i] = localCreateRigidBody(btScalar(1.), offset * transform, m_shapes[1 + 2 * i]);

Ok so that makes the thigh leg bits I guess.

// shin

transform.setIdentity();
transform.setOrigin(btVector3(btScalar(fCos * (fBodySize + fLegLength)), btScalar(fHeight – 0.5 * fForeLegLength), btScalar(fSin * (fBodySize + fLegLength))));
m_bodies[2 + 2 * i] = localCreateRigidBody(btScalar(1.), offset * transform, m_shapes[2 + 2 * i]);

So transform is moved to

X: cos (body + leg)
Y: height – 0.5*foreleg,
Z: sin (body + leg),

Ok so we already rotated 90 degrees, and now we have to move the transform to the axis for the foreleg. So the Y axis is the same for all of them, and then when you have cos and sin, that makes a circle around the y axis, at the (body+leg) distance. That might just make sense.

// Setup some damping on the m_bodies

setDamping, setDeactivationTime, setSleepingThresholds must be so the robot chills for a bit and acts a bit more earth-like.

Ok and finally

// Setup the constraints

Iterates through NUM_LEGS, same angle iteration.

//Hip joints first. (i.e. Between Body and Thigh)

localA.setIdentity();localB.setIdentity();
localA.getBasis().setEulerZYX(0, -fAngle, 0);
localA.setOrigin(btVector3(btScalar(fCos * fBodySize), btScalar(0.), btScalar(fSin * fBodySize)));
localB = m_bodies[1 + 2 * i]->getWorldTransform().inverse() * m_bodies[0]->getWorldTransform() * localA;

hingeC = new btHingeConstraint(*m_bodies[0], *m_bodies[1 + 2 * i], localA, localB);

hingeC->setLimit(btScalar(-0.75 * M_PI_4), btScalar(M_PI_8));
m_joints[2 * i] = hingeC;
m_ownerWorld->addConstraint(m_joints[2 * i], true);

Ok so A is rotated around Y axis by the current angle, and A is moved to bodySize outwards.

OK so for B… the inverse of the leg’s ‘world transform’, times the body’s ‘world transform’, times A. The body is presumably like sitting up straight, and A rotates it about Y, and then the inverse of the leg’s transform makes it point back towards origin.

And then the hinge constraint is set up with body and thigh, A and B. Limits set to -.75*PI/4 to PI/8. Apparently -33.75 degrees to 22.5 degrees.

//knee joints

localA.setIdentity();localB.setIdentity();localC.setIdentity();
localA.getBasis().setEulerZYX(0, -fAngle, 0);
localA.setOrigin(btVector3(btScalar(fCos * (fBodySize + fLegLength)), btScalar(0.), btScalar(fSin * (fBodySize + fLegLength))));
localB = m_bodies[1 + 2 * i]->getWorldTransform().inverse() * m_bodies[0]->getWorldTransform() * localA;
localC = m_bodies[2 + 2 * i]->getWorldTransform().inverse() * m_bodies[0]->getWorldTransform() * localA;
hingeC = new btHingeConstraint(*m_bodies[1 + 2 * i], *m_bodies[2 + 2 * i], localB, localC);
hingeC->setLimit(btScalar(-M_PI_8), btScalar(0.2));
m_joints[1 + 2 * i] = hingeC;
m_ownerWorld->addConstraint(m_joints[1 + 2 * i], true);

And now, same story but between leg and foreleg, from -22.5 degrees to 11.4 degrees.

Anyway, I get the idea.

Categories
3D AI/ML dev simulation

Understanding MotorDemo.cpp (Part 1)

Going through the Bullet3 physics engine example code for ‘MotorDemo’. It’s a hexapod looking robot flexing its joints. The folder is bullet3/examples/DynamicControlDemo/

https://github.com/bulletphysics/bullet3/tree/master/examples/DynamicControlDemo

Little gif of MotorDemo in action

OK so there’s the .h file containing

#ifndef MOTORDEMO_H

#define MOTORDEMO_H

 

class CommonExampleInterface* MotorControlCreateFunc(struct CommonExampleOptions& options);

 

#endif

C++ declares the function in .h files so that when compiling, all the .cpp files know about each other, without necessarily caring about the implementation details.

So this function is called ‘MotorControlCreateFunc’, which returns a pointer to a CommonExampleInterface, and takes a reference to CommonExampleOptions. Nice OOPy programming.

Let’s get into the CPP file.

Ok variables are:

time, cycle period, muscle strength, and an array of TestRig objects called rig

Constructor takes a pointer to a GUIHelperInterface. Destructor does no cleaning up.

They define an ‘initPhysics‘ and ‘exitPhysics‘. exitPhysics seems to have all the destructor code freeing up memory.

And three functions, spawnTestRig, setMotorTargets, and resetCamera.

define NUM_LEGS 6
define BODYPART_COUNT 2 * NUM_LEGS + 1
define JOINT_COUNT BODYPART_COUNT - 1

Looks like we can change some settings maybe. Will play with that later. So a leg had 2 body parts, plus there’s the central ‘body’ of the critter.

Now they define the TestRig class

btDynamicsWorld* m_ownerWorld; 
btCollisionShape* m_shapes[BODYPART_COUNT]; btRigidBody* m_bodies[BODYPART_COUNT]; btTypedConstraint* m_joints[JOINT_COUNT];

TestRig consists of a world, collision shapes, bodies and joints.

Let’s look at the rest of the code, because most of the code takes place in TestRig.

So as mentioned before, initPhysics and exitPhysics set up and shut down the physics engine gui.

So this code will be heavily copy-pasted. In this case, it sets up the 3d world, makes a ground, and spawns two test rigs. (Spider things), with a small ‘start offset’ vector from each other. They are pushed onto the rigs stack variable.

setMotorTargets takes a time as a variable. The comment is

// set per-frame sinusoidal position targets using angular motor (hacky?)

So it iterates over the number of rigs, and then twice per NUM_LEGS, sets up target hinge variables, for the animation.

Per hinge, it gets the current angle, sets the target percentage as time divided by 1000, mod the cycle period, divided by the cycle period. and then sets the target angle as 0.5 * (1 + sin (2*PI*target percentage)). Ok…

Target Limit Angle is the hinge’s lower limit, plus the ( target angle multiplied by the ( upper limit – lower limit)). Ok…

Angle Error is the target limit angle minus the current angle.

DesiredAngularVelocity is angle error / time, which is passed to each hinge’s enableAngularMotor function.

I’d be lying if I said I understood entirely. But this is pretty much the basis of the program.

void motorPreTickCallback(btDynamicsWorld* world, btScalar timeStep)
 {
     MotorDemo* motorDemo = (MotorDemo*)world->getWorldUserInfo();
 motorDemo->setMotorTargets(timeStep);
 }

It basically sets all the motor targets, based on the time that has passed, using some sort of sin-wave and the flexing-limits of the legs

Ok. More later.