sailing simulator using raspberry pi

I liked the idea of the raspberry pi for many different reasons but one thing I had thought of doing previously with arduino and laptop etc was to make a cheap and cheerful sailing simulator so people learning to sail can get the hang of tacking and gybing. I have found that novices continue to get in a muddle with swapping hands and changing sides of the boat for years after starting, often to the detriment of actally learning to sail. After all these are trivial manual skills that shouldn't take up valuable time on the water!

An ideal system (as per Frank Bethwaite and others) would provide proper feedback in terms of tipping and sheet and rudder forces. However as I know that kitchen chairs with garden canes and bits of string work very well I thought a static version with just video feedback would still work ok.

I have done the development so far on a ubuntu laptop. The simulator program is very basic and is written in python with the 3d rendering being done by panda3d. Here is a youtube of the rough, un-tuned system.

- The mark, hull, mast, boom, tiller and burgee are basic blender jobs
- The horizon is the inside of big blender cylinder with a 360deg picture from the top of a hill in the English Lakes wrapped round it (could be taken from the middle of your local pond)
- The sail is a set of five square egg-texture-cards (a panda3dism) which automatically rotate through four png images to give the luffing, flogging, oversheeting looks. The relevant object is loaded into view depending on the wind and sheet positions and the others hidden away
- The water is a tile-able (in three dimensions so it runs continously) egg-texture-card with 128 png images. The transparent areas allow a subsurface circle to be seen that makes the near water darker between reflections but the further away water light. This is what real water seems to do

