babylon.sprite2d.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. module BABYLON {
  2. export class Sprite2DRenderCache extends ModelRenderCache {
  3. effectsReady: boolean = false;
  4. vb: WebGLBuffer = null;
  5. ib: WebGLBuffer = null;
  6. instancingAttributes: InstancingAttributeInfo[] = null;
  7. texture: Texture = null;
  8. effect: Effect = null;
  9. effectInstanced: Effect = null;
  10. render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
  11. // Do nothing if the shader is still loading/preparing
  12. if (!this.effectsReady) {
  13. if ((this.effect && (!this.effect.isReady() || (this.effectInstanced && !this.effectInstanced.isReady())))) {
  14. return false;
  15. }
  16. this.effectsReady = true;
  17. }
  18. // Compute the offset locations of the attributes in the vertex shader that will be mapped to the instance buffer data
  19. let canvas = instanceInfo.owner.owner;
  20. var engine = canvas.engine;
  21. var cur = engine.getAlphaMode();
  22. let effect = context.useInstancing ? this.effectInstanced : this.effect;
  23. engine.enableEffect(effect);
  24. effect.setTexture("diffuseSampler", this.texture);
  25. engine.bindBuffersDirectly(this.vb, this.ib, [1], 4, effect);
  26. if (context.renderMode !== Render2DContext.RenderModeOpaque) {
  27. engine.setAlphaMode(Engine.ALPHA_COMBINE);
  28. }
  29. let pid = context.groupInfoPartData[0];
  30. if (context.useInstancing) {
  31. if (!this.instancingAttributes) {
  32. this.instancingAttributes = this.loadInstancingAttributes(Sprite2D.SPRITE2D_MAINPARTID, effect);
  33. }
  34. let glBuffer = context.instancedBuffers ? context.instancedBuffers[0] : pid._partBuffer;
  35. let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
  36. canvas._addDrawCallCount(1, context.renderMode);
  37. engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingAttributes);
  38. engine.draw(true, 0, 6, count);
  39. engine.unbindInstanceAttributes();
  40. } else {
  41. canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
  42. for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
  43. this.setupUniforms(effect, 0, pid._partData, i);
  44. engine.draw(true, 0, 6);
  45. }
  46. }
  47. engine.setAlphaMode(cur);
  48. return true;
  49. }
  50. public dispose(): boolean {
  51. if (!super.dispose()) {
  52. return false;
  53. }
  54. if (this.vb) {
  55. this._engine._releaseBuffer(this.vb);
  56. this.vb = null;
  57. }
  58. if (this.ib) {
  59. this._engine._releaseBuffer(this.ib);
  60. this.ib = null;
  61. }
  62. if (this.texture) {
  63. this.texture.dispose();
  64. this.texture = null;
  65. }
  66. if (this.effect) {
  67. this._engine._releaseEffect(this.effect);
  68. this.effect = null;
  69. }
  70. if (this.effectInstanced) {
  71. this._engine._releaseEffect(this.effectInstanced);
  72. this.effectInstanced = null;
  73. }
  74. return true;
  75. }
  76. }
  77. export class Sprite2DInstanceData extends InstanceDataBase {
  78. constructor(partId: number) {
  79. super(partId, 1);
  80. }
  81. @instanceData()
  82. get topLeftUV(): Vector2 {
  83. return null;
  84. }
  85. @instanceData()
  86. get sizeUV(): Vector2 {
  87. return null;
  88. }
  89. @instanceData()
  90. get textureSize(): Vector2 {
  91. return null;
  92. }
  93. // 3 floats being:
  94. // - x: frame number to display
  95. // - y: invertY setting
  96. // - z: alignToPixel setting
  97. @instanceData()
  98. get properties(): Vector3 {
  99. return null;
  100. }
  101. }
  102. @className("Sprite2D")
  103. /**
  104. * Primitive that displays a Sprite/Picture
  105. */
  106. export class Sprite2D extends RenderablePrim2D {
  107. static SPRITE2D_MAINPARTID = 1;
  108. public static textureProperty: Prim2DPropInfo;
  109. public static actualSizeProperty: Prim2DPropInfo;
  110. public static spriteLocationProperty: Prim2DPropInfo;
  111. public static spriteFrameProperty: Prim2DPropInfo;
  112. public static invertYProperty: Prim2DPropInfo;
  113. public static alignToPixelProperty: Prim2DPropInfo;
  114. @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Sprite2D.textureProperty = pi)
  115. /**
  116. * Get/set the texture that contains the sprite to display
  117. */
  118. public get texture(): Texture {
  119. return this._texture;
  120. }
  121. public set texture(value: Texture) {
  122. this._texture = value;
  123. }
  124. @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, pi => Sprite2D.actualSizeProperty = pi, false, true)
  125. /**
  126. * Get/set the actual size of the sprite to display
  127. */
  128. public get actualSize(): Size {
  129. if (this._actualSize) {
  130. return this._actualSize;
  131. }
  132. return this.size;
  133. }
  134. public set actualSize(value: Size) {
  135. this._actualSize = value;
  136. }
  137. @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, pi => Sprite2D.spriteLocationProperty = pi)
  138. /**
  139. * Get/set the sprite location (in pixels) in the texture
  140. */
  141. public get spriteLocation(): Vector2 {
  142. return this._location;
  143. }
  144. public set spriteLocation(value: Vector2) {
  145. this._location = value;
  146. }
  147. @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, pi => Sprite2D.spriteFrameProperty = pi)
  148. /**
  149. * Get/set the sprite frame to display.
  150. * The frame number is just an offset applied horizontally, based on the sprite's width. it does not wrap, all the frames must be on the same line.
  151. */
  152. public get spriteFrame(): number {
  153. return this._spriteFrame;
  154. }
  155. public set spriteFrame(value: number) {
  156. this._spriteFrame = value;
  157. }
  158. @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, pi => Sprite2D.invertYProperty = pi)
  159. /**
  160. * Get/set if the sprite texture coordinates should be inverted on the Y axis
  161. */
  162. public get invertY(): boolean {
  163. return this._invertY;
  164. }
  165. public set invertY(value: boolean) {
  166. this._invertY = value;
  167. }
  168. /**
  169. * Get/set if the sprite rendering should be aligned to the target rendering device pixel or not
  170. */
  171. public get alignToPixel(): boolean {
  172. return this._alignToPixel;
  173. }
  174. public set alignToPixel(value: boolean) {
  175. this._alignToPixel = value;
  176. }
  177. protected updateLevelBoundingInfo() {
  178. BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
  179. }
  180. /**
  181. * Get the animatable array (see http://doc.babylonjs.com/tutorials/Animations)
  182. */
  183. public getAnimatables(): IAnimatable[] {
  184. let res = new Array<IAnimatable>();
  185. if (this.texture && this.texture.animations && this.texture.animations.length > 0) {
  186. res.push(this.texture);
  187. }
  188. return res;
  189. }
  190. protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
  191. // If we've made it so far it means the boundingInfo intersection test succeed, the Sprite2D is shaped the same, so we always return true
  192. return true;
  193. }
  194. /**
  195. * Create an 2D Sprite primitive
  196. * @param texture the texture that stores the sprite to render
  197. * @param settings a combination of settings, possible ones are
  198. * - parent: the parent primitive/canvas, must be specified if the primitive is not constructed as a child of another one (i.e. as part of the children array setting)
  199. * - children: an array of direct children
  200. * - id a text identifier, for information purpose
  201. * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
  202. * - rotation: the initial rotation (in radian) of the primitive. default is 0
  203. * - scale: the initial scale of the primitive. default is 1
  204. * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
  205. * - origin: define the normalized origin point location, default [0.5;0.5]
  206. * - spriteSize: the size of the sprite (in pixels), if null the size of the given texture will be used, default is null.
  207. * - spriteLocation: the location (in pixels) in the texture of the top/left corner of the Sprite to display, default is null (0,0)
  208. * - invertY: if true the texture Y will be inverted, default is false.
  209. * - alignToPixel: if true the sprite's texels will be aligned to the rendering viewport pixels, ensuring the best rendering quality but slow animations won't be done as smooth as if you set false. If false a texel could lies between two pixels, being blended by the texture sampling mode you choose, the rendering result won't be as good, but very slow animation will be overall better looking. Default is true: content will be aligned.
  210. * - isVisible: true if the sprite must be visible, false for hidden. Default is true.
  211. * - childrenFlatZOrder: if true all the children (direct and indirect) will share the same Z-Order. Use this when there's a lot of children which don't overlap. The drawing order IS NOT GUARANTED!
  212. * - marginTop: top margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  213. * - marginLeft: left margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  214. * - marginRight: right margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  215. * - marginBottom: bottom margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  216. * - margin: top, left, right and bottom margin formatted as a single string (see PrimitiveThickness.fromString)
  217. * - marginHAlignment: one value of the PrimitiveAlignment type's static properties
  218. * - marginVAlignment: one value of the PrimitiveAlignment type's static properties
  219. * - marginAlignment: a string defining the alignment, see PrimitiveAlignment.fromString
  220. * - paddingTop: top padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  221. * - paddingLeft: left padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  222. * - paddingRight: right padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  223. * - paddingBottom: bottom padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  224. * - padding: top, left, right and bottom padding formatted as a single string (see PrimitiveThickness.fromString)
  225. */
  226. constructor(texture: Texture, settings?: {
  227. parent?: Prim2DBase,
  228. children?: Array<Prim2DBase>,
  229. id?: string,
  230. position?: Vector2,
  231. x?: number,
  232. y?: number,
  233. rotation?: number,
  234. scale?: number,
  235. opacity?: number,
  236. origin?: Vector2,
  237. spriteSize?: Size,
  238. spriteLocation?: Vector2,
  239. invertY?: boolean,
  240. alignToPixel?: boolean,
  241. isVisible?: boolean,
  242. childrenFlatZOrder?: boolean,
  243. marginTop?: number | string,
  244. marginLeft?: number | string,
  245. marginRight?: number | string,
  246. marginBottom?: number | string,
  247. margin?: number | string,
  248. marginHAlignment?: number,
  249. marginVAlignment?: number,
  250. marginAlignment?: string,
  251. paddingTop?: number | string,
  252. paddingLeft?: number | string,
  253. paddingRight?: number | string,
  254. paddingBottom?: number | string,
  255. padding?: string,
  256. }) {
  257. if (!settings) {
  258. settings = {};
  259. }
  260. super(settings);
  261. this.texture = texture;
  262. this.texture.wrapU = Texture.CLAMP_ADDRESSMODE;
  263. this.texture.wrapV = Texture.CLAMP_ADDRESSMODE;
  264. this.size = settings.spriteSize;
  265. this.spriteLocation = settings.spriteLocation || new Vector2(0, 0);
  266. this.spriteFrame = 0;
  267. this.invertY = (settings.invertY == null) ? false : settings.invertY;
  268. this.alignToPixel = (settings.alignToPixel == null) ? true : settings.alignToPixel;
  269. this.isAlphaTest = true;
  270. if (settings.spriteSize == null || !texture.isReady()) {
  271. if (texture.isReady()) {
  272. this.size = <Size>texture.getSize();
  273. } else {
  274. texture.onLoadObservable.add(() => {
  275. if (settings.spriteSize == null) {
  276. this.size = <Size>texture.getSize();
  277. }
  278. this._positioningDirty();
  279. });
  280. }
  281. }
  282. }
  283. static _createCachedCanvasSprite(owner: Canvas2D, texture: MapTexture, size: Size, pos: Vector2): Sprite2D {
  284. let sprite = new Sprite2D(texture, { parent: owner, id: "__cachedCanvasSprite__", position: Vector2.Zero(), origin: Vector2.Zero(), spriteSize: size, spriteLocation: pos, alignToPixel: true });
  285. return sprite;
  286. }
  287. protected createModelRenderCache(modelKey: string): ModelRenderCache {
  288. let renderCache = new Sprite2DRenderCache(this.owner.engine, modelKey);
  289. return renderCache;
  290. }
  291. protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
  292. let renderCache = <Sprite2DRenderCache>modelRenderCache;
  293. let engine = this.owner.engine;
  294. let vb = new Float32Array(4);
  295. for (let i = 0; i < 4; i++) {
  296. vb[i] = i;
  297. }
  298. renderCache.vb = engine.createVertexBuffer(vb);
  299. let ib = new Float32Array(6);
  300. ib[0] = 0;
  301. ib[1] = 2;
  302. ib[2] = 1;
  303. ib[3] = 0;
  304. ib[4] = 3;
  305. ib[5] = 2;
  306. renderCache.ib = engine.createIndexBuffer(ib);
  307. renderCache.texture = this.texture;
  308. // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
  309. let ei = this.getDataPartEffectInfo(Sprite2D.SPRITE2D_MAINPARTID, ["index"], true);
  310. if (ei) {
  311. renderCache.effectInstanced = engine.createEffect("sprite2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
  312. }
  313. ei = this.getDataPartEffectInfo(Sprite2D.SPRITE2D_MAINPARTID, ["index"], false);
  314. renderCache.effect = engine.createEffect("sprite2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
  315. return renderCache;
  316. }
  317. protected createInstanceDataParts(): InstanceDataBase[] {
  318. return [new Sprite2DInstanceData(Sprite2D.SPRITE2D_MAINPARTID)];
  319. }
  320. private static _prop: Vector3 = Vector3.Zero();
  321. private static layoutConstructMode = false;
  322. protected beforeRefreshForLayoutConstruction(part: InstanceDataBase): any {
  323. Sprite2D.layoutConstructMode = true;
  324. }
  325. // if obj contains something, we restore the _text property
  326. protected afterRefreshForLayoutConstruction(part: InstanceDataBase, obj: any) {
  327. Sprite2D.layoutConstructMode = false;
  328. }
  329. protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
  330. if (!super.refreshInstanceDataPart(part)) {
  331. return false;
  332. }
  333. if (!this.texture.isReady() && !Sprite2D.layoutConstructMode) {
  334. return false;
  335. }
  336. if (part.id === Sprite2D.SPRITE2D_MAINPARTID) {
  337. let d = <Sprite2DInstanceData>this._instanceDataParts[0];
  338. if (Sprite2D.layoutConstructMode) {
  339. d.topLeftUV = Vector2.Zero();
  340. d.sizeUV = Vector2.Zero();
  341. d.properties = Vector3.Zero();
  342. d.textureSize = Vector2.Zero();
  343. } else {
  344. let ts = this.texture.getBaseSize();
  345. let sl = this.spriteLocation;
  346. let ss = this.actualSize;
  347. d.topLeftUV = new Vector2(sl.x / ts.width, sl.y / ts.height);
  348. let suv = new Vector2(ss.width / ts.width, ss.height / ts.height);
  349. d.sizeUV = suv;
  350. Sprite2D._prop.x = this.spriteFrame;
  351. Sprite2D._prop.y = this.invertY ? 1 : 0;
  352. Sprite2D._prop.z = this.alignToPixel ? 1 : 0;
  353. d.properties = Sprite2D._prop;
  354. d.textureSize = new Vector2(ts.width, ts.height);
  355. }
  356. }
  357. return true;
  358. }
  359. private _texture: Texture;
  360. private _location: Vector2;
  361. private _spriteFrame: number;
  362. private _invertY: boolean;
  363. private _alignToPixel: boolean;
  364. }
  365. }