How to keep calling a function till it returns a different output than before? [duplicate] - xcode

This question already has answers here:
Randomly choosing an item from a Swift array without repeating
(6 answers)
Closed 6 years ago.
I have a function that will randomly output a SKColor.
func getRandomColor() -> SKColor{
let randomaval = arc4random_uniform(4)
var color = SKColor()
switch(randomaval)
{
case 0:
color = redColor
case 1:
color = greenColor
case 2:
color = blueColor
case 3:
color = yellowColor
default:()
}
return color
}
When two bodies collide I call this function to change colors
aball.color = getRandomColor()
if aball.color == redColor && getRandomColor() == redColor {
aball.color = getRandomColor() //to set the color to something other than red
aball.colorBlendFactor = 1.0
}
What I want to do is that, when I say aball.color = getRandomColor(), if getRandomColor() is redColor again, it needs to run the if statement again till the function returns something other than redColor. Most of the time, when my if condition is true, it calls redColor again and I can't understand how to avoid that. I basically want a different color to be returned everytime getRandomColor is called. How do I accomplish that?

How about repeatedly calling your getRandomColor() function until the result is something other than the ball's current color? :
repeat {
let newColor = getRandomColor()
} while newcolor != aball.color
aball.color = newColor
Alternatively, you could re-write getRandomColor to accept a parameter of a color it shouldn't return and then call your amended function with aball.color:
func getRandomNumber(notColor: SKColor) -> SKColor{
repeat {
let randomaval = arc4random_uniform(4)
var color = SKColor()
switch(randomaval)
{
case 0:
color = redColor
case 1:
color = greenColor
case 2:
color = blueColor
case 3:
color = yellowColor
default:()
}
let newColor = getRandomColor()
} while color != notColor
return color
}
Then, when you want to change the color of the ball:
aball.color = getRandomColor(notColor: aball.color)
another approach is to have getRandomColor track its own last result. In your initialisation decalre:
var lastrandomcolor: SKColor
then
func getRandomNumber() -> SKColor{
repeat {
let randomaval = arc4random_uniform(4)
var color = SKColor()
switch(randomaval)
{
case 0:
color = redColor
case 1:
color = greenColor
case 2:
color = blueColor
case 3:
color = yellowColor
default:()
}
let newColor = getRandomColor()
} while color != lastRandomColor
lastRandomcolor = color
return color
}
then just use:
aball.color = getRandomColor()
I prefer the 2nd approach where your pass a color that you don't want returned, as it gives you more control i.e. at certain times, you might not want the ball to be some other colour, not just it's own color. e.g. not blue if its in the sky. you could even pass an array of colours that shouldn't be returned.

Related

amchart scatterplot change background of one quadrant

Here I tried something like this but, It is colouring entire left side to right from 0 to 50.
function colourQuadrantX(start, end) {
var range = valueAxisX.axisRanges.create();
range.value = start;
range.endValue = end;
range.axisFill.fill = am4core.color("#396478");
range.axisFill.fillOpacity = 0.2;
range.grid.strokeOpacity = 0;
}
Please take a look how can solve this

Showing an image and replacing it with another image if something happens

