PDA

View Full Version : [C/C++/Java] Codice per curve B-Spline e Bezier


di@!
01-06-2007, 17:59
Ciao, per un progetto all'uni ho bisogno di implementare una curva B-Spline e di Bezier mediante un qualsiasi linguaggio tra C/C++/Java. Abbiamo già calcolato tutti i punti per i quali passa la curva, il problema ora è che non abbiamo mai fatto prima d'ora roba grafica all'uni, e quindi siamo totalmente fermi. In giro ho trovato codice per eventuali curve ma al momento della compilazione mancano sempre dei file da includere e, visto che siamo totalmente ignoranti in materia, di un codice senza i sorgenti/librerie non ce ne facciamo praticamente nulla...qualcuno sa dove trovare (o possiede) un codice senza questo tipo di problemi (ovvero compilabile) che può aiutarci a disegnare ste benedette curve?

In un thread precedente un utente ha segnalato di andare a vedere delel librerie API di non so cosa linkando 2 pagine e basta...ecco, questo è un esempio di risposta che non c'aiuta, perchè non sappiamo cosa farci :) Se qualcuno può aiutarci magari con un codice, può spiegare anche come funziona? Se è necessario installare pacchetti di librerie aggiuntive ecc?

Grazie :)

PGI-Bis
01-06-2007, 19:31
Se avete già la funzione e vi manca solo l'ultimo passo, cioè qualcosa che effettivamente disegni dei punti sullo schermo, allora una soluzione in Java potrebbe essere:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

public class Main {
public static void main(String[] args) {
/** Questo crea e lancia la finestra principale ed è necessario
per questioni di concorrenza del framework Swing */
SwingUtilities.invokeLater(new Runnable() { public void run() {
new Main().showWindow();
}});
}

/* Questa è la tela su cui si disegna */
private final JCanvas CANVAS = new JCanvas(512, 512);

/** Questo metodo crea l'interfaccia utente e la apre sullo schermo */
private void showWindow() {
//Crea un pulsante
JButton createCurveButton = new JButton("Crea curva");
//Aggiunge al pulsante un componente che cattura l'interazione utente
createCurveButton.addActionListener(createCurveListener);
//Crea un contenitore per il pulsante
JPanel buttonPanel = new JPanel();
//Aggiunge il pulsante al contenitore
buttonPanel.add(createCurveButton);
//Crea una finestra
JFrame window = new JFrame("Titolo");
//Quando l'utente chiude la finestra la finestra viene "distrutta"
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//Aggiunge il componente-canvas alla finestra
window.add(CANVAS);
//Aggiunge il contenitore dei pulsanti alla finestra, a "SUD"
window.add(buttonPanel, BorderLayout.SOUTH);
//La finestra non è ridimensionabile
window.setResizable(false);
//La finestra assume una dimensione minima sufficiente a visualizzare
//tutto il suo contenuto
window.pack();
//Proietta la finestra sullo schermo
window.setVisible(true);
}

//Il metodo actionPerformed di questo oggetto ActionListener viene invocato
//quando il pulsante creato nel metodo showWindow "rileva" che l'utente
//l'ha premuto (col mouse, con la tastiera eccetera)
private ActionListener createCurveListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
//rinvia per maggiore chiarezza espositiva al metodo createCurve
createCurve();
}
};

//Questo metodo è invocato dal metodo actionPerformed dell'ActionListener
//eccetera eccetera. Qui dentro ci potrebbe andare il metodo che crea
//la curva
private void createCurve() {
CANVAS.clear();
for(int x = 0, y = 0; x < 100; x++, y++) {
CANVAS.drawPixel(x, y, Color.PINK);
}
for(int x = 100, y = 100; x < 200; x++) {
CANVAS.drawPixel(x, y, Color.RED);
}
CANVAS.flush();
}
}

