Modelling - Thargoid/pioneer GitHub Wiki

Making 3d models

All Pioneer models are defined in Lua scripts under data/models/. You can build models using scripting commands by defining primitives, individual triangles etc., or you can import Wavefront OBJ files.

To add a new model either add a define_model() specification (as described below) to an existing .lua file in data/models, or add it to a new .lua file in that directory.

A simple example model:

define_model('cargo', {
  info = {
    lod_pixels = {20, 50, 0},
    bounding_radius = 1.5,
    materials = {'body', 'text'}
  },
  static = function(lod)
    local divs = 8*lod
    set_material('body', .5,.5,.5,1, 0,0,0, 0, 0,0,0)
    set_material('text', 1,0,0,1, 0,0,0, 0, 0,0,0)
    local top = v(0,1,0)
    local bottom = v(0,-1,0)
    use_material('body')
    cylinder(divs, top, bottom, v(1,0,0), 1.0)
  end,
  dynamic = function(lod)
    if lod >= 3 then
      local textpos = v(0,1,0)
      use_material('text')
      zbias(1, textpos, v(0,1,0))
      text(get_arg_string(0), textpos, v(1,0,0), v(0,1,0), 0.1)
    end
  end,
})

info

info provides model metadata.

lod_pixels: up to four pixel sizes used for switching the model's detail level. For example when the model's on-screen size is less than 20px level 1 is used, level 2 when less than 50 and anything else is LOD 3.

bounding_radius: must encompass the whole model from center position (0,0,0). This is used in calculating the apparent pixel size for LOD switches.

materials: all materials used by a model must be declared here

there can be more data for special cases, such as '''tags''' and '''ship_defs'''.

static function

Geometry that does not change is defined here. The lod parameter is the current level of detail where 1 is the least detailed and 4 is the most detailed.

dynamic function

Dynamic function can define animated geometry, such as landing gear, change material properties etc.

Loading Wavefront Obj format models into Pioneer

Rather than specifying geometry as crazy bezier surfaces and cylinders and stuff, you can import Wavefront Obj format 3D models. In your .static model function, use load_obj:

load_obj('my_obj_file.obj')

Optionally, load_obj can take a matrix transform as a second argument:

load_obj('my_obj_file.obj', Matrix.new(v(2,0,0), v(0,2,0), v(0,0,2))

This does the same as the first example, except the model is scaled x2 in all axes.

If Blender is your modeling program of choice, we have some instructions here.

Function reference

The function reference is part of the Lua API documentation (LMR) section.

Model conventions

Scale

Model length units are meters.

Polygon winding

  • All polygons must be wound counter-clockwise.

experience: it depends on, right-handed models counter-clockwise, left-handed clockwise. important for scripted models and depends on how you look at your model when you draw the sketch. see also: http://en.wikipedia.org/wiki/Cartesian_coordinate_system.

simplified: look at your right hand (for Pioneers righthanded coordinate system), spread thumb, forefinger, and middle finger, so that the thumb pointing upwards for the +Y vector, forefinger points away from you and shows +X vector, the middle finger will point to you showing the +Z vector. thanks to Monsieur Descartez.

for the use of CAD programs; set single sided material, to see the winding order used, on a double sided material the result is unpredictable. some older programs always cull backfaces, more recent (like Blender), showing doublesided by default and don't cull, only showing black faces on a single sided material then. if you miss something like "reorient" or "flip polygon" in your program (e.g. Blender), you can fix it by simply flipping the normals of the selected wrong wound polygons. this happens often when you recalculate the normals of a object, because the machine don't know what is inside or outside.

programs/filetypes use either left or righthanded system, so it can be wound wrong after a conversion. if your whole model appears inverted in pioneer, you can change it by negation of either x or z axis in the Matrix.new command when you load the model in the script, of course left will then be right.

Hierarchy

The hierarchy of parts in a model play a role especially to transparency.

  • All models get processed like a batch no matter if it's a object or a generated geometry.
  • Any geometry in a model that is processed previously to a following geometry will "overlay" the following one.

In other words, a transparent set part (alpha channel and/or material) which stands in the script in front of another part will cut the surface of this part.

This has always to be respected when you like to have i.e. a window in a shiphull or building, windows will be placed best at the end of the script, for objects the part has to stand in the objects file as last in row, else all geometry which is visually behind vanishes. You can use this issue even to your advantage when you like to cut a hole into a surface of a geometry, then the cutting part will have to be placed in a hierarchical position before the surface which is to cut and after any other that should stay visible.

General

  • Geometry with geomflag >= 0x8000 is not passed to the collision detector.
  • Objects which sit in a rotating frame (ie orbital starports) rotate in the Y axis.
  • Docking procedure is: XXX TODO: read SpaceStation.cpp::DoDockingAnimation() and SpaceStation::Render()

Defining ship types for models

To define space ship types for a particular model add a ship_defs to the model's info table:

 define_model('mymodel', {
   info = {
     ship_defs = {
       { ... ship definition 1 ... },
       { ... ship definition 2 etc ... },
     }
   },

The ship definition looks like this:

 {
   name='Ship name',
   reverse_thrust=1000,
   forward_thrust=-1000,
   up_thrust=100,
   down thrust=-100,
   left_thrust=-100,
   right_thrust=100
   angular_thrust=1234,
   gun_mounts = { { gun0_position, gun0_direction },
                  { gun1_position, gun1_direction },
                  ... etc up to 3, although pioneer only uses 0 & 1 right now ..
                },
   max_cargo = 50,
   max_fuelscoop = 0,
   max_laser = 2,
   max_missile = 4,
   capacity = 50,
   hull_mass = 40,
   price = 14000,
   hyperdrive_class = 1,
 }

Cargo and equipment slot maximum items are optional and default to 1 when omitted (except max_cargo and max_missile, which default to 0).

The following are available: max_cargo, max_engine, max_laser, max_missile, max_ecm, max_scanner, max_radarmapper, max_hypercloud, max_hullautorepair, max_energybooster, max_atmoshield, max_fuelscoop, max_lasercooler, max_cargolifesupport, max_autopilot

Capacities are in tonnes and thrusts in newtons.

Thrust is in newtons, which means the numbers are generally quite big in order to accelerate ships of hundreds of tons. (Try 10^6 or so).

Space stations

Tags

Space stations should either have the surface_station or orbital_station tag in their info table:

 define_model('my_starport', {
   info = {
     tags = {'surface_station'}

Defining docking procedures for space stations

Space station model's info tables need some additional attributes to describe docking animation, eg:

define_model('my_starport', {
    info = {
        num_docking_ports = 2,
        dock_anim_stage_duration = { DOCKING_TIMEOUT_SECONDS, 4, 6 },
        undock_anim_stage_duration = { 6, 4, 30 }

This describes a space station with two docking bays. The docking animation has 3 stages, taking the specified number of seconds, and the undocking animation has 3 stages.

Next the precise docking animation must be specified (still in the info table):

ship_dock_anim = function(port, stage, t, from, ship_aabb)
-- return { position, xaxis, yaxis }

Arguments are:

  • port - docking port number (counting from 1)
  • stage - docking stage. 1 upwards are docking stages, and -1 downwards are undocking stages. '''Note that stage 1 is always ''docking granted'', which is why in the example it has been given duration DOCKING_TIMEOUT_SECONDS. Note ship_dock_anim should return nil where the docking ship is still freely flying (ie stage 1 of docking, or perhaps the last stage of undocking where the ship is free of the station but the station door closing animation is still active)'''
  • t - time through this stage. will be in range 0.0 .. 1.0
  • from - docking ship's position at the end of the previous stage
  • ship_aabb - ship's axis-aligned bounding box. Useful for calculating what distance to keep landed ships away from surfaces, so they do not intersect them in a weird way.

Returns are:

  • position - Docking ship's position at time t of this docking stage. You may want to interpolate this from the 'from' argument and the new position. ie vlerp(t, from, newpos)
  • xaxis, yaxis - Desired orientation by the end of this docking stage. You don't need to interpolate this, just give the final desired orientation and pioneer will figure out a smooth rotation.
  • OR return can be nil, to indicate the ship is not on animation rails (this is described above).

Animating the station using docking stage

Space station models can be animated during docking by using the stage and stage position arguments:

local stage = get_arg(ARG_STATION_BAY1_STAGE)
local pos = get_arg(ARG_STATOIN_BAY1_POS)
  • stage is as described previously, positive stages being docking and negative being undocking
  • pos is position (time) within the stage, ranging from 0.0 to 1.0
⚠️ **GitHub.com Fallback** ⚠️