@awayjs/scene
Version:
AwayJS scene classes
509 lines (407 loc) • 13.1 kB
text/typescript
import {
PickingCollision,
PickEntity,
_Pick_PickableBase,
IEntity,
} from '@awayjs/view';
import {
RenderableEvent,
MaterialEvent,
IMaterial,
StyleEvent,
} from '@awayjs/renderer';
import { Rectangle, Matrix3D, Box, Vector3D, Sphere } from '@awayjs/core';
import {
ImageSampler,
Image2D,
ImageUtils,
ContextGLTriangleFace,
} from '@awayjs/stage';
import { DisplayObjectContainer } from './DisplayObjectContainer';
/**
* The Billboard class represents display objects that represent bitmap images.
* These can be images that you load with the <code>flash.Assets</code> or
* <code>flash.display.Loader</code> classes, or they can be images that you
* create with the <code>Billboard()</code> constructor.
*
* <p>The <code>Billboard()</code> constructor allows you to create a Billboard
* object that contains a reference to a Image2D object. After you create a
* Billboard object, use the <code>addChild()</code> or <code>addChildAt()</code>
* method of the parent DisplayObjectContainer instance to place the bitmap on
* the display list.</p>
*
* <p>A Billboard object can share its Image2D reference among several Billboard
* objects, independent of translation or rotation properties. Because you can
* create multiple Billboard objects that reference the same Image2D object,
* multiple display objects can use the same complex Image2D object without
* incurring the memory overhead of a Image2D object for each display
* object instance.</p>
*
* <p>A Image2D object can be drawn to the screen by a Billboard object in one
* of two ways: by using the default hardware renderer with a single hardware surface,
* or by using the slower software renderer when 3D acceleration is not available.</p>
*
* <p>If you would prefer to perform a batch rendering command, rather than using a
* single surface for each Billboard object, you can also draw to the screen using the
* <code>drawTiles()</code> or <code>drawTriangles()</code> methods which are
* available to <code>flash.display.Tilesheet</code> and <code>flash.display.Graphics
* objects.</code></p>
*
* <p><b>Note:</b> The Billboard class is not a subclass of the InteractiveObject
* class, so it cannot dispatch mouse events. However, you can use the
* <code>addEventListener()</code> method of the display object container that
* contains the Billboard object.</p>
*/
/**
* @todo billboard needed to extend on DisplayObjectContainer in order
* for as3web/away3d adapters to compile without errors
* (in away3d Sprite3D extends on ObjectContainer3D)
*/
export class Billboard extends DisplayObjectContainer {
private static _billboards: Array<Billboard> = new Array<Billboard>();
public static assetType: string = '[asset Billboard]';
private _width: number;
private _height: number;
private _billboardWidth: number;
private _billboardHeight: number;
private _billboardRect: Rectangle;
private _sampler: ImageSampler;
private _onInvalidateTextureDelegate: (event: MaterialEvent) => void;
public static getNewBillboard(
material: IMaterial, pixelSnapping: string = 'auto', smoothing: boolean = false): Billboard {
if (Billboard._billboards.length) {
const billboard: Billboard = Billboard._billboards.pop();
billboard.material = material;
//billboard.pixelSnapping = pixelSnapping;
//billboard.smoothing = smoothing;
return billboard;
}
return new Billboard(material, pixelSnapping, smoothing);
}
public preserveDimensions: boolean = false;
/**
*
*/
public get assetType(): string {
return Billboard.assetType;
}
/**
*
*/
public get billboardRect(): Rectangle {
return this._billboardRect;
}
/**
*
*/
public get billboardHeight(): number {
return this._billboardHeight;
}
/**
*
*/
public get billboardWidth(): number {
return this._billboardWidth;
}
/**
*
*/
public get material(): IMaterial {
return this._material;
}
public set material(value: IMaterial) {
if (value == this._material)
return;
if (this._material)
this._material.removeEventListener(MaterialEvent.INVALIDATE_TEXTURES, this._onInvalidateTextureDelegate);
this._material = value;
if (this._material)
this._material.addEventListener(MaterialEvent.INVALIDATE_TEXTURES, this._onInvalidateTextureDelegate);
this._updateDimensions();
this._invalidateMaterial();
}
public get sampler(): ImageSampler {
return this._sampler;
}
/**
*
*/
public get width(): number {
return this._width;
}
public set width(val: number) {
if (this._width == val)
return;
this._width = val;
this.scaleX = this._width / this._billboardRect.width;
}
/**
*
*/
public get height(): number {
return this._height;
}
public set height(val: number) {
if (this._height == val)
return;
this._height = val;
this.scaleY = this._height / this._billboardRect.height;
}
constructor(material: IMaterial, _pixelSnapping: string = 'auto', smoothing: boolean = false) {
super();
this._onInvalidateTextureDelegate = (event: MaterialEvent) => this._onInvalidateTexture(event);
this.material = material;
this._updateDimensions();
if (this._sampler)
this._sampler.smooth = smoothing;
}
public advanceFrame(): void {
//override for billboard
}
public getEntity(): IEntity {
return this;
}
/**
* @inheritDoc
*/
public dispose(): void {
this.disposeValues();
Billboard._billboards.push(this);
}
public clone(): Billboard {
const newInstance: Billboard = Billboard.getNewBillboard(this._material);
this.copyTo(newInstance);
return newInstance;
}
public _acceptTraverser(traverser: IEntityTraverser): void {
if (!this.material) {
return;
}
const texture = this.material.getTextureAt(0);
if (!texture) {
return;
}
const image = this.image;
if (!image || image.isDisposed) {
return;
}
traverser.applyTraversable(this);
}
public get image(): Image2D {
if (!this.material) {
return null;
}
const texture = this.material.getTextureAt(0);
if (!texture) {
return null;
}
let image = this._style?.getImageAt(texture);
if (image) {
return <Image2D> image;
}
image = this.material.style?.getImageAt(texture);
if (image) {
return <Image2D> image;
}
return <Image2D> texture.getImageAt(0);
}
private _updateDimensions(): void {
const image = this.image;
if (image && !image.isDisposed) {
const texture = this.material.getTextureAt(0);
this._sampler = <ImageSampler> (
this._style?.getSamplerAt(texture)
|| this.material.style?.getSamplerAt(texture)
|| texture.getSamplerAt(0)
|| ImageUtils.getDefaultImageSampler());
if (this._sampler.imageRect) {
this._billboardWidth = this._sampler.imageRect.width * image.width;
this._billboardHeight = this._sampler.imageRect.height * image.height;
} else {
this._billboardWidth = image.rect.width;
this._billboardHeight = image.rect.height;
}
this._billboardRect =
this._sampler.frameRect || new Rectangle(0, 0, this._billboardWidth, this._billboardHeight);
} else {
this._billboardWidth = 1;
this._billboardHeight = 1;
this._billboardRect = new Rectangle(0, 0, 1, 1);
}
this.invalidate();
this.invalidateElements();
if (!this.preserveDimensions) {
this._width = this._billboardRect.width * this.scaleX;
this._height = this._billboardRect.height * this.scaleY;
} else {
this.scaleX = this._width / this._billboardRect.width;
this.scaleY = this._height / this._billboardRect.height;
}
}
protected _onInvalidateProperties(event: StyleEvent = null): void {
super._onInvalidateProperties(event);
this._updateDimensions();
}
/**
* @private
*/
private _onInvalidateTexture(event: MaterialEvent): void {
this._updateDimensions();
}
}
import { AssetEvent } from '@awayjs/core';
import { AttributesBuffer } from '@awayjs/stage';
import {
MaterialUtils,
_Stage_ElementsBase,
_Render_MaterialBase,
_Render_RenderableBase,
RenderEntity,
Style,
TriangleElements,
_Stage_TriangleElements
} from '@awayjs/renderer';
import { IEntityTraverser } from '@awayjs/view';
/**
* @class away.pool.RenderableListItem
*/
export class _Render_Billboard extends _Render_RenderableBase {
private static _samplerElements: Object = new Object();
public _id: string;
/**
* //TODO
*
* @returns {away.base.TriangleElements}
*/
protected _getStageElements(): _Stage_ElementsBase {
const asset = <Billboard> this._asset;
const width = asset.billboardWidth;
const height = asset.billboardHeight;
const rect = asset.billboardRect;
const id = width.toString() + height.toString() + rect.toString();
this._id = id;
let elements: TriangleElements = _Render_Billboard._samplerElements[id];
if (!elements) {
const left = -rect.x;
const top = -rect.y;
const right = width - rect.x;
const bottom = height - rect.y;
elements = _Render_Billboard._samplerElements[id] = new TriangleElements(new AttributesBuffer(11, 4));
elements.autoDeriveNormals = false;
elements.autoDeriveTangents = false;
/**
* 2 tris with CCW order
* 0(3) 2
* _____
*|\ |
*| \ |
*| \ |
*|___\|
*4 1(5)
*
*/
elements.setPositions(
[
left, top, 0,
right, bottom, 0,
right, top, 0,
left, top, 0,
left, bottom, 0,
right, bottom, 0
]);
elements.setUVs([0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1]);
}/* else {
elements.setPositions(
[-billboardRect.x, height - billboardRect.y, 0,
width - billboardRect.x, height - billboardRect.y, 0,
width - billboardRect.x, -billboardRect.y, 0,
-billboardRect.x, -billboardRect.y, 0]);
}*/
return elements.getAbstraction<_Stage_TriangleElements>(this._stage);
}
public executeRender(
enableDepthAndStencil: boolean = true,
surfaceSelector: number = 0, mipmapSelector: number = 0, maskConfig: number = 0): void {
// disable cull, because for render to texture it is bugged
// we flip normals
this._stage.context.setCulling(ContextGLTriangleFace.NONE);
super.executeRender(enableDepthAndStencil, surfaceSelector, mipmapSelector, maskConfig);
}
protected _getRenderMaterial(): _Render_MaterialBase {
const material: IMaterial = (<Billboard> this._asset).material || MaterialUtils.getDefaultColorMaterial();
return material.getAbstraction<_Render_MaterialBase>(
this.entity.renderer.getRenderElements(this.stageElements.elements));
}
protected _getStyle(): Style {
return (<Billboard> this._asset).style;
}
}
/**
* @class away.pool._Render_Shape
*/
export class _Pick_Billboard extends _Pick_PickableBase {
private _billboardBox: Box;
private _billboardBoxDirty: boolean = true;
private _onInvalidateElementsDelegate: (event: RenderableEvent) => void;
constructor() {
super();
this._onInvalidateElementsDelegate = (event: RenderableEvent) => this._onInvalidateElements(event);
}
/**
* //TODO
*
* @param renderEntity
* @param shape
* @param level
* @param indexOffset
*/
public init(billboard: Billboard, pickEntity: PickEntity): void {
super.init(billboard, pickEntity);
this._asset.addEventListener(RenderableEvent.INVALIDATE_ELEMENTS, this._onInvalidateElementsDelegate);
}
public _onInvalidateElements(event: RenderableEvent): void {
this._billboardBoxDirty = true;
}
public onClear(event: AssetEvent): void {
this._asset.removeEventListener(RenderableEvent.INVALIDATE_ELEMENTS, this._onInvalidateElementsDelegate);
super.onClear(event);
this._billboardBox = null;
this._billboardBoxDirty = true;
}
public hitTestPoint(x: number, y: number, z: number): boolean {
return true;
}
public getBoxBounds(
matrix3D: Matrix3D = null, strokeFlag: boolean = true, cache: Box = null, target: Box = null): Box {
if (this._billboardBoxDirty) {
this._billboardBoxDirty = false;
const billboardRect: Rectangle = (<Billboard> this._asset).billboardRect;
this._billboardBox = new Box(
billboardRect.x, billboardRect.y, 0,
billboardRect.width, billboardRect.height, 0);
}
return (
matrix3D
? matrix3D.transformBox(this._billboardBox)
: this._billboardBox).union(target, target || cache);
}
public getSphereBounds(
center: Vector3D, matrix3D: Matrix3D = null, strokeFlag: boolean = true,
cache: Sphere = null, target: Sphere = null): Sphere {
//TODO
return target;
}
public testCollision(collision: PickingCollision, closestFlag: boolean): boolean {
const rayEntryDistance: number = -collision.rayPosition.z / collision.rayDirection.z;
const position: Vector3D = new Vector3D(
collision.rayPosition.x + rayEntryDistance * collision.rayDirection.x,
collision.rayPosition.y + rayEntryDistance * collision.rayDirection.y);
collision.traversable = this._asset;
collision.rayEntryDistance = rayEntryDistance;
collision.position = position;
collision.normal = new Vector3D(0,0,1);
return true;
}
}
RenderEntity.registerRenderable(_Render_Billboard, Billboard);
PickEntity.registerPickable(_Pick_Billboard, Billboard);