After solving the problem in the question post Raycast for meshes and SDFs here is an example of moving meshes and SDFs via raycast.
Handling is sometimes a little tricky when it comes to selecting the elements. To rotate the auxiliary plane, you have to zoom out and rotate the box.
The Code
<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/sdfs-in-the-scene-raymarching/78355 -->
<html>
<head>
<title>07_SDF_ShaderSoloV13</title>
<meta charset="utf-8" />
<style>
body {
overflow: hidden;
margin: 0;
text-align: center;
}
</style>
</head>
<body>
spacing <input id="spacing" type="number" min="0.0" step="0.05" max="2.0" value="1.0"> </br>
</body>
<script type="module">
// @author hofk
import * as THREE from "../../jsm/three.module.173.js";
import { OrbitControls } from "../../jsm/OrbitControls.173.js";
let space = 1.0;
document.getElementById('spacing').addEventListener( 'input', (e) => {space = e.target.value});
window.addEventListener('mousedown', onDocumentMouseDown, false);
window.addEventListener('mousemove', onDocumentMouseMove, false);
window.addEventListener('mouseup' , onDocumentMouseUp , false);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.01, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor(0xdedede);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(1, 4, 100);
const light = new THREE.AmbientLight(0x404040, 4.5); // soft white light
scene.add(light);
const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
directionalLight.position.set(5, 15, 15);
scene.add(directionalLight);
const controls = new OrbitControls(camera, renderer.domElement);
const axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper);
let selectionMesh;
let selectionSDF;
let offsetMesh = new THREE.Vector3();
let objectsToRaycast = [];
const raycasterSDF = new THREE.Raycaster();
const raycasterMesh = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// Auxiliary layer for determining the mouse position and moving the clicked object in 3D
const auxPlaneGeo = new THREE.PlaneGeometry( 80, 80, 1, 1 );
const auxPlaneMat = new THREE.MeshBasicMaterial({color:0xaaaaaa, transparent:true, opacity:0.05 , side:THREE.DoubleSide, wireframe:true});
const auxPlane = new THREE.Mesh(auxPlaneGeo, auxPlaneMat);
scene.add( auxPlane );
const cyl = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 2 ), new THREE.MeshPhongMaterial( { color: 0x00ff00, wireframe:false }));
objectsToRaycast.push(cyl);
scene.add(cyl);
const ico = new THREE.Mesh(new THREE.IcosahedronGeometry( 1, 4), new THREE.MeshPhongMaterial( { color: 0x00ffff, wireframe:false }));
objectsToRaycast.push(ico);
ico.translateY( 1 );
scene.add(ico);
// Vertex Shader
const vShader = `
varying vec3 vPosition;
varying vec2 vUv;
void main() {
vPosition = position;
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
// Fragment Shader
const fShader = `
uniform float time;
uniform float boundingRadius;
uniform float space;
uniform vec3 camPos;
uniform vec3 mousePosition;
uniform vec2 resolution;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
varying vec3 vPosition;
varying vec2 vUv;
#define MAX_STEPS 250
#define MAX_DIST 100.0
#define SURF_DIST 1e-4
#define PI 3.1415926
// distance color
struct distCol {
float d;
vec4 c;
};
float sdSphere( vec3 p, float s )
{
return length(p)-s;
}
vec3 translateXYZ(vec3 p, vec3 q) {
return p - q;
}
distCol opSmoothUnion( distCol dc1, distCol dc2, float k ){
distCol dc;
float h = clamp( 0.5 + 0.5*(dc2.d-dc1.d)/k, 0.0, 1.0 );
float d = mix( dc2.d, dc1.d, h ) - k*h*(1.0-h);
vec4 c = d < dc2.d ? dc1.c : dc2.c;
dc.d = d;
dc.c = c;
return dc;
}
distCol GetDist(vec3 p) {
distCol dc;
vec3 pMouse;
distCol dcSphereA;
pMouse = translateXYZ(p, mousePosition);
dcSphereA.d = sdSphere( pMouse, boundingRadius);
dcSphereA.c = vec4(1.0, 0.0, 0.0, 1.0);
distCol dcSphereB;
pMouse = translateXYZ(p, mousePosition + vec3(space));
dcSphereB.d = sdSphere( pMouse, boundingRadius * 0.8);
dcSphereB.c = vec4(1.0, 1.0, 0.0, 1.0);
dc = dcSphereA; // apply to reserved dc
dc = opSmoothUnion(dc, dcSphereB, 0.6 );
return dc;
}
distCol RayMarch(vec3 ro, vec3 rd) {
distCol dc;
float dO = 0.0;
for (int i = 0; i < MAX_STEPS; i++) {
vec3 p = ro + rd * dO;
dc = GetDist(p);
dO += dc.d;
if (dO > MAX_DIST || dc.d < SURF_DIST) break;
}
dc.d = dO;
return dc;
}
vec3 GetNormal(vec3 p) {
float d = GetDist(p).d;
vec2 e = vec2(SURF_DIST, 0.0);
float d1 = GetDist(p - e.xyy).d;
float d2 = GetDist(p - e.yxy).d;
float d3 = GetDist(p - e.yyx).d;
vec3 n = d - vec3(d1, d2, d3);
return normalize(n);
}
float GetAo(vec3 p, vec3 n) {
float occ = 0.0;
float sca = 1.0;
for (int i = 0; i < 5; i++) {
float h = 0.001 + 0.15 * float(i) / 4.0;
float d = GetDist(p + h * n).d;
occ += (h - d) * sca;
sca *= 0.95;
}
return clamp(1.0 - 1.5 * occ, 0.0, 1.0);
}
float GetLight(vec3 p, vec3 lPos) {
vec3 l = normalize(lPos - p);
vec3 n = GetNormal(p);
float dif = clamp(dot(n, l), 0.0, 1.0);
return dif;
}
void main() {
vec2 uv = vUv - 0.5;
vec3 ro = camPos;
vec3 rd = normalize(vPosition - ro);
distCol dc = RayMarch(ro, rd);
if (dc.d >= MAX_DIST) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); // no hit
} else {
vec3 p = ro + rd * dc.d;
vec3 lightPos = vec3(2.0, 16.0, 3.0);
float diff = GetLight(p, lightPos);
float ao = 0.051 * GetAo(p, GetNormal(p));
vec4 ct = dc.c;
vec3 c = ct.rgb;
vec3 color = 0.7 * c + 0.5 * diff + 0.2 * ao;
gl_FragColor = vec4(color, ct.a);
}
}
`;
const boxParam = [100.0, 100.0, 100.0, 0.0, 0.0, 0.0 ];
let boxGeo;
let box;
let shaderMaterial;
let camPos;
boxGeo = new THREE.BoxGeometry(boxParam[0], boxParam[1], boxParam[2]);
//box helper
scene.add(
new THREE.Box3Helper(
new THREE.Box3().setFromBufferAttribute(boxGeo.attributes.position),
0x444444
)
);
shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
camPos: { value: new THREE.Vector3().copy(camera.position) },
mousePosition: { value: new THREE.Vector3() },
time: { value: 0.0 },
boundingRadius: { value: 1.2 },
space: { value: 1.0 },
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
},
vertexShader: vShader,
fragmentShader: fShader,
side: THREE.DoubleSide,
transparent: true, // false, to display the box
});
box = new THREE.Mesh(boxGeo, shaderMaterial);
scene.add(box);
box.position.set(boxParam[3], boxParam[4], boxParam[5]);
box.renderOrder = Infinity;
camPos = new THREE.Vector3();
controls.addEventListener("change", event => {
camPos.copy(camera.position);
box.worldToLocal(camPos);
shaderMaterial.uniforms.camPos.value.copy(camPos);
}, false);
camPos.copy(camera.position);
box.worldToLocal(camPos);
shaderMaterial.uniforms.camPos.value.copy(camPos);
animate();
function animate( ) {
requestAnimationFrame(animate);
//shaderMaterial.uniforms.time.value = t;
shaderMaterial.uniforms.space.value = space;
renderer.render(scene, camera);
}
function onDocumentMouseDown(event) {
getMouse( event );
const vector = new THREE.Vector3(mouse.x, mouse.y, 1);
vector.unproject(camera);
raycasterMesh.set(camera.position, vector.sub(camera.position).normalize());
const intersectsMesh = raycasterMesh.intersectObjects(objectsToRaycast);
if (intersectsMesh.length > 0) {
controls.enabled = false;
selectionMesh = intersectsMesh[0].object;
const planeIntersects = raycasterMesh.intersectObject(auxPlane);
if (planeIntersects.length > 0) {
offsetMesh.copy(planeIntersects[0].point).sub(auxPlane.position);
}
}
else {
raycasterSDF.setFromCamera(mouse, camera);
const intersectsSDF = raycasterSDF.intersectObject(box);
if (intersectsSDF.length > 0) {
controls.enabled = false;
selectionSDF = true;
const planeIntersects = raycasterSDF.intersectObject(auxPlane);
}
}
}
function onDocumentMouseMove(event) {
event.preventDefault();
getMouse( event );
const vector = new THREE.Vector3(mouse.x, mouse.y, 1);
vector.unproject(camera);
raycasterMesh.set(camera.position, vector.sub(camera.position).normalize());
if (selectionMesh) {
const intersectsMesh = raycasterMesh.intersectObject(auxPlane);
if (intersectsMesh.length > 0) {
let newPos = intersectsMesh[0].point.clone().sub(offsetMesh);
selectionMesh.position.copy(newPos);
}
} else if (selectionSDF) {
const intersectsSDF = raycasterMesh.intersectObject(auxPlane);
if (intersectsSDF.length > 0) {
let newPos = intersectsSDF[0].point.clone();
let sdfPos = newPos.clone();
box.worldToLocal(sdfPos);
box.material.uniforms.mousePosition.value.copy(sdfPos);
}
} else {
const intersectsMesh = raycasterMesh.intersectObjects(objectsToRaycast);
if (intersectsMesh.length > 0) {
auxPlane.position.copy(intersectsMesh[0].object.position);
auxPlane.lookAt(camera.position);
}
}
}
function onDocumentMouseUp(event) {
controls.enabled = true;
selectionMesh = false;
selectionSDF = false;
}
function getMouse( e ) {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
}
</script>
</html>