Drawing lines between clicks draws from top left corner - java

I'm currently trying to create a program using processing that draws lines from all mouseclicks to new mouseclicks.
As a side it also should save the clicks in a two-dimensional array.
public void setup() {
size(500,500);
}
int clickcount = 0;
int storedclicks = 10000;
int[] mouseclickX = new int [storedclicks];
int[] mouseclickY = new int [storedclicks];
public void draw() {
background(80);
drawing();
}
public void mousePressed() {
if(clickcount >= storedclicks) {
clickcount = 0;
}
mouseclickX[clickcount] = mouseX;
mouseclickY[clickcount] = mouseY;
clickcount++;
}
public void drawing() {
beginShape(LINES);
for (int i = 0; i < storedclicks; i++) {
vertex(mouseclickX[i], mouseclickY[i]);
}
endShape();
}
}
Something works with the code I have now, but something doesn't add up for me. As it is now, on first click I get a line from the upper left corner, next click that line disappears and I get a new line from the ending point of that line and the first line disappears.
Next click a new line from the corner to clicking point comes (line nr. 2 still present). And then it just continues.
I figured if I changed the storeclicks to a number like 5, it doesn't do the from corner thing, just a new line from every previous click position.
It sounds a little confusing, so here's a picture to help (after 3 clicks):

A few notes:
I'd use a lifo ring buffer (you could simulate it with a linked list) or similar to store the clicks, that way you don't have to check storedclicks separately and it should make drawing easier since the head of the buffer moves when you remove elements from the front.
Additionally I'd only draw lines if there are at least two points in the list/buffer.
Third, to prevent synchronization issues (updating only x or y) I'd use a list/buffer/array of Point objects (make your own or use java.awt.Point) rather than two separate x and y arrays.
As for your drawing code you should loop over the stored points rather than all elements (most of which might be empty), i.e. like this:
When using your code:
if( clickcount > 1 ) {
for (int i = 0; i < clickcount ; i++) {
vertex(mouseclickX[i], mouseclickY[i]);
}
}
When using a list/ring buffer as well as Point objects:
if( list.size() > 1 ) {
for (Point clickpos : list ) {
vertex(clickpos.x, clickpos.y);
}
}
Finally, if processing is similar to OpenGL (I don't know processing) the shape type LINES would draw a line between two vertices, i.e. every uneven vertex is a line start and every even vertex is a line end. What you probably want is something like LINE_STRIP (don't know the name or whether it exists in processing) which makes the renderer draw lines between all vertices (i.e. from 0 to 1, from 1 to 2 etc.)
Edit:
As an explanation for the situation in the image you posted, I assume the clicks are ordered from right to left (indices 0, 1, 2). If that's the case then I'd explain it like this (see above for more info):
The first line is drawn between points 0 and 1, which is ok.
The second line is drawn between points 2 and 3 (see paragraph about the loop as well as LINES resp. line strips), which is not what you want.
The third line (if storedclicks > 5) will be drawn between points 4 and 5, which is a point since both vertices have the coordinates 0/0.
The above bulletpoint is repeated for all following lines (again see the paragraph about the loop).
As you can see there are two problems:
You'd want to draw lines between 0 and 1 and between 1 and 2 which is why I suggested a line strip (if that exists in processing, otherwise you'd have to reuse the last point).
You'd want to stop at index 2, i.e. ignore all indices starting from 3, which is why I mentioned not using storedclicks in the loop condition).

