我有一个非常简单的3D矢量类定义TVector3D,以及一些用于实现该TVector3D.Normalise功能的方法.如果我将Normalise函数传递给已经规范化的向量,我希望它返回我传递给它的向量.在这里我已经习惯了,Result := Self但我有一些疯狂的回报.
控制台应用程序:
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.
Run Code Online (Sandbox Code Playgroud)
Normalise适用于非单位vecors,即IsUnitVector返回false.但是对于单位向量,例如(1,0,0),而不是返回自身,我得到的结果是非零的非零数,只要之前有非零值,例如(8.47122...E-38,0,0).
如果我通过调试器运行此操作,并在Result := Self设置为评估Self 的行上设置断点,则Self (1,0,0)结果变为(Very Low Number,0,0).当Very Low Number每个I运行程序,但似乎总是围绕时间变化E-38/E-39.
我不明白为什么会这样.为什么会发生这种情况,如何最好地改变我的Normalise功能以避免它.
您当前的TVector3D.Normalise实施有一些问题:
begin-end之后没有使用过块else,Self,但总是一个新的实例,IsUnitVector返回时True,MagnitudeFactor将跳过赋值,它将是一个随机值(当前存在于该存储器的地址),这解释了为什么你会得到rubish.编译器也会警告您:变量MagnitudeFactor可能尚未初始化.相反,我会按如下方式重写例程:
function TVector3D.Normalise: TVector3D;
begin
if not IsUnitVector then
begin
x := x / GetMagnitude;
y := y / GetMagnitude;
z := z / GetMagnitude;
end;
Result := Self;
end;
Run Code Online (Sandbox Code Playgroud)
所有问题的根源在于您使用的是类,它是一种引用类型.相反,您需要使矢量成为值类型.这意味着使用a record.
在您的代码中,即使您修复了@NGLN标识的问题,您仍然会在开始调用时销毁所有类的实例WriteLn.
除非你很快掌握这个问题,否则我担心你会继续遇到问题.与您当前的方法相比,切换到使用值类型将使您的编码变得简单.
这是让你入门的东西:
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;
Run Code Online (Sandbox Code Playgroud)
这是从我自己的代码库中提取的.我正在使用Double,但如果你真的喜欢使用Single,那么你可以随时改变它.
使用运算符重载使您编写的代码更具可读性.现在你可以写V3 := V1 + V2等等.
以下是此记录的测试代码:
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.
Run Code Online (Sandbox Code Playgroud)
或者如果你想稍微压缩它:
WriteLn('nUVNormed = ' + TVector3.New(1, 1, 1).Normalised.ToString);
WriteLn('uVNormed = ' + TVector3.New(1, 0, 0).Normalised.ToString);
Run Code Online (Sandbox Code Playgroud)
一些提示:
首先,如果我是你,我实际上会将矢量设为记录而不是类,而是YMMV.这会简化很多,因为编译器将管理每个向量的生命周期(您永远不必担心释放事物).第二,
function TVector3D.IsUnitVector;
begin
if self.GetMagnitude = 1 then
result := True
else
result := False;
end;
Run Code Online (Sandbox Code Playgroud)
通常是书面的,语法上和完全相同的,
function TVector3D.IsUnitVector;
begin
result := GetMagnitude = 1
end;
Run Code Online (Sandbox Code Playgroud)
但即使这样,也是不对的.由于您正在处理浮点数,因此无法可靠地测试相等性.相反,您应该看看幅度是否在一定的统一区间内,以便"模糊"不会干扰.例如,你可以做(uses Math)
function TVector3D.IsUnitVector;
begin
result := IsZero(GetMagnitude - 1)
end;
Run Code Online (Sandbox Code Playgroud)
第三,如果需要规范化,则Normalize函数返回一个新的矢量对象,否则返回相同的对象.这非常令人困惑.你永远不知道你有多少个实例!相反,将此作为一个过程:
procedure TVector3D.Normalize;
var
norm: single;
begin
norm := GetMagnitude;
x := x / norm;
y := y / norm;
z := z / norm;
end;
Run Code Online (Sandbox Code Playgroud)
四,为什么用single的,而不是double或real?
第五,正如NGLN所指出的那样(请回答他的答案!),你忘记了函数部分的begin...end块,所以最后四行总是被执行!因此,您始终创建一个新的矢量实例!不过,我的观点非常重要:你的原始函数'打算'(如果你只是添加块)返回或根据条件创建一个新实例,这是相当可怕的,因为那时你不知道你有多少个实例!(所以,你可能会开始泄漏矢量......)elseNormalizebegin...endself