My clever capsule

Everything related to 3D programming
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

My clever capsule

Post by Psychophanta »

Simply try to show and demonstrate once again that no possible learning strategy, or the very misnamed AI, can surpass a predetermined algorithm for any concrete task, at most equal to it in efficiency of results.
It is a vehicle within a track or road, which is in the plane (2D) or in space (3D), and this road path can be designed and modified at run time.
A single algorithm is shown here, perhaps the simplest imaginable for this task. I have another slightly more complex algorithm, theoretically more efficient, but in practice similar.
I challenge anyone to create any strategy to make algorithmic races ;))
NOTE that it is a sketch, a draft program, not free of bugs, which serves only as a sample so that if you dare and if you feel like it you can improve it, modify it as you wish, decorate it, put other "capsules" with other algorithms to run, put pretty images, make it more visually attractive, put more or less speed, share ideas, etc.
By the way, there is even much more fun to play with the code by changing things than with the "game" itself. By the way, there is hardly anything commented, to make it even more fun :D
Tested only in PB6.04 and PB6.12 in Windows, for other versions it may need to be adapted.
LeftCTRL+mouse move key allows you to orbit the camera view,
LeftCTRL+'+/-' on numeric keyboard allows camera to get closer or farer,
RightCTRL key starts or stops the capsule,
left mouse botton onto capsule allows to move it,
middle mouse button onto capsule allows to rotate it,
The tube road can be modified in a "wysiwyg" way using cursor keys and keys Repag, Prevpag, Home and End keys.
etc (look at the code)

Code: Select all

;/ inits
#ventana=1
Global pantallacompleta.b=0,Titulo$="My clever capsule"
If ExamineDesktops()=0:End:EndIf
Global bitplanes.a=DesktopDepth(0),FRX.u=DesktopWidth(0),FRY.u=DesktopHeight(0),RX.u=FRX,RY.u=FRY,FrecuenciadeMuestreo.u=60
If pantallacompleta=0
  If FRX<1280 Or FRY<720:RX=FRX*2/3:RY=FRY*2/3:Else:RX=1280:RY=720:EndIf
