Optimize this struct? (3D Integer Vector)

Discussion in 'Scripting' started by RiokuTheSlayer, Oct 28, 2016.

1. RiokuTheSlayer

Joined:
Aug 22, 2013
Posts:
356
Hey everyone! Been a while since I posted here. I was working the other day on some projects, and I have a class I've been using for a while. I made it myself, it's a simple class that's a Vector3, but it uses integers instead of floats.

I'm just wondering if anyone has any tips on optimizing it? I use this class a LOT (For pathfinding, position storage, ect) and I just need to know if there's ways to better it. Thanks in advance!

Code (CSharp):
1. using UnityEngine;
2.
3. [System.Serializable]
4. public struct IntPos {
5.     public int x, y, z;
6.
7.     public IntPos(int ix, int iy, int iz) {
8.         x = ix;
9.         y = iy;
10.         z = iz;
11.     }
12.
13.     public Vector3 toVec {
14.         get {
15.             return new Vector3(x, y, z);
16.         }
17.     }
18.
19.     public static IntPos zero = new IntPos(0, 0, 0);
20.
21.     public static IntPos up = new IntPos(0, 1, 0);
22.
23.     public static IntPos right = new IntPos(1, 0, 0);
24.
25.     public static IntPos forward = new IntPos(0, 0, 1);
26.
27.     public static IntPos one = new IntPos(1, 1, 1);
28.
29.     //IntPos - IntPos operators
30.     public static IntPos operator +(IntPos i1, IntPos i2) {
31.         return new IntPos(i1.x + i2.x, i1.y + i2.y, i1.z + i2.z);
32.     }
33.     public static IntPos operator -(IntPos i1, IntPos i2) {
34.         return new IntPos(i1.x - i2.x, i1.y - i2.y, i1.z - i2.z);
35.     }
36.     public static IntPos operator *(IntPos i1, IntPos i2) {
37.         return new IntPos(i1.x * i2.x, i1.y * i2.y, i1.z * i2.z);
38.     }
39.     public static IntPos operator /(IntPos i1, IntPos i2) {
40.         return new IntPos(i1.x / i2.x, i1.y / i2.y, i1.z / i2.z);
41.     }
42.     public static bool operator ==(IntPos i1, IntPos i2) {
43.         return i1.x == i2.x && i1.y == i2.y && i1.z == i2.z;
44.     }
45.     public static bool operator !=(IntPos i1, IntPos i2) {
46.         return !(i1==i2);
47.     }
48.
49.     public bool isNegative {
50.         get {
51.             return x < 0 || y < 0 || z < 0;
52.         }
53.     }
54.
55.     //IntPos - Int Operators
56.     public static IntPos operator +(IntPos i1, int i2) {
57.         return new IntPos(i1.x + i2, i1.y + i2, i1.z + i2);
58.     }
59.     public static IntPos operator -(IntPos i1, int i2) {
60.         return new IntPos(i1.x - i2, i1.y - i2, i1.z - i2);
61.     }
62.     public static IntPos operator *(IntPos i1, int i2) {
63.         return new IntPos(i1.x * i2, i1.y * i2, i1.z * i2);
64.     }
65.     public static IntPos operator /(IntPos i1, int i2) {
66.         return new IntPos(Mathf.FloorToInt((float)i1.x / i2), Mathf.FloorToInt((float)i1.y / i2), Mathf.FloorToInt((float)i1.z / i2));
67.     }
68.
69.     public IntPos normalized {
70.         get {
71.             return IntPos.Vector3ToIntPos(toVec * 1.2f);
72.         }
73.     }
74.
75.     public static float DistanceSqr(IntPos i1, IntPos i2) {
76.
77.         return ( i1 - i2 ).sqrMagnitude;
78.     }
79.
80.     public float sqrMagnitude {
81.         get {
82.             return ( x * x + y * y + z * z );
83.         }
84.     }
85.
86.     public static IntPos Vector3ToIntPos(Vector3 input) {
87.         return new IntPos(Mathf.FloorToInt(input.x), Mathf.FloorToInt(input.y), Mathf.FloorToInt(input.z));
88.     }
89.
90.     public override string ToString() {
91.         return "IntPos(" + x + "," + y + "," + z + ")";
92.     }
93.     public IntPos ToFlatIntPos() {
94.         return new IntPos(x, 0, y);
95.     }
96.     public IntPos ToFlatZIntpos() {
97.         return new IntPos(x, 0, z);
98.     }
99. }
A few things. For one, the division operator actually converts i1's values to floats before division. This is to prevent things from truncating to 0, which happens quite a bit. If I don't do this, I have problems with values that are close to 0 coordinates, since they truncate.

