diff --git a/src/Components/MyRoom/helpers/MyRoomViewHelper.ts b/src/Components/MyRoom/helpers/MyRoomViewHelper.ts new file mode 100644 index 0000000..4b19160 --- /dev/null +++ b/src/Components/MyRoom/helpers/MyRoomViewHelper.ts @@ -0,0 +1,1768 @@ +import * as THREE from 'three'; +import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'; +import {FBXLoader} from 'three/examples/jsm/loaders/FBXLoader.js'; +import { + canvasToBlobPromise, + clearCanvas, + createCanvas, +} from '../../../Utils/canvasutils'; +import { + CarpetRatio, + MyRoomPointsOBJ, + PointsHistory, + ScaleFactor, + Vector2Pool, + Vector3Pool, +} from '../types'; +import { + areaOfellipse, + convertArrIntoRad, + convertUnit, + createVector, + resizeKeepingAspect, +} from '../../../Utils/utils'; +import {readImage} from '../../../Utils/domUtils'; + +declare const AppProvider: { + uploadMyRoom: (blob: Blob) => Promise; + uploadMyRoomMask: (params: {maskUrl: Blob; roomId: string}) => Promise; + saveAsRoom: (params: { + mode: string; + roomId: string; + file: unknown; + props: unknown; + floorpoints: {x: number; y: number}[]; + notfloorpoints: {x: number; y: number}[]; + carpetpoints: {x: number; y: number}[]; + }) => Promise; +}; + +const fbxLoader = new FBXLoader(); + +interface DesignData { + Unit: string; + PhysicalWidth: number; + PhysicalHeight: number; + IsIrregular?: boolean; +} + +interface WindowExtended extends Window { + InterfaceElements: {IsWallToWall: boolean}; + flags: { + visualizations?: { + wallToWallCenterRepeat?: {x?: boolean; y?: boolean}; + }; + inhouseChanges?: { + saveFunctionForFlutter?: boolean; + }; + }; + constraints?: {audio: boolean; video: boolean}; + initialData?: {customDesignUrl?: string}; + getByteData?: string; +} + +interface FloorCenter { + x: string; + y: string; + area?: string; + no_floor?: boolean; +} + +interface Orientation { + hor: string; + vert: string; + exception?: boolean; + flag?: boolean; +} + +export interface AnalysisData { + floor_center: FloorCenter; + orientation: Orientation; + fov: string; +} + +interface FloorBounds { + minX: number; + maxX: number; + minZ: number; + maxZ: number; +} + +interface CarpetDimensions { + width: number; + depth: number; +} + +export class RoomViewHelper { + scene: THREE.Scene; + textureLoader: THREE.TextureLoader; + offset: THREE.Vector3; + fbxLoaded: boolean; + surfaceName: string; + + zoomCarpetStep: number; + maxZoom: number; + minZoom: number; + carpetRatio: CarpetRatio; + origCarpetDims: {w: number; h: number}; + scaleFactor: ScaleFactor; + pileHeight: number; + + myRoomPointsOBJ: MyRoomPointsOBJ; + pointsHistory: PointsHistory; + pointsHistoryArray: string[]; + + constraints: {audio: boolean; video: boolean}; + + initCarpetPos: number[] | null; + initCarpetRot: number[] | null; + initCarpetScale: number[] | null; + designDetails: DesignData | null; + designPath: string | null; + canvasInitiated: boolean; + bgCanvas!: HTMLCanvasElement; + rendererCanvas!: HTMLCanvasElement; + maskCanvas!: HTMLCanvasElement; + gizmoCanvas!: HTMLCanvasElement; + inputCanvas!: HTMLCanvasElement; + container!: HTMLElement; + bgImage!: HTMLImageElement; + origWid!: number; + origHgt!: number; + roomAnalysisData: AnalysisData | null; + ratioWid!: number; + ratioHgt!: number; + myroomInputSelected: string = ''; + roomdataurl: string = ''; + origBG: string = ''; + + intersectsGizmo: boolean = false; + prev: {x: number; y: number} = {x: 0, y: 0}; + + _vector3Pool: Vector3Pool; + _vector2Pool: Vector2Pool; + _raycaster: THREE.Raycaster; + + _cachedFloorBounds: FloorBounds | null; + _cachedCarpetDims: CarpetDimensions | null; + _cacheInvalidated: boolean; + + _tempPositionMesh?: THREE.Mesh; + + carpetMesh?: THREE.Mesh; + bounds?: THREE.Box3; + material?: THREE.MeshBasicMaterial; + carpetRatioNew?: CarpetRatio; + + dims!: {width: number; height: number}; + fov: number = 45; + renderer!: THREE.WebGLRenderer; + camera!: THREE.PerspectiveCamera; + orbit!: OrbitControls; + + constructor() { + this.scene = new THREE.Scene(); + this.textureLoader = new THREE.TextureLoader(); + this.offset = new THREE.Vector3(); + this.fbxLoaded = false; + this.surfaceName = 'Box002'; + + this.zoomCarpetStep = 0.05; + this.maxZoom = 2; + this.minZoom = 0.5; + this.carpetRatio = {w: 1, h: 1}; + this.origCarpetDims = {w: 10, h: 10}; + this.scaleFactor = {x: 0, y: 0}; + this.pileHeight = 0.25; + + this.myRoomPointsOBJ = { + floorpoints: [], + notfloorpoints: [], + carpetpoints: [], + }; + + this.pointsHistory = { + floorpoints: [], + notfloorpoints: [], + inputSelected: '', + }; + this.pointsHistoryArray = []; + + this.constraints = (window as unknown as WindowExtended).constraints = { + audio: false, + video: true, + }; + + this.initCarpetPos = null; + this.initCarpetRot = null; + this.initCarpetScale = null; + this.designDetails = null; + this.designPath = null; + this.canvasInitiated = false; + this.roomAnalysisData = null; + + this._vector3Pool = { + v1: new THREE.Vector3(), + v2: new THREE.Vector3(), + v3: new THREE.Vector3(), + v4: new THREE.Vector3(), + }; + this._vector2Pool = { + v1: new THREE.Vector2(), + v2: new THREE.Vector2(), + }; + this._raycaster = new THREE.Raycaster(); + + this._cachedFloorBounds = null; + this._cachedCarpetDims = null; + this._cacheInvalidated = true; + } + + initCanvas({ + bgCanvas, + rendererCanvas, + maskCanvas, + gizmoCanvas, + inputCanvas, + container, + }: { + bgCanvas: HTMLCanvasElement; + rendererCanvas: HTMLCanvasElement; + maskCanvas: HTMLCanvasElement; + gizmoCanvas: HTMLCanvasElement; + inputCanvas: HTMLCanvasElement; + container: HTMLElement; + }) { + this.canvasInitiated = true; + this.designPath = null; + this.bgCanvas = bgCanvas; + this.rendererCanvas = rendererCanvas; + this.maskCanvas = maskCanvas; + this.gizmoCanvas = gizmoCanvas; + this.inputCanvas = inputCanvas; + this.container = container; + } + + updateBackground({ + bgImage, + width, + height, + }: { + bgImage: HTMLImageElement; + width: number; + height: number; + }) { + this.bgImage = bgImage; + const bgCtx = this.bgCanvas.getContext('2d'); + this.bgCanvas.width = width; + this.bgCanvas.height = height; + this.origWid = width; + this.origHgt = height; + this.scaleFactor = {x: 0, y: 0}; + clearCanvas(this.bgCanvas, width, height); + if (bgCtx) bgCtx.drawImage(bgImage, 0, 0, width, height); + } + + initScene({ + dims, + }: { + dims: {width: number; height: number}; + sceneConfig?: Record; + }) { + this.dims = dims; + const {width, height} = this.dims; + this.renderer = new THREE.WebGLRenderer({ + canvas: this.rendererCanvas, + preserveDrawingBuffer: true, + alpha: true, + antialias: false, + }); + this.renderer.setPixelRatio(devicePixelRatio); + this.renderer.setSize(width, height); + this.camera = new THREE.PerspectiveCamera( + this.fov, + width / height, + 0.1, + 10000 + ); + this.camera.position.set(0, 160, 386); + + this.orbit = new OrbitControls(this.camera, this.renderer.domElement); + this.orbit.screenSpacePanning = true; + this.orbit.enabled = true; + this.orbit.minPolarAngle = -0.3490658503988659; + this.orbit.maxPolarAngle = 1.7453292519943295; + this.orbit.target = new THREE.Vector3(0, 0, 0); + this.orbit.addEventListener('change', () => { + this.renderer.render(this.scene, this.camera); + }); + return Promise.resolve(); + } + + setRoomAnalysis(analysisData: AnalysisData) { + this.roomAnalysisData = analysisData; + this._invalidateCache(); + } + + _invalidateCache() { + this._cachedFloorBounds = null; + this._cachedCarpetDims = null; + this._cacheInvalidated = true; + } + + render() { + this.renderer.render(this.scene, this.camera); + } + + setupCarpet(_fbxUrl: string, _shouldSetPosition: boolean): void {} + + setCarpetVisibility(visible: boolean) { + if (this.carpetMesh) this.carpetMesh.visible = visible; + this.render(); + } + + loadDesignCanvas({ + designCanvas, + fbxUrl, + designDetails, + designPath, + customWid, + customHgt, + unit, + }: { + designCanvas: HTMLCanvasElement; + fbxUrl: string; + designDetails: DesignData; + designPath: string; + customWid?: number; + customHgt?: number; + unit: string; + }) { + const shouldResetPosRot = this.designDetails === null; + const shouldResetScale = + this.designPath !== designPath || (customWid && customHgt); + + if ( + this.designDetails !== designDetails || + this.designPath !== designPath + ) { + this._invalidateCache(); + } + + this.designDetails = designDetails; + this.designPath = designPath; + + return new Promise((resolve, reject) => { + const designTexture = new THREE.CanvasTexture(designCanvas); + designTexture.anisotropy = this.renderer.capabilities.getMaxAnisotropy(); + designTexture.colorSpace = THREE.SRGBColorSpace; + + let position = [0, 0, 0]; + let rotation = [-90, 0, 90]; + + if (this.roomAnalysisData && this.roomAnalysisData.floor_center) { + position = this.calculateCarpetPosition(this.roomAnalysisData); + rotation = this.calculateCarpetRotation(this.roomAnalysisData); + } + + let repeat = [1, 1]; + + const floorBounds = this._getFloorBoundsCached(); + if (floorBounds) { + this.bounds = new THREE.Box3( + new THREE.Vector3( + floorBounds.minX, + position[1] - 30, + floorBounds.minZ + ), + new THREE.Vector3( + floorBounds.maxX, + position[1] + 30, + floorBounds.maxZ + ) + ); + } + + const win = window as unknown as WindowExtended; + + if (win.InterfaceElements.IsWallToWall) { + const walltowalldims = [100, 100]; + if (designDetails.Unit !== 'ft') { + const convertedUnitWidth = convertUnit( + designDetails.Unit, + 'ft', + designDetails.PhysicalWidth, + 2 + ); + const convertedUnitHeight = convertUnit( + designDetails.Unit, + 'ft', + designDetails.PhysicalHeight, + 2 + ); + repeat = [ + walltowalldims[0] / convertedUnitWidth, + walltowalldims[1] / convertedUnitHeight, + ]; + } else { + repeat = [ + walltowalldims[0] / designDetails.PhysicalWidth, + walltowalldims[1] / designDetails.PhysicalHeight, + ]; + } + + let offsetX = 0; + let offsetY = 0; + if (win.flags.visualizations?.wallToWallCenterRepeat?.x) { + const halfRepeatX = repeat[0] / 2; + offsetX = 0.5 - (halfRepeatX - Math.floor(halfRepeatX)); + } + if (win.flags.visualizations?.wallToWallCenterRepeat?.y) { + const halfRepeatY = repeat[1] / 2; + offsetY = 0.5 - (halfRepeatY - Math.floor(halfRepeatY)); + } + designTexture.offset.fromArray([offsetX, offsetY]); + designTexture.wrapS = designTexture.wrapT = THREE.RepeatWrapping; + designTexture.repeat.fromArray([repeat[0], repeat[1]]); + designTexture.colorSpace = THREE.SRGBColorSpace; + + const z = + (1.25 * + 107.6 * + 10 * + convertUnit( + designDetails.Unit, + 'ft', + designDetails.PhysicalHeight + ) * + (1 + this.scaleFactor.y)) / + walltowalldims[1]; + const x = + (1.25 * + 107.6 * + 10 * + convertUnit(designDetails.Unit, 'ft', designDetails.PhysicalWidth) * + (1 + this.scaleFactor.x)) / + walltowalldims[0]; + + this.bounds = new THREE.Box3( + new THREE.Vector3(position[0] - x, position[1] - 30, position[2] - z), + new THREE.Vector3(position[0] + x, position[1] + 30, position[2] + z) + ); + } + + this.material = new THREE.MeshBasicMaterial({ + map: designTexture, + transparent: true, + }); + this.material.needsUpdate = true; + + let IsIrregular: boolean | undefined; + const PhysicalWidth = + convertUnit(designDetails.Unit, 'ft', designDetails.PhysicalWidth) * + repeat[0]; + const PhysicalHeight = + convertUnit(designDetails.Unit, 'ft', designDetails.PhysicalHeight) * + repeat[1]; + IsIrregular = designDetails.IsIrregular; + + if ( + this.fbxLoaded && + (this.origCarpetDims.w !== PhysicalWidth || + this.origCarpetDims.h !== PhysicalHeight) + ) { + this.carpetRatioNew = { + w: PhysicalWidth / this.origCarpetDims.w, + h: PhysicalHeight / this.origCarpetDims.h, + }; + if (!shouldResetScale && this.carpetMesh && this.carpetRatioNew) { + this.initCarpetScale = [ + this.carpetRatioNew.w + this.scaleFactor.x * this.carpetRatioNew.w, + this.carpetRatioNew.h + this.scaleFactor.y * this.carpetRatioNew.h, + IsIrregular ? 0.1 : this.pileHeight, + ]; + this.carpetMesh.scale.set( + ...(this.initCarpetScale as [number, number, number]) + ); + } + } + + this.carpetRatio = { + w: PhysicalWidth / this.origCarpetDims.w, + h: PhysicalHeight / this.origCarpetDims.h, + }; + + const setup = () => { + if (!this.carpetMesh) return; + + if (shouldResetPosRot) { + this.initCarpetPos = position; + this.initCarpetRot = rotation; + this.carpetMesh.position.fromArray(position); + this.carpetMesh.rotation.fromArray(convertArrIntoRad(rotation)); + } + if (shouldResetScale) { + this.initCarpetScale = [ + this.carpetRatio.w + this.scaleFactor.x * this.carpetRatio.w, + this.carpetRatio.h + this.scaleFactor.y * this.carpetRatio.h, + IsIrregular ? 0.1 : this.pileHeight, + ]; + + if (customWid && customHgt) { + const localWid = convertUnit(unit, 'ft', customWid); + const localHgt = convertUnit(unit, 'ft', customHgt); + this.carpetMesh.scale.set( + localWid / 10, + localHgt / 10, + IsIrregular ? 0.1 : this.pileHeight + ); + } else { + this.carpetMesh.scale.set( + ...(this.initCarpetScale as [number, number, number]) + ); + } + } + + if (this.material) { + this.carpetMesh.material = this.material; + this.carpetMesh.material.needsUpdate = true; + } + this.fbxLoaded = true; + this.render(); + resolve(); + }; + + if (!this.fbxLoaded) { + fbxLoader.load( + fbxUrl, + (obj) => { + this.carpetMesh = obj.getObjectByName( + this.surfaceName + ) as THREE.Mesh; + this.scene.add(this.carpetMesh); + this.setCarpetVisibility(false); + setup(); + }, + undefined, + (err) => reject(err) + ); + } else { + setup(); + } + }); + } + + updateMap() { + if (!this.carpetMesh) return; + const mat = this.carpetMesh.material as THREE.MeshBasicMaterial; + if (mat.map) mat.map.needsUpdate = true; + this.render(); + } + + calculateCarpetPosition(analysisData: AnalysisData): number[] { + if (!analysisData?.floor_center) return [0, 0, 0]; + + const {floor_center} = analysisData; + if (floor_center.no_floor) return [0, 0, 0]; + if (floor_center.area && parseInt(floor_center.area) < 10000) + return [0, 0, 0]; + if (!this.camera || !this.renderer || !this.origWid || !this.origHgt) + return [0, 0, 0]; + + try { + const normalizedX = (parseFloat(floor_center.x) / this.origWid) * 2 - 1; + const normalizedY = -(parseFloat(floor_center.y) / this.origHgt) * 2 + 1; + + const normVec = this._vector2Pool.v1.set(normalizedX, normalizedY); + this._raycaster.setFromCamera(normVec, this.camera); + + const floorPlane = new THREE.Plane(this._vector3Pool.v1.set(0, 1, 0), 0); + const intersectionPoint = this._vector3Pool.v2; + const hasIntersection = this._raycaster.ray.intersectPlane( + floorPlane, + intersectionPoint + ); + + if (hasIntersection) { + const floorBounds = this._getFloorBoundsCached(); + const carpetDims = this._getCarpetDimensionsCached(); + const finalPosition = this._vector3Pool.v3.copy(intersectionPoint); + + if (floorBounds && carpetDims) { + const safetyMargin = 1.05; + const halfWidth = (carpetDims.width / 2) * safetyMargin; + const halfDepth = (carpetDims.depth / 2) * safetyMargin; + finalPosition.x = Math.max( + floorBounds.minX + halfWidth, + Math.min(floorBounds.maxX - halfWidth, finalPosition.x) + ); + finalPosition.z = Math.max( + floorBounds.minZ + halfDepth, + Math.min(floorBounds.maxZ - halfDepth, finalPosition.z) + ); + } else { + const centerPoint = this._vector3Pool.v4; + this._raycaster.setFromCamera( + this._vector2Pool.v2.set(0, 0), + this.camera + ); + const centerIntersection = this._raycaster.ray.intersectPlane( + new THREE.Plane(this._vector3Pool.v1.set(0, 1, 0), 0), + centerPoint + ); + if (centerIntersection) finalPosition.copy(centerPoint); + } + + const distanceToCamera = Math.sqrt( + Math.pow(finalPosition.x - this.camera.position.x, 2) + + Math.pow(finalPosition.z - this.camera.position.z, 2) + ); + + if (distanceToCamera > 500) { + const centerPoint = this._vector3Pool.v4; + this._raycaster.setFromCamera( + this._vector2Pool.v2.set(0, 0), + this.camera + ); + if ( + this._raycaster.ray.intersectPlane( + new THREE.Plane(this._vector3Pool.v1.set(0, 1, 0), 0), + centerPoint + ) + ) { + return [centerPoint.x, 0, centerPoint.z]; + } + } + + return [finalPosition.x, 0, finalPosition.z]; + } + + const centerPoint = this._vector3Pool.v2; + this._raycaster.setFromCamera( + this._vector2Pool.v1.set(0, 0), + this.camera + ); + if ( + this._raycaster.ray.intersectPlane( + new THREE.Plane(this._vector3Pool.v3.set(0, 1, 0), 0), + centerPoint + ) + ) { + return [centerPoint.x, 0, centerPoint.z]; + } + return [0, 0, 0]; + } catch (error) { + console.error('Error in calculateCarpetPosition:', error); + return [0, 0, 0]; + } + } + + _getFloorBoundsCached(): FloorBounds | null { + if (this._cachedFloorBounds && !this._cacheInvalidated) + return this._cachedFloorBounds; + this._cachedFloorBounds = this._estimateFloorBounds(); + return this._cachedFloorBounds; + } + + _getCarpetDimensionsCached(): CarpetDimensions | null { + if (this._cachedCarpetDims && !this._cacheInvalidated) + return this._cachedCarpetDims; + this._cachedCarpetDims = this._estimateCarpetDimensions(); + this._cacheInvalidated = false; + return this._cachedCarpetDims; + } + + _estimateFloorBounds(): FloorBounds | null { + if (!this.camera || !this.renderer) return null; + + try { + const floorPlane = new THREE.Plane(this._vector3Pool.v1.set(0, 1, 0), 0); + const testPointsData = [ + -0.9, -0.9, 0.9, -0.9, -0.9, 0.9, 0.9, 0.9, 0, -0.9, -0.9, 0, 0.9, 0, 0, + 0, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, + ]; + + let minX = Infinity, + maxX = -Infinity; + let minZ = Infinity, + maxZ = -Infinity; + let validIntersections = 0; + + const testPoint = this._vector2Pool.v2; + const intersection = this._vector3Pool.v2; + + for (let i = 0; i < testPointsData.length; i += 2) { + testPoint.set(testPointsData[i], testPointsData[i + 1]); + this._raycaster.setFromCamera(testPoint, this.camera); + if (this._raycaster.ray.intersectPlane(floorPlane, intersection)) { + minX = Math.min(minX, intersection.x); + maxX = Math.max(maxX, intersection.x); + minZ = Math.min(minZ, intersection.z); + maxZ = Math.max(maxZ, intersection.z); + validIntersections++; + } + } + + if (validIntersections < 1) return null; + + const paddingX = (maxX - minX) * 0.03; + const paddingZ = (maxZ - minZ) * 0.03; + return { + minX: minX + paddingX, + maxX: maxX - paddingX, + minZ: minZ + paddingZ, + maxZ: maxZ - paddingZ, + }; + } catch (error) { + console.warn('Failed to estimate floor bounds:', error); + return null; + } + } + + _estimateCarpetDimensions(): CarpetDimensions | null { + if (!this.designDetails) return null; + try { + const PhysicalWidth = convertUnit( + this.designDetails.Unit, + 'ft', + this.designDetails.PhysicalWidth + ); + const PhysicalHeight = convertUnit( + this.designDetails.Unit, + 'ft', + this.designDetails.PhysicalHeight + ); + return {width: PhysicalWidth * 10, depth: PhysicalHeight * 10}; + } catch (error) { + console.error('Error in _estimateCarpetDimensions:', error); + return null; + } + } + + _isRoomLandscape(): boolean { + const floorBounds = this._getFloorBoundsCached(); + if (floorBounds) { + return ( + floorBounds.maxX - floorBounds.minX > + floorBounds.maxZ - floorBounds.minZ + ); + } + if (this.origWid && this.origHgt) return this.origWid > this.origHgt; + return true; + } + + _isCarpetLandscape(): boolean { + if (!this.designDetails) return true; + try { + const PhysicalWidth = convertUnit( + this.designDetails.Unit, + 'ft', + this.designDetails.PhysicalWidth + ); + const PhysicalHeight = convertUnit( + this.designDetails.Unit, + 'ft', + this.designDetails.PhysicalHeight + ); + return PhysicalWidth > PhysicalHeight; + } catch (error) { + console.error('Error in _isCarpetLandscape:', error); + return true; + } + } + + calculateCarpetRotation(analysisData: AnalysisData): number[] { + const defaultRotation = [-90, 0, 90]; + + if (!analysisData?.orientation) { + console.warn('Room orientation data incomplete, using default rotation'); + return defaultRotation; + } + + const {orientation} = analysisData; + if (orientation.exception) { + console.warn('Orientation detection failed, using default rotation'); + return defaultRotation; + } + + try { + const rotX = -90; + const rotY = 0; + + let horAngle = parseFloat(orientation.hor); + const horAngleSnapTolerance = 10; + if (Math.abs(horAngle) < horAngleSnapTolerance) horAngle = 0; + if (Math.abs(horAngle - 90) < horAngleSnapTolerance) horAngle = 90; + if (Math.abs(horAngle + 90) < horAngleSnapTolerance) horAngle = -90; + + let rotZ = 90 - horAngle; + if (!orientation.flag) rotZ += 90; + + if (this._isRoomLandscape() !== this._isCarpetLandscape()) rotZ += 90; + + while (rotZ > 180) rotZ -= 360; + while (rotZ < -180) rotZ += 360; + + return [rotX, rotY, rotZ]; + } catch (error) { + console.error('Error calculating carpet rotation:', error); + return defaultRotation; + } + } + + uploadRoom({bgCanvas}: {bgCanvas: HTMLCanvasElement}) { + return canvasToBlobPromise(bgCanvas).then((bgBlob) => { + return AppProvider.uploadMyRoom(bgBlob).then((roomId) => { + if (roomId === '' || roomId === 'maxsize') return roomId; + return {roomId}; + }); + }); + } + + updateMask = async ({ + maskUrl, + maskImage, + maskCanvas, + bgCanvas, + width, + height, + }: { + maskUrl?: string; + maskImage?: HTMLImageElement; + maskCanvas: HTMLCanvasElement; + bgCanvas: HTMLCanvasElement; + width?: number; + height?: number; + }) => { + const maskRoomImage = maskUrl ? await readImage(maskUrl) : maskImage!; + width = width || maskCanvas.width; + height = height || maskCanvas.height; + maskCanvas.width = width; + maskCanvas.height = height; + clearCanvas(maskCanvas, width, height); + const tmpCanvas = createCanvas(width, height); + tmpCanvas.getContext('2d')!.drawImage(bgCanvas, 0, 0, width, height); + this.makeMask(tmpCanvas, width, height, maskRoomImage); + maskCanvas.getContext('2d')!.drawImage(tmpCanvas, 0, 0, width, height); + }; + + makeMask( + canvas: HTMLCanvasElement, + w: number, + h: number, + maskImg: HTMLImageElement, + flag = false + ) { + const tCanvas = createCanvas(w, h); + const shCtx = canvas.getContext('2d')!; + const tmpCtx = tCanvas.getContext('2d')!; + tmpCtx.drawImage(maskImg, 0, 0, w, h); + const imgData = shCtx.getImageData(0, 0, w, h); + const maskData = tmpCtx.getImageData(0, 0, w, h); + for (let i = 0; i < maskData.data.length; i += 4) { + imgData.data[i + 3] = flag ? maskData.data[i] : 255 - maskData.data[i]; + } + shCtx.putImageData(imgData, 0, 0); + this.roomdataurl = canvas.toDataURL('image/png'); + return maskData; + } + + async uploadMask({maskUrl, roomId}: {maskUrl: string; roomId: string}) { + if (!maskUrl || !roomId) return; + return new Promise((resolve) => { + readImage(maskUrl).then((maskImage512) => { + const tmpCanvas = createCanvas(maskImage512.width, maskImage512.height); + tmpCanvas.getContext('2d')!.drawImage(maskImage512, 0, 0); + canvasToBlobPromise(tmpCanvas).then((mask512Blob) => { + AppProvider.uploadMyRoomMask({maskUrl: mask512Blob, roomId}).then( + () => resolve() + ); + }); + }); + }); + } + + resetCarpetTransform() { + if ( + !this.carpetMesh || + !this.initCarpetPos || + !this.initCarpetRot || + !this.initCarpetScale + ) + return; + this.carpetMesh.position.fromArray(this.initCarpetPos); + this.carpetMesh.rotation.fromArray(convertArrIntoRad(this.initCarpetRot)); + this.carpetMesh.scale.set( + ...(this.initCarpetScale as [number, number, number]) + ); + this.render(); + } + + rotateCarpet(angleinDeg: number) { + if (!this.carpetMesh) return; + this.carpetMesh.rotation.z += (angleinDeg * Math.PI) / 180; + this.render(); + } + + scaleUpCarpet() { + this.scaleCarpet(this.zoomCarpetStep); + } + + scaleDownCarpet() { + this.scaleCarpet(-this.zoomCarpetStep); + } + + scaleCarpet(step: number) { + if (!this.carpetMesh) return; + const newValue = + (this.carpetMesh.scale.x + step * this.carpetRatio.w) / + this.carpetRatio.w; + if (this.maxZoom >= newValue && newValue >= this.minZoom) { + this.carpetMesh.scale.x += step * this.carpetRatio.w; + this.carpetMesh.scale.y += step * this.carpetRatio.h; + this.scaleFactor.x += step; + this.scaleFactor.y += step; + this.render(); + } + } + + adjustPlaneHeight(increaseBool: boolean, step = 5) { + const deltaY = !increaseBool ? +step : -step; + (this.orbit as any).pan(0, deltaY); + this.orbit.update(); + } + + adjustCameraAngle(increaseBool: boolean, step = 2) { + const delta = ((increaseBool ? +step : -step) * Math.PI) / 180; + this.orbit.rotateUp(delta); + this.orbit.update(); + } + + mouseDownTouchStart(e: {x: number; y: number}) { + if (!this.carpetMesh) return; + this.intersectsGizmo = this.findGizmoIntersection(e); + this.prev = {...e}; + if (!this.intersectsGizmo) { + const intersect = this.raycastMouseOnObject(e); + if (!intersect) return; + const objPos = this.carpetMesh.position.clone(); + this.offset.copy(intersect.point).sub(objPos); + this.render(); + } + } + + mouseTouchMove(e: {x: number; y: number}, translateCarpetMode: boolean) { + if (!this.carpetMesh) return; + if (translateCarpetMode) { + if (!this.intersectsGizmo) { + const intersect = this.raycastMouseOnObject(e); + if (!intersect) return; + const objPos = this.carpetMesh.position.clone(); + const sub = intersect.point.sub(this.offset); + sub.y = objPos.y; + this.carpetMesh.position.copy(sub); + this.render(); + this.updateGizmo(); + } else { + const difference = this.prev.x - e.x; + this.carpetMesh.rotation.z -= (difference * Math.PI) / 180; + this.prev = {...e}; + this.render(); + } + } else { + const difference = this.prev.y - e.y; + this.adjustCameraAngle(false, difference); + this.prev = {...e}; + } + } + + getRendererOffset() { + return { + offsetX: this.renderer.domElement.offsetLeft, + offsetY: this.renderer.domElement.offsetTop, + }; + } + + raycastMouseOnObject(e: {x: number; y: number}) { + if (!this.carpetMesh) return undefined; + const mouse = this.convMouseCord(e); + const raycaster = new THREE.Raycaster(); + raycaster.setFromCamera(mouse, this.camera); + return raycaster.intersectObject(this.carpetMesh)[0]; + } + + convMouseCord(e: {x: number; y: number}) { + const vec = new THREE.Vector2(); + this.renderer.getSize(vec); + return new THREE.Vector2((e.x / vec.x) * 2 - 1, -(e.y / vec.y) * 2 + 1); + } + + getCarpetPositions() { + if (!this.carpetMesh) return; + this.carpetMesh.geometry.computeBoundingBox(); + const box = this.carpetMesh.geometry.boundingBox!; + + const vertex1 = this._vector3Pool.v1.copy(box.min); + const vertex2 = this._vector3Pool.v2.copy(box.max); + const max = + vertex2.x - vertex1.x > vertex2.y - vertex1.y + ? vertex2.x - vertex1.x + : vertex2.y - vertex1.y; + + if (!this._tempPositionMesh) { + this._tempPositionMesh = new THREE.Mesh( + new THREE.PlaneGeometry(max, max), + new THREE.MeshBasicMaterial() + ); + } else { + const currentSize = ( + this._tempPositionMesh.geometry as THREE.PlaneGeometry + ).parameters.width; + if (Math.abs(currentSize - max) > 0.01) { + this._tempPositionMesh.geometry.dispose(); + this._tempPositionMesh.geometry = new THREE.PlaneGeometry(max, max); + } + } + + const invMesh = this._tempPositionMesh; + invMesh.position.copy(this.carpetMesh.position); + invMesh.rotation.copy(this.carpetMesh.rotation); + + if (this.carpetRatio.w < 1.25) { + invMesh.scale.y = this.carpetMesh.scale.x / 0.8; + invMesh.scale.x = this.carpetMesh.scale.x; + } else { + invMesh.scale.x = this.carpetMesh.scale.y * 0.8; + invMesh.scale.y = this.carpetMesh.scale.y; + } + + invMesh.updateMatrix(); + invMesh.updateMatrixWorld(); + + const positionAttr = invMesh.geometry.attributes.position; + const vector = this._vector3Pool.v3; + const size = this._vector2Pool.v1; + this.renderer.getSize(size); + + const temparray: {x: number; y: number}[] = []; + for (let i = 0, l = positionAttr.count; i < l; i++) { + vector.fromBufferAttribute(positionAttr, i); + vector.applyMatrix4(invMesh.matrixWorld); + const canvasVertex = createVector( + vector, + this.camera, + size.width, + size.height + ); + temparray.push({ + x: this.getResizedImageCoordinatesX(canvasVertex.x), + y: this.getResizedImageCoordinatesY(canvasVertex.y), + }); + } + this.myRoomPointsOBJ.carpetpoints = temparray; + } + + updateGizmo(options: {show?: boolean} = {}) { + const {show = true} = options; + if (!this.gizmoCanvas || !this.carpetMesh) return; + const context = this.gizmoCanvas.getContext('2d')!; + const {width, height} = this.gizmoCanvas; + + if (!show) { + clearCanvas(this.gizmoCanvas, width, height); + return; + } + + const carpetSize = this.calculateCarpetSize(); + const smallerDim = + carpetSize.x > carpetSize.y ? carpetSize.x : carpetSize.y; + const carpetRadius = smallerDim / 5; + const carpetCenter = this.carpetMesh.position.clone(); + + const dist1 = this.distbetween2Vertices( + carpetCenter.clone(), + new THREE.Vector3( + carpetCenter.x, + carpetCenter.y, + carpetCenter.z + carpetRadius + ) + ); + const dist2 = this.distbetween2Vertices( + new THREE.Vector3( + carpetCenter.x + carpetRadius, + carpetCenter.y, + carpetCenter.z + ), + carpetCenter.clone() + ); + + const area1 = areaOfellipse(dist1.yDist, dist2.xDist); + const area2 = areaOfellipse(dist2.yDist, dist1.xDist); + const radX = area1 > area2 ? dist2.xDist : dist2.yDist; + const radY = area1 > area2 ? dist1.yDist : dist1.xDist; + + const radiusX = radX > radY ? radX : radY; + const radiusY = radX > radY ? radY : radX; + const diamondHeight = 10; + const canvasCenter = createVector(carpetCenter, this.camera, width, height); + const colorStr = 'rgba(250, 250, 250, 0.8)'; + + context.strokeStyle = colorStr; + context.fillStyle = colorStr; + context.lineWidth = 1; + context.shadowOffsetX = 0; + context.shadowColor = 'black'; + context.shadowOffsetY = 1; + context.clearRect(0, 0, width, height); + context.beginPath(); + context.ellipse( + canvasCenter.x, + canvasCenter.y, + radiusX, + radiusY, + 0, + 0, + 2 * Math.PI + ); + context.stroke(); + context.beginPath(); + context.moveTo(canvasCenter.x, canvasCenter.y + radiusY - 5); + context.lineTo(canvasCenter.x + diamondHeight, canvasCenter.y + radiusY); + context.lineTo(canvasCenter.x, canvasCenter.y + radiusY + 5); + context.lineTo(canvasCenter.x - diamondHeight, canvasCenter.y + radiusY); + context.fill(); + } + + findGizmoIntersection(e: {x: number; y: number}): boolean { + if (!this.gizmoCanvas) return false; + const imgData = this.gizmoCanvas + .getContext('2d')! + .getImageData(e.x - 10, e.y - 10, 20, 20); + for (let i = 0; i < imgData.data.length; i += 4) { + if (imgData.data[i + 3] !== 0) return true; + } + return false; + } + + autoLevel(bgCanvas: HTMLCanvasElement, maskCanvas: HTMLCanvasElement) { + this.origBG = bgCanvas.toDataURL(); + const size = new THREE.Vector2(); + this.renderer.getSize(size); + const {x: width, y: height} = size; + + const canvas = createCanvas(width, height); + const ctx = canvas.getContext('2d')!; + ctx.drawImage(bgCanvas, 0, 0, width, height); + const imgData = ctx.getImageData(0, 0, width, height); + const pixelNum = imgData.data.length; + + let redMax = 0, + redMin = 255, + greenMax = 0, + greenMin = 255, + blueMax = 0, + blueMin = 255; + + for (let i = 0; i < pixelNum; i += 4) { + if (imgData.data[i] > redMax) redMax = imgData.data[i]; + if (imgData.data[i] < redMin) redMin = imgData.data[i]; + if (imgData.data[i + 1] > greenMax) greenMax = imgData.data[i + 1]; + if (imgData.data[i + 1] < greenMin) greenMin = imgData.data[i + 1]; + if (imgData.data[i + 2] > blueMax) blueMax = imgData.data[i + 2]; + if (imgData.data[i + 2] < blueMin) blueMin = imgData.data[i + 2]; + } + + for (let j = 0; j < pixelNum; j += 4) { + imgData.data[j] = (imgData.data[j] - redMin) * (255 / (redMax - redMin)); + imgData.data[j + 1] = + (imgData.data[j + 1] - greenMin) * (255 / (greenMax - greenMin)); + imgData.data[j + 2] = + (imgData.data[j + 2] - blueMin) * (255 / (blueMax - blueMin)); + } + ctx.putImageData(imgData, 0, 0); + bgCanvas.getContext('2d')!.drawImage(canvas, 0, 0); + const imgDataCopy = imgData.valueOf() as ImageData; + + const maskCanvas1 = createCanvas(width, height); + const maskContext = maskCanvas1.getContext('2d')!; + readImage(this.roomdataurl).then((image) => { + maskContext.drawImage(image, 0, 0, width, height); + const maskData = maskContext.getImageData(0, 0, width, height); + for (let k = 0; k < maskData.data.length; k += 4) { + imgDataCopy.data[k + 3] = maskData.data[k + 3]; + } + maskContext.putImageData(imgDataCopy, 0, 0); + maskCanvas.width = width; + maskCanvas.height = height; + maskCanvas.getContext('2d')!.drawImage(maskCanvas1, 0, 0, width, height); + }); + } + + undoAutoLevel(bgCanvas: HTMLCanvasElement, maskCanvas: HTMLCanvasElement) { + readImage(this.origBG).then((origImage) => { + bgCanvas.getContext('2d')!.drawImage(origImage, 0, 0); + }); + + const size = new THREE.Vector2(); + this.renderer.getSize(size); + const {x: width, y: height} = size; + const maskContext = maskCanvas.getContext('2d')!; + readImage(this.roomdataurl).then((image) => { + maskCanvas.width = width; + maskCanvas.height = height; + clearCanvas(maskCanvas, width, height); + maskContext.drawImage(image, 0, 0, width, height); + }); + } + + markFF( + x: number, + y: number, + inputCanvas: HTMLCanvasElement, + inputSelected: string + ) { + if (inputSelected === 'eraser') { + this.removePoint(inputCanvas, x, y); + } else { + const markingColor = inputSelected === 'floor' ? '#FF0000' : '#00FF00'; + this.markWithColor(inputCanvas, markingColor, x, y); + inputSelected === 'floor' + ? this.pushFloorPoints(x, y) + : this.pushFurniPoints(x, y); + } + } + + markWithColor( + inputCanvas: HTMLCanvasElement, + MarkingColor: string, + x: number, + y: number + ) { + const ctx = inputCanvas.getContext('2d')!; + ctx.moveTo(x, y); + ctx.fillStyle = MarkingColor; + ctx.beginPath(); + ctx.arc(x, y, 2, 0, 2 * Math.PI, false); + ctx.fill(); + ctx.closePath(); + } + + isLastPoint(pointsList: {x: number; y: number}[], ptX: number, ptY: number) { + if (!pointsList.length) return false; + const lastPt = pointsList[pointsList.length - 1]; + return lastPt.x === ptX && lastPt.y === ptY; + } + + pushFloorPoints(x: number, y: number) { + x = this.getResizedImageCoordinatesX(x); + y = this.getResizedImageCoordinatesY(y); + if (!this.isLastPoint(this.myRoomPointsOBJ.floorpoints, x, y)) { + this.myRoomPointsOBJ.floorpoints.push({x, y}); + } + } + + pushFurniPoints(x: number, y: number) { + x = this.getResizedImageCoordinatesX(x); + y = this.getResizedImageCoordinatesY(y); + if (!this.isLastPoint(this.myRoomPointsOBJ.notfloorpoints, x, y)) { + this.myRoomPointsOBJ.notfloorpoints.push({x, y}); + } + } + + removePoint(inputCanvas: HTMLCanvasElement, x: number, y: number) { + x = this.getResizedImageCoordinatesX(x); + y = this.getResizedImageCoordinatesY(y); + const isFarEnough = (point: {x: number; y: number}) => { + const a = x - point.x; + const b = y - point.y; + return Math.sqrt(a * a + b * b) >= 30; + }; + this.myRoomPointsOBJ.notfloorpoints = + this.myRoomPointsOBJ.notfloorpoints.filter(isFarEnough); + this.myRoomPointsOBJ.floorpoints = + this.myRoomPointsOBJ.floorpoints.filter(isFarEnough); + this.drawFFPoints(inputCanvas); + } + + drawFFPoints(inputCanvas: HTMLCanvasElement) { + inputCanvas + .getContext('2d')! + .clearRect(0, 0, inputCanvas.width, inputCanvas.height); + this.colorPoints(inputCanvas, this.myRoomPointsOBJ.floorpoints, '#FF0000'); + this.colorPoints( + inputCanvas, + this.myRoomPointsOBJ.notfloorpoints, + '#00FF00' + ); + } + + colorPoints( + inputCanvas: HTMLCanvasElement, + points: {x: number; y: number}[], + markingColor: string + ) { + for (const point of points) { + this.markWithColor( + inputCanvas, + markingColor, + this.getInputCanvasCoordinatesX(point.x), + this.getInputCanvasCoordinatesY(point.y) + ); + } + } + + updatePointHistory() { + this.pointsHistory.floorpoints = this.myRoomPointsOBJ.floorpoints; + this.pointsHistory.notfloorpoints = this.myRoomPointsOBJ.notfloorpoints; + this.pointsHistory.inputSelected = this.myroomInputSelected; + this.pointsHistoryArray.push(JSON.stringify(this.pointsHistory)); + } + + pointsHistoryReset() { + this.myRoomPointsOBJ.floorpoints = []; + this.myRoomPointsOBJ.notfloorpoints = []; + this.pointsHistoryArray = []; + } + + undoPoints(inputCanvas: HTMLCanvasElement) { + if (this.pointsHistoryArray.length === 0) return; + this.pointsHistoryArray.pop(); + if (this.pointsHistoryArray.length === 0) { + this.myRoomPointsOBJ.floorpoints = []; + this.myRoomPointsOBJ.notfloorpoints = []; + this.drawFFPoints(inputCanvas); + return; + } + this.pointsHistory = JSON.parse( + this.pointsHistoryArray[this.pointsHistoryArray.length - 1] + ); + this.myRoomPointsOBJ.floorpoints = this.pointsHistory.floorpoints; + this.myRoomPointsOBJ.notfloorpoints = this.pointsHistory.notfloorpoints; + this.drawFFPoints(inputCanvas); + } + + resetPoints(inputCanvas: HTMLCanvasElement) { + this.pointsHistoryReset(); + this.drawFFPoints(inputCanvas); + clearCanvas(inputCanvas, inputCanvas.width, inputCanvas.height); + } + + getResizedImageCoordinatesX(x: number) { + return Math.round(x * this.ratioWid); + } + + getResizedImageCoordinatesY(y: number) { + return Math.round(y * this.ratioHgt); + } + + getInputCanvasCoordinatesX(x: number) { + return Math.round(x / this.ratioWid); + } + + getInputCanvasCoordinatesY(y: number) { + return Math.round(y / this.ratioHgt); + } + + resize(windowSize: {width: number; height: number}) { + if (!this.canvasInitiated) return; + const {width, height} = resizeKeepingAspect(this.bgImage, windowSize); + + this.bgCanvas.style.width = `${width}px`; + this.bgCanvas.style.height = `${height}px`; + this.maskCanvas.style.width = `${width}px`; + this.maskCanvas.style.height = `${height}px`; + + this.inputCanvas.width = width; + this.inputCanvas.height = height; + this.gizmoCanvas.width = width; + this.gizmoCanvas.height = height; + this.resizeRenderer(width, height); + this.updateGizmo(); + } + + resizeRenderer(width: number, height: number) { + if (this.camera) { + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + } + this.renderer.setSize(width, height); + this.ratioWid = this.origWid / width; + this.ratioHgt = this.origHgt / height; + this.render(); + } + + calculateCarpetSize() { + if (!this.carpetMesh) return new THREE.Vector3(); + this.carpetMesh.geometry.computeBoundingBox(); + const carpetSize = new THREE.Vector3(); + this.carpetMesh.geometry.boundingBox!.getSize(carpetSize); + return carpetSize; + } + + calculateCameraWidth() { + const carpetSize = this.calculateCarpetSize(); + const siz = new THREE.Vector2(); + this.renderer.getSize(siz); + const carpetWid = carpetSize.x > carpetSize.y ? carpetSize.x : carpetSize.y; + return siz.x >= siz.y ? carpetWid * 1.1 : carpetWid * 1.2; + } + + distbetween2Vertices(vertex1: THREE.Vector3, vertex2: THREE.Vector3) { + const vec = new THREE.Vector2(); + this.renderer.getSize(vec); + const v1 = createVector(vertex1, this.camera, vec.x, vec.y); + const v2 = createVector(vertex2, this.camera, vec.x, vec.y); + return { + xDist: Math.abs(Math.abs(v2.x) - Math.abs(v1.x)), + yDist: Math.abs(Math.abs(v2.y) - Math.abs(v1.y)), + }; + } + + calculateVerticalHeight() { + const {x, y} = this.calculateCarpetSize(); + const h1 = this.distbetween2Vertices( + new THREE.Vector3(-x / 2, 0, 0), + new THREE.Vector3(x / 2, 0, 0) + ); + const h2 = this.distbetween2Vertices( + new THREE.Vector3(0, 0, y / 2), + new THREE.Vector3(0, 0, -y / 2) + ); + return h1.yDist > h2.yDist ? h1.yDist : h2.yDist; + } + + angleBetween2Vertices(vertex1: THREE.Vector3, vertex2: THREE.Vector3) { + const size = new THREE.Vector2(); + this.renderer.getSize(size); + const v1 = createVector(vertex1, this.camera, size.width, size.height); + const v2 = createVector(vertex2, this.camera, size.width, size.height); + return (Math.atan((v2.y - v1.y) / (v2.x - v1.x)) * 180) / Math.PI; + } + + changeCameraDist(width: number, fovDeg: number) { + const fovrad = (fovDeg * Math.PI) / 180; + const distance = width / (2 * Math.tan(fovrad / 2)); + const {camera, orbit} = this; + const cur_dist = Math.sqrt( + camera.position.x * camera.position.x + + camera.position.y * camera.position.y + + camera.position.z * camera.position.z + ); + const dist_ratio = distance / cur_dist; + orbit.object.position.set( + dist_ratio * camera.position.x, + dist_ratio * camera.position.y, + dist_ratio * camera.position.z + ); + orbit.update(); + this.render(); + } + + calculateAngle1() { + return this.angleBetween2Vertices( + new THREE.Vector3(0, 0, -500), + new THREE.Vector3(0, 0, 500) + ); + } + + calculateAngle2() { + return ( + 180 - + Math.abs( + this.angleBetween2Vertices( + new THREE.Vector3(400, 0, 0), + new THREE.Vector3(-400, 0, 0) + ) + ) + ); + } + + resetOrbit(x = 0, y = 0) { + this.orbit.reset(); + this.changeCameraDist(this.calculateCameraWidth(), this.camera.fov); + (this.orbit as any).pan(x, y); + this.orbit.update(); + } + + setinitialOrientation(res: AnalysisData) { + this.roomAnalysisData = res; + + if (this.carpetMesh && this.fbxLoaded) { + const newRotation = this.calculateCarpetRotation(res); + this.carpetMesh.rotation.fromArray(convertArrIntoRad(newRotation)); + this.initCarpetRot = newRotation; + const newPosition = this.calculateCarpetPosition(res); + this.carpetMesh.position.fromArray(newPosition); + this.initCarpetPos = newPosition; + this.render(); + } + + this.orbit.reset(); + this.camera.position.set(0, 0, 720); + this.orbit.update(); + + let fov = parseFloat(res.fov); + if (fov < 30) fov = 30; + if (fov > 60) fov = 60; + + this.camera.fov = fov; + this.camera.updateProjectionMatrix(); + this.render(); + this.changeCameraDist(this.calculateCameraWidth(), fov); + + let horAngleForCamera = parseFloat(res.orientation.hor); + const horAngleSnapTolerance = 10; + if (Math.abs(horAngleForCamera) < horAngleSnapTolerance) + horAngleForCamera = 0; + else if (Math.abs(horAngleForCamera - 90) < horAngleSnapTolerance) + horAngleForCamera = 90; + else if (Math.abs(horAngleForCamera + 90) < horAngleSnapTolerance) + horAngleForCamera = -90; + + (this.orbit as any).setAzimuthalAngle( + -((90 - horAngleForCamera) * Math.PI) / 180 + ); + this.orbit.update(); + (this.orbit as any).setPolarAngle((10 * Math.PI) / 180); + this.orbit.update(); + (this.orbit as any).pan( + parseFloat(res.floor_center.x), + parseFloat(res.floor_center.y) + ); + this.orbit.update(); + + const angle1pred = Math.abs(parseFloat(res.orientation.hor)); + const angle2pred = Math.abs(parseFloat(res.orientation.vert)); + const global_iter_limit = 100; + const iter_limit = 50; + let index = 0; + + const calcError = (predicted: number, actual: number) => + (Math.abs(Math.abs(predicted) - actual) / actual) * 100; + + if (res.orientation.exception) { + if (!res.orientation.flag) { + this.orbit.rotateLeft(Math.PI / 2); + this.orbit.update(); + } + } else { + let vertical_error = calcError(this.calculateAngle2(), angle2pred); + let horizontal_error = calcError(this.calculateAngle1(), angle1pred); + let hor_angle_per_step = 1; + let vert_angle_per_step = 1; + const decay_rate = 0.5; + + const optimizeVertically = (step: number) => { + let angle2 = Math.abs(this.calculateAngle2()); + const vert_angle = angle2 < 90 ? -step : step; + let i1 = 0, + i2 = 0; + while (angle2 > angle2pred) { + this.orbit.rotateUp(THREE.MathUtils.degToRad(vert_angle)); + this.orbit.update(); + angle2 = Math.abs(this.calculateAngle2()); + if (++i1 === iter_limit) break; + } + while (angle2 < angle2pred) { + this.orbit.rotateUp(THREE.MathUtils.degToRad(-vert_angle)); + this.orbit.update(); + angle2 = Math.abs(this.calculateAngle2()); + if (++i2 === iter_limit) break; + } + }; + + const optimizeHorizontally = (step: number) => { + let angle1 = Math.abs(this.calculateAngle1()); + let i1 = 0, + i2 = 0; + while (angle1 < angle1pred) { + this.orbit.rotateLeft(THREE.MathUtils.degToRad(step)); + this.orbit.update(); + angle1 = Math.abs(this.calculateAngle1()); + if (++i1 === iter_limit) break; + } + while (angle1 > angle1pred) { + this.orbit.rotateLeft(THREE.MathUtils.degToRad(-step)); + this.orbit.update(); + angle1 = Math.abs(this.calculateAngle1()); + if (++i2 === iter_limit) break; + } + }; + + while (vertical_error > 0.1 || horizontal_error > 2) { + optimizeHorizontally(hor_angle_per_step); + optimizeVertically(vert_angle_per_step); + vertical_error = calcError(this.calculateAngle2(), angle2pred); + horizontal_error = calcError(this.calculateAngle1(), angle1pred); + hor_angle_per_step *= decay_rate; + vert_angle_per_step *= decay_rate; + if (++index === global_iter_limit) break; + } + + this.orbit.update(); + if (!res.orientation.flag) { + this.orbit.rotateLeft(Math.PI / 2); + this.orbit.update(); + } + + const size = new THREE.Vector2(); + this.renderer.getSize(size); + let verticalHeight = this.calculateVerticalHeight(); + while (verticalHeight < 0.15 * size.height) { + this.orbit.rotateUp((0.5 * Math.PI) / 180); + this.orbit.update(); + verticalHeight = this.calculateVerticalHeight(); + } + this.orbit.update(); + } + + this.render(); + this.changeCameraDist(this.calculateCameraWidth(), fov); + this.render(); + } + + removemeshes() { + if (!this.carpetMesh) return; + (this.carpetMesh.material as THREE.MeshBasicMaterial).opacity = 1; + this.render(); + } + + saveAsRoom({ + mode, + roomId, + file, + props, + }: { + mode: string; + roomId: string; + file: unknown; + props: unknown; + }) { + this.getCarpetPositions(); + const {carpetpoints, floorpoints, notfloorpoints} = this.myRoomPointsOBJ; + return AppProvider.saveAsRoom({ + mode, + roomId, + file, + props, + floorpoints, + notfloorpoints, + carpetpoints, + }).then((response) => ({response})); + } + + downloadImage(downloadBLob: Blob, filename: string) { + return new Promise((resolve) => { + const url = window.URL || (window as any).webkitURL; + const imageSrc = url.createObjectURL(downloadBLob); + const link = document.createElement('a'); + document.body.appendChild(link); + link.setAttribute('download', filename); + link.href = imageSrc; + if ((navigator as any).msSaveOrOpenBlob) { + (navigator as any).msSaveOrOpenBlob(downloadBLob, filename); + } else { + link.click(); + } + document.body.removeChild(link); + resolve(imageSrc); + }); + } + + saveAsImage = async ({ + bgCanvas, + maskCanvas, + designPath, + }: { + bgCanvas: HTMLCanvasElement; + maskCanvas: HTMLCanvasElement; + designPath: string; + }) => { + if (!this.carpetMesh) return; + (this.carpetMesh.material as THREE.MeshBasicMaterial).opacity = 1; + this.removemeshes(); + const imgData = this.renderer.domElement.toDataURL('image/png'); + + readImage(imgData).then((rendererImage) => { + const downloadCanvas = createCanvas( + rendererImage.width, + rendererImage.height + ); + const downloadContext = downloadCanvas.getContext('2d')!; + downloadContext.drawImage( + bgCanvas, + 0, + 0, + rendererImage.width, + rendererImage.height + ); + downloadContext.drawImage( + rendererImage, + 0, + 0, + rendererImage.width, + rendererImage.height + ); + downloadContext.drawImage( + maskCanvas, + 0, + 0, + rendererImage.width, + rendererImage.height + ); + + const win = window as unknown as WindowExtended; + if (win.flags.inhouseChanges?.saveFunctionForFlutter) { + win.getByteData = downloadCanvas.toDataURL('image/jpeg', 0.95); + return; + } + + canvasToBlobPromise(downloadCanvas).then((downloadBLob) => { + let name = 'customdesign'; + if (!win.initialData?.customDesignUrl) { + const filename = designPath.split('/').pop()!; + name = filename.split('.')[0]; + } + this.downloadImage(downloadBLob, name + '-myroom.jpg').then(() => { + if (this.carpetMesh) { + (this.carpetMesh.material as THREE.MeshBasicMaterial).opacity = 1; + } + this.render(); + }); + }); + }); + }; + + dispose() { + if (this.renderer) { + this.renderer.dispose(); + this.renderer.forceContextLoss(); + this.renderer = null!; + } + if (this.orbit) { + this.orbit.dispose(); + this.orbit = null!; + } + if (this.carpetMesh) { + this.carpetMesh.geometry?.dispose(); + const mat = this.carpetMesh.material as THREE.MeshBasicMaterial; + mat.map?.dispose(); + mat.dispose(); + this.carpetMesh = undefined; + } + if (this._tempPositionMesh) { + this._tempPositionMesh.geometry?.dispose(); + (this._tempPositionMesh.material as THREE.Material).dispose(); + this._tempPositionMesh = undefined; + } + if (this.scene) { + this.scene.clear(); + this.scene = null!; + } + this.camera = null!; + this.textureLoader = null!; + this.roomAnalysisData = null; + this._cachedFloorBounds = null; + this._cachedCarpetDims = null; + } +} diff --git a/src/Components/RoomView/helpers/PhotographicView.ts b/src/Components/MyRoom/helpers/PhotographicView.ts similarity index 100% rename from src/Components/RoomView/helpers/PhotographicView.ts rename to src/Components/MyRoom/helpers/PhotographicView.ts diff --git a/src/Components/MyRoom/helpers/ThreeViewHelper.ts b/src/Components/MyRoom/helpers/ThreeViewHelper.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/Components/MyRoom/index.ts b/src/Components/MyRoom/index.ts new file mode 100644 index 0000000..4dd7ded --- /dev/null +++ b/src/Components/MyRoom/index.ts @@ -0,0 +1,168 @@ +// src/room-illustration.ts + +import {LitElement, html, css} from 'lit'; +import {customElement, property, query, state} from 'lit/decorators.js'; +import {RoomViewHelper} from './helpers/MyRoomViewHelper'; +import type { + RoomData, + DesignDetails, + CarpetOptions, + AvailableSize, +} from './types'; + +let roomViewHelper = new RoomViewHelper(); + +@customElement('room-illustration') +export class RoomIllustration extends LitElement { + // ─── Props in (replaces all useXxxState() hook reads) ──────────────── + @property({type: Object}) roomData: RoomData | null = null; + @property({type: Object}) designDetails: DesignDetails | null = null; + @property({type: Object}) carpetOptions: CarpetOptions | null = null; + @property({type: Object}) activeColor: {Color: string} | null = null; + @property({type: Object}) selectedSize: AvailableSize | null = null; + @property({type: Boolean}) isIdle = false; + + // ─── Internal state (replaces useState) ────────────────────────────── + @state() private shotReady = false; + @state() private transition = -1; + + // ─── Canvas refs (replaces useRef) ─────────────────────────────────── + @query('.container') private containerEl!: HTMLDivElement; + @query('.bg-canvas') private bgCanvas!: HTMLCanvasElement; + @query('.three-canvas') private threeCanvas!: HTMLCanvasElement; + @query('.mask-canvas') private maskCanvas!: HTMLCanvasElement; + @query('.shadow-canvas') private shadowCanvas!: HTMLCanvasElement; + @query('.input-canvas') private inputCanvas!: HTMLCanvasElement; + @query('.obj-container') private objContainer!: HTMLDivElement; + + // ─── Lifecycle ──────────────────────────────────────────────────────── + + override firstUpdated() { + // Equivalent to useMount in React — canvases are in the DOM + // roomViewHelper.initCanvas({ + // bgCanvas: this.bgCanvas, + // threeCanvas: this.threeCanvas, + // maskCanvas: this.maskCanvas, + // shadowCanvas: this.shadowCanvas, + // container: this.containerEl, + // inputCanvas: this.inputCanvas, + // objCanvasContainer: this.objContainer, + // }); + + window.addEventListener('resize', () => this.sizeContainers()); + } + + override updated(changed: Map) { + // if (changed.has('roomData') && this.roomData) { + // this.loadRoom(); + // } + // if (changed.has('carpetOptions') && this.carpetOptions && this.shotReady) { + // this.updateCarpet(); + // } + // if (changed.has('activeColor') && this.activeColor && this.shotReady) { + // roomViewHelper.updateBackground({ + // dominantColorHex: this.activeColor.Color, + // }); + // roomViewHelper.updateMask(); + // roomViewHelper.updateShadow(); + // } + // if (changed.has('selectedSize') && this.selectedSize) { + // roomViewHelper.change3dObjectScale(this.selectedSize); + // } + } + + override disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener('resize', () => this.sizeContainers()); + // roomViewHelper.clearAll(); + } + + // ─── Internal methods (replaces inline useEffect logic) ────────────── + + private async loadRoom() { + // if (!this.roomData) return; + // const {config, baseUrl, files, shot} = this.roomData; + // roomViewHelper.clearAll(); + // roomViewHelper.initConfig({baseUrl, config, files}); + // this.sizeContainers(); + // await this.initShot({shot: shot ?? config.defaultShot}); + } + + private async initShot(options: {shot: string}) { + // mirrors initShot logic from IllustrationView/index.jsx + // ... + this.shotReady = true; + this.emitRenderingComplete(); + } + + private async updateCarpet() { + if (!this.carpetOptions) return; + // await roomViewHelper.updateCarpetRotation(this.carpetOptions.rotation); + // await roomViewHelper.updateCarpetPosition(this.carpetOptions.position); + // await roomViewHelper.updateShadow(); + } + + private sizeContainers() { + // resize canvas to container size + } + + // ─── Events out (replaces callback props) ──────────────────────────── + + private emitRenderingComplete() { + this.dispatchEvent( + new CustomEvent('rendering-complete', {bubbles: true, composed: true}) + ); + } + + private emitSizesChange(sizes: AvailableSize[]) { + this.dispatchEvent( + new CustomEvent('sizes-change', { + detail: sizes, + bubbles: true, + composed: true, + }) + ); + } + + private emitActiveClick() { + this.dispatchEvent( + new CustomEvent('active-click', {bubbles: true, composed: true}) + ); + } + + // ─── Template (replaces JSX canvas layout) ─────────────────────────── + + static override styles = css` + :host { + display: block; + position: relative; + width: 100%; + height: 100%; + } + .container { + position: relative; + width: 100%; + height: 100%; + } + canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + `; + + override render() { + return html` +
+ + + + + +
+
+ `; + } +} diff --git a/src/Components/RoomView/types.ts b/src/Components/MyRoom/types.ts similarity index 100% rename from src/Components/RoomView/types.ts rename to src/Components/MyRoom/types.ts diff --git a/src/Components/RoomView/helpers/RoomViewHelper.ts b/src/Components/RoomView/helpers/RoomViewHelper.ts index 868fd2a..2ffb933 100644 --- a/src/Components/RoomView/helpers/RoomViewHelper.ts +++ b/src/Components/RoomView/helpers/RoomViewHelper.ts @@ -1,1123 +1,1264 @@ -import * as THREE from 'three'; -import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'; -import {FBXLoader} from 'three/examples/jsm/loaders/FBXLoader.js'; +import { + convertArrintoDeg, + convertUnit, + getCroppedSize, + makeUrl, + resizeKeepingAspect, +} from '../../../Utils/utils'; +import {readImage} from '../../../Utils/domUtils'; import { canvasToBlobPromise, clearCanvas, createCanvas, + cropStitchCanvas, + downloadImageData, } from '../../../Utils/canvasutils'; -import { - CarpetRatio, - MyRoomPointsOBJ, - PointsHistory, - ScaleFactor, - Vector2Pool, - Vector3Pool, -} from '../types'; -declare const convertUnit: ( - fromUnit: string, - toUnit: string, - value: number, - decimals?: number -) => number; -declare const convertArrIntoRad: (arr: number[]) => [number, number, number]; -declare const areaOfellipse: (a: number, b: number) => number; -declare const createVector: ( - vertex: THREE.Vector3, - camera: THREE.Camera, - width: number, - height: number -) => {x: number; y: number}; -declare const resizeKeepingAspect: ( - image: HTMLImageElement, - windowSize: {width: number; height: number} -) => {width: number; height: number}; -declare const readImage: (url: string) => Promise; +// External JS modules without TS declarations +declare const ThreeViewHelper: new () => IThreeViewHelper; +declare const TileCanvas: new () => ITileCanvas; +declare const CDN_domain: string; +declare const myroomStudioSaveUrl: string; +declare const AppProvider: IAppProvider; +declare const entryViews: {STUDIO: string}; -declare const AppProvider: { - uploadMyRoom: (blob: Blob) => Promise; - uploadMyRoomMask: (params: {maskUrl: Blob; roomId: string}) => Promise; - saveAsRoom: (params: { - mode: string; - roomId: string; - file: unknown; - props: unknown; - floorpoints: {x: number; y: number}[]; - notfloorpoints: {x: number; y: number}[]; - carpetpoints: {x: number; y: number}[]; - }) => Promise; -}; +interface IAppProvider { + domain: string; + fetchPdfForRugStudio: (params: { + roomPath: Blob; + description: string; + qrurl: string; + roomName: string; + room: string; + isWatermark: boolean; + paypalName: string; + paypalEmail: string; + purchasedDate: string; + price: string; + }) => Promise<{pdfPath: string}>; + sendEmailMyRoomStudio: (params: { + file: string; + qrurl: string; + roomName: string; + isWatermark: boolean; + paypalName: string; + paypalEmail: string; + purchasedDate: string; + price: string; + }) => void; +} -const fbxLoader = new FBXLoader(); +interface IThreeViewHelper { + renderer: unknown; + designDetails: DesignDetails | null; + init(options: { + canvas: HTMLCanvasElement; + config: SceneConfig; + shots: string[]; + dims: Dimensions; + resolution: number; + baseUrl: string; + roomType: string; + }): void; + setupSceneObjects(options: {carpetRotation: number; fbxUrl: string}): Promise; + setObjectTexture(options: { + designDetails: DesignDetails; + designCanvas: HTMLCanvasElement; + normapCanvas?: HTMLCanvasElement; + customDesign?: boolean; + }): void; + setCarpetTexture(options: { + designDetails: DesignDetails; + designCanvas: HTMLCanvasElement; + normapCanvas: HTMLCanvasElement; + }): void; + setCarpetScale(details: Partial & {PhysicalWidth?: number; PhysicalHeight?: number; Unit?: string}): void; + updateMap(): void; + setCarpetVisibility(visible: boolean): void; + setCarpetPositon(position: {x: number; y: number; z: number}): void; + setCarpetRotation(rotation: number): void; + rotateCarpet(angle: number, axis: string): void; + mouseDownTouchStart(e: PointerEvent): boolean; + mouseTouchMove(e: PointerEvent): void; + render(): void; + getObjectConfig(): {rotation: {toArray(): number[]}; position: {x: number; y: number; z: number}} | null; + getGizmoCordinates(): {radX: number; radY: number; canvasCenter: {x: number; y: number}} | null; + raycastMouseOnSurface(e: PointerEvent): {uv: {x: number; y: number}} | null; + changeFloorVisibility(visible: boolean): void; + setFloorTexture(options: {floorCanvas: HTMLCanvasElement}): void; + changeWallpaperVisibility(visible: boolean): void; + setWallpaperTexture(options: {wallpaperCanvas: HTMLCanvasElement; dims: Dimensions}): void; + setWallpaperSurfaceSize(options: {surfaceSize: unknown}): void; + updateCarpetRepeatWallToWall(): void; + resizeRenderer(dims: Dimensions): void; +} -interface DesignData { - Unit: string; +interface TileCanvasDrawOptions { + designPath: string; + zoom: number; + designDetails: DesignDetails; + hash: string; + drawNormap?: boolean | number; + tileTransparency: number[]; +} + +interface ITileCanvas { + canvas: HTMLCanvasElement; + canvasNorm: HTMLCanvasElement; + width: number; + height: number; + designTilesUpdated: boolean; + normapTilesUpdated: boolean; + init(options: { + maxArea: number | null; + zoom: number; + designDetails: DesignDetails; + canvasSize: unknown; + renderBounds: unknown; + offset: unknown; + }): void; + drawCanvasTiles( + options: TileCanvasDrawOptions, + onTile: () => void, + onComplete: () => void + ): void; + updateDesignTiles( + options: TileUpdateOptions, + onProgress: () => void, + onComplete: () => void + ): void; + updateNormapTiles( + options: TileUpdateOptions, + onProgress: () => void, + onComplete: () => void + ): void; +} + +interface TileUpdateOptions { + tiles: unknown; + zoom: number; + designDetails: DesignDetails; + designPath: string; + hash: string; + tileTransparency: number[]; +} + +interface DesignColor { + PileHeight: number; + Carving?: boolean; +} + +interface DesignDetails { + Width: number; + Height: number; PhysicalWidth: number; PhysicalHeight: number; + Unit: string; + DesignColors: DesignColor[]; + outline?: unknown; IsIrregular?: boolean; } -interface WindowExtended extends Window { - InterfaceElements: {IsWallToWall: boolean}; +interface Dimensions { + width: number; + height: number; +} + +interface SceneConfig { + designScale?: number; + canvasSize?: unknown; + renderBounds?: unknown; + offset?: unknown; +} + +interface RoomConfig { + dims: Dimensions; + shots: string[]; + lights?: string[]; + scenes?: string[]; + roomType: string; + backgroundVideo?: string; + hasTransparencyMask?: boolean; + [key: string]: unknown; +} + +interface CanvasOptions { + bgCanvas: HTMLCanvasElement; + threeCanvas: HTMLCanvasElement; + maskCanvas: HTMLCanvasElement; + shadowCanvas: HTMLCanvasElement; + container: HTMLElement; + inputCanvas: HTMLCanvasElement; + transitionCanvas: HTMLCanvasElement; + floatingOptionsContainer?: HTMLElement; + bgVideo?: HTMLVideoElement; +} + +interface InitConfigOptions { + baseUrl: string; + config: RoomConfig; + files: string[]; + sizeFromConfig?: boolean; +} + +interface ActiveColorEntry { + annotationColor: Uint8ClampedArray | number[]; + dominantColorHex: string; +} + +interface RVWindow { flags: { - visualizations?: { - wallToWallCenterRepeat?: {x?: boolean; y?: boolean}; + visualizations: { + usePlainFbx?: boolean; + renderDesignSpecific: number; + isBorderRugs?: boolean; + maxCanvasSize?: [number, number]; + showGizmoInRoomView?: boolean; + applyNormalMapInJPEG?: boolean; }; - inhouseChanges?: { - saveFunctionForFlutter?: boolean; + applyNormalMapInJPEG?: boolean; + isFelt?: boolean; + ordersheet: { + repeatRugInArea?: boolean; + }; + designView: { + hasDesignWaterMark?: boolean; }; }; - constraints?: {audio: boolean; video: boolean}; - initialData?: {customDesignUrl?: string}; - getByteData?: string; + InterfaceElements: { + IsWallToWall: boolean; + IsJpeg: boolean; + LogoUrl: string; + }; + initialData: { + centerDesignUrl?: string; + borderSize?: number; + unit?: string; + borderDesignWid?: string; + borderDesignHgt?: string; + }; } -interface FloorCenter { - x: string; - y: string; - area?: string; - no_floor?: boolean; +const win = window as unknown as RVWindow; + +const tileCanvas = new TileCanvas(); + +function createName(...args: (string | null | undefined)[]): string { + let res = ''; + args.forEach((argument) => { + if (argument) { + res = `${res}${argument}.`; + } + }); + return res; } -interface Orientation { - hor: string; - vert: string; - exception?: boolean; - flag?: boolean; -} +const rgbFromHex = (hex: string): [number, number, number] => [ + parseInt(hex.substring(1, 3), 16), + parseInt(hex.substring(3, 5), 16), + parseInt(hex.substring(5, 7), 16), +]; -export interface AnalysisData { - floor_center: FloorCenter; - orientation: Orientation; - fov: string; -} +const patchRgb: [number, number, number] = [45, 24, 18]; -interface FloorBounds { - minX: number; - maxX: number; - minZ: number; - maxZ: number; -} +export default class RoomViewHelper { + config: RoomConfig; + baseUrl: string | null; + dimension: Dimensions; + dimensionPixels: Dimensions; + bgImage: HTMLImageElement | null; + maskImage: HTMLImageElement | null; + patchImage: HTMLImageElement | null; + patchGreyImage: HTMLImageElement | null; + patchShadow: HTMLImageElement | null; + shadowImage: HTMLImageElement | null; + highlightImage: HTMLImageElement | null; + glowImage: HTMLImageElement | null; + annotationCanvas: HTMLCanvasElement | null; + zoom: number; + currentActiveColors: ActiveColorEntry[]; + selectedColorCode: Uint8ClampedArray | number[] | null; + resolution: number; + designDetails: DesignDetails | null; + designPath: string; + designCanvasMod: HTMLCanvasElement; + normalCanvasMod: HTMLCanvasElement; + floorCanvas: HTMLCanvasElement | null; + wallpaperCanvas: HTMLCanvasElement | null; + tileDetails: Record | null; -interface CarpetDimensions { - width: number; - depth: number; -} - -export class RoomViewHelper { - scene: THREE.Scene; - textureLoader: THREE.TextureLoader; - offset: THREE.Vector3; - fbxLoaded: boolean; - surfaceName: string; - - zoomCarpetStep: number; - maxZoom: number; - minZoom: number; - carpetRatio: CarpetRatio; - origCarpetDims: {w: number; h: number}; - scaleFactor: ScaleFactor; - pileHeight: number; - - myRoomPointsOBJ: MyRoomPointsOBJ; - pointsHistory: PointsHistory; - pointsHistoryArray: string[]; - - constraints: {audio: boolean; video: boolean}; - - initCarpetPos: number[] | null; - initCarpetRot: number[] | null; - initCarpetScale: number[] | null; - designDetails: DesignData | null; - designPath: string | null; - canvasInitiated: boolean; bgCanvas!: HTMLCanvasElement; - rendererCanvas!: HTMLCanvasElement; + threeCanvas!: HTMLCanvasElement; maskCanvas!: HTMLCanvasElement; - gizmoCanvas!: HTMLCanvasElement; - inputCanvas!: HTMLCanvasElement; + shadowCanvas!: HTMLCanvasElement; + transitionCanvas!: HTMLCanvasElement; container!: HTMLElement; - bgImage!: HTMLImageElement; - origWid!: number; - origHgt!: number; - roomAnalysisData: AnalysisData | null; - ratioWid!: number; - ratioHgt!: number; - myroomInputSelected: string = ''; - roomdataurl: string = ''; - origBG: string = ''; + inputCanvas!: HTMLCanvasElement; + floatingOptionsContainer?: HTMLElement; + bgVideo?: HTMLVideoElement; - intersectsGizmo: boolean = false; - prev: {x: number; y: number} = {x: 0, y: 0}; + threeView: IThreeViewHelper; - _vector3Pool: Vector3Pool; - _vector2Pool: Vector2Pool; - _raycaster: THREE.Raycaster; - - _cachedFloorBounds: FloorBounds | null; - _cachedCarpetDims: CarpetDimensions | null; - _cacheInvalidated: boolean; - - _tempPositionMesh?: THREE.Mesh; - - carpetMesh?: THREE.Mesh; - bounds?: THREE.Box3; - material?: THREE.MeshBasicMaterial; - carpetRatioNew?: CarpetRatio; - - dims!: {width: number; height: number}; - fov: number = 45; - renderer!: THREE.WebGLRenderer; - camera!: THREE.PerspectiveCamera; - orbit!: OrbitControls; + private moved: boolean; + private prev: {x: number; y: number}; + private intersectsGizmo: boolean; + private shadowCleared: boolean; constructor() { - this.scene = new THREE.Scene(); - this.textureLoader = new THREE.TextureLoader(); - this.offset = new THREE.Vector3(); - this.fbxLoaded = false; - this.surfaceName = 'Box002'; - - this.zoomCarpetStep = 0.05; - this.maxZoom = 2; - this.minZoom = 0.5; - this.carpetRatio = {w: 1, h: 1}; - this.origCarpetDims = {w: 10, h: 10}; - this.scaleFactor = {x: 0, y: 0}; - this.pileHeight = 0.25; - - this.myRoomPointsOBJ = { - floorpoints: [], - notfloorpoints: [], - carpetpoints: [], - }; - - this.pointsHistory = { - floorpoints: [], - notfloorpoints: [], - inputSelected: '', - }; - this.pointsHistoryArray = []; - - this.constraints = (window as unknown as WindowExtended).constraints = { - audio: false, - video: true, - }; - - this.initCarpetPos = null; - this.initCarpetRot = null; - this.initCarpetScale = null; + this.config = {} as RoomConfig; + this.baseUrl = null; + this.dimension = {width: 0, height: 0}; + this.dimensionPixels = {width: 0, height: 0}; + this.bgImage = null; + this.maskImage = null; + this.patchImage = null; + this.patchGreyImage = null; + this.patchShadow = null; + this.shadowImage = null; + this.highlightImage = null; + this.glowImage = null; + this.annotationCanvas = null; + this.zoom = 2; + this.currentActiveColors = []; + this.selectedColorCode = null; + this.resolution = 1; this.designDetails = null; - this.designPath = null; - this.canvasInitiated = false; - this.roomAnalysisData = null; + this.designPath = ''; + this.designCanvasMod = createCanvas(1, 1); + this.normalCanvasMod = createCanvas(1, 1); + this.floorCanvas = null; + this.wallpaperCanvas = null; + this.tileDetails = null; + this.moved = false; + this.prev = {x: 0, y: 0}; + this.intersectsGizmo = false; + this.shadowCleared = false; + this.threeView = new ThreeViewHelper(); + } - this._vector3Pool = { - v1: new THREE.Vector3(), - v2: new THREE.Vector3(), - v3: new THREE.Vector3(), - v4: new THREE.Vector3(), + initCanvas(options: CanvasOptions): void { + this.bgCanvas = options.bgCanvas; + this.threeCanvas = options.threeCanvas; + this.maskCanvas = options.maskCanvas; + this.shadowCanvas = options.shadowCanvas; + this.container = options.container; + this.inputCanvas = options.inputCanvas; + this.transitionCanvas = options.transitionCanvas; + this.floatingOptionsContainer = options.floatingOptionsContainer; + this.bgVideo = options.bgVideo; + } + + clearAllCanvases(): void { + clearCanvas(this.bgCanvas, this.bgCanvas.width, this.bgCanvas.height); + clearCanvas(this.maskCanvas, this.maskCanvas.width, this.maskCanvas.height); + clearCanvas(this.shadowCanvas, this.shadowCanvas.width, this.shadowCanvas.height); + clearCanvas(this.inputCanvas, this.inputCanvas.width, this.inputCanvas.height); + } + + initConfig({baseUrl, config, files, sizeFromConfig = false}: InitConfigOptions): Promise[] { + this.baseUrl = baseUrl; + this.config = config; + const illustrationDims = this.config.dims; + const containerDims = { + width: this.container.clientWidth, + height: this.container.clientHeight, }; - this._vector2Pool = { - v1: new THREE.Vector2(), - v2: new THREE.Vector2(), + this.currentActiveColors = []; + this.selectedColorCode = null; + this.resolution = window.devicePixelRatio; + + const normalizedFiles = normalizeDirNames(files); + + if (sessionStorage.getItem('entryview') === entryViews.STUDIO) { + this.dimensionPixels = resizeKeepingAspect(illustrationDims, containerDims, 'crop'); + } else { + this.dimensionPixels = resizeKeepingAspect(illustrationDims, containerDims, 'fit_inside'); + } + + this.dimension = { + width: Math.trunc( + sizeFromConfig && illustrationDims.width + ? illustrationDims.width + : window.screen.width * this.resolution + ), + height: Math.trunc( + sizeFromConfig && illustrationDims.height + ? illustrationDims.height + : window.screen.height * this.resolution + ), }; - this._raycaster = new THREE.Raycaster(); + this.resolution = this.dimension.width / this.dimensionPixels.width; + this.inputCanvas.width = this.dimensionPixels.width; + this.inputCanvas.height = this.dimensionPixels.height; + if (this.floatingOptionsContainer) { + this.floatingOptionsContainer.style.width = `${this.dimensionPixels.width}px`; + this.floatingOptionsContainer.style.height = `${this.dimensionPixels.height}px`; + } + if (this.bgVideo) { + this.bgVideo.style.width = `${this.dimensionPixels.width}px`; + this.bgVideo.style.height = `${this.dimensionPixels.height}px`; + } + this.clearAllCanvases(); - this._cachedFloorBounds = null; - this._cachedCarpetDims = null; - this._cacheInvalidated = true; + const x = { + shot: this.config.shots[0], + light: this.config.lights ? this.config.lights[0] : null, + }; + const bgUrl = `${createName(x.shot, x.light)}bg.jpg`; + const bgPatchUrl = `${createName(x.shot, x.light)}pch.png`; + const bgPatchShadowUrl = `${createName(x.shot, x.light)}pch.sh.jpg`; + const bgPatchGreyUrl = `${createName(x.shot, x.light)}pch.grey.jpg`; + const maskUrl = `${createName(x.shot, x.light)}m.png`; + const shadowUrl = `${createName(x.shot, x.light)}sh.jpg`; + const highlightUrl = `${createName(x.shot, x.light)}hl.jpg`; + const glowUrl = `${createName(x.shot, x.light)}gl.jpg`; + const bgVideoUrl = config.backgroundVideo; + + const promises: Promise[] = []; + this.bgImage = null; + this.maskImage = null; + this.patchImage = null; + this.patchGreyImage = null; + this.patchShadow = null; + this.shadowImage = null; + this.highlightImage = null; + this.glowImage = null; + + if (!normalizedFiles.includes(bgUrl)) { + promises.push(Promise.reject('no background image')); + } else { + promises.push(readImage(makeUrl(baseUrl, bgUrl)).then((img) => (this.bgImage = img))); + if (normalizedFiles.includes(bgPatchUrl)) + promises.push(readImage(makeUrl(baseUrl, bgPatchUrl)).then((img) => (this.patchImage = img))); + if (normalizedFiles.includes(bgPatchShadowUrl)) + promises.push(readImage(makeUrl(baseUrl, bgPatchShadowUrl)).then((img) => (this.patchShadow = img))); + if (normalizedFiles.includes(bgPatchGreyUrl)) + promises.push(readImage(makeUrl(baseUrl, bgPatchGreyUrl)).then((img) => (this.patchGreyImage = img))); + if (normalizedFiles.includes(maskUrl)) + promises.push(readImage(makeUrl(baseUrl, maskUrl)).then((img) => (this.maskImage = img))); + if (normalizedFiles.includes(shadowUrl)) + promises.push(readImage(makeUrl(baseUrl, shadowUrl)).then((img) => (this.shadowImage = img))); + if (normalizedFiles.includes(highlightUrl)) + promises.push(readImage(makeUrl(baseUrl, highlightUrl)).then((img) => (this.highlightImage = img))); + if (normalizedFiles.includes(glowUrl)) + promises.push(readImage(makeUrl(baseUrl, glowUrl)).then((img) => (this.glowImage = img))); + if (this.bgVideo) { + if (bgVideoUrl && normalizedFiles.includes(bgVideoUrl)) { + const mask = makeUrl(baseUrl, maskUrl); + this.bgVideo.src = makeUrl(baseUrl, bgVideoUrl); + this.bgVideo.setAttribute( + 'style', + `-webkit-mask-image:url(${mask});mask-image:url(${mask});` + ); + } else { + this.bgVideo.src = ''; + this.bgVideo.setAttribute('style', 'mask-image:none'); + } + } + } + return promises; } - initCanvas({ - bgCanvas, - rendererCanvas, - maskCanvas, - gizmoCanvas, - inputCanvas, - container, - }: { - bgCanvas: HTMLCanvasElement; - rendererCanvas: HTMLCanvasElement; - maskCanvas: HTMLCanvasElement; - gizmoCanvas: HTMLCanvasElement; - inputCanvas: HTMLCanvasElement; - container: HTMLElement; - }) { - this.canvasInitiated = true; - this.designPath = null; - this.bgCanvas = bgCanvas; - this.rendererCanvas = rendererCanvas; - this.maskCanvas = maskCanvas; - this.gizmoCanvas = gizmoCanvas; - this.inputCanvas = inputCanvas; - this.container = container; + updateBackground(options: {clear?: boolean; dominantColorHex?: string; canvas?: HTMLCanvasElement} = {}): string { + const {clear = false, dominantColorHex, canvas = this.bgCanvas} = options; + const {width, height} = this.dimension; + + const bgCtx = canvas.getContext('2d')!; + const tempBgCanvas = createCanvas(width, height); + tempBgCanvas.getContext('2d')!.drawImage(this.bgCanvas, 0, 0, width, height); + + clearCanvas(canvas, canvas.width, canvas.height); + setCanvasDimensions(canvas, this.dimension, this.dimensionPixels); + if (clear) return 'clear'; + + bgCtx.drawImage(this.bgImage!, 0, 0, width, height); + bgCtx.drawImage(tempBgCanvas, 0, 0, width, height); + + if (this.patchImage) { + this.annotationCanvas = createCanvas(this.dimensionPixels.width, this.dimensionPixels.height); + this.annotationCanvas + .getContext('2d')! + .drawImage(this.patchImage, 0, 0, this.dimensionPixels.width, this.dimensionPixels.height); + } else { + this.annotationCanvas = null; + } + + if (this.patchImage && dominantColorHex) { + const activeColor = rgbFromHex(dominantColorHex); + const patchCanvas = createCanvas(width, height); + const patchCtx = patchCanvas.getContext('2d')!; + patchCtx.drawImage(this.patchImage, 0, 0, width, height); + const patchData = patchCtx.getImageData(0, 0, width, height); + + if (!this.selectedColorCode) this.selectedColorCode = patchRgb; + + for (let i = 0; i < patchData.data.length; i += 4) { + if ( + isNumberInRange(patchData.data[i], this.selectedColorCode[0] - 5, this.selectedColorCode[0] + 5) && + isNumberInRange(patchData.data[i + 1], this.selectedColorCode[1] - 5, this.selectedColorCode[1] + 5) && + isNumberInRange(patchData.data[i + 2], this.selectedColorCode[2] - 5, this.selectedColorCode[2] + 5) + ) { + patchData.data[i] = activeColor[0]; + patchData.data[i + 1] = activeColor[1]; + patchData.data[i + 2] = activeColor[2]; + } else { + patchData.data[i] = 128; + patchData.data[i + 1] = 128; + patchData.data[i + 2] = 128; + patchData.data[i + 3] = 0; + } + } + + const sel = this.currentActiveColors.findIndex( + (item) => item.annotationColor === this.selectedColorCode + ); + if (!sel || sel === -1) { + this.currentActiveColors.push({annotationColor: this.selectedColorCode, dominantColorHex}); + } else { + this.currentActiveColors[sel] = {annotationColor: this.selectedColorCode, dominantColorHex}; + } + + patchCtx.putImageData(patchData, 0, 0); + const patchGreyCanvas = createCanvas(width, height); + const patchGreyCtx = patchGreyCanvas.getContext('2d')!; + if (this.patchGreyImage) { + patchGreyCtx.drawImage(this.patchGreyImage, 0, 0, width, height); + } + patchGreyCtx.globalCompositeOperation = 'overlay'; + patchGreyCtx.drawImage(patchCanvas, 0, 0, width, height); + if (this.patchShadow) { + patchGreyCtx.globalCompositeOperation = 'multiply'; + patchGreyCtx.drawImage(this.patchShadow, 0, 0, width, height); + } + patchGreyCtx.globalCompositeOperation = 'destination-in'; + patchGreyCtx.drawImage(patchCanvas, 0, 0, width, height); + bgCtx.drawImage(patchGreyCanvas, 0, 0, width, height); + } + return 'successfully updated bg'; } - updateBackground({ - bgImage, - width, - height, - }: { - bgImage: HTMLImageElement; - width: number; - height: number; - }) { - this.bgImage = bgImage; - const bgCtx = this.bgCanvas.getContext('2d'); - this.bgCanvas.width = width; - this.bgCanvas.height = height; - this.origWid = width; - this.origHgt = height; - this.scaleFactor = {x: 0, y: 0}; - clearCanvas(this.bgCanvas, width, height); - if (bgCtx) bgCtx.drawImage(bgImage, 0, 0, width, height); - } - - initScene({ - dims, - }: { - dims: {width: number; height: number}; - sceneConfig?: Record; - }) { - this.dims = dims; - const {width, height} = this.dims; - this.renderer = new THREE.WebGLRenderer({ - canvas: this.rendererCanvas, - preserveDrawingBuffer: true, - alpha: true, - antialias: false, + updatethreeCanvas(options: {carpetRotation: number} = {carpetRotation: 0}): Promise | undefined { + const {carpetRotation} = options; + if (!this.config.scenes) return; + const scene = this.config.scenes[0]; + const {roomType, shots} = this.config; + const sceneConfig = this.config[scene] as SceneConfig; + this.threeView.init({ + canvas: this.threeCanvas, + config: sceneConfig, + shots, + dims: this.dimensionPixels, + resolution: this.resolution, + baseUrl: this.baseUrl!, + roomType, }); - this.renderer.setPixelRatio(devicePixelRatio); - this.renderer.setSize(width, height); - this.camera = new THREE.PerspectiveCamera( - this.fov, - width / height, - 0.1, - 10000 - ); - this.camera.position.set(0, 160, 386); + const fbx = + win.flags.visualizations.usePlainFbx || win.InterfaceElements.IsWallToWall + ? 'plain_rug.fbx' + : 'rug.fbx'; + const fbxUrl = `${CDN_domain}v3assets/${fbx}`; + return this.threeView.setupSceneObjects({carpetRotation, fbxUrl}); + } - this.orbit = new OrbitControls(this.camera, this.renderer.domElement); - this.orbit.screenSpacePanning = true; - this.orbit.enabled = true; - this.orbit.minPolarAngle = -0.3490658503988659; - this.orbit.maxPolarAngle = 1.7453292519943295; - this.orbit.target = new THREE.Vector3(0, 0, 0); - this.orbit.addEventListener('change', () => { - this.renderer.render(this.scene, this.camera); + renderDesignFromCustomUrl({ + customUrl, + customDesignOutline, + physicalWidth, + physicalHeight, + unit = 'cm', + }: { + customUrl: string; + customDesignOutline: unknown; + physicalWidth?: number; + physicalHeight?: number; + unit?: string; + }): Promise { + return new Promise((resolve, reject) => { + readImage(customUrl) + .then((image) => { + const {width, height} = image; + const designCanvas = createCanvas(width, height); + const ctx = designCanvas.getContext('2d')!; + ctx.drawImage(image, 0, 0); + const ctxdata = ctx.getImageData(0, 0, designCanvas.width, designCanvas.height).data; + const isIrregular = hasTransparentPixel(ctxdata); + + const normapCanvas = createCanvas(width, height); + const ctxNorm = normapCanvas.getContext('2d')!; + ctxNorm.fillStyle = 'rgb(127,127,255)'; + ctxNorm.fillRect(0, 0, width, height); + + let PhysicalWidth: number, PhysicalHeight: number; + if (!physicalWidth || !physicalHeight) { + const maxDims = {width: 1200, height: 1500}; + const {width: newWidth, height: newHeight} = resizeKeepingAspect({width, height}, maxDims, 'fit_inside'); + PhysicalWidth = convertUnit('in', 'ft', newWidth / 10); + PhysicalHeight = convertUnit('in', 'ft', newHeight / 10); + } else { + PhysicalWidth = convertUnit(unit, 'ft', physicalWidth); + PhysicalHeight = convertUnit(unit, 'ft', physicalHeight); + } + + const designDetails: DesignDetails = { + Width: width, + Height: height, + PhysicalWidth, + PhysicalHeight, + Unit: 'ft', + outline: customDesignOutline, + IsIrregular: isIrregular, + DesignColors: [], + }; + + if (this.config.roomType === 'illustration') { + this.threeView.setObjectTexture({designDetails, designCanvas, normapCanvas, customDesign: true}); + } else { + this.threeView.setCarpetTexture({designDetails, designCanvas, normapCanvas}); + } + this.updateGizmo(); + resolve(); + }) + .catch(reject); }); - return Promise.resolve(); } - setRoomAnalysis(analysisData: AnalysisData) { - this.roomAnalysisData = analysisData; - this._invalidateCache(); + changeRenderedDesignSize({PhysicalWidth, PhysicalHeight, Unit = 'cm'}: {PhysicalWidth: number; PhysicalHeight: number; Unit?: string}): void { + if (!PhysicalWidth || !PhysicalHeight) return; + this.threeView.setCarpetScale({PhysicalWidth, PhysicalHeight, Unit}); + this.threeView.updateMap(); } - _invalidateCache() { - this._cachedFloorBounds = null; - this._cachedCarpetDims = null; - this._cacheInvalidated = true; - } - - render() { - this.renderer.render(this.scene, this.camera); - } - - setupCarpet(_fbxUrl: string, _shouldSetPosition: boolean): void {} - - setCarpetVisibility(visible: boolean) { - if (this.carpetMesh) this.carpetMesh.visible = visible; - this.render(); - } - - loadDesignCanvas({ - designCanvas, - fbxUrl, + renderDesign({ designDetails, designPath, - customWid, - customHgt, - unit, + hash, + designDimsOrig, }: { - designCanvas: HTMLCanvasElement; - fbxUrl: string; - designDetails: DesignData; + designDetails: DesignDetails; designPath: string; - customWid?: number; - customHgt?: number; - unit: string; - }) { - const shouldResetPosRot = this.designDetails === null; - const shouldResetScale = - this.designPath !== designPath || (customWid && customHgt); - - if ( - this.designDetails !== designDetails || - this.designPath !== designPath - ) { - this._invalidateCache(); - } - + hash: string; + designDimsOrig: DesignDetails; + }): Promise { this.designDetails = designDetails; this.designPath = designPath; + const {roomType} = this.config; + const sceneConfig = this.config[this.config.scenes![0]] as SceneConfig; + const {designScale, canvasSize, renderBounds, offset} = sceneConfig; + if (designScale) this.zoom = designScale; + else this.zoom = win.flags.visualizations.renderDesignSpecific; - return new Promise((resolve, reject) => { - const designTexture = new THREE.CanvasTexture(designCanvas); - designTexture.anisotropy = this.renderer.capabilities.getMaxAnisotropy(); - designTexture.colorSpace = THREE.SRGBColorSpace; + const isDimsOrig = + designDimsOrig.Width === designDetails.Width && + designDimsOrig.Height === designDetails.Height; - let position = [0, 0, 0]; - let rotation = [-90, 0, 90]; + let tileTransparency: number[] | undefined; + try { + tileTransparency = (this.tileDetails as Record)[`tileTransparency${this.zoom}`]; + } catch (error) { + console.log(error); + } - if (this.roomAnalysisData && this.roomAnalysisData.floor_center) { - position = this.calculateCarpetPosition(this.roomAnalysisData); - rotation = this.calculateCarpetRotation(this.roomAnalysisData); - } + if (win.InterfaceElements.IsJpeg) { + this.zoom = 1; + tileTransparency = [1]; + } - let repeat = [1, 1]; + let desDetails: DesignDetails = {...designDetails}; + if (!isDimsOrig) desDetails = {...desDetails, ...designDimsOrig}; - const floorBounds = this._getFloorBoundsCached(); - if (floorBounds) { - this.bounds = new THREE.Box3( - new THREE.Vector3( - floorBounds.minX, - position[1] - 30, - floorBounds.minZ - ), - new THREE.Vector3( - floorBounds.maxX, - position[1] + 30, - floorBounds.maxZ - ) - ); - } + let maxArea: number | null = null; + if (win.flags.visualizations.maxCanvasSize) { + const maxCanvasSize = win.flags.visualizations.maxCanvasSize; + maxArea = maxCanvasSize[0] * maxCanvasSize[1]; + } - const win = window as unknown as WindowExtended; - - if (win.InterfaceElements.IsWallToWall) { - const walltowalldims = [100, 100]; - if (designDetails.Unit !== 'ft') { - const convertedUnitWidth = convertUnit( - designDetails.Unit, - 'ft', - designDetails.PhysicalWidth, - 2 - ); - const convertedUnitHeight = convertUnit( - designDetails.Unit, - 'ft', - designDetails.PhysicalHeight, - 2 - ); - repeat = [ - walltowalldims[0] / convertedUnitWidth, - walltowalldims[1] / convertedUnitHeight, - ]; - } else { - repeat = [ - walltowalldims[0] / designDetails.PhysicalWidth, - walltowalldims[1] / designDetails.PhysicalHeight, - ]; - } - - let offsetX = 0; - let offsetY = 0; - if (win.flags.visualizations?.wallToWallCenterRepeat?.x) { - const halfRepeatX = repeat[0] / 2; - offsetX = 0.5 - (halfRepeatX - Math.floor(halfRepeatX)); - } - if (win.flags.visualizations?.wallToWallCenterRepeat?.y) { - const halfRepeatY = repeat[1] / 2; - offsetY = 0.5 - (halfRepeatY - Math.floor(halfRepeatY)); - } - designTexture.offset.fromArray([offsetX, offsetY]); - designTexture.wrapS = designTexture.wrapT = THREE.RepeatWrapping; - designTexture.repeat.fromArray([repeat[0], repeat[1]]); - designTexture.colorSpace = THREE.SRGBColorSpace; - - const z = - (1.25 * - 107.6 * - 10 * - convertUnit( - designDetails.Unit, - 'ft', - designDetails.PhysicalHeight - ) * - (1 + this.scaleFactor.y)) / - walltowalldims[1]; - const x = - (1.25 * - 107.6 * - 10 * - convertUnit(designDetails.Unit, 'ft', designDetails.PhysicalWidth) * - (1 + this.scaleFactor.x)) / - walltowalldims[0]; - - this.bounds = new THREE.Box3( - new THREE.Vector3(position[0] - x, position[1] - 30, position[2] - z), - new THREE.Vector3(position[0] + x, position[1] + 30, position[2] + z) - ); - } - - this.material = new THREE.MeshBasicMaterial({ - map: designTexture, - transparent: true, - }); - this.material.needsUpdate = true; - - let IsIrregular: boolean | undefined; - const PhysicalWidth = - convertUnit(designDetails.Unit, 'ft', designDetails.PhysicalWidth) * - repeat[0]; - const PhysicalHeight = - convertUnit(designDetails.Unit, 'ft', designDetails.PhysicalHeight) * - repeat[1]; - IsIrregular = designDetails.IsIrregular; - - if ( - this.fbxLoaded && - (this.origCarpetDims.w !== PhysicalWidth || - this.origCarpetDims.h !== PhysicalHeight) - ) { - this.carpetRatioNew = { - w: PhysicalWidth / this.origCarpetDims.w, - h: PhysicalHeight / this.origCarpetDims.h, - }; - if (!shouldResetScale && this.carpetMesh && this.carpetRatioNew) { - this.initCarpetScale = [ - this.carpetRatioNew.w + this.scaleFactor.x * this.carpetRatioNew.w, - this.carpetRatioNew.h + this.scaleFactor.y * this.carpetRatioNew.h, - IsIrregular ? 0.1 : this.pileHeight, - ]; - this.carpetMesh.scale.set( - ...(this.initCarpetScale as [number, number, number]) - ); - } - } - - this.carpetRatio = { - w: PhysicalWidth / this.origCarpetDims.w, - h: PhysicalHeight / this.origCarpetDims.h, - }; - - const setup = () => { - if (!this.carpetMesh) return; - - if (shouldResetPosRot) { - this.initCarpetPos = position; - this.initCarpetRot = rotation; - this.carpetMesh.position.fromArray(position); - this.carpetMesh.rotation.fromArray(convertArrIntoRad(rotation)); - } - if (shouldResetScale) { - this.initCarpetScale = [ - this.carpetRatio.w + this.scaleFactor.x * this.carpetRatio.w, - this.carpetRatio.h + this.scaleFactor.y * this.carpetRatio.h, - IsIrregular ? 0.1 : this.pileHeight, - ]; - - if (customWid && customHgt) { - const localWid = convertUnit(unit, 'ft', customWid); - const localHgt = convertUnit(unit, 'ft', customHgt); - this.carpetMesh.scale.set( - localWid / 10, - localHgt / 10, - IsIrregular ? 0.1 : this.pileHeight - ); - } else { - this.carpetMesh.scale.set( - ...(this.initCarpetScale as [number, number, number]) - ); - } - } - - if (this.material) { - this.carpetMesh.material = this.material; - this.carpetMesh.material.needsUpdate = true; - } - this.fbxLoaded = true; - this.render(); - resolve(); - }; - - if (!this.fbxLoaded) { - fbxLoader.load( - fbxUrl, - (obj) => { - this.carpetMesh = obj.getObjectByName( - this.surfaceName - ) as THREE.Mesh; - this.scene.add(this.carpetMesh); - this.setCarpetVisibility(false); - setup(); - }, - undefined, - (err) => reject(err) - ); - } else { - setup(); - } + tileCanvas.init({ + maxArea, + zoom: this.zoom, + designDetails: desDetails, + canvasSize, + renderBounds, + offset, }); - } - updateMap() { - if (!this.carpetMesh) return; - const mat = this.carpetMesh.material as THREE.MeshBasicMaterial; - if (mat.map) mat.map.needsUpdate = true; - this.render(); - } + switch (roomType) { + case 'illustration': + return new Promise((resolve) => { + this.threeView.setObjectTexture({designDetails, designCanvas: tileCanvas.canvas}); + const drawNormap: boolean | number = !win.flags.applyNormalMapInJPEG && win.InterfaceElements.IsJpeg + ? false + : (tileTransparency?.length ?? 0); - calculateCarpetPosition(analysisData: AnalysisData): number[] { - if (!analysisData?.floor_center) return [0, 0, 0]; - - const {floor_center} = analysisData; - if (floor_center.no_floor) return [0, 0, 0]; - if (floor_center.area && parseInt(floor_center.area) < 10000) - return [0, 0, 0]; - if (!this.camera || !this.renderer || !this.origWid || !this.origHgt) - return [0, 0, 0]; - - try { - const normalizedX = (parseFloat(floor_center.x) / this.origWid) * 2 - 1; - const normalizedY = -(parseFloat(floor_center.y) / this.origHgt) * 2 + 1; - - const normVec = this._vector2Pool.v1.set(normalizedX, normalizedY); - this._raycaster.setFromCamera(normVec, this.camera); - - const floorPlane = new THREE.Plane(this._vector3Pool.v1.set(0, 1, 0), 0); - const intersectionPoint = this._vector3Pool.v2; - const hasIntersection = this._raycaster.ray.intersectPlane( - floorPlane, - intersectionPoint - ); - - if (hasIntersection) { - const floorBounds = this._getFloorBoundsCached(); - const carpetDims = this._getCarpetDimensionsCached(); - const finalPosition = this._vector3Pool.v3.copy(intersectionPoint); - - if (floorBounds && carpetDims) { - const safetyMargin = 1.05; - const halfWidth = (carpetDims.width / 2) * safetyMargin; - const halfDepth = (carpetDims.depth / 2) * safetyMargin; - finalPosition.x = Math.max( - floorBounds.minX + halfWidth, - Math.min(floorBounds.maxX - halfWidth, finalPosition.x) - ); - finalPosition.z = Math.max( - floorBounds.minZ + halfDepth, - Math.min(floorBounds.maxZ - halfDepth, finalPosition.z) - ); - } else { - const centerPoint = this._vector3Pool.v4; - this._raycaster.setFromCamera( - this._vector2Pool.v2.set(0, 0), - this.camera - ); - const centerIntersection = this._raycaster.ray.intersectPlane( - new THREE.Plane(this._vector3Pool.v1.set(0, 1, 0), 0), - centerPoint - ); - if (centerIntersection) finalPosition.copy(centerPoint); - } - - const distanceToCamera = Math.sqrt( - Math.pow(finalPosition.x - this.camera.position.x, 2) + - Math.pow(finalPosition.z - this.camera.position.z, 2) - ); - - if (distanceToCamera > 500) { - const centerPoint = this._vector3Pool.v4; - this._raycaster.setFromCamera( - this._vector2Pool.v2.set(0, 0), - this.camera - ); - if ( - this._raycaster.ray.intersectPlane( - new THREE.Plane(this._vector3Pool.v1.set(0, 1, 0), 0), - centerPoint - ) - ) { - return [centerPoint.x, 0, centerPoint.z]; - } - } - - return [finalPosition.x, 0, finalPosition.z]; - } - - const centerPoint = this._vector3Pool.v2; - this._raycaster.setFromCamera( - this._vector2Pool.v1.set(0, 0), - this.camera - ); - if ( - this._raycaster.ray.intersectPlane( - new THREE.Plane(this._vector3Pool.v3.set(0, 1, 0), 0), - centerPoint - ) - ) { - return [centerPoint.x, 0, centerPoint.z]; - } - return [0, 0, 0]; - } catch (error) { - console.error('Error in calculateCarpetPosition:', error); - return [0, 0, 0]; - } - } - - _getFloorBoundsCached(): FloorBounds | null { - if (this._cachedFloorBounds && !this._cacheInvalidated) - return this._cachedFloorBounds; - this._cachedFloorBounds = this._estimateFloorBounds(); - return this._cachedFloorBounds; - } - - _getCarpetDimensionsCached(): CarpetDimensions | null { - if (this._cachedCarpetDims && !this._cacheInvalidated) - return this._cachedCarpetDims; - this._cachedCarpetDims = this._estimateCarpetDimensions(); - this._cacheInvalidated = false; - return this._cachedCarpetDims; - } - - _estimateFloorBounds(): FloorBounds | null { - if (!this.camera || !this.renderer) return null; - - try { - const floorPlane = new THREE.Plane(this._vector3Pool.v1.set(0, 1, 0), 0); - const testPointsData = [ - -0.9, -0.9, 0.9, -0.9, -0.9, 0.9, 0.9, 0.9, 0, -0.9, -0.9, 0, 0.9, 0, 0, - 0, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, - ]; - - let minX = Infinity, - maxX = -Infinity; - let minZ = Infinity, - maxZ = -Infinity; - let validIntersections = 0; - - const testPoint = this._vector2Pool.v2; - const intersection = this._vector3Pool.v2; - - for (let i = 0; i < testPointsData.length; i += 2) { - testPoint.set(testPointsData[i], testPointsData[i + 1]); - this._raycaster.setFromCamera(testPoint, this.camera); - if (this._raycaster.ray.intersectPlane(floorPlane, intersection)) { - minX = Math.min(minX, intersection.x); - maxX = Math.max(maxX, intersection.x); - minZ = Math.min(minZ, intersection.z); - maxZ = Math.max(maxZ, intersection.z); - validIntersections++; - } - } - - if (validIntersections < 1) return null; - - const paddingX = (maxX - minX) * 0.03; - const paddingZ = (maxZ - minZ) * 0.03; - return { - minX: minX + paddingX, - maxX: maxX - paddingX, - minZ: minZ + paddingZ, - maxZ: maxZ - paddingZ, - }; - } catch (error) { - console.warn('Failed to estimate floor bounds:', error); - return null; - } - } - - _estimateCarpetDimensions(): CarpetDimensions | null { - if (!this.designDetails) return null; - try { - const PhysicalWidth = convertUnit( - this.designDetails.Unit, - 'ft', - this.designDetails.PhysicalWidth - ); - const PhysicalHeight = convertUnit( - this.designDetails.Unit, - 'ft', - this.designDetails.PhysicalHeight - ); - return {width: PhysicalWidth * 10, depth: PhysicalHeight * 10}; - } catch (error) { - console.error('Error in _estimateCarpetDimensions:', error); - return null; - } - } - - _isRoomLandscape(): boolean { - const floorBounds = this._getFloorBoundsCached(); - if (floorBounds) { - return ( - floorBounds.maxX - floorBounds.minX > - floorBounds.maxZ - floorBounds.minZ - ); - } - if (this.origWid && this.origHgt) return this.origWid > this.origHgt; - return true; - } - - _isCarpetLandscape(): boolean { - if (!this.designDetails) return true; - try { - const PhysicalWidth = convertUnit( - this.designDetails.Unit, - 'ft', - this.designDetails.PhysicalWidth - ); - const PhysicalHeight = convertUnit( - this.designDetails.Unit, - 'ft', - this.designDetails.PhysicalHeight - ); - return PhysicalWidth > PhysicalHeight; - } catch (error) { - console.error('Error in _isCarpetLandscape:', error); - return true; - } - } - - calculateCarpetRotation(analysisData: AnalysisData): number[] { - const defaultRotation = [-90, 0, 90]; - - if (!analysisData?.orientation) { - console.warn('Room orientation data incomplete, using default rotation'); - return defaultRotation; - } - - const {orientation} = analysisData; - if (orientation.exception) { - console.warn('Orientation detection failed, using default rotation'); - return defaultRotation; - } - - try { - const rotX = -90; - const rotY = 0; - - let horAngle = parseFloat(orientation.hor); - const horAngleSnapTolerance = 10; - if (Math.abs(horAngle) < horAngleSnapTolerance) horAngle = 0; - if (Math.abs(horAngle - 90) < horAngleSnapTolerance) horAngle = 90; - if (Math.abs(horAngle + 90) < horAngleSnapTolerance) horAngle = -90; - - let rotZ = 90 - horAngle; - if (!orientation.flag) rotZ += 90; - - if (this._isRoomLandscape() !== this._isCarpetLandscape()) rotZ += 90; - - while (rotZ > 180) rotZ -= 360; - while (rotZ < -180) rotZ += 360; - - return [rotX, rotY, rotZ]; - } catch (error) { - console.error('Error calculating carpet rotation:', error); - return defaultRotation; - } - } - - uploadRoom({bgCanvas}: {bgCanvas: HTMLCanvasElement}) { - return canvasToBlobPromise(bgCanvas).then((bgBlob) => { - return AppProvider.uploadMyRoom(bgBlob).then((roomId) => { - if (roomId === '' || roomId === 'maxsize') return roomId; - return {roomId}; - }); - }); - } - - updateMask = async ({ - maskUrl, - maskImage, - maskCanvas, - bgCanvas, - width, - height, - }: { - maskUrl?: string; - maskImage?: HTMLImageElement; - maskCanvas: HTMLCanvasElement; - bgCanvas: HTMLCanvasElement; - width?: number; - height?: number; - }) => { - const maskRoomImage = maskUrl ? await readImage(maskUrl) : maskImage!; - width = width || maskCanvas.width; - height = height || maskCanvas.height; - maskCanvas.width = width; - maskCanvas.height = height; - clearCanvas(maskCanvas, width, height); - const tmpCanvas = createCanvas(width, height); - tmpCanvas.getContext('2d')!.drawImage(bgCanvas, 0, 0, width, height); - this.makeMask(tmpCanvas, width, height, maskRoomImage); - maskCanvas.getContext('2d')!.drawImage(tmpCanvas, 0, 0, width, height); - }; - - makeMask( - canvas: HTMLCanvasElement, - w: number, - h: number, - maskImg: HTMLImageElement, - flag = false - ) { - const tCanvas = createCanvas(w, h); - const shCtx = canvas.getContext('2d')!; - const tmpCtx = tCanvas.getContext('2d')!; - tmpCtx.drawImage(maskImg, 0, 0, w, h); - const imgData = shCtx.getImageData(0, 0, w, h); - const maskData = tmpCtx.getImageData(0, 0, w, h); - for (let i = 0; i < maskData.data.length; i += 4) { - imgData.data[i + 3] = flag ? maskData.data[i] : 255 - maskData.data[i]; - } - shCtx.putImageData(imgData, 0, 0); - this.roomdataurl = canvas.toDataURL('image/png'); - return maskData; - } - - async uploadMask({maskUrl, roomId}: {maskUrl: string; roomId: string}) { - if (!maskUrl || !roomId) return; - return new Promise((resolve) => { - readImage(maskUrl).then((maskImage512) => { - const tmpCanvas = createCanvas(maskImage512.width, maskImage512.height); - tmpCanvas.getContext('2d')!.drawImage(maskImage512, 0, 0); - canvasToBlobPromise(tmpCanvas).then((mask512Blob) => { - AppProvider.uploadMyRoomMask({maskUrl: mask512Blob, roomId}).then( - () => resolve() + tileCanvas.drawCanvasTiles( + {designPath, zoom: this.zoom, designDetails, hash, drawNormap, tileTransparency: tileTransparency ?? []}, + () => {}, + () => { + this.threeView.updateMap(); + setTimeout(() => resolve(), 500); + } ); }); + + default: + return new Promise((resolve) => { + this.designCanvasMod = createCanvas(tileCanvas.width, tileCanvas.height); + this.normalCanvasMod = createCanvas(tileCanvas.width, tileCanvas.height); + + this.threeView.setCarpetTexture({ + designDetails: this.designDetails!, + designCanvas: this.designCanvasMod, + normapCanvas: this.normalCanvasMod, + }); + this.threeView.setCarpetVisibility(false); + + const {DesignColors} = designDetails; + let drawNormap: boolean | number; + if (!win.flags.applyNormalMapInJPEG && win.InterfaceElements.IsJpeg) { + drawNormap = false; + } else { + drawNormap = + !DesignColors.every( + (color) => color.PileHeight === DesignColors[0].PileHeight && !color.Carving + ) || !!(tileTransparency?.length); + } + + tileCanvas.drawCanvasTiles( + {designPath, zoom: this.zoom, designDetails: desDetails, hash, tileTransparency: tileTransparency ?? [], drawNormap}, + () => {}, + () => { + if ( + (win.flags.visualizations.isBorderRugs || !isDimsOrig) && + win.flags.ordersheet.repeatRugInArea + ) { + const centerPath = + win.initialData.centerDesignUrl && win.initialData.centerDesignUrl !== '' + ? win.initialData.centerDesignUrl + : null; + const borderWidth = win.initialData.borderSize ?? 0; + const unit = win.initialData.unit ?? null; + + this.repeatDesignForOverSize({ + centerPath, + borderWidth, + unit, + onComplete: () => { + this.threeView.setCarpetScale(this.designDetails!); + this.threeView.updateMap(); + this.threeView.setCarpetVisibility(true); + this.updateGizmo(); + resolve(); + }, + }); + } else { + const cropPadding = + maxArea === null ? 100 : 100 / (designDimsOrig.Width / tileCanvas.canvas.width); + + if (!isDimsOrig) { + const effectiveDimsOrig = + maxArea !== null + ? {...designDimsOrig, Width: tileCanvas.canvas.width, Height: tileCanvas.canvas.height} + : designDimsOrig; + const {width, height} = getCroppedSize(effectiveDimsOrig, designDetails, cropPadding); + this.designCanvasMod.width = width * this.zoom; + this.designCanvasMod.height = height * this.zoom; + this.normalCanvasMod.width = width * this.zoom; + this.normalCanvasMod.height = height * this.zoom; + } + cropStitchCanvas({origCanvas: tileCanvas.canvas, canvas: this.designCanvasMod}); + cropStitchCanvas({origCanvas: tileCanvas.canvasNorm, canvas: this.normalCanvasMod}); + this.threeView.updateMap(); + this.threeView.setCarpetVisibility(true); + this.updateGizmo(); + resolve(); + } + } + ); + }); + } + } + + updateDesignWallToWall(): Promise { + return new Promise((resolve) => { + this.threeView.updateCarpetRepeatWallToWall(); + this.threeView.updateMap(); + this.threeView.setCarpetVisibility(true); + this.updateGizmo(); + resolve(); + }); + } + + renderFloor({path}: {path?: string}): Promise { + return new Promise((resolve) => { + if (!this.threeView) { + resolve(); + return; + } + if (!path) { + this.threeView.changeFloorVisibility(false); + resolve(); + return; + } + readImage(path).then((floorImage) => { + if (!this.floorCanvas) this.floorCanvas = createCanvas(floorImage.width, floorImage.height); + else { + this.floorCanvas.width = floorImage.width; + this.floorCanvas.height = floorImage.height; + } + this.floorCanvas.getContext('2d')!.drawImage(floorImage, 0, 0); + this.threeView.setFloorTexture({floorCanvas: this.floorCanvas}); + resolve(); }); }); } - resetCarpetTransform() { - if ( - !this.carpetMesh || - !this.initCarpetPos || - !this.initCarpetRot || - !this.initCarpetScale - ) - return; - this.carpetMesh.position.fromArray(this.initCarpetPos); - this.carpetMesh.rotation.fromArray(convertArrIntoRad(this.initCarpetRot)); - this.carpetMesh.scale.set( - ...(this.initCarpetScale as [number, number, number]) - ); - this.render(); - } - - rotateCarpet(angleinDeg: number) { - if (!this.carpetMesh) return; - this.carpetMesh.rotation.z += (angleinDeg * Math.PI) / 180; - this.render(); - } - - scaleUpCarpet() { - this.scaleCarpet(this.zoomCarpetStep); - } - - scaleDownCarpet() { - this.scaleCarpet(-this.zoomCarpetStep); - } - - scaleCarpet(step: number) { - if (!this.carpetMesh) return; - const newValue = - (this.carpetMesh.scale.x + step * this.carpetRatio.w) / - this.carpetRatio.w; - if (this.maxZoom >= newValue && newValue >= this.minZoom) { - this.carpetMesh.scale.x += step * this.carpetRatio.w; - this.carpetMesh.scale.y += step * this.carpetRatio.h; - this.scaleFactor.x += step; - this.scaleFactor.y += step; - this.render(); - } - } - - adjustPlaneHeight(increaseBool: boolean, step = 5) { - const deltaY = !increaseBool ? +step : -step; - (this.orbit as any).pan(0, deltaY); - this.orbit.update(); - } - - adjustCameraAngle(increaseBool: boolean, step = 2) { - const delta = ((increaseBool ? +step : -step) * Math.PI) / 180; - this.orbit.rotateUp(delta); - this.orbit.update(); - } - - mouseDownTouchStart(e: {x: number; y: number}) { - if (!this.carpetMesh) return; - this.intersectsGizmo = this.findGizmoIntersection(e); - this.prev = {...e}; - if (!this.intersectsGizmo) { - const intersect = this.raycastMouseOnObject(e); - if (!intersect) return; - const objPos = this.carpetMesh.position.clone(); - this.offset.copy(intersect.point).sub(objPos); - this.render(); - } - } - - mouseTouchMove(e: {x: number; y: number}, translateCarpetMode: boolean) { - if (!this.carpetMesh) return; - if (translateCarpetMode) { - if (!this.intersectsGizmo) { - const intersect = this.raycastMouseOnObject(e); - if (!intersect) return; - const objPos = this.carpetMesh.position.clone(); - const sub = intersect.point.sub(this.offset); - sub.y = objPos.y; - this.carpetMesh.position.copy(sub); - this.render(); - this.updateGizmo(); - } else { - const difference = this.prev.x - e.x; - this.carpetMesh.rotation.z -= (difference * Math.PI) / 180; - this.prev = {...e}; - this.render(); + renderWallpaper({path, dims}: {path?: string; dims: Dimensions}): Promise { + return new Promise((resolve) => { + if (!this.threeView) { + resolve(); + return; } - } else { - const difference = this.prev.y - e.y; - this.adjustCameraAngle(false, difference); - this.prev = {...e}; - } - } - - getRendererOffset() { - return { - offsetX: this.renderer.domElement.offsetLeft, - offsetY: this.renderer.domElement.offsetTop, - }; - } - - raycastMouseOnObject(e: {x: number; y: number}) { - if (!this.carpetMesh) return undefined; - const mouse = this.convMouseCord(e); - const raycaster = new THREE.Raycaster(); - raycaster.setFromCamera(mouse, this.camera); - return raycaster.intersectObject(this.carpetMesh)[0]; - } - - convMouseCord(e: {x: number; y: number}) { - const vec = new THREE.Vector2(); - this.renderer.getSize(vec); - return new THREE.Vector2((e.x / vec.x) * 2 - 1, -(e.y / vec.y) * 2 + 1); - } - - getCarpetPositions() { - if (!this.carpetMesh) return; - this.carpetMesh.geometry.computeBoundingBox(); - const box = this.carpetMesh.geometry.boundingBox!; - - const vertex1 = this._vector3Pool.v1.copy(box.min); - const vertex2 = this._vector3Pool.v2.copy(box.max); - const max = - vertex2.x - vertex1.x > vertex2.y - vertex1.y - ? vertex2.x - vertex1.x - : vertex2.y - vertex1.y; - - if (!this._tempPositionMesh) { - this._tempPositionMesh = new THREE.Mesh( - new THREE.PlaneGeometry(max, max), - new THREE.MeshBasicMaterial() - ); - } else { - const currentSize = ( - this._tempPositionMesh.geometry as THREE.PlaneGeometry - ).parameters.width; - if (Math.abs(currentSize - max) > 0.01) { - this._tempPositionMesh.geometry.dispose(); - this._tempPositionMesh.geometry = new THREE.PlaneGeometry(max, max); + if (!path) { + this.threeView.changeWallpaperVisibility(false); + resolve(); + return; } - } - - const invMesh = this._tempPositionMesh; - invMesh.position.copy(this.carpetMesh.position); - invMesh.rotation.copy(this.carpetMesh.rotation); - - if (this.carpetRatio.w < 1.25) { - invMesh.scale.y = this.carpetMesh.scale.x / 0.8; - invMesh.scale.x = this.carpetMesh.scale.x; - } else { - invMesh.scale.x = this.carpetMesh.scale.y * 0.8; - invMesh.scale.y = this.carpetMesh.scale.y; - } - - invMesh.updateMatrix(); - invMesh.updateMatrixWorld(); - - const positionAttr = invMesh.geometry.attributes.position; - const vector = this._vector3Pool.v3; - const size = this._vector2Pool.v1; - this.renderer.getSize(size); - - const temparray: {x: number; y: number}[] = []; - for (let i = 0, l = positionAttr.count; i < l; i++) { - vector.fromBufferAttribute(positionAttr, i); - vector.applyMatrix4(invMesh.matrixWorld); - const canvasVertex = createVector( - vector, - this.camera, - size.width, - size.height - ); - temparray.push({ - x: this.getResizedImageCoordinatesX(canvasVertex.x), - y: this.getResizedImageCoordinatesY(canvasVertex.y), + readImage(path).then((wallpaperImage) => { + if (!this.wallpaperCanvas) + this.wallpaperCanvas = createCanvas(wallpaperImage.width, wallpaperImage.height); + else { + this.wallpaperCanvas.width = wallpaperImage.width; + this.wallpaperCanvas.height = wallpaperImage.height; + } + this.wallpaperCanvas.getContext('2d')!.drawImage(wallpaperImage, 0, 0); + this.threeView.setWallpaperTexture({wallpaperCanvas: this.wallpaperCanvas, dims}); + resolve(); }); - } - this.myRoomPointsOBJ.carpetpoints = temparray; + }); } - updateGizmo(options: {show?: boolean} = {}) { - const {show = true} = options; - if (!this.gizmoCanvas || !this.carpetMesh) return; - const context = this.gizmoCanvas.getContext('2d')!; - const {width, height} = this.gizmoCanvas; + setWallpaperSurfaceSize({surfaceSize}: {surfaceSize: unknown}): Promise { + return new Promise((resolve) => { + if (!surfaceSize) { + resolve(); + return; + } + this.threeView.setWallpaperSurfaceSize({surfaceSize}); + resolve(); + }); + } - if (!show) { - clearCanvas(this.gizmoCanvas, width, height); - return; + updateCarpetPosition(position: {x: number; y: number; z: number}): void { + this.threeView.setCarpetPositon(position); + this.updateGizmo(); + } + + updateCarpetRotation(rotation: number): void { + this.threeView.setCarpetRotation(rotation); + } + + setTileDetails(tileDetails: Record): void { + this.tileDetails = tileDetails; + } + + updateTiles({ + designDetails, + updateDesignTiles, + updateNormapTiles, + hash, + designDimsOrig, + }: { + designDetails: DesignDetails; + updateDesignTiles?: {colorIndex: number}; + updateNormapTiles?: {colorIndex: number}; + hash: string; + designDimsOrig: DesignDetails; + }): Promise { + if (!this.tileDetails) return Promise.resolve(); + + return new Promise((resolve) => { + let colorIndex: number | undefined; + if (updateDesignTiles) colorIndex = updateDesignTiles.colorIndex; + if (updateNormapTiles) colorIndex = updateNormapTiles.colorIndex; + + const isDimsOrig = + designDimsOrig.Width === designDetails.Width && + designDimsOrig.Height === designDetails.Height; + + if (this.designDetails && this.designDetails.DesignColors === designDetails.DesignColors) { + this.designDetails = designDetails; + this.threeView.designDetails = designDetails; + this.threeView.setCarpetScale(designDetails); + + if ( + (win.flags.visualizations.isBorderRugs || !isDimsOrig) && + win.flags.ordersheet.repeatRugInArea + ) { + const centerPath = win.initialData.centerDesignUrl ?? null; + const borderWidth = win.initialData.borderSize ?? 0; + const unit = win.initialData.unit ?? null; + + this.repeatDesignForOverSize({ + centerPath, + borderWidth, + unit, + onComplete: () => { + this.threeView.setCarpetScale(this.designDetails!); + this.threeView.updateMap(); + resolve(); + }, + }); + } else { + if (!isDimsOrig) { + const cropPadding = 100; + const {width, height} = getCroppedSize(designDimsOrig, designDetails, cropPadding); + this.designCanvasMod.width = width; + this.designCanvasMod.height = height; + this.normalCanvasMod.width = width; + this.normalCanvasMod.height = height; + } else { + this.designCanvasMod.width = designDimsOrig.Width; + this.designCanvasMod.height = designDimsOrig.Height; + this.normalCanvasMod.width = designDimsOrig.Width; + this.normalCanvasMod.height = designDimsOrig.Height; + } + cropStitchCanvas({origCanvas: tileCanvas.canvas, canvas: this.designCanvasMod}); + cropStitchCanvas({origCanvas: tileCanvas.canvasNorm, canvas: this.normalCanvasMod}); + this.threeView.updateMap(); + resolve(); + } + return; + } + + this.designDetails = designDetails; + + let colorTileData: unknown = null; + if (colorIndex !== undefined && colorIndex !== -1 && !win.flags.isFelt) { + const tileData = (this.tileDetails as Record)[`colorTileData${this.zoom}`]; + colorTileData = tileData[colorIndex].tiles; + } + + const tileTransparency = (this.tileDetails as Record)[`tileTransparency${this.zoom}`]; + + const props: TileUpdateOptions = { + tiles: colorTileData, + zoom: this.zoom, + designDetails, + designPath: this.designPath, + hash, + tileTransparency, + }; + + if (!updateNormapTiles) { + tileCanvas.designTilesUpdated = true; + tileCanvas.updateDesignTiles( + props, + () => { + this.threeView.updateMap(); + }, + () => { + if (this.designCanvasMod) + this.designCanvasMod.getContext('2d')!.drawImage(tileCanvas.canvas, 0, 0); + this.threeView.updateMap(); + setTimeout(() => resolve(), 500); + } + ); + } else { + tileCanvas.normapTilesUpdated = true; + tileCanvas.updateNormapTiles( + props, + () => { + this.threeView.updateMap(); + }, + () => { + if (this.normalCanvasMod) + this.normalCanvasMod.getContext('2d')!.drawImage(tileCanvas.canvasNorm, 0, 0); + this.threeView.updateMap(); + setTimeout(() => resolve(), 500); + } + ); + } + }); + } + + repeatDesignForOverSize({ + centerPath = null, + borderWidth = 0, + unit, + onComplete, + }: { + centerPath: string | null; + borderWidth: number; + unit: string | null; + onComplete: () => void; + }): void { + const {Width: width, Height: height} = this.designDetails!; + this.designCanvasMod.width = width; + this.designCanvasMod.height = height; + this.normalCanvasMod.width = width; + this.normalCanvasMod.height = height; + + let centerDims: Dimensions | undefined; + let centerCanvas: HTMLCanvasElement | undefined; + + if (centerPath) { + let bw = borderWidth; + if (unit && this.designDetails!.Unit !== unit) { + bw = convertUnit(unit, this.designDetails!.Unit, bw); + } + const borderDesignWid = parseFloat(win.initialData.borderDesignWid ?? '0'); + const borderDesignHgt = parseFloat(win.initialData.borderDesignHgt ?? '0'); + + centerDims = { + width: ((borderDesignWid - bw * 2) * width) / borderDesignWid, + height: ((borderDesignHgt - bw * 2) * height) / borderDesignHgt, + }; + centerCanvas = createCanvas(centerDims.width, centerDims.height); } - const carpetSize = this.calculateCarpetSize(); - const smallerDim = - carpetSize.x > carpetSize.y ? carpetSize.x : carpetSize.y; - const carpetRadius = smallerDim / 5; - const carpetCenter = this.carpetMesh.position.clone(); + const img = new Image(); + const img1 = new Image(); + const imgCenter = new Image(); + let imgloaded = false; + let img1loaded = false; + let imgCenterloaded = !centerPath; - const dist1 = this.distbetween2Vertices( - carpetCenter.clone(), - new THREE.Vector3( - carpetCenter.x, - carpetCenter.y, - carpetCenter.z + carpetRadius - ) - ); - const dist2 = this.distbetween2Vertices( - new THREE.Vector3( - carpetCenter.x + carpetRadius, - carpetCenter.y, - carpetCenter.z - ), - carpetCenter.clone() - ); + const repeatImg = (canvas: HTMLCanvasElement, imgEl: HTMLImageElement, w: number, h: number): void => { + const ctx = canvas.getContext('2d')!; + const pat = ctx.createPattern(imgEl, 'repeat')!; + const halfRepeatX = (w / imgEl.width) / 2; + const offsetX = 0.5 - (halfRepeatX - Math.floor(halfRepeatX)); + const halfRepeatY = (h / imgEl.height) / 2; + const offsetY = 0.5 - (halfRepeatY - Math.floor(halfRepeatY)); + let offsetXActual = Math.abs(offsetX * imgEl.width); + if (offsetX < 0) offsetXActual = imgEl.width - offsetXActual; + let offsetYActual = Math.abs(offsetY * imgEl.height); + if (offsetY < 0) offsetYActual = imgEl.height - offsetYActual; - const area1 = areaOfellipse(dist1.yDist, dist2.xDist); - const area2 = areaOfellipse(dist2.yDist, dist1.xDist); - const radX = area1 > area2 ? dist2.xDist : dist2.yDist; - const radY = area1 > area2 ? dist1.yDist : dist1.xDist; + ctx.save(); + ctx.translate(-offsetXActual, -offsetYActual); + ctx.rect(0, 0, w + offsetXActual, h + offsetYActual); + ctx.fillStyle = pat; + ctx.fill(); + ctx.restore(); + }; + const finish = (): void => { + if (imgloaded && img1loaded && imgCenterloaded) { + if (centerPath && centerCanvas) { + const startx = (this.designCanvasMod.width - centerCanvas.width) / 2; + const starty = (this.designCanvasMod.height - centerCanvas.height) / 2; + this.designCanvasMod + .getContext('2d')! + .drawImage(centerCanvas, startx, starty, centerCanvas.width, centerCanvas.height); + } + onComplete(); + } + }; + + img.onload = () => { + repeatImg(this.designCanvasMod, img, width, height); + imgloaded = true; + finish(); + }; + img1.onload = () => { + repeatImg(this.normalCanvasMod, img1, width, height); + img1loaded = true; + finish(); + }; + imgCenter.onload = () => { + repeatImg(centerCanvas!, imgCenter, centerDims!.width, centerDims!.height); + imgCenterloaded = true; + finish(); + }; + + img.src = tileCanvas.canvas.toDataURL(); + img1.src = tileCanvas.canvasNorm.toDataURL(); + if (centerPath) { + imgCenter.src = centerPath; + imgCenter.crossOrigin = 'Anonymous'; + } + } + + updateMask(options: {clear?: boolean} = {}): string | undefined { + const {clear = false} = options; + const {width, height} = this.dimension; + setCanvasDimensions(this.maskCanvas, this.dimension, this.dimensionPixels); + clearCanvas(this.maskCanvas, this.maskCanvas.width, this.maskCanvas.height); + if (clear) return 'clear'; + if (!this.maskImage) return; + + const tmpCanvas = createCanvas(width, height); + tmpCanvas.getContext('2d')!.drawImage(this.bgCanvas, 0, 0, width, height); + if (!this.config.hasTransparencyMask) { + makeMask(tmpCanvas, width, height, this.maskImage, true); + } else { + const ctx = tmpCanvas.getContext('2d')!; + ctx.globalCompositeOperation = 'destination-in'; + ctx.drawImage(this.maskImage, 0, 0, width, height); + } + this.maskCanvas.getContext('2d')!.drawImage(tmpCanvas, 0, 0, width, height); + } + + async updateShadow(options: {clear?: boolean} = {}): Promise { + const {clear = false} = options; + clearCanvas(this.shadowCanvas, this.shadowCanvas.width, this.shadowCanvas.height); + if (clear) return 'clear'; + const {width, height} = this.dimension; + const tempCanvas = createCanvas(width, height); + const tCtx = tempCanvas.getContext('2d')!; + setCanvasDimensions(this.shadowCanvas, this.dimension, this.dimensionPixels); + if (!this.threeView.renderer) return; + this.threeView.render(); + tCtx.drawImage(this.threeCanvas, 0, 0, width, height); + tCtx.drawImage(this.maskCanvas, 0, 0, width, height); + + if (this.shadowImage) { + tCtx.globalCompositeOperation = 'multiply'; + tCtx.drawImage(this.shadowImage, 0, 0, width, height); + } + if (this.highlightImage) { + tCtx.globalCompositeOperation = 'screen'; + tCtx.drawImage(this.highlightImage, 0, 0, width, height); + } + if (this.glowImage) { + tCtx.globalCompositeOperation = 'overlay'; + tCtx.drawImage(this.glowImage, 0, 0, width, height); + } + tCtx.globalCompositeOperation = 'destination-in'; + tCtx.drawImage(this.threeCanvas, 0, 0, width, height); + this.shadowCanvas.getContext('2d')!.drawImage(tempCanvas, 0, 0, width, height); + + if (win.flags.designView.hasDesignWaterMark) { + const logoUrl = `${AppProvider.domain}${win.InterfaceElements.LogoUrl}`; + const img = await readImage(logoUrl); + const imgwidth = 100; + const imgheight = (img.height * imgwidth) / img.width; + const startx = width - (imgwidth + 15); + const starty = height - (imgheight + 15); + const context = this.shadowCanvas.getContext('2d')!; + context.globalAlpha = 0.5; + context.drawImage(img, startx, starty, imgwidth, imgheight); + } + } + + makeTransitionCanvas(options: {clear?: boolean} = {}): string { + const {clear = false} = options; + clearCanvas(this.transitionCanvas, this.transitionCanvas.width, this.transitionCanvas.height); + if (clear) return 'clear'; + setCanvasDimensions(this.transitionCanvas, this.dimension, this.dimensionPixels); + const ctx = this.transitionCanvas.getContext('2d')!; + ctx.drawImage(this.bgCanvas, 0, 0, this.dimension.width, this.dimension.height); + ctx.drawImage(this.threeCanvas, 0, 0, this.dimension.width, this.dimension.height); + ctx.drawImage(this.maskCanvas, 0, 0, this.dimension.width, this.dimension.height); + ctx.drawImage(this.shadowCanvas, 0, 0, this.dimension.width, this.dimension.height); + return 'done'; + } + + handleCanvasClick(_e: MouseEvent): void { + return; + } + + mouseDownTouchStart(e: PointerEvent): void { + this.updateGizmo(); + this.intersectsGizmo = this.findGizmoIntersection(e); + this.moved = false; + this.prev = {x: e.x, y: e.y}; + if (!this.intersectsGizmo) { + const intersectsCarpet = this.threeView.mouseDownTouchStart(e); + this.shadowCleared = false; + if (intersectsCarpet) { + this.updateShadow({clear: true}); + this.shadowCleared = true; + } + } else { + this.prev = {x: e.x, y: e.y}; + this.updateShadow({clear: true}); + this.shadowCleared = true; + } + } + + mouseDownTouchMove(e: PointerEvent): void { + const difference = e.x - this.prev.x; + this.moved = difference > 10; + if (!this.intersectsGizmo) { + this.threeView.mouseTouchMove(e); + this.updateGizmo(); + } else { + this.threeView.rotateCarpet(difference, 'z'); + this.prev = {x: e.x, y: e.y}; + } + } + + mouseDownTouchEnd(e: PointerEvent): { + showColorSelectionBox: Uint8ClampedArray | boolean | null; + rotation: number[] | null; + position: {x: number; y: number; z: number} | null; + texCoordinates: {x: number; y: number} | undefined; + isActiveValue: number | null; + } { + if (this.shadowCleared) this.updateShadow(); + let showColorSelectionBox: Uint8ClampedArray | boolean | null = null; + let isActiveValue: number | null = null; + + if (!this.moved && this.annotationCanvas) { + const imgData = this.annotationCanvas.getContext('2d')!.getImageData(e.x, e.y, 1, 1); + if (imgData.data[3] !== 0) { + isActiveValue = imgData.data[3]; + showColorSelectionBox = this.selectedColorCode = imgData.data.slice(0, 3); + if (imgData.data[0] === 45 && imgData.data[1] === 24 && imgData.data[2] === 18) { + showColorSelectionBox = false; + } + } + } + + let rotation: number[] | null = null; + let position: {x: number; y: number; z: number} | null = null; + const object = this.threeView.getObjectConfig(); + let texCoordinates: {x: number; y: number} | undefined; + + const intersect = this.threeView.raycastMouseOnSurface(e); + if (intersect && this.designDetails) { + texCoordinates = { + x: this.designDetails.Width * intersect.uv.x, + y: this.designDetails.Height * (1 - intersect.uv.y), + }; + } + if (object) { + if (this.intersectsGizmo) { + rotation = convertArrintoDeg(object.rotation.toArray().slice(0, 3)); + } else { + position = object.position; + } + } + + return {showColorSelectionBox, rotation, position, texCoordinates, isActiveValue}; + } + + findGizmoIntersection(e: PointerEvent): boolean { + const {x, y} = e; + if (!this.inputCanvas) return false; + const imgData = this.inputCanvas.getContext('2d')!.getImageData(x - 10, y - 10, 20, 20); + for (let i = 0; i < imgData.data.length; i += 4) { + if (imgData.data[i + 3] !== 0) return true; + } + return false; + } + + clearGizmo(): void {} + + updateGizmo(): void { + const {roomType} = this.config; + if (roomType === 'illustration' || !win.flags.visualizations.showGizmoInRoomView) return; + const diamondHeight = 10; + const context = this.inputCanvas.getContext('2d')!; + const {width, height} = this.inputCanvas; + clearCanvas(this.inputCanvas, this.inputCanvas.width, this.inputCanvas.height); + const gizmoCoordinates = this.threeView.getGizmoCordinates(); + if (!gizmoCoordinates) return; + const {radX, radY, canvasCenter} = gizmoCoordinates; + const colorStr = 'rgba(250, 250, 250, 0.8)'; const radiusX = radX > radY ? radX : radY; const radiusY = radX > radY ? radY : radX; - const diamondHeight = 10; - const canvasCenter = createVector(carpetCenter, this.camera, width, height); - const colorStr = 'rgba(250, 250, 250, 0.8)'; context.strokeStyle = colorStr; context.fillStyle = colorStr; @@ -1127,15 +1268,7 @@ export class RoomViewHelper { context.shadowOffsetY = 1; context.clearRect(0, 0, width, height); context.beginPath(); - context.ellipse( - canvasCenter.x, - canvasCenter.y, - radiusX, - radiusY, - 0, - 0, - 2 * Math.PI - ); + context.ellipse(canvasCenter.x, canvasCenter.y, radiusX, radiusY, 0, 0, 2 * Math.PI); context.stroke(); context.beginPath(); context.moveTo(canvasCenter.x, canvasCenter.y + radiusY - 5); @@ -1145,636 +1278,293 @@ export class RoomViewHelper { context.fill(); } - findGizmoIntersection(e: {x: number; y: number}): boolean { - if (!this.gizmoCanvas) return false; - const imgData = this.gizmoCanvas - .getContext('2d')! - .getImageData(e.x - 10, e.y - 10, 20, 20); - for (let i = 0; i < imgData.data.length; i += 4) { - if (imgData.data[i + 3] !== 0) return true; - } - return false; - } - - autoLevel(bgCanvas: HTMLCanvasElement, maskCanvas: HTMLCanvasElement) { - this.origBG = bgCanvas.toDataURL(); - const size = new THREE.Vector2(); - this.renderer.getSize(size); - const {x: width, y: height} = size; - - const canvas = createCanvas(width, height); - const ctx = canvas.getContext('2d')!; - ctx.drawImage(bgCanvas, 0, 0, width, height); - const imgData = ctx.getImageData(0, 0, width, height); - const pixelNum = imgData.data.length; - - let redMax = 0, - redMin = 255, - greenMax = 0, - greenMin = 255, - blueMax = 0, - blueMin = 255; - - for (let i = 0; i < pixelNum; i += 4) { - if (imgData.data[i] > redMax) redMax = imgData.data[i]; - if (imgData.data[i] < redMin) redMin = imgData.data[i]; - if (imgData.data[i + 1] > greenMax) greenMax = imgData.data[i + 1]; - if (imgData.data[i + 1] < greenMin) greenMin = imgData.data[i + 1]; - if (imgData.data[i + 2] > blueMax) blueMax = imgData.data[i + 2]; - if (imgData.data[i + 2] < blueMin) blueMin = imgData.data[i + 2]; - } - - for (let j = 0; j < pixelNum; j += 4) { - imgData.data[j] = (imgData.data[j] - redMin) * (255 / (redMax - redMin)); - imgData.data[j + 1] = - (imgData.data[j + 1] - greenMin) * (255 / (greenMax - greenMin)); - imgData.data[j + 2] = - (imgData.data[j + 2] - blueMin) * (255 / (blueMax - blueMin)); - } - ctx.putImageData(imgData, 0, 0); - bgCanvas.getContext('2d')!.drawImage(canvas, 0, 0); - const imgDataCopy = imgData.valueOf() as ImageData; - - const maskCanvas1 = createCanvas(width, height); - const maskContext = maskCanvas1.getContext('2d')!; - readImage(this.roomdataurl).then((image) => { - maskContext.drawImage(image, 0, 0, width, height); - const maskData = maskContext.getImageData(0, 0, width, height); - for (let k = 0; k < maskData.data.length; k += 4) { - imgDataCopy.data[k + 3] = maskData.data[k + 3]; - } - maskContext.putImageData(imgDataCopy, 0, 0); - maskCanvas.width = width; - maskCanvas.height = height; - maskCanvas.getContext('2d')!.drawImage(maskCanvas1, 0, 0, width, height); - }); - } - - undoAutoLevel(bgCanvas: HTMLCanvasElement, maskCanvas: HTMLCanvasElement) { - readImage(this.origBG).then((origImage) => { - bgCanvas.getContext('2d')!.drawImage(origImage, 0, 0); - }); - - const size = new THREE.Vector2(); - this.renderer.getSize(size); - const {x: width, y: height} = size; - const maskContext = maskCanvas.getContext('2d')!; - readImage(this.roomdataurl).then((image) => { - maskCanvas.width = width; - maskCanvas.height = height; - clearCanvas(maskCanvas, width, height); - maskContext.drawImage(image, 0, 0, width, height); - }); - } - - markFF( - x: number, - y: number, - inputCanvas: HTMLCanvasElement, - inputSelected: string - ) { - if (inputSelected === 'eraser') { - this.removePoint(inputCanvas, x, y); - } else { - const markingColor = inputSelected === 'floor' ? '#FF0000' : '#00FF00'; - this.markWithColor(inputCanvas, markingColor, x, y); - inputSelected === 'floor' - ? this.pushFloorPoints(x, y) - : this.pushFurniPoints(x, y); - } - } - - markWithColor( - inputCanvas: HTMLCanvasElement, - MarkingColor: string, - x: number, - y: number - ) { - const ctx = inputCanvas.getContext('2d')!; - ctx.moveTo(x, y); - ctx.fillStyle = MarkingColor; - ctx.beginPath(); - ctx.arc(x, y, 2, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.closePath(); - } - - isLastPoint(pointsList: {x: number; y: number}[], ptX: number, ptY: number) { - if (!pointsList.length) return false; - const lastPt = pointsList[pointsList.length - 1]; - return lastPt.x === ptX && lastPt.y === ptY; - } - - pushFloorPoints(x: number, y: number) { - x = this.getResizedImageCoordinatesX(x); - y = this.getResizedImageCoordinatesY(y); - if (!this.isLastPoint(this.myRoomPointsOBJ.floorpoints, x, y)) { - this.myRoomPointsOBJ.floorpoints.push({x, y}); - } - } - - pushFurniPoints(x: number, y: number) { - x = this.getResizedImageCoordinatesX(x); - y = this.getResizedImageCoordinatesY(y); - if (!this.isLastPoint(this.myRoomPointsOBJ.notfloorpoints, x, y)) { - this.myRoomPointsOBJ.notfloorpoints.push({x, y}); - } - } - - removePoint(inputCanvas: HTMLCanvasElement, x: number, y: number) { - x = this.getResizedImageCoordinatesX(x); - y = this.getResizedImageCoordinatesY(y); - const isFarEnough = (point: {x: number; y: number}) => { - const a = x - point.x; - const b = y - point.y; - return Math.sqrt(a * a + b * b) >= 30; + resize(): void { + if (!this.config.dims || !this.threeView.renderer) return; + const containerDims = { + width: this.container.clientWidth, + height: this.container.clientHeight, }; - this.myRoomPointsOBJ.notfloorpoints = - this.myRoomPointsOBJ.notfloorpoints.filter(isFarEnough); - this.myRoomPointsOBJ.floorpoints = - this.myRoomPointsOBJ.floorpoints.filter(isFarEnough); - this.drawFFPoints(inputCanvas); - } - - drawFFPoints(inputCanvas: HTMLCanvasElement) { - inputCanvas - .getContext('2d')! - .clearRect(0, 0, inputCanvas.width, inputCanvas.height); - this.colorPoints(inputCanvas, this.myRoomPointsOBJ.floorpoints, '#FF0000'); - this.colorPoints( - inputCanvas, - this.myRoomPointsOBJ.notfloorpoints, - '#00FF00' - ); - } - - colorPoints( - inputCanvas: HTMLCanvasElement, - points: {x: number; y: number}[], - markingColor: string - ) { - for (const point of points) { - this.markWithColor( - inputCanvas, - markingColor, - this.getInputCanvasCoordinatesX(point.x), - this.getInputCanvasCoordinatesY(point.y) - ); + if (sessionStorage.getItem('entryview') === entryViews.STUDIO) { + this.dimensionPixels = resizeKeepingAspect(this.config.dims, containerDims, 'crop'); + } else { + this.dimensionPixels = resizeKeepingAspect(this.config.dims, containerDims, 'fit_inside'); } - } - - updatePointHistory() { - this.pointsHistory.floorpoints = this.myRoomPointsOBJ.floorpoints; - this.pointsHistory.notfloorpoints = this.myRoomPointsOBJ.notfloorpoints; - this.pointsHistory.inputSelected = this.myroomInputSelected; - this.pointsHistoryArray.push(JSON.stringify(this.pointsHistory)); - } - - pointsHistoryReset() { - this.myRoomPointsOBJ.floorpoints = []; - this.myRoomPointsOBJ.notfloorpoints = []; - this.pointsHistoryArray = []; - } - - undoPoints(inputCanvas: HTMLCanvasElement) { - if (this.pointsHistoryArray.length === 0) return; - this.pointsHistoryArray.pop(); - if (this.pointsHistoryArray.length === 0) { - this.myRoomPointsOBJ.floorpoints = []; - this.myRoomPointsOBJ.notfloorpoints = []; - this.drawFFPoints(inputCanvas); - return; + setCanvasDimensionsStyle(this.bgCanvas, this.dimensionPixels); + this.threeView.resizeRenderer(this.dimensionPixels); + setCanvasDimensionsStyle(this.maskCanvas, this.dimensionPixels); + setCanvasDimensionsStyle(this.shadowCanvas, this.dimensionPixels); + this.inputCanvas.width = this.dimensionPixels.width; + this.inputCanvas.height = this.dimensionPixels.height; + if (this.floatingOptionsContainer) { + this.floatingOptionsContainer.style.width = `${this.dimensionPixels.width}px`; + this.floatingOptionsContainer.style.height = `${this.dimensionPixels.height}px`; + } + if (this.bgVideo) { + this.bgVideo.style.width = `${this.dimensionPixels.width}px`; + this.bgVideo.style.height = `${this.dimensionPixels.height}px`; } - this.pointsHistory = JSON.parse( - this.pointsHistoryArray[this.pointsHistoryArray.length - 1] - ); - this.myRoomPointsOBJ.floorpoints = this.pointsHistory.floorpoints; - this.myRoomPointsOBJ.notfloorpoints = this.pointsHistory.notfloorpoints; - this.drawFFPoints(inputCanvas); - } - - resetPoints(inputCanvas: HTMLCanvasElement) { - this.pointsHistoryReset(); - this.drawFFPoints(inputCanvas); - clearCanvas(inputCanvas, inputCanvas.width, inputCanvas.height); - } - - getResizedImageCoordinatesX(x: number) { - return Math.round(x * this.ratioWid); - } - - getResizedImageCoordinatesY(y: number) { - return Math.round(y * this.ratioHgt); - } - - getInputCanvasCoordinatesX(x: number) { - return Math.round(x / this.ratioWid); - } - - getInputCanvasCoordinatesY(y: number) { - return Math.round(y / this.ratioHgt); - } - - resize(windowSize: {width: number; height: number}) { - if (!this.canvasInitiated) return; - const {width, height} = resizeKeepingAspect(this.bgImage, windowSize); - - this.bgCanvas.style.width = `${width}px`; - this.bgCanvas.style.height = `${height}px`; - this.maskCanvas.style.width = `${width}px`; - this.maskCanvas.style.height = `${height}px`; - - this.inputCanvas.width = width; - this.inputCanvas.height = height; - this.gizmoCanvas.width = width; - this.gizmoCanvas.height = height; - this.resizeRenderer(width, height); this.updateGizmo(); } - resizeRenderer(width: number, height: number) { - if (this.camera) { - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - } - this.renderer.setSize(width, height); - this.ratioWid = this.origWid / width; - this.ratioHgt = this.origHgt / height; - this.render(); + private async getWaterMarkLogoForRugStudio(): Promise { + return readImage('https://myroom.studio/images/site/svg/utils/logo.svg'); } - calculateCarpetSize() { - if (!this.carpetMesh) return new THREE.Vector3(); - this.carpetMesh.geometry.computeBoundingBox(); - const carpetSize = new THREE.Vector3(); - this.carpetMesh.geometry.boundingBox!.getSize(carpetSize); - return carpetSize; + private async getQartForRugStudio(): Promise { + return readImage('https://lab.explorug.com/WebComponent/myroomstudio.png'); } - calculateCameraWidth() { - const carpetSize = this.calculateCarpetSize(); - const siz = new THREE.Vector2(); - this.renderer.getSize(siz); - const carpetWid = carpetSize.x > carpetSize.y ? carpetSize.x : carpetSize.y; - return siz.x >= siz.y ? carpetWid * 1.1 : carpetWid * 1.2; - } + async downloadPdfOrImageForRugStudioCustomDesign( + roomName: string, + description: string, + qrurl: string, + room = 'free', + type = 'image', + isWatermark = true, + paypalName = '', + paypalEmail = '', + purchasedDate = '', + price = '' + ): Promise { + const w = 3840; + const h = 2160; + const renderCanvas = createCanvas(w, h); + const renderCtx = renderCanvas.getContext('2d')!; + const context = this.shadowCanvas.getContext('2d')!; + context.globalAlpha = 1; - distbetween2Vertices(vertex1: THREE.Vector3, vertex2: THREE.Vector3) { - const vec = new THREE.Vector2(); - this.renderer.getSize(vec); - const v1 = createVector(vertex1, this.camera, vec.x, vec.y); - const v2 = createVector(vertex2, this.camera, vec.x, vec.y); - return { - xDist: Math.abs(Math.abs(v2.x) - Math.abs(v1.x)), - yDist: Math.abs(Math.abs(v2.y) - Math.abs(v1.y)), - }; - } + const [watermarkImage, qrImage] = await Promise.all([ + this.getWaterMarkLogoForRugStudio(), + this.getQartForRugStudio(), + ]); - calculateVerticalHeight() { - const {x, y} = this.calculateCarpetSize(); - const h1 = this.distbetween2Vertices( - new THREE.Vector3(-x / 2, 0, 0), - new THREE.Vector3(x / 2, 0, 0) - ); - const h2 = this.distbetween2Vertices( - new THREE.Vector3(0, 0, y / 2), - new THREE.Vector3(0, 0, -y / 2) - ); - return h1.yDist > h2.yDist ? h1.yDist : h2.yDist; - } + let startx = 0, starty = 0, imgwidth = 0, imgheight = 0; + let qrStartx = 0, qrStarty = 0, qrImgwidth = 0, qrImgheight = 0; - angleBetween2Vertices(vertex1: THREE.Vector3, vertex2: THREE.Vector3) { - const size = new THREE.Vector2(); - this.renderer.getSize(size); - const v1 = createVector(vertex1, this.camera, size.width, size.height); - const v2 = createVector(vertex2, this.camera, size.width, size.height); - return (Math.atan((v2.y - v1.y) / (v2.x - v1.x)) * 180) / Math.PI; - } + if (isWatermark) { + const canvasWidth = window.screen.width * window.devicePixelRatio; + const canvasHeight = window.screen.height * window.devicePixelRatio; + imgwidth = canvasWidth * 0.2; + imgheight = (watermarkImage.height * imgwidth) / watermarkImage.width; + startx = (canvasWidth - imgwidth) / 2; + starty = (canvasHeight - imgheight) / 2; + context.beginPath(); + context.fillStyle = 'rgba(255, 255, 255, 0.1)'; + context.fill(); + context.drawImage(watermarkImage, startx, starty, imgwidth, imgheight); - changeCameraDist(width: number, fovDeg: number) { - const fovrad = (fovDeg * Math.PI) / 180; - const distance = width / (2 * Math.tan(fovrad / 2)); - const {camera, orbit} = this; - const cur_dist = Math.sqrt( - camera.position.x * camera.position.x + - camera.position.y * camera.position.y + - camera.position.z * camera.position.z - ); - const dist_ratio = distance / cur_dist; - orbit.object.position.set( - dist_ratio * camera.position.x, - dist_ratio * camera.position.y, - dist_ratio * camera.position.z - ); - orbit.update(); - this.render(); - } - - calculateAngle1() { - return this.angleBetween2Vertices( - new THREE.Vector3(0, 0, -500), - new THREE.Vector3(0, 0, 500) - ); - } - - calculateAngle2() { - return ( - 180 - - Math.abs( - this.angleBetween2Vertices( - new THREE.Vector3(400, 0, 0), - new THREE.Vector3(-400, 0, 0) - ) - ) - ); - } - - resetOrbit(x = 0, y = 0) { - this.orbit.reset(); - this.changeCameraDist(this.calculateCameraWidth(), this.camera.fov); - (this.orbit as any).pan(x, y); - this.orbit.update(); - } - - setinitialOrientation(res: AnalysisData) { - this.roomAnalysisData = res; - - if (this.carpetMesh && this.fbxLoaded) { - const newRotation = this.calculateCarpetRotation(res); - this.carpetMesh.rotation.fromArray(convertArrIntoRad(newRotation)); - this.initCarpetRot = newRotation; - const newPosition = this.calculateCarpetPosition(res); - this.carpetMesh.position.fromArray(newPosition); - this.initCarpetPos = newPosition; - this.render(); + qrImgwidth = canvasWidth * 0.08; + qrImgheight = qrImgwidth; + qrStartx = (canvasWidth - qrImgwidth) / 2; + qrStarty = canvasHeight - qrImgheight - 20; + context.drawImage(qrImage, qrStartx, qrStarty, qrImgwidth, qrImgheight); } - this.orbit.reset(); - this.camera.position.set(0, 0, 720); - this.orbit.update(); + renderCtx.drawImage(this.bgCanvas, 0, 0, w, h); + renderCtx.drawImage(this.threeCanvas, 0, 0, w, h); + renderCtx.drawImage(this.maskCanvas, 0, 0, w, h); + renderCtx.drawImage(this.shadowCanvas, 0, 0, w, h); - let fov = parseFloat(res.fov); - if (fov < 30) fov = 30; - if (fov > 60) fov = 60; + if (type === 'pdf') { + canvasToBlobPromise(renderCanvas).then((blob) => { + AppProvider.fetchPdfForRugStudio({ + roomPath: blob, + description, + qrurl, + roomName, + room, + isWatermark, + paypalName, + paypalEmail, + purchasedDate, + price, + }).then((url) => { + const link = document.createElement('a'); + link.href = myroomStudioSaveUrl + 'MyRoomSave.aspx?mode=downloadFile&file=' + url.pdfPath; + link.download = roomName + '.pdf'; + link.dispatchEvent(new MouseEvent('click')); - this.camera.fov = fov; - this.camera.updateProjectionMatrix(); - this.render(); - this.changeCameraDist(this.calculateCameraWidth(), fov); + if (!isWatermark) { + context.clearRect(startx - 20, starty - 20, imgwidth + 40, imgheight + 40); + context.clearRect(qrStartx, qrStarty, qrImgwidth, qrImgheight); + context.clearRect(qrStartx - 10, qrStarty - 10, qrImgwidth + 20, qrImgheight + 20); + clearCanvas(this.shadowCanvas, this.shadowCanvas.width, this.shadowCanvas.height); - let horAngleForCamera = parseFloat(res.orientation.hor); - const horAngleSnapTolerance = 10; - if (Math.abs(horAngleForCamera) < horAngleSnapTolerance) - horAngleForCamera = 0; - else if (Math.abs(horAngleForCamera - 90) < horAngleSnapTolerance) - horAngleForCamera = 90; - else if (Math.abs(horAngleForCamera + 90) < horAngleSnapTolerance) - horAngleForCamera = -90; - - (this.orbit as any).setAzimuthalAngle( - -((90 - horAngleForCamera) * Math.PI) / 180 - ); - this.orbit.update(); - (this.orbit as any).setPolarAngle((10 * Math.PI) / 180); - this.orbit.update(); - (this.orbit as any).pan( - parseFloat(res.floor_center.x), - parseFloat(res.floor_center.y) - ); - this.orbit.update(); - - const angle1pred = Math.abs(parseFloat(res.orientation.hor)); - const angle2pred = Math.abs(parseFloat(res.orientation.vert)); - const global_iter_limit = 100; - const iter_limit = 50; - let index = 0; - - const calcError = (predicted: number, actual: number) => - (Math.abs(Math.abs(predicted) - actual) / actual) * 100; - - if (res.orientation.exception) { - if (!res.orientation.flag) { - this.orbit.rotateLeft(Math.PI / 2); - this.orbit.update(); - } - } else { - let vertical_error = calcError(this.calculateAngle2(), angle2pred); - let horizontal_error = calcError(this.calculateAngle1(), angle1pred); - let hor_angle_per_step = 1; - let vert_angle_per_step = 1; - const decay_rate = 0.5; - - const optimizeVertically = (step: number) => { - let angle2 = Math.abs(this.calculateAngle2()); - const vert_angle = angle2 < 90 ? -step : step; - let i1 = 0, - i2 = 0; - while (angle2 > angle2pred) { - this.orbit.rotateUp(THREE.MathUtils.degToRad(vert_angle)); - this.orbit.update(); - angle2 = Math.abs(this.calculateAngle2()); - if (++i1 === iter_limit) break; - } - while (angle2 < angle2pred) { - this.orbit.rotateUp(THREE.MathUtils.degToRad(-vert_angle)); - this.orbit.update(); - angle2 = Math.abs(this.calculateAngle2()); - if (++i2 === iter_limit) break; - } - }; - - const optimizeHorizontally = (step: number) => { - let angle1 = Math.abs(this.calculateAngle1()); - let i1 = 0, - i2 = 0; - while (angle1 < angle1pred) { - this.orbit.rotateLeft(THREE.MathUtils.degToRad(step)); - this.orbit.update(); - angle1 = Math.abs(this.calculateAngle1()); - if (++i1 === iter_limit) break; - } - while (angle1 > angle1pred) { - this.orbit.rotateLeft(THREE.MathUtils.degToRad(-step)); - this.orbit.update(); - angle1 = Math.abs(this.calculateAngle1()); - if (++i2 === iter_limit) break; - } - }; - - while (vertical_error > 0.1 || horizontal_error > 2) { - optimizeHorizontally(hor_angle_per_step); - optimizeVertically(vert_angle_per_step); - vertical_error = calcError(this.calculateAngle2(), angle2pred); - horizontal_error = calcError(this.calculateAngle1(), angle1pred); - hor_angle_per_step *= decay_rate; - vert_angle_per_step *= decay_rate; - if (++index === global_iter_limit) break; - } - - this.orbit.update(); - if (!res.orientation.flag) { - this.orbit.rotateLeft(Math.PI / 2); - this.orbit.update(); - } - - const size = new THREE.Vector2(); - this.renderer.getSize(size); - let verticalHeight = this.calculateVerticalHeight(); - while (verticalHeight < 0.15 * size.height) { - this.orbit.rotateUp((0.5 * Math.PI) / 180); - this.orbit.update(); - verticalHeight = this.calculateVerticalHeight(); - } - this.orbit.update(); - } - - this.render(); - this.changeCameraDist(this.calculateCameraWidth(), fov); - this.render(); - } - - removemeshes() { - if (!this.carpetMesh) return; - (this.carpetMesh.material as THREE.MeshBasicMaterial).opacity = 1; - this.render(); - } - - saveAsRoom({ - mode, - roomId, - file, - props, - }: { - mode: string; - roomId: string; - file: unknown; - props: unknown; - }) { - this.getCarpetPositions(); - const {carpetpoints, floorpoints, notfloorpoints} = this.myRoomPointsOBJ; - return AppProvider.saveAsRoom({ - mode, - roomId, - file, - props, - floorpoints, - notfloorpoints, - carpetpoints, - }).then((response) => ({response})); - } - - downloadImage(downloadBLob: Blob, filename: string) { - return new Promise((resolve) => { - const url = window.URL || (window as any).webkitURL; - const imageSrc = url.createObjectURL(downloadBLob); - const link = document.createElement('a'); - document.body.appendChild(link); - link.setAttribute('download', filename); - link.href = imageSrc; - if ((navigator as any).msSaveOrOpenBlob) { - (navigator as any).msSaveOrOpenBlob(downloadBLob, filename); - } else { - link.click(); - } - document.body.removeChild(link); - resolve(imageSrc); - }); - } - - saveAsImage = async ({ - bgCanvas, - maskCanvas, - designPath, - }: { - bgCanvas: HTMLCanvasElement; - maskCanvas: HTMLCanvasElement; - designPath: string; - }) => { - if (!this.carpetMesh) return; - (this.carpetMesh.material as THREE.MeshBasicMaterial).opacity = 1; - this.removemeshes(); - const imgData = this.renderer.domElement.toDataURL('image/png'); - - readImage(imgData).then((rendererImage) => { - const downloadCanvas = createCanvas( - rendererImage.width, - rendererImage.height - ); - const downloadContext = downloadCanvas.getContext('2d')!; - downloadContext.drawImage( - bgCanvas, - 0, - 0, - rendererImage.width, - rendererImage.height - ); - downloadContext.drawImage( - rendererImage, - 0, - 0, - rendererImage.width, - rendererImage.height - ); - downloadContext.drawImage( - maskCanvas, - 0, - 0, - rendererImage.width, - rendererImage.height - ); - - const win = window as unknown as WindowExtended; - if (win.flags.inhouseChanges?.saveFunctionForFlutter) { - win.getByteData = downloadCanvas.toDataURL('image/jpeg', 0.95); - return; - } - - canvasToBlobPromise(downloadCanvas).then((downloadBLob) => { - let name = 'customdesign'; - if (!win.initialData?.customDesignUrl) { - const filename = designPath.split('/').pop()!; - name = filename.split('.')[0]; - } - this.downloadImage(downloadBLob, name + '-myroom.jpg').then(() => { - if (this.carpetMesh) { - (this.carpetMesh.material as THREE.MeshBasicMaterial).opacity = 1; + setTimeout(() => { + AppProvider.sendEmailMyRoomStudio({ + file: url.pdfPath, + qrurl, + roomName, + isWatermark, + paypalName, + paypalEmail, + purchasedDate, + price, + }); + }, 10000); } - this.render(); }); }); - }); - }; + } else { + const link = document.createElement('a'); + link.href = renderCanvas.toDataURL('image/jpeg'); + link.download = roomName; + link.click(); - dispose() { - if (this.renderer) { - this.renderer.dispose(); - this.renderer.forceContextLoss(); - this.renderer = null!; + context.clearRect(startx - 20, starty - 20, imgwidth + 40, imgheight + 40); + context.clearRect(qrStartx, qrStarty, qrImgwidth, qrImgheight); + context.clearRect(qrStartx - 10, qrStarty - 10, qrImgwidth + 20, qrImgheight + 20); + clearCanvas(this.shadowCanvas, this.shadowCanvas.width, this.shadowCanvas.height); } - if (this.orbit) { - this.orbit.dispose(); - this.orbit = null!; + } + + renderinCanvas({width, height} = this.dimensionPixels): HTMLCanvasElement { + const renderCanvas = createCanvas(width, height); + const renderCtx = renderCanvas.getContext('2d')!; + renderCtx.drawImage(this.bgCanvas, 0, 0, width, height); + renderCtx.drawImage(this.threeCanvas, 0, 0, width, height); + renderCtx.drawImage(this.maskCanvas, 0, 0, width, height); + renderCtx.drawImage(this.shadowCanvas, 0, 0, width, height); + return renderCanvas; + } + + downloadRendering(name: string, mime: string): void { + const renderCanvas = this.renderinCanvas(); + downloadImageData(renderCanvas, name, mime); + } + + async downloadRenderingForRugStudio( + name: string, + roomName: string, + description: string, + qrurl: string, + room = 'free', + type = 'image', + isWatermark = true, + paypalName = '', + paypalEmail = '', + purchasedDate = '', + price = '' + ): Promise { + const renderCanvas = this.renderinCanvas(); + const context = renderCanvas.getContext('2d')!; + context.globalAlpha = 1; + + const [watermarkImage, qrImage] = await Promise.all([ + this.getWaterMarkLogoForRugStudio(), + this.getQartForRugStudio(), + ]); + + if (isWatermark) { + const canvasWidth = renderCanvas.width; + const canvasHeight = renderCanvas.height; + const imgwidth = canvasWidth * 0.2; + const imgheight = (watermarkImage.height * imgwidth) / watermarkImage.width; + const startx = (canvasWidth - watermarkImage.width) / 2; + const starty = (canvasHeight - watermarkImage.height) / 2; + context.beginPath(); + context.fillStyle = 'rgba(255, 255, 255, 0.1)'; + context.fill(); + context.drawImage(watermarkImage, startx, starty, imgwidth, imgheight); + + const qrImgwidth = 0.08 * canvasWidth; + const qrImgheight = qrImgwidth; + const qrStartx = (canvasWidth - qrImgwidth) / 2; + const qrStarty = canvasHeight - qrImgheight - 20; + context.drawImage(qrImage, qrStartx, qrStarty, qrImgwidth, qrImgheight); } - if (this.carpetMesh) { - this.carpetMesh.geometry?.dispose(); - const mat = this.carpetMesh.material as THREE.MeshBasicMaterial; - mat.map?.dispose(); - mat.dispose(); - this.carpetMesh = undefined; + + if (type === 'pdf') { + canvasToBlobPromise(renderCanvas).then((blob) => { + AppProvider.fetchPdfForRugStudio({ + roomPath: blob, + description, + qrurl, + roomName, + room, + isWatermark, + paypalName, + paypalEmail, + purchasedDate, + price, + }).then((url) => { + const link = document.createElement('a'); + link.href = myroomStudioSaveUrl + 'MyRoomSave.aspx?mode=downloadFile&file=' + url.pdfPath; + link.download = name + '.pdf'; + link.dispatchEvent(new MouseEvent('click')); + if (!isWatermark) { + clearCanvas(this.shadowCanvas, this.shadowCanvas.width, this.shadowCanvas.height); + setTimeout(() => { + AppProvider.sendEmailMyRoomStudio({ + file: url.pdfPath, + qrurl, + roomName, + isWatermark, + paypalName, + paypalEmail, + purchasedDate, + price, + }); + }, 10000); + } + }); + }); + } else { + const link = document.createElement('a'); + link.href = renderCanvas.toDataURL('image/jpeg'); + link.download = name; + link.click(); } - if (this._tempPositionMesh) { - this._tempPositionMesh.geometry?.dispose(); - (this._tempPositionMesh.material as THREE.Material).dispose(); - this._tempPositionMesh = undefined; - } - if (this.scene) { - this.scene.clear(); - this.scene = null!; - } - this.camera = null!; - this.textureLoader = null!; - this.roomAnalysisData = null; - this._cachedFloorBounds = null; - this._cachedCarpetDims = null; } } + +function makeMask( + canvas: HTMLCanvasElement, + w: number, + h: number, + maskImg: HTMLImageElement, + _flag = false +): ImageData { + const tCanvas = createCanvas(w, h); + const shCtx = canvas.getContext('2d')!; + const tmpCtx = tCanvas.getContext('2d')!; + tmpCtx.drawImage(maskImg, 0, 0, w, h); + const imgData = shCtx.getImageData(0, 0, w, h); + const maskData = tmpCtx.getImageData(0, 0, w, h); + for (let i = 0; i < maskData.data.length; i += 4) { + imgData.data[i + 3] = maskData.data[i]; + } + shCtx.putImageData(imgData, 0, 0); + return maskData; +} + +const setCanvasDimensionsStyle = (canvas: HTMLCanvasElement, dimensionPixels: Dimensions): void => { + canvas.style.width = `${dimensionPixels.width}px`; + canvas.style.height = `${dimensionPixels.height}px`; +}; + +const setCanvasDimensions = ( + canvas: HTMLCanvasElement, + dimension: Dimensions, + dimensionPixels: Dimensions +): void => { + canvas.width = dimension.width; + canvas.height = dimension.height; + canvas.style.width = `${dimensionPixels.width}px`; + canvas.style.height = `${dimensionPixels.height}px`; +}; + +const normalizeDirNames = (files: string[]): string[] => + files.map((item) => (item.charAt(0) === '/' ? item.substring(1) : item)); + +const hasTransparentPixel = (data: Uint8ClampedArray): boolean => { + for (let index = 3; index < data.length; index += 4) { + if (data[index] === 0) return true; + } + return false; +}; + +const isNumberInRange = (num: number, min: number, max: number, inclusive = true): boolean => { + if (inclusive) return num >= min && num <= max; + return num > min && num < max; +}; diff --git a/src/Components/RoomView/index.ts b/src/Components/RoomView/index.ts index bd0fc98..e69de29 100644 --- a/src/Components/RoomView/index.ts +++ b/src/Components/RoomView/index.ts @@ -1,168 +0,0 @@ -// src/room-illustration.ts - -import {LitElement, html, css} from 'lit'; -import {customElement, property, query, state} from 'lit/decorators.js'; -import {RoomViewHelper} from './helpers/RoomViewHelper'; -import type { - RoomData, - DesignDetails, - CarpetOptions, - AvailableSize, -} from './types'; - -let roomViewHelper = new RoomViewHelper(); - -@customElement('room-illustration') -export class RoomIllustration extends LitElement { - // ─── Props in (replaces all useXxxState() hook reads) ──────────────── - @property({type: Object}) roomData: RoomData | null = null; - @property({type: Object}) designDetails: DesignDetails | null = null; - @property({type: Object}) carpetOptions: CarpetOptions | null = null; - @property({type: Object}) activeColor: {Color: string} | null = null; - @property({type: Object}) selectedSize: AvailableSize | null = null; - @property({type: Boolean}) isIdle = false; - - // ─── Internal state (replaces useState) ────────────────────────────── - @state() private shotReady = false; - @state() private transition = -1; - - // ─── Canvas refs (replaces useRef) ─────────────────────────────────── - @query('.container') private containerEl!: HTMLDivElement; - @query('.bg-canvas') private bgCanvas!: HTMLCanvasElement; - @query('.three-canvas') private threeCanvas!: HTMLCanvasElement; - @query('.mask-canvas') private maskCanvas!: HTMLCanvasElement; - @query('.shadow-canvas') private shadowCanvas!: HTMLCanvasElement; - @query('.input-canvas') private inputCanvas!: HTMLCanvasElement; - @query('.obj-container') private objContainer!: HTMLDivElement; - - // ─── Lifecycle ──────────────────────────────────────────────────────── - - override firstUpdated() { - // Equivalent to useMount in React — canvases are in the DOM - // roomViewHelper.initCanvas({ - // bgCanvas: this.bgCanvas, - // threeCanvas: this.threeCanvas, - // maskCanvas: this.maskCanvas, - // shadowCanvas: this.shadowCanvas, - // container: this.containerEl, - // inputCanvas: this.inputCanvas, - // objCanvasContainer: this.objContainer, - // }); - - window.addEventListener('resize', () => this.sizeContainers()); - } - - override updated(changed: Map) { - // if (changed.has('roomData') && this.roomData) { - // this.loadRoom(); - // } - // if (changed.has('carpetOptions') && this.carpetOptions && this.shotReady) { - // this.updateCarpet(); - // } - // if (changed.has('activeColor') && this.activeColor && this.shotReady) { - // roomViewHelper.updateBackground({ - // dominantColorHex: this.activeColor.Color, - // }); - // roomViewHelper.updateMask(); - // roomViewHelper.updateShadow(); - // } - // if (changed.has('selectedSize') && this.selectedSize) { - // roomViewHelper.change3dObjectScale(this.selectedSize); - // } - } - - override disconnectedCallback() { - super.disconnectedCallback(); - window.removeEventListener('resize', () => this.sizeContainers()); - // roomViewHelper.clearAll(); - } - - // ─── Internal methods (replaces inline useEffect logic) ────────────── - - private async loadRoom() { - // if (!this.roomData) return; - // const {config, baseUrl, files, shot} = this.roomData; - // roomViewHelper.clearAll(); - // roomViewHelper.initConfig({baseUrl, config, files}); - // this.sizeContainers(); - // await this.initShot({shot: shot ?? config.defaultShot}); - } - - private async initShot(options: {shot: string}) { - // mirrors initShot logic from IllustrationView/index.jsx - // ... - this.shotReady = true; - this.emitRenderingComplete(); - } - - private async updateCarpet() { - if (!this.carpetOptions) return; - // await roomViewHelper.updateCarpetRotation(this.carpetOptions.rotation); - // await roomViewHelper.updateCarpetPosition(this.carpetOptions.position); - // await roomViewHelper.updateShadow(); - } - - private sizeContainers() { - // resize canvas to container size - } - - // ─── Events out (replaces callback props) ──────────────────────────── - - private emitRenderingComplete() { - this.dispatchEvent( - new CustomEvent('rendering-complete', {bubbles: true, composed: true}) - ); - } - - private emitSizesChange(sizes: AvailableSize[]) { - this.dispatchEvent( - new CustomEvent('sizes-change', { - detail: sizes, - bubbles: true, - composed: true, - }) - ); - } - - private emitActiveClick() { - this.dispatchEvent( - new CustomEvent('active-click', {bubbles: true, composed: true}) - ); - } - - // ─── Template (replaces JSX canvas layout) ─────────────────────────── - - static override styles = css` - :host { - display: block; - position: relative; - width: 100%; - height: 100%; - } - .container { - position: relative; - width: 100%; - height: 100%; - } - canvas { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - `; - - override render() { - return html` -
- - - - - -
-
- `; - } -} diff --git a/src/Imports/exploRUG.ts b/src/Imports/exploRUG.ts index a7cceba..f9188a1 100644 --- a/src/Imports/exploRUG.ts +++ b/src/Imports/exploRUG.ts @@ -1,2 +1,2 @@ -import '../Components/RoomView/index.js'; +import '../Components/MyRoom/index.js'; import '../react-wrapper/roomView-react.js'; diff --git a/src/Utils/canvasutils.ts b/src/Utils/canvasutils.ts index 33f6617..1576579 100644 --- a/src/Utils/canvasutils.ts +++ b/src/Utils/canvasutils.ts @@ -8,7 +8,13 @@ declare const AppProvider: { interface WindowExtended extends Window { flutter_inappwebview?: {callHandler: (name: string, data: string) => void}; isNativeApp?: boolean; - flags: {inhouseChanges: {saveFunctionForFlutter?: boolean}}; + flags: { + inhouseChanges: {saveFunctionForFlutter?: boolean}; + ordersheet: { + showAreaInM2?: boolean; + showTotalAreaInYards?: boolean; + }; + }; getByteData?: string; } diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index a89a1d3..72ac4ad 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -86,8 +86,8 @@ export const convertUnits = ( return convertedValue; }; -export function convertArrIntoRad(arrDeg: number[]): number[] { - return arrDeg.map((angle) => (angle * Math.PI) / 180); +export function convertArrIntoRad(arrDeg: any) { + return arrDeg.map((angle: any) => (angle * Math.PI) / 180); } export const convertArrintoDeg = (arrRad: number[]): number[] => @@ -579,7 +579,9 @@ export const decodeColorsFromString = (str: string): unknown => { export const encodeColorsToString = (colors: unknown): string => { const compressed = Pako.deflate(JSON.stringify(colors)); - const binaryStr = Array.from(compressed, (c) => String.fromCharCode(c)).join(''); + const binaryStr = Array.from(compressed, (c) => String.fromCharCode(c)).join( + '' + ); return window.btoa(binaryStr); }; diff --git a/src/react-wrapper/roomView-react.ts b/src/react-wrapper/roomView-react.ts index dd15aee..39067ec 100644 --- a/src/react-wrapper/roomView-react.ts +++ b/src/react-wrapper/roomView-react.ts @@ -1,7 +1,7 @@ // react-wrapper/index.ts import {createComponent} from '@lit/react'; import React from 'react'; -import {RoomIllustration} from '../Components/RoomView'; +import {RoomIllustration} from '../Components/MyRoom'; export const RoomIllustrationReact = createComponent({ react: React, diff --git a/tsconfig.json b/tsconfig.json index 694987d..66bf5cd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,6 +29,6 @@ ], "types": ["mocha"] }, - "include": ["src/**/*.ts", "src/Components/RoomView/helpers/RoomViewHelper.js"], + "include": ["src/**/*.ts", "src/Components/MyRoom/helpers/RoomViewHelper.js"], "exclude": [] }