//Dichiara un insieme di capacità necessarie e sufficienti al disegno "per pixel"
interface PixelPainter {
/** Cancella tutto quello che è stato disegnato in precedenza */
void clear();

/** Colora il pixel di coordinate x, y */
void drawPixel(int x, int y, Color c);

/** Aggiorna lo stato del PixelPainter: i pixel disegnati con il metodo
drawPixel appaiono sicuramente sullo schermo dopo l'invocazione di questo
metodo. */
void flush();
}

/** Un componente swing che è anche un PixelPainter */
class JCanvas extends JPanel implements PixelPainter {
private BufferedImage buffer;
private int width;
private int height;

public JCanvas(int width, int height) {
this.width = width;
this.height = height;
}

public void addNotify() {
super.addNotify();
createBuffer();
}

public Dimension getPreferredSize() {
return new Dimension(width, height);
}

public void clear() {
Graphics2D graphics = buffer.createGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
graphics.dispose();
}

public void drawPixel(int x, int y, Color c) {
if(x >= 0 && x < width && y >= 0 && y < height) {
buffer.setRGB(x, y, c.getRGB());
}
}

public void flush() {
repaint();
}

protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(buffer != null) {
g.drawImage(buffer, 0, 0, null);
}
}

private void createBuffer() {
GraphicsConfiguration cfg = getGraphicsConfiguration();
buffer = cfg.createCompatibleImage(width, height, Transparency.OPAQUE);
clear();
}
}

Tutto il codice qui sopra è destinato ad una singola unità di compilazione. Ci copi dentro il malloppo, lo chiami Main.java, una compilata (javac Main.java) e via (java Main).

Il succo è nel metodo "createCurve". Li si prende il CANVAS, che è un PixelDrawer e quindi fa tre cose:

clear() cancella tutto quanto
drawPixel(x, y, java.awt.Color) colora il pixel di coordinate x, y (origine in alto a sinistra sullo schermo)
flush() si assicura che tutti i pixel appena colorati appaiano sullo schermo.

E' una cosa del genere che state cercando? :fagiano:

di@!
02-06-2007, 10:49
si è esattamente questo, ti ringrazio...ora la proviamo e ti faccio sapere al più presto

di@!
02-06-2007, 11:40
il tuo programma compila, ma mi da un errore in fase di esecuzione:

Exception in thread "main" java.lang.NoClassDefFoundError: Main

cosa può essere?

cmq se hai msn e puoi addarmi lì facciamo prima :)

vash_david@hotmail.com

edit: mi sa che devo impostare la classpath

PGI-Bis
02-06-2007, 11:47
Non occorre impostare il classpath. Controlla i nomi. Il file sorgente è Main.java con la M maiuscola?

Ps.: non uso msn, icq e altre stregonerie di quel tipo :D:

di@!
02-06-2007, 11:54
si ho digitato tutto correttamente

ho cercato in giro e quell'errore dicono che sia dovuto o alla sintassi sbagliata (come hai detto te, ma non è quello) oppure ad una sbagliata impostazione del classpath sotto le variabili d'ambiente (e io nn ce l'ho proprio impostato per java)

PGI-Bis
02-06-2007, 11:59
Controlla che la cartella di lavoro (quella da cui lanci il comando java) sia la stessa in cui si trovano i file .class generati dalla compilazione con javac.

Se è già così allora prova a lanciare il programma con:

java -cp . Main

di@!
02-06-2007, 12:08
Se è già così allora prova a lanciare il programma con:

java -cp . Main

Così funziona, grande :)

Come mai, cosa fa quel comando?

PGI-Bis
02-06-2007, 12:16
-cp, che equivale a -classpath, è un'opzione del comando java che serve a specificare da dove saranno prelevati i file necessari all'esecuzione del programma. Il punto (.) significa "la cartella di lavoro" ed è quella da cui è lanciato il programma java.exe.

Il problema è che la cartella di lavoro dovrebbe essere già inclusa tra quelle che il programma java.exe visiona al fine di trovare i file che gli servono.

Prova anche a controllare se per caso tu non abbia tra le variabili d'ambiente una variabile di nome CLASSPATH. Se c'è, fulminala.

