Este también es el resultado de varias investigaciones...
Líneas y hue en profundidad.
No es algo terminado sino también un escalón en donde me paro para alcanzar el próximo.
Reemplacé los puntos por círculos de diferentes radios y colores según el valor de profundidad.
También agregué líneas verticales.
Estoy trabajando en activar y desactivar zonas (cubos) para que al "entrar" en ellas disparen sonidos. WORK IN PROGRESS...
Para determinar el ancho de la profundidad que quiero que kinect lea utilizo
kinect.getDistanceAt(x, y): este es el valor de z
Esta función calcula la profundidad del pixel x, y
Si bien las distancias son aproximadas podemos decir que 1000 corresponde a 1mt.
En este ejemplo acoté la lectura de profundidad entre 1000 y 1500.
Estos ejemplos de kinect siempre utilizan el ofMesh.
"Mesh" significa malla, esto permite generar una malla y vincular de forma simple los vértices (ofVertex) que la componen.
En este caso uní con en líneas horizontales los vértices que kinect detecta.
En este caso estoy "pintando" los pixeles en Z con un archivo .png
Cada pixel de este archivo reemplada a los colores de los pixeles de la webcam.
Los píxeles alfa se mantienen transparentes... interesante!
No debemos olvidar habilitar la función del canal alfa en el setup del testApp
ofEnableAlphaBlending();
Aquí tengo limitada la "visión" de la cámara infraroja de profundidad.
La aplicación solo lee entre 0.5mts y 2,3mts. Estos valores pueden modificarse según la necesidad.
Para construir la imagen final toma el color del pixel de la webcam y lo imprime en Z según la profundidad del mismo pixel correspondiente a la cámara infraroja.
ofEasyCam ayuda a completar la ilusión del 3D virtual
Aquí uní tres proyectos...
Los círculos y el sistema de partículas en 3D son agitados y generados por la interactividad en la amplitud del sonido através del micrófono en tiempo real.
La rotación de los ejes están programadas y también pueden alterarse con el mouse (ofEasyCam).
Nota: el rendimiento no es bueno porque estoy capturando las imágenes en la misma computadora que corre la aplicación.
Si pude dibujar un línea en el plano y disponemos también del eje Z...
¿por qué no hacerla un círculo en Z y en Y?
for (float i = 0; i < ((pi*2)*10); i+= 1.04){
coseno=cos(i);
seno=sin(i);
ofPoint setVertex(coseno * radio, y -left[i]*180.0f, seno * radio);
ofVertex( setVertex );
}
((pi*2: para completar la circunferencia)*10): para que de 10 vueltas
i+= 1.04: 1.04 es el resultado de 2pi/6 (para hacer el hexágono)
2pi/5 (para el pentágono)
2pi/4 (para el cuadrado)
2pi/3 (para el triángulo)
ofPoint: aplico los valores en x, y, z
left[i]: es el vector en donde voy guardando el audio que entra por el micrófono
No tiene mucha diferencia con la entrada anterior, sólo que la línea está multiplicada en y dentro de un for salteando de a 50.
También tiene ofEasyCam para hacer el movimiento de cámara.
int cruz = 30; // largo de la mitad de los lados de la cruz
int x = ofGetWidth()/2;
int y = ofGetHeight()/2;
ofSetLineWidth(1);
ofSetColor(255, 0, 0); // color de la línea en x
ofLine(x - cruz, y, 0, x + cruz, y, 0); // línea en x
ofSetColor(0, 255, 0); // color de la línea en y
ofLine(x, y - cruz, 0, x, y + cruz, 0); // línea en y
ofSetColor(0, 0, 255); // color de la línea en z
ofLine(x, y, - cruz, x, y, cruz); // línea en z
agregar los valores en z
p.pos.z = 0; // z del origen de la instancia en 0
p.vel.z = ofRandom(-1.0, 1.0); // velocidad en z
para dibujar necesitamos una esfera y no un círculo
ofSphere(pos.x, pos.y, pos.z, radio);
void SistemadeParticula :: addParticula{
Particula p; // Particula es el objeto; p es la instancia del objeto
p.pos.x = ofGetMouseX(); // x del origen de la instancia en el x del mouse
p.pos.y = ofGetMouseY(); // y del origen de la instancia en el y del mouse
p.vel.y = ofRandom(-0.2, -2.0); // velocidad en x
p.vel.x = ofRandom(-2.0, 2.0); // velocidad en y
p.radio = ofRandom(1.0, 15.0); // radio entre 1 y 15
p.vida = 100.0; // valor alfa de la partícula, se hace más transparente
p.col.setHsb(ofRandom(0, 255), 255, 255, 100); // color pleno hue, saturación, brillo, alfa
}
Las ecuaciones de física son como los ejemplos anteriores pero sin los rebotes en y ni en x.
Solo hay que agregar que vaya disminuyendo el valor alfa:
vida -= 1;
Particula :: draw {
ofFill(); // con relleno
ofSetColor(col, vida); // el color combinado Hsb, vida
ofCircle(pos.x ,pos.y, radio); // el dibujo de la instancia en la pantalla
}
Aquí llegué más o menos donde quería... aunque aún queda por resolver que cada instancia de Pelota se sean una con otra y reboten entre ellas.
Todo lo que utilicé aquí está explicado en los blogs anteriores.
3D: 50 instancias de Pelota, 5 instancias de Pared, 1 luz ambiente al centro del cubo.
Este es el mismo ejemplo pero con 50 ball de Pelota.
También en wall de Pared, cada una de ellas cambia el color de cada ball y reproduce un sonido cuando ball rebota en wall.
Después de nuestra clase Pelotas, también armé una clase Pared.
En este caso el piso y las 4 paredes son una clase, todas son límite de las balls (cada instancia de Pelota) pero lo diferente es que cuando una ball toca una pared, esta cambia de color. Los colores están en las instancias (wall) de Pared; y cuando una ball toca a una wall, ball toma el color que le dio wall.
Aquí tampoco están corregidos los problemas y ajustes del rebote. WORK IN PROGRESS...
Bienvenidos al 3D!
Para este ejemplo solo debemos agregar el eje z en todas las coordenadas que tengamos x, y.
Gracias a oF los ofPoint están preparados para esto porque guardan x, y, z. También los ofPoint se ocupan de calcular todo lo que necesitemos.
Un detalle: a la hora de dibujar un ofCircle deberíamos reemplazarlo por ofSphere(x, y, z, radio);
Si estamos trabajando con los 3 ejes y no lo consideramos, el círculo se dibujará en z = 0.
testApp.h
ofEasyCam cam; // la variable cam está formateada como una ofEasyCam
testApp.cpp
::draw{
cam.begin(); // comienza EasyCam
ofPushMatrix(); ofTranslate(x, y, z); // voy a necesitar trasladar para encuadrar la cam con el dibujo ofRotateX(180); // voy a necesitar rotar 180° en X para que la cam no vea patas para arriba
aquí lo que quiera dibujar
ofPopMatrix();
cam.end(); // cierro EasyCam
Trabajar con class es un poco molesto, pero necesario... después de entenderlo uno comienza a disfrutarlo.
Voy a crear una clase Pelota. Iré paso a paso.
-- testApp.h
#define CANTIDAD 10 // cantidad de instancias de la classe
class testApp : public ofBaseApp{
Pelota ball[CANTIDAD]; // la clase Pelota tiene 10 instancias que se llaman ball
}
-- testApp.cpp
testApp.cpp :: update
for (int i = 0; i < CANTIDAD; i++){
ball[i].calcularFisica(); // desde aquí calculo la física en Pelota para cada una de las instancias
}
testApp :: draw(){
for (int i = 0; i < CANTIDAD ; i++){ ball[i].dibujar(); // desde aquí dibujo en Pelota cada una de las instancias }
-- pelota.h
#ifndef PELOTA_H #define PELOTA_H #include "ofMain.h"
class Pelota{ public: Pelota();
void dibujar(); // defino la función dibujar en Pelota
ofPoint pos; // defino el ofPoint pos para la posición de Pelota (pos.x, pos.y) ofPoint vel; // defino el ofPoint vel para la velocidad de Pelota (vel.x, vel.y) ofPoint acc; // defino el ofPoint acc para la aceleración de Pelota (acc.x, acc.y)
ofColor color; // defino el ofColor color para el colorde Pelota
int radio; // defino radio de Pelota que luego será el radio de cada una de las instancias
};
-- pelota.cpp
#include "Pelota.h"
Pelota::Pelota(){ // acá se dan los valores por default. para cada instancia los valores rnd varían color.set(ofRandom(100, 255), ofRandom(100, 255), ofRandom(100, 255)); radio = ofRandom(1, 60); pos.set(ofRandom(450, 650),ofRandom(50, 300)); // posición en x, y acc.set(0, 0); // aceleración vel.set(ofRandom(-10, 10), ofRandom(-10, 10)); // velocidad en x, y
}
void Pelota::calcularFisica(){ // esto es lo interesante de hacer ecuaciones con los ofPoint vel += acc; // vel.x = vel.x + acc.x; vel.y = vel.y + acc.y; vel *= 0.98; // vel.y = vel.y * 0.98; vel.y = vel.y *0.98; pos += vel; // pos.x = pos.x + vel.x; pos.y = pos.y + vel.y; acc *= 0; // acc.x = acc.x * 0; acc.y = acc.y * 0; }
void Pelota::dibujar(){
ofFill(); // relleno ofSetColor(color); // seteo el color ofCircle(pos.x, pos.y, radio); // dibujo un círculo en x, y con radio
ofSetLineWidth(1); // ancho de línea 1
ofNoFill(); // sin releno
ofSetColor(0, 0, 0); // color negro ofCircle(pos.x, pos.y, radio); // solo va a dibujar el contorno del círculo
}
no están desarrollados los rebotes porque ya están en videos anteriores
ofSoundPlayer sonido[3]; // hago un array en sonido con 3 posiciones
sonido[0].loadSound("01.mp3"); // los cargo en su lugar sonido[1].loadSound("02.mp3"); sonido[2].loadSound("03.mp3");
if (pos.x <= 100+radio){ // pared izquierda vel.x *= -1; // que cambie de velocidad para el otro lado sonido[1].play(); // que suene el rebote en la pared }
if (pos.x >= 1000-radio){ // pared derecha vel.x *= -1; sonido[2].play(); }
Estos son los cálculos de física que empleamos para que los objetos imiten el desplazamiento y la gravedad en nuestro mundo programado.
velocidad = velocidad + aceleracion; velocidad = velocidad * 0.98; // 0.9998 mas lento (como se ve en el ejemplo) posicion = posición + velocidad; aceleración = aceleración * 0;
Para el rebote, cuando la posición en Y llega hasta lo que considero piso (en mi caso 500px):
if (posicion.y >= 500){
// también hay que considerar el tamaño del radio porque hay que sumarlo a y del círculos velocidad.y *= -1; // multiplico por -1 la velocidad para que cambie de dirección }
Lo mismo para las paredes en X
Como dejé claro en el video, debo corregir el drag. Mi intención es hacer que la pelota tome la velocidad de impulso que pueda darle el mouse. WORK IN PROGRESS!!!
La librería ofxOpenCv es muy compleja. Aquí sólo estoy utilizando una pequeña parte de ella para el blob.
Es secreto aquí es capturar el "fondo", guardar esta imagen y restarla con la imagen que proviene desde la cámara. Esto es lo que se ve en las 3 formas de mostrar la cámara en la parte superior de la pantalla.
Una vez que tengo el blob, utilizo una función que calcula la posición del centro del blob en x, y. Después de esto utilizo esa posición para mostrar el círculo.
imgCvGrayScale = imgCv; // Convierto la imagen de la webcam en escala de grises
imgCvGrayScaleBckGnd = imgCvGrayScale; // Guardo esta imagen para tener el "fondo".
imgCvGrayScale.absDiff(imgCvGrayScaleBckGnd); // resto las imágenes
contour.findContours(imgCvGrayScale, 50, (ofGetWidth()*ofGetWidth())/2, 1, false);
// en el resultado de la resta busco: area mín del blob, area máx de blob, cant de blobs, sin agujeros.
int nuevoX = contour.blobs[0].centroid.x // este es el x del centro del blob 0
int nuevoY = contour.blobs[0].centroid.y // este es el y del centro del blob 0
apartir de estas coordenadas dibujo lo que quiero:
ofCircle(nuevoX, nuevoY, 20);
ofVideoGrabber video; // para asignarle la webcam
ofImage imagen; // esta será la espajada de la derecha
ofImage imagenBlNe; // esta es en escala de grises
ofImage imagenRojo; // solo en R
video.initGrabber(320, 240); // pongo dentro de video lo que toma la webcam en 320x240
imagen.allocate(320, 240, OF_IMAGE_COLOR); // guardo en imagen imagenes de 320x240 con RGB
imagenBlNe.allocate(320, 240, OF_IMAGE_COLOR);
imagenRojo.allocate(320, 240, OF_IMAGE_COLOR);
video.update(); // fundamental refrescar la entrada de video
para recorrer la imagen que vienen de la webcam que está guardada en un vector
for (int y = 0; y < 240; y++){
for (int x = 0; x < 320; x++){
for (int rgb = 0; rgb < 3; rgb++){ // esto es fundamental si trabajo con RGB
int i = y * 320 + x; // con i recorro cada lugar del vector
int blNg = (i * 3);
pixelOutBlNe[blNg+rgb] = pixelIn[blNg]; // esto es sólo para la imagen en grises
}
}
}
ofSoundPlayer sonido01, sonido02, sonido03 // ... hasta 07
sonido01.loadSound("01.wav"); sonido02.loadSound("02.wav"); sonido03.loadSound("03.wav"); // ... hasta 07
para saber qué fader estoy tocando busco la posición de ofGetMouseX();
para la posición de Y utilizo lo siguiente:
valory = (ofMap(ofGetMouseY(), 500, 100, 0, 255)); // para el color del fader
volumen02 = ofMap(ofGetMouseY(), 500, 100, 0, 1); // para el volumen de la pista
ofSoundPlayer sonido;
sonido.loadSound("meloadvertiste.mp3");
paneo = ofMap(ofGetMouseX(), 0, 1024, -1, 1);
// escalo el ancho de la pantalla para los valores de paneo (-1 a 1). -1 L; 0 C; 1 R.
volumen = ofMap(ofGetMouseY(), 0, 600, 1, 0);
// lo mismo con el volumen pero de 0 a 1 con el alto de la pantalla.
sonido.setVolume(volumen);
sonido.setPan(paneo);
ofImage
ofImage alfa;
ofEnableAlphaBlending(); // habitilo el canal alfa
alfa.loadImage("alfa.png");
ofEnableBlendMode(OF_BLENDMODE_SUBTRACT);
alfa.draw(ofGetMouseX()-45, ofGetMouseY-45);
ofDisableBlendMode();
ofSetColor(colx,0,coly); // colx, coly cambian de valor según la posición del mouse en x
ofCircle(ofGetMouseX()-45, ofGetMouseY,30,30);
float distancia = ofDist(posX, posY, ofGetMouseX(), ofGetMouseY());
if(distancia < radio){ // radio del circulo que está dibujado pincha = true; difx = x - posX; dify = y - posY; }