aims_project_windows 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,994 @@
1
+ module AimsProject
2
+ class CrystalViewer < Wx::Panel
3
+
4
+ include Wx
5
+ include Math
6
+ include Gl
7
+ include Glu
8
+ include Aims
9
+
10
+ ID_ROTATE = 100
11
+ ID_PAN = 101
12
+ ID_ZOOM = 102
13
+
14
+ PICK_ID_ATOM = 0x10000000
15
+ PICK_ID_PLANE = 0x20000000
16
+ PICK_ID_BOND = 0x30000000
17
+
18
+ # An array of Aims::UnitCell's to display
19
+ attr_reader :unit_cell
20
+ attr_reader :unit_cell_corrected
21
+
22
+ # How many times to repeat the unit cell
23
+ # A three element vector
24
+ attr_accessor :repeat
25
+
26
+ # If displaying multiple, then the current cell
27
+ attr_accessor :current_cell
28
+
29
+ # The keyboard actions
30
+ attr_accessor :mouse_motion_func # a string "rotate", "pan" or "zoom"
31
+
32
+ # The background color (glClearColor)
33
+ attr_accessor :background
34
+
35
+ attr_accessor :ortho_side, :ortho_zmin, :ortho_zmax
36
+ attr_accessor :x_down, :y_down, :alt, :az, :offx, :offy, :offz
37
+ attr_accessor :orthographic, :width, :height, :picking, :atom, :x_last, :y_last, :z_last
38
+ attr_accessor :render_mode
39
+
40
+ attr_accessor :xmax_plane, :xmin_plane
41
+ attr_accessor :ymax_plane, :ymin_plane
42
+ attr_accessor :zmax_plane, :zmin_plane
43
+
44
+ attr_accessor :slices, :stacks
45
+
46
+ attr_accessor :atoms_changed
47
+
48
+ attr_accessor :selection
49
+
50
+ def initialize(controller, parent, options = nil)
51
+
52
+ super(parent)
53
+ @controller = controller
54
+
55
+ # Register self as an observer of the options
56
+ if options
57
+ @options = options
58
+ else
59
+ @options = CrystalViewerOptions.new(parent)
60
+ end
61
+ @options.add_observer(self)
62
+
63
+ # Toolbar for the GL Canvas
64
+ basedir = File.dirname(__FILE__)
65
+ rotate_icon = Image.new(File.join(basedir,"rotate.gif"), BITMAP_TYPE_GIF)
66
+ @rotate_tool = BitmapButton.new(self, :id => ID_ROTATE, :bitmap => rotate_icon.rescale(16,15).convert_to_bitmap,:name => "rotate")
67
+
68
+ zoom_icon = Image.new(File.join(basedir,"zoom.gif"), BITMAP_TYPE_GIF)
69
+ @zoom_tool = BitmapButton.new(self, :id => ID_ZOOM, :bitmap => zoom_icon.rescale(16,15).convert_to_bitmap, :name => "zoom")
70
+
71
+ pan_icon = Image.new(File.join(basedir,"pan.gif"), BITMAP_TYPE_GIF)
72
+ @pan_tool = BitmapButton.new(self, :id => ID_PAN, :bitmap => pan_icon.rescale(16,15).convert_to_bitmap, :name => "pan")
73
+
74
+ evt_button(@rotate_tool) {|evt|
75
+ set_mouse_motion_function(:rotate)
76
+ }
77
+
78
+ evt_button(@zoom_tool) {|evt|
79
+ set_mouse_motion_function(:zoom)
80
+ }
81
+
82
+ evt_button(@pan_tool) {|evt|
83
+ set_mouse_motion_function(:pan)
84
+ }
85
+
86
+ @buttonSizer = HBoxSizer.new
87
+ @buttonSizer.add_item(@rotate_tool)
88
+ @buttonSizer.add_item(@zoom_tool)
89
+ @buttonSizer.add_item(@pan_tool)
90
+
91
+
92
+ #@glPanel = CalendarCtrl.new(self)
93
+ attrib = [Wx::GL_RGBA, Wx::GL_DOUBLEBUFFER, Wx::GL_DEPTH_SIZE, 24]
94
+ @glPanel = GLCanvas.new(self, -1, [-1, -1], [-1, -1],
95
+ Wx::FULL_REPAINT_ON_RESIZE, "GLCanvas", attrib)
96
+
97
+ vbox_sizer = VBoxSizer.new
98
+ vbox_sizer.add_item(@buttonSizer, :flag => EXPAND)
99
+ vbox_sizer.add_item(@glPanel, :proportion => 1, :flag => EXPAND)
100
+ set_sizer(vbox_sizer)
101
+
102
+
103
+ set_defaults
104
+
105
+ # Define the method to call for paint requests
106
+ evt_paint { @glPanel.paint { draw_scene }}
107
+ # Create the graphics view and define event handlers
108
+
109
+ # For some reason, not all left-clicks are captured,
110
+ # so we include this catchall as well, to prevent odd
111
+ # behavior when rotating
112
+ @glPanel.evt_mouse_events {|evt|
113
+ if evt.button_down
114
+ self.set_focus
115
+ mouse_down(evt.get_x, evt.get_y)
116
+ end
117
+ }
118
+
119
+ @glPanel.evt_left_up {|evt|
120
+ mouse_up(evt.get_x, evt.get_y)
121
+ draw_scene
122
+ }
123
+
124
+ @glPanel.evt_motion {|evt|
125
+ if evt.dragging
126
+ mouse_dragged(evt.get_x, evt.get_y)
127
+ draw_scene
128
+ end
129
+ }
130
+
131
+ @glPanel.evt_char {|evt|
132
+ nudge_dir = case evt.get_key_code
133
+ when K_LEFT
134
+ [-1,0,0]
135
+ when K_RIGHT
136
+ [1,0,0]
137
+ when K_UP
138
+ [0,1,0]
139
+ when K_DOWN
140
+ [0,-1,0]
141
+ else
142
+ [0,0,0]
143
+ end
144
+ nudge(nudge_dir)
145
+ }
146
+
147
+ # What to do when worker threads return
148
+ evt_thread_callback {|evt|
149
+ self.draw_scene
150
+ }
151
+ end
152
+
153
+ # Called when the options changes
154
+ def update
155
+ self.draw_scene
156
+ end
157
+
158
+ def set_defaults
159
+ self.current_cell = 0
160
+ self.background = Material.new(0.7, 0.7, 1.0, 1)
161
+ self.x_down = 0
162
+ self.y_down = 0
163
+ self.x_last = 0
164
+ self.y_last = 0
165
+ self.z_last = 0
166
+ self.alt = 0
167
+ self.az = 0
168
+ self.offx = 0
169
+ self.offy = 0
170
+ self.offz = -20
171
+ self.orthographic = true
172
+ self.ortho_side = 15
173
+ self.ortho_zmin = -1
174
+ self.ortho_zmax = 50
175
+ self.width = 500
176
+ self.height = 500
177
+ self.picking =false
178
+ # The last clicked atom
179
+ self.atom = nil
180
+ self.atoms_changed = true
181
+ self.hiRes
182
+ self.mouse_motion_func = :rotate
183
+ self.repeat = Vector[1,1,1]
184
+ self.render_mode = :ball_stick
185
+ @options.set_properties(
186
+ :bond_length => 3,
187
+ :show_bonds => true,
188
+ :show_lighting => true,
189
+ :show_xclip => false,
190
+ :show_yclip => false,
191
+ :show_zclip => true,
192
+ :show_supercell => true
193
+ )
194
+ end
195
+
196
+
197
+ def nudge(dir)
198
+ @controller.nudge_selected_atoms(dir[0]*0.5, dir[1]*0.5, 0)
199
+ end
200
+
201
+
202
+ =begin
203
+ Set the unit cell to display.
204
+ =end
205
+ def unit_cell=(uc)
206
+
207
+ if @unit_cell
208
+ @unit_cell.delete_observer(self)
209
+ end
210
+
211
+ @unit_cell = uc
212
+ @unit_cell_corrected = nil
213
+
214
+ init_clip_planes
215
+
216
+ @unit_cell.add_observer(self, :unit_cell_changed)
217
+ draw_scene
218
+
219
+ end
220
+
221
+ def unit_cell_changed
222
+ init_clip_planes
223
+ draw_scene
224
+ end
225
+
226
+ def init_clip_planes
227
+
228
+ Thread.new(self) { |evtHandler|
229
+ @unit_cell_corrected = @unit_cell.correct
230
+ evt = ThreadCallbackEvent.new
231
+ evtHandler.add_pending_event(evt)
232
+ }
233
+
234
+ return unless (@unit_cell and @unit_cell.is_valid?)
235
+
236
+ # each bounding box is a 2 element array [max, min]
237
+ bounding_box = @unit_cell.bounding_box(false)
238
+ xmax = bounding_box[0].x
239
+ xmin = bounding_box[1].x
240
+ ymax = bounding_box[0].y
241
+ ymin = bounding_box[1].y
242
+ zmax = bounding_box[0].z
243
+ zmin = bounding_box[1].z
244
+
245
+ @xmax_plane = Plane.new( 1, 0, 0, xmax, 0, 0)
246
+ @xmin_plane = Plane.new(-1, 0, 0, xmin, 0, 0)
247
+ @ymax_plane = Plane.new( 0, 1, 0, 0, ymax, 0)
248
+ @ymin_plane = Plane.new( 0,-1, 0, 0, ymin, 0)
249
+ @zmax_plane = Plane.new( 0, 0, 1, 0, 0, zmax)
250
+ @zmin_plane = Plane.new( 0, 0,-1, 0, 0, zmin)
251
+
252
+ # Add clip-planes to each unit cell
253
+ @unit_cell.clear_planes
254
+ @unit_cell.add_plane(@xmax_plane, false)
255
+ @unit_cell.add_plane(@xmin_plane, false)
256
+ @unit_cell.add_plane(@ymax_plane, false)
257
+ @unit_cell.add_plane(@ymin_plane, false)
258
+ @unit_cell.add_plane(@zmax_plane, false)
259
+ @unit_cell.add_plane(@zmin_plane, false)
260
+ @unit_cell.recache_visible_atoms
261
+ @unit_cell.make_bonds(@options.bond_length)
262
+
263
+ end
264
+
265
+ def dump_properties
266
+ self.instance_variables.each{|v|
267
+ puts "#{v} = #{self.instance_variable_get(v)}"
268
+ }
269
+ end
270
+
271
+ # The currently displayed unit cell
272
+ def current_unit_cell
273
+ self.unit_cell
274
+ end
275
+
276
+ def dump_geometry
277
+ atoms = self.unit_cell
278
+ if atoms
279
+ puts atoms.format_geometry_in
280
+ end
281
+ end
282
+
283
+ def bond_length=(l)
284
+ @options.bond_length = l
285
+ # FIXME The bonds should be made in the controller
286
+ # self.unit_cell.each{|uc| uc.make_bonds(l)} if self.unit_cell
287
+ end
288
+
289
+ def mouse_down(x,y)
290
+ self.x_down = x
291
+ self.y_down = y
292
+ self.x_last = x
293
+ self.y_last = y
294
+ self.loRes
295
+ end
296
+
297
+ def mouse_up(x,y)
298
+ return unless self.unit_cell
299
+ @picked = pick_object(x,y)
300
+ self.atom = self.unit_cell.atoms.find{|a| a.id == @picked[:atoms].last}
301
+ if self.atom
302
+ @controller.select_atom(self.atom)
303
+ # puts self.atom.format_geometry_in
304
+ end
305
+
306
+ unless @picked[:planes].empty?
307
+ clip_plane_id = @picked[:planes].first
308
+ @active_clip_plane = case clip_plane_id
309
+ when 1
310
+ @zmax_plane
311
+ when 2
312
+ @zmin_plane
313
+ when 3
314
+ @xmax_plane
315
+ when 4
316
+ @xmin_plane
317
+ when 5
318
+ @ymax_plane
319
+ when 6
320
+ @ymin_plane
321
+ else
322
+ nil
323
+ end
324
+ if @active_clip_plane
325
+ set_mouse_motion_function(:move_clip_plane)
326
+ end
327
+ end
328
+ self.hiRes
329
+
330
+ # Harmless correction for bug in WxRuby that doesn't register all mouse down events
331
+ self.x_last = nil
332
+ self.y_last = nil
333
+
334
+ glutPostRedisplay if @using_glut
335
+ end
336
+
337
+ def mouse_dragged(x, y)
338
+
339
+ # Harmless correction for bug in WxRuby that doesn't register all mouse down events
340
+ self.x_last = x if self.x_last.nil?
341
+ self.y_last = y if self.y_last.nil?
342
+ self.z_last = z if self.z_last.nil?
343
+
344
+ case self.mouse_motion_func
345
+ when :rotate
346
+ rotate(x,y)
347
+ when :zoom
348
+ zoom(x,y)
349
+ when :pan
350
+ pan(x,y)
351
+ when :move_clip_plane
352
+ move_clip_plane(x,y)
353
+ else
354
+ rotate(x,y)
355
+ end
356
+ glutPostRedisplay if @using_glut
357
+ end
358
+
359
+ def delete_atom
360
+ if self.atom
361
+ # Remove
362
+ self.unit_cell.remove_atom(self.atom)
363
+ end
364
+ end
365
+
366
+ # Unproject the mouse coordinates to model space
367
+ # Handles the flipping of the y-coordinate
368
+ def unproject(x,y,z)
369
+ model = glGetDoublev(GL_MODELVIEW_MATRIX)
370
+ proj = glGetDoublev(GL_PROJECTION_MATRIX)
371
+ viewport = glGetIntegerv(GL_VIEWPORT);
372
+
373
+ gluUnProject(x, viewport[3]-y, z, model, proj, viewport)
374
+ end
375
+
376
+ # Cast a ray from the near clip-plane to the far clip-plane through the point x,y
377
+ # return a two element array
378
+ # element 0 is the model space coordinates of (x,y) on the near clip plane
379
+ # element 1 is the direction of the ray
380
+ def cast_ray_to(x,y)
381
+ x1,y1,z1 = unproject(x,y,0)
382
+ x2,y2,z2 = unproject(x,y,1)
383
+ [ Vector[x1, y1, z1], Vector[x2-x1, y2-y1, z2-z1] ]
384
+ end
385
+
386
+ def move_clip_plane(x,y)
387
+
388
+ x_obj_last, y_obj_last, z_obj_last = unproject(x_last, y_last, 0)
389
+ x_obj, y_obj, z_obj = unproject(x,y,0)
390
+
391
+ w = [x_obj - x_obj_last, y_obj - y_obj_last, z_obj - z_obj_last]
392
+ n = @active_clip_plane.unit_normal
393
+ d = w[0]*n[0] + w[1]*n[1] + w[2]*n[2]
394
+
395
+ # scale = 0.1
396
+ # dx = (x - self.x_last)
397
+ # dy = (y - self.y_last)
398
+ # dr = sqrt(dx*dx + dy*dy)*(0 > dy ? -1 : 1)*scale
399
+ self.x_last = x
400
+ self.y_last = y
401
+
402
+ @active_clip_plane.displace_along_normal(d)
403
+ self.atoms_changed = true
404
+ end
405
+
406
+ def rotate(x,y)
407
+ self.az += 5*(x - self.x_last)
408
+ self.alt += 5*(y - self.y_last)
409
+ self.x_last = x
410
+ self.y_last = y
411
+ end
412
+
413
+ def zoom(x,y)
414
+ if self.orthographic
415
+ self.ortho_side -= (y - self.y_last)*0.1
416
+ else
417
+ self.offz -= (y - self.y_last)*0.1
418
+ end
419
+ self.x_last = x
420
+ self.y_last = y
421
+ end
422
+
423
+ def pan(x,y)
424
+
425
+ z = 0.0 # This value is normalized with respect to the near and far clip planes
426
+
427
+ obj_x, obj_y, obj_z = unproject(x, y, z)
428
+ obj_x_last, obj_y_last, obj_z_last = unproject(x_last, y_last, z_last)
429
+ @center.x += obj_x_last - obj_x
430
+ @center.y += obj_y_last - obj_y
431
+ @center.z += obj_z_last - obj_z
432
+
433
+ self.x_last = x
434
+ self.y_last = y
435
+ self.z_last = z
436
+ end
437
+
438
+ def loRes
439
+ self.slices = 5
440
+ self.stacks = 5
441
+ end
442
+
443
+ def hiRes
444
+ self.slices = 20
445
+ self.stacks = 20
446
+ end
447
+
448
+ def set_view(offset_x, offset_y, offset_z, alt, az)
449
+ self.offx = offset_x
450
+ self.offy = offset_y
451
+ self.offz = offset_z
452
+ self.alt = alt
453
+ self.az = az
454
+ end
455
+
456
+ # Executes the named method when the mouse is dragged
457
+ def set_mouse_motion_function(method_name)
458
+ self.mouse_motion_func = method_name.to_sym
459
+ end
460
+
461
+ def draw_scene
462
+
463
+ @glPanel.set_current
464
+ sz = @glPanel.size
465
+ viewport_setup(sz.width, sz.height)
466
+
467
+
468
+ begin
469
+ draw_init
470
+ apply_projection
471
+ position_camera
472
+ add_lights if @options.show_lighting
473
+ outline_supercell if @options.show_supercell
474
+
475
+ if self.unit_cell
476
+ atoms = self.unit_cell
477
+ @options.x_repeat.times do |i|
478
+ @options.y_repeat.times do |j|
479
+ @options.z_repeat.times do |k|
480
+
481
+ origin = if self.unit_cell.lattice_vectors
482
+ atoms.lattice_vectors[0]*i + atoms.lattice_vectors[1]*j + atoms.lattice_vectors[2]*k
483
+ else
484
+ Vector[0, 0, 0]
485
+ end
486
+
487
+ draw_bonds(origin) if @options.show_bonds
488
+ draw_lattice(origin)
489
+ end
490
+ end
491
+ end
492
+ end
493
+ draw_clip_planes
494
+ rescue GeometryEvaluationException => e
495
+ rescue AimsProjectException => e
496
+ puts e.message
497
+ puts e.backtrace.join("\n")
498
+ end
499
+
500
+ @glPanel.swap_buffers
501
+ end
502
+
503
+ def viewport_setup(width, height)
504
+ self.width = width
505
+ self.height = height
506
+ glViewport(0, 0, self.width, self.height)
507
+ end
508
+
509
+ def apply_projection
510
+ glMatrixMode(GL_PROJECTION)
511
+ glLoadIdentity()
512
+ if self.orthographic
513
+ glOrtho(-self.ortho_side, self.ortho_side, -self.ortho_side,self.ortho_side, self.ortho_zmin, self.ortho_zmax)
514
+ else
515
+ gluPerspective(60, self.width/self.height, 1, 120)
516
+ end
517
+ glMatrixMode(GL_MODELVIEW)
518
+ end
519
+
520
+ def draw_init
521
+ glClearColor(background.r,background.g,background.b,@options.solid_bg)
522
+ glEnable(GL_DEPTH_TEST)
523
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
524
+
525
+
526
+ # Antialiasing
527
+ glEnable(GL_LINE_SMOOTH)
528
+ glEnable(GL_BLEND)
529
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
530
+ glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
531
+ glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)
532
+
533
+ if @options.show_lighting
534
+ glEnable(GL_LIGHTING)
535
+ glEnable(GL_LIGHT0)
536
+ else
537
+ glDisable(GL_LIGHTING)
538
+ end
539
+
540
+ # Point size
541
+ glPointSize(5)
542
+ glLineWidth(2)
543
+ end
544
+
545
+ def pick_object(x,y)
546
+
547
+ buf = glSelectBuffer(512)
548
+ glRenderMode(GL_SELECT)
549
+ model = glGetDoublev(GL_MODELVIEW_MATRIX)
550
+ proj = glGetDoublev(GL_PROJECTION_MATRIX)
551
+ viewport = glGetIntegerv(GL_VIEWPORT);
552
+
553
+ self.picking = true
554
+ glMatrixMode(GL_PROJECTION)
555
+ glLoadIdentity()
556
+ gluPickMatrix(x,viewport[3]-y,5,5,viewport)
557
+ if self.orthographic
558
+ glOrtho(-self.ortho_side, self.ortho_side, -self.ortho_side,self.ortho_side, self.ortho_zmin, self.ortho_zmax)
559
+ else
560
+ gluPerspective(60, self.width/self.height, 1, 60)
561
+ end
562
+
563
+ z = 0.0 # This value is normalized with respect to the near and far clip planes
564
+ obj_x, obj_y, obj_z = gluUnProject(x, viewport[3]-y, z, model, proj, viewport)
565
+
566
+ glMatrixMode(GL_MODELVIEW)
567
+
568
+ glInitNames
569
+ if self.unit_cell
570
+ atoms = self.unit_cell
571
+ @options.x_repeat.times do |i|
572
+ @options.y_repeat.times do |j|
573
+ @options.z_repeat.times do |k|
574
+
575
+ # Hack to display non-periodic systems
576
+ origin = if self.unit_cell.lattice_vectors
577
+ atoms.lattice_vectors[0]*i + atoms.lattice_vectors[1]*j + atoms.lattice_vectors[2]*k
578
+ else
579
+ Vector[0,0,0]
580
+ end
581
+
582
+ self.draw_lattice(origin)
583
+ end
584
+ end
585
+ end
586
+ end
587
+ self.draw_clip_planes
588
+
589
+ self.picking = false
590
+
591
+ glMatrixMode(GL_MODELVIEW);
592
+ glFlush();
593
+
594
+ count = glRenderMode(GL_RENDER)
595
+ data = buf.unpack("L!*")
596
+ names = []
597
+ count.times do
598
+ num_names = data.shift
599
+ min_depth = data.shift
600
+ max_depth = data.shift
601
+ num_names.times do
602
+ names << data.shift
603
+ end
604
+ end
605
+
606
+ picked_objects = {:atoms => [], :planes => [], :bonds => []}
607
+ names.each{|n|
608
+ if (n & PICK_ID_ATOM) == PICK_ID_ATOM
609
+ picked_objects[:atoms] << (n ^ PICK_ID_ATOM)
610
+ end
611
+ if (n & PICK_ID_PLANE) == PICK_ID_PLANE
612
+ picked_objects[:planes] << (n ^ PICK_ID_PLANE)
613
+ end
614
+ }
615
+
616
+ picked_objects
617
+ end
618
+
619
+ def position_camera
620
+ return unless self.unit_cell
621
+ atoms = self.unit_cell
622
+
623
+ # Find the center of all atoms, not just visible ones.
624
+ @center = atoms.center unless @center
625
+
626
+ # Move camera out along z-axis
627
+ glMatrixMode(GL_MODELVIEW)
628
+ glLoadIdentity()
629
+
630
+ glTranslatef(self.offx,self.offy,self.offz)
631
+ glRotatef(self.alt, 1, 0, 0)
632
+ glRotatef(self.az, 0, 0, 1)
633
+ glTranslatef(-@center.x, -@center.y, -@center.z)
634
+ end
635
+
636
+ def add_lights
637
+ light0_position = [1,1,1,0]
638
+ light1_position = [-1,-1,1,1]
639
+ glLightModel(GL_LIGHT_MODEL_AMBIENT, [0.6, 0.6, 0.6 ,1.0])
640
+ # glLightModel(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE)
641
+ glLightfv(GL_LIGHT0, GL_POSITION, light0_position)
642
+ glLightfv(GL_LIGHT1, GL_POSITION, light1_position)
643
+ end
644
+
645
+ def outline_supercell
646
+ return unless self.unit_cell
647
+ uc = self.unit_cell
648
+
649
+ vecs = uc.lattice_vectors
650
+ return unless vecs
651
+
652
+ origin = [0, 0, 0]
653
+ v1 = vecs[0]
654
+ v2 = vecs[1]
655
+ v3 = vecs[2]
656
+
657
+ # Corner #1
658
+ c1 = v1 + v3
659
+
660
+ # Corner #2
661
+ c2 = v2 + v3
662
+
663
+ # Corner #3
664
+ c3 = v1 + v2
665
+
666
+ # Corner #4
667
+ c4 = v1 + v2 + v3
668
+
669
+ Material.black.apply
670
+ glLineWidth(1.0)
671
+
672
+ glBegin(GL_LINES)
673
+
674
+ glVertex3f(origin[0], origin[1], origin[2])
675
+ glVertex3f(v1[0], v1[1], v1[2])
676
+
677
+ glVertex3f(origin[0], origin[1], origin[2])
678
+ glVertex3f(v2[0], v2[1], v2[2])
679
+
680
+ glVertex3f(origin[0], origin[1], origin[2])
681
+ glVertex3f(v3[0], v3[1], v3[2])
682
+
683
+ glVertex3f(v1[0], v1[1], v1[2])
684
+ glVertex3f(c3[0], c3[1], c3[2])
685
+
686
+ glVertex3f(v2[0], v2[1], v2[2])
687
+ glVertex3f(c3[0], c3[1], c3[2])
688
+
689
+ glVertex3f(c3[0], c3[1], c3[2])
690
+ glVertex3f(c4[0], c4[1], c4[2])
691
+
692
+ glVertex3f(v1[0], v1[1], v1[2])
693
+ glVertex3f(c1[0], c1[1], c1[2])
694
+
695
+ glVertex3f(v2[0], v2[1], v2[2])
696
+ glVertex3f(c2[0], c2[1], c2[2])
697
+
698
+ glVertex3f(c1[0], c1[1], c1[2])
699
+ glVertex3f(c4[0], c4[1], c4[2])
700
+
701
+ glVertex3f(c2[0], c2[1], c2[2])
702
+ glVertex3f(c4[0], c4[1], c4[2])
703
+
704
+ glVertex3f(v3[0], v3[1], v3[2])
705
+ glVertex3f(c1[0], c1[1], c1[2])
706
+
707
+ glVertex3f(v3[0], v3[1], v3[2])
708
+ glVertex3f(c2[0], c2[1], c2[2])
709
+
710
+ glEnd()
711
+ end
712
+
713
+ def draw_bonds(origin = [0,0,0])
714
+ return unless self.unit_cell
715
+ atoms = if @options.correct
716
+ @unit_cell_corrected
717
+ else
718
+ @unit_cell
719
+ end
720
+ return unless atoms.bonds
721
+
722
+ Material.black.apply
723
+ glLineWidth(1.0)
724
+ glBegin(GL_LINES)
725
+ atoms.bonds.each{|b|
726
+ glVertex3f(origin[0] + b[0].x, origin[1] + b[0].y, origin[2] + b[0].z)
727
+ glVertex3f(origin[0] + b[1].x, origin[1] + b[1].y, origin[2] + b[1].z)
728
+ }
729
+ glEnd()
730
+ end
731
+
732
+ #
733
+ # Draw an atom
734
+ # @param x The x-coordinate
735
+ # @param y The y-coordinate
736
+ # @param z The z-coordinate
737
+ # @param r The radius
738
+ # @param name The name of the sphere (for picking)
739
+ # @return a sphere_quadric for reuse if desired. Make sure to delete it when done
740
+ def draw_sphere(x,y,z,r, name, sphere_quadric=nil)
741
+
742
+ unless sphere_quadric
743
+ sphere_quadric = gluNewQuadric()
744
+ gluQuadricDrawStyle(sphere_quadric, GLU_FILL)
745
+ gluQuadricNormals(sphere_quadric, GLU_SMOOTH)
746
+ gluQuadricOrientation(sphere_quadric, GLU_OUTSIDE)
747
+ end
748
+
749
+ # Load a new matrix onto the stack
750
+ glPushMatrix()
751
+ glPushName(name | PICK_ID_ATOM) if self.picking
752
+ glTranslatef(x, y, z)
753
+ gluSphere(sphere_quadric, r, slices, stacks)
754
+ glPopName() if picking
755
+ glPopMatrix()
756
+
757
+ sphere_quadric
758
+ end
759
+
760
+ def draw_lattice(origin = [0,0,0])
761
+
762
+ return unless self.unit_cell
763
+
764
+ atoms = if @options.correct
765
+ @unit_cell_corrected
766
+ else
767
+ @unit_cell
768
+ end
769
+
770
+ return unless atoms
771
+
772
+ # Create sphere object
773
+ rmin = 0.2
774
+ rmax = 0.5
775
+
776
+ if self.atoms_changed
777
+ atoms.recache_visible_atoms
778
+ self.atoms_changed = false
779
+ end
780
+
781
+ # Calculate radius scaling factor vs. depth
782
+ rrange = rmax - rmin
783
+ # bb = atoms.bounding_box
784
+ zmin = atoms.min{|a,b| a.z <=> b.z}.z
785
+ zmax = atoms.max{|a,b| a.z <=> b.z}.z
786
+ zrange = zmax - zmin
787
+ if zrange == 0
788
+ rscale = 0
789
+ else
790
+ rscale = rrange/zrange
791
+ end
792
+
793
+
794
+
795
+ sphere_quadric = nil
796
+ for a in atoms
797
+ a.material.apply(@options.show_lighting)
798
+ case self.render_mode
799
+ when :ball_stick
800
+ r = rmin+(a.z - zmin)*rscale
801
+ else
802
+ r = 2.0
803
+ end
804
+
805
+ sphere_quadric = draw_sphere(origin[0] + a.x, origin[1] + a.y, origin[2] + a.z, r, a.id, sphere_quadric)
806
+
807
+ end
808
+ gluDeleteQuadric(sphere_quadric) if sphere_quadric
809
+
810
+ end
811
+
812
+ # a test method to see of an object of a particular type was picked.
813
+ # valid types are :atom, :plane
814
+ def is_picked?(type, id)
815
+ if @picked
816
+ case type
817
+ when :atom
818
+ @picked[:atoms].member? id
819
+ when :plane
820
+ @picked[:planes].member? id
821
+ when :bond
822
+ @picked[:bonds].member? id
823
+ else
824
+ false
825
+ end
826
+ else
827
+ false
828
+ end
829
+ end
830
+
831
+ #
832
+ # Draw a plane
833
+ # use the vertices defined in lineLoop as the boundary of the plane
834
+ def draw_plane(plane, lineLoop, pickid)
835
+
836
+ glPushName(pickid | PICK_ID_PLANE) if self.picking
837
+
838
+ # if is_picked?(:plane, pickid)
839
+ # Material.black.apply
840
+ # glLineWidth(3.0)
841
+ # glEdgeFlag(GL_TRUE)
842
+ # end
843
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
844
+
845
+ Material.new(0.9, 0.9, 0.0, 0.5).apply
846
+ glBegin(GL_TRIANGLE_FAN)
847
+ glNormal3f(plane.a, plane.b, plane.c)
848
+ lineLoop.each{|p|
849
+ glVertex3f(p[0], p[1], p[2])
850
+ }
851
+ glEnd()
852
+
853
+ glEdgeFlag(GL_FALSE)
854
+ glPopName() if picking
855
+
856
+ if (is_picked?(:plane, pickid))
857
+ Material.black.apply
858
+ glLineWidth(3.0)
859
+ glBegin(GL_LINE_LOOP)
860
+ lineLoop.each{|p|
861
+ glVertex3f(p[0], p[1], p[2])
862
+ }
863
+ glEnd()
864
+ glLineWidth(1.0)
865
+ end
866
+
867
+ end
868
+
869
+ def draw_clip_planes
870
+ return unless self.unit_cell
871
+ atoms = self.unit_cell
872
+
873
+ bb = atoms.bounding_box
874
+ bbx1 = bb[0].x
875
+ bbx2 = bb[1].x
876
+ bby1 = bb[0].y
877
+ bby2 = bb[1].y
878
+ bbz1 = bb[0].z
879
+ bbz2 = bb[1].z
880
+
881
+ # draw z_planes
882
+ # The are bounded by the min and max points in the x-y plane
883
+
884
+ if @options.show_zclip and @zmax_plane and @zmin_plane
885
+ z = -1*@zmax_plane.distance_to_point(0,0,0)
886
+ draw_plane(@zmax_plane, [[bbx1, bby1, z],
887
+ [bbx2, bby1, z],
888
+ [bbx2, bby2, z],
889
+ [bbx1, bby2, z]],1)
890
+
891
+ z = @zmin_plane.distance_to_point(0,0,0)
892
+ draw_plane(@zmin_plane, [[bbx1, bby1, z],
893
+ [bbx2, bby1, z],
894
+ [bbx2, bby2, z],
895
+ [bbx1, bby2, z]],2)
896
+ end
897
+
898
+ if @options.show_xclip and @xmax_plane and @xmin_plane
899
+ x = -1*@xmax_plane.distance_to_point(0,0,0)
900
+ draw_plane(@xmax_plane, [[x, bby1, bbz1],
901
+ [x, bby1, bbz2],
902
+ [x, bby2, bbz2],
903
+ [x, bby2, bbz1]],3)
904
+
905
+ x = @xmin_plane.distance_to_point(0,0,0)
906
+ draw_plane(@xmin_plane, [[x, bby1, bbz1],
907
+ [x, bby1, bbz2],
908
+ [x, bby2, bbz2],
909
+ [x, bby2, bbz1]],4)
910
+ end
911
+
912
+ if @options.show_yclip and @ymax_plane and @ymin_plane
913
+ y = -1*@ymax_plane.distance_to_point(0,0,0)
914
+ draw_plane(@ymax_plane, [[bbx1, y, bbz1],
915
+ [bbx1, y, bbz2],
916
+ [bbx2, y, bbz2],
917
+ [bbx2, y, bbz1]],5)
918
+
919
+ y = @ymin_plane.distance_to_point(0,0,0)
920
+ draw_plane(@ymin_plane, [[bbx1, y, bbz1],
921
+ [bbx1, y, bbz2],
922
+ [bbx2, y, bbz2],
923
+ [bbx2, y, bbz1]],6)
924
+ end
925
+
926
+ end
927
+
928
+ def rgba_image_data
929
+
930
+ # 4 bytes/pixel
931
+ bytewidth = self.width*4
932
+ bytewidth = (bytewidth.to_i + 3) & ~3 # Align to 4 bytes
933
+ bytes = bytewidth * self.height
934
+
935
+ # Finish any pending Commands
936
+ @glPanel.set_current
937
+ glFinish
938
+
939
+ # Setup pixel store
940
+ glPixelStorei(Gl::GL_PACK_ALIGNMENT, 4) # Force 4-byte alignment
941
+ glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
942
+ glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
943
+ glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
944
+
945
+ glReadPixels(0, 0, self.width, self.height, Gl::GL_RGBA, Gl::GL_UNSIGNED_BYTE)
946
+
947
+ end
948
+
949
+ def image
950
+ image = Image.new(self.width, self.height)
951
+ image.set_rgb_data(self.rgb_image_data)
952
+ image.set_alpha_data(self.alpha_image_data)
953
+ image
954
+ end
955
+
956
+ def rgb_image_data
957
+
958
+ # 3 bytes/pixel
959
+ bytewidth = self.width*3
960
+ bytes = bytewidth * self.height
961
+
962
+ # Finish any pending Commands
963
+ @glPanel.set_current
964
+ glFinish
965
+
966
+ # Setup pixel store
967
+ glPixelStorei(Gl::GL_PACK_ALIGNMENT, 1) # Force byte alignment
968
+ glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
969
+ glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
970
+ glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
971
+
972
+ glReadPixels(0, 0, self.width, self.height, Gl::GL_RGB, Gl::GL_UNSIGNED_BYTE)
973
+
974
+ end
975
+
976
+ def alpha_image_data
977
+ # 1 byte/pixel
978
+ bytewidth = self.width;
979
+
980
+ bytes = bytewidth*height;
981
+ @glPanel.set_current
982
+ glFinish
983
+
984
+ # Setup pixel store
985
+ glPixelStorei(Gl::GL_PACK_ALIGNMENT, 1) # Force byte alignment
986
+ glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
987
+ glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
988
+ glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
989
+
990
+ glReadPixels(0, 0, self.width, self.height, Gl::GL_ALPHA, Gl::GL_UNSIGNED_BYTE)
991
+ end
992
+
993
+ end
994
+ end