{
Ported to FPC by Nikolay Nikolov (nickysn@users.sourceforge.net)
}

{
Mojo demo for OpenPTC 1.0 C++ API
Coded by Alex Evans and adapted to OpenPTC 1.0 by Glenn Fiedler

nasty code by alex "statix" evans for ptc. (c) copyright alex evans 1998
time... 02.00 am on 13/1/98.
have fun
it's my take on some classic light mask effect
it's raytracing through properly modelled fog with occlusion, multiple
shadow rays cast back to the light for each pixel ray, and erm, its
s l o w... but it looks nice don't it?

oh and fresnel fall off... or something

UNTESTED! ok?

define inv for interesting fx (not)
}

Program Mojo;

{$MODE objfpc}

Uses
  ptc, SysUtils;

{ $DEFINE INV}

Const
  SC = 12;
  MINSEGSIZE = 2.5;
  NSEG = 5;
  frandtab_seed : short16 = 54;

Var
  MaskMap : Pchar8;
  frandtab : Array[0..65535] Of short16;

Type
  FVector = Object
{    Case Boolean Of
      False : (X, Y, Z : Single);
      True : (R, G, B : Single);}
    X, Y, Z : Single;

    Constructor Init;
    Constructor Init(_x, _y, _z : Single);

    Function Magnitude : Single;
    Function MagnitudeSq : Single;
    Procedure Normalise;
  End;
  FMatrix = Object
    Row : Array[0..2] Of FVector;
    Constructor Init;
    Constructor Init(a, b, c : FVector);
    Function Column0 : FVector;
    Function Column1 : FVector;
    Function Column2 : FVector;
    Procedure MakeXRot(theta : Single);
    Procedure MakeYRot(theta : Single);
    Procedure MakeZRot(theta : Single);
    Procedure MakeID;
    Function Transpose : FMatrix;
    Procedure TransposeInPlace;
    Procedure Normalise;
  End;
  PRay = ^TRay;
  TRay = Object
    mPosn : FVector;
    mDir : FVector;
    Constructor Init(Const p, d : FVector);
  End;
  VLight = Class(TObject)
    mAng : Single;
    mPosn : FVector;
    mTarget : FVector;
    mAxis : FMatrix;
    mCol : FVector;

    p, p2, _d : FVector; { temp space }

    Constructor Create(Const col : FVector);
    Procedure Move(Const q : FVector);
    Procedure MoveT(Const q : FVector);
    Procedure Update;
    Function Light(Const ray : TRay) : FVector;
    Function CalcLight(t : Single) : Single;
  End;

Constructor FVector.Init;

Begin
End;

Constructor FVector.Init(_x, _y, _z : Single);

Begin
  X := _x;
  Y := _y;
  Z := _z;
End;

Function FVector.Magnitude : Single;

Begin
  Magnitude := Sqrt(Sqr(X) + Sqr(Y) + Sqr(Z));
End;

Function FVector.MagnitudeSq : Single;

Begin
  MagnitudeSq := Sqr(X) + Sqr(Y) + Sqr(Z);
End;

Procedure FVector.Normalise;

Var
  l : Single;

Begin
  l := 1 / Magnitude;
  X *= l;
  Y *= l;
  Z *= l;
End;

Operator * (a, b : FVector) res : Single;

Begin
  res := a.X * b.X + a.Y * b.Y + a.Z * b.Z;
End;

Operator * (a : FVector; b : Single) res : FVector;

Begin
  res.X := a.X * b;
  res.Y := a.Y * b;
  res.Z := a.Z * b;
End;

Operator + (a, b : FVector) res : FVector;

Begin
  res.X := a.X + b.X;
  res.Y := a.Y + b.Y;
  res.Z := a.Z + b.Z;
End;

Operator - (a, b : FVector) res : FVector;

Begin
  res.X := a.X - b.X;
  res.Y := a.Y - b.Y;
  res.Z := a.Z - b.Z;
End;

Operator ** (a, b : FVector) res : FVector;

