the renderer wants the geometry
fixing the grey blob for real, and the four wrong turns it took to get there
last time i wrote about the grey blob, when you load a 3d model with more than one material into p5 and it shows up flat and grey, all the material info quietly thrown away. this week was about actually fixing it, and it did not go in a straight line
the plan was simple on paper. instead of mashing the whole model into one shape, split it into one piece per material. each piece keeps its own vertices and its own look (colour, texture, shininess), and the renderer draws them one at a time. the public api does not change at all, you still just call loadModel() and model(), everything happens underneath
the reading side went fine. i taught the parser to read the full .mtl file, not just the diffuse colour it used to grab. then i split the faces into per-material groups, each with its own localised set of vertices, and loaded the textures. wrote tests, all green. felt good
then came the renderer, and that is where it got humbling
the idea was to make the renderer always loop over the parts and draw each one. for a single-material model that is just one part, so nothing should change. easy, right. i made the single part a little wrapper that pointed back at the geometry, ran the tests, and 84 of them went red. okay. tried sharing more state between the wrapper and the geometry, 77 red. tried making them share the same id, 76 red. got fancy and made the wrapper a full javascript proxy that forwarded everything, and that made it way worse, 524 red. at that point i sat back and actually read why
every single time, i was making a separate object to stand in for the geometry. and that stand-in always drifted from the real thing in some way the renderer secretly depended on, a different cache key here, a stale dirty flag there, a missing field the buffer system needed. the renderer does not want a thing that looks like the geometry. it wants the geometry
so the fix was kind of dumb in hindsight. for a single-material model, do not wrap it in anything. just let the geometry be its own first part. parts = [this]. drawing the part is now literally drawing the geometry, same object, same everything, nothing to drift. the renderer loops over parts the same way for everyone, and the single case is byte for byte what it always was. full suite green, including the visual tests that actually compare pixels, and the webgpu ones too
the nice part is the multi-material test that already existed in the repo, the one that loads an 8-colour model, passed without me touching it. that model used to be the grey blob. now it renders in 8 colours and matches the reference screenshot. that was the moment it felt real (i was stupidly happy, no shame)
biggest lesson this week: when something keeps fighting you, stop adding cleverness and go read why it is actually breaking. four of my five attempts were me being clever. the one that worked was the one where i stopped
more soon