Altrimenti dovresti provare a reinstallre la piattaforma java e vedere se l'intoppo si risolve.

di@!
02-06-2007, 12:27
-cp, che equivale a -classpath, è un'opzione del comando java che serve a specificare da dove saranno prelevati i file necessari all'esecuzione del programma. Il punto (.) significa "la cartella di lavoro" ed è quella da cui è lanciato il programma java.exe.

Il problema è che la cartella di lavoro dovrebbe essere già inclusa tra quelle che il programma java.exe visiona al fine di trovare i file che gli servono.

Prova anche a controllare se per caso tu non abbia tra le variabili d'ambiente una variabile di nome CLASSPATH. Se c'è, fulminala.

Altrimenti dovresti provare a reinstallre la piattaforma java e vedere se l'intoppo si risolve.

si ce l'ho classpath tra le variabili d'ambiente, ma c'ho altra roba lì che mi serve (ad esempio per il server tomcat) e nn posso fulminarla :)

senti un'ultima cosa...con questa funzione che mi hai passato, basta che modifico i punti dove passa la curva? nel nostro progetto abbiamo già trovato i punti della curva di bezier e b-spline, dunque nn c'è un codice per disegnare una curva b-spline o di bezier ma c'è un codice generale per disegnare una curva, e quello che cambia sono solo i punti trovati con gli algoritmi di bezier e b-spline? (nn so se mi sono spiegato)

PGI-Bis
02-06-2007, 12:51
Là dentro c'è solo una cosa che permette di colorare dei pixel. La pressione del pulsante causa l'esecuzione del contenuto del metodo drawCurve.

Cosa disegni dipende da te. Ad esempio, supponiamo di voler disegnare un cerchio (con un algoritmo non DDA). Prendi il metodo createCurve lo svuoti e ci scrivi dentro:

CANVAS.clear();
double ray = 100;
double xc = 200;
double yc = 200;
double da = 0.0001;
double a = 0;
while(a < 2 * Math.PI) {
double x = Math.cos(a) * ray + xc;
double y = Math.sin(a) * ray + yc;
int xi = (int)Math.round(x);
int yi = (int)Math.round(y);
a += da;
CANVAS.drawPixel(xi, yi, Color.BLACK);
}
CANVAS.flush();

Ricompili ed esegui. A questo punto quando si preme il pulsante il programma disegna un cerchio di centro (200,200) e raggio 100.

Usando JavaScript si potrebbe evitare questa faccenda del ricompilare ogni volta ma non vorrei complicarvi la vita (anche se sarebbe una sorta di complicazione volta a semplificare).

Se dovete disegnare la curva a partire dai punti controllo e volete che questi punti di controllo siano immessi dall'utente allora anzichè il pulsante "crea curva" vi serve un sistema di gestione dell'interazione un pelo diverso (e un pelo più complicato). Comunque io inizierei prima acquisendo dimestichezza con il disegno "programmatico". Come nell'esempio su riportato, fissate i punti di controllo nel metodo creaCurva, applicate l'algoritmo che avete fatto e vedete cosa salta fuori. Poi si può passare a dire "ok, ora voglio che i punti di controllo siano determinati dall'utente".

Oppure non ho capito niente :D.

di@!
02-06-2007, 13:38
no hai capito bene :)

noi abbiamo i punti di controllo in una matrice per la curva di bezier, e in un'altra matrice per la curva b-spline...quindi niente da inserire dall'utente, ma solo una matrice da scorrere

di@!
02-06-2007, 15:19
una cosa...noi abbiamo ora il codice per disegnare una retta e un cerchio...ma se ho i punti di controllo di una curva, come la disegnamo?

nel senso che i nostri 2 algoritmi c'hanno permesso di trovare i punti per cui passano le 2 curve di bezier e b-spline, ma non di disegnare la curva...una volta che abbiamo i punti, se li mettiamo per esempio nel tuo codice originale esce una retta, capito? :)

