Physics and 3d models in Scheme

posted in Programmology
Published August 03, 2009
Advertisement
Posted from http://jlongster.com/blog/2009/08/3/iphone-physics-and-models/:

-------------------------

Physics and 3d models in Scheme

August 3, 2009

I finished two major parts of my Scheme 3d graphics framework this past week, and got it running on the iPhone: basic physics simulation and importing 3d models.

* Disclaimer

Apple's been getting some really terrible press about the iPhone App Store recently, from Google Voice being taken down to general crapness in their treatment of developers. I'm still confident that I'll be able to get my Scheme apps through the App Store, but please note that most of my work here is applicable for any Scheme game, whether it's on Windows, OS X, or whatever. I look forward to working on full PC games when I have enough resources.

* Physics

">(Click here if you don't see the video above)

In this video, you can see my current physics environment. I implemented velocity and acceleration, which lets me simulate gravity by applying a global acceleration downward of about 9.8 m/s. I proceed to toss balls up in the air randomly, and gravity takes care of the rest.

I then use the procedure `kick` to kick around balls. It simply pushes all the objects in the scene by adding a velocity vector to each object, giving them instant acceleration in the specified direction.

Lastly, I change gravity so the balls gravitate to other areas in the world. Pretty fun stuff.

I'm able to develop interactively using the technique described in my previous post about using remote REPLs.

* Models



I wrote a OBJ file parser so that I can finally import 3d models. It wasn't really that hard, and I think I will just post it here. It's a work in progress, of course, and will be tracked in my git project in the `lib` directory.

Honestly, I didn't do a whole of research before I wrote this, but first, it didn't seem like there was a simple OBJ loader written in C out there, and second, it's nice to have this native in Scheme anyway. It sure looks a lot prettier than this loader written in Objective-C.

I also found this blog post, which suggests using a Blender script to output... C header files with embedded data? That's crazy! I can't imagine having to re-compile my program every time I changed a model.

    (define (read-map #!optional f)      (unfold (lambda (x) eof-object?)              (lambda (x) (if f (f x) x))              (lambda (x) (read))              (read)))        (define (enforce-length name len lst)      (if (eq? (length lst) len)          lst          (error name "assert-length failed")))        (define (obj-parse-vertex)      (enforce-length "vertex" 3 (read-map exact->inexact)))        (define (obj-parse-normal)      (let ((v (vec3d-unit                (apply make-vec3d                       (enforce-length "normal" 3                                       (read-map exact->inexact))))))        (list (vec3d-x v) (vec3d-y v) (vec3d-z v))))        (define (obj-parse-face)      (enforce-length "face" 3 (read-map (lambda (n) (- n 1)))))        (define (obj-parse-line obj line)      (define (appendd lst lst2)        (append (reverse lst) lst2))            (with-input-from-string line        (lambda ()          (let ((type (read)))            (case type              ((v)               (obj-vertices-set!                obj                (appendd (obj-parse-vertex)                         (obj-vertices obj))))              ((vn)               (obj-normals-set!                obj                (appendd (obj-parse-normal)                         (obj-normals obj))))              ((f)               (obj-indices-set!                obj                (appendd (obj-parse-face)                         (obj-indices obj)))))))))        (define-type obj      id: 9F8E77AF-49A7-4974-A071-C91616CD1DD0      constructor: really-make-obj      num-vertices      num-indices      vertices      normals      indices)        (define (make-obj)      (really-make-obj #f #f '() '() '()))        (define (obj-load file #!optional avoid-c-vectors?)      (define (convert data)        (list->vector (reverse data)))          (define (make-c-vectors mesh)        (if (not avoid-c-vectors?)            (begin              (obj-vertices-set! mesh                                 (vector->GLfloat* (obj-vertices mesh)))              (obj-normals-set! mesh                                (vector->GLfloat* (obj-normals mesh)))              (obj-indices-set! mesh                                (vector->GLushort* (obj-indices mesh))))))            (with-input-from-file file        (lambda ()          (let ((mesh (make-obj)))            (let loop ()              (let ((line (read-line)))                (if (not (eof-object? line))                    (begin                      (obj-parse-line mesh line)                      (loop)))))                (obj-num-vertices-set! mesh (length (obj-vertices mesh)))            (obj-num-indices-set! mesh (length (obj-indices mesh)))            (obj-vertices-set! mesh (convert (obj-vertices mesh)))            (obj-normals-set! mesh (convert (obj-normals mesh)))            (obj-indices-set! mesh (convert (obj-indices mesh)))            (make-c-vectors mesh)            mesh))))


Usage:

    (obj-load "mesh.obj")


There are a few caveats to my loader: it doesn't index normals (assumes every vertex has a normal), requires triangulation (faces can only have 3 vertices), and doesn't support texture mapping yet. I will add texture mapping soon, but as to the other caveats, I'm not sure if I want to complicate my loader when I can simply tell Blender to export things properly. Maybe someday though.

** Optimizing

Now, it's a bit slow to load a model with about 3000 vertices and 2000 indices. Why not use Gambit's nice serialization features to speed this up?

Folks, *this* is why I use Scheme, and Gambit Scheme specifically. Gambit Scheme provides two interesting procedures: `object->u8vector` and `u8vector->object`. These procedures convert objects to byte vectors and back. Byte vectors can easily be written to files, too!

So, by adding the following two procedures to our OBJ loader, we immediately support a very efficient binary model format. `compress` basically takes the constructed mesh, converts it to a `u8vector` and saves it out to disk. `decompress` does the opposite.

    (define (compress filename mesh)      (with-output-to-file filename        (lambda ()          (let* ((v (object->u8vector mesh))                 (len (u8vector-length v))                 (len-u8 (object->u8vector len))                 (boot (u8vector-length len-u8)))            (write-u8 boot)            (write-subu8vector len-u8 0 boot)            (write-subu8vector v 0 (u8vector-length v))))))        (define (decompress filename)      (with-input-from-file filename        (lambda ()          (let* ((boot (read-u8))                 (len-u8 (make-u8vector boot)))            (read-subu8vector len-u8 0 boot)            (let* ((len (u8vector->object len-u8))                   (v (make-u8vector len)))              (read-subu8vector v 0 len)              (u8vector->object v))))))


I created a quick script to compress meshes:

    ;;; saves it with the ".gso" extension    (define (main filename)      (compress (string-append filename ".gso") (obj-load filename #f #t)))


I modified `obj-load` to take an extra parameter indicating if the file is compressed or not. So, lets see what we gained with this no-effort improvement:

    james% du -h logo.obj*    1020K	logo.obj    800K	logo.obj.gso    (obj-load "logo.obj")         => 1.05s    (obj-load "logo.obj.gso" #t)  => .215s


We got a 32% compression and 5x load time speed increase!
0 likes 2 comments

Comments

a_insomniac
Pretty impressive! I wanted to look into IPhone development but never got around to it. Like what your doing though, keep it up.
August 03, 2009 06:37 PM
okonomiyaki
Thanks for the feedback. It's very encouraging.
August 04, 2009 09:20 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement