Apophysis-AV/Flame/RndFlame.pas

624 lines
19 KiB
ObjectPascal
Raw Normal View History

2022-03-08 12:25:51 -05:00
{
Apophysis Copyright (C) 2001-2004 Mark Townsend
Apophysis Copyright (C) 2005-2006 Ronald Hordijk, Piotr Borys, Peter Sdobnov
Apophysis Copyright (C) 2007-2008 Piotr Borys, Peter Sdobnov
Apophysis "3D hack" Copyright (C) 2007-2008 Peter Sdobnov
Apophysis "7X" Copyright (C) 2009-2010 Georg Kiehne
Apophysis AV "Phoenix Edition" Copyright (C) 2021-2022 Alice V. Koryagina
2022-03-08 12:25:51 -05:00
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
}
unit RndFlame;
interface
uses
ControlPoint;
2022-03-08 12:25:51 -05:00
function RandomFlame(SourceCP: TControlPoint = nil; algorithm: integer = 0): TControlPoint;
// AV: made the following public to get rid of duplicated code
procedure SetVariation(cp: TControlPoint);
procedure RandomVariation(cp: TControlPoint);
2022-03-08 12:25:51 -05:00
implementation
uses
SysUtils, Global, cmap, GradientHlpr, XFormMan, Classes;
///////////////////////////////////////////////////////////////////////////////
// AV: this procedure repeated in the source code 3 times!
(* procedure RGBBlend(a, b: integer; var Palette: TColorMap);
{ Linear blend between to indices of a palette }
var
c, v: real;
vrange, range: real;
i: integer;
begin
if a = b then
begin
Exit;
end;
range := b - a;
vrange := Palette[b mod 256][0] - Palette[a mod 256][0];
c := Palette[a mod 256][0];
v := vrange / range;
for i := (a + 1) to (b - 1) do
begin
c := c + v;
Palette[i mod 256][0] := Round(c);
end;
vrange := Palette[b mod 256][1] - Palette[a mod 256][1];
c := Palette[a mod 256][1];
v := vrange / range;
for i := a + 1 to b - 1 do
begin
c := c + v;
Palette[i mod 256][1] := Round(c);
end;
vrange := Palette[b mod 256][2] - Palette[a mod 256][2];
c := Palette[a mod 256][2];
v := vrange / range;
for i := a + 1 to b - 1 do
begin
c := c + v;
Palette[i mod 256][2] := Round(c);
end;
end; *)
procedure GetGradientFileGradientsNames(const filename: string; var NamesList: TStringList);
var
i, p: integer;
Title: string;
FStrings: TStringList;
begin
FStrings := TStringList.Create;
FStrings.LoadFromFile(filename);
try
if (Pos('{', FStrings.Text) <> 0) then
begin
for i := 0 to FStrings.Count - 1 do
begin
p := Pos('{', FStrings[i]);
if (p <> 0) and (Pos('(3D)', FStrings[i]) = 0) then
begin
Title := Trim(Copy(FStrings[i], 1, p - 1));
if Title <> '' then
NamesList.Add(Trim(Copy(FStrings[i], 1, p - 1)));
end;
end;
end;
finally
FStrings.Free;
end;
end;
procedure RandomGradient(SourceCP, DestCP: TControlPoint);
var
tmpGrad: string;
tmpGrdList: TStringList;
begin
case randGradient of
0:
begin
cmap_index := Random(NRCMAPS);
GetCMap(cmap_index, 1, DestCP.cmap);
// cmap_index := DestCP.cmapindex;
DestCP.cmapIndex := cmap_index;
end;
1:
begin
DestCP.cmap := DefaultPalette;
DestCP.cmapIndex := cmap_index;
end;
2:
if assigned(SourceCP) then begin
DestCP.cmap := SourceCP.cmap;
DestCP.cmapIndex := SourceCP.cmapIndex;
end else begin
cmap_index := Random(NRCMAPS);
GetCMap(cmap_index, 1, DestCP.cmap);
DestCP.cmapIndex := cmap_index;
end;
3:
DestCP.cmap := GradientHelper.RandomGradient;
4:
if FileExists(randGradientFile) then
begin
tmpGrdList := TStringList.Create;
GetGradientFileGradientsNames(randGradientFile, tmpGrdList);
tmpGrad := GetGradient(randGradientFile, tmpGrdList.Strings[random(tmpGrdList.Count)]);
DestCP.cmap := CreatePalette(tmpGrad);
tmpGrdList.Free;
end else
begin
cmap_index := Random(NRCMAPS);
GetCMap(cmap_index, 1, DestCP.cmap);
DestCP.cmapIndex := cmap_index;
end;
end;
end;
{ ****************** Triangle transformations ******************************* }
function transform_affine(const t: TTriangle; const Triangles: TTriangles): boolean;
var
ra, rb, rc, a, b, c: double;
begin
Result := True;
ra := dist(Triangles[-1].y[0], Triangles[-1].x[0],
Triangles[-1].y[1], Triangles[-1].x[1]);
rb := dist(Triangles[-1].y[1], Triangles[-1].x[1],
Triangles[-1].y[2], Triangles[-1].x[2]);
rc := dist(Triangles[-1].y[2], Triangles[-1].x[2],
Triangles[-1].y[0], Triangles[-1].x[0]);
a := dist(t.y[0], t.x[0], t.y[1], t.x[1]);
b := dist(t.y[1], t.x[1], t.y[2], t.x[2]);
c := dist(t.y[2], t.x[2], t.y[0], t.x[0]);
if (a > ra) then
Result := False
else if (b > rb) then
Result := False
else if (c > rc) then
Result := False
else if ((a = ra) and (b = rb) and (c = rc)) then
Result := False;
end;
function triangle_area(t: TTriangle): double;
var
base, height: double;
begin
try
base := dist(t.x[0], t.y[0], t.x[1], t.y[1]);
height := line_dist(t.x[2], t.y[2], t.x[1], t.y[1],
t.x[0], t.y[0]);
if (base < 1.0) then
Result := height
else if (height < 1.0) then
Result := base
else
Result := 0.5 * base * height;
except on E: EMathError do
Result := 0;
end;
end;
{ ************************************************************************** }
2022-03-08 12:25:51 -05:00
procedure RandomVariation(cp: TControlPoint);
{ Randomise variation parameters }
var
a, b, i, j: integer;
VarPossible: boolean;
begin
inc(MainSeed);
RandSeed := MainSeed;
VarPossible := false;
for j := 0 to NRVAR - 1 do
if Variations[j] then begin // AV: make it faster!
VarPossible := True;
break;
end;
if not VarPossible then Variations[0] := True; // AV
2022-03-08 12:25:51 -05:00
for i := 0 to cp.NumXForms - 1 do begin
for j := 0 to NRVAR - 1 do
cp.xform[i].SetVariation(j, 0);
repeat
a := random(NRVAR);
until Variations[a];
repeat
b := random(NRVAR);
until Variations[b];
2022-03-08 12:25:51 -05:00
if (a = b) then begin
cp.xform[i].SetVariation(a, 1);
end else begin
cp.xform[i].SetVariation(a, random);
cp.xform[i].SetVariation(b, 1 - cp.xform[i].GetVariation(a));
end;
end;
end;
///////////////////////////////////////////////////////////////////////////////
procedure SetVariation(cp: TControlPoint);
{ Set the current Variation }
var
i, j: integer;
begin
if Variation = vRandom then begin
RandomVariation(cp);
end else
for i := 0 to cp.NumXForms - 1 do begin
for j := 0 to NRVAR - 1 do
cp.xform[i].SetVariation(j, 0);
cp.xform[i].SetVariation(Variation, 1);
2022-03-08 12:25:51 -05:00
end;
end;
///////////////////////////////////////////////////////////////////////////////
(* --Z-- hmm, exactly the same function exists in module Main
function TrianglesFromCP(const cp1: TControlPoint; var Triangles: TTriangles): integer;
{ Sets up the triangles from the IFS code }
var
xforms: integer;
i, j: integer;
temp_x, temp_y, xset, yset: double;
left, top, bottom, right: double;
a, b, c, d, e, f: double;
begin
top := 0; bottom := 0; right := 0; left := 0;
xforms := NumXForms(cp1);
Result := xforms;
if not FixedReference then
begin
for i := 0 to xforms - 1 do
begin
a := cp1.xform[i].c[0][0];
b := cp1.xform[i].c[0][1];
c := cp1.xform[i].c[1][0];
d := cp1.xform[i].c[1][1];
e := cp1.xform[i].c[2][0];
f := cp1.xform[i].c[2][1];
xset := 1.0;
yset := 1.0;
for j := 0 to 5 do
begin
temp_x := xset * a + yset * c + e;
temp_y := xset * b + yset * d + f;
xset := temp_x;
yset := temp_y;
end;
if (i = 0) then
begin
left := xset;
right := xset;
top := yset;
bottom := yset;
end
else
begin
if (xset < left) then left := xset;
if (xset > right) then right := xset;
if (yset < top) then top := yset;
if (yset > bottom) then bottom := yset;
end;
end;
Triangles[-1].x[0] := left;
Triangles[-1].x[1] := right;
Triangles[-1].x[2] := right;
Triangles[-1].y[0] := bottom;
Triangles[-1].y[1] := bottom;
Triangles[-1].y[2] := top;
end
else
begin
Triangles[-1].x[0] := 0; Triangles[-1].y[0] := 0;
Triangles[-1].x[1] := 1; Triangles[-1].y[1] := 0;
Triangles[-1].x[2] := 1; Triangles[-1].y[2] := 1.5;
end;
for j := 0 to xforms - 1 do
begin
a := cp1.xform[j].c[0][0];
b := cp1.xform[j].c[0][1];
c := cp1.xform[j].c[1][0];
d := cp1.xform[j].c[1][1];
e := cp1.xform[j].c[2][0];
f := cp1.xform[j].c[2][1];
for i := 0 to 2 do
begin
triangles[j].x[i] := Triangles[-1].x[i] * a + Triangles[-1].y[i] *
c + e;
triangles[j].y[i] := Triangles[-1].x[i] * b + Triangles[-1].y[i] *
d + f;
end;
end;
for i := -1 to xforms - 1 do
for j := 0 to 2 do
triangles[i].y[j] := -triangles[i].y[j];
end;
*)
///////////////////////////////////////////////////////////////////////////////
procedure EqualizeWeights(var cp: TControlPoint);
var
t, i: integer;
begin
t := cp.NumXForms;
for i := 0 to t - 1 do
cp.xform[i].density := 1.0 / t;
end;
///////////////////////////////////////////////////////////////////////////////
procedure NormalizeWeights(var cp: TControlPoint);
var
i: integer;
td: double;
begin
td := 0.0;
for i := 0 to cp.NumXForms - 1 do
td := td + cp.xform[i].Density;
if (td < 0.001) then
EqualizeWeights(cp)
else
for i := 0 to cp.NumXForms - 1 do
cp.xform[i].Density := cp.xform[i].Density / td;
end;
///////////////////////////////////////////////////////////////////////////////
procedure ComputeWeights(var cp1: TControlPoint; Triangles: TTriangles; t: integer);
{ Caclulates transform weight from triangles }
var
i: integer;
total_area: double;
begin
total_area := 0.0;
for i := 0 to t - 1 do
begin
cp1.xform[i].Density := triangle_area(Triangles[i]);
total_area := total_area + cp1.xform[i].Density;
end;
for i := 0 to t - 1 do
begin
cp1.xform[i].Density := cp1.xform[i].Density / total_area;
end;
NormalizeWeights(cp1);
end;
///////////////////////////////////////////////////////////////////////////////
(*
2022-03-08 12:25:51 -05:00
procedure RandomWeights(var cp1: TControlPoint);
{ Randomizes xform weights }
var
i: integer;
begin
for i := 0 to cp1.NumXForms - 1 do // AV: was Transforms - 1
2022-03-08 12:25:51 -05:00
cp1.xform[i].Density := random;
NormalizeWeights(cp1);
end;
*)
2022-03-08 12:25:51 -05:00
///////////////////////////////////////////////////////////////////////////////
function RandomFlame(SourceCP: TControlPoint; algorithm: integer): TControlPoint;
var
Min, Max, i, j, rnd: integer;
Triangles: TTriangles;
r, s, theta, phi: double;
cosphi, sintheta, costheta: double;
skip: boolean;
begin
if Assigned(SourceCP) then
Result := SourceCP.clone
else
Result := TControlPoint.Create;
Min := randMinTransforms;
Max := randMaxTransforms;
inc(MainSeed);
RandSeed := MainSeed;
transforms := random(Max - (Min - 1)) + Min;
2022-03-08 12:25:51 -05:00
repeat
try
inc(MainSeed);
RandSeed := MainSeed;
Result.Clear;
for i := 0 to Transforms - 1 do // AV: set starting non-zero weights...
Result.xform[i].density := 0.5;
// AV: we don't really need to randomize the same flame twice...
//Result.RandomCP(transforms, transforms, false);
//Result.SetVariation(Variation); // AV: deprecated!
SetVariation(Result); // AV: replaced old method from ControlPoint
2022-03-08 12:25:51 -05:00
case algorithm of
1: rnd := 0;
2: rnd := 7;
3: rnd := 9;
else
if (Variation = 0 {vLinear}) or (Variation = vRandom) then
2022-03-08 12:25:51 -05:00
rnd := random(10)
else
rnd := 9;
end;
case rnd of
0..6:
begin
for i := 0 to Transforms - 1 do
begin
// AV: useless assignments
{if Random(10) < 9 then
Result.xform[i].c[0, 0] := 1
else
Result.xform[i].c[0, 0] := -1;
Result.xform[i].c[0, 1] := 0;
Result.xform[i].c[1, 0] := 0;
Result.xform[i].c[1, 1] := 1;
Result.xform[i].c[2, 0] := 0;
Result.xform[i].c[2, 1] := 0;
Result.xform[i].color := 0;
// AV: this will be done with SetVariation method!
2022-03-08 12:25:51 -05:00
Result.xform[i].SetVariation(0, 1);
for j := 1 to NRVAR - 1 do
Result.xform[i].SetVariation(j, 0); }
2022-03-08 12:25:51 -05:00
// AV: hundred of useless calculations
{Result.xform[i].Translate(random * 2 - 1, random * 2 - 1);
Result.xform[i].Rotate(random * 360);
if i > 0 then
Result.xform[i].Scale(random * 0.8 + 0.2)
else
Result.xform[i].Scale(random * 0.4 + 0.6); }
Result.xform[i].symmetry := 0;
2022-03-08 12:25:51 -05:00
if i > 0 then
s := random * 0.8 + 0.2
else
s := random * 0.4 + 0.6;
theta := random * 2 * pi;
sintheta := s * sin(theta);
costheta := s * cos(theta);
if Random(10) < 9 then
begin
Result.xform[i].c[0, 0] := costheta;
Result.xform[i].c[1, 0] := sintheta;
end
else begin
Result.xform[i].c[0, 0] := -costheta;
Result.xform[i].c[1, 0] := -sintheta;
end;
Result.xform[i].c[0, 1] := -sintheta;
Result.xform[i].c[1, 1] := costheta;
Result.xform[i].c[2, 0] := random * 2 - 1;
Result.xform[i].c[2, 1] := random * 2 - 1;
if Random(2) = 0 then
Result.xform[i].Multiply(Result.xform[i].c,
1, random - 0.5, random - 0.5, 1);
2022-03-08 12:25:51 -05:00
if Random(2) = 1 then
begin
// AV: added post-transforms support!
theta := 2 * random * pi;
r := 0.3 + random * 0.7;
sintheta := r * sin(theta);
costheta := r * cos(theta);
Result.xform[i].p[0, 0] := costheta;
Result.xform[i].p[0, 1] := sintheta;
Result.xform[i].p[1, 0] := -sintheta;
Result.xform[i].p[1, 1] := costheta;
Result.xform[i].p[2,0] := random * 2 - 1;
Result.xform[i].p[2,1] := random * 2 - 1;
end;
end;
//SetVariation(Result);
2022-03-08 12:25:51 -05:00
end;
7, 8:
begin
{ From the source to Chaos: The Software }
for i := 0 to Transforms - 1 do begin
r := random * 2 - 1;
if ((0 <= r) and (r < 0.2)) then
r := r + 0.2;
if ((r > -0.2) and (r <= 0)) then
r := r - 0.2;
s := random * 2 - 1;
if ((0 <= s) and (s < 0.2)) then
s := s + 0.2;
if ((s > -0.2) and (s <= 0)) then
s := s - -0.2;
theta := PI * random;
phi := (2 + random) * PI / 4;
// AV: we don't need to calculate everything twice!
sintheta := sin(theta);
costheta := cos(theta);
cosphi := cos(phi);
Result.xform[i].c[0][0] := r * costheta;
Result.xform[i].c[1][0] := s * (costheta * cosphi - sintheta);
Result.xform[i].c[0][1] := r * sintheta;
Result.xform[i].c[1][1] := s * (sintheta * cosphi + costheta);
{ the next bit didn't translate so well, so I fudge it}
Result.xform[i].c[2][0] := random * 2 - 1;
Result.xform[i].c[2][1] := random * 2 - 1;
end;
{ // AV: the following was (or will be) done!
2022-03-08 12:25:51 -05:00
for i := 0 to NXFORMS-1 do
Result.xform[i].density := 0;
for i := 0 to Transforms - 1 do
Result.xform[i].density := 1 / Transforms;
SetVariation(Result);
}
2022-03-08 12:25:51 -05:00
end;
9: begin
{ // AV: the following was (or will be) done!
for i := 0 to NXFORMS-1 do
2022-03-08 12:25:51 -05:00
Result.xform[i].density := 0;
for i := 0 to Transforms - 1 do
Result.xform[i].density := 1 / Transforms;
}
for i := 0 to Transforms - 1 do begin // AV
Result.xform[i].RandomizeCoefs(Result.xform[i].c);
if Random(4) = 1 then
Result.xform[i].RandomizeCoefs(Result.xform[i].p);
end;
2022-03-08 12:25:51 -05:00
end;
end; // case
Result.TrianglesFromCp(Triangles);
2022-03-08 12:25:51 -05:00
if Random(2) > 0 then
ComputeWeights(Result, Triangles, transforms)
else
EqualizeWeights(Result);
except on E: EmathError do
begin
Continue;
end;
end;
for i := 0 to Transforms - 1 do
Result.xform[i].color := i / (transforms - 1);
if Result.xform[0].density = 1 then
Continue;
case SymmetryType of
{ Bilateral }
1: add_symmetry_to_control_point(Result, -1);
{ Rotational }
2: add_symmetry_to_control_point(Result, SymmetryOrder);
{ Rotational and Reflective }
3: add_symmetry_to_control_point(Result, -SymmetryOrder);
end;
{ elimate flames with transforms that aren't affine }
skip := false;
for i := 0 to Transforms - 1 do begin
if not transform_affine(Triangles[i], Triangles) then
skip := True;
end;
if skip then
continue;
until not Result.BlowsUP(5000) and (Result.xform[0].density <> 0);
RandomGradient(SourceCP, Result);
Result.brightness := defBrightness;
Result.gamma := defGamma;
Result.gamma_threshold := defGammaThreshold;
Result.vibrancy := defVibrancy;
Result.sample_density := defSampleDensity;
Result.spatial_oversample := defOversample;
Result.spatial_filter_radius := defFilterRadius;
Result.contrast := defContrast; // AV
if KeepBackground and assigned(SourceCP) then begin
Result.background[0] := SourceCP.background[0];
Result.background[1] := SourceCP.background[1];
Result.background[2] := SourceCP.background[2];
end else if (RandBackColor <> 0) then begin // AV: to set the user's predefined BG
Result.background[0] := RandBackColor and 255;
Result.background[1] := RandBackColor shr 8 and 255;
Result.background[2] := RandBackColor shr 16 and 255;
end else begin
Result.background[0] := 0;
Result.background[1] := 0;
Result.background[2] := 0;
end;
Result.zoom := 0;
Result.xform[Result.NumXForms].Clear;
Result.xform[Result.NumXForms].symmetry := 1;
end;
end.