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 thanks for response. I am new here. Unable to produce in playground. I have bunch of code outside my load model function.
Can I show you screen record video of issue here?
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
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 we need the repro in the playground itself
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