Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions src/webgl/GeometryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,27 @@ class GeometryBuilder {
for (const f of input.faces) {
part.faces.push(f.map(idx => idx + startIdx));
}

// carry custom per-vertex attributes into the part too, the same way the
// combined geometry does above. draws within a part can use different
// properties, so pad the gaps: props the part already has but this draw
// lacks get zeros for the new verts, and a prop new to the part backfills
// zeros for the verts already in it.
const inputProps = input.userVertexProperties;
const partProps = part.userVertexProperties;
for (const propName in partProps) {
if (propName in inputProps) continue;
const size = partProps[propName].getDataSize();
partProps[propName].pushDirect(Array(size * vertices.length).fill(0));
}
for (const propName in inputProps) {
const inProp = inputProps[propName];
const size = inProp.getDataSize();
if (startIdx > 0 && !(propName in partProps)) {
part.vertexProperty(propName, Array(size * startIdx).fill(0), size);
}
part.vertexProperty(propName, inProp.getSrcArray(), size);
}
}

/**
Expand Down Expand Up @@ -266,13 +287,11 @@ class GeometryBuilder {
*/
finish() {
this.renderer._pInst.pop();
// expose the material parts only when there really are multiple materials,
// and not while custom per-vertex attributes are in play (those aren't
// split per part yet). single-material builds keep the geometry as its own
// expose the material parts whenever there really are multiple materials.
// custom per-vertex attributes are now carried per part too, so they work
// alongside the split. single-material builds keep the geometry as its own
// part, so nothing changes for them (zero regression).
const hasUserProps =
Object.keys(this.geometry.userVertexProperties).length > 0;
if (this.parts.length >= 2 && !hasUserProps) {
if (this.parts.length >= 2) {
this.geometry.parts = this.parts;
}
return this.geometry;
Expand Down
69 changes: 7 additions & 62 deletions src/webgl/p5.Geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

import * as constants from '../core/constants';
import { DataArray } from './p5.DataArray';
import { GeometryPart, createPartState } from './p5.GeometryPart';
import {
GeometryPart,
createPartState,
createUserVertexProperty
} from './p5.GeometryPart';
import { Vector } from '../math/p5.Vector';
import { downloadFile } from '../io/utilities';

Expand Down Expand Up @@ -1814,67 +1818,8 @@ class Geometry {
}

_userVertexPropertyHelper(propertyName, data, size) {
const geometryInstance = this;
const prop = (this.userVertexProperties[propertyName] = {
name: propertyName,
dataSize: size ? size : data.length ? data.length : 1,
geometry: geometryInstance,
// Getters
getName() {
return this.name;
},
getCurrentData() {
if (this.currentData === undefined) {
this.currentData = new Array(this.getDataSize()).fill(0);
}
return this.currentData;
},
getDataSize() {
return this.dataSize;
},
getSrcName() {
const src = this.name.concat('Src');
return src;
},
getDstName() {
const dst = this.name.concat('Buffer');
return dst;
},
getSrcArray() {
const srcName = this.getSrcName();
return this.geometry[srcName];
},
//Setters
setCurrentData(data) {
const size = data.length ? data.length : 1;
// if (size != this.getDataSize()){
// p5._friendlyError(`Custom vertex property '${this.name}' has been set with various data sizes. You can change it's name, or if it was an accident, set '${this.name}' to have the same number of inputs each time!`, 'vertexProperty()');
// }
this.currentData = data;
},
// Utilities
pushCurrentData() {
const data = this.getCurrentData();
this.pushDirect(data);
},
pushDirect(data) {
if (data.length) {
this.getSrcArray().push(...data);
} else {
this.getSrcArray().push(data);
}
},
resetSrcArray() {
this.geometry[this.getSrcName()] = [];
},
delete() {
const srcName = this.getSrcName();
delete this.geometry[srcName];
delete this;
}
});
this[prop.getSrcName()] = [];
return this.userVertexProperties[propertyName];
// shared with GeometryPart so parts carry custom attributes identically
return createUserVertexProperty(this, propertyName, data, size);
}
}

Expand Down
73 changes: 72 additions & 1 deletion src/webgl/p5.GeometryPart.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,62 @@ function createPartState() {
};
}

