RSX / RCX & REX Files {Scripting}

Theoretically I think maybe I could do a trailer. I'd have to see how some of the scriptfuncs actually work though.

Very interesting problem :p

Do you happen to have any trailer models or am I gonna have to start pulling around Lamborghinis? I'm thinking export the trailer as a car, with no driven wheels on the ground.

EDIT:
First foray into it - finding a bit of a problem. The following
Code:
set (pbody) pos (float[3])
Is fine, works on cars. I can move them around all I want.

This, on the other hand
Code:
add (pbody) bodyforce (float[3]) at (float[3])
Comment:
Adds bodyforce of physics-body at body position
Doesn't seem to have any effects. Unless the units are some small thing, and 10,000 is not enough to visibly affect the vehicle.

My intention was to add forces located at the trailer hitch, pulling the two cars together, but that can't work as is.

I'll try some alternatives tomorrow. A wing with the appropriate downforce/drag would work, but is a pretty huge kludge to attempt.
Yeah I thought it was an interesting problem, as up to this point, you couldn't really put in a working trailer.

I don't have any trailer models at the moment, I could probably fab one up, or find one online, but I'll do that tomorrow.

Yeah a wing with a bunch of downforce would work, if the vehicle was in motion, otherwise it would sit like there was nothing, of course set it too high and you'll have issues driving at higher speeds. Of course since i know nothing about scripts, I'm useless lol
 
I can also confirm now that this doesn't work for creating joints linking cars:
Code:
create joint (pbody) with parent (pbody) with offset (float[3])

Looks like using wing signals to tweak the wing in realtime might be the way to go. The trouble is, that doesn't handle lateral forces (wings only do downforce and drag), and of course when stopped, it'll behave badly. The trailer might also need powered wheels it can throttle up in order to get moving in the first place.


