Clip a 3d modal

So I am using the clipping-intersection example of threejs and the clipping planes are all in the scene and everything but my 3d modal isn’t affected by the planes, here’s my code:

import * as THREE from "three";

import Stats from "three/addons/libs/stats.module.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DecalGeometry } from "three/addons/geometries/DecalGeometry.js";

const container = document.getElementById("container");

let renderer, scene, camera, stats;
let mesh;
let raycaster;
let line;
let isMouseDown = false;
let isDraggingOnObject = false;

const intersection = {
  intersects: false,
  point: new THREE.Vector3(),
  normal: new THREE.Vector3(),
};

const group = new THREE.Group();

const mouse = new THREE.Vector2();
const intersects = [];

const textureLoader = new THREE.TextureLoader();
const decalDiffuse = textureLoader.load(
  "https://cdn.glitch.global/47d93b03-75c0-4f8e-aa37-e6fe78cb9028/circle-diffuse.png?v=1708434858116"
);
decalDiffuse.colorSpace = THREE.SRGBColorSpace;
const decalNormal = textureLoader.load("textures/decal/circle-normal.jpg");

const decalMaterial = new THREE.MeshPhongMaterial({
  map: decalDiffuse,
  specular: 0x444444,
  normalScale: new THREE.Vector2(1, 1),
  shininess: 30,
  transparent: true,
  depthTest: true,
  depthWrite: false,
  polygonOffset: false,
  polygonOffsetFactor: -8,
  wireframe: false,
  blending: THREE.NormalBlending,
});

const decals = [];
let mouseHelper;
const position = new THREE.Vector3();
const orientation = new THREE.Euler();
const size = new THREE.Vector3(10, 10, 10);

const scale = 10; // Change this to the scaling factor you want

const clipPlanes = [
  new THREE.Plane(new THREE.Vector3(10 * scale, 0, 0), 0),
  new THREE.Plane(new THREE.Vector3(0, -10 * scale, 0), 0),
  new THREE.Plane(new THREE.Vector3(0, 0, -10 * scale), 0),
];

const params = {
  Scale: 20,
  rotate: true,
  delete: function () {
    deletemodal();
  },
  clear: function () {
    removeDecals();
  },
  clipIntersection: true,
  planeConstant: 0,
  showHelpers: true,
  alphaToCoverage: true,
};

init();
animate();

function init() {
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  container.appendChild(renderer.domElement);

  stats = new Stats();
  container.appendChild(stats.dom);

  scene = new THREE.Scene();

  camera = new THREE.PerspectiveCamera(
    45,
    window.innerWidth / window.innerHeight,
    1,
    1000
  );
  camera.position.z = 120;

  const controls = new OrbitControls(camera, renderer.domElement);
  controls.minDistance = 50;
  controls.maxDistance = 200;

  scene.add(new THREE.AmbientLight(0x666666));

  const dirLight1 = new THREE.DirectionalLight(0xffddcc, 3);
  dirLight1.position.set(1, 0.75, 0.5);
  scene.add(dirLight1);

  const dirLight2 = new THREE.DirectionalLight(0xccccff, 3);
  dirLight2.position.set(-1, 0.75, -0.5);
  scene.add(dirLight2);

  const geometry = new THREE.BufferGeometry();
  geometry.setFromPoints([new THREE.Vector3(), new THREE.Vector3()]);

  line = new THREE.Line(geometry, new THREE.LineBasicMaterial());
  scene.add(line);

  const helpers = new THREE.Group();
  helpers.add(new THREE.PlaneHelper(clipPlanes[0], 100, 0xff0000));
  helpers.add(new THREE.PlaneHelper(clipPlanes[1], 100, 0x00ff00));
  helpers.add(new THREE.PlaneHelper(clipPlanes[2], 100, 0x0000ff));
  helpers.visible = false;
  scene.add(helpers);

  raycaster = new THREE.Raycaster();

  mouseHelper = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 10),
    new THREE.MeshNormalMaterial()
  );
  mouseHelper.visible = false;
  scene.add(mouseHelper);

  loadLeePerrySmith();

  window.addEventListener("resize", onWindowResize);

  let moved = false;

  controls.addEventListener("change", function () {
    moved = true;
  });

  window.addEventListener("pointerdown", function () {
    moved = false;
  });

  window.addEventListener("pointerup", function (event) {
    if (moved === false) {
      checkIntersection(event.clientX, event.clientY);

      if (intersection.intersects) shoot();
    }
  });

  window.addEventListener("pointermove", onPointerMove);

  function onPointerMove(event) {
    if (event.isPrimary) {
      checkIntersection(event.clientX, event.clientY);
    }
  }

  window.addEventListener("pointerdown", function () {
    moved = false;
    isMouseDown = true;
    if (intersection.intersects) {
      isDraggingOnObject = true;
      controls.enableRotate = false; // Disable rotation only when dragging on the object
    }
  });

  window.addEventListener("pointerup", function (event) {
    isMouseDown = false;
    isDraggingOnObject = false;
    controls.enableRotate = true; // Enable rotation when the mouse is up
    if (moved === false) {
      checkIntersection(event.clientX, event.clientY);
      if (intersection.intersects) shoot();
    }
  });

  // ...

  function onPointerMove(event) {
    if (event.isPrimary) {
      checkIntersection(event.clientX, event.clientY);
      if (isMouseDown) {
        if (isDraggingOnObject) {
          // If dragging on the object, disable rotation and add decals
          controls.enableRotate = false;
          shoot();
        } else {
          // If dragging elsewhere, enable rotation
          controls.enableRotate = true;
        }
      }
    }
  }

  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;
    }
  }

  const gui = new GUI();

  gui.add(params, "Scale", 1, 100);
  gui.add(params, "rotate");
  gui.add(params, "delete");
  gui.add(params, "clear");
  gui.add(params, "alphaToCoverage").onChange(function (value) {
    group.children.forEach((c) => {
      c.material.alphaToCoverage = Boolean(value);
      c.material.needsUpdate = true;
    });

    animate();
  });

  gui
    .add(params, "clipIntersection")
    .name("clip intersection")
    .onChange(function (value) {
      const children = group.children;

      for (let i = 0; i < children.length; i++) {
        children[i].material.clipIntersection = value;
      }

      animate();
    });

  gui
    .add(params, "planeConstant", -1, 50)
    .step(0.01)
    .name("plane constant")
    .onChange(function (value) {
      for (let j = 0; j < clipPlanes.length; j++) {
        clipPlanes[j].constant = value;
      }

      animate();
    });

  gui
    .add(params, "showHelpers")
    .name("show helpers")
    .onChange(function (value) {
      helpers.visible = value;

      animate();
    });
  gui.open();
}

