This is the trouble code..
multresult := mult(mult(temp, quatview), conjugate(temp));
Full procedure
procedure TForm2.RotateCamera(var angle: Single; x: Single; y: Single; z: Single);
var
temp, QuatView, multResult : TQuaternion;
begin
temp.x := x * sin(Angle/2);
temp.y := y * sin(Angle/2);
temp.z := z * sin(Angle/2);
temp.w := cos(Angle/2);
quatview.x := camera1.Position.x;
quatview.y := camera1.Position.y;
quatview.z := camera1.Position.z;
quatview.w := 0;
multresult := mult(mult(temp, quatview), conjugate(temp));
camera1.Position.x := multresult.x;
camera1.Position.y := multresult.y;
camera1.Position.z := multresult.z;
end;
mult function
function TForm2.mult(var A: TQuaternion; B: TQuaternion) :TQuaternion;
var
c : TQuaternion;
begin
C.x := A.w*B.x + A.x*B.w + A.y*B.z - A.z*B.y;
C.y := A.w*B.y - A.x*B.z + A.y*B.w + A.z*B.x;
C.z := A.w*B.z + A.x*B.y - A.y*B.x + A.z*B.w;
C.w := A.w*B.w - A.x*B.x - A.y*B.y - A.z*B.z;
result := C;
End;
and conjugate
function TForm2.conjugate( var quat:TQuaternion) :TQuaternion;
begin
quat.x := -quat.x;
quat.y := -quat.y;
quat.z := -quat.z;
result := quat;
end;
and if needed TQuaternion
type
TQuaternion = class
x: single;
y: single;
z: single;
w: single;
end;
any idea why i get this error and how to fix it?
The answer to the question you asked is that the parameters to mult should be const. You don't modify them (and you should not), so make them const. Then your code compiles.
In a similar vein, it's bad form for Conjugate to modify its input parameter. That makes the function horrid to use. Don't do that.
Consider this line:
multresult := mult(mult(temp, quatview), conjugate(temp) );
Since conjugate modifies temp, you'd better hope that the call to conjugate is made after the other use of temp. The language makes no such guarantee. So, cross your fingers!
One of the principles worth following with arithmetic code is that input parameters/operands should never be modified, and that functions always return new values. Follow this principle and you'll never fall into trap highlighted above. See the second part of my answer for an illustration.
However the code won't work even with these changes because you are not instantiating any instances of the TQuaternion class. Are you sure that it's not a record?
The real forward progress will come when you create a good quaternion type. This should be a value type since arithmetic operations are better suited to value types for a number of reasons.
In modern Delphi you want to use a record with operators. Here's a flavour of what you need, ready to extend as you need.
type
TQuaternion = record
x: single;
y: single;
z: single;
w: single;
function Conjugate: TQuaternion;
class operator Multiply(const A, B: TQuaternion): TQuaternion;
end;
function TQuaternion.Conjugate: TQuaternion;
begin
Result.x := -x;
Result.y := -y;
Result.z := -z;
Result.w := w;
end;
class operator TQuaternion.Multiply(const A, B: TQuaternion): TQuaternion;
begin
Result.x := A.w*B.x + A.x*B.w + A.y*B.z - A.z*B.y;
Result.y := A.w*B.y - A.x*B.z + A.y*B.w + A.z*B.x;
Result.z := A.w*B.z + A.x*B.y - A.y*B.x + A.z*B.w;
Result.w := A.w*B.w - A.x*B.x - A.y*B.y - A.z*B.z;
end;
With this type your multiplication call becomes:
multresult := temp*quatview*temp.Conjugate;
You'll surely want to write more operators and helper functions for this type.
It's really important to move the arithmetic functions into this type and out of your form. Don't use your high level GUI form class to implement low level arithmetic.
One final piece of advice. Your code has repeated mis-use of var parameters. I suggest you treat var parameters as things to be avoided. Try to write code without them if possible.
The mult method declares the A parameter as a var so you must pass a variable to the method in order to work, like so.
multresult := mult(temp, quatview);
multresult := mult(multresult, conjugate(temp));
Related
How can I mark an integer into thousands and hundreds?
Just say I have an integer 12345678910, then I want to change it into a money value like 12.345.678.910.
I try the following code but it is not working.
procedure TForm1.Button1Click(Sender: TObject);
var
j,iPos,i, x, y : integer;
sTemp, original, hasil, data : string;
begin
original := edit1.Text;
sTemp := '';
j := length(edit1.Text);
i := 3;
while i < j do
begin
insert('.',original, (j-i));
edit1.Text := original;
j := length(edit1.Text);
for x := 1 to y do
begin
i := i + ( i + x );
end;
end;
edit2.Text := original;
There is System.SysUtils.Format call in Delphi http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SysUtils.Format.
This call understand 'm' character as money specific formatter.
Try code like this:
Value := 12345678910;
FormattedStr := Format('Money = %m', [Value])
By default Format will use systemwide format settings, if you have to override default system settings, see official docs:
The conversion is controlled by the CurrencyString, CurrencyFormat,
NegCurrFormat, ThousandSeparator, DecimalSeparator, and
CurrencyDecimals global variables or their equivalent in a
TFormatSettings data structure. If the format string contains a
precision specifier, it overrides the value given by the
CurrencyDecimals global variable or its TFormatSettings equivalent.
This function does what you specify:
function FormatThousandsSeparators(Value: Int64): string;
var
Index: Integer;
begin
Result := IntToStr(Value);
Index := Length(Result) - 3;
while Index > 0 do
begin
Insert('.', Result, Index + 1);
Dec(Index, 3);
end;
end;
Note that your example 12345678910 does not fit into a 32 bit signed integer value which is why I used Int64.
This function does not handle negative values correctly. For instance, it returns '-.999' when passed -999. That can be dealt with like so:
function FormatThousandsSeparators(Value: Int64): string;
var
Index: Integer;
Negative: Boolean;
begin
Negative := Value < 0;
Result := IntToStr(Abs(Value));
Index := Length(Result) - 3;
while Index > 0 do
begin
Insert('.', Result, Index + 1);
Dec(Index, 3);
end;
if Negative then
Result := '-' + Result;
end;
i know now, its so simple. just use
showMessage(formatFloat('#.###.00', strToFloat(original)));
but thanks Remy, you opened my mind.
I have the following formula
X := X + F*(1-i div n);
Where
X, F, i, n: integer;
The code I'm using is this
F := 7; // the initial speed with no friction.
n := 10; // the animation number of steps.
Hn := n * 2 ;
X := 0; // first Pos
i := 1;
J := 1;
while J < Hn do
begin
X := X + F * (1 - i div n);
if X > Xmax then X := 0; <-- line (1).
if i >= n then Dec(i)
else Inc(i);
Inc(J);
end;
If it was possible I would like to use this but without class/record implementation(not inside a class/record implementation/method).not the exact syntax, just the same principle, instead of direct assignment to X the SetX is called then the result is assigned to X.
X: integer write SetX; // This is not a correct delphi syntax. I added it to explain the behavior I want.
function SetX(aValue: integer): integer;
const
Xmax: SomeIntegerValue;
begin
if aValue > Xmax then result := 0
else result := aValue;
end;
So I could omit Line (1). If this was possible, all the lines after the formula would be omitted and the while loop would look like this
while J < Hn do // J will be incremented each time the loop wants to read it.
begin
X := X + F * (1 - i div n);
end;
Is there anyway to use the property like behavior?
Note: I'm looking for a way to alter the assignment and reading ways of a variable like you do in a property of a record/class.
Is there anyway to use the property like approach outside a class/record?
No, property getters and setters can only be implemented in records and classes.
You can use local function like
procedure YourProcedure;
var
X: Integer;
LJ: Integer;
function J: Integer;
begin
Inc(LJ);
Result := LJ;
end;
procedure SetX(const AValue: Integer);
const
Xmax: SomeIntegerValue;
begin
if aValue > Xmax then X := 0
else X := aValue;
end;
//...
begin
while J < Hn do // J will be incremented each time the loop wants to read it.
begin
SetX(X + F * (1 - i div n));
end
end.
I found a way to do what I wanted. I know that overloading the := operator is not possible, However forcing the compiler to produce the same behavior as the overloaded operator would behave is possible.
The overloading would not let me control the LSA (Left Side Argument). but it gave full control to implicitly convert any TType (in my case it is an integer) to TXinteger. So all I had to do is make sure that every operator would result in a TType which will force the compiler to implicitly convert that to a TXinteger.
Forcing the compiler to use my implicit operator every time it wants to assign something to TXinteger means I control the assignment Hence I overloaded the := operator.
the following is a test example that makes omitting Line(1) possible.
program Project4;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TXinteger = record
X: integer;
class operator Add(a, b: TXinteger): integer;
class operator Add(a: TXinteger; b:integer): integer;
class operator Add(a: integer; b:TXinteger): integer;
class operator Implicit(a: Integer): TXinteger;
class operator Implicit(a: TXinteger): Integer;
end;
// Example implementation of Add
class operator TXinteger.Add(a, b: TXinteger): integer;
begin
result := a.X + b.X;
end;(**)
class operator TXinteger.Add(a: TXinteger; b:integer): integer;
begin
result := a.X + b;
end;
class operator TXinteger.Add(a: integer; b:TXinteger): integer;
begin
result := a + b.X;
end;
class operator TXinteger.Implicit(a: Integer): TXinteger;
const
Xmax: integer = 10;
begin
if a > Xmax then result.X := 0 else result.X := a;
end;
class operator TXinteger.Implicit(a: TXinteger): Integer;
begin
result := a.X;
end;
var
X: TXinteger;
Hn, F, i,J, n: integer;
begin
try
F := 7;
n := 10;
Hn := n * 2 ;
X := 0;
i := 1;
J := 1;
while J < Hn do
begin
X := X + F * (1 - i div n);
// Line (1) is gone now.
if i >= n then Dec(i)
else Inc(i);
Inc(J);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Note: for this case it is pointless to do all of this just to omit one line of code. I wanted to share this because it gives an idea of how one could overload the := operator.
What I wanted is this:
Alter how X:Integer is read (value read from the variable x's storage).
Alter how X:Integer is assigned.
by overloading all the operators that use the value of X, I completed the first.
And by forcing the compiler as explained above, I completed the second.
Thank you all for your help.
If possible, I would like to avoid converting a Currency to Extended (and possible losing precision) in code similar to the following:
function CurrencyToNumeric(aCurrency: Currency; aScale: Integer): Int64;
const
scales: array [-{18}5..-1] of int64 = (100000, 10000, 1000, 100, 10);
var
aCurrencyAsInt64: Int64 absolute aCurrency;
begin
if aScale = -4 then
Result := aCurrencyAsInt64
else
Result := Round(aCurrency * scales[aScale]); // currency -> extended -> integer
end;
Is that possible?
I believe that you are looking for a function like this:
function CurrencyToNumeric(aCurrency: Currency; aScale: Integer): int64;
var
aCurrencyAsInt64: int64 absolute aCurrency;
i, factor, rem: Integer;
begin
if aScale <= -4 then begin
factor := 1;
for i := -4 downto aScale+1 do begin
factor := factor * 10;
end;
Result := aCurrencyAsInt64 * factor;
end else begin
factor := 1;
for i := -4 to aScale-1 do begin
factor := factor * 10;
end;
Result := aCurrencyAsInt64 div factor;
rem := aCurrencyAsInt64 mod factor;
if rem>=factor div 2 then begin
inc(Result);
end;
end;
end;
This part of the code
if rem>=factor div 2 then begin
inc(Result);
end;
implements the rounding policy. You may very well wish to make a different choice. Modify this code to do so, it should be obvious how to go about that.
However, I am also not convinced that the version in the question is broken. Do you have any example input where it fails? On the other hand, avoiding converting to binary floating point for a fixed point decimal type does feel sensible. Now, if only Embarcadero had implemented this darn type without resorting to using floating point operations.
Thanks to David's answer, I ended up with following implementation, which is not only float-free but also faster than function from the question.
function CurrencyToNumeric(Value: Currency; Scale: Integer): Int64;
const
factors: array [-4..-1] of Int64 = (10000, 1000, 100, 10);
var
factor: Integer;
ValueAsInt64: Int64 absolute Value;
begin
if Scale = -4 then
Result := ValueAsInt64
else if Scale < -4 then
Result := ValueAsInt64 * factors[4 + Scale]
else begin
factor := factors[-(4 + Scale)];
Result := ValueAsInt64 div factor;
if ValueAsInt64 mod factor >= factor div 2 then Inc(Result);
end;
end;
So, I'm trying to get the angle between two TPoints in Delphi, and it turns out to be harder then what I expected. The result I'm getting I can't explain (seems to be some problem with "to degrees"-part, or ArcTan2 does not return a sum in the form I expected.
- Delpi-v7:
function Modulo(x,y:Extended): Extended;
var d: Extended;
begin
d := x / y;
Result := (d - floor(d)) * y;
end;
function Degrees(Rads: Extended): Extended;
begin
Result := Rads*(180/Pi);
end;
function GetPointAngle(P1, P2: TPoint): Extended;
begin
Result := Modulo(Degrees(ArcTan2(-(P1.Y - P2.Y), P1.X - P2.X)) - 90, 360);
end;
Yet, when I port the code to Python, or test it in another Pascal-variant, the above works. But now, it seems to return a sum that's static (not changing if I "move" the second TPoint).
In case your wondering; I created "modulo"-function simply because the divide-operator used in the "mod"-operator rounds to 0, and not down (so negative numbers don't work).
Edit: I noted that the value (angle) returned from GetPointAngle() increases when p gets further away from the other point c (and vice versa), even tho the TPoint (p) is dragged along the X-axis of the second TPoint (c).
EDIT:
You guys have outdone your self, I've looked over most of the answers, and it seems to be hard to choose best answer! And since you guys wrote everything with such detail, I will go trough everything with the same detail :-)
Also: what I did not share in my initial post, is that my function is being exported as a DLL to be reached from another pascal-interpretor (which is delphi-compatible).
Solution at last (changed):
GetPointAngle(P1, P2: TPoint) To: GetPointAngle(const P1, P2: TPoint)
^ I don't understand the need of declaring constants...
I assume you want to calculate the angle relative to the X-axis of the line which is formed between those two points.
For this situation, the following formula applies:
Tan(a) = (P2.Y - P1.Y) / (P2.X - P1.X)
Which translates to:
a = ArcTan((P2.Y - P1.Y) / (P2.X - P1.X))
When the two points have the same X coordinate, this will obviously result in a EDivByZero exception, so you have to take care of that yourself. Furthermore, ArcTan results in an angle within the range 0°..90° (i.e. 0..π/2) and thus disregards the correct quadrant, while ArcTan2 results in an angle within -180°..180°. Add 360° to the result to convert a negative angle to positive:
function AngleOfLine(const P1, P2: TPoint): Double;
begin
if P2.X = P1.X then
if P2.Y > P1.Y then
Result := 90
else
Result := 270
else
Result := RadToDeg(ArcTan2(P2.Y - P1.Y, P2.X - P1.X));
if Result < 0 then
Result := Result + 360;
end;
Which results in:
A := AngleOfLine(Point(10, 10), Point(20, 10)); // 0
A := AngleOfLine(Point(10, 10), Point(20, 20)); // 45
A := AngleOfLine(Point(10, 10), Point(10, 20)); // 90
A := AngleOfLine(Point(10, 10), Point(0, 20)); // 135
A := AngleOfLine(Point(10, 10), Point(0, 10)); // 180
A := AngleOfLine(Point(10, 10), Point(0, 0)); // 225
A := AngleOfLine(Point(10, 10), Point(10, 0)); // 270
A := AngleOfLine(Point(10, 10), Point(20, 0)); // 315
Now, this is relative to the world coordinate system which has its positive Y-axis pointed upwards by default. If you want to convert the result to the device coordinate system wherein the positive Y-axis points downwards, then subtract the result from 360°:
Result := 360 - Result;
Update:
It seems ArcTan2 dóes take care of division by zero, (even in D7 inspite of the documentation) so the routine becomes much simpler:
function AngleOfLine(const P1, P2: TPoint): Double;
begin
Result := RadToDeg(ArcTan2((P2.Y - P1.Y),(P2.X - P1.X)));
if Result < 0 then
Result := Result + 360;
end;
Edit:
I noted that the value returned from GetPointAngle() increases when p gets furter away from the other point c (and vice versa).
That depends. Looking at the diagram above, if the second point moves further along the x-axis, the angle decreases. If the second point moves further along the y-axis, the angle increases. Of course, this depends on which quadrant both points are in.
Furthermore, your code negates the first parameter of ArcTan2 and subtracts another 90° from the result. I do not know what you mean by that and whether it is intentional, but it could be the source of unexpected results.
I presume what you are looking for is the angle between two vectors. That is θ in this diagram:
The algebraic dot product can be expressed geometrically as <v1,v2> = |v1||v2|cos θ. This can be rearranged to find θ = cos-1 <v1,v2>/(|v1||v2|).
function DotProduct(const v1, v2: TPoint): Integer;
begin
Result := v1.X*v2.X + v1.Y*v2.Y;
end;
function Magnitude(const v: TPoint): Double;
begin
Result := Sqrt(Sqr(v.X)+Sqr(v.Y));
end;
function AngleBetweenVectors(const v1, v2: TPoint): Double;
var
Magv1, Magv2: Double;
begin
Magv1 := Magnitude(v1);
Magv2 := Magnitude(v2);
if abs(Magv1*Magv2)=0.0 then
Result := 0.0
else
Result := ArcCos(EnsureRange(DotProduct(v1,v2)/(Magv1*Magv2), -1.0, 1.0));
end;
That returns an angle in radians. You can convert that into degrees using RadToDeg() from the Math unit.
Now, the other way to interpret your problem is that you want to take two points and form the line between then. And then find the angle between that line and the horizontal, say. As described by this diagram:
The can still be expressed as the angle between two vectors. The first vector is p2-p1 and the other is a vector in the horizontal direction, (0, 1). Feed those two into AngleBetweenVectors and you have your answer. If you want to measure angle to vertical, then you can use the same idea.
Hopefully there's enough here for you to solve the problem, whatever it actually is.
Following code returns same results with Delphi 7 and FPC 2.7.1 and it seems correct.
So main question is: what we are expecting and what we are having?
program Project2;
{$APPTYPE CONSOLE}
uses
Math;
{.$define speed}
function CalcAngle(const lx, ly: extended): extended; {$ifdef speed} inline; {$endif}
begin
Result := RadToDeg(ArcTan2(ly, lx));
end;
function Modulo(x, y: extended): extended; {$ifdef speed} inline; {$endif}
var
d: extended;
begin
d := x / y;
Result := (d - floor(d)) * y;
end;
function Degrees(Rads: Extended): Extended;
begin
Result := Rads*(180/Pi);
end;
function Modulo2(x: extended): extended; {$ifdef speed} inline; {$endif}
begin
if x < 0 then
Result := 360 + x
else
Result := x;
end;
function GetPointAngle(const lx, ly: integer): Extended;
begin
Result := Modulo(Degrees(ArcTan2(ly, lx)) - 90, 360);
end;
procedure OutTest(const lx, ly: extended);
var
a: extended;
begin
a := CalcAngle(lx, ly);
Writeln(
a: 10: 4,
Modulo(a - 90, 360):10:4,
GetPointAngle(round(lx), round(ly)):10:4);
end;
begin
OutTest(2, 0);
OutTest(0, 2);
OutTest(-2, 2);
OutTest(-2, -2);
OutTest(2, 3);
OutTest(100, 2);
Readln;
end.
I have a very simple class definition for 3D Vectors, TVector3D, and a few methods used to implement the TVector3D.Normalise function. If I pass the Normalise function a vector that is already normalised, I want it to return the vector I passed it. Here I have used Result := Self but I am having some crazy returns.
The console application:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TVector3D = Class
public
x : Single;
y : Single;
z : Single;
constructor Create(x : Single;
y : Single;
z : Single);
function GetMagnitude() : Single;
function IsUnitVector() : Boolean;
function Normalise() : TVector3D;
end;
constructor TVector3D.Create(x : Single;
y : Single;
z : Single);
begin
Self.x := x;
Self.y := y;
Self.z := z;
end;
function TVector3D.GetMagnitude;
begin
Result := Sqrt(Sqr(Self.x) + Sqr(Self.y) + Sqr(Self.z));
end;
function TVector3D.IsUnitVector;
begin
if Self.GetMagnitude = 1 then
Result := True
else
Result := False;
end;
function TVector3D.Normalise;
var
x : Single;
y : Single;
z : Single;
MagnitudeFactor : Single;
begin
if IsUnitVector then
Result := Self
else
MagnitudeFactor := 1/(Self.GetMagnitude);
x := Self.x*MagnitudeFactor;
y := Self.y*MagnitudeFactor;
z := Self.z*MagnitudeFactor;
Result := TVector3D.Create(x,
y,
z);
end;
procedure TestNormalise;
var
nonUnitVector : TVector3D;
unitVector : TVector3D;
nUVNormed : TVector3D;
uVNormed : TVector3D;
begin
//Setup Vectors for Test
nonUnitVector := TVector3D.Create(1,
1,
1);
unitVector := TVector3D.Create(1,
0,
0);
//Normalise Vectors & Free Memory
nUVNormed := nonUnitVector.Normalise;
nonUnitVector.Free;
uVNormed := unitVector.Normalise;
unitVector.Free;
//Print Output & Free Memory
WriteLn('nUVNormed = (' + FloatToStr(nUVNormed.x) + ', ' + FloatToStr(nUVNormed.y) + ', ' + FloatToStr(nUVNormed.z) + ')');
nUVNormed.Free;
WriteLn('uVNormed = (' + FloatToStr(uVNormed.x) + ', ' + FloatToStr(uVNormed.y) + ', ' + FloatToStr(uVNormed.z) + ')');
uVNormed.Free;
end;
begin
try
TestNormalise;
Sleep(10000);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Normalise works fine for non-unit vecors, i.e. IsUnitVector returns false. But for unit vectors, such as (1,0,0), instead of returning itself I get a result with very low nonzero numbers wherever there was a nonzero previously, such as (8.47122...E-38,0,0).
If I run this through the debugger with a breakpoint on the line Result := Self set to evaluate Self, Self is (1,0,0) yet result becomes (Very Low Number,0,0). Where Very Low Number changes each time I run the programme but always seems to be around E-38/E-39.
I do not understand why this happens. Why does it happen and how is it best to alter my Normalise function to avoid it.
Your current TVector3D.Normalise implementation has some issues:
The last 4 lines are always executed, because you have not used a begin-end block after the else,
So the routine never returns Self, but always a new instance,
The returned instance's memory is propably leaked because you lost ownership of it after the function call,
When IsUnitVector returns True, then the assignment of MagnitudeFactor will be skipped, and it will be a random value (currently present at that memory's address), which explains why you get rubish. You are also warned by the compiler for this: Variable MagnitudeFactor might not have been initialized.
Instead, I would rewrite the routine as follows:
function TVector3D.Normalise: TVector3D;
begin
if not IsUnitVector then
begin
x := x / GetMagnitude;
y := y / GetMagnitude;
z := z / GetMagnitude;
end;
Result := Self;
end;
The root of all your problems is that you are using a class, which is a reference type. Instead you need to make your vector be a value type. That means use a record.
In your code, even when you fix the problem identified by #NGLN, you have still destroyed all instances of your class by the time you start calling WriteLn.
Unless you grasp this issue soon, I fear that you will continue having problems. Switching to using a value type will make your coding trivially easy in comparison to your current approach.
Here's something to get you started:
type
TVector3 = record
public
class operator Negative(const V: TVector3): TVector3;
class operator Equal(const V1, V2: TVector3): Boolean;
class operator NotEqual(const V1, V2: TVector3): Boolean;
class operator Add(const V1, V2: TVector3): TVector3;
class operator Subtract(const V1, V2: TVector3): TVector3;
class operator Multiply(const V: TVector3; const D: Double): TVector3;
class operator Multiply(const D: Double; const V: TVector3): TVector3;
class operator Divide(const V: TVector3; const D: Double): TVector3;
class function New(const X, Y, Z: Double): TVector3; static;
function IsZero: Boolean;
function IsNonZero: Boolean;
function IsUnit: Boolean;
function Mag: Double;
function SqrMag: Double;
function Normalised: TVector3;
function ToString: string;
public
X, Y, Z: Double;
end;
const
ZeroVector3: TVector3=();
class operator TVector3.Negative(const V: TVector3): TVector3;
begin
Result.X := -V.X;
Result.Y := -V.Y;
Result.Z := -V.Z;
end;
class operator TVector3.Equal(const V1, V2: TVector3): Boolean;
begin
Result := (V1.X=V2.X) and (V1.Y=V2.Y) and (V1.Z=V2.Z);
end;
class operator TVector3.NotEqual(const V1, V2: TVector3): Boolean;
begin
Result := not (V1=V2);
end;
class operator TVector3.Add(const V1, V2: TVector3): TVector3;
begin
Result.X := V1.X + V2.X;
Result.Y := V1.Y + V2.Y;
Result.Z := V1.Z + V2.Z;
end;
class operator TVector3.Subtract(const V1, V2: TVector3): TVector3;
begin
Result.X := V1.X - V2.X;
Result.Y := V1.Y - V2.Y;
Result.Z := V1.Z - V2.Z;
end;
class operator TVector3.Multiply(const V: TVector3; const D: Double): TVector3;
begin
Result.X := D*V.X;
Result.Y := D*V.Y;
Result.Z := D*V.Z;
end;
class operator TVector3.Multiply(const D: Double; const V: TVector3): TVector3;
begin
Result.X := D*V.X;
Result.Y := D*V.Y;
Result.Z := D*V.Z;
end;
class operator TVector3.Divide(const V: TVector3; const D: Double): TVector3;
begin
Result := (1.0/D)*V;
end;
class function TVector3.New(const X, Y, Z: Double): TVector3;
begin
Result.X := X;
Result.Y := Y;
Result.Z := Z;
end;
function TVector3.IsZero: Boolean;
begin
Result := Self=ZeroVector3;
end;
function TVector3.IsNonZero: Boolean;
begin
Result := Self<>ZeroVector3;
end;
function TVector3.IsUnit: Boolean;
begin
Result := abs(1.0-Mag)<1.0e-5;
end;
function TVector3.Mag: Double;
begin
Result := Sqrt(X*X + Y*Y + Z*Z);
end;
function TVector3.SqrMag: Double;
begin
Result := X*X + Y*Y + Z*Z;
end;
function TVector3.Normalised;
begin
Result := Self/Mag;
end;
function TVector3.ToString: string;
begin
Result := Format('(%g, %g, %g)', [X, Y, Z]);
end;
This is extracted from my own codebase. I'm using Double, but if you really prefer to use Single, then you can readily change it.
The use of operator overloading makes the code you write so much more readable. Now you can write V3 := V1 + V2 and so on.
Here's what your test code looks like with this record:
var
nonUnitVector: TVector3;
unitVector: TVector3;
nUVNormed: TVector3;
uVNormed: TVector3;
begin
//Setup Vectors for Test
nonUnitVector := TVector3.New(1, 1, 1);
unitVector := TVector3.New(1, 0, 0);
//Normalise Vectors
nUVNormed := nonUnitVector.Normalised;
uVNormed := unitVector.Normalised;
//Print Output
WriteLn('nUVNormed = ' + nUVNormed.ToString);
WriteLn('uVNormed = ' + uVNormed.ToString);
Readln;
end.
Or if you want to compress it somewhat:
WriteLn('nUVNormed = ' + TVector3.New(1, 1, 1).Normalised.ToString);
WriteLn('uVNormed = ' + TVector3.New(1, 0, 0).Normalised.ToString);
A few hints:
First, I'd actually make the vector a record instead of a class if I were you, but YMMV. That would simplify a lot, since the compiler will manage the lifetime of every vector (you never need to worry about freeing things). Second,
function TVector3D.IsUnitVector;
begin
if self.GetMagnitude = 1 then
result := True
else
result := False;
end;
is normally written, syntactically and exactly equivalently,
function TVector3D.IsUnitVector;
begin
result := GetMagnitude = 1
end;
But even so, it is incorrect. Since you are dealing with floating-point numbers, you cannot reliably test equality. Instead, you should see if the magnitude is within some interval of unity, so that 'fuzz' do not interfere. For instance, you could do (uses Math)
function TVector3D.IsUnitVector;
begin
result := IsZero(GetMagnitude - 1)
end;
Third, your Normalize function returns a new vector object if it needs to normalize, and returns the same object if not. That's very confusing. You'd never know how many instances you have! Instead, make this a procedure:
procedure TVector3D.Normalize;
var
norm: single;
begin
norm := GetMagnitude;
x := x / norm;
y := y / norm;
z := z / norm;
end;
Fourth, why use single instead of double or real?
Fifth, as NGLN pointed out (please upvote his answer!), you forgot the begin...end block in the else part of your Normalize function, so the four last lines are always executed! Hence, you always create a new vector instance! Still, my point is very important: your original function 'intends' (if you just add the begin...end block) to return self or create a new instance depending on a condition, which is rather terrible, since then you do not know how many instances you have! (And so, you'll probably begin to leak vectors...)