Which linear "movement strategy" is better in a canvas? - animation

I've seen 2 moving strategies:
Using time:
point = function(x) {
this.x = x;
this.moveTo = function(x, originalX, speed, startTime) {
var direction = x > originalX ? 1 : (x < originalX ? -1 : 0);
if(direction == 0) return;
// speed is px/seconds, so divide time by 1k
var dt = (new Date().getTime() - startTime) / 1000;
var speedX = speed * direction;
var newX = originalX + speedX * dt;
if (direction == 1 && newX => x || direction == -1 && newX <= x) {
// we went past, stay put and finish this
this.x = x;
return;
}
requestAnimationFrame(function(){
this.moveTo(x, originalX, speed, startTime);
});
}
}
Or using percentages per frame?
point = function(x) {
this.x = x;
this.moveTo = function(x, originalX, percentagePerFrame, p) {
if (p >= 1) return;
var dx = (x - originalX) * p;
this.x = x + dx;
requestAnimationFrame(function(){
this.moveTo(x, originalX, percentagePerFrame, p + percentagePerFrame);
});
}
}
Is it irrelevant? I can tell that using time&speed is slower, but... does it matter? is there a better way?

Just some thoughts:
requestAnimationFrame ("RAF") will skip frames if the system is busy.
So if you need the animation to adjust for the skipped frames, then use elapsed time as in your first example.
If you just want an object to move with every frame and the "jitter" caused by missed frames don't impact your design, then your second example is just fine.
Note that RAF in modern browsers will feed you the elapsed time so you don't need the manual variables and calculations that you've done in the first example.

Related

What is the optimal way to generate a set of integers, each as close as possible to Y, whose sum is X?

X can be a positive decimal or integer and Y is a positive integer. X >= 2 * Y. What is the optimal way (in terms of code performance) to accomplish this?
Context
I am creating a damage over time (DoT) batching algorithm for a game. X is the total duration of the DoT effect and Y is the desired duration (size) of each batch.
If you simply take X / Y you usually end up with a remainder, which introduces significant rounding error. So I want to find a set of numbers that adds up to the X, but wherein each number is as close as possible to Y.
Current Solution
totalDuration is X and batchTarget is Y. BatchDurations is a List of integers containing the batches (the output of the function).
public void CalcBatchRate(float ticksPerUpdate, int batchTarget)
{
float totalDuration = Effect.Duration * ticksPerUpdate;
float batches = totalDuration / batchTarget;
float batchesRemainder = batches % 1;
float batchSize = batchTarget;
if (batchesRemainder != 0)
{
batches -= batchesRemainder;
batchSize = totalDuration / batches;
// Check if adding another batch improves batch size:
while (Math.Abs(batchSize - batchTarget) > Math.Abs((totalDuration / (batches + 1)) - batchTarget))
{
batches++;
batchSize = totalDuration / batches;
}
// Correct for decimals:
float batchSizeRemainder = batchSize % 1;
if (batchSizeRemainder != 0)
{
// Based on the size of the remainder, determine how many batches to round up/down:
int roundDown = (int)Math.Round(batches * Math.Abs(1 - batchSizeRemainder), 0);
int roundUp = (int)(batches - roundDown);
for (int i = roundDown; i > 0; i--)
{
BatchDurations.Add((int)(batchSize - batchSizeRemainder));
}
for (int i = roundUp; i > 0; i--)
{
BatchDurations.Add((int)(batchSize + (1 - batchSizeRemainder)));
}
}
else
{
for (int i = (int)batches; i > 0; i--)
{
BatchDurations.Add((int)batchSize);
}
}
}
else
{
for (int i = (int)batches; i > 0; i--)
{
BatchDurations.Add((int)batchSize);
}
}
}
Not sure what exactly you are trying to calculate, but here's some Python code.
def subdivide(totalDuration, batchTarget):
print (f'Aiming to have total duration of {totalDuration} with batches of size {batchTarget}')
nBatches = totalDuration // batchTarget
totalError = totalDuration % batchTarget
if totalError > batchTarget - totalError:
nBatches = nBatches + 1
totalError = totalError - batchTarget
error = totalError // nBatches
extraNum = totalError % nBatches
regularNum = nBatches - extraNum
regularSize = batchTarget + error
extraSize = regularSize + 1
checkTotalDuration = regularNum * regularSize + extraNum * extraSize
print (f'{regularNum} batches of size {regularSize} and {extraNum} batches of size {extraSize}')
print (f'have total duration of {checkTotalDuration}')

