Rotate map canvas after composition
This commit is contained in:
@@ -2,6 +2,44 @@
|
||||
|
||||
### v3.14.0
|
||||
|
||||
#### Layer pre-/postcompose event changes
|
||||
|
||||
It is the responsibility of the application to undo any canvas transform changes at the end of a layer 'precompose' or 'postcompose' handler. Previously, it was ok to set a null transform. The API now guarantees a device pixel coordinate system on the canvas with its origin in the top left corner of the map. However, applications should not rely on the underlying canvas being the same size as the visible viewport.
|
||||
|
||||
Old code:
|
||||
```js
|
||||
layer.on('precompose', function(e) {
|
||||
// rely on canvas dimensions to move coordinate origin to center
|
||||
e.context.translate(e.context.canvas.width / 2, e.context.canvas.height / 2);
|
||||
e.context.scale(3, 3);
|
||||
// draw an x in the center of the viewport
|
||||
e.context.moveTo(-20, -20);
|
||||
e.context.lineTo(20, 20);
|
||||
e.context.moveTo(-20, 20);
|
||||
e.context.lineTo(20, -20);
|
||||
// rely on the canvas having a null transform
|
||||
e.context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
});
|
||||
```
|
||||
New code:
|
||||
```js
|
||||
layer.on('precompose', function(e) {
|
||||
// use map size and pixel ratio to move coordinate origin to center
|
||||
var size = map.getSize();
|
||||
var pixelRatio = e.frameState.pixelRatio;
|
||||
e.context.translate(size[0] / 2 * pixelRatio, size[1] / 2 * pixelRatio);
|
||||
e.context.scale(3, 3);
|
||||
// draw an x in the center of the viewport
|
||||
e.context.moveTo(-20, -20);
|
||||
e.context.lineTo(20, 20);
|
||||
e.context.moveTo(-20, 20);
|
||||
e.context.lineTo(20, -20);
|
||||
// undo all transforms
|
||||
e.context.scale(1 / 3, 1 / 3);
|
||||
e.context.translate(-size[0] / 2 * pixelRatio, -size[1] / 2 * pixelRatio);
|
||||
});
|
||||
```
|
||||
|
||||
### v3.13.0
|
||||
|
||||
#### `proj4js` integration
|
||||
|
||||
@@ -25,8 +25,10 @@ var map = new ol.Map({
|
||||
osm.on('precompose', function(event) {
|
||||
var ctx = event.context;
|
||||
ctx.save();
|
||||
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
|
||||
ctx.scale(3, 3);
|
||||
var pixelRatio = event.frameState.pixelRatio;
|
||||
var size = map.getSize();
|
||||
ctx.translate(size[0] / 2 * pixelRatio, size[1] / 2 * pixelRatio);
|
||||
ctx.scale(3 * pixelRatio, 3 * pixelRatio);
|
||||
ctx.translate(-75, -80);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(75, 40);
|
||||
@@ -37,7 +39,9 @@ osm.on('precompose', function(event) {
|
||||
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
|
||||
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
|
||||
ctx.clip();
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.translate(75, 80);
|
||||
ctx.scale(1 / 3 / pixelRatio, 1 / 3 / pixelRatio);
|
||||
ctx.translate(-size[0] / 2 * pixelRatio, -size[1] / 2 * pixelRatio);
|
||||
});
|
||||
|
||||
osm.on('postcompose', function(event) {
|
||||
|
||||
@@ -575,18 +575,24 @@ ol.extent.getForViewAndSize = function(center, resolution, rotation, size, opt_e
|
||||
var dy = resolution * size[1] / 2;
|
||||
var cosRotation = Math.cos(rotation);
|
||||
var sinRotation = Math.sin(rotation);
|
||||
/** @type {Array.<number>} */
|
||||
var xs = [-dx, -dx, dx, dx];
|
||||
/** @type {Array.<number>} */
|
||||
var ys = [-dy, dy, -dy, dy];
|
||||
var i, x, y;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
x = xs[i];
|
||||
y = ys[i];
|
||||
xs[i] = center[0] + x * cosRotation - y * sinRotation;
|
||||
ys[i] = center[1] + x * sinRotation + y * cosRotation;
|
||||
}
|
||||
return ol.extent.boundingExtentXYs_(xs, ys, opt_extent);
|
||||
var xCos = dx * cosRotation;
|
||||
var xSin = dx * sinRotation;
|
||||
var yCos = dy * cosRotation;
|
||||
var ySin = dy * sinRotation;
|
||||
var x = center[0];
|
||||
var y = center[1];
|
||||
var x0 = x - xCos + ySin;
|
||||
var x1 = x - xCos - ySin;
|
||||
var x2 = x + xCos - ySin;
|
||||
var x3 = x + xCos + ySin;
|
||||
var y0 = y - xSin - yCos;
|
||||
var y1 = y - xSin + yCos;
|
||||
var y2 = y + xSin + yCos;
|
||||
var y3 = y + xSin - yCos;
|
||||
return ol.extent.createOrUpdate(
|
||||
Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3),
|
||||
Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3),
|
||||
opt_extent);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -147,7 +147,6 @@ ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, laye
|
||||
var viewState = frameState.viewState;
|
||||
var viewCenter = viewState.center;
|
||||
var viewResolution = viewState.resolution;
|
||||
var viewRotation = viewState.rotation;
|
||||
|
||||
var image;
|
||||
var imageLayer = this.getLayer();
|
||||
@@ -195,7 +194,7 @@ ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, laye
|
||||
pixelRatio * frameState.size[0] / 2,
|
||||
pixelRatio * frameState.size[1] / 2,
|
||||
scale, scale,
|
||||
viewRotation,
|
||||
0,
|
||||
imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution,
|
||||
imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution);
|
||||
this.imageTransformInv_ = null;
|
||||
|
||||
@@ -51,6 +51,8 @@ ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerStat
|
||||
goog.asserts.assert(extent !== undefined,
|
||||
'layerState extent is defined');
|
||||
var pixelRatio = frameState.pixelRatio;
|
||||
var width = frameState.size[0] * pixelRatio;
|
||||
var height = frameState.size[1] * pixelRatio;
|
||||
var topLeft = ol.extent.getTopLeft(extent);
|
||||
var topRight = ol.extent.getTopRight(extent);
|
||||
var bottomRight = ol.extent.getBottomRight(extent);
|
||||
@@ -66,12 +68,18 @@ ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerStat
|
||||
bottomLeft, bottomLeft);
|
||||
|
||||
context.save();
|
||||
context.translate(width / 2, height / 2);
|
||||
context.rotate(-frameState.viewState.rotation);
|
||||
context.translate(-width / 2, -height / 2);
|
||||
context.beginPath();
|
||||
context.moveTo(topLeft[0] * pixelRatio, topLeft[1] * pixelRatio);
|
||||
context.lineTo(topRight[0] * pixelRatio, topRight[1] * pixelRatio);
|
||||
context.lineTo(bottomRight[0] * pixelRatio, bottomRight[1] * pixelRatio);
|
||||
context.lineTo(bottomLeft[0] * pixelRatio, bottomLeft[1] * pixelRatio);
|
||||
context.clip();
|
||||
context.translate(width / 2, height / 2);
|
||||
context.rotate(frameState.viewState.rotation);
|
||||
context.translate(-width / 2, -height / 2);
|
||||
}
|
||||
|
||||
var imageTransform = this.getImageTransform();
|
||||
@@ -83,24 +91,12 @@ ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerStat
|
||||
|
||||
// for performance reasons, context.setTransform is only used
|
||||
// when the view is rotated. see http://jsperf.com/canvas-transform
|
||||
if (frameState.viewState.rotation === 0) {
|
||||
var dx = goog.vec.Mat4.getElement(imageTransform, 0, 3);
|
||||
var dy = goog.vec.Mat4.getElement(imageTransform, 1, 3);
|
||||
var dw = image.width * goog.vec.Mat4.getElement(imageTransform, 0, 0);
|
||||
var dh = image.height * goog.vec.Mat4.getElement(imageTransform, 1, 1);
|
||||
context.drawImage(image, 0, 0, +image.width, +image.height,
|
||||
Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
|
||||
} else {
|
||||
context.setTransform(
|
||||
goog.vec.Mat4.getElement(imageTransform, 0, 0),
|
||||
goog.vec.Mat4.getElement(imageTransform, 1, 0),
|
||||
goog.vec.Mat4.getElement(imageTransform, 0, 1),
|
||||
goog.vec.Mat4.getElement(imageTransform, 1, 1),
|
||||
goog.vec.Mat4.getElement(imageTransform, 0, 3),
|
||||
goog.vec.Mat4.getElement(imageTransform, 1, 3));
|
||||
context.drawImage(image, 0, 0);
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
context.globalAlpha = alpha;
|
||||
|
||||
if (clipped) {
|
||||
@@ -123,6 +119,11 @@ ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerStat
|
||||
ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState, opt_transform) {
|
||||
var layer = this.getLayer();
|
||||
if (layer.hasListener(type)) {
|
||||
var width = frameState.size[0] * frameState.pixelRatio;
|
||||
var height = frameState.size[1] * frameState.pixelRatio;
|
||||
context.translate(width / 2, height / 2);
|
||||
context.rotate(-frameState.viewState.rotation);
|
||||
context.translate(-width / 2, -height / 2);
|
||||
var transform = opt_transform !== undefined ?
|
||||
opt_transform : this.getTransform(frameState, 0);
|
||||
var render = new ol.render.canvas.Immediate(
|
||||
@@ -132,6 +133,9 @@ ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, contex
|
||||
context, null);
|
||||
layer.dispatchEvent(composeEvent);
|
||||
render.flush();
|
||||
context.translate(width / 2, height / 2);
|
||||
context.rotate(frameState.viewState.rotation);
|
||||
context.translate(-width / 2, -height / 2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ goog.require('ol.RendererType');
|
||||
goog.require('ol.array');
|
||||
goog.require('ol.css');
|
||||
goog.require('ol.dom');
|
||||
goog.require('ol.extent');
|
||||
goog.require('ol.layer.Image');
|
||||
goog.require('ol.layer.Layer');
|
||||
goog.require('ol.layer.Tile');
|
||||
@@ -45,6 +46,12 @@ ol.renderer.canvas.Map = function(container, map) {
|
||||
*/
|
||||
this.context_ = ol.dom.createCanvasContext2D();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {CanvasRenderingContext2D}
|
||||
*/
|
||||
this.renderContext_ = ol.dom.createCanvasContext2D();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {HTMLCanvasElement}
|
||||
@@ -56,6 +63,24 @@ ol.renderer.canvas.Map = function(container, map) {
|
||||
this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
|
||||
goog.dom.insertChildAt(container, this.canvas_, 0);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {HTMLCanvasElement}
|
||||
*/
|
||||
this.renderCanvas_ = this.renderContext_.canvas;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.Coordinate}
|
||||
*/
|
||||
this.pixelCenter_ = [0, 0];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.Extent}
|
||||
*/
|
||||
this.pixelExtent_ = ol.extent.createEmpty();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {boolean}
|
||||
@@ -156,14 +181,27 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
|
||||
return;
|
||||
}
|
||||
|
||||
var context = this.context_;
|
||||
var width = frameState.size[0] * frameState.pixelRatio;
|
||||
var height = frameState.size[1] * frameState.pixelRatio;
|
||||
if (this.canvas_.width != width || this.canvas_.height != height) {
|
||||
var context;
|
||||
var pixelRatio = frameState.pixelRatio;
|
||||
var width = frameState.size[0] * pixelRatio;
|
||||
var height = frameState.size[1] * pixelRatio;
|
||||
this.canvas_.width = width;
|
||||
this.canvas_.height = height;
|
||||
|
||||
var rotation = frameState.viewState.rotation;
|
||||
var pixelExtent;
|
||||
if (rotation) {
|
||||
context = this.renderContext_;
|
||||
pixelExtent = ol.extent.getForViewAndSize(this.pixelCenter_, pixelRatio,
|
||||
rotation, frameState.size, this.pixelExtent_);
|
||||
var renderWidth = ol.extent.getWidth(pixelExtent);
|
||||
var renderHeight = ol.extent.getHeight(pixelExtent);
|
||||
this.renderCanvas_.width = renderWidth + 0.5;
|
||||
this.renderCanvas_.height = renderHeight + 0.5;
|
||||
this.renderContext_.translate(Math.round((renderWidth - width) / 2),
|
||||
Math.round((renderHeight - height) / 2));
|
||||
} else {
|
||||
context.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
|
||||
context = this.context_;
|
||||
}
|
||||
|
||||
this.calculateMatrices2D(frameState);
|
||||
@@ -190,6 +228,15 @@ ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {
|
||||
}
|
||||
}
|
||||
|
||||
if (rotation) {
|
||||
this.context_.translate(width / 2, height / 2);
|
||||
this.context_.rotate(rotation);
|
||||
this.context_.drawImage(this.renderCanvas_,
|
||||
Math.round(pixelExtent[0]), Math.round(pixelExtent[1]));
|
||||
this.context_.rotate(-rotation);
|
||||
this.context_.translate(-width / 2, -height / 2);
|
||||
}
|
||||
|
||||
this.dispatchComposeEvent_(
|
||||
ol.render.EventType.POSTCOMPOSE, frameState);
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ ol.renderer.canvas.TileLayer.prototype.composeFrame = function(
|
||||
var center = viewState.center;
|
||||
var projection = viewState.projection;
|
||||
var resolution = viewState.resolution;
|
||||
var rotation = viewState.rotation;
|
||||
var size = frameState.size;
|
||||
var pixelScale = pixelRatio / resolution;
|
||||
var layer = this.getLayer();
|
||||
@@ -86,12 +85,6 @@ ol.renderer.canvas.TileLayer.prototype.composeFrame = function(
|
||||
}
|
||||
var offsetX = Math.round(pixelRatio * size[0] / 2);
|
||||
var offsetY = Math.round(pixelRatio * size[1] / 2);
|
||||
// Sub-pixel overlap between tiles to avoid gaps
|
||||
var overlap = (rotation * 180 / Math.PI) % 90 === 0 ? 0 :
|
||||
opaque ? 0.25 : 0.125;
|
||||
|
||||
renderContext.translate(offsetX, offsetY);
|
||||
renderContext.rotate(rotation);
|
||||
|
||||
// for performance reasons, context.save / context.restore is not used
|
||||
// to save and restore the transformation matrix and the opacity.
|
||||
@@ -115,25 +108,25 @@ ol.renderer.canvas.TileLayer.prototype.composeFrame = function(
|
||||
// filled by a higher resolution tile
|
||||
renderContext.save();
|
||||
renderContext.beginPath();
|
||||
renderContext.moveTo((tileExtent[0] - center[0]) * pixelScale,
|
||||
(center[1] - tileExtent[1]) * pixelScale);
|
||||
renderContext.lineTo((tileExtent[2] - center[0]) * pixelScale,
|
||||
(center[1] - tileExtent[1]) * pixelScale);
|
||||
renderContext.lineTo((tileExtent[2] - center[0]) * pixelScale,
|
||||
(center[1] - tileExtent[3]) * pixelScale);
|
||||
renderContext.lineTo((tileExtent[0] - center[0]) * pixelScale,
|
||||
(center[1] - tileExtent[3]) * pixelScale);
|
||||
renderContext.moveTo((tileExtent[0] - center[0]) * pixelScale + offsetX,
|
||||
(center[1] - tileExtent[1]) * pixelScale + offsetY);
|
||||
renderContext.lineTo((tileExtent[2] - center[0]) * pixelScale + offsetX,
|
||||
(center[1] - tileExtent[1]) * pixelScale + offsetY);
|
||||
renderContext.lineTo((tileExtent[2] - center[0]) * pixelScale + offsetX,
|
||||
(center[1] - tileExtent[3]) * pixelScale + offsetY);
|
||||
renderContext.lineTo((tileExtent[0] - center[0]) * pixelScale + offsetX,
|
||||
(center[1] - tileExtent[3]) * pixelScale + offsetY);
|
||||
renderContext.closePath();
|
||||
for (j = 0, jj = clipExtents.length; j < jj; ++j) {
|
||||
clipExtent = clipExtents[j];
|
||||
renderContext.moveTo((clipExtent[0] - center[0]) * pixelScale,
|
||||
(center[1] - clipExtent[1]) * pixelScale);
|
||||
renderContext.lineTo((clipExtent[0] - center[0]) * pixelScale,
|
||||
(center[1] - clipExtent[3]) * pixelScale);
|
||||
renderContext.lineTo((clipExtent[2] - center[0]) * pixelScale,
|
||||
(center[1] - clipExtent[3]) * pixelScale);
|
||||
renderContext.lineTo((clipExtent[2] - center[0]) * pixelScale,
|
||||
(center[1] - clipExtent[1]) * pixelScale);
|
||||
renderContext.moveTo((clipExtent[0] - center[0]) * pixelScale + offsetX,
|
||||
(center[1] - clipExtent[1]) * pixelScale + offsetY);
|
||||
renderContext.lineTo((clipExtent[0] - center[0]) * pixelScale + offsetX,
|
||||
(center[1] - clipExtent[3]) * pixelScale + offsetY);
|
||||
renderContext.lineTo((clipExtent[2] - center[0]) * pixelScale + offsetX,
|
||||
(center[1] - clipExtent[3]) * pixelScale + offsetY);
|
||||
renderContext.lineTo((clipExtent[2] - center[0]) * pixelScale + offsetX,
|
||||
(center[1] - clipExtent[1]) * pixelScale + offsetY);
|
||||
renderContext.closePath();
|
||||
}
|
||||
renderContext.clip();
|
||||
@@ -147,23 +140,19 @@ ol.renderer.canvas.TileLayer.prototype.composeFrame = function(
|
||||
// gaps caused by rounding
|
||||
origin = ol.extent.getBottomLeft(tileGrid.getTileCoordExtent(
|
||||
tileGrid.getTileCoordForCoordAndZ(center, currentZ)));
|
||||
tileOffsetX = Math.round((origin[0] - center[0]) * pixelScale);
|
||||
tileOffsetY = Math.round((center[1] - origin[1]) * pixelScale);
|
||||
tileOffsetX = offsetX + Math.round((origin[0] - center[0]) * pixelScale);
|
||||
tileOffsetY = offsetY + Math.round((center[1] - origin[1]) * pixelScale);
|
||||
renderContext.drawImage(tile.getImage(), tileGutter, tileGutter,
|
||||
tilePixelSize[0], tilePixelSize[1],
|
||||
Math.round((insertPoint[0] - origin[0]) * pixelScale / tileWidth) *
|
||||
tileWidth + tileOffsetX - overlap,
|
||||
tileWidth + tileOffsetX,
|
||||
Math.round((origin[1] - insertPoint[1]) * pixelScale / tileHeight) *
|
||||
tileHeight + tileOffsetY - overlap,
|
||||
tileWidth + 2 * overlap, tileHeight + 2 * overlap);
|
||||
tileHeight + tileOffsetY, tileWidth, tileHeight);
|
||||
if (clipExtents) {
|
||||
renderContext.restore();
|
||||
}
|
||||
}
|
||||
|
||||
renderContext.rotate(-rotation);
|
||||
renderContext.translate(-offsetX, -offsetY);
|
||||
|
||||
if (renderContext != context) {
|
||||
this.dispatchRenderEvent(renderContext, frameState, transform);
|
||||
context.drawImage(renderContext.canvas, 0, 0);
|
||||
|
||||
@@ -106,6 +106,11 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, lay
|
||||
var alpha = replayContext.globalAlpha;
|
||||
replayContext.globalAlpha = layerState.opacity;
|
||||
|
||||
var width = frameState.size[0] * pixelRatio;
|
||||
var height = frameState.size[1] * pixelRatio;
|
||||
replayContext.translate(width / 2, height / 2);
|
||||
replayContext.rotate(-rotation);
|
||||
replayContext.translate(-width / 2, -height / 2);
|
||||
replayGroup.replay(replayContext, pixelRatio, transform, rotation,
|
||||
skippedFeatureUids);
|
||||
if (vectorSource.getWrapX() && projection.canWrapX() &&
|
||||
@@ -135,6 +140,9 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, lay
|
||||
// restore original transform for render and compose events
|
||||
transform = this.getTransform(frameState, 0);
|
||||
}
|
||||
replayContext.translate(width / 2, height / 2);
|
||||
replayContext.rotate(rotation);
|
||||
replayContext.translate(-width / 2, -height / 2);
|
||||
|
||||
if (replayContext != context) {
|
||||
this.dispatchRenderEvent(replayContext, frameState, transform);
|
||||
|
||||
@@ -183,12 +183,9 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState,
|
||||
0, 0, pixelScale, -pixelScale, 0, -center[0], -center[1]);
|
||||
insertPoint = ol.geom.flat.transform.transform2D(
|
||||
ol.extent.getTopLeft(tileExtent), 0, 1, 2, insertTransform);
|
||||
replayContext.translate(offsetX, offsetY);
|
||||
replayContext.rotate(rotation);
|
||||
replayContext.drawImage(tileContext.canvas,
|
||||
Math.round(insertPoint[0]), Math.round(insertPoint[1]));
|
||||
replayContext.rotate(-rotation);
|
||||
replayContext.translate(-offsetX, -offsetY);
|
||||
Math.round(insertPoint[0] + offsetX),
|
||||
Math.round(insertPoint[1]) + offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 753 B |
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -78,7 +78,7 @@ describe('ol.rendering.Map', function() {
|
||||
map.getView().setRotation(90);
|
||||
map.getView().setCenter([10, 10]);
|
||||
expectResemble(
|
||||
map, 'spec/ol/expected/rotate-canvas.png', IMAGE_TOLERANCE, done);
|
||||
map, 'spec/ol/expected/rotate-canvas.png', 2.8, done);
|
||||
});
|
||||
|
||||
it('tests the WebGL renderer', function(done) {
|
||||
|
||||
Reference in New Issue
Block a user