I use a color tracking code for processing.
What I want (example):
If red is detected show image 1
If green is detected show image 2
If blue is detected show image 3
The problem is, if the last color is detected and the last image is shown, and I track now the first color the first image is not in front (I can't see it).
The whole code:
import processing.video.*;
//import hypermedia.net.*;
PImage img;
PImage img2;
PImage img3;
Capture video;
final int TOLERANCE = 20;
float XRc = 0;// XY coordinate of the center of the first target
float YRc = 0;
float XRh = 0;// XY coordinate of the center of the second target
float YRh = 0;
float XRc2 = 0; // XY coordinate of the center of the third target
float YRc2 = 0;
float XRh2 = 0;// XY coordinate of the center of the fourth target
float YRh2 = 0;
int ii=0; //Mouse click counter
color trackColor; //The first color is the center of the robot
color trackColor2; //The second color is the head of the robot
color trackColor3; //The first color is the center of the robot 2
color trackColor4; //The first color is the center of the robot 2
void setup() {
img = loadImage("IMG_4700.JPG");
img2 = loadImage("2.JPG");
img3 = loadImage("3.JPG");
size(800,800);
video = new Capture(this,640,480);
video.start();
trackColor = color(94,164,126);
trackColor2 = color(60,110,194);
trackColor3 = color(197, 76,64);
trackColor4 = color(255,0,0);
smooth();
}
void draw() {
background(0);
if (video.available()) {
video.read();
}
video.loadPixels();
image(video,0,0);
float r2 = red(trackColor);
float g2 = green(trackColor);
float b2 = blue(trackColor);
float r3 = red(trackColor2);
float g3 = green(trackColor2);
float b3 = blue(trackColor2);
float r4 = red(trackColor3);
float g4 = green(trackColor3);
float b4 = blue(trackColor3);
float r5 = red(trackColor4);
float g5 = green(trackColor4);
float b5 = blue(trackColor4);
int somme_x = 0, somme_y = 0; // pour le calcul des baricentres
int compteur = 0;
int somme_x2 = 0, somme_y2 = 0; // pour le calcul des baricentres
int compteur2 = 0;
int somme_x3 = 0, somme_y3 = 0; // pour le calcul des baricentres
int compteur3 = 0;
int somme_x4 = 0, somme_y4 = 0; // pour le calcul des baricentres
int compteur4 = 0;
for(int x = 0; x < video.width; x++) {
for(int y = 0; y < video.height; y++) {
int currentLoc = x + y*video.width;
color currentColor = video.pixels[currentLoc];
float r1 = red(currentColor);
float g1 = green(currentColor);
float b1 = blue(currentColor);
if(dist(r1,g1,b1,r2,g2,b2) < TOLERANCE) {
somme_x += x;
somme_y += y;
compteur++;
}
else if(compteur > 0) {
XRc = somme_x / compteur;
YRc = somme_y / compteur;
}
if(dist(r1,g1,b1,r3,g3,b3) < TOLERANCE) {
somme_x2 += x;
somme_y2 += y;
compteur2++;
}
else if(compteur2 > 0) {
XRh = somme_x2 / compteur2;
YRh = somme_y2 / compteur2;
}
if(dist(r1,g1,b1,r4,g4,b4) < TOLERANCE) {
somme_x3 += x;
somme_y3 += y;
compteur3++;
}
else if(compteur3 > 0) {
XRc2 = somme_x3 / compteur3;
YRc2 = somme_y3 / compteur3;
}
if(dist(r1,g1,b1,r5,g5,b5) < TOLERANCE) {
somme_x4 += x;
somme_y4 += y;
compteur4++;
}
else if(compteur4 > 0) {
XRh2 = somme_x4 / compteur4;
YRh2 = somme_y4 / compteur4;
}
}
}
// track the color and show images
boolean c1 = false;
boolean c2 = false;
boolean c3 = false;
if(XRc != 0 || YRc != 0) { // color Green detected
c1 = true;
c2 = false;
c3 = false;
}
if(XRh != 0 || YRh != 0) { // color blue detected
c2 = true;
c1 = false;
c3 = false;
}
if(XRc2 != 0 || YRc2 != 0) { // color red detected
c3 = true;
c1 = false;
c2 = false;
}
if(c1 == true) {
image(img,0,0); // show image 1
} else if (c2 == true) {
image(img2,0,0); // show image 2
} else if (c3 == true) {
image(img3,0,0); // show image 3
}
}
The important snippet:
// detect color and show images
boolean c1 = false;
boolean c2 = false;
boolean c3 = false;
if(XRc != 0 || YRc != 0) { // color Green detected
c1 = true;
c2 = false;
c3 = false;
}
if(XRh != 0 || YRh != 0) { // color blue detected
c2 = true;
c1 = false;
c3 = false;
}
if(XRc2 != 0 || YRc2 != 0) { // color red detected
c3 = true;
c1 = false;
c2 = false;
}
if(c1 == true) {
image(img,0,0); // show image 1
} else if (c2 == true) {
image(img2,0,0); // show image 2
} else if (c3 == true) {
image(img3,0,0); // show image 3
}
Screenshots:
First object is tracked and image is shown
Second object is tracked and image is shown
Third object is tracked and image is shown
My Problem:
(the first object should be tracked and the first image should me shown)
There are few things that could be improved.
In terms of efficiency, a couple of minor suggestions:
you could pre-compute the RGB components of the colours you want to track once in setup() rather than many times per second in draw() (e.g.float r2 = red(trackColor); etc.)
You could use a flat 1D loop rather than a nested loop using video.pixels[]. One minor disadvantage is that you'd need to compute the x,y position from the pixel index. Since you need to display an image and it doesn't seem to matter where, you might not even need to compute x,y.(e.g. for(int currentLoc = 0; currentLoc < video.pixels.length; currentLoc++))
In terms of the algorithm itself:
You are using a single threshold value(TOLERANCE). This will cut out anything on the left of the value which is ok, but not cut a whole range of other colours that might mess with your counter. I recommend using a range (e.g. MIN_TOLERANCE,MAX_TOLERANCE).
You are using R,G,B colour space. R,G,B colours don't mix together as we'd expect. A more perceptual colour space will behave as you'd expect (e.g. orange will be closer to red and yellow). For that you would need to convert from RGB to CIE XYZ, then to Lab*, compute the euclidean distance between two colours, then convert the result back to RGB if you need to display it.You can find an example here. It's in OpenFrameworks (c++), but you should be able to see the similarities to Processing and port it.
There is another option: HSB colour space. More on that bellow
You could draw some visualisation of how your code is segmenting the image. It will be faster to get an idea of which values work better, which don't
I recommend trying the OpenCV for Processing library which uses a more modern OpenCV library under the hood and provides more functionalities and great examples. One of them is particularly useful for you: HueRangeSelection
Give it a go. Notice you can move the mouse to shift the range and if you hold a key pressed you can increase the range. For example, here's how the quick demo of it with your images. (The HSB range threshold result is displayed smaller in the bottom right corner):
From my own experience, I'd recommend not using shiny/reflective materials (e.g. the coke can). You can see in the image above the segmentation isn't as good as on the green and blue objects with flatter colours. Because the can is reflective, it will appear to have different colours not only with global lighting changes, but also it's position/rotation and objects close to it. It's a pain to cater to all these.
Also, to take the HueRange example further you can:
Apply a morphologic filter (for example erode(), then dilate()) to remove some of the noise (smaller white pixel patches). At this point you can count the number of white pixels per colour range and decide which image to display.
find contours on the filtered image which will can use to determine the x,y,width,height of the region that falls within a range of the colour you want to track.
Good luck and most importantly have fun!
Hummm... Without running the code, I'd bet the problem is that you rely on the coordinates (XRc and it's siblings) being zero to choose which image to use. They are all initiated to 0, so the first run goes fine, but... you never reset them to zero, do you? So after they all have being changed once by detecting the 3 colors your test became useless. Perhaps you can reset them all to zero whenever a color is detected.
And maybe you don't need the boolean at all...
What do think of this?
PSEUDO
//global
PImage imgs = new PImage[3];
int imageToDispaly = 0;
//all the stuff...
if(XRc != 0 || YRc != 0) { // color Green detected
// not sure this will work, but the idea is some thing like this.
XRh = YRh = XRc2 = YRc2 = 0;
imageToDispaly = 0;
}
if(XRh != 0 || YRh != 0) { // color blue detected
XRc = YRc = XRc2 = YRc2 = 0;
imageToDispaly = 1;
}
if(XRc2 != 0 || YRc2 != 0) { // color red detected
XRh = YRh = XRc = YRc = 0;
imageToDispaly = 2;
}
// at appropriated time...
image(imgs[imageToDispaly], x, y);