EDIT: Here's code for a fairly simple 'jet car' script (just playing around with wing forces) / drop into car/scripts/physics/jato.rsx:
Code:
rcar $car = get scriptowner car
shared float $curforce
float $targetforce = 10000.0
float $throttle = 0.0
float $reqsignal = 1.0
while 1
{
// thrust using wings
$curforce = get system "car0.wing0.force_z"
$throttle = get $car throttle
if $curforce < $targetforce*$throttle {
  // ramp up thrust if target force is not met (multiplied by throttle so it's controllable)
  $reqsignal = $reqsignal*1.01
  // stop the number from growing forever
  if $reqsignal > 1000000.0 {
    $reqsignal = 1000000.0
  }
}
if $curforce > $targetforce*$throttle {
  // pull back on thrust if target force is exceeded.
  $reqsignal = $reqsignal*0.99
  if $reqsignal < 0.00000001 {
    $reqsignal = 0.00000001
  }
}
set system "car0.wing0.coeff_drag" to -$reqsignal
interrupt
}
Driving the jet car:
In 1st, accelerate to about 1kph.
Shift into neutral.
Hit the throttle.
Plan for lots of stopping distance. When I strapped this to the RX-7 (at 10,000N it's about 2 of the solid fuel rockets mythbusters used), it hit 400km/h on the Carlswood mile-long straight. Due to something-or-other, it also won't throttle down below 1/10 of max thrust... which compounds the braking issues.

You should probably set wing0 where you want the thrust to emanate, or change that in the script, too.
 
Hehe, I've done something similar which reacts much more jet like... I use the throttle and rpm to dictate the thrust generated, and then simply have a very weak engine that just spins up and down against a slipping clutch.

That low torque output also gets you moving off the line nicely.

I use an inverse square rule for the wing drag force too, so as velocity increases the thrust doesn't go through the roof :D


Yes, I've thought about this too much hehe... I'm working on a jet powered racing car type thing... like LMP1 but with jet engines, 400mph top speeds, lots of active aero and stuff to get breaking speeds that are useful etc :D



Ultimately I'd prefer just to run the engine in isolation (neutral), so it revs up and down at the rough rpm a real jet engine would do, and then I can simply tie a force along the car axis set to look at throttle position and rpm :D

But for now it kinda works. Setting off isn't perfect but it works ok..


Much better than my old fudged version which used rear tyre b2 values of 15000 or something crazy so you could get the acceleration needed for a 4g launch without any wheel spin :D


Dave
 
I'm actually wondering a little if an aircraft could be fudged using dynamic wings...

I was thinking use the throttle as engine throttle, brakes or clutch signal for attitude control, and steering to turn. Then if you had a yoke you could set the axes to the appropriate controls.

With proper generic models you could even see the flaps flapping.


Not that Racer would be a good plane sim but it'd be an interesting mod :p



A more practical script, which I am gonna get around to in a minute, is dynamic camber to simulate a solid axle. I just need to dig up my F100 and figure out the signals and geometry, then see if it behaves at all sanely.

edit: nope :/ Wheels don't have any accessible signals (either for current suspension length or for camber - car0.wheel0.camber always returns the parameter, not the signal)



Instead I implemented a continuously variable transmission. Set the car.ini to gears=2 (one reverse, one forward) - this script will adjust the forward gear's ratio continuously to hit specified RPMs.
Drop into car/scripts/physics/cvt.rsx or similar.
Code:
// Continuously variable transmission: Uses gear 1 of the car.
// approximate 'efficient' rpm
float $minrpm = 1400
// approximate 'max acceleration' rpm
float $maxrpm = 3300
// highest ratio possible, used at low speeds
float $minrat = 15.0
// lowest ratio possible, used at high speeds
float $maxrat = 0.9
// tracking vars
float $throttle = 0
float $rpm = 0
float $tt = 0
rcar $car = get scriptowner car
// grab the string associated with this car
int $ncars = get carcount
int $i = 0
rcar $ccar = get car $i
while $car != $ccar {
  $i = $i + 1
  $ccar = get car $i
}
string $req = "car" + $i
// $req now contains car0, car1, whatever this car is.
// tag on the specific thing we're modifying - gear2 is 1st forward gear
string $out = $req + ".gearbox.gear2_ratio"
float $ratio = get system $out
while 1
{
  // use throttle^2 to determine target rpm between min and max
  // at full throttle it aims for maxrpm, at no throttle, min.
  $throttle = get $car throttle
  $throttle = $throttle*$throttle
  $rpm = get $car rpm
  $tt = ($maxrpm-$minrpm)*$throttle + $minrpm
  // update transmission ratio to approach correct speed - constant affects speed it varies
  $ratio = $ratio + 0.00001*($tt-$rpm)
  // check ratio's not too high
  if $ratio < $maxrat {
    $ratio = $maxrat
  }
  // and not too low
  if $ratio > $minrat {
    $ratio = $minrat
  }
  // update system variable
  set system $out to $ratio
  interrupt
}
Basically it varies between min rpm and max rpm, which should be set to the optimal rpm range for the engine (from most efficient to most powerful). If you want it to just go for a specific RPM, set them equal. The final gear and min ratio will affect the car's top speed.

I hope people are finding it helpful if I post various scripts, to see how stuff goes.
 
Thanks for sharing, definitely nice to get some fresh ideas.

I tried your CVT script and added a check "if $gear == 2" for the main body, so the vehicle doesn't stall when trying to adjust ratios for reverse driving.
Personally, I'm also using a higher exponent throttle function, ie. 3 feels like a good mix between smoothness and allowing economic driving.

What is the purpose of catching the index of vehicles? Is that limiting access to the script (though scriptowner should do that already) or extending it for traffic or so?



A few posts up you mentioned using interval since it's supposed to be independent of framerate, but by definition and from testing it out it's actually purely framerate dependent, I found. It's the reciprocal of framerate and even if you have vsync enabled for example, it jumps around a bit all the time.

I've been playing around with some simple scripts for AWD torque split, but they don't really make much sense until Ruud at least fixes basic issues with wheels that use powergearing not receiving any inertia effects - vehicles behave oddly one way or another.

He mentioned the upcoming new script language and indeed uploaded some tutorial bits to the documentation about it, perhaps we get access to some more useful parameters like wheel rotational velocities, brake heat, being able to map keypress to our non-keyboard controllers and so on.
 
What is the purpose of catching the index of vehicles? Is that limiting access to the script (though scriptowner should do that already) or extending it for traffic or so?



A few posts up you mentioned using interval since it's supposed to be independent of framerate, but by definition and from testing it out it's actually purely framerate dependent, I found. It's the reciprocal of framerate and even if you have vsync enabled for example, it jumps around a bit all the time.
1) It's using system parameters, which uses strings to look up the cars - the first car loaded is car0, second is car1, etc. The scriptowner line returns an rcar object, not the integer id - so the loop figures out which string to use.
It doesn't matter alone, but it would come up in multiplayer - if it was hardcoded to car0, it would always be tweaking one of the cars' variables. It's only run when the script loads so I don't think it's a problem to be figuring it out this way - car ids don't change during races.
Scripts in car folder are run once each time the car's loaded so there'll be a separate one running. I had 6 Lamborghinis with CVTs at no framerate loss, though I don't know how much more complex they can get before they would impact simulation.