How to simplify shapes for triangulation with three.js and jsclipper

I try to display geometry which is constructed by constructpath commands like moveto lineto beziercurveto in Three.js.
Therefore I create a THREE.ShapePath(); and execute the command toShapes(isClockwise).
After this I use THREE.ExtrudeBufferGeometry to create the 3D shape.
Unfortunately the shapes are sometimes really complex and are not created correctly which means they are distorted.
Using libtess as triangulation library solves some issues. But I have still distorted geometry.
Now I want to use jsclipper to simplify the shapes prior triangulation.
I modified three.js in such way:
in the method addShape in ExtrudeBufferGeometry I have added:
$.each(vertices, function(index, item) {
vertices[index]['X'] = vertices[index]['x'];
vertices[index]['Y'] = vertices[index]['y'];
delete vertices[index]['x'];
delete vertices[index]['y'];
});
if (holes[0]) {
for (i = 0; i < holes.length; i++ ) {
$.each(holes[i], function(index, item) {
holes[i][index]['X'] = holes[i][index]['x'];
holes[i][index]['Y'] = holes[i][index]['y'];
delete holes[i][index]['x'];
delete holes[i][index]['y'];
});
}
}
var scale = 100;
ClipperLib.JS.ScaleUpPaths([vertices], scale);
if (holes[0]) {
ClipperLib.JS.ScaleUpPaths(holes, scale);
}
vertices = ClipperLib.Clipper.SimplifyPolygons([vertices], ClipperLib.PolyFillType.pftNonZero);
// or ClipperLib.PolyFillType.pftEvenOdd
if (holes[0]) {
holes = ClipperLib.Clipper.SimplifyPolygons(holes, ClipperLib.PolyFillType.pftNonZero);
// or ClipperLib.PolyFillType.pftEvenOdd
}
// var cleandelta = 0.1; // 0.1 should be the appropriate delta in different cases
// vertices = ClipperLib.Clipper.CleanPolygons([vertices], cleandelta * scale);
// if (holes[0]) {
// holes = ClipperLib.Clipper.CleanPolygons(holes, cleandelta * scale);
// }
ClipperLib.JS.ScaleDownPaths(vertices, scale);
if (holes[0]) {
ClipperLib.JS.ScaleDownPaths(holes, scale);
}
for (i = 0; i < vertices.length; i++ ) {
$.each(vertices[i], function(index, item) {
vertices[i][index]['x'] = vertices[i][index]['X'];
vertices[i][index]['y'] = vertices[i][index]['Y'];
delete vertices[i][index]['X'];
delete vertices[i][index]['Y'];
});
}
if (holes[0]) {
for (i = 0; i < holes.length; i++ ) {
$.each(holes[i], function(index, item) {
holes[i][index]['x'] = holes[i][index]['X'];
holes[i][index]['y'] = holes[i][index]['Y'];
delete holes[i][index]['X'];
delete holes[i][index]['Y'];
});
}
}
Now I can see that the vertices are "reduced".
But var faces = ShapeUtils.triangulateShape( vertices, holes ); doesn't generate faces for some examples anymore.
Please can one help how to simplify the shapes correctly?
A bit hard to figure out what the problem is actually. Clipper (also when using SimplifyPolygons or SimplifyPolygon) can only produce weakly-simple polygons, which means that there can be pseudo-duplicate points: although sequential coordinates are quaranteed to be not indentical, some of the next points can share the same coordinate. Also a coordinate can be on the line between two points.
After simplifying (or any other boolean operation) you could make a cleaning step using Offsetting with a small negative value: https://sourceforge.net/p/jsclipper/wiki/documentation/#clipperlibclipperoffsetexecute.
This possibly removes all of the pseudo-duplicate points.
I have made also a float version of Clipper (http://jsclipper.sourceforge.net/6.4.2.2_fpoint/). It is extensively tested, but because Angus Johnson, the author of the original C# Clipper (of which JS-version is ported from), has thought that using floats causes robustness problems although according to my tests the are no such, the original C# float version does not exists. The float version is simpler to use and you can try there a small negative offset: eg. -0.001 or -0.01.
You could also give a try to PolyTree or ExPolygons (https://sourceforge.net/p/jsclipper/wiki/ExPolygons%20and%20PolyTree%206/). ExPolygons can be used to get holes and contours and PolyTree can be used to get the full parent-child-relationship of holes and contours.
The last resort is a broken-pen-nib -function. It detects all pseudo-duplicate points and make a broken-pen-nib -effect to them, so that the result is free of any duplicates. The attached images shows what this effect means using large nib-effect-value to make the effect meaning clearer. Three.js polygon triangulation fails in pseudo duplicate points. There are a discussion https://github.com/mrdoob/three.js/issues/3386 of this subject.
// Make polygons to simple by making "a broken pen tip" effect on each semi-adjacent (duplicate) vertex
// ORIGPOLY can be a contour
// or exPolygon structure
function BreakPenNibs(ORIGPOLY, dist, scale)
{
if (!dist || dist < 0) return;
var sqrt = Math.sqrt;
var allpoints = {}, point = {};
var key = "";
var currX = 0.0,
currY = 0.0;
var prevX = 0.0,
prevY = 0.0;
var nextX = 0.0,
nextY;
var x = 0.0,
y = 0.0,
length = 0.0,
i = 0,
duplcount = 0,
j = 0;
var prev_i = 0,
next_i = 0,
last_i;
var extra_vertices = new Array(100),
moved_vertices = new Array(100);
// Get first all duplicates
var duplicates = new Array(100),
indexi = "",
indexstr = "",
arraystr = "",
polys, outer, holes;
if (ORIGPOLY instanceof Array)
{
outer = ORIGPOLY;
}
else if (ORIGPOLY.outer instanceof Array)
{
outer = ORIGPOLY.outer;
}
else return;
if (ORIGPOLY.holes instanceof Array) holes = ORIGPOLY.holes;
else holes = [];
polys = [outer].concat(holes);
var polys_length = polys.length;
// Get first max lenght of arrays
var max_index_len = 0;
var arr_len;
i = polys_length;
while (i--)
{
arr_len = polys[i].length;
if (arr_len > max_index_len) max_index_len = arr_len;
}
max_index_len = max_index_len.toString().length;
var max_polys_length = polys_length.toString().length;
var poly;
j = polys_length;
var scaling = scale/10;
while (j--)
{
poly = polys[j];
ilen = poly.length;
i = ilen;
while (i--)
{
point = poly[i];
//key = Math.round(point.X) + ":" + Math.round(point.Y);
key = (Math.round(point.X / scaling) * scaling)
+ ":" + (Math.round(point.Y / scaling) * scaling);
indexi = allpoints[key];
if (typeof (indexi) != "undefined")
{
// first found duplicate
duplicates[duplcount] = indexi;
duplcount++;
arraystr = j.toString();
while (arraystr.length < max_polys_length) arraystr = "0" + arraystr;
indexstr = i.toString();
while (indexstr.length < max_index_len) indexstr = "0" + indexstr;
duplicates[duplcount] = arraystr + "." + indexstr;
duplcount++;
}
arraystr = j.toString();
while (arraystr.length < max_polys_length) arraystr = "0" + arraystr;
indexstr = i.toString();
while (indexstr.length < max_index_len) indexstr = "0" + indexstr;
allpoints[key] = arraystr + "." + indexstr;
}
}
if (!duplcount) return;
duplicates.length = duplcount;
duplicates.sort();
//console.log(JSON.stringify(duplicates));
var splitted, poly_index = 0,
nth_dupl = 0;
var prev_poly_index = -1;
poly_index = 0;
for (j = 0; j < duplcount; j++)
{
splitted = duplicates[j].split(".");
poly_index = parseInt(splitted[0], 10);
if (poly_index != prev_poly_index) nth_dupl = 0;
else nth_dupl++;
i = parseInt(splitted[1], 10);
poly = polys[poly_index];
len = poly.length;
if (poly[0].X === poly[len - 1].X &&
poly[0].Y === poly[len - 1].Y)
{
last_i = len - 2;
}
else
{
last_i = len - 1;
}
point = poly[i];
// Calculate "broken pen tip" effect
// for current point by finding
// a coordinate at a distance dist
// along the edge between current and
// previous point
// This is inlined to maximize speed
currX = point.X;
currY = point.Y;
if (i === 0) prev_i = last_i; // last element in array
else prev_i = i - 1;
prevX = poly[prev_i].X;
prevY = poly[prev_i].Y;
x=0;y=0;
if (!point.Collinear)
{
length = sqrt((-currX + prevX) * (-currX + prevX) + (currY - prevY) * (currY - prevY));
//console.log(length);
x = currX - (dist * (currX - prevX)) / length;
y = currY - (dist * (currY - prevY)) / length;
}
// save the found (calculated) point
moved_vertices[j] = {
X: x,
Y: y,
Collinear:point.Collinear,
index: i,
poly_index: poly_index
};
// "broken nib effect" for next point also
if (i == len - 1) next_i = 0;
else next_i = i + 1;
nextX = poly[next_i].X;
nextY = poly[next_i].Y;
x=0;y=0;
if (!point.Collinear)
{
length = sqrt((-currX + nextX) * (-currX + nextX) + (currY - nextY) * (currY - nextY));
x = currX - (dist * (currX - nextX)) / length;
y = currY - (dist * (currY - nextY)) / length;
}
// save the found (calculated) point
extra_vertices[j] = {
X: x,
Y: y,
Collinear:point.Collinear,
index: i + nth_dupl,
poly_index: poly_index
};
prev_poly_index = poly_index;
}
moved_vertices.length = extra_vertices.length = duplcount;
//console.log("MOVED:" + JSON.stringify(moved_vertices));
//console.log("EXTRA:" + JSON.stringify(extra_vertices));
// Update moved coordinates
i = duplcount;
var point2;
while (i--)
{
point = moved_vertices[i];
x = point.X;
y = point.Y;
// Faster than isNaN: http://jsperf.com/isnan-alternatives
if (x != x || x == Infinity || x == -Infinity) continue;
if (y != y || y == Infinity || y == -Infinity) continue;
point2 = polys[point.poly_index][point.index];
point2.X = point.X;
point2.Y = point.Y;
point2.Collinear = point.Collinear;
}
// Add an extra vertex
// This is needed to remain the angle of the next edge
for (i = 0; i < duplcount; i++)
{
point = extra_vertices[i];
x = point.X;
y = point.Y;
// Faster than isNaN: http://jsperf.com/isnan-alternatives
if (x != x || x == Infinity || x == -Infinity) continue;
if (y != y || y == Infinity || y == -Infinity) continue;
polys[point.poly_index].splice(point.index + 1, 0,
{
X: point.X,
Y: point.Y,
Collinear: point.Collinear
});
}
// Remove collinear points
// and for some reason coming
// sequential duplicates
// TODO: check why seq. duplicates becomes
j = polys.length;
var prev_point = null;
while (j--)
{
poly = polys[j];
ilen = poly.length;
i = ilen;
while (i--)
{
point = poly[i];
if(prev_point!=null && point.X == prev_point.X && point.Y == prev_point.Y) poly.splice(i, 1);
else
if(point.Collinear) poly.splice(i, 1);
prev_point = point;
}
}
//console.log(JSON.stringify(polys));
// because original array is modified, no need to return anything
}
var BreakPenNipsOfExPolygons = function (exPolygons, dist, scale)
{
var i = 0,
j = 0,
ilen = exPolygons.length,
jlen = 0;
for (; i < ilen; i++)
{
//if(i!=4) continue;
BreakPenNibs(exPolygons[i], dist, scale);
}
};

Adaptive algorithm for filtering gyroscope data

Is there an adaptive algorithm for filtering gyroscope noise?
My app currently has a startup dialog to calibrate gyroscope, where it asks user to put the phone on the table for 5 seconds, and records min/max values of gyro data collected in these 5 seconds, then the app discards all values between that min/max, that's technically a high-pass filter.
The adaptive algorithm would determine these min/max values automatically over time, without any dialogs.
Something like storing last 100 values, and finding min/max of these values, but how do I know which values represent movement, and which are zero movement + noise?
I've looked into Kalman filter, but it's for a combined gyroscope + accelerometer sensors.
The gyroscope in my phone is not only noisy, but also has shifted zero coordinate, so when the phone is lying perfectly still, the gyroscope reports constant small rotation.
If I understand correctly, a very simple heuristic such as finding the mean of the data and defining a threshold that signifies true movement should both combat the offset zero coordinate and give pretty accurate peak recognition.
// Initialize starting mean and threshold
mean = 0
dataCount = 0
thresholdDelta = 0.1
def findPeaks(data) {
mean = updateMean(data)
for point in data {
if (point > mean + thresholdDelta) || (point < mean - thresholdDelta) {
peaks.append(point)
}
}
max = peaks.max()
min = peaks.min()
thresholdDelta = updateThreshold(max, min, mean)
return {max, min}
}
def updateThreshold(max, min) {
// 1 will make threshold equal the average peak value, 0 will make threshold equal mean
weight = 0.5
newThreshold = (weight * (max - min)) / 2
return newThreshold
}
def updateMean(data) {
newMean = (sum(data) + (dataCount * mean)) / (dataCount + data.size)
dataCount += data.size
return newMean
}
Here we have a threshold and mean that will update over time to become more accurate to present data.
If you have peaks that vary quite strongly (say your largest of peaks can be quadruple the smallest) you would want to set your threshold weight accordingly (for our example, 0.25 would just catch the smallest of your peaks, in theory.)
Edit:
I think doing things like averaging your thresholds would probably make it more resistant to decay from small peaks.
thresholdCount = 0
def updateThreshold(max, min) {
// 1 will make threshold equal the average peak value, 0 will make threshold equal mean
weight = 0.5
newThreshold = (weight * (max - min)) / 2
averagedThreshold = (newThreshold + (thresholdCount * thresholdDelta)) / (thresholdCount + 1)
return averagedThreshold
}
Here's the piece of code I ended up with (Java, Android).
The algorithm takes very large initial values for filter range, and gradually decrease them, and it filters out movement by comparing input data to the previous filter range, and discarding 10 last measured values if it detects movement.
It works best when the phone is lying still on the table, but still works reasonably okay when the phone is moved and rotated.
class GyroscopeListener implements SensorEventListener
{
// Noise filter with sane initial values, so user will be able
// to move gyroscope during the first 10 seconds, while the noise is measured.
// After that the values are replaced by noiseMin/noiseMax.
final float filterMin[] = new float[] { -0.05f, -0.05f, -0.05f };
final float filterMax[] = new float[] { 0.05f, 0.05f, 0.05f };
// The noise levels we're measuring.
// Large initial values, they will decrease, but never increase.
float noiseMin[] = new float[] { -1.0f, -1.0f, -1.0f };
float noiseMax[] = new float[] { 1.0f, 1.0f, 1.0f };
// The gyro data buffer, from which we care calculating min/max noise values.
// The bigger it is, the more precise the calclations, and the longer it takes to converge.
float noiseData[][] = new float[200][noiseMin.length];
int noiseDataIdx = 0;
// When we detect movement, we remove last few values of the measured data.
// The movement is detected by comparing values to noiseMin/noiseMax of the previous iteration.
int movementBackoff = 0;
// Difference between min/max in the previous measurement iteration,
// used to determine when we should stop measuring, when the change becomes negligilbe.
float measuredNoiseRange[] = null;
// How long the algorithm is running, to stop it if it does not converge.
int measurementIteration = 0;
public GyroscopeListener(Context context)
{
SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
if ( manager == null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) == null )
return;
manager.registerListener(gyro, manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
SensorManager.SENSOR_DELAY_GAME);
}
public void onSensorChanged(final SensorEvent event)
{
boolean filtered = true;
final float[] data = event.values;
if( noiseData != null )
collectNoiseData(data);
for( int i = 0; i < 3; i++ )
{
if( data[i] < filterMin[i] )
{
filtered = false;
data[i] -= filterMin[i];
}
else if( data[i] > filterMax[i] )
{
filtered = false;
data[i] -= filterMax[i];
}
}
if( filtered )
return;
// Use the filtered gyroscope data here
}
void collectNoiseData(final float[] data)
{
for( int i = 0; i < noiseMin.length; i++ )
{
if( data[i] < noiseMin[i] || data[i] > noiseMax[i] )
{
// Movement detected, this can converge our min/max too early, so we're discarding last few values
if( movementBackoff < 0 )
{
int discard = 10;
if( -movementBackoff < discard )
discard = -movementBackoff;
noiseDataIdx -= discard;
if( noiseDataIdx < 0 )
noiseDataIdx = 0;
}
movementBackoff = 10;
return;
}
noiseData[noiseDataIdx][i] = data[i];
}
movementBackoff--;
if( movementBackoff >= 0 )
return; // Also discard several values after the movement stopped
noiseDataIdx++;
if( noiseDataIdx < noiseData.length )
return;
measurementIteration++;
if( measurementIteration > 5 )
{
// We've collected enough data to use our noise min/max values as a new filter
System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length);
System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length);
}
if( measurementIteration > 15 )
{
// Finish measuring if the algorithm cannot converge in a long time
noiseData = null;
measuredNoiseRange = null;
return;
}
noiseDataIdx = 0;
boolean changed = false;
for( int i = 0; i < noiseMin.length; i++ )
{
float min = 1.0f;
float max = -1.0f;
for( int ii = 0; ii < noiseData.length; ii++ )
{
if( min > noiseData[ii][i] )
min = noiseData[ii][i];
if( max < noiseData[ii][i] )
max = noiseData[ii][i];
}
// Increase the range a bit, for safe conservative filtering
float middle = (min + max) / 2.0f;
min += (min - middle) * 0.2f;
max += (max - middle) * 0.2f;
// Check if range between min/max is less then the current range, as a safety measure,
// and min/max range is not jumping outside of previously measured range
if( max - min < noiseMax[i] - noiseMin[i] && min >= noiseMin[i] && max <= noiseMax[i] )
{
// Move old min/max closer to the measured min/max, but do not replace the values altogether
noiseMin[i] = (noiseMin[i] + min * 4.0f) / 5.0f;
noiseMax[i] = (noiseMax[i] + max * 4.0f) / 5.0f;
changed = true;
}
}
if( !changed )
return;
// Determine when to stop measuring - check that the previous min/max range is close enough to the current one
float range[] = new float[noiseMin.length];
for( int i = 0; i < noiseMin.length; i++ )
range[i] = noiseMax[i] - noiseMin[i];
if( measuredNoiseRange == null )
{
measuredNoiseRange = range;
return; // First iteration, skip further checks
}
for( int i = 0; i < range.length; i++ )
{
if( measuredNoiseRange[i] / range[i] > 1.2f )
{
measuredNoiseRange = range;
return;
}
}
// We converged to the final min/max filter values, stop measuring
System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length);
System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length);
noiseData = null;
measuredNoiseRange = null;
}
public void onAccuracyChanged(Sensor s, int a)
{
}
}

