First commit
127
BlobDetection.pde
Normal file
@ -0,0 +1,127 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
class BlobDetection {
|
||||
|
||||
PImage findConnectedComponents(PImage input, boolean onlyBiggest) {
|
||||
PImage result = input.copy();
|
||||
int [] labels = new int [result.width * result.height];
|
||||
|
||||
|
||||
// **********************************************************************
|
||||
// First pass: label the pixels and store labels' equivalences
|
||||
// **********************************************************************
|
||||
List<TreeSet<Integer>> labelsEquivalences = new ArrayList<TreeSet<Integer>>();
|
||||
ArrayList<Integer> meter = new ArrayList<Integer>();
|
||||
|
||||
int currLabel = 0;
|
||||
|
||||
// First loop - for height
|
||||
for (int y = 0; y < result.height; ++y) {
|
||||
TreeSet<Integer> colourAdjacent = new TreeSet<Integer>();
|
||||
// Second loop - for width
|
||||
for (int x = 0; x < result.width; ++x) {
|
||||
if (brightness(result.pixels[y*result.width+x]) == 255) {
|
||||
colourAdjacent.clear();
|
||||
for (int i = x-1; i <= x+1; i++) {
|
||||
// Checks first row
|
||||
if (y != 0) {
|
||||
if (0 <= i && i < result.width && labels[(y-1)*result.width+i] != 0) {
|
||||
colourAdjacent.add(labels[(y-1)*result.width+i]);
|
||||
}
|
||||
} else {
|
||||
// Do nothing since we are at the first line and we cannot consider the y-1 th line...
|
||||
}
|
||||
}
|
||||
|
||||
if (colourAdjacent.isEmpty()) {
|
||||
TreeSet tree_set = new TreeSet<Integer>();
|
||||
tree_set.add(++currLabel);
|
||||
labelsEquivalences.add(tree_set);
|
||||
meter.add(1);
|
||||
labels[y*result.width+x] = currLabel;
|
||||
} else {
|
||||
if (colourAdjacent.size()>1) {
|
||||
for (Integer i : colourAdjacent) {
|
||||
labelsEquivalences.get(i-1).addAll(colourAdjacent);
|
||||
}
|
||||
}
|
||||
int first = colourAdjacent.first();
|
||||
meter.set(first-1, meter.get(first-1)+1);
|
||||
labels[y*result.width+x] = first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// **********************************************************************
|
||||
// Second pass: re-label the pixels by their equivalent class
|
||||
// if onlyBiggest = = true, count the number of pixels for each label
|
||||
// **********************************************************************
|
||||
|
||||
// Merge all equivalence classes of labels
|
||||
for (int labEq = 0; labEq < labelsEquivalences.size(); ++labEq) {
|
||||
TreeSet<Integer> tree_set = labelsEquivalences.get(labEq);
|
||||
if (tree_set.size()>1) {
|
||||
TreeSet<Integer> acc = new TreeSet<Integer>();
|
||||
for (Integer i : tree_set) {
|
||||
TreeSet<Integer> other = labelsEquivalences.get(i-1);
|
||||
if (tree_set != other) {
|
||||
acc.addAll(other);
|
||||
}
|
||||
}
|
||||
tree_set.addAll(acc);
|
||||
for (Integer i : tree_set) {
|
||||
labelsEquivalences.set(i-1, tree_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate size of blob
|
||||
int[] blobSize = new int[labelsEquivalences.size()];
|
||||
for (int labEq = 0; labEq<labelsEquivalences.size(); ++labEq) {
|
||||
TreeSet<Integer> tree_set = labelsEquivalences.get(labEq);
|
||||
int total = 0;
|
||||
for (Integer i : tree_set) {
|
||||
total += meter.get(i-1);
|
||||
}
|
||||
blobSize[labEq] = total;
|
||||
}
|
||||
|
||||
|
||||
// **********************************************************************
|
||||
// Finally:
|
||||
// if onlyBiggest = = false, output an image with each blob colored in one uniform color
|
||||
// if onlyBiggest = = true, output an image with the biggest blob in white and others in black
|
||||
// **********************************************************************
|
||||
|
||||
int[] colorArray = new int[blobSize.length];
|
||||
if (onlyBiggest) {
|
||||
int maximum = -1;
|
||||
for (int i = 0; i < blobSize.length; i++) {
|
||||
maximum = max(maximum, blobSize[i]);
|
||||
}
|
||||
for (int i = 0; i < blobSize.length; i++) {
|
||||
colorArray[i] = (blobSize[i] == maximum) ? color(255) : color(0);
|
||||
}
|
||||
} else {
|
||||
for (TreeSet<Integer> tree_set : labelsEquivalences) {
|
||||
int randomColor = color(random(255), random(255), random(255));
|
||||
for (Integer i : tree_set) {
|
||||
colorArray[i-1] = randomColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the map with color according to their label
|
||||
for (int i = 0; i < result.width*result.height; ++i) {
|
||||
if (labels[i] != 0) {
|
||||
result.pixels[i] = colorArray[labels[i]-1];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
70
Cylinder.pde
Normal file
@ -0,0 +1,70 @@
|
||||
final float cylinderBaseSize = 20;
|
||||
final float cylinderHeight = 50;
|
||||
final int cylinderResolution = 40;
|
||||
final color defaultCylinderColour = color(220, 60, 60);
|
||||
|
||||
class Cylinder {
|
||||
PShape closedCylinder = new PShape();
|
||||
PShape openCylinder = new PShape();
|
||||
PShape topDisk = new PShape();
|
||||
|
||||
//#############################################
|
||||
//-----------CONSTRUCTOR OF CYLINDER-----------
|
||||
//#############################################
|
||||
Cylinder(color cylinderColour) {
|
||||
|
||||
// Initialise the Cylinder
|
||||
closedCylinder = new PShape();
|
||||
openCylinder = new PShape();
|
||||
topDisk = new PShape();
|
||||
|
||||
float angle;
|
||||
float[] x = new float[cylinderResolution + 1];
|
||||
float[] z = new float[cylinderResolution + 1];
|
||||
|
||||
//Get the x and y position on a circle for all the sides
|
||||
for(int i = 0; i < x.length; i++) {
|
||||
angle = (TWO_PI / cylinderResolution) * i;
|
||||
x[i] = sin(angle) * cylinderBaseSize;
|
||||
z[i] = cos(angle) * cylinderBaseSize;
|
||||
}
|
||||
|
||||
//#############################################
|
||||
//-----------SHAPE OF OPEN CYLINDER------------
|
||||
//#############################################
|
||||
openCylinder = createShape();
|
||||
openCylinder.beginShape(QUAD_STRIP);
|
||||
openCylinder.fill(cylinderColour);
|
||||
|
||||
//Draw the border of the cylinder
|
||||
for(int i = 0; i < x.length; i++) {
|
||||
openCylinder.vertex(x[i] , 0, z[i]);
|
||||
openCylinder.vertex(x[i], -cylinderHeight, z[i]);
|
||||
}
|
||||
openCylinder.endShape();
|
||||
|
||||
//#############################################
|
||||
//-----------DISK OF CLOSED CYLINDER-----------
|
||||
//#############################################
|
||||
topDisk = createShape();
|
||||
topDisk.beginShape(TRIANGLE_FAN);
|
||||
topDisk.fill(cylinderColour);
|
||||
|
||||
for (int i = 0; i< x.length; i++) {
|
||||
topDisk.vertex(x[i], -cylinderHeight, z[i]);
|
||||
}
|
||||
topDisk.endShape();
|
||||
|
||||
// MERGE TOP DISK WITH OPEN CYLINDER
|
||||
closedCylinder = createShape(GROUP);
|
||||
closedCylinder.addChild(openCylinder);
|
||||
closedCylinder.addChild(topDisk);
|
||||
}
|
||||
|
||||
//#############################################
|
||||
//----------------DISPLAY METHOD---------------
|
||||
//#############################################
|
||||
void display() {
|
||||
gameSurface.shape(closedCylinder);
|
||||
}
|
||||
}
|
||||
140
DrawMethods.pde
Normal file
@ -0,0 +1,140 @@
|
||||
//#############################################
|
||||
//------------------DRAW GAME------------------
|
||||
//#############################################
|
||||
void drawGame() {
|
||||
gameSurface.beginDraw();
|
||||
basic_settings();
|
||||
drawBoard();
|
||||
drawSphere();
|
||||
drawParticleSystem();
|
||||
gameSurface.endDraw();
|
||||
}
|
||||
|
||||
|
||||
//#############################################
|
||||
//--------------DRAW SCORE_BOARD---------------
|
||||
//#############################################
|
||||
void drawScoreBoard() {
|
||||
scoreBoard.beginDraw();
|
||||
scoreBoard.background(scoreColor);
|
||||
scoreBoard.text("Total Score : ", 10, 25);
|
||||
scoreBoard.text(Float.toString(getPoints()), 20, 45);
|
||||
scoreBoard.text("Velocity : ", 10, 65);
|
||||
scoreBoard.text(Float.toString(playable_sphere.velocity.mag()), 20, 85);
|
||||
scoreBoard.text("Last score : ", 10, 105);
|
||||
scoreBoard.text(Float.toString(getLastPoints()), 20, 125);
|
||||
scoreBoard.endDraw();
|
||||
}
|
||||
|
||||
|
||||
//#############################################
|
||||
//----------------DRAW TOP_VEIW----------------
|
||||
//#############################################
|
||||
void drawTopView() {
|
||||
topView.beginDraw();
|
||||
topView.background(topViewBoard);
|
||||
|
||||
float ellipseX = (playable_sphere.location().x + boxLength/2) * square_for_score/boxLength;
|
||||
float ellipseY = (playable_sphere.location().z + boxLength/2) * square_for_score/boxLength;
|
||||
float ellipseWidth = 2*radius_sphere*square_for_score/boxLength;
|
||||
float ellipseHeight = ellipseWidth;
|
||||
topView.fill(colorSphereTopView);
|
||||
topView.ellipse(ellipseX, ellipseY, ellipseWidth, ellipseHeight);
|
||||
|
||||
if (particle_system!=null) {
|
||||
particle_system.particleTopView();
|
||||
}
|
||||
topView.endDraw();
|
||||
}
|
||||
|
||||
|
||||
//#############################################
|
||||
//---------------DRAW BAR_CHART----------------
|
||||
//#############################################
|
||||
void drawBarChart() {
|
||||
barChart.beginDraw();
|
||||
barChart.background(colorBarChart);
|
||||
barChart.rectMode(CORNER);
|
||||
barChart.fill(255);
|
||||
|
||||
score_scale = 0.5 + hs_scroll_bar.getPos();
|
||||
rectIndex = 0;
|
||||
|
||||
if (lastSecond != second() && !gamePaused) {
|
||||
scores.add(getPoints());
|
||||
}
|
||||
|
||||
// Display the chart
|
||||
for (Float score_index : scores) {
|
||||
float scaling = (Math.abs(score_index) > 2) ? 2*(log(Math.abs(score_index))) : score_index;
|
||||
|
||||
float dimRect = rectSize*score_scale;
|
||||
float coordX = rectIndex*rectSize*score_scale;
|
||||
|
||||
for (int col = 0; col <= Math.abs(scaling); ++col) {
|
||||
float coordY = col*rectSize*score_scale;
|
||||
if(score_index < 0) {
|
||||
barChart.rect(coordX, coordY + score_graphic/2, dimRect, dimRect);
|
||||
} else {
|
||||
barChart.rect(coordX, -coordY + score_graphic/2, dimRect, dimRect);
|
||||
}
|
||||
}
|
||||
rectIndex++;
|
||||
}
|
||||
lastSecond = second();
|
||||
barChart.endDraw();
|
||||
|
||||
}
|
||||
|
||||
//#############################################
|
||||
//----------AUX METHODS FOR DRAW_GAME----------
|
||||
//#############################################
|
||||
|
||||
//---------------BASIC SETTINGS----------------
|
||||
void basic_settings(){
|
||||
gameSurface.noStroke();
|
||||
gameSurface.directionalLight(directionalColor.x, directionalColor.y, directionalColor.z, 0, 1, 0);
|
||||
gameSurface.ambientLight(ambientColor, ambientColor, ambientColor);
|
||||
gameSurface.background(backgroundColor);
|
||||
}
|
||||
|
||||
//-------------SETUP OF THE BOARD--------------
|
||||
void drawBoard(){
|
||||
gameSurface.translate(width/2, height/2, -score_graphic);
|
||||
gameSurface.rotateX(thetaX);
|
||||
gameSurface.rotateY(thetaY);
|
||||
gameSurface.rotateZ(thetaZ);
|
||||
gameSurface.fill(boardColor);
|
||||
gameSurface.box(boxLength, boxThickness, boxLength);
|
||||
}
|
||||
|
||||
//------------SETUP OF THE SPHERE--------------
|
||||
void drawSphere(){
|
||||
if(!gamePaused) {
|
||||
playable_sphere.update();
|
||||
}
|
||||
playable_sphere.checkEdges();
|
||||
playable_sphere.display();
|
||||
}
|
||||
|
||||
//--------SETUP OF THE PARTICLE SYSTEM---------
|
||||
void drawParticleSystem(){
|
||||
for (int i = particle_system.particles.size(); i > 0; --i) {
|
||||
PVector position = particle_system.particles.get(i-1);
|
||||
gameSurface.pushMatrix();
|
||||
gameSurface.translate(position.x,0 , position.z ); //translate origin back to left upper corner
|
||||
cylinder.display();
|
||||
gameSurface.popMatrix();
|
||||
}
|
||||
|
||||
if(particle_system.origin != null) {
|
||||
gameSurface.pushMatrix();
|
||||
gameSurface.translate(particle_system.origin.x , -cylinderHeight , particle_system.origin.z ); //translate origin back to left upper corner
|
||||
gameSurface.shape(robotnik);
|
||||
gameSurface.popMatrix();
|
||||
}
|
||||
|
||||
if(!particle_system.particles.isEmpty() && !gamePaused) {
|
||||
particle_system.run();
|
||||
}
|
||||
}
|
||||
54
GameSettings.pde
Normal file
@ -0,0 +1,54 @@
|
||||
final int window_size_x = 800;
|
||||
final int window_size_y = 800;
|
||||
final float depth = -1000;
|
||||
final int score_graphic = 150;
|
||||
final int square_for_score = score_graphic-10;
|
||||
|
||||
// Colors drawGame
|
||||
final color backgroundColor = color(216, 223, 239);
|
||||
final color boardColor = color(78, 122, 207);
|
||||
// Color bottom of window
|
||||
final color bottomColor = color(162, 165, 200);
|
||||
// Color drawScore
|
||||
final color scoreColor = color(132, 60, 101);
|
||||
// Color drawTopView
|
||||
final color topViewBoard = color(56, 91, 185);
|
||||
final color colorSphereTopView = color(0, 0, 0);
|
||||
final color colorVillainTopView = color(150,20,20);
|
||||
// Color drawBarChart
|
||||
final color colorBarChart = color(125,128,190);
|
||||
final PVector directionalColor = new PVector(50, 100, 200);
|
||||
final float ambientColor = 200;
|
||||
|
||||
final PVector topViewBoardColor = new PVector(56, 91, 185);
|
||||
final int fps = 60;
|
||||
|
||||
final float boxLength = 500;
|
||||
final float boxThickness = 20;
|
||||
|
||||
final float gravityConstant = 0.1;
|
||||
final float normalForce = 1;
|
||||
final float mu = 0.01;
|
||||
final float frictionMagnitude = normalForce * mu;
|
||||
|
||||
final float radius_sphere = 25;
|
||||
|
||||
float angularSpeed = 1;
|
||||
float defaultGain = 1;
|
||||
float thetaX = 0;
|
||||
float thetaY = 0;
|
||||
float thetaZ = 0;
|
||||
float oldThetaX = 0;
|
||||
float oldThetaY = 0;
|
||||
float oldThetaZ = 0;
|
||||
|
||||
PShape robotnik;
|
||||
PImage texture;
|
||||
|
||||
boolean gamePaused = false;
|
||||
boolean shiftPressed = false;
|
||||
|
||||
float score_scale = 1;
|
||||
float rectIndex = 0;
|
||||
float lastSecond = 0;
|
||||
float rectSize = 8;
|
||||
110
HScrollbar.pde
Normal file
@ -0,0 +1,110 @@
|
||||
class HScrollbar {
|
||||
float barWidth; //Bar's width in pixels
|
||||
float barHeight; //Bar's height in pixels
|
||||
float xPosition; //Bar's x position in pixels
|
||||
float yPosition; //Bar's y position in pixels
|
||||
|
||||
float sliderPosition, newSliderPosition; //Position of slider
|
||||
float sliderPositionMin, sliderPositionMax; //Max and min values of slider
|
||||
|
||||
boolean mouseOver; //Is the mouse over the slider?
|
||||
boolean locked; //Is the mouse clicking and dragging the slider now?
|
||||
|
||||
/**
|
||||
* @brief Creates a new horizontal scrollbar
|
||||
*
|
||||
* @param x The x position of the top left corner of the bar in pixels
|
||||
* @param y The y position of the top left corner of the bar in pixels
|
||||
* @param w The width of the bar in pixels
|
||||
* @param h The height of the bar in pixels
|
||||
*/
|
||||
HScrollbar (float x, float y, float w, float h) {
|
||||
barWidth = w;
|
||||
barHeight = h;
|
||||
xPosition = x;
|
||||
yPosition = y;
|
||||
|
||||
sliderPosition = xPosition + barWidth/2 - barHeight/2;
|
||||
newSliderPosition = sliderPosition;
|
||||
|
||||
sliderPositionMin = xPosition;
|
||||
sliderPositionMax = xPosition + barWidth - barHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the state of the scrollbar according to the mouse movement
|
||||
*/
|
||||
void update() {
|
||||
if (isMouseOver()) {
|
||||
mouseOver = true;
|
||||
}
|
||||
else {
|
||||
mouseOver = false;
|
||||
}
|
||||
if (mousePressed && mouseOver) {
|
||||
locked = true;
|
||||
}
|
||||
if (!mousePressed) {
|
||||
locked = false;
|
||||
}
|
||||
if (locked) {
|
||||
newSliderPosition = constrain(mouseX - barHeight/2, sliderPositionMin, sliderPositionMax);
|
||||
}
|
||||
if (abs(newSliderPosition - sliderPosition) > 1) {
|
||||
sliderPosition = sliderPosition + (newSliderPosition - sliderPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clamps the value into the interval
|
||||
*
|
||||
* @param val The value to be clamped
|
||||
* @param minVal Smallest value possible
|
||||
* @param maxVal Largest value possible
|
||||
*
|
||||
* @return val clamped into the interval [minVal, maxVal]
|
||||
*/
|
||||
float constrain(float val, float minVal, float maxVal) {
|
||||
return min(max(val, minVal), maxVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets whether the mouse is hovering the scrollbar
|
||||
*
|
||||
* @return Whether the mouse is hovering the scrollbar
|
||||
*/
|
||||
boolean isMouseOver() {
|
||||
if (mouseX > xPosition && mouseX < xPosition+barWidth &&
|
||||
mouseY > yPosition && mouseY < yPosition+barHeight) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draws the scrollbar in its current state
|
||||
*/
|
||||
void display() {
|
||||
noStroke();
|
||||
fill(204);
|
||||
rect(xPosition, yPosition, barWidth, barHeight);
|
||||
if (mouseOver || locked) {
|
||||
fill(0, 0, 0);
|
||||
}
|
||||
else {
|
||||
fill(102, 102, 102);
|
||||
}
|
||||
rect(sliderPosition, yPosition, barHeight, barHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the slider position
|
||||
*
|
||||
* @return The slider position in the interval [0,1] corresponding to [leftmost position, rightmost position]
|
||||
*/
|
||||
float getPos() {
|
||||
return (sliderPosition - xPosition)/(barWidth - barHeight);
|
||||
}
|
||||
}
|
||||
14
HoughComparator.pde
Normal file
@ -0,0 +1,14 @@
|
||||
class HoughComparator implements java.util.Comparator<Integer> {
|
||||
int[] accumulator;
|
||||
|
||||
public HoughComparator(int[] accumulator) {
|
||||
this.accumulator = accumulator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Integer l1, Integer l2) {
|
||||
if (accumulator[l1] > accumulator[l2] || (accumulator[l1] == accumulator[l2] && l1 < l2))
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
384
ImageProcessing.pde
Normal file
@ -0,0 +1,384 @@
|
||||
//********Merge with ImgProcessing******** //<>//
|
||||
import gab.opencv.*;
|
||||
//********Merge with ImgProcessing********
|
||||
import processing.video.*;
|
||||
|
||||
class ImageProcessing extends PApplet {
|
||||
Movie cam;
|
||||
OpenCV opencv;
|
||||
TwoDThreeD converter;
|
||||
PVector rotation;
|
||||
PImage img;
|
||||
|
||||
/********SETTINGS FOR HSB TRESHOLD********/
|
||||
final int Hmin = 105, Hmax = 140;
|
||||
final int Smin = 35, Smax = 255;
|
||||
final int Bmin = 40, Bmax = 255;
|
||||
/*****************************************/
|
||||
|
||||
|
||||
BlobDetection blobDetection;
|
||||
|
||||
List<PVector> detectedCorners;
|
||||
|
||||
void settings() {
|
||||
size(455, 255, P2D);
|
||||
}
|
||||
|
||||
|
||||
void setup() {
|
||||
|
||||
opencv = new OpenCV(this, 100, 100);
|
||||
|
||||
blobDetection = new BlobDetection();
|
||||
|
||||
//Video
|
||||
cam = new Movie (this, "testvideo.avi"); //Put the absolute path here !!
|
||||
cam.loop();
|
||||
}
|
||||
|
||||
|
||||
void draw() {
|
||||
|
||||
if (cam.available() == true) cam.read();
|
||||
img = cam.get();
|
||||
|
||||
converter =new TwoDThreeD(img.width, img.height, 0);
|
||||
|
||||
img.resize(img.width/2, img.height/2);
|
||||
image(img, 0, 0);
|
||||
|
||||
img = thresholdHSB(img, Hmin, Hmax, Smin, Smax, Bmin, Bmax );
|
||||
img = convolute(img);
|
||||
img = blobDetection.findConnectedComponents(img, false);
|
||||
img = scharr(img);
|
||||
img = threshold(img, 100); // PImage threshold(PImage img, int threshold) the threshold value can be changed
|
||||
plotLines(hough(img, 4), img);
|
||||
|
||||
ArrayList<PVector> lines = hough(img, 4);
|
||||
detectedCorners = new QuadGraph().findBestQuad(lines, width, height, width*height, width*height/64, false);
|
||||
|
||||
stroke(0);
|
||||
for (PVector vector : detectedCorners) {
|
||||
fill(color(255, 255, 255));
|
||||
ellipse(vector.x, vector.y, 30, 30);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PVector getRotation() {
|
||||
if (detectedCorners == null) {
|
||||
return new PVector(0, 0, 0);
|
||||
}
|
||||
|
||||
if (detectedCorners.size() == 4) {
|
||||
for (PVector corner : detectedCorners) {
|
||||
corner.set(corner.x, corner.y, 1);
|
||||
}
|
||||
|
||||
rotation = converter.get3DRotations(detectedCorners);
|
||||
if ((rotation.x) <= - PI/3) rotation.set(rotation.x + PI, rotation.y, rotation.z);
|
||||
else if ((rotation.x) >= PI/3) rotation.set(rotation.x - PI, rotation.y, rotation.z);
|
||||
}
|
||||
|
||||
return rotation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//*******************************
|
||||
// CONVOLUTION
|
||||
//*******************************
|
||||
PImage convolute(PImage img) {
|
||||
float[][] kernel = {
|
||||
{ 9, 12, 9},
|
||||
{ 12, 15, 12},
|
||||
{ 9, 12, 9}};
|
||||
|
||||
|
||||
float normFactor = 99.f;
|
||||
|
||||
// create a greyscale image (type: ALPHA) for output
|
||||
PImage result = createImage(img.width, img.height, ALPHA);
|
||||
|
||||
// kernel size N = 3
|
||||
for (int i = 1; i < img.width -1; i++) {
|
||||
for (int j = 1; j< img.height -1; j++) {
|
||||
int tot = 0;
|
||||
int c = 0;
|
||||
for (int l = 0; l<3; l++) {
|
||||
for (int h = 0; h<3; h++) {
|
||||
c = i-1 + img.width*(j-1+h) +l;
|
||||
tot += kernel[h][l] * brightness(img.pixels[c]);
|
||||
}
|
||||
}
|
||||
result.pixels[j * img.width + i] = color(tot/normFactor);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//*******************************
|
||||
// THRESHOLD
|
||||
//*******************************
|
||||
// Inverted Binary Threshold
|
||||
PImage threshold(PImage img, int threshold) {
|
||||
// create a new, initially transparent, 'result' image
|
||||
PImage result = createImage(img.width, img.height, RGB);
|
||||
for (int i = 0; i < img.width * img.height; i++) {
|
||||
if (brightness(img.pixels[i]) < threshold) {
|
||||
result.pixels[i] = color(0);
|
||||
} else {
|
||||
result.pixels[i] = color(255);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// HUE Method
|
||||
PImage applyHue(PImage img, int min, int max) {
|
||||
// create a new, initially transparent, 'result' image
|
||||
PImage result = createImage(img.width, img.height, RGB);
|
||||
|
||||
for (int i = 0; i < img.width * img.height; i++) {
|
||||
float h = hue(img.pixels[i]);
|
||||
if (min < h && max > h) {
|
||||
result.pixels[i] = img.pixels[i];
|
||||
} else {
|
||||
result.pixels[i] = color(h);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Threhold method for HUE, BRIGHTNESS and SATURATION
|
||||
PImage thresholdHSB(PImage img, int minH, int maxH, int minS, int maxS, int minB, int maxB) {
|
||||
PImage result = createImage(img.width, img.height, RGB);
|
||||
|
||||
for (int i = 0; i < img.width * img.height; i++) {
|
||||
float hue = hue(img.pixels[i]);
|
||||
float bri = brightness(img.pixels[i]);
|
||||
float sat = saturation(img.pixels[i]);
|
||||
if (hue >= minH && hue <= maxH && bri <= maxB && bri >= minB && sat >= minS && sat <= maxS)
|
||||
result.pixels[i] = color(255);
|
||||
else
|
||||
result.pixels[i] = color(0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//*******************************
|
||||
// SCHARR
|
||||
//*******************************
|
||||
PImage scharr(PImage img) {
|
||||
float[][] vKernel = {
|
||||
{ 3, 0, -3 },
|
||||
{ 10, 0, -10 },
|
||||
{ 3, 0, -3 } };
|
||||
|
||||
float[][] hKernel = {
|
||||
{ 3, 10, 3 },
|
||||
{ 0, 0, 0 },
|
||||
{ -3, -10, -3 } };
|
||||
PImage result = createImage(img.width, img.height, ALPHA);
|
||||
// clear the image
|
||||
for (int i = 0; i < img.width * img.height; i++) {
|
||||
result.pixels[i] = color(0);
|
||||
}
|
||||
float max=0;
|
||||
float[] buffer = new float[img.width * img.height];
|
||||
|
||||
// ***********************************
|
||||
// Implement here the double convolution
|
||||
// ***********************************
|
||||
for (int j = 1; j< img.height -1; j++) {
|
||||
for (int i = 1; i < img.width -1; i++) {
|
||||
float sum_v = 0;
|
||||
float sum_h = 0;
|
||||
int c = 0;
|
||||
for (int l = 0; l<3; l++) {
|
||||
for (int h = 0; h<3; h++) {
|
||||
c = i -1 + img.width * (j-1+h) +l;
|
||||
sum_v += vKernel[h][l] * brightness(img.pixels[c]);
|
||||
sum_h += hKernel[h][l] * brightness(img.pixels[c]);
|
||||
}
|
||||
}
|
||||
|
||||
float sum = sqrt(pow(sum_h, 2) + pow(sum_v, 2));
|
||||
buffer[j * img.width + i] = sum;
|
||||
if (max <= sum) max = sum;
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 1; y < img.height - 1; y++) { // Skip top and bottom edges
|
||||
for (int x = 1; x < img.width - 1; x++) { // Skip left and right
|
||||
int val=(int) ((buffer[y * img.width + x] / max)*255);
|
||||
result.pixels[y * img.width + x]=color(val);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//*******************************
|
||||
// HOUGH
|
||||
//*******************************
|
||||
|
||||
// **********************************************************************
|
||||
// Step_1 - Draw the lines requiered - Compute and Store the polar representation
|
||||
// of lines passing through edge pixels
|
||||
// **********************************************************************
|
||||
ArrayList<PVector> hough(PImage edgeImg, int nLines) {
|
||||
|
||||
float discretizationStepsPhi = 0.07f;
|
||||
float discretizationStepsR = 2.8f;
|
||||
|
||||
ArrayList<Integer> bestCandidates=new ArrayList<Integer>();
|
||||
|
||||
// dimensions of the accumulator
|
||||
int phiDim = (int) (Math.PI / discretizationStepsPhi +1);
|
||||
//The max radius is the image diagonal, but it can be also negative
|
||||
int rDim = (int) ((sqrt(edgeImg.width*edgeImg.width +
|
||||
edgeImg.height*edgeImg.height) * 2) / discretizationStepsR +1);
|
||||
// our accumulator
|
||||
int[] accumulator = new int[phiDim * rDim];
|
||||
|
||||
|
||||
// pre-compute the sin and cos values
|
||||
float[] tabSin = new float[phiDim];
|
||||
float[] tabCos = new float[phiDim];
|
||||
float ang = 0;
|
||||
float inverseR = 1.f / discretizationStepsR;
|
||||
for (int accPhi = 0; accPhi < phiDim; ang += discretizationStepsPhi, accPhi++) {
|
||||
// we can also pre-multiply by (1/discretizationStepsR) since we need it in the Hough loop
|
||||
tabSin[accPhi] = (float) (Math.sin(ang) * inverseR);
|
||||
tabCos[accPhi] = (float) (Math.cos(ang) * inverseR);
|
||||
}
|
||||
|
||||
|
||||
// Fill the accumulator: on edge points (ie, white pixels of the edge
|
||||
// image), store all possible (r, phi) pairs describing lines going
|
||||
// through the point.
|
||||
for (int y = 0; y < edgeImg.height; y++) {
|
||||
for (int x = 0; x < edgeImg.width; x++) {
|
||||
// Are we on an edge?
|
||||
if (brightness(edgeImg.pixels[y * edgeImg.width + x]) != 0) {
|
||||
|
||||
// ...determine here all the lines (r, phi) passing through
|
||||
// pixel (x,y), convert (r,phi) to coordinates in the
|
||||
// accumulator, and increment accordingly the accumulator.
|
||||
// Be careful: r may be negative, so you may want to center onto
|
||||
// the accumulator: r += rDim / 2
|
||||
for (int phi=0; phi<phiDim; ++phi) {
|
||||
int accR = (int) ((x*tabCos[phi]+y*tabSin[phi])+rDim/2);
|
||||
++accumulator[phi*rDim+accR];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// **********************************************************************
|
||||
// Step_2 - Display the accumulator
|
||||
// **********************************************************************
|
||||
/*PImage houghImg = createImage(rDim, phiDim, ALPHA);
|
||||
for (int i = 0; i < accumulator.length; i++) {
|
||||
houghImg.pixels[i] = color(min(255, accumulator[i]));
|
||||
}
|
||||
// You may want to resize the accumulator to make it easier to see:
|
||||
houghImg.resize(400, 400);
|
||||
houghImg.updatePixels();
|
||||
image(houghImg,img.width+50,0);*/
|
||||
|
||||
final int minVotes=50;
|
||||
final int REGION_SIZE = 10;
|
||||
|
||||
// Step 2 - Week 11 - Find Local Maxima
|
||||
for (int elem = 0; elem < accumulator.length; ++elem) {
|
||||
if (accumulator[elem] > minVotes && isMaxOverArea(accumulator, elem, REGION_SIZE, phiDim, rDim)) {
|
||||
bestCandidates.add(elem);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the lsit of bestCandidates with the HoughComparator class
|
||||
bestCandidates.sort(new HoughComparator(accumulator));
|
||||
|
||||
// Construction of the arrayList of the lines for the return
|
||||
ArrayList<PVector> lines = new ArrayList<PVector>();
|
||||
|
||||
// New method to find the lines (do not need to check if " > minVotes"
|
||||
// since the array bestCandidate already checked that condition
|
||||
for (int i=0; i < bestCandidates.size() && i < nLines; ++i) {
|
||||
int idx = bestCandidates.get(i);
|
||||
// first, compute back the (r, phi) polar coordinates:
|
||||
int accPhi = (int) (idx / (rDim));
|
||||
int accR = idx - (accPhi) * (rDim);
|
||||
float r = (accR - (rDim) * 0.5f) * discretizationStepsR;
|
||||
float phi = accPhi * discretizationStepsPhi;
|
||||
lines.add(new PVector(r, phi));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private boolean isMaxOverArea(int[] accumulator, int idx, int REGION_SIZE, int phiDim, int rDim) {
|
||||
int threshold=accumulator[idx];
|
||||
for (int dx=Math.max(0, idx%rDim-REGION_SIZE); dx<Math.min(rDim, idx%rDim+REGION_SIZE); ++dx) {
|
||||
for (int dy=Math.max(0, idx/rDim-REGION_SIZE); dy<Math.min(phiDim, idx/rDim+REGION_SIZE); ++dy) {
|
||||
if (accumulator[dx+dy*rDim]>threshold) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// **********************************************************************
|
||||
// Step_3 - Plot lines on the top of the image
|
||||
// **********************************************************************
|
||||
void plotLines(ArrayList<PVector> lines, PImage edgeImg) {
|
||||
for (int idx = 0; idx < lines.size(); idx++) {
|
||||
PVector line=lines.get(idx);
|
||||
float r = line.x;
|
||||
float phi = line.y;
|
||||
|
||||
// Cartesian equation of a line: y = ax + b
|
||||
// in polar, y = (-cos(phi)/sin(phi))x + (r/sin(phi))
|
||||
// => y = 0 : x = r / cos(phi)
|
||||
// => x = 0 : y = r / sin(phi)
|
||||
|
||||
// compute the intersection of this line with the 4 borders of the image
|
||||
int x0 = 0;
|
||||
int y0 = (int) (r / sin(phi));
|
||||
int x1 = (int) (r / cos(phi));
|
||||
int y1 = 0;
|
||||
int x2 = edgeImg.width;
|
||||
int y2 = (int) (-cos(phi) / sin(phi) * x2 + r / sin(phi));
|
||||
int y3 = edgeImg.width;
|
||||
int x3 = (int) (-(y3 - r / sin(phi)) * (sin(phi) / cos(phi)));
|
||||
// Finally, plot the lines
|
||||
//stroke(255, 0, 0);
|
||||
stroke(204, 102, 0);
|
||||
if (y0 > 0) {
|
||||
if (x1 > 0)
|
||||
line(x0, y0, x1, y1);
|
||||
else if (y2 > 0)
|
||||
line(x0, y0, x2, y2);
|
||||
else
|
||||
line(x0, y0, x3, y3);
|
||||
} else {
|
||||
if (x1 > 0) {
|
||||
if (y2 > 0)
|
||||
line(x1, y1, x2, y2);
|
||||
else
|
||||
line(x1, y1, x3, y3);
|
||||
} else
|
||||
line(x2, y2, x3, y3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
ParticleSystem.pde
Normal file
@ -0,0 +1,146 @@
|
||||
final PVector normalX = new PVector(1, 0, 0);
|
||||
|
||||
class ParticleSystem {
|
||||
ArrayList<PVector> particles;
|
||||
PVector originVillain;
|
||||
PVector origin;
|
||||
|
||||
float points;
|
||||
float previousPoints = 0;
|
||||
|
||||
ParticleSystem() {
|
||||
originVillain = new PVector(0,-boxThickness/2 - cylinderHeight,0);
|
||||
particles = new ArrayList<PVector>();
|
||||
setupVillain();
|
||||
}
|
||||
|
||||
//#############################################
|
||||
//------------SETUP OF THE VILLAIN------------
|
||||
//#############################################
|
||||
void setupVillain() {
|
||||
points = 0;
|
||||
|
||||
robotnik = loadShape("robotnik.obj");
|
||||
robotnik.scale(50);
|
||||
robotnik.rotateX(PI);
|
||||
texture = loadImage("robotnik.png");
|
||||
robotnik.setTexture(texture);
|
||||
}
|
||||
|
||||
//#############################################
|
||||
// Main method that will add and adjacent cylinder to the whole system
|
||||
//#############################################
|
||||
void addParticle() {
|
||||
PVector center;
|
||||
int numAttempts = 100;
|
||||
for(int i=0; i<numAttempts; i++) {
|
||||
// Pick a cylinder and its center.
|
||||
int index = int(random(particles.size()));
|
||||
center = particles.get(index).copy();
|
||||
// Try to add an adjacent cylinder.
|
||||
float angle = random(TWO_PI);
|
||||
center.x += sin(angle) * 2*cylinderBaseSize;
|
||||
center.z += cos(angle) * 2*cylinderBaseSize;
|
||||
if(checkPosition(center)) {
|
||||
particles.add(new PVector(center.x , 0, center.z));
|
||||
points -= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#############################################
|
||||
// Check if a position is available, i.e.
|
||||
// - would not overlap with particles that are already created
|
||||
// (for each particle, call checkOverlap())
|
||||
// - is inside the board boundaries
|
||||
//#############################################
|
||||
boolean checkPosition(PVector center) {
|
||||
boolean result = true;
|
||||
pushMatrix();
|
||||
translate(width/2, height/2);
|
||||
if ( (center.x > (boxLength/2 - cylinderBaseSize)) || (center.x < - boxLength/2 + cylinderBaseSize) || (center.z > (boxLength/2 - cylinderBaseSize)) || (center.z < - boxLength/2 + cylinderBaseSize) ) result = false;
|
||||
for(PVector v : particles)
|
||||
if (checkOverlap(v, center)) result = false;
|
||||
popMatrix();
|
||||
return result;
|
||||
}
|
||||
|
||||
//#############################################
|
||||
// Check if a particle with center c1 and another particle with center c2 overlap.
|
||||
//#############################################
|
||||
boolean checkOverlap(PVector c1, PVector c2) {
|
||||
double distance = sqrt((c1.x - c2.x)*(c1.x - c2.x) + (c1.z - c2.z)*(c1.z - c2.z));
|
||||
return distance < 2*cylinderBaseSize;
|
||||
}
|
||||
|
||||
//#############################################
|
||||
// Do the following two things :
|
||||
// - Add a new cylinder at a regular time interval
|
||||
// - Move the bad guy so that it always faces the movement of the ball
|
||||
//#############################################
|
||||
void run() {
|
||||
playable_sphere.checkCylinderCollision();
|
||||
if(frameCount % 30 == 0 ) {
|
||||
addParticle(); //interval set to 0.5 seconds
|
||||
}
|
||||
|
||||
// Rotation of the villain according to the position of the ball
|
||||
float sphereZ = playable_sphere.location().z - originVillain.copy().z;
|
||||
float sphereX = playable_sphere.location().x - originVillain.copy().x;
|
||||
float posNormZ = normalX.copy().normalize().z;
|
||||
float posNormX = normalX.copy().normalize().x;
|
||||
float angleVillain = (float) Math.atan2(sphereX*posNormZ - sphereZ*posNormX, sphereX*posNormX + sphereZ*posNormZ);
|
||||
normalX.x = playable_sphere.location().x - originVillain.x;
|
||||
normalX.z = playable_sphere.location().z - originVillain.z;
|
||||
normalX.y = playable_sphere.location().y - originVillain.y;
|
||||
robotnik.rotateY(angleVillain);
|
||||
}
|
||||
|
||||
//#############################################
|
||||
// Draw the position of the cylinder in the square top view box
|
||||
//#############################################
|
||||
void particleTopView() {
|
||||
topView.noStroke();
|
||||
|
||||
for (int i = 0; i < particles.size(); ++i){
|
||||
PVector particle_vector = particles.get(i);
|
||||
float ellipseX = (particle_vector.x + boxLength/2) * square_for_score/boxLength;
|
||||
float ellipseY = (particle_vector.z + boxLength/2) * square_for_score/boxLength;
|
||||
float ellipseDim = (2*cylinderBaseSize) * square_for_score/boxLength;
|
||||
if(i==0){
|
||||
topView.fill(colorVillainTopView);
|
||||
topView.ellipse(ellipseX, ellipseY, ellipseDim, ellipseDim);
|
||||
} else {
|
||||
topView.fill(defaultCylinderColour);
|
||||
topView.ellipse(ellipseX, ellipseY, ellipseDim, ellipseDim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#############################################
|
||||
// Checks if we can add a cylinder given a x and y coordinates
|
||||
//#############################################
|
||||
boolean canAddCylinder(float x, float y) {
|
||||
return !( (x > (width/2 + boxLength/2 - cylinderBaseSize)) || (x < width/2 - boxLength/2 + cylinderBaseSize) || (y > (height/2 + boxLength/2 - cylinderBaseSize)) || (y < height/2 - boxLength/2 + cylinderBaseSize) );
|
||||
}
|
||||
|
||||
//#############################################
|
||||
// Set up the origin of the villain in order to place it on the board.
|
||||
//#############################################
|
||||
void addCenterOfVillain() {
|
||||
//when a new origin is set, remove all the cylinders and add a new cylinder at the origin
|
||||
particle_system.particles.clear();
|
||||
particle_system.previousPoints = particle_system.points;
|
||||
particle_system.points = 0;
|
||||
scores = new ArrayList<Float>();
|
||||
|
||||
if(canAddCylinder(mouseX, mouseY)){
|
||||
PVector origin = new PVector(mouseX - width/2 , 0 , mouseY - height/2); //origin in upper left corner
|
||||
particle_system.particles.add(origin);
|
||||
particle_system.origin = origin;
|
||||
} else {
|
||||
particle_system.origin = null;
|
||||
}
|
||||
}
|
||||
385
QuadGraph.pde
Normal file
@ -0,0 +1,385 @@
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
class QuadGraph {
|
||||
|
||||
boolean verbose=false;
|
||||
|
||||
List<int[]> cycles = new ArrayList<int[]>();
|
||||
int[][] graph;
|
||||
|
||||
List<PVector> findBestQuad(List<PVector> lines, int width, int height, int max_quad_area, int min_quad_area, boolean verbose) {
|
||||
this.verbose=verbose;
|
||||
build(lines, width, height); //<>//
|
||||
findCycles(verbose);
|
||||
ArrayList<PVector> bestQuad=new ArrayList<PVector>();
|
||||
float bestQuadArea=0;
|
||||
for (int [] cy : cycles) {
|
||||
ArrayList<PVector> quad= new ArrayList<PVector>();
|
||||
PVector l1 = lines.get(cy[0]);
|
||||
PVector l2 = lines.get(cy[1]);
|
||||
PVector l3 = lines.get(cy[2]);
|
||||
PVector l4 = lines.get(cy[3]);
|
||||
|
||||
|
||||
quad.add(intersection(l1, l2));
|
||||
quad.add(intersection(l2, l3));
|
||||
quad.add(intersection(l3, l4));
|
||||
quad.add(intersection(l4, l1));
|
||||
quad=sortCorners(quad);
|
||||
|
||||
PVector c1 = quad.get(0);
|
||||
PVector c2 = quad.get(1);
|
||||
PVector c3 = quad.get(2);
|
||||
PVector c4 = quad.get(3);
|
||||
|
||||
if (isConvex(c1, c2, c3, c4) &&
|
||||
nonFlatQuad(c1, c2, c3, c4)) {
|
||||
float quadArea=validArea(c1, c2, c3, c4, max_quad_area, min_quad_area);
|
||||
if (quadArea>0 && quadArea>bestQuadArea) {
|
||||
bestQuadArea=quadArea;
|
||||
bestQuad=quad;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestQuadArea>0)
|
||||
return bestQuad;
|
||||
else
|
||||
return new ArrayList<PVector>();
|
||||
}
|
||||
|
||||
|
||||
void build(List<PVector> lines, int width, int height) {
|
||||
|
||||
int n = lines.size();
|
||||
|
||||
// The maximum possible number of edges is n * (n - 1)/2
|
||||
graph = new int[n * (n - 1)/2][2];
|
||||
|
||||
int idx =0;
|
||||
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
for (int j = i + 1; j < lines.size(); j++) {
|
||||
if (intersect(lines.get(i), lines.get(j), width, height)) {
|
||||
|
||||
graph[idx][0]=i;
|
||||
graph[idx][1]=j;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if polar lines 1 and 2 intersect
|
||||
* inside an area of size (width, height)
|
||||
*/
|
||||
boolean intersect(PVector line1, PVector line2, int width, int height) {
|
||||
|
||||
double sin_t1 = Math.sin(line1.y);
|
||||
double sin_t2 = Math.sin(line2.y);
|
||||
double cos_t1 = Math.cos(line1.y);
|
||||
double cos_t2 = Math.cos(line2.y);
|
||||
float r1 = line1.x;
|
||||
float r2 = line2.x;
|
||||
|
||||
double denom = cos_t2 * sin_t1 - cos_t1 * sin_t2;
|
||||
|
||||
int x = (int) ((r2 * sin_t1 - r1 * sin_t2) / denom);
|
||||
int y = (int) ((-r2 * cos_t1 + r1 * cos_t2) / denom);
|
||||
|
||||
if (0 <= x && 0 <= y && width >= x && height >= y)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
PVector intersection(PVector line1, PVector line2) {
|
||||
|
||||
double sin_t1 = Math.sin(line1.y);
|
||||
double sin_t2 = Math.sin(line2.y);
|
||||
double cos_t1 = Math.cos(line1.y);
|
||||
double cos_t2 = Math.cos(line2.y);
|
||||
float r1 = line1.x;
|
||||
float r2 = line2.x;
|
||||
|
||||
double denom = cos_t2 * sin_t1 - cos_t1 * sin_t2;
|
||||
|
||||
int x = (int) ((r2 * sin_t1 - r1 * sin_t2) / denom);
|
||||
int y = (int) ((-r2 * cos_t1 + r1 * cos_t2) / denom);
|
||||
|
||||
return new PVector(x,y);
|
||||
}
|
||||
|
||||
void findCycles(boolean verbose) {
|
||||
cycles.clear();
|
||||
for (int i = 0; i < graph.length; i++) {
|
||||
for (int j = 0; j < graph[i].length; j++) {
|
||||
findNewCycles(new int[] {graph[i][j]});
|
||||
}
|
||||
}
|
||||
if (verbose) {
|
||||
for (int[] cy : cycles) {
|
||||
String s = "" + cy[0];
|
||||
for (int i = 1; i < cy.length; i++) {
|
||||
s += "," + cy[i];
|
||||
}
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void findNewCycles(int[] path)
|
||||
{
|
||||
int n = path[0];
|
||||
int x;
|
||||
int[] sub = new int[path.length + 1];
|
||||
|
||||
for (int i = 0; i < graph.length; i++)
|
||||
for (int y = 0; y <= 1; y++)
|
||||
if (graph[i][y] == n)
|
||||
// edge refers to our current node
|
||||
{
|
||||
x = graph[i][(y + 1) % 2];
|
||||
if (!visited(x, path))
|
||||
// neighbor node not on path yet
|
||||
{
|
||||
sub[0] = x;
|
||||
System.arraycopy(path, 0, sub, 1, path.length);
|
||||
// explore extended path
|
||||
findNewCycles(sub);
|
||||
} else if ((path.length == 4) && (x == path[path.length - 1]))
|
||||
// cycle found
|
||||
{
|
||||
int[] p = normalize(path);
|
||||
int[] inv = invert(p);
|
||||
if (isNew(p) && isNew(inv))
|
||||
{
|
||||
cycles.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check of both arrays have same lengths and contents
|
||||
Boolean equals(int[] a, int[] b)
|
||||
{
|
||||
Boolean ret = (a[0] == b[0]) && (a.length == b.length);
|
||||
|
||||
for (int i = 1; ret && (i < a.length); i++)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// create a path array with reversed order
|
||||
int[] invert(int[] path)
|
||||
{
|
||||
int[] p = new int[path.length];
|
||||
|
||||
for (int i = 0; i < path.length; i++)
|
||||
{
|
||||
p[i] = path[path.length - 1 - i];
|
||||
}
|
||||
|
||||
return normalize(p);
|
||||
}
|
||||
|
||||
// rotate cycle path such that it begins with the smallest node
|
||||
int[] normalize(int[] path)
|
||||
{
|
||||
int[] p = new int[path.length];
|
||||
int x = smallest(path);
|
||||
int n;
|
||||
|
||||
System.arraycopy(path, 0, p, 0, path.length);
|
||||
|
||||
while (p[0] != x)
|
||||
{
|
||||
n = p[0];
|
||||
System.arraycopy(p, 1, p, 0, p.length - 1);
|
||||
p[p.length - 1] = n;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// compare path against known cycles
|
||||
// return true, iff path is not a known cycle
|
||||
Boolean isNew(int[] path)
|
||||
{
|
||||
Boolean ret = true;
|
||||
|
||||
for (int[] p : cycles)
|
||||
{
|
||||
if (equals(p, path))
|
||||
{
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// return the int of the array which is the smallest
|
||||
int smallest(int[] path)
|
||||
{
|
||||
int min = path[0];
|
||||
|
||||
for (int p : path)
|
||||
{
|
||||
if (p < min)
|
||||
{
|
||||
min = p;
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
// check if vertex n is contained in path
|
||||
Boolean visited(int n, int[] path)
|
||||
{
|
||||
Boolean ret = false;
|
||||
|
||||
for (int p : path)
|
||||
{
|
||||
if (p == n)
|
||||
{
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Check if a quad is convex or not.
|
||||
*
|
||||
* Algo: take two adjacent edges and compute their cross-product.
|
||||
* The sign of the z-component of all the cross-products is the
|
||||
* same for a convex polygon.
|
||||
*
|
||||
* See http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/clockwise.htm
|
||||
* for justification.
|
||||
*
|
||||
* @param c1
|
||||
*/
|
||||
boolean isConvex(PVector c1, PVector c2, PVector c3, PVector c4) {
|
||||
|
||||
PVector v21= PVector.sub(c1, c2);
|
||||
PVector v32= PVector.sub(c2, c3);
|
||||
PVector v43= PVector.sub(c3, c4);
|
||||
PVector v14= PVector.sub(c4, c1);
|
||||
|
||||
float i1=v21.cross(v32).z;
|
||||
float i2=v32.cross(v43).z;
|
||||
float i3=v43.cross(v14).z;
|
||||
float i4=v14.cross(v21).z;
|
||||
|
||||
if ( (i1>0 && i2>0 && i3>0 && i4>0)
|
||||
|| (i1<0 && i2<0 && i3<0 && i4<0))
|
||||
return true;
|
||||
else if(verbose)
|
||||
System.out.println("Eliminating non-convex quad");
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Compute the area of a quad, and check it lays within a specific range
|
||||
*/
|
||||
float validArea(PVector c1, PVector c2, PVector c3, PVector c4, float max_area, float min_area) {
|
||||
|
||||
float i1=c1.cross(c2).z;
|
||||
float i2=c2.cross(c3).z;
|
||||
float i3=c3.cross(c4).z;
|
||||
float i4=c4.cross(c1).z;
|
||||
|
||||
float area = Math.abs(0.5f * (i1 + i2 + i3 + i4));
|
||||
|
||||
|
||||
if (area < max_area && area > min_area){
|
||||
return area;
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/** Compute the (cosine) of the four angles of the quad, and check they are all large enough
|
||||
* (the quad representing our board should be close to a rectangle)
|
||||
*/
|
||||
boolean nonFlatQuad(PVector c1, PVector c2, PVector c3, PVector c4) {
|
||||
|
||||
// cos(70deg) ~= 0.3
|
||||
float min_cos = 0.5f;
|
||||
|
||||
PVector v21= PVector.sub(c1, c2);
|
||||
PVector v32= PVector.sub(c2, c3);
|
||||
PVector v43= PVector.sub(c3, c4);
|
||||
PVector v14= PVector.sub(c4, c1);
|
||||
|
||||
float cos1=Math.abs(v21.dot(v32) / (v21.mag() * v32.mag()));
|
||||
float cos2=Math.abs(v32.dot(v43) / (v32.mag() * v43.mag()));
|
||||
float cos3=Math.abs(v43.dot(v14) / (v43.mag() * v14.mag()));
|
||||
float cos4=Math.abs(v14.dot(v21) / (v14.mag() * v21.mag()));
|
||||
|
||||
if (cos1 < min_cos && cos2 < min_cos && cos3 < min_cos && cos4 < min_cos)
|
||||
return true;
|
||||
else {
|
||||
if(verbose)
|
||||
System.out.println("Flat quad");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ArrayList<PVector> sortCorners(ArrayList<PVector> quad) {
|
||||
|
||||
// 1 - Sort corners so that they are ordered clockwise
|
||||
PVector a = quad.get(0);
|
||||
PVector b = quad.get(2);
|
||||
|
||||
PVector center = new PVector((a.x+b.x)/2, (a.y+b.y)/2);
|
||||
|
||||
Collections.sort(quad, new CWComparator(center));
|
||||
|
||||
|
||||
|
||||
// 2 - Sort by upper left most corner
|
||||
PVector origin = new PVector(0, 0);
|
||||
float distToOrigin = 1000;
|
||||
|
||||
for (PVector p : quad) {
|
||||
if (p.dist(origin) < distToOrigin) distToOrigin = p.dist(origin);
|
||||
}
|
||||
|
||||
while (quad.get(0).dist(origin) != distToOrigin)
|
||||
Collections.rotate(quad, 1);
|
||||
|
||||
return quad;
|
||||
}
|
||||
}
|
||||
|
||||
class CWComparator implements Comparator<PVector> {
|
||||
|
||||
PVector center;
|
||||
|
||||
public CWComparator(PVector center) {
|
||||
this.center = center;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(PVector b, PVector d) {
|
||||
if (Math.atan2(b.y-center.y, b.x-center.x)<Math.atan2(d.y-center.y, d.x-center.x))
|
||||
return -1;
|
||||
else return 1;
|
||||
}
|
||||
}
|
||||
116
Sphere.pde
Normal file
@ -0,0 +1,116 @@
|
||||
class Mover {
|
||||
PVector location;
|
||||
PVector velocity;
|
||||
PVector gravityForce;
|
||||
|
||||
//Rotation angles of the sphere
|
||||
float sphere_rotX = 0;
|
||||
float sphere_rotZ = 0;
|
||||
|
||||
//Add textures
|
||||
private PImage img;
|
||||
private PShape globe;
|
||||
|
||||
//Related to the geometry of the object and have an impact on its moment inertia
|
||||
final float K_INERTIA = 2/3;
|
||||
|
||||
Mover() {
|
||||
location = new PVector(0, -(radius_sphere + boxThickness/2), 0);
|
||||
velocity = new PVector(0, 0, 0);
|
||||
gravityForce = new PVector(0, 0, 0);
|
||||
|
||||
//Adding texture of a pool ball
|
||||
img = loadImage("PoolBall.jpeg");
|
||||
globe = createShape();
|
||||
globe = createShape(SPHERE, radius_sphere);
|
||||
globe.setStroke(false);
|
||||
globe.setTexture(img);
|
||||
|
||||
}
|
||||
|
||||
//#############################################
|
||||
// Update the position of the ball so that it has a real movement
|
||||
//#############################################
|
||||
void update() {
|
||||
gravityForce = new PVector(sin(thetaZ) * gravityConstant, 0, -sin(thetaX) * gravityConstant);
|
||||
velocity.add(gravityForce);
|
||||
PVector friction = velocity.copy();
|
||||
friction.mult(-1);
|
||||
friction.normalize();
|
||||
friction.mult(frictionMagnitude);
|
||||
velocity.add(friction);
|
||||
location.add(velocity);
|
||||
|
||||
//Update rotation angles of the sphere
|
||||
sphere_rotX = (-velocity.z)/radius_sphere;
|
||||
sphere_rotZ = (velocity.x)/radius_sphere;
|
||||
checkCylinderCollision();
|
||||
}
|
||||
|
||||
//#############################################
|
||||
//Displays the ball and makes it move with real rolling effect
|
||||
//#############################################
|
||||
void display() {
|
||||
gameSurface.noStroke();
|
||||
gameSurface.pushMatrix();
|
||||
gameSurface.translate(location.x, location.y, location.z);
|
||||
//Add rolling effect to the ball
|
||||
if (!gamePaused) {
|
||||
gameSurface.rotateX(PI/2);
|
||||
globe.rotateX(sphere_rotX);
|
||||
globe.rotateY(sphere_rotZ);
|
||||
}
|
||||
gameSurface.shape(globe);
|
||||
gameSurface.popMatrix();
|
||||
}
|
||||
|
||||
//#############################################
|
||||
//---AVOID THE BALL TO GO OUT OF THE BOARD-----
|
||||
//#############################################
|
||||
void checkEdges() {
|
||||
if (location.x + radius_sphere > boxLength/2) {
|
||||
velocity.x *= -1;
|
||||
location.x = boxLength/2 - radius_sphere;
|
||||
} else if (location.x - radius_sphere < -boxLength/2) {
|
||||
velocity.x *= -1;
|
||||
location.x = -boxLength/2 + radius_sphere;
|
||||
}
|
||||
|
||||
if (location.z + radius_sphere > boxLength/2) {
|
||||
velocity.z *= -1;
|
||||
location.z = boxLength/2 - radius_sphere;
|
||||
} else if (location.z - radius_sphere < -boxLength/2) {
|
||||
velocity.z *= -1;
|
||||
location.z = -boxLength/2 + radius_sphere;
|
||||
}
|
||||
}
|
||||
|
||||
//#############################################
|
||||
//-------CHECK COLLISIONS WITH CYLINDERS-------
|
||||
//#############################################
|
||||
void checkCylinderCollision() {
|
||||
for (int i = 0; i < particle_system.particles.size(); ++i) {
|
||||
PVector cylinderPosition = particle_system.particles.get(i);
|
||||
PVector ballCylVect = new PVector(location.x - cylinderPosition.x, 0, location.z - cylinderPosition.z);
|
||||
|
||||
if (ballCylVect.mag() <= (radius_sphere + cylinderBaseSize)) {
|
||||
if (cylinderPosition == particle_system.origin) {
|
||||
particle_system.particles.clear();
|
||||
particle_system.origin = null;
|
||||
} else {
|
||||
//remove the cylinder if the ball hits it
|
||||
particle_system.particles.remove(i);
|
||||
PVector normal = ballCylVect.normalize();
|
||||
float veloNorm = PVector.dot(velocity, normal)*2;
|
||||
PVector vector = PVector.mult(normal, veloNorm);
|
||||
velocity = PVector.sub(velocity, vector);
|
||||
particle_system.points += defaultGain * playable_sphere.velocity.mag();;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PVector location() {
|
||||
return location.copy();
|
||||
}
|
||||
}
|
||||
144
TangibleGame.pde
Normal file
@ -0,0 +1,144 @@
|
||||
PGraphics gameSurface; //<>//
|
||||
PGraphics scoreBoard;
|
||||
PGraphics topView;
|
||||
PGraphics barChart;
|
||||
|
||||
Mover playable_sphere;
|
||||
Cylinder cylinder;
|
||||
ParticleSystem particle_system;
|
||||
HScrollbar hs_scroll_bar;
|
||||
|
||||
ImageProcessing imgproc;
|
||||
|
||||
ArrayList<PVector> cylindersPosition = new ArrayList<PVector>();
|
||||
ArrayList<Float> scores = new ArrayList<Float>();
|
||||
|
||||
void settings() {
|
||||
size(window_size_x, window_size_y, P3D);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
|
||||
//has to be in the main game process
|
||||
//********Merge with ImgProcessing********
|
||||
imgproc = new ImageProcessing();
|
||||
String []args = {"Image processing window"};
|
||||
PApplet.runSketch(args, imgproc);
|
||||
//********Merge with ImgProcessing********
|
||||
|
||||
|
||||
noStroke();
|
||||
frameRate(fps);
|
||||
|
||||
playable_sphere = new Mover();
|
||||
cylinder = new Cylinder(defaultCylinderColour);
|
||||
particle_system = new ParticleSystem();
|
||||
|
||||
gameSurface = createGraphics(width, height - score_graphic, P3D);
|
||||
scoreBoard = createGraphics(square_for_score, square_for_score, P2D);
|
||||
topView = createGraphics(square_for_score, square_for_score, P2D);
|
||||
barChart = createGraphics(width - 2*square_for_score - 20, square_for_score, P2D);
|
||||
hs_scroll_bar = new HScrollbar(2*score_graphic, height-25, score_graphic*3/4, 15);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
|
||||
|
||||
//has to be in the main game process
|
||||
//********Merge with ImgProcessing********
|
||||
PVector rotation = imgproc.getRotation();
|
||||
changeAngleForRotation(rotation);
|
||||
//********Merge with ImgProcessing********
|
||||
|
||||
background(bottomColor);
|
||||
drawGame();
|
||||
image(gameSurface, 0, 0);
|
||||
drawScoreBoard();
|
||||
image(scoreBoard, 150, height-score_graphic+5);
|
||||
drawTopView();
|
||||
image(topView, 5, height-score_graphic+5);
|
||||
drawBarChart();
|
||||
image(barChart, 2*square_for_score+10+5, height-score_graphic+5);
|
||||
hs_scroll_bar.update();
|
||||
hs_scroll_bar.display();
|
||||
}
|
||||
|
||||
|
||||
void changeAngleForRotation(PVector rotation)
|
||||
{
|
||||
if (!shiftPressed) {
|
||||
thetaX = rotation.x;
|
||||
thetaZ = rotation.z;
|
||||
|
||||
if (thetaX <= -PI/3) thetaX = -PI/3;
|
||||
else if (thetaX >= PI/3) thetaX = PI/3;
|
||||
|
||||
if (thetaZ <= -PI/3) thetaZ = -PI/3;
|
||||
else if (thetaZ >= PI/3) thetaZ = PI/3;
|
||||
}
|
||||
}
|
||||
|
||||
void mouseWheel(MouseEvent evenement) {
|
||||
float e = evenement.getCount();
|
||||
if (e < 1 ) {
|
||||
angularSpeed+=0.1;
|
||||
} else {
|
||||
angularSpeed-=0.1;
|
||||
}
|
||||
|
||||
if (angularSpeed >= 1.5) {
|
||||
angularSpeed = 1.5;
|
||||
} else if (angularSpeed <= 0.3) {
|
||||
angularSpeed = 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
void mousePressed() {
|
||||
if (shiftPressed) {
|
||||
addCenterOfVillain();
|
||||
}
|
||||
}
|
||||
|
||||
void keyPressed() {
|
||||
if (key == CODED && keyCode == SHIFT) {
|
||||
shiftPressed = true;
|
||||
oldThetaX = thetaX;
|
||||
oldThetaY = thetaY;
|
||||
oldThetaZ = thetaZ;
|
||||
thetaX = -PI/2;
|
||||
thetaY = 0;
|
||||
thetaZ = 0;
|
||||
pauseGame();
|
||||
}
|
||||
}
|
||||
|
||||
void keyReleased() {
|
||||
if (key == CODED && keyCode == SHIFT ) {
|
||||
shiftPressed = false;
|
||||
thetaX = oldThetaX;
|
||||
thetaY = oldThetaY;
|
||||
thetaZ = oldThetaZ;
|
||||
resumeGame();
|
||||
}
|
||||
}
|
||||
|
||||
void pauseGame() {
|
||||
gamePaused = true;
|
||||
}
|
||||
|
||||
void resumeGame() {
|
||||
gamePaused = false;
|
||||
}
|
||||
|
||||
|
||||
//#############################################
|
||||
// Getters for points & previousPoints variables
|
||||
//#############################################
|
||||
float getPoints() {
|
||||
if (particle_system == null) return 0;
|
||||
return particle_system.points;
|
||||
}
|
||||
float getLastPoints() {
|
||||
if (particle_system == null) return 0;
|
||||
return particle_system.previousPoints;
|
||||
}
|
||||
355
TwoDThreeD.pde
Normal file
@ -0,0 +1,355 @@
|
||||
import java.util.List;
|
||||
|
||||
import processing.core.PVector;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Core;
|
||||
|
||||
class TwoDThreeD {
|
||||
|
||||
// default focal length, well suited for most webcams
|
||||
float f = 700;
|
||||
|
||||
// intrisic camera matrix
|
||||
float [][] K = {{f, 0, 0},
|
||||
{0, f, 0},
|
||||
{0, 0, 1}};
|
||||
float [][] invK;
|
||||
PVector invK_r1, invK_r2, invK_r3;
|
||||
Mat opencv_A, w, u, vt;
|
||||
double [][] V;
|
||||
|
||||
// Real physical coordinates of the Lego board in mm
|
||||
//float boardSize = 380.f; // large Duplo board
|
||||
// float boardSize = 255.f; // smaller Lego board
|
||||
|
||||
// the 3D coordinates of the physical board corners, clockwise
|
||||
float [][] physicalCorners = {
|
||||
{-128, 128, 0, 1},
|
||||
{128, 128, 0, 1},
|
||||
{128, -128, 0, 1},
|
||||
{-128, -128, 0, 1}
|
||||
};
|
||||
|
||||
//Filtering variables: low-pass filter based on arFilterTrans from ARToolKit v5 */
|
||||
float[] q;
|
||||
float sampleRate;
|
||||
float cutOffFreq;
|
||||
float alpha;
|
||||
|
||||
|
||||
public TwoDThreeD(int width, int height, float sampleRate) {
|
||||
|
||||
// set the offset to the center of the webcam image
|
||||
K[0][2] = 0.5f * width;
|
||||
K[1][2] = 0.5f * height;
|
||||
//compute inverse of K
|
||||
Mat opencv_K= new Mat(3, 3, CvType.CV_32F);
|
||||
opencv_K.put(0, 0, K[0][0]);
|
||||
opencv_K.put(0, 1, K[0][1]);
|
||||
opencv_K.put(0, 2, K[0][2]);
|
||||
opencv_K.put(1, 0, K[1][0]);
|
||||
opencv_K.put(1, 1, K[1][1]);
|
||||
opencv_K.put(1, 2, K[1][2]);
|
||||
opencv_K.put(2, 0, K[2][0]);
|
||||
opencv_K.put(2, 1, K[2][1]);
|
||||
opencv_K.put(2, 2, K[2][2]);
|
||||
Mat opencv_invK=opencv_K.inv();
|
||||
|
||||
invK = new float[][]{
|
||||
{ (float)opencv_invK.get(0, 0)[0], (float)opencv_invK.get(0, 1)[0], (float)opencv_invK.get(0, 2)[0] },
|
||||
{ (float)opencv_invK.get(1, 0)[0], (float)opencv_invK.get(1, 1)[0], (float)opencv_invK.get(1, 2)[0] },
|
||||
{ (float)opencv_invK.get(2, 0)[0], (float)opencv_invK.get(2, 1)[0], (float)opencv_invK.get(2, 2)[0] }};
|
||||
invK_r1=new PVector(invK[0][0], invK[0][1], invK[0][2]);
|
||||
invK_r2=new PVector(invK[1][0], invK[1][1], invK[1][2]);
|
||||
invK_r3=new PVector(invK[2][0], invK[2][1], invK[2][2]);
|
||||
|
||||
opencv_A=new Mat(12, 9, CvType.CV_32F);
|
||||
w=new Mat();
|
||||
u=new Mat();
|
||||
vt=new Mat();
|
||||
V= new double[9][9];
|
||||
|
||||
q=new float[4];
|
||||
q[3]=1;
|
||||
|
||||
this.sampleRate=sampleRate;
|
||||
if (sampleRate>0) {
|
||||
cutOffFreq=sampleRate/2;
|
||||
alpha= (1/sampleRate)/(1/sampleRate + 1/cutOffFreq);
|
||||
}
|
||||
}
|
||||
|
||||
PVector get3DRotations(List<PVector> points2D) {
|
||||
|
||||
// 1- Solve the extrinsic matrix from the projected 2D points
|
||||
double[][] E = solveExtrinsicMatrix(points2D);
|
||||
|
||||
|
||||
// 2 - Re-build a proper 3x3 rotation matrix from the camera's
|
||||
// extrinsic matrix E
|
||||
PVector firstColumn=new PVector((float)E[0][0], (float)E[1][0], (float)E[2][0]);
|
||||
PVector secondColumn=new PVector((float)E[0][1], (float)E[1][1], (float)E[2][1]);
|
||||
firstColumn.normalize();
|
||||
secondColumn.normalize();
|
||||
PVector thirdColumn=firstColumn.cross(secondColumn);
|
||||
float [][] rotationMatrix={{firstColumn.x, secondColumn.x, thirdColumn.x},
|
||||
{firstColumn.y, secondColumn.y, thirdColumn.y},
|
||||
{firstColumn.z, secondColumn.z, thirdColumn.z}};
|
||||
|
||||
if (sampleRate>0)
|
||||
filter(rotationMatrix, false);
|
||||
|
||||
// 3 - Computes and returns Euler angles (rx, ry, rz) from this matrix
|
||||
return rotationFromMatrix(rotationMatrix);
|
||||
}
|
||||
|
||||
|
||||
double[][] solveExtrinsicMatrix(List<PVector> points2D) {
|
||||
|
||||
// p ~= K · [R|t] · P
|
||||
// with P the (3D) corners of the physical board, p the (2D)
|
||||
// projected points onto the webcam image, K the intrinsic
|
||||
// matrix and R and t the rotation and translation we want to
|
||||
// compute.
|
||||
//
|
||||
// => We want to solve: (K^(-1) · p) X ([R|t] · P) = 0
|
||||
|
||||
float[][] projectedCorners = new float[4][3];
|
||||
|
||||
if(points2D.size() >= 4)
|
||||
for (int i=0; i<4; i++) {
|
||||
// TODO:
|
||||
// store in projectedCorners the result of (K^(-1) · p), for each
|
||||
// corner p found in the webcam image.
|
||||
// You can use PVector dot function for computing dot product between K^(-1) lines and p.
|
||||
//Do not forget to normalize the result
|
||||
PVector point =points2D.get(i);
|
||||
projectedCorners[i][0]=point.dot(invK_r1)/point.dot(invK_r3);
|
||||
projectedCorners[i][1]=point.dot(invK_r2)/point.dot(invK_r3);
|
||||
projectedCorners[i][2]=1;
|
||||
}
|
||||
|
||||
// 'A' contains the cross-product (K^(-1) · p) X P
|
||||
float[][] A= new float[12][9];
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
A[i*3][0]=0;
|
||||
A[i*3][1]=0;
|
||||
A[i*3][2]=0;
|
||||
|
||||
// note that we take physicalCorners[0,1,*3*]: we drop the Z
|
||||
// coordinate and use the 2D homogenous coordinates of the physical
|
||||
// corners
|
||||
A[i*3][3]=-projectedCorners[i][2] * physicalCorners[i][0];
|
||||
A[i*3][4]=-projectedCorners[i][2] * physicalCorners[i][1];
|
||||
A[i*3][5]=-projectedCorners[i][2] * physicalCorners[i][3];
|
||||
|
||||
A[i*3][6]= projectedCorners[i][1] * physicalCorners[i][0];
|
||||
A[i*3][7]= projectedCorners[i][1] * physicalCorners[i][1];
|
||||
A[i*3][8]= projectedCorners[i][1] * physicalCorners[i][3];
|
||||
|
||||
A[i*3+1][0]= projectedCorners[i][2] * physicalCorners[i][0];
|
||||
A[i*3+1][1]= projectedCorners[i][2] * physicalCorners[i][1];
|
||||
A[i*3+1][2]= projectedCorners[i][2] * physicalCorners[i][3];
|
||||
|
||||
A[i*3+1][3]=0;
|
||||
A[i*3+1][4]=0;
|
||||
A[i*3+1][5]=0;
|
||||
|
||||
A[i*3+1][6]=-projectedCorners[i][0] * physicalCorners[i][0];
|
||||
A[i*3+1][7]=-projectedCorners[i][0] * physicalCorners[i][1];
|
||||
A[i*3+1][8]=-projectedCorners[i][0] * physicalCorners[i][3];
|
||||
|
||||
A[i*3+2][0]=-projectedCorners[i][1] * physicalCorners[i][0];
|
||||
A[i*3+2][1]=-projectedCorners[i][1] * physicalCorners[i][1];
|
||||
A[i*3+2][2]=-projectedCorners[i][1] * physicalCorners[i][3];
|
||||
|
||||
A[i*3+2][3]= projectedCorners[i][0] * physicalCorners[i][0];
|
||||
A[i*3+2][4]= projectedCorners[i][0] * physicalCorners[i][1];
|
||||
A[i*3+2][5]= projectedCorners[i][0] * physicalCorners[i][3];
|
||||
|
||||
A[i*3+2][6]=0;
|
||||
A[i*3+2][7]=0;
|
||||
A[i*3+2][8]=0;
|
||||
}
|
||||
|
||||
for (int i=0; i<12; i++)
|
||||
for (int j=0; j<9; j++)
|
||||
opencv_A.put(i, j, A[i][j]);
|
||||
|
||||
Core.SVDecomp(opencv_A, w, u, vt);
|
||||
|
||||
for (int i=0; i<9; i++)
|
||||
for (int j=0; j<9; j++)
|
||||
V[j][i]=vt.get(i, j)[0];
|
||||
|
||||
double[][] E = new double[3][3];
|
||||
|
||||
//E is the last column of V
|
||||
for (int i=0; i<9; i++) {
|
||||
E[i/3][i%3] = V[i][V.length-1] / V[8][V.length-1];
|
||||
}
|
||||
|
||||
return E;
|
||||
}
|
||||
|
||||
PVector rotationFromMatrix(float[][] mat) {
|
||||
|
||||
// Assuming rotation order is around x,y,z
|
||||
PVector rot = new PVector();
|
||||
|
||||
if (mat[1][0] > 0.998) { // singularity at north pole
|
||||
rot.z = 0;
|
||||
float delta = (float) Math.atan2(mat[0][1], mat[0][2]);
|
||||
rot.y = -(float) Math.PI/2;
|
||||
rot.x = -rot.z + delta;
|
||||
return rot;
|
||||
}
|
||||
|
||||
if (mat[1][0] < -0.998) { // singularity at south pole
|
||||
rot.z = 0;
|
||||
float delta = (float) Math.atan2(mat[0][1], mat[0][2]);
|
||||
rot.y = (float) Math.PI/2;
|
||||
rot.x = rot.z + delta;
|
||||
return rot;
|
||||
}
|
||||
|
||||
rot.y =-(float)Math.asin(mat[2][0]);
|
||||
rot.x = (float)Math.atan2(mat[2][1]/Math.cos(rot.y), mat[2][2]/Math.cos(rot.y));
|
||||
rot.z = (float)Math.atan2(mat[1][0]/Math.cos(rot.y), mat[0][0]/Math.cos(rot.y));
|
||||
|
||||
return rot;
|
||||
}
|
||||
|
||||
int filter(float m[][], boolean reset) {
|
||||
|
||||
float[] q= new float[4];
|
||||
float alpha, oneminusalpha, omega, cosomega, sinomega, s0, s1;
|
||||
|
||||
mat2Quat(m, q);
|
||||
if (nomalizeQuaternion(q)<0) return -1;
|
||||
|
||||
if (reset) {
|
||||
this.q[0] = q[0];
|
||||
this.q[1] = q[1];
|
||||
this.q[2] = q[2];
|
||||
this.q[3] = q[3];
|
||||
} else {
|
||||
alpha = this.alpha;
|
||||
|
||||
oneminusalpha = 1.0 - alpha;
|
||||
|
||||
// SLERP for orientation.
|
||||
cosomega = q[0]*this.q[0] + q[1]*this.q[1] + q[2]*this.q[2] + q[3]*this.q[3]; // cos of angle between vectors.
|
||||
if (cosomega < 0.0) {
|
||||
cosomega = -cosomega;
|
||||
q[0] = -q[0];
|
||||
q[1] = -q[1];
|
||||
q[2] = -q[2];
|
||||
q[3] = -q[3];
|
||||
}
|
||||
if (cosomega > 0.9995) {
|
||||
s0 = oneminusalpha;
|
||||
s1 = alpha;
|
||||
} else {
|
||||
omega = acos(cosomega);
|
||||
sinomega = sin(omega);
|
||||
s0 = sin(oneminusalpha * omega) / sinomega;
|
||||
s1 = sin(alpha * omega) / sinomega;
|
||||
}
|
||||
this.q[0] = q[0]*s1 + this.q[0]*s0;
|
||||
this.q[1] = q[1]*s1 + this.q[1]*s0;
|
||||
this.q[2] = q[2]*s1 + this.q[2]*s0;
|
||||
this.q[3] = q[3]*s1 + this.q[3]*s0;
|
||||
nomalizeQuaternion(this.q);
|
||||
}
|
||||
|
||||
if (quat2Mat(this.q, m) < 0) return (-2);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
int nomalizeQuaternion(float[] q) {// Normalise quaternion.
|
||||
float mag2 = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3];
|
||||
if (mag2==0) return (-1);
|
||||
|
||||
float mag = sqrt(mag2);
|
||||
|
||||
q[0] /= mag;
|
||||
q[1] /= mag;
|
||||
q[2] /= mag;
|
||||
q[3] /= mag;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int mat2Quat(float m[][], float q[]) {
|
||||
float t, s;
|
||||
t = m[0][0] + m[1][1] + m[2][2] + 1.0;
|
||||
if (t > 0.0001) {
|
||||
s = sqrt(t) * 2.0;
|
||||
q[0] = (m[1][2] - m[2][1]) / s;
|
||||
q[1] = (m[2][0] - m[0][2]) / s;
|
||||
q[2] = (m[0][1] - m[1][0]) / s;
|
||||
q[3] = 0.25 * s;
|
||||
} else {
|
||||
if (m[0][0] > m[1][1] && m[0][0] > m[2][2]) { // Column 0:
|
||||
s = sqrt(1.0 + m[0][0] - m[1][1] - m[2][2]) * 2.0;
|
||||
q[0] = 0.25 * s;
|
||||
q[1] = (m[0][1] + m[1][0] ) / s;
|
||||
q[2] = (m[2][0] + m[0][2] ) / s;
|
||||
q[3] = (m[1][2] - m[2][1] ) / s;
|
||||
} else if (m[1][1] > m[2][2]) { // Column 1:
|
||||
s = sqrt(1.0 + m[1][1] - m[0][0] - m[2][2]) * 2.0;
|
||||
q[0] = (m[0][1] + m[1][0] ) / s;
|
||||
q[1] = 0.25 * s;
|
||||
q[2] = (m[1][2] + m[2][1] ) / s;
|
||||
q[3] = (m[2][0] - m[0][2] ) / s;
|
||||
} else { // Column 2:
|
||||
s = sqrt(1.0 + m[2][2] - m[0][0] - m[1][1]) * 2.0;
|
||||
q[0] = (m[2][0] + m[0][2] ) / s;
|
||||
q[1] = (m[1][2] + m[2][1] ) / s;
|
||||
q[2] = 0.25 * s;
|
||||
q[3] = (m[0][1] - m[1][0] ) / s;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int quat2Mat( float q[], float m[][] )
|
||||
{
|
||||
float x2, y2, z2;
|
||||
float xx, xy, xz;
|
||||
float yy, yz, zz;
|
||||
float wx, wy, wz;
|
||||
|
||||
x2 = q[0] * 2.0;
|
||||
y2 = q[1] * 2.0;
|
||||
z2 = q[2] * 2.0;
|
||||
|
||||
xx = q[0] * x2;
|
||||
xy = q[0] * y2;
|
||||
xz = q[0] * z2;
|
||||
yy = q[1] * y2;
|
||||
yz = q[1] * z2;
|
||||
zz = q[2] * z2;
|
||||
wx = q[3] * x2;
|
||||
wy = q[3] * y2;
|
||||
wz = q[3] * z2;
|
||||
|
||||
m[0][0] = 1.0 - (yy + zz);
|
||||
m[1][1] = 1.0 - (xx + zz);
|
||||
m[2][2] = 1.0 - (xx + yy);
|
||||
|
||||
m[1][0] = xy - wz;
|
||||
m[0][1] = xy + wz;
|
||||
m[2][0] = xz + wy;
|
||||
m[0][2] = xz - wy;
|
||||
m[2][1] = yz - wx;
|
||||
m[1][2] = yz + wx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
BIN
data/PoolBall.jpeg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
data/board1.jpg
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
data/board2.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
data/board3.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
data/board4.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
data/nao.jpg
Normal file
|
After Width: | Height: | Size: 385 KiB |
BIN
data/nao_blob.jpg
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
12
data/robotnik.mtl
Normal file
@ -0,0 +1,12 @@
|
||||
# File produced by Open Asset Import Library (http://www.assimp.sf.net)
|
||||
# (assimp v3.1.187496374)
|
||||
|
||||
newmtl _1_-_Default-material
|
||||
Kd 0.6 0.6 0.6
|
||||
Ka 0.588235 0.588235 0.588235
|
||||
Ks 0.9 0.9 0.9
|
||||
Ke 0 0 0
|
||||
Ns 0.43
|
||||
illum 2
|
||||
map_Kd robotnik.png
|
||||
|
||||
30111
data/robotnik.obj
Normal file
BIN
data/robotnik.png
Normal file
|
After Width: | Height: | Size: 110 KiB |