1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
|
# Copyright 2004-2009 Nanorex, Inc. See LICENSE file for details.
"""
ColorSorter.py - sort primitives and store them in a ColorSortedDisplayList
@author: Grantham, Russ
@version: $Id$
@copyright: 2004-2009 Nanorex, Inc. See LICENSE file for details.
History:
This was originally written by Brad Grantham (as part of drawer.py)
as a GL optimization to minimize calls on apply_material,
by sorting primitives into display lists based on color.
Later, it was repurposed by Russ to support runtime-determined drawing styles
and to permit some primitives to be drawn using GLSL shaders.
For early details see drawer.py history. More recent details are here
(though some of them cover the time before drawer.py was split
and might be redundant with drawer.py history):
080210 russ Split the single display-list into two second-level lists (with and
without color) and a set of per-color sublists so selection and hover-highlight
can over-ride Chunk base colors. ColorSortedDisplayList is now a class in the
parent's displist attr to keep track of all that stuff.
080311 piotr Added a "drawpolycone_multicolor" function for drawing polycone
tubes with per-vertex colors (necessary for DNA display style)
080313 russ Added triangle-strip icosa-sphere constructor, "getSphereTriStrips".
080420 piotr Solved highlighting and selection problems for multi-colored
objects (e.g. rainbow colored DNA structures).
080519 russ pulled the globals into a drawing_globals module and broke drawer.py
into 10 smaller chunks: glprefs.py setup_draw.py shape_vertices.py
ColorSorter.py CS_workers.py c_renderer.py CS_draw_primitives.py drawers.py
gl_lighting.py gl_buffers.py
081003 russ Added sphere shader-primitive support to CSDLs.
081126 russ Added CSDL shader hover-highlight support, including glname drawing.
090119 russ Added cylinder shader-primitive support.
090220 bruce split class ColorSortedDisplayList into its own file
REVIEW [bruce 090114; some of this now belongs in ColorSortedDisplayList.py]:
* there are a lot of calls of glGenLists(1) that would be VRAM memory leaks
if they didn't deallocate any prior display list being stored in the same attribute
as the new one. I *think* they are not leaks, since self._reset is called
before them, and it calls self.deallocate_displists, but it would be good
to confirm this by independent review, and perhaps to assert that all
re-allocated DL id attributes are zero (and zero them in deallocate_displists),
to make this very clear.
* see my comments dated 090114 about cleaning up .dl, .selected, .selectPick,
and activate (and related comments in CrystalShape.py and chunk.py).
TODO:
Change ColorSorter into a normal class with distinct instances.
Give it a stack of instances rather than its current _suspend system.
Maybe, refactor some more things between ColorSorter and ColorSortedDisplayList
(which also needs renaming). [bruce 090220/090224 comment]
"""
from OpenGL.GL import GL_BLEND
from OpenGL.GL import glBlendFunc
from OpenGL.GL import glColor3fv
from OpenGL.GL import glDepthMask
from OpenGL.GL import glDisable
from OpenGL.GL import glEnable
from OpenGL.GL import GL_FALSE
from OpenGL.GL import GL_LIGHTING
from OpenGL.GL import GL_ONE_MINUS_SRC_ALPHA
from OpenGL.GL import glPopName
from OpenGL.GL import glPushName
from OpenGL.GL import GL_SRC_ALPHA
from OpenGL.GL import GL_TRUE
from geometry.VQT import A
from utilities.debug import print_compact_stack
from graphics.drawing.c_renderer import quux_module_import_succeeded
if quux_module_import_succeeded:
import quux
from graphics.drawing.c_renderer import ShapeList_inplace
from graphics.drawing.CS_workers import drawcylinder_worker
from graphics.drawing.CS_workers import drawline_worker
from graphics.drawing.CS_workers import drawpolycone_multicolor_worker
from graphics.drawing.CS_workers import drawpolycone_worker
from graphics.drawing.CS_workers import drawsphere_worker
from graphics.drawing.CS_workers import drawsphere_worker_loop
from graphics.drawing.CS_workers import drawsurface_worker
from graphics.drawing.CS_workers import drawwiresphere_worker
from graphics.drawing.CS_workers import drawtriangle_strip_worker
from graphics.drawing.gl_lighting import apply_material
# ==
_DEBUG = False
# ==
import graphics.drawing.drawing_globals as drawing_globals
class _fake_GLPane: #bruce 090220
permit_shaders = False
# not sure if this matters -- maybe it does for immediate-mode spheres?
transforms = () # or make this [] if necessary, but value should never change
glprefs = drawing_globals.glprefs #bruce 090304
pass
_the_fake_GLPane = _fake_GLPane()
# as of 090304 we use this as the non-sorting value of
# ColorSorter.glpane, rather than None like before
class ColorSorter:
"""
State Sorter specializing in color (really any object that can be
passed to apply_material, which on 20051204 is only color 4-tuples)
Invoke start() to begin sorting.
Call finish() to complete sorting; pass draw_now = True to also draw
all sorted objects at that time.
Call schedule() with any function and parameters to be sorted by color.
If not sorting, schedule() will invoke the function immediately. If
sorting, then the function will not be called until "finish()" or later.
In any function which will take part in sorting which previously did
not, create a worker function from the old function except the call to
apply_material. Then create a wrapper which calls
ColorSorter.schedule with the worker function and its params.
Also an app can call schedule_sphere and schedule_cylinder to
schedule a sphere or a cylinder. Right now this is the only way
to directly access the native C++ rendering engine.
"""
__author__ = "grantham@plunk.org"
# For now, these are class globals. As long as OpenGL drawing is
# serialized and Sorting isn't nested, this is okay.
#
# [update, bruce 090220: it's now nested, so this should be changed.
# for now, we kluge it with _suspend/_unsuspend_if_needed methods.
# Note that this makes ColorSorter.start/finish slower than if we had
# a stack of ColorSorter instances, which is what we *should* do.]
#
# When/if
# OpenGL drawing becomes multi-threaded, sorters will have to
# become instances. This is probably okay because objects and
# materials will probably become objects of their own about that
# time so the whole system will get a redesign and
# reimplementation.
_suspended_states = [] #bruce 090220
def _init_state(): # staticmethod
"""
Initialize all state variables.except _suspended_states.
@note: this is called immediately after defining this class,
and in _suspend.
"""
# Note: all state variables (except _suspended_states) must be
# initialized here, saved in _suspend, and restored in
# _unsuspend_if_needed.
ColorSorter.sorting = False # Guard against nested sorting
ColorSorter._sorted = 0 # Number of calls to _add_to_sorter since last _printstats
ColorSorter._immediate = 0 # Number of calls to _invoke_immediately since last _printstats
ColorSorter._gl_name_stack = [0] # internal record of GL name stack; 0 is a sentinel
ColorSorter._parent_csdl = None # Passed from start() to finish()
ColorSorter.glpane = _the_fake_GLPane #bruce 090220/090304
ColorSorter._initial_transforms = None #bruce 090220
ColorSorter._permit_shaders = True
# modified in ColorSorter.start based on glpane;
# note that it has no effect on use of specific shaders
# unless a corresponding shader_desired function in ColorSorter.glpane.glprefs
# returns true (due to how it's used in this code)
# [bruce 090224, revised 090303]
# following are guesses by bruce 090220:
ColorSorter.sorted_by_color = None
ColorSorter._cur_shapelist = None
ColorSorter.sphereLevel = -1
return
_init_state = staticmethod(_init_state)
def _suspend(): # staticmethod
"""
Save our state so we can reentrantly start.
"""
# ColorSorter._suspended_states is a list of suspended states.
class _attrholder:
pass
state = _attrholder()
if len(ColorSorter._gl_name_stack) > 1:
print "fyi: name stack is non-null when suspended -- bug?", ColorSorter._gl_name_stack #####
state.sorting = ColorSorter.sorting
state._sorted = ColorSorter._sorted
state._immediate = ColorSorter._immediate
state._gl_name_stack = ColorSorter._gl_name_stack
state._parent_csdl = ColorSorter._parent_csdl
state.glpane = ColorSorter.glpane
state._initial_transforms = ColorSorter._initial_transforms
state._permit_shaders = ColorSorter._permit_shaders
state.sorted_by_color = ColorSorter.sorted_by_color
state._cur_shapelist = ColorSorter._cur_shapelist
state.sphereLevel = ColorSorter.sphereLevel
ColorSorter._suspended_states += [state]
ColorSorter._init_state()
return
_suspend = staticmethod(_suspend)
def _unsuspend_if_needed(): # staticmethod
"""
If we're suspended, unsuspend.
Otherwise, reinit state as a precaution.
"""
if ColorSorter._suspended_states:
state = ColorSorter._suspended_states.pop()
ColorSorter.sorting = state.sorting
ColorSorter._sorted = state._sorted
ColorSorter._immediate = state._immediate
ColorSorter._gl_name_stack = state._gl_name_stack
ColorSorter._parent_csdl = state._parent_csdl
ColorSorter.glpane = state.glpane
ColorSorter._initial_transforms = state._initial_transforms
ColorSorter._permit_shaders = state._permit_shaders
ColorSorter.sorted_by_color = state.sorted_by_color
ColorSorter._cur_shapelist = state._cur_shapelist
ColorSorter.sphereLevel = state.sphereLevel
pass
else:
#bruce 090220 guess precaution
assert len(ColorSorter._gl_name_stack) == 1, \
"should be length 1: %r" % (ColorSorter._gl_name_stack,)
ColorSorter._init_state()
return
_unsuspend_if_needed = staticmethod(_unsuspend_if_needed)
# ==
def _relative_transforms(): # staticmethod #bruce 090220
"""
Return a list or tuple (owned by caller) of the additional transforms
we're presently inside, relative to when start() was called.
Also do related sanity checks (as assertions).
"""
t1 = ColorSorter._initial_transforms
n1 = len(t1)
t2 = ColorSorter.glpane.transforms # should be same or more
n2 = len(t2)
if n1 < n2:
assert t1 == t2[:n1]
return tuple(t2[n1:])
else:
assert n1 <= n2
return ()
pass
_relative_transforms = staticmethod(_relative_transforms)
def _debug_transforms(): # staticmethod #bruce 090220
return [ColorSorter._initial_transforms,
ColorSorter._relative_transforms()]
_debug_transforms = staticmethod(_debug_transforms)
def _transform_point(point): # staticmethod #bruce 090223
"""
Apply our current relative transforms to the given point.
@return: the transformed point.
"""
for t in ColorSorter._relative_transforms():
point = t.applyToPoint(point)
### todo (not urgent): implem in TransformControl,
# document it there as part of TransformControl_API,
# make a class of that name, inherit it from Chunk
return point
_transform_point = staticmethod(_transform_point)
# if necessary, we'll also implement _transform_vector
def _warn_transforms_nim(funcname): # staticmethod #bruce 090223
print_compact_stack("bug: use of _relative_transforms nim in %s: " % funcname)
return
_warn_transforms_nim = staticmethod(_warn_transforms_nim)
# ==
def pushName(glname):
"""
Record the current pushed GL name, which must not be 0.
"""
#bruce 060217 Added this assert.
assert glname, "glname of 0 is illegal (used as sentinel)"
ColorSorter._gl_name_stack.append(glname)
pushName = staticmethod(pushName)
def popName():
"""
Record a pop of the GL name.
"""
del ColorSorter._gl_name_stack[-1]
popName = staticmethod(popName)
def _printstats():
"""
Internal function for developers to call to print stats on number of
sorted and immediately-called objects.
"""
print ("Since previous 'stats', %d sorted, %d immediate: " %
(ColorSorter._sorted, ColorSorter._immediate))
ColorSorter._sorted = 0
ColorSorter._immediate = 0
_printstats = staticmethod(_printstats)
def _add_to_sorter(color, func, params):
"""
Internal function that stores 'scheduled' operations for a later
sort, between a start/finish
"""
ColorSorter._sorted += 1
color = tuple(color)
if not ColorSorter.sorted_by_color.has_key(color):
ColorSorter.sorted_by_color[color] = []
ColorSorter.sorted_by_color[color].append(
(func, params, ColorSorter._gl_name_stack[-1]))
_add_to_sorter = staticmethod(_add_to_sorter)
def schedule(color, func, params): # staticmethod
if ColorSorter.sorting:
ColorSorter._add_to_sorter(color, func, params)
else:
ColorSorter._immediate += 1 # for benchmark/debug stats, mostly
# 20060216 We know we can do this here because the stack is
# only ever one element deep
name = ColorSorter._gl_name_stack[-1]
if name:
glPushName(name)
## Don't check in immediate drawing, e.g. preDraw_glselect_dict.
## else:
## print "bug_1: attempt to push non-glname", name
#Apply appropriate opacity for the object if it is specified
#in the 'color' param. (Also do necessary things such as
#call glBlendFunc it. -- Ninad 20071009
if len(color) == 4:
opacity = color[3]
else:
opacity = 1.0
if opacity >= 0.0 and opacity != 1.0:
glDepthMask(GL_FALSE)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
elif opacity == -1:
# piotr 080429: I replaced the " < 0" condition with " == -1"
# The opacity flag is now used to signal either "unshaded
# colors" (opacity == -1) or "multicolor object" (opacity == -2)
glDisable(GL_LIGHTING) # Don't forget to re-enable it!
glColor3fv(color[:3])
pass
apply_material(color)
func(params) # Call the draw worker function.
if opacity > 0.0 and opacity != 1.0:
glDisable(GL_BLEND)
glDepthMask(GL_TRUE)
elif opacity == -1:
# piotr 080429: See my comment above.
glEnable(GL_LIGHTING)
if name:
glPopName()
return
schedule = staticmethod(schedule)
# ==
def schedule_sphere(color, pos, radius, detailLevel,
opacity = 1.0, testloop = 0):
"""
Schedule a sphere for rendering whenever ColorSorter thinks is
appropriate.
"""
if _DEBUG and ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
print "bare_prim sphere:", ColorSorter._gl_name_stack[-1], \
color, pos, radius, ColorSorter._debug_transforms()
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
pos = ColorSorter._transform_point(pos)
if ColorSorter.glpane.glprefs.use_c_renderer and ColorSorter.sorting:
if len(color) == 3:
lcolor = (color[0], color[1], color[2], opacity)
else:
lcolor = color
ColorSorter._cur_shapelist.add_sphere(
lcolor, pos, radius, ColorSorter._gl_name_stack[-1])
# 20060208 grantham - I happen to know that one detailLevel
# is chosen for all spheres, I just record it over and
# over here, and use the last one for the render
if (ColorSorter.sphereLevel > -1 and
ColorSorter.sphereLevel != detailLevel):
raise ValueError, \
"unexpected different sphere LOD levels within same frame"
ColorSorter.sphereLevel = detailLevel
pass
else:
# Non-C-coded material rendering (might be sorted and/or use shaders)
sphereBatches = ( ColorSorter._permit_shaders and
ColorSorter.glpane.glprefs.sphereShader_desired() and
drawing_globals.sphereShader_available()
)
if len(color) == 3:
lcolor = (color[0], color[1], color[2], opacity)
else:
lcolor = color
pass
if sphereBatches and ColorSorter._parent_csdl: # Russ 080925: Added.
# Collect lists of primitives in the CSDL, rather than sending
# them down through the ColorSorter schedule methods into DLs.
assert ColorSorter.sorting # since _parent_csdl is present
ColorSorter._parent_csdl.addSphere(
pos, radius, lcolor,
# glnames come from ColorSorter.pushName()
ColorSorter._gl_name_stack[-1])
else:
vboLevel = ColorSorter.glpane.glprefs.use_drawing_variant
# vboLevel sets drawing strategy for unbatched spheres
detailLevel = (vboLevel, detailLevel)
# KLUGE, explained in a comment inside drawsphere_worker
if testloop > 0:
worker = drawsphere_worker_loop
else:
worker = drawsphere_worker
ColorSorter.schedule(
lcolor, worker, (pos, radius, detailLevel, testloop))
pass
return
schedule_sphere = staticmethod(schedule_sphere)
def schedule_wiresphere(color, pos, radius, detailLevel = 1):
"""
Schedule a wiresphere for rendering whenever ColorSorter thinks is
appropriate.
"""
if _DEBUG and ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
print "bare_prim wiresphere:", ColorSorter._gl_name_stack[-1], \
color, pos, radius, ColorSorter._debug_transforms()
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
pos = ColorSorter._transform_point(pos)
### TODO: for this primitive more than most, it's also necessary to
# transform the local coordinate system orientation
# used to align the wiresphere.
if ColorSorter.glpane.glprefs.use_c_renderer and ColorSorter.sorting:
if len(color) == 3:
lcolor = (color[0], color[1], color[2], 1.0)
else:
lcolor = color
assert 0, "Need to implement a C add_wiresphere function."
ColorSorter._cur_shapelist.add_wiresphere(
lcolor, pos, radius, ColorSorter._gl_name_stack[-1])
else:
if len(color) == 3:
lcolor = (color[0], color[1], color[2], 1.0)
else:
lcolor = color
ColorSorter.schedule(lcolor, drawwiresphere_worker,
# Use constant-color line drawing.
(color, pos, radius, detailLevel))
schedule_wiresphere = staticmethod(schedule_wiresphere)
def schedule_cylinder(color, pos1, pos2, radius, capped = 0, opacity = 1.0):
"""
Schedule a cylinder for rendering whenever ColorSorter thinks is
appropriate.
@param pos1: axis endpoint 1
@type pos1: Numeric array (list or tuple won't always work)
@param pos2: axis endpoint 2
@type pos2: Numeric array
@note: you can pass a tuple of two radii (in place of radius)
to make this a tapered cylinder. When cylinder shaders are active
and supported, this will use them, otherwise it will use a polycone.
"""
# check type of radius in the same way for all calls [bruce 090225]
if type(radius) == type(()):
# radius must be a tuple of 2 radii (length not checked here)
ColorSorter._schedule_tapered_cylinder(color, pos1, pos2, radius, capped, opacity)
return
radius = float(radius)
if _DEBUG and ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
print "bare_prim cylinder:", ColorSorter._gl_name_stack[-1], \
color, pos1, pos2, radius, capped, ColorSorter._debug_transforms()
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
# note: if drawing a cylinder requires an implicit coordinate system
# rather than just the axis endpoints (e.g. if it has a polygonal
# cross-section or a surface texture), we'd also need to pick a
# disambiguating point on the barrel here, outside this condition,
# and include it in what we transform, inside this condition.
# This need may be present now if we use this case for non-shader
# cylinders (since they have polygonal cross-section). ##### REVIEW
pos1 = ColorSorter._transform_point(pos1)
pos2 = ColorSorter._transform_point(pos2)
if ColorSorter.glpane.glprefs.use_c_renderer and ColorSorter.sorting:
if len(color) == 3:
lcolor = (color[0], color[1], color[2], 1.0)
else:
lcolor = color
ColorSorter._cur_shapelist.add_cylinder(
lcolor, pos1, pos2, radius,
ColorSorter._gl_name_stack[-1], capped)
else:
if len(color) == 3:
lcolor = (color[0], color[1], color[2], opacity)
else:
lcolor = color
# Russ 090119: Added.
cylinderBatches = ( ColorSorter._permit_shaders and
ColorSorter.glpane.glprefs.cylinderShader_desired() and
drawing_globals.cylinderShader_available()
)
if cylinderBatches and ColorSorter._parent_csdl:
# Note: capped is not used; a test indicates it's always on
# (at least in the tapered case). [bruce 090225 comment]
assert ColorSorter.sorting # since _parent_csdl is present
# note: radius can legally be a number, or a tuple of two radii,
# but the tuple case never gets this far -- it's diverted above
# into _schedule_tapered_cylinder.
ColorSorter._parent_csdl.addCylinder(
(pos1, pos2), radius, lcolor,
# glnames come from ColorSorter.pushName()
ColorSorter._gl_name_stack[-1])
else:
ColorSorter.schedule(lcolor,
drawcylinder_worker,
(pos1, pos2, radius, capped))
schedule_cylinder = staticmethod(schedule_cylinder)
def _schedule_tapered_cylinder(color, pos1, pos2, radius, capped = 0, opacity = 1.0):
"""
Schedule a tapered cylinder for rendering whenever ColorSorter thinks is
appropriate.
@param pos1: axis endpoint 1
@type pos1: Numeric array (list or tuple won't always work)
@param pos2: axis endpoint 2
@type pos2: Numeric array
@note: When cylinder shaders are active and supported, this will use
them, otherwise it will use a polycone.
@note: this is for internal use only; it does less checking that it
might need to do if there was a public function drawtaperedcylinder
that called it.
"""
#bruce 090225 made this by copying and modifying schedule_cylinder.
r1, r2 = map(float, radius)
if _DEBUG and ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
print "bare_prim tapered cylinder:", ColorSorter._gl_name_stack[-1], \
color, pos1, pos2, radius, capped, ColorSorter._debug_transforms()
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
# (see also comments in schedule_cylinder)
pos1 = ColorSorter._transform_point(pos1)
pos2 = ColorSorter._transform_point(pos2)
if len(color) == 3:
lcolor = (color[0], color[1], color[2], opacity)
else:
lcolor = color
use_cylinder_shader = ( ColorSorter._permit_shaders and
ColorSorter.glpane.glprefs.coneShader_desired() and
drawing_globals.coneShader_available()
)
if use_cylinder_shader and ColorSorter._parent_csdl:
assert ColorSorter.sorting
ColorSorter._parent_csdl.addCylinder(
# Note: capped is not used; a test indicates it's always on
# (at least in the tapered case). [bruce 090225 comment]
(pos1, pos2), radius, lcolor,
# glnames come from ColorSorter.pushName()
ColorSorter._gl_name_stack[-1])
else:
# Note: capped is not used in this case either; a test indicates
# it's effectively always on. (If it wasn't, we could try fixing
# that by repeating pos1 and pos2 in pos_array, with 0 radius.)
# [bruce 090225 comment]
vec = pos2 - pos1
pos_array = [pos1 - vec, pos1, pos2, pos2 + vec]
rad_array = [r1, r1, r2, r2]
ColorSorter.schedule(lcolor, drawpolycone_worker,
(pos_array, rad_array))
_schedule_tapered_cylinder = staticmethod(_schedule_tapered_cylinder)
def schedule_polycone(color, pos_array, rad_array,
capped = 0, opacity = 1.0):
"""
Schedule a polycone for rendering whenever ColorSorter thinks is
appropriate.
@note: this never uses shaders, even if it could. For a simple cone
or tapered cylinder, you can pass a tuple of 2 radii to drawcylinder
which will use shaders when available.
"""
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
pos_array = [ColorSorter._transform_point(A(pos)) for pos in pos_array]
if ColorSorter.glpane.glprefs.use_c_renderer and ColorSorter.sorting and 0: #bruce 090225 'and 0'
if len(color) == 3:
lcolor = (color[0], color[1], color[2], 1.0)
else:
lcolor = color
assert 0, "Need to implement a C add_polycone_multicolor function."
ColorSorter._cur_shapelist.add_polycone(
lcolor, pos_array, rad_array,
ColorSorter._gl_name_stack[-1], capped)
else:
if len(color) == 3:
lcolor = (color[0], color[1], color[2], opacity)
else:
lcolor = color
ColorSorter.schedule(lcolor, drawpolycone_worker,
(pos_array, rad_array))
schedule_polycone = staticmethod(schedule_polycone)
def schedule_polycone_multicolor(color, pos_array, color_array, rad_array,
capped = 0, opacity = 1.0):
"""
Schedule a polycone for rendering whenever ColorSorter thinks is
appropriate.
piotr 080311: this variant accepts a color array as an additional
parameter
"""
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
pos_array = [ColorSorter._transform_point(A(pos)) for pos in pos_array]
if ColorSorter.glpane.glprefs.use_c_renderer and ColorSorter.sorting:
if len(color) == 3:
lcolor = (color[0], color[1], color[2], 1.0)
else:
lcolor = color
assert 0, "Need to implement a C add_polycone function."
ColorSorter._cur_shapelist.add_polycone_multicolor(
lcolor, pos_array, color_array, rad_array,
ColorSorter._gl_name_stack[-1], capped)
else:
if len(color) == 3:
lcolor = (color[0], color[1], color[2], opacity)
else:
lcolor = color
ColorSorter.schedule(lcolor,
drawpolycone_multicolor_worker,
(pos_array, color_array, rad_array))
schedule_polycone_multicolor = staticmethod(schedule_polycone_multicolor)
def schedule_surface(color, pos, radius, tm, nm):
"""
Schedule a surface for rendering whenever ColorSorter thinks is
appropriate.
"""
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
if ColorSorter._relative_transforms():
ColorSorter._warn_transforms_nim("schedule_surface")
if len(color) == 3:
lcolor = (color[0], color[1], color[2], 1.0)
else:
lcolor = color
ColorSorter.schedule(lcolor, drawsurface_worker, (pos, radius, tm, nm))
schedule_surface = staticmethod(schedule_surface)
def schedule_line(color, endpt1, endpt2, dashEnabled,
stipleFactor, width, isSmooth):
"""
Schedule a line for rendering whenever ColorSorter thinks is
appropriate.
"""
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
endpt1 = ColorSorter._transform_point(endpt1)
endpt2 = ColorSorter._transform_point(endpt2)
#russ 080306: Signal "unshaded colors" for lines by an opacity of -1.
color = tuple(color) + (-1,)
ColorSorter.schedule(color, drawline_worker,
(endpt1, endpt2, dashEnabled,
stipleFactor, width, isSmooth))
schedule_line = staticmethod(schedule_line)
def schedule_triangle_strip(color, triangles, normals, colors):
"""
Schedule a line for rendering whenever ColorSorter thinks is
appropriate.
"""
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
# todo: use different flag than .reentrant
if ColorSorter._relative_transforms():
ColorSorter._warn_transforms_nim("schedule_triangle_strip")
ColorSorter.schedule(color, drawtriangle_strip_worker,
(triangles, normals, colors))
schedule_triangle_strip = staticmethod(schedule_triangle_strip)
# ==
def start(glpane, csdl, pickstate = None): # staticmethod
"""
Start sorting - objects provided to "schedule" and primitives such as
"schedule_sphere" and "schedule_cylinder" will be stored for a sort at
the time "finish" is called.
glpane is used for its transform stack. It can be None, but then
we will not notice whether primitives we collect are inside
any transforms beyond the ones current when we start,
and we will also never permit shaders if it's None.
csdl is a ColorSortedDisplayList or None, in which case immediate
drawing is done.
pickstate (optional) indicates whether the parent is currently selected.
"""
#russ 080225: Moved glNewList here for displist re-org.
# (bruce 090114: removed support for use_color_sorted_vbos)
#bruce 090220: support _parent_csdl.reentrant
#bruce 090220: added new required first argument glpane
if ColorSorter._parent_csdl and ColorSorter._parent_csdl.reentrant:
assert ColorSorter.sorting
ColorSorter._suspend()
assert not ColorSorter.sorting, \
"Called ColorSorter.start but already sorting?!"
ColorSorter.sorting = True
assert ColorSorter._parent_csdl is None #bruce 090105
ColorSorter._parent_csdl = csdl # used by finish()
assert ColorSorter.glpane is _the_fake_GLPane
if glpane is None:
glpane = _the_fake_GLPane
assert not glpane.transforms
ColorSorter.glpane = glpane
ColorSorter._initial_transforms = list(glpane.transforms)
ColorSorter._permit_shaders = glpane and glpane.permit_shaders
if _DEBUG:
print "CS.initial transforms:", ColorSorter._debug_transforms()
if csdl is not None:
csdl.start(pickstate)
#bruce 090224 refactored this into csdl.start
if ColorSorter.glpane.glprefs.use_c_renderer:
ColorSorter._cur_shapelist = ShapeList_inplace()
ColorSorter.sphereLevel = -1
else:
ColorSorter.sorted_by_color = {}
start = staticmethod(start)
# ==
def finish(draw_now = None): # staticmethod
"""
Finish sorting -- objects recorded since "start" will be sorted;
if draw_now is True, they'll also then be drawn.
If there's no parent CSDL, we're in all-in-one-display-list mode,
which is still a big speedup over plain immediate-mode drawing.
In that case, draw_now must be True since it doesn't make sense
to not draw (the drawing has already happened).
"""
# TODO: refactor by moving some of this into methods in CSDL
# (specifically, in ColorSorter._parent_csdl). [bruce 090224 comment]
assert ColorSorter.sorting #bruce 090220, appears to be true from this code
assert ColorSorter.glpane is not None #bruce 090220
assert ColorSorter.glpane is not _the_fake_GLPane #bruce 090304
if not ColorSorter._parent_csdl:
#bruce 090220 revised, check _parent_csdl rather than sorting
# (since sorting is always true); looks right but not fully analyzed
assert draw_now, "finish(draw_now = False) makes no sense unless ColorSorter._parent_csdl"
### WARNING: DUPLICATE CODE with end of this method
# (todo: split out some submethods to clean up)
ColorSorter.sorting = False
ColorSorter._unsuspend_if_needed()
return # Plain immediate-mode, nothing to do.
if draw_now is None:
draw_now = False # not really needed
print_compact_stack( "temporary warning: draw_now was not explicitly passed, using False: ") ####
#### before release, leaving it out can mean False without a warning,
# since by then it ought to be the "usual case". [bruce 090219]
pass
from utilities.debug_prefs import debug_pref, Choice_boolean_False
debug_which_renderer = debug_pref(
"debug print which renderer",
Choice_boolean_False) #bruce 060314, imperfect but tolerable
parent_csdl = ColorSorter._parent_csdl
ColorSorter._parent_csdl = None
# this must be done sometime before we return;
# it can be done here, since nothing in this method after this
# should use it directly or add primitives to it [bruce 090105]
if ColorSorter.glpane.glprefs.use_c_renderer:
# WARNING: this case has not been maintained for a long time
# [bruce 090219 comment]
quux.shapeRendererInit()
if debug_which_renderer:
#bruce 060314 uncommented/revised the next line; it might have
# to come after shapeRendererInit (not sure); it definitely has
# to come after a graphics context is created and initialized.
# 20060314 grantham - yes, has to come after
# quux.shapeRendererInit .
enabled = quux.shapeRendererGetInteger(quux.IS_VBO_ENABLED)
print ("using C renderer: VBO %s enabled" %
(('is NOT', 'is')[enabled]))
quux.shapeRendererSetUseDynamicLOD(0)
if ColorSorter.sphereLevel != -1:
quux.shapeRendererSetStaticLODLevels(ColorSorter.sphereLevel, 1)
quux.shapeRendererStartDrawing()
ColorSorter._cur_shapelist.draw()
quux.shapeRendererFinishDrawing()
ColorSorter.sorting = False
# So chunks can actually record their shapelist
# at some point if they want to
# ColorSorter._cur_shapelist.petrify()
# return ColorSorter._cur_shapelist
else:
if debug_which_renderer:
print "using Python renderer"
if parent_csdl is None:
# Either all in one display list, or immediate-mode drawing.
### REVIEW [bruce 090114]: are both possibilities still present,
# now that several old options have been removed?
ColorSorter._draw_sorted( ColorSorter.sorted_by_color)
pass
else: #russ 080225
parent_csdl.finish( ColorSorter.sorted_by_color)
#bruce 090224 refactored this into parent_csdl
pass
ColorSorter.sorted_by_color = None
pass
### WARNING: DUPLICATE CODE with another return statement in this method
ColorSorter.sorting = False
if draw_now:
# Draw the newly-built display list, and any shader primitives as well.
if parent_csdl is not None:
parent_csdl.draw(
# Use either the normal-color display list or the selected one.
selected = parent_csdl.selected)
ColorSorter._unsuspend_if_needed()
return
finish = staticmethod(finish)
def _draw_sorted(sorted_by_color): #russ 080320: factored out of finish().
"""
Traverse color-sorted lists, invoking worker procedures.
"""
### REVIEW: still needed? does this have some duplicated code with
# parent_csdl.finish? If so, has this been maintained as that's been
# modified? [bruce 090224 questions]
glEnable(GL_LIGHTING)
for color, funcs in sorted_by_color.iteritems():
opacity = color[3]
if opacity == -1:
#piotr 080429: Opacity == -1 signals the "unshaded color".
# Also, see my comment in "schedule".
glDisable(GL_LIGHTING) # reenabled below
glColor3fv(color[:3])
else:
apply_material(color)
pass
for func, params, name in funcs:
if name:
glPushName(name)
else:
pass ## print "bug_4: attempt to push non-glname", name
func(params) # Call the draw worker function.
if name:
glPopName()
pass
continue
if opacity == -1:
glEnable(GL_LIGHTING)
continue
return
_draw_sorted = staticmethod(_draw_sorted)
pass # end of class ColorSorter
ColorSorter._init_state()
# (this would be called in __init__ if ColorSorter was a normal class.)
# end
|