Placing decals without mouse click

I’m new to ThreeJS and trying to place decals on a mesh using predefined locations marked with a Dummy object that was created in 3DS. The object is positioned and rotated appropriately for the decal location. I can get the placement and (apparently?) the rotation from the Dummy object for use in the DecalGeometry, but the texture gets skewed when it’s applied. I also tried using a plane with similar results, hoping to also use the plane’s dimensions to constrain/size the decal.

I’ve created a simple sample here: Three.js glTF Load Sample - JSFiddle - Code Playground

The sample texture is being placed on the teapot using either of the decalLocation objects listed, but the texture is stretched.

    //let decalLocation = scene.getObjectByName("decal_right"); //the Dummy object
    let decalLocation = scene.getObjectByName("plane_decal_right"); //OR the plane

I’ve tried the “shooter” samples from three.js webgl - decal splatter and three.js dev template - module - JSFiddle - Code Playground but not understanding the difference between the intersection point in the example from the raycaster vs. my statically placed placeholder object. I noticed that the rotation I get from the mouse examples is much different than my placeholder rotation. But the texture appears to rotate appropriately anyway.

Secondarily, is there a way to scale the texture if I know the physical dimensions of the decal? The examples I’ve seen divide the physical dimensions by the pixel width/height of the texture, but that doesn’t make sense to me if the pixel density is not specified as in the print world. I understand that I’d have to get the texture.image after the loader completes to get it’s width/height. I also see that DecalGeometry takes a Vector3 for size, not just x/y but don’t understand why the third dimension is needed to scale the texture.

Are you calculating the face normal of the position that the decal is projected on to? I’m quite sure that’ll be an important part of your equation to getting the correct projection for a programmatically applied decal…

1 Like

The decal placement is a volume… like a box. That’s why the size is 3d.

The surface within that volume is what is shaded with the decal. The decal is projected along the Z axis of the box, onto the intersecting surface.
That’s why the size is 3d. If it was only 2d, there wouldn’t be any limit on how deep the projection goes, and the decal would project onto both sides of the target object.

You can see this when projecting decals onto small/skinny objects… you get overlap… or things projecting onto the back sides etc.

I fixed the projection in your sample by doing:

decalLocation.lookAt(new THREE.Vector3(10,10,10));

.lookAt makes an object orient its Z axis towards a point in space.

As @Lawrence3DPK mentioned… if you want to place decals by clicking… You’ll get the point on the surface to place the box, and you’ll get back the “surface normal” which you can then .add to the clicked point to come up with a point set slightly outward from the clicked surface. Then you decalLocation.lookAt( that point ) to orient the box. You can control which axis becomes “up” on the decal by changing the decalLocation.up vector to be a vector in the direction you want the vertical axis of the decal to be. Object3D.up defaults to a shared vector that is (0,1,0)…

So you would do something like…


decalLocation.position.copy( rayHit.point )
let lookTarget = rayHit.point.clone().add(rayHit.normal);
decalLocation.lookAt(lookTarget);
1 Like

Thank you very much. I need to have our modeler indicate placement of the decals when he creates the model as the client has very specific locations and sizes for the decals on the product. Hence the Dummy object decalLocation. I want to use these placeholders ( as opposed to the point from the click examples ) so that I can place all logos/decals when the model loads without having to store placement data in a separate configuration. How would I get the lookAt location for these placeholders? Analogous to this

let lookTarget = rayHit.point.clone().add(rayHit.normal);

but for the decalLocation objects.

Thank you. I just used the mouse examples as a reference to learn. We’re actually using the decalLocation object in my sample to indicate placement and rotation of the decals. This is where I stopped understanding. Specifically how to get from this in the mouse example:

			var n = intersects[ 0 ].face.normal.clone();
			n.transformDirection( mesh.matrixWorld );
			n.add( intersects[ 0 ].point );

			helper.position.copy( intersects[ 0 ].point );
			helper.lookAt( n );
		
			var position = intersects[ 0 ].point;

