Project

General

Profile

Download (30.5 KB) Statistics
| Branch: | Revision:

library / src / main / java / org / distorted / library / main / DistortedNode.java @ 9a3607b3

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2016 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Distorted.                                                               //
5
//                                                                                               //
6
// Distorted is free software: you can redistribute it and/or modify                             //
7
// it under the terms of the GNU General Public License as published by                          //
8
// the Free Software Foundation, either version 2 of the License, or                             //
9
// (at your option) any later version.                                                           //
10
//                                                                                               //
11
// Distorted is distributed in the hope that it will be useful,                                  //
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
14
// GNU General Public License for more details.                                                  //
15
//                                                                                               //
16
// You should have received a copy of the GNU General Public License                             //
17
// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
package org.distorted.library.main;
21

    
22
import android.opengl.GLES31;
23

    
24
import org.distorted.library.mesh.MeshBase;
25

    
26
import java.util.ArrayList;
27
import java.util.Collections;
28
import java.util.HashMap;
29

    
30
///////////////////////////////////////////////////////////////////////////////////////////////////
31
/**
32
 * Class which represents a Node in a Tree of (InputSurface,Mesh,Effects) triplets.
33
 * <p>
34
 * Having organized such sets into a Tree, we can then render any Node to any OutputSurface.
35
 * That recursively renders the set held in the Node and all its children.
36
 * <p>
37
 * The class takes special care to only render identical sub-trees once. Each Node holds a reference
38
 * to sub-class 'NodeData'. Two identical sub-trees attached at different points of the main tree
39
 * will point to the same NodeData; only the first of this is rendered (mData.numRender!).
40
 */
