Merge pull request #8969 from tschaub/inverse-transforms

Create and use inverse pixel transforms during render
This commit is contained in:
Tim Schaub
2018-11-17 16:41:41 +01:00
committed by GitHub
11 changed files with 166 additions and 98 deletions

View File

@@ -3,7 +3,7 @@ import View from '../src/ol/View.js';
import TileLayer from '../src/ol/layer/Tile.js'; import TileLayer from '../src/ol/layer/Tile.js';
import {fromLonLat} from '../src/ol/proj.js'; import {fromLonLat} from '../src/ol/proj.js';
import BingMaps from '../src/ol/source/BingMaps.js'; import BingMaps from '../src/ol/source/BingMaps.js';
import {getPixelFromPixel} from '../src/ol/render.js'; import {getRenderPixel} from '../src/ol/render.js';
const key = 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5'; const key = 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5';
@@ -51,8 +51,8 @@ container.addEventListener('mouseout', function() {
// after rendering the layer, show an oversampled version around the pointer // after rendering the layer, show an oversampled version around the pointer
imagery.on('postrender', function(event) { imagery.on('postrender', function(event) {
if (mousePosition) { if (mousePosition) {
const pixel = getPixelFromPixel(event, mousePosition); const pixel = getRenderPixel(event, mousePosition);
const offset = getPixelFromPixel(event, [mousePosition[0] + radius, mousePosition[1]]); const offset = getRenderPixel(event, [mousePosition[0] + radius, mousePosition[1]]);
const half = Math.sqrt(Math.pow(offset[0] - pixel[0], 2) + Math.pow(offset[1] - pixel[1], 2)); const half = Math.sqrt(Math.pow(offset[0] - pixel[0], 2) + Math.pow(offset[1] - pixel[1], 2));
const context = event.context; const context = event.context;
const centerX = pixel[0]; const centerX = pixel[0];

View File

@@ -5,7 +5,6 @@ import {DEVICE_PIXEL_RATIO} from './has.js';
import { import {
apply as applyTransform, apply as applyTransform,
create as createTransform, create as createTransform,
invert as invertTransform,
multiply as multiplyTransform, multiply as multiplyTransform,
scale as scaleTransform scale as scaleTransform
} from './transform.js'; } from './transform.js';
@@ -92,22 +91,22 @@ export function toContext(context, opt_options) {
*/ */
export function getVectorContext(event) { export function getVectorContext(event) {
const frameState = event.frameState; const frameState = event.frameState;
const transform = multiplyTransform(invertTransform(event.pixelTransform.slice()), frameState.coordinateToPixelTransform); const transform = multiplyTransform(event.inversePixelTransform.slice(), frameState.coordinateToPixelTransform);
return new CanvasImmediateRenderer( return new CanvasImmediateRenderer(
event.context, frameState.pixelRatio, frameState.extent, transform, event.context, frameState.pixelRatio, frameState.extent, transform,
frameState.viewState.rotation); frameState.viewState.rotation);
} }
/** /**
* Gets the pixel of the event's canvas context from the map viewport's css pixel * Gets the pixel of the event's canvas context from the map viewport's CSS pixel.
* @param {import("./render/Event.js").default} event Render event. * @param {import("./render/Event.js").default} event Render event.
* @param {import("./pixel.js").Pixel} pixel Css pixel relative to the top-left * @param {import("./pixel.js").Pixel} pixel CSS pixel relative to the top-left
* corner of the map viewport. * corner of the map viewport.
* @returns {import("./pixel.js").Pixel} Pixel on the event's canvas context. * @returns {import("./pixel.js").Pixel} Pixel on the event's canvas context.
* @api * @api
*/ */
export function getPixelFromPixel(event, pixel) { export function getRenderPixel(event, pixel) {
const result = pixel.slice(0); const result = pixel.slice(0);
applyTransform(invertTransform(event.pixelTransform.slice()), result); applyTransform(event.inversePixelTransform.slice(), result);
return result; return result;
} }

View File

@@ -8,22 +8,23 @@ class RenderEvent extends Event {
/** /**
* @param {import("./EventType.js").default} type Type. * @param {import("./EventType.js").default} type Type.
* @param {import("../transform.js").Transform=} opt_pixelTransform Transform. * @param {import("../transform.js").Transform=} opt_inversePixelTransform Transform for
* CSS pixels to rendered pixels.
* @param {import("../PluggableMap.js").FrameState=} opt_frameState Frame state. * @param {import("../PluggableMap.js").FrameState=} opt_frameState Frame state.
* @param {?CanvasRenderingContext2D=} opt_context Context. * @param {?CanvasRenderingContext2D=} opt_context Context.
* @param {?import("../webgl/Helper.js").default=} opt_glContext WebGL Context. * @param {?import("../webgl/Helper.js").default=} opt_glContext WebGL Context.
*/ */
constructor(type, opt_pixelTransform, opt_frameState, opt_context, opt_glContext) { constructor(type, opt_inversePixelTransform, opt_frameState, opt_context, opt_glContext) {
super(type); super(type);
/** /**
* Transform from css pixels (relative to the top-left corner of the map viewport) * Transform from CSS pixels (relative to the top-left corner of the map viewport)
* to render pixel on this event's `context`. * to rendered pixels on this event's `context`.
* @type {import("../transform.js").Transform|undefined} * @type {import("../transform.js").Transform|undefined}
* @api * @api
*/ */
this.pixelTransform = opt_pixelTransform; this.inversePixelTransform = opt_inversePixelTransform;
/** /**
* An object representing the current render frame state. * An object representing the current render frame state.

View File

@@ -9,7 +9,7 @@ import {getWidth} from '../extent.js';
import {TRUE} from '../functions.js'; import {TRUE} from '../functions.js';
import {visibleAtResolution} from '../layer/Layer.js'; import {visibleAtResolution} from '../layer/Layer.js';
import {shared as iconImageCache} from '../style/IconImageCache.js'; import {shared as iconImageCache} from '../style/IconImageCache.js';
import {compose as composeTransform, invert as invertTransform, setFromArray as transformSetFromArray} from '../transform.js'; import {compose as composeTransform, makeInverse} from '../transform.js';
/** /**
* @abstract * @abstract
@@ -66,8 +66,7 @@ class MapRenderer extends Disposable {
-viewState.rotation, -viewState.rotation,
-viewState.center[0], -viewState.center[1]); -viewState.center[0], -viewState.center[1]);
invertTransform( makeInverse(pixelToCoordinateTransform, coordinateToPixelTransform);
transformSetFromArray(pixelToCoordinateTransform, coordinateToPixelTransform));
} }
/** /**

View File

@@ -6,7 +6,7 @@ import ViewHint from '../../ViewHint.js';
import {containsExtent, intersects} from '../../extent.js'; import {containsExtent, intersects} from '../../extent.js';
import {getIntersection, isEmpty} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js';
import CanvasLayerRenderer from './Layer.js'; import CanvasLayerRenderer from './Layer.js';
import {compose as composeTransform, toString as transformToString} from '../../transform.js'; import {compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js';
/** /**
* @classdesc * @classdesc
@@ -93,16 +93,17 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
width = height = size; width = height = size;
} }
const context = this.context; // set forward and inverse pixel transforms
const canvas = context.canvas; composeTransform(this.pixelTransform_,
const pixelTransform = composeTransform(this.pixelTransform_,
frameState.size[0] / 2, frameState.size[1] / 2, frameState.size[0] / 2, frameState.size[1] / 2,
1 / pixelRatio, 1 / pixelRatio, 1 / pixelRatio, 1 / pixelRatio,
rotation, rotation,
-width / 2, -height / 2 -width / 2, -height / 2
); );
const canvasTransform = transformToString(pixelTransform); makeInverse(this.inversePixelTransform_, this.pixelTransform_);
const context = this.context;
const canvas = context.canvas;
if (canvas.width != width || canvas.height != height) { if (canvas.width != width || canvas.height != height) {
canvas.width = width; canvas.width = width;
@@ -150,6 +151,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
canvas.style.opacity = opacity; canvas.style.opacity = opacity;
} }
const canvasTransform = transformToString(this.pixelTransform_);
if (canvasTransform !== canvas.style.transform) { if (canvasTransform !== canvas.style.transform) {
canvas.style.transform = canvasTransform; canvas.style.transform = canvasTransform;
} }

View File

@@ -7,7 +7,7 @@ import RenderEvent from '../../render/Event.js';
import RenderEventType from '../../render/EventType.js'; import RenderEventType from '../../render/EventType.js';
import {rotateAtOffset} from '../../render/canvas.js'; import {rotateAtOffset} from '../../render/canvas.js';
import LayerRenderer from '../Layer.js'; import LayerRenderer from '../Layer.js';
import {invert as invertTransform, create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js'; import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js';
/** /**
* @abstract * @abstract
@@ -28,19 +28,29 @@ class CanvasLayerRenderer extends LayerRenderer {
this.renderedResolution; this.renderedResolution;
/** /**
* A temporary transform. * A temporary transform. The values in this transform should only be used in a
* function that sets the values.
* @private * @private
* @type {import("../../transform.js").Transform} * @type {import("../../transform.js").Transform}
*/ */
this.tempTransform_ = createTransform(); this.tempTransform_ = createTransform();
/** /**
* The transform for rendered pixels to viewport CSS pixels. * The transform for rendered pixels to viewport CSS pixels. This transform must
* be set when rendering a frame and may be used by other functions after rendering.
* @private * @private
* @type {import("../../transform.js").Transform} * @type {import("../../transform.js").Transform}
*/ */
this.pixelTransform_ = createTransform(); this.pixelTransform_ = createTransform();
/**
* The transform for viewport CSS pixels to rendered pixels. This transform must
* be set when rendering a frame and may be used by other functions after rendering.
* @private
* @type {import("../../transform.js").Transform}
*/
this.inversePixelTransform_ = createTransform();
/** /**
* @protected * @protected
* @type {CanvasRenderingContext2D} * @type {CanvasRenderingContext2D}
@@ -102,8 +112,7 @@ class CanvasLayerRenderer extends LayerRenderer {
applyTransform(frameState.coordinateToPixelTransform, bottomRight); applyTransform(frameState.coordinateToPixelTransform, bottomRight);
applyTransform(frameState.coordinateToPixelTransform, bottomLeft); applyTransform(frameState.coordinateToPixelTransform, bottomLeft);
const inverted = invertTransform(this.pixelTransform_.slice()); const inverted = this.inversePixelTransform_;
applyTransform(inverted, topLeft); applyTransform(inverted, topLeft);
applyTransform(inverted, topRight); applyTransform(inverted, topRight);
applyTransform(inverted, bottomRight); applyTransform(inverted, bottomRight);
@@ -122,46 +131,32 @@ class CanvasLayerRenderer extends LayerRenderer {
* @param {import("../../render/EventType.js").default} type Event type. * @param {import("../../render/EventType.js").default} type Event type.
* @param {CanvasRenderingContext2D} context Context. * @param {CanvasRenderingContext2D} context Context.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../../transform.js").Transform} pixelTransform Transform.
* @private * @private
*/ */
dispatchComposeEvent_(type, context, frameState, pixelTransform) { dispatchRenderEvent_(type, context, frameState) {
const layer = this.getLayer(); const layer = this.getLayer();
if (layer.hasListener(type)) { if (layer.hasListener(type)) {
const composeEvent = new RenderEvent(type, pixelTransform, frameState, const event = new RenderEvent(type, this.inversePixelTransform_, frameState, context, null);
context, null); layer.dispatchEvent(event);
layer.dispatchEvent(composeEvent);
} }
} }
/** /**
* @param {CanvasRenderingContext2D} context Context. * @param {CanvasRenderingContext2D} context Context.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../../transform.js").Transform=} opt_transform Transform.
* @protected * @protected
*/ */
preRender(context, frameState, opt_transform) { preRender(context, frameState) {
this.dispatchComposeEvent_(RenderEventType.PRERENDER, context, frameState, opt_transform); this.dispatchRenderEvent_(RenderEventType.PRERENDER, context, frameState);
} }
/** /**
* @param {CanvasRenderingContext2D} context Context. * @param {CanvasRenderingContext2D} context Context.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../../transform.js").Transform=} opt_transform Transform.
* @protected * @protected
*/ */
postRender(context, frameState, opt_transform) { postRender(context, frameState) {
this.dispatchComposeEvent_(RenderEventType.POSTRENDER, context, frameState, opt_transform); this.dispatchRenderEvent_(RenderEventType.POSTRENDER, context, frameState);
}
/**
* @param {CanvasRenderingContext2D} context Context.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../../transform.js").Transform=} opt_transform Transform.
* @protected
*/
dispatchRenderEvent(context, frameState, opt_transform) {
this.dispatchComposeEvent_(RenderEventType.RENDER, context, frameState, opt_transform);
} }
/** /**
@@ -194,7 +189,7 @@ class CanvasLayerRenderer extends LayerRenderer {
* returned, and empty array will be returned. * returned, and empty array will be returned.
*/ */
getDataAtPixel(pixel, frameState, hitTolerance) { getDataAtPixel(pixel, frameState, hitTolerance) {
const renderPixel = applyTransform(invertTransform(this.pixelTransform_.slice()), pixel.slice()); const renderPixel = applyTransform(this.inversePixelTransform_, pixel.slice());
const context = this.context; const context = this.context;
let data; let data;

View File

@@ -6,7 +6,7 @@ import TileRange from '../../TileRange.js';
import TileState from '../../TileState.js'; import TileState from '../../TileState.js';
import {createEmpty, getIntersection, getTopLeft} from '../../extent.js'; import {createEmpty, getIntersection, getTopLeft} from '../../extent.js';
import CanvasLayerRenderer from './Layer.js'; import CanvasLayerRenderer from './Layer.js';
import {compose as composeTransform, toString as transformToString} from '../../transform.js'; import {compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js';
/** /**
* @classdesc * @classdesc
@@ -222,15 +222,18 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
} }
} }
const canvas = context.canvas; const canvas = context.canvas;
const canvasScale = tileResolution / frameState.viewState.resolution / tilePixelRatio; const canvasScale = tileResolution / frameState.viewState.resolution / tilePixelRatio;
const pixelTransform = composeTransform(this.pixelTransform_,
// set forward and inverse pixel transforms
composeTransform(this.pixelTransform_,
frameState.size[0] / 2, frameState.size[1] / 2, frameState.size[0] / 2, frameState.size[1] / 2,
canvasScale, canvasScale, canvasScale, canvasScale,
rotation, rotation,
-width / 2, -height / 2 -width / 2, -height / 2
); );
const canvasTransform = transformToString(pixelTransform); makeInverse(this.inversePixelTransform_, this.pixelTransform_);
if (canvas.width != width || canvas.height != height) { if (canvas.width != width || canvas.height != height) {
canvas.width = width; canvas.width = width;
@@ -243,7 +246,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
this.clipUnrotated(context, frameState, layerState.extent); this.clipUnrotated(context, frameState, layerState.extent);
} }
this.preRender(context, frameState, pixelTransform); this.preRender(context, frameState);
this.renderedTiles.length = 0; this.renderedTiles.length = 0;
/** @type {Array<number>} */ /** @type {Array<number>} */
@@ -291,7 +294,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
projection, extent, z, tileLayer.getPreload()); projection, extent, z, tileLayer.getPreload());
this.scheduleExpireCache(frameState, tileSource); this.scheduleExpireCache(frameState, tileSource);
this.postRender(context, frameState, pixelTransform); this.postRender(context, frameState);
if (layerState.extent) { if (layerState.extent) {
context.restore(); context.restore();
@@ -302,6 +305,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
canvas.style.opacity = opacity; canvas.style.opacity = opacity;
} }
const canvasTransform = transformToString(this.pixelTransform_);
if (canvasTransform !== canvas.style.transform) { if (canvasTransform !== canvas.style.transform) {
canvas.style.transform = canvasTransform; canvas.style.transform = canvasTransform;
} }

View File

@@ -7,13 +7,12 @@ import {listen, unlisten} from '../../events.js';
import EventType from '../../events/EventType.js'; import EventType from '../../events/EventType.js';
import rbush from 'rbush'; import rbush from 'rbush';
import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js';
import RenderEventType from '../../render/EventType.js';
import {labelCache} from '../../render/canvas.js'; import {labelCache} from '../../render/canvas.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js'; import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js';
import CanvasLayerRenderer from './Layer.js'; import CanvasLayerRenderer from './Layer.js';
import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js';
import {toString as transformToString} from '../../transform.js'; import {toString as transformToString, makeScale, makeInverse} from '../../transform.js';
/** /**
* @classdesc * @classdesc
@@ -105,8 +104,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const pixelRatio = frameState.pixelRatio; const pixelRatio = frameState.pixelRatio;
// a scale transform (we could add a createScale function in ol/transform) // set forward and inverse pixel transforms
const pixelTransform = [1 / pixelRatio, 0, 0, 1 / pixelRatio, 0, 0]; makeScale(this.pixelTransform_, 1 / pixelRatio, 1 / pixelRatio);
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
// resize and clear // resize and clear
const width = Math.round(frameState.size[0] * pixelRatio); const width = Math.round(frameState.size[0] * pixelRatio);
@@ -114,7 +114,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
if (canvas.width != width || canvas.height != height) { if (canvas.width != width || canvas.height != height) {
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
const canvasTransform = transformToString(pixelTransform); const canvasTransform = transformToString(this.pixelTransform_);
if (canvas.style.transform !== canvasTransform) { if (canvas.style.transform !== canvasTransform) {
canvas.style.transform = canvasTransform; canvas.style.transform = canvasTransform;
} }
@@ -122,7 +122,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
context.clearRect(0, 0, width, height); context.clearRect(0, 0, width, height);
} }
this.preRender(context, frameState, pixelTransform); this.preRender(context, frameState);
const extent = frameState.extent; const extent = frameState.extent;
const viewState = frameState.viewState; const viewState = frameState.viewState;
@@ -173,15 +173,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
} }
} }
if (this.getLayer().hasListener(RenderEventType.RENDER)) {
this.dispatchRenderEvent(context, frameState, pixelTransform);
}
if (clipped) { if (clipped) {
context.restore(); context.restore();
} }
this.postRender(context, frameState, pixelTransform); this.postRender(context, frameState);
const opacity = layerState.opacity; const opacity = layerState.opacity;
if (opacity !== canvas.style.opacity) { if (opacity !== canvas.style.opacity) {

View File

@@ -22,11 +22,12 @@ import {
apply as applyTransform, apply as applyTransform,
create as createTransform, create as createTransform,
compose as composeTransform, compose as composeTransform,
invert as invertTransform,
reset as resetTransform, reset as resetTransform,
scale as scaleTransform, scale as scaleTransform,
translate as translateTransform, translate as translateTransform,
toString as transformToString toString as transformToString,
makeScale,
makeInverse
} from '../../transform.js'; } from '../../transform.js';
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js'; import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
@@ -98,6 +99,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
*/ */
this.overlayPixelTransform_ = createTransform(); this.overlayPixelTransform_ = createTransform();
/**
* The transform for viewport CSS pixels to rendered pixels for the overlay canvas.
* @private
* @type {import("../../transform.js").Transform}
*/
this.inverseOverlayPixelTransform_ = createTransform();
/** /**
* Declutter tree. * Declutter tree.
* @private * @private
@@ -384,12 +392,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
const rotation = frameState.viewState.rotation; const rotation = frameState.viewState.rotation;
const size = frameState.size; const size = frameState.size;
// set forward and inverse pixel transforms
makeScale(this.overlayPixelTransform_, 1 / pixelRatio, 1 / pixelRatio);
makeInverse(this.inverseOverlayPixelTransform_, this.overlayPixelTransform_);
// resize and clear // resize and clear
const canvas = context.canvas; const canvas = context.canvas;
const width = Math.round(size[0] * pixelRatio); const width = Math.round(size[0] * pixelRatio);
const height = Math.round(size[1] * pixelRatio); const height = Math.round(size[1] * pixelRatio);
this.overlayPixelTransform_[0] = 1 / pixelRatio;
this.overlayPixelTransform_[3] = 1 / pixelRatio;
if (canvas.width != width || canvas.height != height) { if (canvas.width != width || canvas.height != height) {
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
@@ -544,7 +554,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
return data; return data;
} }
const renderPixel = applyTransform(invertTransform(this.overlayPixelTransform_.slice()), pixel.slice()); const renderPixel = applyTransform(this.inverseOverlayPixelTransform_, pixel.slice());
const context = this.overlayContext_; const context = this.overlayContext_;
try { try {

View File

@@ -162,6 +162,16 @@ export function scale(transform, x, y) {
return multiply(transform, set(tmp_, x, 0, 0, y, 0, 0)); return multiply(transform, set(tmp_, x, 0, 0, y, 0, 0));
} }
/**
* Creates a scale transform.
* @param {!Transform} target Transform to overwrite.
* @param {number} x Scale factor x.
* @param {number} y Scale factor y.
* @return {!Transform} The scale transform.
*/
export function makeScale(target, x, y) {
return set(target, x, 0, 0, y, 0, 0);
}
/** /**
* Applies translation to the given transform. * Applies translation to the given transform.
@@ -203,30 +213,40 @@ export function compose(transform, dx1, dy1, sx, sy, angle, dx2, dy2) {
/** /**
* Invert the given transform. * Invert the given transform.
* @param {!Transform} transform Transform. * @param {!Transform} source The source transform to invert.
* @return {!Transform} Inverse of the transform. * @return {!Transform} The inverted (source) transform.
*/ */
export function invert(transform) { export function invert(source) {
const det = determinant(transform); return makeInverse(source, source);
assert(det !== 0, 32); // Transformation matrix cannot be inverted
const a = transform[0];
const b = transform[1];
const c = transform[2];
const d = transform[3];
const e = transform[4];
const f = transform[5];
transform[0] = d / det;
transform[1] = -b / det;
transform[2] = -c / det;
transform[3] = a / det;
transform[4] = (c * f - d * e) / det;
transform[5] = -(a * f - b * e) / det;
return transform;
} }
/**
* Invert the given transform.
* @param {!Transform} target Transform to be set as the inverse of
* the source transform.
* @param {!Transform} source The source transform to invert.
* @return {!Transform} The inverted (target) transform.
*/
export function makeInverse(target, source) {
const det = determinant(source);
assert(det !== 0, 32); // Transformation matrix cannot be inverted
const a = source[0];
const b = source[1];
const c = source[2];
const d = source[3];
const e = source[4];
const f = source[5];
target[0] = d / det;
target[1] = -b / det;
target[2] = -c / det;
target[3] = a / det;
target[4] = (c * f - d * e) / det;
target[5] = -(a * f - b * e) / det;
return target;
}
/** /**
* Returns the determinant of the given matrix. * Returns the determinant of the given matrix.

View File

@@ -5,10 +5,12 @@ import {
setFromArray, setFromArray,
translate, translate,
scale, scale,
makeScale,
rotate, rotate,
multiply, multiply,
compose, compose,
invert, invert,
makeInverse,
apply apply
} from '../../../src/ol/transform.js'; } from '../../../src/ol/transform.js';
@@ -68,6 +70,20 @@ describe('ol.transform', function() {
}); });
}); });
describe('makeScale()', function() {
it('creates a scale transform', function() {
const target = create();
makeScale(target, 2, 3);
expect(target).to.eql([2, 0, 0, 3, 0, 0]);
});
it('returns the target', function() {
const target = create();
const transform = makeScale(target, 2, 3);
expect(transform).to.be(target);
});
});
describe('rotate()', function() { describe('rotate()', function() {
it('applies rotation to a transform', function() { it('applies rotation to a transform', function() {
const transform = create(); const transform = create();
@@ -110,13 +126,39 @@ describe('ol.transform', function() {
describe('invert()', function() { describe('invert()', function() {
it('inverts a transform', function() { it('inverts a transform', function() {
let transform = [1, 0, 1, 0, 1, 0]; const transform = [1, 1, 1, 2, 2, 0];
expect(function() {
invert(transform);
}).to.throwException();
transform = [1, 1, 1, 2, 2, 0];
expect(invert(transform)).to.eql([2, -1, -1, 1, -4, 2]); expect(invert(transform)).to.eql([2, -1, -1, 1, -4, 2]);
expect(transform).to.eql([2, -1, -1, 1, -4, 2]); });
it('throws if the transform cannot be inverted', function() {
const indeterminant = [1, 0, 1, 0, 1, 0];
expect(function() {
invert(indeterminant);
}).to.throwException();
});
it('modifies the source', function() {
const source = [1, 1, 1, 2, 2, 0];
const inverted = invert(source);
expect(inverted).to.eql([2, -1, -1, 1, -4, 2]);
expect(source).to.be(inverted);
});
});
describe('makeInverse()', function() {
it('makes the target the inverse of the source', function() {
const source = [1, 1, 1, 2, 2, 0];
const target = [1, 0, 0, 1, 0, 0];
makeInverse(target, source);
expect(source).to.eql([1, 1, 1, 2, 2, 0]);
expect(target).to.eql([2, -1, -1, 1, -4, 2]);
});
it('returns the target', function() {
const source = [1, 1, 1, 2, 2, 0];
const target = [1, 0, 0, 1, 0, 0];
const inverted = makeInverse(target, source);
expect(target).to.be(inverted);
}); });
}); });