di@!
02-06-2007, 15:58
Ho fatto un po' di prove con il metodo createCurve...questa funzione praticamente "colora" il pixel alle coordinate x,y, quindi facendo un ciclo for da 1 a 100 ovviamente colora tutti i pixel uno accanto all'altro disegnando in questo caso una retta. Ma se io ho SOLO alcuni punti per i quali passa la curva, mettiamo caso una decina di punti parecchio distanti tra loro, come disegno l'intera curva che passa tra quei punti? Se uso la funzione CANVAS.drawPixel per ogni punto, alla fine vengono fuori ovviamente solo alcuni punti segnati qua e la, ma non un'intera curva che passa per quei punti...capito che voglio dire?

PGI-Bis
02-06-2007, 16:15
Pensavo che aveste l'algoritmo per il disegno di curve. Soluzione B. Nuovo Main.java

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.script.*;
import java.io.*;

public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() { public void run() {
new Main().showWindow();
}});
}

private final JCanvas CANVAS = new JCanvas(512, 512);

private void showWindow() {
Action action = new AbstractAction("Crea curva") {
public void actionPerformed(ActionEvent e) { createCurve(); }
};
JPanel buttonPanel = new JPanel();
buttonPanel.add(new JButton(action));
JFrame window = new JFrame("Titolo");
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
window.add(CANVAS);
window.add(buttonPanel, BorderLayout.SOUTH);
window.setResizable(false);
window.pack();
window.setVisible(true);
}

private void createCurve() {
java.io.Reader reader = null;
try {
ScriptEngine engine = new ScriptEngineManager().getEngineByName(
"JavaScript");
InputStream in = getClass().getResourceAsStream("/drawcurve.js");
engine.put("CANVAS", CANVAS);
reader = new java.io.InputStreamReader(in);
engine.eval(reader);
Invocable inv = (javax.script.Invocable)engine;
inv.invokeFunction("drawCurve");
} catch(Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(CANVAS, "Errore nello script");
} finally {
try { if(reader != null) { reader.close(); }} catch (Exception ex) {}
}
}
}

class JCanvas extends JPanel {
private BufferedImage buffer;
private int width;
private int height;

public JCanvas(int width, int height) {
this.width = width;
this.height = height;
}

public void addNotify() {
super.addNotify();
createBuffer();
}

public Dimension getPreferredSize() {
return new Dimension(width, height);
}

public void clear() {
Graphics2D graphics = buffer.createGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
graphics.dispose();
}

public void drawPixel(int x, int y, Color c) {
if(x >= 0 && x < width && y >= 0 && y < height) {
buffer.setRGB(x, y, c.getRGB());
}
}

public Graphics2D getBufferGraphics() {
return buffer.createGraphics();
}

public void flush() {
repaint();
}

protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(buffer != null) {
g.drawImage(buffer, 0, 0, null);
}
}

private void createBuffer() {
GraphicsConfiguration cfg = getGraphicsConfiguration();
buffer = cfg.createCompatibleImage(width, height, Transparency.OPAQUE);
clear();
}
}

Salva, compila ed esegui. Nella cartella del Main, crea un file "drawcurve.js". Dentro a quel file crea una funzione JavaScript di nome "drawCurve". Ad esempio:

//drawcurve.js
function drawCurve() {
CANVAS.clear();
var graphics = CANVAS.getBufferGraphics();
var origin = new java.awt.Point(0, 0);
var control1 = new java.awt.Point(50, 100);
var control2 = new java.awt.Point(100, 50);
var destination = new java.awt.Point(200, 200);
var curve = new java.awt.geom.CubicCurve2D.Double(
origin.getX(), origin.getY(),
control1.getX(), control1.getY(),
control2.getX(), control2.getY(),
destination.getX(), destination.getY());
graphics.setPaint(java.awt.Color.BLACK);
graphics.draw(curve);
CANVAS.flush();
}

Salva il file e premi il pulsante "crea curva" sull'interfaccia. Si può usare anche GeneralPath per ottenere la stessa curva.

