{$mode objfpc} {$longstrings+} {$modeswitch duplicatelocals}
uses
SysUtils, Math, StrUtils;
type
float = single;
// для полуавтоматического подсчёта ссылок: сразу после создания объекта его
// нужно присвоить какой-нибудь Reference, и пока объект нужен,
// он должен быть присвоен хотя бы одной Reference.
// Таким образом, сильная ссылка на объект T хранится как v: T + life: Reference.
Reference = IInterface;
Vec3 = object
x, y, z: float;
class function Make(const x, y, z: float): Vec3; static;
procedure &Set(const x, y, z: float);
class function Zero: Vec3; static;
function Length: float;
function Normalized: Vec3;
function ToString: string;
end;
operator +(const a, b: Vec3): Vec3; begin result.&Set(a.x + b.x, a.y + b.y, a.z + b.z); end;
operator -(const a, b: Vec3): Vec3;begin result.&Set(a.x - b.x, a.y - b.y, a.z - b.z); end;
operator *(const a: Vec3; const b: float): Vec3; begin result.&Set(a.x * b, a.y * b, a.z * b); end;
class function Vec3.Make(const x, y, z: float): Vec3; begin result.&Set(x, y, z); end;
procedure Vec3.&Set(const x, y, z: float); begin self.x := x; self.y := y; self.z := z; end;
class function Vec3.Zero: Vec3; const ZeroVec3: Vec3 = (x: 0; y: 0; z: 0); begin result := ZeroVec3; end;
function Vec3.Length: float; begin result := sqrt(sqr(x) + sqr(y) + sqr(z)); end;
function Vec3.Normalized: Vec3; begin result := self * (1 / Length); end;
function Vec3.ToString: string; begin result := FloatToStr(x) + ', ' + FloatToStr(y) + ', ' + FloatToStr(z); end;
// Для динамического роста массивов:
// if count > length(storage) then SetLength(storage, ArrayGrowStgy(count, length(storage)));
function ArrayGrowStgy(desired, physical: SizeInt): SizeInt;
var
delta: SizeInt;
begin
Assert(desired > physical);
if physical <= 8 then delta := 4
else if physical <= 64 then delta := 16
else delta := physical div 4;
result := max(desired, physical + delta);
end;
type
// Менеджер текущих и новых значений.
// BindVec3 создаёт новый вектор и возвращает Vec3Binding с автоподсчётом в life.
// Vec3Binding.value предоставляет доступ к этому вектору, при этом
// запись откладывается до CommitFrame, или, другими словами,
// чтение возвращает значение с последнего CommitFrame.
CurNewManager = class(TInterfacedObject)
type
Vec3Binding = class(TInterfacedObject)
destructor Destroy; override;
private
mgr: CurNewManager;
mgrLife: Reference;
id: SizeInt;
curValue, newValue: Vec3;
public
property value: Vec3 read curValue write newValue;
end;
function BindVec3(var life: Reference): Vec3Binding;
procedure CommitFrame;
private
nVec3s: SizeInt;
vec3s: array of Vec3Binding;
end;
destructor CurNewManager.Vec3Binding.Destroy;
begin
// Удалить вектор из менеджера.
if Assigned(mgr) then
begin
mgr.vec3s[id] := mgr.vec3s[mgr.nVec3s - 1];
mgr.vec3s[id].id := id;
dec(mgr.nVec3s);
mgr := nil;
end;
end;
function CurNewManager.BindVec3(var life: Reference): Vec3Binding;
begin
if nVec3s + 1 > length(vec3s) then
SetLength(vec3s, ArrayGrowStgy(nVec3s + 1, length(vec3s)));
result := Vec3Binding.Create; life := result;
nVec3s += 1;
vec3s[nVec3s - 1] := result;
result.mgr := self; result.mgrLife := self;
result.id := nVec3s - 1;
end;
procedure CurNewManager.CommitFrame;
var
i: SizeInt;
begin
for i := 0 to nVec3s - 1 do
vec3s[i].curValue := vec3s[i].newValue;
end;
type
Thing = class(TInterfacedObject)
name: string;
posBind: CurNewManager.Vec3Binding;
posBindLife: Reference;
constructor Create(const name: string; mgr: CurNewManager);
function ToString: string; override;
function GetPos: Vec3;
procedure SetPos(const value: Vec3);
property pos: Vec3 read GetPos write SetPos;
end;
constructor Thing.Create(const name: string; mgr: CurNewManager);
begin
inherited Create;
self.name := name;
posBind := mgr.BindVec3(posBindLife);
end;
function Thing.ToString: string;
begin
result := name + ' (' + pos.ToString + ')';
end;
function Thing.GetPos: Vec3;
begin
result := posBind.value;
end;
procedure Thing.SetPos(const value: Vec3);
begin
posBind.value := value;
end;
var
mgr: CurNewManager;
mgrLife: Reference;
things: array of record
t: Thing;
tLife: Reference;
end;
procedure CohereThingToCenter(t: Thing; const d: float);
var
i: SizeInt;
center: Vec3;
begin
center := Vec3.Zero;
for i := 0 to High(things) do
center += things[i].t.pos;
center *= 1 / length(things);
writeln('Обновление ' + t.name + ': центр = ' + center.ToString);
t.pos := t.pos + (center - t.pos).Normalized * d;
end;
function DescribeThings: string;
var
i: SizeInt;
begin
result := '';
for i := 0 to High(things) do
result += IfThen(i > 0, ', ') + things[i].t.ToString;
end;
var
i: SizeInt;
begin
mgr := CurNewManager.Create; mgrLife := mgr;
SetLength(things, 3);
for i := 0 to High(things) do
begin
things[i].t := Thing.Create(chr(ord('A') + i mod (ord('Z') - ord('A') + 1)), mgr);
things[i].tLife := things[i].t;
end;
things[0].t.pos := Vec3.Make(10, 0, 0);
things[1].t.pos := Vec3.Make(18, 0, 0);
things[2].t.pos := Vec3.Make(20, 0, 0);
mgr.CommitFrame;
writeln('Начальные позиции: ' + DescribeThings);
for i := 0 to High(things) do CohereThingToCenter(things[i].t, 0.5);
writeln('Позиции перед коммитом: ' + DescribeThings);
mgr.CommitFrame;
writeln('Позиции после коммита, придвинутые к центру на 0.5:' + LineEnding + DescribeThings);
end.