DecalGeometry constructor
DecalGeometry(
- Mesh mesh,
- Vector3 position,
- Euler orientation, [
- Vector3? size,
mesh
— Any mesh object.
position
— Position of the decal projector.
orientation
— Orientation of the decal projector.
size
— Size of the decal projector.
Implementation
DecalGeometry(Mesh mesh, Vector3 position, Euler orientation,[ Vector3? size ]):super(){
size ??= Vector3(1,1,1);
final List<double> vertices = [];
final List<double> normals = [];
final List<double> uvs = [];
final plane = Vector3();
final normalMatrix = Matrix3.identity().getNormalMatrix( mesh.matrixWorld );
// this matrix represents the transformation of the decal projector
final projectorMatrix = Matrix4.identity();
projectorMatrix.makeRotationFromEuler( orientation );
projectorMatrix.setPosition( position.x,position.y,position.z );
final projectorMatrixInverse = Matrix4.identity();
projectorMatrixInverse.setFrom( projectorMatrix ).invert();
void pushDecalVertex(List<DecalVertex> decalVertices,Vector3 vertex,[Vector3? normal]) {
// transform the vertex to world space, then to projector space
vertex.applyMatrix4( mesh.matrixWorld );
vertex.applyMatrix4( projectorMatrixInverse );
if ( normal != null) {
normal.applyNormalMatrix(normalMatrix);
decalVertices.add(DecalVertex(vertex.clone(), normal.clone()));
}
else {
decalVertices.add(DecalVertex(vertex.clone()));
}
}
DecalVertex clip(DecalVertex v0, DecalVertex v1, p, s ) {
final d0 = v0.position.dot( p ) - s;
final d1 = v1.position.dot( p ) - s;
final s0 = d0 / ( d0 - d1 );
final position = Vector3(
v0.position.x + s0 * ( v1.position.x - v0.position.x ),
v0.position.y + s0 * ( v1.position.y - v0.position.y ),
v0.position.z + s0 * ( v1.position.z - v0.position.z )
);
Vector3? normal;
if (v0.normal != null && v1.normal != null) {
normal = Vector3(
v0.normal!.x + s0 * ( v1.normal!.x - v0.normal!.x ),
v0.normal!.y + s0 * ( v1.normal!.y - v0.normal!.y ),
v0.normal!.z + s0 * ( v1.normal!.z - v0.normal!.z )
);
}
final v = DecalVertex( position, normal );
// need to clip more values (texture coordinates)? do it this way:
// intersectpoint.value = a.value + s * ( b.value - a.value );
return v;
}
List<DecalVertex> clipGeometry(List<DecalVertex> inVertices, plane ) {
final List<DecalVertex> outVertices = [];
final s = 0.5 * size!.dot( plane ).abs();
// a single iteration clips one face,
// which consists of three consecutive 'DecalVertex' objects
for (int i = 0; i < inVertices.length; i += 3 ) {
int total = 0;
late DecalVertex nV1;
late DecalVertex nV2;
late DecalVertex nV3;
late DecalVertex nV4;
final d1 = inVertices[ i + 0 ].position.dot( plane ) - s;
final d2 = inVertices[ i + 1 ].position.dot( plane ) - s;
final d3 = inVertices[ i + 2 ].position.dot( plane ) - s;
final v1Out = d1 > 0;
final v2Out = d2 > 0;
final v3Out = d3 > 0;
// calculate, how many vertices of the face lie outside of the clipping plane
total = ( v1Out ? 1 : 0 ) + ( v2Out ? 1 : 0 ) + ( v3Out ? 1 : 0 );
switch ( total ) {
case 0: {
// the entire face lies inside of the plane, no clipping needed
outVertices.add( inVertices[ i ] );
outVertices.add( inVertices[ i + 1 ] );
outVertices.add( inVertices[ i + 2 ] );
break;
}
case 1: {
// one vertex lies outside of the plane, perform clipping
if ( v1Out ) {
nV1 = inVertices[ i + 1 ];
nV2 = inVertices[ i + 2 ];
nV3 = clip( inVertices[ i ], nV1, plane, s );
nV4 = clip( inVertices[ i ], nV2, plane, s );
}
if ( v2Out ) {
nV1 = inVertices[ i ];
nV2 = inVertices[ i + 2 ];
nV3 = clip( inVertices[ i + 1 ], nV1, plane, s );
nV4 = clip( inVertices[ i + 1 ], nV2, plane, s );
outVertices.add( nV3 );
outVertices.add( nV2.clone() );
outVertices.add( nV1.clone() );
outVertices.add( nV2.clone() );
outVertices.add( nV3.clone() );
outVertices.add( nV4 );
break;
}
if ( v3Out ) {
nV1 = inVertices[ i ];
nV2 = inVertices[ i + 1 ];
nV3 = clip( inVertices[ i + 2 ], nV1, plane, s );
nV4 = clip( inVertices[ i + 2 ], nV2, plane, s );
}
outVertices.add( nV1.clone() );
outVertices.add( nV2.clone() );
outVertices.add( nV3 );
outVertices.add( nV4 );
outVertices.add( nV3.clone() );
outVertices.add( nV2.clone() );
break;
}
case 2: {
// two vertices lies outside of the plane, perform clipping
if ( ! v1Out ) {
nV1 = inVertices[ i ].clone();
nV2 = clip( nV1, inVertices[ i + 1 ], plane, s );
nV3 = clip( nV1, inVertices[ i + 2 ], plane, s );
outVertices.add( nV1 );
outVertices.add( nV2 );
outVertices.add( nV3 );
}
if ( ! v2Out ) {
nV1 = inVertices[ i + 1 ].clone();
nV2 = clip( nV1, inVertices[ i + 2 ], plane, s );
nV3 = clip( nV1, inVertices[ i ], plane, s );
outVertices.add( nV1 );
outVertices.add( nV2 );
outVertices.add( nV3 );
}
if ( ! v3Out ) {
nV1 = inVertices[ i + 2 ].clone();
nV2 = clip( nV1, inVertices[ i ], plane, s );
nV3 = clip( nV1, inVertices[ i + 1 ], plane, s );
outVertices.add( nV1 );
outVertices.add( nV2 );
outVertices.add( nV3 );
}
break;
}
case 3: {
// the entire face lies outside of the plane, so let's discard the corresponding vertices
break;
}
}
}
return outVertices;
}
void generate() {
List<DecalVertex> decalVertices = [];
final vertex = Vector3();
final normal = Vector3();
// handle different geometry types
final geometry = mesh.geometry;
final positionAttribute = geometry?.attributes['position'];
final normalAttribute = geometry?.attributes['normal'];
// first, create an array of 'DecalVertex' objects
// three consecutive 'DecalVertex' objects represent a single face
//
// this data structure will be later used to perform the clipping
if ( geometry?.index != null ) {
// indexed BufferGeometry
final index = geometry!.index;
for (int i = 0; i < index!.count; i ++ ) {
vertex.fromBuffer( positionAttribute, index.getX( i )!.toInt() );
if ( normalAttribute != null) {
normal.fromBuffer( normalAttribute, index.getX( i )!.toInt() );
pushDecalVertex( decalVertices, vertex, normal );
}
else{
pushDecalVertex( decalVertices, vertex );
}
}
}
else {
// non-indexed BufferGeometry
for (int i = 0; i < positionAttribute.count; i ++ ) {
vertex.fromBuffer( positionAttribute, i );
if ( normalAttribute != null) {
normal.fromBuffer( normalAttribute, i );
pushDecalVertex( decalVertices, vertex, normal );
}
else {
pushDecalVertex( decalVertices, vertex );
}
}
}
// second, clip the geometry so that it doesn't extend out from the projector
decalVertices = clipGeometry( decalVertices, plane.setValues( 1, 0, 0 ) );
decalVertices = clipGeometry( decalVertices, plane.setValues( - 1, 0, 0 ) );
decalVertices = clipGeometry( decalVertices, plane.setValues( 0, 1, 0 ) );
decalVertices = clipGeometry( decalVertices, plane.setValues( 0, - 1, 0 ) );
decalVertices = clipGeometry( decalVertices, plane.setValues( 0, 0, 1 ) );
decalVertices = clipGeometry( decalVertices, plane.setValues( 0, 0, - 1 ) );
// third, generate final vertices, normals and uvs
for (int i = 0; i < decalVertices.length; i ++ ) {
final decalVertex = decalVertices[ i ];
// create texture coordinates (we are still in projector space)
uvs.addAll([
0.5 + ( decalVertex.position.x / size!.x ),
0.5 + ( decalVertex.position.y / size.y )
]);
// transform the vertex back to world space
decalVertex.position.applyMatrix4( projectorMatrix );
// now create vertex and normal buffer data
vertices.addAll( [decalVertex.position.x, decalVertex.position.y, decalVertex.position.z] );
if ( decalVertex.normal != null ) {
normals.addAll( [decalVertex.normal!.x, decalVertex.normal!.y, decalVertex.normal!.z] );
}
}
}
// generate buffers
generate();
// build geometry
setAttributeFromString( 'position', Float32BufferAttribute.fromList( vertices, 3 ) );
setAttributeFromString( 'normal', Float32BufferAttribute.fromList( normals, 3 ) );
setAttributeFromString( 'uv', Float32BufferAttribute.fromList( uvs, 2 ) );
}