2) Interval is for paint scripts, that want to do something based on real time. For example, the trunk opening script goes for a linear opening speed, but only needs to update once per frame - multiplying interval by speed (per millisecond) gives the change per frame.

I saw a brake heat function in the script documentation,
Code:
get heat
Returns:
int
Comment:
Returns current heat number.

The wheel signals do need a few more options to really be useful. Right now the only one that really does much is
Code:
car#.differential#.vel_asymmetric = 0.000000 rad/s
I guess power could be biased based on which one's slipping but I haven't tested the power gearing functions.
 
Thanks for the reply.

I understand collecting the car indices, but I'm not sure yet if the goal is to improve framerate performance and if so, how? You mention the example of running several identical AI vehicles - does that mean the script isn't loaded separately, once for each instance of the vehicle? If that was the case, I can see a performance benefit of course, I just don't know how Racer handles this (multiple instances of a vehicle with scripts). My initial thought was this part of the script is about scope, like "scriptowner" makes sure that your script is not having effects on others' vehicles in multiplayer for example.



Gotcha on the interval now. I recall it's also being used in your turbo script and that is where it looked odd to me at first - cleared up now though :)



"get heat" isn't related to brake heat as far as I can tell - it's the output of racer.ini.race.heat, but it sure got my hopes up initially as well.
 
My initial thought was this part of the script is about scope, like "scriptowner" makes sure that your script is not having effects on others' vehicles in multiplayer for example.
Yeah I got a little off track mentioning performance... it's all about making sure the script affects the right car. To my knowledge it runs one instance of the script per car loaded, and unless the variable is 'shared' it can hold a separate value for each of them.

I probably wrote the turbo script on paint interval so it was easier to debug, I don't remember anymore. It should be smoother using the physics but it wasn't exactly written to emulate any particular device so that was less important than seeing what it was doing onscreen (physics tick can't paint text, or rather, you'd get about 15 paints per frame, overlapping each other)


This stuff's a little abstract - quaternion operations for float[4]s in wxyz form (and one for rotating a position vector)
Code:
func float[4] qmul(float $q1[4], float $q2[4])
{
  // need to swizzle to copy code to make it more readable
  // incoming in [w,x,y,z] form
  // which is .yzwx
  // making it [x,y,z,w] so references match appearances
  $q1 = $q1.yzwx
  $q2 = $q2.yzwx
  // note that this is returning them in w,x,y,z form
  return float[4]{ $q1.w*$q2.w - $q1.x*$q2.x - $q1.y*$q2.y - $q1.z*$q2.z,
                   $q1.w*$q2.x + $q1.x*$q2.w + $q1.y*$q2.z - $q1.z*$q2.y,
                   $q1.w*$q2.y + $q1.y*$q2.w + $q1.z*$q2.x - $q1.x*$q2.z,
                   $q1.w*$q2.z + $q1.z*$q2.w + $q1.x*$q2.y - $q1.y*$q2.x }
}

