Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Screen to world coord conversion when using fit container #2961

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/engine/Engine.ts
Expand Up @@ -7,7 +7,7 @@ import { polyfill } from './Polyfill';
polyfill();
import { CanUpdate, CanDraw, CanInitialize } from './Interfaces/LifecycleEvents';
import { Vector } from './Math/vector';
import { Screen, DisplayMode, ScreenDimension, Resolution } from './Screen';
import { Screen, DisplayMode, Resolution, ViewportDimension } from './Screen';
import { ScreenElement } from './ScreenElement';
import { Actor } from './Actor';
import { Timer } from './Timer';
Expand Down Expand Up @@ -121,13 +121,13 @@ export interface EngineOptions<TKnownScenes extends string = any> {
* Optionally configure the width & height of the viewport in css pixels.
* Use `viewport` instead of [[EngineOptions.width]] and [[EngineOptions.height]], or vice versa.
*/
viewport?: ScreenDimension;
viewport?: ViewportDimension;

/**
* Optionally specify the size the logical pixel resolution, if not specified it will be width x height.
* See [[Resolution]] for common presets.
*/
resolution?: ScreenDimension;
resolution?: Resolution;

/**
* Optionally specify antialiasing (smoothing), by default true (smooth pixels)
Expand Down
4 changes: 2 additions & 2 deletions src/engine/Graphics/Context/ExcaliburGraphicsContext.ts
@@ -1,6 +1,6 @@
import { Vector } from '../../Math/vector';
import { Color } from '../../Color';
import { ScreenDimension } from '../../Screen';
import { Resolution } from '../../Screen';
import { PostProcessor } from '../PostProcessor/PostProcessor';
import { AffineMatrix } from '../../Math/affine-matrix';
import { Material, MaterialOptions } from './material';
Expand Down Expand Up @@ -240,7 +240,7 @@ export interface ExcaliburGraphicsContext {
/**
* Update the context with the current viewport dimensions (used in resizing)
*/
updateViewport(resolution: ScreenDimension): void;
updateViewport(resolution: Resolution): void;

/**
* Access the debug drawing api
Expand Down
Expand Up @@ -11,7 +11,7 @@ import { Color } from '../../Color';
import { StateStack } from './state-stack';
import { GraphicsDiagnostics } from '../GraphicsDiagnostics';
import { DebugText } from './debug-text';
import { ScreenDimension } from '../../Screen';
import { Resolution } from '../../Screen';
import { PostProcessor } from '../PostProcessor/PostProcessor';
import { AffineMatrix } from '../../Math/affine-matrix';
import { Material, MaterialOptions } from './material';
Expand Down Expand Up @@ -154,7 +154,7 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex
this.__ctx.resetTransform();
}

public updateViewport(_resolution: ScreenDimension): void {
public updateViewport(_resolution: Resolution): void {
// pass
}

Expand Down
6 changes: 3 additions & 3 deletions src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
Expand Up @@ -15,7 +15,7 @@ import { Color } from '../../Color';
import { StateStack } from './state-stack';
import { Logger } from '../../Util/Log';
import { DebugText } from './debug-text';
import { ScreenDimension } from '../../Screen';
import { Resolution } from '../../Screen';
import { RenderTarget } from './render-target';
import { PostProcessor } from '../PostProcessor/PostProcessor';
import { TextureLoader } from './texture-loader';
Expand Down Expand Up @@ -199,7 +199,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
* Checks the underlying webgl implementation if the requested internal resolution is supported
* @param dim
*/
public checkIfResolutionSupported(dim: ScreenDimension): boolean {
public checkIfResolutionSupported(dim: Resolution): boolean {
// Slight hack based on this thread https://groups.google.com/g/webgl-dev-list/c/AHONvz3oQTo
let supported = true;
if (dim.width > 4096 || dim.height > 4096) {
Expand Down Expand Up @@ -435,7 +435,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
this._transform.current = AffineMatrix.identity();
}

public updateViewport(resolution: ScreenDimension): void {
public updateViewport(resolution: Resolution): void {
const gl = this.__gl;
this._ortho = this._ortho = Matrix.ortho(0, resolution.width, resolution.height, 0, 400, -400);

Expand Down
103 changes: 58 additions & 45 deletions src/engine/Screen.ts
Expand Up @@ -100,53 +100,58 @@ export enum DisplayMode {
*/
export class Resolution {
/* istanbul ignore next */
public static get SVGA(): ScreenDimension {
public static get SVGA(): Resolution {
return { width: 800, height: 600 };
}

/* istanbul ignore next */
public static get Standard(): ScreenDimension {
public static get Standard(): Resolution {
return { width: 1920, height: 1080 };
}

/* istanbul ignore next */
public static get Atari2600(): ScreenDimension {
public static get Atari2600(): Resolution {
return { width: 160, height: 192 };
}

/* istanbul ignore next */
public static get GameBoy(): ScreenDimension {
public static get GameBoy(): Resolution {
return { width: 160, height: 144 };
}

/* istanbul ignore next */
public static get GameBoyAdvance(): ScreenDimension {
public static get GameBoyAdvance(): Resolution {
return { width: 240, height: 160 };
}

/* istanbul ignore next */
public static get NintendoDS(): ScreenDimension {
public static get NintendoDS(): Resolution {
return { width: 256, height: 192 };
}

/* istanbul ignore next */
public static get NES(): ScreenDimension {
public static get NES(): Resolution {
return { width: 256, height: 224 };
}

/* istanbul ignore next */
public static get SNES(): ScreenDimension {
public static get SNES(): Resolution {
return { width: 256, height: 244 };
}
}

export type ScreenUnit = 'pixel' | 'percent';
export type ViewportUnit = 'pixel' | 'percent';

export interface ScreenDimension {
export interface Resolution {
width: number;
height: number;
}

export interface ViewportDimension {
widthUnit?: ViewportUnit;
heightUnit?: ViewportUnit;
width: number;
height: number;
widthUnit?: ScreenUnit;
heightUnit?: ScreenUnit;
}

export interface ScreenOptions {
Expand Down Expand Up @@ -182,11 +187,11 @@ export interface ScreenOptions {
* resolution will be the same as the viewport. Resolution will be overridden by [[DisplayMode.FillContainer]] and
* [[DisplayMode.FillScreen]].
*/
resolution?: ScreenDimension;
resolution?: Resolution;
/**
* Visual viewport size in css pixel, if resolution is not specified it will be the same as the viewport
*/
viewport: ScreenDimension;
viewport: ViewportDimension;
/**
* Set the display mode of the screen, by default DisplayMode.Fixed.
*/
Expand All @@ -200,11 +205,11 @@ export interface ScreenResizeEvent {
/**
* Current viewport in css pixels of the screen
*/
viewport: ScreenDimension;
viewport: ViewportDimension;
/**
* Current resolution in world pixels of the screen
*/
resolution: ScreenDimension;
resolution: Resolution;
}

/**
Expand Down Expand Up @@ -263,13 +268,13 @@ export class Screen {
private _canvas: HTMLCanvasElement;
private _antialiasing: boolean = true;
private _canvasImageRendering: 'auto' | 'pixelated' = 'auto';
private _contentResolution: ScreenDimension;
private _contentResolution: Resolution;
private _browser: BrowserEvents;
private _camera: Camera;
private _resolution: ScreenDimension;
private _resolutionStack: ScreenDimension[] = [];
private _viewport: ScreenDimension;
private _viewportStack: ScreenDimension[] = [];
private _resolution: Resolution;
private _resolutionStack: Resolution[] = [];
private _viewport: ViewportDimension;
private _viewportStack: ViewportDimension[] = [];
private _pixelRatioOverride: number | null = null;
private _displayMode: DisplayMode;
private _isFullScreen = false;
Expand Down Expand Up @@ -435,28 +440,25 @@ export class Screen {
}
}

public get resolution(): ScreenDimension {
public get resolution(): Resolution {
return this._resolution;
}

public set resolution(resolution: ScreenDimension) {
if (resolution.heightUnit === 'percent' || resolution.widthUnit === 'percent') {
throw Error('Screen resolution only supports pixels not percentage sizes');
}
public set resolution(resolution: Resolution) {
this._resolution = resolution;
}

/**
* Returns screen dimensions in pixels or percentage
*/
public get viewport(): ScreenDimension {
public get viewport(): ViewportDimension {
if (this._viewport) {
return this._viewport;
}
return this._resolution;
}

public set viewport(viewport: ScreenDimension) {
public set viewport(viewport: ViewportDimension) {
this._viewport = viewport;
}

Expand Down Expand Up @@ -484,11 +486,11 @@ export class Screen {
this.viewport = { ...this.viewport };
}

public peekViewport(): ScreenDimension {
public peekViewport(): ViewportDimension {
return this._viewportStack[this._viewportStack.length - 1];
}

public peekResolution(): ScreenDimension {
public peekResolution(): Resolution {
return this._resolutionStack[this._resolutionStack.length - 1];
}

Expand Down Expand Up @@ -608,6 +610,13 @@ export class Screen {
return document.exitFullscreen();
}

private _viewportToPixels(viewport: ViewportDimension) {
return {
width: viewport.widthUnit === 'percent' ? this.canvas.offsetWidth : viewport.width,
height: viewport.heightUnit === 'percent' ? this.canvas.offsetHeight : viewport.height
} satisfies ViewportDimension;
}

/**
* Takes a coordinate in normal html page space, for example from a pointer move event, and translates it to
* Excalibur screen space.
Expand All @@ -626,24 +635,26 @@ export class Screen {
newY -= getPosition(this._canvas).y;
}

const viewport = this._viewportToPixels(this.viewport);

// if fullscreen api on it centers with black bars
// we need to adjust the screen to world coordinates in this case
if (this._isFullScreen) {
if (window.innerWidth / this.aspectRatio < window.innerHeight) {
const screenHeight = window.innerWidth / this.aspectRatio;
const screenMarginY = (window.innerHeight - screenHeight) / 2;
newY = ((newY - screenMarginY) / screenHeight) * this.viewport.height;
newX = (newX / window.innerWidth) * this.viewport.width;
newY = ((newY - screenMarginY) / screenHeight) * viewport.height;
newX = (newX / window.innerWidth) * viewport.width;
} else {
const screenWidth = window.innerHeight * this.aspectRatio;
const screenMarginX = (window.innerWidth - screenWidth) / 2;
newX = ((newX - screenMarginX) / screenWidth) * this.viewport.width;
newY = (newY / window.innerHeight) * this.viewport.height;
newX = ((newX - screenMarginX) / screenWidth) * viewport.width;
newY = (newY / window.innerHeight) * viewport.height;
}
}

newX = (newX / this.viewport.width) * this.resolution.width;
newY = (newY / this.viewport.height) * this.resolution.height;
newX = (newX / viewport.width) * this.resolution.width;
newY = (newY / viewport.height) * this.resolution.height;

// offset by content area
newX = newX - this.contentArea.left;
Expand All @@ -666,20 +677,22 @@ export class Screen {

// no need to offset by content area, drawing is already offset by this

newX = (newX / this.resolution.width) * this.viewport.width;
newY = (newY / this.resolution.height) * this.viewport.height;
const viewport = this._viewportToPixels(this.viewport);

newX = (newX / this.resolution.width) * viewport.width;
newY = (newY / this.resolution.height) * viewport.height;

if (this._isFullScreen) {
if (window.innerWidth / this.aspectRatio < window.innerHeight) {
const screenHeight = window.innerWidth / this.aspectRatio;
const screenMarginY = (window.innerHeight - screenHeight) / 2;
newY = (newY / this.viewport.height) * screenHeight + screenMarginY;
newX = (newX / this.viewport.width) * window.innerWidth;
newY = (newY / viewport.height) * screenHeight + screenMarginY;
newX = (newX / viewport.width) * window.innerWidth;
} else {
const screenWidth = window.innerHeight * this.aspectRatio;
const screenMarginX = (window.innerWidth - screenWidth) / 2;
newX = (newX / this.viewport.width) * screenWidth + screenMarginX;
newY = (newY / this.viewport.height) * window.innerHeight;
newX = (newX / viewport.width) * screenWidth + screenMarginX;
newY = (newY / viewport.height) * window.innerHeight;
}
}

Expand Down Expand Up @@ -908,7 +921,7 @@ export class Screen {
} satisfies ScreenResizeEvent);
}

private _computeFitAndFill(vw: number, vh: number, viewport?: ScreenDimension) {
private _computeFitAndFill(vw: number, vh: number, viewport?: ViewportDimension) {
this.viewport = viewport ?? {
width: vw,
height: vh
Expand Down Expand Up @@ -1046,8 +1059,8 @@ export class Screen {
const aspect = this.aspectRatio;
let adjustedWidth = 0;
let adjustedHeight = 0;
let widthUnit: ScreenUnit = 'pixel';
let heightUnit: ScreenUnit = 'pixel';
let widthUnit: ViewportUnit = 'pixel';
let heightUnit: ViewportUnit = 'pixel';
const parent = this.canvas.parentElement;
if (parent.clientWidth / aspect < parent.clientHeight) {
this.canvas.style.width = '100%';
Expand Down