Bakcover Edge wrapping issue

Hi guys. I got wrapping decal on backcover mesh edge during drop image event. But at the time of using resize handlers wrapping not working. can anybody help. I can share my relevant part of code.

Anyone please help me regarding the issue. I am stuck at this point

Hi! can you reproduce your issue in a playground?

Hi thanks for response. I am new here. Unable to produce in playground. I have bunch of code outside my load model function.

It might be helpful to start by isolating the issue (figuring out the possible parts where the problem is).

You can do this by simplifying your code and gradually adding components back in to see where the problem arises.

Once you figure that out try creating an example of it in the playground : )

Can I show you screen record video of issue here?

sure, can you also explain this in more detail with screenshots and snippets of relevant code?

var textureSize = { width: 300, height: 200 }; // Initialize textureSize
var pickResultHitPoint;
imagePreviewCanvas.addEventListener('drop', function(event) {
  event.preventDefault();

  // Get the file from the drop event
  var file = event.dataTransfer.files[0];
  if (!file) {
      console.error("No file found on drop event.");
      return;
  }

  // Create a new image and URL for the dropped file
  var imageUrl = URL.createObjectURL(file);
  renderImagePreview(imageUrl);

  // Get the canvas' bounding rectangle
  var rect = imagePreviewCanvas.getBoundingClientRect();

  // Use the last known mouse position, adjust for canvas position
  var x = lastKnownMousePos.x - rect.left;
  var y = lastKnownMousePos.y - rect.top;

  // Check if the drop happened within the canvas area
  if (x < 0 || y < 0 || x > rect.width || y > rect.height) {
      console.error("Drop position is outside the canvas bounds.");
      return;
  }

  // Convert to scene coordinates based on the canvas's actual dimensions
  var sceneX = (x / rect.width) * canvas.width;
  var sceneY = (y / rect.height) * canvas.height;

  // Create the picking ray using Babylon.js
  var ray = scene.createPickingRay(sceneX, sceneY, BABYLON.Matrix.Identity(), scene.activeCamera);
  ray.direction.normalize();

  // Now you can use this ray to apply your decal
  //applyImageAsDecal(imageUrl, ray);
  applyImageAsDecal(imageUrl, backCoverMesh, scene, ray)
});

function getRayToCenterOfMesh(mesh) {
  if (!mesh || !scene.activeCamera) {
      console.error("Mesh or camera not available.");
      return null;
  }

  // Get the center of the mesh's bounding box
  var boundingInfo = mesh.getBoundingInfo();
  var center = boundingInfo.boundingBox.centerWorld;

  // Calculate a point in front of the camera to start the ray
  var cameraFrontPoint = BABYLON.Vector3.TransformCoordinates(
      new BABYLON.Vector3(0, 0, -1), 
      scene.activeCamera.getWorldMatrix()
  );

  // Calculate the direction from the camera to the center of the mesh
  var direction = center.subtract(cameraFrontPoint);
  direction.normalize();

  // Create the ray
  var ray = new BABYLON.Ray(cameraFrontPoint, direction, 1000); // 1000 is the length of the ray

  // Visualize the ray (for debugging purposes)
  var rayHelper = new BABYLON.RayHelper(ray);
  rayHelper.show(scene, new BABYLON.Color3(1, 0, 1)); // Pink ray for visibility

  return ray;
}

// Function to render image on the image-preview canvas
function renderImagePreview(imageUrl) {
  var img = new Image();
  img.onload = function() {
      var ctx = imagePreviewCanvas.getContext('2d');
      ctx.clearRect(0, 0, imagePreviewCanvas.width, imagePreviewCanvas.height); // Clear the canvas before drawing the new image
      // Calculate the maximum width and height for the image
      var aspectRatio = img.width / img.height;
      var width = Math.min(imagePreviewCanvas.width, imagePreviewCanvas.height * aspectRatio);
      var height = width / aspectRatio;
      // Center the image
      var x = (imagePreviewCanvas.width - width) / 2;
      var y = (imagePreviewCanvas.height - height) / 2;
      // Draw the image
      ctx.drawImage(img, x, y, width, height);
  };
  img.src = imageUrl; // Set the image source which triggers the onload event
}