I made a processing program that generates a mandelbrot set but don't know how to effectively implement a zoom method

I'm not sure if it is possible in processing but I would like to be able to zoom in on the fractal without it being extremely laggy and buggy. What I currently have is:
int maxIter = 100;
float zoom = 1;
float x0 = width/2;
float y0 = height/2;
void setup(){
size(500,300);
noStroke();
smooth();
}
void draw(){
translate(x0, y0);
scale(zoom);
for(float Py = 0; Py < height; Py++){
for(float Px = 0; Px < width; Px++){
// scale pixel coordinates to Mandelbrot scale
float w = width;
float h = height;
float xScaled = (Px * (3.5/w)) - 2.5;
float yScaled = (Py * (2/h)) - 1;
float x = 0;
float y = 0;
int iter = 0;
while( x*x + y*y < 2*2 && iter < maxIter){
float tempX = x*x - y*y + xScaled;
y = 2*x*y + yScaled;
x = tempX;
iter += 1;
}
// color pixels
color c;
c = pickColor(iter);
rect(Px, Py,1,1);
fill(c);
}
}
}
// pick color based on time pixel took to escape (number of iterations through loop)
color pickColor(int iters){
color b = color(0,0,0);
if(iters == maxIter) return b;
int l = 1;
color[] colors = new color[maxIter];
for(int i = 0; i < colors.length; i++){
switch(l){
case 1 : colors[i] = color(255,0,0); break;
case 2 : colors[i] = color(0,0,255); break;
case 3 : colors[i] = color(0,255,0); break;
}
if(l == 1 || l == 2) l++;
else if(l == 3) l = 1;
else l--;
}
return colors[iters];
}
// allow zooming in and out
void mouseWheel(MouseEvent event){
float direction = event.getCount();
if(direction < 0) zoom += .02;
if(direction > 0) zoom -= .02;
}
// allow dragging back and forth to change view
void mouseDragged(){
x0+= mouseX-pmouseX;
y0+= mouseY-pmouseY;
}
but it doesn't work very well. It works alright at the size and max iteration I have it set to now (but still not well) and is completely unusable at larger sizes or higher maximum iterations.
The G4P library has an example that does exactly this. Download the library and go to the G4P_MandelBrot example. The example can be found online here.
Hope this helps!