I use the same algorithm that generated the perlin noise for the water tiles ( to create realistically fluctuating wind strength and direction.

Source here for people to mess around with it. It's 3.5 MB mainly because of all the water pictures! On the raspberry pi I might have to use 64x64x64 to reduce the memory.
Here are just the pythons (sorry about the lack of comments, I will pad it out soon):

from math import pi, sin, cos, radians, degrees, atan2, hypot

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from import Actor
from direct.interval.IntervalGlobal import Sequence
from panda3d.core import Point3
from panda3d.core import Fog
from panda3d.core import DirectionalLight
from panda3d.core import AmbientLight

from noise import Noise3D

class MyApp(ShowBase):
    def __init__(self):

        # Disable the camera trackball controls.
       = 0 #s
        self.dTm = 0.5 # time between each re-check of conditions
        self.lastTm = 0 #s
        self.xpos = 0 #m
        self.ypos = 0 #m
        self.tilesize = 0
        self.heading = 0 #degrees
        self.speed = 0 #m per second
        self.heel = 0 #degrees
        self.pitch = 0 #degrees
        self.sheet, self.sheetMin, self.sheetMax = 0, 5, 100 #degrees
        self.hike = 10 # abritrary scale
        self.rudder, self.rudderMin, self.rudderMax = 0, -pi/2, pi/2 #arbitrary scale
        self.sPot, self.sPotMin, self.sPotMax = 4, 0, 50 #abitrary scale would be set by calibration procedure
        self.Iturn, self.wt = 30,0.0 #rate of turn degrees/second
        self.lastXtile = -1
        self.lastYtile = -1
        self.wDirMin, self.wDirMax = 1.0, 1.25
        self.wStrMin, self.wStrMax = 3, 6

        self.sailInfo = [[1.00,2.16,3.50,4.95,6.06,6.76,7.00,6.76,6.06,4.95,3.50,1.81,0.0],[pi,0.6*pi,0.54*pi,0.58*pi,0.6*pi,0.95*pi,pi,1.05*pi,1.1*pi,1.25*pi,1.4*pi,1.45*pi,1.49*pi],[0,1,2,3,3,3,3,3,3,4,4,4,4]]
        self.rudderInfo = [[0.01, 0.03, 0.06, 0.08, 0.1, 0.11], [pi,0.52*pi,0.55*pi,0.6*pi, 0.7*pi, 0.95*pi]]
        self.noiseDir = Noise3D(128, 1/32.0, 5)
        self.noiseStr = Noise3D(128, 1/32.0, 5, 7)

        dlight = DirectionalLight('my dlight')
#       dlight.setShadowCaster(True,512,512)
        dlnp = render.attachNewNode(dlight)
        alight = AmbientLight('alight')
        alnp = render.attachNewNode(alight)

        self.seaRoot = render.attachNewNode("Sea Root")

        for i in range(-5,6):
            for j in range(-5,6):
                sea = self.loader.loadModel("water.egg")
                sea.setScale(15, 15, 15)
                if (self.tilesize == 0):
                    t1,t2 = sea.getTightBounds()
                    self.tilesize = t2.getX() - t1.getX() #sea tiles have to be square
                sea.setPos(i*self.tilesize, j*self.tilesize, 0)           

        self.subsurface = self.loader.loadModel("subsurface.egg")

        self.horizon = self.loader.loadModel("horizon.egg")
        self.hull = self.loader.loadModel("hull.egg")
#       self.hull.setShaderAuto()

        self.boom = self.loader.loadModel("boom.egg")
        self.sail = []
        for i in range(5):
            self.sail[i].setPos(0,2.5,2.6) # this is the correct height to be seen, put them under the water when not in use!
        self.burgee = self.loader.loadModel("burgee.egg")
        self.tiller = self.loader.loadModel("boom.egg")
        self.mark = self.loader.loadModel("mark.egg")
        colour = (0.6,0.6,0.9)
        expfog = Fog("Scene-wide exponential Fog object")

        self.accept('arrow_left', self.turnLeft)
        self.accept('arrow_right', self.turnRight)
        self.accept('arrow_up', self.goFaster)
        self.accept('arrow_down', self.goSlower)

        # Add the moveCameraTask and updateVariables procedure to the task manager.
        self.taskMgr.add(self.moveCameraTask, "MoveCameraTask")
        self.taskMgr.doMethodLater(self.dTm, self.updateVariables, "UpdateVariables")

    #motion control
    def turnLeft(self):
#       self.heading = (self.heading + 5) % 360
        if (self.rudder < self.rudderMax):
            self.rudder += 0.02
    def turnRight(self):
#       self.heading = (self.heading - 4.867) % 360
        if (self.rudder > self.rudderMin):
            self.rudder -= 0.02
    def goFaster(self):
         if (self.sPot > self.sPotMin):
            self.sPot -= 1
    def goSlower(self):
        if (self.sPot < self.sPotMax):
            self.sPot += 1
    # Define a procedure to move the camera, boat and subsurface; also retile the sea
    def moveCameraTask(self, task):
        if (self.lastTm == 0):
            self.lastTm = task.time
        angleRadians = radians(self.heading)
        self.xpos -= self.speed * sin(angleRadians) * (task.time - self.lastTm) * 3
        self.ypos += self.speed * cos(angleRadians) * (task.time - self.lastTm) * 3, self.ypos, 3)
        self.heading += self.wt * (task.time - self.lastTm), 0, 0)
        self.subsurface.setPos(self.xpos, self.ypos, -0.1)
        self.hull.setPos(self.xpos - 12*sin(angleRadians), self.ypos + 12*cos(angleRadians), 0.0)
        self.hull.setHpr(self.heading - 180, self.pitch, self.heel)
        self.boom.setHpr(self.sheet, 0, 0)
        # check if gone over a tile boundry and refresh tiles
        newXtile = int(self.xpos/self.tilesize) * self.tilesize
        newYtile = int(self.ypos/self.tilesize) * self.tilesize
        if (newXtile != self.lastXtile or newYtile != self.lastYtile):
            xOff = -1* int(2*sin(angleRadians))
            yOff = int(2*cos(angleRadians))
            self.seaRoot.setPos(newXtile + xOff*self.tilesize, newYtile + yOff*self.tilesize, 0)
            self.lastXtile = newXtile
            self.lastYtile = newYtile
        self.lastTm = task.time
        return task.cont
    # manage the physics of winds and forces
    def updateVariables(self, task):
        self.sheet = interpolate([self.sheetMin, self.sheetMax], self.sPot, self.sPotMin, self.sPotMax) * (1 if (self.sheet >= 0) else -1)
        wDir = interpolate([self.wDirMin, self.wDirMax],self.noiseDir.generate((int(self.xpos/8))%128, (int(self.ypos/8))%128,, -1.0, 1.0)
        wStr = interpolate([self.wStrMin, self.wStrMax],self.noiseStr.generate((int(self.xpos/8))%128, (int(self.ypos/8))%128,, -1.0, 1.0)
        v1 = sin(wDir - radians(self.heading))*wStr
        v2 = cos(wDir - radians(self.heading))*wStr + self.speed
        wDirRel = atan2(v1, v2)
        wStrRel = hypot(v1, v2)
        if (wDirRel > pi):
            wDirRel = wDirRel - 2*pi
        if (abs(wDirRel) < abs(radians(self.sheet))): # flogging sail
            self.sheet = degrees(wDirRel)
        angAtt = wDirRel - radians(self.sheet)
        if (self.sheet > 0 and wDirRel < 0):
            angAtt += 2*pi
        if (self.sheet < 0 and wDirRel > 0):
            angAtt -= 2*pi
        if (abs(angAtt) > 3.0): #gybe
            self.sheet *= -1
        area = interpolate(self.sailInfo[0], abs(angAtt), 0, pi)
        LDangle = interpolate(self.sailInfo[1], abs(angAtt), 0, pi)
        sailEgg = int(interpolate(self.sailInfo[2], abs(angAtt), 0, pi))
        for i in range(5):
        F = 0.5*wStrRel*wStrRel*area*cos(radians(self.heel))
        Fheel = F*sin(wDirRel - LDangle*(1 if (wDirRel > 0) else -1))
        self.heel = Fheel/10
        Fdrive = F*cos(wDirRel - LDangle*(1 if (wDirRel > 0) else -1))
#       print Fheel, Fdrive

        rWt = radians(self.wt)
#       rudderRel = atan2(self.speed, 0.5*rWt) - self.rudder
        rudderRel = atan2(1.0*rWt,self.speed) - self.rudder
        rArea = interpolate(self.rudderInfo[0], abs(rudderRel), 0, self.rudderMax)
        rLDangle = interpolate(self.rudderInfo[1], abs(rudderRel), 0, self.rudderMax)
        rF = 10*self.speed*self.speed*rArea
        rFdrag = -rF*cos(rLDangle)
        rFturn = rF*sin(rLDangle)*(1 if (rudderRel > 0) else -1)
        hullDrag = 2*self.speed*self.speed*(1 if (self.speed > 0) else -1)
        acc = (Fdrive - rFdrag - hullDrag)/100
        self.speed += acc*self.dTm
        if (self.speed < -0.5):
            self.speed = -0.5
        #TODO the height up the mast for turning force needs to be a static var
        wtDot = (-rFturn - 50*rWt*rWt*(1 if (rWt > 0) else -1) - Fdrive*0.25*sin(radians(self.heel)))/self.Iturn
        self.wt += degrees(wtDot*self.dTm)

#       print "rudder=%0.2f rLDangle=%0.2f rudderRel=%0.2f wt==%0.10f rFturn=%0.10f wtDot=%0.10f rArea=%0.2f" % (self.rudder,rLDangle,rudderRel, self.wt, rFturn, wtDot, rArea)
        print "speed=%0.2f LDangle=%0.2fpi cos(heel)=%0.2f F=%0.2f drive=%0.2f rudder drag=%0.2f hull drag=%0.2f wDirRel=%0.2f angAtt=%0.2f wStrRel=%0.2f" % (self.speed, LDangle/pi, cos(self.heel), F, Fdrive, rFdrag, hullDrag, degrees(wDirRel), degrees(angAtt), wStrRel) += self.dTm
        return task.again

def interpolate(valList, valIn, valMin, valMax):
    n = len(valList)
    val = (float)(valIn - valMin)/(valMax - valMin)*(n - 1.0)
    i = int(val)
    if (val <= 0):
        return valList[0]
    elif (val >= (n-1)):
        return valList[n-1]
        return (valList[i] + (valList[i+1] - valList[i])*(val - i))

app = MyApp()

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# code originally came from a forum:
# suggestion by boojum. I have added an extra dimension for time continuity

import random
import math

class Noise3D():
# initialize class with the grid size (inSize), frequency (inFreq) and number of octaves (octs)
    def __init__(self, inSize, inFreq, octs, seedVal=1):
        self.perm = range(256)
        self.perm += self.perm
        self.dirs = [(math.cos(a * 2.0 * math.pi / 256),
                 math.cos((a+85) * 2.0 * math.pi / 256),
                 math.cos((a+170) * 2.0 * math.pi / 256))
                 for a in range(256)]
        self.size = inSize
        self.freq = inFreq
        self.octs = octs

    def noise(self, x, y, z, per):
        def surflet(gridX, gridY, gridZ):
            distX, distY, distZ = abs(x-gridX), abs(y-gridY), abs(z-gridZ)
            polyX = 1 - 6*distX**5 + 15*distX**4 - 10*distX**3
            polyY = 1 - 6*distY**5 + 15*distY**4 - 10*distY**3
            polyZ = 1 - 6*distZ**5 + 15*distZ**4 - 10*distZ**3
            hashed = self.perm[self.perm[self.perm[int(gridX)%per] + int(gridY)%per] + int(gridZ)%per]
            grad = (x-gridX)*self.dirs[hashed][0] + (y-gridY)*self.dirs[hashed][1] + (z-gridZ)*self.dirs[hashed][2]
            return polyX * polyY * polyZ * grad
        intX, intY, intZ = int(x), int(y), int(z)
        return (surflet(intX+0, intY+0, intZ+0) + surflet(intX+0, intY+0, intZ+1) + surflet(intX+0, intY+1, intZ+0) +
                surflet(intX+0, intY+1, intZ+1) + surflet(intX+1, intY+0, intZ+0) + surflet(intX+1, intY+0, intZ+1) +
                surflet(intX+1, intY+1, intZ+0) + surflet(intX+1, intY+1, intZ+1))

    #return a value for noise in 3D volume (2D over time etc)
    def generate(self, x, y, z):
        val = 0
        x = x * self.freq
        y = y * self.freq
        z = z * self.freq
        per = int(self.freq * self.size)
        for o in range(self.octs):
            val += 0.5**o * self.noise(x*2**o, y*2**o, z*2**o, per*2**o)
        return val
# this is some code to use the Noise3D to create a water surface effect i.e. has transparency to allow the dark to show through
# in the foreground. Made into a simple animated tile with Panda3D.
# Other (not commented out version!) is artificial colour for visualizing noise patterns

from PIL import Image

size = 128
freq = 1/32.0
octs = 5
nObj = Noise3D(size, freq, octs, 6)

#rCurve = [0, 2, 4, 150, 12, 16, 20, 30, 40, 50, 60, 70, 90, 120, 180, 250, 255]
#gCurve = [50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 170, 200, 220, 245, 250, 250, 255]
#bCurve = [10, 20, 30, 40, 50, 60, 70, 80, 90, 110, 130, 190, 200, 253, 254, 255, 255]
#aCurve = [50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 170, 200, 220, 245, 250, 250, 255]
rCurve = [0, 0, 0, 0, 0, 0, 0, 50, 100, 150, 200, 250, 255, 200, 150, 100, 50]
gCurve = [0, 0, 100, 150, 200, 250, 255, 200, 150, 100, 50, 0, 0, 0, 0, 0, 0 ]
bCurve = [0, 20, 255, 200, 150, 125, 100, 80, 60, 40, 20, 0, 0, 0, 0, 0, 0]
aCurve = [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]

for z in range (min(10,size)):
    data = [] 
    print z 
    for y in range(size):
        for x in range(size):
            fBmVal = nObj.generate(x, y, z)
            iV = int(fBmVal*16) % 16
            data.append((int(rCurve[iV] + (rCurve[iV+1] - rCurve[iV]) * (fBmVal - iV/16)),
                    int(gCurve[iV] + (gCurve[iV+1] - gCurve[iV]) * (fBmVal - iV/16)),
                    int(bCurve[iV] + (bCurve[iV+1] - bCurve[iV]) * (fBmVal - iV/16)),
                    int(aCurve[iV] + (aCurve[iV+1] - aCurve[iV]) * (fBmVal - iV/16))))
    im ="RGBA", (size, size))
    im.putdata(data, 128.0, 128.0)"classified"+format(z, '03d')+".png")