Next, the !=/== operators. I don't know if those should be better or if they are fine.

The normalized value could be optimized to not do the vector3 conversion.. but I barely use it so I don't see much point.

Volunteer ModeratorModerator

Joined:
Jul 19, 2006
Posts:
31,892
I wouldn't use float division in the "/" operator; since you're dealing with ints, the expected result would be integer division. You could make a separate function for floating point division. I expect the != operator would be a bit faster if it didn't call the == operator. Also you need to override Equals and I think GetHashCode if you're going to override == and !=. Ideally the struct would extend System.IEquatable with a strongly-typed overload of Equals, to prevent boxing/unboxing.

--Eric

Lysander and Kiwasi like this.
3. Kiwasi

Joined:
Dec 5, 2013
Posts:
15,920
Your IntVector-Int operators are a little weird. Mathematically the result of a vector plus a scalar is undefined. I'm not sure exactly what you intend to do with the operation, but it seems to be an easy way to add in bugs.

4. Lysander

Joined:
Feb 24, 2013
Posts:
1,497
I want to stress implementing IEquatable for value types- the boxing for object comparisons is pretty harsh if you just do the override, since it can be easily avoided. Definitely need to override GetHashCode if you're planning on using this type as keys in hashtables/dictionaries. You can use the Unity implementation for Vector3s directly, which is:
Code (csharp):
1. public override int GetHashCode()
2. {
3.     return this.x.GetHashCode() ^ this.y.GetHashCode() << 2 ^ this.z.GetHashCode() >> 2;
4. }
... if memory serves.

EDIT: Also, don't forget the negative operator overload (unary minus).
Code (csharp):
1. public static IntPos operator -(IntPos a)
2. {
3.     return new IntPos(-a.x, -a.y, -a.z);
4. }

Last edited: Oct 28, 2016
5. Lysander

Joined:
Feb 24, 2013
Posts:
1,497
I figured this might actually come in handy at some point in the future, so I wrote my own version, adapted from the internal Unity implementation of the Vector3. There were some functions that just made no sense to include for an integer version, like the Lerp/Slerp functions, RotateTowards, etc, but I kept as much as I could. I chose to use RoundToInt instead of FloorToInt for situations where floats were inevitable, and I made a ShiftAll function to replace your addition/subtraction operator overload idea- BoredMormon is right that it's not a legal operation and you could (and almost definitely will) end up hiding some mistakes in the math if you leave it that way.

Added a few conversion methods to and from other Vector formats, including an integer version of Vector2 which I wrote alongside this. Worth noting that the normalize function is going to be less than ideal- you're essentially comparing a spherical calculation to a box with the same dimensions, and you'd have to be VERY close to the perfect "corner" directions to snap to them, so it'll end up with the six straight directions (forward, back, up, down, left, right) far more often than the diagonals. This really needs to be changed to something a little more fitting to the situation, like angle ranges.

Last edited: Oct 28, 2016
Kiwasi likes this.
6. RiokuTheSlayer

Joined:
Aug 22, 2013
Posts:
356
The floating point conversion was so that the integers didn't truncate towards zero. The rest was pretty much what I expected
This is literally exactly what I was looking for, thanks for this. I figured if nobody did anything I'd write my script based on Unity's Vector3 class, but... You did it for me!

As I said, I made this class a WHILE ago (At least half a year) and now's the time I got around to optimizing it, is all. So the operator ideas for add/sub with integers was an old idea. If I re-wrote it from the ground up now, I'd probably do a ShiftAll like thing myself.

I WAS also wondering about hashcode optimizations, since I use this class for my position references in a lot of things. So it's used in dictionary keys and the like a lot. So, thanks again. I'll be using this from now on.

7. Piflik

Joined:
Sep 11, 2011
Posts:
214
Just a little remark: Mark the Vector3i.up etc as readonly to prevent redefinition. These should not be changed anytime anyway.

And the constructor should idealy look like this (Unity will compile without calling the parameterless constructor, but VS will complain, due to the use of Auto-Properties):

Code (CSharp):
1.  public Vector2i(int x, int y) : this() {
2.     this.x = x;
3.     this.y = y;
4. }

Last edited: Oct 28, 2016
Lysander and Kiwasi like this.