//drawcurve.js
function drawCurve() {
CANVAS.clear();
var graphics = CANVAS.getBufferGraphics();
var curve = new java.awt.geom.GeneralPath();
curve.moveTo(0, 0);
curve.curveTo(50, 100, 100, 50, 200, 200);
graphics.setPaint(java.awt.Color.BLACK);
graphics.draw(curve);
CANVAS.flush();
}

L'unico intoppo è che non so se queste siano B-Spline o curve di Bezièr.

Oppure dovete fare anche l'algoritmo per il disegno delle curve a partire dai punti di controllo?

di@!
02-06-2007, 16:51
L'unico intoppo è che non so se queste siano B-Spline o curve di Bezièr.

Io nn ho capito una cosa...c'è un algoritmo che disegna le curve di bezier e uno che disegna le b-spline? Io avevo capito che una volta trovati i punti di controllo per le b-spline e per bezier, poi un qualsiasi algoritmo che disegna una curva va bene, ma ovviamente la curva cambia a seconda che inseriamo i punti di controllo trovati per b-spline e per bezier.


Oppure dovete fare anche l'algoritmo per il disegno delle curve a partire dai punti di controllo?

Noi quello dobbiamo fare...nel senso che abbiamo i punti di controllo e basta, e dobbiamo disegnare la curva. In pratica cerchiamo un codice in cui basti inserire in qualche modo i punti di controllo, e ci disegna la curva. Ovviamente vengono fuori 2 curve diverse, a seconda che inseriamo i punti di controllo trovati con l'algoritmo di bezier e di b-spline.

PGI-Bis
02-06-2007, 17:06
Guarda io posso dirti come disegnare qualcosa in Java ma se mi chiedi la differenza tra una curva di Bezièr, una B-Spline e una porzione di panna cotta potrei dirti che l'ultima si mangia e le altre due non le ho mai provate.

Poi non so se le curve di Bèzier siano un tipo di B-Spline o viceversa, se sia possibile disegnare l'una usando l'algoritmo dell'altra e via dicendo.

Magari sì. In questo caso puoi provare a passare i punti di controllo ai metodi draw di GeneralPath e vedere cosa salta fuori.

di@!
02-06-2007, 17:30
Magari sì. In questo caso puoi provare a passare i punti di controllo ai metodi draw di GeneralPath e vedere cosa salta fuori.

Ok, perfetto...c'è solo un probl, mi da "package javax.script does not exist" in fase di compilazione...ho l'ultima versione di Java, dov'è il probl?

PGI-Bis
02-06-2007, 17:35
digita

javac -version

e premi invio.

Controlla che sia la versione 1.6 o superiore.

di@!
02-06-2007, 17:40
nada, 1.5.0_06, credevo di avere l'ultima del jdk

di@!
02-06-2007, 17:58
ok funge, ora provo a smanettare un po vediamo che viene fuori :)

di@!
02-06-2007, 18:06
ok, ti dico subito che quest'ultimo codice che c'hai dato è mostruoso, addirittura non c'è nemmeno bisogno di ricompilare ogni volta, basta che cambi i valori in general path e aggiorna la curva al volo...potrebbe essere esattamente quello che cercavamo, volevo chiederti una cosa però

noi abbiamo i punti di controllo su coordinate x,y..e su generalpath ho la funzioni

curve.moveTo(0, 0); (da dove la faccio partire, coordinate x e y)
curve.curveTo(100, 200, 300, 400, 500, 600); (passa per i punti xxx, ma cmq questa funzione accetta sempre e solo 6 punti, se io ne ho di meno o di più?)

me le spieghi un secondo? se io ho i punti di controllo come coordinate x,y come le inserisco in queste funzioni?

update: ok mi son letto un po di cose qua e la...generalpath è usato per disegnare una curva di bezier, ma con 3 coordinate, mentre noi ne abbiamo 4...suppongo non si possa ampliare il numero di punti di controllo utilizzabili

