diff --git a/docs/api/geometries/DecalGeometry.html b/docs/api/geometries/DecalGeometry.html new file mode 100644 index 0000000000000000000000000000000000000000..a0b5b5c0c552f24ac006a01186d674fea6846457 --- /dev/null +++ b/docs/api/geometries/DecalGeometry.html @@ -0,0 +1,42 @@ + + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +
You can use this geometry to create a decal mesh, that serves different kinds of purposes e.g.: + adding unique details to models, performing dynamic visual environmental changes or covering seams.
+ +

Example

+ + [example:webgl_decals decals] + + var geometry = new THREE.DecalGeometry( mesh, position, orientation, size ); + var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + var decal = new THREE.Mesh( geometry, material ); + scene.add( decal ); + + +

Constructor

+ +

[name]( [page:Mesh mesh], [page:Vector3 position], [page:Euler orientation], [page:Vector3 size] )

+
+ mesh — Any mesh object.
+ position — Position of the decal projector.
+ orientation — Orientation of the decal projector.
+ size — Size of the decal projector.
+
+ +

Source

+ + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] + + diff --git a/docs/list.js b/docs/list.js index 3fa4ff941e1ecb2b6eb02216d2a1df8429c6e5cd..b3d5c46034bb4ed342ebfa4acace360778930bf3 100644 --- a/docs/list.js +++ b/docs/list.js @@ -140,6 +140,7 @@ var list = { [ "ConeGeometry", "api/geometries/ConeGeometry" ], [ "CylinderBufferGeometry", "api/geometries/CylinderBufferGeometry" ], [ "CylinderGeometry", "api/geometries/CylinderGeometry" ], + [ "DecalGeometry", "api/geometries/DecalGeometry" ], [ "DodecahedronBufferGeometry", "api/geometries/DodecahedronBufferGeometry" ], [ "DodecahedronGeometry", "api/geometries/DodecahedronGeometry" ], [ "EdgesGeometry", "api/geometries/EdgesGeometry" ], diff --git a/examples/webgl_decals.html b/examples/webgl_decals.html index 0b666a3c3957b0f7897f0156e53aa8713969510c..bb579f2a577ada18688baa56451cc141458e8aeb 100644 --- a/examples/webgl_decals.html +++ b/examples/webgl_decals.html @@ -1,30 +1,38 @@ - WebGL decals + three.js webgl - decals - Decal Splatter
-
-

- Decal Splatter
- Click or tap to shoot. -

-
+
three.js - decals - Decal Splatter (click or tap to shoot)
@@ -66,15 +74,13 @@ } ); var decals = []; - var decalHelper, mouseHelper; - var p = new THREE.Vector3(); - var r = new THREE.Euler(); - var s = new THREE.Vector3( 10, 10, 10 ); + var mouseHelper; + var position = new THREE.Vector3(); + var orientation = new THREE.Euler(); + var size = new THREE.Vector3( 10, 10, 10 ); var up = new THREE.Vector3( 0, 1, 0 ); - var check = new THREE.Vector3( 1, 1, 1 ); var params = { - projection: 'normal', minScale: 10, maxScale: 20, rotate: true, @@ -97,7 +103,7 @@ scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 1, 1000 ); - camera.position.z = 100; + camera.position.z = 120; camera.target = new THREE.Vector3(); controls = new THREE.OrbitControls( camera, renderer.domElement ); @@ -147,7 +153,7 @@ window.addEventListener( 'mouseup', function() { checkIntersection(); - if ( ! moved ) shoot(); + if ( ! moved && intersection.intersects ) shoot(); } ); @@ -212,7 +218,6 @@ var gui = new dat.GUI(); - gui.add( params, 'projection', { 'From cam to mesh': 'camera', 'Normal to mesh': 'normal' } ); gui.add( params, 'minScale', 1, 30 ); gui.add( params, 'maxScale', 1, 30 ); gui.add( params, 'rotate' ); @@ -235,7 +240,6 @@ map: textureLoader.load( 'obj/leeperrysmith/Map-COL.jpg' ), specularMap: textureLoader.load( 'obj/leeperrysmith/Map-SPEC.jpg' ), normalMap: textureLoader.load( 'obj/leeperrysmith/Infinite-Level_02_Tangent_SmoothUV.jpg' ), - normalScale: new THREE.Vector2( 0.75, 0.75 ), shininess: 25 } ); @@ -243,50 +247,25 @@ scene.add( mesh ); mesh.scale.set( 10, 10, 10 ); - //scene.add( new THREE.FaceNormalsHelper( mesh, 1 ) ); - //scene.add( new THREE.VertexNormalsHelper( mesh, 1 ) ); - } ); } function shoot() { - if ( params.projection == 'camera' ) { - - var dir = camera.target.clone(); - dir.sub( camera.position ); - - p = intersection.point; - - var m = new THREE.Matrix4(); - var c = dir.clone(); - c.negate(); - c.multiplyScalar( 10 ); - c.add( p ); - m.lookAt( p, c, up ); - m = m.extractRotation( m ); + position.copy( intersection.point ); + orientation.copy( mouseHelper.rotation ); - dummy = new THREE.Object3D(); - dummy.rotation.setFromRotationMatrix( m ); - r.copy( dummy.rotation ); - - } else { - - p = intersection.point; - r.copy( mouseHelper.rotation ); - - } + if ( params.rotate ) orientation.z = Math.random() * 2 * Math.PI; var scale = params.minScale + Math.random() * ( params.maxScale - params.minScale ); - s.set( scale, scale, scale ); - - if ( params.rotate ) r.z = Math.random() * 2 * Math.PI; + size.set( scale, scale, scale ); var material = decalMaterial.clone(); material.color.setHex( Math.random() * 0xffffff ); - var m = new THREE.Mesh( new THREE.DecalGeometry( mesh, p, r, s, check ), material ); + var m = new THREE.Mesh( new THREE.DecalGeometry( mesh, position, orientation, size ), material ); + decals.push( m ); scene.add( m ); @@ -297,9 +276,9 @@ decals.forEach( function( d ) { scene.remove( d ); - d = null; } ); + decals = []; } diff --git a/src/geometries/DecalGeometry.js b/src/geometries/DecalGeometry.js index 87b6d0ccd5a099b56641b59d8a53623d62b857ef..125d7d378b040e7ee2e2a6445c81d782f936c36a 100644 --- a/src/geometries/DecalGeometry.js +++ b/src/geometries/DecalGeometry.js @@ -5,16 +5,18 @@ import { Matrix4 } from '../math/Matrix4'; /** * @author Mugen87 / https://github.com/Mugen87 + * @author spite / https://github.com/spite + * + * reference: http://blog.wolfire.com/2009/06/how-to-project-decals/ + * */ -function DecalGeometry( mesh, position, rotation, dimensions, check ) { +function DecalGeometry( mesh, position, orientation, size ) { BufferGeometry.call( this ); this.type = 'DecalGeometry'; - check = check || new Vector3( 1, 1, 1 ); - // buffers var vertices = []; @@ -23,14 +25,16 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { // helpers + var plane = new Vector3(); + + // this matrix represents the transformation of the decal projector + var projectorMatrix = new Matrix4(); - projectorMatrix.makeRotationFromEuler( rotation ); + projectorMatrix.makeRotationFromEuler( orientation ); projectorMatrix.setPosition( position ); var projectorMatrixInverse = new Matrix4().getInverse( projectorMatrix ); - var plane = new Vector3(); - // generate buffers generate(); @@ -65,6 +69,11 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { var positionAttribute = geometry.attributes.position; var 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 @@ -76,10 +85,7 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { vertex.fromBufferAttribute( positionAttribute, index[ i ] ); normal.fromBufferAttribute( normalAttribute, index[ i ] ); - vertex.applyMatrix4( mesh.matrix ); - vertex.applyMatrix4( projectorMatrixInverse ); - - decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) ); + pushDecalVertex( decalVertices, vertex, normal ); } @@ -92,49 +98,40 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { vertex.fromBufferAttribute( positionAttribute, i ); normal.fromBufferAttribute( normalAttribute, i ); - vertex.applyMatrix4( mesh.matrix ); - vertex.applyMatrix4( projectorMatrixInverse ); - - decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) ); + pushDecalVertex( decalVertices, vertex, normal ); } } - // check - - if ( check.x ) { - - decalVertices = clipGeometry( decalVertices, plane.set( 1, 0, 0 ) ); - decalVertices = clipGeometry( decalVertices, plane.set( - 1, 0, 0 ) ); - - } - if ( check.y ) { - - decalVertices = clipGeometry( decalVertices, plane.set( 0, 1, 0 ) ); - decalVertices = clipGeometry( decalVertices, plane.set( 0, - 1, 0 ) ); - - } - if ( check.z ) { - - decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, 1 ) ); - decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, - 1 ) ); + // second, clip the geometry so that it doesn't extend out from the projector - } + decalVertices = clipGeometry( decalVertices, plane.set( 1, 0, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( - 1, 0, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 1, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, - 1, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, 1 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, - 1 ) ); - // generate vertices, normals and uvs + // third, generate final vertices, normals and uvs for ( i = 0; i < decalVertices.length; i ++ ) { var decalVertex = decalVertices[ i ]; + // create texture coordinates (we are still in projector space) + uvs.push( - 0.5 + ( decalVertex.position.x / dimensions.x ), - 0.5 + ( decalVertex.position.y / dimensions.y ) + 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.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z ); normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z ); @@ -142,55 +139,75 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { } + function pushDecalVertex( decalVertices, vertex, normal ) { + + // transform the vertex to world space, then to projector space + + vertex.applyMatrix4( mesh.matrix ); + vertex.applyMatrix4( projectorMatrixInverse ); + + decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) ); + + } + function clipGeometry( inVertices, plane ) { var outVertices = []; - var size = 0.5 * Math.abs( dimensions.dot( plane ) ); + var s = 0.5 * Math.abs( size.dot( plane ) ); + + // a single iteration clips one face, + // which consists of three consecutive 'DecalVertex' objects - for ( var j = 0; j < inVertices.length; j += 3 ) { + for ( var i = 0; i < inVertices.length; i += 3 ) { var v1Out, v2Out, v3Out, total = 0; var nV1, nV2, nV3, nV4; - var d1 = inVertices[ j + 0 ].position.dot( plane ) - size; - var d2 = inVertices[ j + 1 ].position.dot( plane ) - size; - var d3 = inVertices[ j + 2 ].position.dot( plane ) - size; + var d1 = inVertices[ i + 0 ].position.dot( plane ) - s; + var d2 = inVertices[ i + 1 ].position.dot( plane ) - s; + var d3 = inVertices[ i + 2 ].position.dot( plane ) - s; v1Out = d1 > 0; v2Out = d2 > 0; 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: { - outVertices.push( inVertices[ j ] ); - outVertices.push( inVertices[ j + 1 ] ); - outVertices.push( inVertices[ j + 2 ] ); + // the entire face lies inside of the plane, no clipping needed + + outVertices.push( inVertices[ i ] ); + outVertices.push( inVertices[ i + 1 ] ); + outVertices.push( inVertices[ i + 2 ] ); break; } case 1: { + // one vertex lies outside of the plane, perform clipping + if ( v1Out ) { - nV1 = inVertices[ j + 1 ]; - nV2 = inVertices[ j + 2 ]; - nV3 = clip( inVertices[ j ], nV1, plane, size ); - nV4 = clip( inVertices[ j ], nV2, plane, size ); + 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[ j ]; - nV2 = inVertices[ j + 2 ]; - nV3 = clip( inVertices[ j + 1 ], nV1, plane, size ); - nV4 = clip( inVertices[ j + 1 ], nV2, plane, size ); + nV1 = inVertices[ i ]; + nV2 = inVertices[ i + 2 ]; + nV3 = clip( inVertices[ i + 1 ], nV1, plane, s ); + nV4 = clip( inVertices[ i + 1 ], nV2, plane, s ); outVertices.push( nV3 ); outVertices.push( nV2.clone() ); @@ -205,10 +222,10 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { if ( v3Out ) { - nV1 = inVertices[ j ]; - nV2 = inVertices[ j + 1 ]; - nV3 = clip( inVertices[ j + 2 ], nV1, plane, size ); - nV4 = clip( inVertices[ j + 2 ], nV2, plane, size ); + nV1 = inVertices[ i ]; + nV2 = inVertices[ i + 1 ]; + nV3 = clip( inVertices[ i + 2 ], nV1, plane, s ); + nV4 = clip( inVertices[ i + 2 ], nV2, plane, s ); } @@ -226,11 +243,13 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { case 2: { + // two vertices lies outside of the plane, perform clipping + if ( ! v1Out ) { - nV1 = inVertices[ j ].clone(); - nV2 = clip( nV1, inVertices[ j + 1 ], plane, size ); - nV3 = clip( nV1, inVertices[ j + 2 ], plane, size ); + nV1 = inVertices[ i ].clone(); + nV2 = clip( nV1, inVertices[ i + 1 ], plane, s ); + nV3 = clip( nV1, inVertices[ i + 2 ], plane, s ); outVertices.push( nV1 ); outVertices.push( nV2 ); outVertices.push( nV3 ); @@ -239,9 +258,9 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { if ( ! v2Out ) { - nV1 = inVertices[ j + 1 ].clone(); - nV2 = clip( nV1, inVertices[ j + 2 ], plane, size ); - nV3 = clip( nV1, inVertices[ j ], plane, size ); + nV1 = inVertices[ i + 1 ].clone(); + nV2 = clip( nV1, inVertices[ i + 2 ], plane, s ); + nV3 = clip( nV1, inVertices[ i ], plane, s ); outVertices.push( nV1 ); outVertices.push( nV2 ); outVertices.push( nV3 ); @@ -250,9 +269,9 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { if ( ! v3Out ) { - nV1 = inVertices[ j + 2 ].clone(); - nV2 = clip( nV1, inVertices[ j ], plane, size ); - nV3 = clip( nV1, inVertices[ j + 1 ], plane, size ); + nV1 = inVertices[ i + 2 ].clone(); + nV2 = clip( nV1, inVertices[ i ], plane, s ); + nV3 = clip( nV1, inVertices[ i + 1 ], plane, s ); outVertices.push( nV1 ); outVertices.push( nV2 ); outVertices.push( nV3 ); @@ -265,6 +284,8 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { case 3: { + // the entire face lies outside of the plane, so let's discard the corresponding vertices + break; } @@ -277,22 +298,23 @@ function DecalGeometry( mesh, position, rotation, dimensions, check ) { } - function clip( v0, v1, p, size ) { + function clip( v0, v1, p, s ) { + + var d0 = v0.position.dot( p ) - s; + var d1 = v1.position.dot( p ) - s; - var d0 = v0.position.dot( p ) - size; - var d1 = v1.position.dot( p ) - size; + var s0 = d0 / ( d0 - d1 ); - var s = d0 / ( d0 - d1 ); var v = new DecalVertex( new Vector3( - v0.position.x + s * ( v1.position.x - v0.position.x ), - v0.position.y + s * ( v1.position.y - v0.position.y ), - v0.position.z + s * ( v1.position.z - v0.position.z ) + 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 ) ), new Vector3( - v0.normal.x + s * ( v1.normal.x - v0.normal.x ), - v0.normal.y + s * ( v1.normal.y - v0.normal.y ), - v0.normal.z + s * ( v1.normal.z - v0.normal.z ) + 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 ) ) );