func float[4] qconj(float $q[4])
{
  // return the complex conjugate of the input
  // note that they're actually in wxyz format, but .xyzw is 
  // how script uses indices.
  return float[4]{$q.x,-$q.y,-$q.z,-$q.w}
}

func float[4] qnorm(float $q[4])
{
  float $tol = 0.00001
  float $mag2 = $q.w*$q.w + $q.x*$q.x + $q.y*$q.y + $q.z*$q.z
  if abs($mag2) > $tol {
    if abs($mag2-1.0) > $tol {
      float $mag = sqrt($mag2)
      return float[4]{$q.x/$mag, $q.y/$mag, $q.z/$mag, $q.w/$mag}
    }
  }
  return $q
}

func float[3] qrot(float $q[4], float $v[3])
{
  // rotate vector v using quaternion q
  float $vecq[4] = float[4]{0.0,$v.x,$v.y,$v.z}
  float $resq[4] = qmul($vecq,qconj($q))
  $resq = qmul($q,$resq)
  // once again, wxyz form -> xyzw = yzw is xyz
  return $resq.yzw
}

In practice, this lets you define a point relative to the car - for example,
Code:
$curpos = get $veh pos
$currot = get $veh rot
// move down 0.2 and back 2.0 metres from the car's center
$curpos = $curpos + qrot($currot, float[3]{0.0,-0.2,-2.0})
Strictly speaking rotation quaternions need to be normalized every so often due to roundoff errors, so $q = qnorm($q) does this.

I guess this is why quaternion rotation is used, it's quite convenient.
The inverse, turning a world coordinate into a car coordinate, is
Code:
$worldpos = float[3]{0.0,0.0,0.0} // some point in world space
$curpos = get $veh pos
$currot = get $veh rot 
$carspace = qrot(qconj($currot), $worldpos - $curpos)
How's this useful? Well, you could use it to make a GPS arrow - if the point is in front of the car in carspace, it'll have +z, if it's to the left it'll have +x, etc.

More concretely, I'm using this to figure out where the trailer hitch is relative to the car - hitch in trailer space to hitch in world space to hitch in car space, then compare it with where it hooks onto the car. Once that's done, forces can be applied to the car & trailer to bring them into line. At least, that's the plan...
 
Okay, first in a series of hopefully progressively better scripts for having a trailer.

This one just uses a generic model.
Code:
// quaternion ops used to figure out car rotation
func float[4] qmul(float $q1[4], float $q2[4])
{
  // need to swizzle to copy code to make it more readable
  // incoming in [w,x,y,z] form
  // which is .yzwx
  // making it [x,y,z,w] so references match appearances
  $q1 = $q1.yzwx
  $q2 = $q2.yzwx
  // note that this is returning them in w,x,y,z form
  return float[4]{ $q1.w*$q2.w - $q1.x*$q2.x - $q1.y*$q2.y - $q1.z*$q2.z,
                   $q1.w*$q2.x + $q1.x*$q2.w + $q1.y*$q2.z - $q1.z*$q2.y,
                   $q1.w*$q2.y + $q1.y*$q2.w + $q1.z*$q2.x - $q1.x*$q2.z,
                   $q1.w*$q2.z + $q1.z*$q2.w + $q1.x*$q2.y - $q1.y*$q2.x }
}

func float[4] qconj(float $q[4])
{
  // return the complex conjugate of the input
  // note that they're actually in wxyz format, but .xyzw is 
  // how script uses indices.
  return float[4]{$q.x,-$q.y,-$q.z,-$q.w}
}

func float[4] qnorm(float $q[4])
{
  float $tol = 0.00001
  float $mag2 = $q.w*$q.w + $q.x*$q.x + $q.y*$q.y + $q.z*$q.z
  if abs($mag2) > $tol {
    if abs($mag2-1.0) > $tol {
      float $mag = sqrt($mag2)
      return float[4]{$q.x/$mag, $q.y/$mag, $q.z/$mag, $q.w/$mag}
    }
  }
  return $q
}