print "finished doing it"


хранение крупы влажность

Фасоль рябая оптом

[url=]рис рапан характеристики[/url]

The Best CBD Oil for Sleep, Anxiety, Pain, and Insomnia ...

If Snoop Dogg's smash hit track "Smoke cigarettes Weed Everyday" perpetually plays in your head as well as you possess a growing rate of interest in mentioning on a selection of weed items, at that point this medical weed company might have the job opportunity of your aspirations. American Marijuana, an online health care cannabis resource, will likely view a high lot of curious applicants for a brand-new role that can make up to $36,000 a year to examine a collection of cannabis products.


ничего такого

Круто + за пост

не работает

+ за пост
казіно ігри безплатно без регистрации, [url=]играть на гривны казино[/url], ігрові автомати безплатно книжки.

Dirty Porn Photos, daily updated galleries

Teen Girls Pussy Pics. Hot galleries
young black porn hub free backdoor porn pics ang jolie porn cunnilingus video porn trailers fantastic four porn

go вакансии

работа выездной

[url=]разовая работа в москве для женщин подработка[/url]

KoreaKorea today

Korea today [url=]Korea news Korea[/url]

College Girls Porn Pics

Enjoy our scandal amateur galleries that looks incredibly dirty

true real porn site de recherche porn star maxim porn porn movies of parents fucking teens hot fat woman porn clip


