Models in Simulations
CS 482 Lecture,
Dr. Lawlor
You can use random points, mathematical
functions, or hand coordinate entry to make simple shapes.
But to model real-world objects, you need better tools.
- 3D scanners are getting fairly cheap--the Microsoft Kinect is
fairly low resolution, but approaching $100. One universal
issue with scanners is the need to grab 3D scans from several
different directions, and integrate them together; some packages
like ReconstructMe can
automate this, at least for simple camera motions.
- Photometric modeling is another option. Autodesk's 123D Catch or Agisoft PhotoScan
does this semi-automatically from a few dozen ordinary digital
camera photos. The resulting model is a bit lumpy, and the
matching can be problematic if there isn't much texture on the
object for automated cross-correlation.
- For manufactured objects, a CAD program like OpenSCAD,
Solidworks, Inventor, or OnShape can make a 3D object with
precise dimensions and angles.
- 3D modeling programs, like Blender below, are used to import,
clean up, and generate models from scratch. UAF's Miho
Aiko teaches ART 472, Visualization and Animation, where you
learn the commercial program Maya (available for free from Autodesk for students).
Blender is a pretty capable free 3D modeling program.
It's available for all platforms. The only downside is the
bizarre, utterly unique user interface. This is typical for
3D modeling programs, even pro versions--they're just each their
own weird thing. Start with the official installer (use the .zip
option on the Chapman lab machines, since the .exe installer needs
admin access).
Check out the Blender Tutorials and Blender Doc Wiki. Here's my
super-compressed cheat sheet:
- Left-click manipulates the currently selected object with the
current tool. Or it does nothing.
- Middle-click (or left and right simultaneously) rotates in
3D. Control-middle-click zooms in.
- Right-click selects. Shift right-click selects several
objects or vertices, so you can move them at the same time.
- Spacebar brings up a context-sensitive menu.
There's a "mode" popup menu directly in
the middle of the screen.
- "Object mode" lets you translate, scale, and rotate entire
objects (arrange models).
- "Edit mode" lets you translate, scale, rotate, extrude
vertices and faces (edit polygons one at a time).
- Pressing the Tab key cycles between Edit and Object mode,
because they're so common.
- Shift-right-click to select vertices one at a time.
Translate, scale, or rotate them like in Object mode.
- Press 'A' to deselect, then toggle selection between
All/None.
- Press 'B' then click out the corners of a selection box
(rectangle).
- Press 'C' to get a continuous drag-select tool.
- Press 'D' to duplicate the currently selected face.
- Press 'E' to extrude the currently selected face.
- Press 'F' to fill the selected edges with a new face.
- (See the problem with keyboard shortcuts? Too
many to remember!)
- "Limit selection to visible" is a useful icon, switching
between X-ray mode and only selecting the topmost geometry.
- "Sculpt mode" lets you push and pull groups of faces (smoosh
polygons like clay).
- "Vertex paint" lets you apply colors to polygon vertices.
Blender starts you out with a
cube.
To model anything with this, we need
more polygons. Press the bottom-right icon to switch from
Timeline to Properties, select the wrench icon to get Object
Modifiers, and hit Add Modifier -> Generate column ->
Multiresolution. Scroll down and hit "Subdivide" six times,
to generate 2^6 smaller polygons on each face. Zoom into the
now-smoothed high-poly sphere with scroll wheel or
control-middle-click. Switch to "Sculpt Mode".
The "Brush" tab on the left shows your sculpting options.
Scroll down to Symmetry, and turn on symmetry about the X
axis. Use the "Add", "Grab", and "Smooth" tools to sculpt
the object into something meaningful, like a potato.
Note you can switch back to "Edit" mode,
and deform the original (non-subdivided, non-sculpted) cube, and
everything works nicely.
Save the original as a .blend
file. Export as a .obj file. To save a low-poly
triangle version in a nice ASCII format, go back to Object Mode
and find Object Modifiers again. Add the Modifer "Decimate",
and set the decimation ratio to 0.1 or so. Hit Apply, and
File->Export as a RAW or Wavefront .obj file.
Exporting
from 3D modelers to "Real Code"
So 3D modeling programs make it pretty
easy to generate cool geometry. The trick is then you've got
to somehow move that geometry into your application (game,
visualization app, etc).
The easiest way to do this is skip it
entirely--just do all your modeling and rendering inside the 3D
modeling program! But the modeling performance of these
programs usually isn't that good, and you often need to add some
scripting or shading features that would be tricky in the 3D
program.
The standard way to exchange data
between programs is of course files. We've looked at several
very simple file formats, like the OBJ file format, but modeling
programs usually support more than the very simplest "just the
polygons" formats, because the modeling programs support way more
than just polygons--they have colors, textures, "instanced"
sub-pieces (like function calls), and transforms.
Every program supports vertices,
the XYZ positions of geometric points. Some care about edges,
pairs of points. Others want faces like triangles,
triplets of points, or quads, with four points (planar or
non-planar). Most programs need additional data like texture
coordinates (often per vertex), rendering needs normals (per face
for flat shading, per vertex for smooth shading), and simulations
need boundary condition information (per face, vertex, or
both). The situation is hence something of a mess, with many
possible conversions workable with some loss of data, but few
lossless model format conversions are possible.
Blender, at least, supports a bunch of
decent file formats:
- Blender internal format, extension ".blend". A
Blender-proprietary binary file format.
- RAW triangles, which really are just "X Y Z X Y
Z X Y Z \n". Very simple to read, but the
triangles aren't even indexed, so the files are huge.
Plus, there's no way to add normals or texture coordinates.
- Wavefront .obj format, which
consists of vertex lines starting with "v X Y Z", vertex texture
coordinates like "vt S T", vertex normals "vn X Y Z", and faces,
which list the 1-based index of their vertices. Faces can
be either triangles (three vertices) or quads (four
vertices). Face lines either have the simple format "f I J
K" (I J and K are a 1-based vertex index), or with texture
coordinates "f I/TI J/TJ K/TK" (TI TJ and TK are a vertex
texture coordinate index), or finally if you included normals
each face has separate normals, like "f I//NI J//NJ K//NK" (NI
NJ and NK are vertex normal index). For example, here's a
two-triangle OBJ file: the vertices are numbered 1-4, and then
used by the two faces.
# written by foolib v3.7
v 0.0 0.0 0.0
v 0.0 0.1 0.0
v 0.1 0.1 0.0
v 0.1 0.0 0.0
f 1 2 3
f 2 3 4
- VRML 1.0,
extension ".wrl". It's all ASCII, but with a strange
XML-like nested structure. VMRL can represent object
instances and transforms.
- Videoscape
format, extension ".obj". This format came from the
1980's Amiga program "Videoscape 3D". This is NOT the same
as the OBJ format we've been using, but it is a very simple
ASCII format:
"3DG1"
<number of vertices>
List of vertices:
<x y z coordinates for each vertex>
List of faces:
<number of vertices for this face> <1-based vertex numbers...> <face RGB color, in hex>
Example 3-vertex, 1-face file:
3DG1
3
0.0 0.1 0.0
0.1 0.0 0.0
0.0 0.0 0.1
3 1 2 3 0xff0000
- STL
(binary) format, extension ".stl". This format is
used by 3D printers to generate hardcopy models. This is a
binary file format, but it's pretty simple: it's an 80-byte
header, followed by one little-endian 32-bit triangle count,
followed by a set of "triangle records". Each triangle
record has 12 floats: an XYZ normal (sometimes all-zeros) and
three XYZ vertices in little-endian 32-bit IEEE floating-point,
followed by two attribute bytes, which are normally zero.
For a 3D printer to work, the mesh must be manifold (Edit mode
-> Select -> Non-manifold) and have no intersecting
geometry.
- STL (ascii) format, extension ".stl". Typically a set of
"facet" records, giving the XYZ coordinates of each vertex like
so:
facet normal 0 0 0
outer loop
vertex -0.099739 -0.086973 1.836396
vertex -0.054167 -0.390427 1.914516
vertex -0.055830 -0.220691 1.952528
endloop
endfacet
One annoyance with either binary or ascii STL files is the same
vertex gets written many times, because they don't send a vertex
list, just the XYZ positions. This uses much more memory
than an indexed format.
- DXF (ascii) format,
extension ".dxf". This is AutoCad's "Drawing eXchange
Format", but it also supports 3D models (barely!). It's
basically a long series of AutoCad commands, and so isn't very
easy to read.
- To export a fully-rigged model, preserving all the animation
and bone info, takes an industrial-strength file format, the 3D
analog of a complicated image file format like JPEG/EXIF.
There's a recent XML-based standard called COLLADA that attempts to be
that format; the extent of my experience with this is that the
XML looks pretty unreadable, and so does the code that reads it.
Typically, when reading a new 3D object
file format, I will:
- Research and use a hex dump tool to figure out how the file is
organized.
- Try to read and dump some XYZ coordinates to the screen.
This usually takes a few tries, and tells me what the scale
factors are (e.g., all coordinates between -0.00001 and
+0.00003, or -1000 and +30000).
- Splat some GL_POINTS at the XYZ coordinates of the
vertices. This usually takes some tweaking to get the
scale factor correct (meters, inches, or millimeters?), and
here's where I need to fight the Y/Z up axis question.
- Draw GL_TRIANGLES at the face indices. There are often
issues with silly things like 0-based versus 1-based numbering
(which makes a spiky-looking glob instead of a smooth object).
- Try to recover the existing normals, or compute my own
normals. I usually need to compute my own.
- Try to figure out texture coordinates. By this point,
I'll likely just bodge something together!
Loading
Models in Babylon
There are Babylon plugins to read .obj or .stl format
files.
Aside from figuring out the return
value, it's really pretty straightforward. One annoying part
is the data gets loaded using an XMLHttpRequest, which sadly is
subject to the "same-origin" rule: it can only fetch data from the
same server, not a local file (this is to protect your computer
from random JavaScript reading your files).
CS 482 Lecture
Solid Models via Tetrahedra
To simulate a solid, you need a solid
mesh, not just triangles. In 2D, you can use the Delaunay triangulation to
make a good triangle mesh from scattered points. TetGen can
build a tetrahedral mesh using the 3D version of this, the
Delaunay tetrahedralization.
To use TetGen:
- Install the program:
- sudo apt-get install tetgen
- Or download the source code, and compile the C++ code
predicates.cxx and tetgen.cxx into a "tetgen" executable.
- Remesh or decimate, triangulate, and export your surface to
ASCII .stl or Stanford .ply format, which TetGen
can read.
- Run with command line arguments:
- Generate bigger tets inside: tetgen -peqOB YOURMODEL.ply
- Generate mostly the same size everywhere: tetgen -pq1.1OB
YOURMODEL.ply
(Note this will fail with "terminate
called after throwing an instance of 'int'" if the outside surface
is not well formed, like self-intersections or not closed.
Rerun tetgen with -d to check for self-intersections, which you
can manually clean up in Blender.)
This will dump the XYZ vertex/node locations (including interior
nodes) to MODEL.1.node, the node numbers for each tetrahedron to
MODEL.1.ele, and the renumbered surface mesh to
MODEL.1.face. I crudely converted these to javascript
function calls with these UNIX awk commands, and some hand editing
on the first and last lines; it'd be cleaner to read them directly
from JavaScript (via XMLHttpRequest) or convert them in C++.
m=MyModel
awk 'NF==4 && NR>1 {printf("vertex(%.3f,%.3f,%.3f);\n", $2,$3,$4); }' < $m.1.node > $m.js
awk 'NF==3 && NR>1 {printf("edge(%d,%d);\n", $2-1,$3-1); }' < $m.1.edge >> $m.js
awk 'NF==5 && NR>1 {printf("tet(%d,%d,%d,%d);\n", $2-1,$3-1,$4-1,$5-1); }' < $m.1.ele >> $m.js
awk 'NF==4 && NR>1 {printf("face(%d,%d,%d);\n", $2-1,$3-1,$4-1); }' < $m.1.face >> $m.js
You can paste small models directly into
a PixAnvil tab, and fetch the data as a string with
PixAnvil.loadTab("ModelData").
Be aware that many surface modeling programs, including Blender,
can produce fairly spiky narrow triangles, which results in spiky
narrow tetrahedra. These are fine for rendering, but don't
always work well for simulation, where the numerics work better
for larger angles. You can get better-shaped triangles by
remeshing the surface (in Blender, Add Modifier -> Geometry
-> Remesh), which internally uses a "marching cubes" type
volumetric re-subdivision of the mesh, at a selectable
resolution. After applying, enter Edit Mode, select all, and
triangulate with Mesh -> Faces -> Quads to Tris.
Given a tet mesh, you can construct
springs along each edge (every pair of vertices is connected with
an edge, so this is easy). In fact, typically the same
indexed vertex scheme used for faces is extended for tets.
You can even use the same single vertex list, and index into it
from an array of tets for simulation, and then index into it from
an array of faces for rendering. There might be a few
interior vertices used only for simulation, but the graphics card
is fine with this.