func float[3] qrot(float $q[4], float $v[3])
{
  // rotate vector v using quaternion q
  float $vecq[4] = float[4]{0.0,$v.x,$v.y,$v.z}
  float $resq[4] = qmul($vecq,qconj($q))
  $resq = qmul($q,$resq)
  // once again, wxyz form -> xyzw = yzw is xyz
  return $resq.yzw
}
// length of a vector
func float length(float $v[3])
{
  return sqrt($v.x*$v.x + $v.y*$v.y + $v.z*$v.z)
}

// atan2, one that actually works.  Helpfully ripped from the internet
// this is an approximation.
func float atan2Stereo(float $y, float $x)
{
  float $c1 = 3.14159/4.0
  float $c2 = 3.0*$c1
  float $absy = abs($y)
  float $angle
  if $x > 0.0 {
    float $r = ($x - $absy) / ($x + $absy)
    $angle = $c1 - $c1*$r
  } else {
    float $r = ($x + $absy) / ($absy - $x)
    $angle = $c2 - $c1*$r
  }
  if $y > 0 {
    return $angle
  } else {
    return -$angle
  }
}

// just for consistency I'm making these functions too
// carpos is the car's position, carrot is car's rotation
// point is in car space
func float[3] car2world(float $carpos[3], float $carrot[4], float $point[3])
{
  return $carpos + qrot($carrot, $point)
}
// point is in world space
func float[3] world2car(float $carpos[3], float $carrot[4], float $point[3])
{
  return qrot(qconj($carrot), $point - $carpos)
}
Okay so that's actually just the functions I need. Personally, I have them at the top of the script itself, but you could import them from another file.

This is the actual script controlling the trailer.
Code:
// generic 0 is trailer so load that one
rcar $car = get scriptowner car
rcarmodel $trailer = get generic model 0 of $car
set $trailer scriptcontrolled 1

// set up a couple of important points
// world position of trailer's wheels
float $tw_pos[3]
// car position of trailer's wheels
float $tc_pos[3]
// position on the cab of the hitch
float $hitchpos[3] = float[3]{0.0,0.0,-1.875}
// offset of the trailer's wheels from the hitch
float $twhl[3] = float[3]{0.0,0.0,-10.8}
// offset of the trailer's nullpoint
// in this case, it's actually using the same nullpoint as the tractor.
// so just set it equal to hitch pos.
float $nulloffset[3] = $hitchpos
// consequently calculate the rotation
float $rot[3] = 0.0

// we need to know some stuff about the car too
float $carpos[3] = get $car pos
float $carrot[4] = get $car rot
// initialize some of the values
$tc_pos = $hitchpos + $twhl
$tw_pos = car2world($carpos,$carrot,$tc_pos)

// more variables to use in the loop
float $curlen
float $hwpos[3]

while 1 {
  $carpos = get $car pos
  $carrot = get $car rot

// calculate hitch position in world coords
  $hwpos = car2world($carpos,$carrot,$hitchpos)
// calculate distance from twpos to hwpos
  $curlen = length($hwpos - $tw_pos)
// update tw pos to match where it should be
  $tw_pos = $tw_pos + ($hwpos - $tw_pos)*($curlen - length($twhl))/$curlen
// calculate new tc pos
  $tc_pos = world2car($carpos,$carrot,$tw_pos)
   // rotation needs to be recalculated
   $rot[0] = 3.14159265 - atan2Stereo($tc_pos.x, $tc_pos.z)
   // and another axis
   // this doesn't seem to work - depending if the truck points +z or -z
   //$rot[1] = -sin($tc_pos.y/length($twhl))
   // update rotation
 set $trailer rotation $rot
 // update position - this is because the nullpoint of the trailer is not at the hitch
 set $trailer position (float[3]{(-$nulloffset.z/length($twhl)),0.0,($nulloffset.z/length($twhl))}*($tc_pos - $hitchpos) + $nulloffset)
// end while block
// move while block end to end of file to get debug printing

interrupt
}
 string $out = "rotation " + $rot[0]
  paint $out at float[2]{20,20}
 string $tcprint = "Trailer car coords:   " + $tc_pos.x
 $tcprint = $tcprint + ", "
 $tcprint = $tcprint + $tc_pos.y
 $tcprint = $tcprint + ", "
 $tcprint = $tcprint + ($tc_pos.z - $hitchpos.z)
 paint $tcprint at float[2]{20,40}
 string $tlen =    "Trailer distance:     " + $curlen
 paint $tlen at float[2]{20,60}
  string $twprint = "Trailer world coords: " + $tw_pos.x
  $twprint = $twprint + ", "
  $twprint = $twprint + $tw_pos.y
  $twprint = $twprint + ", "
  $twprint = $twprint + $tw_pos.z
 paint $twprint at float[2]{20,80}