[url=] China China [/url]

вполне себе годнота

Спасидо, +
[URL=]Журавский букмекеры[/URL]

Правельный выбор

выполненные с использованием практичного нержавеющего металла. Важный конструктивный элемент, часть дизайна здания, гаранты безопасности и удобного спуска и подъема по лестнице

thai New Yourk

[b]Nuru Massage Erotic - [url=]body rub in ny[/url][/b]

Hello! Our employees those who make your life easier. Society that active more than eight years.

Distinctive individuality our Turkish soap salon is not an enforced setting. We search promotion groups in a social network to advertising.
Advise all of you try whatever type massage now. Go to service and further ascertain specialty.

ripe vapes salts bazaar

[b][url=]Bad Drip vape juice[/url][/b] - this universal stop, where can find all required mods, electronic juices and supplies accessories for e-cigarettes. We respect vaping for this reason gathered in one place widest assortment goods for vaping on the market. Our most important value rightfully is service visitors, we guaranteefast delivery, best consumer quality products and affordable retail prices.

New sexy website is available on the web

Sexy pictures each day

preg sex porn couples reality porn katie lea burchill porn dinosaur king porn porn movies preggo

realsensual massag in New Yourk

[b][url=]happy ending spa for women[/url][/b]

Good day .

Top craftsmen at the present time worth its weight in gold , in this regard prices for Thai massage in Manhattan Beach rather expensive . In our SPA you can find out all the beauty useful procedure practically for nothing .

