BPV stands for Basic Particle Viewer and is a Qt user interface used to... well, visualize particles!
Particles can be used to represent a lot of different things in physics (electrons, atoms, balls, sand, even liquids or gases, etc. ).
When you start making your own program to simulate them it can be convenient to have an easy way to visualize them. Hence BPV!
The BPV project contains actually three targets:
- dir2hcpp: used to transform images (e.g. icons) into source code that can be compiled and included in the bpv binary. The code generated by dir2hcpp will be saved under ./app/include_gen and ./app/source_gen.
- bpv: the main application (requires the files generated by dir2hcpp).
- examples: binary generating several data and geometry files. WARNING: running this binary will generate over 300MB of data (most of which comes from the Box example).
To compile the code, first you'll need to download and install the open source distribution of Qt and update your PATH environment variable with the bin directory containing qmake (and also the one containing mingw32-make.exe if you are using Windows). Then you have two options:
> qmake bpv.pro
> [mingw32-make, make] release
The bpv binary will be located under ./bin
- Open Qt Creator.
- Click on 'Open Project'.
- Select bpv.pro.
- Click on 'Configure Project'.
- On the left hand side, click on 'Projects' and select the 'Release' build configuration.
- On the left hand side, click on 'Edit'. Right click on the sub-directory dir2hcpp and select 'Build "dir2hcpp"'.
- In the Menu, click on 'Build', then on 'Build Project "bpv"' (or simply Ctrl+B).
Here again the bpv binary will be located under ./bin. To start the application from Qt Creator, click on the 'Build' menu, then 'Run' (or just Ctrl+R).
BPV can read two kinds of files: data files and geometry files.
- Data files are binary files containing all the simulation data (particles positions, particles type, bonds, etc.).
- Geometry files are text files containing the static geometry of the environment in which the particles move (e.g. shape of a transistor in which electrons flow, box in which balls bounce, etc.)
Data files can be split in several sections: The first ones describe the particles (e.g. particle type, variables, bonds between particles, etc.) while the last one contains the simulation data itself (i.e. particle position and variables value at a given time).
Here is the detailed structure of a data file. Sections are given in order, and sizes are in bytes.
- PAR. Size = 3. Every data file must starts with the 3 bytes 'PAR' (i.e. 0x50 0x41 0x52).
PAR (3 bytes)
- Types. Size = 1 + nbTypes * 39. Particles can be grouped in different types. Each type has its own name, default radius, and default color which are used to display the particles (e.g. you can have one type to represent Hydrogen atoms in white, another one for Oxygen atoms in red, etc.)
nbTypes (1 byte. >= 1)
name type 1 (32 bytes)
radius type 1 (1 float)
red type 1 (1 byte)
green type 1 (1 byte)
blue type 1 (1 byte)
name type 2 (32 bytes)
radius type 2 (1 float)
etc.
- Variables. Size = 97 + nbVariables * 32. Particles have 3 coordinates, but can also have additional variables associated to them (e.g. energy, velocity, etc.). This section lets you name each axis and each variable.
nbVariables (1 byte. Can be 0 and does not include the x, y, and z axis, which are mandatory)
name x axis (32 bytes)
name y axis (32 bytes)
name z axis (32 bytes)
name variable 1 (32 bytes)
name variable 2 (32 bytes)
etc.
- Particles. Size = 4 + nbParticles. This section contains the number of particles in the simulation and specifies the type of each particle.
nbParticles (1 unsigned int. >= 1)
type of particle 1 (1 byte. 0 based: 0 for type 1, 1 for type 2, etc.)
type of particle 2 (1 byte)
etc.
- Bonds. Size = 8 + nbBonds * 8. Bonds must be specified even if your simulation does not contain bonds (in which case set both nbBonds and bonds radius to 0 ).
nbBonds (1 unsigned int. Can be 0)
bonds radius (1 float)
1st index of bond 1 (1 unsigned int. 0 based)
2nd index of bond 1 (1 unsigned int)
1st index of bond 2 (1 unsigned int)
etc.
- Meshes. Size = 4 + nbMeshes * 36 + sum(nbTriangles) * 12. More than one mesh can be defined. A mesh is a set of triangles and each triangle is defined with a set of three indices (each index is in the range [0,nbParticles[). The order in which the indices are given is important as it defines which side of the triangle is visible.
nbMeshes (1 unsigned int. Can be 0)
name mesh1 (32 bytes)
nbTriangles mesh1 (1 unsigned int)
indices triangle1 mesh1 (3 unsigned int)
indices triangle2 mesh1 (3 unsigded int)
etc.
name mesh2 (32 bytes)
etc.
- Steps. Size = 4 + nbSteps * (4 + nbParticles * (12 + nbVariables * 4)). A simulation is made of successive steps corresponding to a given time and in which each particle has a given set of coordintes and variable values.
nbSteps (1 unsigned int. >= 1)
time step 1 (1 float. Expressed in seconds)
x coordinate particle 1 step 1 (1 float)
y coordinate particle 1 step 1 (1 float)
z coordinate particle 1 step 1 (1 float)
variable 1 particle 1 step 1 (1 float)
variable 2 particle 1 step 1 (1 float)
etc.
x coordinate particle 2 step 1 (1 float)
etc.
time step 2 (1 float)
etc.
The total size of a data file is 121 + nbTypes * 39 + nbVariables * 32 + nbParticles + nbBonds * 8 + nbMeshes * 36 + sum(nbTriangles) * 12 + nbSteps * (4 + nbParticles * (12 + nbVariables * 4)) bytes.
Geometry files are text files containing the geometry of the scene in which the particles move.
The syntax used for these files was chosen such that they could be imported as-is in 3DSMax (Cf. MAXScript).
Five types of primitives can be used (box, cylinder, sphere, line, and mesh). Each primitive is defined with one single line which starts with the name of the primitive, followed by a set of key:value pairs separated by a space character. A few of these keys are common to all primitives:
name
: Name of the primitive. Value = string (e.g."my box"
).wirecolor
: Color used to draw the primitive. Value =(color R G B A)
whereR
,G
,B
, andA
have values between 0 and 255. The alpha value (A
) is used to set the transparency (0: invisible, 255: fully opaque).transform
: A 4x4 transformation matrixA
used to transform the primitive. Value =(matrix3[a00,a10,a20][a01,a11,a21][a02,a12,a22][a03,a13,a23])
whereaij
are floats,a30=a31=a32=0
anda33=1
.
Here is the list of primitives with their specific keys:
- Box. Starts with
box
.pos
: Position of the center of the bottom side of the box (not the center of the box...). This overwrites the translation component of the transformation matrix if any. Value =[x,y,z]
. Default =[0,0,0]
.width
: Box width (x axis). Value = float. Default =0
.length
: Box length (y axis). Value = float. Default =0
.height
: Box height (z axis). Value = float. Default =0
.widthsegs
: Number of segments along the x axis. Value = integer. Default =1
.lengthsegs
: Number of segments along the y axis. Value = integer. Default =1
.heightsegs
: Number of segments along the z axis. Value = integer. Default =1
.
- Cylinder. Starts with
cylinder
pos
: Position of the center of the bottom of the cylinder (not the center of the cylinder...). This overwrites the translation component of the transformation matrix if any. Value =[x,y,z]
. Default =[0,0,0]
.radius
: Cylinder radius. Value = float. Default =0
.height
: Cylinder height (z axis). Value = float. Default =0
.heightsegs
: Number of segments along the z axis. Value = integer. Default =1
.sides
: Number of sides. Value = integer. Default =18
.capsegs
: Number of segments for the caps. Value = integer. Default = '1'.
- Sphere. Starts with
sphere
pos
: Position of the center of the sphere. This overwrites the translation component of the transformation matrix if any. Value =[x,y,z]
. Default =[0,0,0]
.radius
: Sphere radius. Value = float. Default =0
segs
: Number of 'segments' used to draw the sphere. Value = integer. Minimum value =1
. Default =8
.
- Lines. Starts with
line
points
: List of points defining the lines. Value =[x1,y1,z1][x2,y2,z2]...[xN,yN,zN]
.close
: Connect the first and last point if true. Value = bool (yes
orno
). Default =no
.
- Mesh. Starts with
mesh
vertices
: List of vertices in the mesh. Value =#([x1,y1,z1],[x2,y2,z2],...,[xN,yN,zN])
.faces
: List of faces in the mesh. Value =#([i11,i12,i13],[i21,i22,i23],...,[iM1,iM2,iM3])
wherei..
are integers and are the 1-based indices of the vertices given in thevertices
array. Each set of 3 indices creates a triangle (order of vertices is important as only one side of the triangle is visible).
Examples:
box wirecolor:(color 0 255 255 60) width:20 length:10 height:50 pos:[10,0,0] name:"box 1"
cylinder radius:2.5 height:20 wirecolor:(color 255 0 0 255) transform:(matrix3[0,0,1][0,1,0][1,0,0][0,0,0])
sphere radius:2.5 wirecolor:(color 255 255 0 60)
line wirecolor:(color 0 0 255 255) points:[0,0,0][20,0,0][20,5,0][0,5,0] close:yes
mesh wirecolor:(color 255 0 255 255) vertices:#([0,0,0],[10,0,0],[0,10,0],[0,0,10]) faces:#([1,3,2],[1,2,4],[1,4,3],[2,3,4])
Finally, lines starting with two dashes (--
) are ignored and can be used for comments.