EndIf
If InitEngine3D(#PB_Engine3D_NoLog,#PB_Compiler_Home+"Compilers\Engine3d.dll")=0
  MessageRequester("Error","The 3D Engine can't be initialized",0):End
EndIf
If InitEngine3D(#PB_Engine3D_NoLog,#PB_Compiler_Home+"Compilers\Engine3d.dll")=0
  MessageRequester("Error","The 3D Engine can't be initialized",0):End
EndIf
;AntialiasingMode(#PB_AntialiasingMode_x4)
If InitMouse()=0 Or InitSprite()=0 Or InitKeyboard()=0:MessageRequester("Error","Can't open DirectX",0):End:EndIf
If pantallacompleta.b=2
  OpenScreen(RX,RY,bitplanes.a,Titulo$,#PB_Screen_WaitSynchronization,FrecuenciadeMuestreo.u)
Else
  OpenWindow(#ventana,0,0,RX,RY,Titulo$,#PB_Window_BorderLess|#PB_Window_ScreenCentered)
  OpenWindowedScreen(WindowID(#ventana),0,0,RX,RY,1,0,0,#PB_Screen_WaitSynchronization)
EndIf
Add3DArchive(#PB_Compiler_Home+"examples\3D\Data\GUI",#PB_3DArchive_FileSystem)
Enumeration Ventanas3D
  #TextoInfoGeneral
  #TextoInfoObjeto
EndEnumeration
Enumeration Luces
  #Luz
EndEnumeration
Enumeration Camaras
  #Camara
EndEnumeration
Enumeration Texturas
  #texturacubo1
EndEnumeration
Enumeration Materiales
  #materialrojo
  #materialverde
  #materialazul
  #materialamarillo
  #materialcapsula
  #materialvectorescapsula
  #materialtuberia
EndEnumeration
Enumeration Mallas
  #mallacapsula
  #mallavectorescapsula
  #MallaCoordenadaTuberia
  #mallatuberia
EndEnumeration
Enumeration Nodos
  #pivotcamara
  #pivottuberia
EndEnumeration
Enumeration Entidades
  #capsula
  #CoordenadaTuberia0; <- 0 a 255 entidades
  #tuberia=300
EndEnumeration
Enumeration Texto3D
  #texto1
EndEnumeration
Enumeration Splines
  #splinetuberia
EndEnumeration
CreateMaterial(#materialrojo,#Null,$3333F7):CreateMaterial(#materialverde,#Null,$33F733):CreateMaterial(#materialazul,#Null,$F93333):CreateMaterial(#materialamarillo,#Null,$33F7F9)
CreateLight(#luz,$EEEEEE,4,4,2,#PB_Light_Point):SetLightColor(#luz,#PB_Light_DiffuseColor,$EEEEEE):MoveLight(#luz,4,4,2,#PB_Absolute)
CreateCamera(#camara,0,0,100,100):CreateNode(#pivotcamara,0,0,0):AttachNodeObject(#pivotcamara,CameraID(#camara)):CameraRange(#camara,0.1,1E4):CameraBackColor(#camara,$181911)
;\
Structure D3DXVECTOR3
  x.f
  y.f
  z.f
EndStructure
Structure Vector3D Extends D3DXVECTOR3
  m.f;<-length(modulo)
EndStructure
;/ Globales:
Global.Vector3D DELTA,THETA; para puntero ratón en su uso 2D
Global.D3DXVECTOR3 pick,pickv,pos; <- para usar puntero del ratón para referir elementos 3D
Global EntidadSeleccionada.i,wireframe.a
Global.Vector3D vector,var.f ; comodin para todo
Global Origenhaz.Vector3D,haz0.Vector3D,haznormal.Vector3D,sector.Vector3D,KF.f=0.1,KL.f=0.1,capsulacel.Vector3D,medialat.Vector3D
Global capsula.a,radiocapsula.f=0.323,alturacapsula.f=1.5622,longhaz0.f=4,inclhazlat.f=1,nhacescapsula.a=6,Dim hacescapsula.Vector3D(nhacescapsula)
Global tuberia.a,nverticestuberia.a=188,nverticescirctuberia.a=10,npuntosbeziertuberia.a=10,grosortuberia.f=4,NewList puntosbeziertuberia.D3DXVECTOR3(); <- para tuberia 3D
DataSection; <- preset road for npuntosbeziertuberia.a=30
  tuberia1:
  Data.f 7.64,-1.38,-9.77
  Data.f 6.00,9.31,-14.45
  Data.f 4.23,20.10,-12.13
  Data.f 3.28,29.38,-5.68
  Data.f -0.98,37.11,-1.15
  Data.f -7.91,43.51,-2.10
  Data.f -16.62,49.39,-1.72
  Data.f -27.34,48.01,-1.87
  Data.f -38.58,46.44,-0.69
  Data.f -47.58,51.46,2.49
  Data.f -55.33,47.38,0.14
  Data.f -60.13,34.85,-11.40
  Data.f -70.14,24.43,-25.07
  Data.f -79.20,17.72,-23.31
  Data.f -79.44,13.33,-10.98
  Data.f -72.61,8.54,4.78
  Data.f -73.81,-0.76,11.38
  Data.f -83.02,-10.47,10.23
  Data.f -78.25,-22.32,12.36
  Data.f -73.13,-30.18,2.19
  Data.f -64.98,-26.57,-6.82
  Data.f -60.64,-5.78,-17.13
  Data.f -50.06,2.83,-20.78
  Data.f -35.62,-4.51,-20.46
  Data.f -27.07,-24.66,-8.24
  Data.f -17.43,-34.73,-8.13
  Data.f -7.87,-35.69,-4.52
  Data.f -1.80,-27.64,1.69
  Data.f 4.20,-19.07,3.42
  Data.f 8.12,-14.34,-3.74
  Data.f 9.23,-7.61,-8.11
EndDataSection
DataSection; <- preset road for npuntosbeziertuberia.a=30
  tuberia2:
  Data.f 7.22,0.32,-1.77
  Data.f 6.64,6.91,-12.05
  Data.f 2.43,13.98,-13.40
  Data.f -2.25,18.04,1.25
  Data.f -6.02,22.77,3.17
  Data.f -7.96,27.81,1.70
  Data.f -10.91,30.75,-1.21
  Data.f -15.83,31.32,-2.40
  Data.f -22.19,33.61,-3.71
  Data.f -27.63,33.59,-3.61
  Data.f -33.81,31.02,-0.85
  Data.f -39.11,27.28,2.81
  Data.f -42.38,21.69,-0.32
  Data.f -47.28,17.40,-0.48
  Data.f -49.58,10.89,0.67
  Data.f -49.60,5.06,-0.27
  Data.f -50.73,1.02,-1.17
  Data.f -50.18,-5.54,-1.88
  Data.f -47.92,-10.74,-2.03
  Data.f -45.42,-18.02,-9.10
  Data.f -38.48,-22.97,-9.57
  Data.f -33.43,-24.51,-4.04
  Data.f -28.51,-25.07,0.00
  Data.f -22.54,-25.70,0.00
  Data.f -17.32,-24.18,0.62
  Data.f -14.34,-21.00,1.30
  Data.f -13.11,-16.40,3.31
  Data.f -15.52,-9.47,6.04
  Data.f -7.59,-4.41,8.77
  Data.f 4.50,-4.66,4.10
  Data.f 7.02,-1.60,0.27
EndDataSection
DataSection; <- preset road for puntosbeziertuberia.a=10
  tuberia3:
  Data.f 10.76,3.27,0.98
  Data.f 9.80,7.16,-0.82
  Data.f 4.46,16.57,-1.64
  Data.f 2.32,11.02,-18.46
  Data.f -19.11,26.56,-6.86
  Data.f -20.15,8.99,6.54
  Data.f -11.07,-3.39,8.87
  Data.f -18.19,-8.89,-3.96
  Data.f -5.57,-16.64,-5.79
  Data.f 5.39,-22.60,17.67
  Data.f 9.27,-2.52,1.69
EndDataSection
;\
Macro RND(v1,v2,ndecimales=3); maximo 'ndecimales' = 9
  ;genera un número aleatorio entre los valores v1 y v2
  ;NOTE: usar con variables de punto flotante para que esta macro funcione correctamente
  (Random(1E#ndecimales#)*(v2#-v1#)/1E#ndecimales#+v1#)
EndMacro
Procedure.b MouseButtonEdgeDetection(boton.b,estado.b)
  Static mb.b:Protected i.b=1<<boton
  If estado;<- if current key status is PUSHED
    If mb&i=0:mb|i:ProcedureReturn 1:EndIf;<- if previous key status was NOT PUSHED, then assign previous state to current one, and EXIT.
  ElseIf mb&i;<- else (if previous key status was PUSHED and current key status is NOT PUSHED):
    mb!i:ProcedureReturn -1;<- set previous key status to NOT PUSHED.
  EndIf
  ProcedureReturn 0
EndProcedure
Procedure.b KeyEdgeDetection(tecla.a,estado.b)
  Static pka.a
  If estado;<-if current key status is PUSHED
    If pka=0:pka=tecla:ProcedureReturn 1:EndIf;<-if previous key status was NOT PUSHED, then assign previous state to current one, and EXIT.
  ElseIf pka=tecla;<-else (if previous key status was PUSHED and current key status is NOT PUSHED):
    pka=0:ProcedureReturn -1;<-set previous key status to NOT PUSHED.
  EndIf
  ProcedureReturn 0
EndProcedure
Macro ProductoEscalar(a,b,ax=x,ay=y,az=z,bx=x,by=y,bz=z)
  (a#\ax#*b#\bx#+a#\ay#*b#\by#+a#\az#*b#\bz#)
EndMacro
Macro getmodulo(v,vx=x,vy=y,vz=z)
  (Sqr#ProductoEscalar(v#,v#,vx#,vy#,vz#,vx#,vy#,vz#))
EndMacro
Macro ProductoVectorial(in1,in2,out,in1x=x,in1y=y,in1z=z,in2x=x,in2y=y,in2z=z,outx=x,outy=y,outz=z); <- Calculates the vectorial product of two 3D vectors. Just modify this procedure to get the vectorial product for 4D, 5D, 6D or any dimension you need.
  out#\outx#=in1#\in1y#*in2#\in2z#-in1#\in1z#*in2#\in2y#
  out#\outy#=in1#\in1z#*in2#\in2x#-in1#\in1x#*in2#\in2z#
  out#\outz#=in1#\in1x#*in2#\in2y#-in1#\in1y#*in2#\in2x#
EndMacro
Macro ProyeccionVectorial(A,B,P,Ax=x,Ay=y,Az=z,Bx=x,By=y,Bz=z,Px=x,Py=y,Pz=z)
  P#\Pz#=ProductoEscalar(A#,B#,Ax#,Ay#,Az#,Bx#,By#,Bz#)/ProductoEscalar(B#,B#,Bx#,By#,Bz#,Bx#,By#,Bz#)
  P#\Px#=P#\Pz#*B#\Bx#
  P#\Py#=P#\Pz#*B#\By#
  P#\Pz#*B#\Bz#
EndMacro
Macro ProyeccionVectorialOrtogonal(A,B,P,Ax=x,Ay=y,Az=z,Bx=x,By=y,Bz=z,Px=x,Py=y,Pz=z)
  ProyeccionVectorial(A#,B#,P#,Ax#,Ay#,Az#,Bx#,By#,Bz#,Px#,Py#,Pz#)
  P#\Px#=A#\Ax#-P#\Px#
  P#\Py#=A#\Ay#-P#\Py#
  P#\Pz#=A#\Az#-P#\Pz#
EndMacro
Procedure AnguloFormadoPor2Vectores(*in1.Vector3D,*in2.Vector3D,*out.Vector3D)
  Protected.Vector3D v1,v2:CopyMemory(*in1,@v1,SizeOf(Vector3D)):CopyMemory(*in2,@v2,SizeOf(Vector3D))
  ProductoVectorial(*in1,*in2,*out):*out\m=getmodulo(*out)
  v1\m=getmodulo(v1):v2\m=getmodulo(v2)
  If *out\m=0.0
    If v1\m=0.0 Or v2\m=0.0:FillMemory(*out,SizeOf(Vector3D),0,#PB_Byte):ProcedureReturn:EndIf
    *out\m=1.0
  EndIf
  v1\x/v1\m:v1\y/v1\m:v1\z/v1\m
  v2\x/v2\m:v2\y/v2\m:v2\z/v2\m
  v1\x-v2\x:v1\y-v2\y:v1\z-v2\z:v1\m=getmodulo(v1)
  v2\m=2*ASin(v1\m/2)
  *out\x*v2\m/*out\m:*out\y*v2\m/*out\m:*out\z*v2\m/*out\m
  *out\m=v2\m
EndProcedure
Procedure.b Rotar_Vector3D_por_adicion_Angular(*fi.Vector3D,*R0.Vector3D)
  Protected Rt.Vector3D,u.Vector3D,P0.Vector3D
  *fi\m=ProductoEscalar(*fi,*fi)
  If *fi\m
    u\m=ProductoEscalar(*R0,*fi)/*fi\m; <- aqui lo uso como variable comodin
    *fi\m=Sqr(*fi\m)
    ;Rt-> = Proyeccion de *R0-> sobre *fi->:
    Rt\x=u\m**fi\x
    Rt\y=u\m**fi\y
    Rt\z=u\m**fi\z
    ;P0-> = proyeccion ortogonal de *R0-> sobre *fi->:
    P0\x=*R0\x-Rt\x
    P0\y=*R0\y-Rt\y
    P0\z=*R0\z-Rt\z
    P0\m=getmodulo(P0)
    If P0\m<1E-5; <= no hay giro ya que *R0-> y *fi-> son colineales:
      ProcedureReturn -1
    EndIf
    ;Calcular el producto vectorial: u-> = *fi-> X *R0-> (o por P0-> daría igual)
    u\x=*fi\y**R0\z-*fi\z**R0\y
    u\y=*fi\z**R0\x-*fi\x**R0\z
    u\z=*fi\x**R0\y-*fi\y**R0\x
    ;ahora obtener *R0-> = (Proyeccion de *R0-> sobre *fi->)-> + (cos(|*fi->|)·P0-> + |P0->|/|u->|·sin(|*fi->|)·u->)->:
    *R0\x=Rt\x
    *R0\y=Rt\y
    *R0\z=Rt\z
    Rt\x=Cos(*fi\m):Rt\y=Sin(*fi\m)
    u\m=getmodulo(u)
    P0\m*Rt\y/u\m
    *R0\x+Rt\x*P0\x+P0\m*u\x
    *R0\y+Rt\x*P0\y+P0\m*u\y
    *R0\z+Rt\x*P0\z+P0\m*u\z
    ProcedureReturn 1
  EndIf
  ProcedureReturn 0
EndProcedure
Macro AnimarGiroenPropioEjeXYZ(rot,tipoobjeto,objeto,grados,veloc=5)
  seno2.d=Pow(Sin(Animaciondiferencial.d),2)
;   seno2.d=1-Abs(Cos(Animaciondiferencial.d)); con este va pero el paso no es de #PI/(grados#*2/veloc#). Interesante estudiar esto matemáticamente.
  Animaciondiferencial.d+#PI/(grados#*2/veloc#)
  If Abs(Animaciondiferencial.d)>=#PI:Animaciondiferencial.d=0:AnimacionPivotCamara.b=0
  Else:rot#(tipoobjeto#ID(objeto#),Sign(grados#)*veloc#*seno2.d,#PB_Local|#PB_Relative)
  EndIf
EndMacro
Macro OrbitarObjeto1SobreObjeto0(tecla=LeftControl,tipo1=Camera,objeto1=#Camara,tipo0=Node,objeto0=#Pivotcamara); <- el 'objeto1' orbita sobre el 'objeto0', y este debe ser nodo de 'objeto1'
  If AnimacionPivotCamara.b
    Select TipodeGiro.b
    Case 1; pitch 90 grados
      AnimarGiroenPropioEjeXYZ(Pitch,tipo0#,objeto0#,Gradosdegiro.d)
    Case 2; pitch -90 grados
      AnimarGiroenPropioEjeXYZ(Pitch,tipo0#,objeto0#,Gradosdegiro.d)
    Case 3; yaw 90 grados
      AnimarGiroenPropioEjeXYZ(Yaw,tipo0#,objeto0#,Gradosdegiro.d)
    Case 4; yaw -90 grados
      AnimarGiroenPropioEjeXYZ(Yaw,tipo0#,objeto0#,Gradosdegiro.d)
    Case 5; roll 90 grados
      AnimarGiroenPropioEjeXYZ(Roll,tipo0#,objeto0#,Gradosdegiro.d)
    Case 6; roll -90 grados
      AnimarGiroenPropioEjeXYZ(Roll,tipo0#,objeto0#,Gradosdegiro.d)
    EndSelect
  Else
    If KeyboardPushed(#PB_Key_Pad0)
      Rotate#tipo0#(objeto0#,0,0,0,#PB_Absolute)
      Move#tipo1#(objeto1#,0,0,9,#PB_Absolute)
    EndIf
    estadotecla.b=KeyboardPushed(#PB_Key_#tecla#):estadoteclaf.b=KeyEdgeDetection(#PB_Key_#tecla#,estadotecla.b)
    If estadoteclaf.b>0; <- inicia control camara
      pasocam.f=0.01:pasocamincr.f=0.001
      ShowGUI(180,0,#camara,1):CursorX0.f=CursorX.f:CursorY0.f=CursorY.f
    ElseIf estadoteclaf.b<0; <- termina control camara
      ShowGUI(180,1,#camara,1)
    ElseIf estadotecla.b; <- mover el punto de vista
      MouseLocate(CursorX0.f,CursorY0.f)
      ;para desplazar la camara hacia delante, atras, arriba, abajo, izq o der
      If DELTA\m
        If mmb.b
          Move#tipo1#(objeto1#,DELTA\x/40,-DELTA\y/40,0,#PB_Local)
        Else
          Rotate#tipo0#(objeto0#,-DELTA\y/10,-DELTA\x/10,0,#PB_Relative)
          If DELTA\z
            Move#tipo1#(objeto1#,0,0,-DELTA\z,#PB_Local)
          EndIf
        EndIf
      ElseIf KeyboardPushed(#PB_Key_Add)
        Move#tipo1#(objeto1#,0,0,-pasocam,#PB_Local)
        pasocam+pasocamincr
      ElseIf KeyboardPushed(#PB_Key_Subtract)
        Move#tipo1#(objeto1#,0,0,pasocam,#PB_Local)
        pasocam+pasocamincr
      EndIf
    ElseIf KeyboardPushed(#PB_Key_Pad8)
      If KeyboardPushed(#PB_Key_Pad5)
        Gradosdegiro.d=-90
        AnimacionPivotCamara.b=1:TipodeGiro.b=1
      Else
        Pitch(tipo0#ID(objeto0#),-0.5,#PB_Local|#PB_Relative)
      EndIf
    ElseIf KeyboardPushed(#PB_Key_Pad2)
      If KeyboardPushed(#PB_Key_Pad5)
        Gradosdegiro.d=90
        AnimacionPivotCamara.b=1:TipodeGiro.b=2
      Else
        Pitch(tipo0#ID(objeto0#),0.5,#PB_Local|#PB_Relative)
      EndIf
    ElseIf KeyboardPushed(#PB_Key_Pad4)
      If KeyboardPushed(#PB_Key_Pad5)
        Gradosdegiro.d=-90
        AnimacionPivotCamara.b=1:TipodeGiro.b=3
      Else
        Yaw(tipo0#ID(objeto0#),-0.5,#PB_Local|#PB_Relative)
      EndIf
    ElseIf KeyboardPushed(#PB_Key_Pad6)
      If KeyboardPushed(#PB_Key_Pad5)
        Gradosdegiro.d=90
        AnimacionPivotCamara.b=1:TipodeGiro.b=4
      Else
        Yaw(tipo0#ID(objeto0#),0.5,#PB_Local|#PB_Relative)
      EndIf
    ElseIf KeyboardPushed(#PB_Key_Pad7)
      If KeyboardPushed(#PB_Key_Pad5)
        Gradosdegiro.d=-90
        AnimacionPivotCamara.b=1:TipodeGiro.b=5
      Else
        Roll(tipo0#ID(objeto0#),-0.5,#PB_Local|#PB_Relative)
      EndIf
    ElseIf KeyboardPushed(#PB_Key_Pad9)
      If KeyboardPushed(#PB_Key_Pad5)
        Gradosdegiro.d=90
        AnimacionPivotCamara.b=1:TipodeGiro.b=6
      Else
        Roll(tipo0#ID(objeto0#),0.5,#PB_Local|#PB_Relative)
      EndIf
    EndIf
  EndIf
EndMacro
Macro desplazardesdepuntodevista(entidad)
  ConvertLocalToWorldPosition(CameraID(#camara),THETA\x,-THETA\y,THETA\z)
  THETA\x=GetX():THETA\y=GetY():THETA\z=GetZ():THETA\m=getmodulo(THETA)
  MoveEntity(entidad#,THETA\x,THETA\y,THETA\z,#PB_World|#PB_Relative)
EndMacro
Macro rotardesdepuntodevista(entidad)
  Swap THETA\x,THETA\y
  ConvertLocalToWorldPosition(CameraID(#camara),THETA\x,THETA\y,THETA\z)
  THETA\x=GetX():THETA\y=GetY():THETA\z=GetZ():THETA\m=getmodulo(THETA)
  EntityFixedYawAxis(entidad#,1,THETA\x/THETA\m,THETA\y/THETA\m,THETA\z/THETA\m); <- esta funcion interpreta el eje como local
  Yaw(EntityID(entidad#),Degree(THETA\m),#PB_World|#PB_Relative)
  ;EntityFixedYawAxis(entidad#,0)
EndMacro
Macro ponequitacapsula(capx=4.68,capy=10.81,capz=-14.74)
  If capsula.a
    ;capsula:
    CreateMaterial(#materialcapsula,0,$90A8D4):SetMaterialColor(#materialcapsula,#PB_Material_SelfIlluminationColor,$ABF81F)
    CreateCapsule(#mallacapsula,radiocapsula,alturacapsula,2,8,1):TransformMesh(#mallacapsula,0,0,0,1,1,1,-90,0,0,0)
    CreateEntity(#capsula,MeshID(#mallacapsula),MaterialID(#materialcapsula),capx#,capy#,capz#)
  Else
    FreeEntity(#capsula)
    FreeMesh(#mallacapsula)
    FreeMaterial(#materialcapsula)
  EndIf
EndMacro
Procedure TuberiaBezier(actualizar.b=1,color.l=$43D4E2)
  Protected sp1.Vector3D,sp0.Vector3D,sp.Vector3D,vector0.Vector3D,compas.Vector3D,radio.Vector3D,i.a,j.a,texcoordu.f,texcoordv.f,ncara.u=0,Dim primercirculo.D3DXVECTOR3(nverticescirctuberia)
  If actualizar
    ForEach puntosbeziertuberia()
      UpdateSplinePoint(#splinetuberia,ListIndex(puntosbeziertuberia()),puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z)
    Next
    If IsEntity(#tuberia):FreeEntity(#tuberia):EndIf; o bien si la malla va con 'node' en lugar de con 'entity': ;UpdateMesh(#mallatuberia,0)
  Else
    CreateSpline(#splinetuberia)
    CreateSphere(#MallaCoordenadaTuberia,0.05)
    CreateMaterial(#materialtuberia,0,color):DisableMaterialLighting(#materialtuberia,1)
    ForEach puntosbeziertuberia()
      AddSplinePoint(#splinetuberia,puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z)
      CreateEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia()),MeshID(#MallaCoordenadaTuberia),#PB_Material_None,puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z,0)
    Next
  EndIf
  CreateMesh(#mallatuberia,#PB_Mesh_TriangleList,#PB_Mesh_Static); <- quitar si la malla va con 'node' en lugar de con 'entity'
  ComputeSpline(#splinetuberia,0.0):sp\x=SplineX(#splinetuberia):sp\y=SplineY(#splinetuberia):sp\z=SplineZ(#splinetuberia); <- tiempo 0
  sp0=sp; <- inicio nulo
  FillMemory(@vector,SizeOf(Vector3D)); <- inicio nulo
  For i=0 To nverticestuberia; <- recorrer toda la linea spline; desde tiempo=0 hasta tiempo=1 en la spline
    sp1=sp0
    sp0=sp
    ComputeSpline(#splinetuberia,(i+1.0)/(nverticestuberia+1.0))
    sp\x=SplineX(#splinetuberia):sp\y=SplineY(#splinetuberia):sp\z=SplineZ(#splinetuberia)
    vector0=vector
    vector\x=sp\x-sp0\x:vector\y=sp\y-sp0\y:vector\z=sp\z-sp0\z:vector\m=getmodulo(vector); <- vector sentido del tiempo spline
    vector\x*2*#PI/vector\m/nverticescirctuberia:vector\y*2*#PI/vector\m/nverticescirctuberia:vector\z*2*#PI/vector\m/nverticescirctuberia; <- vector sentido del tiempo spline modulado
    compas\x=0:compas\y=grosortuberia/2:compas\z=0:compas\m=compas\y
    If i=0
      ProyeccionVectorialOrtogonal(compas,vector,radio):radio\m=getmodulo(radio)
      If radio\m<1E-2
        Swap compas\y,compas\z
        ProyeccionVectorialOrtogonal(compas,vector,radio):radio\m=getmodulo(radio)
      EndIf
    Else
      ProyeccionVectorialOrtogonal(radio,vector,compas):compas\m=getmodulo(compas)
      vector0\x*grosortuberia/2/vector0\m:vector0\y*grosortuberia/2/vector0\m:vector0\z*grosortuberia/2/vector0\m
      ProyeccionVectorialOrtogonal(vector0,vector,radio):radio\m=getmodulo(radio)
      If radio\m<compas\m:radio=compas:EndIf
    EndIf
    radio\x*compas\m/radio\m:radio\y*compas\m/radio\m:radio\z*compas\m/radio\m
    For j=0 To nverticescirctuberia
      If i=0
        primercirculo(j)\x=sp0\x+radio\x:primercirculo(j)\y=sp0\y+radio\y:primercirculo(j)\z=sp0\z+radio\z
      EndIf
      Rotar_Vector3D_por_adicion_Angular(@vector,@radio)
      MeshVertex(sp0\x+radio\x,sp0\y+radio\y,sp0\z+radio\z,texcoordu.f,texcoordv.f,color)
    Next
  Next
  ;Para no cerrar el lazo, comentar esta linea:
  For j=0 To nverticescirctuberia:MeshVertex(primercirculo(j)\x,primercirculo(j)\y,primercirculo(j)\z,texcoordu.f,texcoordv.f,color):Next
  For i=0 To nverticestuberia
    For j=1 To nverticescirctuberia
      ncara+1
      MeshFace(ncara,ncara+nverticescirctuberia,ncara+nverticescirctuberia+1)
      MeshFace(ncara,ncara-1,ncara+nverticescirctuberia)
    Next
    ncara+1
  Next
  FinishMesh(1); <- FinishMesh(0) si la malla va con 'node' en lugar de con 'entity'
;   NormalizeMesh(#mallatuberia,0):UpdateMeshBoundingBox(#mallatuberia)
  CreateEntity(#tuberia,MeshID(#mallatuberia),MaterialID(#materialtuberia),0,0,0); <- si la malla va con 'node' quitar esta linea
  FreeArray(primercirculo.D3DXVECTOR3())
EndProcedure
Procedure ponequitatuberiabezier()
  If tuberia.a
    TuberiaBezier(0)
  Else
    If IsEntity(#tuberia):FreeEntity(#tuberia):EndIf
    If IsMesh(#mallatuberia):FreeMesh(#mallatuberia):EndIf
    If IsMaterial(#materialtuberia):FreeMaterial(#materialtuberia):EndIf
    ForEach puntosbeziertuberia()
      If IsEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia())):FreeEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia())):EndIf
    Next
    If IsMesh(#MallaCoordenadaTuberia):FreeMesh(#MallaCoordenadaTuberia):EndIf
    If IsNode(#pivottuberia):FreeNode(#pivottuberia):EndIf
;     If ListSize(puntosbeziertuberia.D3DXVECTOR3())>0:FreeList(puntosbeziertuberia.D3DXVECTOR3()):EndIf
    FreeSpline(#splinetuberia)
  EndIf
EndProcedure
Macro grabarformatuberia()
  ;     Grab the road shape:
  OpenConsole("Get coords",#PB_Ascii)
  ClearConsole()
  PrintN("Grab the coords of the spline points:")
  PrintN("DataSection")
  foreach puntosbeziertuberia()
    PrintN("  Data.f "+StrF(puntosbeziertuberia()\x,2)+","+StrF(puntosbeziertuberia()\y,2)+","+StrF(puntosbeziertuberia()\z,2))
  next
  PrintN("EndDataSection")
  PrintN("")
  PrintN("Initial position fo the capsule: "+StrF(EntityX(#capsula),2)+","+StrF(EntityY(#capsula),2)+","+StrF(EntityZ(#capsula),2))
  PrintN("")
  PrintN("Number of rings in road: "+Str(nverticestuberia))
  PrintN("")
  PrintN("Press a key to exit")
  While Inkey()="":Delay(20):Wend
  CloseConsole()
EndMacro
Macro reiniciar()
  posicion.D3DXVECTOR3:fiposicion.Vector3D
  Restore tuberia3
  For i.a=0 To npuntosbeziertuberia
    AddElement(puntosbeziertuberia())
;     If i=0
;       puntosbeziertuberia()\x=10:puntosbeziertuberia()\y=0:puntosbeziertuberia()\z=0
;       posicion=puntosbeziertuberia()
;     ElseIf i=1
;       puntosbeziertuberia()\x=posicion\x:puntosbeziertuberia()\y=posicion\y+10:puntosbeziertuberia()\z=posicion\z
;       vector\x=puntosbeziertuberia()\x-posicion\x:vector\y=puntosbeziertuberia()\y-posicion\y:vector\z=puntosbeziertuberia()\z-posicion\z
;       puntosbeziertuberia()\x=posicion\x+vector\x:puntosbeziertuberia()\y=posicion\y+vector\y:puntosbeziertuberia()\z=posicion\z+vector\z
;       posicion=puntosbeziertuberia()
;     Else
;       fiposicion\x=0:fiposicion\y=0:fiposicion\z=2*#PI/npuntosbeziertuberia
;       Rotar_Vector3D_por_adicion_Angular(@fiposicion,@vector)
;       puntosbeziertuberia()\x=posicion\x+vector\x:puntosbeziertuberia()\y=posicion\y+vector\y:puntosbeziertuberia()\z=posicion\z+vector\z
;       posicion=puntosbeziertuberia()
;     EndIf
    Read.f puntosbeziertuberia()\x:Read.f puntosbeziertuberia()\y:Read.f puntosbeziertuberia()\z
  Next
  MoveNode(#Pivotcamara,0,0,0,#PB_Absolute):RotateNode(#Pivotcamara,0,0,0,#PB_Absolute)
  MoveCamera(#camara,0,0,54,#PB_Absolute)
  ReleaseMouse(0)
  ShowGUI(180,1,#camara,1)
  InputEvent3D(0,0,0); <- para el cursor del ratón nada más.
  MouseLocate(RX/2,RY/2)
  tuberia.a=1:ponequitatuberiabezier()
  capsula.a=1:ponequitacapsula()
  wireframe.a=1:CameraRenderMode(#camara,#PB_Camera_Wireframe)
  EntidadSeleccionada.i=0
  run.a=1
EndMacro
reiniciar()
;/ BUCLE
Repeat
  Repeat:evento.i=WindowEvent():Until evento=#PB_Event_None
  ExamineMouse():ExamineKeyboard()
  CursorX.f=MouseX():CursorY.f=MouseY():lmb.b=MouseButton(#PB_MouseButton_Left):rmb.b=MouseButton(#PB_MouseButton_Right):mmb.b=MouseButton(#PB_MouseButton_Middle)
  DELTA\x=MouseDeltaX():DELTA\y=MouseDeltaY():DELTA\z=MouseWheel():DELTA\m=getmodulo(DELTA)
  OrbitarObjeto1SobreObjeto0(LeftControl,Camera,#camara,Node,#Pivotcamara)
  InputEvent3D(CursorX.f,CursorY.f,lmb.b); <- para el cursor del ratón nada más.
  lmbe.b=MouseButtonEdgeDetection(#PB_MouseButton_Left,lmb)
  mmbe.b=MouseButtonEdgeDetection(#PB_MouseButton_Middle,mmb)
  If lmbe.b=1 Or mmbe.b=1
    ShowGUI(180,0,#camara,1)
    EntidadSeleccionada.i=MouseRayCast(#camara,CursorX,CursorY,-1)
    Select EntidadSeleccionada.i
    Case #capsula:EntidadSeleccionada$="capsula"
    Case #CoordenadaTuberia0 To #CoordenadaTuberia0+255:EntidadSeleccionada$="Point"+Str(EntidadSeleccionada-#CoordenadaTuberia0)
    Default
      If tuberia.a
        ;buscar el punto spline más cercano al puntero
        mindistanciaspuntostuberia.f=4000
        ForEach puntosbeziertuberia()
          px.f=CameraProjectionX(#Camara,puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z)
          py.f=CameraProjectionY(#Camara,puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z)
          distanciaspuntostuberia.f=Sqr(Pow(px-CursorX,2)+Pow(py-CursorY,2))
          If mindistanciaspuntostuberia>distanciaspuntostuberia
            mindistanciaspuntostuberia=distanciaspuntostuberia
            nodotuberia.a=ListIndex(puntosbeziertuberia())
          EndIf
        Next
        EntidadSeleccionada.i=#CoordenadaTuberia0+nodotuberia
        EntidadSeleccionada$="Point"+Str(nodotuberia)
      Else
        EntidadSeleccionada$=""
      EndIf
    EndSelect
    If IsEntity(EntidadSeleccionada)
      anchuratexto.u=Len(EntidadSeleccionada$)*18
      pick\x=PickX():pick\y=PickY():pick\z=PickZ()
      pos\x=EntityX(EntidadSeleccionada):pos\y=EntityY(EntidadSeleccionada):pos\z=EntityZ(EntidadSeleccionada)
      ;pickv\x=CameraProjectionX(#camara,pick\x,pick\y,pick\z)-CameraProjectionX(#camara,pos\x,pos\y,pos\z)
      ;pickv\y=CameraProjectionY(#camara,pick\x,pick\y,pick\z)-CameraProjectionY(#camara,pos\x,pos\y,pos\z)
      ;pickv\z=0
      pick=pos
      OpenWindow3D(#TextoInfoObjeto,CameraProjectionX(#camara,pick\x,pick\y,pick\z),CameraProjectionY(#camara,pick\x,pick\y,pick\z),anchuratexto.u,32,"detalles objeto",#PB_Window3D_SizeGadget|#PB_Window3D_BorderLess)
      TextGadget3D(#TextoInfoObjeto,0,-1,anchuratexto.u,32,EntidadSeleccionada$)
    EndIf
  ElseIf lmbe.b=-1 Or mmbe.b=-1
    If IsWindow3D(#TextoInfoObjeto)
      FreeGadget3D(#TextoInfoObjeto)
      CloseWindow3D(#TextoInfoObjeto)
    EndIf
    ShowGUI(180,1,#camara,1)
    If IsEntity(EntidadSeleccionada)
      pos\x=EntityX(EntidadSeleccionada):pos\y=EntityY(EntidadSeleccionada):pos\z=EntityZ(EntidadSeleccionada)
      MouseLocate(CameraProjectionX(#camara,pos\x,pos\y,pos\z),CameraProjectionY(#camara,pos\x,pos\y,pos\z))
      EntidadSeleccionada$=""
    EndIf
  EndIf
  If DELTA\m And IsWindow3D(#TextoInfoObjeto)
    If IsEntity(EntidadSeleccionada)
      THETA=DELTA:THETA\x/100:THETA\y/100:THETA\z/20:THETA\m=getmodulo(THETA)
      If lmb.b; mover
        pos\x=EntityX(EntidadSeleccionada):pos\y=EntityY(EntidadSeleccionada):pos\z=EntityZ(EntidadSeleccionada)
        pick\x=CameraProjectionX(#camara,pos\x,pos\y,pos\z);+pickv\x
        pick\y=CameraProjectionY(#camara,pos\x,pos\y,pos\z);+pickv\y
        ResizeWindow3D(#TextoInfoObjeto,pick\x,pick\y,anchuratexto.u,#PB_Ignore)
        desplazardesdepuntodevista(EntidadSeleccionada); <- mover
        If tuberia.a And FindString(EntidadSeleccionada$,"Point")
          nodotuberia.a=Val(RemoveString(EntidadSeleccionada$,"Point"))
          SelectElement(puntosbeziertuberia(),nodotuberia)
          puntosbeziertuberia()\x+THETA\x:puntosbeziertuberia()\y+THETA\y:puntosbeziertuberia()\z+THETA\z
;             If Listindex(puntosbeziertuberia())=0
;               CopyMemory(@puntosbeziertuberia(),@vector,Sizeof(D3DXVECTOR3))
;               LastElement(puntosbeziertuberia())
;               MoveEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia()),vector\x,vector\y,vector\z,#PB_Absolute|#PB_World); <- mover
;               CopyMemory(@vector,@puntosbeziertuberia(),Sizeof(D3DXVECTOR3))
;               FirstElement(puntosbeziertuberia())
;             ElseIf Listindex(puntosbeziertuberia())=ListSize(puntosbeziertuberia())-1
;               CopyMemory(@puntosbeziertuberia(),@vector,Sizeof(D3DXVECTOR3))
;               FirstElement(puntosbeziertuberia())
;               MoveEntity(#CoordenadaTuberia0,vector\x,vector\y,vector\z,#PB_Absolute|#PB_World); <- mover
;               CopyMemory(@vector,@puntosbeziertuberia(),Sizeof(D3DXVECTOR3))
;               LastElement(puntosbeziertuberia())
;             EndIf
          TuberiaBezier()
        EndIf
      ElseIf mmb.b; rotar
        rotardesdepuntodevista(EntidadSeleccionada)
      ElseIf rmb.b; menu
      EndIf
    Else
      FreeGadget3D(#TextoInfoObjeto)
      CloseWindow3D(#TextoInfoObjeto)
    EndIf
  EndIf
  If EntidadSeleccionada$="":MouseLocate(CursorX,CursorY):EndIf
  If KeyboardReleased(#PB_Key_W):wireframe.a!1:If wireframe.a:CameraRenderMode(#camara,#PB_Camera_Wireframe):Else:CameraRenderMode(#camara,#PB_Camera_Textured):EndIf
  ElseIf KeyboardPushed(#PB_Key_Right)
    If tuberia
      If grosortuberia<20:grosortuberia+0.01:TuberiaBezier():EndIf
    EndIf
  ElseIf KeyboardPushed(#PB_Key_Left) 
    If tuberia
      If grosortuberia>0.01:grosortuberia-0.01:TuberiaBezier():EndIf
    EndIf
  ElseIf KeyboardPushed(#PB_Key_Up)
    If tuberia
      If nverticestuberia<255:nverticestuberia+1:TuberiaBezier():EndIf
    EndIf
  ElseIf KeyboardPushed(#PB_Key_Down)
    If tuberia
      If nverticestuberia>npuntosbeziertuberia:nverticestuberia-1:TuberiaBezier():EndIf
    EndIf
  ElseIf KeyboardReleased(#PB_Key_Home)
    If tuberia
      If nverticescirctuberia<255:nverticescirctuberia+1:TuberiaBezier():EndIf
    EndIf
  ElseIf KeyboardReleased(#PB_Key_End)
    If tuberia
      If nverticescirctuberia>3:nverticescirctuberia-1:TuberiaBezier():EndIf
    EndIf
  ElseIf KeyboardReleased(#PB_Key_PageUp)
    If tuberia
      If npuntosbeziertuberia<30:npuntosbeziertuberia+1
        ClearSpline(#splinetuberia)
        LastElement(puntosbeziertuberia())
        CopyMemory(@puntosbeziertuberia(),@vector,Sizeof(D3DXVECTOR3))
        AddElement(puntosbeziertuberia())
        CopyMemory(@vector,@puntosbeziertuberia(),Sizeof(D3DXVECTOR3))
        ForEach puntosbeziertuberia()
          If IsEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia())):FreeEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia())):EndIf
          AddSplinePoint(#splinetuberia,puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z)
          CreateEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia()),MeshID(#MallaCoordenadaTuberia),#PB_Material_None,puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z)
        Next
        TuberiaBezier()
      EndIf
    EndIf
  ElseIf KeyboardReleased(#PB_Key_PageDown)
    If tuberia
      If npuntosbeziertuberia>2:npuntosbeziertuberia-1
        ClearSpline(#splinetuberia)
        LastElement(puntosbeziertuberia())
        If IsEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia())):FreeEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia())):EndIf
        DeleteElement(puntosbeziertuberia(),1)
        ForEach puntosbeziertuberia()
          If IsEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia())):FreeEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia())):EndIf
          AddSplinePoint(#splinetuberia,puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z)
          CreateEntity(#CoordenadaTuberia0+ListIndex(puntosbeziertuberia()),MeshID(#MallaCoordenadaTuberia),#PB_Material_None,puntosbeziertuberia()\x,puntosbeziertuberia()\y,puntosbeziertuberia()\z)
        Next
        TuberiaBezier()
      EndIf
    EndIf
  ElseIf KeyboardReleased(#PB_Key_RightControl):run.a!1
  EndIf
  If run.a
    Origenhaz\x=EntityX(#capsula)+EntityDirectionX(#capsula)*alturacapsula/2
    Origenhaz\y=EntityY(#capsula)+EntityDirectionY(#capsula)*alturacapsula/2
    Origenhaz\z=EntityZ(#capsula)+EntityDirectionZ(#capsula)*alturacapsula/2
    haz0\x=EntityDirectionX(#capsula)*alturacapsula*longhaz0
    haz0\y=EntityDirectionY(#capsula)*alturacapsula*longhaz0
    haz0\z=EntityDirectionZ(#capsula)*alturacapsula*longhaz0
    haz0\m=getmodulo(haz0)
    FetchOrientation(EntityID(#capsula))
    Pitch(EntityID(#capsula),90,#PB_Relative|#PB_Local)
    haznormal\x=EntityDirectionX(#capsula)*alturacapsula*longhaz0*inclhazlat
    haznormal\y=EntityDirectionY(#capsula)*alturacapsula*longhaz0*inclhazlat
    haznormal\z=EntityDirectionZ(#capsula)*alturacapsula*longhaz0*inclhazlat
    SetOrientation(EntityID(#capsula),GetX(),GetY(),GetZ(),GetW()); o bien Pitch(EntityID(#capsula),-90,#PB_Relative|#PB_Local)
    sector\x=haz0\x*2*#PI/nhacescapsula/haz0\m:sector\y=haz0\y*2*#PI/nhacescapsula/haz0\m:sector\z=haz0\z*2*#PI/nhacescapsula/haz0\m
    i.a=0
    If RayCast(Origenhaz\x,Origenhaz\y,Origenhaz\z,haz0\x,haz0\y,haz0\z,-1)=#tuberia
      hacescapsula(i)\x=PickX()-Origenhaz\x:hacescapsula(i)\y=PickY()-Origenhaz\y:hacescapsula(i)\z=PickZ()-Origenhaz\z:hacescapsula(i)\m=getmodulo(hacescapsula(i)); <- haces distancia
    Else
      FillMemory(@hacescapsula.Vector3D(i),SizeOf(Vector3D))
    EndIf
    For i.a=1 To nhacescapsula
      If i.a=1
        hacescapsula(i)\x=haz0\x+haznormal\x:hacescapsula(i)\y=haz0\y+haznormal\y:hacescapsula(i)\z=haz0\z+haznormal\z:hacescapsula(i)\m=getmodulo(hacescapsula(i))
      Else
        hacescapsula(i)=hacescapsula(i-1)
        Rotar_Vector3D_por_adicion_Angular(@sector,@hacescapsula(i))
      EndIf
      If RayCast(Origenhaz\x,Origenhaz\y,Origenhaz\z,hacescapsula(i)\x,hacescapsula(i)\y,hacescapsula(i)\z,-1)=#tuberia
        hacescapsula(i)\x=PickX()-Origenhaz\x:hacescapsula(i)\y=PickY()-Origenhaz\y:hacescapsula(i)\z=PickZ()-Origenhaz\z:hacescapsula(i)\m=getmodulo(hacescapsula(i)); <- haces distancia
      Else
        FillMemory(@hacescapsula.Vector3D(i),SizeOf(Vector3D))
      EndIf
    Next
    FillMemory(@vector.Vector3D,SizeOf(Vector3D)); <- inicio nulo
    For i.a=1 To nhacescapsula
      vector\x+hacescapsula(i)\x
      vector\y+hacescapsula(i)\y
      vector\z+hacescapsula(i)\z
    Next:vector\x/nhacescapsula:vector\y/nhacescapsula:vector\z/nhacescapsula:vector\m=getmodulo(vector)
    If vector\m>1E-3
      ProyeccionVectorialOrtogonal(vector,hacescapsula(0),medialat)
      capsulacel\x=hacescapsula(0)\x*KF + medialat\x*KL
      capsulacel\y=hacescapsula(0)\y*KF + medialat\y*KL
      capsulacel\z=hacescapsula(0)\z*KF + medialat\z*KL
      EntityDirection(#capsula,capsulacel\x,capsulacel\y,capsulacel\z,#PB_World,#PB_Vector_NegativeZ)
      MoveEntity(#capsula,capsulacel\x,capsulacel\y,capsulacel\z,#PB_World|#PB_Relative)
    EndIf
    If KeyboardReleased(#PB_Key_G):grabarformatuberia():EndIf
  EndIf
  TimeSinceLastFrame.i=RenderWorld(50)
  FlipBuffers()
  Delay(6)
Until KeyboardPushed(#PB_Key_Escape)
ADDENDUM:
https://mega.nz/file/c050WZTA#Y5SbOuzcd ... arqw2VwFps

The same absurd algorithm using the 2D tracks of these ones:
https://www.youtube.com/watch?v=wL7tSgUpy8w
https://www.youtube.com/watch?v=-sg-GgoFCP0 y
https://www.youtube.com/watch?v=3myQK1WXEqE

... by the way: in PB6.20(x86), this is much slower than PB6.04(x86), at least in windows.
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;