function loadLeePerrySmith() {
  const loader = new GLTFLoader();

  loader.load(
    "https://cdn.glitch.me/47d93b03-75c0-4f8e-aa37-e6fe78cb9028/untitled.glb?v=1708434848354",
    function (gltf) {
      mesh = gltf.scene.children[0];
      mesh.material = new THREE.MeshPhongMaterial({
        specular: 0x111111,
        shininess: 5,
        side: THREE.DoubleSide,
        clippingPlanes: clipPlanes,
        clipIntersection: params.clipIntersection,
        alphaToCoverage: true,
      });
      group.add(mesh);
      scene.add(mesh);
      mesh.scale.set(1, 1, 1);
      mesh.position.y = 25;
      mesh.rotation.x = -0.85;
    }
  );
}

const decalPool = [];
const maxDecals = 100; // Maximum number of decals to rendere

function shoot() {
  if (decals.length >= maxDecals) {
    // Remove the oldest decal from the scene and the decals array
    const oldestDecal = decals.shift();
    scene.remove(oldestDecal);
  }

  position.copy(intersection.point);
  orientation.copy(mouseHelper.rotation);

  if (params.rotate) orientation.z = Math.random() * 2 * Math.PI;

  const scale = params.Scale;
  size.set(scale, scale, scale);

  let decal = decalPool.pop(); // Reuse decal from the pool

  if (!decal) {
    // Create a new decal if the pool is empty
    decal = new THREE.Mesh(
      new DecalGeometry(mesh, position, orientation, size, false), // Set mirror to false
      decalMaterial
    );
  } else {
    // Reuse existing decal
    decal.geometry.copy(
      new DecalGeometry(mesh, position, orientation, size, false)
    ); // Set mirror to false
    decal.material = decalMaterial;
  }

  decal.material.color.set(0x00ff00); // Set color to green

  decal.renderOrder = decals.length; // give decals a fixed render order

  decals.push(decal);
  scene.add(decal);
}

function debounce(func, wait) {
  let timeout;
  return function (...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
  };
}

const debouncedShoot = debounce(shoot, 100); // Adjust the 100ms to a suitable value for your use case

function deletemodal() {
  if (mesh) {
    scene.remove(mesh); // Remove the GLB model from the scene
    mesh.geometry.dispose(); // Dispose the geometry to free up memory
    mesh.material.dispose(); // Dispose the material to free up memory
    mesh = undefined; // Set the mesh variable to undefined
  }
}

function removeDecals() {
  decals.forEach(function (d) {
    scene.remove(d);
  });

  decals.length = 0;
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  requestAnimationFrame(animate);

  renderer.render(scene, camera);

  stats.update();
}

Duplicate ?

Nah they are totally different, in that topic I was asking for a clipping tool, this one is about the error I am facing inside of the clipping tool.

1 Like

Material#clippingPlanes – three.js docs (threejs.org)

" This requires WebGLRenderer.localClippingEnabled to be true"

3 Likes

That did some work for me thanks!