edgeSplit static method
dynamic
edgeSplit(
- dynamic geometry
////////////////////////////////////////////////////////////////////////////////// // Split Hypotenuse /////////////////
Applies one iteration of split subdivision. Splits all triangles at edges shared by coplanar triangles. Starts by splitting at longest shared edge, followed by splitting from that center edge point to the center of any other shared edges.
Implementation
///
/// Applies one iteration of split subdivision. Splits all triangles at edges shared by coplanar triangles.
/// Starts by splitting at longest shared edge, followed by splitting from that center edge point to the
/// center of any other shared edges.
///
static edgeSplit(geometry) {
///// Geometries
if (! verifyGeometry(geometry)) return geometry;
final existing = (geometry.index != null) ? geometry.toNonIndexed() : geometry.clone();
final split = BufferGeometry();
///// Attributes
final attributeList = gatherAttributes(existing);
final vertexCount = existing.attributes['position'].count;
final posAttribute = existing.getAttributeFromString('position');
final norAttribute = existing.getAttributeFromString('normal');
final edgeHashToTriangle = {};
final triangleEdgeHashes = [];
final edgeLength = {};
final triangleExist = [];
///// Edges
for (int i = 0; i < vertexCount; i += 3) {
// Positions
_vector0.fromBuffer(posAttribute, i + 0);
_vector1.fromBuffer(posAttribute, i + 1);
_vector2.fromBuffer(posAttribute, i + 2);
_normal.fromBuffer(norAttribute, i);
final vecHash0 = hashFromVector(_vector0);
final vecHash1 = hashFromVector(_vector1);
final vecHash2 = hashFromVector(_vector2);
// Verify Area
final triangleSize = _triangle.set(_vector0, _vector1, _vector2).getArea();
triangleExist.add(! fuzzy(triangleSize, 0));
if (! triangleExist[i ~/ 3]) {
triangleEdgeHashes.add([]);
continue;
}
// Calculate Normals
calcNormal(_normal, _vector0, _vector1, _vector2);
final normalHash = hashFromVector(_normal);
// Vertex Hashes
final hashes = [
'${vecHash0}_${vecHash1}_$normalHash', // [0]: 0to1
'${vecHash1}_${vecHash0}_$normalHash', // [1]: 1to0
'${vecHash1}_${vecHash2}_$normalHash', // [2]: 1to2
'${vecHash2}_${vecHash1}_$normalHash', // [3]: 2to1
'${vecHash2}_${vecHash0}_$normalHash', // [4]: 2to0
'${vecHash0}_${vecHash2}_$normalHash', // [5]: 0to2
];
// Store Edge Hashes
final index = i / 3;
for (int j = 0; j < hashes.length; j++) {
// Attach Triangle Index to Edge Hash
if (edgeHashToTriangle[hashes[j]] == null) edgeHashToTriangle[hashes[j]] = [];
edgeHashToTriangle[hashes[j]].add(index);
// Edge Length
if (edgeLength[hashes[j]] == null) {
if (j == 0 || j == 1) edgeLength[hashes[j]] = _vector0.distanceTo(_vector1);
if (j == 2 || j == 3) edgeLength[hashes[j]] = _vector1.distanceTo(_vector2);
if (j == 4 || j == 5) edgeLength[hashes[j]] = _vector2.distanceTo(_vector0);
}
}
// Triangle Edge Reference
triangleEdgeHashes.add([ hashes[0], hashes[2], hashes[4] ]);
}
// Loop Subdivide Function
List<double> splitAttribute(BufferAttribute attribute, String attributeName, [bool morph = false]) {
const newTriangles = 4; /* maximum number of triangles */
final arrayLength = (vertexCount * attribute.itemSize) * newTriangles;
final List<num> floatArray = List.filled(arrayLength, 0, growable: true);//attribute.array.sublist(0,arrayLength);//.constructor(arrayLength);
floatArray.replaceRange(0, attribute.array.length, attribute.array.sublist(0));
final processGroups = (attributeName == 'position' && ! morph && existing.groups.length > 0);
int? groupStart;
int? groupMaterial;
int index = 0;
int skipped = 0;
int step = attribute.itemSize;
for (int i = 0; i < vertexCount; i += 3) {
// Verify Triangle is Valid
if (! triangleExist[i ~/ 3]) {
skipped += 3;
continue;
}
// Get Triangle Points
_vector0.fromBuffer(attribute, i + 0);
_vector1.fromBuffer(attribute, i + 1);
_vector2.fromBuffer(attribute, i + 2);
// Check for Shared Edges
final existingIndex = i ~/ 3;
final edgeHash0to1 = triangleEdgeHashes[existingIndex][0];
final edgeHash1to2 = triangleEdgeHashes[existingIndex][1];
final edgeHash2to0 = triangleEdgeHashes[existingIndex][2];
final edgeCount0to1 = edgeHashToTriangle[edgeHash0to1].length;
final edgeCount1to2 = edgeHashToTriangle[edgeHash1to2].length;
final edgeCount2to0 = edgeHashToTriangle[edgeHash2to0].length;
final sharedCount = (edgeCount0to1 + edgeCount1to2 + edgeCount2to0) - 3;
// New Index (Before New Triangles, used for Groups)
final int loopStartIndex = ((index * 3) / step) ~/ 3;
// No Shared Edges
if (sharedCount == 0) {
setTriangle(floatArray, index, step, _vector0, _vector1, _vector2);
index += (step * 3);
}
else {
final length0to1 = edgeLength[edgeHash0to1];
final length1to2 = edgeLength[edgeHash1to2];
final length2to0 = edgeLength[edgeHash2to0];
// Add New Triangle Positions
if ((length0to1 > length1to2 || edgeCount1to2 <= 1) &&
(length0to1 > length2to0 || edgeCount2to0 <= 1) && edgeCount0to1 > 1) {
_center.setFrom(_vector0).add(_vector1).divideScalar(2.0);
if (edgeCount2to0 > 1) {
_midpoint.setFrom(_vector2).add(_vector0).divideScalar(2.0);
setTriangle(floatArray, index, step, _vector0, _center, _midpoint); index += (step * 3);
setTriangle(floatArray, index, step, _center, _vector2, _midpoint); index += (step * 3);
} else {
setTriangle(floatArray, index, step, _vector0, _center, _vector2); index += (step * 3);
}
if (edgeCount1to2 > 1) {
_midpoint.setFrom(_vector1).add(_vector2).divideScalar(2.0);
setTriangle(floatArray, index, step, _center, _vector1, _midpoint); index += (step * 3);
setTriangle(floatArray, index, step, _midpoint, _vector2, _center); index += (step * 3);
} else {
setTriangle(floatArray, index, step, _vector1, _vector2, _center); index += (step * 3);
}
} else if ((length1to2 > length2to0 || edgeCount2to0 <= 1) && edgeCount1to2 > 1) {
_center.setFrom(_vector1).add(_vector2).divideScalar(2.0);
if (edgeCount0to1 > 1) {
_midpoint.setFrom(_vector0).add(_vector1).divideScalar(2.0);
setTriangle(floatArray, index, step, _center, _midpoint, _vector1); index += (step * 3);
setTriangle(floatArray, index, step, _midpoint, _center, _vector0); index += (step * 3);
} else {
setTriangle(floatArray, index, step, _vector1, _center, _vector0); index += (step * 3);
}
if (edgeCount2to0 > 1) {
_midpoint.setFrom(_vector2).add(_vector0).divideScalar(2.0);
setTriangle(floatArray, index, step, _center, _vector2, _midpoint); index += (step * 3);
setTriangle(floatArray, index, step, _midpoint, _vector0, _center); index += (step * 3);
} else {
setTriangle(floatArray, index, step, _vector2, _vector0, _center); index += (step * 3);
}
} else if (edgeCount2to0 > 1) {
_center.setFrom(_vector2).add(_vector0).divideScalar(2.0);
if (edgeCount1to2 > 1) {
_midpoint.setFrom(_vector1).add(_vector2).divideScalar(2.0);
setTriangle(floatArray, index, step, _vector2, _center, _midpoint); index += (step * 3);
setTriangle(floatArray, index, step, _center, _vector1, _midpoint); index += (step * 3);
} else {
setTriangle(floatArray, index, step, _vector2, _center, _vector1); index += (step * 3);
}
if (edgeCount0to1 > 1) {
_midpoint.setFrom(_vector0).add(_vector1).divideScalar(2.0);
setTriangle(floatArray, index, step, _vector0, _midpoint, _center); index += (step * 3);
setTriangle(floatArray, index, step, _midpoint, _vector1, _center); index += (step * 3);
} else {
setTriangle(floatArray, index, step, _vector0, _vector1, _center); index += (step * 3);
}
} else {
setTriangle(floatArray, index, step, _vector0, _vector1, _vector2); index += (step * 3);
}
}
// Process Groups
if (processGroups) {
existing.groups.forEach((group){
if (group['start'] == (i - skipped)) {
if (groupStart != null && groupMaterial != null) {
split.addGroup(groupStart!, loopStartIndex - groupStart!, groupMaterial!);
}
groupStart = loopStartIndex;
groupMaterial = group['materialIndex'];
}
});
}
// Reset Skipped Triangle Counter
skipped = 0;
}
// Resize Array
final reducedCount = (index * 3) ~/ step;
final List<double> reducedArray = List.filled(reducedCount, 0.0);//attribute.array.sublist(0,reducedCount);//.constructor(reducedCount);
final len = floatArray.length > reducedCount?reducedCount:floatArray.length;
for (int i = 0; i < len; i++) {
reducedArray[i] = floatArray[i].toDouble();
}
// Final Group
if (processGroups && groupStart != null && groupMaterial != null) {
split.addGroup(groupStart!, (((index * 3) / step) ~/ 3) - groupStart!, groupMaterial!);
}
return reducedArray;
}
///// Build Geometry, Set Attributes
attributeList.forEach((attributeName){
final attribute = existing.getAttributeFromString(attributeName);
if (attribute == null) return;
final floatArray = splitAttribute(attribute, attributeName);
split.setAttributeFromString(attributeName, Float32BufferAttribute.fromList(floatArray, attribute.itemSize));
});
///// Morph Attributes
final morphAttributes = existing.morphAttributes;
for (final attributeName in morphAttributes.keys) {
final List<BufferAttribute> array = [];
final morphAttribute = morphAttributes[attributeName];
// Process Array of Float32BufferAttributes
for (int i = 0, l = morphAttribute.length; i < l; i++) {
if (morphAttribute[i].count != vertexCount) continue;
final floatArray = splitAttribute(morphAttribute[i], attributeName, true);
array.add(Float32BufferAttribute.fromList(floatArray, morphAttribute[i].itemSize));
}
split.morphAttributes[attributeName] = array;
}
split.morphTargetsRelative = existing.morphTargetsRelative;
// Clean Up, Return New Geometry
existing.dispose();
return split;
}