Begin
  res.X := a.Y * b.Z - a.Z * b.Y;
  res.Y := a.Z * b.X - a.X * b.Z;
  res.Z := a.X * b.Y - a.Y * b.X;
End;

Constructor FMatrix.Init;

Begin
End;

Constructor FMatrix.Init(a, b, c : FVector);

Begin
  Row[0] := a;
  Row[1] := b;
  Row[2] := c;
End;

Function FMatrix.Column0 : FVector;

Var
  res : FVector;

Begin
  res.Init(Row[0].X, Row[1].X, Row[2].X);
  Column0 := res;
End;

Function FMatrix.Column1 : FVector;

Var
  res : FVector;

Begin
  res.Init(Row[0].Y, Row[1].Y, Row[2].Y);
  Column1 := res;
End;

Function FMatrix.Column2 : FVector;

Var
  res : FVector;

Begin
  res.Init(Row[0].Z, Row[1].Z, Row[2].Z);
  Column2 := res;
End;

Procedure FMatrix.MakeXRot(theta : Single);

Var
  c, s : Single;

Begin
  c := cos(theta);
  s := sin(theta);
  Row[1].Y := c; Row[1].Z := s; Row[1].X := 0;
  Row[2].Y := -s; Row[2].Z := c; Row[2].X := 0;
  Row[0].Y := 0; Row[0].Z := 0; Row[0].X := 1;
End;

Procedure FMatrix.MakeYRot(theta : Single);

Var
  c, s : Single;

Begin
  c := cos(theta);
  s := sin(theta);
  Row[2].Z := c; Row[2].X := s; Row[2].Y := 0;
  Row[0].Z := -s; Row[0].X := c; Row[0].Y := 0;
  Row[1].Z := 0; Row[1].X := 0; Row[1].Y := 1;
End;

Procedure FMatrix.MakeZRot(theta : Single);

Var
  c, s : Single;

Begin
  c := cos(theta);
  s := sin(theta);
  Row[0].X := c; Row[0].Y := s; Row[0].Z := 0;
  Row[1].X := -s; Row[1].Y := c; Row[1].Z := 0;
  Row[2].X := 0; Row[2].Y := 0; Row[2].Z := 1;
End;

Procedure FMatrix.MakeID;

Begin
  Row[0].Init(1, 0, 0);
  Row[1].Init(0, 1, 0);
  Row[2].Init(0, 0, 1);
End;

Function FMatrix.Transpose : FMatrix;

Var
  res : FMatrix;

Begin
  res.Init(Column0, Column1, Column2);
  Transpose := res;
End;

Procedure FMatrix.TransposeInPlace;

Begin
  Init(Column0, Column1, Column2);
End;

Procedure FMatrix.Normalise;

Begin
  Row[2].Normalise;
  Row[0] := Row[1]**Row[2];
  Row[0].Normalise;
  Row[1] := Row[2]**Row[0];
  Row[1].Normalise;
End;

Operator * (Const m : FMatrix; Const a : Single) res : FMatrix;

Begin
  res.Init(m.Row[0]*a, m.Row[1]*a, m.Row[2]*a);
End;

Operator * (Const m, a : FMatrix) res : FMatrix;

Var
  v1, v2, v3 : FVector;

Begin
  v1.Init(m.Row[0].X*a.Row[0].X+m.Row[0].Y*a.Row[1].X+m.Row[0].Z*a.Row[2].X,
	  m.Row[0].X*a.Row[0].Y+m.Row[0].Y*a.Row[1].Y+m.Row[0].Z*a.Row[2].Y,
	  m.Row[0].X*a.Row[0].Z+m.Row[0].Y*a.Row[1].Z+m.Row[0].Z*a.Row[2].Z);
  v2.Init(m.Row[1].X*a.Row[0].X+m.Row[1].Y*a.Row[1].X+m.Row[1].Z*a.Row[2].X,
	  m.Row[1].X*a.Row[0].Y+m.Row[1].Y*a.Row[1].Y+m.Row[1].Z*a.Row[2].Y,
	  m.Row[1].X*a.Row[0].Z+m.Row[1].Y*a.Row[1].Z+m.Row[1].Z*a.Row[2].Z);
  v3.Init(m.Row[2].X*a.Row[0].X+m.Row[2].Y*a.Row[1].X+m.Row[2].Z*a.Row[2].X,
	  m.Row[2].X*a.Row[0].Y+m.Row[2].Y*a.Row[1].Y+m.Row[2].Z*a.Row[2].Y,
	  m.Row[2].X*a.Row[0].Z+m.Row[2].Y*a.Row[1].Z+m.Row[2].Z*a.Row[2].Z);
  res.Init(v1, v2, v3);
End;

Operator * (Const m : FMatrix; Const a : FVector) res : FVector;

Begin
  res.Init(a*m.Row[0], a*m.Row[1], a*m.Row[2]);
End;

Operator + (Const m, a : FMatrix) res : FMatrix;

Begin
  res.Init(m.Row[0]+a.Row[0], m.Row[1]+a.Row[1], m.Row[2]+a.Row[2]);
End;

Operator - (Const m, a : FMatrix) res : FMatrix;

Begin
  res.Init(m.Row[0]+a.Row[0], m.Row[1]+a.Row[1], m.Row[2]+a.Row[2]);
End;

Constructor TRay.Init(Const p, d : FVector);

Begin
  mPosn := p;
  mDir := d;
  mDir.Normalise;
End;

Constructor VLight.Create(Const col : FVector);

Begin
  mCol := col * 0.9;
  mAng := 2.8;
  mPosn.Init(0, 0, 20);
  mTarget.Init(0, 0, 0.1);
  mAxis.MakeID;
  Update;
End;

Procedure VLight.Move(Const q : FVector);

Begin
  mPosn := q;
  Update;
End;

Procedure VLight.MoveT(Const q : FVector);

Begin
  mTarget := q;
  Update;
End;

Procedure VLight.Update;

Begin
  mAxis.Row[2] := (mTarget - mPosn);
  mAxis.Normalise;
End;

Function VLight.Light(Const ray : TRay) : FVector;

Var
  f, A, B, C, D, t1, t2, t3, fr, l1, l2, t, h : Single;
  frc, x, y, q : Integer;
  pp : FVector;
  res : FVector;

Begin
  f := 0;

  p2 := ray.mPosn;
  p := mAxis * (ray.mPosn - mPosn);
  _d := mAxis * ray.mDir;
  A := (_d.X*_d.X+_d.Y*_d.Y);
  B := 2*(_d.X*p.X+_d.Y*p.Y)-mAng*(_d.Z);
  C := (p.X*p.X+p.Y*p.Y)-mAng*(p.Z);
  D := B*B-4*A*C;
  If D <= 0 Then
  Begin
    res.Init(0, 0, 0);
    Light := res;
    Exit;
  End;
  D := Sqrt(D);
  A *= 2;
  t1 := (-B-D)/A;
  t2 := (-B+D)/A;
  frc := 255;
  t3 := -ray.mPosn.Z/ray.mDir.Z;
  If t2<=0 Then
  Begin
    res.Init(0, 0, 0);
    Light := res;
    Exit;
  End;
  If t1<0 Then
    t1 := 0;
  If t3>0 Then
  Begin
    { clip to bitmap plane }
    pp := ray.mPosn + ray.mDir*t3;
    x := 160+Trunc(SC*pp.X);
{$IFNDEF INV}
    If (x>=0) And (x<=319) Then
    Begin
      y := 100 + Trunc(SC*pp.Y);
      If (y>=0) And (y<=199) Then
      Begin
	{res.Init(0, 0, 1);
	Light := res;
	Exit;}
	frc := MaskMap[y*320+x];
	If frc<1 Then
	Begin
	  If t1>t3 Then
	    t1 := t3;
	  If t2>t3 Then
	    t2 := t3;
	End;
      End
      Else
	t3 := t2
    End
    Else
      t3 := t2;
{$ELSE}
    If (x >= 0) And (x <= 319) Then
    Begin
      y := 100 + Trunc(SC*pp.Y);
      If (y >= 0) And (y <= 199) And (MaskMap[y*320 + x] < 128) Then
        t3 := t2;
    End;
    If t1 > t3 Then
      t1 := t3;
    If t2 > t3 Then
      t2 := t3;
{$ENDIF}
  End;
  If t1>=t2 Then
  Begin
    res.Init(0, 0, 0);
    Light := res;
    Exit;
  End;
  fr := frc/255;
  l1 := CalcLight(t1);
  If t1>t3 Then
    l1 *= fr;
  q := NSEG;
  t := t1;
  h := (t2-t1)/NSEG;
  If h<MINSEGSIZE Then
    h := MINSEGSIZE;
  While (t<t3) And (q>0) And (t<t2) Do
  Begin
    t += h;
    If (t>t2) Then
    Begin
      h -= t2-t;
      t := t2;
      q := 0;
    End
    Else
      Dec(q);
    h := (t-t1);
    p += _d*h;
    p2 += ray.mDir*h;
    l2 := CalcLight(t);
    f += (l1+l2)*h;
    l1 := l2;
    t1 := t;
  End;
  While (q>0) And (t<t2) Do
  Begin
    t += h;
    If t>t2 Then
    Begin
      h -= t2-t;
      t := t2;
      q := 0;
    End
    Else
      Dec(q);
    p += _d*h;
    p2 += ray.mDir*h;
    l2 := CalcLight(t);
    If t>t3 Then
      l2 *= fr;
    f += (l1+l2)*h;
    l1 := l2;
    t1 := t;
  End;
  Light := mCol*f;
End;

Function VLight.CalcLight(t : Single) : Single;

Var
  f : Single;
  x, y, c : Integer;

Begin
  { trace line to bitmap from mPosn to p2 }
  If Not ((mPosn.Z > 0) Xor (p2.Z > 0)) Then
  Begin
    { fresnel fall off... }
    CalcLight := p.Z / p.MagnitudeSq;
    Exit;
  End;
  f := -(mPosn.Z)/(p2.Z - mPosn.Z);
  x := 160 + Trunc(SC*((p2.X-mPosn.X)*f+mPosn.X));
{$IFNDEF INV}
  If (x < 0) Or (x > 319) Then
  Begin
    CalcLight := p.Z / p.MagnitudeSq;
    Exit;
  End;
  y := 100 + Trunc(SC*((p2.Y-mPosn.Y)*f+mPosn.Y));
  If (y < 0) Or (y > 199) Then
  Begin
    CalcLight := p.Z / p.MagnitudeSq;
    Exit;
  End;
  c := MaskMap[y * 320 + x];
{$ELSE}
  If (x < 0) Or (x > 319) Then
  Begin
    CalcLight := 0;
    Exit;
  End;
  y := 100 + Trunc(SC*((p2.Y-mPosn.Y)*f+mPosn.Y));
  If (y < 0) Or (y > 199) Then
  Begin
    CalcLight := 0;
    Exit;
  End;
  c := 255 - MaskMap[y * 320 + x];
{$ENDIF}
  If c = 0 Then
  Begin
    CalcLight := 0;
    Exit;
  End;
  CalcLight := (c*(1/255))*p.Z / p.MagnitudeSq;
End;

Function CLIPC(f : Single) : Integer; {Inline;}

Var
  a : Integer;

Begin
  a := Trunc(f * 255);
  If a < 0 Then
    a := 0
  Else
    If a > 255 Then
      a := 255;
  CLIPC := a;
End;

Procedure initfrand;

Var
  s, c1 : Integer;

Begin
  FillChar(frandtab, SizeOf(frandtab), 0);
  s := 1;
  For c1 := 1 To 65535 Do
  Begin
    frandtab[c1] := s And $FFFF;
    s := (((s Shr 4) Xor (s Shr 13) Xor (s Shr 15)) And 1) + (s Shl 1);
  End;
End;

Function frand : Integer; {Inline;}