to an analogous way using my predefined placement decalLocation objects.

When your artist places your markers (a box) the X and Y axis of the box is the bounds of the decal, and the Z axis is the coverage area. It should be placed like this I think:

If you place the markers like that… then you should be able to use them directly to place the decals.

This is exactly what’s in my sample I think, except I tried a Dummy object (non-visual) from 3DS and later a plane instead of a cube. The Z is orthogonal to the center point of the object/decal. Here, there are four objects (two Dummy axis-only objects and two planes at the same location) which are in my fiddle. I figured I could use the location, rotation directly to place my item. And then later figured I could constrain size using the plane dimensions. But as you pointed out the Z is incorrect and I’m not understanding why or how to get the correct one from the position objects.

Your setup isn’t using Z. since its a 2d plane. That’s why you want to use a box as a marker…

You start with a 1x1x1 cube… then scale the mesh (not its vertices!) to the X/Y/Z that it needs to be, and export that as your marker.

Then when its loaded… the scale.x, scale.y, and scale.z of the marker should be the “size” that you want.
So then your decal geometry gets made something like this:
let decalGeometry = new DecalGeometry(targetMesh, decalMarker.position, decalMarker.rotation, decalMarker.scale );

1 Like

Thank you! I will give that a try. I really appreciate your time.

1 Like

Np, hope it helps. A bit more background… I’ve used that DecalGeometry technique to splat bullet decals on surfaces in an FPS style game, so I got pretty intimate with it.

1 Like

I tried this and it works. I get a little bit of movement from where the cube intersects the target mesh. Is this because of the depth of the cube, maybe mitigated by making the cube more shallow? I can’t thank you enough for this.

What kind of movement? It behaved pretty stably for me… and yeah, you probably want to adjust the z thickness to cover just the bare minimum that you need it to.

It moves up/left relative to either the intersection or the cube face.

Seems to be the cube depth. We made it very shallow and the shift appears to have gone. Thank you again. This was super helpful.

1 Like

@manthrax This worked perfectly with one issue. On highly curved surfaces, the depth needed for the cube to round the curve makes the decal bleed to the back side of the mesh. This is a problem for the products I’m working on as you can see the inside. None of the shooter examples show the back side of the target mesh. Is there a way to prevent this?

You don’t need a mouse position or camera to cast a ray, you can set a ray from an origin and a direction whereby the origin would be the position of an empty dummy marker and the direction would be the objects (teapot) position sub the origin position eg…

const direction = new THREE.Vector3();
direction.subVectors(object.position, dummy.position).normalize();

This would cast a ray from the dummy to the center of the object whereby you’ll now have an intersects array and run the exact code in the shooter example…

var n = intersects[ 0 ].face.normal.clone();
n.transformDirection( mesh.matrixWorld );
n.add( intersects[ 0 ].point );

helper.position.copy( intersects[ 0 ].point );
helper.lookAt( n );
		
var position = intersects[ 0 ].point;

You now have the face normal of the intersected object from which you can calculate the rotation (lookAt) and position needed to place the decal…

@Lawrence3DPK thank you! I will try this out.

You can read more on the raycaster and it’s .set method here

@Lawrence3DPK. I currently get the position, rotation and scale from the dummy cube. It seems like your suggestion would be a replacement for the two items that work well already… position and rotation. I think the depth of the scale/size is what’s causing my bleed problem. How would the raycast fix that issue? I meant above that the shooter examples don’t show the back side of the mesh, so I can’t tell if they have the same issue I’m encountering.

Do you have side: THREE.DoubleSide set on the decals material? If so try setting it to THREE.FrontSide.

In terms of wether or not casting a ray would fix the issue is something you’d have to experiment with but essentially if you programmatically set a ray in this way then you’d have the exact setup of the “shooter” decal example without having to click to find the surface normal and position to apply to the decal.

Do you have some images or a video of the current issue you’re facing?