GSOC 26: render multi-material models per part#8955
Conversation
davepagurek
left a comment
There was a problem hiding this comment.
Looking good! I left a few comments. One thing that would be good to clarify is what the valid states of a geometry are (should they always have parts?) so that that can be an invariant in the code and simplifies implementation.
| // material around the draw. | ||
| const parts = geometry.parts && geometry.parts.length | ||
| ? geometry.parts | ||
| : [geometry]; |
There was a problem hiding this comment.
What's a scenario where we'd end up with no parts?
There was a problem hiding this comment.
there isn't one. the constructor always sets parts = [this] and loaders replace it, so a geometry always has at least one part. i'll make that an invariant and just loop geometry.parts directly, dropping the fallback
| if (!partState) return; | ||
| if (partState.fill) { | ||
| const c = partState.fill; | ||
| this.states.setValue('curFillColor', [c[0], c[1], c[2], 1]); |
There was a problem hiding this comment.
Is there a reason why it's not possible for a part to have alpha in the fill? Although the obj/mtl spec puts transparency into its own command, in p5 fills can have alpha (e.g. geometry that comes from buildGeometry would be able to have fills with alpha if a user calls something like fill(255, 100) in the middle of building it.) Maybe we can store the transparency from a mtl in the alpha field of its fill color?
There was a problem hiding this comment.
yeah that makes sense. fills carry alpha in p5, so i'll store the mtl d/Tr transparency in the fill colour's alpha (4th component) instead of forcing 1. that also covers the buildGeometry fill(255,100) case. nice, this brings transparency back in the right place
| suite('multi-material rendering', function() { | ||
| test('draws each part and passes the instance count through', async function() { | ||
| const renderer = myp5.createCanvas(50, 50, myp5.WEBGL); | ||
| const model = await new Promise(resolve => |
There was a problem hiding this comment.
I think loadModel(url) should already returns a promise, without needing to wrap it further
There was a problem hiding this comment.
right, will simplify to const model = await myp5.loadModel('/test/unit/assets/octa-color.obj') and drop the wrapper
|
|
||
| const spy = vi.spyOn(renderer, '_drawFills'); | ||
| myp5.background(255); | ||
| myp5.model(model, 4); |
There was a problem hiding this comment.
The second parameter here is for WebGL instanced rendering, is that intended to be set in this test?
There was a problem hiding this comment.
the 4 was to confirm the instance count reaches each part's draw, but you're right it muddies this test, i'll split it, assert per-part drawing on its own and keep the instancing check separate
| } | ||
|
|
||
| function geometryPart(p5, fn) { | ||
| p5.GeometryPart = GeometryPart; |
There was a problem hiding this comment.
I think this maybe could be done in the existing geometry addon, since there's not a way to use geometry without geometryPart currently. So this file could just export the class, and p5.Geometry can import it and add it to the p5 object
There was a problem hiding this comment.
agreed, no reason for a separate addon. i'll have p5.GeometryPart.js just export the class and let p5.Geometry import it and add it to the p5 object, dropping the extra registerAddon
| // octa-color has several materials, so several parts. | ||
| expect(model.parts.length).toBeGreaterThan(1); | ||
|
|
||
| const spy = vi.spyOn(renderer, '_drawFills'); |
There was a problem hiding this comment.
We should have a similar spy and assertion for _drawStrokes, which I believe we still need to implement.
There was a problem hiding this comment.
good catch, strokes aren't per-part yet, only fills. i'll add per-part stroke drawing and a _drawStrokes spy + assertion alongside the fills one
thanks for the review! on the invariant, yeah a geometry should always have at least one part, the constructor guarantees it (parts = [this]) and loaders replace it. i'll lean into that so the renderer just loops parts with no fallback, like you said, addressing the rest now |
|
heads up, the failing check is just flaky ci, not anything in this pr. |
part of my google summer of code 2026 project, "full texture support for .mtl files in p5.js". works toward #6924.
this is phase 3: the renderer side, building on the data + parser foundation in #8879. multi-material models now actually draw, one part per material, each with its own look. public api is unchanged, you still just call loadModel() and model().
project phases for context:
what's in here:
tests:
stacked on #8879, so it should go in after that one merges.