/** * Copyright(c) Live2D Inc. All rights reserved. * * Use of this source code is governed by the Live2D Open Software license * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. */ import * as LAppDefine from './lappdefine'; import { LAppGlManager } from './lappglmanager'; import { LAppLive2DManager } from './lapplive2dmanager'; import { LAppPal } from './lapppal'; import { LAppTextureManager } from './lapptexturemanager'; import { LAppView } from './lappview'; import { ResourceModel } from '@/lib/protocol'; /** * Canvasに関連する操作を取りまとめるクラス */ export class LAppSubdelegate { /** * コンストラクタ */ public constructor() { this._canvas = null; this._glManager = new LAppGlManager(); this._textureManager = new LAppTextureManager(); this._live2dManager = new LAppLive2DManager(); this._view = new LAppView(); this._frameBuffer = null; this._captured = false; } /** * デストラクタ相当の処理 */ public release(): void { this._resizeObserver.unobserve(this._canvas); this._resizeObserver.disconnect(); this._resizeObserver = null; this._live2dManager.release(); this._live2dManager = null; this._view.release(); this._view = null; this._textureManager.release(); this._textureManager = null; this._glManager.release(); this._glManager = null; } /** * APPに必要な物を初期化する。 */ public initialize(canvas: HTMLCanvasElement): boolean { if (!this._glManager.initialize(canvas)) { return false; } this._canvas = canvas; if (LAppDefine.CanvasSize === 'auto') { this.resizeCanvas(); } else { canvas.width = LAppDefine.CanvasSize.width; canvas.height = LAppDefine.CanvasSize.height; } this._textureManager.setGlManager(this._glManager); const gl = this._glManager.getGl(); if (!this._frameBuffer) { this._frameBuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); } // 透過設定 gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // AppViewの初期化 this._view.initialize(this); this._view.initializeSprite(); this._live2dManager.initialize(this); this._resizeObserver = new ResizeObserver( (entries: ResizeObserverEntry[], observer: ResizeObserver) => this.resizeObserverCallback.call(this, entries, observer) ); this._resizeObserver.observe(this._canvas); return true; } /** * Resize canvas and re-initialize view. */ public onResize(): void { this.resizeCanvas(); this._view.initialize(this); this._view.initializeSprite(); } private resizeObserverCallback( entries: ResizeObserverEntry[], observer: ResizeObserver ): void { if (LAppDefine.CanvasSize === 'auto') { this._needResize = true; } } /** * ループ処理 */ public update(): void { if (this._glManager.getGl().isContextLost()) { return; } // キャンバスのサイズが変わっている場合はリサイズに必要な処理をする。 if (this._needResize) { this.onResize(); this._needResize = false; } const gl = this._glManager.getGl(); // 画面的初始化.默认就是白色(rgb: 1.0, 1.0, 1.0)且不透明(alpha: 1.0) gl.clearColor(0.0, 0.0, 0.0, 0.0); // 启用深度测试 gl.enable(gl.DEPTH_TEST); // 附近的物体会掩盖远处的物体(也就是说如果近的点已经被渲染了,一个新的像素更远就不用渲染了) gl.depthFunc(gl.LEQUAL); // 清除颜色缓冲区和深度缓冲区,每次渲染的时候都做清除处理 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 设置深度缓冲区的初始值1.0, 也就是大家都是最远的初始化值 gl.clearDepth(1.0); // 透明设置 gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // 描画更新 this._view.render(); } /** * シェーダーを登録する。 */ public createShader(): WebGLProgram { const gl = this._glManager.getGl(); // バーテックスシェーダーのコンパイル const vertexShaderId = gl.createShader(gl.VERTEX_SHADER); if (vertexShaderId == null) { LAppPal.printMessage('failed to create vertexShader'); return null; } const vertexShader: string = 'precision mediump float;' + 'attribute vec3 position;' + 'attribute vec2 uv;' + 'varying vec2 vuv;' + 'void main(void)' + '{' + ' gl_Position = vec4(position, 1.0);' + ' vuv = uv;' + '}'; gl.shaderSource(vertexShaderId, vertexShader); gl.compileShader(vertexShaderId); // フラグメントシェーダのコンパイル const fragmentShaderId = gl.createShader(gl.FRAGMENT_SHADER); if (fragmentShaderId == null) { LAppPal.printMessage('failed to create fragmentShader'); return null; } const fragmentShader: string = 'precision mediump float;' + 'varying vec2 vuv;' + 'uniform sampler2D texture;' + 'void main(void)' + '{' + ' gl_FragColor = texture2D(texture, vuv);' + '}'; gl.shaderSource(fragmentShaderId, fragmentShader); gl.compileShader(fragmentShaderId); // プログラムオブジェクトの作成 const programId = gl.createProgram(); gl.attachShader(programId, vertexShaderId); gl.attachShader(programId, fragmentShaderId); gl.deleteShader(vertexShaderId); gl.deleteShader(fragmentShaderId); // リンク gl.linkProgram(programId); gl.useProgram(programId); return programId; } public getTextureManager(): LAppTextureManager { return this._textureManager; } public getFrameBuffer(): WebGLFramebuffer { return this._frameBuffer; } public getCanvas(): HTMLCanvasElement { return this._canvas; } public getGlManager(): LAppGlManager { return this._glManager; } public getLive2DManager(): LAppLive2DManager { return this._live2dManager; } /** * Resize the canvas to fill the screen. */ private resizeCanvas(): void { this._canvas.width = this._canvas.clientWidth * window.devicePixelRatio; this._canvas.height = this._canvas.clientHeight * window.devicePixelRatio; const gl = this._glManager.getGl(); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); } /** * マウスダウン、タッチダウンしたときに呼ばれる。 */ public onPointBegan(pageX: number, pageY: number): void { if (!this._view) { LAppPal.printMessage('view notfound'); return; } this._captured = true; const localX: number = pageX - this._canvas.offsetLeft; const localY: number = pageY - this._canvas.offsetTop; this._view.onTouchesBegan(localX, localY); } /** * マウスポインタが動いたら呼ばれる。 */ public onPointMoved(pageX: number, pageY: number): void { if (!this._captured) { return; } const localX: number = pageX - this._canvas.offsetLeft; const localY: number = pageY - this._canvas.offsetTop; this._view.onTouchesMoved(localX, localY); } /** * クリックが終了したら呼ばれる。 */ public onPointEnded(pageX: number, pageY: number): void { this._captured = false; if (!this._view) { LAppPal.printMessage('view notfound'); return; } const localX: number = pageX - this._canvas.offsetLeft; const localY: number = pageY - this._canvas.offsetTop; this._view.onTouchesEnded(localX, localY); } /** * タッチがキャンセルされると呼ばれる。 */ public onTouchCancel(pageX: number, pageY: number): void { this._captured = false; if (!this._view) { LAppPal.printMessage('view notfound'); return; } const localX: number = pageX - this._canvas.offsetLeft; const localY: number = pageY - this._canvas.offsetTop; this._view.onTouchesEnded(localX, localY); } public isContextLost(): boolean { return this._glManager.getGl().isContextLost(); } public changeCharacter(character: ResourceModel | null) { // _subdelegates中只有一个画布, 所以设置第一个即可 this._live2dManager.changeCharacter(character); } private _canvas: HTMLCanvasElement; /** * View情報 */ private _view: LAppView; /** * テクスチャマネージャー */ private _textureManager: LAppTextureManager; private _frameBuffer: WebGLFramebuffer; private _glManager: LAppGlManager; private _live2dManager: LAppLive2DManager; /** * ResizeObserver */ private _resizeObserver: ResizeObserver; /** * クリックしているか */ private _captured: boolean; private _needResize: boolean; }