Newsgroup: it.comp.os.linux.development
Titolo: Classe in Ruby per geometria

Questo e` lo snippet che nel thread sul CAD avevo promesso di postare.
Si tratta della classe che rappresenta il punto geometrico, e di un
``mixin'', cioe` un insieme di procedure e funzioni che puo` essere
incluso a mo' di componente in qualsiasi classe per cui risulti
appropriato, e i suoi metodi entrano a far parte dei metodi di quella
classe.
# mixin per trasformazioni rigide
#
# usa matrici 4x4 per trasformazioni nello spazio tridimensionale
# omogeneo
#
module Transformable
  # costante che rappresenta la matrice di trasformazione neutra
  #
  IDENTITY = Matrix[    
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]
  ]
  def initialize(*args)
    @transform = IDENTITY
  end
 
  # il parametro e` un vettore, per scalare contemporaneamente su piu`
  # assi
  #
  def scale(v)                              
    # se una componente del vettore e` assente, viene settata a uno
    # che e` neutro rispetto all'operazione di scaling (per usare in
    # modo trasparente anche il bidimensionale)
    #
    v = (0..2).collect {|i| v[i].nil? ? 1 : v[i]}
    # aggiungere questa trasformazione alla matrice di trasformazione
    #
    @transform = Matrix[
      [v[0], 0, 0, 0],
      [0, v[1], 0, 0],
      [0, 0, v[2], 0],
      [0, 0, 0, 1]
    ] * @transform
  end
  def translate(v)
    v = (0..2).collect {|i| v[i].nil? ? 0 : v[i]}  
    @transform = Matrix[
      [1, 0, 0, v[0]],
      [0, 1, 0, v[1]],
      [0, 0, 1, v[2]],
      [0, 0, 0, 1]
    ] * @transform
  end
  # ruota attorno all'asse z. come si fa a ruotare attorno ad un asse
  # arbitraria, ne' x ne' y?
  #
  def rotate(angle)
    s, c = sin(angle.to_rad), cos(angle.to_rad)
    @transform = Matrix[
      [c, -s, 0, 0],
      [s, c, 0, 0],
      [0, 0, 1, 0],
      [0, 0, 0, 1]
    ] * @transform
  end
  # applica le trasformazioni conservate nella matrice di
  # trasformazione e reimposta quest'ultima sulla matrice identita`
  #
  # notare che il metodo apply_transform non e` definito qui ma lo
  # definisce la classe in cui il mixin viene incluso; in questo modo
  # un punto puo` applicare la matrice di trasformazione al solo
  # vettore che ne rappresenta la posizione, un poligono puo`
  # applicarla iterando su tutti i punti di cui e` composto, e cosi`
  # via
  #
  def transform!
    apply_transform
    @transform = IDENTITY
  end
end  
class Point
  include Transformable
 
  attr_reader :pos
  def initialize(array = [0,0,0])
    super
    pos = array
  end
  def apply_transform
    @pos = @transform * @pos
  end
end
Spero avrete capito perche' definivo pulito e semplice il codice in
Ruby. ;-) Ora, la mia richiesta: mi indicate una procedura per ruotare
un punto attorno ad un'asse arbitraria, e come potrebbe essere
integrata nel codice qui sopra?
Massimiliano

 integrata nel codice qui sopra?
Rappresenta l'asse in forma parametrica: P(t) = A + U*t, dove A e'
un punto dell'asse e U ne indica la direzione.
E' tutto piu' semplice se ti riconduci alla rotazione intorno ad un asse,
per es. z. A questo scopo costruisci la trasformazione che:
- trasla A nell'origine: T(-A)
- ruota U intorno a z in modo che vada a finire sul piano xz: Rz(alfa)
- ruota ancora intorno a y in modo che U vada a coincidere con z: Ry(beta)
- ruota dell'angolo richiesto: Rz(theta)
- rimetti le cose a posto
In sintesi:
       T(A) Rz(-alfa) Ry(-beta) Rz(theta) Ry(beta) Rz(alfa) T(-A)
Gli angoli alfa e beta li ricavi facilmente da U:
       tan alfa = Ux / Uy
       tan beta = Uz / sqrt(Ux^2 + Uy^2)