Essentially you insert ~3 values: the generic model's number, the point on the car where the hitch is (for now just adjust z position), the distance from the hitch to the trailer's wheels. If the trailer was exported with a different nullpoint from the car then you'd need to adjust that, too (point on the trailer where the hitch is - I unhelpfully called this $nulloffset)
 
Cool stuff Stereo ;)

Hope you'll at 1 point, get the whole truck working as it should, visually + physically working.
Maybe having another variable in the car.ini where we decide the nb. of cars/trailers attached to the 1st powered vehicle...
 
Haha, nice work Stereo :D

I'm not sure if this is even possible, but it'd be nice if you could load a 2nd car with yours via script that you essentially towed around.
No engine (or starts stalled), but had brakes, mass, inertia, high cofg, pacejka etc... it'd alleviate lots of fudge coding later to make the trailer act properly if anyone wanted it to get that far!


Now I can put a tow bar on my F458 and take a trailer full of junk to the local tip ;) :D

Dave
 
Hi, I have some problems with scripting... I want to make mirrors folding, like in AMGFan's CRX. I copied him scrpits (door.rsx and door.rcx), I made my mirror models, added those models to generic, and... nothing. I can't see them anywhere, under the car, in the car, after the car... I've got the same problem with electric windows ( scripts from skybh's espace ). What should I change ? I know it's something with position, but I don't understand what line exactly is it in rsx file. Can somebody help me? Thanks :)
 
Hi, I have some problems with scripting... I want to make mirrors folding, like in AMGFan's CRX. I copied him scrpits (door.rsx and door.rcx), I made my mirror models, added those models to generic, and... nothing. I can't see them anywhere, under the car, in the car, after the car... I've got the same problem with electric windows ( scripts from skybh's espace ). What should I change ? I know it's something with position, but I don't understand what line exactly is it in rsx file. Can somebody help me? Thanks :)
Hum where are placed the movable, i mean the separate part, where are they?
 
It possibly has to do with their center point, I don't know how the script works though. I'd assume it includes some sort of rotation which would mess up if the center point isn't near the center of the mirror model.
 
The centre of rotation is the origin of the generic object's .dof, offset by the car's cog (this second part is slightly odd and tripped me up at first. Put your cg and nullpoint at 0,0,0 if possible). So the axis you want it to rotate around should be on the origin in the modeling program.

If you can't see them at all, maybe remove the script? Until you run the 'set script controlled' command, it should be showing up at exactly the place it was exported, just by being listed as a generic model.
 
Hi, I've got a question. Is it possible to have smoke from exhaust in Racer? Is it doable with script or without? Also, I would want to have rain drops on car's windows when it's raining, like in Forza or Gran Turismo... is it possible ?
 

Latest News

Online or Offline racing?

  • 100% online racing

    Votes: 84 7.4%
  • 75% online 25% offline

    Votes: 122 10.7%
  • 50% online 50% offline

    Votes: 166 14.6%
  • 25% online 75% offline

    Votes: 316 27.7%
  • 100% offline racing

    Votes: 447 39.2%
  • Something else, explain in comment

    Votes: 4 0.4%
Back
Top