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 |