Thicket data repository for the EEG
at main 11 kB view raw
1{ 2 "id": "https://www.tunbury.org/2025/06/07/claude-animates-in-ocaml", 3 "title": "Animating 3D models in OCaml with Claude", 4 "link": "https://www.tunbury.org/2025/06/07/claude-animates-in-ocaml/", 5 "updated": "2025-06-07T00:00:00", 6 "published": "2025-06-07T00:00:00", 7 "summary": "In the week, Jon mentioned UTM, which uses Apple’s Hypervisor virtualisation framework to run ARM64 operating systems on Apple Silicon. It looked awesome, and the speed of virtualised macOS was fantastic. It also offers x86_64 emulation; we mused how well it would perform running Windows, but found it disappointing.", 8 "content": "<p>In the week, Jon mentioned <a href=\"https://mac.getutm.app\">UTM</a>, which uses Apple’s Hypervisor virtualisation framework to run ARM64 operating systems on Apple Silicon. It looked awesome, and the speed of virtualised macOS was fantastic. It also offers x86_64 emulation; we mused how well it would perform running Windows, but found it disappointing.</p>\n\n<p>I was particularly interested in this because I am stuck in the past with macOS Monterey on my Intel Mac Pro ‘trashcan’, as I have a niche Windows application that I can’t live without. A few years ago, I got a prototype running written in Swift. I never finished it as other events got in the way. The learning curve of <a href=\"https://youtu.be/8Jb3v2HRv_E\">SceneKit and Blender</a> was intense. I still had the Collada files on my machine and today, of course, we have Claude.</p>\n\n<p>“How would I animate a Collada (.dae) file using OCaml?”. Claude acknowledged the complexity and proposed that <code>lablgl</code>, the OCaml bindings for OpenGL, would be a good starting point. Claude obliged and wrote the entire pipeline, giving me opam commands and Dune configuration files.</p>\n\n<p>The code wouldn’t build, so I looked for the API for <code>labgl</code>. The library seemed old, with no recent activity. I mentioned this to Claude; he was happy to suggest an alternative approach of <code>tgls</code>, thin OpenGL bindings, with <code>tsdl</code>, SDL2 bindings, or the higher-level API from <code>raylib</code>. The idea of a high-level API sounded better, so I asked Claude to rewrite it with <code>raylib</code>.</p>\n\n<p>The code had some compilation issues. Claude had proposed <code>Mesh.gen_cube</code>, which didn’t exist. Claude consulted the API documentation and found <code>gen_mesh_cube</code> instead. This went through several iterations, with <code>Model.load</code> becoming <code>load_model</code> and <code>Model.draw_ex</code> becoming <code>draw_model_ex</code>, etc. Twenty-two versions later, the code nearly compiles. This block continued to fail with two issues. The first being <code>Array.find</code> doesn’t exist and the second being that the type inferred for <code>a</code> was wrong. There are two types and they both contain <code>target: string;</code>. I manually fixed this with <code>(a:animation_channel)</code> and used <code>match Array.find_opt ... with</code> instead of the <code>try ... with</code>.</p>\n\n<div><div><pre><code><span>(* Update animations *)</span>\n<span>let</span> <span>update_object_animations</span> <span>objects</span> <span>animations</span> <span>elapsed_time</span> <span>=</span>\n <span>Array</span><span>.</span><span>map</span> <span>(</span><span>fun</span> <span>obj</span> <span>-&gt;</span>\n <span>try</span>\n <span>let</span> <span>anim</span> <span>=</span> <span>Array</span><span>.</span><span>find</span> <span>(</span><span>fun</span> <span>a</span> <span>-&gt;</span> <span>a</span><span>.</span><span>target</span> <span>=</span> <span>obj</span><span>.</span><span>name</span><span>)</span> <span>animations</span> <span>in</span>\n <span>(* Loop animation *)</span>\n <span>let</span> <span>loop_time</span> <span>=</span> <span>mod_float</span> <span>elapsed_time</span> <span>anim</span><span>.</span><span>duration</span> <span>in</span>\n <span>let</span> <span>new_transform</span> <span>=</span> <span>interpolate_animation</span> <span>anim</span> <span>loop_time</span> <span>in</span>\n <span>{</span> <span>obj</span> <span>with</span> <span>current_transform</span> <span>=</span> <span>new_transform</span> <span>}</span>\n <span>with</span>\n <span>Not_found</span> <span>-&gt;</span> <span>obj</span>\n <span>)</span> <span>objects</span>\n</code></pre></div></div>\n\n<p>There were still many unused variables, but the code could be built using <code>dune build --release</code>.</p>\n\n<p>Unfortunately, it couldn’t load my Collada file as the load functions were just stubs! Claude duly obliged and wrote a simple XML parser using regular expressions through the <code>Str</code> library, but interestingly suggested that I include <code>xmlm</code> as a dependency. Adding the parser broke the code, and it no longer compiled. The issue was similar to above; the compiler had inferred a type that wasn’t what Claude expected. I fixed this as above. The code also had some issues with the ordering - functions were used before they were defined. Again, this was an easy fix.</p>\n\n<p>The parser still didn’t work, so I suggested ditching the regular expression-based approach and using <code>xmlm</code> instead. This loaded the mesh; it looked bad, but I could see that it was my mesh. However, it still didn’t animate, and I took a wrong turn here. I told Claude that the Collada file contained both the mesh and the animation, but that’s not right. It has been a while since I created the Collada files, and I had forgotten that the animation and the mesh definitions were in different files.</p>\n\n<p>I asked Claude to improve the parser so that it would expect the animation data to be in the same file as the mesh. This is within the specification for Collada, but this was not the structure of my file.</p>\n\n<p>Is there a better approach than dealing with the complexity of writing a Collada XML parser? What formats are supported by <code>raylib</code>?</p>\n\n<p>In a new thread, I asked, “Using OCaml with Raylib, what format should I use for my 3D mode and animation data?”. Claude suggested GLTF 2.0. As my animation is in Blender, it can be exported in GLTF format. Let’s try it!</p>\n\n<p>Claude used the <code>raylib</code> library to read and display a GLTF file and run the animation. The code was much shorter, but … it didn’t compile. I wrote to Claude, “The API for Raylib appears to be different to the one you have used. For example, <code>camera3d.create</code> doesn’t take named parameters, <code>camera3d.prespective</code> should be <code>cameraprojection.perspective</code> etc.” We set to work, and a dozen versions later, we built it successfully.</p>\n\n<p>It didn’t work, though; the console produced an error over and over:</p>\n\n<div><div><pre><code>Joint attribute data format not supported, use vec4 u8\n</code></pre></div></div>\n\n<p>This looked like a problem with the model. I wondered if my GLTF file was compatible with <code>raylib</code>. I asked Claude if he knew of any validation tools, and he suggested an online viewer. This loaded my file perfectly and animated it in the browser. Claude also gave me some simple code to validate, which only loaded the model.</p>\n\n<div><div><pre><code><span>let</span> <span>main</span> <span>()</span> <span>=</span>\n <span>init_window</span> <span>800</span> <span>600</span> <span>\"Static Model Test\"</span><span>;</span>\n <span>let</span> <span>camera</span> <span>=</span> <span>Camera3D</span><span>.</span><span>create</span>\n <span>(</span><span>Vector3</span><span>.</span><span>create</span> <span>25</span><span>.</span><span>0</span> <span>25</span><span>.</span><span>0</span> <span>25</span><span>.</span><span>0</span><span>)</span>\n <span>(</span><span>Vector3</span><span>.</span><span>create</span> <span>0</span><span>.</span><span>0</span> <span>0</span><span>.</span><span>0</span> <span>0</span><span>.</span><span>0</span><span>)</span>\n <span>(</span><span>Vector3</span><span>.</span><span>create</span> <span>0</span><span>.</span><span>0</span> <span>1</span><span>.</span><span>0</span> <span>0</span><span>.</span><span>0</span><span>)</span>\n <span>45</span><span>.</span><span>0</span> <span>CameraProjection</span><span>.</span><span>Perspective</span> <span>in</span>\n\n <span>let</span> <span>model</span> <span>=</span> <span>load_model</span> <span>\"assets/character.gltf\"</span> <span>in</span>\n\n <span>while</span> <span>not</span> <span>(</span><span>window_should_close</span> <span>()</span><span>)</span> <span>do</span>\n <span>begin_drawing</span> <span>()</span><span>;</span>\n <span>clear_background</span> <span>Color</span><span>.</span><span>darkgray</span><span>;</span>\n <span>begin_mode_3d</span> <span>camera</span><span>;</span>\n <span>draw_model</span> <span>model</span> <span>(</span><span>Vector3</span><span>.</span><span>create</span> <span>0</span><span>.</span><span>0</span> <span>0</span><span>.</span><span>0</span> <span>0</span><span>.</span><span>0</span><span>)</span> <span>1</span><span>.</span><span>0</span> <span>Color</span><span>.</span><span>white</span><span>;</span>\n <span>draw_grid</span> <span>10</span> <span>1</span><span>.</span><span>0</span><span>;</span>\n <span>end_mode_3d</span> <span>()</span><span>;</span>\n <span>draw_text</span> <span>\"Static Model Test\"</span> <span>10</span> <span>10</span> <span>20</span> <span>Color</span><span>.</span><span>white</span><span>;</span>\n <span>end_drawing</span> <span>()</span>\n <span>done</span><span>;</span>\n\n <span>unload_model</span> <span>model</span><span>;</span>\n <span>close_window</span> <span>()</span>\n</code></pre></div></div>\n\n<p>Even this didn’t work! As I said at the top, it’s been a few years since I looked at this, and I still had Blender installed on my machine: version 2.83.4. The current version is 4.4, so I decided to upgrade. The GLTF export in 4.4 didn’t work on my Mac and instead displayed a page of Python warnings about <code>numpy</code>. On the Blender Forum, this <a href=\"https://blenderartists.org/t/multiple-addons-giving-numpy-errors-blender-4-4-mac/1590436/2\">thread</a> showed me how to fix it. Armed with a new GLTF file, the static test worked. Returning to the animation code showed that it worked with the updated file; however, there are some significant visual distortions. These aren’t present when viewed in Blender, which I think comes down to how the library interpolates between keyframes. I will look into this another day.</p>\n\n<p>I enjoyed the collaborative approach. I’m annoyed with myself for not remembering the separate file with the animation data. However, I think the change of direction from Collada to GLTF was a good decision, and the speed at which Claude can explore ideas is very impressive.</p>", 9 "content_type": "html", 10 "author": { 11 "name": "Mark Elvers", 12 "email": "mark.elvers@tunbury.org", 13 "uri": null 14 }, 15 "categories": [ 16 "claude,collada,gltf", 17 "tunbury.org" 18 ], 19 "source": "https://www.tunbury.org/atom.xml" 20}