// build a custom vertex-property accessor bound to `owner` (a p5.Geometry or a
// GeometryPart). the raw data lives on owner[name + 'Src'] and the renderer
// reads it back through getSrcArray()/getDataSize(). shared between geometries
// and parts so a per-material part carries custom attributes the same way the
// whole geometry does.
function createUserVertexProperty(owner, propertyName, data, size) {
const prop = (owner.userVertexProperties[propertyName] = {
name: propertyName,
dataSize: size ? size : data.length ? data.length : 1,
geometry: owner,
getName() {
return this.name;
},
getCurrentData() {
if (this.currentData === undefined) {
this.currentData = new Array(this.getDataSize()).fill(0);
}
return this.currentData;
},
getDataSize() {
return this.dataSize;
},
getSrcName() {
return this.name.concat('Src');
},
getDstName() {
return this.name.concat('Buffer');
},
getSrcArray() {
return this.geometry[this.getSrcName()];
},
setCurrentData(data) {
this.currentData = data;
},
pushCurrentData() {
this.pushDirect(this.getCurrentData());
},
pushDirect(data) {
if (data.length) {
this.getSrcArray().push(...data);
} else {
this.getSrcArray().push(data);
}
},
resetSrcArray() {
this.geometry[this.getSrcName()] = [];
},
delete() {
delete this.geometry[this.getSrcName()];
delete this;
}
});
owner[prop.getSrcName()] = [];
return owner.userVertexProperties[propertyName];
}

// one part of a geometry. a multi-material model is a p5.Geometry made of
// several parts, each holding the verts/faces/uvs for one material plus the
// state to draw them. single-material models are just one part.
Expand All @@ -42,6 +98,21 @@ class GeometryPart {
this.userVertexProperties = {};
}

// append custom per-vertex attribute data to this part, same shape as
// p5.Geometry.vertexProperty so the renderer binds it identically.
vertexProperty(propertyName, data, size) {
let prop = this.userVertexProperties[propertyName];
if (!prop) {
prop = createUserVertexProperty(this, propertyName, data, size);
}
if (size) {
prop.pushDirect(data);
} else {
prop.setCurrentData(data);
prop.pushCurrentData();
}
}

// the renderer needs this to pick a blend mode. a part is transparent if its
// fill has alpha below 1, or any of its vertex colors does.
hasFillTransparency() {
Expand All @@ -54,4 +125,4 @@ class GeometryPart {
}
}

export { GeometryPart, createPartState };
export { GeometryPart, createPartState, createUserVertexProperty };
44 changes: 44 additions & 0 deletions test/unit/webgl/p5.Geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,50 @@ suite('p5.Geometry', function () {
.not.toEqual(geom.parts[1].partState.texture);
});

test('custom vertex attributes survive the multi-material part split',
function() {
myp5.createCanvas(50, 50, myp5.WEBGL);
const texA = myp5.createGraphics(10, 10);
const texB = myp5.createGraphics(10, 10);
const geom = myp5.buildGeometry(() => {
// first material: aCustom values in 1..3
myp5.texture(texA);
myp5.beginShape(myp5.TRIANGLES);
myp5.vertexProperty('aCustom', 1);
myp5.vertex(-10, -10, 0);
myp5.vertexProperty('aCustom', 2);
myp5.vertex(10, -10, 0);
myp5.vertexProperty('aCustom', 3);
myp5.vertex(0, 10, 0);
myp5.endShape();
// second material: aCustom values in 4..6
myp5.texture(texB);
myp5.beginShape(myp5.TRIANGLES);
myp5.vertexProperty('aCustom', 4);
myp5.vertex(-10, -10, 0);
myp5.vertexProperty('aCustom', 5);
myp5.vertex(10, -10, 0);
myp5.vertexProperty('aCustom', 6);
myp5.vertex(0, 10, 0);
myp5.endShape();
});

// the texture change still splits into two parts even with a custom attr
expect(geom.parts.length).toEqual(2);

const a0 = geom.parts[0].userVertexProperties.aCustom;
const a1 = geom.parts[1].userVertexProperties.aCustom;
expect(a0).toBeTruthy();
expect(a1).toBeTruthy();
// one value per vertex, aligned to each part
expect(a0.getSrcArray().length).toEqual(geom.parts[0].vertices.length);
expect(a1.getSrcArray().length).toEqual(geom.parts[1].vertices.length);
// and each part only carries its own draw's values
expect(a0.getSrcArray().every(v => v >= 1 && v <= 3)).toBe(true);
expect(a1.getSrcArray().every(v => v >= 4 && v <= 6)).toBe(true);
}
);

test('a fill change alone does not split the build', function() {
myp5.createCanvas(50, 50, myp5.WEBGL);
const geom = myp5.buildGeometry(() => {
Expand Down
Loading