PGI-Bis
02-06-2007, 19:47
Puoi tentare la manovra del maiale :oink:

Grufoli in rete cercando un algoritmo che faccia al caso tuo e lo copi bellamente: è una porcata (da cui il nome della mossa) colossale ma... si fa :D.

Ad esempio in rete

http://www.ibiblio.org/e-notes/Splines/Basis.htm

ho trovato un codice francamente illeggibile ma che tradotto in JavaScript x Java diventa:

//drawcurve.js
function drawCurve() {
CANVAS.clear();
graphics = CANVAS.getBufferGraphics();
graphics.setColor(java.awt.Color.BLACK);
var w = 512;
var h = 512;
var w2 = w;
var h1 = h - 1;
var n = 4; //numero di punti
var k = 3; //ordine
var n1 = n + 1;
var nt = n + k + 1;
/* Coordinate dei punti di controllo (origine in basso a sinistra,
x cresce verso destra, y cresce verso l'alto */
var Px = new Array(100.0, 400.0, 400.0, 300.0, 200.0);
var Py = new Array(50.0, 50.0, 400.0, 300.0, 400.0);

var ti = new Array(nt + k);
for(var i = 0; i < nt + k; i++) {
if(i < nt) {
ti[i] = i;
} else {
ti[i] = 0;
}
}
var N = new Array(nt + 1);
for(var i = 0; i < nt + 1; i++) {
N[i] = new Array(w2);
}

var to = ti[0];

var dt = ti[nt-1]-to;

for(var i = 0; i < nt; i++) {
ti[i] = w2 + w2 * (ti[i] - to) / dt;
}

// DRAW FUN
var step = (ti[nt - 1] - ti[0]) / (w2 - .9);

var t = ti[0];

var Tmin = java.lang.Math.round(((ti[k - 1] - ti[0]) / step) + 1);
var Tmax = java.lang.Math.round(((ti[n1] - ti[0]) / step));
var i1 = 0;

for(var l = 0; l < w2; l++) {
while(t >= ti[i1]) {
i1++;
}

var i = i1 - 1;

for(var j = 0; j < nt; j++) {
N[j][l] = 0;
}

N[i][l] = 1;

for(var m = 2; m <= k; m++) {
var jb = i - m + 1;
if(jb < 0) {
jb = 0;
}
for(var j = jb; j <= i; j++) {
N[j][l] = N[j][l]*(t - ti[j]) / (ti[j + m - 1] - ti[j])
+ N[j + 1][l] * (ti[j + m] - t) / (ti[j + m] - ti[j+1]);
}
}

t += step;
}
var X, Y;

for(var i = 0; i < n1; i++) {
X = Px[i];
Y = h1 - Py[i];
graphics.drawRect(X - 1, Y - 1, 3, 3);
var label = "C" + java.lang.Math.round(i);
graphics.drawString(label, X - 1, Y - 1);
}
var sX = 0.0;
var sY = 0.0;
for(var j = 0; j < n1; j++) {
sX += Px[j]*N[j][Tmin];
sY += Py[j]*N[j][Tmin];
}
var Xold = sX;
var Yold = h1 - sY;
for(k = Tmin + 1; k <= Tmax; k++) {
sX = 0;
sY = 0;
for(var j = 0; j < n1; j++) {
sX += Px[j]*N[j][k];
sY += Py[j]*N[j][k];
}
X = sX;
Y = h1-sY;
if((X < w2) && (Xold < w2)) {
graphics.drawLine(Xold, Yold, X, Y);
Xold = X;
Yold = Y;
}
}
CANVAS.flush();
}

In grassetto ci sono le parti che dovresti provare a smanacciare. n è il numero di punti, Px e Py sono le coordinate (x e y) dei punti di controllo.

L'ho provato e funziona ma avendolo io suinamente tradotto non so assolutamente perchè faccia quel che fa.

di@!
03-06-2007, 02:06
ti ringrazio anche al nome del mio amico, sei stato gentilissimo :)

regola del maiale2win :oink: :cool: