threejs.org/examples/#w…

const decalMaterial = new THREE.MeshPhongMaterial( {
specular: 0x444444,
map: decalDiffuse,
normalMap: decalNormal,
normalScale: new THREE.Vector2( 1, 1 ),
shininess: 30,
transparent: true,
depthTest: true,
depthWrite: false,
polygonOffset: true,
polygonOffsetFactor: - 4,
wireframe: false
} );
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints( [ new THREE.Vector3(), new THREE.Vector3() ] );
line = new THREE.Line( geometry, new THREE.LineBasicMaterial() );
scene.add( line );
raycaster = new THREE.Raycaster();
function checkIntersection( x, y ) {
if ( mesh === undefined ) return;
mouse.x = ( x / window.innerWidth ) * 2 - 1;
mouse.y = - ( y / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
raycaster.intersectObject( mesh, false, intersects );
if ( intersects.length > 0 ) {
const p = intersects[ 0 ].point;
mouseHelper.position.copy( p );
intersection.point.copy( p );
const n = intersects[ 0 ].face.normal.clone();
n.transformDirection( mesh.matrixWorld );
n.multiplyScalar( 10 );
n.add( intersects[ 0 ].point );
intersection.normal.copy( intersects[ 0 ].face.normal );
mouseHelper.lookAt( n );
const positions = line.geometry.attributes.position;
positions.setXYZ( 0, p.x, p.y, p.z );
positions.setXYZ( 1, n.x, n.y, n.z );
positions.needsUpdate = true;
intersection.intersects = true;
intersects.length = 0;
} else {
intersection.intersects = false;
}
}
position.copy( intersection.point );
orientation.copy( mouseHelper.rotation );
if ( params.rotate ) orientation.z = Math.random() * 2 * Math.PI;
const scale = params.minScale + Math.random() * ( params.maxScale - params.minScale );
size.set( scale, scale, scale );
const material = decalMaterial.clone();
material.color.setHex( Math.random() * 0xffffff );
const m = new THREE.Mesh( new DecalGeometry( mesh, position, orientation, size ), material );
decals.push( m );
scene.add( m );
decals.forEach( function ( d ) {
scene.remove( d );
} );
decals.length = 0;