import { BoundingBox } from './BoundingBox.js'; import { Transform } from './Transform.js' /** * Contain functions to pass between different coordinate system. * Here described the coordinate system in sequence * - CanvasHTML: Html coordinates: 0,0 left,top to width height at bottom right (y Down) * - CanvasContext: Same as Html, but scaled by devicePixelRatio (y Down) (required for WebGL, not for SVG) * - Viewport: 0,0 left,bottom to (width,height) at top right (y Up) * - Center: 0,0 at viewport center (y Up) * - Scene: 0,0 at dataset center (y Up). The dataset is placed here through the camera transform * - Layer: 0,0 at Layer center (y Up). Layer is placed over the dataset by the layer transform * - Image: 0,0 at left,top (y Down) * - Layout: 0,0 at left,top (y Down). Depends on layout */ class CoordinateSystem { /** * Transform point from Viewport to CanvasHTML * @param {*} p point in Viewport: 0,0 at left,bottom * @param {Camera} camera Camera which contains viewport information * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns point in CanvasHtml: 0,0 left,top */ static fromViewportToCanvasHtml(p, camera, useGL) { const viewport = this.getViewport(camera, useGL); let result = this.invertY(p, viewport); return useGL ? this.scale(result, 1/window.devicePixelRatio) : result; } /** * Transform point from CanvasHTML to GLViewport * @param {*} p point in CanvasHtml: 0,0 left,top y Down * @param {Camera} camera Camera * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns point in GLViewport: 0,0 left,bottom, scaled by devicePixelRatio */ static fromCanvasHtmlToViewport(p, camera, useGL) { let result = useGL ? this.scale(p, window.devicePixelRatio) : p; const viewport = this.getViewport(camera, useGL); return this.invertY(result, viewport); } /** * Transform a point from Viewport to Layer coordinates * @param {*} p point {x,y} in Viewport (0,0 left,bottom, y Up) * @param {Camera} camera camera * @param {Transform} layerT layer transform * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns point in Layer coordinates (0, 0 at layer center, y Up) */ static fromViewportToLayer(p, camera, layerT, useGL) { // M = InvLayerT * InvCameraT * Tr(-Vw/2, -Vh/2) const cameraT = this.getCurrentTransform(camera, useGL); const invCameraT = cameraT.inverse(); const invLayerT = layerT.inverse(); const v2c = this.getFromViewportToCenterTransform(camera, useGL); const M = v2c.compose(invCameraT.compose(invLayerT)); // First apply v2c, then invCamera, then invLayer return M.apply(p.x, p.y); } /** * Transform a point from Layer to Viewport coordinates * @param {*} p point {x,y} Layer (0,0 at Layer center y Up) * @param {Camera} camera * @param {Transform} layerT layer transform * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns point in viewport coordinates (0,0 at left,bottom y Up) */ static fromLayerToViewport(p, camera, layerT, useGL) { const M = this.getFromLayerToViewportTransform(camera, layerT, useGL); return M.apply(p.x, p.y); } /** * Transform a point from Layer to Center * @param {*} p point {x,y} in Layer coordinates (0,0 at Layer center) * @param {Camera} camera camera * @param {Transform} layerT layer transform * @returns point in Center (0, 0 at glViewport center) coordinates. */ static fromLayerToCenter(p, camera, layerT, useGL) { // M = cameraT * layerT const cameraT = this.getCurrentTransform(camera, useGL); const M = layerT.compose(cameraT); return M.apply(p.x, p.y); } ////////////// CHECKED UP TO HERE //////////////////// /** * Transform a point from Layer to Image coordinates * @param {*} p point {x, y} Layer coordinates (0,0 at Layer center) * @param {*} layerSize {w, h} Size in pixel of the Layer * @returns Point in Image coordinates (0,0 at left,top, y Down) */ static fromLayerToImage(p, layerSize) { // InvertY * Tr(Lw/2, Lh/2) let result = {x: p.x + layerSize.w/2, y: p.y + layerSize.h/2}; return this.invertY(result, layerSize); } /** * Transform a point from CanvasHtml to Scene * @param {*} p point {x, y} in CanvasHtml (0,0 left,top, y Down) * @param {Camera} camera camera * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns Point in Scene coordinates (0,0 at scene center, y Up) */ static fromCanvasHtmlToScene(p, camera, useGL) { // invCameraT * Tr(-Vw/2, -Vh/2) * InvertY * [Scale(devPixRatio)] let result = this.fromCanvasHtmlToViewport(p, camera, useGL); const v2c = this.getFromViewportToCenterTransform(camera, useGL); const invCameraT = this.getCurrentTransform(camera, useGL).inverse(); const M = v2c.compose(invCameraT); return M.apply(result.x, result.y); } /** * Transform a point from Scene to CanvasHtml * @param {*} p point {x, y} Scene coordinates (0,0 at scene center, y Up) * @param {Camera} camera camera * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns Point in CanvasHtml (0,0 left,top, y Down) */ static fromSceneToCanvasHtml(p, camera, useGL) { // invCameraT * Tr(-Vw/2, -Vh/2) * InvertY * [Scale(devPixRatio)] let result = this.fromSceneToViewport(p, camera, useGL) return this.fromViewportToCanvasHtml(result, camera, useGL); } /** * Transform a point from Scene to Viewport * @param {*} p point {x, y} Scene coordinates (0,0 at scene center, y Up) * @param {Camera} camera camera * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns Point in Viewport (0,0 left,bottom, y Up) */ static fromSceneToViewport(p, camera, useGL) { // FromCenterToViewport * CamT const c2v = this.getFromViewportToCenterTransform(camera, useGL).inverse(); const CameraT = this.getCurrentTransform(camera, useGL); const M = CameraT.compose(c2v); return M.apply(p.x, p.y); } /** * Transform a point from Scene to Viewport, using given transform and viewport * @param {*} p point {x, y} Scene coordinates (0,0 at scene center, y Up) * @param {Transform} cameraT camera transform * @param {*} viewport viewport {x,y,dx,dy,w,h} * @returns Point in Viewport (0,0 left,bottom, y Up) */ static fromSceneToViewportNoCamera(p, cameraT, viewport) { // invCameraT * Tr(-Vw/2, -Vh/2) * InvertY * [Scale(devPixRatio)] const c2v = this.getFromViewportToCenterTransformNoCamera(viewport).inverse(); const M = cameraT.compose(c2v); return M.apply(p.x, p.y); } /** * Transform a point from Viewport to Scene. * @param {*} p point {x, y} Viewport coordinates (0,0 at left,bottom, y Up) * @param {Camera} camera camera * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns Point in Viewport (0,0 at scene center, y Up) */ static fromViewportToScene(p, camera, useGL) { // invCamT * FromViewportToCenter const v2c = this.getFromViewportToCenterTransform(camera, useGL); const invCameraT = this.getCurrentTransform(camera, useGL).inverse(); const M = v2c.compose(invCameraT); return M.apply(p.x, p.y); } /** * Transform a point from Viewport to Scene, using given transform and viewport * @param {*} p point {x, y} Viewport coordinates (0,0 at left,bottom, y Up) * @param {Transform} cameraT camera transform * @param {*} viewport viewport {x,y,dx,dy,w,h} * @returns Point in Viewport (0,0 at scene center, y Up) */ static fromViewportToSceneNoCamera(p, cameraT, viewport) { // invCamT * FromViewportToCenter const v2c = this.getFromViewportToCenterTransformNoCamera(viewport); const invCameraT = cameraT.inverse(); const M = v2c.compose(invCameraT); return M.apply(p.x, p.y); } /** * Transform a point from CanvasHtml to Image * @param {*} p point {x, y} in CanvasHtml (0,0 left,top, y Down) * @param {Camera} camera camera * @param {Transform} layerT layer transform * @param {*} layerSize {w, h} Size in pixel of the Layer * @param {bool} applyGLScale if true apply devPixelRatio scale. Keep it false when working with SVG * @returns Point in Image space (0,0 left,top of the image, y Down) */ static fromCanvasHtmlToImage(p, camera, layerT, layerSize, useGL) { // Translate(Lw/2, Lh/2) * InvLayerT * InvCameraT * Translate(-Vw/2, -Vh/2) * invertY * [Scale(devicePixelRatio)] // in other words... fromLayerToImage * invLayerT * fromCanvasHtmlToScene let result = this.fromCanvasHtmlToScene(p, camera, useGL); const invLayerT = layerT.inverse(); result = invLayerT.apply(result.x, result.y); result = this.fromLayerToImage(result, layerSize); return result; } /** * Transform a box from Viewport to Image coordinates * @param {BoundingBox} box in Viewport coordinates (0,0 at left,bottom, y Up) * @param {Transform} cameraT camera Transform * @param {*} viewport {x,y,dx,dy,w,h} * @param {Transform} layerT layer transform * @param {*} layerSize {w,h} layer pixel size * @returns box in Image coordinates (0,0 left,top, y Dowm) */ static fromViewportBoxToImageBox(box, cameraT, viewport, layerT, layerSize) { // InvertYonImage * T(Lw/2, Lh/2) * InvL * InvCam * T(-Vw/2,-Vh/2) let V2C = new Transform({x:-viewport.w/2, y:-viewport.h/2}); let C2S = cameraT.inverse(); let S2L = layerT.inverse(); let L2I = new Transform({x:layerSize.w/2, y:layerSize.h/2}); let M = V2C.compose(C2S.compose(S2L.compose(L2I))); let resultBox = new BoundingBox(); for(let i = 0; i < 4; ++i) { let p = box.corner(i); p = M.apply(p.x, p.y); p = CoordinateSystem.invertY(p, layerSize); resultBox.mergePoint(p); } return resultBox; } /** * Transform a box from Layer to Scene * @param {BoundingBox} box box in Layer coordinates (0,0 at layer center) * @param {Transform} layerT layer transform * @returns box in Scene coordinates (0,0 at scene center) */ static fromLayerBoxToSceneBox(box, layerT) { return layerT.transformBox(box); } /** * Transform a box from Scene to Layer * @param {BoundingBox} box box in Layer coordinates (0,0 at layer center) * @param {Transform} layerT layer transform * @returns box in Scene coordinates (0,0 at scene center) */ static fromSceneBoxToLayerBox(box, layerT) { return layerT.inverse().transformBox(box); } /** * Transform a box from Layer to Viewport coordinates * @param {BoundingBox} box box in Layer coordinates (0,0 at Layer center y Up) * @param {Camera} camera * @param {Transform} layerT layer transform * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns Box in Viewport coordinates (0,0 at left, bottom y Up) */ static fromLayerBoxToViewportBox(box, camera, layerT, useGL) { const M = this.getFromLayerToViewportTransform(camera, layerT, useGL); return M.transformBox(box); } /** * Transform a box from Layer to Viewport coordinates * @param {BoundingBox} box box in Layer coordinates (0,0 at Layer center y Up) * @param {Camera} camera * @param {Transform} layerT layer transform * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns Box in Viewport coordinates (0,0 at left, bottom y Up) */ static fromViewportBoxToLayerBox(box, camera, layerT, useGL) { const M = this.getFromLayerToViewportTransform(camera, layerT, useGL).inverse(); return M.transformBox(box); } /** * Get a transform to go from viewport 0,0 at left, bottom y Up, to Center 0,0 at viewport center * @param {Camera} camera camera * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns transform from Viewport to Center */ static getFromViewportToCenterTransform(camera, useGL) { const viewport = this.getViewport(camera, useGL); return this.getFromViewportToCenterTransformNoCamera(viewport); } /** * Get a transform to go from viewport 0,0 at left, bottom y Up, to Center 0,0 at viewport center * from explicit viewport param. (Not using camera parameter here) * @param {*} viewport viewport * @returns transform from Viewport to Center */ static getFromViewportToCenterTransformNoCamera(viewport) { return new Transform({x:viewport.x-viewport.w/2, y:viewport.y-viewport.h/2, z:1, a:0, t:0}); } /** * Return transform with y reflected wrt origin (y=-y) * @param {Transform} t * @returns {Transform} transform, with y reflected (around 0) */ static reflectY(t) { return new Transform({x:t.x, y:-t.y, z:t.z, a:t.a, t:t.t}); } /** * Get a transform to go from Layer (0,0 at Layer center y Up) to Viewport (0,0 at left,bottom y Up) * @param {Camera} camera * @param {Transform} layerT layer transform * @param {bool} useGL True to work with WebGL, false for SVG. When true, it uses devPixelRatio scale * @returns transform from Layer to Viewport */ static getFromLayerToViewportTransform(camera, layerT, useGL) { // M = Center2Viewport * CameraT * LayerT const cameraT = this.getCurrentTransform(camera, useGL); const c2v = this.getFromViewportToCenterTransform(camera, useGL).inverse(); const M = layerT.compose(cameraT.compose(c2v)); return M; } /** * Get a transform to go from Layer (0,0 at Layer center y Up) to Viewport (0,0 at left,bottom y Up) * @param {Transform} CameraT camera transform * @param {viewport} viewport {x,y,dx,dy,w,h} viewport * @param {Transform} layerT layer transform * @returns transform from Layer to Viewport */ static getFromLayerToViewportTransformNoCamera(cameraT, viewport, layerT) { // M = Center2Viewport * CameraT * LayerT const c2v = this.getFromViewportToCenterTransformNoCamera(viewport).inverse(); const M = layerT.compose(cameraT.compose(c2v)); return M; } /** * Scale x applying f scale factor * @param {*} p Point to be scaled * @param {Number} f Scale factor * @returns Point in CanvasContext (Scaled by devicePixelRation) */ static scale(p, f) { return { x:p.x * f, y:p.y * f}; } /** * Invert y with respect to viewport.h * @param {*} p Point to be transformed * @param {*} viewport current viewport * @returns Point with y inverted with respect to viewport.h */ static invertY(p, viewport) { return {x:p.x, y:viewport.h - p.y}; } /** * Return the camera viewport: scaled by devicePixelRatio if useGL is true. * @param {bool} useGL True to work with WebGL, false for SVG. When true viewport scaled by devPixelRatio * @returns Viewport */ static getViewport(camera, useGL) { return useGL ? camera.glViewport() : camera.viewport; } static getCurrentTransform(camera, useGL) { let cameraT = useGL ? camera.getGlCurrentTransform(performance.now()) : camera.getCurrentTransform(performance.now()); return cameraT; } } export { CoordinateSystem }