Naturalmente, se U giace su z la trasformazione e' una banale rotazione
Rz(theta).
P.S. - se funziona, fammelo sapere che serve anche a me (in Java, pero').
Ciao,
// Umberto Salsi
//
// 
http://digilander.iol.it/salsi
 <-- GnuPG key available here!
// Key fingerprint = B957 FB00 10F5 A770 4A26  2D80 BE2C F6CD 77E9 29AE

>>>>> "Max" == Massimiliano Mirra <ba...@prism.localnet> writes:
[...]
Max> Spero avrete capito perche' definivo pulito e semplice il codice
Max> in Ruby. ;-)
Soltanto una nota a margine: esiste un interprete lisp in python che
potrebbe far molto comodo nel vostro caso. E il python č altrettanto
leggibile del ruby, con in piů alcune cose che forse sono piů avanzate
sia a livello di interfaccia che a livello di formati.
Fateci un pensierino.
Saluti
Marco

 P.S. - se funziona, fammelo sapere che serve anche a me (in Java, pero').
Digerisco, metabolizzo e ti faccio sapere.
Massimiliano

 Fateci un pensierino.
In realta` ci sarebbero delle classi per interfacciare Ruby tanto a
Python che a Perl (e persino a Delphi) e quindi usarne tranquillamente
le risorse, ma credo di avere un'idea molto piu` furba che non
appoggiarsi ad un interprete scritto in un altro linguaggio
interpretato (!).
Si include un normalissimo interprete LISP free, solo che, prima di
dargli in pasto lo script, si fa passare al volo quest'ultimo in un
preprocessore che sostitusce tutti i dati e le chiamate dirette al
motore CAD con delle chiamate a una socket (Unix o TCP).  In questo
modo tra l'altro il motore ne resta separato dall'interprete.  Cool,
uh?  Modularity rulez. ;-)
Massimiliano

 prendo come A uno dei due punti da cui sono partito?
Quasi esatto: non e' necessario normalizzare.


 P.S. - se funziona, fammelo sapere che serve anche a me (in Java, pero').
Ecco quel che ho tirato fuori.  Il principio e` quello che mi hai
spiegato ma le rotazioni no (avevo problemi con quelle che mi hai
indicato e le ho ripensate).
  # x, y, z = u[0], u[1], u[2]
  #
  def rotate(angle, u)
    alpha = atan2(u[0], u[2])
    beta = atan2(u[1], u[2])
    rotate_y(-alpha)
    rotate_x(beta)
    rotate_z(angle)
    rotate_x(-beta)
    rotate_y(alpha)
  end
Questa e` la descrizione della funziona atan2:
------------------------------------------------------------ Math::atan2
     Math.atan2( y, x ) -> aFloat
------------------------------------------------------------------------
     Computes the arc tangent given y and x. Returns -PI..PI.
Non e` molto efficiente per come l'ho scritta, perche' per quegli
angoli saranno ricomputati seno e coseno due volte ciascuno.
Probabilmente sarebbe meglio generalizzare questa funzione e far
dipendere da essa rotate_x, rotate_y e rotate_z, con qualche if per
saltare i punti inutili nel caso di un vettore ortogonale.
Alpha l'ho dovuto sottrarre, beta addizionare, non mi chiedere perche'
tanto non lo so ;-).  Questione di sistema di coordinate, suppongo.
Non ho casi complessi contro cui testarla.  Se tu o qualcun altro puo`
darmi coordinate di punti di partenza, coordinate di punti di arrivo,
e angoli (es. con [4,2,10] per punto di partenza e 42 di angolo, qual
e` il punto di arrivo?) magari ricavati da un altro programma, infilo
i dati e controllo.  Questi sono i tre test che passa:
  def testRotateRadians
    ent = Point.new [1,0,0]
    ent.r!(180.to_rad, Vector[1,0,1].normalized)
    assert_equal_float 0, ent.pos[0], 1e-10
    assert_equal_float 0, ent.pos[1], 1e-10
    assert_equal_float 1, ent.pos[2], 1e-10
    ent = Point.new [0,0,1]
    ent.r!(180.to_rad, Vector[0,1,1].normalized)
    assert_equal_float 0, ent.pos[0], 1e-10
    assert_equal_float 1, ent.pos[1], 1e-10
    assert_equal_float 0, ent.pos[2], 1e-10
    ent = Point.new [1,0,0]
    ent.r!(90.to_rad, Vector[0,1,0].normalized)
    assert_equal_float 0, ent.pos[0], 1e-10
    assert_equal_float 0, ent.pos[1], 1e-10
    assert_equal_float -1, ent.pos[2], 1e-10
  end    
Nota che (almeno per questo test) non e` necessario normalizzare i
vettori.
Massimiliano