Commit d5c1a94f authored by wangjianfeng.yz's avatar wangjianfeng.yz

2.0.66

parent 14ed7b3c
...@@ -20,4 +20,5 @@ docs ...@@ -20,4 +20,5 @@ docs
adapters adapters
testThree testThree
README.md README.md
abandoned abandoned
\ No newline at end of file beta
\ No newline at end of file
export interface IContainer {
children: IContainer[]
name: string
}
function _getElementsByName(name: RegExp | string, root: IContainer, isRecursive?: boolean): IContainer[] {
if (!name || !root || !root.children || !root.children.length) return [];
let list: IContainer[] = [];
let rex: RegExp;
if (typeof (name) == "string") {
rex = new RegExp("^" + name + "$");
} else {
rex = name;
}
let child: IContainer;
for (let i = 0; i < root.children.length; i++) {
child = root.children[i];
if (rex.test(child.name)) {
list.push(child);
}
if (isRecursive) {
list.push(..._getElementsByName(rex, child, isRecursive));
}
}
return list;
}
\ No newline at end of file
declare namespace FYGE{export const VERSION = "2.0.65"; declare namespace FYGE{export const VERSION = "2.0.66";
export function cos(angle: number): number; export function cos(angle: number): number;
...@@ -8775,6 +8775,7 @@ export class ScrollView extends Container { ...@@ -8775,6 +8775,7 @@ export class ScrollView extends Container {
hasMomentum: boolean; hasMomentum: boolean;
/** /**
* 惯性的阻尼 * 惯性的阻尼
* 默认0.002
*/ */
damping: number; damping: number;
/** /**
...@@ -9462,6 +9463,7 @@ export interface ILottieKeyData { ...@@ -9462,6 +9463,7 @@ export interface ILottieKeyData {
export interface ILottieKeyAniData { export interface ILottieKeyAniData {
t: number; t: number;
s: number[]; s: number[];
e: number[];
i: { i: {
x: number | number[]; x: number | number[];
y: number | number[]; y: number | number[];
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
export const VERSION = "2.0.65"; export const VERSION = "2.0.66";
export function cos(angle: number): number; export function cos(angle: number): number;
...@@ -8775,6 +8775,7 @@ export class ScrollView extends Container { ...@@ -8775,6 +8775,7 @@ export class ScrollView extends Container {
hasMomentum: boolean; hasMomentum: boolean;
/** /**
* 惯性的阻尼 * 惯性的阻尼
* 默认0.002
*/ */
damping: number; damping: number;
/** /**
...@@ -9462,6 +9463,7 @@ export interface ILottieKeyData { ...@@ -9462,6 +9463,7 @@ export interface ILottieKeyData {
export interface ILottieKeyAniData { export interface ILottieKeyAniData {
t: number; t: number;
s: number[]; s: number[];
e: number[];
i: { i: {
x: number | number[]; x: number | number[];
y: number | number[]; y: number | number[];
......
{ {
"name": "fyge", "name": "fyge",
"version": "2.0.65", "version": "2.0.66",
"description": "canvas渲染引擎", "description": "canvas渲染引擎",
"main": "./build/fyge.min.js", "main": "./build/fyge.min.js",
"module": "./build/fyge.esm.js", "module": "./build/fyge.esm.js",
......
...@@ -592,12 +592,26 @@ ...@@ -592,12 +592,26 @@
Object3D的lookAt方法新增一个重载,可传xyz对象 Object3D的lookAt方法新增一个重载,可传xyz对象
Object3D的update方法去掉自身的visible判断 Object3D的update方法去掉自身的visible判断
2.0.66 FpsPanel的maxValue改成120
Button的构造函数添加提示,不希望被使用的类
Lottie添加e属性,当前关键帧的s不存在时取前一帧的e
ScrollView的damping默认值改成0.002
ScrollView的up时判断累计的速度绝对值是否大于0.02,大于时才缓冲
ScrollView修改了toMomentum的计算方式
ScrollView的_scrollTo缓动方法默认值改成Ease.quadOut
Object3D的destroy方法吧removeAllEventListener的操作放到removeChild之后,否则从scene移除的事件触发不了
getElementsByName到时要处理下
stage的鼠标事件,changedTouches考虑放开
three的wireframe会修改index,以后考虑
批处理着色器最后参考pixi的改
destroyed啥时候要处理下 destroyed啥时候要处理下
......
...@@ -16,7 +16,7 @@ export class FpsPanel extends Sprite { ...@@ -16,7 +16,7 @@ export class FpsPanel extends Sprite {
private GRAPH_WIDTH = 74 * this.PR; private GRAPH_WIDTH = 74 * this.PR;
private GRAPH_HEIGHT = 30 * this.PR; private GRAPH_HEIGHT = 30 * this.PR;
private GRAPH_SIZE = 74; private GRAPH_SIZE = 74;
private maxValue = 100; private maxValue = 120;
private min = Infinity; private min = Infinity;
private max = 0; private max = 0;
private items = []; private items = [];
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* @name VERSION * @name VERSION
* @type {string} * @type {string}
*/ */
export const VERSION = "2.0.65"; export const VERSION = "2.0.66";
/** /**
......
...@@ -5,12 +5,35 @@ import { MouseEvent } from "../events/MouseEvent"; ...@@ -5,12 +5,35 @@ import { MouseEvent } from "../events/MouseEvent";
import Texture from "../texture/Texture"; import Texture from "../texture/Texture";
import Tween from "../../tween/Tween"; import Tween from "../../tween/Tween";
let hasWarned: boolean;
export class Button extends Sprite { export class Button extends Sprite {
private textureUp: Texture; private textureUp: Texture;
private textureDown: Texture; private textureDown: Texture;
private textureDisable: Texture; private textureDisable: Texture;
constructor(tUp: Texture, tDown?: Texture, tDisable?: Texture) { constructor(tUp: Texture, tDown?: Texture, tDisable?: Texture) {
super(tUp); super(tUp);
if (!hasWarned) console.warn(`Class Button will be abandoned later,maybe you can:
function addClickZoom(dis: DisplayObject, zoomCenter: boolean = true) {
if (zoomCenter) {
dis.anchorX = dis.width / 2;
dis.anchorY = dis.height / 2;
}
dis.addEventListener("onMouseDown", _mouseEvent, dis)
.addEventListener("onMouseUp", _mouseEvent, dis)
.addEventListener("onMouseOut", _mouseEvent, dis);
let s = dis;
function _mouseEvent(e: MouseEvent) {
if (e.type == MouseEvent.MOUSE_DOWN) {
Tween.removeTweens(s);
Tween.get(s).to({ scaleX: 0.9, scaleY: 0.9 }, 50);
} else {
Tween.removeTweens(s);
Tween.get(s).to({ scaleX: 1, scaleY: 1 }, 50);
}
}
}`)
hasWarned = true;
this._instanceType = "Button"; this._instanceType = "Button";
this.textureUp = tUp; this.textureUp = tUp;
......
...@@ -173,6 +173,7 @@ export interface ILottieKeyData { ...@@ -173,6 +173,7 @@ export interface ILottieKeyData {
export interface ILottieKeyAniData { export interface ILottieKeyAniData {
t: number, t: number,
s: number[], s: number[],
e: number[],//s不存在时取前一帧数据的e
i: { x: number | number[], y: number | number[] }, i: { x: number | number[], y: number | number[] },
o: { x: number | number[], y: number | number[] }, o: { x: number | number[], y: number | number[] },
...@@ -274,13 +275,13 @@ class LottieBaseTrack extends HashObject implements IAnimationTrack { ...@@ -274,13 +275,13 @@ class LottieBaseTrack extends HashObject implements IAnimationTrack {
time = (this.loop.loopInOrOut == 1 ? loopIn : loopOut)(this.loop.type, this.loop.duration, this.times, time) time = (this.loop.loopInOrOut == 1 ? loopIn : loopOut)(this.loop.type, this.loop.duration, this.times, time)
} }
if (time <= this.times[0].t) return this.times[0].s; if (time <= this.times[0].t) return this.times[0].s;
if (time >= this.times[this.times.length - 1].t) return this.times[this.times.length - 1].s; if (time >= this.times[this.times.length - 1].t) return this.times[this.times.length - 1].s || this.times[this.times.length - 2].e;
//其他的计算补间了要,找前后两个索引 //其他的计算补间了要,找前后两个索引
var after = this.findIndex(time); var after = this.findIndex(time);
var before = after - 1; var before = after - 1;
var da = this.times[after]; var da = this.times[after];
var db = this.times[before]; var db = this.times[before];
var beforeValue = db.s, afterValue = da.s; var beforeValue = db.s, afterValue = da.s || db.e;
var value: number[] var value: number[]
//有路径的 //有路径的
if (db.to) { if (db.to) {
......
...@@ -126,8 +126,9 @@ export class ScrollView extends Container { ...@@ -126,8 +126,9 @@ export class ScrollView extends Container {
public hasMomentum: boolean = true; public hasMomentum: boolean = true;
/** /**
* 惯性的阻尼 * 惯性的阻尼
* 默认0.002
*/ */
public damping: number = 0.0006 public damping: number = 0.002
/** /**
* 0无交互状态 * 0无交互状态
* 1点击状态 * 1点击状态
...@@ -328,11 +329,14 @@ export class ScrollView extends Container { ...@@ -328,11 +329,14 @@ export class ScrollView extends Container {
totalW += w; totalW += w;
} }
s.velocitys.length = 0; s.velocitys.length = 0;
let momentumX = s.scrollWidth ? toMomentum(newX, sum.x / totalW, s.maxScrollX, s.damping) : { let sx = sum.x / totalW,
sy = sum.y / totalW;
let momentumX = (s.scrollWidth && Math.abs(sx) > 0.02) ? toMomentum(newX, sx, s.maxScrollX, s.damping) : {
destination: newX, destination: newX,
duration: 0 duration: 0
}; };
let momentumY = s.scrollHeight ? toMomentum(newY, sum.y / totalW, s.maxScrollY, s.damping) : { let momentumY = (s.scrollHeight && Math.abs(sy) > 0.02) ? toMomentum(newY, sy, s.maxScrollY, s.damping) : {
destination: newY, destination: newY,
duration: 0 duration: 0
}; };
...@@ -356,8 +360,8 @@ export class ScrollView extends Container { ...@@ -356,8 +360,8 @@ export class ScrollView extends Container {
private onEnterFrame(e: Event) { private onEnterFrame(e: Event) {
let s = this; let s = this;
let now = Date.now(); let now = Date.now();
//不清楚有啥用 //用户点击交互时需要加
if (s.lastTouchTime && now - s.lastTouchTime > 100 && now - s.lastTouchTime < 300) { if (s.mouseStatus && s.lastTouchTime && now - s.lastTouchTime > 100 && now - s.lastTouchTime < 300) {
s.pushVelocitys(0, 0); s.pushVelocitys(0, 0);
} }
if (!s.isRunning) return; if (!s.isRunning) return;
...@@ -457,7 +461,7 @@ export class ScrollView extends Container { ...@@ -457,7 +461,7 @@ export class ScrollView extends Container {
s.destX = x; s.destX = x;
s.destY = y; s.destY = y;
s.duration = time; s.duration = time;
s.easeFn = easeFn || Ease.circOut; s.easeFn = easeFn || Ease.quadOut;
s.isRunning = true; s.isRunning = true;
} }
} }
...@@ -559,6 +563,30 @@ function toMomentum( ...@@ -559,6 +563,30 @@ function toMomentum(
speed: number, speed: number,
lowerMargin: number, lowerMargin: number,
deceleration: number = 0.0006 deceleration: number = 0.0006
) {
//衰减到0的时间
let duration = Math.abs(speed) / deceleration;
let destination = current + duration * (speed / 2)
// console.log(current, speed)
if (destination < lowerMargin) {
destination = lowerMargin;
duration = Math.abs((destination - current) / (speed / 2));
}
else if (destination > 0) {
destination = 0;
duration = Math.abs(current / (speed / 2));
}
return {
destination,
duration
};
};
function toMomentum1(
current: number,
speed: number,
lowerMargin: number,
deceleration: number = 0.0006
) { ) {
let destination = current + (speed * speed) / (2 * deceleration) * (speed < 0 ? -1 : 1); let destination = current + (speed * speed) / (2 * deceleration) * (speed < 0 ? -1 : 1);
let duration = Math.abs(speed) / deceleration; let duration = Math.abs(speed) / deceleration;
...@@ -577,6 +605,38 @@ function toMomentum( ...@@ -577,6 +605,38 @@ function toMomentum(
}; };
}; };
function toMomentum2(
curPos: number,
pixelsPerMS: number,
maxPos: number,
damping: number
): { destination: number; duration: number } {
let absPixelsPerMS: number = Math.abs(pixelsPerMS);
let extraFricition: number = 0.95;
let duration: number = 0;
let friction: number = 0.998;
let minVelocity: number = 0.02;
let posTo: number = curPos + pixelsPerMS * 500;
if (posTo < maxPos) {
posTo = maxPos;
duration = Math.abs((posTo - curPos) / pixelsPerMS);
}
else if (posTo > 0) {
posTo = 0;
duration = Math.abs(curPos / pixelsPerMS);
}
else {
duration = -Math.log(minVelocity / absPixelsPerMS) * 500;
}
let result = {
destination: posTo,
duration
};
return result;
}
export interface IScrollData { export interface IScrollData {
/** /**
* 横向滚动位置,通常为正 * 横向滚动位置,通常为正
......
class BufferAttribute { import { BaseTextureCache, createCanvas, Dict, EventDispatcher, GC_MODES, isPow2, loadImage, Point, Rectangle, removeItems, SCALE_MODES, TextureCache, uid, WebglRenderer, WRAP_MODES } from "..";
import { HashObject } from "../2d/HashObject";
import RenderTarget from "../2d/renderers/renderTarget/RenderTarget";
import TextureMatrix from "../2d/texture/TextureMatrix";
import TextureUvs from "../2d/texture/TextureUvs";
import { defaultValue, GLTexture } from "../glCore";
import { compileProgram, extractAttributes, generateUniformAccessObject, mapType } from "../glCore/shader";
import { Box3 } from "./math/Box3";
import { Sphere } from "./math/Sphere";
//作为buffer的对应数据,应该具备的属性,可以是普通的,也可以是交错的数据
interface IDataAttribute {
/** /**
* 实际数据数量,比如顶点个数,array/itemSize * 类型化数组
*/ */
public count: number; array: any,
usage: BUFFER_USAGE,
_updateId: number,
count: number
}
/**
* 记录顶点数据对象,对于交错的数据怎么搞比较好,还是考虑都分开
*/
class DataAttribute extends HashObject implements IDataAttribute {
public name: string; public name: string;
//绑定buffer是使用
usage: BUFFER_USAGE = BUFFER_USAGE.STATIC_DRAW
/** /**
* 是否需要更新标识,如果glBuffer的_updateId与_dirtyId不一致,就需要upload * 是否需要重新传数据标识,有可能多个地方使用,所以只++,别人和他保持一致
*/
_updateId: number = 0;
/**
* array作为原数据要被传入glBuffer
* @param array Int8Array,Uint8Array, Int16Array,Uint16Array,Uint32Array,Float32Array等类型化数组
* @param itemSize 步长,比如位置是3
* @param normalized 是否标准化 默认false
*/ */
_dirtyId: number = 0;
/**
* array作为原数据要被传入glBuffer
* @param array Int8Array,Uint8Array, Int16Array,Uint16Array,Uint32Array,Float32Array等类型化数组
* @param itemSize 步长,比如位置是3
* @param normalized 是否标准化 默认false
*/
constructor( constructor(
public array: any, array: any,
public itemSize: number, public itemSize: number,
public normalized: boolean = false public normalized: boolean = false
) { ) {
super();
this._instanceType = "DataAttribute";
if (Array.isArray(array)) { if (Array.isArray(array)) {
throw new TypeError('Array should be a Typed Array'); throw new TypeError('Array should be a Typed Array');
} }
this.count = array !== undefined ? array.length / itemSize : 0; this._count = array ? array.length / itemSize : 0;
this._array = array;
} }
private _count: number
/** /**
* 重写覆盖 * 实际数据数量,比如顶点个数,array.length/itemSize
* @param value 普通数组,或类型化数组 */
* @param offset 默认0 get count() {
*/ return this._count;
set(value, offset: number = 0) {
this.array.set(value, offset);
return this;
} }
/** private _array: any;
* 重置数据 get array() {
* @param array 类型化数组 return this._array;
*/ }
setArray(array) { set array(array: any) {
if (Array.isArray(array)) { if (Array.isArray(array)) {
throw new TypeError('Array should be a Typed Array'); throw new TypeError('Array should be a Typed Array');
} }
this.count = array !== undefined ? array.length / this.itemSize : 0; this._count = array ? array.length / this.itemSize : 0;
this.array = array; this._array = array;
this._updateId++;
}
/**
* 重写覆盖,无法重置长度,所以value长度不能超过原array
* @param value 普通数组,或类型化数组
* @param offset 默认0
*/
set(value: ArrayLike<number>, offset: number = 0) {
this.array.set(value, offset);
return this; return this;
} }
/** /**
* *
* @param index 第几个点 * @param index 第几个点
*/ */
getX(index: number) { getX(index: number) {
return this.array[index * this.itemSize]; return this.array[index * this.itemSize];
} }
...@@ -110,16 +143,2577 @@ class BufferAttribute { ...@@ -110,16 +143,2577 @@ class BufferAttribute {
this.array[index + 3] = w; this.array[index + 3] = w;
return this; return this;
} }
copy(source: BufferAttribute) { copy(source: DataAttribute) {
this.name = source.name; this.name = source.name;
this.itemSize = source.itemSize;//先赋值步长,count在array赋值力有计算
this.array = new source.array.constructor(source.array); this.array = new source.array.constructor(source.array);
this.itemSize = source.itemSize;
this.count = source.count;
this.normalized = source.normalized; this.normalized = source.normalized;
return this; return this;
} }
clone() { clone() {
return new BufferAttribute(this.array, this.itemSize).copy(this); return new DataAttribute(null, 0).copy(this);
}
destroy() {
this._array = null;
}
}
//用作交错数据
class InterleavedData extends HashObject implements IDataAttribute {
usage: BUFFER_USAGE = BUFFER_USAGE.STATIC_DRAW;
_updateId: number = 0;
constructor(
array: any,
public stride: number
) {
super()
this._instanceType = "InterleavedData";
this._count = array ? array.length / stride : 0;
this._array = array;
}
private _count: number
/**
* 实际数据数量,比如顶点个数,array.length/itemSize
*/
get count() {
return this._count;
}
private _array: any;
/**
* 数据可以整体替换,但是对应的glbuffer还是只有一个,且vao也不需要重新处理通道,index是否需要每次bind,index也替换里面的数据(buffer还是不变)
*/
get array() {
return this._array;
}
set array(array: any) {
if (Array.isArray(array)) {
throw new TypeError('Array should be a Typed Array');
}
this._count = array ? array.length / this.stride : 0;
this._array = array;
this._updateId++;
}
copy(source: InterleavedData) {
this.stride = source.stride;
this.array = new source.array.constructor(source.array);
this.usage = source.usage;
return this;
}
set(value: ArrayLike<number>, offset = 0) {
this.array.set(value, offset);
return this;
}
//这个得考虑下,怎么搞TODO,尽量先不用
clone() {
return new InterleavedData(null, 0).copy(this);
}
destroy() {
this._array = null;
}
}
class InterleavedDataAttribute extends HashObject {
constructor(
public data: InterleavedData,
public itemSize: number,
public offset: number,
public normalized = false
) {
super();
this._instanceType = "InterleavedDataAttribute"
}
get count() {
return this.data.count;
}
get array() {
return this.data.array;
}
setX(index, x) {
this.data.array[index * this.data.stride + this.offset] = x;
return this;
}
setY(index, y) {
this.data.array[index * this.data.stride + this.offset + 1] = y;
return this;
}
setZ(index, z) {
this.data.array[index * this.data.stride + this.offset + 2] = z;
return this;
}
setW(index, w) {
this.data.array[index * this.data.stride + this.offset + 3] = w;
return this;
}
getX(index) {
return this.data.array[index * this.data.stride + this.offset];
}
getY(index) {
return this.data.array[index * this.data.stride + this.offset + 1];
}
getZ(index) {
return this.data.array[index * this.data.stride + this.offset + 2];
}
getW(index) {
return this.data.array[index * this.data.stride + this.offset + 3];
}
setXY(index, x, y) {
index = index * this.data.stride + this.offset;
this.data.array[index + 0] = x;
this.data.array[index + 1] = y;
return this;
}
setXYZ(index, x, y, z) {
index = index * this.data.stride + this.offset;
this.data.array[index + 0] = x;
this.data.array[index + 1] = y;
this.data.array[index + 2] = z;
return this;
}
setXYZW(index, x, y, z, w) {
index = index * this.data.stride + this.offset;
this.data.array[index + 0] = x;
this.data.array[index + 1] = y;
this.data.array[index + 2] = z;
this.data.array[index + 3] = w;
return this;
}
//TODO
clone() {
}
destroy() {
this.data = null;
}
}
/**
* 其实具体常量在gl上是固定的,但是还是做映射吧,BUFFER_TYPE也是
*/
enum BUFFER_USAGE {
/**
* 缓冲区的内容可能经常使用,而不会经常更改。内容被写入缓冲区,但不被读取。
*/
STATIC_DRAW = "STATIC_DRAW",
/**
* 缓冲区的内容可能经常被使用,并且经常更改。内容被写入缓冲区,但不被读取。
*/
DYNAMIC_DRAW = "DYNAMIC_DRAW",
/**
* 缓冲区的内容可能不会经常使用。内容被写入缓冲区,但不被读取。
*/
STREAM_DRAW = "STREAM_DRAW"
}
enum BUFFER_TYPE {
ELEMENT_ARRAY_BUFFER = "ELEMENT_ARRAY_BUFFER",
ARRAY_BUFFER = "ARRAY_BUFFER",
}
interface IBufferData {
buffer: WebGLBuffer,
//gl.FLOAT
type: number,
/**
* 比如4,是不是也可以是arrayBuffer(那type也得是各自的类型)
*/
bytesPerElement: number,
/**
* 更新id,默认-1开始
*/
updateId: number
}
//用于处理attribute对应的webglbuffer,传顶点数据用
class BufferManager {
private map: WeakMap<IDataAttribute, IBufferData>
constructor(public renderer: WebglRenderer) {
this.renderer.addEventListener('onContextChange', this.onContextChange, this);
}
onContextChange() {
this.map = new WeakMap();
}
get(attribute: DataAttribute | InterleavedDataAttribute) {
return this.map.get(this.getTrueAttribute(attribute));
}
/**
* 主要方法,用于更新顶点数据
* @param attribute
* @param bufferType
*/
update(attribute: DataAttribute | InterleavedDataAttribute, bufferType: BUFFER_TYPE) {
let attr = this.getTrueAttribute(attribute);
const buffer = this.map.get(attr);
if (!buffer) {
this.map.set(attr, this.create(attr, bufferType));
} else if (buffer.updateId < attr._updateId) {
let gl = this.renderer.gl;
gl.bindBuffer(gl[bufferType], buffer.buffer);
//先都这样,后面有更新范围和交错的再说
gl.bufferSubData(gl[bufferType], 0, attr.array);
buffer.updateId = attr._updateId;
}
}
remove(attribute: DataAttribute | InterleavedDataAttribute) {
let attr = this.getTrueAttribute(attribute)
const buffer = this.map.get(attr);
if (buffer) {
this.renderer.gl.deleteBuffer(buffer.buffer);
this.map.delete(attr);
}
}
private create(attribute: IDataAttribute, bufferType: BUFFER_TYPE): IBufferData {
const gl = this.renderer.gl;
const { array, usage } = attribute;
const buffer = gl.createBuffer();
gl.bindBuffer(gl[bufferType], buffer);
gl.bufferData(gl[bufferType], array, gl[usage]);
let type = gl.FLOAT;
if (array instanceof Float32Array) {
type = gl.FLOAT;
} else if (array instanceof Uint16Array) {
type = gl.UNSIGNED_SHORT;
} else if (array instanceof Int16Array) {
type = gl.SHORT;
} else if (array instanceof Uint32Array) {
type = gl.UNSIGNED_INT;
} else if (array instanceof Int32Array) {
type = gl.INT;
} else if (array instanceof Int8Array) {
type = gl.BYTE;
} else if (array instanceof Uint8Array) {
type = gl.UNSIGNED_BYTE;
} else if (array instanceof Uint8ClampedArray) {
type = gl.UNSIGNED_BYTE;
}
return {
buffer: buffer,
type,
bytesPerElement: array.BYTES_PER_ELEMENT,
updateId: attribute._updateId
};
}
private getTrueAttribute(attribute: DataAttribute | InterleavedDataAttribute): IDataAttribute {
let attr: IDataAttribute = attribute as IDataAttribute;
if (attribute.instanceType == "InterleavedDataAttribute") {
attr = (attribute as InterleavedDataAttribute).data
}
return attr;
}
destroy() {
this.renderer.removeEventListener('onContextChange', this.onContextChange, this);
this.renderer = null;
}
}
//shader,如果想自定义,该怎么搞,根据文本内容缓存?
//buffer和pointer是对应的,attr通道只需要开启或关闭,所以pointer的时候单独执行
//需要根据着色器和几何缓存,都用instanceId,到时继承hashObject
interface IVaoState {
geometryId?: number,
shaderId?: number,
tempAttributes: number[],
usedAttributes: number[],
vao: any,
attributes: Dict<DataAttribute>,
attributesNum: number,
//索引,可能索引可以外部随便给,不一定是用几何内部的
index: DataAttribute
}
class VaoManager {
private vaoExtension
private maxAttribs: number;
private defaultState: IVaoState;
private currentState: IVaoState;
/**
* key用几何id和着色器id,仅仅针对有vao扩展时,不然只用一个defaultState
*
*/
private boundVaoStates: Dict<Dict<IVaoState>>
constructor(public renderer: WebglRenderer) {
this.renderer.addEventListener('onContextChange', this.onContextChange, this);
}
onContextChange() {
const gl = this.renderer.gl;
this.vaoExtension = gl.getExtension('OES_vertex_array_object') ||
gl.getExtension('MOZ_OES_vertex_array_object') ||
gl.getExtension('WEBKIT_OES_vertex_array_object');
this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
this.defaultState = this.createVaoState(null);
this.currentState = this.defaultState;
this.boundVaoStates = {};
}
/**
* 处理绑定的纹理通道,这里需要在BufferManager.update之后,确保WebglBuffer都有
* 包括,bindBuffer,pointer,enableAttr
* @param geometry
* @param shader
*/
bind(geometry: Geometry, shader: GLShader) {
const gl = this.renderer.gl;
//是否需要更新顶点通道
let updateAttributes = false;
if (this.vaoExtension) {
let state = this.getBoundState(geometry, shader);
if (this.currentState !== state) {
this.currentState = state;
this.vaoExtension.bindVertexArrayOES(state.vao);
}
updateAttributes = this.checkAttributesUpdate(geometry);
//需要更新的话,重新缓存
if (updateAttributes) {
const cache = {};
const attributes = geometry._attributes;
let attributesNum = 0;
for (const key in attributes) {
const attribute = attributes[key];
cache[key] = attribute;
attributesNum++;
}
this.currentState.attributes = cache;
this.currentState.attributesNum = attributesNum;
this.currentState.index = geometry.index;
}
}
//不支持vao对象后,判断几何和着色器是不是同一个,貌似这样不保险,同一个几何里的attr可以变啊,TODO
else if (this.currentState.geometryId !== geometry.instanceId ||
this.currentState.shaderId !== shader.instanceId
) {
this.currentState.geometryId = geometry.instanceId;
this.currentState.shaderId = shader.instanceId;
//标记需要更新
updateAttributes = true;
}
let bufferManager: BufferManager = (this.renderer as any).bufferManager;
let index = geometry.index;
//这里为啥需要,TODO,暂时感觉是为了这个新的index有glbuffer,现在不需要,three里这个index可以不是geo里的
// if (index) {
// bufferManager.update(index, BUFFER_TYPE.ELEMENT_ARRAY_BUFFER);
// }
//不需要更新,retrun
if (!updateAttributes) return;
//重置,也许以后有用,到时开放方法,TODO
const tempAttributes = this.currentState.tempAttributes;
for (let i = 0; i < tempAttributes.length; i++) {
tempAttributes[i] = 0;
}
//处理各个通道
const geoAttributes = geometry._attributes;
const shaderAttributes = shader.attributes;
let lastBuffer = null;
//用着色器的
for (const name in shaderAttributes) {
const { size, location } = shaderAttributes[name];
//这个是否有必要
if (location < 0) continue;
let geoAttribute = geoAttributes[name];
//instance的情况,TODO
// if (!geoAttribute) {
// if (name === 'instanceMatrix' && object.instanceMatrix) geoAttribute = object.instanceMatrix;
// if (name === 'instanceColor' && object.instanceColor) geoAttribute = object.instanceColor;
// }
if (!geoAttribute) continue;
//
let { normalized, itemSize } = geoAttribute;
let offset = 0;
//
if (geoAttribute.instanceType == "InterleavedDataAttribute") {
offset = (geoAttribute as InterleavedDataAttribute).offset;
itemSize = (geoAttribute as InterleavedDataAttribute).data.stride;
}
const bufferData = bufferManager.get(geoAttribute);
// TODO Attribute may not be available on context restore
if (!bufferData) continue;
const { buffer, type, bytesPerElement } = bufferData;
//打开通道,此外instance网格的打开通道方式不一样,其他都一致
this.enableAttribute(location);
//绑定buffer,和pointer关联,同一个没必要重复绑定
if (lastBuffer !== buffer) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
lastBuffer = buffer;
}
//pointer
gl.vertexAttribPointer(
location,
size,
type,
normalized,
itemSize * bytesPerElement,
offset * bytesPerElement
);
//对于交错数据和instance的,TODO
}
//关闭不用的通道
const usedAttributes = this.currentState.usedAttributes;
for (let i = 0; i < usedAttributes.length; i++) {
if (usedAttributes[i] !== tempAttributes[i]) {
gl.disableVertexAttribArray(i);
usedAttributes[i] = 0;
}
}
//索引绑定
if (index) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferManager.get(index).buffer);
}
}
unbindByGeometry(geometry: Geometry) {
const shaderMap = this.boundVaoStates[geometry.instanceId];
if (!shaderMap) return;
for (const shaderId in shaderMap) {
const state = shaderMap[shaderId];
this.vaoExtension.deleteVertexArrayOES(state.vao);
delete shaderMap[shaderId];
}
delete this.boundVaoStates[geometry.instanceId];
}
unbindByShader(shader: GLShader) {
for (const geometryId in this.boundVaoStates) {
const shaderMap = this.boundVaoStates[geometryId];
const id = shader.instanceId;
if (!shaderMap[id]) continue;
const state = shaderMap[id];
this.vaoExtension.deleteVertexArrayOES(state.vao);
delete shaderMap[id];
}
}
private createVaoState(vao): IVaoState {
const tempAttributes = [];
const usedAttributes = [];
for (let i = 0; i < this.maxAttribs; i++) {
tempAttributes[i] = usedAttributes[i] = 0;
}
return {
geometryId: null,
shaderId: null,
tempAttributes,
usedAttributes,
vao,
attributes: {},
attributesNum: 0,
index: null
};
}
private checkAttributesUpdate(geometry: Geometry) {
const { attributes, attributesNum, index } = this.currentState;
const geoAttributes = geometry._attributes;
let geoAttributesNum = 0;
for (const key in geoAttributes) {
const cachedAttribute = attributes[key];
const geoAttribute = geoAttributes[key];
//有不一致的返回true
if (cachedAttribute != geoAttribute) return true;
geoAttributesNum++;
}
//通道数量不一致
if (attributesNum !== geoAttributesNum) return true;
//索引不一致
if (index !== geometry.index) return true;
return false;
}
private enableAttribute(location: number) {
const { tempAttributes, usedAttributes } = this.currentState;
tempAttributes[location] = 1;
//原来是关闭的
if (!usedAttributes[location]) {
this.renderer.gl.enableVertexAttribArray(location);
usedAttributes[location] = 1;
}
}
private getBoundState(geometry: Geometry, shader: GLShader) {
let shaderMap = this.boundVaoStates[geometry.instanceId];
if (!shaderMap) {
shaderMap = this.boundVaoStates[geometry.instanceId] = {};
}
let state = shaderMap[shader.instanceId];
if (!state) {
state = shaderMap[shader.instanceId] = this.createVaoState(
this.vaoExtension.createVertexArrayOES()
);
}
return state;
}
destroy() {
this.renderer.removeEventListener('onContextChange', this.onContextChange, this);
this.renderer = null;
}
}
interface IWebglTexture {
dataId: number,
styleId: number,
webglTexture: WebGLTexture
}
//
class TextureManager {
private map: WeakMap<BaseTexture, IWebglTexture>
/**
* 绑定的纹理,通道作为索引,filterManager,batchrender等有用到
*/
boundTextures: BaseTexture[];
/**
* 只放两个,2d和cube
*/
private emptyTextures: Dict<WebGLTexture>;
private _nextTextureLocation: number = -1;
/**
* 当前激活的纹理通道,和bind无关
*/
private currentLocation: number;
/**
* 用于gc或销毁用,注意gc的有问题,判断帧缓存时
*/
_managedTextures: Array<BaseTexture>;
private textureFloatExtension
/**
* @param {WebGLRenderer} renderer
*/
constructor(public renderer: WebglRenderer) {
this._managedTextures = [];
this.boundTextures = [];
this.renderer.addEventListener('onContextChange', this.onContextChange, this);
this.renderer.addEventListener('onPostRender', this.onPostRender, this);
}
onContextChange() {
//重置
this.textureFloatExtension = null;
//新的映射
this.map = new WeakMap();
const gl = this.renderer.gl;//MAX_COMBINED_TEXTURE_IMAGE_UNITS
const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
//重置
this.boundTextures.length = maxTextures;
for (var i = 0; i < maxTextures; i++)this.boundTextures[i] = null;
//两种空纹理加上
this.emptyTextures = {};
this.emptyTextures[gl.TEXTURE_2D] = this.createTexture(gl.TEXTURE_2D, gl.TEXTURE_2D, 1);
this.emptyTextures[gl.TEXTURE_CUBE_MAP] = this.createTexture(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6);
//全部通道都加上
for (let i = 0; i < this.boundTextures.length; i++) {
this.bind(null, i);
}
//GC的
this._count = 0;
this.checkCount = 0;
this.maxIdle = 60 * 60;
this.checkCountMax = 60 * 10;
this.mode = GC_MODES.AUTO;
}
private _count: number;
get count() {
return this._count
}
private checkCount: number;
private maxIdle: number;
private checkCountMax: number;
mode: GC_MODES;
//处理GC
onPostRender() {
this._count++;
if (this.mode === GC_MODES.MANUAL) return;
this.checkCount++;
//计数没到
if (this.checkCount <= this.checkCountMax) return;
this.checkCount = 0;
//执行移除
const managedTextures = this._managedTextures;
//会出现移除,数组长度会改变
for (let i = managedTextures.length - 1; i >= 0; i--) {
const texture = managedTextures[i];
//去掉长期不用的纹理,不处理帧缓存的纹理
if (!(texture as BaseRenderTexture).framebuffer && this._count - texture._touchedId > this.maxIdle) {
//里面会移除managedTextures里对应的纹理
this.destroyTexture(texture);
}
}
}
get(texture: Texture | BaseTexture): IWebglTexture {
return this.map.get(this.getTrueBaseTexture(texture));
}
private getTrueBaseTexture(texture: Texture | BaseTexture): BaseTexture {
if (!texture) return null;
return (texture as Texture).baseTexture || texture as BaseTexture;
}
/**
*
* @param {Texture} texture - TODO,可以是Texture或BaseTexture
* @param {number} location - 纹理通道,传了就表示强制使用该通道
* @return {number} 纹理通道
*/
bind(texture: Texture | BaseTexture, location?: number): number {
const gl = this.renderer.gl;
//转成base;
let baseTexture: BaseTexture = this.getTrueBaseTexture(texture);
//没有就表示bind空
if (!baseTexture || !baseTexture.hasLoaded) {
location = location || 0;
if (this.currentLocation !== location) {
this.currentLocation = location;
gl.activeTexture(gl.TEXTURE0 + location);
}
//绑定一个空的
gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D]);
this.boundTextures[location] = null;
return location;
}
//用于gc标记
baseTexture._touchedId = this.renderer.textureGC.count;
//从原先绑定过的纹理里找,如果location没传
if (location == undefined) {
for (let i = 0; i < this.boundTextures.length; i++) {
if (this.boundTextures[i] === baseTexture) {
location = i;
break;
}
}
if (location == undefined) {
//没找到,尽量从后面的通道开始使用
this._nextTextureLocation++;
this._nextTextureLocation %= this.boundTextures.length;
location = this.boundTextures.length - this._nextTextureLocation - 1;
}
}
let glTexture = this.map.get(baseTexture);
if (!glTexture) {
glTexture = {
dataId: -1,//保证下面的会更新
styleId: -1,
webglTexture: gl.createTexture()
};
//销毁的事件加上,destroyTexture做些啥,TODO
baseTexture.addEventListener('dispose', this.destroyTexture, this);
this.map.set(baseTexture, glTexture);
//进管理
this._managedTextures.push(baseTexture);
}
//是否当前激活的通道,不是就激活
if (this.currentLocation !== location) {
this.currentLocation = location;
gl.activeTexture(gl.TEXTURE0 + location);
}
let hasBind: boolean = false;
//维护的不一致说明该通道不对,修改
if (this.boundTextures[location] !== baseTexture) {
gl.bindTexture(gl.TEXTURE_2D, glTexture.webglTexture);
this.boundTextures[location] = baseTexture;
hasBind = true;
}
const { source, dataType, dataFormat, width, height, _dataId,
_styleId, isPowerOfTwo, scaleMode, mipmap, wrapMode } = baseTexture
//TODO,一部分属性的更新,一部分数据的更新,考虑分开,three直接就是一起的
//还有部分帧缓存的,TODO
//如果数据变了,比如离屏canvas的,需要重新传数据
if (glTexture.dataId !== _dataId) {
glTexture.dataId = _dataId;
if (!hasBind) {
hasBind = true;
gl.bindTexture(gl.TEXTURE_2D, glTexture.webglTexture);
}
//正常有source处理
if (source) {
//是否预乘纹理通道,好像需要在传数据前执行,pixelStorei设置纹理相关,应该都是要在数据上传前执行
//所以这部分属性的维护,算进_dataId,后面可能还会有flip-y
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, baseTexture.premultipliedAlpha);
// texSubImage2D只有特殊时候才用到,直接使用texImage2D,
//传gpu
if (source._data) {//是data形式
//是否开了扩展
if (dataType == DATA_TYPE.FLOAT && !this.textureFloatExtension) {
this.textureFloatExtension = gl.getExtension("OES_texture_float");
if (!this.textureFloatExtension) {
throw new Error('floating point textures not available');
}
}
//TEXTURE_2D以后取base的target,TODO
gl.texImage2D(gl.TEXTURE_2D, 0, gl[dataFormat], width, height, 0, gl[dataFormat], gl[dataType], source._data);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl[dataFormat], gl[dataFormat], gl[dataType], source);
}
}
//如果是renderTexture,没有source的
else {
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl[dataFormat],
width,
height,
0,
gl[dataFormat],
gl[dataType],
null
);
}
}
//属性的,现在是一次性的,_styleId在baseTexture里还没维护,TODO
if (glTexture.styleId !== _styleId) {
glTexture.styleId = _styleId;
if (!hasBind) {
hasBind = true;
gl.bindTexture(gl.TEXTURE_2D, glTexture.webglTexture);
}
let glWrapMode = gl.CLAMP_TO_EDGE;
if (isPowerOfTwo) {
if (mipmap) {
gl.generateMipmap(gl.TEXTURE_2D);
}
if (wrapMode === WRAP_MODES.REPEAT) {
glWrapMode = gl.REPEAT;
}
else if (wrapMode === WRAP_MODES.MIRRORED_REPEAT) {
glWrapMode = gl.MIRRORED_REPEAT;
}
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, glWrapMode);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, glWrapMode);
let minMode, magMode;
if (scaleMode === SCALE_MODES.NEAREST) {
minMode = mipmap ? gl.NEAREST_MIPMAP_NEAREST : gl.NEAREST;
magMode = gl.NEAREST;
}
else {
minMode = mipmap ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR;
magMode = gl.LINEAR;
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minMode);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magMode);
}
return location;
}
/**
* 解除纹理的绑定
* @param texture
*/
unbind(texture: Texture | BaseTexture) {
const gl = this.renderer.gl;
let baseTexture: BaseTexture = this.getTrueBaseTexture(texture);
for (let i = 0; i < this.boundTextures.length; i++) {
if (this.boundTextures[i] === baseTexture) {
this.boundTextures[i] = null;
if (this.currentLocation !== i) {
gl.activeTexture(gl.TEXTURE0 + i);
this.currentLocation = i;
}
gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D]);
}
}
}
/**
* TODO
* @param {BaseTexture|Texture} texture
*/
destroyTexture(texture) {
//如果是事件返回的
if (texture.instanceType === "Event") {
//选择它的target
texture = texture.target;
}
texture = texture.baseTexture || texture;
if (!texture.hasLoaded) {//TODO待处理,应该不这么判断了
return;
}
const glTexture = this.map.get(texture);
if (glTexture) {
this.unbind(texture);
//从gpu移除
this.renderer.gl.deleteTexture(glTexture.webglTexture);
//事件移除
texture.removeEventListener('dispose', this.destroyTexture, this);
//映射数据移除
this.map.delete(texture);
//
const i = this._managedTextures.indexOf(texture);
if (i !== -1) {
this._managedTextures.splice(i, 1)
}
}
}
/**
* 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];
this.map.delete(texture);
}
}
private createTexture(type: number, target: number, count: number) {
const gl = this.renderer.gl;
const data = new Uint8Array(4);
const texture = gl.createTexture();
gl.bindTexture(type, texture);
//用NEAREST
gl.texParameteri(type, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(type, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
for (let i = 0; i < count; i++) {
gl.texImage2D(target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
}
return texture;
}
/**
* Destroys this manager and removes all its textures
*/
destroy() {
// destroy managed textures
for (let i = this._managedTextures.length - 1; i >= 0; i--) {
const texture = this._managedTextures[i];
this.destroyTexture(texture);
}
//移除绑定事件
this.renderer.removeEventListener('onContextChange', this.onContextChange, this);
this.renderer.removeEventListener('onPostRender', this.onPostRender, this);
this.renderer = null;
this._managedTextures = null;
}
}
//还是存映射,不用gl固定的数值
enum FORMAT_TYPE {
RGBA = "RGBA",
RGB = "RGB",
}
enum DATA_TYPE {
UNSIGNED_BYTE = "UNSIGNED_BYTE",
FLOAT = "FLOAT",
UNSIGNED_SHORT = "UNSIGNED_SHORT",
}
enum TEXTURE_TARGET_TYPE {
TEXTURE_2D = "TEXTURE_2D",
TEXTURE_CUBE_MAP = "TEXTURE_CUBE_MAP",
}
/**
* 每个texture都有一个BaseTexture,多数用于图集,texture可自身设置属性
* 实际绘制用的都是BaseTexture
* @class
* @extends EventDispatcher
*/
class BaseTexture extends EventDispatcher {
/**
* 贴图回收时用到,
* 标记是否被使用过
*/
_touchedId: number = 0;
/**
* 批处理时用到的标志位
* 被使用着的id
*/
_enabledId: number = 0;
/**
* 数据有修改时++,初始0
* source改变,宽高改变,
* premultipliedAlpha、dataFormat、dataType等改变都需要
*/
_dataId: number = 0;
/**
* 样式有修改时更新,修改时++,初始0,预留吧,TODO
*/
_styleId: number = 0;
/**
* 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[] = [];
/**
* image对象,canvas对象,或者imageData(canvas渲染模式用不了,是否考虑去掉),以后类型增多后再说
*/
source = null;
/**
* 加载完成会设置为true
* 加载失败或没有贴图数据则为false,或者宽高为0等都为false
* 通常用于标记基础纹理是否可用,TODO
*/
hasLoaded: boolean = false;
protected _width: number = 100;
/**
* 宽度
*/
get width(): number {
return this._width;
};
protected _height: number = 100;
/**
* 高度
*/
get height(): number {
return this._height;
};
//////webgl需要用到的属性///////
/**
* rgb预乘alpha,webgl用到,png图片设置必为true,否则色值会出问题
* @default true
*/
premultipliedAlpha: boolean = true;
/**
* 纹理格式,默认gl.RGBA 还有gl.RGB,还没有地方维护,TODO
*/
dataFormat: FORMAT_TYPE = FORMAT_TYPE.RGBA;
/**
* 纹理数据类型,默认gl.UNSIGNED_BYTE //https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/texImage2D
*/
dataType: DATA_TYPE = DATA_TYPE.UNSIGNED_BYTE;
protected _isPowerOfTwo: boolean = false;
/**
* 是否尺寸为2的次方,尽可能图集尺寸都为2的次方,gpu处理方便,并且能做mipmap缓存,性能更好
*/
get isPowerOfTwo(): boolean {
return this._isPowerOfTwo;
};
/**
* SCALE_MODES,一般是线性
* 用于glTexture
*/
scaleMode: SCALE_MODES = SCALE_MODES.LINEAR;
/**
* 尺寸是2的次方下才能设置true,用于生成mipmap缓存
* @default true
*/
mipmap: boolean = true;
/**
* 非2的次方时要设置CLAMP
* WebGL Texture wrap mode
* @default WRAP_MODES.CLAMP
*/
wrapMode: WRAP_MODES = WRAP_MODES.CLAMP;
////////////
/**
* @param {} [source] - 源数据,image对象,canvas对象,或者imageData
*/
constructor(source = null) {
super();
if (!source) return
//有source要加载,构造函数不缓存baseTexture,要缓存用fromUrl
//是imageData
if (source._data) {
this._sourceLoaded(source);
//数据类型改一下
if (source._data instanceof Float32Array) {
this.dataType = DATA_TYPE.FLOAT;
}
else {
this.dataType = DATA_TYPE.UNSIGNED_BYTE;
}
}
//是canvas
else if (source.getContext) {
this._sourceLoaded(source);
}
//普通图片
else {
if (source.complete) {
this._sourceLoaded(source);
// this.dispatchEvent("loaded");//上面的方法会执行loaded
} else {
var self = this;
//会覆盖onload方法
source.onload = function () {
self._sourceLoaded(source);
// self.dispatchEvent("loaded");//上面的方法会执行loaded
};
//失败的
source.onerror = function () {
self.dispatchEvent("loaded");
};
}
}
}
/**
* 会触发更新事件,一般用于纹理source变更或修改后调用
* @fires BaseTexture#update
*/
update() {
if (!this.source) return;
//尺寸重置
this._setSize(this.source.width, this.source.height);
//再来判断一次
this.hasLoaded = this.width && this.height ? true : false;
//数据改变
this._dataId++;
//触发绑定过的更新事件,比如webgl纹理更新
this.dispatchEvent("update");
}
/**
* 不希望外部使用
* @param width
* @param height
* @returns
*/
protected _setSize(width: number, height: number) {
if (this._width == width && this._height == height) return;
//尺寸重置
this._width = width;
this._height = height;
this._isPowerOfTwo = isPow2(this.width) && isPow2(this.height);
this._dataId++;
}
/**
* 原先的_sourceChange改成了_sourceLoaded,且变成私有
*/
private _sourceLoaded(source) {
//赋值
this.source = source;
//路径,暂时没用,需要时再处理,先找src 再path(基本没有)
// this.imageUrl = source.src || source.path || null;
//更新类型,暂时没用,也没做,需要是处理
// this.imageType = this.source.type || null;
//加载完成
this.hasLoaded = true;
//更新
this.update();
//触发事件
this.dispatchEvent("loaded");
}
/**
* 销毁 base texture
* 基本不会销毁纹理
*/
destroy() {
let imageUrl = this.source ? (this.source.src || this.source.path) : null;
if (imageUrl) {
//普通纹理删除缓存,万一有用到
delete TextureCache[imageUrl];
}
this.source = null;
this.dispose();
BaseTexture.removeFromCache(this);
this.textureCacheIds = null;
}
/**
* 用于释放gpu缓存,并不销毁纹理,需要时可再上传到GPU
* @fires BaseTexture#dispose
*/
dispose() {
//用于触发TextureManager中监听的
this.dispatchEvent("dispose")
}
//辅助静态方法
/**
* 根据路径,会缓存baseTexture
* @param {string} url 路径
*/
static fromUrl(url: string) {
if (BaseTextureCache[url]) return BaseTextureCache[url];
let baseTexture = new BaseTexture();
loadImage(url,
(res) => {
baseTexture._sourceLoaded(res);
//添加进缓存
if (url.indexOf('data:') !== 0) BaseTexture.addToCache(baseTexture, url);
},
() => {
//失败的时候也需要派发加载完成事件
baseTexture.dispatchEvent("loaded");
}
)
return baseTexture
}
/**
* 随便啥形式的,比如IImageData形式,
* @param data
*/
static fromData(data: IImageData) {
return new BaseTexture(data);
}
/**
* 从离屏canvas创建的,会给canvas加唯一标识_canvasId,并缓存
*/
static fromCanvas(canvas: HTMLCanvasElement, origin: string = 'canvas') {
//标记canvasId
if (!canvas["_canvasId"]) {
canvas["_canvasId"] = `${origin}_${uid()}`;
}
let baseTexture = BaseTextureCache[canvas["_canvasId"]];
if (!baseTexture) {
baseTexture = new BaseTexture(canvas);
BaseTexture.addToCache(baseTexture, canvas["_canvasId"]);
}
return baseTexture;
}
/**
* 根据图片
* @param image
*/
static fromImage(image: HTMLImageElement) {
//图片标签
const imageUrl = image.src;
let baseTexture = BaseTextureCache[imageUrl];
if (!baseTexture) {
baseTexture = new BaseTexture(image);
//不缓存base64,如需要,设名字,手动缓存
if (imageUrl && imageUrl.indexOf('data:') !== 0) BaseTexture.addToCache(baseTexture, imageUrl);
}
return baseTexture;
}
/**
* 所有形式,图片路径,canvas标签,图片标签,或者数据
* @param anything
* @returns
*/
static from(anything: string | HTMLCanvasElement | HTMLImageElement | IImageData) {
//路径
if (typeof anything === 'string') {
return BaseTexture.fromUrl(anything);
}
//@ts-ignore
else if (anything._data) {
//@ts-ignore
return BaseTexture.fromData(anything);
}
//@ts-ignore canvas
else if (anything.getContext) {
//@ts-ignore
return BaseTexture.fromCanvas(anything);
} else {
//@ts-ignore
return BaseTexture.fromImage(anything);
}
}
/**
* 加入全局基础纹理缓存
* @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(`rewrite cached baseTexture: ${id}`);
}
BaseTextureCache[id] = baseTexture;
}
}
/**
* 移除缓存
* @static
* @param {string|BaseTexture} baseTexture id或者BaseTexture
* @return {BaseTexture|null} 移除的BaseTexture或null
*/
static removeFromCache(baseTexture: string | BaseTexture): BaseTexture {
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;
}
}
/**
* 图片原始数据形式的数据接口
*/
interface IImageData {
//这里需要修改下data改为_data,tt里的Image对象居然有data(ArrayBuffer虽然能用,但是有偏移),导致判断有问题,需要同步修改TextureManager
/**
* 原始图片数据
* 比如从getImageData获取的数据
* var imgData = ctx.getImageData(0, 0, w, h);
* var _data = new Uint8Array(imgData.data)
*/
_data: Uint8Array | ArrayBuffer | Float32Array | Uint8ClampedArray,
width: number,
height: number,
/**
* 暂时不必要
*/
type?: string,
/**
* 暂时不必要
*/
path?: string
}
/**
* 一张图片或图集的一部分,如果没有frame。默认整张图片
* eg
* let texture = Texture.fromImage('assets/image.png');
* let sprite1 = new Sprite(texture);
* let sprite2 = new Sprite(texture);
*
* @class
* @extends EventDispatcher
*/
export class Texture extends EventDispatcher {
protected _baseTexture: BaseTexture;
/**
* BaseTexture,必有
*/
get baseTexture() {
return this._baseTexture;
}
/**
* 标记texture没有frame
* 表示不是从图集来的固定frame。需要根据BaseTexture的更新而改变frame
*/
private noFrame: boolean;
/**
* 实际绘制矩形框,对于有trimmed的纹理很重要
* 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)
*/
protected _frame: Rectangle;
/**
* The frame specifies the region of the base texture that this texture uses.
* 手动修改frame时,而不是set赋值,比如frame.x=111,frame.width=333,需要手动调用updateUvs,不推荐这种方式修改
* @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;
//如果无裁切透明像素,无旋转,orig和frame完全一致
if (!this.trim && !this.rotate) this.orig = frame;
//如果可用,更新uv
if (this.valid) this.updateUvs();
}
/**
* 贴图旋转及镜像
*/
private _rotate: number;
/**
* Indicates whether the texture is rotated inside the atlas
* set to 2 to compensate for texture packer rotation
* set to 6 to compensate for spine packer rotation
* can be used to rotate or mirror sprites
* See {@link GroupD8} for explanation
*
* @member {number}
*/
get rotate(): number {
return this._rotate;
}
//先去掉了,TODO
// set rotate(rotate: number) {
// if (this._rotate != rotate) {
// this._rotate = rotate;
// if (this.valid) this.updateUvs();
// }
// }
/**
* 宽高都是纹理真实的宽高,不管trim
* @member {number}
*/
get width() {
return this.orig.width;
}
/**
* 宽高都是纹理真实的宽高,不管trim
* @member {number}
*/
get height() {
return this.orig.height;
}
/**
* 如果矩形边缘有透明像素被裁减后的缩小的区域
* 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;
/**
* 原始尺寸,放入图集前
* This is the area of original texture, before it was put in atlas
*/
orig: Rectangle;
/**
* 贴图是否可用,true为可用
*/
valid: boolean = false;
/**
* 对应贴图uv
* The WebGL UV data cache.
*/
_uvs: TextureUvs = null;
/**
* 贴图的锚点,默认0,0,左上角,范围0到1
* 设置后会影响使用该纹理的sprite的绘制起始点
* @default {0,0}
*/
defaultAnchor: Point;
/**
* 更新的id标志
*/
_updateID: number = 0;
/**
* 一般不用,需要时再说
* 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: TextureMatrix = null;
/**
* 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;
/**
* 白贴图
*/
private static _WHITE: Texture;
static get WHITE(): Texture {//用get方式,用到了才创建,引擎在淘宝小部件里自执行my._createOffscreenCanvas()返回的是undefined
if (!Texture._WHITE) {
//白图纹理用canvas建
const canvas = createCanvas();
canvas.width = 16;
canvas.height = 16;
const context = canvas.getContext('2d');
context.clearRect(0, 0, 16, 16);//淘宝小程序的问题,必须先调用过clearRect,否则有几率绘制无效
context.fillStyle = 'white';//淘宝小程序待测试
context.fillRect(0, 0, 16, 16);
//生成纹理
const white = new Texture(new BaseTexture(canvas));
//置空事件方法
removeAllHandlers(white);
removeAllHandlers(white.baseTexture);
//缓存赋值
Texture._WHITE = white;
}
return Texture._WHITE;
};
/**
* @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 {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link GroupD8}
* @param {Point} [anchor] - Default anchor point used for sprite placement / rotation
*/
constructor(
baseTexture: BaseTexture | Texture,
frame?: Rectangle,
orig?: Rectangle,
trim?: Rectangle,
rotate?: number,
anchor?: Point
) {
super();
this._instanceType = "Texture";
this.noFrame = false;
if (!frame) {
//标记没有固定的frame
this.noFrame = true;
//frame初始化个
frame = new Rectangle(0, 0, 1, 1);
}
//如果传入的是Texture,取其base
if (baseTexture instanceof Texture) baseTexture = baseTexture.baseTexture;
this._baseTexture = baseTexture;
this._frame = frame;
this.trim = trim || null;
this.orig = orig || frame;// new Rectangle(0, 0, 1, 1);
this._rotate = Number(rotate || 0);
//对于canvas形式的判断hasLoaded有问题,导致不能监听update,hasLoaded还用于判断纹理是否可用(canvas宽高为0不可用),所以这里判断吧
if (baseTexture.hasLoaded || (baseTexture.source && baseTexture.source.getContext)) {
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);
}
this.frame = frame;
}
else {
baseTexture.once('loaded', this.onBaseTextureLoaded, this);
}
this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0);
}
/**
* 更新方法,直接调用base的,source尺寸有改变,或内容有改变,基本用于Shape和TextField
* 在base更新时会触发自己的
*/
update() {
this.baseTexture.update();
}
// 下面Loaded和Updated两个监听函数基本用于两种情况,不会用于图集中的纹理(除非图集还没加载好)
// 1、canvas作为贴图需要监听尺寸变化,Shape里以及TextField里
// 2、单张图片(如网络图片临时加载)需要监听尺寸变化,比如奖品图片,尺寸不一且未知
/**
*
* @private
* @param {BaseTexture} baseTexture - The base texture.
*/
private onBaseTextureLoaded(e: Event/*,baseTexture: BaseTexture*/) {
var baseTexture = e.target as BaseTexture;
this._updateID++;
//执行这个函数意味着noFrame为true
if (this.noFrame) {
if (baseTexture.source) {//加载成功了才修改frame和监听
this.frame = new Rectangle(0, 0, baseTexture.width, baseTexture.height);
//如果是没传过固定的frame就要监听了
baseTexture.addEventListener('update', this.onBaseTextureUpdated, this);
}
}
else {
this.frame = this._frame;
}
//加载成功的时候有必要触发update吗?
this.dispatchEvent("update")
this.dispatchEvent("loaded")
}
/**
*
* @private
* @param {BaseTexture} baseTexture - The base texture.
*/
private onBaseTextureUpdated(e: Event/*,baseTexture*/) {
var baseTexture = e.target as BaseTexture;
//标志纹理已更新
this._updateID++;
//只有尺寸需要
this._frame.width = baseTexture.width;
this._frame.height = baseTexture.height;
this.orig.width = this._frame.width;
this.orig.height = this._frame.height;
this.valid = baseTexture.hasLoaded;
this.dispatchEvent("update")
}
/**
* 销毁
*/
destroy() {
if (this.baseTexture) {
//考虑是否销毁baseTexture,暂时不
//事件要移除
this.baseTexture.removeEventListener('update', this.onBaseTextureUpdated, this);
this.baseTexture.removeEventListener('loaded', this.onBaseTextureLoaded, this);
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,
this.trim,
this.rotate,
this.defaultAnchor
);
}
/**
* 更新uv,比如在frame改变或trim改变之后
*/
updateUvs() {
if (!this._uvs) this._uvs = new TextureUvs();
//uvs方法的类型还没改
this._uvs.set(this._frame, this._baseTexture, this.rotate);
this._updateID++;
}
//辅助方法
/**
* 会缓存进全局纹理
* @param {string} url
*/
static fromUrl(url: string) {
let texture = TextureCache[url];
if (!texture) {
texture = new Texture(BaseTexture.fromUrl(url));
Texture.addToCache(texture, url);
}
return texture;
}
static fromCanvas(canvas: HTMLCanvasElement, origin: string = 'canvas') {
return new Texture(BaseTexture.fromCanvas(canvas, origin))
}
static fromData(data: IImageData) {
return new Texture(BaseTexture.fromData(data))
}
static fromImage(image: HTMLImageElement) {
var imageUrl = image.src;
let texture: Texture = TextureCache[imageUrl];
if (!texture) {
texture = new Texture(BaseTexture.fromImage(image));
if (imageUrl && imageUrl.indexOf('data:') !== 0) Texture.addToCache(texture, imageUrl);
}
return texture;
}
/**
* 图片路径,canvas标签,图片标签,图片数据
* @param anything
* @returns
*/
static from(anything: string | HTMLCanvasElement | HTMLImageElement | IImageData) {
//路径
if (typeof anything === 'string') {
return Texture.fromUrl(anything);
}
else if ((anything as IImageData)._data) {
return Texture.fromData(anything as IImageData);
}
else if ((anything as HTMLCanvasElement).getContext) {
return Texture.fromCanvas(anything as HTMLCanvasElement);
} else {
return Texture.fromImage(anything as HTMLImageElement);
}
}
/**
* 加入全局纹理缓存,TextureCache[name]调用
* @static
* @param {Texture} texture
* @param {string} id
*/
static addToCache(texture: Texture, id: string) {
if (id) {
if (texture.textureCacheIds.indexOf(id) === -1) {
texture.textureCacheIds.push(id);
}
if (TextureCache[id]) {
//覆盖
console.warn(`rewrite cached texture: ${id}`);
}
TextureCache[id] = texture;
}
}
/**
* 从全局缓存中移除
* @static
* @param {string|Texture} texture - 纹理的id或纹理自身
* @return {Texture} 返回移除的纹理
*/
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;
}
}
//将事件置空,空纹理或白纹理不需要响应任何加载或更新事件
function removeAllHandlers(tex) {
tex.destroy = function _emptyDestroy() { /* empty */ };
tex.addEventListener = function _emptyOn() { /* empty */ };
tex.once = function _emptyOnce() { /* empty */ };
tex.dispatchEvent = function _emptyEmit() { /* empty */ };
tex.removeEventListener = function _emptyOff() { /* empty */ };
}
/**
* 空纹理,用于绑定webgl绑定空纹理
* @static
* @constant
*/
Texture.EMPTY = new Texture(new BaseTexture());
removeAllHandlers(Texture.EMPTY);
removeAllHandlers(Texture.EMPTY.baseTexture);
class BaseRenderTexture extends BaseTexture {
// /**
// * webgl帧缓存列表,键值为渲染器的id
// * 画在他的帧缓存中
// * @private
// * @member {object<number, WebGLTexture>}
// */
// _glRenderTargets: {}={};
// /**
// * 离屏canvas,多canvas渲染器可共享,所以不需要列表
// * @private
// * @member {CanvasRenderTarget}
// */
// _canvasRenderTarget: any;
public framebuffer: Framebuffer;
maskStack: Array<any>;
filterStack: Array<any>;
/**
* @param {number} [width=100] - 宽度
* @param {number} [height=100] - 高度
*/
constructor(width: number = 100, height: number = 100) {
super();
this._width = Math.ceil(width);
this._height = Math.ceil(height);
//这个标记下
this.hasLoaded = true;
this.mipmap = false;
this.framebuffer = new Framebuffer(width, height);
this.framebuffer.colorTexture = this;
//这个在镇缓存中需要先bind,生成glTexture,然后给frameBuffer
}
/**
* 重置尺寸
* @param {number} width - 宽度
* @param {number} height - 高度
*/
resize(width: number, height: number) {
width = Math.ceil(width);
height = Math.ceil(height);
if (width === this.width && height === this.height) return;
this._setSize(width, height);
//
this.framebuffer.resize(width, height);
//这里待考虑
this.dispatchEvent('update');
}
/**
* 销毁
*/
destroy() {
super.destroy();
}
}
//用weakmap和GlFramebuffer对应,还有个管理器,和renderTarget很像
class Framebuffer {
public width: number;
public height: number;
//标记,是否启用,是否只能用
private _stencil: boolean = false;
get stencil() {
return this._stencil;
}
set stencil(v: boolean) {
this.stencil = v;
this._dirtyId++;
}
_dirtyId: number = 0;
//基本是为了gltexture对应用,没有source
private _depthTexture: BaseTexture;
get depthTexture(): BaseTexture {
return this._depthTexture
};
set depthTexture(v) {
this._depthTexture = v;
this._dirtyId++;
};
//需要绑定一个,为了取bastT的gltexture
private _colorTexture: BaseTexture;
get colorTexture(): BaseTexture {
return this._colorTexture
};
set colorTexture(v) {
this._colorTexture = v;
this._dirtyId++;
};
/**
* @param {number} width - 默认100
* @param {number} height - 默认100
*/
constructor(width?: number, height?: number) {
this.width = Math.round(width || 100);
this.height = Math.round(height || 100);
}
/**
* Resize the frame buffer
*
* @param {number} width - Width of the frame buffer to resize to
* @param {number} height - Height of the frame buffer to resize to
*/
resize(width: number, height: number): void {
width = Math.round(width);
height = Math.round(height);
if (width === this.width && height === this.height) return;
this.width = width;
this.height = height;
this._dirtyId++;
//
if (this.colorTexture) {
this.colorTexture["_setSize"](width, height)
};
if (this.depthTexture) {
this.depthTexture["_setSize"](width, height)
}
}
}
class RenderTexture extends Texture {
protected _baseTexture: BaseRenderTexture;
get baseTexture(): BaseRenderTexture {
return this._baseTexture
}
//filter的TODO/////////
/**
* Stores `sourceFrame` when this texture is inside current filter stack.
*
* You can read it inside filters.
*
* @readonly
*/
public filterFrame: Rectangle | null;
/**
* The key for pooled texture of FilterSystem.
*
* @see PIXI.RenderTexturePool
*/
public filterPoolKey: string | number | null;
/**
* @param {BaseRenderTexture} baseRenderTexture
* @param {Rectangle} [frame]
*/
constructor(baseRenderTexture: BaseRenderTexture, frame?: Rectangle) {
super(baseRenderTexture, frame);
this.valid = true;
this.updateUvs();
}
get framebuffer(): Framebuffer {
return (this.baseTexture as BaseRenderTexture).framebuffer;
}
/**
* 重置尺寸
* @param {number} width - 宽度
* @param {number} height - 高度
* @param {boolean} doNotResizeBaseTexture - 是否不重置基础纹理的尺寸,默认false,表示也重置
*/
resize(width: number, height: number, doNotResizeBaseTexture: boolean = false) {
width = Math.ceil(width);
height = Math.ceil(height);
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();
}
/**
* 创建renderTexture快捷静态方法
* @param {number} [width=100] - 宽度
* @param {number} [height=100] - 高度
* @return {RenderTexture}
*/
static create(width: number, height: number): RenderTexture {
return new RenderTexture(new BaseRenderTexture(width, height));
}
}
interface IGLFrameBuffer {
framebuffer: WebGLFramebuffer;
stencil?: WebGLRenderbuffer;
dirtyId: number;
mipLevel: number;
}
class FramebufferManager {
public readonly _managedFramebuffers: Array<Framebuffer> = [];
public current: Framebuffer;
public viewport: Rectangle = new Rectangle();
private map: WeakMap<Framebuffer, IGLFrameBuffer>;
/////renderTexture的放这里;
private currentRenderTexture: RenderTexture;
public defaultMaskStack: any[] = [];
public readonly sourceFrame: Rectangle = new Rectangle();
public readonly destinationFrame: Rectangle = new Rectangle();;
constructor(public renderer: WebglRenderer) {
this.renderer.addEventListener('onContextChange', this.onContextChange, this);
}
onContextChange() {
this.map = new WeakMap();
this.current = null;
this.viewport.clear();
this.disposeAll(true);
//有些扩展的,用到时再说了TODO
}
bindRenderTexture(renderTexture?: RenderTexture, sourceFrame?: Rectangle, destinationFrame?: Rectangle): void {
const renderer = this.renderer;
this.currentRenderTexture = renderTexture;
let baseTexture: BaseRenderTexture;
let framebuffer: Framebuffer;
//有的时候
if (renderTexture) {
baseTexture = renderTexture.baseTexture as BaseRenderTexture;
if (!sourceFrame) {
sourceFrame = new Rectangle(
0, 0,
renderTexture.frame.width,
renderTexture.frame.height
);
}
if (!destinationFrame) {
destinationFrame = new Rectangle(
renderTexture.frame.x,
renderTexture.frame.y,
sourceFrame.width,
sourceFrame.height
);
}
framebuffer = baseTexture.framebuffer;
}
else {
if (!sourceFrame) {
sourceFrame = new Rectangle(
0, 0,
renderer.width,
renderer.height
);
}
if (!destinationFrame) {
destinationFrame = new Rectangle();
destinationFrame.width = sourceFrame.width;
destinationFrame.height = sourceFrame.height;
}
}
const viewportFrame = new Rectangle();
viewportFrame.x = destinationFrame.x;
viewportFrame.y = destinationFrame.y;
viewportFrame.width = destinationFrame.width;
viewportFrame.height = destinationFrame.height;
//如果root,处理下y,为啥区分root
if (!renderTexture) {
viewportFrame.y = renderer.height - (viewportFrame.y + viewportFrame.height);
}
this.bind(framebuffer, viewportFrame);
//计算project,TODO
// this.renderer.projection.update(destinationFrame, sourceFrame, resolution, !framebuffer);
//TODO
// if (renderTexture) {
// this.renderer.maskManager.setMaskStack(baseTexture.maskStack);
// }
// else {
// this.renderer.maskManager.setMaskStack(this.defaultMaskStack);
// }
//暂时这么处理,只有模版缓存,setMaskStack还需要加逻辑判断,TODO
this.renderer.stencilManager.setMaskStack(renderTexture ? baseTexture.maskStack : this.defaultMaskStack)
this.sourceFrame.copy(sourceFrame);
this.destinationFrame.copy(destinationFrame);
}
reset() {
this.bindRenderTexture(null);
}
bind(framebuffer?: Framebuffer, frame?: Rectangle, mipLevel = 0): void {
const gl = this.renderer.gl;
//作为主窗口root
if (!framebuffer) {
//有绑定过的移除
if (this.current) {
this.current = null;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
if (frame) {
this.setViewport(frame.x, frame.y, frame.width, frame.height);
}
else {
this.setViewport(0, 0, this.renderer.width, this.renderer.height);
}
return;
}
let fbo = this.map.get(framebuffer)
if (!fbo) {
fbo = {
dirtyId: -1,
mipLevel: 0,
framebuffer: gl.createFramebuffer()
};
this.map.set(framebuffer, fbo);
//进管理
this._managedFramebuffers.push(framebuffer);
}
if (this.current !== framebuffer) {
this.current = framebuffer;
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer);
}
// make sure all textures are unbound..
if (fbo.mipLevel !== mipLevel) {
framebuffer._dirtyId++;
fbo.mipLevel = mipLevel;
}
//TODO
const textureManager: any = this.renderer.textureManager;
// now check for updates...
if (fbo.dirtyId !== framebuffer._dirtyId) {
fbo.dirtyId = framebuffer._dirtyId;
const colorTexture = framebuffer.colorTexture;
if (colorTexture) {
(textureManager as TextureManager).bind(colorTexture, 0);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0 + 0,
gl.TEXTURE_2D, // texture.target,
(textureManager as TextureManager).get(colorTexture),
mipLevel);
}
// if (framebuffer.depthTexture) {
// const writeDepthTexture = this.writeDepthTexture;
// if (writeDepthTexture) {
// const depthTexture = framebuffer.depthTexture;
// this.renderer.texture.bind(depthTexture, 0);
// gl.framebufferTexture2D(gl.FRAMEBUFFER,
// gl.DEPTH_ATTACHMENT,
// gl.TEXTURE_2D,
// depthTexture._glTextures[this.CONTEXT_UID].texture,
// mipLevel);
// }
// }
//和上面的是冲突的,上面的不用,就用下面的
// if ((framebuffer.stencil || framebuffer.depth) && !(framebuffer.depthTexture && this.writeDepthTexture)) {
if (framebuffer.stencil) {//帧缓存里需要模版缓存时,比如滤镜里的显示对象有遮罩
fbo.stencil = fbo.stencil || gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, framebuffer.width, framebuffer.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, fbo.stencil);
}
//否则删除
else if (fbo.stencil) {
gl.deleteRenderbuffer(fbo.stencil);
fbo.stencil = null;
}
}
//所有的纹理解除绑定
if (framebuffer.colorTexture) (textureManager as TextureManager).unbind(framebuffer.colorTexture);
// if (framebuffer.depthTexture) {
// (textureManager as TextureManager).unbind(framebuffer.depthTexture);
// }
if (frame) {
const mipWidth = (frame.width >> mipLevel);
const mipHeight = (frame.height >> mipLevel);
const scale = mipWidth / frame.width;
this.setViewport(
frame.x * scale,
frame.y * scale,
mipWidth,
mipHeight
);
}
else {
const mipWidth = (framebuffer.width >> mipLevel);
const mipHeight = (framebuffer.height >> mipLevel);
this.setViewport(0, 0, mipWidth, mipHeight);
}
}
/**
* webgl窗口,注意是左下角开始
* @param {Number} x 左下角x
* @param {Number} y 左下角y
* @param {Number} width
* @param {Number} height
*/
setViewport(x: number, y: number, width: number, height: number): void {
const v = this.viewport;
//处理下,整数
x = Math.round(x);
y = Math.round(y);
width = Math.round(width);
height = Math.round(height);
if (v.width !== width || v.height !== height || v.x !== x || v.y !== y) {
v.x = x;
v.y = y;
v.width = width;
v.height = height;
this.renderer.gl.viewport(x, y, width, height);
}
}
/**
* 对于帧缓存内的遮罩,必须开启
* @returns
*/
forceStencil(): void {
const framebuffer = this.current;
//是root
if (!framebuffer) return;
const fbo = this.map.get(framebuffer);
//没有或已经加了
if (!fbo || fbo.stencil) return;
framebuffer.stencil = true;
const w = framebuffer.width;
const h = framebuffer.height;
const gl = this.renderer.gl;
const stencil = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, stencil);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h);
fbo.stencil = stencil;
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencil);
}
clear(clearColor?: [number, number, number, number]): void {
const gl = this.renderer.gl;
const destinationFrame = this.destinationFrame;
const baseFrame = this.currentRenderTexture ? this.currentRenderTexture.baseTexture : this.renderer;
const clearMask = destinationFrame.width !== baseFrame.width || destinationFrame.height !== baseFrame.height;
if (clearMask) {
let { x, y, width, height } = this.viewport;
x = Math.round(x);
y = Math.round(y);
width = Math.round(width);
height = Math.round(height);
// TODO: ScissorSystem should cache whether the scissor test is enabled or not.
this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST);
this.renderer.gl.scissor(x, y, width, height);
}
gl.clearColor(...clearColor);
//默认,以后有需要再改
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
if (clearMask) {
gl.disable(gl.SCISSOR_TEST);
//这里原先考虑其他有裁切的地方,需要恢复的意思,目前没有,直接处理,TODO到时得用起来
// this.renderer.scissor.pop();
}
}
/**
*
* @param framebuffer
* @param contextLost
* @returns
*/
dispose(framebuffer: Framebuffer, contextLost?: boolean): void {
const fbo = this.map.get(framebuffer);
const gl = this.renderer.gl;
if (!fbo) return;
this.map.delete(framebuffer)
const index = this._managedFramebuffers.indexOf(framebuffer);
if (index >= 0) {
this._managedFramebuffers.splice(index, 1);
}
//不是上下文丢失的情况需要相应删除Framebuffer
if (!contextLost) {
gl.deleteFramebuffer(fbo.framebuffer);
if (fbo.stencil) {
gl.deleteRenderbuffer(fbo.stencil);
}
}
}
disposeAll(contextLost?: boolean): void {
for (let i = 0; i < this._managedFramebuffers.length; i++) {
this.dispose(this._managedFramebuffers[i], contextLost);
}
this._managedFramebuffers.length = 0;
}
destroy() {
this.renderer = null;
}
}
////////着色器的
interface IShaderAttribute {
//float,vec2,mat4等等,暂时好像没怎么用到
type: string,
size: number,
location: number,
}
interface IShaderUniformData {
//float,vec2,mat4等等,暂时好像没怎么用到
type: string,
size: number,
location: number,
value: any
}
class GLShader extends HashObject {
/**
* 上下文
*/
private gl: WebGLRenderingContext;
/**
* 着色器标识,可以根据参数,也可以直接是顶点和片元的字符串,默认就用v__f
*/
_glShaderKey: string;
private program: WebGLProgram;
private _attributes: Dict<IShaderAttribute>;
//
get attributes() {
return this._attributes;
}
//这个感觉没有必要暴露,后续考虑删除
private _uniformData: Dict<IShaderUniformData>;
//
get uniformData() {
return this._uniformData;
}
private _uniforms: any//Dict<{ data: IShaderUniformData }>;
/**
* 这是个特殊的对象,直接uniforms.color=[1,1,1]就能传uniform
*/
get uniforms() {
return this._uniforms
}
/**
*
* @param gl
* @param vertexSrc
* @param fragmentSrc
* @param removeBracketUnifroms 解析uniform时是否需要移除中括号,对于数字数组应该移除,然后传数组
* @param attributeLocations 这个先留着吧,暂时就没有用到过
*/
constructor(
gl: WebGLRenderingContext,
vertexSrc: string,
fragmentSrc: string,
removeBracketUnifroms?: string[],
attributeLocations?
) {
super();
this._instanceType = "GLShader";
this.gl = gl;
//编译着色器
this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations);
//解析顶点通道
this._attributes = extractAttributes(gl, this.program);
//解析uniform通道
this._uniformData = extractUniforms(gl, this.program, removeBracketUnifroms);
//给uniform建立get set映射
this._uniforms = generateUniformAccessObject(gl, this._uniformData);
//缓存唯一值
this._glShaderKey = generateShaderKey(vertexSrc, fragmentSrc);
};
/**
* 状态机当前使用的shader
* @return
*/
public bind(): this {
this.gl.useProgram(this.program);
return this;
};
/**
* 销毁方法
*/
public destroy() {
this._attributes = null;
this._uniformData = null;
this._uniforms = null;
var gl = this.gl;
gl.deleteProgram(this.program);
};
}
/**
* 修改uniform获取方法
* @param gl
* @param program
* @param removeBracketUnifroms 需要移除中括号的uniform,比如["morphTargetInfluences","uBoneMatrices","uSamplers"]
* @returns
*/
function extractUniforms(gl: WebGLRenderingContext, program: WebGLProgram, removeBracketUnifroms?: string[]): Dict<IShaderUniformData> {
var uniforms = {};
var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (var i = 0; i < totalUniforms; i++) {//morphTargetInfluences
var uniformData: WebGLActiveInfo = gl.getActiveUniform(program, i);
var name = uniformData.name;
//初略判断,
if (removeBracketUnifroms && removeBracketUnifroms.length) {
for (let j = 0; j < removeBracketUnifroms.length; j++) {
//name在列表内,去掉中括号
if (name.indexOf(removeBracketUnifroms[j]) == 0) {
name = name.replace(/\[.*?\]/, "");
break;
}
}
}
var type = mapType(gl, uniformData.type);
// console.log(name)
uniforms[name] = {
type: type,
size: uniformData.size,
location: gl.getUniformLocation(program, name),
value: defaultValue(type, uniformData.size)
};
}
return uniforms;
};
/**
* 默认逗号间隔,统一的方法
* @param str
* @returns
*/
function generateShaderKey(...str: string[]) {
//默认逗号间隔,不传就是逗号
return str.join();
}
//一样和渲染器绑定,没啥多余的功能,考虑是否根据着色器语言进行绑定?还是自行处理GLShader
class ShaderManager {
private currentShader: GLShader;
//缓存着色器,用对象是怕字符串太长?
private cacheShaders: GLShader[];
constructor(public renderer: WebglRenderer) {
this.cacheShaders = [];
this.renderer.addEventListener('onContextChange', this.onContextChange, this);
}
onContextChange() {
//原来存的都清了;
this.cacheShaders.length = 0;
}
/**
*
* @param vertexSrc 顶点着色器字符串
* @param fragmentSrc 片元着色器字符串
*/
bind(vertexSrc: string, fragmentSrc: string) {
let shaderKey = generateShaderKey(vertexSrc, fragmentSrc);
for (var i = 0; i < this.cacheShaders.length; i++) {
if (this.cacheShaders[i]._glShaderKey == shaderKey) {
this.bindShader(this.cacheShaders[i]);
return;
}
}
this.bindShader(new GLShader(this.renderer.gl, vertexSrc, fragmentSrc))
}
/**
* 直接固定的着色器绑定
* @param shader
* @returns
*/
bindShader(shader: GLShader) {
if (this.currentShader == shader) return;
this.currentShader = shader;
shader.bind();
//存一下
if (this.cacheShaders.indexOf(shader) == -1) {
this.cacheShaders.push(shader);
}
}
destroy() {
this.renderer = null;
}
}
class Geometry extends HashObject {
/**
* 基本就两个,postion和normal
*/
_morphAttributes: Dict<DataAttribute[]>;
/**
* 所有顶点数据
*/
_attributes: Dict<DataAttribute | InterleavedDataAttribute>;
//这两个后面再说
boundingBox: Box3;
boundingSphere: Sphere;
constructor() {
super();
this._instanceType = "Geometry";
this._index = null;
this._attributes = {};
this._morphAttributes = {};
this.boundingBox = null;
this.boundingSphere = null;
}
private _index: DataAttribute;
/**
* 赋值建议直接用普通数组
*/
get index(): DataAttribute {
return this._index;
}
set index(index: ArrayLike<number> | DataAttribute) {
//纯数组的转换下
if (Array.isArray(index)) {
//如果索引最大值超过了65535,记得需要开扩展和glType
this._index = new DataAttribute(new (Math.max(...index) > 65535 ? Uint32Array : Uint16Array)(index), 1);
}
else if (index instanceof DataAttribute) {
this._index = index;
}
//不存在
else if (!index) {
this._index = null;
}
//类型化数组
else {
this._index = new DataAttribute(index, 1);
}
}
getAttribute(name: string) {
return this._attributes[name];
}
/**
* 会覆盖同名attribute
* @param name
* @param attribute
* @returns
*/
addAttribute(name: string, attribute: DataAttribute | InterleavedDataAttribute): this {
this._attributes[name] = attribute;
return this;
}
/**
* 移除
* @param name
* @returns
*/
removeAttribute(name: string): this {
delete this._attributes[name];
return this;
}
clone() {
return new Geometry().copy(this);
}
//
copy(source: Geometry) {
const { index, _attributes, _morphAttributes, boundingBox, boundingSphere } = source
this.boundingBox = null;
this.boundingSphere = null;
this._index = null;
if (index) {
this.index = index.clone();
}
this._attributes = {};
let interleavedDatasClone: Dict<InterleavedData> = {};
for (const name in _attributes) {
const attribute = _attributes[name];
if (attribute.instanceType == "InterleavedDataAttribute") {
let id = (attribute as InterleavedDataAttribute).data;
let idClone = interleavedDatasClone[id.instanceId];
if (!idClone) {
idClone = interleavedDatasClone[id.instanceId] = id.clone();
}
this.addAttribute(name, new InterleavedDataAttribute(
idClone,
attribute.itemSize,
(attribute as InterleavedDataAttribute).offset,
attribute.normalized
))
} else {
this.addAttribute(name, (attribute as DataAttribute).clone());
}
}
this._morphAttributes = {};
for (const name in _morphAttributes) {
const array: DataAttribute[] = [];
const morphAttribute = _morphAttributes[name];
for (let i = 0, l = morphAttribute.length; i < l; i++) {
array.push(morphAttribute[i].clone());
}
//赋值给自己
this._morphAttributes[name] = array;
}
if (boundingBox) {
this.boundingBox = boundingBox.clone();
}
if (boundingSphere) {
this.boundingSphere = boundingSphere.clone();
}
return this;
}
destroy() {
//TODO
// this._index = null;
// this._attributes = {};
// this._morphAttributes = null;
// this.boundingBox = null;
// this.boundingSphere = null;
} }
} }
\ No newline at end of file
...@@ -425,11 +425,11 @@ export class Object3D extends EventDispatcher { ...@@ -425,11 +425,11 @@ export class Object3D extends EventDispatcher {
destroy() { destroy() {
//子级的destroy //子级的destroy
for (let i = this.children.length - 1; i >= 0; i--) this.children[i].destroy(); for (let i = this.children.length - 1; i >= 0; i--) this.children[i].destroy();
//移除事件
this.removeAllEventListener();
// super.destroy();//不适用继承的,里面的eventTypes置空很麻烦 // super.destroy();//不适用继承的,里面的eventTypes置空很麻烦
//从父级移除自己 //从父级移除自己
if (this.parent) this.parent.removeChild(this); if (this.parent) this.parent.removeChild(this);
//移除事件
this.removeAllEventListener();
//还有很多自己的属性,再说了 //还有很多自己的属性,再说了
} }
......
( function () {
/**
* Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
*
* Supports both binary and ASCII encoded files, with automatic detection of type.
*
* The loader returns a non-indexed buffer geometry.
*
* Limitations:
* Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL).
* There is perhaps some question as to how valid it is to always assume little-endian-ness.
* ASCII decoding assumes file is UTF-8.
*
* Usage:
* const loader = new STLLoader();
* loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
* scene.add( new THREE.Mesh( geometry ) );
* });
*
* For binary STLs geometry might contain colors for vertices. To use it:
* // use the same code to load STL as above
* if (geometry.hasColors) {
* material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true });
* } else { .... }
* const mesh = new THREE.Mesh( geometry, material );
*
* For ASCII STLs containing multiple solids, each solid is assigned to a different group.
* Groups can be used to assign a different color by defining an array of materials with the same length of
* geometry.groups and passing it to the Mesh constructor:
*
* const mesh = new THREE.Mesh( geometry, material );
*
* For example:
*
* const materials = [];
* const nGeometryGroups = geometry.groups.length;
*
* const colorMap = ...; // Some logic to index colors.
*
* for (let i = 0; i < nGeometryGroups; i++) {
*
* const material = new THREE.MeshPhongMaterial({
* color: colorMap[i],
* wireframe: false
* });
*
* }
*
* materials.push(material);
* const mesh = new THREE.Mesh(geometry, materials);
*/
class STLLoader extends THREE.Loader {
constructor( manager ) {
super( manager );
}
load( url, onLoad, onProgress, onError ) {
const scope = this;
const loader = new THREE.FileLoader( this.manager );
loader.setPath( this.path );
loader.setResponseType( 'arraybuffer' );
loader.setRequestHeader( this.requestHeader );
loader.setWithCredentials( this.withCredentials );
loader.load( url, function ( text ) {
try {
onLoad( scope.parse( text ) );
} catch ( e ) {
if ( onError ) {
onError( e );
} else {
console.error( e );
}
scope.manager.itemError( url );
}
}, onProgress, onError );
}
parse( data ) {
function isBinary( data ) {
const reader = new DataView( data );
const face_size = 32 / 8 * 3 + 32 / 8 * 3 * 3 + 16 / 8;
const n_faces = reader.getUint32( 80, true );
const expect = 80 + 32 / 8 + n_faces * face_size;
if ( expect === reader.byteLength ) {
return true;
} // An ASCII STL data must begin with 'solid ' as the first six bytes.
// However, ASCII STLs lacking the SPACE after the 'd' are known to be
// plentiful. So, check the first 5 bytes for 'solid'.
// Several encodings, such as UTF-8, precede the text with up to 5 bytes:
// https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
// Search for "solid" to start anywhere after those prefixes.
// US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd'
const solid = [ 115, 111, 108, 105, 100 ];
for ( let off = 0; off < 5; off ++ ) {
// If "solid" text is matched to the current offset, declare it to be an ASCII STL.
if ( matchDataViewAt( solid, reader, off ) ) return false;
} // Couldn't find "solid" text at the beginning; it is binary STL.
return true;
}
function matchDataViewAt( query, reader, offset ) {
// Check if each byte in query matches the corresponding byte from the current offset
for ( let i = 0, il = query.length; i < il; i ++ ) {
if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false;
}
return true;
}
function parseBinary( data ) {
const reader = new DataView( data );
const faces = reader.getUint32( 80, true );
let r,
g,
b,
hasColors = false,
colors;
let defaultR, defaultG, defaultB, alpha; // process STL header
// check for default color in header ("COLOR=rgba" sequence).
for ( let index = 0; index < 80 - 10; index ++ ) {
if ( reader.getUint32( index, false ) == 0x434F4C4F
/*COLO*/
&& reader.getUint8( index + 4 ) == 0x52
/*'R'*/
&& reader.getUint8( index + 5 ) == 0x3D
/*'='*/
) {
hasColors = true;
colors = new Float32Array( faces * 3 * 3 );
defaultR = reader.getUint8( index + 6 ) / 255;
defaultG = reader.getUint8( index + 7 ) / 255;
defaultB = reader.getUint8( index + 8 ) / 255;
alpha = reader.getUint8( index + 9 ) / 255;
}
}
const dataOffset = 84;
const faceLength = 12 * 4 + 2;
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array( faces * 3 * 3 );
const normals = new Float32Array( faces * 3 * 3 );
for ( let face = 0; face < faces; face ++ ) {
const start = dataOffset + face * faceLength;
const normalX = reader.getFloat32( start, true );
const normalY = reader.getFloat32( start + 4, true );
const normalZ = reader.getFloat32( start + 8, true );
if ( hasColors ) {
const packedColor = reader.getUint16( start + 48, true );
if ( ( packedColor & 0x8000 ) === 0 ) {
// facet has its own unique color
r = ( packedColor & 0x1F ) / 31;
g = ( packedColor >> 5 & 0x1F ) / 31;
b = ( packedColor >> 10 & 0x1F ) / 31;
} else {
r = defaultR;
g = defaultG;
b = defaultB;
}
}
for ( let i = 1; i <= 3; i ++ ) {
const vertexstart = start + i * 12;
const componentIdx = face * 3 * 3 + ( i - 1 ) * 3;
vertices[ componentIdx ] = reader.getFloat32( vertexstart, true );
vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true );
vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true );
normals[ componentIdx ] = normalX;
normals[ componentIdx + 1 ] = normalY;
normals[ componentIdx + 2 ] = normalZ;
if ( hasColors ) {
colors[ componentIdx ] = r;
colors[ componentIdx + 1 ] = g;
colors[ componentIdx + 2 ] = b;
}
}
}
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
geometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
if ( hasColors ) {
geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
geometry.hasColors = true;
geometry.alpha = alpha;
}
return geometry;
}
function parseASCII( data ) {
const geometry = new THREE.BufferGeometry();
const patternSolid = /solid([\s\S]*?)endsolid/g;
const patternFace = /facet([\s\S]*?)endfacet/g;
let faceCounter = 0;
const patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source;
const patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' );
const patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' );
const vertices = [];
const normals = [];
const normal = new THREE.Vector3();
let result;
let groupCount = 0;
let startVertex = 0;
let endVertex = 0;
while ( ( result = patternSolid.exec( data ) ) !== null ) {
startVertex = endVertex;
const solid = result[ 0 ];
while ( ( result = patternFace.exec( solid ) ) !== null ) {
let vertexCountPerFace = 0;
let normalCountPerFace = 0;
const text = result[ 0 ];
while ( ( result = patternNormal.exec( text ) ) !== null ) {
normal.x = parseFloat( result[ 1 ] );
normal.y = parseFloat( result[ 2 ] );
normal.z = parseFloat( result[ 3 ] );
normalCountPerFace ++;
}
while ( ( result = patternVertex.exec( text ) ) !== null ) {
vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );
normals.push( normal.x, normal.y, normal.z );
vertexCountPerFace ++;
endVertex ++;
} // every face have to own ONE valid normal
if ( normalCountPerFace !== 1 ) {
console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter );
} // each face have to own THREE valid vertices
if ( vertexCountPerFace !== 3 ) {
console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter );
}
faceCounter ++;
}
const start = startVertex;
const count = endVertex - startVertex;
geometry.addGroup( start, count, groupCount );
groupCount ++;
}
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
return geometry;
}
function ensureString( buffer ) {
if ( typeof buffer !== 'string' ) {
return THREE.LoaderUtils.decodeText( new Uint8Array( buffer ) );
}
return buffer;
}
function ensureBinary( buffer ) {
if ( typeof buffer === 'string' ) {
const array_buffer = new Uint8Array( buffer.length );
for ( let i = 0; i < buffer.length; i ++ ) {
array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian
}
return array_buffer.buffer || array_buffer;
} else {
return buffer;
}
} // start
const binData = ensureBinary( data );
return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) );
}
}
THREE.STLLoader = STLLoader;
} )();
<!DOCTYPE html>
<html lang="en">
<head>
<title>test</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #cccccc;
font-family: Monospace;
font-size: 13px;
text-align: center;
background-color: #999999;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="./js/three.js"></script>
<script src="./js/OrbitControls.js"></script>
<script src="./js/STLLoader.js"></script>
<script>
// if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var container, stats;
var camera, scene, renderer;
var mesh, parent_node;
var testObjs = [];
init();
render();
function init() {
container = document.getElementById('container');
camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 10000);
// camera.position.set(10, 10, 10);
camera.position.set(0, 0, 130);
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.gammaInput = true;
renderer.gammaOutput = true;
//相机控件,方便调整角度
var orbit = new THREE.OrbitControls(camera, renderer.domElement);
// orbit.enableZoom = false;
container.appendChild(renderer.domElement);
// 直线光
var dl = new THREE.DirectionalLight()
dl.position.set(1, -2, 3)
scene.add(dl);
scene.add(new THREE.AmbientLight(0xffffff, 0.2));
//网格
scene.add(new THREE.GridHelper(100, 100, 100, 0x888888, 0x888888))
//加坐标轴
scene.add(new THREE.AxesHelper(1000))
//加载模型
const loader = new THREE.STLLoader();
const material = new THREE.MeshPhongMaterial({ color: 0xAAAAAA, specular: 0x111111, shininess: 200, wireframe: true });
loader.load('./assets/glass.stl', function (geometry) {
const mesh = new THREE.Mesh(geometry, material);
console.log(geometry)
geometry.computeBoundingBox()
// mesh.position.set(0, - 0.37, - 0.6);
mesh.rotation.set(- Math.PI / 2, 0, 0);
mesh.scale.set(0.2, 0.2, 0.2);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
//测试下拉伸
//只处理右边x轴的先
//变形基准点,左边区间往左,右边区间往右
let mark = 0,
//拉伸区间,绝对坐标?后续再考虑
range = [20, 50],
//拉伸宽度
width = 23;
//取顶点数据
let attrP = geometry.attributes["position"];
//拷贝一份初始数据
let orignArray = new Float32Array(attrP.array);
for (let i = 0; i < orignArray.length; i += 3) {
let x = orignArray[i];
let [start, end] = range;
//在拉伸区间内的
if (x > start && x < end) {
//均匀偏移,如果失真,考虑修改权重,靠近边界的权重小,远离的权重大
attrP.array[i] = x + Math.abs(x - start) / (end - start) * width;
}
//大于end的所有点便宜固定的量
else if (x >= end) {
attrP.array[i] = x + width
}
}
//更新一下顶点
attrP.needsUpdate = true;
});
}
function render() {
var time = Date.now() * 0.001;
// parent_node.rotation.z = time * 0.5;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
</script>
</body>
</html>
\ No newline at end of file
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
"lib": [ "lib": [
"es5", "es5",
"dom", "dom",
"es6",
"es2015.promise" "es2015.promise"
] ]
}, },
......
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