Commit 7730c34f authored by rockyl's avatar rockyl

scilla新模式开始

parents
Pipeline #140314 failed with stages
in 0 seconds
# Created by .ignore support plugin (hsz.mobi)
/dist/
/node_modules/
\ No newline at end of file
1. 常量补全,不要出现魔法字符串
2. 成员变量的默认值直接在声明时赋值即可,不需要在构造方法里赋值,不然编辑器无法识别默认值
3. 不需要暴露出来的成员变量加上'$'或者'_'前缀,能被编辑器过滤掉
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<meta name="viewport" content="width=device-width,initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="full-screen" content="true" />
<meta name="screen-orientation" content="portrait" />
<meta name="x5-fullscreen" content="true" />
<meta name="360-fullscreen" content="true" />
<!-- <meta name="viewport" content="width=device-width,minimum-scale=1.0,user-scalable=no"> -->
<style>
html,
body {
padding: 0;
margin: 0;
border: 0;
width: 100%;
height: 100%;
overflow: hidden;
position: absolute;
background-color: white;
}
/* .top {
width: 100%;
height: 100px;
} */
</style>
</head>
<body>
<!-- <div id="testCanvas" style="background-color: #000000; width: 500px; height: 500px; margin: auto"></div> -->
<!-- <img src="" id="img" /> -->
<div id="cusEngine" style="line-height:0;font-size:0"></div>
<!-- <canvas id='my-canvas' width='480' height='400'>
对不起,您的浏览器未支持H5内容.
</canvas> -->
<!-- <script>
console.log(window["p2"])
</script> -->
<!-- 只是视图,全局量为p2,用于测试,pixi估计很久的版本 -->
<!-- <script type="text/javascript" src="./p2.renderer.js"></script>
<script type="text/javascript" src="./index.js"></script> -->
<script type="text/javascript" src="dist/bundle.js"></script>
</body>
<script>
var cusMaxScore = 0;
function cusInitShareFunc(t) {
wx.ready(function () {
wx.onMenuShareTimeline({
title: "我在南塘河赛龙舟得了" + t + "分,快来比一比谁是赛龙舟高手。",
link: window.shareLink,
imgUrl: window.shareImgUrl,
success: function (t) {
}
});
wx.onMenuShareAppMessage({
title: "我在南塘河赛龙舟得了" + t + "分,快来比一比谁是赛龙舟高手。",
desc: "撞蛋、薄饼、粽子,体验不一样的赛龙舟!",
link: window.shareLink,
imgUrl: window.shareImgUrl,
success: function (t) {
}
})
});
};
</script>
</html>
\ No newline at end of file
{
"name": "scilla-engine",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript": "^1.0.1",
"rollup-plugin-uglify": "^6.0.2",
"tslib": "^1.10.0",
"typescript": "^3.5.3"
}
}
/**
* Created by rockyl on 2018/11/16.
*/
const resolve = require('rollup-plugin-node-resolve');
const typescript = require('rollup-plugin-typescript');
const {uglify} = require('rollup-plugin-uglify');
const name = 'Main';
export default {
input: 'src/Main.ts',
output: [
{
file: `dist/bundle.js`,
format: 'umd',
name,
sourcemap: true,
}
],
plugins: [
resolve({
browser: true,
}),
typescript({
typescript: require('typescript'),
tslib: require('tslib'),
declaration: false,
}),
//uglify(),
]
};
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
/**
* 考虑是否需要
*/
export abstract class HashObject {
protected _instanceId: number = 0;
protected _instanceType: string = "HashObject";
protected static _object_id = 0;
constructor() {
this._instanceId = HashObject._object_id++;
}
/**
* 每一个对象都会有一个唯一的id码。
* @property instanceId
* @public
* @since 1.0.0
* @return {number}
* @readonly
* @example
* //获取 对象唯一码
* trace(this.instanceId);
*/
public get instanceId(): number {
return this._instanceId;
}
/**
* 每一个类都有一个实例类型字符串,通过这个字符串,你能知道这个实例是从哪个类实例而来
* @property instanceType
* @since 1.0.3
* @public
* @return {string}
* @readonly
*/
public get instanceType(): string {
return this._instanceType;
}
/**
* 销毁一个对象
* 销毁之前一定要从显示对象移除,否则将会出错
* @method destroy
* @since 2.0.0
* @public
* @return {void}
*/
abstract destroy(): void;
}
/**
* Created by rockyl on 2018/11/5.
*/
import {HashObject} from "../HashObject";
import {DisplayObject} from "../display/DisplayObject";
// import {EngineConfig} from "../engine-config";
const interactiveMap = [
'_dealGlobalTouchBegin',
'_dealGlobalTouchMove',
'_dealGlobalTouchEnd',
];
/**
* 组件基类
*/
export class Component extends HashObject {
/**
* 所依附的实体
*/
entity: DisplayObject;
protected delayCallbacks = [];
//是否有效
protected _enabled: boolean /*= EngineConfig.componentEnabled;*/
private _firstUpdate: boolean /*= EngineConfig.componentEnabled;*/
constructor(){
super();
this._instanceType="Component";
this.onCreate();
}
/**
* 是否有效状态
*/
get enabled(): boolean {
return this._enabled;
}
set enabled(value: boolean) {
if (this._enabled !== value) {
this._enabled = value;
// if (this.entity && this.entity.isActive) {
// if (!EngineConfig.editorMode) {
// if (this._enabled) {
// this.onEnable();
// } else {
// this.onDisable();
// }
// }
// }
}
}
/**
* 装配实体
* @param entity
*/
_setup(entity: DisplayObject) {
this.entity = entity;
}
/**
* 卸载实体
*/
_unSetup() {
this.entity = null;
}
/**
* 当组件被创建时
*/
onCreate() {
}
$onAwake() {
// if (!EngineConfig.editorMode) {
// this.onAwake();
// }
}
/**
* 当组件被唤醒时
*/
onAwake() {
}
/**
* 当组件生效时
*/
onEnable() {
}
/**
* 当组件失效时
*/
onDisable() {
}
$onUpdate(t) {
this.onUpdate(t);
if (!this._firstUpdate) {
this.invokeDelayCallback(t);
}
this._firstUpdate = false;
}
$onEditorUpdate(t) {
this.onEditorUpdate(t);
}
$afterUpdate() {
this.afterUpdate();
}
$afterEditorUpdate() {
this.afterEditorUpdate();
}
private invokeDelayCallback(t) {
for (let i = 0, li = this.delayCallbacks.length; i < li; i++) {
let {callback, once} = this.delayCallbacks[i];
if (once) {
this.delayCallbacks.splice(i, 1);
i--;
li--;
}
callback.call(this, t);
}
}
/**
* 当时钟更新时
* @param t 从引擎开始到当前的毫秒数
*/
onUpdate(t) {
}
onEditorUpdate(t) {
}
/**
* 当子实体的时钟更新结束后
*/
afterUpdate() {
}
afterEditorUpdate() {
}
$onSleep() {
// if (!EngineConfig.editorMode) {
// this.onSleep();
// }
}
/**
* 当组件沉睡时
*/
onSleep() {
}
/**
* 当组件被销毁时
*/
onDestroy() {
}
/**
* 当被监听的属性被修改时
* @param value
* @param key
* @param oldValue
*/
protected onModify(value, key, oldValue) {
}
private getDelayCallback(callback) {
let result;
for (let item of this.delayCallbacks) {
if (item.callback == callback) {
result = item;
break;
}
}
return result;
}
/**
* 执行延迟回调
* @param callback
* @param once 是否只执行一次
*/
callOnNextTick(callback, once = true) {
const item = this.getDelayCallback(callback);
if (!item) {
this.delayCallbacks.push({callback, once});
}
}
cancelOnNextTick(callback) {
const item = this.getDelayCallback(callback);
const index = this.delayCallbacks.indexOf(item);
if (index >= 0) {
this.delayCallbacks.splice(index, 1);
}
}
/**
* 当交互时
* @param type
* @param event
*/
onInteract(type, event) {
try {
const hitOn = this[interactiveMap[type]](event);
return hitOn;
} catch (e) {
console.warn(e);
}
}
_dealGlobalTouchBegin(e) {
return this.onGlobalTouchBegin(e);
}
_dealGlobalTouchMove(e) {
return this.onGlobalTouchMove(e);
}
_dealGlobalTouchEnd(e) {
return this.onGlobalTouchEnd(e);
}
/**
* 当全局触摸开始
* @param e
*/
onGlobalTouchBegin(e) {
return false;
}
/**
* 当全触摸移动
* @param e
*/
onGlobalTouchMove(e) {
return false;
}
/**
* 当全触摸结束
* @param e
*/
onGlobalTouchEnd(e) {
return false;
}
destroy(){
}
}
/**
* 版本号
* @static
* @constant
* @name VERSION
* @type {string}
*/
export const VERSION = "1.0";
/**
* 屏幕分辨率,尽量最大为2,否则画布太大影响性能
*/
export let devicePixelRatio: number = window.devicePixelRatio ? (window.devicePixelRatio > 2 ? 2 : window.devicePixelRatio) : 1;
/**
* 一个 StageScaleMode 中指定要使用哪种缩放模式的值。以下是有效值:
* StageScaleMode.EXACT_FIT -- 整个应用程序在指定区域中可见,但不尝试保持原始高宽比。可能会发生扭曲,应用程序可能会拉伸或压缩显示。
* StageScaleMode.SHOW_ALL -- 整个应用程序在指定区域中可见,且不发生扭曲,同时保持应用程序的原始高宽比。应用程序的两侧可能会显示边框。
* StageScaleMode.NO_BORDER -- 整个应用程序填满指定区域,不发生扭曲,但有可能进行一些裁切,同时保持应用程序的原始高宽比。
* StageScaleMode.NO_SCALE -- 整个应用程序的大小固定,因此,即使播放器窗口的大小更改,它也会保持不变。如果播放器窗口比内容小,则可能进行一些裁切。
* StageScaleMode.FIXED_WIDTH -- 整个应用程序的宽固定,因此,即使播放器窗口的大小更改,它也会保持不变。如果播放器窗口比内容小,则可能进行一些裁切。
* StageScaleMode.FIXED_HEIGHT -- 整个应用程序的高固定,因此,即使播放器窗口的大小更改,它也会保持不变。如果播放器窗口比内容小,则可能进行一些裁切。
* @property StageScaleMode
* @type {Object}
* @public
* @since 1.0.0
* @static
* @example
* //动态更改stage的对齐方式示例
* //以下代码放到一个舞台的显示对象的构造函数中
* let s=this;
* s.addEventListener(Event.ADD_TO_STAGE,function(e){
* let i=0;
* s.stage.addEventListener(MouseEvent.CLICK,function(e){
* let aList=[StageScaleMode.EXACT_FIT,StageScaleMode.NO_BORDER,StageScaleMode.NO_SCALE,StageScaleMode.SHOW_ALL,StageScaleMode.FIXED_WIDTH,StageScaleMode.FIXED_HEIGHT]
* let state=e.currentTarget;
* state.scaleMode=aList[i];
* state.resize();
* if(i>5){i=0;}
* }
* }
*
*/
export let StageScaleMode: { EXACT_FIT: string, NO_BORDER: string, NO_SCALE: string, SHOW_ALL: string, FIXED_WIDTH: string, FIXED_HEIGHT: string } = {
EXACT_FIT: "exactFit",
NO_BORDER: "noBorder",
NO_SCALE: "noScale",
SHOW_ALL: "showAll",
FIXED_WIDTH: "fixedWidth",
FIXED_HEIGHT: "fixedHeight"
};
/**
* 获取设备号
*/
export const osType: string = (function () {
let n = navigator.userAgent.toLocaleLowerCase();
let reg1 = /android/;
let reg2 = /iphone|ipod|ipad/;
if (reg1.test(n)) {
return "android";
} else if (reg2.test(n)) {
return "ios"
} else {
return "pc";
}
})();
console.log(devicePixelRatio, osType)
let PI: number = Math.PI;
let HalfPI: number = PI >> 1;
let PacPI: number = PI + HalfPI;
/**
* @method cos
* @private
* @param {number} angle,弧度制
* @return {number}
*/
export function cos(angle: number): number {
switch (angle) {
case HalfPI:
case -PacPI:
return 0;
case PI:
case -PI:
return -1;
case PacPI:
case -HalfPI:
return 0;
default:
return Math.cos(angle);
}
}
/**
* @method sin
* @param {number} angle,弧度制
* @return {number}
*/
export function sin(angle: number): number {
switch (angle) {
case HalfPI:
case -PacPI:
return 1;
case PI:
case -PI:
return 0;
case PacPI:
case -HalfPI:
return -1;
default:
return Math.sin(angle);
}
}
/**
* Two Pi.
*
* @static
* @constant
* @type {number}
*/
export const PI_2: number = PI * 2;
/**
* Conversion factor for converting radians to degrees.
* 弧度转角度
* @static
* @constant
* @type {number}
*/
export const RAD_TO_DEG: number = 180 / PI;
/**
* Conversion factor for converting degrees to radians.
* 角度转弧度
* @static
* @constant
* @type {number}
*/
export const DEG_TO_RAD: number = PI / 180;
/**
* Constant to identify the Renderer Type.
*
* @static
* @constant
* @name RENDERER_TYPE
* @type {object}
* @property {number} UNKNOWN - Unknown render type.
* @property {number} WEBGL - WebGL render type.
* @property {number} CANVAS - Canvas render type.
*/
export const RENDERER_TYPE = {
UNKNOWN: 0,
WEBGL: 1,
CANVAS: 2,
};
/**
* Various blend modes supported by
*
* IMPORTANT - The WebGL renderer only supports the NORMAL, ADD, MULTIPLY and SCREEN blend modes.
* Anything else will silently act like NORMAL.
*
* @static
* @constant
* @name BLEND_MODES
* @type {object}
* @property {number} NORMAL
* @property {number} ADD
* @property {number} MULTIPLY
* @property {number} SCREEN
* @property {number} OVERLAY
* @property {number} DARKEN
* @property {number} LIGHTEN
* @property {number} COLOR_DODGE
* @property {number} COLOR_BURN
* @property {number} HARD_LIGHT
* @property {number} SOFT_LIGHT
* @property {number} DIFFERENCE
* @property {number} EXCLUSION
* @property {number} HUE
* @property {number} SATURATION
* @property {number} COLOR
* @property {number} LUMINOSITY
*/
export const BLEND_MODES: any = {
NORMAL: 0,
ADD: 1,
MULTIPLY: 2,
SCREEN: 3,
OVERLAY: 4,
DARKEN: 5,
LIGHTEN: 6,
COLOR_DODGE: 7,
COLOR_BURN: 8,
HARD_LIGHT: 9,
SOFT_LIGHT: 10,
DIFFERENCE: 11,
EXCLUSION: 12,
HUE: 13,
SATURATION: 14,
COLOR: 15,
LUMINOSITY: 16,
NORMAL_NPM: 17,
ADD_NPM: 18,
SCREEN_NPM: 19,
};
/**
* Various webgl draw modes. These can be used to specify which GL drawMode to use
* under certain situations and renderers.
*
* @static
* @constant
* @name DRAW_MODES
* @type {object}
* @property {number} POINTS
* @property {number} LINES
* @property {number} LINE_LOOP
* @property {number} LINE_STRIP
* @property {number} TRIANGLES
* @property {number} TRIANGLE_STRIP
* @property {number} TRIANGLE_FAN
*/
export const DRAW_MODES = {
POINTS: 0,
LINES: 1,
LINE_LOOP: 2,
LINE_STRIP: 3,
TRIANGLES: 4,
TRIANGLE_STRIP: 5,
TRIANGLE_FAN: 6,
};
/**
* The scale modes that are supported by
*
* The {@link settings.SCALE_MODE} scale mode affects the default scaling mode of future operations.
* It can be re-assigned to either LINEAR or NEAREST, depending upon suitability.
*
* @static
* @constant
* @name SCALE_MODES
* @type {object}
* @property {number} LINEAR Smooth scaling
* @property {number} NEAREST Pixelating scaling
*/
export const SCALE_MODES = {
LINEAR: 0,
NEAREST: 1,
};
/**
* The wrap modes that are supported by
*
* The {@link settings.WRAP_MODE} wrap mode affects the default wrapping mode of future operations.
* It can be re-assigned to either CLAMP or REPEAT, depending upon suitability.
* If the texture is non power of two then clamp will be used regardless as webGL can
* only use REPEAT if the texture is po2.
*
* This property only affects WebGL.
*
* @static
* @constant
* @name WRAP_MODES
* @type {object}
* @property {number} CLAMP - The textures uvs are clamped
* @property {number} REPEAT - The texture uvs tile and repeat
* @property {number} MIRRORED_REPEAT - The texture uvs tile and repeat with mirroring
*/
export const WRAP_MODES: { CLAMP: number, REPEAT: number, MIRRORED_REPEAT: number } = {
CLAMP: 0,
REPEAT: 1,
MIRRORED_REPEAT: 2,
};
/**
* The gc modes that are supported by
*
* The {@link settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO
* If set to GC_MODE, the renderer will occasionally check textures usage. If they are not
* used for a specified period of time they will be removed from the GPU. They will of course
* be uploaded again when they are required. This is a silent behind the scenes process that
* should ensure that the GPU does not get filled up.
*
* Handy for mobile devices!
* This property only affects WebGL.
*
* @static
* @constant
* @name GC_MODES
* @type {object}
* @property {number} AUTO - Garbage collection will happen periodically automatically
* @property {number} MANUAL - Garbage collection will need to be called manually
*/
export const GC_MODES = {
AUTO: 0,
MANUAL: 1,
};
/**
* Regexp for image type by extension.
*
* @static
* @constant
* @type {RegExp|string}
* @example `image.png`
*/
export const URL_FILE_EXTENSION: any = /\.(\w{3,4})(?:$|\?|#)/i;
/**
* Regexp for data URI.
* Based on: {@link https://github.com/ragingwind/data-uri-regex}
*
* @static
* @constant
* @name DATA_URI
* @example data:image/png;base64
*/
export const DATA_URI: any = /^\s*data:(?:([\w-]+)\/([\w+.-]+))?(?:;charset=([\w-]+))?(?:;(base64))?,(.*)/i;
/**
* 各种形状
* @static
* @constant
* @name SHAPES
* @type {object}
* @property {number} POLY Polygon 多边形
* @property {number} RECT Rectangle 矩形
* @property {number} CIRC Circle 圆形
* @property {number} ELIP Ellipse 椭圆
* @property {number} RREC Rounded Rectangle 圆角矩形
*/
export const SHAPES = {
POLY: 0,
RECT: 1,
CIRC: 2,
ELIP: 3,
RREC: 4,
};
/**
* 着色器浮点精度
* @static
* @constant
* @name PRECISION
* @type {object}
* @property {string} LOW='lowp'
* @property {string} MEDIUM='mediump'
* @property {string} HIGH='highp'
*/
export const PRECISION = {
LOW: 'lowp',
MEDIUM: 'mediump',
HIGH: 'highp',
};
/**
* Constants that define the type of gradient on text.
*
* @static
* @constant
* @name TEXT_GRADIENT
* @type {object}
* @property {number} LINEAR_VERTICAL Vertical gradient
* @property {number} LINEAR_HORIZONTAL Linear gradient
*/
export const TEXT_GRADIENT = {
LINEAR_VERTICAL: 0,
LINEAR_HORIZONTAL: 1,
};
/**
* Represents the update priorities used by internal PIXI classes when registered with
* the {@link ticker.Ticker} object. Higher priority items are updated first and lower
* priority items, such as render, should go later.
*
* @static
* @constant
* @name UPDATE_PRIORITY
* @type {object}
* @property {number} INTERACTION=50 Highest priority, used for {@link interaction.InteractionManager}
* @property {number} HIGH=25 High priority updating, {@link VideoBaseTexture} and {@link extras.AnimatedSprite}
* @property {number} NORMAL=0 Default priority for ticker events, see {@link ticker.Ticker#add}.
* @property {number} LOW=-25 Low priority used for {@link Application} rendering.
* @property {number} UTILITY=-50 Lowest priority used for {@link prepare.BasePrepare} utility.
*/
export const UPDATE_PRIORITY = {
INTERACTION: 50,
HIGH: 25,
NORMAL: 0,
LOW: -25,
UTILITY: -50,
};
import { DisplayObject } from "./DisplayObject";
import Texture from "../texture/Texture";
import { ObservablePoint, Rectangle } from "../math";
import { sign, TextureCache, hex2rgb } from "../utils";
import { WebglRenderer } from "../renderers/WebglRenderer";
import { Event } from "../events/Event";
import CanvasRenderer from "../renderers/CanvasRenderer";
import { SCALE_MODES } from "../const";
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
/**
* 继承显示对象,不继承容器
*/
export class Bitmap extends DisplayObject {
/**
* 关于贴图的锚点,0到1,默认未texture自己的
*/
private _anchorTexture: ObservablePoint
/**
* 使用的贴图
* @member {Texture}
*/
_texture: Texture;
/**
* 宽度,见DisplayObject,设置过width才有,用于确定scale
*/
_width: number;
/**
*
*/
_height: number;
/**
* this is used to store the vertex data of the sprite (basically a quad)
*/
vertexData: Float32Array;
/**
* This is used to calculate the bounds of the object IF it is a trimmed sprite
*/
vertexTrimmedData: Float32Array;
_transformID: number;
_textureID: number;
_transformTrimmedID: number;
_textureTrimmedID: number;
/**
* Plugin that is responsible for rendering this element.
* Allows to customize the rendering process without overriding '_renderWebGL' & '_renderCanvas' methods.
* 指定渲染用的渲染器插件
*/
pluginName: string;
indices: Uint16Array;
size: number;
start: number;
uvs: Float32Array;
/**
* @param {Texture} texture - The texture for this sprite
*/
constructor(texture?: Texture) {
super();
this._anchorTexture = new ObservablePoint(
this._onAnchorUpdate,
this,
(texture ? texture.defaultAnchor.x : 0),
(texture ? texture.defaultAnchor.y : 0)
);
this._texture = null;
this._width = 0;
this._height = 0;
this.uvs = null;
// 下面texture set时用到
this._onTextureUpdate = this._onTextureUpdate.bind(this);
// call texture setter
this.texture = texture || Texture.EMPTY;
this.vertexData = new Float32Array(8);
this.vertexTrimmedData = null;
this._transformID = -1;
this._textureID = -1;
this._transformTrimmedID = -1;
this._textureTrimmedID = -1;
// Batchable stuff..
// TODO could make this a mixin?
this.indices = indices;
this.size = 4;
this.start = 0;
this.pluginName = 'sprite';
}
/**
* 修改_localBoundsSelf
*
* @private
*/
_onTextureUpdate() {
this._textureID = -1;
this._textureTrimmedID = -1;
this.uvs = this._texture._uvs.uvsFloat32;
//设置过宽高的话,就需要改变缩放值
// so if _width is 0 then width was not set..
if (this._width) {
this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width;
}
if (this._height) {
this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height;
}
//修改_localBoundsSelf
const width = this._texture.orig.width;
const height = this._texture.orig.height;
this._localBoundsSelf.x = -width * this.anchorTexture.x;
this._localBoundsSelf.y = -height * this.anchorTexture.y;
this._localBoundsSelf.width = width;
this._localBoundsSelf.height = height;
}
/**
* Called when the anchor position updates.
*
* @private
*/
_onAnchorUpdate() {
this._transformID = -1;
this._transformTrimmedID = -1;
}
/**
* 01——23
* | |
* 67——45
* calculates worldTransform * vertices, store it in vertexData
*/
calculateVertices() {
if (this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) {
return;
}
this._transformID = this.transform._worldID;
this._textureID = this._texture._updateID;
// set the vertex data
const texture = this._texture;
const wt = this.transform.worldMatrix;
const a = wt.a;
const b = wt.b;
const c = wt.c;
const d = wt.d;
const tx = wt.tx;
const ty = wt.ty;
const vertexData = this.vertexData;
const trim = texture.trim;
const orig = texture.orig;
const anchor = this._anchorTexture;
let w0 = 0;
let w1 = 0;
let h0 = 0;
let h1 = 0;
if (trim) {
// if the sprite is trimmed and is not a tilingsprite then we need to add the extra
// space before transforming the sprite coords.
//绘制永远根据frame,所以有trim过的,加个偏移
w1 = trim.x - (anchor._x * orig.width);
w0 = w1 + trim.width;
h1 = trim.y - (anchor._y * orig.height);
h0 = h1 + trim.height;
}
else {
w1 = -anchor._x * orig.width;
w0 = w1 + orig.width;
h1 = -anchor._y * orig.height;
h0 = h1 + orig.height;
}
// xy
vertexData[0] = (a * w1) + (c * h1) + tx;
vertexData[1] = (d * h1) + (b * w1) + ty;
// xy
vertexData[2] = (a * w0) + (c * h1) + tx;
vertexData[3] = (d * h1) + (b * w0) + ty;
// xy
vertexData[4] = (a * w0) + (c * h0) + tx;
vertexData[5] = (d * h0) + (b * w0) + ty;
// xy
vertexData[6] = (a * w1) + (c * h0) + tx;
vertexData[7] = (d * h0) + (b * w1) + ty;
}
/**
* calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData
* This is used to ensure that the true width and height of a trimmed texture is respected
*/
calculateTrimmedVertices() {
if (!this.vertexTrimmedData) {
this.vertexTrimmedData = new Float32Array(8);
}
else if (this._transformTrimmedID === this.transform._worldID && this._textureTrimmedID === this._texture._updateID) {
return;
}
this._transformTrimmedID = this.transform._worldID;
this._textureTrimmedID = this._texture._updateID;
// lets do some special trim code!
const texture = this._texture;
const vertexData = this.vertexTrimmedData;
const orig = texture.orig;
const anchor = this._anchorTexture;
// lets calculate the new untrimmed bounds..
const wt = this.transform.worldMatrix;
const a = wt.a;
const b = wt.b;
const c = wt.c;
const d = wt.d;
const tx = wt.tx;
const ty = wt.ty;
const w1 = -anchor._x * orig.width;
const w0 = w1 + orig.width;
const h1 = -anchor._y * orig.height;
const h0 = h1 + orig.height;
// xy
vertexData[0] = (a * w1) + (c * h1) + tx;
vertexData[1] = (d * h1) + (b * w1) + ty;
// xy
vertexData[2] = (a * w0) + (c * h1) + tx;
vertexData[3] = (d * h1) + (b * w0) + ty;
// xy
vertexData[4] = (a * w0) + (c * h0) + tx;
vertexData[5] = (d * h0) + (b * w0) + ty;
// xy
vertexData[6] = (a * w1) + (c * h0) + tx;
vertexData[7] = (d * h0) + (b * w1) + ty;
}
renderWebGL(renderer) {
// if the object is not visible or the alpha is 0 then no need to render this element
if (!this.visible || this.worldAlpha <= 0 || !this.renderable) {
return;
}
// do a quick check to see if this element has a mask or a filter.
if (this._mask/* || this._filters*/) {
this.renderAdvancedWebGL(renderer);
}
else {
this._renderWebGL(renderer);
}
this.dispatchEvent(Event.ENTER_FRAME)
}
renderAdvancedWebGL(renderer: WebglRenderer) {
//之前的批处理刷掉先
renderer.batchManager.flush();
const mask = this._mask;
if (mask) {
renderer.maskManager.pushMask(this, this._mask);
}
this._renderWebGL(renderer);
renderer.batchManager.flush();
if (mask) {
renderer.maskManager.popMask(this, this._mask);
}
}
/**
*
* Renders the object using the WebGL renderer
*
* @private
* @param {WebglRenderer} renderer - The webgl renderer to use.
*/
_renderWebGL(renderer: WebglRenderer) {
this.calculateVertices();
renderer.batchManager.setObjectRenderer(renderer.plugins["batch"]);
renderer.plugins["batch"].render(this);
}
/**
* Renders the object using the Canvas renderer
*
* @param {CanvasRenderer} renderer - The renderer
*/
renderCanvas(renderer: CanvasRenderer) {
if (!this.visible || this.worldAlpha <= 0 || !this.renderable) {
return;
}
if (this._mask) {
renderer.maskManager.pushMask(this._mask);
}
this._renderCanvas(renderer);
if (this._mask) {
renderer.maskManager.popMask(renderer);
}
this.dispatchEvent(Event.ENTER_FRAME)
}
aa;
/**
* Renders the object using the Canvas renderer
*
* @private
* @param {CanvasRenderer} renderer - The renderer
*/
_renderCanvas(renderer: CanvasRenderer) {
// this.aa=this.aa||Date.now();
// var t=Date.now();
// console.log(t-this.aa);
// this.aa=t
renderer.plugins[this.pluginName].render(this);
}
//计算父级宽高用的,计算自己的不需要
calculateBounds() {
super.calculateBounds()
this._bounds.clear();
this._calculateBounds()
}
/**
* Updates the bounds of the sprite.
* 更新自己的bounds,计算全局
* @private
*/
_calculateBounds() {
const trim = this._texture.trim;
const orig = this._texture.orig;
// First lets check to see if the current texture has a trim..
if (!trim || (trim.width === orig.width && trim.height === orig.height)) {
// no trim! lets use the usual calculations..
this.calculateVertices();
Rectangle.createFromVertexData(this._bounds, this.vertexData);
}
else {
// lets calculate a special trimmed bounds...
this.calculateTrimmedVertices();
Rectangle.createFromVertexData(this._bounds, this.vertexTrimmedData);
}
}
/**
* Gets the local bounds of the sprite object.
*
* @param {Rectangle} rect - The output rectangle.
* @return {Rectangle} The bounds.
*/
getLocalBounds(rect?: Rectangle): Rectangle {
if (!rect) {
rect = DisplayObject.temBounds;
}
//直接用_localBoundsSelf
rect.copy(this._localBoundsSelf)
return rect;
}
/**
* Destroys this sprite and optionally its texture and children
*
*/
destroy() {
super.destroy();
this._texture.removeEventListener('update', this._onTextureUpdate);
this._anchorTexture = null;
// const destroyTexture = typeof options === 'boolean' ? options : options && options.texture;
// if (destroyTexture) {
// const destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture;
// this._texture.destroy(!!destroyBaseTexture);
// }
this._texture = null;
}
/**
* The width of the sprite, setting this will actually modify the scale to achieve the value set
* 重写,只返回图片的
* @member {number}
*/
get width() {
return Math.abs(this.scale.x) * this._texture.orig.width;
}
set width(value) {
const s = sign(this.scale.x) || 1;
this.scale.x = s * value / this._texture.orig.width;
this._width = value;
}
/**
* The height of the sprite, setting this will actually modify the scale to achieve the value set
*
* @member {number}
*/
get height() {
return Math.abs(this.scale.y) * this._texture.orig.height;
}
set height(value) {
const s = sign(this.scale.y) || 1;
this.scale.y = s * value / this._texture.orig.height;
this._height = value;
}
/**
* The anchor sets the origin point of the texture.
* The default is 0,0 or taken from the {@link Texture|Texture} passed to the constructor.
* Setting the texture at a later point of time does not change the anchor.
*
* 0,0 means the texture's origin is the top left, 0.5,0.5 is the center, 1,1 the bottom right corner.
*
* @member {ObservablePoint}
*/
get anchorTexture() {
return this._anchorTexture;
}
set anchorTexture(value:any) {
this._anchorTexture.copy(value);
}
/**
* The texture that the sprite is using
*
* @member {Texture}
*/
get texture() {
return this._texture;
}
set texture(value) // eslint-disable-line require-jsdoc
{
if (this._texture === value) {
return;
}
this._texture = value || Texture.EMPTY;
this._textureID = -1;
this._textureTrimmedID = -1;
if (value) {
// wait for the texture to load
if (value.baseTexture.hasLoaded) {
this._onTextureUpdate();
}
else {
value.once('update', this._onTextureUpdate);
}
}
}
// some helper functions..
/**
* Helper function that creates a new sprite based on the source you provide.
* The source can be - frame id, image url, video url, canvas element, video element, base texture
*
* @static
* @param {number|string|BaseTexture|HTMLCanvasElement} source Source to create texture from
* @return {Sprite} The newly created sprite
*/
static from(source: any): Bitmap {
return new Bitmap(Texture.from(source));
}
/**
* Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId
* The frame ids are created when a Texture packer file has been loaded
*
* @static
* @param {string} frameId - The frame Id of the texture in the cache
* @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId
*/
static fromFrame(frameId: string): Bitmap {
const texture = TextureCache[frameId];
if (!texture) {
throw new Error(`The frameId "${frameId}" does not exist in the texture cache`);
}
return new Bitmap(texture);
}
/**
* Helper function that creates a sprite that will contain a texture based on an image url
* If the image is not in the texture cache it will be loaded
*
* @static
* @param {string} imageId - The image url of the texture
* @param {boolean} [crossorigin=(auto)] - if you want to specify the cross-origin parameter
* @param {number} [scaleMode=settings.SCALE_MODE] - if you want to specify the scale mode,
* see {@link SCALE_MODES} for possible values
* @return {Sprite} A new Sprite using a texture from the texture cache matching the image id
*/
static fromImage(imageId: string, crossorigin: boolean = true, scaleMode: number = SCALE_MODES.LINEAR): Bitmap {
return new Bitmap(Texture.fromImage(imageId, crossorigin, scaleMode));
}
}
\ No newline at end of file
// import { removeItems } from '../utils';
import { DisplayObject } from './DisplayObject';
import { Rectangle } from "../math/Rectangle";
import { Point } from '../math';
import CanvasRenderer from '../renderers/CanvasRenderer';
import { Event } from "../events/Event"
import { WebglRenderer } from '../renderers/WebglRenderer';
/**
* 容器
* @class
* @extends DisplayObject
*/
export default class Container extends DisplayObject {
/**
* 为false鼠标事件不再向下传递
*/
mouseChildren: boolean = true;
/**
* 儿子们
* @member {DisplayObject[]}
* @readonly
*/
children;
/**
* 特殊用处
* 缓存的Container的updateTransform
*/
containerUpdateTransform;
constructor() {
super();
this._instanceType = "Container";
this.children = [];
}
/**
* children改变时
* @private
*/
onChildrenChange(index) {
/* empty */
//子类需要时重写
}
/**
* 添加child
* @param {DisplayObject} child
* @return {DisplayObject}
*/
addChild(child: DisplayObject): DisplayObject {
this.addChildAt(child, this.children.length);
return child;
}
/**
* 在相应index处添加child
* @param {DisplayObject} child - The child to add
* @param {number} index - The index to place the child in
* @return {DisplayObject} The child that was added.
*/
addChildAt(child: DisplayObject, index: number): DisplayObject {
if (!child) return;
let s = this;
let sameParent = (s == child.parent);
let len: number;
if (child.parent) {
if (!sameParent) {
child.parent.removeChild(child);
} else {
len = s.children.length;
for (let i = 0; i < len; i++) {
if (s.children[i] == child) {
s.children.splice(i, 1);
break;
}
}
}
}
child.parent = s;
//保证child的transform会被更新
child.transform._parentID = -1;
//确保包围盒重新计算
this._boundsID++;
len = s.children.length;
if (index >= len) {
s.children[s.children.length] = child;
index = len;
} else if (index == 0 || index < 0) {
s.children.unshift(child);
index = 0;
} else {
s.children.splice(index, 0, child);
}
if (s.stage && !sameParent) {
// child["_cp"] = true;
child._onDispatchBubbledEvent("onAddToStage");
}
//被添加时child触发,暂时不用
// child.dispatchEvent('added', this);
this.onChildrenChange(index);
return child;
}
/**
* 只用于交换索引
* @param {DisplayObject} child - First display object to swap
* @param {DisplayObject} child2 - Second display object to swap
*/
swapChildren(child1: DisplayObject, child2: DisplayObject) {
if (child1 === child2) {
return;
}
let s = this;
let id1 = -1;
let id2 = -1;
let childCount = s.children.length;
if (typeof (child1) == "number") {
id1 = child1;
} else {
id1 = s.getChildIndex(child1);
}
if (typeof (child2) == "number") {
id2 = child2;
} else {
id2 = s.getChildIndex(child2);
}
if (id1 == id2 || id1 < 0 || id1 >= childCount || id2 < 0 || id2 >= childCount) {
return false;
} else {
let temp: any = s.children[id1];
s.children[id1] = s.children[id2];
s.children[id2] = temp;
this.onChildrenChange(id1 < id2 ? id1 : id2);
return true;
}
}
/**
* 获取child的层级索引index
* @param {DisplayObject} child - The DisplayObject instance to identify
* @return {number} The index position of the child display object to identify
*/
getChildIndex(child: DisplayObject): number {
const index = this.children.indexOf(child);
if (index === -1) {
return null
}
return index;
}
/**
* 设置child的层级
* @param {DisplayObject} child
* @param {number} index
*/
setChildIndex(child: DisplayObject, index: number) {
this.addChildAt(child, index);
}
/**
* Returns the child at the specified index
* @param {number} index - The index to get the child at
* @return {DisplayObject} The child at the given index, if any.
*/
getChildAt(index: number) {
if (index < 0 || index >= this.children.length) {
return null
}
return this.children[index];
}
/**
* 通过名字获取子级
* @param name
* @param isOnlyOne
* @param isRecursive
*/
public getChildByName(name: string | RegExp, isOnlyOne: boolean = true, isRecursive: boolean = false): any {
if (!name) return null;
let s = this;
let rex: any;
if (typeof (name) == "string") {
rex = new RegExp("^" + name + "$");
} else {
rex = name;
}
let elements: Array<DisplayObject> = [];
Container._getElementsByName(rex, s, isOnlyOne, isRecursive, elements);
let len = elements.length;
if (len == 0) {
return null;
} else if (len == 1) {
return elements[0];
} else {
return elements;
}
}
/**
* 移除child
* @param {DisplayObject} child
* @return {DisplayObject}
*/
removeChild(child: DisplayObject): DisplayObject {
const index = this.children.indexOf(child);
if (index === -1) return null;
this.removeChildAt(index);
return child;
}
/**
* 在index处移除child
* @param {number} index - The index to get the child from
* @return {DisplayObject} The child that was removed.
*/
removeChildAt(index: number): DisplayObject {
let s = this;
let child: any;
let len = s.children.length - 1;
if (len < 0) return;
if (index == len) {
child = s.children.pop();
} else if (index == 0) {
child = s.children.shift();
} else {
child = s.children.splice(index, 1)[0];
}
child._onDispatchBubbledEvent("onRemoveToStage");
//保证子级会被更新
child.parent = null;
child.transform._parentID = -1;
//保证包围盒重新计算
this._boundsID++;
// child.dispatchEvent('removed', this);
this.onChildrenChange(index);
return child;
}
/**
* 通过索引批量移除child
* @param {number} [beginIndex=0]
* @param {number} [endIndex=this.children.length]
* @returns {DisplayObject[]} List of removed children
*/
removeChildren(beginIndex: number = 0, endIndex: number = this.children.length): DisplayObject[] {
const begin = beginIndex;
const end = typeof endIndex === 'number' ? endIndex : this.children.length;
const range = end - begin;
let removed;
if (range > 0 && range <= end) {
removed = this.children.splice(begin, range);
for (let i = 0; i < removed.length; ++i) {
removed[i].parent = null;
if (removed[i].transform) {
removed[i].transform._parentID = -1;
}
}
this._boundsID++;
this.onChildrenChange(beginIndex);
for (let i = 0; i < removed.length; ++i) {
// removed[i].dispatchEvent('removed', this);
removed[i]._onDispatchBubbledEvent("onRemoveToStage");
}
return removed;
}
else if (range === 0 && this.children.length === 0) {
return [];
}
throw new RangeError('removeChildren: numeric values are outside the acceptable range.');
}
/**
* Updates the transform on all children of this container for rendering
*/
updateTransform() {
//自己先算
super.updateTransform();
//考虑是否要加
//this._boundsID++;
//children遍历计算
for (let i = 0, j = this.children.length; i < j; ++i) {
const child = this.children[i];
if (child.visible) {
child.updateTransform();
}
}
}
/**
* 父类重写
* 都是全局的
*/
calculateBounds() {
super.calculateBounds();
this._bounds.clear();
this._calculateBounds();
for (let i = 0; i < this.children.length; i++) {
const child = this.children[i];
if (!child.visible || !child.renderable) {
continue;
}
child.calculateBounds();
if (child._mask) {
child._mask.calculateBounds();
//取交集矩形
if (child._bounds.x < child._mask._bounds.x) {
child._bounds.x = child._mask._bounds.x;
}
if (child._bounds.y < child._mask._bounds.y) {
child._bounds.y = child._mask._bounds.y;
}
if (child._bounds.width > child._mask._bounds.width) {
child._bounds.width = child._mask._bounds.width;
}
if (child._bounds.height > child._mask._bounds.height) {
child._bounds.height = child._mask._bounds.height;
}
Rectangle.createFromRects(this._bounds, child._bounds);
}
else {
Rectangle.createFromRects(this._bounds, child._bounds);
}
}
}
/**
* 加"_"的方法基本是为了自己特殊处理
*/
_calculateBounds() {
//子类自己重写
}
/**
* 检测点是否在任何child上
* 重写父类方法
*/
hitTestPoint(globalPoint: Point, isMouseEvent: boolean = false) {
if (!this.visible) return null
//如果禁止子级的鼠标事件
if (isMouseEvent && !this.mouseEnable) return null;
var children = this.children;
var length = children.length
let child, hitDisplayObject;
for (var i = length - 1; i >= 0; i--) {
child = children[i];
if (child.isUsedToMask) {
continue;
}
//mask在设置的时候要手动加入到parent
if (child.mask /*&& child.mask.parent == child.parent*/) {
//不在遮罩里,不再检测
if (!child.mask.hitTestPoint(globalPoint, isMouseEvent)) {
continue;
}
}
hitDisplayObject = child.hitTestPoint(globalPoint, isMouseEvent);
if (hitDisplayObject) {
return hitDisplayObject;
}
}
return null;
}
/**
* webgl渲染
* @param {WebglRenderer} renderer - The renderer
*/
renderWebGL(renderer: WebglRenderer) {
//不可见,全局透明度为0,或者 不渲染,直接return
if (!this.visible || this.worldAlpha <= 0 || !this.renderable) {
return;
}
//是否有遮罩。到时如果有滤镜,
if (this._mask) {
this.renderAdvancedWebGL(renderer);
}
else {
//自身先渲染
this._renderWebGL(renderer);
//遍历children
for (let i = 0, j = this.children.length; i < j; ++i) {
this.children[i].renderWebGL(renderer);
}
}
}
/**
* 高级渲染方法
*
* @private
* @param {WebGLRenderer} renderer - The renderer
*/
renderAdvancedWebGL(renderer: WebglRenderer) {
//之前的批处理刷掉先
renderer.batchManager.flush();
//有滤镜再说
const mask = this._mask;
if (mask) {
//先画遮罩
renderer.maskManager.pushMask(this, this._mask);
}
//渲染自身
this._renderWebGL(renderer);
//遍历children
for (let i = 0, j = this.children.length; i < j; i++) {
this.children[i].renderWebGL(renderer);
}
//刷掉批处理
renderer.batchManager.flush();
if (mask) {
//移除遮罩,支持多重遮罩
renderer.maskManager.popMask(this, this._mask);
}
}
/**
* 自身渲染方式
* @private
* @param {WebglRenderer} renderer - The renderer
*/
_renderWebGL(renderer: WebglRenderer) {
//自身绘制方法
}
/**
* canvas渲染方式
* @param {CanvasRenderer} renderer - The renderer
*/
renderCanvas(renderer: CanvasRenderer) {
if (!this.visible || this.worldAlpha <= 0 || !this.renderable) {
return;
}
if (this._mask) {
renderer.maskManager.pushMask(this._mask);
}
this._renderCanvas(renderer);
for (let i = 0, j = this.children.length; i < j; ++i) {
this.children[i].renderCanvas(renderer);
}
if (this._mask) {
renderer.maskManager.popMask(renderer);
}
this.dispatchEvent(Event.ENTER_FRAME)
}
/**
* 自身渲染方法
*
* @private
* @param {CanvasRenderer} renderer - The renderer
*/
_renderCanvas(renderer: CanvasRenderer) {
//自身绘制方法
}
/**
* 更新方法
*/
update() {
if (!this.visible) return;
//更新自己的
super.update()
//更新儿子们的
let len = this.children.length;
for (let i = len - 1; i >= 0; i--) {
const child = this.children[i];
if (child.visible) child.update();
}
}
/**
* 调用此方法对自己及其child触发一次指定事件
* @method _onDispatchBubbledEvent
* @private
* @param {string} type
* @since 1.0.0
*/
public _onDispatchBubbledEvent(type: string): void {
let s = this;
let len = s.children.length;
if (type == "onRemoveToStage" && !s.stage) return;
s.stage = s.parent.stage;
for (let i = 0; i < len; i++) {
s.children[i]._onDispatchBubbledEvent(type);
}
super._onDispatchBubbledEvent(type);
}
/**
*
*/
destroy() {
let s = this;
//让子级也destroy
for (let i = s.children.length - 1; i >= 0; i--) {
s.children[i].destroy();
}
super.destroy();
this.mouseChildren = false;
}
/**
* 一般用于获取宽高并设置
* 包括子级的,尽量少用,子类可重写
* @member {number}
*/
get width() {
return this.scale.x * this.getLocalBounds().width;
}
set width(value) {
const width = this.getLocalBounds().width;
if (width !== 0) {
this.scale.x = value / width;
}
else {
this.scale.x = 1;
}
//子类有用,有_width,才需设置scaleX
this._width = value
}
/**
* 高度同width
* @member {number}
*/
get height() {
return this.scale.y * this.getLocalBounds().height;
}
set height(value) {
const height = this.getLocalBounds().height;
if (height !== 0) {
this.scale.y = value / height;
}
else {
this.scale.y = 1;
}
this._height = value
}
//全局遍历
/**
* @method _getElementsByName
* @param {RegExp} rex
* @param {Container} root
* @param {boolean} isOnlyOne
* @param {boolean} isRecursive
* @param {Array<DisplayObject>} resultList
* @private
* @static
*/
private static _getElementsByName(rex: RegExp, root: Container, isOnlyOne: boolean, isRecursive: boolean, resultList: Array<DisplayObject>): void {
let len = root.children.length;
if (len > 0) {
let name: string;
let child: any;
for (let i = 0; i < len; i++) {
child = root.children[i];
name = child.name;
if (name && name != "") {
if (rex.test(name)) {
resultList[resultList.length] = child;
if (isOnlyOne) {
return;
}
}
}
if (isRecursive) {
if (child["children"] != null) {
Container._getElementsByName(rex, child, isOnlyOne, isRecursive, resultList);
}
}
}
}
}
}
Container.prototype.containerUpdateTransform = Container.prototype.updateTransform;
import { EventDispatcher } from '../events/EventDispatcher';
import Transform from '../math/Transform';
import { Rectangle } from '../math/Rectangle';
import { Point } from "../math/Point";
import { Event } from "../events/Event";
import { Component } from '../component/Component';
import Graphics from '../graphics/Graphics';
/**
* 基础显示对象抽象类
* @class
* @extends EventDispatcher
*/
export class DisplayObject extends EventDispatcher {
/**
* 基础信息
*/
transform: Transform;
/**
* 透明度
* 0到1
*/
alpha: number;
/**
* 是否可见
*/
visible: boolean;
/**
* 是否可绘制
* 自身不绘制,但是参与updateTransform
*/
renderable: boolean;
/**
* 父级
* @member {Container}
* @readonly
*/
parent: any;
/**
* 舞台
*/
stage: any
/**
* 名字,预留
*/
name: string
/**
* 全局透明度
* @member {number}
* @readonly
*/
worldAlpha: number;
/**
* 用于只管局部自己,不包括子级,用于各继承修改自身,hitTest检测自己用
*/
_localBoundsSelf: Rectangle;
/**
* 用于计算世界bounds,包括自身和子级,一般只在获取宽高时使用,变形后的
*/
_bounds: Rectangle
_boundsID: number;
_lastBoundsID: number;
/**
* 遮罩
* @member {Graphics}
* @private
*/
_mask: any;
/**
* 是否已销毁
* @member {boolean}
* @private
* @readonly
*/
private _destroyed: boolean;
/**
* 是否可响应鼠标事件
*/
mouseEnable: boolean = true;
/**
* 临时父级,特殊用处
*/
tempDisplayObjectParent: DisplayObject;
/**
* 特殊用处
* 缓存的DisplayObject的updateTransform
*/
displayObjectUpdateTransform;
/**
* 特殊用处
* 缓存的DisplayObject的hitTestPoint
*/
displayObjectHitTestPoint;
_width: number;
_height: number;
constructor() {
super();
this._instanceType="DisplayObject";
this.tempDisplayObjectParent = null;
this.transform = new Transform();
this.alpha = 1;
this.visible = true;
this.renderable = true;
this.parent = null;
this.worldAlpha = 1;
this._localBoundsSelf = new Rectangle();
this._bounds = new Rectangle();
this._boundsID = 0;
this._lastBoundsID = -1;
this._mask = null;
this._destroyed = false;
}
/**
* @private
* @member {DisplayObject}
*/
get _tempDisplayObjectParent() {
if (this.tempDisplayObjectParent === null) {
this.tempDisplayObjectParent = new DisplayObject();
}
return this.tempDisplayObjectParent;
}
/**
* 更新矩阵
*/
updateTransform() {
this.transform.updateWorldMatrix(this.parent.transform);
//更新透明度
this.worldAlpha = this.alpha * this.parent.worldAlpha;
}
/**
* 只获取自己的显示texture,不包括子级,无变形的bounds
* 各子类重写
*/
getLocalBoundsSelf(): Rectangle {
return this._localBoundsSelf
}
//为了hitTestPoint,localToGlobal,globalToLocal等方法不复新不重复生成新的点对象而节约内存
public static _bp: Point = new Point();
public static _p1: Point = new Point();
public static _p2: Point = new Point();
public static _p3: Point = new Point();
public static _p4: Point = new Point();
//bounds缓存
public static temBounds = new Rectangle()
/**
* 点击碰撞测试,就是舞台上的一个point是否在显示对象内,在则返回该对象,不在则返回null
* 对于那些不是继承container,而直接继承displayObject的不用重写,如Bitmap
* @method hitTestPoint
* @public
* @since 1.0.0
* @param {Point} point 需要碰到的坐标点
* @param {boolean} isMouseEvent 是否是鼠标事件调用此方法,一般都为true
* @return {DisplayObject}
*/
public hitTestPoint(point: Point, isMouseEvent: boolean = false): DisplayObject {
let s = this;
if (!s.visible) return null;
if (isMouseEvent && !s.mouseEnable) return null;
if (!isMouseEvent) {
//如果不是系统调用则不考虑这个点是从全局来的,只认为这个点就是当前要碰撞测试同级别下的坐标点
if (s.getLocalBoundsSelf().isPointIn(point)) {
return s;
}
} else {
if (s.getLocalBoundsSelf().isPointIn(s.globalToLocal(point, DisplayObject._bp))) {
return s;
}
}
return null;
}
/**
* 递归更新矩阵方法
*/
_recursivePostUpdateTransform() {
if (this.parent) {
this.parent._recursivePostUpdateTransform();
this.transform.updateWorldMatrix(this.parent.transform);
}
else {
this.transform.updateWorldMatrix(this._tempDisplayObjectParent.transform);
}
}
/**
* 获取全局的bounds,变形后的
* @param {boolean} skipUpdate - setting to true will stop the transforms of the scene graph from
* being updated. This means the calculation returned MAY be out of date BUT will give you a
* nice performance boost
* @param {Rectangle} rect - Optional rectangle to store the result of the bounds calculation
* @return {Rectangle} the rectangular bounding area
*/
getBounds(skipUpdate: boolean = false, rect: Rectangle = DisplayObject.temBounds): Rectangle {
//先把父级的都变换
if (!skipUpdate) {
if (!this.parent) {
this.parent = this._tempDisplayObjectParent;
this.updateTransform();
this.parent = null;
}
else {
this._recursivePostUpdateTransform();
this.updateTransform();
}
}
//里面计算了_bounds
this.calculateBounds();
return rect.copy(this._bounds);
}
/**
* 以父级为世界坐标系的本地包围盒
* @param {Rectangle} [rect] - Optional rectangle to store the result of the bounds calculation
* @return {Rectangle} the rectangular bounding area
*/
getLocalBounds(rect: Rectangle = DisplayObject.temBounds): Rectangle {
const transformRef = this.transform;
const parentRef = this.parent;
this.parent = null;
this.transform = this._tempDisplayObjectParent.transform;
const bounds = this.getBounds(false, rect);
this.parent = parentRef;
this.transform = transformRef;
return bounds;
}
calculateBounds() {
//重写
if (this._lastBoundsID == this._boundsID) return
this._lastBoundsID = this._boundsID
}
/**
*将全局坐标转换到本地坐标值
* @method globalToLocal
* @since 1.0.0
* @public
* @param {Point} point
* @return {Point}
*/
public globalToLocal(point: Point, bp: Point = null): Point {
return this.worldMatrix.transformPointInverse(point.x, point.y, bp || DisplayObject._bp);
}
/**
*将本地坐标转换到全局坐标值
* @method localToGlobal
* @public
* @since 1.0.0
* @param {Point} point
* @return {Point}
*/
public localToGlobal(point: Point, bp: Point = null): Point {
// let s = this;
// if (s.parent) {
// //下一级的坐标始终应该是相对父级来说的,所以是用父级的矩阵去转换
// return s.parent.worldMatrix.transformPoint(point.x, point.y, bp);
// } else {
// //没有父级
// return s.worldMatrix.transformPoint(point.x, point.y, bp);
// }
//得到全局坐标
return this.worldMatrix.transformPoint(point.x, point.y, bp || DisplayObject._bp);
}
/**
* 调用些方法会冒泡的将事件向显示列表下方传递,主要用于移除舞台,和添加进舞台事件,修改stage
* @method _onDispatchBubbledEvent
* @private
* @since 1.0.0
* @param {string} type
* @return {void}
*/
public _onDispatchBubbledEvent(type: string): void {
let s: any = this;
if (type == "onRemoveToStage" && !s.stage) return;
s.stage = s.parent.stage;
if (type == "onRemoveToStage") {
s.dispatchEvent(type);
s.stage = null;
} else if (type == "onAddToStage") {
s.dispatchEvent(type);
}
}
/**
* webgl渲染
* @param {WebGLRenderer} renderer - The renderer
*/
renderWebGL(renderer: any) {
//子类重写
}
/**
* canvas渲染
* @param {CanvasRenderer} renderer - The renderer
*/
renderCanvas(renderer: any) {
//子类重写
}
/**
* 设置父级
* @param {Container} container - The Container to add this DisplayObject to
* @return {Container} The Container that this DisplayObject was added to
*/
setParent(container: any): any {
if (!container || !container.addChild) {
throw new Error('setParent: Argument must be a Container');
}
container.addChild(this);
return container;
}
/**
*根据常规属性 设置矩阵属性,弧度制
* @param {number} [x=0] - The X position
* @param {number} [y=0] - The Y position
* @param {number} [scaleX=1] - The X scale value
* @param {number} [scaleY=1] - The Y scale value
* @param {number} [rotation=0] - The rotation
* @param {number} [skewX=0] - The X skew value
* @param {number} [skewY=0] - The Y skew value
* @param {number} [anchorX=0] - The X anchor value
* @param {number} [anchorY=0] - The Y anchor value
* @return {DisplayObject} The DisplayObject instance
*/
setTransform(
x: number = 0,
y: number = 0,
scaleX: number = 1,
scaleY: number = 1,
rotation: number = 0,
skewX: number = 0,
skewY: number = 0,
anchorX: number = 0,
anchorY: number = 0
): DisplayObject {
this.position.x = x;
this.position.y = y;
this.scale.x = !scaleX ? 1 : scaleX;
this.scale.y = !scaleY ? 1 : scaleY;
this.rotation = rotation;
this.skew.x = skewX;
this.skew.y = skewY;
this.anchor.x = anchorX;
this.anchor.y = anchorY;
return this;
}
/**
* 基本销毁方法
*/
destroy() {
this.removeAllEventListener();
if (this.parent) {
this.parent.removeChild(this);
}
this.transform = null;
this.parent = null;
this._localBoundsSelf = null;
this._mask = null;
this.mouseEnable = false;
this._destroyed = true;
}
get x(): number {
return this.position.x;
}
set x(value: number) {
this.transform.position.x = value;
}
get y(): number {
return this.position.y;
}
set y(value: number) {
this.transform.position.y = value;
}
/**
* 获取世界矩阵
* 手动修改时,this.transform._worldID++,保证子级的worldMatrix会修改,尽量别那么做
* @member {Matrix}
* @readonly
*/
get worldMatrix() {
return this.transform.worldMatrix;
}
/**
* 获取本地矩阵
* 手动修改时this.transform._parentID=-1;保证其worldMatrix会更新
* @member {Matrix}
* @readonly
*/
get localMatrix() {
return this.transform.localMatrix;
}
/**
* 获取位置对象
* @member {Point|ObservablePoint}
*/
get position() {
return this.transform.position;
}
/**
* 设置位置对象
*/
set position(value) {
this.transform.position.copy(value);
}
/**
* 获取缩放对象
* @member {Point|ObservablePoint}
*/
get scale() {
return this.transform.scale;
}
/**
* 设置缩放对象
*/
set scale(value) {
this.transform.scale.copy(value);
}
get scaleX() {
return this.transform.scale.x;
}
set scaleX(value) {
this.transform.scale.x = value;
}
get scaleY() {
return this.transform.scale.y;
}
set scaleY(value) {
this.transform.scale.y = value;
}
/**
* 获取锚点对象
* @member {Point|ObservablePoint}
*/
get anchor() {
return this.transform.anchor;
}
set anchor(value) {
this.transform.anchor.copy(value);
}
get anchorX() {
return this.transform.anchor.x;
}
set anchorX(value) {
this.transform.anchor.x = value;
}
get anchorY() {
return this.transform.anchor.y;
}
set anchorY(value) {
this.transform.anchor.y = value;
}
/**
* 获取斜切对象
* @member {ObservablePoint}
*/
get skew() {
return this.transform.skew;
}
set skew(value) {
this.transform.skew.copy(value);
}
/**
* 获取旋转值,弧度制,顺时针
* @member {number}
*/
get rotation(): number {
return this.transform.rotation;
}
set rotation(value: number) {
this.transform.rotation = value;
}
/**
* 自身是否可见,检测所有父级的visible
* @member {boolean}
* @readonly
*/
get worldVisible() {
let item = this;
do {
if (!item.visible) {
return false;
}
item = item.parent;
} while (item);
return true;
}
/**
* 获取遮罩
* @member {Graphics}
*/
get mask():Graphics {
return this._mask;
}
set mask(value) {
if (this._mask) {
//原先有的遮罩,重置属性
this._mask.renderable = true;
this._mask.isUsedToMask = false;
//手动移除
// if (this._mask.parent) {
// this._mask.parent.removeChild(this._mask)
// }
}
this._mask = value;
if (this._mask) {
this._mask.renderable = false;
this._mask.isUsedToMask = true;
//保证更新包围盒
this._boundsID++;
//手动添加吧
// this.parent.addChild(this._mask)
}
}
/**
* 组件数组
*/
_components: Component[] = [];
/**
* @param t 从引擎开始到当前的毫秒数
*/
public update(t?) {
//更新组件方法
for (var i = 0; i < this._components.length;i++) {
if(this._components[i].enabled){
this._components[i].$onUpdate(t)
}
}
//监听的
if (this.hasEventListener("onEnterFrame")) {
this.dispatchEvent("onEnterFrame");
}
}
//组件相关方法,添加移除等
/**
* 增加组件
* @param component
*/
addComponent(component: Component) {
this.onAddComponent(component);
this._components.push(component);
}
/**
* 在指定索引增加组件,重复添加可作为顺序交换
* @param component
* @param index
*/
addComponentAt(component: Component, index: number) {
const currentIndex = this._components.indexOf(component);
if (currentIndex == index) {
return;
}
if (currentIndex >= 0) {
this._components.splice(currentIndex, 1);
}
this._components.splice(index, 0, component);
this.onAddComponent(component);
}
/**
* 移除组件
* @param component
*/
removeComponent(component: Component) {
this.onRemoveComponent(component);
const index = this._components.indexOf(component);
if (index >= 0) {
this._components.splice(index, 1);
}
}
/**
* 移除所有组件
*/
removeAllComponents() {
while (this._components.length > 0) {
this.removeComponent(this._components[0]);
}
}
/**
* 当添加组件时
* @param component
*/
onAddComponent(component: Component) {
component._setup(this);
// if (EngineConfig.awakeComponentWhenAdded) {
// this.awakeComponent(component);
// }
}
/**
* 当移除组件时
* @param component
*/
onRemoveComponent(component: Component) {
// if (EngineConfig.sleepComponentWhenRemoved) {
// this.sleepComponent(component);
// }
component._unSetup();
}
/**
* 睡眠组件
* @param component
*/
sleepComponent(component: Component) {
// if (!this._isFree && this._enabled) {
// component.$onSleep();
// }
}
}
/**
* 比用call性能好
* 不会被子集覆盖,部分地方使用
*/
DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform;
DisplayObject.prototype.displayObjectHitTestPoint = DisplayObject.prototype.hitTestPoint;
import { Event } from "../events/Event";
import { DisplayObject } from "./DisplayObject";
import { devicePixelRatio } from "../const";
/**
* 此类对于需要在canvas上放置html其他类型元素的时候非常有用<br/>
* 比如有时候我们需要放置一个注册,登录或者其他的内容.这些内容包含了输入框<br/>
* 或者下拉框什么的,无法在canvas里实现,但这些元素又跟canvas里面的元素<br/>
* 位置,大小,缩放对应.就相当于是一个显示对象一样。可以随意设置他的<br/>
* 属性,那么将你的html元素通过此类封装成显示对象再合适不过了
* @class FloatDisplay
* @extends DisplayObject
* @public
* @since 1.0.0
*/
export class FloatDisplay extends DisplayObject {
/**
* 需要封装起来的html元素的引用。你可以通过这个引用来调用或设置此元素自身的属性方法和事件,甚至是样式
* @property htmlElement
* @public
* @since 1.0.0
* @type{HtmlElement}
*/
public htmlElement: any = null;
/**
* 是否已经添加了舞台事件
* @property _isAdded
* @since 1.0.0
* @type {boolean}
* @private
*/
private _isAdded: boolean = false;
_transformID: number;
/**
* 构造函数
* @method FloatDisplay
* @since 1.0.0
* @public
* @example
* var floatDisplay = new FloatDisplay();
* floatDisplay.init(document.getElementById('aaa'));
* s.addChild(floatDisplay);
*
* <p><a href="" target="_blank">测试链接</a></p>
*
* @example
* //创建悬浮的html元素
* var section = document.createElement('section');
* section.id = "rule";
* section.style.overflowX = "hidden";
* section.style.overflowY = "auto";
* section.style.width = w + "px";
* section.style.height = h + "px";
* section.style.lineHeight = lh + "px";
* section.style.fontFamily = '微软雅黑';
* section.style.fontSize = fs + 'px';
* section.style.color = "#ffffff";
* //创建Floatview 把我们要悬浮的元素封装进去
* var rule = new FloatDisplay();
* stage.addChild(rule);
* rule.x = ox;
* rule.y = oy;
* rule.init(this.section);
* section.innerHTML = DataManager.ins.getData("ajaxElement").data.rule;
*
*/
public constructor() {
super();
let s = this;
s._instanceType = "FloatDisplay";
s.addEventListener(Event.REMOVE_TO_STAGE, function (e: Event) {
if (s.htmlElement) {
s.htmlElement.style.display = "none";
}
});
s.addEventListener(Event.ADD_TO_STAGE, function (e: Event) {
if (s.htmlElement) {
let style = s.htmlElement.style;
if (!s._isAdded) {
s._isAdded = true;
s.stage.rootDiv.insertBefore(s.htmlElement, s.stage.rootDiv.childNodes[0]);
s.stage["_floatDisplayList"].push(s);
} else {
if (s.htmlElement && s.visible) {
style.display = "block";
}
}
}
});
this._transformID = -1;
}
/**
* 初始化方法,htmlElement 一定要设置width和height样式,并且一定要用px单位
* @method init
* @public
* @since 1.0.0
* @param {HtmlElement} htmlElement 需要封装起来的html元素的引用。你可以通过这个引用来调用或设置此元素自身的属性方法和事件,甚至是样式
*/
public init(htmlElement: any): void {
let s = this;
let she: any;
if (typeof (htmlElement) == "string") {
she = document.getElementById(htmlElement);
} else if (htmlElement._instanceType == "Video") {
she = htmlElement.media;
} else {
she = htmlElement;
}
let style = she.style;
style.position = "absolute";
style.display = "none";
style.transformOrigin = style.WebkitTransformOrigin = "0 0 0";
let ws = s.getStyle(she, "width");
let hs = s.getStyle(she, "height");
let w = 0, h = 0;
if (ws.indexOf("px")) {
w = parseInt(ws);
}
if (hs.indexOf("px")) {
h = parseInt(hs);
}
s._bounds.width = w;
s._bounds.height = h;
s.htmlElement = she;
}
/**
* @method getStyle
* @param {HTMLElement} elem
* @param cssName
* @return {any}
*/
private getStyle(elem: HTMLElement, cssName: any): any {
//如果该属性存在于style[]中,则它最近被设置过(且就是当前的)
if (elem.style[cssName]) {
return elem.style[cssName];
}
if (document.defaultView && document.defaultView.getComputedStyle) {
//它使用传统的"text-Align"风格的规则书写方式,而不是"textAlign"
cssName = cssName.replace(/([A-Z])/g, "-$1");
cssName = cssName.toLowerCase();
//获取style对象并取得属性的值(如果存在的话)
let s = document.defaultView.getComputedStyle(elem, "");
return s && s.getPropertyValue(cssName);
}
return null;
}
/**
* @method updateStyle
* @public
* @since 1.1.4
*/
public updateStyle(): void {
let s = this;
let o = s.htmlElement;
if (o) {
let style = o.style;
let visible = s.visible;
if (visible) {
let parent = s.parent;
while (parent) {
if (!parent._visible) {
visible = false;
break;
}
parent = parent.parent;
}
}
let show = visible ? "block" : "none";
if (show != style.display) {
style.display = show;
}
if (visible) {
if (this._transformID != this.transform._worldID) {
this._transformID = this.transform._worldID
let mtx = s.transform.worldMatrix;
let d = devicePixelRatio;
style.transform = style.webkitTransform = "matrix(" + (mtx.a / d).toFixed(4) + "," + (mtx.b / d).toFixed(4) + "," + (mtx.c / d).toFixed(4) + "," + (mtx.d / d).toFixed(4) + "," + (mtx.tx / d).toFixed(4) + "," + (mtx.ty / d).toFixed(4) + ")";
}
style.opacity = s.worldAlpha;
}
}
}
public destroy(): void {
//清除相应的数据引用
let s = this;
let elem = s.htmlElement;
if (elem) {
elem.style.display = "none";
if (elem.parentNode) {
elem.parentNode.removeChild(elem);
}
s._isAdded = false;
s.htmlElement = null;
}
let sf: any = s.stage["_floatDisplayList"];
let len = sf.length;
for (let i = 0; i < len; i++) {
if (sf[i] == s) {
sf.splice(i, 1);
break;
}
}
super.destroy();
}
}
import { Point, ObservablePoint, Rectangle } from '../math';
import { sign, TextureCache, hex2rgb } from '../utils';
// import { BLEND_MODES } from '../const';
import Texture from '../texture/Texture';
import Container from './Container';
import { DisplayObject } from "./DisplayObject";
import CanvasRenderer from '../renderers/CanvasRenderer';
import { SCALE_MODES } from '../const';
import { WebglRenderer } from '../renderers/WebglRenderer';
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
/**
*
* @class
* @extends Container
*/
export default class Sprite extends Container {
/**
* 关于贴图的锚点,0到1,默认为texture自己的
*
*/
private _anchorTexture: ObservablePoint
/**
* 使用的贴图
* @member {Texture}
*/
_texture: Texture;
/**
* 初始化为texture的宽度
*/
_width: number;
/**
* 初始化为texture的宽度
*/
_height: number;
/**
* 标记更新transform
*/
_transformID: number;
/**
* 标记更新过texture
*/
_textureID: number;
_transformTrimmedID: number;
_textureTrimmedID: number;
/**
* 指定渲染用的渲染器插件
*/
pluginName: string;
/**
* 顶点索引,下面几个均为webgl用到
*/
indices: Uint16Array;
size: number;
start: number;
uvs: Float32Array;
/**
* 顶点数据,长度8
*/
vertexData: Float32Array;
/**
* Trimmed的顶点数据,用于裁剪掉过透明像素的texture
*/
vertexTrimmedData: Float32Array;
/**
* @param {Texture} texture
*/
constructor(texture?: Texture) {
super();
this._anchorTexture = new ObservablePoint(
this._onAnchorUpdate,
this,
(texture ? texture.defaultAnchor.x : 0),
(texture ? texture.defaultAnchor.y : 0)
);
this._texture = null;
this._width = 0;
this._height = 0;
this.uvs = null;
// 下面texture set时用到
this._onTextureUpdate = this._onTextureUpdate.bind(this);
// call texture setter
this.texture = texture || Texture.EMPTY;
this.vertexData = new Float32Array(8);
this.vertexTrimmedData = null;
this._transformID = -1;
this._textureID = -1;
this._transformTrimmedID = -1;
this._textureTrimmedID = -1;
// 批处理设置
this.indices = indices;
this.size = 4;
this.start = 0;
this.pluginName = 'sprite';
}
/**
* texture更新时触发
* @private
*/
_onTextureUpdate() {
//保证顶点要更新
this._textureID = -1;
this._textureTrimmedID = -1;
this.uvs = this._texture._uvs.uvsFloat32;
//设置过宽高的话,就需要改变缩放值
// so if _width is 0 then width was not set..
if (this._width) {
this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width;
}
if (this._height) {
this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height;
}
//修改_localBoundsSelf
const width = this._texture.orig.width;
const height = this._texture.orig.height;
this._localBoundsSelf.x = -width * this.anchorTexture.x;
this._localBoundsSelf.y = -height * this.anchorTexture.y;
this._localBoundsSelf.width = width;
this._localBoundsSelf.height = height;
}
/**
* 当贴图锚点修改时
* @private
*/
_onAnchorUpdate() {
this._transformID = -1;
this._transformTrimmedID = -1;
}
/**
* 01——23
* | |
* 67——45
* calculates worldTransform * vertices, store it in vertexData
*/
calculateVertices() {
if (this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) {
return;
}
this._transformID = this.transform._worldID;
this._textureID = this._texture._updateID;
// set the vertex data
const texture = this._texture;
const wt = this.transform.worldMatrix;
const a = wt.a;
const b = wt.b;
const c = wt.c;
const d = wt.d;
const tx = wt.tx;
const ty = wt.ty;
const vertexData = this.vertexData;
const trim = texture.trim;
const orig = texture.orig;
const anchor = this._anchorTexture;
let w0 = 0;
let w1 = 0;
let h0 = 0;
let h1 = 0;
if (trim) {
// if the sprite is trimmed and is not a tilingsprite then we need to add the extra
// space before transforming the sprite coords.
//绘制永远根据frame,所以有trim过的,加个偏移
w1 = trim.x - (anchor._x * orig.width);
w0 = w1 + trim.width;
h1 = trim.y - (anchor._y * orig.height);
h0 = h1 + trim.height;
}
else {
w1 = -anchor._x * orig.width;
w0 = w1 + orig.width;
h1 = -anchor._y * orig.height;
h0 = h1 + orig.height;
}
// xy
vertexData[0] = (a * w1) + (c * h1) + tx;
vertexData[1] = (d * h1) + (b * w1) + ty;
// xy
vertexData[2] = (a * w0) + (c * h1) + tx;
vertexData[3] = (d * h1) + (b * w0) + ty;
// xy
vertexData[4] = (a * w0) + (c * h0) + tx;
vertexData[5] = (d * h0) + (b * w0) + ty;
// xy
vertexData[6] = (a * w1) + (c * h0) + tx;
vertexData[7] = (d * h0) + (b * w1) + ty;
}
/**
* calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData
* This is used to ensure that the true width and height of a trimmed texture is respected
*/
calculateTrimmedVertices() {
if (!this.vertexTrimmedData) {
this.vertexTrimmedData = new Float32Array(8);
}
else if (this._transformTrimmedID === this.transform._worldID && this._textureTrimmedID === this._texture._updateID) {
return;
}
this._transformTrimmedID = this.transform._worldID;
this._textureTrimmedID = this._texture._updateID;
// lets do some special trim code!
const texture = this._texture;
const vertexData = this.vertexTrimmedData;
const orig = texture.orig;
const anchor = this._anchorTexture;
// lets calculate the new untrimmed bounds..
const wt = this.transform.worldMatrix;
const a = wt.a;
const b = wt.b;
const c = wt.c;
const d = wt.d;
const tx = wt.tx;
const ty = wt.ty;
const w1 = -anchor._x * orig.width;
const w0 = w1 + orig.width;
const h1 = -anchor._y * orig.height;
const h0 = h1 + orig.height;
// xy
vertexData[0] = (a * w1) + (c * h1) + tx;
vertexData[1] = (d * h1) + (b * w1) + ty;
// xy
vertexData[2] = (a * w0) + (c * h1) + tx;
vertexData[3] = (d * h1) + (b * w0) + ty;
// xy
vertexData[4] = (a * w0) + (c * h0) + tx;
vertexData[5] = (d * h0) + (b * w0) + ty;
// xy
vertexData[6] = (a * w1) + (c * h0) + tx;
vertexData[7] = (d * h0) + (b * w1) + ty;
}
/**
* 自身webgl绘制方法
* @private
* @param {WebglRenderer} renderer
*/
_renderWebGL(renderer: WebglRenderer) {
//先计算顶点
this.calculateVertices();
renderer.batchManager.setObjectRenderer(renderer.plugins["batch"]);
renderer.plugins["batch"].render(this);
}
aa;
/**
* 自身canvas绘制方法
* @private
* @param {CanvasRenderer} renderer
*/
_renderCanvas(renderer: CanvasRenderer) {
// this.aa=this.aa||Date.now();
// var t=Date.now();
// console.log(t-this.aa);
// this.aa=t
renderer.plugins[this.pluginName].render(this);
}
/**
* 更新自己的bounds,计算全局
* @private
*/
_calculateBounds() {
const trim = this._texture.trim;
const orig = this._texture.orig;
//无trim。或者trim的尺寸和orig相等
if (!trim || (trim.width === orig.width && trim.height === orig.height)) {
this.calculateVertices();
Rectangle.createFromVertexData(this._bounds, this.vertexData);
}
else {
//计算trimmed bounds...
this.calculateTrimmedVertices();
Rectangle.createFromVertexData(this._bounds, this.vertexTrimmedData);
}
}
/**
* 重写父类
* @param {Rectangle} rect - The output rectangle.
* @return {Rectangle} The bounds.
*/
getLocalBounds(rect?: Rectangle): Rectangle {
//如果没有child,直接
if (this.children.length === 0) {
if (!rect) {
rect = DisplayObject.temBounds;
}
//直接用_localBoundsSelf
rect.copy(this._localBoundsSelf)
return rect;
}
return super.getLocalBounds.call(this, rect);
}
/**
* 重写碰撞检测方法
* @param globalPoint
* @param isMouseEvent
*/
hitTestPoint(globalPoint: Point, isMouseEvent: boolean = false) {
//如果以后加缓存,另写
let hitDisplayObject;
//先检查子级,因为子级层级更高
hitDisplayObject = super.hitTestPoint(globalPoint, isMouseEvent);
if (hitDisplayObject) {
return hitDisplayObject
}
//检查自己
hitDisplayObject = this.displayObjectHitTestPoint(globalPoint, isMouseEvent);
if (hitDisplayObject) {
return hitDisplayObject
}
return null;
}
/**
* 销毁
*/
destroy() {
super.destroy();
//相应texture移除监听
this._texture.removeEventListener('update', this._onTextureUpdate);
this._anchorTexture = null;
this._texture = null;
}
/**
* 重写Container父类
* texture的宽度和缩放乘积
* @member {number}
*/
get width() {
return Math.abs(this.scale.x) * this._texture.orig.width;
}
set width(value) {
const s = sign(this.scale.x) || 1;
this.scale.x = s * value / this._texture.orig.width;
this._width = value;
}
/**
* texture的高度和缩放乘积
* @member {number}
*/
get height() {
return Math.abs(this.scale.y) * this._texture.orig.height;
}
set height(value) {
const s = sign(this.scale.y) || 1;
this.scale.y = s * value / this._texture.orig.height;
this._height = value;
}
/**
* 0,0标识左上角,0.5,0.5表示中间,1,1表示右下角
* @member {ObservablePoint}
*/
get anchorTexture() {
return this._anchorTexture;
}
set anchorTexture(value:any) {
this._anchorTexture.copy(value);
}
/**
* @member {Texture}
*/
get texture() {
return this._texture;
}
set texture(value) {
if (this._texture === value) {
return;
}
this._texture = value || Texture.EMPTY;
this._textureID = -1;
this._textureTrimmedID = -1;
if (value) {
if (value.baseTexture.hasLoaded) {
this._onTextureUpdate();
}
else {
value.once('update', this._onTextureUpdate);
}
}
}
//一些静态类方法
/**
* 方便创建sprite
* The source can be - frame id, image url, canvas element, base texture
* @static
* @param {number|string|BaseTexture|HTMLCanvasElement} source
* @return {Sprite} The newly created sprite
*/
static from(source: any): Sprite {
return new Sprite(Texture.from(source));
}
/**
* TextureCache缓存里取得frameId,通常图集里得名字
* @static
* @param {string} frameId - The frame Id of the texture in the cache
* @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId
*/
static fromFrame(frameId: string): Sprite {
const texture = TextureCache[frameId];
if (!texture) {
throw new Error(`The frameId "${frameId}" does not exist in the texture cache`);
}
return new Sprite(texture);
}
/**
* 根据图片路径创建sprite
* @static
* @param {string} imageId - The image url of the texture
* @param {boolean} [crossorigin=(auto)] - if you want to specify the cross-origin parameter
* @param {number} [scaleMode=settings.SCALE_MODE] - if you want to specify the scale mode,
* see {@link SCALE_MODES} for possible values
* @return {Sprite} A new Sprite using a texture from the texture cache matching the image id
*/
static fromImage(imageId: string, crossorigin: boolean = true, scaleMode: number = SCALE_MODES.LINEAR): Sprite {
return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode));
}
}
import Container from "./Container";
import {osType, devicePixelRatio, RENDERER_TYPE, StageScaleMode} from "../const"
import SystemRenderer from "../renderers/SystemRenderer";
import {Rectangle, Point} from "../math";
import {EventDispatcher} from "../events/EventDispatcher";
import {Event} from "../events/Event";
import {FloatDisplay} from "./FloatDisplay";
import {DisplayObject} from "./DisplayObject";
import {MouseEvent} from "../events/MouseEvent";
import {TouchEvent} from "../events/TouchEvent";
import {WebglRenderer} from "../renderers/WebglRenderer";
import {GDispatcher} from "../events/GDispatcher";
import CanvasRenderer from "../renderers/CanvasRenderer";
//兼容requestAnimationFrame
const requestAnimationFrame = (function () {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || // name has changed in Webkit
window[vendors[x] + 'CancelRequestAnimationFrame'];
}
let requestAnimationFrame: any = window.requestAnimationFrame;
if (!requestAnimationFrame) {
requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));
var id = window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}
return requestAnimationFrame
}())
export class Stage extends Container {
timer: number = 0;
/**
* 是否阻止ios端双击后页面会往上弹的效果,因为如果阻止了,可能有些html元素出现全选框后无法取消
* 所以需要自己灵活设置,默认阻止.
* @property iosTouchendPreventDefault
* @type {boolean}
* @default true
* @since 1.0.4
* @public
*/
public iosTouchendPreventDefault: boolean = true;
/**
* 是否禁止引擎所在的canvas的鼠标事件或触摸事件的默认形为,默认为true是禁止的。
* @property isPreventDefaultEvent
* @since 1.0.9
* @default true
* @type {boolean}
*/
public isPreventDefaultEvent: boolean = true;
/**
* 整个引擎的最上层的div元素,
* 承载canvas的那个div html元素
* @property rootDiv
* @public
* @since 1.0.0
* @type {Html Div}
* @default null
*/
public rootDiv: any = null;
/**
* 当前stage所使用的渲染器
* 渲染器有两种,一种是canvas 一种是webGl
* @property renderObj
* @public
* @since 1.0.0
* @type {IRender}
* @default null
*/
public renderObj: SystemRenderer = null;
/**
* 渲染模式值 只读 CANVAS:2, webGl: 1
* @property renderType
* @readonly
* @public
* @since 1.0.0
* @type {number}
* @default 0
* @readonly
*/
public renderType: number = 0;
/**
* 直接获取stage的引用,避免总是从Event.ADD_TO_STAGE 事件中去获取stage引用
* @property getStage
* @param {string} stageName
* @return {any}
* @since 2.0.0
*/
public static getStage(stageName: string = "cusEngine"): Stage {
return Stage._stageList[stageName];
}
/**
* @property _stageList
* @static
* @type {Object}
* @private
*/
private static _stageList: any = {};
/**
* 是否暂停
* @property pause
* @static
* @type {boolean}
* @public
* @since 1.0.0
* @default false
*/
static get pause(): boolean {
return this._pause;
}
static set pause(value: boolean) {
this._pause = value;
if (value != this._pause) {
//触发事件
GDispatcher.dispatchEvent("onStagePause", {pause: value});
}
}
/**
* @property _pause
* @type {boolean}
* @private
* @static
*/
private static _pause: boolean = false;
/**
* 舞台在设备里截取后的可见区域,有些时候知道可见区域是非常重要的,因为这样你就可以根据舞台的可见区域做自适应了。
* @property viewRect
* @public
* @readonly
* @since 1.0.0
* @type {Rectangle}
* @default {x:0,y:0,width:0,height:0}
* @readonly
* @example
* //始终让一个对象顶对齐,或者
*/
public viewRect: Rectangle = new Rectangle();
/**
* 当设备尺寸更新,或者旋转后是否自动更新舞台方向
* 端默认不开启
* @property autoSteering
* @public
* @since 1.0.0
* @type {boolean}
* @default false
*/
public autoSteering: boolean = false;
/**
* 当设备尺寸更新,或者旋转后是否自动更新舞台尺寸
* @property autoResize
* @public
* @since 1.0.0
* @type {boolean}
* @default false
*/
public autoResize: boolean = false;
/**
* 舞台的尺寸宽,也就是我们常说的设计尺寸
* @property desWidth
* @public
* @since 1.0.0
* @default 320
* @type {number}
* @readonly
*/
public desWidth: number = 0;
/**
* 舞台的尺寸高,也就是我们常说的设计尺寸
* @property desHeight
* @public
* @since 1.0.0
* @default 240
* @type {number}
* @readonly
*/
public desHeight: number = 0;
/**
* 舞台在当前设备中的真实高
* @property divHeight
* @public
* @since 1.0.0
* @default 320
* @type {number}
* @readonly
*/
public divHeight: number = 0;
/**
* 舞台在当前设备中的真实宽
* @property divWidth
* @public
* @since 1.0.0
* @default 240
* @readonly
* @type {number}
*/
public divWidth: number = 0;
/**
* 舞台的背景色
* 默认就是透明背景
* 可能设置一个颜色值改变舞台背景
* @property bgColor
* @public
* @since 1.0.0
* @type {number}
* @default "";
*/
private _bgColor: number = 0x000000;
public get bgColor(): number {
return this._bgColor;
}
/**
* 设置颜色,即改变渲染器颜色
*/
public set bgColor(value: number) {
if (this._bgColor === value) return
this._bgColor = value;
this.renderObj.backgroundColor = value;
}
/**
* 舞台的缩放模式
* 默认为空就是无缩放的真实大小
* "noBorder" 无边框模式
* ”showAll" 显示所有内容
* “fixedWidth" 固定宽
* ”fixedHeight" 固定高
* @property scaleMode
* @public
* @since 1.0.0
* @default "onScale"
* @type {string}
* @example
* //动态更改stage的对齐方式示例
* //以下代码放到一个舞台的显示对象的构造函数中
* let s=this;
* s.addEventListener(Event.ADD_TO_STAGE,function(e){
* let i=0;
* s.stage.addEventListener(MouseEvent.CLICK,function(e){
* let aList=[StageScaleMode.EXACT_FIT,StageScaleMode.NO_BORDER,StageScaleMode.NO_SCALE,StageScaleMode.SHOW_ALL,StageScaleMode.FIXED_WIDTH,StageScaleMode.FIXED_HEIGHT]
* let state=e.currentTarget;
* state.scaleMode=aList[i];
* state.resize();
* if(i>5){i=0;}
* }
* }
*
*/
get scaleMode(): string {
return this._scaleMode;
}
set scaleMode(value: string) {
let s = this;
if (value != s._scaleMode) {
s._scaleMode = value;
s.setAlign();
}
}
private _scaleMode: string = "onScale";
/**
* 原始为60的刷新速度时的计数器
* @property _flush
* @private
* @since 1.0.0
* @default 0
* @type {number}
*/
private _flush: number = 0;
/**
* 当前的刷新次数计数器
* @property _currentFlush
* @private
* @since 1.0.0
* @default 0
* @type {number}
*/
private _currentFlush: number = 0;
/**
* @property _dragDisplay
* @private
* @type {null}
* @private
* @static
*/
public static _dragDisplay: DisplayObject = null;
/**
* @property _isLoadedVConsole
* @type {Array}
* @private
* @static
*/
private static _isLoadedVConsole: boolean = false;
/**
* 上一次鼠标或触碰经过的显示对象列表
* @property _lastDpList
* @type {Object}
* @private
*/
private _lastDpList: any = {};
/**
* 窗口resize计时器id
* @property _rid
* @type {number}
* @private
*/
private _rid: any = -1;
/**
* @property _floatDisplayList
* @type {any[]}
* @private
*/
private _floatDisplayList: Array<FloatDisplay> = [];
/**
* 显示对象入口函数
* @method Stage
* @param {string} rootDivId
* @param {number} desW 舞台宽
* @param {number} desH 舞台高
* @param {number} frameRate 刷新率
* @param {string} scaleMode 缩放模式 StageScaleMode
* @param {number} renderType 渲染类型2canvas
* @param {boolean} transparent 透明否,默认透明true,此时bgColor无效
* @param {number} bgColor 背景颜色十六进制
* @public
* @since 1.0.0
*/
public constructor(
rootDivId: string = "cusEngine",
desW: number = 750,
desH: number = 1206,
frameRate: number = 60,
scaleMode: string = StageScaleMode.FIXED_WIDTH,
renderType: number = RENDERER_TYPE.WEBGL,
transparent: boolean = true,
bgColor: number = 0x000000
) {
super();
let s: Stage = this;
this._instanceType = "Stage";
Stage._stageList[rootDivId] = s;
s.stage = this;
let resizeEvent = "resize";
s.name = "stageInstance_" + s.instanceId;
let div: any = document.getElementById(rootDivId);
s.renderType = renderType;
s.desWidth = desW;
s.desHeight = desH;
s.rootDiv = div;
s.setFrameRate(frameRate);
s._scaleMode = scaleMode;
s.anchorX = desW >> 1;
s.anchorY = desH >> 1;
//初始化canvas
var canvas = document.createElement("canvas");
s.rootDiv.appendChild(canvas);
canvas.id = "cusCanvas";
// s.renderObj = new CanvasRender(s);
if (renderType == RENDERER_TYPE.CANVAS) {
//canvas
s.renderObj = new CanvasRenderer({
htmlElement: canvas,
transparent: transparent,
backgroundColor: bgColor
});
} else {
//webgl
s.renderObj = new WebglRenderer({
htmlElement: canvas,
transparent: transparent,
// antialias:true,
preserveDrawingBuffer: false,
backgroundColor: bgColor
});
}
// s.renderObj.init();
window.addEventListener(resizeEvent, function (e: any) {
clearTimeout(s._rid);
s._rid = setTimeout(function () {
if (s.autoResize) {
s.resize();
}
let event = new Event("onResize");
s.dispatchEvent(event);
}, 300);
});
setTimeout(function () {
s.resize();
//同时添加到主更新循环中
Stage.addUpdateObj(s);
s.dispatchEvent("onInitStage");
// }
}, 100);
let rc = s.rootDiv;
let mouseEvent = s.onMouseEvent.bind(s);
if (osType != "pc") {
rc.addEventListener("touchstart", mouseEvent, false);
rc.addEventListener('touchmove', mouseEvent, false);
rc.addEventListener('touchend', mouseEvent, false);
} else {
rc.addEventListener("mousedown", mouseEvent, false);
rc.addEventListener('mousemove', mouseEvent, false);
rc.addEventListener('mouseup', mouseEvent, false);
}
}
/**
* @property _touchEvent
* @private
*/
private _touchEvent: TouchEvent;
/**
* 主渲染函数
* @method render
*/
public render(): void {
//放入渲染器中渲染
this.renderObj.render(this);
//dom层的更新
let sf: any = this._floatDisplayList;
let len = sf.length;
for (let i = 0; i < len; i++) {
sf[i].updateStyle();
}
}
/**
* 这个是鼠标事件的MouseEvent对象池,因为如果用户有监听鼠标事件,如果不建立对象池,那每一秒将会new Fps个数的事件对象,影响性能
* @property _ml
* @type {Array}
* @private
*/
private _ml: any = [];
/**
* 这个是事件中用到的Point对象池,以提高性能
* @property _mp
* @type {Array}
* @private
*/
private _mp: any = [];
/**
* 刷新mouse或者touch事件
* @method _initMouseEvent
* @private
*/
private _initMouseEvent(event: MouseEvent, cp: Point, sp: Point, identifier: number): void {
event["_pd"] = false;
event["_bpd"] = false;
event.clientX = cp.x;
event.clientY = cp.y;
event.stageX = sp.x;
event.stageY = sp.y;
event.identifier = identifier;
}
//每一个手指事件的对象池
/**
* @property _mouseDownPoint
* @type {Object}
* @private
*/
private _mouseDownPoint: any = {};
/**
* 循环刷新页面的函数
* @method flush
* @private
* @return {void}
*/
public flush(t): void {
this.timer = t;
let s = this;
if (s._flush == 0) {
s.render();
} else {
if (s._currentFlush == 0) {
s.render();
s._currentFlush = s._flush;
} else {
s._currentFlush--;
}
}
}
/**
* 引擎的刷新率,就是一秒中执行多少次刷新
* @method setFrameRate
* @param {number} fps 最好是60的倍数如 1 2 3 6 10 12 15 20 30 60
* @since 1.0.0
* @public
*/
public setFrameRate(fps: number): void {
let s = this;
s._flush = 60 / fps - 1 >> 0;
if (s._flush < 0) {
s._flush = 0;
}
}
/**
* 引擎的刷新率,就是一秒中执行多少次刷新
* @method getFrameRate
* @since 1.0.0
* @public
*/
// public getFrameRate(): number {
// return 60 / (this._flush + 1);
// }
/**
* 获取引擎所在的div宽高
* @method getRootDivWH
* @public
* @since 1.0.0
* @param {HTMLDivElement} div
* @return {{w: number, h: number}}
*/
public getRootDivWH(div: HTMLDivElement) {
let sw = div.style.width;
let sh = div.style.height;
let iw = document.body.clientWidth;
// let ih = document.body.clientHeight-40;
let ih = document.body.clientHeight;
let vW = parseInt(sw);
let vH = parseInt(sh);
if (vW.toString() == "NaN") {
vW = iw;
} else {
if (sw.indexOf("%") > 0) {
vW *= iw / 100;
}
}
if (vH.toString() == "NaN") {
vH = ih;
} else {
if (sh.indexOf("%") > 0) {
vH *= ih / 100;
}
}
return {w: vW, h: vH};
}
/**
* 当一个stage不再需要使用,或者要从浏览器移除之前,请先停止它,避免内存泄漏
* @method kill
* @since 1.0.0
* @public
*/
public kill(): void {
Stage.removeUpdateObj(this);
}
/**
* html的鼠标或单点触摸对应的引擎事件类型名
* @property _mouseEventTypes
* @type {{mousedown: string, mouseup: string, mousemove: string, touchstart: string, touchmove: string, touchend: string}}
* @private
*/
private _mouseEventTypes: any = {
mousedown: "onMouseDown",
mouseup: "onMouseUp",
mousemove: "onMouseMove",
touchstart: "onMouseDown",
touchmove: "onMouseMove",
touchend: "onMouseUp"
};
/**
* @property muliPoints
* @type {Object[]}
* @private
*/
private muliPoints: Array<any> = [];
/**
* 当document有鼠标或触摸事件时调用
* @property _mP1
* @param e
*/
private _mP1: Point = new Point();
/**
* 当document有鼠标或触摸事件时调用
* @property _mP2
* @param e
*/
private _mP2: Point = new Point();
/**
* 无多指,无拖动
* @method onMouseEvent
* @param e
* @private
*/
private onMouseEvent(e: any): void {
let s: any = this;
//检查mouse或touch事件是否有,如果有的话,就触发事件函数
if (EventDispatcher._totalMEC > 0) {
let points: any;
//事件类型
let item = s._mouseEventTypes[e.type];
let events: any;
let event: any;
//stageMousePoint
let sp: Point;
//localPoint;
let lp: Point;
//clientPoint
let cp: Point;
//事件个数
let eLen: number;
let identifier: any;
if (osType == "pc") {
e.identifier = 0;
points = [e];
} else {
points = [e.changedTouches[0]];
}
for (let o = 0; o < points.length; o++) {
eLen = 0;
events = [];
identifier = "m" + points[o].identifier;
if (s._mp.length > 0) {
cp = s._mp.shift();
} else {
cp = new Point();
}
cp.x = (points[o].clientX - points[o].target.offsetLeft) * devicePixelRatio;
cp.y = (points[o].clientY - points[o].target.offsetTop) * devicePixelRatio;
//计算舞台中的点
sp = s.globalToLocal(cp, DisplayObject._bp);
//检查是否有鼠标事件
if (EventDispatcher.getMouseEventCount() > 0) {
if (!s._ml[eLen]) {
event = new MouseEvent(item);
s._ml[eLen] = event;
} else {
event = s._ml[eLen];
event.type = item;
}
events[events.length] = event;
s._initMouseEvent(event, cp, sp, identifier);
eLen++;
}
if (item == "onMouseDown") {
s._mouseDownPoint[identifier] = cp;
//清空上次存在的显示列表
} else if (item == "onMouseUp") {
if (s._mouseDownPoint[identifier]) {
if (Point.distance(s._mouseDownPoint[identifier], cp) < 20) {
//检查是否有添加对应的click事件
if (EventDispatcher.getMouseEventCount("onMouseClick") > 0) {
if (!s._ml[eLen]) {
event = new MouseEvent("onMouseClick");
s._ml[eLen] = event;
} else {
event = s._ml[eLen];
event.type = "onMouseClick";
}
events[events.length] = event;
s._initMouseEvent(event, cp, sp, identifier);
eLen++;
}
}
}
}
if (eLen > 0) {
//有事件开始遍历显示列表
//找出最底层的显示对象
let d: any = s.hitTestPoint(cp, true);
// console.log(d)
let displayList: Array<DisplayObject> = [];
if (d) {
//证明有点击到事件,然后从最底层追上来,看看一路是否有人添加过mouse或touch事件,还要考虑mousechildren和阻止事件方法
//找出真正的target,因为有些父级可能会mouseChildren=false;
while (d) {
if (d["mouseChildren"] === false) {
//丢掉之前的层级,因为根本没用了
displayList.length = 0;
}
displayList[displayList.length] = d;
d = d.parent;
}
} else {
displayList[displayList.length] = s;
}
let len: number = displayList.length;
for (let i = len - 1; i >= 0; i--) {
d = displayList[i];
for (let j = 0; j < eLen; j++) {
if (!events[j]["_bpd"]) {
if (d.hasEventListener(events[j].type)) {
events[j].currentTarget = d;
events[j].target = displayList[0];
lp = d.globalToLocal(cp, DisplayObject._bp);
events[j].localX = lp.x;
events[j].localY = lp.y;
d.dispatchEvent(events[j]);
}
}
}
}
//这里一定要反转一下,因为会影响mouseOut mouseOver
displayList.reverse();
for (let i = len - 1; i >= 0; i--) {
d = displayList[i];
for (let j = 0; j < eLen; j++) {
if (!events[j]["_bpd"]) {
if (d.hasEventListener(events[j].type)) {
events[j].currentTarget = d;
events[j].target = displayList[eLen - 1];
lp = d.globalToLocal(cp, DisplayObject._bp);
events[j].localX = lp.x;
events[j].localY = lp.y;
d.dispatchEvent(events[j], null, false);
}
}
}
}
//最后要和上一次的遍历者对比下,如果不相同则要触发onMouseOver和onMouseOut
if (item != "onMouseDown") {
if (EventDispatcher.getMouseEventCount("onMouseOver") > 0 || EventDispatcher.getMouseEventCount("onMouseOut") > 0) {
if (s._lastDpList[identifier]) {
//从第二个开始,因为第一个对象始终是stage顶级对象
let len1 = s._lastDpList[identifier].length;
let len2 = displayList.length;
len = len1 > len2 ? len1 : len2;
let isDiff = false;
let overEvent: MouseEvent;
let outEvent: MouseEvent;
for (let i = 1; i < len; i++) {
if (!isDiff) {
if (s._lastDpList[identifier][i] != displayList[i]) {
//确定哪些有onMouseOver,哪些有onMouseOut
isDiff = true;
if (!s._ml[eLen]) {
overEvent = new MouseEvent("onMouseOver");
s._ml[eLen] = overEvent;
} else {
overEvent = s._ml[eLen];
overEvent.type = "onMouseOver";
}
s._initMouseEvent(overEvent, cp, sp, identifier);
eLen++;
if (!s._ml[eLen]) {
outEvent = new MouseEvent("onMouseOut");
s._ml[eLen] = outEvent;
} else {
outEvent = s._ml[eLen];
outEvent.type = "onMouseOut";
}
s._initMouseEvent(outEvent, cp, sp, identifier);
}
}
if (isDiff) {
if (s._lastDpList[identifier][i]) {
//触发onMouseOut事件
if (!outEvent["_bpd"]) {
d = s._lastDpList[identifier][i];
if (d.hasEventListener("onMouseOut")) {
outEvent.currentTarget = d;
outEvent.target = s._lastDpList[identifier][len1 - 1];
lp = d.globalToLocal(cp, DisplayObject._bp);
outEvent.localX = lp.x;
outEvent.localY = lp.y;
d.dispatchEvent(outEvent);
}
}
}
if (displayList[i]) {
//触发onMouseOver事件
if (!overEvent["_bpd"]) {
d = displayList[i];
if (d.hasEventListener("onMouseOver")) {
overEvent.currentTarget = d;
overEvent.target = displayList[len2 - 1];
lp = d.globalToLocal(cp, DisplayObject._bp);
overEvent.localX = lp.x;
overEvent.localY = lp.y;
d.dispatchEvent(overEvent);
}
}
}
}
}
}
}
s._mp[s._mp.length] = cp;
}
if (item == "onMouseUp") {
delete s._mouseDownPoint[identifier];
delete s._lastDpList[identifier];
} else {
s._lastDpList[identifier] = displayList;
}
}
}
}
//禁止默认事件
if (e.target.id == "cusCanvas") {
if (s.isPreventDefaultEvent) {
if ((e.type == "touchend") && (osType == "ios") && (s.iosTouchendPreventDefault)) {
e.preventDefault();
}
if ((e.type == "touchmove") || (e.type == "touchstart" && osType == "android")) {
e.preventDefault();
}
}
}
//暂时不刷新
// if (s._cp) {
// s.update();
// }
};
/**
* 设置舞台的对齐模式
* @method setAlign
* @private
* @return {void}
*/
private setAlign(): void {
let s = this;
let divH = s.divHeight * devicePixelRatio;
let divW = s.divWidth * devicePixelRatio;
let desH = s.desHeight;
let desW = s.desWidth;
s.anchorX = desW >> 1;
s.anchorY = desH >> 1;
//设备是否为竖屏
let isDivH = divH > divW;
//内容是否为竖屏内容
let isDesH = desH > desW;
let scaleY = 1;
let scaleX = 1;
s.x = (divW - desW) >> 1;
s.y = (divH - desH) >> 1;
if (s.autoSteering) {
if (isDesH != isDivH) {
let d = divH;
divH = divW;
divW = d;
}
}
if (s._scaleMode != "noScale") {
scaleY = divH / desH;
scaleX = divW / desW;
switch (s._scaleMode) {
case "noBorder":
if (scaleX > scaleY) {
scaleY = scaleX;
} else {
scaleX = scaleY;
}
break;
case "showAll":
if (scaleX < scaleY) {
scaleY = scaleX;
} else {
scaleX = scaleY;
}
break;
case "fixedWidth":
scaleY = scaleX;
break;
case "fixedHeight":
scaleX = scaleY;
break;
}
}
s.scaleX = scaleX;
s.scaleY = scaleY;
// s.viewRect=new Rectangle();
s.viewRect.x = (desW - divW / scaleX) >> 1;
s.viewRect.y = (desH - divH / scaleY) >> 1;
s.viewRect.width = desW - s.viewRect.x * 2;
s.viewRect.height = desH - s.viewRect.y * 2;
if (s.autoSteering) {
if (isDesH == isDivH) {
s.rotation = 0;
} else {
if (desH > desW) {
s.rotation = -90;
} else {
s.rotation = 90;
}
}
} else {
s.rotation = 0;
}
};
/**
* 当舞台尺寸发生改变时,如果stage autoResize 为 true,则此方法会自己调用;
* 如果设置stage autoResize 为 false 你需要手动调用此方法以更新界面.
* 不管autoResize 的状态是什么,你只要侦听 了stage 的 Event.RESIZE 事件
* 都可以接收到舞台变化的通知。
* @method resize
* @public
* @since 1.0.0
*/
public resize = function (): void {
let s: Stage = this;
let whObj = s.getRootDivWH(s.rootDiv);
s.divHeight = whObj.h;
s.divWidth = whObj.w;
s.renderObj.resize(s.divWidth, s.divHeight);
s.setAlign();
s.render();
};
public getBounds(): Rectangle {
return this.viewRect;
}
/**
* 要循环调用 flush 函数对象列表
* @method allUpdateObjList
* @static
* @since 1.0.0
* @type {Array}
*/
private static allUpdateObjList: Array<any> = [];
/**
* 刷新所有定时器
* @static
* @private
* @since 1.0.0
* @method flushAll
*/
static flushAll(t): void {
if (!Stage._pause) {
let len = Stage.allUpdateObjList.length;
for (let i = 0; i < len; i++) {
Stage.allUpdateObjList[i] && Stage.allUpdateObjList[i].flush(t);
}
}
requestAnimationFrame(Stage.flushAll);
}
/**
* 添加一个刷新对象,这个对象里一定要有一个 flush 函数。
* 因为一但添加,这个对象的 flush 函数会以stage的fps间隔调用
* 如,你的stage是30fps 那么你这个对象的 flush 函数1秒会调用30次。
* @method addUpdateObj
* @param target 要循化调用 flush 函数的对象
* @public
* @static
* @since
*/
public static addUpdateObj(target: any): void {
let isHave: boolean = false;
let len = Stage.allUpdateObjList.length;
for (let i = 0; i < len; i++) {
if (Stage.allUpdateObjList[i] === target) {
isHave = true;
break;
}
}
if (!isHave) {
Stage.allUpdateObjList.unshift(target);
}
}
/**
* 移除掉已经添加的循环刷新对象
* @method removeUpdateObj
* @param target
* @public
* @static
* @since 1.0.0
*/
public static removeUpdateObj(target: any): void {
let len = Stage.allUpdateObjList.length;
for (let i = 0; i < len; i++) {
if (Stage.allUpdateObjList[i] === target) {
Stage.allUpdateObjList.splice(i, 1);
break;
}
}
}
public destroy(): void {
let s = this;
Stage.removeUpdateObj(s);
s.rootDiv = null;
s._floatDisplayList = null;
s.renderObj = null;
s.viewRect = null;
s._lastDpList = null;
s._touchEvent = null;
s.muliPoints = null;
s._mP1 = null;
s._mP2 = null;
s._ml = null;
super.destroy();
}
}
\ No newline at end of file
import { HashObject } from "../HashObject";
/**
* 事件类,引擎中一切事件的基类
* @class Event
* @extends AObject
* @public
* @since 1.0.0
*/
export class Event extends HashObject {
// public static IMAGE_LOADED: string = "onImageLoaded"
public static INIT_TO_STAGE: string = "onInitStage";
/**
* 舞台尺寸发生变化时触发
* @Event
* @property RESIZE
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static RESIZE: string = "onResize";
/**
* 引擎暂停或者恢复暂停时触发,这个事件只能在globalDispatcher 中监听
* @Event
* @property ON_RUN_CHANGED
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static ON_RUN_CHANGED: string = "onRunChanged";
/**
* Media相关媒体类的播放刷新事件。像Sound Video都可以捕捉这种事件。
* @property ON_PLAY_UPDATE
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_PLAY_UPDATE = "onPlayUpdate";
/**
* Media相关媒体类的播放完成事件。像Sound Video都可以捕捉这种事件。
* @property ON_PLAY_END
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_PLAY_END = "onPlayEnd";
/**
* Media相关媒体类的开始播放事件。像Sound Video都可以捕捉这种事件。
* @property ON_PLAY_START
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_PLAY_START = "onPlayStart";
/**
* FlipBook组件翻页开始事件
* @property ON_FLIP_START
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_FLIP_START = "onFlipStart";
/**
* FlipBook组件翻页结束事件
* @property ON_FLIP_STOP
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_FLIP_STOP = "onFlipStop";
/**
* ScrollPage组件滑动到开始位置事件
* @property ON_SCROLL_TO_HEAD
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_SCROLL_TO_HEAD = "onScrollToHead";
/**
* ScrollPage组件停止滑动事件
* @property ON_SCROLL_STOP
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_SCROLL_STOP = "onScrollStop";
/**
* ScrollPage组件开始滑动事件
* @property ON_SCROLL_START
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_SCROLL_START = "onScrollStart";
/**
* ScrollPage组件滑动到结束位置事件
* @property ON_SCROLL_TO_END
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_SCROLL_TO_END = "onScrollToEnd";
/**
* Slide 组件开始滑动事件
* @property ON_SLIDE_START
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_SLIDE_START = "onSlideStart";
/**
* Slide 组件结束滑动事件
* @property ON_SLIDE_END
* @static
* @since 1.1.0
* @type {string}
*/
public static ON_SLIDE_END = "onSlideEnd";
/**
* 舞台初始化完成后会触发的事件
* @property ON_INIT_STAGE
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static ON_INIT_STAGE: string = "onInitStage";
/**
* 显示对象加入到舞台事件
* @Event
* @property ADD_TO_STAGE
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static ADD_TO_STAGE: string = "onAddToStage";
/**
* 显示对象从舞台移出事件
* @Event
* @property REMOVE_TO_STAGE
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static REMOVE_TO_STAGE: string = "onRemoveToStage";
/**
* 显示对象 循环帧事件
* @Event
* @property ENTER_FRAME
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static ENTER_FRAME: string = "onEnterFrame";
/**
* MovieClip 播放完成事件
* @Event
* @property END_FRAME
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static END_FRAME: string = "onEndFrame";
/**
* MovieClip 帧标签事件
* @Event
* @property CALL_FRAME
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static CALL_FRAME: string = "onCallFrame";
/**
* 完成事件
* @Event
* @property COMPLETE
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static COMPLETE: string = "onComplete";
/**
* 加载过程事件
* @Event
* @property PROGRESS
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static PROGRESS: string = "onProgress";
/**
* 出错事件
* @Event
* @property ERROR
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static ERROR: string = "onError";
/**
* 中断事件
* @Event
* @property ABORT
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static ABORT: string = "onAbort";
/**
* 开始事件
* @Event
* @property START
* @type {string}
* @static
* @public
* @since 1.0.0
*/
public static START: string = "onStart";
/**
* 定时器触发事件
* @property TIMER
* @static
* @since 1.0.9
* @public
* @type {string}
*/
public static TIMER: string = "onTimer";
/**
* 定时器完成事件
* @property TIMER_COMPLETE
* @since 1.0.9
* @static
* @public
* @type {string}
*/
public static TIMER_COMPLETE: string = "onTimerComplete";
/**
* 事件类型名
* @property type
* @type {string}
* @public
* @since 1.0.0
*/
public type: string = "";
/**
* 触发此事件的对象
* @property target
* @public
* @since 1.0.0
* @type {any}
*/
public target: any = null;
/**
* 随着事件一起附带的信息对象
* 所有需要随事件一起发送的信息都可以放在此对象中
* @property data
* @public
* @since 1.0.0
* @type {any}
* @default null
*/
public data: any = null;
/**
* @method Event
* @param {string} type 事件类型
* @public
* @since 1.0.0
*/
public constructor(type: string) {
super();
this._instanceType = "Event";
this.type = type;
}
/**
* 防止对事件流中当前节点中和所有后续节点中的事件侦听器进行处理。
* @method stopImmediatePropagation
* @public
* @return {void}
* @since 2.0.0
*/
public stopImmediatePropagation(): void {
this._pd = true;
}
/**
* 防止对事件流中当前节点的后续节点中的所有事件侦听器进行处理。
* @method stopPropagation
* @public
* @since 2.0.0
* @return {void}
*/
public stopPropagation(): void {
this._bpd = true;
}
private _bpd: boolean = false;
/**
* 是否阻止事件向下冒泡
* @property _pd
* @type {boolean}
* @private
* @since 1.0.0
*/
private _pd: boolean = false;
public destroy(): void {
let s = this;
s.target = null;
s.data = null;
}
/**
* 重围事件到初始状态方便重复利用
* @method reset
* @param {string} type
* @param target
* @since 2.0.0
* @return {void}
* @public
*/
public reset(type: string, target: any): void {
let s = this;
s.target = target;
s._pd = false;
s._bpd = false;
s.type = type;
}
}
import { HashObject } from "../HashObject";
import { Event } from "./Event";
/**
* 事件触发基类 功能简单,如果需全能的,到时用EventEmitter3,现成库,事件名,事件,once否集合成实例
* @class EventDispatcher
* @extends HashObject
* @public
* @since 1.0.0
*/
export class EventDispatcher extends HashObject {
/**
* 捕获阶段事件名
*/
protected eventTypes: any = {};
/**
* 冒泡阶段事件名
*/
protected eventTypes1: any = {};
public constructor() {
super();
this._instanceType = "EventDispatcher";
}
/**
* 全局的鼠标事件的监听数对象表,比如{"onMouseMove":9,"onMouseDown":7}
* @property _MECO
* @private
* @since 1.0.0
*/
private static _MECO: any = {};
/**
* 所有鼠标事件的数量
*/
public static _totalMEC: number = 0;
/**
* 看看有多少mouse或者touch侦听数
* @method getMouseEventCount
* @return {number}
* @static
* @private
* @since 1.0.0
* @param {string} type 获取事件类型,默认是所有
*/
public static getMouseEventCount(type: string = ""): number {
let count: number = 0;
if (type == "") {
//返回所有鼠标事件数
for (let item in EventDispatcher._MECO) {
if (item.indexOf("onMouse") == 0) {
count += EventDispatcher._MECO[item];
}
}
} else {
if (EventDispatcher._MECO[type]) {
count = EventDispatcher._MECO[type];
}
}
return count;
}
/**
* 给对象添加一个侦听
* @method addEventListener
* @public
* @since 1.0.0
* @param {string} type 侦听类形
* @param {Function}listener 侦听后的回调方法,如果这个方法是类实例的方法,为了this引用的正确性,请在方法参数后加上.bind(this);
* @param context thisObject
* @param {boolean} useCapture true 捕获阶段 false 冒泡阶段 默认 true
* @example
* this.addEventListener(Event.ADD_TO_STAGE,function(e){trace(this);}.bind(this));
*/
public addEventListener(type: string, listener: Function, context?: any, useCapture: boolean = true): void {
if (!type) {
throw new Error("添加侦听的type值为undefined");
}
if (!listener) {
throw new Error("侦听回调函数不能为null");
}
let s = this;
let eventTypes = s.eventTypes;
if (!useCapture) {
eventTypes = s.eventTypes1;
}
if (!eventTypes[type]) {
eventTypes[type] = [];
}
// if (eventTypes[type].indexOf(listener) < 0) {
// eventTypes[type].unshift(listener);
// if (type.indexOf("onMouse") == 0) {
// s._changeMouseCount(type, true);
// }
// }
//改成EE的
// for (var i = 0, len = eventTypes[type].length; i < len; i++) {
// let ee: EE = eventTypes[type][i]
// if (ee.fn === listener && ee.context === context) {
// console.log("已添加过该事件")
// }
// }
eventTypes[type].unshift(new EE(listener, context || s));
if (type.indexOf("onMouse") == 0) {
s._changeMouseCount(type, true);
}
}
/**
* 监听一次
* @param type
* @param listener
* @param context
* @param useCapture
*/
public once(type: string, listener: Function, context?: any, useCapture: boolean = true): void {
if (!type) {
throw new Error("添加侦听的type值为undefined");
}
if (!listener) {
throw new Error("侦听回调函数不能为null");
}
let s = this;
let eventTypes = s.eventTypes;
if (!useCapture) {
eventTypes = s.eventTypes1;
}
if (!eventTypes[type]) {
eventTypes[type] = [];
}
eventTypes[type].unshift(new EE(listener, context || s, true));
if (type.indexOf("onMouse") == 0) {
s._changeMouseCount(type, true);
}
}
/**
* 增加或删除相应mouse或touch侦听记数
* @method _changeMouseCount
* @private
* @since 1.0.0
* @param {string} type
* @param {boolean} isAdd
*/
private _changeMouseCount(type: string, isAdd: boolean): void {
let count = isAdd ? 1 : -1;
if (!EventDispatcher._MECO[type]) {
EventDispatcher._MECO[type] = 0;
}
EventDispatcher._MECO[type] += count;
if (EventDispatcher._MECO[type] < 0) {
EventDispatcher._MECO[type] = 0;
}
EventDispatcher._totalMEC += count;
}
private _defaultEvent: Event;
/**
* 广播侦听
* @method dispatchEvent
* @public
* @since 1.0.0
* @param {Event|string} event 广播所带的事件对象,如果传的是字符串则直接自动生成一个的事件对象,事件类型就是你传入进来的字符串的值
* @param {Object} data 广播后跟着事件一起传过去的其他任信息,默认值为null,在传参中
* @param {boolean} useCapture true 捕获阶段 false 冒泡阶段 默认 true
* @return {boolean} 如果有收听者则返回true
* @example
* var mySprite=new Sprite(),
* yourEvent=new Event("yourCustomerEvent");
* yourEvent.data='false2x';
* mySprite.addEventListener("yourCustomerEvent",function(e){
* trace(e.data);
* })
* mySprite.dispatchEvent(yourEvent);
*/
public dispatchEvent(event: any, data: any = null, useCapture: boolean = true): boolean {
let s = this;
if (typeof (event) == "string") {
if (!s._defaultEvent) {
s._defaultEvent = new Event(event);
} else {
s._defaultEvent.reset(event, s);
}
event = s._defaultEvent;
}
let listeners = s.eventTypes[event.type];
if (!useCapture) {
listeners = s.eventTypes1[event.type];
}
if (listeners) {
let len = listeners.length;
if (event.target == null) {
event.target = s;
}
if (data != null) {
event.data = data;
}
for (let i = len - 1; i >= 0; i--) {
if (!event["_pd"]) {
if (listeners[i]) {
listeners[i].fn.call(listeners[i].context, event)
if (listeners[i].once) {
s.removeEventListener(event.type, listeners[i].fn, listeners[i].context);
}
// listeners[i](event);
} else {
listeners.splice(i, 1);
}
}
}
return true;
} else {
return false;
}
}
/**
* 是否有添加过此类形的侦听
* @method hasEventListener
* @public
* @since 1.0.0
* @param {string} type 侦听类形
* @param {boolean} useCapture true 捕获阶段 false 冒泡阶段 默认 true
* @return {boolean} 如果有则返回true
*/
public hasEventListener(type: string, useCapture: boolean = true): boolean {
let s = this;
if (useCapture) {
if (s.eventTypes[type] && s.eventTypes[type].length > 0) {
return true
}
} else {
if (s.eventTypes1[type] && s.eventTypes1[type].length > 0) {
return true
}
}
return false;
}
/**
* 移除对应类型的侦听
* @method removeEventListener
* @public
* @since 1.0.0
* @param {string} type 要移除的侦听类型
* @param {Function} listener 及侦听时绑定的回调方法
* @param context listener和context都相等的才移除,默认自身
* @param {boolean} useCapture true 捕获阶段 false 冒泡阶段 默认 true
*/
public removeEventListener(type: string, listener: Function, context?: any, useCapture: boolean = true): void {
let s = this;
let listeners = s.eventTypes[type];
if (!useCapture) {
listeners = s.eventTypes1[type];
}
if (listeners) {
let len = listeners.length;
let thisObject = context || s;
for (let i = len - 1; i >= 0; i--) {
if (listeners[i].fn === listener && listeners[i].context === thisObject) {
listeners.splice(i, 1);
if (type.indexOf("onMouse") == 0) {
s._changeMouseCount(type, false);
}
}
// if (listeners[i] === listener) {
// listeners.splice(i, 1);
// if (type.indexOf("onMouse") == 0) {
// s._changeMouseCount(type, false);
// }
// }
}
}
}
/**
* 移除对象中所有的侦听
* @method removeAllEventListener
* @public
* @since 1.0.0
*/
public removeAllEventListener() {
let s = this;
for (let type in s.eventTypes) {
if (type.indexOf("onMouse") == 0) {
for (let j = 0; j < s.eventTypes[type].length; j++) {
s._changeMouseCount(type, false);
}
}
}
for (let type in s.eventTypes1) {
if (type.indexOf("onMouse") == 0) {
for (let j = 0; j < s.eventTypes1[type].length; j++) {
s._changeMouseCount(type, false);
}
}
}
s.eventTypes1 = {};
s.eventTypes = {};
}
destroy(): void {
let s = this;
s.removeAllEventListener();
s.eventTypes = null;
}
}
/**
* 为了实现带入this和once
*/
class EE {
fn: Function;
context: any;
once: boolean;
constructor(fn: Function, context: any, once: boolean = false) {
this.fn = fn;
this.context = context;
this.once = once;
}
}
export class GDispatcher {
/**
* 事件回调池
*/
private static callbackPool: any = {};
/**
* 事件作用域池
*/
private static thisObjPool: any = {};
/**
*
* @param name 事件名
* @param callback 回调
* @param thisObj 作用域
*/
public static addEvent(name: string, callback, thisObj: any): void {
if (!this.callbackPool[name]) {
this.callbackPool[name] = [];
this.thisObjPool[name] = [];
}
const index: number = this.callbackPool[name].indexOf(callback);
if (index != -1) {
this.callbackPool[name][index] = callback;
this.thisObjPool[name][index] = thisObj;
} else {
this.callbackPool[name].push(callback);
this.thisObjPool[name].push(thisObj);
}
}
/**
*
* @param name 事件名
* @param callback 回调
* @param thisObj 作用域
*/
public static removeEvent(name: string, callback, thisObj?: any): void {
if (this.callbackPool[name]) {
const index: number = this.callbackPool[name].indexOf(callback);
if (index != -1) {
this.callbackPool[name].splice(index, 1);
this.thisObjPool[name].splice(index, 1);
}
}
}
/**
* 派发事件
* @param name 事件名
* @param args 任意参数
*/
public static dispatchEvent(name: string, ...args): void {
const callbacks: Function[] = this.callbackPool[name];
const thisObjs: any = this.thisObjPool[name];
if (callbacks) {
let i = 0;
const len: number = callbacks.length;
for (i; i < len; i++) {
callbacks[i].apply(thisObjs[i], args);
}
}
}
}
\ No newline at end of file
import { Event } from "./Event";
import { DisplayObject } from "../display/DisplayObject";
/**
* 鼠标事件类,电脑端鼠标,移动设备端的触摸都使用此事件来监听
* @class MouseEvent
* @extends Event
* @public
* @since 1.0.0
*/
export class MouseEvent extends Event {
/**
* 鼠标或者手指按下事件
* @property MOUSE_DOWN
* @static
* @public
* @since 1.0.0
* @type {string}
*/
public static MOUSE_DOWN: string = "onMouseDown";
/**
* 鼠标或者手指抬起事件
* @property MOUSE_UP
* @static
* @public
* @since 1.0.0
* @type {string}
*/
public static MOUSE_UP: string = "onMouseUp";
/**
* 鼠标或者手指单击
* @property CLICK
* @static
* @public
* @since 1.0.0
* @type {string}
*/
public static CLICK: string = "onMouseClick";
/**
* 鼠标或者手指移动事件
* @property MOUSE_MOVE
* @static
* @public
* @since 1.0.0
* @type {string}
*/
public static MOUSE_MOVE: string = "onMouseMove";
/**
* 鼠标或者手指移入到显示对象上里触发的事件
* @property MOUSE_OVER
* @static
* @public
* @since 1.0.0
* @type {string}
*/
public static MOUSE_OVER: string = "onMouseOver";
/**
* 鼠标或者手指移出显示对象边界触发的事件
* @property MOUSE_OUT
* @static
* @public
* @since 1.0.0
* @type {string}
*/
public static MOUSE_OUT: string = "onMouseOut";
/**
* mouse或touch事件时rootDiv坐标x点
* @property clientX
* @public
* @since 1.0.0
* @type {number}
*/
public clientX: number = 0;
/**
* mouse或touch事件时rootDiv坐标y点
* @property clientY
* @public
* @since 1.0.0
* @type {number}
*/
public clientY: number = 0;
/**
* mouse或touch事件时全局坐标x点
* @property stageX
* @public
* @since 1.0.0
* @type {number}
*/
public stageX: number = 0;
/**
* mouse或touch事件时全局坐标y点
* @property stageY
* @public
* @since 1.0.0
* @type {number}
*/
public stageY: number = 0;
/**
* mouse或touch事件时本地坐标x点
* @property localX
* @public
* @since 1.0.0
* @type {number}
*/
public localX: number = 0;
/**
* mouse或touch事件时本地坐标y点
* @property localY
* @public
* @since 1.0.0
* @type {number}
*/
public localY: number = 0;
/**
* 绑定此事件的侦听对象
* @property currentTarget
* @public
* @since 1.0.0
* @type{DisplayObject}
* @default null
*/
public currentTarget: DisplayObject = null;
/**
* 触摸或者鼠标事件的手指唯一标识
* @property identifier
* @type {number}
* @since 1.1.2
* @public
*/
public identifier: any = 0;
/**
* @method MouseEvent
* @public
* @since 1.0.0
* @param {string} type
*/
public constructor(type: string) {
super(type);
this._instanceType = "MouseEvent";
}
/**
* 事件后立即更新显示列表状态
* @method updateAfterEvent
* @since 1.0.9
* @public
*/
public updateAfterEvent() {
this.target.stage._cp = true;
}
public destroy(): void {
//清除相应的数据引用
let s = this;
s.currentTarget = null;
super.destroy();
}
}
\ No newline at end of file
import { Point } from "../math/Point";
import { Event } from "./Event";
/**
* 暂时不用,无效
* 多点触碰事件。单点事件请使用mouseEvent,pc和mobile通用
* @class TouchEvent
* @extends Event
*/
export class TouchEvent extends Event {
/**
* @property ON_MULTI_TOUCH
* @static
* @public
* @since 1.0.3
* @type {string}
*/
public static ON_MULTI_TOUCH: string = "onMultiTouch";
/**
* 多点事件中点的信息,两个手指的点的在Canvas中的信息,第1个点。
* 此点坐标不是显示对象中的点坐标,是原始的canvas中的点坐标。
* 如果需要获取显示对象中此点对应的位置,包括stage在内,请用对象的getGlobalToLocal方法转换。
* @property clientPoint1
* @public
* @since 1.0.3
* @type {Point}
*/
public clientPoint1: Point = new Point();
/**
* 多点事件中点的信息,两个手指的点的在Canvas中的信息,第2个点。
* 此点坐标不是显示对象中的点坐标,是原始的canvas中的点坐标。
* 如果需要获取显示对象中此点对应的位置,包括stage在内,请用对象的getGlobalToLocal方法转换。
* @property clientPoint2
* @public
* @since 1.0.3
* @type {Point}
*/
public clientPoint2: Point = new Point();
/**
* 相对于上一次的缩放值
* @property scale
* @since 1.0.3
*/
public scale: number = 0;
/**
* 相对于上一次的旋转值
* @property rotate
* @since 1.0.3
*/
public rotate: number = 0;
/**
* @method TouchEvent
* @public
* @since 1.0.3
* @param {string} type
*/
public constructor(type: string) {
super(type);
this._instanceType = "TouchEvent";
}
/**
* 事件后立即更新显示列表状态
* @method updateAfterEvent
* @since 1.0.9
* @public
*/
public updateAfterEvent() {
this.target.stage._cp = true;
}
public destroy(): void {
//清除相应的数据引用
let s = this;
s.clientPoint1 = null;
s.clientPoint2 = null;
super.destroy();
}
}
\ No newline at end of file
import GraphicsData from './GraphicsData';
import RenderTexture from "../texture/RenderTexture";
import { Matrix, Point, Rectangle } from '../math';
import { RoundedRectangle, Ellipse, Polygon, Circle } from "./shapes"
import { hex2rgb, rgb2hex, sign, string2hex } from '../utils';
import { SHAPES, BLEND_MODES, PI_2, SCALE_MODES } from '../const';
import bezierCurveTo from './bezierCurveTo';
import { DisplayObject } from '../display/DisplayObject';
import Texture from '../texture/Texture';
import CanvasRenderer from '../renderers/CanvasRenderer';
import { Event } from "../events/Event"
import { WebglRenderer } from '../renderers/WebglRenderer';
import buildPoly from './geomBuild/buildPoly';
import buildCircle from './geomBuild/buildCircle';
import buildRectangle from './geomBuild/buildRectangle';
import buildRoundedRectangle from './geomBuild/buildRoundedRectangle';
let canvasRenderer: CanvasRenderer;
const tempMatrix = new Matrix();
const tempPoint = new Point();
const tempColor1 = new Float32Array(4);
const tempColor2 = new Float32Array(4);
//用于canvas缓存标志
let tempCacheStatus;
//geoBatchPart对象池
const GEOBATCH_POOL: geoBatchPart[] = [];
//分割三角指令托管
const fillCommands = {};
fillCommands[SHAPES.POLY] = buildPoly;
fillCommands[SHAPES.CIRC] = buildCircle;
fillCommands[SHAPES.ELIP] = buildCircle;
fillCommands[SHAPES.RECT] = buildRectangle;
fillCommands[SHAPES.RREC] = buildRoundedRectangle;
/**
* @class Graphics
* 不继承container,不然缓存时太麻烦了
*/
export default class Graphics extends DisplayObject {
/**
* 填充透明度
* @member {number}
* @default 1
*/
fillAlpha: number;
/**
* 填充颜色
*/
fillColor: number;
/**
* 默认false,webgl用
* If true the lines will be draw using LINES instead of TRIANGLE_STRIP
* @member {boolean}
*/
nativeLines: boolean;
/**
* 线宽。为0则不绘制stroke
* @member {number}
* @default 0
*/
lineWidth: number;
/**
* 线透明度
* @default 1
*/
lineAlpha: number;
/**
* strokeColor
* @member {string}
* @default 0
*/
lineColor: number;
/**
* 线的对齐方式
* The alignment of any lines drawn (0.5 = middle, 1 = outter, 0 = inner).
* @member {number}
* @default 0
*/
lineAlignment: number;
/**
* 图形数据,为了一个Graphics里能绘制多个
* @member {GraphicsData[]}
*/
graphicsData: GraphicsData[];
/**
* Current path
* 只用于画多边形时用,其他时候会置null
* @member {GraphicsData}
* @private
*/
private currentPath: GraphicsData;
/**
* webgl用对象
* Array containing some WebGL-related properties used by the WebGL renderer.
* @member {object<number, object>}
* @private
*/
// TODO - _webgl should use a prototype object, not a random undocumented object...
_webGL: {};
/**
* 是否用作mask
* @member {boolean}
*/
isUsedToMask: boolean;
/**
* The bounds' padding used for bounds calculation.
* @member {number}
*/
boundsPadding: number;
/**
* Used to detect if the graphics object has changed. If this is set to true then the graphics
* object will be recalculated.
* 通过比对确定是否该刷新
* @member {boolean}
* @private
*/
private dirty: number;
/**
* webgl数据的刷新检测
* Used to detect if we clear the graphics webGL data
* @type {Number}
*/
clearDirty: number;
/**
*
* Used to detect if we we need to recalculate local bounds
* @type {Number}
*/
boundsDirty: number;
/**
* 当前是否填充fill
*/
filling: boolean;
/**
* canvas必用缓存
* renderCanvas默认用缓存,也就canvas上使用,如果经常需要重绘,设置为false
* webgl上用贴图占用GPU空间太大,用其他方法实现,贴图是白图就一张,用几何方法实现
* @name cacheAsBitmap
* @member {boolean}
* @memberof Graphics#
* @default false
*/
cacheAsBitmap: boolean = true;
/**
* 需与dirty一致
*/
cacheDirty: number;
_canvasBuffer: RenderTexture;
//缓存的贴图
_texture: Texture;
//如果用缓存绘图,必须考虑偏移量;
offsetX: number;
offsetY: number;
//webgl专用数据
verts;
indices;
batches;
geoBatches: geoBatchPart[];
batchDirty;
uvs;
shapeIndex;
vertexData
_transformID;
constructor() {
super();
this.fillAlpha = 1;
this.lineWidth = 0;
this.nativeLines = false;
this.lineColor = 0;
this.lineAlignment = 0.5;
this.graphicsData = [];
this.currentPath = null;
this._webGL = {};
this.isUsedToMask = false;
this.boundsPadding = 0;
this.dirty = 0;
this.clearDirty = 0;
this.boundsDirty = -1;
this.cacheDirty = -1;
this.verts = [];
this.indices = []
this.batches = [];
this.geoBatches = [];
this.uvs = [];
this.batchDirty = -1;
this.shapeIndex = 0;
this.vertexData = null;
this._transformID = -1;
}
/**
* 克隆该Graphics的几何绘制,不包括它自身的transform
* @return {Graphics} A clone of the graphics object
*/
clone(): Graphics {
const clone = new Graphics();
clone.renderable = this.renderable;
clone.fillAlpha = this.fillAlpha;
clone.lineWidth = this.lineWidth;
clone.lineColor = this.lineColor;
clone.lineAlignment = this.lineAlignment;
clone.isUsedToMask = this.isUsedToMask;
clone.boundsPadding = this.boundsPadding;
clone.dirty = 0;
// copy graphics data
for (let i = 0; i < this.graphicsData.length; ++i) {
clone.graphicsData.push(this.graphicsData[i].clone());
}
clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1];
clone.updateLocalBoundsSelf();
return clone;
}
/**
* Calculate length of quadratic curve
* @see {@link http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/}
* for the detailed explanation of math behind this.
*
* @private
* @param {number} fromX - x-coordinate of curve start point
* @param {number} fromY - y-coordinate of curve start point
* @param {number} cpX - x-coordinate of curve control point
* @param {number} cpY - y-coordinate of curve control point
* @param {number} toX - x-coordinate of curve end point
* @param {number} toY - y-coordinate of curve end point
* @return {number} Length of quadratic curve
*/
private _quadraticCurveLength(
fromX: number,
fromY: number,
cpX: number,
cpY: number,
toX: number,
toY: number): number {
const ax = fromX - ((2.0 * cpX) + toX);
const ay = fromY - ((2.0 * cpY) + toY);
const bx = 2.0 * ((cpX - 2.0) * fromX);
const by = 2.0 * ((cpY - 2.0) * fromY);
const a = 4.0 * ((ax * ax) + (ay * ay));
const b = 4.0 * ((ax * bx) + (ay * by));
const c = (bx * bx) + (by * by);
const s = 2.0 * Math.sqrt(a + b + c);
const a2 = Math.sqrt(a);
const a32 = 2.0 * a * a2;
const c2 = 2.0 * Math.sqrt(c);
const ba = b / a2;
return (
(a32 * s)
+ (a2 * b * (s - c2))
+ (
((4.0 * c * a) - (b * b))
* Math.log(((2.0 * a2) + ba + s) / (ba + c2))
)
)
/ (4.0 * a32);
}
/**
* Calculate length of bezier curve.
* Analytical solution is impossible, since it involves an integral that does not integrate in general.
* Therefore numerical solution is used.
*
* @private
* @param {number} fromX - Starting point x
* @param {number} fromY - Starting point y
* @param {number} cpX - Control point x
* @param {number} cpY - Control point y
* @param {number} cpX2 - Second Control point x
* @param {number} cpY2 - Second Control point y
* @param {number} toX - Destination point x
* @param {number} toY - Destination point y
* @return {number} Length of bezier curve
*/
private _bezierCurveLength(
fromX: number,
fromY: number,
cpX: number,
cpY: number,
cpX2: number,
cpY2: number,
toX: number,
toY: number): number {
const n = 10;
let result = 0.0;
let t = 0.0;
let t2 = 0.0;
let t3 = 0.0;
let nt = 0.0;
let nt2 = 0.0;
let nt3 = 0.0;
let x = 0.0;
let y = 0.0;
let dx = 0.0;
let dy = 0.0;
let prevX = fromX;
let prevY = fromY;
for (let i = 1; i <= n; ++i) {
t = i / n;
t2 = t * t;
t3 = t2 * t;
nt = (1.0 - t);
nt2 = nt * nt;
nt3 = nt2 * nt;
x = (nt3 * fromX) + (3.0 * nt2 * t * cpX) + (3.0 * nt * t2 * cpX2) + (t3 * toX);
y = (nt3 * fromY) + (3.0 * nt2 * t * cpY) + (3 * nt * t2 * cpY2) + (t3 * toY);
dx = prevX - x;
dy = prevY - y;
prevX = x;
prevY = y;
result += Math.sqrt((dx * dx) + (dy * dy));
}
return result;
}
/**
* Calculate number of segments for the curve based on its length to ensure its smoothness.
*
* @private
* @param {number} length - length of curve
* @return {number} Number of segments
*/
private _segmentsCount(length: number): number {
let result = Math.ceil(length / Graphics.CURVES.maxLength);
if (result < Graphics.CURVES.minSegments) {
result = Graphics.CURVES.minSegments;
}
else if (result > Graphics.CURVES.maxSegments) {
result = Graphics.CURVES.maxSegments;
}
return result;
}
/**
* line属于附属属性,暂不写beginStroke,只要没调用过beginFill就是纯stroke
* Specifies the line style used for subsequent calls to Graphics methods such as the lineTo()
* method or the drawCircle() method.
* @param {number} [lineWidth=0] - width of the line to draw, will update the objects stored style
* @param {number} [color=0] - color of the line to draw, will update the objects stored style
* @param {number} [alpha=1] - alpha of the line to draw, will update the objects stored style
* @param {number} [alignment=0.5] - alignment of the line to draw, (0 = inner, 0.5 = middle, 1 = outter)
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
lineStyle(lineWidth: number = 0, color: number = 0, alpha: number = 1, alignment: number = 0.5): Graphics {
this.lineWidth = lineWidth;
this.lineColor = color;
this.lineAlpha = alpha;
this.lineAlignment = alignment;
if (this.currentPath) {
if (this.currentPath.shape["points"].length) {
// halfway through a line? start a new one!
const shape = new Polygon(this.currentPath.shape["points"].slice(-2));
shape.closed = false;
this.drawShape(shape);
}
else {
// otherwise its empty so lets just set the line properties
this.currentPath.lineWidth = this.lineWidth;
this.currentPath.lineColor = this.lineColor;
this.currentPath.lineAlpha = this.lineAlpha;
this.currentPath.lineAlignment = this.lineAlignment;
}
}
return this;
}
/**
* Moves the current drawing position to x, y.
*
* @param {number} x - the X coordinate to move to
* @param {number} y - the Y coordinate to move to
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
moveTo(x: number, y: number): Graphics {
const shape = new Polygon([x, y]);
shape.closed = false;
this.drawShape(shape);
return this;
}
/**
* Draws a line using the current line style from the current drawing position to (x, y);
* The current drawing position is then set to (x, y).
*
* @param {number} x - the X coordinate to draw to
* @param {number} y - the Y coordinate to draw to
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
lineTo(x: number, y: number): Graphics {
this.currentPath.shape["points"].push(x, y);
this.dirty++;
return this;
}
/**
* Calculate the points for a quadratic bezier curve and then draws it.
* Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c
*
* @param {number} cpX - Control point x
* @param {number} cpY - Control point y
* @param {number} toX - Destination point x
* @param {number} toY - Destination point y
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
quadraticCurveTo(cpX: number, cpY: number, toX: number, toY: number): Graphics {
if (this.currentPath) {
if (this.currentPath.shape["points"].length === 0) {
this.currentPath.shape["points"] = [0, 0];
}
}
else {
this.moveTo(0, 0);
}
const points = this.currentPath.shape["points"];
let xa = 0;
let ya = 0;
if (points.length === 0) {
this.moveTo(0, 0);
}
const fromX = points[points.length - 2];
const fromY = points[points.length - 1];
const n = Graphics.CURVES.adaptive
? this._segmentsCount(this._quadraticCurveLength(fromX, fromY, cpX, cpY, toX, toY))
: 20;
for (let i = 1; i <= n; ++i) {
const j = i / n;
xa = fromX + ((cpX - fromX) * j);
ya = fromY + ((cpY - fromY) * j);
points.push(xa + (((cpX + ((toX - cpX) * j)) - xa) * j),
ya + (((cpY + ((toY - cpY) * j)) - ya) * j));
}
this.dirty++;
return this;
}
/**
* Calculate the points for a bezier curve and then draws it.
*
* @param {number} cpX - Control point x
* @param {number} cpY - Control point y
* @param {number} cpX2 - Second Control point x
* @param {number} cpY2 - Second Control point y
* @param {number} toX - Destination point x
* @param {number} toY - Destination point y
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
bezierCurveTo(cpX: number, cpY: number, cpX2: number, cpY2: number, toX: number, toY: number): Graphics {
if (this.currentPath) {
if (this.currentPath.shape["points"].length === 0) {
this.currentPath.shape["points"] = [0, 0];
}
}
else {
this.moveTo(0, 0);
}
const points = this.currentPath.shape["points"];
const fromX = points[points.length - 2];
const fromY = points[points.length - 1];
points.length -= 2;
const n = Graphics.CURVES.adaptive
? this._segmentsCount(this._bezierCurveLength(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY))
: 20;
bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, n, points);
this.dirty++;
return this;
}
/**
* The arcTo() method creates an arc/curve between two tangents on the canvas.
*
* "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google!
*
* @param {number} x1 - The x-coordinate of the beginning of the arc
* @param {number} y1 - The y-coordinate of the beginning of the arc
* @param {number} x2 - The x-coordinate of the end of the arc
* @param {number} y2 - The y-coordinate of the end of the arc
* @param {number} radius - The radius of the arc
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): Graphics {
if (this.currentPath) {
if (this.currentPath.shape["points"].length === 0) {
this.currentPath.shape["points"].push(x1, y1);
}
}
else {
this.moveTo(x1, y1);
}
const points = this.currentPath.shape["points"];
const fromX = points[points.length - 2];
const fromY = points[points.length - 1];
const a1 = fromY - y1;
const b1 = fromX - x1;
const a2 = y2 - y1;
const b2 = x2 - x1;
const mm = Math.abs((a1 * b2) - (b1 * a2));
if (mm < 1.0e-8 || radius === 0) {
if (points[points.length - 2] !== x1 || points[points.length - 1] !== y1) {
points.push(x1, y1);
}
}
else {
const dd = (a1 * a1) + (b1 * b1);
const cc = (a2 * a2) + (b2 * b2);
const tt = (a1 * a2) + (b1 * b2);
const k1 = radius * Math.sqrt(dd) / mm;
const k2 = radius * Math.sqrt(cc) / mm;
const j1 = k1 * tt / dd;
const j2 = k2 * tt / cc;
const cx = (k1 * b2) + (k2 * b1);
const cy = (k1 * a2) + (k2 * a1);
const px = b1 * (k2 + j1);
const py = a1 * (k2 + j1);
const qx = b2 * (k1 + j2);
const qy = a2 * (k1 + j2);
const startAngle = Math.atan2(py - cy, px - cx);
const endAngle = Math.atan2(qy - cy, qx - cx);
this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1);
}
this.dirty++;
return this;
}
/**
* The arc method creates an arc/curve (used to create circles, or parts of circles).
*
* @param {number} cx - The x-coordinate of the center of the circle
* @param {number} cy - The y-coordinate of the center of the circle
* @param {number} radius - The radius of the circle
* @param {number} startAngle - The starting angle, in radians (0 is at the 3 o'clock position
* of the arc's circle)
* @param {number} endAngle - The ending angle, in radians
* @param {boolean} [anticlockwise=false] - Specifies whether the drawing should be
* counter-clockwise or clockwise. False is default, and indicates clockwise, while true
* indicates counter-clockwise.
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
arc(cx: number, cy: number, radius: number, startAngle: number, endAngle: number, anticlockwise: boolean = false): Graphics {
if (startAngle === endAngle) {
return this;
}
if (!anticlockwise && endAngle <= startAngle) {
endAngle += PI_2;
}
else if (anticlockwise && startAngle <= endAngle) {
startAngle += PI_2;
}
const sweep = endAngle - startAngle;
const segs = Graphics.CURVES.adaptive
? this._segmentsCount(Math.abs(sweep) * radius)
: Math.ceil(Math.abs(sweep) / PI_2) * 40;
if (sweep === 0) {
return this;
}
const startX = cx + (Math.cos(startAngle) * radius);
const startY = cy + (Math.sin(startAngle) * radius);
// If the currentPath exists, take its points. Otherwise call `moveTo` to start a path.
let points = this.currentPath ? this.currentPath.shape["points"] : null;
if (points) {
// We check how far our start is from the last existing point
const xDiff = Math.abs(points[points.length - 2] - startX);
const yDiff = Math.abs(points[points.length - 1] - startY);
if (xDiff < 0.001 && yDiff < 0.001) {
// If the point is very close, we don't add it, since this would lead to artifacts
// during tesselation due to floating point imprecision.
}
else {
points.push(startX, startY);
}
}
else {
this.moveTo(startX, startY);
points = this.currentPath.shape["points"];
}
const theta = sweep / (segs * 2);
const theta2 = theta * 2;
const cTheta = Math.cos(theta);
const sTheta = Math.sin(theta);
const segMinus = segs - 1;
const remainder = (segMinus % 1) / segMinus;
for (let i = 0; i <= segMinus; ++i) {
const real = i + (remainder * i);
const angle = ((theta) + startAngle + (theta2 * real));
const c = Math.cos(angle);
const s = -Math.sin(angle);
points.push(
(((cTheta * c) + (sTheta * s)) * radius) + cx,
(((cTheta * -s) + (sTheta * c)) * radius) + cy
);
}
this.dirty++;
return this;
}
/**
* Specifies a simple one-color fill that subsequent calls to other Graphics methods
* (such as lineTo() or drawCircle()) use when drawing.
*
* @param {number} [color=0] - the color of the fill 十六进制颜色0xffffff
* @param {number} [alpha=1] - the alpha of the fill
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
beginFill(color: number | string = 0, alpha: number = 1): Graphics {
if (typeof (color) == "string") color = string2hex(color)
this.filling = true;
this.fillColor = color;
this.fillAlpha = alpha;
if (this.currentPath) {
if (this.currentPath.shape["points"].length <= 2) {
this.currentPath.fill = this.filling;
this.currentPath.fillColor = this.fillColor;
this.currentPath.fillAlpha = this.fillAlpha;
}
}
return this;
}
/**
* Applies a fill to the lines and shapes that were added since the last call to the beginFill() method.
*
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
endFill(): Graphics {
this.filling = false;
this.fillColor = null;
this.fillAlpha = 1;
return this;
}
/**
* 画矩形
* @param {number} x - The X coord of the top-left of the rectangle
* @param {number} y - The Y coord of the top-left of the rectangle
* @param {number} width - The width of the rectangle
* @param {number} height - The height of the rectangle
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
drawRect(x: number, y: number, width: number, height: number): Graphics {
this.drawShape(new Rectangle(x, y, width, height));
return this;
}
/**
* 画圆角矩形
* @param {number} x - The X coord of the top-left of the rectangle
* @param {number} y - The Y coord of the top-left of the rectangle
* @param {number} width - The width of the rectangle
* @param {number} height - The height of the rectangle
* @param {number} radius - Radius of the rectangle corners
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
drawRoundedRect(x: number, y: number, width: number, height: number, radius: number): Graphics {
this.drawShape(new RoundedRectangle(x, y, width, height, radius));
return this;
}
/**
* 画圆形
* @param {number} x - The X coordinate of the center of the circle
* @param {number} y - The Y coordinate of the center of the circle
* @param {number} radius - The radius of the circle
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
drawCircle(x: number, y: number, radius: number): Graphics {
this.drawShape(new Circle(x, y, radius));
return this;
}
/**
* 画椭圆
* @param {number} x - The X coordinate of the center of the ellipse
* @param {number} y - The Y coordinate of the center of the ellipse
* @param {number} width - The half width of the ellipse
* @param {number} height - The half height of the ellipse
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
drawEllipse(x: number, y: number, width: number, height: number): Graphics {
this.drawShape(new Ellipse(x, y, width, height));
return this;
}
/**
* 画多边形
* @param {number[]|Point[]|Polygon} path - The path data used to construct the polygon.
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
drawPolygon(path: number[] | Point[] | Polygon): Graphics {
// prevents an argument assignment deopt
// see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
let points = path;
let closed = true;
if (points instanceof Polygon) {
closed = points.closed;
points = points.points;
}
if (!Array.isArray(points)) {
// prevents an argument leak deopt
// see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
points = new Array(arguments.length);
for (let i = 0; i < points.length; ++i) {
points[i] = arguments[i]; // eslint-disable-line prefer-rest-params
}
}
const shape = new Polygon(points);
shape.closed = closed;
this.drawShape(shape);
return this;
}
/**
* 画任意多角形。points为数量
* @param {number} x - Center X position of the star
* @param {number} y - Center Y position of the star
* @param {number} points - The number of points of the star, must be > 1
* @param {number} radius - The outer radius of the star
* @param {number} [innerRadius] - The inner radius between points, default half `radius`
* @param {number} [rotation=0] - The rotation of the star in radians, where 0 is vertical
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
drawStar(
x: number,
y: number,
points: number,
radius: number,
innerRadius: number,
rotation: number = 0): Graphics {
innerRadius = innerRadius || radius / 2;
const startAngle = (-1 * Math.PI / 2) + rotation;
const len = points * 2;
const delta = PI_2 / len;
const polygon = [];
for (let i = 0; i < len; i++) {
const r = i % 2 ? innerRadius : radius;
const angle = (i * delta) + startAngle;
polygon.push(
x + (r * Math.cos(angle)),
y + (r * Math.sin(angle))
);
}
return this.drawPolygon(polygon);
}
/**
* Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings.
*
* @return {Graphics} This Graphics object. Good for chaining method calls
*/
clear(): Graphics {
if (this.lineWidth || this.filling || this.graphicsData.length > 0) {
this.lineWidth = 0;
this.lineAlignment = 0.5;
this.filling = false;
this.boundsDirty = -1;
this.dirty++;
this.clearDirty++;
this.graphicsData.length = 0;
this.verts.length = 0;
this.uvs.length = 0;
this.indices.length = 0;
this.shapeIndex = 0;
for (let i = 0; i < this.geoBatches.length; i++) {
const batch = this.geoBatches[i];
batch.start = 0;
batch.attribStart = 0;
batch.alpha = 1;
batch.color = 0xffffff;
GEOBATCH_POOL.push(batch);
}
this.geoBatches.length = 0;
}
this.currentPath = null;
return this;
}
/**
* 暂时不用
* @returns {boolean} True if only 1 rect.
*/
isFastRect(): boolean {
// return this.graphicsData.length === 1
// && this.graphicsData[0].shape.type === SHAPES.RECT
// && !this.graphicsData[0].lineWidth;
return false
}
/**
* webgl渲染
* @private
* @param {WebGLRenderer} renderer - The renderer
*/
renderWebGL(renderer: WebglRenderer) {
if (!this.visible || this.worldAlpha <= 0 || !this.renderable) {
return;
}
if (this._mask) {
//之前的批处理刷掉先
renderer.batchManager.flush();
const mask = this._mask;
if (mask) {
renderer.maskManager.pushMask(this, this._mask);
}
this._renderWebGL(renderer);
renderer.batchManager.flush();
if (mask) {
renderer.maskManager.popMask(this, this._mask);
}
}
else {
this._renderWebGL(renderer);
}
}
_renderWebGL(renderer: WebglRenderer) {
this.updateBatch()
renderer.batchManager.setObjectRenderer(renderer.plugins["batch"]);
this.calculateVertices();
for (let i = 0; i < this.batches.length; i++) {
const batch = this.batches[i];
renderer.plugins["batch"].render(batch);
}
}
/**
* 根据GraphicsData分出batch,主要根据透明度和颜色
* _texture 里面有.BaseTexture
* vertexData
* indices
* worldAlpha(如果是gra,则传乘过fillAlpha的)
* color?
* uvs
*/
updateBatch() {
if (this.batchDirty === this.dirty) return
if (this.graphicsData.length === 0) return;
this.batchDirty = this.dirty;
const uvs = this.uvs;
//取最后一个
let batchPart;
batchPart = this.geoBatches.pop();
if (!batchPart) {
batchPart = GEOBATCH_POOL.pop() || new geoBatchPart();
batchPart.color = this.graphicsData[0].fillColor;
batchPart.alpha = this.graphicsData[0].fillAlpha;
}
let currentColor = batchPart.color + batchPart.alpha;
this.geoBatches.push(batchPart);
//先更新geoBatches
//基本可能只有一个,多个时加
for (var i = this.shapeIndex; i < this.graphicsData.length; i++) {
this.shapeIndex++
const data = this.graphicsData[i];
const command = fillCommands[data.type];
//初始化 data里的points,fill和stroke一样的
command.build(data);
for (let j = 0; j < 2; j++) {
var color = (j === 0) ? data.fillColor : data.lineColor;
var alpha = (j === 0) ? data.fillAlpha : data.lineAlpha;
if (j === 0 && !data.fill) continue;
if (j === 1 && !data.lineWidth) continue;
if ((color + alpha) !== currentColor) {
currentColor = color + alpha
const index = this.indices.length;
const attribIndex = this.verts.length / 2;
batchPart.size = index - batchPart.start;
batchPart.attribSize = attribIndex - batchPart.attribStart;
if (batchPart.size > 0) {
batchPart = GEOBATCH_POOL.pop() || new geoBatchPart();
this.geoBatches.push(batchPart);
}
batchPart.color = color;
batchPart.alpha = alpha;
batchPart.start = index;
batchPart.attribStart = attribIndex;
}
const start = this.verts.length / 2;
if (j === 0) {
//处理verts和indices
if (data.holes.length) {
this.proccessHoles(data.holes);
buildPoly.triangulate(data, this);
}
else {
command.triangulate(data, this);
}
} else {
command.triangulate(data, this);
}
const size = (this.verts.length / 2) - start;
this.addUvs(this.verts, uvs, Texture.WHITE, start, size);
}
}
const index = this.indices.length;
const attrib = this.verts.length / 2;
batchPart.size = index - batchPart.start;
batchPart.attribSize = attrib - batchPart.attribStart;
let indicesUint16 = new Uint16Array(this.indices);
let uvsFloat32 = new Float32Array(this.uvs);
// offset the indices so that it works with the batcher...
for (let i = 0; i < this.geoBatches.length; i++) {
const batch = this.geoBatches[i];
for (let j = 0; j < batch.size; j++) {
const index = batch.start + j;
indicesUint16[index] = indicesUint16[index] - batch.attribStart;
}
}
//设置_transformID,确保计算顶点数据
this._transformID = -1;
//更新batches
this.batches = [];
this.vertexData = new Float32Array(this.verts);
for (let i = 0; i < this.geoBatches.length; i++) {
const gI = this.geoBatches[i];
const color = gI.color;
const vertexData = new Float32Array(this.vertexData.buffer,
gI.attribStart * 4 * 2,
gI.attribSize * 2);
const uvs = new Float32Array(uvsFloat32.buffer,
gI.attribStart * 4 * 2,
gI.attribSize * 2);
const indices = new Uint16Array(indicesUint16.buffer,
gI.start * 2,
gI.size);
const batch = {
vertexData,
indices,
uvs,
color,
_texture: Texture.WHITE,
worldAlpha: gI.alpha * this.worldAlpha
};
this.batches[i] = batch;
}
}
/**
* If there's a transform update or a change to the shape of the
* geometry, recaculate the vertices.
* @protected
*/
calculateVertices() {
if (this._transformID === this.transform._worldID) {
return;
}
this._transformID = this.transform._worldID;
const wt = this.transform.worldMatrix;
const a = wt.a;
const b = wt.b;
const c = wt.c;
const d = wt.d;
const tx = wt.tx;
const ty = wt.ty;
const data = this.verts;// batch.vertexDataOriginal;
const vertexData = this.vertexData;
let count = 0;
for (let i = 0; i < data.length; i += 2) {
const x = data[i];
const y = data[i + 1];
vertexData[count++] = (a * x) + (c * y) + tx;
vertexData[count++] = (d * y) + (b * x) + ty;
}
}
/**
* canvas渲染
* @private
* @param {CanvasRenderer} renderer - The renderer
*/
renderCanvas(renderer: CanvasRenderer) {
if (/*this.isUsedToMask === true||*/!this.visible || this.worldAlpha <= 0 || !this.renderable) {
return;
}
if (this._mask) {
renderer.maskManager.pushMask(this._mask);
}
if (this.cacheAsBitmap) {
if (this.cacheDirty != this.dirty) {
this.cacheDirty = this.dirty;
//如果在生成贴图时需要去掉遮罩和缓存属性
var tempMask = this._mask;
this._mask = null;
// tempCacheStatus = true
this.cacheAsBitmap = false;
this.generateCanvasTexture();
//恢复
this._mask = tempMask;
// tempCacheStatus = false;
this.cacheAsBitmap = true;
this.updateTransform()
}
//普通画图的渲染,提供图片和图形的插件,先判断_anchorTexture,offsetX,0
renderer.plugins.sprite.render(this);
} else {
renderer.plugins.graphics.render(this);
}
if (this._mask) {
renderer.maskManager.popMask(renderer);
}
// if (!tempCacheStatus) this.dispatchEvent(Event.ENTER_FRAME)
}
/**
* _boundsId不知道怎么改,暂时不管,少用
* 计算全局bounds,_localBoundsSelf做个全局转换就行
* @private
*/
calculateBounds() {
this.updateLocalBoundsSelf();
this._lastBoundsID = this._boundsID
const rect = this._localBoundsSelf;
var matrix = this.transform.worldMatrix;
matrix.transformPoint(rect.x, rect.y, DisplayObject._p1);
matrix.transformPoint(rect.x + rect.width, rect.y, DisplayObject._p2);
matrix.transformPoint(rect.x + rect.width, rect.y + rect.height, DisplayObject._p3);
matrix.transformPoint(rect.x, rect.y + rect.height, DisplayObject._p4);
Rectangle.createFromPoints(this._bounds, DisplayObject._p1, DisplayObject._p2, DisplayObject._p3, DisplayObject._p4);
//至少有父级,必定会算,自己不算
// if (this._mask) {
// //有遮罩,遮罩的_bounds;
// this._mask.calculateBounds();
// let maskRect = this._mask._bounds;
// if (this._bounds.x < maskRect.x) {
// this._bounds.x = maskRect.x;
// }
// if (this._bounds.y < maskRect.y) {
// this._bounds.y = maskRect.y;
// }
// if (this._bounds.width > maskRect.width) {
// this._bounds.width = maskRect.width;
// }
// if (this._bounds.height > maskRect.height) {
// this._bounds.height = maskRect.height;
// }
// }
}
/**
* Tests if a point is inside this graphics object
*
* @param {Point} point - the point to test
*/
hitTestPoint(point: Point, isMouseEvent: boolean = false): DisplayObject {
let s = this;
if (!s.visible) return null;
if (isMouseEvent && !s.mouseEnable) return null;
this.worldMatrix.transformPointInverse(point.x, point.y, tempPoint);
const graphicsData = this.graphicsData;
for (let i = 0; i < graphicsData.length; ++i) {
const data = graphicsData[i];
if (!data.fill) {
continue;
}
// only deal with fills..
if (data.shape) {
if (data.shape.isPointIn(tempPoint)) {
if (data.holes) {
for (let i = 0; i < data.holes.length; i++) {
const hole = data.holes[i];
if (hole.isPointIn(tempPoint)) {
return null;
}
}
}
return s;
}
}
}
return null;
}
/**
* 更新自身包围盒
*/
updateLocalBoundsSelf() {
if (this.boundsDirty == this.dirty) return;
this.boundsDirty = this.dirty;
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
if (this.graphicsData.length) {
let shape;
let x = 0;
let y = 0;
let w = 0;
let h = 0;
for (let i = 0; i < this.graphicsData.length; i++) {
const data = this.graphicsData[i];
const type = data.type;
const lineWidth = data.lineWidth;
shape = data.shape;
if (type === SHAPES.RECT || type === SHAPES.RREC) {
x = shape.x - (lineWidth / 2);
y = shape.y - (lineWidth / 2);
w = shape.width + lineWidth;
h = shape.height + lineWidth;
minX = x < minX ? x : minX;
maxX = x + w > maxX ? x + w : maxX;
minY = y < minY ? y : minY;
maxY = y + h > maxY ? y + h : maxY;
}
else if (type === SHAPES.CIRC) {
x = shape.x;
y = shape.y;
w = shape.radius + (lineWidth / 2);
h = shape.radius + (lineWidth / 2);
minX = x - w < minX ? x - w : minX;
maxX = x + w > maxX ? x + w : maxX;
minY = y - h < minY ? y - h : minY;
maxY = y + h > maxY ? y + h : maxY;
}
else if (type === SHAPES.ELIP) {
x = shape.x;
y = shape.y;
w = shape.width + (lineWidth / 2);
h = shape.height + (lineWidth / 2);
minX = x - w < minX ? x - w : minX;
maxX = x + w > maxX ? x + w : maxX;
minY = y - h < minY ? y - h : minY;
maxY = y + h > maxY ? y + h : maxY;
}
else {
// POLY
const points = shape.points;
let x2 = 0;
let y2 = 0;
let dx = 0;
let dy = 0;
let rw = 0;
let rh = 0;
let cx = 0;
let cy = 0;
for (let j = 0; j + 2 < points.length; j += 2) {
x = points[j];
y = points[j + 1];
x2 = points[j + 2];
y2 = points[j + 3];
dx = Math.abs(x2 - x);
dy = Math.abs(y2 - y);
h = lineWidth;
w = Math.sqrt((dx * dx) + (dy * dy));
if (w < 1e-9) {
continue;
}
rw = ((h / w * dy) + dx) / 2;
rh = ((h / w * dx) + dy) / 2;
cx = (x2 + x) / 2;
cy = (y2 + y) / 2;
minX = cx - rw < minX ? cx - rw : minX;
maxX = cx + rw > maxX ? cx + rw : maxX;
minY = cy - rh < minY ? cy - rh : minY;
maxY = cy + rh > maxY ? cy + rh : maxY;
}
}
}
}
else {
minX = 0;
maxX = 0;
minY = 0;
maxY = 0;
}
const padding = this.boundsPadding;
// this._localBounds.minX = minX - padding;
// this._localBounds.maxX = maxX + padding;
// this._localBounds.minY = minY - padding;
// this._localBounds.maxY = maxY + padding;
this._localBoundsSelf.x = minX - padding;
this._localBoundsSelf.y = minY - padding;
this._localBoundsSelf.width = maxX - minX + padding * 2;
this._localBoundsSelf.height = maxY - minY + padding * 2;
}
/**
* Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon.
*
* @param {Circle|Ellipse|Polygon|Rectangle|RoundedRectangle} shape - The shape object to draw.
* @return {GraphicsData} The generated GraphicsData object.
*/
drawShape(shape: Circle | Ellipse | Polygon | Rectangle | RoundedRectangle): GraphicsData {
if (this.currentPath) {
// check current path!
if (this.currentPath.shape["points"].length <= 2) {
//去掉最后一个路径
this.graphicsData.pop();
}
}
this.currentPath = null;
const data = new GraphicsData(
this.lineWidth,
this.lineColor,
this.lineAlpha,
this.fillColor,
this.fillAlpha,
this.filling,
this.nativeLines,
shape,
this.lineAlignment
);
this.graphicsData.push(data);
if (data.type === SHAPES.POLY) {
data.shape["closed"] = data.shape["closed"];
this.currentPath = data;
}
this.dirty++;
return data;
}
/**
* Generates a canvas texture.
* 不包括变形的,只根据图形数据产生的原生贴图,经过变形的,考虑,是否新建方法,这个暂时只为canvas缓存
* 也不考虑遮罩
* @param {number} scaleMode - The scale mode of the texture.
* @param {number} resolution - The resolution of the texture.
* @return {Texture} The new texture.
*/
generateCanvasTexture(scaleMode: number = SCALE_MODES.LINEAR): Texture {
this.updateLocalBoundsSelf();
const bounds = this._localBoundsSelf;
if (!this._canvasBuffer) {
this._canvasBuffer = RenderTexture.create(bounds.width, bounds.height, scaleMode);
} else {
this._canvasBuffer.resize(bounds.width, bounds.height)
}
if (!canvasRenderer) {
canvasRenderer = new CanvasRenderer({});
}
this.transform.updateLocalMatrix();
tempMatrix.copy(this.transform.localMatrix);
tempMatrix.invert();
tempMatrix.tx -= bounds.x;
tempMatrix.ty -= bounds.y;
canvasRenderer.render(this, this._canvasBuffer, tempMatrix);
// document.body.appendChild(this._canvasBuffer.baseTexture["_canvasRenderTarget"].canvas)
if (!this._texture) {
this._texture = Texture.fromCanvas(this._canvasBuffer.baseTexture["_canvasRenderTarget"].canvas, scaleMode, 'graphics');
this._texture.baseTexture.update();
}
//可能需要更改_texture,this._texture.baseTexture尺寸
this.offsetX = bounds.x;
this.offsetY = bounds.y;
return this._texture;
}
/**
* Closes the current path.
* 只用在多边形的路径里
* @return {Graphics} Returns itself.
*/
closePath(): Graphics {
// ok so close path assumes next one is a hole!
const currentPath = this.currentPath;
if (currentPath && currentPath.shape) {
currentPath.shape["close"]();
}
return this;
}
/**
* Adds a hole in the current path.注意hole的路径要和上一路径相反绘制
* 用法,在画任何一种图形时,比如moveTo,lineTo 后,addHole(),则会加到前一种路径里
* 只有多边形有用,洞和被加洞的图形
* @return {Graphics} Returns itself.
*/
addHole(): Graphics {
// this is a hole!
const hole = this.graphicsData.pop();
this.currentPath = this.graphicsData[this.graphicsData.length - 1];
this.currentPath.addHole(hole.shape);
this.currentPath = null;
return this;
}
/**
*
*/
destroy() {
super.destroy();
// destroy each of the GraphicsData objects
for (let i = 0; i < this.graphicsData.length; ++i) {
this.graphicsData[i].destroy();
}
// for each webgl data entry, destroy the WebGLGraphicsData
for (const id in this._webGL) {
for (let j = 0; j < this._webGL[id].data.length; ++j) {
this._webGL[id].data[j].destroy();
}
}
this.graphicsData = null;
this.currentPath = null;
this._webGL = null;
this._localBoundsSelf = null;
}
/**
* Graphics curves resolution settings. If `adaptive` flag is set to `true`,
* the resolution is calculated based on the curve's length to ensure better visual quality.
* Adaptive draw works with `bezierCurveTo` and `quadraticCurveTo`.
*
* @static
* @constant
* @memberof Graphics
* @name CURVES
* @type {object}
* @property {boolean} adaptive=false - flag indicating if the resolution should be adaptive
* @property {number} maxLength=10 - maximal length of a single segment of the curve (if adaptive = false, ignored)
* @property {number} minSegments=8 - minimal number of segments in the curve (if adaptive = false, ignored)
* @property {number} maxSegments=2048 - maximal number of segments in the curve (if adaptive = false, ignored)
*/
static CURVES = {
adaptive: false,
maxLength: 10,
minSegments: 8,
maxSegments: 2048,
};
/**
* The width of the sprite, setting this will actually modify the scale to achieve the value set
* 重写,只返回图片的
* @member {number}
*/
get width() {
this.updateLocalBoundsSelf()
return Math.abs(this.scale.x) * this._localBoundsSelf.width;
}
set width(value) {
const s = sign(this.scale.x) || 1;
this.scale.x = s * value / this._localBoundsSelf.width;
this._width = value;
}
/**
* The height of the sprite, setting this will actually modify the scale to achieve the value set
*
* @member {number}
*/
get height() {
this.updateLocalBoundsSelf()
return Math.abs(this.scale.y) * this._localBoundsSelf.height;
}
set height(value) {
const s = sign(this.scale.y) || 1;
this.scale.y = s * value / this._localBoundsSelf.height;
this._height = value;
}
/**
* Process the holes data.
*
* @param {GraphicsData[]} holes - Holes to render
* @protected
*/
proccessHoles(holes: GraphicsData[]) {
for (let i = 0; i < holes.length; i++) {
const hole = holes[i];
const command = fillCommands[hole.type];
command.build(hole);
// if (hole.matrix) {
// this.transformPoints(hole.points, hole.matrix);
// }
}
}
/**
* Generates the UVs for a shape.
*
* @protected
* @param {number[]} verts - Vertices
* @param {number[]} uvs - UVs
* @param {Texture} texture - Reference to Texture
* @param {number} start - Index buffer start index.
* @param {number} size - The size/length for index buffer.
* @param {Matrix} [matrix] - Optional transform for all points.
*/
addUvs(verts, uvs, texture, start, size, matrix?) {
let index = 0;
while (index < size) {
// let x = verts[(start + index) * 2];
// let y = verts[((start + index) * 2) + 1];
// if (matrix) {
// const nx = (matrix.a * x) + (matrix.c * y) + matrix.tx;
// y = (matrix.b * x) + (matrix.d * y) + matrix.ty;
// x = nx;
// }
index++;
// const frame = texture.frame;
// uvs.push(x / frame.width, y / frame.height);
uvs.push(0, 0);
}
}
}
/**
* A little internal structure to hold interim batch objects.
*
* @private
*/
class geoBatchPart {
color: number
alpha: number;
size: number;
start: number;
attribStart: number;
attribSize: number;
constructor() {
this.color = 0xffffff
this.alpha = 1;
this.size = 0;
this.start = 0;
this.attribStart = 0;
this.attribSize = 0;
}
}
\ No newline at end of file
import Circle from "./shapes/Circle";
import { Rectangle } from "../math";
import Ellipse from "./shapes/Ellipse";
import Polygon from "./shapes/Polygon";
import RoundedRectangle from "./shapes/RoundedRectangle";
import { HashObject } from "../HashObject";
/**
* A GraphicsData object.
* 记录图形数据
*/
export default class GraphicsData extends HashObject {
/**
* the width of the line to draw
* @member {number}
*/
lineWidth: number;
/**
* The alignment of any lines drawn (0.5 = middle, 1 = outter, 0 = inner).
*
* @member {number}
* @default 0
*/
lineAlignment: number;
/**
* if true the liens will be draw using LINES instead of TRIANGLE_STRIP
* @member {boolean}
*/
nativeLines: boolean;
/**
* the color of the line to draw
* @member {number}
*/
lineColor: number;
/**
* the alpha of the line to draw
* @member {number}
*/
lineAlpha: number;
/**
* cached tint of the line to draw
* 计算颜色用的,暂时不用,图形本身不能设置颜色
* @member {number}
*/
private _lineTint: number;
/**
* the color of the fill
* @member {number}
*/
fillColor: number;
/**
* the alpha of the fill
* @member {number}
*/
fillAlpha: number;
/**
* cached tint of the fill
* @member {number}
*/
private _fillTint: number;
/**
* whether or not the shape is filled with a colour
* @member {boolean}
*/
fill: boolean;
holes: any[];
/**
* The shape object to draw.
* @member {Circle|Ellipse|Polygon|Rectangle|RoundedRectangle}
*/
shape: any;
/**
* The type of the shape, see the Const.Shapes file for all the existing types,
* @member {number}
*/
type: number;
/**
* 点的一维数组[x,y,x1,y1,x2,y2]
*/
points: number[]
/**
*
* @param {number} lineWidth - the width of the line to draw
* @param {number} lineColor - the color of the line to draw
* @param {number} lineAlpha - the alpha of the line to draw
* @param {number} fillColor - the color of the fill
* @param {number} fillAlpha - the alpha of the fill
* @param {boolean} fill - whether or not the shape is filled with a colour
* @param {boolean} nativeLines - the method for drawing lines
* @param {Circle|Rectangle|Ellipse|Polygon} shape - The shape object to draw.
* @param {number} lineAlignment - the alignment of the line.
*/
constructor(
lineWidth: number,
lineColor: number,
lineAlpha: number,
fillColor: number,
fillAlpha: number,
fill: boolean,
nativeLines: boolean,
shape: Circle | Rectangle | Ellipse | Polygon | RoundedRectangle,
lineAlignment: number = 0
) {
super();
this._instanceType = "GraphicsData"
this.lineWidth = lineWidth;
this.lineAlignment = lineAlignment;
this.nativeLines = nativeLines;
this.lineColor = lineColor;
this.lineAlpha = lineAlpha;
this._lineTint = lineColor;
this.fillColor = fillColor;
this.fillAlpha = fillAlpha;
this._fillTint = fillColor;
this.fill = fill;
this.holes = [];
this.shape = shape;
this.type = shape.type;
this.points = [];
}
/**
* Creates a new GraphicsData object with the same values as this one.
*
* @return {GraphicsData} Cloned GraphicsData object
*/
clone(): GraphicsData {
return new GraphicsData(
this.lineWidth,
this.lineColor,
this.lineAlpha,
this.fillColor,
this.fillAlpha,
this.fill,
this.nativeLines,
this.shape,
this.lineAlignment
);
}
/**
* Adds a hole to the shape.
* 只允许添加在多边形,和添加多边形为hole
* The shape of the hole.
*/
addHole(shape) {
this.holes.push(shape);
}
/**
* Destroys the Graphics data.
*/
destroy() {
this.shape = null;
this.holes = null;
}
}
/**
* Calculate the points for a bezier curve and then draws it.
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @param {number} fromX - Starting point x
* @param {number} fromY - Starting point y
* @param {number} cpX - Control point x
* @param {number} cpY - Control point y
* @param {number} cpX2 - Second Control point x
* @param {number} cpY2 - Second Control point y
* @param {number} toX - Destination point x
* @param {number} toY - Destination point y
* @param {number} n - Number of segments approximating the bezier curve
* @param {number[]} [path=[]] - Path array to push points into
* @return {number[]} Array of points of the curve
*/
export default function bezierCurveTo(
fromX: number,
fromY: number,
cpX: number,
cpY: number,
cpX2: number,
cpY2: number,
toX: number,
toY: number,
n: number,
path: number[] = []
): number[] {
let dt = 0;
let dt2 = 0;
let dt3 = 0;
let t2 = 0;
let t3 = 0;
path.push(fromX, fromY);
for (let i = 1, j = 0; i <= n; ++i) {
j = i / n;
dt = (1 - j);
dt2 = dt * dt;
dt3 = dt2 * dt;
t2 = j * j;
t3 = t2 * j;
path.push(
(dt3 * fromX) + (3 * dt2 * j * cpX) + (3 * dt * t2 * cpX2) + (t3 * toX),
(dt3 * fromY) + (3 * dt2 * j * cpY) + (3 * dt * t2 * cpY2) + (t3 * toY)
);
}
return path;
}
import { SHAPES } from "../../const";
import GraphicsData from "../GraphicsData";
import Graphics from "../Graphics";
/**
* Builds a circle to draw
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {WebGLGraphicsData} graphicsData - The graphics object to draw
* @param {object} webGLData - an object containing all the WebGL-specific information to create this shape
* @param {object} webGLDataNativeLines - an object containing all the WebGL-specific information to create nativeLines
*/
export default {
build(graphicsData: GraphicsData) {
// need to convert points to a nice regular data
const circleData = graphicsData.shape;
const points = graphicsData.points;
const x = circleData.x;
const y = circleData.y;
let width;
let height;
points.length = 0;
// TODO - bit hacky??
if (graphicsData.type === SHAPES.CIRC) {
width = circleData.radius;
height = circleData.radius;
}
else {
width = circleData.width;
height = circleData.height;
}
if (width === 0 || height === 0) {
return;
}
let totalSegs = Math.floor(30 * Math.sqrt(circleData.radius))
|| Math.floor(15 * Math.sqrt(circleData.width + circleData.height));
totalSegs /= 2.3;
const seg = (Math.PI * 2) / totalSegs;
for (let i = 0; i < totalSegs; i++) {
points.push(
x + (Math.sin(seg * i) * width),
y + (Math.cos(seg * i) * height)
);
}
points.push(
points[0],
points[1]
);
},
triangulate(graphicsData, graphicsGeometry:Graphics) {
const points = graphicsData.points;
const verts = graphicsGeometry.verts;
const indices = graphicsGeometry.indices;
let vertPos = verts.length / 2;
const center = vertPos;
verts.push(graphicsData.shape.x, graphicsData.shape.y);
for (let i = 0; i < points.length; i += 2) {
verts.push(points[i], points[i + 1]);
// add some uvs
indices.push(vertPos++, center, vertPos);
}
},
};
import GraphicsData from "../GraphicsData";
import Graphics from "../Graphics";
import { Point } from "../../math";
import { SHAPES } from "../../const";
/**
* Builds a line to draw
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {WebGLGraphicsData} graphicsData - The graphics object containing all the necessary properties
* @param {object} webGLData - an object containing all the WebGL-specific information to create this shape
* @param {object} webGLDataNativeLines - an object containing all the WebGL-specific information to create nativeLines
*/
export default function (graphicsData: GraphicsData, graphicsGeometry: Graphics) {
// if (graphicsData.lineStyle.native)
// {
// buildNativeLine(graphicsData, graphicsGeometry);
// }
// else
// {
buildLine(graphicsData, graphicsGeometry);
// }
}
/**
* Builds a line to draw using the polygon method.
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {PIXI.GraphicsData} graphicsData - The graphics object containing all the necessary properties
* @param {PIXI.GraphicsGeometry} graphicsGeometry - Geometry where to append output
*/
function buildLine(graphicsData:GraphicsData, graphicsGeometry:Graphics) {
const shape = graphicsData.shape;
let points = graphicsData.points || shape.points.slice();
if (points.length === 0) {
return;
}
// if the line width is an odd number add 0.5 to align to a whole pixel
// commenting this out fixes #711 and #1620
// if (graphicsData.lineWidth%2)
// {
// for (i = 0; i < points.length; i++)
// {
// points[i] += 0.5;
// }
// }
// get first and last point.. figure out the middle!
const firstPoint = new Point(points[0], points[1]);
const lastPoint = new Point(points[points.length - 2], points[points.length - 1]);
const closedShape = shape.type !== SHAPES.POLY;
const closedPath = firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y;
// if the first point is the last point - gonna have issues :)
if (closedPath || closedShape) {
// need to clone as we are going to slightly modify the shape..
points = points.slice();
if (closedPath) {
points.pop();
points.pop();
lastPoint.set(points[points.length - 2], points[points.length - 1]);
}
const midPointX = lastPoint.x + ((firstPoint.x - lastPoint.x) * 0.5);
const midPointY = lastPoint.y + ((firstPoint.y - lastPoint.y) * 0.5);
points.unshift(midPointX, midPointY);
points.push(midPointX, midPointY);
}
const verts = graphicsGeometry.verts;
const length = points.length / 2;
let indexCount = points.length;
let indexStart = verts.length / 2;
// DRAW the Line
const width = graphicsData.lineWidth / 2;
// sort color
let p1x = points[0];
let p1y = points[1];
let p2x = points[2];
let p2y = points[3];
let p3x = 0;
let p3y = 0;
let perpx = -(p1y - p2y);
let perpy = p1x - p2x;
let perp2x = 0;
let perp2y = 0;
let perp3x = 0;
let perp3y = 0;
let dist = Math.sqrt((perpx * perpx) + (perpy * perpy));
perpx /= dist;
perpy /= dist;
perpx *= width;
perpy *= width;
const ratio = graphicsData.lineAlignment;// 0.5;
const r1 = (1 - ratio) * 2;
const r2 = ratio * 2;
// start
verts.push(
p1x - (perpx * r1),
p1y - (perpy * r1));
verts.push(
p1x + (perpx * r2),
p1y + (perpy * r2));
for (let i = 1; i < length - 1; ++i) {
p1x = points[(i - 1) * 2];
p1y = points[((i - 1) * 2) + 1];
p2x = points[i * 2];
p2y = points[(i * 2) + 1];
p3x = points[(i + 1) * 2];
p3y = points[((i + 1) * 2) + 1];
perpx = -(p1y - p2y);
perpy = p1x - p2x;
dist = Math.sqrt((perpx * perpx) + (perpy * perpy));
perpx /= dist;
perpy /= dist;
perpx *= width;
perpy *= width;
perp2x = -(p2y - p3y);
perp2y = p2x - p3x;
dist = Math.sqrt((perp2x * perp2x) + (perp2y * perp2y));
perp2x /= dist;
perp2y /= dist;
perp2x *= width;
perp2y *= width;
const a1 = (-perpy + p1y) - (-perpy + p2y);
const b1 = (-perpx + p2x) - (-perpx + p1x);
const c1 = ((-perpx + p1x) * (-perpy + p2y)) - ((-perpx + p2x) * (-perpy + p1y));
const a2 = (-perp2y + p3y) - (-perp2y + p2y);
const b2 = (-perp2x + p2x) - (-perp2x + p3x);
const c2 = ((-perp2x + p3x) * (-perp2y + p2y)) - ((-perp2x + p2x) * (-perp2y + p3y));
let denom = (a1 * b2) - (a2 * b1);
if (Math.abs(denom) < 0.1) {
denom += 10.1;
verts.push(
p2x - (perpx * r1),
p2y - (perpy * r1));
verts.push(
p2x + (perpx * r2),
p2y + (perpy * r2));
continue;
}
const px = ((b1 * c2) - (b2 * c1)) / denom;
const py = ((a2 * c1) - (a1 * c2)) / denom;
const pdist = ((px - p2x) * (px - p2x)) + ((py - p2y) * (py - p2y));
if (pdist > (196 * width * width)) {
perp3x = perpx - perp2x;
perp3y = perpy - perp2y;
dist = Math.sqrt((perp3x * perp3x) + (perp3y * perp3y));
perp3x /= dist;
perp3y /= dist;
perp3x *= width;
perp3y *= width;
verts.push(p2x - (perp3x * r1), p2y - (perp3y * r1));
verts.push(p2x + (perp3x * r2), p2y + (perp3y * r2));
verts.push(p2x - (perp3x * r2 * r1), p2y - (perp3y * r1));
indexCount++;
}
else {
verts.push(p2x + ((px - p2x) * r1), p2y + ((py - p2y) * r1));
verts.push(p2x - ((px - p2x) * r2), p2y - ((py - p2y) * r2));
}
}
p1x = points[(length - 2) * 2];
p1y = points[((length - 2) * 2) + 1];
p2x = points[(length - 1) * 2];
p2y = points[((length - 1) * 2) + 1];
perpx = -(p1y - p2y);
perpy = p1x - p2x;
dist = Math.sqrt((perpx * perpx) + (perpy * perpy));
perpx /= dist;
perpy /= dist;
perpx *= width;
perpy *= width;
verts.push(p2x - (perpx * r1), p2y - (perpy * r1));
verts.push(p2x + (perpx * r2), p2y + (perpy * r2));
const indices = graphicsGeometry.indices;
// indices.push(indexStart);
for (let i = 0; i < indexCount - 2; ++i) {
indices.push(indexStart, indexStart + 1, indexStart + 2);
indexStart++;
}
}
/**
* Builds a line to draw using the gl.drawArrays(gl.LINES) method
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {PIXI.WebGLGraphicsData} graphicsData - The graphics object containing all the necessary properties
* @param {object} webGLData - an object containing all the WebGL-specific information to create this shape
*/
function buildNativeLine(graphicsData, graphicsGeometry) {
let i = 0;
const points = graphicsData.points || graphicsData.shape.points;
if (points.length === 0) return;
const verts = graphicsGeometry.points;
const indices = graphicsGeometry.indices;
const length = points.length / 2;
let indexStart = verts.length / 2;
// sort color
for (i = 1; i < length; i++) {
const p1x = points[(i - 1) * 2];
const p1y = points[((i - 1) * 2) + 1];
const p2x = points[i * 2];
const p2y = points[(i * 2) + 1];
verts.push(p1x, p1y);
verts.push(p2x, p2y);
indices.push(indexStart++, indexStart++);
}
}
import { earcut } from "./earcut";
import Graphics from "../Graphics";
import GraphicsData from "../GraphicsData";
/**
* Builds a polygon to draw
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {WebGLGraphicsData} graphicsData - The graphics object containing all the necessary properties
* @param {object} webGLData - an object containing all the WebGL-specific information to create this shape
* @param {object} webGLDataNativeLines - an object containing all the WebGL-specific information to create nativeLines
*/
export default {
build(graphicsData: GraphicsData) {
graphicsData.points = graphicsData.shape.points.slice();
},
triangulate(graphicsData:GraphicsData, graphicsGeometry:Graphics) {
let points = graphicsData.points;
const holes = graphicsData.holes;
const verts = graphicsGeometry.verts;
const indices = graphicsGeometry.indices;
if (points.length >= 6) {
const holeArray = [];
// Process holes..
for (let i = 0; i < holes.length; i++) {
const hole = holes[i];
holeArray.push(points.length / 2);
points = points.concat(hole.points);
}
// sort color
const triangles = earcut(points, holeArray, 2);
if (!triangles) {
return;
}
const vertPos = verts.length / 2;
for (let i = 0; i < triangles.length; i += 3) {
indices.push(triangles[i] + vertPos);
indices.push(triangles[i + 1] + vertPos);
indices.push(triangles[i + 2] + vertPos);
}
for (let i = 0; i < points.length; i++) {
verts.push(points[i]);
}
}
},
};
import { earcut } from "./earcut"
import GraphicsData from "../GraphicsData";
import Graphics from "../Graphics";
/**
* Builds a rectangle to draw
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {WebGLGraphicsData} graphicsData - The graphics object containing all the necessary properties
* @param {object} webGLData - an object containing all the WebGL-specific information to create this shape
* @param {object} webGLDataNativeLines - an object containing all the WebGL-specific information to create nativeLines
*/
export default {
//计算点points
build(graphicsData:GraphicsData) {
// --- //
// need to convert points to a nice regular data
//
const rectData = graphicsData.shape;
const x = rectData.x;
const y = rectData.y;
const width = rectData.width;
const height = rectData.height;
const points = graphicsData.points;
points.length = 0;
points.push(x, y,
x + width, y,
x + width, y + height,
x, y + height);
},
//计算顶点和索引
triangulate(graphicsData:GraphicsData, graphics: Graphics) {
const points = graphicsData.points;
//graphics之前可能已经有点
const verts = graphics.verts;
const vertPos = verts.length / 2;
verts.push(points[0], points[1],
points[2], points[3],
points[6], points[7],
points[4], points[5]);
graphics.indices.push(vertPos, vertPos + 1, vertPos + 2,
vertPos + 1, vertPos + 2, vertPos + 3);
},
};
import { earcut } from "./earcut"
import GraphicsData from "../GraphicsData";
import Graphics from "../Graphics";
/**
* Builds a rounded rectangle to draw
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {WebGLGraphicsData} graphicsData - The graphics object containing all the necessary properties
* @param {object} webGLData - an object containing all the WebGL-specific information to create this shape
* @param {object} webGLDataNativeLines - an object containing all the WebGL-specific information to create nativeLines
*/
export default {
build(graphicsData:GraphicsData)
{
const rrectData = graphicsData.shape;
const points = graphicsData.points;
const x = rrectData.x;
const y = rrectData.y;
const width = rrectData.width;
const height = rrectData.height;
const radius = rrectData.radius;
points.length = 0;
points.push(x, y + radius);
quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height, points);
quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius, points);
quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y, points);
quadraticBezierCurve(x + radius, y, x, y, x, y + radius + 0.0000000001, points);
// this tiny number deals with the issue that occurs when points overlap and earcut fails to triangulate the item.
// TODO - fix this properly, this is not very elegant.. but it works for now.
},
triangulate(graphicsData:GraphicsData, graphicsGeometry:Graphics)
{
const points = graphicsData.points;
const verts = graphicsGeometry.verts;
const indices = graphicsGeometry.indices;
const vecPos = verts.length / 2;
const triangles = earcut(points, null, 2);
for (let i = 0, j = triangles.length; i < j; i += 3)
{
indices.push(triangles[i] + vecPos);
// indices.push(triangles[i] + vecPos);
indices.push(triangles[i + 1] + vecPos);
// indices.push(triangles[i + 2] + vecPos);
indices.push(triangles[i + 2] + vecPos);
}
for (let i = 0, j = points.length; i < j; i++)
{
verts.push(points[i], points[++i]);
}
},
};
/**
* Calculate a single point for a quadratic bezier curve.
* Utility function used by quadraticBezierCurve.
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {number} n1 - first number
* @param {number} n2 - second number
* @param {number} perc - percentage
* @return {number} the result
*
*/
function getPt(n1, n2, perc)
{
const diff = n2 - n1;
return n1 + (diff * perc);
}
/**
* Calculate the points for a quadratic bezier curve. (helper function..)
* Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c
*
* Ignored from docs since it is not directly exposed.
*
* @ignore
* @private
* @param {number} fromX - Origin point x
* @param {number} fromY - Origin point x
* @param {number} cpX - Control point x
* @param {number} cpY - Control point y
* @param {number} toX - Destination point x
* @param {number} toY - Destination point y
* @param {number[]} [out=[]] - The output array to add points into. If not passed, a new array is created.
* @return {number[]} an array of points
*/
function quadraticBezierCurve(fromX, fromY, cpX, cpY, toX, toY, out = [])
{
const n = 20;
const points = out;
let xa = 0;
let ya = 0;
let xb = 0;
let yb = 0;
let x = 0;
let y = 0;
for (let i = 0, j = 0; i <= n; ++i)
{
j = i / n;
// The Green Line
xa = getPt(fromX, cpX, j);
ya = getPt(fromY, cpY, j);
xb = getPt(cpX, toX, j);
yb = getPt(cpY, toY, j);
// The Black Dot
x = getPt(xa, xb, j);
y = getPt(ya, yb, j);
points.push(x, y);
}
return points;
}
export function earcut(data, holeIndices, dim) {
dim = dim || 2;
var hasHoles = holeIndices && holeIndices.length,
outerLen = hasHoles ? holeIndices[0] * dim : data.length,
outerNode = linkedList(data, 0, outerLen, dim, true),
triangles = [];
if (!outerNode || outerNode.next === outerNode.prev) return triangles;
var minX, minY, maxX, maxY, x, y, invSize;
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
if (data.length > 80 * dim) {
minX = maxX = data[0];
minY = maxY = data[1];
for (var i = dim; i < outerLen; i += dim) {
x = data[i];
y = data[i + 1];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
}
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
invSize = Math.max(maxX - minX, maxY - minY);
invSize = invSize !== 0 ? 1 / invSize : 0;
}
earcutLinked(outerNode, triangles, dim, minX, minY, invSize);
return triangles;
}
// create a circular doubly linked list from polygon points in the specified winding order
function linkedList(data, start, end, dim, clockwise) {
var i, last;
if (clockwise === (signedArea(data, start, end, dim) > 0)) {
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
} else {
for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
}
if (last && equals(last, last.next)) {
removeNode(last);
last = last.next;
}
return last;
}
// eliminate colinear or duplicate points
function filterPoints(start, end?) {
if (!start) return start;
if (!end) end = start;
var p = start,
again;
do {
again = false;
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
removeNode(p);
p = end = p.prev;
if (p === p.next) break;
again = true;
} else {
p = p.next;
}
} while (again || p !== end);
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass?) {
if (!ear) return;
// interlink polygon nodes in z-order
if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
var stop = ear,
prev, next;
// iterate through ears, slicing them one by one
while (ear.prev !== ear.next) {
prev = ear.prev;
next = ear.next;
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
// cut off the triangle
triangles.push(prev.i / dim);
triangles.push(ear.i / dim);
triangles.push(next.i / dim);
removeNode(ear);
// skipping the next vertex leads to less sliver triangles
ear = next.next;
stop = next.next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear === stop) {
// try filtering points and slicing again
if (!pass) {
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
// if this didn't work, try curing all small self-intersections locally
} else if (pass === 1) {
ear = cureLocalIntersections(ear, triangles, dim);
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
// as a last resort, try splitting the remaining polygon into two
} else if (pass === 2) {
splitEarcut(ear, triangles, dim, minX, minY, invSize);
}
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
function isEar(ear) {
var a = ear.prev,
b = ear,
c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
var p = ear.next.next;
while (p !== ear.prev) {
if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.next;
}
return true;
}
function isEarHashed(ear, minX, minY, invSize) {
var a = ear.prev,
b = ear,
c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
// z-order range for the current triangle bbox;
var minZ = zOrder(minTX, minTY, minX, minY, invSize),
maxZ = zOrder(maxTX, maxTY, minX, minY, invSize);
var p = ear.prevZ,
n = ear.nextZ;
// look for points inside the triangle in both directions
while (p && p.z >= minZ && n && n.z <= maxZ) {
if (p !== ear.prev && p !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.prevZ;
if (n !== ear.prev && n !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
area(n.prev, n, n.next) >= 0) return false;
n = n.nextZ;
}
// look for remaining points in decreasing z-order
while (p && p.z >= minZ) {
if (p !== ear.prev && p !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.prevZ;
}
// look for remaining points in increasing z-order
while (n && n.z <= maxZ) {
if (n !== ear.prev && n !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
area(n.prev, n, n.next) >= 0) return false;
n = n.nextZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
function cureLocalIntersections(start, triangles, dim) {
var p = start;
do {
var a = p.prev,
b = p.next.next;
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
triangles.push(a.i / dim);
triangles.push(p.i / dim);
triangles.push(b.i / dim);
// remove two nodes involved
removeNode(p);
removeNode(p.next);
p = start = b;
}
p = p.next;
} while (p !== start);
return p;
}
// try splitting polygon into two and triangulate them independently
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
// look for a valid diagonal that divides the polygon into two
var a = start;
do {
var b = a.next.next;
while (b !== a.prev) {
if (a.i !== b.i && isValidDiagonal(a, b)) {
// split the polygon in two by the diagonal
var c = splitPolygon(a, b);
// filter colinear points around the cuts
a = filterPoints(a, a.next);
c = filterPoints(c, c.next);
// run earcut on each half
earcutLinked(a, triangles, dim, minX, minY, invSize);
earcutLinked(c, triangles, dim, minX, minY, invSize);
return;
}
b = b.next;
}
a = a.next;
} while (a !== start);
}
// link every hole into the outer loop, producing a single-ring polygon without holes
function eliminateHoles(data, holeIndices, outerNode, dim) {
var queue = [],
i, len, start, end, list;
for (i = 0, len = holeIndices.length; i < len; i++) {
start = holeIndices[i] * dim;
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
list = linkedList(data, start, end, dim, false);
if (list === list.next) list.steiner = true;
queue.push(getLeftmost(list));
}
queue.sort(compareX);
// process holes from left to right
for (i = 0; i < queue.length; i++) {
eliminateHole(queue[i], outerNode);
outerNode = filterPoints(outerNode, outerNode.next);
}
return outerNode;
}
function compareX(a, b) {
return a.x - b.x;
}
// find a bridge between vertices that connects hole with an outer ring and and link it
function eliminateHole(hole, outerNode) {
outerNode = findHoleBridge(hole, outerNode);
if (outerNode) {
var b = splitPolygon(outerNode, hole);
filterPoints(b, b.next);
}
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
function findHoleBridge(hole, outerNode) {
var p = outerNode,
hx = hole.x,
hy = hole.y,
qx = -Infinity,
m;
// find a segment intersected by a ray from the hole's leftmost point to the left;
// segment's endpoint with lesser x will be potential connection point
do {
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
if (x <= hx && x > qx) {
qx = x;
if (x === hx) {
if (hy === p.y) return p;
if (hy === p.next.y) return p.next;
}
m = p.x < p.next.x ? p : p.next;
}
}
p = p.next;
} while (p !== outerNode);
if (!m) return null;
if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint
// look for points inside the triangle of hole point, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the point of the minimum angle with the ray as connection point
var stop = m,
mx = m.x,
my = m.y,
tanMin = Infinity,
tan;
p = m.next;
while (p !== stop) {
if (hx >= p.x && p.x >= mx && hx !== p.x &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) {
m = p;
tanMin = tan;
}
}
p = p.next;
}
return m;
}
// interlink polygon nodes in z-order
function indexCurve(start, minX, minY, invSize) {
var p = start;
do {
if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize);
p.prevZ = p.prev;
p.nextZ = p.next;
p = p.next;
} while (p !== start);
p.prevZ.nextZ = null;
p.prevZ = null;
sortLinked(p);
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
function sortLinked(list) {
var i, p, q, e, tail, numMerges, pSize, qSize,
inSize = 1;
do {
p = list;
list = null;
tail = null;
numMerges = 0;
while (p) {
numMerges++;
q = p;
pSize = 0;
for (i = 0; i < inSize; i++) {
pSize++;
q = q.nextZ;
if (!q) break;
}
qSize = inSize;
while (pSize > 0 || (qSize > 0 && q)) {
if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
e = p;
p = p.nextZ;
pSize--;
} else {
e = q;
q = q.nextZ;
qSize--;
}
if (tail) tail.nextZ = e;
else list = e;
e.prevZ = tail;
tail = e;
}
p = q;
}
tail.nextZ = null;
inSize *= 2;
} while (numMerges > 1);
return list;
}
// z-order of a point given coords and inverse of the longer side of data bbox
function zOrder(x, y, minX, minY, invSize) {
// coords are transformed into non-negative 15-bit integer range
x = 32767 * (x - minX) * invSize;
y = 32767 * (y - minY) * invSize;
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
// find the leftmost node of a polygon ring
function getLeftmost(start) {
var p = start,
leftmost = start;
do {
if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
p = p.next;
} while (p !== start);
return leftmost;
}
// check if a point lies within a convex triangle
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
(ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
(bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
function isValidDiagonal(a, b) {
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
}
// signed area of a triangle
function area(p, q, r) {
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
}
// check if two points are equal
function equals(p1, p2) {
return p1.x === p2.x && p1.y === p2.y;
}
// check if two segments intersect
function intersects(p1, q1, p2, q2) {
if ((equals(p1, q1) && equals(p2, q2)) ||
(equals(p1, q2) && equals(p2, q1))) return true;
return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 &&
area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;
}
// check if a polygon diagonal intersects any polygon segments
function intersectsPolygon(a, b) {
var p = a;
do {
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
intersects(p, p.next, a, b)) return true;
p = p.next;
} while (p !== a);
return false;
}
// check if a polygon diagonal is locally inside the polygon
function locallyInside(a, b) {
return area(a.prev, a, a.next) < 0 ?
area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
}
// check if the middle point of a polygon diagonal is inside the polygon
function middleInside(a, b) {
var p = a,
inside = false,
px = (a.x + b.x) / 2,
py = (a.y + b.y) / 2;
do {
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
inside = !inside;
p = p.next;
} while (p !== a);
return inside;
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
function splitPolygon(a, b) {
var a2 = new Node(a.i, a.x, a.y),
b2 = new Node(b.i, b.x, b.y),
an = a.next,
bp = b.prev;
a.next = b;
b.prev = a;
a2.next = an;
an.prev = a2;
b2.next = a2;
a2.prev = b2;
bp.next = b2;
b2.prev = bp;
return b2;
}
// create a node and optionally link it with previous one (in a circular doubly linked list)
function insertNode(i, x, y, last) {
var p = new Node(i, x, y);
if (!last) {
p.prev = p;
p.next = p;
} else {
p.next = last.next;
p.prev = last;
last.next.prev = p;
last.next = p;
}
return p;
}
function removeNode(p) {
p.next.prev = p.prev;
p.prev.next = p.next;
if (p.prevZ) p.prevZ.nextZ = p.nextZ;
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
}
function Node(i, x, y) {
// vertex index in coordinates array
this.i = i;
// vertex coordinates
this.x = x;
this.y = y;
// previous and next vertex nodes in a polygon ring
this.prev = null;
this.next = null;
// z-order curve value
this.z = null;
// previous and next nodes in z-order
this.prevZ = null;
this.nextZ = null;
// indicates whether this is a steiner point
this.steiner = false;
}
// return a percentage difference between the polygon area and its triangulation area;
// used to verify correctness of triangulation
earcut["deviation"] = function (data, holeIndices, dim, triangles) {
var hasHoles = holeIndices && holeIndices.length;
var outerLen = hasHoles ? holeIndices[0] * dim : data.length;
var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
if (hasHoles) {
for (var i = 0, len = holeIndices.length; i < len; i++) {
var start = holeIndices[i] * dim;
var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
polygonArea -= Math.abs(signedArea(data, start, end, dim));
}
}
var trianglesArea = 0;
for (i = 0; i < triangles.length; i += 3) {
var a = triangles[i] * dim;
var b = triangles[i + 1] * dim;
var c = triangles[i + 2] * dim;
trianglesArea += Math.abs(
(data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
(data[a] - data[b]) * (data[c + 1] - data[a + 1]));
}
return polygonArea === 0 && trianglesArea === 0 ? 0 :
Math.abs((trianglesArea - polygonArea) / polygonArea);
};
function signedArea(data, start, end, dim) {
var sum = 0;
for (var i = start, j = end - dim; i < end; i += dim) {
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
j = i;
}
return sum;
}
// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
earcut["flatten"] = function (data) {
var dim = data[0][0].length,
result = {vertices: [], holes: [], dimensions: dim},
holeIndex = 0;
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
}
if (i > 0) {
holeIndex += data[i - 1].length;
result.holes.push(holeIndex);
}
}
return result;
};
import { Rectangle, Point } from '../../math';
import { SHAPES } from '../../const';
/**
* 圆形
*/
export default class Circle {
/**
* @member {number}
* @default 0
*/
x: number;
/**
* @member {number}
* @default 0
*/
y: number;
/**
* @member {number}
* @default 0
*/
radius: number;
/**
* 类型
* @member {number}
* @readOnly
* @default SHAPES.CIRC
* @see SHAPES
*/
type: number;
/**
* @param {number} [x=0] - The X coordinate of the center of this circle
* @param {number} [y=0] - The Y coordinate of the center of this circle
* @param {number} [radius=0] - The radius of the circle
*/
constructor(x: number = 0, y: number = 0, radius: number = 0) {
this.x = x;
this.y = y;
this.radius = radius;
this.type = SHAPES.CIRC;
}
/**
* Creates a clone of this Circle instance
*
* @return {Circle} a copy of the Circle
*/
clone():Circle {
return new Circle(this.x, this.y, this.radius);
}
/**
* @param {Point} point - The point to test
* @return {boolean} Whether the x/y coordinates are within this Circle
*/
isPointIn(point:Point):boolean {
if (this.radius <= 0) {
return false;
}
const r2 = this.radius * this.radius;
let dx = (this.x - point.x);
let dy = (this.y - point.y);
dx *= dx;
dy *= dy;
return (dx + dy <= r2);
}
/**
* Returns the framing rectangle of the circle as a Rectangle object
*
* @return {Rectangle} the framing rectangle
*/
getBounds():Rectangle {
return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2);
}
}
import { Rectangle, Point } from '../../math';
import { SHAPES } from '../../const';
/**
* The Ellipse object can be used to specify a hit area for displayObjects
*
*/
export default class Ellipse {
/**
* @member {number}
* @default 0
*/
x: number;
/**
* @member {number}
* @default 0
*/
y: number;
/**
* @member {number}
* @default 0
*/
width: number;
/**
* @member {number}
* @default 0
*/
height: number;
/**
* The type of the object, mainly used to avoid `instanceof` checks
*
* @member {number}
* @readOnly
* @default SHAPES.ELIP
* @see SHAPES
*/
type: number;
/**
* @param {number} [x=0] - The X coordinate of the center of this circle
* @param {number} [y=0] - The Y coordinate of the center of this circle
* @param {number} [width=0] - The half width of this ellipse
* @param {number} [height=0] - The half height of this ellipse
*/
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.type = SHAPES.ELIP;
}
/**
* Creates a clone of this Ellipse instance
*
* @return {Ellipse} a copy of the ellipse
*/
clone():Ellipse {
return new Ellipse(this.x, this.y, this.width, this.height);
}
/**
* Checks whether the x and y coordinates given are contained within this ellipse
*
* @param {Point} point - The point to test
* @return {boolean} Whether the x/y coords are within this ellipse
*/
isPointIn(point:Point):boolean {
if (this.width <= 0 || this.height <= 0) {
return false;
}
// normalize the coords to an ellipse with center 0,0
let normx = ((point.x - this.x) / this.width);
let normy = ((point.y - this.y) / this.height);
normx *= normx;
normy *= normy;
return (normx + normy <= 1);
}
/**
* Returns the framing rectangle of the ellipse as a Rectangle object
*
* @return {Rectangle} the framing rectangle
*/
getBounds():Rectangle {
return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height);
}
}
import { Point } from '../../math/Point';
import { SHAPES } from '../../const';
/**
*
*/
export default class Polygon {
/**
* 是否闭合
*/
closed: boolean;
/**
* [0,1, 2,3, 2,3, 2,3]
* An array of the points of this polygon
* @member {number[]}
*/
points: number[];
/**
* The type of the object, mainly used to avoid `instanceof` checks
*
* @member {number}
* @readOnly
* @default SHAPES.POLY
* @see SHAPES
*/
type: number;
/**
* new Polygon(new Point(), new Point(), ...)
*
* new Polygon([x,y, x,y, ...])
* new Polygon(x,y, x,y, x,y, ...)
* @param {Point[]|number[]} points - This can be an array of Points
* that form the polygon, a flat array of numbers that will be interpreted as [x,y, x,y, ...], or
* the arguments passed can be all the points of the polygon e.g.
* `new Polygon(new Point(), new Point(), ...)`, or the arguments passed can be flat
* x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are Numbers.
*/
constructor(...points) {
if (Array.isArray(points[0])) {
points = points[0];
}
// if this is an array of points, convert it to a flat array of numbers
if (points[0] instanceof Point) {
const p = [];
for (let i = 0, il = points.length; i < il; i++) {
p.push(points[i].x, points[i].y);
}
points = p;
}
this.closed = true;
this.points = points;
this.type = SHAPES.POLY;
}
/**
* Creates a clone of this polygon
*
* @return {Polygon} a copy of the polygon
*/
clone(): Polygon {
return new Polygon(this.points.slice());
}
/**
* Closes the polygon, adding points if necessary.
*
*/
close() {
const points = this.points;
// close the poly if the value is true!
if (points[0] !== points[points.length - 2] || points[1] !== points[points.length - 1]) {
points.push(points[0], points[1]);
}
}
/**
* Checks whether the x and y coordinates passed to this function are contained within this polygon
*
* @param {Point} point - The point to test
* @return {boolean} Whether the x/y coordinates are within this polygon
*/
isPointIn(point: Point): boolean {
var x = point.x;
var y = point.y;
let inside = false;
// use some raycasting to test hits
// https://github.com/substack/point-in-polygon/blob/master/index.js
const length = this.points.length / 2;
for (let i = 0, j = length - 1; i < length; j = i++) {
const xi = this.points[i * 2];
const yi = this.points[(i * 2) + 1];
const xj = this.points[j * 2];
const yj = this.points[(j * 2) + 1];
const intersect = ((yi > y) !== (yj > y)) && (x < ((xj - xi) * ((y - yi) / (yj - yi))) + xi);
if (intersect) {
inside = !inside;
}
}
return inside;
}
}
import { SHAPES } from '../../const';
//用math里的哪个Rectangle
/**
* Rectangle object is an area defined by its position, as indicated by its top-left corner
* point (x, y) and by its width and its height.
*
*/
export default class Rectangle
{
/**
* @param {number} [x=0] - The X coordinate of the upper-left corner of the rectangle
* @param {number} [y=0] - The Y coordinate of the upper-left corner of the rectangle
* @param {number} [width=0] - The overall width of this rectangle
* @param {number} [height=0] - The overall height of this rectangle
*/
constructor(x = 0, y = 0, width = 0, height = 0)
{
/**
* @member {number}
* @default 0
*/
this.x = Number(x);
/**
* @member {number}
* @default 0
*/
this.y = Number(y);
/**
* @member {number}
* @default 0
*/
this.width = Number(width);
/**
* @member {number}
* @default 0
*/
this.height = Number(height);
/**
* The type of the object, mainly used to avoid `instanceof` checks
*
* @member {number}
* @readOnly
* @default SHAPES.RECT
* @see SHAPES
*/
this.type = SHAPES.RECT;
}
/**
* returns the left edge of the rectangle
*
* @member {number}
*/
get left()
{
return this.x;
}
/**
* returns the right edge of the rectangle
*
* @member {number}
*/
get right()
{
return this.x + this.width;
}
/**
* returns the top edge of the rectangle
*
* @member {number}
*/
get top()
{
return this.y;
}
/**
* returns the bottom edge of the rectangle
*
* @member {number}
*/
get bottom()
{
return this.y + this.height;
}
/**
* A constant empty rectangle.
*
* @static
* @constant
*/
static get EMPTY()
{
return new Rectangle(0, 0, 0, 0);
}
/**
* Creates a clone of this Rectangle
*
* @return {Rectangle} a copy of the rectangle
*/
clone()
{
return new Rectangle(this.x, this.y, this.width, this.height);
}
/**
* Copies another rectangle to this one.
*
* @param {Rectangle} rectangle - The rectangle to copy.
* @return {Rectangle} Returns itself.
*/
copy(rectangle)
{
this.x = rectangle.x;
this.y = rectangle.y;
this.width = rectangle.width;
this.height = rectangle.height;
return this;
}
/**
* Checks whether the x and y coordinates given are contained within this Rectangle
*
* @param {number} x - The X coordinate of the point to test
* @param {number} y - The Y coordinate of the point to test
* @return {boolean} Whether the x/y coordinates are within this Rectangle
*/
contains(x, y)
{
if (this.width <= 0 || this.height <= 0)
{
return false;
}
if (x >= this.x && x < this.x + this.width)
{
if (y >= this.y && y < this.y + this.height)
{
return true;
}
}
return false;
}
/**
* Pads the rectangle making it grow in all directions.
*
* @param {number} paddingX - The horizontal padding amount.
* @param {number} [paddingY] - The vertical padding amount.
*/
pad(paddingX, paddingY)
{
paddingX = paddingX || 0;
paddingY = paddingY || ((paddingY !== 0) ? paddingX : 0);
this.x -= paddingX;
this.y -= paddingY;
this.width += paddingX * 2;
this.height += paddingY * 2;
}
/**
* Fits this rectangle around the passed one.
*
* @param {Rectangle} rectangle - The rectangle to fit.
*/
fit(rectangle)
{
if (this.x < rectangle.x)
{
this.width += this.x;
if (this.width < 0)
{
this.width = 0;
}
this.x = rectangle.x;
}
if (this.y < rectangle.y)
{
this.height += this.y;
if (this.height < 0)
{
this.height = 0;
}
this.y = rectangle.y;
}
if (this.x + this.width > rectangle.x + rectangle.width)
{
this.width = rectangle.width - this.x;
if (this.width < 0)
{
this.width = 0;
}
}
if (this.y + this.height > rectangle.y + rectangle.height)
{
this.height = rectangle.height - this.y;
if (this.height < 0)
{
this.height = 0;
}
}
}
/**
* Enlarges this rectangle to include the passed rectangle.
*
* @param {Rectangle} rectangle - The rectangle to include.
*/
enlarge(rectangle)
{
const x1 = Math.min(this.x, rectangle.x);
const x2 = Math.max(this.x + this.width, rectangle.x + rectangle.width);
const y1 = Math.min(this.y, rectangle.y);
const y2 = Math.max(this.y + this.height, rectangle.y + rectangle.height);
this.x = x1;
this.width = x2 - x1;
this.y = y1;
this.height = y2 - y1;
}
}
import { SHAPES } from '../../const';
import { Point } from '../../math';
/**
* The Rounded Rectangle object is an area that has nice rounded corners, as indicated by its
* top-left corner point (x, y) and by its width and its height and its radius.
*/
export default class RoundedRectangle {
x: number;
y: number;
width: number;
height: number;
/**
* @member {number}
* @default 20
*/
radius: number;
type: number;
/**
* @param {number} [x=0] - The X coordinate of the upper-left corner of the rounded rectangle
* @param {number} [y=0] - The Y coordinate of the upper-left corner of the rounded rectangle
* @param {number} [width=0] - The overall width of this rounded rectangle
* @param {number} [height=0] - The overall height of this rounded rectangle
* @param {number} [radius=20] - Controls the radius of the rounded corners
*/
constructor(
x: number = 0,
y: number = 0,
width: number = 0,
height: number = 0,
radius: number = 20
) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.radius = radius;
this.type = SHAPES.RREC;
}
/**
* Creates a clone of this Rounded Rectangle
*
* @return {RoundedRectangle} a copy of the rounded rectangle
*/
clone(): RoundedRectangle {
return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius);
}
/**
* Checks whether the x and y coordinates given are contained within this ellipse
*
* @param {Point} point - The point to test
* @return {boolean} Whether the x/y coords are within this ellipse
*/
isPointIn(point: Point): boolean {
var x = point.x;
var y = point.y;
if (this.width <= 0 || this.height <= 0) {
return false;
}
if (x >= this.x && x <= this.x + this.width) {
if (y >= this.y && y <= this.y + this.height) {
if ((y >= this.y + this.radius && y <= this.y + this.height - this.radius)
|| (x >= this.x + this.radius && x <= this.x + this.width - this.radius)) {
return true;
}
let dx = x - (this.x + this.radius);
let dy = y - (this.y + this.radius);
const radius2 = this.radius * this.radius;
if ((dx * dx) + (dy * dy) <= radius2) {
return true;
}
dx = x - (this.x + this.width - this.radius);
if ((dx * dx) + (dy * dy) <= radius2) {
return true;
}
dy = y - (this.y + this.height - this.radius);
if ((dx * dx) + (dy * dy) <= radius2) {
return true;
}
dx = x - (this.x + this.radius);
if ((dx * dx) + (dy * dy) <= radius2) {
return true;
}
}
}
return false;
}
}
export {default as Circle} from './Circle';
export {default as Ellipse} from './Ellipse';
export {default as Polygon} from './Polygon';
export {default as RoundedRectangle} from './RoundedRectangle';
\ No newline at end of file
import { EventDispatcher } from "../events/EventDispatcher";
//再考虑,ani里用到不用到最好都导,到时处理
export class Loader extends EventDispatcher {
/**
* 记录原始数据,json和image,贴图在建立时会被缓存
*/
caches = {};
/**
*
*/
constructor() {
super();
this._instanceType = "Loader";
}
/**
*
* @param callback
* @param url 图集一般是png格式,传的是json
*/
loadSheet(callback: Function, url: string) {
let pngFile = url.substring(0, url.lastIndexOf('.')) + '.png';
this.loadImage((suc, data) => {
if (suc) {
this.cache(pngFile, data);
if (this.caches[url]) {
callback(true, { json: this.caches[url], img: data })
}
} else {
callback(false, data)
}
}, pngFile);
fetchAsync(url)
.then((data) => {
this.cache(url, data);
if (this.caches[pngFile]) {
callback(true, { json: data, img: this.caches[pngFile] })
}
// console.log(data)
})
.catch(reason => {
callback(false, reason)
// console.log(reason.message)
})
}
loadImage(callback: Function, url: string, crossOrigin: boolean = true) {
let img = new Image();
if (crossOrigin) {
img.setAttribute('crossOrigin', 'anonymous');
}
img.onload = function (e) {
callback(true, img);
};
img.onerror = function (e) {
callback(false, e);
};
img.src = url;
}
cache(name: string, data: any) {
if (this.caches[name]) {
console.log("覆盖原先数据");
this.caches[name] = data
}
}
}
async function fetchAsync(url: string) {
// await response of fetch call
let response = await fetch(url);
// only proceed once promise is resolved
let data = await response.json();
// only proceed once second promise is resolved
return data;
}
// fetchAsync("")
// .then(data => console.log(data))
// .catch(reason => console.log(reason.message))
\ No newline at end of file
import { Point } from "././Point";
import { HashObject } from "../HashObject";
import { DEG_TO_RAD, cos, sin } from "../const";
/**
* 2维矩阵
* @class Matrix
* @extends HashObject
* @public
* @since 1.0.0
*/
export class Matrix extends HashObject {
/**
* @property a
* @type {number}
* @public
* @default 1
* @since 1.0.0
*/
public a: number = 1;
/**
* @property b
* @public
* @since 1.0.0
* @type {number}
*/
public b: number = 0;
/**
* @property c
* @type {number}
* @public
* @since 1.0.0
*/
public c: number = 0;
/**
* @property d
* @type {number}
* @public
* @since 1.0.0
*/
public d: number = 1;
/**
* @property tx
* @type {number}
* @public
* @since 1.0.0
*/
public tx: number = 0;
/**
* @property ty
* @type {number}
* @since 1.0.0
* @public
*/
public ty: number = 0;
//数组形式
public array = null;
/**
* 构造函数
* @method Matrix
* @param {number} a
* @param {number} b
* @param {number} c
* @param {number} d
* @param {number} tx
* @param {number} ty
* @public
* @since 1.0.0
*/
public constructor(a: number = 1, b: number = 0, c: number = 0, d: number = 1, tx: number = 0, ty: number = 0) {
super();
let s = this;
s._instanceType = "Matrix";
s.a = a;
s.b = b;
s.c = c;
s.d = d;
s.tx = tx;
s.ty = ty;
}
/**
* 复制一个矩阵
* @method clone
* @since 1.0.0
* @public
* @return {Matrix}
*/
public clone(): Matrix {
let s = this;
return new Matrix(s.a, s.b, s.c, s.d, s.tx, s.ty);
}
/**
* 复制一个矩阵的所有属性
* @param matrix
*/
copy(matrix: Matrix|any) {
this.a = matrix.a;
this.b = matrix.b;
this.c = matrix.c;
this.d = matrix.d;
this.tx = matrix.tx;
this.ty = matrix.ty;
return this;
}
/**
* 将一个点通过矩阵变换后的点,世界矩阵应用于局部坐标,转化为世界坐标
* @method transformPoint
* @param {number} x
* @param {number} y
* @param {Point} 默认为空,如果不为null,则返回的是Point就是此对象,如果为null,则返回来的Point是新建的对象
* @return {Point}
* @public
* @since 1.0.0
*/
public transformPoint(x: number, y: number, bp: Point = null): Point {
let s = this;
if (!bp) {
bp = new Point();
}
bp.x = x * s.a + y * s.c + s.tx;
bp.y = x * s.b + y * s.d + s.ty;
return bp
};
/**
* Get a new position with the inverse of the current transformation applied.
* Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input)
* 用于世界坐标转化为局部坐标
* @param {number} x
* @param {number} y
* @param {Point} 默认为空,如果不为null,则返回的是Point就是此对象,如果为null,则返回来的Point是新建的对象
* @return {Point}
*/
public transformPointInverse(x: number, y: number, bp: Point = null): Point {
let s = this;
if (!bp) {
bp = new Point();
}
const id = 1 / ((this.a * this.d) + (this.c * -this.b));
bp.x = (this.d * id * x) + (-this.c * id * y) + (((this.ty * this.c) - (this.tx * this.d)) * id);
bp.y = (this.a * id * y) + (-this.b * id * x) + (((-this.ty * this.a) + (this.tx * this.b)) * id);
return bp;
}
/**
* 从一个矩阵里赋值给这个矩阵
* @method setFrom
* @param {Matrix} mtx
* @public
* @since 1.0.0
*/
public setFrom(mtx: Matrix): void {
let s = this;
s.a = mtx.a;
s.b = mtx.b;
s.c = mtx.c;
s.d = mtx.d;
s.tx = mtx.tx;
s.ty = mtx.ty;
}
/**
* 将矩阵恢复成原始矩阵
* @method identity
* @public
* @since 1.0.0
*/
public identity(): void {
let s = this;
s.a = s.d = 1;
s.b = s.c = s.tx = s.ty = 0;
}
/**
* 反转一个矩阵
* @method invert
* @return {Matrix}
* @since 1.0.0
* @public
*/
public invert(): Matrix {
let s = this;
let a = s.a;
let b = s.b;
let c = s.c;
let d = s.d;
let tx = s.tx;
let ty = s.ty;
if (b == 0 && c == 0) {
if (a == 0 || d == 0) {
s.a = s.d = s.tx = s.ty = 0;
}
else {
a = s.a = 1 / a;
d = s.d = 1 / d;
s.tx = -a * tx;
s.ty = -d * ty;
}
return s;
}
let determinant = a * d - b * c;
if (determinant == 0) {
s.identity();
return s;
}
determinant = 1 / determinant;
let k = s.a = d * determinant;
b = s.b = -b * determinant;
c = s.c = -c * determinant;
d = s.d = a * determinant;
s.tx = -(k * tx + c * ty);
s.ty = -(b * tx + d * ty);
return s;
}
/**
* 设置一个矩阵通过普通的显示对象的相关九大属性,锚点不影响坐标原点,暂时不用
* @method createBox
* @param {number} x
* @param {number} y
* @param {number} scaleX
* @param {number} scaleY
* @param {number} rotation 角度制
* @param {number} skewX 角度制
* @param {number} skewY 角度制
* @param {number} ax
* @param {number} ay
* @since 1.0.0
* @public
*/
public createBox(x: number, y: number, scaleX: number, scaleY: number, rotation: number, skewX: number, skewY: number, ax: number, ay: number): void {
let s = this;
if (rotation != 0) {
skewX = skewY = rotation % 360;
} else {
skewX %= 360;
skewY %= 360;
}
if ((skewX == 0) && (skewY == 0)) {
s.a = scaleX;
s.b = s.c = 0;
s.d = scaleY;
} else {
skewX *= DEG_TO_RAD;
skewY *= DEG_TO_RAD;
let u = cos(skewX);
let v = sin(skewX);
if (skewX == skewY) {
s.a = u * scaleX;
s.b = v * scaleX;
}
else {
s.a = cos(skewY) * scaleX;
s.b = sin(skewY) * scaleX;
}
s.c = -v * scaleY;
s.d = u * scaleY;
};
s.tx = x + ax - (ax * s.a + ay * s.c);
s.ty = y + ay - (ax * s.b + ay * s.d);
}
/**
* 矩阵相乘
* @method prepend
* @public
* @since 1.0.0
* @param {Matrix} mtx
*/
public prepend = function (mtx: Matrix): void {
let s = this;
let a = mtx.a;
let b = mtx.b;
let c = mtx.c;
let d = mtx.d;
let tx = mtx.tx;
let ty = mtx.ty;
let a1 = s.a;
let c1 = s.c;
let tx1 = s.tx;
s.a = a * a1 + c * s.b;
s.b = b * a1 + d * s.b;
s.c = a * c1 + c * s.d;
s.d = b * c1 + d * s.d;
s.tx = a * tx1 + c * s.ty + tx;
s.ty = b * tx1 + d * s.ty + ty;
};
/**
* Appends the given Matrix to this Matrix.
*
* @param {Matrix} matrix - The matrix to append.
* @return {Matrix} This matrix. Good for chaining method calls.
*/
public append(matrix: Matrix) {
const a1 = this.a;
const b1 = this.b;
const c1 = this.c;
const d1 = this.d;
this.a = (matrix.a * a1) + (matrix.b * c1);
this.b = (matrix.a * b1) + (matrix.b * d1);
this.c = (matrix.c * a1) + (matrix.d * c1);
this.d = (matrix.c * b1) + (matrix.d * d1);
this.tx = (matrix.tx * a1) + (matrix.ty * c1) + this.tx;
this.ty = (matrix.tx * b1) + (matrix.ty * d1) + this.ty;
// return this;
}
/**
* 判断两个矩阵是否相等
* @method isEqual
* @static
* @public
* @since 1.0.0
* @param {Matrix} m1
* @param {Matrix} m2
* @return {boolean}
*/
public static isEqual(m1: Matrix, m2: Matrix): boolean {
return m1.tx == m2.tx && m1.ty == m2.ty && m1.a == m2.a && m1.b == m2.b && m1.c == m2.c && m1.d == m2.d;
}
public concat(mtx: Matrix): void {
let s = this;
let a = s.a, b = s.b, c = s.c, d = s.d,
tx = s.tx, ty = s.ty;
let ma = mtx.a, mb = mtx.b, mc = mtx.c, md = mtx.d,
mx = mtx.tx, my = mtx.ty;
s.a = a * ma + b * mc;
s.b = a * mb + b * md;
s.c = c * ma + d * mc;
s.d = c * mb + d * md;
s.tx = tx * ma + ty * mc + mx;
s.ty = tx * mb + ty * md + my;
}
/**
* 对矩阵应用旋转转换。
* @method rotate
* @param angle 弧度制
* @since 1.0.3
* @public
*/
public rotate(angle: number): void {
let s = this;
let sin = Math.sin(angle), cos = Math.cos(angle),
a = s.a, b = s.b, c = s.c, d = s.d,
tx = s.tx, ty = s.ty;
s.a = a * cos - b * sin;
s.b = a * sin + b * cos;
s.c = c * cos - d * sin;
s.d = c * sin + d * cos;
s.tx = tx * cos - ty * sin;
s.ty = tx * sin + ty * cos;
}
/**
* 对矩阵应用缩放转换。
* @method scale
* @param {Number} sx 用于沿 x 轴缩放对象的乘数。
* @param {Number} sy 用于沿 y 轴缩放对象的乘数。
* @since 1.0.3
* @public
*/
public scale(sx: number, sy: number): void {
let s = this;
s.a *= sx;
s.d *= sy;
s.c *= sx;
s.b *= sy;
s.tx *= sx;
s.ty *= sy;
}
/**
* 沿 x 和 y 轴平移矩阵,由 dx 和 dy 参数指定。
* @method translate
* @public
* @since 1.0.3
* @param {Number} dx 沿 x 轴向右移动的量(以像素为单位
* @param {Number} dy 沿 y 轴向右移动的量(以像素为单位
*/
public translate(dx: number, dy: number) {
let s = this;
s.tx += dx;
s.ty += dy;
}
public set(a, b, c, d, tx, ty) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.tx = tx;
this.ty = ty;
return this;
}
/**
* Creates an array from the current Matrix object.与glsl中的mat3对应,注意行列主序执行transpose;
*
* @param {boolean} transpose - Whether we need to transpose the matrix or not
* @param {Float32Array} [out=new Float32Array(9)] - If provided the array will be assigned to out
* @return {number[]} the newly created array which contains the matrix
*/
public toArray(transpose = false, out?) {
if (!this.array) {
this.array = new Float32Array(9);
}
const array = out || this.array;
if (transpose) {
array[0] = this.a;
array[1] = this.b;
array[2] = 0;
array[3] = this.c;
array[4] = this.d;
array[5] = 0;
array[6] = this.tx;
array[7] = this.ty;
array[8] = 1;
} else {
array[0] = this.a;
array[1] = this.c;
array[2] = this.tx;
array[3] = this.b;
array[4] = this.d;
array[5] = this.ty;
array[6] = 0;
array[7] = 0;
array[8] = 1;
}
return array;
}
/**
* A default (identity) matrix
*
* @static
* @const
*/
public static get IDENTITY() {
return new Matrix();
}
/**
* A temp matrix
*
* @static
* @const
*/
public static get TEMP_MATRIX() {
return new Matrix();
}
destroy(): void {
}
}
\ No newline at end of file
import { Point } from "../math/Point";
import { HashObject } from "../HashObject";
/**
* 动态可监控ObservablePoint类
* @class
*/
export class ObservablePoint extends HashObject {
_x: number;
_y: number;
cb: any;
scope: any;
/**
* @param {Function} cb - 值改变时的回调
* @param {object} scope - 回调里的上下文this
* @param {number} [x=0] - x
* @param {number} [y=0] - y
*/
constructor(cb: Function, scope: any, x: number = 0, y: number = 0) {
super();
let s = this;
s._instanceType = "ObservablePoint";
this._x = x;
this._y = y;
this.cb = cb;
this.scope = scope;
}
/**
* 设置xy
* @param {number} [x=0]
* @param {number} [y=0]
*/
set(x: number = 0, y: number = 0) {
const _x = x || 0;
const _y = y || ((y !== 0) ? _x : 0);
if (this._x !== _x || this._y !== _y) {
this._x = _x;
this._y = _y;
this.cb.call(this.scope);
}
}
/**
* 从一个点复制xy
*
* @param {Point|ObservablePoint} point
*/
copy(point: Point | ObservablePoint) {
if (this._x !== point.x || this._y !== point.y) {
this._x = point.x;
this._y = point.y;
this.cb.call(this.scope);
}
}
get x() {
return this._x;
}
set x(value) {
if (this._x !== value) {
this._x = value;
this.cb.call(this.scope);
}
}
get y() {
return this._y;
}
set y(value) {
if (this._y !== value) {
this._y = value;
this.cb.call(this.scope);
}
}
destroy() {
}
}
import { HashObject } from "../HashObject";
/**
* @class Point
* @extends HashObject
* @since 1.0.0
* @public
*/
export class Point extends HashObject {
public destroy(): void { }
/**
* 构造函数
* @method Point
* @public
* @since 1.0.0
* @param x
* @param y
*/
constructor(x: number = 0, y: number = 0) {
super();
let s = this;
s._instanceType = "Point";
s.x = x;
s.y = y;
}
/**
* 水平坐标
* @property x
* @public
* @since 1.0.0
* @type{number}
*/
public x: number = 0;
/**
* 垂直坐标
* @property y
* @since 1.0.0
* @public
* @type {number}
*/
public y: number = 0;
/**
* 求两点之间的距离
* @method distance
* @param args 可变参数 传两个参数的话就是两个Point类型 传四个参数的话分别是两个点的x y x y
* @return {number}
* @static
*/
public static distance(...args: any[]): number {
let len = args.length;
if (len == 4) {
return Math.sqrt((args[0] - args[2]) * (args[0] - args[2]) + (args[1] - args[3]) * (args[1] - args[3]));
} else if (len == 2) {
return Math.sqrt((args[0].x - args[1].x) * (args[0].x - args[1].x) + (args[0].y - args[1].y) * (args[0].y - args[1].y));
}
}
set(x: number, y: number) {
this.x = x;
this.y = y;
}
}
import { HashObject } from "../HashObject";
import { Point } from "./Point";
import { SHAPES } from "../const";
/**
*
* @class Rectangle
* @extends HashObject
* @public
* @since 1.0.0
*/
export class Rectangle extends HashObject {
/**
* 类型
*/
type: number
/**
* 构造函数
* @method Rectangle
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
public constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
super();
var s = this;
s._instanceType = "Rectangle";
s.x = x;
s.y = y;
s.height = height;
s.width = width;
this.type = SHAPES.RECT
}
clear() {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
copy(rect: Rectangle) {
this.x = rect.x;
this.y = rect.y;
this.width = rect.width;
this.height = rect.height;
return this;
}
clone() {
return new Rectangle(this.x, this.y, this.width, this.height);
}
/**
* 矩形左上角的 x 坐标
* @property x
* @public
* @since 1.0.0
* @type{number}
* @default 0
*/
public x: number = 0;
/**
* 矩形左上角的 y 坐标
* @property y
* @public
* @since 1.0.0
* @type{number}
* @default 0
*/
public y: number = 0;
/**
* 矩形的宽度(以像素为单位)
* @property width
* @public
* @since 1.0.0
* @type{number}
* @default 0
*/
public width: number = 0;
/**
* 矩形的高度(以像素为单位)
* @property height
* @public
* @since 1.0.0
* @type{number}
* @default 0
*/
public height: number = 0;
/**
* returns the left edge of the rectangle
*/
get left(): number {
return this.x;
}
/**
* returns the right edge of the rectangle
*/
get right(): number {
return this.x + this.width;
}
/**
* returns the top edge of the rectangle
*/
get top(): number {
return this.y;
}
/**
* returns the bottom edge of the rectangle
*/
get bottom(): number {
return this.y + this.height;
}
/**
* 判断一个点是否在矩形内包括边
* @method isPointIn
* @param {Point} point
* @return {boolean}
* @public
* @since 1.0.0
*/
public isPointIn(point: Point): boolean {
let s = this;
return point.x >= s.x && point.x <= (s.x + s.width) && point.y >= s.y && point.y <= (s.y + s.height);
}
/**
* Fits this rectangle around the passed one.
*
* @param {Rectangle} rectangle - The rectangle to fit.
*/
fit(rectangle: Rectangle) {
var x1 = Math.max(this.x, rectangle.x);
var x2 = Math.min(this.x + this.width, rectangle.x + rectangle.width);
var y1 = Math.max(this.y, rectangle.y);
var y2 = Math.min(this.y + this.height, rectangle.y + rectangle.height);
this.x = x1;
this.width = Math.max(x2 - x1, 0);
this.y = y1;
this.height = Math.max(y2 - y1, 0);
};
/**
* 将多个矩形合成为一个矩形,并将结果存到第一个矩形参数,并返回
* @method createFromRects
* @param {Rectangle} rect
* @param {..arg} arg
* @public
* @since 1.0.0
* @static
*/
public static createFromRects(...arg: Rectangle[]): Rectangle {
if (arg.length == 0) {
return null;
} else if (arg.length == 1) {
return arg[0];
} else {
let rect = arg[0];
let x = rect.x, y = rect.y, w = rect.width, h = rect.height, wx1: number, wx2: number, hy1: number, hy2: number;
for (let i: number = 1; i < arg.length; i++) {
wx1 = x + w;
hy1 = y + h;
wx2 = arg[i].x + arg[i].width;
hy2 = arg[i].y + arg[i].height;
if (x > arg[i].x || wx1 == 0) {
x = arg[i].x;
}
if (y > arg[i].y || hy1 == 0) {
y = arg[i].y;
}
if (wx1 < wx2) {
wx1 = wx2;
}
if (hy1 < hy2) {
hy1 = hy2;
}
rect.x = x;
rect.y = y;
rect.width = wx1 - x;
rect.height = hy1 - y;
}
return rect;
}
}
/**
* 通过一系列点来生成一个矩形
* 返回包含所有给定的点的最小矩形
* @method createFromPoints
* @static
* @public
* @since 1.0.0
* @param {Point} p1
* @param {..arg} ary
* @return {Rectangle}
*/
public static createFromPoints(rect: Rectangle, ...arg: Point[]): Rectangle {
let x = arg[0].x, y = arg[0].y, w = arg[0].x, h = arg[0].y;
for (let i: number = 1; i < arg.length; i++) {
if (arg[i] == null) continue;
if (x > arg[i].x) {
x = arg[i].x;
}
if (y > arg[i].y) {
y = arg[i].y;
}
if (w < arg[i].x) {
w = arg[i].x;
}
if (h < arg[i].y) {
h = arg[i].y;
}
}
rect.x = x;
rect.y = y;
rect.width = w - x;
rect.height = h - y;
return rect;
}
/**
* 通过顶点数据 [0,1,
* 2,3,
* 1,3,
* 1,0]
* @param rect
* @param vertexData 一般为8长度
*/
public static createFromVertexData(rect: Rectangle, vertexData: Float32Array): Rectangle {
let x = vertexData[0], y = vertexData[1], w = vertexData[0], h = vertexData[1];
for (let i: number = 2; i < vertexData.length; i += 2) {
if (vertexData[i] == null) continue;
if (x > vertexData[i]) {
x = vertexData[i];
}
if (y > vertexData[i + 1]) {
y = vertexData[i + 1];
}
if (w < vertexData[i]) {
w = vertexData[i];
}
if (h < vertexData[i + 1]) {
h = vertexData[i + 1];
}
}
rect.x = x;
rect.y = y;
rect.width = w - x;
rect.height = h - y;
return rect;
}
/**
* 通过两个点来确定一个矩形
* @method createRectform2Point
* @static
* @param rect
* @param p1
* @param p2
* @return
*/
public static createRectfrom2Point(rect: Rectangle, p1: Point, p2: Point): Rectangle {
let x = p1.x, y = p1.y, w = p1.x, h = p1.y;
if (x > p2.x) {
x = p2.x;
}
if (y > p2.y) {
y = p2.y;
}
if (w < p2.x) {
w = p2.x;
}
if (h < p2.y) {
h = p2.y;
}
rect.x = x, rect.y = y, rect.width = w - x, rect.height = h - y;
return rect;
}
/**
* 判读两个矩形是否相交
* @method testRectCross
* @public
* @since 1.0.2
* @param r1
* @param r2
* @return {boolean}
*/
public static testRectCross(ra: Rectangle, rb: Rectangle): boolean {
let a_cx: number, a_cy: number; /* 第一个中心点*/
let b_cx: number, b_cy: number; /* 第二个中心点*/
a_cx = ra.x + (ra.width / 2);
a_cy = ra.y + (ra.height / 2);
b_cx = rb.x + (rb.width / 2);
b_cy = rb.y + (rb.height / 2);
return ((Math.abs(a_cx - b_cx) <= (ra.width / 2 + rb.width / 2)) && (Math.abs(a_cy - b_cy) <= (ra.height / 2 + rb.height / 2)));
}
public destroy(): void {
}
}
import { ObservablePoint } from './ObservablePoint';
import { Matrix } from "./Matrix";
import { HashObject } from '../HashObject';
import { cos, sin } from '../const';
/**
* @class
*/
export default class Transform extends HashObject {
/**
* 世界矩阵
*/
worldMatrix: Matrix;
/**
* 本地矩阵
*/
localMatrix: Matrix;
/**
* 记录是否更新worldMatrix
*/
_worldID: number;
/**
* 记录是否和父级更新的_worldID一致
*/
_parentID: number;
/**
* 位置
*/
position: ObservablePoint;
/**
* 缩放
*/
scale: ObservablePoint;
/**
* 锚点
* 不改变坐标原点
*/
anchor: ObservablePoint;
/**
* 斜切值
*/
skew: ObservablePoint;
/**
* 弧度制
*/
_rotation: number;
_cx: number;
_sx: number;
_cy: number;
_sy: number;
/**
* 记录的本地坐标id
*/
_localID: number;
/**
* 当前本地坐标id
*/
_currentLocalID: number;
constructor() {
super();
let s = this;
s._instanceType = "Transform";
this.worldMatrix = new Matrix();
this.localMatrix = new Matrix();
this._worldID = 0;
this._parentID = 0;
this.position = new ObservablePoint(this.onChange, this, 0, 0);
this.scale = new ObservablePoint(this.onChange, this, 1, 1);
this.anchor = new ObservablePoint(this.onChange, this, 0, 0);
this.skew = new ObservablePoint(this.updateSkew, this, 0, 0);
this._rotation = 0;
this._cx = 1; // cos rotation + skewY;
this._sx = 0; // sin rotation + skewY;
this._cy = 0; // cos rotation + Math.PI/2 - skewX;
this._sy = 1; // sin rotation + Math.PI/2 - skewX;
this._localID = 0;
this._currentLocalID = 0;
}
/**
* 任何属性更改
* @private
*/
onChange() {
this._localID++;
}
/**
* 当斜切改变时,先记录,优化计算
* @private
*/
updateSkew() {
this._cx = cos(this._rotation + this.skew._y);
this._sx = sin(this._rotation + this.skew._y);
this._cy = -sin(this._rotation - this.skew._x); // cos, added PI/2
this._sy = cos(this._rotation - this.skew._x); // sin, added PI/2
this._localID++;
}
/**
* 更新本地矩阵
*/
updateLocalMatrix() {
const lt = this.localMatrix;
if (this._localID !== this._currentLocalID) {
// get the matrix values of the displayobject based on its transform properties..
lt.a = this._cx * this.scale._x;
lt.b = this._sx * this.scale._x;
lt.c = this._cy * this.scale._y;
lt.d = this._sy * this.scale._y;
//不改变位置原点,只改缩放和旋转中心点,+this.anchor._x
lt.tx = this.position._x + this.anchor._x - ((this.anchor._x * lt.a) + (this.anchor._y * lt.c));
lt.ty = this.position._y + this.anchor._y - ((this.anchor._x * lt.b) + (this.anchor._y * lt.d));
this._currentLocalID = this._localID;
//保证会更新世界坐标
this._parentID = -1;
}
}
/**
* 更新世界矩阵,跟随父级修改
* @param {Transform} parentTransform - 父级矩阵
*/
updateWorldMatrix(parentTransform: Transform) {
const lt = this.localMatrix;
//先确定local是否需要更新
if (this._localID !== this._currentLocalID) {
// get the matrix values of the displayobject based on its transform properties..
lt.a = this._cx * this.scale._x;
lt.b = this._sx * this.scale._x;
lt.c = this._cy * this.scale._y;
lt.d = this._sy * this.scale._y;
//不改变位置原点,只改缩放和旋转中心点,+this.anchor._x
lt.tx = this.position._x + this.anchor._x - ((this.anchor._x * lt.a) + (this.anchor._y * lt.c));
lt.ty = this.position._y + this.anchor._y - ((this.anchor._x * lt.b) + (this.anchor._y * lt.d));
this._currentLocalID = this._localID;
//保证会更新世界坐标
this._parentID = -1;
}
//判断是否和父级更新的_worldID一致
if (this._parentID !== parentTransform._worldID) {
// concat the parent matrix with the objects transform.
const pt = parentTransform.worldMatrix;
const wt = this.worldMatrix;
wt.a = (lt.a * pt.a) + (lt.b * pt.c);
wt.b = (lt.a * pt.b) + (lt.b * pt.d);
wt.c = (lt.c * pt.a) + (lt.d * pt.c);
wt.d = (lt.c * pt.b) + (lt.d * pt.d);
wt.tx = (lt.tx * pt.a) + (lt.ty * pt.c) + pt.tx;
wt.ty = (lt.tx * pt.b) + (lt.ty * pt.d) + pt.ty;
this._parentID = parentTransform._worldID;
//修改自身worldId,保证子级会更新
this._worldID++;
}
}
/**
* 弧度制
*
* @member {number}
*/
get rotation() {
return this._rotation;
}
set rotation(value) {
if (value === this._rotation) return
this._rotation = value;
this.updateSkew();
}
destroy() {
}
}
export { Matrix } from './Matrix';
export { Point } from './Point';
export { ObservablePoint } from './ObservablePoint';
export { Rectangle } from './Rectangle';
// export {default as Transform} from './Transform';
export {default as Transform} from './Transform';
import SystemRenderer from './SystemRenderer';
import CanvasMaskManager from './managers/CanvasMaskManager';
import CanvasRenderTarget from './renderTarget/CanvasRenderTarget';
import { RENDERER_TYPE } from '../const';
import { RendererOptions } from './RendererOptions';
import RenderTexture from '../texture/RenderTexture';
import { Matrix } from '../math';
import { DisplayObject } from '../display/DisplayObject';
import CanvasSpriteRenderer from './plugins/CanvasSpriteRenderer';
import CanvasGraphicsRenderer from './plugins/CanvasGraphicsRenderer';
/**
* @class
* @extends SystemRenderer
*/
export default class CanvasRenderer extends SystemRenderer {
/**
* 主屏幕渲染上下文
*/
rootContext: CanvasRenderingContext2D;
/**
* 当前使用的上下文
*/
context: CanvasRenderingContext2D;
/**
* 遮罩管理类
*/
maskManager: CanvasMaskManager;
/**
* 考虑是否需要支持
*/
smoothProperty: string;
/**
* 插件,暂时只有图形和图片的
*/
plugins: { sprite: CanvasSpriteRenderer; graphics: CanvasGraphicsRenderer; };
renderingToScreen: boolean;
constructor(options: RendererOptions) {
super(options);
this._instanceType = "CanvasRenderer";
this.type = RENDERER_TYPE.CANVAS;
this.rootContext = this.htmlElement ? this.htmlElement.getContext('2d', { alpha: this.transparent }) : null;
this.context = this.rootContext;
this.maskManager = new CanvasMaskManager(this);
//smooth兼容
this.smoothProperty = 'imageSmoothingEnabled';
if (this.rootContext && !this.rootContext.imageSmoothingEnabled) {
if (this.rootContext["webkitImageSmoothingEnabled"]) {
this.smoothProperty = 'webkitImageSmoothingEnabled';
}
else if (this.rootContext["mozImageSmoothingEnabled"]) {
this.smoothProperty = 'mozImageSmoothingEnabled';
}
else if (this.rootContext["oImageSmoothingEnabled"]) {
this.smoothProperty = 'oImageSmoothingEnabled';
}
else if (this.rootContext["msImageSmoothingEnabled"]) {
this.smoothProperty = 'msImageSmoothingEnabled';
}
}
// this.initPlugins();
this.plugins = {
sprite: null,
graphics: null,
}
this.plugins.sprite = new CanvasSpriteRenderer(this);
this.plugins.graphics = new CanvasGraphicsRenderer(this);
this.renderingToScreen = false;
}
/**
* 渲染方法
* @param {DisplayObject} displayObject - 渲染对象
* @param {RenderTexture} [renderTexture] -离屏渲染纹理
* @param {Matrix} [transform] - 矩阵偏移
*/
render(displayObject: DisplayObject, renderTexture?: any, transform?: Matrix) {
//渲染开始前触发
this.dispatchEvent('prerender');
//是否渲染到主屏幕
this.renderingToScreen = !renderTexture;
if (renderTexture) {
renderTexture = renderTexture.baseTexture || renderTexture;
if (!renderTexture._canvasRenderTarget) {
renderTexture._canvasRenderTarget = new CanvasRenderTarget(
renderTexture.width,
renderTexture.height,
);
renderTexture.source = renderTexture._canvasRenderTarget.canvas;
renderTexture.valid = true;
} else {
//已有的话,需要重画
renderTexture._canvasRenderTarget.clear();
renderTexture._canvasRenderTarget.resize(renderTexture.width, renderTexture.height);
}
//当前上下文要修改成离屏的
this.context = renderTexture._canvasRenderTarget.context;
}
else {
//当前上下文就是根节点的
this.context = this.rootContext;
}
const context = this.context;
if (!renderTexture) {
this._lastObjectRendered = displayObject;
}
//update更新属性
displayObject.update()
//存下真实的父级对象
const cacheParent = displayObject.parent;
const tempWt = this._tempDisplayObjectParent.transform.worldMatrix;
if (transform) {
//有transform则复制
tempWt.copy(transform);
//标记要更新transform
this._tempDisplayObjectParent.transform._worldID = -1;
}
else {
//没有就初始化
tempWt.identity();
}
displayObject.parent = this._tempDisplayObjectParent;
displayObject.updateTransform();
displayObject.parent = cacheParent;
//初始化上下文状态
context.save();
context.setTransform(1, 0, 0, 1, 0, 0);
context.globalAlpha = 1;
if (this.renderingToScreen) {
if (this.transparent) {
context.clearRect(0, 0, this.htmlElement.width, this.htmlElement.height);
}
else {
context.fillStyle = this._backgroundColorString;
context.fillRect(0, 0, this.htmlElement.width, this.htmlElement.height);
}
}
//执行绘制
displayObject.renderCanvas(this);
context.restore();
//渲染后触发
this.dispatchEvent('postrender');
}
/**
* 清空画布
*/
clear(clearColor: string) {
const context = this.context;
clearColor = clearColor || this._backgroundColorString;
if (!this.transparent && clearColor) {
context.fillStyle = clearColor;
context.fillRect(0, 0, this.htmlElement.width, this.htmlElement.height);
}
else {
context.clearRect(0, 0, this.htmlElement.width, this.htmlElement.height);
}
}
/**
* 销毁
*/
destroy() {
this.destroyPlugins();
super.destroy();
this.context = null;
this.maskManager.destroy();
this.maskManager = null;
this.smoothProperty = null;
}
/**
* 缩放尺寸
* @param {number} screenWidth
* @param {number} screenHeight
*/
resize(screenWidth: number, screenHeight: number) {
super.resize(screenWidth, screenHeight);
if (this.smoothProperty) {
this.rootContext[this.smoothProperty] = true;
}
}
destroyPlugins() {
this.plugins.sprite.destroy();
this.plugins.graphics.destroy();
}
}
export interface RendererOptions {
/**
* canvas标签,注意只有一种情况,canvasRender用于画质缓存canvas时不用,其他时候都必带
*/
htmlElement?: HTMLCanvasElement
/**
* If the render view is transparent
* 设置透明,则背景色无效
*/
transparent?: boolean;
/**
* sets antialias (only applicable in chrome at the moment)
*
*/
antialias?: boolean;
/**
* enables drawing buffer preservation,
* enable this if you need to call toDataUrl on the webgl context
*/
preserveDrawingBuffer?: boolean;
/**
* The background color of the rendered area
* (shown if not transparent).0x000000
*/
backgroundColor?: number;
/**
* If true PixiJS will Math.floor() x/y values when rendering,
* stopping pixel interpolation.
*/
roundPixels?: boolean;
}
\ No newline at end of file
import { hex2string, hex2rgb } from '../utils';
import { Matrix, Rectangle } from '../math';
import { RENDERER_TYPE, devicePixelRatio } from '../const';
import Container from '../display/Container';
import { EventDispatcher } from '../events/EventDispatcher';
import { DisplayObject } from "../display/DisplayObject";
import { RendererOptions } from "./RendererOptions";
const tempMatrix = new Matrix();
/**
* The SystemRenderer is the base for a Renderer. It is extended by the {@link CanvasRenderer}
* and {@link WebGLRenderer} which can be used for rendering a scene.
* @abstract
* @class
*/
export default class SystemRenderer extends EventDispatcher {
/**
* 渲染器参数
*/
options: RendererOptions;
/**
* 渲染类型
*
* @member {number}
* @default RENDERER_TYPE.UNKNOWN
* @see RENDERER_TYPE
*/
type: number;
/**
* canvas对象
*/
htmlElement: HTMLCanvasElement;
/**
* 是否透明
*/
transparent: boolean;
/**
* The value of the preserveDrawingBuffer flag affects whether or not the contents of
* the stencil buffer is retained after rendering.
*/
preserveDrawingBuffer: boolean;
/**
* 背景色,十六进制数值
* @member {number}
*/
_backgroundColor: number;
/**
* 背景色,rgba值
* @member {number[]}
*/
_backgroundColorRgba: number[];
/**
* 背景色,字符串
*/
_backgroundColorString: string;
/**
* 临时父级对象,用于更新updateTransform
*/
_tempDisplayObjectParent: Container;
/**
* 上一个被渲染的根显示对象
*/
_lastObjectRendered: DisplayObject;
constructor(options: RendererOptions) {
super();
this.options = options;
this.type = RENDERER_TYPE.UNKNOWN;
this.htmlElement = options.htmlElement;
this.transparent = options.transparent;
this.preserveDrawingBuffer = options.preserveDrawingBuffer;
this._backgroundColor = 0x000000;
this._backgroundColorRgba = [0, 0, 0, 0];
this._backgroundColorString = '#000000';
this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter
this._tempDisplayObjectParent = new Container();
this._lastObjectRendered = this._tempDisplayObjectParent;
}
/**
* 尺寸重置,暂时根据屏幕分辨率
* @param {number} screenWidth
* @param {number} screenHeight
*/
resize(width: number, height: number) {
this.htmlElement.width = width * devicePixelRatio;
this.htmlElement.height = height * devicePixelRatio;
this.htmlElement.style.width = `${width}px`;
this.htmlElement.style.height = `${height}px`;
}
/**
* 核心渲染方法,子级重写
*/
render(displayObject, renderTexture?, transform?) {
}
/**
* 销毁方法
*/
destroy() {
this.type = RENDERER_TYPE.UNKNOWN;
this.htmlElement = null;
this.transparent = false;
this.options = null;
this.preserveDrawingBuffer = false;
this._backgroundColor = 0;
this._backgroundColorRgba = null;
this._backgroundColorString = null;
this._tempDisplayObjectParent = null;
this._lastObjectRendered = null;
}
/**
* 背景色,十六进制颜色
* @member {number}
*/
get backgroundColor(): number {
return this._backgroundColor;
}
/**
* 背景色,十六进制颜色
*/
set backgroundColor(value: number) {
if (this._backgroundColor === value) return
this._backgroundColor = value;
this._backgroundColorString = hex2string(value);
hex2rgb(value, this._backgroundColorRgba);
}
}
import { EventDispatcher } from "../events/EventDispatcher";
import BatchRenderer from "./plugins/BatchRenderer";
import SystemRenderer from "./SystemRenderer";
import { RendererOptions } from "./RendererOptions";
import { RENDERER_TYPE } from "../const";
import { createContext, GLShader, VertexArrayObject } from "../../glCore";
import ObjectRenderer from "./webgl/ObjectRenderer";
import RenderTarget from "./renderTarget/RenderTarget";
import TextureManager from "./managers/TextureManager";
import TextureGarbageCollector from "./managers/TextureGarbageCollector";
import { SCALE_MODES, devicePixelRatio } from "../const";
import RenderTexture from "../texture/RenderTexture";
import { Matrix } from "../math";
import WebGLState from "./webgl/WebGLState";
import BatchManager from "./managers/BatchManager";
import MaskManager from "./managers/MaskManager";
import StencilManager from "./managers/StencilManager";
import { DisplayObject } from "../display/DisplayObject";
let CONTEXT_UID = 0;
export class WebglRenderer extends SystemRenderer {
/**
* 所有插件列表,目前只有batch
*/
plugins = {};
gl: WebGLRenderingContext
CONTEXT_UID: number;
maskManager: MaskManager;
stencilManager: StencilManager;
batchManager: BatchManager
textureManager: TextureManager;
textureGC: TextureGarbageCollector;
state: WebGLState;
renderingToScreen: boolean;
_activeShader: any;
_activeVao: VertexArrayObject;
/**
* Holds the current render target
*
* @member {RenderTarget}
*/
_activeRenderTarget: RenderTarget;
rootRenderTarget: RenderTarget;
constructor(options: RendererOptions) {
super(options);
this._instanceType = "WebglRenderer";
this.type = RENDERER_TYPE.WEBGL;
this.handleContextLost = this.handleContextLost.bind(this);
this.handleContextRestored = this.handleContextRestored.bind(this);
this.htmlElement.addEventListener('webglcontextlost', this.handleContextLost, false);
this.htmlElement.addEventListener('webglcontextrestored', this.handleContextRestored, false);
//The options passed in to create a new webgl context.
var contextOptions = {
alpha: this.transparent,
antialias: this.options.antialias,
premultipliedAlpha: true,
stencil: true,
preserveDrawingBuffer: this.options.preserveDrawingBuffer,
// powerPreference: this.options.powerPreference,
};
this._backgroundColorRgba[3] = this.transparent ? 0 : 1;
this.gl = createContext(this.htmlElement, contextOptions);
this.CONTEXT_UID = CONTEXT_UID++;
this.maskManager = new MaskManager(this);
this.stencilManager = new StencilManager(this);
this.batchManager = new BatchManager(this)
this.textureManager = null;
//初始化插件
this.initPlugins(WebglRenderer.__plugins)
this.state = new WebGLState(this.gl);
this.renderingToScreen = true;
/**
* Holds the current shader
*
* @member {Shader}
*/
this._activeShader = null;
this._activeVao = null;
this._activeRenderTarget = null;
this._initContext();
// this.state.setBlendMode(0);
}
_initContext() {
const gl = this.gl;
// restore a context if it was previously lost
if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) {
gl.getExtension('WEBGL_lose_context').restoreContext();
}
this._activeShader = null;
this._activeVao = null;
// create a texture manager...
this.textureManager = new TextureManager(this);
this.textureGC = new TextureGarbageCollector(this);
this.state.resetToDefault();
this.rootRenderTarget = new RenderTarget(gl, 1, 1, SCALE_MODES.LINEAR, true);
this.rootRenderTarget.clearColor = this._backgroundColorRgba;
this.bindRenderTarget(this.rootRenderTarget);
this.dispatchEvent('onContextChange', gl);
}
render(displayObject: DisplayObject, renderTexture?, transform?) {
//触发onPreRender
this.dispatchEvent('onPreRender');
//是否渲染到屏幕root
this.renderingToScreen = !renderTexture;
//如果上下文丢失
if (!this.gl || this.gl.isContextLost()) {
return;
}
if (!renderTexture) {
this._lastObjectRendered = displayObject;
}
//update更新属性
displayObject.update()
//更新矩阵
const cacheParent = displayObject.parent;
displayObject.parent = this._tempDisplayObjectParent;
displayObject.updateTransform();
displayObject.parent = cacheParent;
//绑定渲染对象,没有则是默认root
this.bindRenderTexture(renderTexture, transform);
//当前插件start
this.batchManager.currentRenderer.start();
//渲染对象清空画布
this._activeRenderTarget.clear();
//开始渲染所有的显示对象
displayObject.renderWebGL(this);
//
this.batchManager.currentRenderer.flush();
if (renderTexture) {
renderTexture.baseTexture.update();
}
// this.textureGC.update();
//触发onPostRender
this.dispatchEvent('onPostRender');
}
/**
* Erases the active render target and fills the drawing area with a colour
*
* @param {number} [clearColor] - The colour
*/
clear(clearColor: number[]) {
this._activeRenderTarget.clear(clearColor);
}
/**
* Sets the transform of the active render target to the given matrix
*
* @param {Matrix} matrix - The transformation matrix
*/
setTransform(matrix: Matrix) {
this._activeRenderTarget.transform = matrix;
}
/**
* Erases the render texture and fills the drawing area with a colour
*
* @param {RenderTexture} renderTexture - The render texture to clear
* @param {number} [clearColor] - The colour
* @return {WebGLRenderer} Returns itself.
*/
clearRenderTexture(renderTexture, clearColor) {
const baseTexture = renderTexture.baseTexture;
const renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID];
if (renderTarget) {
renderTarget.clear(clearColor);
}
return this;
}
/**
* Changes the current shader to the one given in parameter
*
* @param {Shader} shader - the new shader
* @param {boolean} [autoProject=true] - Whether automatically set the projection matrix
* @return {WebGLRenderer} Returns itself.
*/
bindShader(shader: GLShader, autoProject: boolean = true): WebglRenderer {
// TODO cache
if (this._activeShader !== shader) {
this._activeShader = shader;
shader.bind();
// `autoProject` normally would be a default parameter set to true
// but because of how Babel transpiles default parameters
// it hinders the performance of this method.
if (autoProject !== false) {
// automatically set the projection matrix
shader.uniforms["projectionMatrix"] = this._activeRenderTarget.projectionMatrix.toArray(true);
}
}
return this;
}
/**
* Creates a new VAO from this renderer's context and state.
*
* @return {VertexArrayObject} The new VAO.
*/
createVao(): VertexArrayObject {
return new VertexArrayObject(this.gl, this.state.attribState);
}
/**
* Changes the current Vao to the one given in parameter
*
* @param {PIXI.VertexArrayObject} vao - the new Vao
* @return {PIXI.WebGLRenderer} Returns itself.
*/
bindVao(vao: VertexArrayObject): WebglRenderer {
if (this._activeVao === vao) {
return this;
}
if (vao) {
vao.bind();
}
else if (this._activeVao) {
// TODO this should always be true i think?
this._activeVao.unbind();
}
this._activeVao = vao;
return this;
}
/**
* Resets the WebGL state so you can render things however you fancy!
*
* @return {WebGLRenderer} Returns itself.
*/
reset(): WebglRenderer {
this.batchManager.reset();
this.bindVao(null);
this._activeShader = null;
this._activeRenderTarget = this.rootRenderTarget;
for (let i = 0; i < this.textureManager.boundTextures.length; i++) {
this.textureManager.boundTextures[i] = this.textureManager.emptyTextures[i];
}
// bind the main frame buffer (the screen);
this.rootRenderTarget.activate();
this.state.resetToDefault();
return this;
}
/**
* Binds a render texture for rendering
*
* @param {RenderTexture} renderTexture - The render texture to render
* @param {Matrix} transform - The transform to be applied to the render texture
* @return {WebGLRenderer} Returns itself.
*/
bindRenderTexture(renderTexture: any, transform?: Matrix): WebglRenderer {
let renderTarget;
if (renderTexture) {
const baseTexture = renderTexture.baseTexture;
if (!baseTexture._glRenderTargets[this.CONTEXT_UID]) {
// bind the current texture
this.textureManager.updateTexture(baseTexture, 0);
}
this.textureManager.unbindTexture(baseTexture);
renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID];
renderTarget.setFrame(renderTexture.frame);
}
else {
renderTarget = this.rootRenderTarget;
}
renderTarget.transform = transform;
this.bindRenderTarget(renderTarget);
return this;
}
/**
* Changes the current render target to the one given in parameter
*
* @param {RenderTarget} renderTarget - the new render target
* @return {WebglRenderer} Returns itself.
*/
bindRenderTarget(renderTarget: RenderTarget): WebglRenderer {
if (renderTarget !== this._activeRenderTarget) {
this._activeRenderTarget = renderTarget;
renderTarget.activate();
if (this._activeShader) {
this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true);
}
this.stencilManager.setMaskStack(renderTarget.stencilMaskStack);
}
return this;
}
resize(screenWidth, screenHeight) {
// if(width * this.resolution === this.width && height * this.resolution === this.height)return;
super.resize(screenWidth, screenHeight);
this.rootRenderTarget.resize(screenWidth * devicePixelRatio, screenHeight * devicePixelRatio);
if (this._activeRenderTarget === this.rootRenderTarget) {
this.rootRenderTarget.activate();
if (this._activeShader) {
this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true);
}
}
}
destroy() {
this.destroyPlugins();
// remove listeners
this.htmlElement.removeEventListener('webglcontextlost', this.handleContextLost);
this.htmlElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
this.textureManager.destroy();
// call base destroy
super.destroy();
// destroy the managers
this.maskManager.destroy();
this.stencilManager.destroy();
// this.filterManager.destroy();
this.maskManager = null;
// this.filterManager = null;
this.textureManager = null;
this.handleContextLost = null;
this.handleContextRestored = null;
this.gl.useProgram(null);
if (this.gl.getExtension('WEBGL_lose_context')) {
this.gl.getExtension('WEBGL_lose_context').loseContext();
}
this.gl = null;
}
/**
* Handles a restored webgl context
*
* @private
*/
handleContextRestored() {
this.textureManager.removeAll();
// this.filterManager.destroy(true);
this._initContext();
}
/**
* Handles a lost webgl context
*
* @private
* @param {WebGLContextEvent} event - The context lost event.
*/
handleContextLost(event: WebGLContextEvent) {
event.preventDefault();
}
/**
* Initialize the plugins.
*
* @protected
* @param {object} staticMap - The dictionary of statically saved plugins.
*/
initPlugins(staticMap: any) {
for (const o in staticMap) {
this.plugins[o] = new (staticMap[o])(this);
}
}
destroyPlugins() {
for (const o in this.plugins) {
this.plugins[o].destroy();
this.plugins[o] = null;
}
this.plugins = null;
}
static __plugins;
/**
* Adds a plugin to the renderer.
*
* @method
* @param {string} pluginName - The name of the plugin.
* @param {Function} ctor - The constructor function or class for the plugin.
*/
static registerPlugin(pluginName: string, ctor: Function) {
WebglRenderer.__plugins = WebglRenderer.__plugins || {};
WebglRenderer.__plugins[pluginName] = ctor;
}
}
//注册所有插件
WebglRenderer.registerPlugin('batch', BatchRenderer);
import ObjectRenderer from '../webgl/ObjectRenderer';
import { WebglRenderer } from '../WebglRenderer';
/**
* System plugin to the renderer to manage batching.
*
* 批处理管理器,用于切换渲染插件
* @class
*/
export default class BatchManager {
renderer: WebglRenderer;
emptyRenderer: ObjectRenderer;
currentRenderer: ObjectRenderer;
/**
* The renderer this System works for.
*/
constructor(renderer: WebglRenderer) {
this.renderer = renderer;
/**
* An empty renderer.
*
* @member {ObjectRenderer}
*/
this.emptyRenderer = new ObjectRenderer(renderer);
/**
* The currently active ObjectRenderer.
*
* @member {ObjectRenderer}
*/
this.currentRenderer = this.emptyRenderer;
}
/**
* Changes the current renderer to the one given in parameter
*
* @param {ObjectRenderer} objectRenderer - The object renderer to use.
*/
setObjectRenderer(objectRenderer:ObjectRenderer) {
if (this.currentRenderer === objectRenderer) {
return;
}
this.currentRenderer.stop();
this.currentRenderer = objectRenderer;
this.currentRenderer.start();
}
/**
* This should be called if you wish to do some custom rendering
* It will basically render anything that may be batched up such as sprites
*/
flush() {
this.setObjectRenderer(this.emptyRenderer);
}
/**
* Reset the system to an empty renderer
*/
reset() {
this.setObjectRenderer(this.emptyRenderer);
}
}
import { SHAPES } from '../../const';
import { HashObject } from '../../HashObject';
import CanvasRenderer from '../CanvasRenderer';
import Graphics from '../../graphics/Graphics';
/**
* A set of functions used to handle masking.
*
*/
export default class CanvasMaskManager extends HashObject {
renderer
/**
* @param {CanvasRenderer} renderer - The canvas renderer.
*/
constructor(renderer:CanvasRenderer) {
super();
this._instanceType="CanvasMaskManager"
this.renderer = renderer;
}
/**
* This method adds it to the current stack of masks.
*
* @param {object} maskData - the maskData that will be pushed
*/
pushMask(maskData:Graphics) {
const renderer = this.renderer;
renderer.context.save();
const transform = maskData.transform.worldMatrix;
renderer.context.setTransform(
transform.a,
transform.b,
transform.c,
transform.d,
transform.tx,
transform.ty
);
this.renderGraphicsShape(maskData);
renderer.context.clip();
}
/**
* Renders a Graphics shape.
*
* @param {Graphics} graphics - The object to render.
*/
renderGraphicsShape(graphics) {
const context = this.renderer.context;
const len = graphics.graphicsData.length;
if (len === 0) {
return;
}
context.beginPath();
for (let i = 0; i < len; i++) {
const data = graphics.graphicsData[i];
const shape = data.shape;
if (data.type === SHAPES.POLY) {
const points = shape.points;
context.moveTo(points[0], points[1]);
for (let j = 1; j < points.length / 2; j++) {
context.lineTo(points[j * 2], points[(j * 2) + 1]);
}
// if the first and last point are the same close the path - much neater :)
if (points[0] === points[points.length - 2] && points[1] === points[points.length - 1]) {
context.closePath();
}
}
else if (data.type === SHAPES.RECT) {
context.rect(shape.x, shape.y, shape.width, shape.height);
context.closePath();
}
else if (data.type === SHAPES.CIRC) {
// TODO - need to be Undefined!
context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI);
context.closePath();
}
else if (data.type === SHAPES.ELIP) {
// ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
const w = shape.width * 2;
const h = shape.height * 2;
const x = shape.x - (w / 2);
const y = shape.y - (h / 2);
const kappa = 0.5522848;
const ox = (w / 2) * kappa; // control point offset horizontal
const oy = (h / 2) * kappa; // control point offset vertical
const xe = x + w; // x-end
const ye = y + h; // y-end
const xm = x + (w / 2); // x-middle
const ym = y + (h / 2); // y-middle
context.moveTo(x, ym);
context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
context.closePath();
}
else if (data.type === SHAPES.RREC) {
const rx = shape.x;
const ry = shape.y;
const width = shape.width;
const height = shape.height;
let radius = shape.radius;
const maxRadius = Math.min(width, height) / 2 | 0;
radius = radius > maxRadius ? maxRadius : radius;
context.moveTo(rx, ry + radius);
context.lineTo(rx, ry + height - radius);
context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height);
context.lineTo(rx + width - radius, ry + height);
context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius);
context.lineTo(rx + width, ry + radius);
context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry);
context.lineTo(rx + radius, ry);
context.quadraticCurveTo(rx, ry, rx, ry + radius);
context.closePath();
}
}
}
/**
* Restores the current drawing context to the state it was before the mask was applied.
*
* @param {CanvasRenderer} renderer - The renderer context to use.
*/
popMask(renderer) {
renderer.context.restore();
}
/**
* Destroys this canvas mask manager.
*
*/
destroy() {
/* empty */
}
}
import Graphics from '../../graphics/Graphics';
import { WebglRenderer } from '../WebglRenderer';
import RenderTarget from '../renderTarget/RenderTarget';
/**
* @class
*/
export default class MaskManager {
renderer: WebglRenderer;
scissor: boolean;
scissorData: any;
scissorRenderTarget: RenderTarget;
enableScissor: boolean;
alphaMaskPool: any[];
alphaMaskIndex: number;
/**
* - The renderer this manager works for.
*/
constructor(renderer: WebglRenderer) {
this.renderer = renderer
// TODO - we don't need both!
this.scissor = false;
this.scissorData = null;
this.scissorRenderTarget = null;
this.enableScissor = true;
this.alphaMaskPool = [];
this.alphaMaskIndex = 0;
}
/**
* Applies the Mask and adds it to the current filter stack.
*
* @param {DisplayObject} target - Display Object to push the mask to
* @param {Sprite|Graphics} maskData - The masking data.
*/
pushMask(target, maskData) {
// TODO the root check means scissor rect will not
// be used on render textures more info here:
// https://github.com/pixijs/js/pull/3545
// if (this.enableScissor
// && !this.scissor
// && this.renderer._activeRenderTarget.root
// && !this.renderer.stencilManager.stencilMaskStack.length
// && maskData.isFastRect()) {
// const matrix = maskData.worldMatrix;
// let rot = Math.atan2(matrix.b, matrix.a);
// // use the nearest degree!
// rot = Math.round(rot * (180 / Math.PI));
// if (rot % 90) {
// this.pushStencilMask(maskData);
// }
// else {
// this.pushScissorMask(target, maskData);
// }
// }
// else {
this.pushStencilMask(maskData);
// }
}
/**
* Removes the last mask from the mask stack and doesn't return it.
*
* @param {DisplayObject} target - Display Object to pop the mask from
* @param {Sprite|Graphics} maskData - The masking data.
*/
popMask(target, maskData) {
// if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) {
// this.popScissorMask();
// }
// else {
this.popStencilMask();
// }
}
/**
* Applies the Mask and adds it to the current filter stack.
*
* @param {Graphics} maskData - The masking data.
*/
pushStencilMask(maskData: Graphics) {
this.renderer.batchManager.flush();
this.renderer.stencilManager.pushStencil(maskData);
}
/**
* Removes the last filter from the filter stack and doesn't return it.
*
*/
popStencilMask() {
// this.renderer.currentRenderer.stop();
this.renderer.stencilManager.popStencil();
}
/**
*
* @param {DisplayObject} target - Display Object to push the mask to
* @param {Graphics} maskData - The masking data.
*/
pushScissorMask(target, maskData: Graphics) {
maskData.renderable = true;
const renderTarget = this.renderer._activeRenderTarget;
const bounds = maskData.getBounds();
bounds.fit(renderTarget.size);
maskData.renderable = false;
this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST);
this.renderer.gl.scissor(
bounds.x,
(renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y),
bounds.width,
bounds.height
);
this.scissorRenderTarget = renderTarget;
this.scissorData = maskData;
this.scissor = true;
}
/**
*
*
*/
popScissorMask() {
this.scissorRenderTarget = null;
this.scissorData = null;
this.scissor = false;
// must be scissor!
const gl = this.renderer.gl;
gl.disable(gl.SCISSOR_TEST);
}
destroy() {
this.renderer = null
}
}
import { WebglRenderer } from '../WebglRenderer';
import Graphics from '../../graphics/Graphics';
/**
* 管理模板缓存,主要用于遮罩
* @class
*/
export default class StencilManager {
renderer: WebglRenderer
stencilMaskStack: Graphics[];
/**
* @param {WebGLRenderer} renderer - The renderer this manager works for.
*/
constructor(renderer: WebglRenderer) {
this.renderer = renderer;
this.stencilMaskStack = null;
}
/**
* Changes the mask stack that is used by this manager.
*
* @param {Graphics[]} stencilMaskStack - The mask stack
*/
setMaskStack(stencilMaskStack:Graphics[]) {
this.stencilMaskStack = stencilMaskStack;
const gl = this.renderer.gl;
if (stencilMaskStack.length === 0) {
gl.disable(gl.STENCIL_TEST);
}
else {
gl.enable(gl.STENCIL_TEST);
}
}
/**
* Applies the Mask and adds it to the current stencil stack. @alvin
*
* @param {Graphics} graphics - The mask
*/
pushStencil(graphics:Graphics) {
this.renderer._activeRenderTarget.attachStencilBuffer();
const gl = this.renderer.gl;
const prevMaskCount = this.stencilMaskStack.length;
if (prevMaskCount === 0) {
gl.enable(gl.STENCIL_TEST);
}
this.stencilMaskStack.push(graphics);
// Increment the refference stencil value where the new mask overlaps with the old ones.
gl.colorMask(false, false, false, false);
gl.stencilFunc(gl.EQUAL, prevMaskCount, this._getBitwiseMask());
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
graphics.renderable = true;
graphics.renderWebGL(this.renderer);
this.renderer.batchManager.flush();
graphics.renderable = false;
// this.renderer.plugins.graphics.render(graphics);
this._useCurrent();
}
/**
* Removes the last mask from the stencil stack. @alvin
*/
popStencil() {
const gl = this.renderer.gl;
const graphics = this.stencilMaskStack.pop();
if (this.stencilMaskStack.length === 0) {
// the stack is empty!
gl.disable(gl.STENCIL_TEST);
gl.clear(gl.STENCIL_BUFFER_BIT);
gl.clearStencil(0);
}
else {
// Decrement the refference stencil value where the popped mask overlaps with the other ones
gl.colorMask(false, false, false, false);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
graphics.renderable = true;
graphics.renderWebGL(this.renderer);
this.renderer.batchManager.flush();
graphics.renderable = false;
// this.renderer.plugins.graphics.render(graphics);
this._useCurrent();
}
}
/**
* Setup renderer to use the current stencil data.
*/
_useCurrent() {
const gl = this.renderer.gl;
gl.colorMask(true, true, true, true);
gl.stencilFunc(gl.EQUAL, this.stencilMaskStack.length, this._getBitwiseMask());
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
}
/**
* Fill 1s equal to the number of acitve stencil masks.
*
* @return {number} The bitwise mask.
*/
_getBitwiseMask() {
return (1 << this.stencilMaskStack.length) - 1;
}
/**
* Destroys the mask stack.
*
*/
destroy() {
this.renderer = null;
this.stencilMaskStack = null;
}
}
import { GC_MODES } from '../../const';
import { WebglRenderer } from '../WebglRenderer';
/**
* TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged
* up with textures that are no longer being used.
*
*/
export default class TextureGarbageCollector {
renderer: WebglRenderer;
count: number;
checkCount: number;
maxIdle: number;
checkCountMax: number;
mode: number;
/**
* @param {WebGLRenderer} renderer - The renderer this manager works for.
*/
constructor(renderer: WebglRenderer) {
this.renderer = renderer;
this.count = 0;
this.checkCount = 0;
this.maxIdle = 60 * 60;
this.checkCountMax = 60 * 10;
this.mode = GC_MODES.AUTO;
}
/**
* Checks to see when the last time a texture was used
* if the texture has not been used for a specified amount of time it will be removed from the GPU
*/
update() {
this.count++;
if (this.mode === GC_MODES.MANUAL) {
return;
}
this.checkCount++;
if (this.checkCount > this.checkCountMax) {
this.checkCount = 0;
this.run();
}
}
/**
* Checks to see when the last time a texture was used
* if the texture has not been used for a specified amount of time it will be removed from the GPU
*/
run() {
const tm = this.renderer.textureManager;
const managedTextures = tm._managedTextures;
let wasRemoved = false;
for (let i = 0; i < managedTextures.length; i++) {
const texture = managedTextures[i];
// only supports non generated textures at the moment!
if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) {
tm.destroyTexture(texture, true);
managedTextures[i] = null;
wasRemoved = true;
}
}
if (wasRemoved) {
let j = 0;
for (let i = 0; i < managedTextures.length; i++) {
if (managedTextures[i] !== null) {
managedTextures[j++] = managedTextures[i];
}
}
managedTextures.length = j;
}
}
/**
* Removes all the textures within the specified displayObject and its children from the GPU
*
* @param {DisplayObject} displayObject - the displayObject to remove the textures from.
*/
unload(displayObject) {
const tm = this.renderer.textureManager;
// only destroy non generated textures
if (displayObject._texture && displayObject._texture._glRenderTargets) {
tm.destroyTexture(displayObject._texture, true);
}
for (let i = displayObject.children.length - 1; i >= 0; i--) {
this.unload(displayObject.children[i]);
}
}
}
import { GLTexture } from '../../../glCore';
import { WRAP_MODES, SCALE_MODES } from '../../const';
import RenderTarget from '../renderTarget/RenderTarget';
import { removeItems } from '../../utils';
import BaseTexture from '../../texture/BaseTexture';
import { WebglRenderer } from '../WebglRenderer';
/**
* Helper class to create a webGL Texture
*
*/
export default class TextureManager {
boundTextures;
renderer: WebglRenderer;
emptyTextures;
_nextTextureLocation: number = 0;
currentLocation: number;
gl: WebGLRenderingContext;
/**
* Track textures in the renderer so we can no longer listen to them on destruction.
*/
_managedTextures: Array<any>;
/**
* @param {WebGLRenderer} renderer - A reference to the current renderer
*/
constructor(renderer: WebglRenderer) {
this.renderer = renderer;
this.gl = renderer.gl;
this._managedTextures = [];
this.updateTexture = this.updateTexture.bind(this);
this.destroyTexture = this.destroyTexture.bind(this);
this.onContextChange = this.onContextChange.bind(this);
this.renderer.addEventListener('onContextChange', this.onContextChange);
}
onContextChange() {
const gl = this.gl;
const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
this.boundTextures = new Array(maxTextures);
this.emptyTextures = new Array(maxTextures);
// now lets fill up the textures with empty ones!
const emptyGLTexture = GLTexture.fromData(gl, null, 1, 1);
const tempObj = { _glTextures: {} };
tempObj._glTextures[this.renderer.CONTEXT_UID] = {};
for (let i = 0; i < maxTextures; i++) {
const empty = new BaseTexture();
empty._glTextures[this.renderer.CONTEXT_UID] = emptyGLTexture;
this.boundTextures[i] = tempObj;
this.emptyTextures[i] = empty;
this.bindTexture(null, i);
}
}
/**
* Binds the texture. This will return the location of the bound texture.
* It may not be the same as the one you pass in. This is due to optimisation that prevents
* needless binding of textures. For example if the texture is already bound it will return the
* current location of the texture instead of the one provided. To bypass this use force location
*
* @param {Texture} texture - the new texture
* @param {number} location - the suggested texture location
* @param {boolean} forceLocation - force the location
* @return {number} bound texture location
*/
bindTexture(texture: any, location?: number, forceLocation?: boolean): number {
texture = texture || this.emptyTextures[location];
texture = texture.baseTexture || texture;
texture.touched = this.renderer.textureGC.count;
//从原先绑定过的纹理里找
if (!forceLocation) {
// TODO - maybe look into adding boundIds.. save us the loop?
for (let i = 0; i < this.boundTextures.length; i++) {
if (this.boundTextures[i] === texture) {
return i;
}
}
if (location === undefined) {
this._nextTextureLocation++;
this._nextTextureLocation %= this.boundTextures.length;
location = this.boundTextures.length - this._nextTextureLocation - 1;
}
}
else {
location = location || 0;
}
const gl = this.gl;
const glTexture = texture._glTextures[this.renderer.CONTEXT_UID];
//考虑到时用dirty更新的方法,不然有些属性更新可能无效,mipmap等
if (!glTexture) {
// this will also bind the texture..
this.updateTexture(texture, location);
}
else {
// bind the current texture
if (this.currentLocation !== location) {
this.currentLocation = location;
gl.activeTexture(gl.TEXTURE0 + location);
}
if (this.boundTextures[location] !== texture) {
gl.bindTexture(gl.TEXTURE_2D, glTexture.texture);
}
this.boundTextures[location] = texture;
}
return location;
}
/**
* Gets a texture.
*
*/
getTexture() {
// empty
}
/**
* Updates and/or Creates a WebGL texture for the renderer's context.
*
* @param {BaseTexture|Texture} texture - the texture to update
* @param {number} location - the location the texture will be bound to.
* @return {GLTexture} The gl texture.
*/
updateTexture(texture: any, location?: number): GLTexture {
//如果是事件返回的
if(texture.instanceType==="Event"){
//选择它的target
texture=texture.target.baseTexture||texture.target
}else{
texture = texture.baseTexture || texture;
}
const gl = this.gl;
const isRenderTexture = !!texture["_glRenderTargets"];
if (!texture.hasLoaded) {
return null;
}
const boundTextures = this.boundTextures;
// if the location is undefined then this may have been called by n event.
// this being the case the texture may already be bound to a slot. As a texture can only be bound once
// we need to find its current location if it exists.
if (location === undefined) {
location = 0;
// TODO maybe we can use texture bound ids later on...
// check if texture is already bound..
for (let i = 0; i < boundTextures.length; ++i) {
if (boundTextures[i] === texture) {
location = i;
break;
}
}
}
boundTextures[location] = texture;
if (this.currentLocation !== location) {
this.currentLocation = location;
gl.activeTexture(gl.TEXTURE0 + location);
}
let glTexture: GLTexture = texture._glTextures[this.renderer.CONTEXT_UID];
if (!glTexture) {
if (isRenderTexture) {
const renderTarget = new RenderTarget(
this.gl,
texture.width,
texture.height,
texture.scaleMode,
false
);
renderTarget.resize(texture.width, texture.height);
texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget;
glTexture = renderTarget.texture;
}
else {
glTexture = new GLTexture(this.gl, null, null, null, null);
//之前已经active过了,upload里也有bind,这里也许不用执行
// glTexture.bind(location);
glTexture.premultiplyAlpha = true;
glTexture.upload(texture.source);
}
texture._glTextures[this.renderer.CONTEXT_UID] = glTexture;
texture.addEventListener('update', this.updateTexture);
texture.addEventListener('dispose', this.destroyTexture);
this._managedTextures.push(texture);
if (texture.isPowerOfTwo) {
if (texture.mipmap) {
glTexture.enableMipmap();
}
if (texture.wrapMode === WRAP_MODES.CLAMP) {
glTexture.enableWrapClamp();
}
else if (texture.wrapMode === WRAP_MODES.REPEAT) {
glTexture.enableWrapRepeat();
}
else {
glTexture.enableWrapMirrorRepeat();
}
}
else {
glTexture.enableWrapClamp();
}
if (texture.scaleMode === SCALE_MODES.NEAREST) {
glTexture.enableNearestScaling();
}
else {
glTexture.enableLinearScaling();
}
}
// the texture already exists so we only need to update it..
else if (isRenderTexture) {
texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height);
}
else {
glTexture.upload(texture.source);
}
return glTexture;
}
/**
* unbinds the texture ...
*
* @param {Texture} texture - the texture to unbind
*/
unbindTexture(texture) {
const gl = this.gl;
texture = texture.baseTexture || texture;
for (let i = 0; i < this.boundTextures.length; i++) {
if (this.boundTextures[i] === texture) {
this.boundTextures[i] = this.emptyTextures[i];
if (this.currentLocation !== i) {
gl.activeTexture(gl.TEXTURE0 + i);
this.currentLocation = i;
}
gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.renderer.CONTEXT_UID].texture);
}
}
}
/**
* Deletes the texture from WebGL
*
* @param {BaseTexture|Texture} texture - the texture to destroy
* @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager.
*/
destroyTexture(texture, skipRemove) {
texture = texture.baseTexture || texture;
if (!texture.hasLoaded) {
return;
}
const uid = this.renderer.CONTEXT_UID;
const glTextures = texture._glTextures;
const glRenderTargets = texture._glRenderTargets;
if (glTextures[uid]) {
this.unbindTexture(texture);
glTextures[uid].destroy();
texture.removeEventListener('update', this.updateTexture);
texture.removeEventListener('dispose', this.destroyTexture);
delete glTextures[uid];
if (!skipRemove) {
const i = this._managedTextures.indexOf(texture);
if (i !== -1) {
removeItems(this._managedTextures, i, 1);
}
}
}
if (glRenderTargets && glRenderTargets[uid]) {
glRenderTargets[uid].destroy();
delete glRenderTargets[uid];
}
}
/**
* Deletes all the textures from WebGL
*/
removeAll() {
// empty all the old gl textures as they are useless now
for (let i = 0; i < this._managedTextures.length; ++i) {
const texture = this._managedTextures[i];
if (texture._glTextures[this.renderer.CONTEXT_UID]) {
delete texture._glTextures[this.renderer.CONTEXT_UID];
}
}
}
/**
* Destroys this manager and removes all its textures
*/
destroy() {
// destroy managed textures
for (let i = 0; i < this._managedTextures.length; ++i) {
const texture = this._managedTextures[i];
this.destroyTexture(texture, true);
texture.removeEventListener('update', this.updateTexture);
texture.removeEventListener('dispose', this.destroyTexture);
}
this._managedTextures = null;
}
}
import BatchDrawCall from '../webgl/BatchDrawCall';
import { osType } from "../../const"
// import State from '../state/State';
import ObjectRenderer from '../webgl/ObjectRenderer';
import { checkMaxIfStatementsInShader } from '../../../glCore/checkMaxIfStatementsInShader';
// import { settings } from '@pixi/settings';
// import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils';
import { BatchBuffer } from '../webgl/BatchBuffer';
import { generateMultiTextureShader } from '../webgl/generateMultiTextureShader';
import { WebglRenderer } from '../WebglRenderer';
import { GLShader, GLBuffer, VertexArrayObject } from '../../../glCore';
import { nextPow2, log2, premultiplyTint } from '../../utils';
import { DisplayObject } from '../../display/DisplayObject';
let TICK = 0;
/**
* 批处理核心渲染插件
*
* @class
* @extends ObjectRenderer
*/
export default class BatchRenderer extends ObjectRenderer {
vertSize: number;
vertByteSize: number;
size: number;
/**
* 当前顶点数量
*/
currentSize: number;
/**
* 当前索引数量
*/
currentIndexSize: number;
/**
* 顶点数据
*/
aBuffers: {};
/**
* 索引数据
*/
iBuffers: {};
/**
* The default shaders that is used if a sprite doesn't have a more specific one.
* there is a shader for each number of textures that can be rendered.
* These shaders will also be generated on the fly as required.
*/
shader: GLShader;
/**
* 当前累计的绘制对象数量
*/
currentIndex: number;
groups: BatchDrawCall[];
/**
* 绘制对象数组
*/
elements: any[];
vaos: VertexArrayObject[];
vaoMax: number;
vertexCount: number;
MAX_TEXTURES: number;
vertexBuffers: GLBuffer[];
indexBuffers: GLBuffer[];
constructor(renderer: WebglRenderer) {
super(renderer);
/**
* 每个点
* Number of values sent in the vertex buffer.
* aVertexPosition(2), aTextureCoord(2), aColor(1), aTextureId(1) = 6
*
* @member {number}
*/
this.vertSize = 6;
/**
* The size of the vertex information in bytes.
*
* @member {number}
*/
this.vertByteSize = this.vertSize * 4;
/**
* The number of images in the SpriteRenderer before it flushes.
*
* @member {number}
*/
this.size = 2000 * 4;// settings.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop
this.currentSize = 0;
this.currentIndexSize = 0;
// the total number of bytes in our batch
// let numVerts = this.size * 4 * this.vertByteSize;
this.aBuffers = {};
this.iBuffers = {};
this.shader = null;
this.currentIndex = 0;
this.groups = [];
for (let k = 0; k < this.size / 4; k++) {
this.groups[k] = new BatchDrawCall();
}
this.elements = [];
this.vertexBuffers = [];
this.indexBuffers = [];
this.vaos = [];
this.vaoMax = 2;
this.vertexCount = 0;
this.onPreRender = this.onPreRender.bind(this);
this.renderer.addEventListener('onPreRender', this.onPreRender);
// this.state = State.for2d();深度检测去掉
}
/**
* Sets up the renderer context and necessary buffers.
*/
onContextChange() {
const gl = this.renderer.gl;
if (false /*传统方式*/) {
this.MAX_TEXTURES = 1;
}
else {
// step 1: first check max textures the GPU can handle.
this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), 16);
// step 2: check the maximum number of if statements the shader can have too..
this.MAX_TEXTURES = checkMaxIfStatementsInShader(this.MAX_TEXTURES, gl);
}
// generate generateMultiTextureProgram, may be a better move?
this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES);
// we use the second shader as the first one depending on your browser may omit aTextureId
// as it is not used by the shader so is optimized out.
this.renderer.bindVao(null);
const attrs = this.shader.attributes;
for (let i = 0; i < this.vaoMax; i++) {
/* eslint-disable max-len */
const vertexBuffer = this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);
const indexBuffer = this.indexBuffers[i] = GLBuffer.createIndexBuffer(gl, null, gl.STREAM_DRAW);
// build the vao object that will render..
const vao = this.renderer.createVao()
.addIndex(indexBuffer)
.addAttribute(vertexBuffer, attrs.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0)
// .addAttribute(vertexBuffer, attrs.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4)
// .addAttribute(vertexBuffer, attrs.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4);
.addAttribute(vertexBuffer, attrs.aTextureCoord, gl.FLOAT, true, this.vertByteSize, 2 * 4)
.addAttribute(vertexBuffer, attrs.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 4 * 4)
.addAttribute(vertexBuffer, attrs.aTextureId, gl.FLOAT, false, this.vertByteSize, 5 * 4);
// if (attrs.aTextureId)
// {
// vao.addAttribute(vertexBuffer, attrs.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4);
// }
this.vaos[i] = vao;
}
}
/**
* Called before the renderer starts rendering.
*
*/
onPreRender() {
this.vertexCount = 0;
}
/**
* Renders the sprite object.
* element必须的属性
* _texture 里面有.BaseTexture
* vertexData
* indices
* worldAlpha(如果是gra,则传乘过fillAlpha的)
* color?
* uvs
* the sprite to render when using this spritebatch
*/
render(element) {
if (!element._texture || !element._texture.valid) {
return;
}
if (this.currentSize + (element.vertexData.length / 2) > this.size) {
this.flush();
}
this.elements[this.currentIndex++] = element;
this.currentSize += element.vertexData.length / 2;
this.currentIndexSize += element.indices.length;
}
/**
* 获得索引buffer
* @param size
*/
getIndexBuffer(size) {
// 12 indices is enough for 2 quads
const roundedP2 = nextPow2(Math.ceil(size / 12));
const roundedSizeIndex = log2(roundedP2);
const roundedSize = roundedP2 * 12;
// if (this.iBuffers.length <= roundedSizeIndex) {
// this.iBuffers.length = roundedSizeIndex + 1;
// }
let buffer = this.iBuffers[roundedSizeIndex];
if (!buffer) {
this.iBuffers[roundedSizeIndex] = buffer = new Uint16Array(roundedSize);
}
return buffer;
}
/**
* 获取相应的顶点数据buffer
* @param size
*/
getAttributeBuffer(size: number) {
// 8 vertices is enough for 2 quads
const roundedP2 = nextPow2(Math.ceil(size / 8));
const roundedSizeIndex = log2(roundedP2);
const roundedSize = roundedP2 * 8;
// if (this.aBuffers.length <= roundedSizeIndex) {
// this.iBuffers.length = roundedSizeIndex + 1;
// }
let buffer = this.aBuffers[roundedSize];
if (!buffer) {
this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertByteSize);
}
return buffer;
}
/**
* Renders the content and empties the current batch.
*
*/
flush() {
if (this.currentSize === 0) {
return;
}
const gl = this.renderer.gl;
const MAX_TEXTURES = this.MAX_TEXTURES;
const buffer = this.getAttributeBuffer(this.currentSize);
const indexBuffer = this.getIndexBuffer(this.currentIndexSize);
const elements = this.elements;
const groups = this.groups;
const float32View = buffer.float32View;
const uint32View = buffer.uint32View;
const touch = this.renderer.textureGC.count;
let index = 0;
let indexCount = 0;
let nextTexture;
let currentTexture;
let groupCount = 1;
let textureCount = 0;
let currentGroup = groups[0];
//let blendMode = -1;// premultiplyBlendMode[elements[0]._texture.baseTexture.premultiplyAlpha ? 0 : ][elements[0].blendMode];
currentGroup.textureCount = 0;
currentGroup.start = 0;
// currentGroup.blend = blendMode;
TICK++;
let i;
for (i = 0; i < this.currentIndex; ++i) {
// upload the sprite elements...
// they have all ready been calculated so we just need to push them into the buffer.
const sprite = elements[i];
elements[i] = null;
nextTexture = sprite._texture.baseTexture;
// const spriteBlendMode = premultiplyBlendMode[nextTexture.premultiplyAlpha ? 1 : 0][sprite.blendMode];
// if (blendMode !== spriteBlendMode) {
// blendMode = spriteBlendMode;
// // force the batch to break!
// currentTexture = null;
// textureCount = MAX_TEXTURES;
// TICK++;
// }
if (currentTexture !== nextTexture) {
currentTexture = nextTexture;
if (nextTexture._enabled !== TICK) {
if (textureCount === MAX_TEXTURES) {
TICK++;
textureCount = 0;
currentGroup.size = indexCount - currentGroup.start;
currentGroup = groups[groupCount++];
currentGroup.textureCount = 0;
// currentGroup.blend = blendMode;
currentGroup.start = indexCount;
}
nextTexture.touched = touch;
nextTexture._enabled = TICK;
nextTexture._id = textureCount;
currentGroup.textures[currentGroup.textureCount++] = nextTexture;
textureCount++;
}
}
// argb, nextTexture._id, float32View, uint32View, indexBuffer, index, indexCount);
this.packGeometry(sprite, float32View, uint32View, indexBuffer, index, indexCount);
// push a graphics..
index += (sprite.vertexData.length / 2) * this.vertSize;
indexCount += sprite.indices.length;
}
currentGroup.size = indexCount - currentGroup.start;
// this.indexBuffer.update();
if (osType == "ios") {
//可能有一帧,在多个地方执行flush
// this is still needed for IOS performance..
// it really does not like uploading to the same buffer in a single frame!
if (this.vaoMax <= this.vertexCount) {
this.vaoMax++;
const attrs = this.shader.attributes;
/* eslint-disable max-len */
const vertexBuffer = this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);
const indexBufferAdd = this.indexBuffers[this.vertexCount] = GLBuffer.createIndexBuffer(gl, null, gl.STREAM_DRAW);
// build the vao object that will render..
const vao = this.renderer.createVao()
.addIndex(indexBufferAdd)
.addAttribute(vertexBuffer, attrs.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0)
.addAttribute(vertexBuffer, attrs.aTextureCoord, gl.FLOAT, true, this.vertByteSize, 2 * 4)
.addAttribute(vertexBuffer, attrs.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 4 * 4)
.addAttribute(vertexBuffer, attrs.aTextureId, gl.FLOAT, false, this.vertByteSize, 5 * 4);
this.vaos[this.vertexCount] = vao;
console.log(this.vertexCount)
}
this.renderer.bindVao(this.vaos[this.vertexCount]);
this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false);
this.indexBuffers[this.vertexCount].upload(indexBuffer, 0, false);
this.vertexCount++;
}
else {
// lets use the faster option, always use buffer number 0
this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true);
this.indexBuffers[this.vertexCount].upload(indexBuffer, 0, true);
}
// this.renderer.state.set(this.state);
const textureManager = this.renderer.textureManager;
// const stateSystem = this.renderer.state;
// e.log(groupCount);
// / render the groups..
for (i = 0; i < groupCount; i++) {
const group = groups[i];
const groupTextureCount = group.textureCount;
for (let j = 0; j < groupTextureCount; j++) {
if (group.textures[j] != textureManager.boundTextures[j]) {
//location已经和sampleId固定了,就算原先boundsTextures有,也要重新绑定位置
// console.log("bindTexture")
textureManager.bindTexture(group.textures[j], j, true);
}
group.textures[j] = null;
}
// this.state.blendMode = group.blend;
// this.state.blend = true;
// this.renderer.state.setState(this.state);
// set the blend mode..
// stateSystem.setBlendMode(group.blend);
gl.drawElements(group.type, group.size, gl.UNSIGNED_SHORT, group.start * 2);
}
// reset elements for the next flush
this.currentIndex = 0;
this.currentSize = 0;
this.currentIndexSize = 0;
}
packGeometry(element, float32View, uint32View, indexBuffer, index, indexCount) {
const p = index / this.vertSize;// float32View.length / 6 / 2;
const uvs = element.uvs;
const indicies = element.indices;// geometry.getIndex().data;// indicies;
const vertexData = element.vertexData;
const textureId = element._texture.baseTexture._id;
//颜色暂时计算,到时候修改
var _tintRGB = 16777215
if (element.color!==undefined) {
// var num = parseInt(sprite.borderColor.slice(1), 16);
var num = element.color;
_tintRGB = (num >> 16) + (num & 0xff00) + ((num & 0xff) << 16);
}
const alpha = Math.min(element.worldAlpha, 1.0);
// we dont call extra function if alpha is 1.0, that's faster
const argb = alpha < 1.0 /*&& nextTexture.premultipliedAlpha*/ ? premultiplyTint(/*sprite._tintRGB*/_tintRGB, alpha)
: /*sprite._tintRGB*/_tintRGB + (alpha * 255 << 24);
// lets not worry about tint! for now..
for (let i = 0; i < vertexData.length; i += 2) {
float32View[index++] = vertexData[i];
float32View[index++] = vertexData[i + 1];
float32View[index++] = uvs[i];
float32View[index++] = uvs[i + 1];
uint32View[index++] = argb;
float32View[index++] = textureId;
}
for (let i = 0; i < indicies.length; i++) {
indexBuffer[indexCount++] = p + indicies[i];
}
}
/*
renderQuad(vertexData, uvs, argb, textureId, float32View, uint32View, indexBuffer, index, indexCount)
{
const p = index / 6;
float32View[index++] = vertexData[0];
float32View[index++] = vertexData[1];
float32View[index++] = uvs.x0;
float32View[index++] = uvs.y0;
uint32View[index++] = argb;
float32View[index++] = textureId;
float32View[index++] = vertexData[2];
float32View[index++] = vertexData[3];
float32View[index++] = uvs.x1;
float32View[index++] = uvs.y1;
uint32View[index++] = argb;
float32View[index++] = textureId;
float32View[index++] = vertexData[4];
float32View[index++] = vertexData[5];
float32View[index++] = uvs.x2;
float32View[index++] = uvs.y2;
uint32View[index++] = argb;
float32View[index++] = textureId;
float32View[index++] = vertexData[6];
float32View[index++] = vertexData[7];
float32View[index++] = uvs.x3;
float32View[index++] = uvs.y3;
uint32View[index++] = argb;
float32View[index++] = textureId;
indexBuffer[indexCount++] = p + 0;
indexBuffer[indexCount++] = p + 1;
indexBuffer[indexCount++] = p + 2;
indexBuffer[indexCount++] = p + 0;
indexBuffer[indexCount++] = p + 2;
indexBuffer[indexCount++] = p + 3;
}
*/
/**
* Starts a new sprite batch.
*/
start() {
//暂时不考虑2d专门的状态机属性
// this.renderer.state.setState(this.state);
this.renderer.bindShader(this.shader);
if (osType != "ios") {
this.renderer.bindVao(this.vaos[this.vertexCount]);
this.vertexBuffers[this.vertexCount].bind();
this.indexBuffers[this.vertexCount].bind();
}
}
/**
* Stops and flushes the current batch.
*
*/
stop() {
this.flush();
}
/**
* Destroys the SpriteRenderer.
*
*/
destroy() {
for (let i = 0; i < this.vaoMax; i++) {
// if (this.vertexBuffers[i])
// {
// this.vertexBuffers[i].destroy();
// }
if (this.vaos[i]) {
this.vaos[i].destroy();
}
}
this.renderer.removeEventListener('onPreRender', this.onPreRender);
if (this.shader) {
this.shader.destroy();
this.shader = null;
}
this.vaos = null;
super.destroy();
}
}
import CanvasRenderer from '../CanvasRenderer';
import { SHAPES } from '../../const';
import Graphics from '../../graphics/Graphics';
/**
* Renderer dedicated to drawing and batching graphics objects.
*
* @class
* @private
*/
export default class CanvasGraphicsRenderer {
renderer: CanvasRenderer;
/**
* @param {CanvasRenderer} renderer - The current renderer.
*/
constructor(renderer: CanvasRenderer) {
this.renderer = renderer;
}
/**
* Renders a Graphics object to a canvas.
*
* @param {Graphics} graphics - the actual graphics object to render
*/
render(graphics: Graphics) {
const renderer = this.renderer;
const context = renderer.context;
const worldAlpha = graphics.worldAlpha;
const transform = graphics.transform.worldMatrix;
context.setTransform(
transform.a,
transform.b,
transform.c,
transform.d,
transform.tx,
transform.ty
);
// update tint if graphics was dirty,计算混色,暂时不用
// if (graphics.canvasTintDirty !== graphics.dirty
// || graphics._prevTint !== graphics.tint) {
// this.updateGraphicsTint(graphics);
// }
// renderer.setBlendMode(graphics.blendMode);
for (let i = 0; i < graphics.graphicsData.length; i++) {
const data = graphics.graphicsData[i];
const shape = data.shape;
// const fillColor = data._fillTint;
// const lineColor = data._lineTint;
const fillColor = data.fillColor;
const lineColor = data.lineColor;
context.lineWidth = data.lineWidth;
if (data.type === SHAPES.POLY) {
context.beginPath();
this.renderPolygon(shape["points"], shape["closed"], context);
for (let j = 0; j < data.holes.length; j++) {
this.renderPolygon(data.holes[j].points, true, context);
}
if (data.fill) {
context.globalAlpha = data.fillAlpha * worldAlpha;
context.fillStyle = `#${(`00000${(fillColor | 0).toString(16)}`).substr(-6)}`;
context.fill();
}
if (data.lineWidth) {
context.globalAlpha = data.lineAlpha * worldAlpha;
context.strokeStyle = `#${(`00000${(lineColor | 0).toString(16)}`).substr(-6)}`;
context.stroke();
}
}
else if (data.type === SHAPES.RECT) {
if (data.fillColor || data.fillColor === 0) {
context.globalAlpha = data.fillAlpha * worldAlpha;
context.fillStyle = `#${(`00000${(fillColor | 0).toString(16)}`).substr(-6)}`;
context.fillRect(shape["x"], shape["y"], shape["width"], shape["height"]);
}
if (data.lineWidth) {
context.globalAlpha = data.lineAlpha * worldAlpha;
context.strokeStyle = `#${(`00000${(lineColor | 0).toString(16)}`).substr(-6)}`;
context.strokeRect(shape["x"], shape["y"], shape["width"], shape["height"]);
}
}
else if (data.type === SHAPES.CIRC) {
// TODO - need to be Undefined!
context.beginPath();
context.arc(shape["x"], shape["y"], shape["radius"], 0, 2 * Math.PI);
context.closePath();
if (data.fill) {
context.globalAlpha = data.fillAlpha * worldAlpha;
context.fillStyle = `#${(`00000${(fillColor | 0).toString(16)}`).substr(-6)}`;
context.fill();
}
if (data.lineWidth) {
context.globalAlpha = data.lineAlpha * worldAlpha;
context.strokeStyle = `#${(`00000${(lineColor | 0).toString(16)}`).substr(-6)}`;
context.stroke();
}
}
else if (data.type === SHAPES.ELIP) {
// ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
const w = shape["width"] * 2;
const h = shape["height"] * 2;
const x = shape["x"] - (w / 2);
const y = shape["y"] - (h / 2);
context.beginPath();
const kappa = 0.5522848;
const ox = (w / 2) * kappa; // control point offset horizontal
const oy = (h / 2) * kappa; // control point offset vertical
const xe = x + w; // x-end
const ye = y + h; // y-end
const xm = x + (w / 2); // x-middle
const ym = y + (h / 2); // y-middle
context.moveTo(x, ym);
context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
context.closePath();
if (data.fill) {
context.globalAlpha = data.fillAlpha * worldAlpha;
context.fillStyle = `#${(`00000${(fillColor | 0).toString(16)}`).substr(-6)}`;
context.fill();
}
if (data.lineWidth) {
context.globalAlpha = data.lineAlpha * worldAlpha;
context.strokeStyle = `#${(`00000${(lineColor | 0).toString(16)}`).substr(-6)}`;
context.stroke();
}
}
else if (data.type === SHAPES.RREC) {
const rx = shape["x"];
const ry = shape["y"];
const width = shape["width"];
const height = shape["height"];
let radius = shape["radius"];
const maxRadius = Math.min(width, height) / 2 | 0;
radius = radius > maxRadius ? maxRadius : radius;
context.beginPath();
context.moveTo(rx, ry + radius);
context.lineTo(rx, ry + height - radius);
context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height);
context.lineTo(rx + width - radius, ry + height);
context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius);
context.lineTo(rx + width, ry + radius);
context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry);
context.lineTo(rx + radius, ry);
context.quadraticCurveTo(rx, ry, rx, ry + radius);
context.closePath();
if (data.fillColor || data.fillColor === 0) {
context.globalAlpha = data.fillAlpha * worldAlpha;
context.fillStyle = `#${(`00000${(fillColor | 0).toString(16)}`).substr(-6)}`;
context.fill();
}
if (data.lineWidth) {
context.globalAlpha = data.lineAlpha * worldAlpha;
context.strokeStyle = `#${(`00000${(lineColor | 0).toString(16)}`).substr(-6)}`;
context.stroke();
}
}
}
}
/**
* Renders a polygon.
*
* @param {number[]} points - The points to render,数组,没两个数字一组坐标
* @param {boolean} close - Should the polygon be closed
* @param {CanvasRenderingContext2D} context - The rendering context to use
*/
renderPolygon(points:number[], close:boolean, context:CanvasRenderingContext2D) {
context.moveTo(points[0], points[1]);
for (let j = 1; j < points.length / 2; ++j) {
context.lineTo(points[j * 2], points[(j * 2) + 1]);
}
if (close) {
context.closePath();
}
}
/**
* destroy graphics object
*
*/
destroy() {
this.renderer = null;
}
}
\ No newline at end of file
import CanvasRenderer from '../CanvasRenderer';
import { SCALE_MODES } from '../../const';
import { Matrix } from '../../math';
import Sprite from '../../display/Sprite';
import { DisplayObject } from '../../display/DisplayObject';
import Graphics from '../../graphics/Graphics';
const canvasRenderWorldTransform = new Matrix();
/**
* Renderer dedicated to drawing and batching sprites.
*
* @class
* @private
*/
export default class CanvasSpriteRenderer {
renderer: CanvasRenderer;
/**
* @param {CanvasRenderer} renderer -The renderer sprite this batch works for.
*/
constructor(renderer: CanvasRenderer) {
this.renderer = renderer;
}
/**
* Renders the sprite object.
*
* @param {Sprite} sprite - the sprite to render when using this spritebatch
*/
render(sprite: any) {
const texture = sprite._texture;
const renderer = this.renderer;
const width = texture._frame.width;
const height = texture._frame.height;
let wt = sprite.transform.worldMatrix;
let dx = 0;
let dy = 0;
if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) {
return;
}
// renderer.setBlendMode(sprite.blendMode);
// Ignore null sources
if (texture.valid) {
renderer.context.globalAlpha = sprite.worldAlpha;
renderer.context.setTransform(
wt.a,
wt.b,
wt.c,
wt.d,
wt.tx,
wt.ty
);
// If smoothingEnabled is supported and we need to change the smoothing property for sprite texture
const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR;
if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) {
// 如果遇到性能问题,先禁掉
renderer.context[renderer.smoothProperty] = smoothingEnabled;
}
if (sprite._anchorTexture) {
if (texture.trim) {
dx = (texture.trim.width / 2) + texture.trim.x - (sprite._anchorTexture.x * texture.orig.width);
dy = (texture.trim.height / 2) + texture.trim.y - (sprite._anchorTexture.y * texture.orig.height);
}
else {
dx = (0.5 - sprite._anchorTexture.x) * texture.orig.width;
dy = (0.5 - sprite._anchorTexture.y) * texture.orig.height;
}
dx -= width / 2;
dy -= height / 2;
} else {
dx = sprite["offsetX"] || 0;
dy = sprite["offsetY"] || 0
}
//考虑是否分开,
renderer.context.drawImage(
texture.baseTexture.source,
texture._frame.x,
texture._frame.y,
width,
height,
dx,
dy,
width,
height
);
}
}
/**
* destroy the sprite object.
*
*/
destroy() {
this.renderer = null;
}
}
\ No newline at end of file
/**
* Creates a Canvas element of the given size.
* 其实就是一个离屏canvas,webgl模式不需要建canvas,因为可以用帧缓存
*/
export default class CanvasRenderTarget {
/**
* The Canvas object that belongs to this CanvasRenderTarget.
*/
canvas: HTMLCanvasElement;
/**
* A CanvasRenderingContext2D object representing a two-dimensional rendering context.
*/
context: CanvasRenderingContext2D;
/**
* @param {number} width - the width for the newly created canvas
* @param {number} height - the height for the newly created canvas
*/
constructor(width: number, height: number) {
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
this.resize(width, height);
}
/**
* Clears the canvas that was created by the CanvasRenderTarget class.
*
* @private
*/
clear() {
this.context.setTransform(1, 0, 0, 1, 0, 0);
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
/**
* Resizes the canvas to the specified width and height.
*
* @param {number} width - the new width of the canvas
* @param {number} height - the new height of the canvas
*/
resize(width: number, height: number): void {
this.canvas.width = width;
this.canvas.height = height;
}
/**
* Destroys this canvas.
*
*/
destroy() {
this.context = null;
this.canvas = null;
}
/**
* The width of the canvas buffer in pixels.
*
* @member {number}
*/
get width(): number {
return this.canvas.width;
}
set width(val: number) {
this.canvas.width = val;
}
/**
* The height of the canvas buffer in pixels.
*
* @member {number}
*/
get height(): number {
return this.canvas.height;
}
set height(val: number) {
this.canvas.height = val;
}
}
import { Rectangle, Matrix } from '../../math';
import { SCALE_MODES } from '../../const';
// import settings from '../../../settings';
import { GLFramebuffer, GLTexture, GLBuffer } from '../../../glCore';
import Graphics from '../../graphics/Graphics';
export default class RenderTarget {
/**
* The current WebGL drawing context.
*
*/
gl: WebGLRenderingContext;
/**
* A frame buffer
*/
frameBuffer: GLFramebuffer;
/**
* The texture
*/
texture: GLTexture;
/**
* The background colour of this render target, as an array of [r,g,b,a] values
*
*/
clearColor: number[];
/**
* The size of the object as a rectangle
*/
size: Rectangle;
/**
* The projection matrix
*/
projectionMatrix: Matrix;
/**
* The object's transform
*/
transform: Matrix;
/**
* The frame.
*
*/
frame: Rectangle;
defaultFrame: Rectangle;
destinationFrame: any;
sourceFrame: any;
/**
* The stencil buffer stores masking data for the render target
*
*/
stencilBuffer: GLBuffer;
/**
* The data structure for the stencil masks
*/
stencilMaskStack: Graphics[];
/**
* The scale mode.
*
*/
scaleMode: number;
/**
* Whether this object is the root element or not
*/
root: boolean;
/**
* @param {WebGLRenderingContext} gl - The current WebGL drawing context
* @param {number} [width=0] - the horizontal range of the filter
* @param {number} [height=0] - the vertical range of the filter
* @param {number} [scaleMode=SCALE_MODES.LINEAR] - See {@link SCALE_MODES} for possible values
* @param {boolean} [root=false] - Whether this object is the root element or not
*/
constructor(
gl: WebGLRenderingContext,
width: number = 0,
height: number = 0,
scaleMode: number = SCALE_MODES.LINEAR,
root: boolean = false
) {
this.gl = gl;
// next time to create a frame buffer and texture
this.frameBuffer = null;
this.texture = null;
this.clearColor = [0, 0, 0, 0];
this.size = new Rectangle(0, 0, 1, 1);
this.projectionMatrix = new Matrix();
this.transform = null;
this.frame = null;
this.defaultFrame = new Rectangle();
this.destinationFrame = null;
this.sourceFrame = null;
this.stencilBuffer = null;
this.stencilMaskStack = [];
this.scaleMode = scaleMode;
this.root = root;
if (!this.root) {
this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100);
if (this.scaleMode === SCALE_MODES.NEAREST) {
this.frameBuffer.texture.enableNearestScaling();
}
else {
this.frameBuffer.texture.enableLinearScaling();
}
/*
A frame buffer needs a target to render to..
create a texture and bind it attach it to the framebuffer..
*/
// this is used by the base texture
this.texture = this.frameBuffer.texture;
}
else {
// make it a null framebuffer..
this.frameBuffer = new GLFramebuffer(gl, 100, 100);
//帧缓存为空,gltexture也是空
this.frameBuffer.framebuffer = null;
}
this.setFrame();
this.resize(width, height);
}
/**
* Clears the filter texture.
*
* @param {number[]} [clearColor=this.clearColor] - Array of [r,g,b,a] to clear the framebuffer
*/
clear(clearColor?:number[]) {
const cc = clearColor || this.clearColor;
this.frameBuffer.clear(cc[0], cc[1], cc[2], cc[3]);// r,g,b,a);
}
/**
* Binds the stencil buffer.
*
*/
attachStencilBuffer() {
// TODO check if stencil is done?
/**
* The stencil buffer is used for masking in
* lets create one and then add attach it to the framebuffer..
*/
if (!this.root) {
this.frameBuffer.enableStencil();
}
}
/**
* Sets the frame of the render target.
*
* @param {Rectangle} destinationFrame - The destination frame.
* @param {Rectangle} sourceFrame - The source frame.
*/
setFrame(destinationFrame?: Rectangle, sourceFrame?: Rectangle) {
this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame;
this.sourceFrame = sourceFrame || this.sourceFrame || this.destinationFrame;
}
/**
* Binds the buffers and initialises the viewport.
*
*/
activate() {
// TOOD refactor usage of frame..
const gl = this.gl;
// make sure the texture is unbound!
this.frameBuffer.bind();
this.calculateProjection(this.destinationFrame, this.sourceFrame);
if (this.transform) {
this.projectionMatrix.append(this.transform);
}
// TODO add a check as them may be the same!
if (this.destinationFrame !== this.sourceFrame) {
gl.enable(gl.SCISSOR_TEST);
gl.scissor(
this.destinationFrame.x | 0,
this.destinationFrame.y | 0,
this.destinationFrame.width | 0,
this.destinationFrame.height | 0
);
}
else {
gl.disable(gl.SCISSOR_TEST);
}
// TODO - does not need to be updated all the time??
gl.viewport(
this.destinationFrame.x | 0,
this.destinationFrame.y | 0,
this.destinationFrame.width | 0,
this.destinationFrame.height | 0
);
}
/**
* Updates the projection matrix based on a projection frame (which is a rectangle)
*
* @param {Rectangle} destinationFrame - The destination frame.
* @param {Rectangle} sourceFrame - The source frame.
*/
calculateProjection(destinationFrame, sourceFrame?) {
const pm = this.projectionMatrix;
console.log(destinationFrame)
sourceFrame = sourceFrame || destinationFrame;
pm.identity();
// TODO: make dest scale source
if (!this.root) {
pm.a = 1 / destinationFrame.width * 2;
pm.d = 1 / destinationFrame.height * 2;
pm.tx = -1 - (sourceFrame.x * pm.a);
pm.ty = -1 - (sourceFrame.y * pm.d);
}
else {
pm.a = 1 / destinationFrame.width * 2;
pm.d = -1 / destinationFrame.height * 2;
pm.tx = -1 - (sourceFrame.x * pm.a);
pm.ty = 1 - (sourceFrame.y * pm.d);
}
}
/**
* Resizes the texture to the specified width and height
*
* @param {number} width - the new width of the texture
* @param {number} height - the new height of the texture
*/
resize(width, height) {
width = width | 0;
height = height | 0;
if (this.size.width === width && this.size.height === height) {
return;
}
this.size.width = width;
this.size.height = height;
this.defaultFrame.width = width;
this.defaultFrame.height = height;
this.frameBuffer.resize(width, height);
const projectionFrame = this.frame || this.size;
this.calculateProjection(projectionFrame);
}
/**
* Destroys the render target.
*
*/
destroy() {
this.frameBuffer.destroy();
this.frameBuffer = null;
this.texture = null;
}
}
/**
* 批处理用buffer数据
*/
export class BatchBuffer {
/**
* 顶点数据,类型化数组
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
*/
vertices: ArrayBuffer;
/**
* 顶点的位置信息视图,浮点数
* View on the vertices as a Float32Array for positions
*/
float32View: Float32Array;
/**
* uv及颜色值的信息视图,整数
* View on the vertices as a Uint32Array for uvs
*/
uint32View: Uint32Array;
/**
* positions,uvs,colors暂时不用做单独记录
*/
positions: any;
uvs: any;
colors: any;
/**
* @param {number} size - The size of the buffer in bytes.
*/
constructor(size: number) {
this.vertices = new ArrayBuffer(size);
this.float32View = new Float32Array(this.vertices);
this.uint32View = new Uint32Array(this.vertices);
}
/**
* Destroys the buffer.
*
*/
public destroy() {
this.vertices = null;
this.positions = null;
this.uvs = null;
this.colors = null;
}
}
import { DRAW_MODES } from "../../const";
import BaseTexture from "../../texture/BaseTexture";
/**
*
* @class
*/
export default class BatchDrawCall {
/**
* 存储基础图片
*/
textures: BaseTexture[];
/**
* 图片总数
*/
textureCount: number;
/**
* 索引起始
*/
start: number;
/**
* 总数
*/
size: number;
/**
* 绘制类型
*/
type: number;
constructor() {
this.textures = [];
// this.ids = [];
// this.blend = 0;
this.textureCount = 0;
this.start = 0;
this.size = 0;
this.type = DRAW_MODES.TRIANGLES;
}
}
import { DisplayObject } from "../../display/DisplayObject";
import { WebglRenderer } from "../WebglRenderer";
/**
* Base for a common object renderer that can be used as a system renderer plugin.
* 基础渲染插件基类
*/
export default class ObjectRenderer {
/**
* The renderer this manager works for.
*
* @member {Renderer}
*/
renderer: WebglRenderer
constructor(renderer: WebglRenderer) {
this.renderer = renderer;
//监听
this.onContextChange = this.onContextChange.bind(this);
this.renderer.addEventListener('onContextChange', this.onContextChange);
}
/**
* Generic method called when there is a WebGL context change.
*
* @param {WebGLRenderingContext} gl new webgl context
*/
onContextChange(gl: WebGLRenderingContext) {
// do some codes init!
}
/**
* Starts the renderer and sets the shader
*
*/
start() {
// set the shader..
}
/**
* Stops the renderer
*
*/
stop() {
this.flush();
}
/**
* Stub method for rendering content and emptying the current batch.
*
*/
flush() {
// flush!
}
/**
* Renders an object
*
*/
render(object: DisplayObject) {
// render the object
}
/**
* Generic destroy methods to be overridden by the subclass
*/
destroy() {
this.renderer.removeEventListener('onContextChange', this.onContextChange);
this.renderer = null;
}
}
import mapWebGLBlendModesToPixi from '../../utils';
const BLEND = 0;
const DEPTH_TEST = 1;
const FRONT_FACE = 2;
const CULL_FACE = 3;
const BLEND_FUNC = 4;
/**
* A WebGL state machines
* 状态机管理类
* @class
*/
export default class WebGLState {
blendModes
/**
* The current active state
*
*/
activeState: Uint8Array;
/**
* The default state
*
*/
defaultState: Uint8Array;
/**
* The current state index in the stack
*
* @private
*/
stackIndex: number;
/**
* The stack holding all the different states
*
*/
stack: Array<any>;
gl: any;
maxAttribs: any;
attribState: { tempAttribState: any[]; attribState: any[]; };
nativeVaoExtension: any;
/**
* @param {WebGLRenderingContext} gl - The current WebGL rendering context
*/
constructor(gl: WebGLRenderingContext) {
this.activeState = new Uint8Array(16);
this.defaultState = new Uint8Array(16);
// default blend mode..
this.defaultState[0] = 1;
this.stackIndex = 0;
this.stack = [];
this.gl = gl;
this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
this.attribState = {
tempAttribState: new Array(this.maxAttribs),
attribState: new Array(this.maxAttribs),
};
this.blendModes = mapWebGLBlendModesToPixi(gl);
// check we have vao..
this.nativeVaoExtension = (
gl.getExtension('OES_vertex_array_object')
|| gl.getExtension('MOZ_OES_vertex_array_object')
|| gl.getExtension('WEBKIT_OES_vertex_array_object')
);
}
/**
* Pushes a new active state
*/
push() {
// next state..
let state = this.stack[this.stackIndex];
if (!state) {
state = this.stack[this.stackIndex] = new Uint8Array(16);
}
++this.stackIndex;
// copy state..
// set active state so we can force overrides of gl state
for (let i = 0; i < this.activeState.length; i++) {
state[i] = this.activeState[i];
}
}
/**
* Pops a state out
*/
pop() {
const state = this.stack[--this.stackIndex];
this.setState(state);
}
/**
* Sets the current state
*
* @param {*} state - The state to set.
*/
setState(state) {
this.setBlend(state[BLEND]);
this.setDepthTest(state[DEPTH_TEST]);
this.setFrontFace(state[FRONT_FACE]);
this.setCullFace(state[CULL_FACE]);
this.setBlendMode(state[BLEND_FUNC]);
}
/**
* Enables or disabled blending.
*
* @param {boolean} value - Turn on or off webgl blending.
*/
setBlend(value)
{
value = value ? 1 : 0;
if (this.activeState[BLEND] === value)
{
return;
}
this.activeState[BLEND] = value;
this.gl[value ? 'enable' : 'disable'](this.gl.BLEND);
}
/**
* Sets the blend mode.
*
* @param {number} value - The blend mode to set to.
*/
setBlendMode(value)
{
if (value === this.activeState[BLEND_FUNC])
{
return;
}
this.activeState[BLEND_FUNC] = value;
const mode = this.blendModes[value];
if (mode.length === 2)
{
this.gl.blendFunc(mode[0], mode[1]);
}
else
{
this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]);
}
}
/**
* Sets whether to enable or disable depth test.
*
* @param {boolean} value - Turn on or off webgl depth testing.
*/
setDepthTest(value) {
value = value ? 1 : 0;
if (this.activeState[DEPTH_TEST] === value) {
return;
}
this.activeState[DEPTH_TEST] = value;
this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST);
}
/**
* Sets whether to enable or disable cull face.
*
* @param {boolean} value - Turn on or off webgl cull face.
*/
setCullFace(value) {
value = value ? 1 : 0;
if (this.activeState[CULL_FACE] === value) {
return;
}
this.activeState[CULL_FACE] = value;
this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE);
}
/**
* Sets the gl front face.
*
* @param {boolean} value - true is clockwise and false is counter-clockwise
*/
setFrontFace(value) {
value = value ? 1 : 0;
if (this.activeState[FRONT_FACE] === value) {
return;
}
this.activeState[FRONT_FACE] = value;
this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']);
}
/**
* Disables all the vaos in use
*
*/
resetAttributes() {
for (let i = 0; i < this.attribState.tempAttribState.length; i++) {
this.attribState.tempAttribState[i] = 0;
}
for (let i = 0; i < this.attribState.attribState.length; i++) {
this.attribState.attribState[i] = 0;
}
// im going to assume one is always active for performance reasons.
for (let i = 1; i < this.maxAttribs; i++) {
this.gl.disableVertexAttribArray(i);
}
}
// used
/**
* Resets all the logic and disables the vaos
*/
resetToDefault() {
// unbind any VAO if they exist..
if (this.nativeVaoExtension) {
this.nativeVaoExtension.bindVertexArrayOES(null);
}
// reset all attributes..
this.resetAttributes();
// set active state so we can force overrides of gl state
for (let i = 0; i < this.activeState.length; ++i) {
this.activeState[i] = 32;
}
this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false);
this.setState(this.defaultState);
}
}
/**
* Generic Mask Stack data structure
* 根据图片数量创建索引数组
* 0....1
* . . .
* . . .
* 3....2
* @function createIndicesForQuads
* @private
* @param {number} size - Number of quads
* @return {Uint16Array} indices
*/
export function createIndicesForQuads(size: number): Uint16Array {
// the total number of indices in our array, there are 6 points per quad.
const totalIndices = size * 6;
const indices = new Uint16Array(totalIndices);
// fill the indices with the quads to draw
for (let i = 0, j = 0; i < totalIndices; i += 6, j += 4) {
indices[i + 0] = j + 0;
indices[i + 1] = j + 1;
indices[i + 2] = j + 2;
indices[i + 3] = j + 0;
indices[i + 4] = j + 2;
indices[i + 5] = j + 3;
}
return indices;
}
import { GLShader } from '../../../glCore';
//顶点着色器程序
const VSHADER_SOURCE =
"precision highp float;" +
"attribute vec2 aVertexPosition;" +
"attribute vec2 aTextureCoord;" +
"attribute vec4 aColor;" +
"attribute float aTextureId;" +
"uniform mat3 projectionMatrix;" +
// "uniform mat3 modelMatrix;" +
"varying vec2 vTextureCoord;" +
"varying vec4 vColor;" +
"varying float vTextureId;" +
"void main(void){" +
"gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);" +
// "gl_Position = vec4((projectionMatrix *modelMatrix* vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);" +
"vTextureCoord = aTextureCoord;" +
"vTextureId = aTextureId;" +
"vColor = aColor;" +
"}";
//片元着色器程序
const fragTemplate = [
'precision mediump float;',
'varying vec2 vTextureCoord;',
'varying vec4 vColor;',
'varying float vTextureId;',
'uniform sampler2D uSamplers[%count%];',
'void main(void){',
'vec4 color;',
'float textureId = floor(vTextureId+0.5);',
'%forloop%',
'gl_FragColor = color * vColor;',
'}',
].join('\n');
/**
* 创建批处理专用着色器,核心,切换纹理
* @param gl
* @param maxTextures 最大纹理数
*/
export function generateMultiTextureShader(gl, maxTextures) {
// const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8');
let fragmentSrc = fragTemplate;
fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures);
fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures));
// console.log(fragmentSrc)
const shader = new GLShader(gl, VSHADER_SOURCE, fragmentSrc);
const sampleValues = [];
for (let i = 0; i < maxTextures; i++) {
sampleValues[i] = i;
}
shader.bind();
shader.uniforms["uSamplers"] = sampleValues;
// console.log(fragmentSrc)
return shader;
}
function generateSampleSrc(maxTextures) {
let src = '';
src += '\n';
src += '\n';
for (let i = 0; i < maxTextures; i++) {
if (i > 0) {
src += '\nelse ';
}
if (i < maxTextures - 1) {
src += `if(textureId == ${i}.0)`;
}
src += '\n{';
src += `\n\tcolor = texture2D(uSamplers[${i}], vTextureCoord);`;
src += '\n}';
}
src += '\n';
src += '\n';
return src;
}
import { osType } from "../const";
import { Event } from "../events/Event";
import { MouseEvent } from "../events/MouseEvent";
import { FloatDisplay } from "../display/FloatDisplay";
/**
* 输入文本,待测试
* @class InputText
* @public
* @since 1.0.0
* @extends annie.FloatDisplay
*/
export class InputText extends FloatDisplay {
/**
* 输入文本的类型.
* @property inputType
* @public
* @since 1.0.0
* @type {number} 0 input 1 password 2 mulit
* @default 0
*/
public inputType: number = 0;
/**
* 在手机端是否需要自动收回软键盘,在pc端此参数无效
* @property isAutoDownKeyBoard
* @type {boolean}
* @since 1.0.3
* @default true
*/
public isAutoDownKeyBoard: boolean = true;
/**
* @property _inputTypeList
* @static
* @type {string[]}
* @private
* @since 2.0.0
*/
private static _inputTypeList: Array<string> = ["input", "password", "textarea"];
/**
* @method InputText
* @public
* @since 1.0.0
* @param {number} inputType 0 input 1 password 2 multiline
* @example
* var inputText=new InputText();
* inputText.initInfo('aa',100,100,'#ffffff','left',14,'微软雅黑',false,2);
*/
public constructor(inputType: number = 0) {
super();
var input: any = null;
let s: InputText = this;
s._instanceType = "InputText";
if (inputType < 2) {
input = document.createElement("input");
input.type = InputText._inputTypeList[inputType];
} else {
input = document.createElement("textarea");
input.style.resize = "none";
input.style.overflow = "hidden";
}
s.inputType = inputType;
var remove = function () {
if (s.isAutoDownKeyBoard && osType != "pc") {
s.htmlElement && s.htmlElement.blur();
}
}.bind(s);
s.addEventListener(Event.REMOVE_TO_STAGE, function (e: Event) {
s.stage.removeEventListener(MouseEvent.MOUSE_UP, remove);
});
s.addEventListener(Event.ADD_TO_STAGE, function (e: Event) {
s.stage.addEventListener(MouseEvent.MOUSE_UP, remove);
});
s.init(input);
}
/**
* 初始化输入文本
* @method init
* @param htmlElement
* @public
* @return {void}
* @since 1.0.0
*/
public init(htmlElement: any): void {
super.init(htmlElement);
//默认设置
let s = this;
s.htmlElement.style.outline = "none";
s.htmlElement.style.borderWidth = "thin";
s.htmlElement.style.borderColor = "#000";
}
/**
* 被始化输入文件的一些属性
* @method initInfo
* @public
* @since 1.0.0
* @param {string} text 默认文字
* @param {string}color 文字颜色
* @param {string}align 文字的对齐方式
* @param {number}size 文字大小
* @param {string}font 文字所使用的字体
* @param {boolean}showBorder 是否需要显示边框
* @param {number}lineSpacing 如果是多行,请设置行高
*/
public initInfo(text: string, color: string, align: string, size: number, font: string, showBorder: boolean, lineSpacing: number): void {
let s: InputText = this;
s.htmlElement.placeholder = text;
//font包括字体和大小
s.htmlElement.style.font = size + "px " + font;
s.htmlElement.style.color = color;
s.htmlElement.style.textAlign = align;
/////////////////////设置边框//////////////
s.border = showBorder;
//color:blue; text-align:center"
if (s.inputType == 2) {
s.htmlElement.style.lineHeight = lineSpacing + "px";
}
}
/**
* @property lineSpacing
* @public
* @since 2.0.0
* @param {number} value
*/
public set lineSpacing(value: number) {
this.htmlElement.style.lineHeight = value + "px";
}
public get lineSpacing(): number {
return parseInt(this.htmlElement.style.lineHeight);
}
/**
* 设置文本是否为粗体
* @property bold
* @param {boolean} bold true或false
* @public
* @since 1.0.3
*/
public set bold(bold: boolean) {
let ss = this.htmlElement.style;
if (bold) {
ss.fontWeight = "bold";
} else {
ss.fontWeight = "normal";
}
}
public get bold(): boolean {
return this.htmlElement.style.fontWeight == "bold";
}
/**
* 设置文本是否倾斜
* @property italic
* @param {boolean} italic true或false
* @public
* @since 1.0.3
*/
public set italic(italic: boolean) {
let s = this.htmlElement.style;
if (italic) {
s.fontStyle = "italic";
} else {
s.fontStyle = "normal";
}
}
public get italic(): boolean {
return this.htmlElement.style.fontStyle == "italic"
}
/**
* 文本的行高
* @property textHeight
* @public
* @since 1.0.0
* @type {number}
* @default 0
*/
public set textHeight(value: number) {
this.htmlElement.style.height = value + "px";
}
public get textHeight(): number {
return parseInt(this.htmlElement.style.height);
}
/**
* 文本的宽
* @property textWidth
* @public
* @since 1.0.0
* @type {number}
* @default 0
*/
public set textWidth(value: number) {
this.htmlElement.style.width = value + "px";
}
public get textWidth(): number {
return parseInt(this.htmlElement.style.width);
}
/**
* 设置文本颜色
* @property color
* @param {boolean} italic true或false
* @public
* @since 1.0.3
*/
public set color(value: string) {
var ss = this.htmlElement.style;
ss.color = value;
}
public get color(): string {
return this.htmlElement.style.color;
}
/**
* 设置或获取是否有边框
* @property property
* @param {boolean} show true或false
* @public
* @since 1.0.3
*/
public set border(show: boolean) {
let s = this;
if (show) {
s.htmlElement.style.borderStyle = "inset";
s.htmlElement.style.backgroundColor = "#fff";
} else {
s.htmlElement.style.borderStyle = "none";
s.htmlElement.style.backgroundColor = "transparent";
}
}
public get border(): boolean {
return this.htmlElement.style.borderStyle != "none";
}
/**
* 获取或设置输入文本的值
* 之前的getText 和setText 已废弃
* @property text
* @public
* @since 1.0.3
* @return {string}
*/
public get text(): string {
let s = this;
if (s.htmlElement) {
return s.htmlElement.value;
}
}
public set text(value: string) {
let s = this;
if (s.htmlElement) {
s.htmlElement.value = value;
}
}
/**
* 输入文本的最大输入字数
* @public
* @since 1.1.0
* @property maxCharacters
* @return {number}
*/
public get maxCharacters(): number {
let l: any = this.htmlElement.getAttribute("maxlength");
if (l === null) {
return 0;
} else {
return l;
}
}
public set maxCharacters(value: number) {
this.htmlElement.setAttribute("maxlength", value);
}
//预留,如果遇到相应问题则加上
aa() {
var u = navigator.userAgent, app = navigator.appVersion
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
if (isIOS) {
setTimeout(() => {
window["$"]("input").blur(function () {
if (isIOS) {
blurAdjust()
// alert("1231321233")
}
});
}, 50)
}
// 解决苹果不回弹页面
function blurAdjust() {
setTimeout(() => {
// alert("1231321233")
if (document.activeElement.tagName == 'INPUT' || document.activeElement.tagName == 'TEXTAREA') {
return
}
let result = 'pc';
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { //判断iPhone|iPad|iPod|iOS
result = 'ios'
} else if (/(Android)/i.test(navigator.userAgent)) {  //判断Android
result = 'android'
}
if (result = 'ios') {
document.activeElement["scrollIntoViewIfNeeded"](true);
}
}, 100)
}
}
}
import { DisplayObject } from "../display/DisplayObject";
import { SCALE_MODES } from "../const";
import Texture from "../texture/Texture";
import { getRGBA } from "../utils";
import { Rectangle } from "../math";
import { Event } from "../events/Event"
import { sign } from "../utils";
import { Bitmap } from "../display/Bitmap";
/**
*
*
* 动态文本类,有时需要在canvas里有一个动态文本,能根据我们的显示内容来改变
* @class TextField
* @extends DisplayObject
* @since 1.0.0
* @public
*/
export class TextField extends Bitmap {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
/**
* 与其他类不同,用了Boolean,再考虑
*/
dirty: boolean;
offsetX: number;
offsetY: number;
constructor() {
super();
this._instanceType = "TextField";
var canvas = document.createElement('canvas');
canvas.width = 3;
canvas.height = 3;
const texture = Texture.fromCanvas(canvas, SCALE_MODES.LINEAR, 'textCanvas');
texture.orig = new Rectangle();
this.texture=texture
// super(texture);
// base texture is already automatically added to the cache, now adding the actual texture
Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]);
this.canvas = canvas;
this.context = canvas.getContext("2d");
this.dirty = true;
this._transformID = -1;
this._textureID = -1;
this.vertexData = new Float32Array(8);
}
/**
* 文本的对齐方式 left center right
* @property textAlign
* @public
* @since 1.0.0
* @type {string}
* @default left
*/
public set textAlign(value: string) {
if (this._textAlign != value) {
this._textAlign = value;
this.dirty = true;
}
}
public get textAlign(): string {
return this._textAlign;
}
private _textAlign = "left";
/**
* @property textAlpha
* @since 2.0.0
* @public
*/
public set textAlpha(value: number) {
if (this._textAlpha != value) {
this._textAlpha = value;
this.dirty = true;
}
}
public get textAlpha(): number {
return this._textAlpha;
}
private _textAlpha: number = 1;
/**
* 文本的行高
* @property textHeight
* @public
* @since 1.0.0
* @type {number}
* @default 0
*/
public set textHeight(value: number) {
if (this._textHeight != value) {
this._textHeight = value;
this.dirty = true;
}
}
public get textHeight(): number {
return this._textHeight;
}
private _textHeight: number = 0;
/**
* 行间距
* @property lineSpacing
* @public
* @since 1.0.0
* @param {number} value
*/
public set lineSpacing(value: number) {
if (this._lineSpacing != value) {
this._lineSpacing = value;
this.dirty = true;
};
}
public get lineSpacing(): number {
return this._lineSpacing;
}
private _lineSpacing: number = 14;
/**
* 文本的宽,不会自动更新,要显示多,得设置
* @property textWidth
* @public
* @since 1.0.0
* @type {number}
* @default 0
*/
public set textWidth(value: number) {
if (this._textWidth != value) {
this._textWidth = value;
this.dirty = true;
};
}
public get textWidth(): number {
return this._textWidth;
}
private _textWidth: number = 120;
/**
* 文本类型,单行还是多行 single multi
* @property lineType
* @public
* @since 1.0.0
* @type {string} 两种 single和multi
* @default single
*/
public set lineType(value: string) {
if (this._lineType != value) {
this._lineType = value;
this.dirty = true;
};
}
public get lineType(): string {
return this._lineType;
}
private _lineType: string = "single";
/**
* 文本内容
* @property text
* @type {string}
* @public
* @default ""
* @since 1.0.0
*/
public set text(value: string) {
if (this._text != value) {
this._text = value;
this.dirty = true;
};
}
public get text(): string {
return this._text;
}
private _text: string = "";
/**
* 文本的css字体样式
* @property font
* @public
* @since 1.0.0
* @type {string}
* @default 12px Arial
*/
public set font(value: string) {
if (this._font != value) {
this._font = value;
this.dirty = true;
};
}
public get font(): string {
return this._font;
}
private _font: string = "Arial";
/**
* 文本的size
* @property size
* @public
* @since 1.0.0
* @type {number}
* @default 12
*/
public set size(value: number) {
if (this._size != value) {
this._size = value;
this.dirty = true;
};
}
public get size(): number {
return this._size;
}
private _size: number = 12;
/**
* 文本的填充颜色值
* @property fillColor
* @type {string}
* @public
* @since 1.0.0
* @default #fff
*/
public set fillColor(value: string) {
if (this._fillColor != value) {
this._fillColor = value;
this.dirty = true;
};
}
public get fillColor(): string {
return this._fillColor;
}
public _fillColor: string = "#fff";
/**
* 文本的描边颜色值
* @property strokeColor
* @type {string}
* @public
* @since 1.0.0
* @default #fff
*/
public set strokeColor(value: string) {
if (this._strokeColor != value) {
this._strokeColor = value;
this.dirty = true;
};
}
public get strokeColor(): string {
return this._strokeColor;
}
public _strokeColor: string = "#fff";
/**
* 文本描边宽度,为0则不描边
* @property stroke
* @public
* @since
* @default 0
* @type {number}
*/
public set stroke(value: number) {
if (this._stroke != value) {
this._stroke = value;
this.dirty = true;
};
}
public get stroke(): number {
return this._stroke;
}
private _stroke: number = 0;
/**
* 文本是否倾斜
* @property italic
* @public
* @since
* @default false
* @type {boolean}
*/
public set italic(value: boolean) {
if (this._italic != value) {
this._italic = value;
this.dirty = true;
};
}
public get italic(): boolean {
return this._italic;
}
private _italic: boolean = false;
/**
* 文本是否加粗
* @property bold
* @public
* @since
* @default false
* @type {boolean}
*/
public set bold(value: boolean) {
if (this._bold != value) {
this._bold = value;
this.dirty = true;
};
}
public get bold(): boolean {
return this._bold;
}
public _bold: boolean = false;
/**
* 设置或获取是否有边框
* @property property
* @param {boolean} show true或false
* @public
* @since 1.0.6
*/
public set border(value: boolean) {
if (this._border != value) {
this._border = value;
this.dirty = true;
};
}
public get border(): boolean {
return this._border;
}
private _border: boolean = false;
/**
* 设置文本在canvas里的渲染样式
* @method _prepContext
* @param ctx
* @private
* @since 1.0.0
*/
private _prepContext(ctx: any): void {
let s = this;
let font: any = s.size || 12;
font += "px ";
font += s.font;
//font-weight:bold;font-style:italic;
if (s._bold) {
font = "bold " + font;
}
if (s._italic) {
font = "italic " + font;
}
ctx.font = font;
ctx.textAlign = s._textAlign || "left";
//暂时没开放
ctx.textBaseline = "top";
ctx.fillStyle = getRGBA(s._fillColor, s._textAlpha)
}
/**
* 获取当前文本中单行文字的宽,注意是文字的不是文本框的宽
* @method getTextWidth
* @param {number} lineIndex 获取的哪一行的高度 默认是第1行
* @since 2.0.0
* @public
* @return {number}
*/
public getTextWidth(lineIndex: number = 0): number {
let s = this;
s.updateText();
// let can = s.canvas;
let ctx = s.context;
let obj: any = ctx.measureText(s.realLines[lineIndex]);
return obj.width;
}
/**
* @property _lines 获取当前文本行数
* @type {number}
* @public
* @readonly
* @since 2.0.0
*/
get lines(): number {
return this.realLines.length;
}
/**
* 获取文本宽
* @method _getMeasuredWidth
* @param text
* @return {number}
* @private
* @since 1.0.0
*/
private _getMeasuredWidth(text: string): number {
let ctx = this.context;
//ctx.save();
let w = ctx.measureText(text).width;
//ctx.restore();
return w;
}
private realLines: any = [];
/**
* 更新文本,主要重画canvas
*/
public updateText(): void {
let s: TextField = this;
//
if (!s.dirty) return;
s.dirty = false;
s._text += "";
let can = s.canvas;
let ctx = s.context;
let hardLines: any = s._text.toString().split(/(?:\r\n|\r|\n)/);
let realLines: any = [];
s.realLines = realLines;
s._prepContext(ctx);
let lineH = s._lineSpacing + s.size;
if (s._text.indexOf("\n") < 0 && s.lineType == "single") {
realLines[realLines.length] = hardLines[0];
let str = hardLines[0];
let lineW = s._getMeasuredWidth(str);
if (lineW > s._textWidth) {
let w = s._getMeasuredWidth(str[0]);
let lineStr = str[0];
let wordW = 0;
let strLen = str.length;
for (let j = 1; j < strLen; j++) {
wordW = ctx.measureText(str[j]).width;
w += wordW;
if (w > s._textWidth) {
realLines[0] = lineStr;
break;
} else {
lineStr += str[j];
}
}
}
} else {
for (let i = 0, l = hardLines.length; i < l; i++) {
let str = hardLines[i];
if (!str) continue;
let w = s._getMeasuredWidth(str[0]);
let lineStr = str[0];
let wordW = 0;
let strLen = str.length;
for (let j = 1; j < strLen; j++) {
wordW = ctx.measureText(str[j]).width;
w += wordW;
if (w > s._textWidth) {
realLines[realLines.length] = lineStr;
lineStr = str[j];
w = wordW;
} else {
lineStr += str[j];
}
}
realLines[realLines.length] = lineStr;
}
}
let maxH = lineH * realLines.length;
let maxW = s._textWidth;
let tx = 0;
if (s._textAlign == "center") {
tx = maxW * 0.5;
} else if (s._textAlign == "right") {
tx = maxW;
}
can.width = maxW + 20;
can.height = maxH + 20;
ctx.clearRect(0, 0, can.width, can.width);
if (s.border) {
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.lineWidth = 1;
ctx.strokeRect(10.5, 10.5, maxW, maxH);
ctx.closePath();
}
ctx.setTransform(1, 0, 0, 1, tx + 10, 10);
s._prepContext(ctx);
for (let i = 0; i < realLines.length; i++) {
if (s.stroke) {
ctx.lineWidth = s.stroke * 2;
ctx.strokeText(realLines[i], 0, i * lineH, maxW);
}
ctx.fillText(realLines[i], 0, i * lineH, maxW);
}
//offset用_anchorTexture代替
s.offsetX = -10;
s.offsetY = -10;
this.anchorTexture = { x: 10.5 / can.width, y: 10 / can.height }
// document.body.appendChild(can)
// s._bounds.height = maxH;
// s._bounds.width = maxW;
//x,y都是0
s._localBoundsSelf.width = maxW;
s._localBoundsSelf.height = maxH;
//修改texture及baseTexture属性
s.updateTexture()
}
/**
* 更新texture及baseTexture属性
* 不考虑trim,
*/
updateTexture() {
const canvas = this.canvas;
const texture = this._texture;
const baseTexture = texture.baseTexture;
baseTexture.hasLoaded = true;
baseTexture.width = canvas.width
baseTexture.height = canvas.height
//texture,已绑定过尺寸改变onBaseTextureUpdated
baseTexture.dispatchEvent('update');
texture._frame.width = canvas.width
texture._frame.height = canvas.height;
texture.orig.width = texture._frame.width
texture.orig.height = texture._frame.height
}
_renderCanvas(renderer) {
this.updateText();
super._renderCanvas(renderer);
}
_renderWebGL(renderer) {
this.updateText();
super._renderWebGL(renderer)
}
}
import { DisplayObject } from "../display/DisplayObject";
import { SCALE_MODES } from "../const";
import Texture from "../texture/Texture";
import { getRGBA } from "../utils";
import { Rectangle } from "../math";
import { Event } from "../events/Event"
import { sign } from "../utils";
import { Bitmap } from "../display/Bitmap";
import Sprite from "../display/Sprite";
/**
*
* 继承Sprite,暂时发现,只需要切换bitmap和Sprite
* 动态文本类,有时需要在canvas里有一个动态文本,能根据我们的显示内容来改变
* @class TextField
* @extends DisplayObject
* @since 1.0.0
* @public
*/
export class TextField extends Sprite {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
/**
* 与其他类不同,用了Boolean,再考虑
*/
dirty: boolean;
offsetX: number;
offsetY: number;
constructor() {
super();
this._instanceType = "TextField";
var canvas = document.createElement('canvas');
canvas.width = 3;
canvas.height = 3;
const texture = Texture.fromCanvas(canvas, SCALE_MODES.LINEAR, 'textCanvas');
texture.orig = new Rectangle();
this.texture = texture
// super(texture);
// base texture is already automatically added to the cache, now adding the actual texture
Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]);
this.canvas = canvas;
this.context = canvas.getContext("2d");
this.dirty = true;
this._transformID = -1;
this._textureID = -1;
this.vertexData = new Float32Array(8);
}
/**
* 文本的对齐方式 left center right
* @property textAlign
* @public
* @since 1.0.0
* @type {string}
* @default left
*/
public set textAlign(value: string) {
if (this._textAlign != value) {
this._textAlign = value;
this.dirty = true;
}
}
public get textAlign(): string {
return this._textAlign;
}
private _textAlign = "left";
/**
* @property textAlpha
* @since 2.0.0
* @public
*/
public set textAlpha(value: number) {
if (this._textAlpha != value) {
this._textAlpha = value;
this.dirty = true;
}
}
public get textAlpha(): number {
return this._textAlpha;
}
private _textAlpha: number = 1;
/**
* 文本的行高
* @property textHeight
* @public
* @since 1.0.0
* @type {number}
* @default 0
*/
public set textHeight(value: number) {
if (this._textHeight != value) {
this._textHeight = value;
this.dirty = true;
}
}
public get textHeight(): number {
return this._textHeight;
}
private _textHeight: number = 0;
/**
* 行间距
* @property lineSpacing
* @public
* @since 1.0.0
* @param {number} value
*/
public set lineSpacing(value: number) {
if (this._lineSpacing != value) {
this._lineSpacing = value;
this.dirty = true;
};
}
public get lineSpacing(): number {
return this._lineSpacing;
}
private _lineSpacing: number = 14;
/**
* 文本的宽,不会自动更新,要显示多,得设置
* @property textWidth
* @public
* @since 1.0.0
* @type {number}
* @default 0
*/
public set textWidth(value: number) {
if (this._textWidth != value) {
this._textWidth = value;
this.dirty = true;
};
}
public get textWidth(): number {
return this._textWidth;
}
private _textWidth: number = 120;
/**
* 文本类型,单行还是多行 single multi
* @property lineType
* @public
* @since 1.0.0
* @type {string} 两种 single和multi
* @default single
*/
public set lineType(value: string) {
if (this._lineType != value) {
this._lineType = value;
this.dirty = true;
};
}
public get lineType(): string {
return this._lineType;
}
private _lineType: string = "single";
/**
* 文本内容
* @property text
* @type {string}
* @public
* @default ""
* @since 1.0.0
*/
public set text(value: string) {
if (this._text != value) {
this._text = value;
this.dirty = true;
};
}
public get text(): string {
return this._text;
}
private _text: string = "";
/**
* 文本的css字体样式
* @property font
* @public
* @since 1.0.0
* @type {string}
* @default 12px Arial
*/
public set font(value: string) {
if (this._font != value) {
this._font = value;
this.dirty = true;
};
}
public get font(): string {
return this._font;
}
private _font: string = "Arial";
/**
* 文本的size
* @property size
* @public
* @since 1.0.0
* @type {number}
* @default 12
*/
public set size(value: number) {
if (this._size != value) {
this._size = value;
this.dirty = true;
};
}
public get size(): number {
return this._size;
}
private _size: number = 12;
/**
* 文本的填充颜色值
* @property fillColor
* @type {string}
* @public
* @since 1.0.0
* @default #fff
*/
public set fillColor(value: string) {
if (this._fillColor != value) {
this._fillColor = value;
this.dirty = true;
};
}
public get fillColor(): string {
return this._fillColor;
}
public _fillColor: string = "#fff";
/**
* 文本是否倾斜
* @property italic
* @public
* @since
* @default false
* @type {boolean}
*/
public set italic(value: boolean) {
if (this._italic != value) {
this._italic = value;
this.dirty = true;
};
}
public get italic(): boolean {
return this._italic;
}
private _italic: boolean = false;
/**
* 文本是否加粗
* @property bold
* @public
* @since
* @default false
* @type {boolean}
*/
public set bold(value: boolean) {
if (this._bold != value) {
this._bold = value;
this.dirty = true;
};
}
public get bold(): boolean {
return this._bold;
}
public _bold: boolean = false;
/**
* 设置或获取是否有边框
* @property property
* @param {boolean} show true或false
* @public
* @since 1.0.6
*/
public set border(value: boolean) {
if (this._border != value) {
this._border = value;
this.dirty = true;
};
}
public get border(): boolean {
return this._border;
}
private _border: boolean = false;
/**
* 设置文本在canvas里的渲染样式
* @method _prepContext
* @param ctx
* @private
* @since 1.0.0
*/
private _prepContext(ctx: any): void {
let s = this;
let font: any = s.size || 12;
font += "px ";
font += s.font;
//font-weight:bold;font-style:italic;
if (s._bold) {
font = "bold " + font;
}
if (s._italic) {
font = "italic " + font;
}
ctx.font = font;
ctx.textAlign = s._textAlign || "left";
//暂时没开放
ctx.textBaseline = "top";
ctx.fillStyle = getRGBA(s._fillColor, s._textAlpha)
}
/**
* 获取当前文本中单行文字的宽,注意是文字的不是文本框的宽
* @method getTextWidth
* @param {number} lineIndex 获取的哪一行的高度 默认是第1行
* @since 2.0.0
* @public
* @return {number}
*/
public getTextWidth(lineIndex: number = 0): number {
let s = this;
s.updateText();
// let can = s.canvas;
let ctx = s.context;
let obj: any = ctx.measureText(s.realLines[lineIndex]);
return obj.width;
}
/**
* @property _lines 获取当前文本行数
* @type {number}
* @public
* @readonly
* @since 2.0.0
*/
get lines(): number {
return this.realLines.length;
}
/**
* 获取文本宽
* @method _getMeasuredWidth
* @param text
* @return {number}
* @private
* @since 1.0.0
*/
private _getMeasuredWidth(text: string): number {
let ctx = this.context;
//ctx.save();
let w = ctx.measureText(text).width;
//ctx.restore();
return w;
}
private realLines: any = [];
/**
* 更新文本,主要重画canvas
*/
public updateText(): void {
let s: TextField = this;
//
if (!s.dirty) return;
s.dirty = false;
s._text += "";
let can = s.canvas;
let ctx = s.context;
let hardLines: any = s._text.toString().split(/(?:\r\n|\r|\n)/);
let realLines: any = [];
s.realLines = realLines;
s._prepContext(ctx);
let lineH = s._lineSpacing + s.size;
if (s._text.indexOf("\n") < 0 && s.lineType == "single") {
realLines[realLines.length] = hardLines[0];
let str = hardLines[0];
let lineW = s._getMeasuredWidth(str);
if (lineW > s._textWidth) {
let w = s._getMeasuredWidth(str[0]);
let lineStr = str[0];
let wordW = 0;
let strLen = str.length;
for (let j = 1; j < strLen; j++) {
wordW = ctx.measureText(str[j]).width;
w += wordW;
if (w > s._textWidth) {
realLines[0] = lineStr;
break;
} else {
lineStr += str[j];
}
}
}
} else {
for (let i = 0, l = hardLines.length; i < l; i++) {
let str = hardLines[i];
if (!str) continue;
let w = s._getMeasuredWidth(str[0]);
let lineStr = str[0];
let wordW = 0;
let strLen = str.length;
for (let j = 1; j < strLen; j++) {
wordW = ctx.measureText(str[j]).width;
w += wordW;
if (w > s._textWidth) {
realLines[realLines.length] = lineStr;
lineStr = str[j];
w = wordW;
} else {
lineStr += str[j];
}
}
realLines[realLines.length] = lineStr;
}
}
let maxH = lineH * realLines.length;
let maxW = s._textWidth;
let tx = 0;
if (s._textAlign == "center") {
tx = maxW * 0.5;
} else if (s._textAlign == "right") {
tx = maxW;
}
can.width = maxW + 20;
can.height = maxH + 20;
ctx.clearRect(0, 0, can.width, can.width);
if (s.border) {
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.lineWidth = 1;
ctx.strokeRect(10.5, 10.5, maxW, maxH);
ctx.closePath();
}
ctx.setTransform(1, 0, 0, 1, tx + 10, 10);
s._prepContext(ctx);
for (let i = 0; i < realLines.length; i++) {
ctx.fillText(realLines[i], 0, i * lineH, maxW);
}
//offset用_anchorTexture代替
s.offsetX = -10;
s.offsetY = -10;
this.anchorTexture = { x: 10.5 / can.width, y: 10 / can.height }
// document.body.appendChild(can)
// s._bounds.height = maxH;
// s._bounds.width = maxW;
//x,y都是0
s._localBoundsSelf.width = maxW;
s._localBoundsSelf.height = maxH;
//修改texture及baseTexture属性
s.updateTexture()
}
/**
* 更新texture及baseTexture属性
* 不考虑trim,
*/
updateTexture() {
const canvas = this.canvas;
const texture = this._texture;
const baseTexture = texture.baseTexture;
baseTexture.hasLoaded = true;
baseTexture.width = canvas.width
baseTexture.height = canvas.height
//texture,已绑定过尺寸改变onBaseTextureUpdated
baseTexture.dispatchEvent('update');
texture._frame.width = canvas.width
texture._frame.height = canvas.height;
texture.orig.width = texture._frame.width
texture.orig.height = texture._frame.height
}
_renderCanvas(renderer) {
this.updateText();
super._renderCanvas(renderer);
}
_renderWebGL(renderer) {
this.updateText();
super._renderWebGL(renderer)
}
}
import BaseTexture from './BaseTexture';
import { SCALE_MODES } from '../const';
/**
* 将显示对象画在上面的贴图
* A BaseRenderTexture is a special texture that allows any display object to be rendered to it.
*
* __Hint__: All DisplayObjects (i.e. Sprites) that render to a BaseRenderTexture should be preloaded
* otherwise black rectangles will be drawn instead.
*
* A BaseRenderTexture takes a snapshot of any Display Object given to its render method. The position
* and rotation of the given Display Objects is ignored. For example:
*
* ```js
* let renderer = autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 });
* let baseRenderTexture = new BaseRenderTexture(800, 600);
* let sprite = Sprite.fromImage("spinObj_01.png");
*
* sprite.position.x = 800/2;
* sprite.position.y = 600/2;
* sprite.anchorTexture.x = 0.5;
* sprite.anchorTexture.y = 0.5;
*
* renderer.render(sprite,baseRenderTexture);
* ```
*
* The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0
* you can clear the transform
*
* ```js
*
* sprite.setTransform()
*
* let baseRenderTexture = new BaseRenderTexture(100, 100);
* let renderTexture = new RenderTexture(baseRenderTexture);
*
* renderer.render(sprite, renderTexture); // Renders to center of RenderTexture
* ```
*
* @class
* @extends BaseTexture
*/
export default class BaseRenderTexture extends BaseTexture {
/**
* A map of renderer IDs to webgl renderTargets
* 画在他的帧缓存中
* @private
* @member {object<number, WebGLTexture>}
*/
_glRenderTargets: {};
/**
* A reference to the canvas render target (we only need one as this can be shared across renderers)
* 画在他的上下文中canvas中
* @private
* @member {object<number, WebGLTexture>}
*/
_canvasRenderTarget: any;
/**
* This will let the renderer know if the texture is valid. If it's not then it cannot be rendered.
*/
valid: boolean;
/**
* @param {number} [width=100] - The width of the base render texture
* @param {number} [height=100] - The height of the base render texture
* @param {number} [scaleMode=SCALE_MODES.LINEAR] - See {@link SCALE_MODES} for possible values
*/
constructor(width: number = 100, height: number = 100, scaleMode: number = SCALE_MODES.LINEAR) {
super(null, scaleMode);
this.width = Math.ceil(width);
this.height = Math.ceil(height);
this.scaleMode = scaleMode;
this.hasLoaded = true;
this._glRenderTargets = {};
this._canvasRenderTarget = null;
this.valid = false;
}
/**
* Resizes the BaseRenderTexture.
*
* @param {number} width - The width to resize to.
* @param {number} height - The height to resize to.
*/
resize(width: number, height: number) {
width = Math.ceil(width);
height = Math.ceil(height);
if (width === this.width && height === this.height) {
return;
}
this.valid = (width > 0 && height > 0);
this.width = width;
this.height = height;
if (!this.valid) {
return;
}
this.dispatchEvent('update');
}
/**
* Destroys this texture
*
*/
destroy() {
super.destroy();
}
}
import { EventDispatcher } from '../events/EventDispatcher';
import { decomposeDataUri, getUrlFileExtension, isPow2, BaseTextureCache, TextureCache, uid } from '../utils';
import { SCALE_MODES, WRAP_MODES } from "../const"
/**
* 每个texture都有一个BaseTexture,多数用于图集,texture可自身设置属性
* @class
* @extends EventDispatcher
*/
export default class BaseTexture extends EventDispatcher {
/**
* 贴图回收时用到
*/
touched: number;
width: number;
height: number;
/**
* SCALE_MODES,一般是线性
* 用于glTexture
*/
scaleMode: number;
/**
* 加载完成会设置为true
* 加载失败或没有贴图数据则为false
* @readonly
* @member {boolean}
*/
hasLoaded: boolean;
/**
* 正在加载
*/
isLoading: boolean;
/**
* 实际元素,考虑是否set方法调用loadSource();
* 图片标签,canvas画布
* @readonly
* @member {HTMLImageElement|HTMLCanvasElement}
*/
source: HTMLImageElement | HTMLCanvasElement;
/**
* image类型 eg. `png`
* 暂时不考虑svg
* @readonly
*/
imageType: string;
/**
* rgb预乘alpha,webgl用到,png图片设置必为true,否则色值会出问题
* @default true
*/
premultipliedAlpha: boolean;
/**
* 图片路径
* @member {string}
*/
imageUrl: string;
/**
* 是否尺寸为2的次方,尽可能图集尺寸都为2的次方,gpu处理方便,并且能做mipmap缓存,性能更好
*/
isPowerOfTwo: boolean;
/**
* 尺寸是2的次方下才能设置true,用于生成mipmap缓存
* @default true
*/
mipmap: boolean;
/**
* 非2的次方时要设置CLAMP
* WebGL Texture wrap mode
* @default WRAP_MODES.CLAMP
*/
wrapMode: number;
/**
* A map of renderer IDs to webgl textures
* 不同渲染器对应的记录,暂时应该只需要一个
* @member {object<number, WebGLTexture>}
*/
_glTextures: {};
/**
* 批处理时用到的标志位
*/
_enabled: number;
/**
* 是否已被销毁,为true,该纹理不可用
* @member {boolean}
* @private
* @readonly
*/
_destroyed: boolean;
/**
* The ids under which this BaseTexture has been added to the base texture cache. This is
* automatically set as long as BaseTexture.addToCache is used, but may not be set if a
* BaseTexture is added directly to the BaseTextureCache array.
* @member {string[]}
*/
textureCacheIds: string[];
/**
* @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture.
* @param {number} [scaleMode=settings.SCALE_MODE] - possible values
*/
constructor(source?: HTMLImageElement | HTMLCanvasElement, scaleMode: number = SCALE_MODES.LINEAR) {
super();
this.touched = 0;
this.width = 100;
this.height = 100;
this.scaleMode = scaleMode;
this.hasLoaded = false;
this.isLoading = false;
this.source = null; // set in loadSource, if at all
this.imageType = null; // set in updateImageType
this.premultipliedAlpha = true;
this.imageUrl = null;
this.isPowerOfTwo = false;
this.mipmap = true;
this.wrapMode = WRAP_MODES.CLAMP;
this._glTextures = {};
this._enabled = 0;
this._destroyed = false;
this.textureCacheIds = [];
// if no source passed don't try to load
if (source) {
this.loadSource(source);
}
}
/**
* Updates the texture on all the webgl renderers, this also assumes the src has changed.
*
* @fires BaseTexture#update
*/
update() {
this.width = this.source.width;
this.height = this.source.height;
this.isPowerOfTwo = isPow2(this.width) && isPow2(this.width);
this.dispatchEvent("update")
}
/**
* Load a source.
*
* If the source is not-immediately-available, such as an image that needs to be
* downloaded, then the 'loaded' or 'error' event will be dispatched in the future
* and `hasLoaded` will remain false after this call.
*
* The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is:
*
* if (texture.hasLoaded) {
* // texture ready for use
* } else if (texture.isLoading) {
* // listen to 'loaded' and/or 'error' events on texture
* } else {
* // not loading, not going to load UNLESS the source is reloaded
* // (it may still make sense to listen to the events)
* }
*
* @protected
* @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture.
*/
loadSource(source: any) {
const wasLoading = this.isLoading;
this.hasLoaded = false;
this.isLoading = false;
if (wasLoading && this.source) {
this.source.onload = null;
this.source.onerror = null;
}
//原先没有source
const firstSourceLoaded = !this.source;
this.source = source;
// Apply source if loaded. Otherwise setup appropriate loading monitors.,已加载,并且宽高都存在
if (((source.src && source.complete) || source.getContext) && source.width && source.height) {
this._updateImageType();
this._sourceLoaded();
if (firstSourceLoaded) {
// send loaded event if previous source was null and we have been passed a pre-loaded IMG element
this.dispatchEvent("loaded");
}
}
//加载图片
else if (!source.getContext) {
// Image fail / not ready
this.isLoading = true;
const scope = this;
source.onload = () => {
scope._updateImageType();
source.onload = null;
source.onerror = null;
if (!scope.isLoading) {
return;
}
scope.isLoading = false;
scope._sourceLoaded();
scope.dispatchEvent("loaded")
};
source.onerror = () => {
source.onload = null;
source.onerror = null;
if (!scope.isLoading) {
return;
}
scope.isLoading = false;
// scope.emit('error', scope);
scope.dispatchEvent("error")
};
// Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element
// "The value of `complete` can thus change while a script is executing."
// So complete needs to be re-checked after the callbacks have been added..
// NOTE: complete will be true if the image has no src so best to check if the src is set.
if (source.complete && source.src) {
// ..and if we're complete now, no need for callbacks
source.onload = null;
source.onerror = null;
this.isLoading = false;
if (source.width && source.height) {
this._sourceLoaded();
// If any previous subscribers possible
if (wasLoading) {
this.dispatchEvent("loaded")
}
}
// If any previous subscribers possible
else if (wasLoading) {
this.dispatchEvent("error")
}
}
}
}
/**
* 更新图片类型
*/
_updateImageType() {
if (!this.imageUrl) {
return;
}
const dataUri = decomposeDataUri(this.imageUrl);
let imageType;
if (dataUri && dataUri.mediaType === 'image') {
// Check for subType validity
const firstSubType = dataUri.subType.split('+')[0];
imageType = getUrlFileExtension(`.${firstSubType}`);
if (!imageType) {
throw new Error('Invalid image type in data URI.');
}
}
else {
imageType = getUrlFileExtension(this.imageUrl);
if (!imageType) {
imageType = 'png';
}
}
this.imageType = imageType;
}
/**
* 加载完成后执行
* @private
*/
_sourceLoaded() {
this.hasLoaded = true;
this.update();
}
/**
* 销毁 base texture
*/
destroy() {
if (this.imageUrl) {
delete TextureCache[this.imageUrl];
this.imageUrl = null;
}
this.source = null;
this.dispose();
BaseTexture.removeFromCache(this);
this.textureCacheIds = null;
this._destroyed = true;
}
/**
* 用于释放gpu,需要时再上传到GPU
* Frees the texture from WebGL memory without destroying this texture object.
* This means you can still use the texture later which will upload it to GPU
* memory again.
* @fires BaseTexture#dispose
*/
dispose() {
// this.emit('dispose', this);
this.dispatchEvent("dispose")
}
/**
* 修改source路径
* 原先source必须也是imageElement
* @param {string} newSrc - the path of the image
*/
updateSourceImage(newSrc: string) {
this.source["src"] = newSrc;
this.loadSource(this.source);
}
//辅助静态方法
/**
* 根据图片路径创建BaseTexture
* @static
* @param {string} imageUrl 图片路径
* @param {boolean} [crossorigin=(auto)] -是否跨域,也可传string
* @param {number} [scaleMode]
* @return {BaseTexture} The new base texture.
*/
static fromImage(imageUrl:string, crossorigin?:any, scaleMode:number = SCALE_MODES.LINEAR):BaseTexture {
let baseTexture = BaseTextureCache[imageUrl];
if (!baseTexture) {
// new Image() breaks tex loading in some versions of Chrome.
// See https://code.google.com/p/chromium/issues/detail?id=238071
const image = new Image();// document.createElement('img');
if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) {
image.crossOrigin = "anonymous"
}
else if (crossorigin) {
image.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous';
}
baseTexture = new BaseTexture(image, scaleMode);
baseTexture.imageUrl = imageUrl;
image.src = imageUrl;
//加入缓存
BaseTexture.addToCache(baseTexture, imageUrl);
}
return baseTexture;
}
/**
* 根据canvas创建BaseTexture
* @static
* @param {HTMLCanvasElement} canvas - canvas标签
* @param {number} scaleMode - See {@link SCALE_MODES} for possible values
* @param {string} [origin='canvas'] - 类型标志位,用于生成缓存id
* @return {BaseTexture} The new base texture.
*/
static fromCanvas(canvas:HTMLCanvasElement, scaleMode:number=SCALE_MODES.LINEAR, origin:string = 'canvas'):BaseTexture {
if (!canvas["_canvasId"]) {
canvas["_canvasId"] = `${origin}_${uid()}`;
}
let baseTexture = BaseTextureCache[canvas["_canvasId"]];
if (!baseTexture) {
baseTexture = new BaseTexture(canvas, scaleMode);
BaseTexture.addToCache(baseTexture, canvas["_canvasId"]);
}
return baseTexture;
}
/**
* @static
* @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from.
* @param {number} [scaleMode=settings.SCALE_MODE] - See {@link SCALE_MODES} for possible values
* @return {BaseTexture} The new base texture.
*/
static from(source:any, scaleMode:number=SCALE_MODES.LINEAR):BaseTexture {
if (typeof source === 'string') {
//图片路径
return BaseTexture.fromImage(source, undefined, scaleMode);
}
else if (source instanceof HTMLImageElement) {
//图片标签
const imageUrl = source.src;
let baseTexture = BaseTextureCache[imageUrl];
if (!baseTexture) {
baseTexture = new BaseTexture(source, scaleMode);
baseTexture.imageUrl = imageUrl;
BaseTexture.addToCache(baseTexture, imageUrl);
}
return baseTexture;
}
else if (source instanceof HTMLCanvasElement) {
//canvas标签
return BaseTexture.fromCanvas(source, scaleMode);
}
//如果source本身就是BaseTexture
return source;
}
/**
* 加入缓存
* @static
* @param {BaseTexture} baseTexture
* @param {string} id
*/
static addToCache(baseTexture: BaseTexture, id: string) {
if (id) {
if (baseTexture.textureCacheIds.indexOf(id) === -1) {
baseTexture.textureCacheIds.push(id);
}
if (BaseTextureCache[id]) {
//覆盖
console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`);
}
BaseTextureCache[id] = baseTexture;
}
}
/**
* 移除缓存
* @static
* @param {string|BaseTexture} baseTexture id或者BaseTexture
* @return {BaseTexture|null} 移除的BaseTexture或null
*/
static removeFromCache(baseTexture: string | BaseTexture): BaseTexture | null {
if (typeof baseTexture === 'string') {
const baseTextureFromCache = BaseTextureCache[baseTexture];
if (baseTextureFromCache) {
const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture);
if (index > -1) {
baseTextureFromCache.textureCacheIds.splice(index, 1);
}
delete BaseTextureCache[baseTexture];
return baseTextureFromCache;
}
}
else if (baseTexture && baseTexture.textureCacheIds) {
for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) {
delete BaseTextureCache[baseTexture.textureCacheIds[i]];
}
baseTexture.textureCacheIds.length = 0;
return baseTexture;
}
return null;
}
}
import BaseRenderTexture from './BaseRenderTexture';
import Texture from './Texture';
import { Rectangle } from '../math';
import { SCALE_MODES } from '../const';
/**
* A RenderTexture is a special texture that allows any display object to be rendered to it.
*
* __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded
* otherwise black rectangles will be drawn instead.
*
* A RenderTexture takes a snapshot of any Display Object given to its render method. For example:
*
* ```js
* let renderer = autoDetectRenderer(1024, 1024, { view: canvas, ratio: 1 });
* let renderTexture = RenderTexture.create(800, 600);
* let sprite = Sprite.fromImage("spinObj_01.png");
*
* sprite.position.x = 800/2;
* sprite.position.y = 600/2;
* sprite.anchorTexture.x = 0.5;
* sprite.anchorTexture.y = 0.5;
*
* renderer.render(sprite, renderTexture);
* ```
*
* The Sprite in this case will be rendered using its local transform. To render this sprite at 0,0
* you can clear the transform
*
* ```js
*
* sprite.setTransform()
*
* let renderTexture = new RenderTexture.create(100, 100);
*
* renderer.render(sprite, renderTexture); // Renders to center of RenderTexture
* ```
*
* @class
* @extends Texture
*/
export default class RenderTexture extends Texture {
/**
* @param {BaseRenderTexture} baseRenderTexture - The renderer used for this RenderTexture
* @param {Rectangle} [frame] - The rectangle frame of the texture to show
*/
constructor(baseRenderTexture: BaseRenderTexture, frame?: Rectangle) {
super(
baseRenderTexture,
frame
);
/**
* This will let the renderer know if the texture is valid. If it's not then it cannot be rendered.
*
* @member {boolean}
*/
this.valid = true;
this._updateUvs();
}
/**
* Resizes the RenderTexture.
*
* @param {number} width - The width to resize to.
* @param {number} height - The height to resize to.
* @param {boolean} doNotResizeBaseTexture - Should the baseTexture.width and height values be resized as well?
*/
resize(width: number, height: number, doNotResizeBaseTexture: boolean=false) {
width = Math.ceil(width);
height = Math.ceil(height);
// TODO - could be not required..
this.valid = (width > 0 && height > 0);
this._frame.width = this.orig.width = width;
this._frame.height = this.orig.height = height;
if (!doNotResizeBaseTexture) {
this.baseTexture["resize"](width, height);
}
this._updateUvs();
}
/**
* A short hand way of creating a render texture.
*
* @param {number} [width=100] - The width of the render texture
* @param {number} [height=100] - The height of the render texture
* @param {number} [scaleMode=settings.SCALE_MODE] - See {@link SCALE_MODES} for possible values
* @return {RenderTexture} The new render texture
*/
static create(width: number, height: number, scaleMode: number = SCALE_MODES.LINEAR): RenderTexture {
return new RenderTexture(new BaseRenderTexture(width, height, scaleMode));
}
}
import BaseTexture from './BaseTexture';
import TextureUvs from './TextureUvs';
import { EventDispatcher } from '../events/EventDispatcher';
import { Event } from "../events/Event";
import { Rectangle, Point } from '../math';
import { TextureCache } from '../utils';
import { SCALE_MODES } from '../const';
/**
* 一张图片或图集的一部分,如果没有frame。默认整张图片
*
* eg
* let texture = Texture.fromImage('assets/image.png');
* let sprite1 = new Sprite(texture);
* let sprite2 = new Sprite(texture);
*
* @class
* @extends EventDispatcher
*/
export default class Texture extends EventDispatcher {
/**
* texture没有frame
*/
noFrame: boolean;
/**
* BaseTexture,必有
*/
baseTexture: BaseTexture;
/**
* This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering,
* irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases)
*/
_frame: Rectangle;
/**
* 如果矩形边缘有透明像素被裁减后的缩小的区域
* This is the trimmed area of original texture, before it was put in atlas
* Please call `_updateUvs()` after you change coordinates of `trim` manually.
*/
trim: Rectangle;
/**
* 贴图是否可用,true为可用
*/
valid: boolean;
/**
* 对应贴图uv
* The WebGL UV data cache.
*/
_uvs: TextureUvs;
/**
* 原始尺寸,放入图集前
* This is the area of original texture, before it was put in atlas
*/
orig: Rectangle;
/**
* 贴图的锚点,默认0,0,左上角,范围0到1
*
* @default {0,0}
*/
defaultAnchor: Point;
/**
* 更新的id标志
*/
_updateID: number;
/**
* 一般不用,需要时再说
* Contains data for uvs. May contain clamp settings and some matrices.
* Its a bit heavy, so by default that object is not created.
* @member {TextureMatrix}
* @default null
*/
transform: any;
/**
* The ids under which this Texture has been added to the texture cache. This is
* automatically set as long as Texture.addToCache is used, but may not be set if a
* Texture is added directly to the TextureCache array.
*
* @member {string[]}
*/
textureCacheIds: string[];
/**
* 空纹理
*/
static EMPTY: Texture;
/**
* 白贴图
*/
static WHITE: Texture;
/**
* @param {BaseTexture} baseTexture - The base texture source to create the texture from
* @param {Rectangle} [frame] - The rectangle frame of the texture to show
* @param {Rectangle} [orig] - The area of original texture
* @param {Rectangle} [trim] - Trimmed rectangle of original texture
* @param {Point} [anchor] - Default anchor point used for sprite placement / rotation
* 暂时不需要rotate
*/
constructor(baseTexture: BaseTexture | Texture,
frame?: Rectangle,
orig?: Rectangle,
trim?: Rectangle,
anchor?: Point) {
super();
this._instanceType = "Texture";
this.noFrame = false;
if (!frame) {
this.noFrame = true;
frame = new Rectangle(0, 0, 1, 1);
}
if (baseTexture instanceof Texture) {
baseTexture = baseTexture.baseTexture;
}
this.baseTexture = baseTexture;
this._frame = frame;
this.trim = trim;
this.valid = false;
this._uvs = null;
this.orig = orig || frame;// new Rectangle(0, 0, 1, 1);
this.onBaseTextureUpdated = this.onBaseTextureUpdated.bind(this);
this.onBaseTextureLoaded = this.onBaseTextureLoaded.bind(this)
if (baseTexture.hasLoaded) {
if (this.noFrame) {
frame = new Rectangle(0, 0, baseTexture.width, baseTexture.height);
// if there is no frame we should monitor for any base texture changes..
baseTexture.addEventListener('update', this.onBaseTextureUpdated);
}
this.frame = frame;
}
else {
baseTexture.once('loaded', this.onBaseTextureLoaded);
}
this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0);
this._updateID = 0;
this.transform = null;
this.textureCacheIds = [];
}
/**
* Updates this texture on the gpu.
*
*/
update() {
this.baseTexture.update();
}
/**
* base texture 加载完成时执行
* @private
* @param {BaseTexture} baseTexture - The base texture.
*/
onBaseTextureLoaded(e: Event/*,baseTexture: BaseTexture*/) {
var baseTexture = e.target;
this._updateID++;
//执行这个函数意味着noFrame为true
// TODO this code looks confusing.. boo to abusing getters and setters!
if (this.noFrame) {
this.frame = new Rectangle(0, 0, baseTexture.width, baseTexture.height);
}
else {
this.frame = this._frame;
}
this.baseTexture.addEventListener('update', this.onBaseTextureUpdated);
this.dispatchEvent("update")
//加一个,监听texture,在BaseTexture加载完成后执行
// Texture.from("asd.png").once("loaded",()=>{
// 考虑是否有必要传参数,
// 回调
// })
this.dispatchEvent("loaded")
}
/**
* base texture 更新时触发
* @private
* @param {BaseTexture} baseTexture - The base texture.
*/
onBaseTextureUpdated(e: Event/*,baseTexture*/) {
var baseTexture = e.target;
this._updateID++;
this._frame.width = baseTexture.width;
this._frame.height = baseTexture.height;
// this.emit('update', this);
this.dispatchEvent("update")
}
/**
* 销毁
*/
destroy() {
if (this.baseTexture) {
//考虑是否销毁baseTexture
// if (destroyBase) {
// // delete the texture if it exists in the texture cache..
// // this only needs to be removed if the base texture is actually destroyed too..
// if (TextureCache[this.baseTexture.imageUrl]) {
// Texture.removeFromCache(this.baseTexture.imageUrl);
// }
// this.baseTexture.destroy();
// }
this.baseTexture.removeEventListener('update', this.onBaseTextureUpdated);
this.baseTexture.removeEventListener('loaded', this.onBaseTextureLoaded);
this.baseTexture = null;
}
this._frame = null;
this._uvs = null;
this.trim = null;
this.orig = null;
this.valid = false;
Texture.removeFromCache(this);
this.textureCacheIds = null;
}
/**
* 克隆
* @return {Texture} The new texture
*/
clone(): Texture {
return new Texture(this.baseTexture, this.frame, this.orig);
}
/**
*
* Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture.
*/
_updateUvs() {
if (!this._uvs) {
this._uvs = new TextureUvs();
}
this._uvs.set(this._frame, this.baseTexture);
this._updateID++;
}
/**
* The frame specifies the region of the base texture that this texture uses.
* Please call `_updateUvs()` after you change coordinates of `frame` manually.
*
* @member {Rectangle}
*/
get frame() {
return this._frame;
}
set frame(frame) {
this._frame = frame;
this.noFrame = false;
const { x, y, width, height } = frame;
const xNotFit = x + width > this.baseTexture.width;
const yNotFit = y + height > this.baseTexture.height;
if (xNotFit || yNotFit) {
const relationship = xNotFit && yNotFit ? 'and' : 'or';
const errorX = `X: ${x} + ${width} = ${x + width} > ${this.baseTexture.width}`;
const errorY = `Y: ${y} + ${height} = ${y + height} > ${this.baseTexture.height}`;
throw new Error('Texture Error: frame does not fit inside the base Texture dimensions: '
+ `${errorX} ${relationship} ${errorY}`);
}
// this.valid = width && height && this.baseTexture.source && this.baseTexture.hasLoaded;
this.valid = width && height && this.baseTexture.hasLoaded;
if (!this.trim) {
this.orig = frame;
}
if (this.valid) {
this._updateUvs();
}
}
/**
* 宽高都是贴图真实的宽高,不管trim
* The width of the Texture in pixels.
*
* @member {number}
*/
get width() {
return this.orig.width;
}
/**
* The height of the Texture in pixels.
*
* @member {number}
*/
get height() {
return this.orig.height;
}
//辅助方法
/**
* Helper function that creates a Texture object from the given image url.
* If the image is not in the texture cache it will be created and loaded.
*
* @static
* @param {string} imageUrl - The image url of the texture
* @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin
* @param {number} [scaleMode=settings.SCALE_MODE] - See {@link SCALE_MODES} for possible values
* @return {Texture} The newly created texture
*/
static fromImage(
imageUrl: string,
crossorigin: boolean = true,
scaleMode: number = SCALE_MODES.LINEAR): Texture {
let texture = TextureCache[imageUrl];
if (!texture) {
texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode));
Texture.addToCache(texture, imageUrl);
}
return texture;
}
/**
* Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId
* The frame ids are created when a Texture packer file has been loaded
*
* @static
* @param {string} frameId - The frame Id of the texture in the cache
* @return {Texture} The newly created texture
*/
static fromFrame(frameId: string): Texture {
const texture = TextureCache[frameId];
if (!texture) {
throw new Error(`The frameId "${frameId}" does not exist in the texture cache`);
}
return texture;
}
/**
* Helper function that creates a new Texture based on the given canvas element.
*
* @static
* @param {HTMLCanvasElement} canvas - The canvas element source of the texture
* @param {number} [scaleMode=settings.SCALE_MODE] - See {@link SCALE_MODES} for possible values
* @param {string} [origin='canvas'] - A string origin of who created the base texture
* @return {Texture} The newly created texture
*/
static fromCanvas(canvas: HTMLCanvasElement, scaleMode: number, origin: string = 'canvas'): Texture {
return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin));
}
/**
* Helper function that creates a new Texture based on the source you provide.
* The source can be - frame id, image url, video url, canvas element, video element, base texture
*
* @static
* @param {number|string|HTMLImageElement|HTMLCanvasElement|BaseTexture}
* source - Source to create texture from
* @return {Texture} The newly created texture
*/
static from(source:any):Texture {
// TODO auto detect cross origin..
// TODO pass in scale mode?
if (typeof source === 'string') {
const texture = TextureCache[source];
if (!texture) {
return Texture.fromImage(source);
}
return texture;
}
else if (source instanceof HTMLImageElement) {
return new Texture(BaseTexture.from(source));
}
else if (source instanceof HTMLCanvasElement) {
return Texture.fromCanvas(source, SCALE_MODES.LINEAR, 'canvas');
}
else if (source instanceof BaseTexture) {
return new Texture(source);
}
//如果source本身就是Texture
return source;
}
/**
* Create a texture from a source and add to the cache.
* 作为加载器中使用,加入到缓存
* @static
* @param {HTMLImageElement|HTMLCanvasElement} source - The input source.
* @param {String} imageUrl - File name of texture, for cache and resolving resolution.
* @param {String} [name] - Human readible name for the texture cache. If no name is
* specified, only `imageUrl` will be used as the cache ID.
* @return {Texture} Output texture
*/
static fromLoader(source, imageUrl, name) {
const baseTexture = new BaseTexture(source, undefined);
const texture = new Texture(baseTexture);
baseTexture.imageUrl = imageUrl;
// No name, use imageUrl instead
if (!name) {
name = imageUrl;
}
// lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions
BaseTexture.addToCache(texture.baseTexture, name);
Texture.addToCache(texture, name);
// also add references by url if they are different.
if (name !== imageUrl) {
BaseTexture.addToCache(texture.baseTexture, imageUrl);
Texture.addToCache(texture, imageUrl);
}
return texture;
}
/**
* Adds a Texture to the global TextureCache. This cache is shared across the whole object.
*
* @static
* @param {Texture} texture - The Texture to add to the cache.
* @param {string} id - The id that the Texture will be stored against.
*/
static addToCache(texture:Texture, id:string) {
if (id) {
if (texture.textureCacheIds.indexOf(id) === -1) {
texture.textureCacheIds.push(id);
}
if (TextureCache[id]) {
//覆盖
console.warn(`Texture added to the cache with an id [${id}] that already had an entry`);
}
TextureCache[id] = texture;
}
}
/**
* Remove a Texture from the global TextureCache.
*
* @static
* @param {string|Texture} texture - id of a Texture to be removed, or a Texture instance itself
* @return {Texture|null} The Texture that was removed
*/
static removeFromCache(texture:any):Texture {
if (typeof texture === 'string') {
const textureFromCache = TextureCache[texture];
if (textureFromCache) {
const index = textureFromCache.textureCacheIds.indexOf(texture);
if (index > -1) {
textureFromCache.textureCacheIds.splice(index, 1);
}
delete TextureCache[texture];
return textureFromCache;
}
}
else if (texture && texture.textureCacheIds) {
for (let i = 0; i < texture.textureCacheIds.length; ++i) {
// Check that texture matches the one being passed in before deleting it from the cache.
if (TextureCache[texture.textureCacheIds[i]] === texture) {
delete TextureCache[texture.textureCacheIds[i]];
}
}
texture.textureCacheIds.length = 0;
return texture;
}
return null;
}
}
/**
* 16*16的空白canvas
*/
function createWhiteTexture() {
const canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;
const context = canvas.getContext('2d');
context.fillStyle = 'white';
context.fillRect(0, 0, 16, 16);
return new Texture(new BaseTexture(canvas));
}
//将事件置空
function removeAllHandlers(tex) {
tex.destroy = function _emptyDestroy() { /* empty */ };
tex.addEventListener = function _emptyOn() { /* empty */ };
tex.once = function _emptyOnce() { /* empty */ };
tex.dispatchEvent = function _emptyEmit() { /* empty */ };
}
/**
* An empty texture, used often to not have to create multiple empty textures.
* Can not be destroyed.
*
* @static
* @constant
*/
Texture.EMPTY = new Texture(new BaseTexture());
removeAllHandlers(Texture.EMPTY);
removeAllHandlers(Texture.EMPTY.baseTexture);
/**
* A white texture of 16x16 size, used for graphics and other things
* Can not be destroyed.
*
* @static
* @constant
*/
Texture.WHITE = createWhiteTexture();
removeAllHandlers(Texture.WHITE);
removeAllHandlers(Texture.WHITE.baseTexture);
import { Rectangle } from '../';
import Texture from "./Texture"
import BaseTexture from './BaseTexture';
/**
* 简单点来说,就是用来拆图集的
* Utility class for maintaining reference to a collection
* of Textures on a single TextureSheet.
* With the `sheet.textures` you can create Sprite objects,`sheet.animations` can be used to create an AnimatedSprite.
*/
export default class TextureSheet {
/**
* Reference to ths source texture
*/
baseTexture: BaseTexture;
/**
* A map containing all textures of the sprite sheet.
* Can be used to create a {@link Sprite|Sprite}:
* ```js
* new Sprite(sheet.textures["image.png"]);
*/
textures: {};
/**
* A map containing the textures for each animation.
* Can be used to create an {@link extras.AnimatedSprite|AnimatedSprite}:
* ```js
* new extras.AnimatedSprite(sheet.animations["anim_name"])
* ```
* @member {Object}
*/
animations: {};
/**
* Reference to the original JSON data.
* @type {Object}
*/
data: any;
/**
* Map of TextureSheet frames.
* @type {Object}
* @private
*/
_frames: any;
/**
* Collection of frame names.
* @type {string[]}
* @private
*/
_frameKeys: string[];
/**
* Callback when parse is completed.
* @private
*/
_callback: Function;
/**
* @param {BaseTexture} baseTexture Reference to the source BaseTexture object.
* @param {Object} data - TextureSheet image data.
*/
constructor(baseTexture: BaseTexture, data: any) {
this.baseTexture = baseTexture;
this.textures = {};
this.animations = {};
this.data = data;
this._frames = this.data.frames;
this._frameKeys = Object.keys(this._frames);
this._callback = null;
}
/**
* Parser TextureSheet from loaded data. This is done asynchronously
* to prevent creating too many Texture within a single process.
*
* @param {Function} callback - Callback when complete returns
* a map of the Textures for this TextureSheet.
*/
parse(callback: Function) {
this._callback = callback;
this._processFrames(0);
this._processAnimations();
//原先里面可能又超过数量的贴图,需要多次处理,所以,额外放一个complete
this._parseComplete();
}
/**
* Process a batch of frames
*
* @private
* @param {number} initialFrameIndex - The index of frame to start.
*/
_processFrames(initialFrameIndex: number) {
let frameIndex = initialFrameIndex;
while (frameIndex < this._frameKeys.length) {
const i = this._frameKeys[frameIndex];
const data = this._frames[i];
const rect = data.frame;
if (rect) {
let frame = null;
let trim = null;
//如果是被截掉过透明边界的,则取data.sourceSize(原尺寸)
const sourceSize = data.trimmed !== false && data.sourceSize
? data.sourceSize : data.frame;
//贴图原始尺寸
const orig = new Rectangle(
0,
0,
Math.floor(sourceSize.w),
Math.floor(sourceSize.h)
);
//图集上的位置
frame = new Rectangle(
Math.floor(rect.x),
Math.floor(rect.y),
Math.floor(rect.w),
Math.floor(rect.h)
);
// Check to see if the sprite is trimmed
if (data.trimmed !== false && data.spriteSourceSize) {
//其实就是在orig上切图,偏移
trim = new Rectangle(
Math.floor(data.spriteSourceSize.x),
Math.floor(data.spriteSourceSize.y),
Math.floor(rect.w),
Math.floor(rect.h)
);
}
this.textures[i] = new Texture(
this.baseTexture,
frame,
orig,
trim,
// data.anchor
);
// lets also add the frame to global cache for fromFrame and fromImage functions
Texture.addToCache(this.textures[i], i);
}
frameIndex++;
}
}
/**
* Parse animations config
*
* @private
*/
_processAnimations() {
const animations = this.data.animations || {};
for (const animName in animations) {
this.animations[animName] = [];
for (const frameName of animations[animName]) {
this.animations[animName].push(this.textures[frameName]);
}
}
}
/**
* The parse has completed.
*
* @private
*/
_parseComplete() {
const callback = this._callback;
this._callback = null;
callback.call(this, this.textures);
}
/**
* Destroy TextureSheet and don't use after this.
*
* @param {boolean} [destroyBase=false] Whether to destroy the base texture as well
*/
destroy(destroyBase:boolean = false) {
for (const i in this.textures) {
this.textures[i].destroy();
}
this._frames = null;
this._frameKeys = null;
this.data = null;
this.textures = null;
if (destroyBase) {
this.baseTexture.destroy();
}
this.baseTexture = null;
}
}
import { Rectangle } from "../math";
import BaseTexture from "./BaseTexture";
/**
* Texture的uv
* @class
* @private
*/
export default class TextureUvs {
x0: number;
y0: number;
x1: number;
y1: number;
x2: number;
y2: number;
x3: number;
y3: number;
uvsUint32: Uint32Array;
uvsFloat32: Float32Array;
/**
* 用于记录图片的uv
* 00.....10
* . .
* . .
* 01.....11
*/
constructor() {
this.x0 = 0;
this.y0 = 0;
this.x1 = 1;
this.y1 = 0;
this.x2 = 1;
this.y2 = 1;
this.x3 = 0;
this.y3 = 1;
this.uvsUint32 = new Uint32Array(4);
this.uvsFloat32 = new Float32Array(8);
}
/**
* Sets the texture Uvs based on the given frame information.
*
* @private
* @param {Rectangle} frame - The frame of the texture
* @param {Rectangle} baseFrame - The base frame of the texture
*/
set(frame: Rectangle, baseFrame: Rectangle|BaseTexture) {
const tw = baseFrame.width;
const th = baseFrame.height;
this.x0 = frame.x / tw;
this.y0 = frame.y / th;
this.x1 = (frame.x + frame.width) / tw;
this.y1 = frame.y / th;
this.x2 = (frame.x + frame.width) / tw;
this.y2 = (frame.y + frame.height) / th;
this.x3 = frame.x / tw;
this.y3 = (frame.y + frame.height) / th;
this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF);
this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF);
this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF);
this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF);
this.uvsFloat32[0] = this.x0;
this.uvsFloat32[1] = this.y0;
this.uvsFloat32[2] = this.x1;
this.uvsFloat32[3] = this.y1;
this.uvsFloat32[4] = this.x2;
this.uvsFloat32[5] = this.y2;
this.uvsFloat32[6] = this.x3;
this.uvsFloat32[7] = this.y3;
}
}
import { ScrollPage } from "./ScrollPage";
import { DisplayObject } from "../display/DisplayObject";
import { Event } from "../events/Event";
/**
* 滚动类的Item类接口
* @class IScrollListItem
* @public
* @extends DisplayObject
* @since 1.0.9
*/
export interface IScrollListItem extends DisplayObject {
initData(id: number, data: Array<any>): void;
id: number;
data: number;
}
/**
* 滚动列表
* @class ScrollList
* @public
* @extends ScrollPage
* @since 1.0.9
*/
export class ScrollList extends ScrollPage {
private _items: Array<IScrollListItem> = null;
private _itemW: number;
private _itemH: number;
private _itemRow: number;
private _itemCol: number;
private _itemCount: number;
private _itemClass: any;
private _isInit: boolean;
public data: Array<any> = [];
private downL: DisplayObject = null;
private _cols: number;
private _disParam: string;
private _lastFirstId: number = -1;
/**
* 获取下拉滚动的loadingView对象
* @property loadingView
* @since 1.0.9
* @return {DisplayObject}
*/
public get loadingView(): DisplayObject {
return this.downL;
}
/**
* 构造函数
* @method ScrollList
* @param {Class} itemClassName 可以做为Item的类
* @param {number} itemWidth item宽
* @param {number} itemHeight item高
* @param {number} vW 列表的宽
* @param {number} vH 列表的高
* @param {boolean} isVertical 是横向滚动还是纵向滚动 默认是纵向
* @param {number} cols 分几列,默认是1列
* @since 1.0.9
*/
constructor(itemClassName: any, itemWidth: number, itemHeight: number, vW: number, vH: number, isVertical: boolean = true, cols: number = 1) {
super(vW, vH, 0, isVertical);
let s = this;
s._isInit = false;
s._instanceType = "ScrollList";
s._itemW = itemWidth;
s._itemH = itemHeight;
s._items = [];
s._itemClass = itemClassName;
s._itemCount = 0;
s._cols = cols;
s._updateViewRect();
s.addEventListener(Event.ENTER_FRAME, s.flushData.bind(s));
}
/**
* 更新列表数据
* @method updateData
* @param {Array} data
* @param {boolean} isReset 是否重围数据列表。
* @since 1.0.9
*/
public updateData(data: Array<any>, isReset: boolean = false): void {
let s: any = this;
if (!s._isInit || isReset) {
s.data = data;
s._isInit = true;
} else {
s.data = s.data.concat(data);
}
s._lastFirstId = -1;
s.maxDistance = Math.ceil(s.data.length / s._cols) * s._itemRow;
if (s.downL) {
s.downL[s.paramXY] = Math.max(s.distance, s.maxDistance);
var wh = s.downL.getWH();
s.maxDistance += (s.paramXY == "x" ? wh.width : wh.height);
}
}
private flushData() {
let s: any = this;
if (s._isInit) {
if (s.view._UI.UM) {
let id: number = (Math.abs(Math.floor(s.view[s.paramXY] / s._itemRow)) - 1) * s._cols;
id = id < 0 ? 0 : id;
if (id != s._lastFirstId) {
s._lastFirstId = id;
if (id != s._items[0].id) {
for (let r = 0; r < s._cols; r++) {
if (s.speed > 0) {
s._items.unshift(s._items.pop());
} else {
s._items.push(s._items.shift());
}
}
}
}
for (let i = 0; i < s._itemCount; i++) {
let item: any = s._items[i];
if (item._a2x_sl_id != id) {
item.initData(s.data[id] ? id : -1, s.data[id]);
item.visible = s.data[id] ? true : false;
item[s.paramXY] = Math.floor(id / s._cols) * s._itemRow;
item[s._disParam] = (id % s._cols) * s._itemCol;
item._a2x_sl_id = id;
}
id++;
}
}
}
}
/**
* 设置可见区域,可见区域的坐标始终在本地坐标中0,0点位置
* @method setViewRect
* @param {number}w 设置可见区域的宽
* @param {number}h 设置可见区域的高
* @param {boolean} isVertical 方向
* @public
* @since 1.1.1
*/
public setViewRect(w: number, h: number, isVertical: boolean): void {
super.setViewRect(w, h, isVertical);
let s = this;
if (s._itemRow && s._itemCol) {
s._updateViewRect();
}
}
private _updateViewRect() {
let s: any = this;
if (s.isVertical) {
s._disParam = "x";
s._itemRow = s._itemH;
s._itemCol = s._itemW;
} else {
s._disParam = "y";
s._itemRow = s._itemW;
s._itemCol = s._itemH;
}
let newCount: number = (Math.ceil(s.distance / s._itemRow) + 1) * s._cols;
if (newCount != s._itemCount) {
if (newCount > s._itemCount) {
for (let i = s._itemCount; i < newCount; i++) {
let item = new s._itemClass();
item.id = -1;
item.data = null;
s._items.push(item);
s.view.addChild(item);
}
} else {
for (let i = 0; i < s._itemCount - newCount; i++) {
s.view.removeChild(s._items.pop());
}
}
s._itemCount = newCount;
s._lastFirstId = -1;
}
}
/**
* 设置加载数据时显示的loading对象
* @since 1.0.9
* @method setLoading
* @param {DisplayObject} downLoading
*/
public setLoading(downLoading: DisplayObject): void {
let s: any = this;
if (s.downL) {
s.view.removeChild(s.downL);
let wh = s.downL.getWH();
s.maxDistance -= (s.paramXY == "x" ? wh.width : wh.height);
s.downL = null;
}
if (downLoading) {
s.downL = downLoading;
s.view.addChild(downLoading);
s.downL[s.paramXY] = Math.max(s.distance, s.maxDistance);
let wh = s.downL.getWH();
s.maxDistance += (s.paramXY == "x" ? wh.width : wh.height);
} else {
s.isStop = false;
}
}
public destroy(): void {
let s = this;
s._items = null;
s._itemClass = null;
s.data = null;
s.downL = null;
s["maskObj"] = null;
s.view = null;
super.destroy();
}
}
import Container from "../display/Container";
import Graphics from "../graphics/Graphics";
import { MouseEvent } from "../events/MouseEvent";
import { Event } from "../events/Event";
import { Tween } from "../../tweenSimple/Tween";
/**
* 滚动视图
* @class ScrollPage
* @public
* @extends Container
* @since 1.0.0
*/
export class ScrollPage extends Container {
/**
* 横向还是纵向 默认为纵向
* @property isVertical
* @type {boolean}
* @private
* @since 1.0.0
* @default true
*/
protected isVertical: boolean = true;
/**
* 可见区域的宽
* @property viewWidth
* @type {number}
* @private
* @since 1.0.0
* @default 0
*/
private viewWidth: number = 0;
/**
* 可见区域的高
* @property viewHeight
* @type {number}
* @private
* @since 1.0.0
* @default 0
*/
private viewHeight: number = 0;
private _tweenId: number = 0;
/**
* 整个滚动的最大距离值
* @property maxDistance
* @type {number}
* @public
* @since 1.0.0
* @default 1040
*/
public maxDistance: number = 1040;
/**
* @property 滚动距离
* @type {number}
* @protected
* @default 0
* @since 1.0.0
*/
protected distance: number = 0;
/**
* 最小鼠标滑动距离
* @type {number}
*/
private minDis: number = 2;
/**
* 遮罩对象
* @property maskObj
* @since 1.0.0
* @private
* @type {Graphics}
*/
private maskObj: Graphics = new Graphics();
/**
* 真正的容器对象,所有滚动的内容都应该是添加到这个容器中
* @property view
* @public
* @since 1.0.0
* @type {Container}
*/
public view: Container = new Container();
/**
* 最后鼠标经过的坐标值
* @property lastValue
* @private
* @since 1.0.0
* @type {number}
*/
private lastValue: number = 0;
/**
* 速度
* @property speed
* @protected
* @since 1.0.0
* @type {number}
*/
protected speed: number = 0;
/**
* 加速度
* @property addSpeed
* @private
* @since 1.0.0
* @type {number}
*/
private addSpeed: number = 0;
/**
* 是否是停止滚动状态
* @property isStop
* @public
* @since 1.0.0
* @type {boolean}
* @default true
*/
public isStop: boolean = true;
/**
* 滚动的最大速度,直接影响一次滑动之后最长可以滚多远
* @property maxSpeed
* @public
* @since 1.0.0
* @default 100
* @type {number}
*/
public maxSpeed: number = 100;
/**
* 摩擦力,值越大,减速越快
* @property fSpeed
* @public
* @since 1.0.0
* @default 20
* @type {number}
*/
public fSpeed: number = 20;
protected paramXY: string = "y";
private stopTimes: number = -1;
private isMouseDownState: number = 0;
/**
* 是否是通过scrollTo方法在滑动中
* @property autoScroll
* @since 1.0.2
* @type {boolean}
* @private
* @default false
*/
private autoScroll: boolean = false;
public isSpringBack: boolean = true;
/**
* 构造函数
* @method ScrollPage
* @param {number} vW 可视区域宽
* @param {number} vH 可视区域高
* @param {number} maxDistance 最大滚动的长度
* @param {boolean} isVertical 是纵向还是横向,也就是说是滚x还是滚y,默认值为沿y方向滚动
* @example
* var sPage=new ScrollPage(640,s.stage.viewRect.height,4943);
* sPage.isSpringBack = false;//是否回弹
* stage.addChild(sPage);
* sPage.view.addChild(view);
* sPage.y=stage.viewRect.y;
*
*/
constructor(vW: number, vH: number, maxDistance: number, isVertical: boolean = true) {
super();
let s = this;
s._instanceType = "ScrollPage";
s.addChild(s.maskObj);
s.addChild(s.view);
s.view.mask = s.maskObj;
//为了能接收鼠标事件设置isUsedToMask
s.maskObj.isUsedToMask = false;
s.maskObj.alpha = 0;
s.maxDistance = maxDistance;
s.setViewRect(vW, vH, isVertical);
// s.addEventListener(MouseEvent.MOUSE_DOWN, s.onMouseEvent.bind(s));
s.addEventListener(MouseEvent.MOUSE_MOVE, s.onMouseEvent.bind(s));
s.addEventListener(MouseEvent.MOUSE_UP, s.onMouseEvent.bind(s));
s.addEventListener(MouseEvent.MOUSE_OUT, s.onMouseEvent.bind(s));
s.addEventListener(Event.ENTER_FRAME, function () {
let view: any = s.view;
if (s.autoScroll) return;
if (!s.isSpringBack) {
if (view[s.paramXY] > 0) {
s.addSpeed = 0;
s.speed = 0;
s.isStop = true;
view[s.paramXY] = 0;
return;
}
else if (view[s.paramXY] < s.distance - s.maxDistance) {
s.addSpeed = 0;
s.speed = 0;
s.isStop = true;
view[s.paramXY] = s.distance - s.maxDistance;
return;
}
}
if (!s.isStop) {
if (Math.abs(s.speed) > 0) {
view[s.paramXY] += s.speed;
//是否超过了边界,如果超过了,则加快加速度,让其停止
if (view[s.paramXY] > 0 || view[s.paramXY] < s.distance - s.maxDistance) {
s.speed += s.addSpeed * s.fSpeed;
} else {
s.speed += s.addSpeed;
}
//说明超过了界线,准备回弹
if (s.speed * s.addSpeed > 0) {
s.dispatchEvent("onScrollStop");
s.speed = 0;
}
} else {
//检测是否超出了边界,如果超出了边界则回弹
if (s.addSpeed != 0) {
if (view[s.paramXY] > 0 || view[s.paramXY] < s.distance - s.maxDistance) {
let tarP: number = 0;
if (s.addSpeed > 0) {
if (s.distance < s.maxDistance) {
tarP = s.distance - s.maxDistance;
}
}
view[s.paramXY] += 0.4 * (tarP - view[s.paramXY]);
if (Math.abs(tarP - view[s.paramXY]) < 0.1) {
s.isStop = true;
if (s.addSpeed > 0) {
s.dispatchEvent("onScrollToEnd");
} else {
s.dispatchEvent("onScrollToHead");
}
}
}
} else {
s.isStop = true;
}
}
} else {
if (s.stopTimes >= 0) {
s.stopTimes++;
if (s.stopTimes >= 15) {
s.speed = 0;
if (view[s.paramXY] > 0 || view[s.paramXY] < s.distance - s.maxDistance) {
s.isStop = false;
s.stopTimes = -1;
}
}
}
}
})
}
/**
* 设置可见区域,可见区域的坐标始终在本地坐标中0,0点位置
* @method setViewRect
* @param {number}w 设置可见区域的宽
* @param {number}h 设置可见区域的高
* @param {boolean} isVertical 方向
* @public
* @since 1.1.1
*/
public setViewRect(w: number, h: number, isVertical: boolean): void {
let s: any = this;
s.maskObj.clear();
s.maskObj.beginFill("#000000");
s.maskObj.drawRect(0, 0, w, h);
s.viewWidth = w;
s.viewHeight = h;
s.maskObj.endFill();
s.isVertical = isVertical;
if (isVertical) {
s.distance = s.viewHeight;
s.paramXY = "y";
} else {
s.distance = s.viewWidth;
s.paramXY = "x";
}
s.isVertical = isVertical;
}
private onMouseEvent(e: MouseEvent): void {
let s = this;
let view: any = s.view;
// if (s.distance < s.maxDistance) {
if (e.type == MouseEvent.MOUSE_MOVE) {
if (s.isMouseDownState < 1) {
if (!s.isStop) {
s.isStop = true;
}
if (s.autoScroll) {
s.autoScroll = false;
Tween.kill(s._tweenId);
}
if (s.isVertical) {
s.lastValue = e.localY;
} else {
s.lastValue = e.localX;
}
s.speed = 0;
s.isMouseDownState = 1;
return;
};
if (s.isMouseDownState == 1) {
s.dispatchEvent("onScrollStart");
}
s.isMouseDownState = 2;
let currentValue: number;
if (s.isVertical) {
currentValue = e.localY;
} else {
currentValue = e.localX;
}
s.speed = currentValue - s.lastValue;
if (s.speed > s.minDis) {
s.addSpeed = -2;
if (s.speed > s.maxSpeed) {
s.speed = s.maxSpeed;
}
} else if (s.speed < -s.minDis) {
if (s.speed < -s.maxSpeed) {
s.speed = -s.maxSpeed;
}
s.addSpeed = 2;
} else {
s.speed = 0;
}
if (s.speed != 0) {
let speedPer: number = 1;
if (view[s.paramXY] > 0 || view[s.paramXY] < s.distance - s.maxDistance) {
speedPer = 0.2;
}
view[s.paramXY] += (currentValue - s.lastValue) * speedPer;
}
s.lastValue = currentValue;
s.stopTimes = 0;
} else {
s.isStop = false;
s.stopTimes = -1;
if (s.speed == 0 && s.isMouseDownState == 2) {
s.dispatchEvent("onScrollStop");
}
s.isMouseDownState = 0;
}
// }
}
/**
* 滚到指定的坐标位置
* @method scrollTo
* @param {number} dis 需要去到的位置
* @param {number} time 滚动需要的时间 默认为0 即没有动画效果直接跳到指定页
* @since 1.1.1
* @public
*/
public scrollTo(dis: number, time: number = 0): void {
let s: any = this;
let newDis = s.paramXY == "x" ? s.viewWidth : s.viewHeight;
if (dis < 0) {
dis = 0;
} else if (dis > s.maxDistance - newDis) {
dis = s.maxDistance - newDis;
}
if (Math.abs(s.view[s.paramXY] + dis) > 2) {
s.autoScroll = true;
s.isStop = true;
s.isMouseDownState = 0;
let obj: any = {};
obj.onComplete = function () {
s.autoScroll = false;
};
obj[s.paramXY] = -dis;
s._tweenId = Tween.to(s.view, time, obj);
if (s.speed == 0) {
s.dispatchEvent("onScrollStart");
}
}
}
public destroy(): void {
let s = this;
s.maskObj.destroy();
s.view.destroy();
s.maskObj = null;
s.view = null;
super.destroy();
}
}
// import _url from 'url';
let tempAnchor;
/**
*
* 暂时只判断base64不跨域,否则加跨域属性
* Sets the `crossOrigin` property for this resource based on if the url
* for this resource is cross-origin. If crossOrigin was manually set, this
* function does nothing.
* Nipped from the resource loader!
*
* @ignore
* @param {string} url - The url to test.
* @param {object} [loc=window.location] - The location object to test against.
* @return {string} The crossOrigin value to use (or empty string for none).
*/
export default function determineCrossOrigin(url: string, loc: any = window.location): string {
// data: and javascript: urls are considered same-origin
if (url.indexOf('data:') === 0) {
return '';
}
return 'anonymous';
// if (!tempAnchor)
// {
// tempAnchor = document.createElement('a');
// }
// // let the browser determine the full href for the url of this resource and then
// // parse with the node url lib, we can't use the properties of the anchor element
// // because they don't work in IE9 :(
// tempAnchor.href = url;
// url = _url.parse(tempAnchor.href);
// const samePort = (!url.port && loc.port === '') || (url.port === loc.port);
// // if cross origin
// if (url.hostname !== loc.hostname || !samePort || url.protocol !== loc.protocol)
// {
// return 'anonymous';
// }
// return '';
}
import { DATA_URI, URL_FILE_EXTENSION } from "../const"
import { BLEND_MODES } from "../const";
let nextUid = 0;
/**
* Gets the next unique identifier
*
* @memberof PIXI.utils
* @function uid
* @return {number} The next unique identifier to use.
*/
export function uid(): number {
return ++nextUid;
}
/**
* Converts a hex color number to an [R, G, B] array
* @function hex2rgb
* @param {number} hex - The number to convert
* @param {number[]} [out=[]] If supplied, this array will be used rather than returning a new one
* @return {number[]} An array representing the [R, G, B] of the color.
*/
export function hex2rgb(hex: number, out?: number[]): number[] {
out = out || [];
out[0] = ((hex >> 16) & 0xFF) / 255;
out[1] = ((hex >> 8) & 0xFF) / 255;
out[2] = (hex & 0xFF) / 255;
return out;
}
/**
* 十六进制颜色转字符串
* 0xffffff转"#ffffff"
* Converts a hex color number to a string.
* @function hex2string
* @param {number} hex - Number in hex
* @return {string} The string color.
*/
export function hex2string(hex) {
hex = hex.toString(16);
hex = '000000'.substr(0, 6 - hex.length) + hex;
return `#${hex}`;
}
/**
* 字符串颜色转十六进制
* "#ffffff"或"0xffffff"转0xffffff
* @param string
*/
export function string2hex(string:string) {
if(string.indexOf("#")==0){
string=string.replace("#","0x")
}
return parseInt(string)
}
/**
* Converts a color as an [R, G, B] array to a hex number
* @function rgb2hex
* @param {number[]} rgb - rgb array
* @return {number} The color number
*/
export function rgb2hex(rgb) {
return (((rgb[0] * 255) << 16) + ((rgb[1] * 255) << 8) + (rgb[2] * 255 | 0));
}
/**
* 通过24位颜色值和一个透明度值生成RGBA值
* @method getRGBA
* @static
* @public
* @since 1.0.0
* @param {string} color 字符串的颜色值,如:#33ffee
* @param {number} alpha 0-1区间的一个数据 0完全透明 1完全不透明
* @return {string}
*/
export function getRGBA(color: string, alpha: number): string {
if (color.indexOf("0x") == 0) {
color = color.replace("0x", "#");
}
if (color.length < 7) {
color = "#000000";
}
if (alpha != 1) {
let r = parseInt("0x" + color.substr(1, 2));
let g = parseInt("0x" + color.substr(3, 2));
let b = parseInt("0x" + color.substr(5, 2));
color = "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
}
return color;
}
/**
* Split a data URI into components. Returns undefined if
* parameter `dataUri` is not a valid data URI.
*
* @memberof utils
* @function decomposeDataUri
* @param {string} dataUri - the data URI to check
* @return {utils~DecomposedDataUri|undefined} The decomposed data uri or undefined
*/
export function decomposeDataUri(dataUri) {
const dataUriMatch = DATA_URI.exec(dataUri);
if (dataUriMatch) {
return {
mediaType: dataUriMatch[1] ? dataUriMatch[1].toLowerCase() : undefined,
subType: dataUriMatch[2] ? dataUriMatch[2].toLowerCase() : undefined,
charset: dataUriMatch[3] ? dataUriMatch[3].toLowerCase() : undefined,
encoding: dataUriMatch[4] ? dataUriMatch[4].toLowerCase() : undefined,
data: dataUriMatch[5],
};
}
return undefined;
}
/**
* Get type of the image by regexp for extension. Returns undefined for unknown extensions.
*
* @memberof utils
* @function getUrlFileExtension
* @param {string} url - the image path
* @return {string|undefined} image extension
*/
export function getUrlFileExtension(url) {
const extension = URL_FILE_EXTENSION.exec(url);
if (extension) {
return extension[1].toLowerCase();
}
return undefined;
}
/**
* Returns sign of number
*
* @memberof utils
* @function sign
* @param {number} n - the number to check the sign of
* @returns {number} 0 if `n` is 0, -1 if `n` is negative, 1 if `n` is positive
*/
export function sign(n: number): number {
if (n === 0) return 0;
return n < 0 ? -1 : 1;
}
export * from './twiddle';
//determineCrossOrigin随意改名。上面形式就不能改
export { default as determineCrossOrigin } from './determineCrossOrigin';
export function premultiplyTint(tint: number, alpha: number): number {
if (alpha === 1.0) {
return (alpha * 255 << 24) + tint;
}
if (alpha === 0.0) {
return 0;
}
var R = ((tint >> 16) & 0xFF);
var G = ((tint >> 8) & 0xFF);
var B = (tint & 0xFF);
R = ((R * alpha) + 0.5) | 0;
G = ((G * alpha) + 0.5) | 0;
B = ((B * alpha) + 0.5) | 0;
return (alpha * 255 << 24) + (R << 16) + (G << 8) + B;
}
/**
* 贴图缓存
* @private
*/
export const TextureCache = Object.create(null);
/**
* 基础贴图缓存
* @private
*/
export const BaseTextureCache = Object.create(null);
/**
* 记录TextureSheet,便于帧动画贴图数据查找
*/
export const TextureSheetCache = Object.create(null);
/**
* Destroys all texture in the cache
* @function destroyTextureCache
*/
export function destroyTextureCache() {
let key;
for (key in TextureCache) {
TextureCache[key].destroy();
}
for (key in BaseTextureCache) {
BaseTextureCache[key].destroy();
}
}
/**
* Removes all textures from cache, but does not destroy them
* @function clearTextureCache
*/
export function clearTextureCache() {
let key;
for (key in TextureCache) {
delete TextureCache[key];
}
for (key in BaseTextureCache) {
delete BaseTextureCache[key];
}
}
/**
* Helper for checking for webgl support
* @function isWebGLSupported
* @return {boolean} is webgl supported
*/
export function isWebGLSupported(): boolean {
const contextOptions = { stencil: true, failIfMajorPerformanceCaveat: true };
try {
if (!window["WebGLRenderingContext"]) {
return false;
}
const canvas = document.createElement('canvas');
let gl = canvas.getContext('webgl', contextOptions) || canvas.getContext('experimental-webgl', contextOptions);
const success = !!(gl && gl["getContextAttributes"]().stencil);
if (gl) {
const loseContext = gl["getExtension"]('WEBGL_lose_context');
if (loseContext) {
loseContext.loseContext();
}
}
gl = null;
return success;
}
catch (e) {
return false;
}
}
export function removeItems(arr: Array<any>, startIdx: number, removeCount: number) {
var i, length = arr.length
if (startIdx >= length || removeCount === 0) {
return
}
removeCount = (startIdx + removeCount > length ? length - startIdx : removeCount)
var len = length - removeCount
for (i = startIdx; i < len; ++i) {
arr[i] = arr[i + removeCount]
}
arr.length = len
}
/**
* Maps gl blend combinations to WebGL.
*
* @memberof PIXI
* @function mapWebGLBlendModesToPixi
* @private
* @param {WebGLRenderingContext} gl - The rendering context.
* @param {string[]} [array=[]] - The array to output into.
* @return {string[]} Mapped modes.
*/
export default function mapWebGLBlendModesToPixi(gl, array = []) {
// TODO - premultiply alpha would be different.
// add a boolean for that!
array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA];
array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR];
array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
// not-premultiplied blend modes
array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA];
array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR];
return array;
}
var u = navigator.userAgent, app = navigator.appVersion
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
if (isIOS) {
setTimeout(() => {
// window["$"]("input").blur(function () {
// if (isIOS) {
// blurAdjust()
// // alert("1231321233")
// }
// });
}, 50)
}
// 解决苹果不回弹页面
function blurAdjust() {
setTimeout(() => {
// alert("1231321233")
if (document.activeElement.tagName == 'INPUT' || document.activeElement.tagName == 'TEXTAREA') {
return
}
let result = 'pc';
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { //判断iPhone|iPad|iPod|iOS
result = 'ios'
} else if (/(Android)/i.test(navigator.userAgent)) {  //判断Android
result = 'android'
}
if (result = 'ios') {
document.activeElement["scrollIntoViewIfNeeded"](true);
}
}, 100)
}
\ No newline at end of file
/**
* Bit twiddling hacks for JavaScript.
*
* Author: Mikola Lysenko
*
* Ported from Stanford bit twiddling hack library:
* http://graphics.stanford.edu/~seander/bithacks.html
*/
//Number of bits in an integer
var INT_BITS = 32;
//Constants
export const INT_BITS1 = 32;
export const INT_MAX = 0x7fffffff;
export const INT_MIN = -1 << (INT_BITS - 1);
//Computes absolute value of integer
export function abs(v) {
var mask = v >> (INT_BITS - 1);
return (v ^ mask) - mask;
}
//Computes minimum of integers x and y
export function min(x, y) {
return y ^ ((x ^ y) & -(x < y));
}
//Computes maximum of integers x and y
export function max(x, y) {
return x ^ ((x ^ y) & -(x < y));
}
//Checks if a number is a power of two
export function isPow2(v) {
return !(v & (v - 1)) && (!!v);
}
//Computes log base 2 of v
export function log2(v) {
//Ts报错,但实际可运行
var r, shift;
r = Number(v > 0xFFFF) << 4; v >>>= r;
shift = Number(v > 0xFF ) << 3; v >>>= shift; r |= shift;
shift = Number(v > 0xF ) << 2; v >>>= shift; r |= shift;
shift = Number(v > 0x3 ) << 1; v >>>= shift; r |= shift;
return r | (v >> 1);
}
//Computes log base 10 of v
export function log10(v) {
return (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 :
(v >= 1000000) ? 6 : (v >= 100000) ? 5 : (v >= 10000) ? 4 :
(v >= 1000) ? 3 : (v >= 100) ? 2 : (v >= 10) ? 1 : 0;
}
//Counts number of bits
export function popCount(v) {
v = v - ((v >>> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
return ((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24;
}
//Counts number of trailing zeros
function countTrailingZeros1(v) {
var c = 32;
v &= -v;
if (v) c--;
if (v & 0x0000FFFF) c -= 16;
if (v & 0x00FF00FF) c -= 8;
if (v & 0x0F0F0F0F) c -= 4;
if (v & 0x33333333) c -= 2;
if (v & 0x55555555) c -= 1;
return c;
}
export function countTrailingZeros(v) {
var c = 32;
v &= -v;
if (v) c--;
if (v & 0x0000FFFF) c -= 16;
if (v & 0x00FF00FF) c -= 8;
if (v & 0x0F0F0F0F) c -= 4;
if (v & 0x33333333) c -= 2;
if (v & 0x55555555) c -= 1;
return c;
};
//Rounds to next power of 2
export function nextPow2(v) {
v += v === 0;
--v;
v |= v >>> 1;
v |= v >>> 2;
v |= v >>> 4;
v |= v >>> 8;
v |= v >>> 16;
return v + 1;
}
//Rounds down to previous power of 2
export function prevPow2(v) {
v |= v >>> 1;
v |= v >>> 2;
v |= v >>> 4;
v |= v >>> 8;
v |= v >>> 16;
return v - (v >>> 1);
}
//Computes parity of word
export function parity(v) {
v ^= v >>> 16;
v ^= v >>> 8;
v ^= v >>> 4;
v &= 0xf;
return (0x6996 >>> v) & 1;
}
var REVERSE_TABLE = new Array(256);
(function (tab) {
for (var i = 0; i < 256; ++i) {
var v = i, r = i, s = 7;
for (v >>>= 1; v; v >>>= 1) {
r <<= 1;
r |= v & 1;
--s;
}
tab[i] = (r << s) & 0xff;
}
})(REVERSE_TABLE);
//Reverse bits in a 32 bit word
export function reverse(v) {
return (REVERSE_TABLE[v & 0xff] << 24) |
(REVERSE_TABLE[(v >>> 8) & 0xff] << 16) |
(REVERSE_TABLE[(v >>> 16) & 0xff] << 8) |
REVERSE_TABLE[(v >>> 24) & 0xff];
}
//Interleave bits of 2 coordinates with 16 bits. Useful for fast quadtree codes
export function interleave2(x, y) {
x &= 0xFFFF;
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y &= 0xFFFF;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
//Extracts the nth interleaved component
export function deinterleave2(v, n) {
v = (v >>> n) & 0x55555555;
v = (v | (v >>> 1)) & 0x33333333;
v = (v | (v >>> 2)) & 0x0F0F0F0F;
v = (v | (v >>> 4)) & 0x00FF00FF;
v = (v | (v >>> 16)) & 0x000FFFF;
return (v << 16) >> 16;
}
//Interleave bits of 3 coordinates, each with 10 bits. Useful for fast octree codes
export function interleave3(x, y, z) {
x &= 0x3FF;
x = (x | (x << 16)) & 4278190335;
x = (x | (x << 8)) & 251719695;
x = (x | (x << 4)) & 3272356035;
x = (x | (x << 2)) & 1227133513;
y &= 0x3FF;
y = (y | (y << 16)) & 4278190335;
y = (y | (y << 8)) & 251719695;
y = (y | (y << 4)) & 3272356035;
y = (y | (y << 2)) & 1227133513;
x |= (y << 1);
z &= 0x3FF;
z = (z | (z << 16)) & 4278190335;
z = (z | (z << 8)) & 251719695;
z = (z | (z << 4)) & 3272356035;
z = (z | (z << 2)) & 1227133513;
return x | (z << 2);
}
//Extracts nth interleaved component of a 3-tuple
export function deinterleave3(v, n) {
v = (v >>> n) & 1227133513;
v = (v | (v >>> 2)) & 3272356035;
v = (v | (v >>> 4)) & 251719695;
v = (v | (v >>> 8)) & 4278190335;
v = (v | (v >>> 16)) & 0x3FF;
return (v << 22) >> 22;
}
//Computes next combination in colexicographic order (this is mistakenly called nextPermutation on the bit twiddling hacks page)
export function nextCombination(v) {
var t = v | (v - 1);
return (t + 1) | (((~t & -~t) - 1) >>> (countTrailingZeros(v) + 1));
}
/**
* Created by rockyl on 16/5/19.
*
* 事件管理器
*/
export class EgretEventManager {
private static _instance: EgretEventManager;
public static get instance(): EgretEventManager {
if (this._instance == undefined) {
this._instance = new EgretEventManager();
}
return this._instance;
}
private _groups: any = {};
register(groupName: string, target: any, eventName, callback: Function, thisObj?: any, priority: number = 0): void {
if (!target) {
console.error('target is empty');
}
let item: RegisterItem = new RegisterItem();
Object.assign(item, {target, eventName, callback, thisObj, priority});
let group: any = this._groups[groupName];
if (!group) {
group = this._groups[groupName] = {enable: false, items: []};
}
group.items.push(item);
if (group.enable) { //如果组已生效,添加进去的就立马生效
this.addEventListener(item);
}
}
registerOn(obj: any, target: any, eventName, callback: Function, thisObj?: any, priority: number = 0): void {
this.register(getObjName(obj), target, eventName, callback, thisObj, priority);
}
enable(groupName: string): void {
let group: any = this._groups[groupName];
if (!group) {
group = this._groups[groupName] = {enable: false, items: []};
}
if (!group.enable) {
group.enable = true;
group.items.forEach(this.addEventListener);
}
}
private addEventListener(item: RegisterItem) {
item.target['addEventListener'](item.eventName, item.callback, item.thisObj);
}
enableOn(obj: any): void {
this.enable(getObjName(obj));
}
disable(groupName: string): void {
let group: any = this._groups[groupName];
if (group && group.enable) {
group.enable = false;
group.items.forEach(this.removeEventListener);
}
}
private removeEventListener(item: RegisterItem) {
item.target['removeEventListener'](item.eventName, item.callback, item.thisObj);
}
disableOn(obj: any): void {
this.disable(getObjName(obj));
}
dump(groupName: string = null): void {
for (let key in this._groups) {
let group: any = this._groups[key];
console.log(key + '[' + group.items.length + ']: ' + (group.enable ? '● enable' : '○ disable'));
console.log(group.items.map((item: RegisterItem) => {
return item.eventName;
}).join(','));
}
}
}
class RegisterItem {
target: any;
eventName: string;
callback: Function;
thisObj: any;
priority: number;
}
function getObjName(obj){
return obj.constructor.name + '_' + obj.instanceId;
}
/**
* Created by rockyl on 2019-07-14.
*
* 场景
*/
import {RootEntity} from "./entities";
import {assetsManager, setupScene, IScene} from "scilla";
import Container from "../2d/display/Container";
export class Scene extends Container implements IScene{
root: RootEntity;
constructor(root: RootEntity) {
super();
this.root = root;
}
async setup() {
const sceneConfig = await assetsManager.loadJson5('assets/scenes/main.scene');
let {root, root: {parent}} = this;
parent.removeChild(root);
setupScene(sceneConfig, this.root);
parent.addChild(root);
}
}
/**
* Created by rockyl on 2019-07-12.
*/
import {IEntity, ComponentsManager} from "scilla";
import Container from "../2d/display/Container";
import {Event} from "../2d/events/Event";
import {TextField} from "../2d/text/TextFieldCon";
export class Entity extends Container implements IEntity {
readonly components: ComponentsManager;
readonly uuid: string;
get enabled(){
return this.stage && this.visible;
}
}
export class Text extends TextField implements IEntity {
readonly components: ComponentsManager;
readonly uuid: string;
get enabled(){
return this.stage && this.visible;
}
}
export class RootEntity extends Entity {
constructor() {
super();
this.once(Event.ADD_TO_STAGE, this.onAddedToStage, this);
}
protected onAddedToStage(event: Event) {
}
}
/**
* Created by rockyl on 2019-07-14.
*/
import {ComponentsManager} from "scilla";
import {EgretEventManager} from "./EgretEventManager";
import {Event} from "../2d/events/Event";
import {MouseEvent} from "../2d/events/MouseEvent";
export function implementLifecycle(target) {
target.components = new ComponentsManager(target);
let events = EgretEventManager.instance;
target.addEventListener(Event.ADD_TO_STAGE, onAddedToStage);
events.registerOn(target, target, Event.REMOVE_TO_STAGE, onRemovedFromStage);
events.registerOn(target, target, Event.ENTER_FRAME, onEnterFrame);
events.registerOn(target, target, MouseEvent.MOUSE_DOWN, onTouchBegin);
events.registerOn(target, target, MouseEvent.MOUSE_MOVE, onTouchMove);
events.registerOn(target, target, MouseEvent.MOUSE_UP, onTouchEnd);
events.registerOn(target, target, MouseEvent.CLICK, onTouchTap);
/*events.registerOn(target, target, TouchEvent.TOUCH_BEGIN, onStageTouchBegin);
events.registerOn(target, target, TouchEvent.TOUCH_MOVE, onStageTouchMove);
events.registerOn(target, target, TouchEvent.TOUCH_END, onStageTouchEnd);
events.registerOn(target, target, TouchEvent.TOUCH_TAP, onStageTouchTap);*/
function onAddedToStage(event: Event) {
target.components.enabled = true;
target.components.onEnable();
events.enableOn(target);
}
function onRemovedFromStage(event: Event) {
target.components.enabled = false;
target.components.onDisable();
events.disableOn(target);
}
function onEnterFrame(event: Event) {
target.components.onUpdate(this.stage.timer);
}
function onStageTouchBegin(event: TouchEvent) {
target.components.onInteract(0, event);
}
function onStageTouchMove(event: TouchEvent) {
target.components.onInteract(1, event);
}
function onStageTouchEnd(event: TouchEvent) {
target.components.onInteract(2, event);
}
function onStageTouchTap(event: TouchEvent) {
target.components.onInteract(3, event);
}
function onTouchBegin(event: TouchEvent) {
target.components.onInteract(4, event);
}
function onTouchMove(event: TouchEvent) {
target.components.onInteract(5, event);
}
function onTouchEnd(event: TouchEvent) {
target.components.onInteract(6, event);
}
function onTouchTap(event: TouchEvent) {
target.components.onInteract(7, event);
}
}
/**
* Created by rockyl on 2019-07-13.
*/
import {IBridge, IEntity} from "scilla";
import {implementLifecycle} from "./entity-proxy";
import {launch} from "./launcher";
export * from './entities'
export class Bridge implements IBridge{
createEntity(def, options?): IEntity {
let instance = new def();
if(options.name){
instance['name'] = options.name;
}
if(options.uuid){
instance['uuid'] = options.uuid;
}
implementLifecycle(instance);
return instance;
}
launch(options?) {
launch(options);
}
}
/**
* Created by rockyl on 2019-07-14.
*/
import {RootEntity} from "./entities";
import {Scene} from "./Scene";
import {Event} from "../2d/events/Event";
import {Stage} from "../2d/display/Stage";
import {RENDERER_TYPE, StageScaleMode} from "../2d/const";
import Container from "../2d/display/Container";
export function launch(options?) {
let stage = new Stage("cusEngine", 750, 1206, 60, StageScaleMode.FIXED_WIDTH, RENDERER_TYPE.CANVAS);
Stage.flushAll(0);
stage.addEventListener(Event.INIT_TO_STAGE, () => {
var container = new Container();
stage.addChild(container)
container.y = stage.viewRect.y;
let root = new RootEntity();
root.name = 'root';
container.addChild(root);
let scene = new Scene(root);
scene.setup();
});
}
var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0);
/**
* 用于创建webGL buffer,顶点和索引专用
* @class
* @memberof glCore
* @param gl {WebGLRenderingContext} The current WebGL rendering context
* @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat
* @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data
* @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW}
*/
export class GLBuffer {
/**
* 当前上下文
* @member {WebGLRenderingContext}
*/
gl: any;
/**
* The WebGL buffer, created upon instantiation
* @member {WebGLBuffer}
*/
buffer: any;
/**
* buffer类型
* 顶点或索引
* @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER}
*/
type: any;
/**
* The draw type of the buffer
* 绘制类型
* gl.STATIC_DRAW: 缓冲区的内容可能经常使用,而不会经常更改
* gl.DYNAMIC_DRAW: 缓冲区的内容可能经常被使用,并且经常更改
* gl.STREAM_DRAW: 缓冲区的内容可能不会经常使用,且不会经常更改
* @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW}
*/
drawType: any;
/**
* The data in the buffer, as a typed array
* 用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,
* 而是要通过类型数组对象或 DataView 对象来操作,
* 它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
* @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView}
*/
data;
/**
* 更新ID
*/
_updateID: number;
constructor(gl, type?, data?, drawType?) {
this.gl = gl;
this.buffer = gl.createBuffer();
this.type = type || gl.ARRAY_BUFFER;
this.drawType = drawType || gl.STATIC_DRAW;
this.data = EMPTY_ARRAY_BUFFER;
if (data) {
this.upload(data);
}
this._updateID = 0;
};
/**
* 上传数据
* Uploads the buffer to the GPU
* @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload
* @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract
* @param dontBind {Boolean} whether to bind the buffer before uploading it 是否不绑定buffer
*/
public upload(data, offset?:number, dontBind?:boolean) {
// todo - needed?
if (!dontBind) this.bind();
var gl = this.gl;
data = data || this.data;
offset = offset || 0;
if (this.data.byteLength >= data.byteLength) {
gl.bufferSubData(this.type, offset, data);
}
else {
gl.bufferData(this.type, data, this.drawType);
}
this.data = data;
};
/**
* Binds the buffer
* 状态机接下来使用的buffer
*
*/
public bind() {
var gl = this.gl;
gl.bindBuffer(this.type, this.buffer);
};
/**
* Destroys the buffer
*
*/
public destroy = function () {
this.gl.deleteBuffer(this.buffer);
};
/**
* 创建顶点缓存
* @param gl
* @param data
* @param drawType
*/
public static createVertexBuffer(gl, data?, drawType?) {
return new GLBuffer(gl, gl.ARRAY_BUFFER, data, drawType);
};
/**
* 创建索引缓存
* @param gl
* @param data
* @param drawType
*/
public static createIndexBuffer(gl, data?, drawType?) {
return new GLBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType);
};
public static create(gl, type, data, drawType) {
return new GLBuffer(gl, type, data, drawType);
};
}
import { GLTexture } from './GLTexture';
/**
* Helper class to create a webGL Framebuffer
* 帧缓存,暂时不使用,renderTarget里用,主要用于滤镜处理
*
* @class
* @memberof glCore
* @param gl {WebGLRenderingContext} The current WebGL rendering context
* @param width {Number} the width of the drawing area of the frame buffer
* @param height {Number} the height of the drawing area of the frame buffer
*/
export class GLFramebuffer {
gl: any;
framebuffer: any;
stencil: any;
texture: any;
width: any;
height: any;
constructor(gl, width, height) {
/**
* The current WebGL rendering context
*
* @member {WebGLRenderingContext}
*/
this.gl = gl;
/**
* The frame buffer
*
* @member {WebGLFramebuffer}
*/
this.framebuffer = gl.createFramebuffer();
/**
* The stencil buffer
*
* @member {WebGLRenderbuffer}
*/
this.stencil = null;
/**
* The stencil buffer
*
* @member {glCore.GLTexture}
*/
this.texture = null;
/**
* The width of the drawing area of the buffer
*
* @member {Number}
*/
this.width = width || 100;
/**
* The height of the drawing area of the buffer
*
* @member {Number}
*/
this.height = height || 100;
};
/**
* Adds a texture to the frame buffer
* @param texture {glCore.GLTexture}
*/
public enableTexture(texture) {
var gl = this.gl;
this.texture = texture || new GLTexture(gl);
this.texture.bind();
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
this.bind();
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture.texture, 0);
};
/**
* Initialises the stencil buffer
*/
public enableStencil() {
if (this.stencil) return;
var gl = this.gl;
this.stencil = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil);
// TODO.. this is depth AND stencil?
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, this.stencil);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, this.width, this.height);
};
/**
* Erases the drawing area and fills it with a colour
* @param r {Number} the red value of the clearing colour
* @param g {Number} the green value of the clearing colour
* @param b {Number} the blue value of the clearing colour
* @param a {Number} the alpha value of the clearing colour
*/
public clear(r, g, b, a) {
this.bind();
var gl = this.gl;
gl.clearColor(r, g, b, a);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
};
/**
* Binds the frame buffer to the WebGL context
*/
public bind() {
var gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
};
/**
* Unbinds the frame buffer to the WebGL context
*/
public unbind() {
var gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
};
/**
* Resizes the drawing area of the buffer to the given width and height
* @param width {Number} the new width
* @param height {Number} the new height
*/
public resize(width, height) {
var gl = this.gl;
this.width = width;
this.height = height;
if (this.texture) {
this.texture.uploadData(null, width, height);
}
if (this.stencil) {
// update the stencil buffer width and height
gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencil);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height);
}
};
/**
* Destroys this buffer
*/
public destroy() {
var gl = this.gl;
//TODO
if (this.texture) {
this.texture.destroy();
}
gl.deleteFramebuffer(this.framebuffer);
this.gl = null;
this.stencil = null;
this.texture = null;
};
/**
* Creates a frame buffer with a texture containing the given data
* @static
* @param gl {WebGLRenderingContext} The current WebGL rendering context
* @param width {Number} the width of the drawing area of the frame buffer
* @param height {Number} the height of the drawing area of the frame buffer
* @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data
*/
public static createRGBA(gl, width, height, data?) {
var texture = GLTexture.fromData(gl, null, width, height);
texture.enableNearestScaling();
texture.enableWrapClamp();
//now create the framebuffer object and attach the texture to it.
var fbo = new GLFramebuffer(gl, width, height);
fbo.enableTexture(texture);
//fbo.enableStencil(); // get this back on soon!
//fbo.enableStencil(); // get this back on soon!
fbo.unbind();
return fbo;
};
/**
* Creates a frame buffer with a texture containing the given data
* @static
* @param gl {WebGLRenderingContext} The current WebGL rendering context
* @param width {Number} the width of the drawing area of the frame buffer
* @param height {Number} the height of the drawing area of the frame buffer
* @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data
*/
public static createFloat32(gl, width, height, data) {
// create a new texture..
var texture = GLTexture.fromData(gl, data, width, height);
texture.enableNearestScaling();
texture.enableWrapClamp();
//now create the framebuffer object and attach the texture to it.
var fbo = new GLFramebuffer(gl, width, height);
fbo.enableTexture(texture);
fbo.unbind();
return fbo;
};
}
import { compileProgram } from './shader/compileProgram';
import { extractAttributes } from './shader/extractAttributes';
import { extractUniforms } from './shader/extractUniforms';
import { setPrecision } from './shader/setPrecision';
import { generateUniformAccessObject } from './shader/generateUniformAccessObject';
/**
* Helper class to create a webGL Shader
* 创建webgl shader用,里面主要用到attributes和uniforms
* @class
* @memberof glCore
* @param gl {WebGLRenderingContext}
* @param vertexSrc {string|string[]} The vertex shader source as an array of strings.
* @param fragmentSrc {string|string[]} The fragment shader source as an array of strings.
* @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'.
* @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1}
*/
export class GLShader {
/**
* The current WebGL rendering context
* @member {WebGLRenderingContext}
*/
gl: WebGLRenderingContext;
/**
* The shader program
* @member {WebGLProgram}
*/
program: WebGLProgram;
/**
* The attributes of the shader as an object containing the following properties
* {
* type,
* size,
* location,
* pointer
* }
* @member {Object}
*/
attributes: any;
uniformData: any;
/**
* The uniforms of the shader as an object containing the following properties
* {
* gl,
* data
* }
* @member {Object}
*/
uniforms: any;
constructor(
gl:WebGLRenderingContext,
vertexSrc:string,
fragmentSrc:string,
precision?:string,
attributeLocations?) {
this.gl = gl;
if (precision) {
vertexSrc = setPrecision(vertexSrc, precision);
fragmentSrc = setPrecision(fragmentSrc, precision);
}
// First compile the program..
this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations);
// next extract the attributes
this.attributes = extractAttributes(gl, this.program);
this.uniformData = extractUniforms(gl, this.program);
this.uniforms = generateUniformAccessObject(gl, this.uniformData);
};
/**
* Uses this shader
* 状态机当前使用的shader
* @return {glCore.GLShader} Returns itself.
*/
public bind() {
this.gl.useProgram(this.program);
return this;
};
/**
* Destroys this shader
* TODO
*/
public destroy() {
this.attributes = null;
this.uniformData = null;
this.uniforms = null;
var gl = this.gl;
gl.deleteProgram(this.program);
};
}
/**
* Helper class to create a WebGL Texture
* 用于创建WebGL Texture
* @class
* @memberof glCore
* @param gl {WebGLRenderingContext} The current WebGL context
* @param width {number} the width of the texture
* @param height {number} the height of the texture
* @param format {number} the pixel format of the texture. defaults to gl.RGBA
* @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE
*/
export class GLTexture {
/**
* 当前上下文
* The current WebGL rendering context
*/
gl: WebGLRenderingContext;
texture: WebGLTexture;
/**
* If mipmapping was used for this texture, enable and disable with enableMipmap()
* 是否对纹理进行存储缩小的各种尺寸纹理,比如原图1024*1024,存储512*512,256*256,128*128等一直到1*1;为了纹理的缩放时处理,是取相邻或线性插值
*/
mipmap: boolean;
/**
* Set to true to enable pre-multiplied alpha
* 设置纹理预乘透明值,为true,https://blog.csdn.net/mydreamremindme/article/details/50817294
*/
premultiplyAlpha;
/**
* 纹理宽度
*/
width: number;
/**
* 纹理高度
*/
height: number;
/**
* {number} the pixel format of the texture. defaults to gl.RGBA
* 纹理格式,默认gl.RGBA 还有gl.RGB
*/
format: any;
/**
* {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE
* 纹理类型,默认gl.UNSIGNED_BYTE //https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/texImage2D
*/
type: any;
constructor(gl: WebGLRenderingContext, width?: number, height?: number, format?, type?) {
this.gl = gl;
this.texture = gl.createTexture();
// some settings..
this.mipmap = false;
this.premultiplyAlpha = false;
this.width = width || -1;
this.height = height || -1;
/**
* The pixel format of the texture. defaults to gl.RGBA
*
* @member {Number}
*/
this.format = format || gl.RGBA;
/**
* The gl type of the texture. defaults to gl.UNSIGNED_BYTE
*
* @member {Number}
*/
this.type = type || gl.UNSIGNED_BYTE;
};
/**
* Uploads this texture to the GPU
* GPU存储纹理数据
* @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture
*/
public upload(source) {
this.bind();
var gl = this.gl;
//设置是否对纹理进行预乘透明通道
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha);
var newWidth = source.videoWidth || source.width;
var newHeight = source.videoHeight || source.height;
if (newHeight !== this.height || newWidth !== this.width) {
//https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/texImage2D
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source);
}
else {
//https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source);
}
// if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect.
this.width = newWidth;
this.height = newHeight;
};
/**
* Use a data source and uploads this texture to the GPU
* 数据类型的纹理
* @param data {TypedArray} the data to upload to the texture
* @param width {number} the new width of the texture
* @param height {number} the new height of the texture
*/
public uploadData = function (data, width, height) {
this.bind();
var gl = this.gl;
if (data instanceof Float32Array) {
if (!FLOATING_POINT_AVAILABLE) {
var ext = gl.getExtension("OES_texture_float");
if (ext) {
FLOATING_POINT_AVAILABLE = true;
}
else {
throw new Error('floating point textures not available');
}
}
this.type = gl.FLOAT;
}
else {
// TODO support for other types
this.type = this.type || gl.UNSIGNED_BYTE;
}
// what type of data?
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha);
if (width !== this.width || height !== this.height) {
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null);
}
else {
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null);
}
this.width = width;
this.height = height;
// texSubImage2D
};
/**
* Binds the texture
* 绑定纹理,不传location表示不激活额外纹理,绑定的纹理位置与原状态相同
* @param location
*/
public bind(location?: number) {
var gl = this.gl;
if (location !== undefined) {
gl.activeTexture(gl.TEXTURE0 + location);
}
gl.bindTexture(gl.TEXTURE_2D, this.texture);
};
/**
* Unbinds the texture
* 解除纹理绑定,解除位置与原状态相同
*/
public unbind() {
var gl = this.gl;
gl.bindTexture(gl.TEXTURE_2D, null);
};
/**
* @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation
* 缩小的纹理像素 按线性插值,还是按钮邻近原则
*/
public minFilter(linear: boolean) {
var gl = this.gl;
this.bind();
if (this.mipmap) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST);
}
else {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST);
}
};
/**
* @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation
* 放大的纹理像素 按线性插值,还是按钮邻近原则
*/
public magFilter(linear: boolean) {
var gl = this.gl;
this.bind();
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST);
};
/**
* Enables mipmapping
* 生成缩小的纹理集,只能在图片宽高满足2的指数时使用
*/
public enableMipmap() {
var gl = this.gl;
this.bind();
this.mipmap = true;
gl.generateMipmap(gl.TEXTURE_2D);
};
/**
* Enables linear filtering
* 设置线性
*/
public enableLinearScaling() {
this.minFilter(true);
this.magFilter(true);
};
/**
* Enables nearest neighbour interpolation
* 设置邻近
*/
public enableNearestScaling() {
this.minFilter(false);
this.magFilter(false);
};
/**
* Enables clamping on the texture so WebGL will not repeat it
* 如果纹理不满足2的指数时必设,以边缘像素延申
*/
public enableWrapClamp() {
var gl = this.gl;
this.bind();
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
};
/**
* Enable tiling on the texture
* 允许纹理重复,地砖模式
*/
public enableWrapRepeat() {
var gl = this.gl;
this.bind();
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
};
/**
* 镜像形式重复
*/
public enableWrapMirrorRepeat() {
var gl = this.gl;
this.bind();
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
};
/**
* Destroys this texture
*/
public destroy() {
var gl = this.gl;
//TODO
gl.deleteTexture(this.texture);
};
/**
* 从图片数据创建纹理
* @static
* @param gl {WebGLRenderingContext} The current WebGL context
* @param source {HTMLImageElement|ImageData} the source image of the texture
* @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha
*/
public static fromSource(gl: WebGLRenderingContext, source: HTMLImageElement | ImageData, premultiplyAlpha?: boolean) {
var texture: GLTexture = new GLTexture(gl);
texture.premultiplyAlpha = premultiplyAlpha || false;
texture.upload(source);
return texture;
};
/**
* @static
* @param gl {WebGLRenderingContext} The current WebGL context
* @param data {TypedArray} the data to upload to the texture
* @param width {number} the new width of the texture
* @param height {number} the new height of the texture
*/
public static fromData(gl, data, width, height) {
//console.log(data, width, height);
var texture = new GLTexture(gl);
texture.uploadData(data, width, height);
return texture;
};
}
var FLOATING_POINT_AVAILABLE = false;
// state object//
import { setVertexAttribArrays } from './setVertexAttribArrays';
/**
* Helper class to work with WebGL VertexArrayObjects (vaos)
* Only works if WebGL extensions are enabled (they usually are)
* 核心类VAOs
* @class
* @memberof glCore
* @param gl {WebGLRenderingContext} The current WebGL rendering context
*/
export class VertexArrayObject {
nativeVaoExtension: any;
nativeState: any;
nativeVao: any;
/**
* 当前上下文
*/
gl: WebGLRenderingContext;
/**
* An array of attributes
* attributes数组
*/
attributes: any[];
/**
* 索引buffer
* @member {GLBuffer}
*/
indexBuffer: any;
/**
* A boolean flag
*/
dirty: boolean;
constructor(gl: WebGLRenderingContext, state) {
this.nativeVaoExtension = null;
//不要求必须使用原生时,基本都支持扩展
if (!VertexArrayObject.FORCE_NATIVE) {
this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') ||
gl.getExtension('MOZ_OES_vertex_array_object') ||
gl.getExtension('WEBKIT_OES_vertex_array_object');
}
this.nativeState = state;
if (this.nativeVaoExtension) {
this.nativeVao = this.nativeVaoExtension.createVertexArrayOES();
var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
// VAO - overwrite the state..
this.nativeState = {
tempAttribState: new Array(maxAttribs),
attribState: new Array(maxAttribs)
};
}
this.gl = gl;
this.attributes = [];
this.indexBuffer = null;
this.dirty = false;
};
/**
* Binds the buffer
* 绑定数据
*/
public bind() {
if (this.nativeVao) {
this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao);
if (this.dirty) {
this.dirty = false;
this.activate();
return this;
}
if (this.indexBuffer) {
this.indexBuffer.bind();
}
}
else {
this.activate();
}
return this;
};
/**
* Unbinds the buffer
* 解绑数据
*/
public unbind() {
if (this.nativeVao) {
this.nativeVaoExtension.bindVertexArrayOES(null);
}
return this;
};
/**
* Uses this vao
* 激活vao
*/
public activate() {
var gl = this.gl;
var lastBuffer = null;
for (var i = 0; i < this.attributes.length; i++) {
var attrib = this.attributes[i];
if (lastBuffer !== attrib.buffer) {
attrib.buffer.bind();
lastBuffer = attrib.buffer;
}
gl.vertexAttribPointer(attrib.attribute.location,
attrib.attribute.size,
attrib.type || gl.FLOAT,
attrib.normalized || false,
attrib.stride || 0,
attrib.start || 0);
}
setVertexAttribArrays(gl, this.attributes, this.nativeState);
if (this.indexBuffer) {
this.indexBuffer.bind();
}
return this;
};
/**
* 添加attribute
* @param buffer {gl.GLBuffer}
* @param attribute {*}
* @param type {String}
* @param normalized {Boolean}
* @param stride {Number}
* @param start {Number}
*/
public addAttribute(buffer, attribute, type?, normalized?, stride?, start?) {
this.attributes.push({
buffer: buffer,
attribute: attribute,
location: attribute.location,
type: type || this.gl.FLOAT,
normalized: normalized || false,
stride: stride || 0,
start: start || 0
});
this.dirty = true;
return this;
};
/**
* 添加索引数据
* @param buffer {gl.GLBuffer}
*/
public addIndex(buffer/*, options*/) {
this.indexBuffer = buffer;
this.dirty = true;
return this;
};
/**
* Unbinds this vao and disables it
* 解绑废弃vao
*/
public clear() {
// var gl = this.gl;
// TODO - should this function unbind after clear?
// for now, no but lets see what happens in the real world!
if (this.nativeVao) {
this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao);
}
this.attributes.length = 0;
this.indexBuffer = null;
return this;
};
/**
* 执行绘制
* @param type {Number} gl.TRIANGLES\gl.TRIANGLE_STRIP等
* @param size {Number} 个数
* @param start {Number} 偏移
*/
public draw(type, size?, start?) {
var gl = this.gl;
if (this.indexBuffer) {
//有索引 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements
gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2);
}
else {
//无索引 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawArrays
// TODO need a better way to calculate size..
gl.drawArrays(type, start || 0, size || this.getSize());
}
return this;
};
/**
* Destroy this vao
*/
public destroy() {
// lose references
this.gl = null;
this.indexBuffer = null;
this.attributes = null;
this.nativeState = null;
if (this.nativeVao) {
this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao);
}
this.nativeVaoExtension = null;
this.nativeVao = null;
};
public getSize() {
var attrib = this.attributes[0];
return attrib.buffer.data.length / ((attrib.stride / 4) || attrib.attribute.size);
};
/**
* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!)
* If you find on older devices that things have gone a bit weird then set this to true.
*/
/**
* Lets the VAO know if you should use the WebGL extension or the native methods.
* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!)
* If you find on older devices that things have gone a bit weird then set this to true.
* @static
* @property {Boolean} FORCE_NATIVE
*/
public static FORCE_NATIVE = false;
}
import { createContext } from './createContext';
const fragTemplate = [
'precision mediump float;',
'void main(void){',
'float test = 0.1;',
'%forloop%',
'gl_FragColor = vec4(0.0);',
'}',
].join('\n');
/**
* 检查最大纹理数是否合法
* @param maxIfs
* @param gl
*/
export function checkMaxIfStatementsInShader(maxIfs:number, gl:WebGLRenderingContext) {
const createTempContext = !gl;
// @if DEBUG
if (maxIfs === 0) {
throw new Error('Invalid value of `0` passed to `checkMaxIfStatementsInShader`');
}
// @endif
if (createTempContext) {
const tinyCanvas = document.createElement('canvas');
tinyCanvas.width = 1;
tinyCanvas.height = 1;
gl = createContext(tinyCanvas);
}
const shader = gl.createShader(gl.FRAGMENT_SHADER);
while (true) // eslint-disable-line no-constant-condition
{
const fragmentSrc = fragTemplate.replace(/%forloop%/gi, generateIfTestSrc(maxIfs));
gl.shaderSource(shader, fragmentSrc);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
maxIfs = (maxIfs / 2) | 0;
}
else {
// valid!
break;
}
}
if (createTempContext) {
// get rid of context
if (gl.getExtension('WEBGL_lose_context')) {
gl.getExtension('WEBGL_lose_context').loseContext();
}
}
return maxIfs;
}
function generateIfTestSrc(maxIfs) {
let src = '';
for (let i = 0; i < maxIfs; ++i) {
if (i > 0) {
src += '\nelse ';
}
if (i < maxIfs - 1) {
src += `if(test == ${i}.0){}`;
}
}
return src;
}
/**
* Helper class to create a webGL Context
* 创建webgl上下文
* @param canvas {HTMLCanvasElement} the canvas element that we will get the context from
* @param options {Object} An options object that gets passed in to the canvas element containing the context attributes,
* see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available
* @return {WebGLRenderingContext} the WebGL context
*/
export function createContext(canvas:HTMLCanvasElement, options?:any):WebGLRenderingContext {
var gl = canvas.getContext('webgl', options) ||
canvas.getContext('experimental-webgl', options);
if (!gl) {
// fail, not able to get a context
throw new Error('This browser does not support webGL. Try using the canvas renderer');
}
return gl;
};
\ No newline at end of file
// export { default as GroupD8 } from './GroupD8';
export { GLTexture } from './GLTexture';
export { GLBuffer } from './GLBuffer';
export { VertexArrayObject } from './VertexArrayObject';
export { GLFramebuffer } from "./GLFramebuffer";
export { GLShader } from "./GLShader";
// export { BatchBuffer } from "./BatchBuffer";
export {createContext}from "./createContext";
// export { TextureUvs } from "./TextureUvs";
export { checkMaxIfStatementsInShader } from "./checkMaxIfStatementsInShader"
// var GL_MAP = {};
/**
* 连接attribute变量与分配给它的缓冲区对象,两种方式,与顶点着色器通信核心方法
* @param gl {WebGLRenderingContext} The current WebGL context
* @param attribs {*}
* @param state {*}
*/
export function setVertexAttribArrays(gl, attribs, state?) {
var i;
if (state) {
var tempAttribState = state.tempAttribState,
attribState = state.attribState;
for (i = 0; i < tempAttribState.length; i++) {
tempAttribState[i] = false;
}
// set the new attribs
for (i = 0; i < attribs.length; i++) {
tempAttribState[attribs[i].attribute.location] = true;
}
for (i = 0; i < attribState.length; i++) {
if (attribState[i] !== tempAttribState[i]) {
attribState[i] = tempAttribState[i];
if (state.attribState[i]) {
gl.enableVertexAttribArray(i);
}
else {
gl.disableVertexAttribArray(i);
}
}
}
}
else {
for (i = 0; i < attribs.length; i++) {
var attrib = attribs[i];
gl.enableVertexAttribArray(attrib.attribute.location);
}
}
};
\ No newline at end of file
/**
* 编译着色器程序
* @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram}
* @param vertexSrc The vertex shader source as an array of strings.顶点着色器
* @param fragmentSrc The fragment shader source as an array of strings.片元着色器
* @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations
* @return {WebGLProgram} the shader program 返回着色器程序
*/
export function compileProgram(gl:WebGLRenderingContext, vertexSrc:string, fragmentSrc:string, attributeLocations?:any):WebGLProgram {
var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);
var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);
var program = gl.createProgram();
gl.attachShader(program, glVertShader);
gl.attachShader(program, glFragShader);
// optionally, set the attributes manually for the program rather than letting WebGL decide..
if (attributeLocations) {
for (var i in attributeLocations) {
gl.bindAttribLocation(program, attributeLocations[i], i);
}
}
gl.linkProgram(program);
// if linking fails, then log and cleanup
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Error: Could not initialize shader.');
console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS));
console.error('gl.getError()', gl.getError());
// if there is a program info log, log it
if (gl.getProgramInfoLog(program) !== '') {
console.warn('Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program));
}
gl.deleteProgram(program);
program = null;
}
// clean up some shaders
gl.deleteShader(glVertShader);
gl.deleteShader(glFragShader);
return program;
};
/**
* 创建shader
* @private
* @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram}
* @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER
* @param vertexSrc {string|string[]} The vertex shader source as an array of strings.
* @return {WebGLShader} the shader
*/
var compileShader = function (gl:WebGLRenderingContext, type:number, src:string):WebGLShader {
var shader = gl.createShader(type);
gl.shaderSource(shader, src);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.log(gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
/**
* @class
* @memberof glCore.shader
* @param type {String} Type of value
* @param size {Number}
*/
export function defaultValue(type:string, size:number) {
switch (type) {
case 'float':
return 0;
case 'vec2':
return new Float32Array(2 * size);
case 'vec3':
return new Float32Array(3 * size);
case 'vec4':
return new Float32Array(4 * size);
case 'int':
case 'sampler2D':
return 0;
case 'ivec2':
return new Int32Array(2 * size);
case 'ivec3':
return new Int32Array(3 * size);
case 'ivec4':
return new Int32Array(4 * size);
case 'bool':
return false;
case 'bvec2':
return booleanArray(2 * size);
case 'bvec3':
return booleanArray(3 * size);
case 'bvec4':
return booleanArray(4 * size);
case 'mat2':
return new Float32Array([1, 0,
0, 1]);
case 'mat3':
return new Float32Array([1, 0, 0,
0, 1, 0,
0, 0, 1]);
case 'mat4':
return new Float32Array([1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1]);
}
};
var booleanArray = function (size) {
var array = new Array(size);
for (var i = 0; i < array.length; i++) {
array[i] = false;
}
return array;
};
import { mapType } from './mapType';
import { mapSize } from './mapSize';
/**
* Extracts the attributes获取attributes属性
* @class
* @memberof glCore.shader
* @param gl {WebGLRenderingContext} The current WebGL rendering context
* @param program {WebGLProgram} The shader program to get the attributes from
* @return attributes {Object}
*/
export function extractAttributes(gl: WebGLRenderingContext, program: WebGLProgram) {
var attributes = {};
var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
for (var i = 0; i < totalAttributes; i++) {
var attribData = gl.getActiveAttrib(program, i);
var type = mapType(gl, attribData.type);
attributes[attribData.name] = {
type: type,
size: mapSize(type),
location: gl.getAttribLocation(program, attribData.name),
//TODO - make an attribute object
pointer: function (type = gl.FLOAT, normalized = false, stride = 0, start = 0) {
// console.log(this.location)
gl.vertexAttribPointer(this.location, this.size, type, normalized, stride, start);
}
};
}
return attributes;
};
import { mapType } from './mapType';
import { defaultValue } from './defaultValue';
/**
* Extracts the uniforms 获取uniforms属性
* @class
* @memberof glCore.shader
* @param gl {WebGLRenderingContext} The current WebGL rendering context
* @param program {WebGLProgram} The shader program to get the uniforms from
* @return uniforms {Object}
*/
export function extractUniforms(gl: WebGLRenderingContext, program: WebGLProgram) {
var uniforms = {};
var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (var i = 0; i < totalUniforms; i++) {
var uniformData = gl.getActiveUniform(program, i);
var name = uniformData.name.replace(/\[.*?\]/, "");
var type = mapType(gl, uniformData.type);
uniforms[name] = {
type: type,
size: uniformData.size,
location: gl.getUniformLocation(program, name),
value: defaultValue(type, uniformData.size)
};
}
return uniforms;
};
/**
* 使uniform能通过赋值直接和着色器程序通信
* @class
* @memberof glCore.shader
* @param gl {WebGLRenderingContext} The current WebGL rendering context
* @param uniforms {Array} @mat ?
* @return attributes {Object}
*/
export function generateUniformAccessObject(gl:WebGLRenderingContext, uniformData) {
// this is the object we will be sending back.
// an object hierachy will be created for structs
var uniforms = { data: {} };
uniforms["gl"] = gl;
var uniformKeys = Object.keys(uniformData);
for (var i = 0; i < uniformKeys.length; i++) {
var fullName = uniformKeys[i];
var nameTokens = fullName.split('.');
var name = nameTokens[nameTokens.length - 1];
var uniformGroup = getUniformGroup(nameTokens, uniforms);
var uniform = uniformData[fullName];
uniformGroup.data[name] = uniform;
uniformGroup.gl = gl;
Object.defineProperty(uniformGroup, name, {
get: generateGetter(name),
set: generateSetter(name, uniform)
});
}
return uniforms;
};
var generateGetter = function (name) {
return function () {
return this.data[name].value;
};
};
var GLSL_SINGLE_SETTERS = {
float: function setSingleFloat(gl, location, value) { gl.uniform1f(location, value); },
vec2: function setSingleVec2(gl, location, value) { gl.uniform2f(location, value[0], value[1]); },
vec3: function setSingleVec3(gl, location, value) { gl.uniform3f(location, value[0], value[1], value[2]); },
vec4: function setSingleVec4(gl, location, value) { gl.uniform4f(location, value[0], value[1], value[2], value[3]); },
int: function setSingleInt(gl, location, value) { gl.uniform1i(location, value); },
ivec2: function setSingleIvec2(gl, location, value) { gl.uniform2i(location, value[0], value[1]); },
ivec3: function setSingleIvec3(gl, location, value) { gl.uniform3i(location, value[0], value[1], value[2]); },
ivec4: function setSingleIvec4(gl, location, value) { gl.uniform4i(location, value[0], value[1], value[2], value[3]); },
bool: function setSingleBool(gl, location, value) { gl.uniform1i(location, value); },
bvec2: function setSingleBvec2(gl, location, value) { gl.uniform2i(location, value[0], value[1]); },
bvec3: function setSingleBvec3(gl, location, value) { gl.uniform3i(location, value[0], value[1], value[2]); },
bvec4: function setSingleBvec4(gl, location, value) { gl.uniform4i(location, value[0], value[1], value[2], value[3]); },
mat2: function setSingleMat2(gl, location, value) { gl.uniformMatrix2fv(location, false, value); },
mat3: function setSingleMat3(gl, location, value) { gl.uniformMatrix3fv(location, false, value); },
mat4: function setSingleMat4(gl, location, value) { gl.uniformMatrix4fv(location, false, value); },
sampler2D: function setSingleSampler2D(gl, location, value) { gl.uniform1i(location, value); },
};
var GLSL_ARRAY_SETTERS = {
float: function setFloatArray(gl, location, value) { gl.uniform1fv(location, value); },
vec2: function setVec2Array(gl, location, value) { gl.uniform2fv(location, value); },
vec3: function setVec3Array(gl, location, value) { gl.uniform3fv(location, value); },
vec4: function setVec4Array(gl, location, value) { gl.uniform4fv(location, value); },
int: function setIntArray(gl, location, value) { gl.uniform1iv(location, value); },
ivec2: function setIvec2Array(gl, location, value) { gl.uniform2iv(location, value); },
ivec3: function setIvec3Array(gl, location, value) { gl.uniform3iv(location, value); },
ivec4: function setIvec4Array(gl, location, value) { gl.uniform4iv(location, value); },
bool: function setBoolArray(gl, location, value) { gl.uniform1iv(location, value); },
bvec2: function setBvec2Array(gl, location, value) { gl.uniform2iv(location, value); },
bvec3: function setBvec3Array(gl, location, value) { gl.uniform3iv(location, value); },
bvec4: function setBvec4Array(gl, location, value) { gl.uniform4iv(location, value); },
sampler2D: function setSampler2DArray(gl, location, value) { gl.uniform1iv(location, value); },
};
function generateSetter(name, uniform) {
return function (value) {
this.data[name].value = value;
var location = this.data[name].location;
if (uniform.size === 1) {
GLSL_SINGLE_SETTERS[uniform.type](this.gl, location, value);
}
else {
// glslSetArray(gl, location, type, value) {
GLSL_ARRAY_SETTERS[uniform.type](this.gl, location, value);
}
};
}
function getUniformGroup(nameTokens, uniform) {
var cur = uniform;
for (var i = 0; i < nameTokens.length - 1; i++) {
var o = cur[nameTokens[i]] || { data: {} };
cur[nameTokens[i]] = o;
cur = o;
}
return cur;
}
export { compileProgram } from './compileProgram';
export { defaultValue } from './defaultValue';
export { extractAttributes } from './extractAttributes';
export { extractUniforms } from './extractUniforms';
export { generateUniformAccessObject } from './generateUniformAccessObject';
export { setPrecision } from './setPrecision';
export { mapSize } from './mapSize';
export { mapType } from './mapType';
/**
* @class
* @memberof glCore.shader
* @param type {String}
* @return {Number}
*/
export function mapSize(type: string): number {
return GLSL_TO_SIZE[type];
};
var GLSL_TO_SIZE = {
'float': 1,
'vec2': 2,
'vec3': 3,
'vec4': 4,
'int': 1,
'ivec2': 2,
'ivec3': 3,
'ivec4': 4,
'bool': 1,
'bvec2': 2,
'bvec3': 3,
'bvec4': 4,
'mat2': 4,
'mat3': 9,
'mat4': 16,
'sampler2D': 1
};
\ No newline at end of file
export function mapType(gl, type) {
if (!GL_TABLE) {
var typeNames = Object.keys(GL_TO_GLSL_TYPES);
GL_TABLE = {};
for (var i = 0; i < typeNames.length; ++i) {
var tn = typeNames[i];
GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn];
}
}
return GL_TABLE[type];
};
var GL_TABLE = null;
var GL_TO_GLSL_TYPES = {
'FLOAT': 'float',
'FLOAT_VEC2': 'vec2',
'FLOAT_VEC3': 'vec3',
'FLOAT_VEC4': 'vec4',
'INT': 'int',
'INT_VEC2': 'ivec2',
'INT_VEC3': 'ivec3',
'INT_VEC4': 'ivec4',
'BOOL': 'bool',
'BOOL_VEC2': 'bvec2',
'BOOL_VEC3': 'bvec3',
'BOOL_VEC4': 'bvec4',
'FLOAT_MAT2': 'mat2',
'FLOAT_MAT3': 'mat3',
'FLOAT_MAT4': 'mat4',
'SAMPLER_2D': 'sampler2D'
};
/**
* 给shader source设置precision
* Sets the float precision on the shader. If the precision is already present this function will do nothing
* @param {string} src the shader source
* @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'.
*
* @return {string} modified shader source
*/
export function setPrecision(src: string, precision: string): string {
if (src.substring(0, 9) !== 'precision') {
return 'precision ' + precision + ' float;\n' + src;
}
return src;
};
\ No newline at end of file
/**
* Created by rockyl on 2019-07-16.
*/
export * from './editor-bridge'
\ No newline at end of file
{
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"removeComments": true,
"sourceMap": true,
"noImplicitAny": false,
"noEmitOnError": false,
"downlevelIteration": true,
"lib": [
"es5",
"es6",
"dom",
"es2015.promise"
],
"baseUrl": "./",
"paths": {
"scilla": [
"node_modules/scilla/src"
]
}
},
"include": [
"src",
"libs"
],
"compileOnSave": false
}
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/code-frame@^7.0.0":
version "7.0.0"
resolved "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
integrity sha1-BuKrGb21NThVWaq7W6WXKUgoAPg=
dependencies:
"@babel/highlight" "^7.0.0"
"@babel/highlight@^7.0.0":
version "7.5.0"
resolved "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540"
integrity sha1-VtETEr2SSPphlZHQJHK+boyzJUA=
dependencies:
chalk "^2.0.0"
esutils "^2.0.2"
js-tokens "^4.0.0"
"@types/node@*":
version "12.6.2"
resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-12.6.2.tgz#a5ccec6abb6060d5f20d256fb03ed743e9774999"
integrity sha1-pczsartgYNXyDSVvsD7XQ+l3SZk=
"@types/resolve@0.0.8":
version "0.0.8"
resolved "https://registry.npm.taobao.org/@types/resolve/download/@types/resolve-0.0.8.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fresolve%2Fdownload%2F%40types%2Fresolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
integrity sha1-8mB00jjgJlnjI84aE9BB7uKA4ZQ=
dependencies:
"@types/node" "*"
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=
dependencies:
color-convert "^1.9.0"
builtin-modules@^3.1.0:
version "3.1.0"
resolved "https://registry.npm.taobao.org/builtin-modules/download/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
integrity sha1-qtl8FRMet2tltQ7yCOdYTNdqdIQ=
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.npm.taobao.org/chalk/download/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.npm.taobao.org/color-convert/download/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npm.taobao.org/color-name/download/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
commander@~2.20.0:
version "2.20.0"
resolved "https://registry.npm.taobao.org/commander/download/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha1-1YuytcHuj4ew00ACfp6U4iLFpCI=
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fescape-string-regexp%2Fdownload%2Fescape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.npm.taobao.org/estree-walker/download/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
integrity sha1-UwSRQ/QMbrkYsjZx0f4yGfOhs2I=
esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.npm.taobao.org/esutils/download/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/is-module/download/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz?cache=0&sync_timestamp=1562592096220&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fisarray%2Fdownload%2Fisarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
jest-worker@^24.0.0:
version "24.6.0"
resolved "https://registry.npm.taobao.org/jest-worker/download/jest-worker-24.6.0.tgz#7f81ceae34b7cde0c9827a6980c35b7cdc0161b3"
integrity sha1-f4HOrjS3zeDJgnppgMNbfNwBYbM=
dependencies:
merge-stream "^1.0.1"
supports-color "^6.1.0"
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npm.taobao.org/js-tokens/download/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha1-GSA/tZmR35jjoocFDUZHzerzJJk=
merge-stream@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/merge-stream/download/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=
dependencies:
readable-stream "^2.0.1"
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.npm.taobao.org/path-parse/download/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha1-eCDZsWEgzFXKmud5JoCufbptf+I=
readable-stream@^2.0.1:
version "2.3.6"
resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
resolve@^1.10.0, resolve@^1.11.1:
version "1.11.1"
resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e"
integrity sha1-6hDYEQN2mC/vV434/DC5rDCgej4=
dependencies:
path-parse "^1.0.6"
rollup-plugin-node-resolve@^5.2.0:
version "5.2.0"
resolved "https://registry.npm.taobao.org/rollup-plugin-node-resolve/download/rollup-plugin-node-resolve-5.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frollup-plugin-node-resolve%2Fdownload%2Frollup-plugin-node-resolve-5.2.0.tgz#730f93d10ed202473b1fb54a5997a7db8c6d8523"
integrity sha1-cw+T0Q7SAkc7H7VKWZen24xthSM=
dependencies:
"@types/resolve" "0.0.8"
builtin-modules "^3.1.0"
is-module "^1.0.0"
resolve "^1.11.1"
rollup-pluginutils "^2.8.1"
rollup-plugin-typescript@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/rollup-plugin-typescript/download/rollup-plugin-typescript-1.0.1.tgz#86565033b714c3d1f3aba510aad3dc519f7091e9"
integrity sha1-hlZQM7cUw9Hzq6UQqtPcUZ9wkek=
dependencies:
resolve "^1.10.0"
rollup-pluginutils "^2.5.0"
rollup-plugin-uglify@^6.0.2:
version "6.0.2"
resolved "https://registry.npm.taobao.org/rollup-plugin-uglify/download/rollup-plugin-uglify-6.0.2.tgz#681042cfdf7ea4e514971946344e1a95bc2772fe"
integrity sha1-aBBCz99+pOUUlxlGNE4albwncv4=
dependencies:
"@babel/code-frame" "^7.0.0"
jest-worker "^24.0.0"
serialize-javascript "^1.6.1"
uglify-js "^3.4.9"
rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1:
version "2.8.1"
resolved "https://registry.npm.taobao.org/rollup-pluginutils/download/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97"
integrity sha1-j6bdBpc0STjvJsLAnSSIzp4zzpc=
dependencies:
estree-walker "^0.6.1"
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha1-mR7GnSluAxN0fVm9/St0XDX4go0=
serialize-javascript@^1.6.1:
version "1.7.0"
resolved "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
integrity sha1-1uDfsqODKoyURo5usduX5VoZKmU=
source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha1-dHIq8y6WFOnCh6jQu95IteLxomM=
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=
dependencies:
safe-buffer "~5.1.0"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.npm.taobao.org/supports-color/download/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=
dependencies:
has-flag "^3.0.0"
supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
integrity sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=
dependencies:
has-flag "^3.0.0"
tslib@^1.10.0:
version "1.10.0"
resolved "https://registry.npm.taobao.org/tslib/download/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha1-w8GflZc/sKYpc/sJ2Q2WHuQ+XIo=
typescript@^3.5.3:
version "3.5.3"
resolved "https://registry.npm.taobao.org/typescript/download/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
integrity sha1-yDD2V/k/HqhGgZ6SkJL1/lmD6Xc=
uglify-js@^3.4.9:
version "3.6.0"
resolved "https://registry.npm.taobao.org/uglify-js/download/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
integrity sha1-cEaBNFxTqLIHn7bOwpSwXq0kL/U=
dependencies:
commander "~2.20.0"
source-map "~0.6.1"
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment