Related
I am creating a custom anti-cheat. However, I have come to a point where I am quite stumped. I am attempting to detect whether a player can place a block at a said location, but it is becoming increasingly convoluted as I try to make it more reliable for non-cheating players. Currently, I am incorporating a raycast algorithm (usingAxisAllignedBB) whenever a player interacts with a block (PlayerInteractEvent) to see if the player is actually looking at the Block and BlockFace the event says they were. The problem, I believe, is the player's direction is only updated 20 times a second where their frame rate might be much higher. This often causes (about once every 15 or so block places from my testing) the PlayerInteractEvent to be incorrectly canceled.
Raycast Algorithm for finding Block looked at
public static Block getTargetBlock(Location location, Vector direction, double rangeSquared, int maxTrials, TargetMethod targetMethod) {
Location loc = location.clone();
Vector dir = direction.normalize();
final double directionX = direction.getX();
final double directionY = direction.getY();
final double directionZ = direction.getZ();
Block block = loc.getBlock();
for (int i = 0; i <= maxTrials; i++) {
final double locX = loc.getX();
final double locY = loc.getY();
final double locZ = loc.getZ();
double wholeMoreX = wholeMore(locX,directionX);
double moreX = Math.abs(wholeMoreX /directionX);
double wholeMoreY = wholeMore(locY,directionY);
double moreY = Math.abs(wholeMoreY /directionY);
double wholeMoreZ = wholeMore(locZ,directionZ);
double moreZ = Math.abs(wholeMoreZ /directionZ);
if(moreX < moreY && moreX < moreZ){
if(directionX > 0)
block = block.getRelative(BlockFace.EAST);
else {
block = block.getRelative(BlockFace.WEST);
}
}
else if(moreY < moreX && moreY < moreZ){
if(directionY > 0){
block = block.getRelative(BlockFace.UP);
}
else{
block = block.getRelative(BlockFace.DOWN);
}
}
else{
if(directionZ > 0){
block = block.getRelative(BlockFace.SOUTH);
}
else{
block = block.getRelative(BlockFace.NORTH);
}
}
final double scalar = Math.min(Math.min(moreX,moreY),moreZ);
Vector addAmount = dir.clone().multiply(scalar);
loc.add(addAmount);
if(loc.distanceSquared(location) > rangeSquared)
return null;
AxisAlignedBB boundry = getBoundry(block,targetMethod);
if(boundry != null)
if(blockFaceCollide(location,direction,boundry) != null)
return block;
}
return null;
}
However, I doubt this is the issue. From my testing, it works perfectly fine. Thus, I think I must rely on alternative methods. Here are some ideas, but I am not quite sure they are satisfying.
Idea: Near Blocks
I have thought about seeing if the block placed is within a 1 block radius (or possibly shorter if I am looking at closest distance to block from ray) of the block found from the raycast, but this offers too many problems. If a player is moving their cursor from a barrier to a further out area, a false positive for cheating would be fired. On the other hand, players could still build in a fully enclosed area if they had block pillars North, East, South, West but not North-West, North-East, etc.
Idea: A* Path finding Algorithm
If I made points on the ray in the raycast have 0 G-Cost, with G-Cost increasing with distance from the ray and the H-Cost being the closest distance to the targeting block, I feel this could solve this dilemma. I could set a max G-Cost threshold before the PlayerInteractEvent is canceled. The problem, however, is incorporating A* with various AxisAllignedBB of blocks seems difficult. I might be able to create a grid which consists of 100x100x100 points per block, but I am not sure this would be efficient nor best practice.
Idea: See if the player can see the block
This would be highly effective, but I am not sure whether it would be realistic. For this, each time a player places a block I would need to detect which blocks would completely overlap other blocks in the player's interact radius. Taking all the final non-overlapped blocks, I could see if the interacted block contains these. If not, the interaction would be canceled. This seems like it might take a performance hit, and I could see how there could also be some false positives for cheating.
I'd suggest to create a method that informs if Player and block intersects.
Sample Code
public static final double ONE_UNIT = 1.0;
public static final double ZERO_UNIT = 0.0;
public static Location getPlayerBlockIntersection(Player player, Block target) {
if (player == null || target == null) {
return null;
}
double minX = target.getX();
double minY = target.getY();
double minZ = target.getZ();
double maxX = minX + ONE_UNIT;
double maxY = minY + ONE_UNIT;
double maxZ = minZ + ONE_UNIT;
Location origin = player.getEyeLocation();
double originX = origin.getX();
double originY = origin.getY();
double originZ = origin.getZ();
Vector dir = origin.getDirection();
double dirX = dir.getX();
double dirY = dir.getY();
double dirZ = dir.getZ();
double divX = ONE_UNIT / dirX;
double divY = ONE_UNIT / dirY;
double divZ = ONE_UNIT / dirZ;
double t0 = ZERO_UNIT;
double t1 = Double.MAX_VALUE;
double imin, imax, iymin, iymax, izmin, izmax;
if (dirX >= ZERO_UNIT) {
imin = (minX - originX) * divX;
imax = (maxX - originX) * divX;
} else {
imin = (maxX - originX) * divX;
imax = (minX - originX) * divX;
}
if (dirY >= ZERO_UNIT) {
iymin = (minY - originY) * divY;
iymax = (maxY - originY) * divY;
} else {
iymin = (maxY - originY) * divY;
iymax = (minY - originY) * divY;
}
if ((imin > iymax) || (iymin > imax)) {
return null;
}
if (iymin > imin) {
imin = iymin;
}
if (iymax < imax) {
imax = iymax;
}
if (dirZ >= ZERO_UNIT) {
izmin = (minZ - originZ) * divZ;
izmax = (maxZ - originZ) * divZ;
} else {
izmin = (maxZ - originZ) * divZ;
izmax = (minZ - originZ) * divZ;
}
if ((imin > izmax) || (izmin > imax)) {
return null;
}
if (izmin > imin) {
imin = izmin;
}
if (izmax < imax) {
imax = izmax;
}
if ((imin >= t1) || (imax <= t0)) {
return null;
}
// check this baby and see if both objects represent an intersection:
Location intersection = origin.add(dir.multiply(imin));
return intersection;
}
I'm not sure if this works. But I thought about using the BlockPlaceEvent and check when the Block is placed if the Player is looking at that block.
#EventHandler
public void blockplace(BlockPlaceEvent event){
Player p = event.getPlayer();
int x = p.getLocation().getDirection().getBlockX();
int y = p.getLocation().getDirection().getBlockY();
int z = p.getLocation().getDirection().getBlockZ();
Location lookingLoc = new Location(p.getWorld(),x,y,z);
if (!event.getBlockPlaced().getLocation().equals(lookingLoc)){
//flag player...
}
}
Feel free to leave recommendations.
I read the question a couple times to be sure. If I understand the premise,
you wish to verify when an interaction occurs that the player is
looking at the block being interacted with. I take it that you want to prevent "auto-build" mods or
the like that may fake such events.
The validation should be straightforward using Player.getTargetBlock().
If the block returned by getTargetBlock() is the same as that reported
by PlayerInteractEvent you should be reasonably confident that the player is
looking at the block.
I’m trying to move at a constant speed over a curved path in a given amount of time. I calculated the average speed needed to travel the curve by taking the derivative at various points along the curve and averaging them. Then I multiply the path’s position (t) by a ratio of the average derivative and the derivative at the current location of the curve. This method for setting constant speed works great.
The problem I’m having occurs when multiple control points (3 or more) are put in the same location. Then the speed (or derivative) at this point is 0 and dividing the average speed by a speed of 0 obviously causes problems in the calculations.
BSpline requires three control points to be placed at the ends in order to have the curve actually reach the start and end at the end points. If I only put 1 or 2 control points at the ends the path starts after the first control point and ends before the last control point. For my application it is important that the motion reaches the end points because I will be linking together multiple BSplines and it’s important for them to line up correctly and to not have any time gaps between them either.
I’ve tried a few different attempts at fixing it, but none of them were successful.
Here is my sample code and I've included comments to indicate where the problem is.
NOTE: I used CatmullRomSpline in my example instead of BSpline only because I found a bug in the BSpline’s derivative method, which has been fixed but is not yet in the stable version of LibGDX.
Test.java
public class Test extends Game {
private Stage stage;
private MyPath path;
#Override
public void create () {
Gdx.graphics.setDisplayMode(1000, 1000, false);
stage = new Stage();
stage.setViewport(new ScreenViewport(stage.getViewport().getCamera()));
Gdx.input.setInputProcessor(stage);
path = new MyPath(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
stage.addActor(path);
}
#Override
public void render () {
Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
#Override
public void dispose(){
path.dispose();
stage.dispose();
super.dispose();
}
}
MyPath.java
public class MyPath extends WidgetGroup implements Disposable {
private Path<Vector2> path;
private Vector2 result=new Vector2(), derivative=new Vector2();
private float time, t, tPrev, dt, tConst, tConstPrev, derivativeAverage;
private Array<Texture> textures = new Array<Texture>(Texture.class);
private Array<Image> points = new Array<Image>(Image.class);
private Image dot;
private final float CYCLE = 4; // path cycle time (in seconds)
private Vector2[] pointsData = {
new Vector2(100, 100),
new Vector2(100, 100),
// new Vector2(100, 100), // << UN-COMMENT TO PRODUCE BUG
new Vector2(350, 800),
new Vector2(550, 200),
new Vector2(650, 400),
new Vector2(900, 100),
new Vector2(900, 100)
};
public MyPath(int width, int height){
this.setSize(width, height);
path = new CatmullRomSpline<Vector2>(pointsData, false);
// create and add images
createImages();
for (int i=0; i<points.size; i++){
points.items[i].setPosition(pointsData[i].x - points.items[i].getWidth()/2, pointsData[i].y - points.items[i].getHeight()/2);
addActor(points.items[i]);
}
addActor(dot);
// calculate derivative average
derivativeAverage();
}
#Override
public void act(float delta){
result = getValue(delta);
dot.setPosition(result.x - dot.getWidth()/2, result.y - dot.getHeight()/2);
}
private Vector2 getValue(float delta){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
}
t = time / CYCLE;
dt = t - tPrev;
tPrev = t;
// constant speed (tConst)
path.derivativeAt(derivative, tConstPrev);
tConst += dt * (derivativeAverage / derivative.len()); // << ERROR when derivative.len() is 0
tConstPrev = tConst;
path.valueAt(result, tConst);
return result;
}
private void derivativeAverage(){
float segmentCount = 20000;
derivativeAverage = 0;
for (float i=0; i<=1; i+=1.0/segmentCount) {
path.derivativeAt(result, i);
derivativeAverage += result.len();
}
derivativeAverage /= segmentCount;
if (derivativeAverage==0){ throw new GdxRuntimeException("ERROR: derivative average is zero"); }
}
private void createImages(){
dot = getImage(Color.GREEN, true);
for (int i=0; i<pointsData.length; i++){
points.add(getImage(Color.WHITE, false));
}
}
private Image getImage(Color color, boolean fillCircle){
Pixmap pixmap = new Pixmap(50, 50, Pixmap.Format.RGBA8888);
pixmap.setColor(color);
if (fillCircle){
pixmap.fillCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getWidth()/2-1);
} else {
pixmap.drawCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getWidth()/2-1);
}
textures.add(new Texture(pixmap));
pixmap.dispose();
return new Image(textures.peek());
}
#Override
public void dispose(){
while (textures.size > 0){
textures.pop().dispose();
}
}
}
===================================================================
EDIT
===================================================================
Here is my latest attempt at increasing t until the dot is moving.
This method does occasionally work on some frames (moving smoothly past the zero derivative). But other times the dot does weird things lie starting over at the beginning of the curve when it hits the zero derivative or extending beyond the end of the curve moving a different direction or disappearing completely (because the position gets set to negative values).
So it seems like this method is really close as it does occasionally work on some frames, but it glitches and does weird things on other frames.
Vector2 lastPoint = new Vector2();
float minSpeed = 1;
float minDerivative = 1;
float temp;
...
private Vector2 getValue(float delta){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
}
t = time / CYCLE;
// CONSTANT SPEED
dt = t - tPrev;
path.derivativeAt(derivative, tConstPrev);
temp = dt * (derivativeAverage / derivative.len());
path.valueAt(result, tConst + temp);
//**************************************
// FIX FOR ZERO SPEED
// increase t in loop until speed > 0
//**************************************
while (result.dst(lastPoint)<minSpeed || derivative.len()<minDerivative){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
lastPoint.set(0,0);
}
t = time / CYCLE;
// CONSTANT SPEED
dt = t - tPrev;
// new derivative
path.valueAt(derivative, t);
derivative.sub(lastPoint);
temp = dt * (speedAverage / derivative.len());
path.valueAt(result, tConst + temp);
}
tConst += temp;
lastPoint.set(result);
tPrev = t;
tConstPrev = tConst;
return result;
}
I also do a similar thing when calculating the average speed to keep the zero derivatives from affecting it. I also tried using the commented out sections with the "addedSegmentCount" variable when calculating the average, but that actually caused more glitches for some reason...even though theoretically this seems like the "correct" way to calculate the average since some segments don't get added if the distance is too small.
private void pathLength_SpeedAverage(){
float segmentCount = 20000;
// float addedSegmentCount = 0;
pathLength = 0;
path.valueAt(lastPoint, 0);
for (float i=0; i<=1; i+=1.0/segmentCount){
path.valueAt(result, i);
if (result.dst(lastPoint) >= minSpeed){
path.derivativeAt(result, i);
if (result.len() >= minDerivative){
pathLength += result.len();
lastPoint.set(result);
// ++addedSegmentCount;
}
}
}
speedAverage = pathLength / segmentCount;
// speedAverage = pathLength / addedSegmentCount;
lastPoint.set(0,0);
}
You cannot completely avoid zero first derivatives if control points could be coincident. So, what I suggest is to not use the first derivatives at all. Your purpose is to traverse the path at a constant speed, which is equivalent to sample points along the path with equal arc length. The theoretical approach to solve this involves calculus to compute arc length numerically, but we can go with an approximation approach as in the following:
Suppose you would like to traverse the path in N steps,
1) Sample M points along the path uniformly in parameter domain (i.e., t=0.0, 0.1, 0.2, 0.3, ....) where M is preferred to be greater than N. Denoting these points as P0, P1, P2,....
2) Compute the distance between P0P1, P1P2, P2P3,....
3) Compile a look-up table that maps from parameter t(i) to the cumulative chord length |P0P1|+|P1P2|+.....+|P(i-1)P(i)|. At the end, you will also obtain the overall length of the path, denoted as L.
4) Now, for each value of kL/N (where k=0 to N), you can compute the corresponding t value from the look-up table by linear interpolating the two parameter values in which the kL/N falls on.
I'm not sure if I can express this question correctly, but here it goes..
I want to code an example, where small dots have a velocity according to which they move - but also, there is a random motion superimposed to the "proper" motion. Using the Processing code below, I get the following animation:
The right dot is supposed to be going towards the bottom right corner, and I'm OK with how it behaves. The problem is the left dot, which is supposed to be "static" - so it would only show the "random" motion "in place"; however, as the animated .gif shows, it tends to end up veering some distance away from its original location. The random velocity is calculated with:
this.randspeed.set(random(0,1)-0.5, random(0,1)-0.5);
I would have guessed that random(0,1)-0.5 doesn't give me a Gaussian-like "normal distribution" centered around (or converging? to) zero; but then again, even if it was a "proper" Gaussian, I still could have such "luck" so that say, positive values [0:0.5) are returned for a whole day, and then negative values [-0.5:0) are returned the next day, and in the end, it would still be a proper Gaussian.
So, I guess, I'm looking for a way to convert a (pseudo?)-random sequence (as the one generated by random(0,1)-0.5) to a pseudo-random one, but in which the average sum of N samples (say, 10) is 0. I'm not sure how to call this - a random sequence periodically converging to zero, I guess??
Note that I've been trying below with both changing position directly; and saving position with changing finalpos instead - changing the position seems more like a "natural", smoothed motion (especially with the modulo frame operation, so a new random velocity isn't assigned every frame); but then, it also allows that the random noise accumulates, and "pushes" the dot away from its central location. Also, note that it took me a few takes until I could reproduce this on the .gif, running the program "live" seems to cause the dot to diverge from the original location more quickly (I had read something about hardware events like hard-disk writes being used for changing entropy for /dev/random on Linux, but I don't really know if it's related).
Also, I thought of setting some sort of a virtual border around the dot position, and having a collision detection for the random motion going out of the border - but that seems to me like too much work (and CPU cycles for vector operations) for this kind of thing; I would have hoped that the random function can somehow be "tempered" in an easier manner, instead.
So, would there be a recommended way to approach this kind of random motion around a central location in a limited area?
marbles.pde:
import java.util.*; // added for Iterator;
ArrayList<Marble> marbles = new ArrayList<Marble>();
Iterator<Marble> imarb;
color mclr = #0000FF;
int RNDLIMIT = 2;
int framecount = 0;
void setup() {
size(600,400,P2D);
Marble m_moving = new Marble(width/2, height/2, 2, 2);
marbles.add(m_moving);
Marble m_stopped = new Marble(width/2-100, height/2, 0, 0);
marbles.add(m_stopped);
}
void draw() {
background(255);
strokeWeight(1);
stroke(mclr);
fill(mclr);
imarb = marbles.iterator();
while (imarb.hasNext()) {
Marble m = imarb.next();
m.update();
ellipse(m.finalpos.x, m.finalpos.y, m.radius*2, m.radius*2);
}
framecount++;
//~ saveFrame("marbles-######.png");
}
class Marble {
PVector position = new PVector(0,0);
PVector finalpos = new PVector(0,0);
PVector speed = new PVector(0,0);
PVector randspeed = new PVector(0,0);
float radius=4;
public Marble() {
}
public Marble(float inx, float iny, float invx, float invy) {
this.position.set(inx, iny);
this.speed.set(invx, invy);
}
public void update() {
this.position.add(this.speed);
if (framecount % 4 == 0) {
this.randspeed.set(random(0,1)-0.5, random(0,1)-0.5);
this.randspeed.setMag(RNDLIMIT);
}
int algoTry = 1; // 0
switch(algoTry) {
case 0:
this.finalpos.set(PVector.add(this.position, this.randspeed));
break;
case 1:
this.position.set(PVector.add(this.position, this.randspeed));
this.finalpos.set(this.position);
break;
}
}
}
A typical 'random walk' will always meander away, because statistics don't 'balance'. Moving a lot to the left will not be corrected with movement to the right. So quality of the randomness isn't the issue.
If you want the dot to stay around a specific location, you should store that location and make the "proper" motion (as you called it) always move towards that location. Some subtraction of current location from target location should get you the correct "proper" motion. With this solution, the dot will always be inclined to head back to where is started.
Well, I think I got somewhere; thanks to the comment by #Teepeemm, I learned about Ornstein - Uhlenbeck process, and also that Brownian motion: "is described by the Wiener process ... one of the best known Lévy processes". Rereading Ornstein - Uhlenbeck process ("Over time, the process tends to drift towards its long-term mean ... is a prototype of a noisy relaxation process ...the length x(t) of the spring will fluctuate stochastically around the spring rest length x0;"), I realized it is not what I want - it would have caused my dot eventually to settle in the central position, and then I would have had to "ping" it every once in a while.
Just as I realized that it would take me forever to fist understand, and then code, those processes - I found this:
Generation of noise with given PSD - Newsreader - MATLAB Central
I want to generate noise data with especific frequency
characteristics: That is, the power spectral density (PSD) has to be
proportional to f^0, f, f^2 etc.
f^0 -- use randn
f^(-2) -- low-pass filter the f^0 time series, or integrate with cumsum
f^2 -- differentiate, as with diff
... so I thought, maybe I can somehow process the raw random numbers, to get a "distribution" as I want. So I came up with a Processing patch, which you'll find below as rndquest2.pde. Processing makes it easy to use alpha colors for points, and if the background is not erased, they accumulate - so it's easier to see what is the actual distribution of a random output being tweaked. I got this image:
The "choice 0" seems to point out that random() generates a sequence with uniform distribution (white noise). For instance, "choice 1" would cause the dot to tend to stick on the edges; "choice 2" quite obviously shows folding ; and I'd prefer a circle, too. In the end, I got something most resembling a Gauss (most frequent in the center, and slowly diminishing to the edges) on "choice 9", by something like a radial folding, I guess. There's still a visible threshold border on "choice 9", but if it is implemented in the code above in OP, then I get something like this:
... which is, actually, as I wanted it! (not sure why the start came out as it did, though) The trick is that the random vector, once limited/processed, should be interpreted as a position (or rather, should be added to the position, to obtain a new position, used to calculate a new velocity for finalpos); it should not be directly added to the speed/velocity!
So, only these changes need to be added in the OP code:
...
float r1 =0, r2 = 0;
PVector rv = new PVector(r1, r2);
float radius = 10;
float pr1 =0; int pr3 =0;
...
int signum(float f) {
if (f > 0) return 1;
if (f < 0) return -1;
return 0;
}
float getRandom() {
float ret;
ret = random(-radius,radius);
return ret;
}
void getRandomVect() {
r1 = getRandom();
r2 = getRandom();
rv.set(r1,r2);
while(rv.mag() > radius) {
r1 = getRandom();
r2 = getRandom();
rv.set(r1,r2);
}
pr1 = rv.mag()-radius/2;
pr3 = int(radius-rv.mag());
pr3 = (pr3 == 0) ? 1 : pr3;
if (pr1>0) {
r1 = rv.x - random(1)*2*signum(rv.x)*pr3;
r2 = rv.y - random(1)*2*signum(rv.y)*pr3;
}
rv.set(r1,r2);
}
...
public void update() {
this.position.add(this.speed);
if (framecount % 4 == 0) {
getRandomVect();
this.randspeed.set(PVector.div(PVector.sub(PVector.add(this.position, rv), this.finalpos), 4));
}
this.finalpos.set(PVector.add(this.finalpos, this.randspeed));
}
...
... to get it working as shown on the gif in this post.
Well, hope this helps someone,
Cheers!
rndquest2.pde
PVector mainpos = new PVector(200.0, 200.0);
float radius = 50;
float x1 =0, y1 = 0;
float r1 =0, r2 = 0;
float pr1 =0, pr2 = 0;
int pr3 =0, pr4 = 0;
PVector rv = new PVector(r1, r2);
color clr = color(0,0,255,30);
int choice = 0;
int framecount = 0;
void setup() {
size(600,400,P2D);
background(255);
textSize(14);
textAlign(LEFT, TOP);
}
void draw() {
try {
strokeWeight(2);
stroke(clr); // #0000FF web colors only
fill(clr);
point(mainpos.x, mainpos.y);
r1 = getRandom();
r2 = getRandom();
switch(choice) {
case 0:
x1 = mainpos.x + r1;
y1 = mainpos.y + r2;
println("0"); // these help trigger the draw(), apparently..
break;
case 1:
rv.set(r1,r2);
if(rv.mag() > radius) {
rv.setMag(radius);
}
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("1");
break;
case 2:
rv.set(r1,r2);
if(rv.mag() > radius) {
rv.sub(PVector.mult(rv,0.1*(rv.mag()-radius)));
}
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("2");
break;
case 3:
rv.set(r1,r2);
while(rv.mag() > radius) {
r1 = getRandom();
r2 = getRandom();
rv.set(r1,r2);
}
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("3");
break;
case 4:
pr1 = rv.x;
pr2 = rv.y;
rv.set(r1-pr1,r2-pr2);
while(rv.mag() > radius) {
r1 = getRandom();
r2 = getRandom();
rv.set(r1-pr1,r2-pr2);
}
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("4");
break;
case 5:
pr1 = rv.x;
pr2 = rv.y;
rv.set(r1-pr1,r2-pr2);
if(rv.mag() > radius) {
rv.mult(1.0/(rv.mag()-radius));
}
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("5");
break;
case 6:
pr1 = (pr1 + r1)/2.0;
pr2 = (pr2 + r2)/2.0;
rv.set(pr1,pr2);
if(rv.mag() > radius) {
rv.mult(1.0/(rv.mag()-radius));
}
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("6");
break;
case 7:
r1 = (pr1 + r1)/2.0;
r2 = (pr2 + r2)/2.0;
rv.set(r1,r2);
while(rv.mag() > radius) {
r1 = getRandom();
r2 = getRandom();
r1 = (pr1 + r1)/2.0;
r2 = (pr2 + r2)/2.0;
rv.set(r1,r2);
}
pr1 = rv.x;
pr2 = rv.y;
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("7");
break;
case 8:
rv.set(r1,r2);
while(rv.mag() > radius) {
r1 = getRandom();
r2 = getRandom();
rv.set(r1,r2);
}
//~ pr1 = abs(rv.x)-radius/2;
//~ pr2 = abs(rv.y)-radius/2;
pr1 = rv.mag()-radius/2;
//~ pr3 = int(radius-abs(rv.x));
//~ pr4 = int(radius-abs(rv.y));
pr3 = int(radius-pr1);
pr3 = (pr3 == 0) ? 1 : pr3;
//~ pr4 = (pr4 == 0) ? 1 : pr4;
if (pr1>0)
r1 = rv.x - random(1)*2*signum(rv.x)*pr1; //framecount ; b2i(int(random(radius)) % pr3 == 0)*
if (pr1>0) //(pr2>0)
r2 = rv.y - random(1)*2*signum(rv.y)*pr1;//pr2;
rv.set(r1,r2);
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("8");
break;
case 9:
rv.set(r1,r2);
while(rv.mag() > radius) {
r1 = getRandom();
r2 = getRandom();
rv.set(r1,r2);
}
pr1 = rv.mag()-radius/2;
pr3 = int(radius-rv.mag()); //pr1);
pr3 = (pr3 == 0) ? 1 : pr3;
if (pr1>0) {
r1 = rv.x - random(1)*2*signum(rv.x)*pr3; //framecount ; b2i(int(random(radius)) % pr3 == 0)*
r2 = rv.y - random(1)*2*signum(rv.y)*pr3;//pr2;
//~ r1 = rv.x - 2*signum(rv.x)*pr3; //like an X for pr3 = int(radius-pr1);
//~ r2 = rv.y - 2*signum(rv.y)*pr3;
}
rv.set(r1,r2);
x1 = mainpos.x + rv.x;
y1 = mainpos.y + rv.y;
println("9");
break;
}
// note: patch does not draw point(mainpos.x + getRandom(), ..)
point(x1, y1);
fill(255);
stroke(255); //~ stroke(255,0,0);
rect(mainpos.x-radius,100,mainpos.x-radius+100,20);
fill(0,0,255);
stroke(clr);
text(String.format("choice %d (f:%d)", choice, framecount), mainpos.x-radius, 100);
framecount++;
if (framecount % 5000 == 0) {
saveFrame(String.format("rndquest2-%d-%d-######.png", choice, framecount));
}
} catch(Exception e) {
e.printStackTrace();
}
}
int signum(float f) {
if (f > 0) return 1;
if (f < 0) return -1;
return 0;
}
int b2i(boolean inb) {
if (inb) return 1;
else return 0;
}
float getRandom() {
float ret;
ret = random(-radius,radius);
return ret;
}
void mousePressed() {
choice = (choice + 1) % 10;
background(255);
framecount = 0;
}
If you want random movement within a certain distance of an "actual" point, you could try having a fixed, maximum-distance from the "actual" location, and not allowing the ball outside of that radius.
If you don't want a hard limit, you could add some kind of force that attracts the object toward its "actual" location, and make it increase with the distance from that point linearly, quadratically, or by some other function of your choosing. Then the object would be free to move around its "actual" location, but still be kept relatively nearby.
You are simulating a random walk. Generally, a random walk after n steps will be on the order of sqrt(n) from where it started (more specifically, it will obey the Law of the Iterated Logarithm, so that its magnitude after n steps is O(sqrt(n log log n))). Which is a long way of saying that the walk will wander away as time goes on (but because it's two dimensional, it will eventually return to the origin).
To solve this, you want to have a drift back toward the origin. One random process which has this property is the Ornstein - Uhlenbeck process, which has a drift toward the origin that is proportional to its distance from the origin. (And the random part of the random walk would still cause it to wiggle around its origin.)
This could be accomplished in your original code by something along the lines of
double driftScale = .01;
double wiggleScale = 1;
Point origin = new Point(0,0);
...
this.randspeed.set(driftScale*(origin.x-this.position.x)+wiggleScale*(random(0,1)-.5),
driftScale*(origin.y-this.position.y)+wiggleScale*(random(0,1)-.5));
It would be better to replace random(0,1)-.5 with a standard normal Gaussian, but I don't know how noticeable that affect would be. The biggest difference is that with the current code, there is a maximum distance the point can get from its start. With a Gaussian, it could theoretically get arbitrarily far (but it would still drift back to the origin).
I'm also not quite sure how much this matches with your eventual solution. I'm having trouble following your logic (using PVector and 10 cases didn't help).
I have encountered some issues regarding angles. I have an angle A and another angle B, I want to animate A the shortest way so that it reaches B. The first confusion for me is that angles go from 0 to 180, and 0 to -180. Not sure what the pros of that is. Anyway, I will give a for instance:
float a = -35;
float b = 90;
For each update I want to either add 1 or subtract 1 degree from a, until it reaches b, and I want to make sure it goes the shortest way.
Here's my code, which seems to be working. But it does not seem very efficient:
b += 360;
if (b > a) {
if (b - a < 180) {
a += 1;
} else {
a -= 1;
}
} else {
if (a - b < 180) {
a -= 1;
} else {
a += 1;
}
}
Is there a better/easier way to do it?
So you want the shortest route from a to b.
Since we are looking at a difference lets subtract:
float d = a-b;
If the value of the result is greater than 180 then we want to subtract 360.
if (d > 180) {
d -= 360;
} else if (d<-180) {
d += 360;
}
Now d is the total distance to travel. You can compare that with 0 to see which way to go. You can also do nice things like move further the larger d is. For example to make it always move 10% of the way (note that this series will never end as it will constantly approach by smaller and smaller amounts so you need to cope with that scenario):
a += d/10;
You also need to consider frame rate if you want a smooth animation.
If you work out tpf (time per frame) as a floating point fraction of a second.
long frameTook = lastFrame - System.currentTimeMillis();
long lastFrame = System.currentTimeMillis();
float tpf = frameTook / 1000;
You can now do a constant animation (where degreesPerFrame is the speed of animation) using:
float move = degreesPerFrame * tpf;
Check we aren't going to move past the destination, if we are just move to it.
if (move > FastMath.abs(d)) {
a = b;
} else {
if (d>0) {
a+=move;
} else {
a-=move;
}
}
I've issued myself a sort of challenge, and thought I could stand to ask for help getting my head around it. I want to use java Graphics to draw something that looks like lightning striking a given point.
Right now I just have this, which shoots cheap "lightning" in random directions, and I don't care where it ends up.
lightning[0] = new Point(370,130); //This is a given start point.
// Start in a random direction, each line segment has a length of 25
double theta = rand.nextDouble()*2*Math.PI;
int X = (int)(25*Math.cos(theta));
int Y = (int)(25*Math.sin(theta));
//Populate the array with more points
for (int i = 1 ; i < lightning.length ; i++)
{
lightning[i] = new Point(X + lightning[i-1].x, Y + lightning[i-1].y);
boolean plusminus = rand.nextBoolean();
if (plusminus) theta = theta + rand.nextDouble()*(Math.PI/2);
else theta = theta - rand.nextDouble()*(Math.PI/2);
X = (int)(25*Math.cos(theta));
Y = (int)(25*Math.sin(theta));
}
// Draw lines connecting each point
canvas.setColor(Color.WHITE);
for (int i = 1 ; i < lightning.length ; i++)
{
int Xbegin = lightning[i-1].x;
int Xend = lightning[i].x;
int Ybegin = lightning[i-1].y;
int Yend = lightning[i].y;
canvas.drawLine(Xbegin, Ybegin, Xend, Yend);
//if (Xend != Xbegin) theta = Math.atan((Yend - Ybegin)/(Xend - Xbegin));
// Restrict the angle to 90 degrees in either direction
boolean plusminus = rand.nextBoolean();
if (plusminus) theta = theta + rand.nextDouble()*(Math.PI/2);
else theta = theta - rand.nextDouble()*(Math.PI/2);
// 50/50 chance of creating a half-length off-shoot branch on the end
if (rand.nextBoolean())
{
int Xoff = (int)(Xend+(12*Math.cos(theta)));
int Yoff = (int)(Yend+(12*Math.sin(theta)));
canvas.drawLine(Xend, Yend, Xoff, Yoff);
}
}
I'm trying to think of some similar way to create this effect, but have the last point in the array pre-defined, so that the lightning can "strike" a specific point. In other words, I want to populate a Point array in a way that is random, but still converges on one final point.
Anyone care to weigh in?
I think this is fairly simple, accurate, and elegant approach. It uses a divide and conquer strategy. Start with only 2 values:
start point
end point
Calculate the midpoint. Offset that midpoint some value variance (which can be calculated relative to the length). The offset should ideally be normal to the vector connecting start and end, but you could be cheap by making that offset horizontal, as long as your bolts travel mostly vertically, like real lightning. Repeat above procedure for both (start, offset_mid) and (offset_mid, end), but this time using a smaller number for variance. This is a recursive approach which can terminate when either a threshold variance is achieved, or a threshold line segment length. As the recursion unwinds, you can draw all the connector segments. The idea is that the largest variance happens in the center of the bolt (when the start-to-end distance is the longest), and with each recursive call, the distance between points shrinks, and so does the variance. This way, the global variance of the bolt will be much greater than any local variances (like a real lightning bolt).
Here is an image of 3 different bolts generated from the same pre-determined points with this algorithm. Those points happen to be (250,100) and (500,800). If you want bolts that travel in any direction (not just "mostly vertical"), then you'll need to add more complexity to the point shifting code, shifting both X and Y based on the angle of travel of the bolt.
And here is some Java code for this approach. I used an ArrayList since the the divide and conquer approach doesn't know ahead of time how many elements it will end up with.
// play with these values to fine-tune the appearance of your bolt
private static final double VAR_FACTOR = 0.40;
private static final double VAR_DECREASE = 0.55;
private static final int MIN_LENGTH = 50;
public static ArrayList<Point> buildBolt(Point start, Point end) {
ArrayList<Point> bolt = new ArrayList<Point>();
double dx = start.getX() - end.getX();
double dy = start.getY() - end.getY();
double length = Math.sqrt(dx*dx + dy*dy);
double variance = length * VAR_FACTOR;
bolt.add(start);
buildBolt(start, end, bolt, variance);
return bolt;
}
private static void buildBolt(Point start, Point end,
List<Point> bolt, double variance) {
double dx = start.getX() - end.getX();
double dy = start.getY() - end.getY();
double length = Math.sqrt(dx*dx + dy*dy);
if (length > MIN_LENGTH) {
int varX = (int) ((Math.random() * variance * 2) - variance);
int midX = (start.x + end.x)/2 + varX;
int midY = (start.y + end.y)/2;
Point mid = new Point(midX, midY);
buildBolt(start, mid, bolt, variance * VAR_DECREASE);
buildBolt(mid, end, bolt, variance * VAR_DECREASE);
} else {
bolt.add(end);
}
return;
}
Without any graphics experience, I have this advice: it's going to be hard to pick a specific point, then try to reach it in a "random" way. Instead, I'd recommend creating a straight line from the origin to destination, then randomly bending parts of it. You could make several passes, bending smaller and smaller segments down to a certain limit to get the desired look. Again, I'm saying this without any knowledge of the graphics API.