// Function to calculate intersection points between a ray and a mesh
function calculateIntersectionPoints(ray, mesh) {
  var pickResult = scene.pickWithRay(ray, function(meshTest) {
      return meshTest === backCoverMesh;
  });

  // Log the result of the hit test and the picked point if hit is true
  console.log("pickResult.hit:", pickResult.hit); // Should log true if the ray intersects the mesh
  if (pickResult.hit) {
  	console.log("Intersection Point:", pickResult.pickedPoint);
  } else {
  	console.log("No intersection found. Check mesh position and ray direction.");
  }
}


function projectIntersectionToUV(intersectionPoint, mesh) {
  let boundingBox = mesh.getBoundingInfo().boundingBox;
  let size = boundingBox.maximum.subtract(boundingBox.minimum);

  // Normalizing the coordinates to be between 0 and 1 based on mesh size
  var u = (intersectionPoint.x - boundingBox.minimum.x) / size.x;
  var v = 1 - ((intersectionPoint.y - boundingBox.minimum.y) / size.y); // Inverting Y-axis for Babylon.js UV mapping

  // Wrapping UVs to prevent overflow or underflow
  u = (u + 10) % 1;  // Using +10 ensures positive values before modulus operation
  v = (v + 10) % 1;

  return new BABYLON.Vector2(u, v);
}



  
// Function to update the decal UV mapping based on the projected UV coordinates
function updateDecalUV(decal, intersectionUV) {
  if (decal.material && decal.material.diffuseTexture) {
      var texture = decal.material.diffuseTexture;

      // Instead of just changing the scale, we change the offset to "scroll" the texture and create a wrapping effect
      texture.uOffset = -intersectionUV.x;
      texture.vOffset = -intersectionUV.y;
  }
}



// Global variable to track if the decal is being dragged
var isDraggingDecal = false;

// Function to start dragging
function startDrag() {
  isDraggingDecal = true;
}

// Function to end dragging
function endDrag() {
  isDraggingDecal = false;
}

// Function to handle mouse move event
function onMouseMove(evt) {
  // Perform actions only if dragging is true
  if (isDraggingDecal && backCoverMesh) {
      var pickInfo = scene.pick(scene.pointerX, scene.pointerY);
      if (pickInfo.hit && pickInfo.pickedMesh === backCoverMesh) {
          var pickedPoint = pickInfo.pickedPoint;
          var normal = pickInfo.getNormal(true);

          console.log("Picked Point:", pickedPoint); // Logging the picked point
          console.log("Normal:", normal); // Logging the normal

          // Offset the picked point slightly along the normal to ensure it's on the correct side.
          var offset = normal.scale(0.05); // 0.01 units offset along the normal
          var correctedPickedPoint = pickedPoint.add(offset);

          console.log("Corrected Picked Point:", correctedPickedPoint); // Logging the corrected point

          // Assume 'projectIntersectionToUV' and 'updateDecalUV' are functions you have defined to handle the UV mapping
          var intersectionUV = projectIntersectionToUV(correctedPickedPoint, textureSize.width, textureSize.height);
          updateDecalUV(intersectionUV);

          console.log("Intersection UV:", intersectionUV); // Logging the UVs
      }
  }
}


// Debounce function to limit the frequency of execution
function debounce(func, delay) {
  var inDebounce;
  return function() {
      var context = this;
      var args = arguments;
      clearTimeout(inDebounce);
      inDebounce = setTimeout(function () {
          func.apply(context, args);
      }, delay);
  }
}

// Attach event listeners for dragging
canvas.addEventListener('mousedown', startDrag);
canvas.addEventListener('mouseup', endDrag);
canvas.addEventListener('mouseleave', endDrag); // Consider drag end when the mouse leaves the canvas

// Apply the debounce function to the mouse move event
var onMouseMoveDebounced = debounce(onMouseMove, 50);

// Add the event listener for mousemove
canvas.addEventListener('mousemove', onMouseMoveDebounced);


// Function to calculate quaternion from two vectors
function calculateQuaternionFromVector(v1, v2) {
  // Calculate the axis of rotation
  var axis = BABYLON.Vector3.Cross(v1, v2);
  axis.normalize();

  // Calculate the dot product to find the angle between vectors
  var dot = BABYLON.Vector3.Dot(v1, v2);

  // Avoid precision issues with Math.acos
  if (dot > 1.0) {
      dot = 1.0;
  } else if (dot < -1.0) {
      dot = -1.0;
  }

  // Calculate the angle
  var angle = Math.acos(dot);

  // Create the quaternion
  var quaternion = BABYLON.Quaternion.RotationAxis(axis, angle);
  
  return quaternion;
}