Relaxing massage of the whole body positive affects all without exception systems and organs our body:

o Muscles and Joints - improved mobility , you will recover faster after a visual load, elimination of lactic acid and recovery from exercise
o Skin - activates flow blood, occurs saturation oxygen
o Vessels - getting rid of edema , normalizing the cardiovascular system and relieving anemia
o Nervous system - improve mood , headaches go away , improve health, relief from headaches and spasms , relieve nervous tension and improve well-being .
Swedish massage more important in case losing weight and getting rid of c ellulite.
The Luxurious Classic massage in Ditmars waiting customer here.

o Impressive variety options massage techniques
o Sessions Hardware - vibration full body massage from 1 hour
Offer all of you , visit the site and learn all methods of massage personally.


Allahabad, officially known as Prayagraj, and also known as Illahabad and Prayag, is a city in the Indian state of Uttar Pradesh. It is the administrative headquarters of Allahabad district—the most populous district in the state and 13th most populous district in India—and the Allahabad division.

[url=] Prayagrai [/url]

Правильно выполняем


hydra ruz

index [url=]hydraruz[/url]


our website

Уборка квартиры

[url=]Экспресс-уборка [/url] - Бурение скважин, Сборка металлических стеллажей

бетсити промокод

[url=]1xslots промокод[/url] - бонус коды leon, winline промокод