NSBezierPath: How to control numbers of segments with LineDashStyle ?

I want to draw a square, with NSBezierPath. The border of square must to be discontinue so i use dashStyle, but I don't have any control on numbers of segments created.
On Apple documentation the explanation is a bit vague. They said that "When setting a line dash pattern, you specify the width (in points) of each successive solid or transparent swatch".
So i think, I need a way to get the length of a curved bezier.
Does anyone have an idea how i can achive that ?
extension NSBezierPath {
var lenght:Double {
get{
let flattenedPath = self.bezierPathByFlatteningPath
let segments = flattenedPath.elementCount
var lastPoint:NSPoint = NSZeroPoint
var point:NSPoint = NSZeroPoint
var size :Double = 0
for i in 0...segments - 1 {
let e:NSBezierPathElement = flattenedPath.elementAtIndex(i, associatedPoints: &point)
if e == .MoveToBezierPathElement {
lastPoint = point
} else {
let distance:Double = sqrt(pow(Double(point.x - lastPoint.x) , 2) + pow(Double(point.y - lastPoint.y) , 2))
size += distance
lastPoint = point
}
}
return size
}
}
}
With this extension i get the "approximative" length of a bezier. After that, everything is simple:
let myPath = NSBezierPath(roundedRect:myRect, xRadius:50, yRadius:50)
let pattern = myPath.length / (numbersOfSegments * 2) // we divide the length to double of segments we need.
myPath.setLineDash([CGFloat(pattern),CGFloat(pattern)], countL:2 , phase: 0)
myPath.stroke()

Openlayers 3 pan map always to the left

I am developing an app where I get a user location from the server and I display it on the map.
I am also provided with an angle so I can display in which direction the user is looking.
There is also a possibility to open a sidepanel for some more information. In this situation I have to move the map center left which I do like this.
var center = map.getView().getCenter();
var pan = ol.animation.pan({
duration: 500,
source: (center)
});
if (isSidePanelVisible) {
center[0] += 1000;
} else {
center[0] -= 1000;
}
map.beforeRender(pan);
map.getView().setCenter(center);
This works great while the user is "looking" north but not when I rotate the map by an angle. Is there a quick way to pan the map always left by a certain amount regardles of angle?
You could use ol.Map.html#getPixelFromCoordinate to convert the center to pixel coordinates, do your calculation and then convert back to real coordinates with ol.Map.html#getCoordinateFromPixel:
var view = map.getView();
var center = view.getCenter();
var centerInPx = map.getPixelFromCoordinate(center);
var newCenterInPx = [centerInPx[0] - 100, centerInPx[1]];
var newCenter = map.getCoordinateFromPixel(newCenterInPx);
view.setCenter(newCenter);
http://jsfiddle.net/6mh110xv/1/
To pan left , right , bottom , top , you can use the following code :
panMap(direction: string) {
let newCenterInPx;
let center = map.getView().getCenter();
let centerInPx = map.getPixelFromCoordinate(center);
switch (direction) {
case 'left': newCenterInPx = [centerInPx[0] - 100, centerInPx[1]]; break;
case 'right': newCenterInPx = [centerInPx[0] + 100, centerInPx[1]]; break;
case 'top': newCenterInPx = [centerInPx[0], centerInPx[1] - 100]; break;
case 'bottom': newCenterInPx = [centerInPx[0], centerInPx[1] + 100]; break;
}
var newCenter = map.getCoordinateFromPixel(newCenterInPx);
map.getView().setCenter(newCenter);
}

kendoChart: Is there any way to display multiple series of differing value scales using a single valueAxis?