function applyImageAsDecal(imageUrl, backCoverMesh, scene, ray) {
  if (!scene || !scene.activeCamera) {
      console.error("Scene or camera is not defined.");
      return;
  }

  // Calculate the center of the mesh's bounding box
  var boundingInfo = backCoverMesh.getBoundingInfo();
  var center = boundingInfo.boundingBox.centerWorld;

  // Calculate the direction from the camera to the center of the mesh
  var direction = center.subtract(scene.activeCamera.position);
  direction.normalize();

  // Create the ray from the camera position towards the mesh center
  var ray = new BABYLON.Ray(scene.activeCamera.position, direction, 100); // Adjust the length as needed
  
  // Visualize the ray
  var rayHelper = new BABYLON.RayHelper(ray);
  rayHelper.show(scene, new BABYLON.Color3(1, 0, 0)); // Show the ray in red

  var pickResult = scene.pickWithRay(ray);
  if (pickResult.hit) {
      var atEdge = isNearEdge(pickResult.pickedPoint, boundingInfo.boundingBox);

      var img = new Image();
      img.onload = function() {
          var aspectRatio = img.width / img.height;
          var decalSize = aspectRatio >= 1 ?
              new BABYLON.Vector3(aspectRatio, 1, 1) :
              new BABYLON.Vector3(1, 1 / aspectRatio, 1);

          var scaleFactor = 17; // Example scale factor
          decalSize.scaleInPlace(scaleFactor);

          var decalMaterial = new BABYLON.StandardMaterial("decalMat", scene);
  		decalMaterial.diffuseTexture = new BABYLON.Texture(imageUrl, scene);
  		decalMaterial.diffuseTexture.hasAlpha = true;
  		decalMaterial.useAlphaFromDiffuseTexture = true;
  		decalMaterial.depthPrePass = true;

  		// Set the texture to wrap in both U and V directions
  		//decalMaterial.diffuseTexture = new BABYLON.Texture(imageUrl, scene, true, false, BABYLON.Texture.TRILINEAR_SAMPLINGMODE, null, null, null, true);
  		decalMaterial.diffuseTexture.wrapU = BABYLON.Texture.WRAP_ADDRESSMODE;
  		decalMaterial.diffuseTexture.wrapV = BABYLON.Texture.WRAP_ADDRESSMODE;
  		
  	

          var decal = BABYLON.MeshBuilder.CreateDecal("decal", backCoverMesh, {
              position: pickResult.pickedPoint,
              normal: pickResult.getNormal(true),
              size: decalSize
          });

          decal.material = decalMaterial;
          window.decalPlane = decal;
  		
          console.log("Decal Position:", decal.position);
          console.log("Decal Scaling:", decal.scaling);
          console.log("Decal Rotation (Euler):", decal.rotation);

          // Wrap the decal edges if they exceed mesh bounds
          wrapDecalEdge(decal, backCoverMesh); // Adjust UVs

          if (decal.rotationQuaternion) {
              console.log("Decal Rotation (Quaternion):", decal.rotationQuaternion);
          }
      };
      img.onerror = function() {
          console.error("Failed to load image for the decal.");
      };
      img.src = imageUrl;
  } else {
      console.error("No intersection point found. Ray origin:", ray.origin, "Direction:", ray.direction);
  }
}




function isNearEdge(point, bounds, threshold = 0.01) {
  return Math.abs(point.x - bounds.minimum.x) < threshold ||
         Math.abs(point.x - bounds.maximum.x) < threshold ||
         Math.abs(point.y - bounds.minimum.y) < threshold ||
         Math.abs(point.y - bounds.maximum.y) < threshold ||
         Math.abs(point.z - bounds.minimum.z) < threshold ||
         Math.abs(point.z - bounds.maximum.z) < threshold;
}

function calculateWrappingUVs(mesh, decalBBox, meshBBox) {
  let uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind);
  let newUVs = [];
  let vertices = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind);

  for (let i = 0; i < vertices.length; i += 3) {
      let x = vertices[i];
      let y = vertices[i + 1];
      let uIndex = (i / 3) * 2;
      let vIndex = uIndex + 1;

      // Calculate UVs based on vertex position relative to the mesh and decal bounding boxes
      if (x < meshBBox.minimumWorld.x || x > meshBBox.maximumWorld.x || y < meshBBox.minimumWorld.y || y > meshBBox.maximumWorld.y) {
          let relativeU = (x - decalBBox.minimumWorld.x) / (decalBBox.maximumWorld.x - decalBBox.minimumWorld.x);
          let relativeV = (y - decalBBox.minimumWorld.y) / (decalBBox.maximumWorld.y - decalBBox.minimumWorld.y);
          newUVs[uIndex] = relativeU % 1;  // Wrap the U coordinate
          newUVs[vIndex] = relativeV % 1;  // Wrap the V coordinate
      } else {
          // Use existing UVs if within bounds
          newUVs[uIndex] = uvs[uIndex];
          newUVs[vIndex] = uvs[vIndex];
      }
  }

  return newUVs;
}



