aims_project_windows 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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