I'm using a single kendoChart to display up to 10 lines of data.
Each line represents process data that may have widely different context and min/max ranges, but all lines are related in time, the categoryAxis. When displayed, each valueAxis correctly shows the scale for the corresponding line.
However, with 10 lines, the 10 valueAxes take up far too much of the screen to be usable for my requirements.
I tried hiding all axes except one with the expectation that the chart would expand to fill up the space taken by the hidden axes, but that does
not happen. I get a lone axis surrounded by blank space and the chart's plot area remains the same size.
I tried setting all of the series to use the same valueAxis and then varying the valueAxis min/max per the active channel as chosen by clicking
a legend item. This expands the plot area as needed, but removes the ability to see all lines since the scale is specific to one line.
Is it possible for kendoChart to show multiple plots independently from a single valueAxis (e.g. a line with values between 0.5 and 0.7 would appear scaled to the full chart area, and so would a line with values between 25 and 100, but the valueAxis might display either scale.)
The solution I used for this problem is more code than I expected to need. Perhaps Telerik's other products have an API for this.
Essentially, I maintain a structure outside of the kendoChart that stores the real data for each series, and this real data is mapped to the expected scale of the currently visible valueAxis. The mapping function is the standard transform from one scale into another.
The valueAxis is 'swapped' depending on which legend item is clicked, and that event triggers a redraw on the chart where all the series data is mapped to the 'active' axis.
Some code snippets. A series is also described as a channel.
// The data structure.
this._channelDescriptors.push({
fullName: ch.fullName || "",
axisTitle: (ch.fullName + axisEUString) || "",
axisFont: ch.axisFont || "",
axisColor: ch.color || "#000000",
realData: [],
minData: Number.MAX_VALUE,
maxData: Number.MIN_VALUE
});
// This event causes the switching of valueAxis for all members of the series.
$("#" + chartID).kendoChart({
// Other kendoChart configurations
//
legendItemClick: function (e) {
var idx = e.seriesIndex;
sncTrender.updateAxis(idx);
e.preventDefault();
},
tooltip: {
visible: true,
template: "#=series.name# : #=kendo.format('{0:N4}', dataItem.realValue)#<br />#=kendo.format('{0:MM-dd HH:mm:ss.fff}', dataItem.Time)#",
},
//
// Other kendoChart configurations
});
// All code snippets are members of a wrapper object.
updateAxis: function (ch) {
if (this.series[ch].visible) {
this.setAxis(ch);
}
},
// Every series is set to the same valueAxis via the selected series' valueAxis.name property.
setAxis: function (ch) {
var i,
channel = this._channelDescriptors[ch];
this._currentChannel = ch;
for (i = 0; i < this.series.length; i++) {
this.series[i].axis = this._channelDescriptors[ch].fullName;
}
// Set the active valueAxis properties. This is the only axis visible maintained for the chart.
this.valueAxis.name = channel.fullName;
this.valueAxis.title.text = channel.axisTitle;
this.valueAxis.title.font = channel.axisFont;
this.valueAxis.line.color = channel.axisColor;
},
// The mapping occurs here, and the transform calculation is this line
// Y: (yRange * (chDesc.realData[k].realValue - newMin) / newRange) + this.valueAxis.min,
//
updateChart: function (allTrends) {
// ...
timeStamps = trendDataResponse.curve.Timestamp;
t1 = trendArgs.t1;
t2 = trendArgs.t2;
xValues = trendDataResponse.curve.X;
yValues = trendDataResponse.curve.Y;
pointCount = xValues.length;
min = Number.MAX_VALUE;
max = Number.MIN_VALUE;
categoryTimes = [pointCount];
newData = [];
for (l = 0; l < pointCount; l++) {
min = Math.min(min, yValues[l]);
max = Math.max(max, yValues[l]);
ts = new Date(timeStamps[l]);
categoryTimes[l] = ts;
// The Y data will be plotted on the chart, but the cursor tooltip will
// use the realValue data. In this way, the series can be visible regardless of
// the valueAxis scaling, but the actual data is also available. Refer to the
// tooltip template.
newData.push({ X: xValues[l], Y: yValues[l], realValue: yValues[l], Time: ts });
}
// Real data for each channel is stored in channelDescriptors.
chDesc = this._channelDescriptors[channelID];
chDesc.realData = newData;
chDesc.minData = min;
chDesc.maxData = max;
// The valueAxis min/max is set only for the 'active' series.
if (this._currentChannel === channelID) {
this.categoryAxis.categories = categoryTimes;
yRange = max - min;
scaleAdjustment = yRange * SNC.CONST_yAxisScaleAdjustmentFactor;
this.valueAxis.min = min - scaleAdjustment;
this.valueAxis.max = max + scaleAdjustment;
}
}
// Scale curves to current axis.
// Use real data for the current series.
for (j = 0; j < this.series.length; ++j) {
chDesc = this._channelDescriptors[j];
if (j === this._currentChannel) {
this.series[j].data = chDesc.realData;
continue;
}
// Use mapped data for all other series.
recalcData = [];
newMin = chDesc.minData;
newMax = chDesc.maxData;
newRange = newMax - newMin;
rangeAdjustment = newRange * SNC.CONST_yAxisScaleAdjustmentFactor;
newMin = newMin - rangeAdjustment;
newMax = newMax + rangeAdjustment;
for (k = 0; k < chDesc.realData.length; ++k) {
recalcData.push({
X: chDesc.realData[k].X,
Y: (yRange * (chDesc.realData[k].realValue - newMin) / newRange) + this.valueAxis.min,
realValue: chDesc.realData[k].realValue,
Time: chDesc.realData[k].Time,
});
}
this.series[j].data = recalcData;
}
chart.redraw();
}

Resources