Begin
  frand := frandtab[frandtab_seed];
  frandtab_seed := (frandtab_seed + 1) And $FFFF;
End;

Procedure VLightPart(console : TPTCConsole; surface : TPTCSurface);

Var
  vl, vl2 : VLight;
  camposn : FVector;
  camaxis : FMatrix;
  c1, c2, c3, ti, xx, yy, zz, i, a, x, y : Integer;
  idx : Array[0..(200 Div 16) - 1, 0..(320 Div 16) - 1] Of char8;
  order : Array[0..10*19 - 1, 0..1] Of Integer;
  vlightt, t, cz, camf : Single;
  col : FVector;
  ray : TRay;
  oc, c, c2_ : int32;
  time, delta : Single;
  pitch : Integer;
  screenbuf, pd : Pchar8;
  tmp : FVector;
  F : File;

Begin
  oc := 0;
  initfrand;
  tmp.Init(0.1, 0.4, 1);
  vl := VLight.Create(tmp);
  tmp.Init(1, 0.5, 0.2);
  vl2 := VLight.Create(tmp);
  tmp.Init(0, 0, 20);
  vl.Move(tmp);
  tmp.Init(0, 6, 30);
  vl2.Move(tmp);

  camposn.Init(7, 0.5, -10);
  camaxis.Init;
  camaxis.MakeID;
  tmp.Init(0, 0, 0);
  camaxis.Row[2] := tmp - camposn;
  camaxis.Normalise;
  camf := 100;

  MaskMap := GetMem(320 * 200);
  FillChar(MaskMap^, 320 * 200, 0);

  { load mojo.raw }
  ASSign(F, 'mojo.raw');
  Reset(F, 1);
  BlockRead(F, MaskMap^, 320*200);
  Close(F);

  { build the order of the squares }
  For c1 := 0 To 10*19 - 1 Do
  Begin
    order[c1, 0] := c1 Mod 19;
    order[c1, 1] := (c1 Div 19) + 1;
  End;

  { swap them around }
  For c1 := 0 To 9999 Do
  Begin
    c2 := Random(190);
    c3 := Random(190);
    ti := order[c2, 0]; order[c2, 0] := order[c3, 0]; order[c3, 0] := ti;
    ti := order[c2, 1]; order[c2, 1] := order[c3, 1]; order[c3, 1] := ti;
  End;

  { time settings }
  time := 0;
  delta := 0.01; { this controls the speed of the effect }

  { main loop }
  While Not console.KeyPressed Do
  Begin
    { get surface data }
    pitch := surface.pitch;

    { light time (makes the effect loop) }
    vlightt := 320 * Abs(Sin(time/5));
    
    t := 13 - 0.1822 * vlightt;
    cz := 1 - 0.01 * vlightt;
    {tmp.Init(Sin(t)*5, Cos(t*-0.675+4543)*5, 15);
    vl.Move(tmp);
    tmp.Init(0, 0, -15);
    vl.Move(tmp);}
    tmp.Init(t, 0, 22);
    vl.Move(tmp);
    tmp.Init(-t, -7, 28);
    vl2.Move(tmp);

    camposn.Init(cz*4+9, cz, -t/7-13);
    tmp.Init(0, 0, 0);
    camaxis.Row[2] := tmp - camposn;
    camaxis.Normalise;

    FillChar(idx, SizeOf(idx), 25);

    { swap them around }
    For c1 := 0 To 99 Do
    Begin
      c2 := Random(190);
      c3 := Random(190);
      ti := order[c2, 0]; order[c2, 0] := order[c3, 0]; order[c3, 0] := ti;
      ti := order[c2, 1]; order[c2, 1] := order[c3, 1]; order[c3, 1] := ti;
    End;
    For zz := 0 To 189 Do
    Begin
      xx := order[zz, 0];
      yy := order[zz, 1];
      i := 0;
      
      { lock surface }
      screenbuf := surface.lock;
      Try
        c2 := idx[yy, xx] Shr 1;
        For c1 := 0 To c2 - 1 Do
        Begin
	  a := frand And 255;
	  x := xx * 16 + (a And 15) + 6 + 4;
	  y := yy * 16 + (a Shr 4) + 6;

	  col.Init(0, 0, 0);
	  ray.Init(camposn, camaxis.Row[2]*camf+camaxis.Row[0]*(x-160)+camaxis.Row[1]*(y-100));
	  col += vl.Light(ray);
	  col += vl2.Light(ray);

	  c := (CLIPC(col.X) Shl 16) + (CLIPC(col.Y) Shl 8) + (CLIPC(col.Z));
	  pd := screenbuf + x*4 + y*pitch;
	  Inc(i, Abs(Integer(c And 255)-Integer(pd[321] And 255)) + Abs(Integer(c Shr 16)-Integer(pd[321] Shr 16)));
	  If c1 <> 0 Then
	    Inc(i, Abs(Integer(c And 255)-Integer(oc And 255)) + Abs(Integer(c Shr 16)-Integer(oc Shr 16)));
	  oc := c;

	  c2_ := (c Shr 1) And $7F7F7F;
	  Pint32(pd)[1] := ((Pint32(pd)[1]) Shr 1) And $7F7F7F+ c2_;
	  Pint32(pd)[2] := ((Pint32(pd)[2]) Shr 1) And $7F7F7F+ c2_;
	  Inc(pd, pitch);
	  Pint32(pd)[0] := ((Pint32(pd)[0]) Shr 1) And $7F7F7F+ c2_;
	  Pint32(pd)[1] := c;
	  Pint32(pd)[2] := c;
	  Pint32(pd)[3] := ((Pint32(pd)[3]) Shr 1) And $7F7F7F+ c2_;
	  Inc(pd, pitch);
	  Pint32(pd)[0] := ((Pint32(pd)[0]) Shr 1) And $7F7F7F+ c2_;
	  Pint32(pd)[1] := c;
	  Pint32(pd)[2] := c;
	  Pint32(pd)[3] := ((Pint32(pd)[3]) Shr 1) And $7F7F7F+ c2_;
	  Inc(pd, pitch);
	  Pint32(pd)[1] := ((Pint32(pd)[1]) Shr 1) And $7F7F7F+ c2_;
	  Pint32(pd)[2] := ((Pint32(pd)[2]) Shr 1) And $7F7F7F+ c2_;
        End;
        i *= 5;
        i := i Div (3*idx[yy, xx]);
        If i < 2 Then
	  i := 2;
        If i > {256}255 Then
	  i := {256}255;
        idx[yy, xx] := i;
      Finally
        { unlock surface }
        surface.unlock;
      End;
      
      If (zz Mod 95) = 0 Then
      Begin
        { copy surface to console }
	surface.copy(console);
	
        { update console }
	console.update;
      End;
    End;
    { update time }
    time += delta;
  End;
  FreeMem(MaskMap);
  vl.Free;
  vl2.Free;
End;

Var
  format : TPTCFormat;
  console : TPTCConsole;
  surface : TPTCSurface;

Begin
  format := Nil;
  surface := Nil;
  console := Nil;
  Try
    Try
      { create format }
      format := TPTCFormat.Create(32, $00FF0000, $0000FF00, $000000FF);
      
      { create console }
      console := TPTCConsole.Create;
      
      { open console }
      console.open('mojo by statix', 320, 200, format);
      
      { create main drawing surface }
      surface := TPTCSurface.Create(320, 200, format);
      
      { do the light effect }
      VLightPart(console, surface);
      
    Finally
      { close console }
      console.close;
      console.Free;
      surface.Free;
      format.Free;
    End;
    
    { print message to stdout }
    Writeln('mojo by alex "statix" evans');
    Writeln('to be used as an example of bad coding and good ptc');
    Writeln('no responsibility taken for this!');
    Writeln('enjoy ptc! it''s great');
    Writeln;
    Writeln('-statix 13/1/98');
  Except
    On error : TPTCError Do
      { report error }
      error.report;
  End;
End.