41
public class DistortedNode implements DistortedMaster.Slave
42
  {
43
  private static final int ATTACH = 0;
44
  private static final int DETACH = 1;
45
  private static final int DETALL = 2;
46
  private static final int SORT   = 3;
47

    
48
  private ArrayList<DistortedNode> mChildren;
49
  private int[] mNumChildren;  // ==mChildren.length(), but we only create mChildren if the first one gets added
50

    
51
  private class Job
52
    {
53
    int type;
54
    DistortedNode node;
55

    
56
    Job(int t, DistortedNode n)
57
      {
58
      type = t;
59
      node = n;
60
      }
61
    }
62

    
63
  private ArrayList<Job> mJobs = new ArrayList<>();
64

    
65
  private static HashMap<ArrayList<Long>,NodeData> mMapNodeID = new HashMap<>();
66
  private static long mNextNodeID =0;
67

    
68
  private boolean mRenderWayOIT;
69
  private DistortedNode mParent;
70
  private DistortedOutputSurface mSurfaceParent;
71
  private MeshBase mMesh;
72
  private DistortedEffects mEffects;
73
  private DistortedSurface mSurface;
74
  private DistortedRenderState mState;
75
  private NodeData mData;
76
  private int mFboW, mFboH, mFboDepthStencil;
77

    
78
  private class NodeData
79
    {
80
    long ID;
81
    int numPointingNodes;
82
    long currTime;
83
    ArrayList<Long> key;
84
    DistortedFramebuffer mFBO;
85

    
86
    NodeData(long id, ArrayList<Long> k)
87
      {
88
      ID              = id;
89
      key             = k;
90
      numPointingNodes= 1;
91
      currTime        =-1;
92
      mFBO            = null;
93
      }
94
    }
95

    
96
///////////////////////////////////////////////////////////////////////////////////////////////////
97

    
98
  static synchronized void onDestroy()
99
    {
100
    mNextNodeID = 0;
101
    mMapNodeID.clear();
102
    }
103

    
104
///////////////////////////////////////////////////////////////////////////////////////////////////
105

    
106
  private ArrayList<Long> generateIDList()
107
    {
108
    ArrayList<Long> ret = new ArrayList<>();
109

    
110
    if( mNumChildren[0]==0 )
111
      {
112
      // add a negative number so this leaf never gets confused with a internal node
113
      // with a single child that happens to have ID identical to some leaf's Effects ID.
114
      ret.add(-mEffects.getID());
115
      }
116
    else
117
      {
118
      DistortedNode node;
119
   
120
      for(int i=0; i<mNumChildren[0]; i++)
121
        {
122
        node = mChildren.get(i);
123
        ret.add(node.mData.ID);
124
        }
125

    
126
      // A bit questionable decision here - we are sorting the children IDs, which means
127
      // that order in which we draw the children is going to be undefined (well, this is not
128
      // strictly speaking true - when rendering, if no postprocessing and isomorphism are
129
      // involved, we *DO* render the children in order they were added; if however there
130
      // are two internal nodes with the same list of identical children, just added in a
131
      // different order each time, then we consider them isomorphic, i.e. identical and only
132
      // render the first one. If then two children of such 'pseudo-isomorphic' nodes are at
133
      // exactly the same Z-height this might result in some unexpected sights).
134
      //
135
      // Reason: with the children being sorted by postprocessing buckets, the order is
136
      // undefined anyway (although only when postprocessing is applied).
137
      //
138
      // See the consequences in the 'Olympic' app - remove a few leaves and add them back in
139
      // different order. You will see the number of renders go back to the original 15.
140
      Collections.sort(ret);
141
      }
142

    
143
    ret.add( 0, mSurface.getID() );
144

    
145
    return ret;
146
    }
147

    
148
///////////////////////////////////////////////////////////////////////////////////////////////////
149
// Debug - print all the Node IDs
150

    
151
  @SuppressWarnings("unused")
152
  void debug(int depth)
153
    {
154
    String tmp="";
155
    int i;
156

    
157
    for(i=0; i<depth; i++) tmp +="   ";
158
    tmp += ("NodeID="+mData.ID+" nodes pointing: "+mData.numPointingNodes+" surfaceID="+
159
            mSurface.getID()+" FBO="+(mData.mFBO==null ? "null":mData.mFBO.getID()))+
160
            " parent sID="+(mParent==null ? "null": (mParent.mSurface.getID()));
161

    
162
    android.util.Log.e("NODE", tmp);
163

    
164
    for(i=0; i<mNumChildren[0]; i++)
165
      mChildren.get(i).debug(depth+1);
166
    }
167

    
168
///////////////////////////////////////////////////////////////////////////////////////////////////
169
// Debug - print contents of the HashMap
170

    
171
  @SuppressWarnings("unused")
172
  static void debugMap()
173
    {
174
    NodeData tmp;
175

    
176
    for(ArrayList<Long> key: mMapNodeID.keySet())
177
      {
178
      tmp = mMapNodeID.get(key);
179
      android.util.Log.e("NODE", "NodeID: "+tmp.ID+" <-- "+key);
180
      }
181
    }
182

    
183
///////////////////////////////////////////////////////////////////////////////////////////////////
184
// tree isomorphism algorithm
185

    
186
  private void adjustIsomorphism()
187
    {
188
    ArrayList<Long> newList = generateIDList();
189
    NodeData newData = mMapNodeID.get(newList);
190

    
191
    if( newData!=null )
192
      {
193
      newData.numPointingNodes++;
194
      }
195
    else
196
      {
197
      newData = new NodeData(++mNextNodeID,newList);
198
      mMapNodeID.put(newList,newData);
199
      }
200

    
201
    boolean deleteOldFBO = false;
202
    boolean createNewFBO = false;
203

    
204
    if( --mData.numPointingNodes==0 )
205
      {
206
      mMapNodeID.remove(mData.key);
207
      if( mData.mFBO!=null ) deleteOldFBO=true;
208
      }
209
    if( mNumChildren[0]>0 && newData.mFBO==null )
210
      {
211
      createNewFBO = true;
212
      }
213
    if( mNumChildren[0]==0 && newData.mFBO!=null )
214
      {
215
      newData.mFBO.markForDeletion();
216
      android.util.Log.e("NODE", "ERROR!! this NodeData cannot possibly contain a non-null FBO!! "+newData.mFBO.getID() );
217
      newData.mFBO = null;
218
      }
219

    
220
    if( deleteOldFBO && createNewFBO )
221
      {
222
      newData.mFBO = mData.mFBO;  // just copy over
223
      //android.util.Log.d("NODE", "copying over FBOs "+mData.mFBO.getID() );
224
      }
225
    else if( deleteOldFBO )
226
      {
227
      mData.mFBO.markForDeletion();
228
      //android.util.Log.d("NODE", "deleting old FBO "+mData.mFBO.getID() );
229
      mData.mFBO = null;
230
      }
231
    else if( createNewFBO )
232
      {
233
      int width  = mFboW <= 0 ? mSurface.getWidth()  : mFboW;
234
      int height = mFboH <= 0 ? mSurface.getHeight() : mFboH;
235
      newData.mFBO = new DistortedFramebuffer(1,mFboDepthStencil, DistortedSurface.TYPE_TREE, width, height);
236
      //android.util.Log.d("NODE", "creating new FBO "+newData.mFBO.getID() );
237
      }
238

    
239
    mData = newData;
240

    
241
    if( mParent!=null ) mParent.adjustIsomorphism();
242
    }
243

    
244
///////////////////////////////////////////////////////////////////////////////////////////////////
245
// return the total number of render calls issued
246

    
247
  int drawNoBlend(long currTime, DistortedOutputSurface surface)
248
    {
249
    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
250

    
251
    if( input.setAsInput() )
252
      {
253
      mState.apply();
254
      GLES31.glDisable(GLES31.GL_BLEND);
255
      mEffects.drawPriv(mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
256
      GLES31.glEnable(GLES31.GL_BLEND);
257
      return 1;
258
      }
259

    
260
    return 0;
261
    }
262

    
263
///////////////////////////////////////////////////////////////////////////////////////////////////
264
// Use the Order Independent Transparency method to draw a non-postprocessed child.
265

    
266
  int drawOIT(long currTime, DistortedOutputSurface surface)
267
    {
268
    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
269

    
270
    if( input.setAsInput() )
271
      {
272
      mState.apply();
273
      mEffects.drawPrivOIT(mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
274
      return 1;
275
      }
276

    
277
    return 0;
278
    }
279

    
280
///////////////////////////////////////////////////////////////////////////////////////////////////
281
// return the total number of render calls issued
282

    
283
  int draw(long currTime, DistortedOutputSurface surface)
284
    {
285
    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
286

    
287
    if( input.setAsInput() )
288
      {
289
      mState.apply();
290
      mEffects.drawPriv(mSurface.getWidth()/2.0f, mSurface.getHeight()/2.0f, mMesh, surface, currTime);
291
      return 1;
292
      }
293

    
294
    return 0;
295
    }
296

    
297
///////////////////////////////////////////////////////////////////////////////////////////////////
298
// return the total number of render calls issued
299

    
300
  int renderRecursive(long currTime)
301
    {
302
    int numRenders = 0;
303

    
304
    if( mNumChildren[0]>0 && mData.currTime!=currTime )
305
      {
306
      mData.currTime = currTime;
307

    
308
      for (int i=0; i<mNumChildren[0]; i++)
309
        {
310
        numRenders += mChildren.get(i).renderRecursive(currTime);
311
        }
312

    
313
      if( mData.mFBO==null )
314
        {
315
        int width  = mFboW <= 0 ? mSurface.getWidth()  : mFboW;
316
        int height = mFboH <= 0 ? mSurface.getHeight() : mFboH;
317
        mData.mFBO = new DistortedFramebuffer(1,mFboDepthStencil, DistortedSurface.TYPE_TREE, width, height);
318
        }
319

    
320
      mData.mFBO.setAsOutput(currTime);
321

    
322
      if( mSurface.setAsInput() )
323
        {
324
        numRenders++;
325
        DistortedEffects.blitPriv(mData.mFBO);
326
        }
327

    
328
      numRenders += mData.mFBO.renderChildren(currTime,mNumChildren[0],mChildren,0, mRenderWayOIT);
329
      }
330

    
331
    return numRenders;
332
    }
333

    
334
///////////////////////////////////////////////////////////////////////////////////////////////////
335

    
336
  void setSurfaceParent(DistortedOutputSurface dep)
337
    {
338
    mSurfaceParent = dep;
339
    mParent = null;
340
    }
341

    
342
///////////////////////////////////////////////////////////////////////////////////////////////////
343

    
344
  void sort()
345
    {
346
    if( mParent!=null )
347
      {
348
      mParent.mChildren.remove(this);
349
      DistortedMaster.addSortingByBuckets(mParent.mChildren,this);
350
      }
351
    else if( mSurfaceParent!=null )
352
      {
353
      ArrayList<DistortedNode> children = mSurfaceParent.getChildren();
354
      children.remove(this);
355
      DistortedMaster.addSortingByBuckets(children,this);
356
      }
357
    }
358

    
359
///////////////////////////////////////////////////////////////////////////////////////////////////
360
/**
361
 * Not part of the Public API.
362
 *
363
 * @y.exclude
364
 */
365
  public EffectQueuePostprocess getPostprocessQueue()
366
    {
367
    return mEffects.getPostprocess();
368
    }
369

    
370
///////////////////////////////////////////////////////////////////////////////////////////////////
371
// PUBLIC API
372
///////////////////////////////////////////////////////////////////////////////////////////////////
373
/**
374
 * Constructs new Node.
375
 *     
376
 * @param surface InputSurface to put into the new Node.
377
 * @param effects DistortedEffects to put into the new Node.
378
 * @param mesh MeshBase to put into the new Node.
379
 */
380
  public DistortedNode(DistortedSurface surface, DistortedEffects effects, MeshBase mesh)
381
    {
382
    mSurface       = surface;
383
    mEffects       = effects;
384
    mMesh          = mesh;
385
    mState         = new DistortedRenderState();
386
    mChildren      = null;
387
    mNumChildren   = new int[1];
388
    mNumChildren[0]= 0;
389
    mParent        = null;
390
    mSurfaceParent = null;
391
    mRenderWayOIT  = false;
392

    
393
    mFboW            = 0;  // i.e. take this from
394
    mFboH            = 0;  // mSurface's dimensions
395
    mFboDepthStencil = DistortedFramebuffer.DEPTH_NO_STENCIL;
396

    
397
    ArrayList<Long> list = new ArrayList<>();
398
    list.add(mSurface.getID());
399
    list.add(-mEffects.getID());
400

    
401
    mData = mMapNodeID.get(list);
402
   
403
    if( mData!=null )
404
      {
405
      mData.numPointingNodes++;
406
      }
407
    else
408
      {
409
      mData = new NodeData(++mNextNodeID,list);
410
      mMapNodeID.put(list, mData);
411
      }
412

    
413
    mEffects.newNode(this);
414
    }
415

    
416
///////////////////////////////////////////////////////////////////////////////////////////////////  
417
/**
418
 * Copy-constructs new Node from another Node.
419
 *     
420
 * @param node The DistortedNode to copy data from.
421
 * @param flags bit field composed of a subset of the following:
422
 *        {@link Distorted#CLONE_SURFACE},  {@link Distorted#CLONE_MATRIX}, {@link Distorted#CLONE_VERTEX},
423
 *        {@link Distorted#CLONE_FRAGMENT} and {@link Distorted#CLONE_CHILDREN}.
424
 *        For example flags = CLONE_SURFACE | CLONE_CHILDREN.
425
 */
426
  public DistortedNode(DistortedNode node, int flags)
427
    {
428
    mEffects      = new DistortedEffects(node.mEffects,flags);
429
    mMesh         = node.mMesh;
430
    mState        = new DistortedRenderState();
431
    mParent       = null;
432
    mSurfaceParent= null;
433
    mRenderWayOIT = false;
434

    
435
    mFboW            = node.mFboW;
436
    mFboH            = node.mFboH;
437
    mFboDepthStencil = node.mFboDepthStencil;
438

    
439
    if( (flags & Distorted.CLONE_SURFACE) != 0 )
440
      {
441
      mSurface = node.mSurface;
442
      }
443
    else
444
      {
445
      int w = node.mSurface.getWidth();
446
      int h = node.mSurface.getHeight();
447

    
448
      if( node.mSurface instanceof DistortedTexture )
449
        {
450
        mSurface = new DistortedTexture(w,h, DistortedSurface.TYPE_TREE);
451
        }
452
      else if( node.mSurface instanceof DistortedFramebuffer )
453
        {
454
        int depthStencil = DistortedFramebuffer.NO_DEPTH_NO_STENCIL;
455

    
456
        if( ((DistortedFramebuffer) node.mSurface).hasDepth() )
457
          {
458
          boolean hasStencil = ((DistortedFramebuffer) node.mSurface).hasStencil();
459
          depthStencil = (hasStencil ? DistortedFramebuffer.BOTH_DEPTH_STENCIL:DistortedFramebuffer.DEPTH_NO_STENCIL);
460
          }
461

    
462
        mSurface = new DistortedFramebuffer(1,depthStencil,DistortedSurface.TYPE_TREE,w,h);
463
        }
464
      }
465
    if( (flags & Distorted.CLONE_CHILDREN) != 0 )
466
      {
467
      if( node.mChildren==null )     // do NOT copy over the NULL!
468
        {
469
        node.mChildren = new ArrayList<>(2);
470
        }
471

    
472
      mChildren = node.mChildren;
473
      mNumChildren = node.mNumChildren;
474
      }
475
    else
476
      {
477
      mChildren = null;
478
      mNumChildren = new int[1];
479
      mNumChildren[0] = 0;
480
      }
481
   
482
    ArrayList<Long> list = generateIDList();
483
   
484
    mData = mMapNodeID.get(list);
485
   
486
    if( mData!=null )
487
      {
488
      mData.numPointingNodes++;
489
      }
490
    else
491
      {
492
      mData = new NodeData(++mNextNodeID,list);
493
      mMapNodeID.put(list, mData);
494
      }
495

    
496
    mEffects.newNode(this);
497
    }
498

    
499
///////////////////////////////////////////////////////////////////////////////////////////////////
500
  /**
501
   * When rendering this Node, should we use the Order Independent Transparency render more?
502
   * <p>
503
   * There are two modes of rendering: the fast 'normal' way, which however renders transparent
504
   * fragments in different ways depending on which fragments get rendered first, or the slower
505
   * 'oit' way, which renders transparent fragments correctly regardless of their order.
506
   *
507
   * @param oit True if we want to render more slowly, but in a way which accounts for transparency.
508
   */
509
  public void setOrderIndependentTransparency(boolean oit)
510
    {
511
    mRenderWayOIT = oit;
512
    }
513

    
514
///////////////////////////////////////////////////////////////////////////////////////////////////
515
  /**
516
   * When rendering this Node, should we use the Order Independent Transparency render more?
517
   * <p>
518
   * There are two modes of rendering: the fast 'normal' way, which however renders transparent
519
   * fragments in different ways depending on which fragments get rendered first, or the slower
520
   * 'oit' way, which renders transparent fragments correctly regardless of their order.
521
   *
522
   * @param oit True if we want to render more slowly, but in a way which accounts for transparency.
523
   * @param initialSize Initial number of transparent fragments we expect, in screenfulls.
524
   *                    I.e '1.0' means 'the scene we are going to render contains about 1 screen
525
   *                    worth of transparent fragments'. Valid values: 0.0 &lt; initialSize &lt; 10.0
526
   *                    Even if you get this wrong, the library will detect that there are more
527
   *                    transparent fragments than it has space for and readjust its internal buffers,
528
   *                    but only after a few frames during which one will probably see missing objects.
529
   */
530
  public void setOrderIndependentTransparency(boolean oit, float initialSize)
531
    {
532
    mRenderWayOIT = oit;
533

    
534
    if( initialSize>0.0f && initialSize<10.0f )
535
      DistortedEffects.setSSBOSize(initialSize);
536
    }
537

    
538
///////////////////////////////////////////////////////////////////////////////////////////////////
539
/**
540
 * Adds a new child to the last position in the list of our Node's children.
541
 * <p>
542
 * We cannot do this mid-render - actual attachment will be done just before the next render, by the
543
 * DistortedMaster (by calling doWork())
544
 *
545
 * @param node The new Node to add.
546
 */
547
  public void attach(DistortedNode node)
548
    {
549
    mJobs.add(new Job(ATTACH,node));
550
    DistortedMaster.newSlave(this);
551
    }
552

    
553
///////////////////////////////////////////////////////////////////////////////////////////////////
554
/**
555
 * Adds a new child to the last position in the list of our Node's children.
556
 * <p>
557
 * We cannot do this mid-render - actual attachment will be done just before the next render, by the
558
 * DistortedMaster (by calling doWork())
559
 *
560
 * @param surface InputSurface to initialize our child Node with.
561
 * @param effects DistortedEffects to initialize our child Node with.
562
 * @param mesh MeshBase to initialize our child Node with.
563
 * @return the newly constructed child Node, or null if we couldn't allocate resources.
564
 */
565
  public DistortedNode attach(DistortedSurface surface, DistortedEffects effects, MeshBase mesh)
566
    {
567
    DistortedNode node = new DistortedNode(surface,effects,mesh);
568
    mJobs.add(new Job(ATTACH,node));
569
    DistortedMaster.newSlave(this);
570
    return node;
571
    }
572

    
573
///////////////////////////////////////////////////////////////////////////////////////////////////
574
/**
575
 * Removes the first occurrence of a specified child from the list of children of our Node.
576
 * <p>
577
 * We cannot do this mid-render - actual detachment will be done just before the next render, by the
578
 * DistortedMaster (by calling doWork())
579
 *
580
 * @param node The Node to remove.
581
 */
582
  public void detach(DistortedNode node)
583
    {
584
    mJobs.add(new Job(DETACH,node));
585
    DistortedMaster.newSlave(this);
586
    }
587

    
588
///////////////////////////////////////////////////////////////////////////////////////////////////
589
/**
590
 * Removes the first occurrence of a specified child from the list of children of our Node.
591
 * <p>
592
 * A bit questionable method as there can be many different Nodes attached as children, some
593
 * of them having the same Effects but - for instance - different Mesh. Use with care.
594
 * <p>
595
 * We cannot do this mid-render - actual detachment will be done just before the next render, by the
596
 * DistortedMaster (by calling doWork())
597
 *
598
 * @param effects DistortedEffects to remove.
599
 */
600
  public void detach(DistortedEffects effects)
601
    {
602
    long id = effects.getID();
603
    DistortedNode node;
604
    boolean detached = false;
605

    
606
    for(int i=0; i<mNumChildren[0]; i++)
607
      {
608
      node = mChildren.get(i);
609

    
610
      if( node.getEffects().getID()==id )
611
        {
612
        detached = true;
613
        mJobs.add(new Job(DETACH,node));
614
        DistortedMaster.newSlave(this);
615
        break;
616
        }
617
      }
618

    
619
    if( !detached )
620
      {
621
      // if we failed to detach any, it still might be the case that
622
      // there's an ATTACH job that we need to cancel.
623
      int num = mJobs.size();
624
      Job job;
625

    
626
      for(int i=0; i<num; i++)
627
        {
628
        job = mJobs.get(i);
629

    
630
        if( job.type==ATTACH && job.node.getEffects()==effects )
631
          {
632
          mJobs.remove(i);
633
          break;
634
          }
635
        }
636
      }
637
    }
638

    
639
///////////////////////////////////////////////////////////////////////////////////////////////////
640
/**
641
 * Removes all children Nodes.
642
 * <p>
643
 * We cannot do this mid-render - actual detachment will be done just before the next render, by the
644
 * DistortedMaster (by calling doWork())
645
 */
646
  public void detachAll()
647
    {
648
    mJobs.add(new Job(DETALL,null));
649
    DistortedMaster.newSlave(this);
650
    }
651

    
652
///////////////////////////////////////////////////////////////////////////////////////////////////
653
/**
654
 * This is not really part of the public API. Has to be public only because it is a part of the
655
 * DistortedSlave interface, which should really be a class that we extend here instead but
656
 * Java has no multiple inheritance.
657
 *
658
 * @y.exclude
659
 */
660
  public void doWork()
661
    {
662
    int num = mJobs.size();
663
    Job job;
664

    
665
    int numChanges=0;
666

    
667
    for(int i=0; i<num; i++)
668
      {
669
      job = mJobs.remove(0);
670

    
671
      switch(job.type)
672
        {
673
        case ATTACH: numChanges++;
674
                     if( mChildren==null ) mChildren = new ArrayList<>(2);
675
                     job.node.mParent = this;
676
                     job.node.mSurfaceParent = null;
677
                     DistortedMaster.addSortingByBuckets(mChildren,job.node);
678
                     mNumChildren[0]++;
679
                     break;
680
        case DETACH: numChanges++;
681
                     if( mNumChildren[0]>0 && mChildren.remove(job.node) )
682
                       {
683
                       job.node.mParent = null;
684
                       job.node.mSurfaceParent = null;
685
                       mNumChildren[0]--;
686
                       }
687
                     break;
688
        case DETALL: numChanges++;
689
                     if( mNumChildren[0]>0 )
690
                       {
691
                       DistortedNode tmp;
692

    
693
                       for(int j=mNumChildren[0]-1; j>=0; j--)
694
                         {
695
                         tmp = mChildren.remove(j);
696
                         tmp.mParent = null;
697
                         tmp.mSurfaceParent = null;
698
                         }
699

    
700
                       mNumChildren[0] = 0;
701
                       }
702
                     break;
703
        case SORT  : mChildren.remove(job.node);
704
                     DistortedMaster.addSortingByBuckets(mChildren,job.node);
705
                     break;
706
        }
707
      }
708

    
709
    if( numChanges>0 ) adjustIsomorphism();
710
    }
711

    
712
///////////////////////////////////////////////////////////////////////////////////////////////////
713
/**
714
 * Returns the DistortedEffects object that's in the Node.
715
 * 
716
 * @return The DistortedEffects contained in the Node.
717
 */
718
  public DistortedEffects getEffects()
719
    {
720
    return mEffects;
721
    }
722

    
723
///////////////////////////////////////////////////////////////////////////////////////////////////
724
/**
725
 * Returns the DistortedSurface object that's in the Node.
726
 *
727
 * @return The DistortedSurface contained in the Node.
728
 */
729
  public DistortedSurface getSurface()
730
    {
731
    return mSurface;
732
    }
733

    
734
///////////////////////////////////////////////////////////////////////////////////////////////////
735
  /**
736
   * Returns the DistortedSurface object that's in the Node.
737
   *
738
   * @return The DistortedSurface contained in the Node (if a leaf), or the FBO (if an internal Node)
739
   */
740
  public DistortedSurface getInternalSurface()
741
    {
742
    return mNumChildren[0]==0 ? mSurface : mData.mFBO;
743
    }
744

    
745
///////////////////////////////////////////////////////////////////////////////////////////////////
746
/**
747
 * Returns the Mesh object that's in the Node.
748
 *
749
 * @return Mesh contained in the Node.
750
 */
751
  public MeshBase getMesh()
752
    {
753
    return mMesh;
754
    }
755

    
756
///////////////////////////////////////////////////////////////////////////////////////////////////
757
/**
758
 * Resizes the DistortedFramebuffer object that we render this Node to.
759
 */
760
  public void resize(int width, int height)
761
    {
762
    mFboW = width;
763
    mFboH = height;
764

    
765
    if ( mData.mFBO !=null )
766
      {
767
      // TODO: potentially allocate a new NodeData if we have to
768
      mData.mFBO.resize(width,height);
769
      }
770
    }
771

    
772
///////////////////////////////////////////////////////////////////////////////////////////////////
773
/**
774
 * Enables/disables DEPTH and STENCIL buffers in the Framebuffer object that we render this Node to.
775
 */
776
  public void enableDepthStencil(int depthStencil)
777
    {
778
    mFboDepthStencil = depthStencil;
779

    
780
    if ( mData.mFBO !=null )
781
      {
782
      // TODO: potentially allocate a new NodeData if we have to
783
      mData.mFBO.enableDepthStencil(depthStencil);
784
      }
785
    }
786

    
787
///////////////////////////////////////////////////////////////////////////////////////////////////
788
// APIs that control how to set the OpenGL state just before rendering this Node.
789
///////////////////////////////////////////////////////////////////////////////////////////////////
790
/**
791
 * When rendering this Node, use ColorMask (r,g,b,a).
792
 *
793
 * @param r Write to the RED color channel when rendering this Node?
794
 * @param g Write to the GREEN color channel when rendering this Node?
795
 * @param b Write to the BLUE color channel when rendering this Node?
796
 * @param a Write to the ALPHA channel when rendering this Node?
797
 */
798
  @SuppressWarnings("unused")
799
  public void glColorMask(boolean r, boolean g, boolean b, boolean a)
800
    {
801
    mState.glColorMask(r,g,b,a);
802
    }
803

    
804
///////////////////////////////////////////////////////////////////////////////////////////////////
805
/**
806
 * When rendering this Node, switch on writing to Depth buffer?
807
 *
808
 * @param mask Write to the Depth buffer when rendering this Node?
809
 */
810
  @SuppressWarnings("unused")
811
  public void glDepthMask(boolean mask)
812
    {
813
    mState.glDepthMask(mask);
814
    }
815

    
816
///////////////////////////////////////////////////////////////////////////////////////////////////
817
/**
818
 * When rendering this Node, which bits of the Stencil buffer to write to?
819
 *
820
 * @param mask Marks the bits of the Stencil buffer we will write to when rendering this Node.
821
 */
822
  @SuppressWarnings("unused")
823
  public void glStencilMask(int mask)
824
    {
825
    mState.glStencilMask(mask);
826
    }
827

    
828
///////////////////////////////////////////////////////////////////////////////////////////////////
829
/**
830
 * When rendering this Node, which Tests to enable?
831
 *
832
 * @param test Valid values: GL_DEPTH_TEST, GL_STENCIL_TEST, GL_BLEND
833
 */
834
  @SuppressWarnings("unused")
835
  public void glEnable(int test)
836
    {
837
    mState.glEnable(test);
838
    }
839

    
840
///////////////////////////////////////////////////////////////////////////////////////////////////
841
/**
842
 * When rendering this Node, which Tests to enable?
843
 *
844
 * @param test Valid values: GL_DEPTH_TEST, GL_STENCIL_TEST, GL_BLEND
845
 */
846
  @SuppressWarnings("unused")
847
  public void glDisable(int test)
848
    {
849
    mState.glDisable(test);
850
    }
851

    
852
///////////////////////////////////////////////////////////////////////////////////////////////////
853
/**
854
 * When rendering this Node, use the following StencilFunc.
855
 *
856
 * @param func Valid values: GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL
857
 * @param ref  Reference valut to compare our stencil with.
858
 * @param mask Mask used when comparing.
859
 */
860
  @SuppressWarnings("unused")
861
  public void glStencilFunc(int func, int ref, int mask)
862
    {
863
    mState.glStencilFunc(func,ref,mask);
864
    }
865

    
866
///////////////////////////////////////////////////////////////////////////////////////////////////
867
/**
868
 * When rendering this Node, use the following StencilOp.
869
 * <p>
870
 * Valid values of all 3 parameters: GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP
871
 *
872
 * @param sfail  What to do when Stencil Test fails.
873
 * @param dpfail What to do when Depth Test fails.
874
 * @param dppass What to do when Depth Test passes.
875
 */
876
  @SuppressWarnings("unused")
877
  public void glStencilOp(int sfail, int dpfail, int dppass)
878
    {
879
    mState.glStencilOp(sfail,dpfail,dppass);
880
    }
881

    
882
///////////////////////////////////////////////////////////////////////////////////////////////////
883
/**
884
 * When rendering this Node, use the following DepthFunc.
885
 *
886
 * @param func Valid values: GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL
887
 */
888
  @SuppressWarnings("unused")
889
  public void glDepthFunc(int func)
890
    {
891
    mState.glDepthFunc(func);
892
    }
893

    
894
///////////////////////////////////////////////////////////////////////////////////////////////////
895
/**
896
 * When rendering this Node, use the following Blending mode.
897
 * <p>
898
 * Valid values: GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
899
 *               GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR,
900
 *               GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_SRC_ALPHA_SATURATE
901
 *
902
 * @param src Source Blend function
903
 * @param dst Destination Blend function
904
 */
905
  @SuppressWarnings("unused")
906
  public void glBlendFunc(int src, int dst)
907
    {
908
    mState.glBlendFunc(src,dst);
909
    }
910

    
911
///////////////////////////////////////////////////////////////////////////////////////////////////
912
/**
913
 * Before rendering this Node, clear the following buffers.
914
 * <p>
915
 * Valid values: 0, or bitwise OR of one or more values from the set GL_COLOR_BUFFER_BIT,
916
 *               GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT.
917
 * Default: 0
918
 *
919
 * @param mask bitwise OR of BUFFER_BITs to clear.
920
 */
921
  @SuppressWarnings("unused")
922
  public void glClear(int mask)
923
    {
924
    mState.glClear(mask);
925
    }
926
  }
(6-6/17)