Changing Y-axis of spritesheet with click to move

I'm new here and to programming. I have been searching for a while, though I can't find anything to help with the problem.
I'm trying to make my spritesheet cycle through the different walking frames of my spritesheet, I have done it easily with IsKeyDown but when it comes to using the mouse to walk somewhere it took me a while to nut out a 'bad' solution:
if (destination.X > position.X)
currentFrame.Y = 6;
if (destination.X > position.X && destination.Y >= position.Y + 35)
currentFrame.Y = 7;
if (destination.X > position.X && destination.Y <= position.Y - 35)
currentFrame.Y = 5;
It sort of works, but was wondering if there was a better work-around for this.
What I want is to be able to click on the game-screen and the appropriate sprite row be selected, relative to sprites current position and destination, to make it animate the proper way.
Sorry if this has been asked before, but I have searched around for a few hours before posting this and found nothing.
I am a bit unclear as to exactly what you are doing. Do you only have 2 sprites, one for left and one for right? Thats all i can see in your current code, but you are referring to animating. I am going to assume that you have a full set of sprites to animate walking, in which case I think this tutorial will cover what you need:
http://coderplex.blogspot.ca/2010/04/2d-animation-part-1-basics.html
(more specifically, part 4 of the tutorial: http://coderplex.blogspot.ca/2010/04/2d-animation-part-4-sprite-animation.html )
Basically, you are going to need to set timers to control the sprite animation, as with this type of mouse movement there is no more input (related to moving) between the time you click the mouse and the time the object gets to the destination. So you need to use a timer to determine when the next sprite in the walking animation should be called.
Alternatively, you could further elaborate on your if statements (if currentFrame = 1 then currentFrame = 2, if currentFrame = 2 then currentFrame = 3, etc), but it would be messy and pretty difficult to maintain if you ever make changes to the graphic or the way the sprite is pulled from the spritesheet. It also would most likely animate too fast, and you'd have to use timers to slow it down anyway.
Figured it out I think. Here's my code (hoping it's formatted correctly. Sorry if I'm not meant to answer my own question, thought this might be helpful to someone else):
public Vector2 position = new Vector2(200, 200);
Point frameSize = new Point(48, 92);
Point currentFrame = new Point(0, 0);
Point sheetSize = new Point(9, 8);
float speed = 10;
Vector2 direction;
Vector2 destination;
bool mousePressed = false;
float difference;
KeyboardState currentState;
KeyboardState theKeyboardState;
KeyboardState oldKeyboardState;
enum State
{
Walking
}
State mcurrentState = State.Walking;
TimeSpan nextFrameInterval =
TimeSpan.FromSeconds((float)1 / 16);
TimeSpan nextFrame;
MouseState mouseState;
MouseState oldState;
public void Move()
{
direction = destination - position;
direction.Normalize();
position += direction * speed;
float Xdistance = destination.X - position.X;
float Ydistance = destination.Y - position.Y;
difference = (float)Math.Atan2(Ydistance, Xdistance);
float differ;
differ = MathHelper.ToDegrees(difference);
if (destination.X >= position.X || destination.X <= position.X)
{
currentFrame.X++;
if (currentFrame.X >= 9)
currentFrame.X = 0;
//down = 90dg
if (differ >= 67.6 && differ <= 112.5)
currentFrame.Y = 0;
if (differ >= 112.6 && differ <= 157.5)
currentFrame.Y = 1;
if (differ >= 157.6 && differ <= 180 || differ >= -180 && differ <= -157.5)
currentFrame.Y = 2;
if (differ >= -157.4 && differ <= -112.5)
currentFrame.Y = 3;
if (differ >= -112.4 && differ <= -67.5)
currentFrame.Y = 4;
if (differ >= -67.4 && differ <= -22.5)
currentFrame.Y = 5;
if (differ >= -22.4 && differ <= 22.5)
currentFrame.Y = 6;
if (differ >= 22.6 && differ <= 67.5)
currentFrame.Y = 7;
}
}
public void Update()
{
mouseState = Mouse.GetState();
currentState = Keyboard.GetState();
theKeyboardState = Keyboard.GetState();
if (mousePressed == true)
{
if (Vector2.DistanceSquared(destination, position) >= speed * speed)
{
Move();
}
}
if (mouseState.LeftButton == ButtonState.Pressed && oldState.LeftButton == ButtonState.Released)
{
int mouseY = mouseState.Y;
int mouseX = mouseState.X;
destination = new Vector2(mouseX, mouseY);
mousePressed = true;
}
oldState = mouseState;
if (mcurrentState == State.Walking)
{
#region KB animation
if (currentState.IsKeyDown(Keys.Down))
{
mousePressed = false;
currentFrame.X++;
currentFrame.Y = 0;
if (currentFrame.X >= 9)
currentFrame.X = 0;
position.Y += speed;
}
if (currentState.IsKeyDown(Keys.Up))
{
mousePressed = false;
currentFrame.X++;
currentFrame.Y = 4;
if (currentFrame.X >= 9)
currentFrame.X = 0;
position.Y -= speed;
}
if (currentState.IsKeyDown(Keys.Right))
{
mousePressed = false;
currentFrame.X++;
currentFrame.Y = 6;
if (currentState.IsKeyDown(Keys.Down))
currentFrame.Y = 7;
if (currentState.IsKeyDown(Keys.Up))
currentFrame.Y = 5;
if (currentFrame.X >= 9)
currentFrame.X = 0;
position.X += speed;
}
if (currentState.IsKeyDown(Keys.Left))
{
mousePressed = false;
currentFrame.X++;
currentFrame.Y = 2;
if (currentState.IsKeyDown(Keys.Down))
currentFrame.Y = 1;
if (currentState.IsKeyDown(Keys.Up))
currentFrame.Y = 3;
if (currentFrame.X >= 9)
currentFrame.X = 0;
position.X -= speed;
}
}
oldKeyboardState = theKeyboardState;
#endregion
}
public void Draw(SpriteBatch spriteBatch, Texture2D character)
{
spriteBatch.Begin();
spriteBatch.Draw(character, position, new Rectangle(frameSize.X * currentFrame.X,
frameSize.Y * currentFrame.Y, frameSize.X, frameSize.Y), Color.White, 0, new Vector2(frameSize.X / 2, frameSize.Y / 2), 1, SpriteEffects.None, 0);
spriteBatch.End();
}

Resources