Calculate the midpoint of two Points


#1

How would I calculate the midpoint between two Point objects?

It would be trivial if Points were just single integers, but because they’re row/column pairs, and the number of columns in a row is variable, this seems fairly hard to calculate. Does anyone have any suggestions? Perhaps this should be a utility method on the Point class?


#2

It’s not difficult. You just have to take half of the difference and round it.

pointA = [5, 0]
pointB = [10, 1]
diff = [ pointB[0] - pointA[0], pointB[1] - pointA[1] ]
midpoint = [ Math.round(pointA[0] + diff[0] / 2), Math.round(pointA[1] + diff[1] / 2) ]
console.log(midpoint)

#3

This doesn’t cover all situations, though. If you have

pointA = [1, 5] // Row 1, column 5
pointB = [2, 4] // On the next row, but one column back

then

diff = [1, -1]

then

midpoint = [(1+1)/2, (5-1)/2]
= [1, 2]

Which is very wrong.

For situations where the columns are different by an odd number (in this case, 1), you have to take add half the columns of the first row… or something, it seems fairly complicated.


#4

(PointA[0] + PointB[0]) / 2, (PointA[1] + PointB[1]) / 2)?


#5

You use a wrong parenthesis before dividing by two.
its start + half difference.
not half (start + difference)

Alternatively you can half (start+end)


#7

I messed up the maths, but that’s still missing the point.

pointA = [1, 5]
pointB = [2, 4]
diff = [1, -1]
midpoint = [1+(1/2), 5-(1/2)]
= [2, 5]

The actual midpoint can’t be [2, 5], it should be somewhere less than point B.

The real problem is, you need to know how long line 1 is in order to calculate the midpoint. For example, if we had the following text, with the bolded text indicating a Range that we want to find the midpoint of:

for (let i=0; i < 20; i++){
console.log(i);
}

The selection consists of 23 characters on line 1, and 4 characters on line 2, adding to 27 characters total. The midpoint is then 13 characters after the start of the selection (the left parenthesis), so is actually the < symbol. You can’t work this out just by adding together the start and the end and dividing by two.


#8

I’m sorry I misunderstood your problem.
What do you mean by midpoint ?

You seems to hint for a need for precise character count, can you share the context / background need ?


#9

Yes, I mean the midpoint in terms of characters, as I explained above. If Atom didn’t use rows/columns, but instead just used character offsets in the text file, then it would be easy to calculate the midpoint between character 20 and character 60 - it would be 40. But because selections all use rows and columns, it’s more tricky.

My specific use-case, is I want to place a visual Decorator in the exact middle of a selection range, but this selection can span multiple rows and columns.


#10

I think cursor::moveLeft has the math you want.
Ie: if you know the selection has 160 characters, cursor::moveLeft(80) will wrap around lines.

I’m not sure if you should use an actual cursor or just get inspired by the implementation.


#11

Well, you can’t have half-points, so if you’re going to constrain it like that, then you’ll be disappointed.

No, I’ve looked back through the thread and I don’t see you explaining anything of the sort. You’ve been asking for the Point at the midpoint of two Points. My code will get you to the geometrical midpoint, as far as possible considering the resolution. If you want to find the middle of a string of characters, then the math is even easier, as @jeancroy said.

editor = atom.workspace.getActiveTextEditor()
selection = editor.getLastSelection()
cursor = editor.getLastCursor()
count = Math.round(selection.getText().length / 2)

if(selection.getBufferRange().end.compare(cursor.getBufferPosition()) != -1)
  cursor.moveLeft(count)
else
  cursor.moveRight(count)

console.log( cursor.getBufferPosition() )

#12

Thank you! This is the right idea. Is it possible to do this without moving a cursor, though? I suppose I can just copy the logic from the cursor.


#13

You can always move the cursor back to where it was in the first place.

This is an aspect of Atom’s particular level and kind of abstraction. It’s difficult to deal with the contents of a TextBuffer without using cursors.


#14

As a simpler way which avoids mutating a cursor you can use TextBuffer::characterIndexForPosition() on both points, calculate the (integer) midpoint of the two, then use TextBuffer::positionForCharacterIndex to get this midpoint back into a [line, column] format.

So your code would be something like:

function getSelectionMidpoint () {
  const editor = atom.workspace.getActiveTextEditor()
  const buffer = editor.getBuffer()
  const selection = editor.getLastSelection().getBufferRange()
  const startChar = buffer.characterIndexForPosition(selection.start)
  const endChar = buffer.characterIndexForPosition(selection.end)
  const midpointChar = startChar + Math.round((endChar - startChar) / 2)
  const midpoint = buffer.positionForCharacterIndex(midpointChar)

  return midpoint
}

Note that as this code deals in raw buffer positions you will get different results based on the line endings of the file since the raw line ending characters are counted as well. To work around that you would need to do some fancy character counting and line checking based solely in buffer coordinates instead of character indexes.


#15

Thank you! These methods are exactly what I was looking for.

My guess is that I can just count the number of lines in the selection, and subtract one less than that (the number of linebreaks) from the end position, to get around it counting linebreaks.


#16

This is correct. It can also be extended to three points to get the controls of a triangle:
C.x = (v1.x + v2.x + v3.x)/3, etc.


#17

Finding a midpoint doesn’t have to be as complicated as some of the answers given here.
Mid.x = (Pt1.x + Pt2.x)/2. Ditto for y. Don’t calculate differences, just get the average of the two x values. Same for y values.