ремонт двери цена спб

[url=https://xn--b1adccayqiirhu.xn--p1ai/services/izgotovlenie-reznyh-nakladok-na-dver]резные наличники на двери[/url] - ремонт дверей в квартире, замена роликов шкафа

стоматология московского района спб

[url=https://xn--78-6kcmzqfpcb1amd1q.xn--p1ai/uslugi/implantacziya-zubov/]стоматология импланты[/url] - протезирование зубов петербург, компьютерная диагностика зубов


[url=]XZC Core[/url] - Bitcoin SV (BSV): Blockchain Blocks, BSV

Ремонт рулевой рейки Хендай

[url=]Ремонт рулевой рейки Вольво[/url] - Ремонт рулевой рейки Форд Фокус 2, Ремонт рулевой рейки Форд Фокус 2

good website Bitniex

click here for more info [url=]Bitniex[/url]

Stairs and fences made of glass, wood, metal

лестницы и [url=]перила для лестницы[/url], цена на их изготовление продуманна

cryptocurrency transactions

click for source [url=]token protections[/url]

Интересный пост

спасибо интересное чтиво
[URL=]Real online casinos that pay real money[/URL]

добрый сайт брекеты томск

благоустроенный ресурс [url=https://xn-----6kcacs9ajdmhcwdcbwwcnbgd13a.xn--p1ai/index.php/uslugi/ortodontiya]брекеты томск[/url]

я тут придумывать

Люблю веселиться во время работы Сват, рекомендовал [url=][/url] покрутил бырабаны, мгновенно перечислил выигрыш. Кто-то скинул обалденный досуг
Сосед каждый день немного проигрывает [url=][/url] отыгрывает вейджер, перевод денег мгновенный. Кто скажет, из-за чего отсутствуют веселые чемпионаты
По вечерам делаю ставки, [url=][/url] перевод выигрыша на карту быстрый.

хорошенький вебресурс кардинг форум

годный вебресурс [url=]кардинг форум[/url] Cryptocurrency Exchange

the original source [url=] Bitcoin Exchange[/url]

ничего особенного

Супер давно искал
промокод на pin up ставки - ставки на спорт онлайн pin up.

Hot galleries, daily updated collections

Hot photo galleries blogs and pictures

granny interracial porn classic 80 s porn movies jungle best hardcore porn all time kat von d porn pics free brother sister porn stories

брекеты томск

блестящий сайт [url=https://xn-----6kcacs9ajdmhcwdcbwwcnbgd13a.xn--p1ai/index.php/uslugi/ortodontiya]брекеты томск[/url]

ладный вебсайт кардинг форум

click to find out more [url=]кардинг форум[/url]

настройка адреналин бота

[url=]l2adrenalin[/url] - купить бота, бот для ла2

anydesk free download for windows 8

blog link [url=]down load anydesk[/url]

porno150 com

click this over here now

Naked sissy cumming with vibrator in ass 3.

Chat to gay, bi and curious guys in Melbourne. Older Gent Looking for Fun with younger guys. Is full of single gay men like you looking for dates, lovers, boyfriends, friends, and fun in Patna. Watch all best Vietnamese Gay XXX vids right now! Watch all best Chubby Gay XXX vids right now! VGL is FREE and open to EVERYONE, but has become the favorite social networking app for gay millennials tired of /5(K). We cater to all your homosexual needs and make you rock hard in seconds. Straight teen dude does gay sex for cash.

клевый сайт

человечный вебресурс

Круто + за пост

Круто + за пост

хоть куда веб ресурс 1xbets

душевный вебсайт

погожий ресурс riobetcasino1

правильный вебресурс

изрядный сайт 1xbetstavkionline

фартовый вебресурс

отборный сайт booi-cazinos

отрадный вебресурс

путный вебсайт 1xbetts

изрядный вебсайт