Just to add to the existing great answers, here's a hacky approach that simply takes advantage of not clearing the background:
int lastClickX = -1;
int lastClickY = -1;
void setup(){
size(500,500);
background(80);
}
void draw(){}
void mouseReleased(){
if(lastClickX >= 0 && lastClickY >= 0) line(mouseX,mouseY,lastClickX,lastClickY);
lastClickX = mouseX;
lastClickY = mouseY;
}
You can run this as demo bellow:
var lastClickX = -1;
var lastClickY = -1;
function setup(){
createCanvas(500,500);
background(80);
}
function draw(){}
function mouseReleased(){
if(lastClickX >= 0 && lastClickY >= 0) line(mouseX,mouseY,lastClickX,lastClickY);
lastClickX = mouseX;
lastClickY = mouseY;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.17/p5.min.js"></script>
Bare in mind, although this method doesn't store position, it simply takes advantage of not clearing the screen, which isn't the most flexible of options.

To understand what's happening in your sketch, look to the reference.
This code:
beginShape(LINES);
vertex(30, 20);
vertex(85, 20);
vertex(85, 75);
vertex(30, 75);
endShape();
Generates this image:
(source: processing.org)
So, that's why you're getting gaps where your line segments aren't drawn. Instead of using LINES, you probably want to use the no-args beginShape() function along with the endShape(CLOSE) function:
(source: processing.org)
noFill();
beginShape();
vertex(30, 20);
vertex(85, 20);
vertex(85, 75);
vertex(30, 75);
endShape(CLOSE);
As for why you're getting a point at 0,0: keep in mind that int arrays are initialized to contain all zeroes by default. So when you loop through your entire array, you end up drawing a bunch of points at 0,0. Instead, you probably want a separate variable that keeps track of the total number of clicks (which you stop updating when you reach the maximum number of clicks you want to hold).
As a side it also should save the clicks in a two-dimensional array.
Note that you aren't storing the clicks in a two-dimensional array. You're storing them in two one-dimensional arrays, also known as parallel arrays. This is generally a bad design. Instead, consider using Processing's PVector class along with an ArrayList instead of an array. That way you can keep track of how many valid clicks you have without needing the separate variable I mentioned above.
Putting it all together, it might look something like this:
ArrayList<PVector> clicks = new ArrayList<PVector>();
public void setup() {
size(500, 500);
}
public void draw() {
background(80);
drawing();
}
public void mousePressed() {
clicks.add(new PVector(mouseX, mouseY));
if(clicks.size() > 5){
clicks.remove(0);
}
}
public void drawing() {
noFill();
beginShape();
for(PVector v : clicks){
vertex(v.x, v.y);
}
endShape(CLOSE);
}
draws lines from all mouseclicks to new mouseclicks.
If instead of a closed polygon, you want something like a spider web that connects every point to every other point, then you just have to loop through each click and draw the connecting lines:
public void drawing() {
for (PVector v1 : clicks) {
for (PVector v2 : clicks) {
line(v1.x, v1.y, v2.x, v2.y);
}
}
}

Related

Render multiple heights in an isometric tile based map

Good evening everyone.
I've been messing a bit with isometric tile worlds and I have a few doubts about rendering the elements on it.
When I build a single height map I render it first and then add the diferent elements on top, and the displacement of the last seem right.
public void render(Graphics2D g2d) {
for(int i = 0; i < tileGrid.length; i++) {
Point isop = NeoMath.getInstance().cartToIso(i % yAmmount, i / yAmmount, GlobalDataStorage.tileWidth, GlobalDataStorage.tileHeight);
TileManager.getInstance().getTileByID(tileGrid[i]).render(g2d, isop.x, isop.y);
}
for(Entity entity : entityList) {
entity.render(g2d);
}
}
(The position of the entity is calculated inside it's update).
With this I have no problems as everything is rendered on the same height, the problem comes when I try to add other floors to it.
Let's say that I want it to have three heights. I have a list of list of tiles instead of the single array, and render every element on them:
public void render(Graphics2D g2d) {
int flag = 0;
for(int i = 0; i < tileGrid.size(); i++) {
Point isop = NeoMath.getInstance().cartToIso(i % yAmmount, i / yAmmount, GlobalDataStorage.tileWidth, GlobalDataStorage.tileHeight);
for(int k = 0; k < tileGrid.get(i).size(); k++) {
TileManager.getInstance().getTileByID(tileGrid.get(i).get(k)).render(g2d, isop.x, isop.y - (GlobalDataStorage.tileZ * k));
}
while(flag < currentList.size() &&
currentList.get(flag).getPosition().equals(new Point(i % yAmmount, i /
yAmmount))) {
currentList.get(flag).render(g2d);
flag++;
}
}
}
Where the currentList is the list of entities.
With this I have the problem that, when the entities move to a new position, they get overlaped by the tiles, as these are rendered after the entity, and the position of the entity does not change until it reached the destiny. I could change the position to the new one before rendering, but that implies that in the other axis the previous tile get rendered after the entity, making it disapear for a second due to the overlap.
This also mess when I try to draw selection rectangle as it get stuck behind the tiles being rendered. I don't want them to overlap the whole map so can't draw them after all the rendering has been done either.
Does someone know of another approach that I can try out?
Thank you beforehand.
Draw your entire floor layer in a first pass. Then in the second pass draw all walls and objects and moving entities.
I could change the position to the new one before rendering,
David Brevik, programmer on Diablo, mentions using this option in his GDC talk Diablo: A Classic Games Postmortem. It was his first "What Went Wrong" example!
Reference: https://www.youtube.com/watch?v=VscdPA6sUkc&t=20m17s
Turns out this is a classic hurdle in isometric games.

How should I drag content from one array of JTextFields to another?

I figured out how to drag to highlight multiple cells in a GridLayout-designed grid (which wasn't too hard) and how to drag a cell from one such grid to another (which involved brute force and math, but it turned out not to be all that hard, either).
But the code looks and feels hacked.
How should I have done it?
Here are code fragments that typify what I did to drag content (one char):
For each cell in txtUser[] grid add mouse listener to identify the cell about to be dragged and also access its content:
txtUser[i].addMouseListener(new java.awt.event.MouseListener()
{
public void mousePressed(MouseEvent e) {
currentUserCell.index = interp(e.getXOnScreen(), ulcUser.x, txtUser[0].getWidth());
if(txtUser[currentUserCell.index].getText().length() > 0)
currentUserCell.content = txtUser[currentUserCell.index].getText().charAt(0);
}
Here's interp(), which converts from absolute screen pixel (x) to (returned) grid element number, given the upper-left corner of the text field array and the width of one element:
static int interp(int x, int ulc, int w){
return (x - ulc)/w;
}
If the user moves the frame, interp() above doesn't work, requiring need to reorient():
void reorient(){
ulcGrid = new Point(cells[ 0][ 0].getLocationOnScreen().x, cells[ 0][ 0].getLocationOnScreen().y);
ulcUser = new Point(txtUser[ 0] .getLocationOnScreen().x, txtUser[ 0] .getLocationOnScreen().y);
}
(I tried to use relative pixel locations, but couldn't make it work. I may revisit this.)
In order to drop the dragged content, the destination had better be inbounds():
boolean inbounds(int r, int c){
return ! (r >= N || c >= N || r < 0 || c < 0);
}
If inbounds, the letter is dropped, as long as destination is empty:
public void mouseReleased(MouseEvent e) {
int x, y;
if(! dragging)
return;
dragging = false;
x = e.getLocationOnScreen().x;
y = e.getLocationOnScreen().y;
int c = Utilities.interp(x, ulcGrid.x);
int r = Utilities.interp(y, ulcGrid.y);
if(! inbounds(r, c))
return;
if(cells[r][c].getText().length() > 0)
return;
cells[r][c].setText("" + currentUserCell.content);
The previous method required a MouseMotionAdapter for each cell of the source array.
And it just seems so hacked. One reason I say this is that I rely on several global variables, such as ulcGrid and ulcUser and currentUserCell and dragging:
private void txtUserMouseDragged(MouseEvent evt)
{
dragging = true;
}
I had a nice learning experience, but I'd rather have more-professional-looking code, most notably with fewer global variables. (I realize that a good start would be to not rely on absolute pixel addresses.)
So I'm asking where to find a better way, specifically how to identify the drag source and destination cells of a one- or two-dimensional array of text fields.
=================
--EDIT--
My program works. My question is about whether there is a library that would make it easier and more reliable than what I've written to drag from the one-dimenional array at the bottom of the screen below onto the large grid.
But now that I've read the comments, maybe this is just another bad question that should be deleted.

Slick2d, How can i draw a string on screen for a few seconds?

I've been trying to figure this out, all I want to do is be able to draw a string for longer than just a frame, but when I call it in the method I want it to flash up then disappear immediately, any advice would be appreciated :) I'm using something like this:
g.drawString("You got a Key!", 100, 100);
I'm doing this in a method which is called after an Item is picked up
public void addItemFound(Graphics g){
ip.mainInventory[ip.getFirstEmptyStack()] = getItemStackFound();
System.out.println(this.getItemFound() + " added");
g.drawString("You Got a Key!", 100, 100);
}
That's the full method if you were interested :) Thanks!Also apologies for the dumb question, i'm a newbie to this :P
I believe that the best way to do this project would be to draw the scene at regular intervals e.g. 10 milliseconds using a Thread.sleep(). This way, you can simply add a variable to show the message for, say, 100 loops (1 second) like this:
private LinkedList<String> drawStringList= new LinkedList<>();
private LinkedList<Integer> drawStringTimeout= new LinkedList<>();
private LinkedList<Integer[]> drawStringPos= new LinkedList<>();
public void addText(String stringToWrite, int posX, int posY, int timeOut) {
drawStringList.add(stringToWrite);
int[] pos = new int[2];
pos[0] = posX;
pos[1] = posY;
drawStringPos.add(pos);
drawStringTimeout.add(timeOut);
}
private void mainLoop() {
...items to be drawn here...
for(int i=0;i<drawStringList.size();i++){
g.drawString(drawStringList.get(i),drawStringPos.get(i)[0],drawStringPos.get(i)[1]);
drawStringTimeout.set(i,drawStringTimeout.get(i)-1);
if(drawStringTimeout.get(i)<=0) {
drawStringList.remove(i);
drawStringTimeout.remove(i);
drawStringPos.remove(i);
}
}
try { Thread.sleep(10); } catch (Exception e) {}
}
In this code, you must add the string you want to draw to drawStringList, add the number of loops you want it to stay for to drawStringTimeout and add the position you would like to draw it in to drawStringPos as an array (you could use a point if you wanted to). I have made a method to do this.
I don't know what Dan300 is trying to tell you to do but that's way, way, way over complicated. Slick2D works on gamestates:
http://slick.ninjacave.com/javadoc/org/newdawn/slick/state/GameState.html
The gamestate has a method called render(). The render() is called every single cycle of the loop to update your screen with drawing information. If you want to draw the text on the screen for a longer time you should be drawing the text somewhere within the stack space of this render() function.
What is happening now is you have a function with one specific purpose that only exists every so briefly: add an item to the player. The game comes across this statement and when adding an item within that 1 cycle the text will be drawn. But the next cycle when the player isn't picking up an item it won't come by that drawString statement and you won't have your string on your screen longer than 1 game cycle.

User-selected marker in time series data in Java

My code plots 5000 points of time series data in a panel that is 581 pixels wide by default, but this width changes when the user resizes the window. My code also plots several rectangular markers that each identify a local maximum/peak in this same space.
I need to enable the user to right click on any of the rectangular-peak-markers so that the user can manually delete any false peak. The problem is that my code is reporting different x-coordinates than expected when the user right-clicks on a peak-marker. I suspect that the reason may have to do with rounding error in converting from 581 x-pixels back to 5000 data indices. But I am not certain of the reason.
Can anyone suggest a solution that enables my users to manually select one of the above-described peak markers by right-clicking on it?
I am enclosing relevant sections of the code below. My actual code is very, very long, and too complicated to post. But the relevant portions below should be enough for someone to see the logic of my approach, and to then suggest a more effective approach.
The code that declares the class in question is:
class SineDraw extends JPanel implements MouseMotionListener, MouseListener {
// lots of code, including the two segments excerpted below
}
This segment of code overloads the paintComponent of the JPanel so that my data is plotted:
// declare some variables
ArrayList<Double> PeakList = new ArrayList<Double>() // this ArrayList is populated by an extraneous process
visiblePoints = 5000
hstep = getWidth()/visiblePoints //=581/5000 by default, but will change when user resizes window
int numPeaks = PeakList.size();
// scale (y-coordinate) data relative to height of panel
pts = new double[visiblePoints]
for (int i = 0; i < pts.length-1; i++){pts[i]=//data vertical scaled to fill panel;}
// plot the 5000 time-series-data-points within the 581 pixels in x-axis
for (int i = 1; i < visiblePoints; i++) {
int x1 = (int) ((i - 1) * hstep);
int x2 = (int) (i * hstep);
int y1 = (int)pts[i - 1];
int y2 = (int)pts[i];
g2.drawLine(x1, y1, x2, y2);
}
// plot a rectangle for each of the local peaks
for(int m=0;m<=(numPeaks-1);m++){
if(i==(int)(PeakList.get(m)){
int currentVal = (int)pts[(int)(PeakList.get(m)];
g2.drawRect((int)(PeakList.get(m), currentVal, 6, 6);
}
}
This section of code is for handling the right-clicking of the mouse:
public void mousePressed(MouseEvent e){
// check to see if right mouse button was clicked
boolean jones = (e.getModifiers()&InputEvent.BUTTON3_MASK)==InputEvent.BUTTON3_MASK;
if(jones==true){
// test the value returned as x-coordinate when user right-clicks (code always underestimates x-coordinate of local peaks by this test)
double ReverseHstep = visiblePoints/getWidth();
int getX_ConvertedTo_i = (int) (e.getX()*ReverseHstep);
System.out.println("getX_ConvertedTo_i is: "+getX_ConvertedTo_i );
// check to see if peaklist contains a value within the x-coordinates of the user-selected-rectangle
if(PeakList.contains((double)(e.getX()-3))
||PeakList.contains((double)(e.getX()-2))
||PeakList.contains((double)(e.getX()-1))
||PeakList.contains((double)(e.getX()))
||PeakList.contains((double)(e.getX()+1))
||PeakList.contains((double)(e.getX()+2))
||PeakList.contains((double)(e.getX()+3))
){
// handling code will go here, but for now it is a print test that never succeeds because x-coordinate is always underestimated
System.out.println("You just selected a peak!");
}
}
repaint();
}
I suggest you create objects (in this case Rectangles) for each thing you want to be clickable. Here is an over-simplified example of how you can make something you draw clickable. The key thing to take away from this is the mouseClicked method which will display a dialog only if the mouse clicked within the rectangle.
One tricky point is that I wasn't able to figure out how to make the rectangle filled in with color without drawing another rectangle over it. I'll leave that one for you ;-)
public class Canvas extends JPanel implements MouseListener{
private Rectangle rect = new Rectangle(100,100);
public Canvas(){
this.addMouseListener(this);
rect.setSize(100, 100);
}
#Override
public void paintComponent(Graphics g){
g.setClip(rect);
g.setColor(Color.RED);
g.fillRect(0, 0, 100, 100);
}
#Override
public void mouseClicked(MouseEvent e){
if(rect.contains(e.getPoint())){
JOptionPane.showConfirmDialog(this, "Click!");
}
}
// The rest of the MouseListener methods have been cut out
public static void main(String[] a){
JFrame frame = new JFrame("Canvas Thingy");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 300, 300);
frame.add(new Canvas());
frame.setVisible(true);
}
}

Animating a line between two Point objects

What's the simplest way to draw a line between two Point objects in a way
that will look like I am drawing that line in real time by hand?
For example:
Point a = new Point(5,20);
Point b = new Point(15,20);
How do I connect these points with a "moving" line?
In other words I want to make the user feel "motion" of some sort. Is there a simple way to do that?
Given two points, you can determine the equation of the line connecting them. The equation of a line is of the form y = mx + c, where m is the slope, and c is the y intercept.
So, given your two points (5,20) and (15,20), we first determine m.
m = (y2-y1)/(x2-x1)
= (20-20)/(15-5)
= (0)/10
= 0
Substituting into the equation for a straight line, we get y = 0x + c or y = c. Now that we know this, we simply need to know the points where y = c and 5<=x<=15. Simply draw each of these points in the normal way (look at this for the exact method) with a Thread.sleep() call in between drawing each point. In this case, you have only 11 points to draw, so it would make sense to draw 1 point every 100 ms. For details on Thread.sleep() see here.
EDIT: Since Thread.sleep() won't work on the EDT, look at javax.swing.Timer instead, as Uhlen suggested.
Following the answer by Chinmay Kanchi, you need to create a feeling of animation. As mentioned above in comments by Uhlen you should use Swing's Timer when working on EDT. To give you example of how to use Timer. Lets assume we have a panel and we want it to slide open on e.g. a button click, thus we need to animate it sliding open by increasing its size. Below is an example showing pretty much how you would use Timer to do the operations.
this.extendingTimer = new Timer(0, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
//assume sliding is vertical
int value = maximumHeight;
//make sure the size will not be bigger then allowed maximum
if(currentExtensionSize + extensionRate >= value)
{
currentExtensionSize = value;
stopExtending();
}
else
currentExtensionSize += extensionRate;
setSize(new Dimension(maximumWidth, currentExtensionSize));
}
});
extendingTimer.setInitialDelay(0);
extendingTimer.setDelay(100);
extendingTimer.setRepeats(true);
int lineCount = 0; //global
// timer calls the below
xLocation = (*a)[a->size()-1] * timeSoFar / duration ;
if(xLocation > (*a)[lineCount+1]){
lineCount++;
}
double m = ((*b)[lineCount+1] - (*b)[lineCount])/((*a)[lineCount+1]-(*a)[lineCount]);
double yIntercept = (*b)[lineCount]-m*(*a)[lineCount];
yLocation = m * xLocation + yIntercept;
xLocation = (yLocation - yIntercept) / m;
this is in c++ and using vectors but its the theory we want. This allows for multiple lines not just one.

Categories