// Define initial canvas width and height variables
var initialCanvasWidth, initialCanvasHeight;

function setupCanvasInteractions() {
  // Store the initial width and height of the canvas preview image
  initialCanvasWidth = $('#image-preview').width();
  initialCanvasHeight = $('#image-preview').height();

  $('#image-preview').resizable({
      aspectRatio: true,
      handles: 'nw, ne, sw, se',
      resize: function(event, ui) {
          handleResize(event, ui);
          syncDecalWithCanvas(ui.size.width, ui.size.height);
      },
      stop: function(event, ui) {
          initialCanvasWidth = ui.size.width;
          initialCanvasHeight = ui.size.height;
      }
  });

  $('#canvas-container').draggable({
      containment: "parent",
      handle: "#image-preview",
      drag: handleDrag,
      start: function(event, ui) {
          ui.originalPosition.left = ui.position.left;
          ui.originalPosition.top = ui.position.top;
      }
  });

  $('.rotate-handle').draggable({
      axis: "y",
      start: function(event, ui) {
          // Store initial rotation if needed
      },
      drag: function(event, ui) {
          var deltaY = ui.position.top - ui.originalPosition.top;
          var rotationChange = deltaY * 0.01; // Adjust rotation sensitivity
          rotateDecal(rotationChange);
      },
      stop: function(event, ui) {
          // Update initial rotation after rotating is complete if needed
      }
  });
}
// Usage in resize handler
function handleResize(event, ui) {
  var newWidth = ui.size.width;
  var newHeight = ui.size.height;
  var widthScale = newWidth / initialCanvasWidth;
  var heightScale = newHeight / initialCanvasHeight;

  if (window.decalPlane) {
      // Scale the decal
      window.decalPlane.scaling.x *= widthScale;
      window.decalPlane.scaling.y *= heightScale;

      // Update transformations
      wrapDecalEdge(window.decalPlane, window.backCoverMesh);

      // Retrieve UVs and log their values for debugging
      var uvs = window.decalPlane.getVerticesData(BABYLON.VertexBuffer.UVKind);
      console.log('Original UVs:', uvs.slice(0, 10)); // Log the first few UVs for debugging

      const updatedUVs = uvs.map((uv, index) => {
          // Calculate new UVs and check for wrapping
          const newUV = (index % 2 === 0) ? uv * widthScale : uv * heightScale;
          // Log any UV values indicating wrapping
          if(newUV > 1 || newUV < 0) {
              console.log(`Wrapping UV found at index ${index}:`, newUV);
          }
          // Wrap the UVs around [0, 1]
          return newUV - Math.floor(newUV); 
      });

      console.log('Updated UVs for wrapping:', updatedUVs.slice(0, 10)); // Log the first few updated UVs for wrapping

      // Apply updated UVs to the decal mesh
      window.decalPlane.updateVerticesData(BABYLON.VertexBuffer.UVKind, updatedUVs);

      // Update texture wrapping if necessary
      const decalMaterial = window.decalPlane.material;
      decalMaterial.diffuseTexture.wrapU = BABYLON.Texture.WRAP_ADDRESSMODE;
      decalMaterial.diffuseTexture.wrapV = BABYLON.Texture.WRAP_ADDRESSMODE;

      // Update initial dimensions for the next resize operation
      initialCanvasWidth = newWidth;
      initialCanvasHeight = newHeight;
  }
}


I want when I scale down or increase the decal image is no goes beyond model’s backcover edges . I pasted above code snippet related to applyimageas decal from drop event to resize events

hello friend please check my screen recording regarding issue

Hello Anyone please help me

no playground no help :slight_smile:

1 Like

:frowning_face: I have live link. Kindly please check. Mobile Cover Customization

We cannot debug in user code because it takes too much time to setup our dev tools :frowning: we need the repro in the playground itself

1 Like

ACtually I am new here. Can you help me to setup playground. i got error while setup

Just go there: playground.babylonjs.com and try to repro with an extract of your code which is relevant

use stencil