// SPDX-License-Identifier: LGPL-3.0-linking-exception
{$ifdef PARAM_PHONGSSE}
  {$asmmode intel}
  //SSE rotate singles
  const Shift231 = 1 + 8;
        Shift312 = 2 + 16;
{$endif}

var
  //Light source normal.
  vL: TPoint3D_128;  {xmm0}
  //Light source position.
  vLS: TPoint3D_128; {xmm1}
  //Vector H is the unit normal to the hypothetical surface oriented
  //halfway between the light direction vector (L) and the viewing vector (V).
  vH: TPoint3D_128;  {xmm2}

  vN: TPoint3D_128;  {xmm3}        // surface normal
  vP: TPoint3D_128;  {xmm4}        // position of lighted pixel
  vV: TPoint3D_128;                // viewer direction
{$ifdef PARAM_PHONGSSE}
  LightDestFactor4: TPoint3D_128;  // for multiplication
{$endif}

  //Calculate LdotN and NnH
  NH: Single;
{$ifndef PARAM_PHONGSSE}
  vD: TPoint3D_128;
{$endif}

  Iw, Ic: integer;    // Iw: specular intensity, Ic: ambient+diffuse intensity
  sIw: single;        // floating point value for Iw

  z, LdotN, NnH,
  dist, distfactor, diffuseterm, specularterm: single;
  eLight: TExpandedPixel;
  mc,mcLeft,mcRight,mcTop,mcBottom: TBGRAPixel; ///map values

{$ifdef PARAM_SIMPLECOLOR}
  eColor: TExpandedPixel;
{$else}
  {$ifndef PARAM_SCANNER}
    pcolormap: PBGRAPixel;
  {$endif}
{$endif}

  {$hints off}
  function ComputePixel(x,y: integer; DiffuseLight, SpecularLight: Word; Alpha: Byte): TBGRAPixel; inline;
  var ec: TExpandedPixel;
    {$ifndef PARAM_SIMPLECOLOR}
      eColor: TExpandedPixel;
    {$endif}
  begin
    {$ifndef PARAM_SIMPLECOLOR}
      {$ifdef PARAM_SCANNER}
        eColor := GammaExpansion(ColorScan.ScanNextPixel);
      {$else}
        eColor := GammaExpansion(pcolormap^);
      {$endif}
    {$endif}
    Alpha := ApplyOpacity(Alpha, eColor.alpha shr 8);
    ec.red := (eColor.Red*DiffuseLight+eLight.Red*SpecularLight+PhongLightPrecisionDiv2) shr PhongLightPrecisionSh;
    ec.green := (eColor.Green*DiffuseLight+eLight.Green*SpecularLight+PhongLightPrecisionDiv2) shr PhongLightPrecisionSh;
    ec.blue := (eColor.Blue*DiffuseLight+eLight.Blue*SpecularLight+PhongLightPrecisionDiv2) shr PhongLightPrecisionSh;
    ec.alpha := Alpha shl 8+Alpha;
    result := GammaCompression(ec);
  end;
  {$hints on}

var
  minx,miny,maxx,maxy: integer;
  pmap: PBGRAPixel;
  pdest: PBGRAPixel;
  x,y : integer;      // Coordinates of point in height map.
  vS1,vS2: TPoint3D_128; // surface vectors (plane)
  deltaDown: Int32or64;
  IsLineUp,IsLineDown: boolean;

begin
  if map = nil then exit;
  {$ifndef PARAM_SIMPLECOLOR}
    {$ifndef PARAM_SCANNER}
      if (colorMap.Width < map.width) or (colorMap.Height < map.height) then
        raise Exception.Create('Dimension mismatch');
    {$endif}
  {$endif}

  if (map.width = 0) or (map.Height = 0) then exit;
  if ofsX >= dest.ClipRect.Right then exit;
  if ofsY >= dest.ClipRect.Bottom then exit;
  if ofsX <= dest.ClipRect.Left-map.Width then exit;
  if ofsY <= dest.ClipRect.Top-map.Height then exit;

  minx := 0;
  miny := 0;
  maxx := map.Width-1;
  maxy := map.Height-1;
  if ofsX < dest.clipRect.Left then minx := dest.clipRect.Left-ofsX;
  if ofsY < dest.clipRect.Top then miny := dest.clipRect.Top-ofsY;
  if OfsX+maxx > dest.ClipRect.Right-1 then maxx := dest.ClipRect.Right-1-ofsX;
  if OfsY+maxy > dest.ClipRect.Bottom-1 then maxy := dest.ClipRect.Bottom-1-ofsY;

  eLight := GammaExpansion(LightColor);
  {$ifdef PARAM_SIMPLECOLOR}
    eColor := GammaExpansion(color);
  {$endif}

  //light origin
  vLS := Point3D_128(FLightPosition3D.X-ofsX,
                  FLightPosition3D.Y-ofsY,
                  FLightPosition3D.Z);

  //surface vectors
  vS1 := Point3D_128(1,0,0);
  vS2 := Point3D_128(0,1,0);

  vV := Point3D_128(0,0,1);

  dist := 0;
  LdotN := 0;
  NnH := 0;

  {$ifdef PARAM_PHONGSSE}
  LightDestFactor4 := Point3D_128(LightDestFactor,LightDestFactor,LightDestFactor,LightDestFactor);
  {$endif}

  if map.LineOrder = riloTopToBottom then
    deltaDown := map.Width*sizeof(TBGRAPixel)
  else
    deltaDown := -map.Width*sizeof(TBGRAPixel);
  for y := miny to maxy do
  begin
    //read map values
    pmap := map.ScanLine[y]+minx;
    mc := BGRAPixelTransparent;
    mcRight := pmap^;
    pdest := dest.ScanLine[y+ofsY]+ofsX+minx;
    {$ifndef PARAM_SIMPLECOLOR}
      {$ifdef PARAM_SCANNER}
        ColorScan.ScanMoveTo(OfsX+minx,OfsY+Y);
      {$else}
        pcolormap := ColorMap.ScanLine[y];
      {$endif}
    {$endif}
    IsLineUp := y > 0;
    IsLineDown := y < map.Height-1;
    mcTop := BGRAPixelTransparent;
    mcBottom := BGRAPixelTransparent;
    for x := minx to maxx do
    begin
      mcLeft := mc;
      mc := mcRight;
      if x < map.width-1 then
        mcRight := (pmap+1)^ else
        mcRight := BGRAPixelTransparent;
      if mc.alpha = 0 then
      begin
        {$ifndef PARAM_SIMPLECOLOR}
          {$ifdef PARAM_SCANNER}
            ColorScan.ScanNextPixel;
          {$else}
            inc(pcolormap);
          {$endif}
        {$endif}
        inc(pdest);
        inc(pmap);
        continue;
      end;

      //compute surface vectors
      if IsLineUp then mcTop := pbgrapixel(pbyte(pmap)-deltaDown)^;
      if IsLineDown then mcBottom := pbgrapixel(pbyte(pmap)+deltaDown)^;
      inc(pmap);

      z := MapHeight(mc)*mapAltitude;
      if mcLeft.alpha = 0 then
      begin
        if mcRight.alpha = 0 then
          vS1.z := 0
        else
          vS1.z := (MapHeight(mcRight)-MapHeight(mc))*mapAltitude*2;
      end else
      begin
        if mcRight.alpha = 0 then
          vS1.z := (MapHeight(mc)-MapHeight(mcLeft))*mapAltitude*2
        else
          vS1.z := (MapHeight(mcRight)-MapHeight(mcLeft))*mapAltitude;
      end;
      if mcTop.alpha = 0 then
      begin
        if mcBottom.alpha = 0 then
          vS2.z := 0
        else
          vS2.z := (MapHeight(mcBottom)-MapHeight(mc))*mapAltitude*2;
      end else
      begin
        if mcBottom.alpha = 0 then
          vS2.z := (MapHeight(mc)-MapHeight(mcTop))*mapAltitude*2
        else
          vS2.z := (MapHeight(mcBottom)-MapHeight(mcTop))*mapAltitude;
      end;

      //position vector
      vP := Point3D_128(x, y, z);
      {$ifdef PARAM_PHONGSSE}
      if UseSSE3 then
      begin
        {$DEFINE PARAM_USESSE3}
        asm
          movups xmm1, vLS
        end;
        {$i phongdrawsse.inc}
        {$UNDEF PARAM_USESSE3}
      end else
      begin
        asm
          movups xmm1, vLS
        end;
        {$i phongdrawsse.inc}
      end;
      {$else}
        vP := Point3D_128(x, y, z);
        vL := vLS- vP*LightDestFactor;
        Normalize3D_128(vL);

        //compute bisector of angle between light and observer
        vH := vL + vV;
        Normalize3D_128(vH);

        // compute normal vector to the surface
        VectProduct3D_128(vS1,vS2,vN);
        Normalize3D_128(vN);

        //Calculate LdotN and NnH
        LdotN := DotProduct3D_128(vN,vL);
        vD := vLS-vP;
        dist := sqrt(DotProduct3D_128(vD,vD));

        NH := DotProduct3D_128(vH,vN);
      {$endif}

      if NH <= 0 then
        NnH := 0
      else
        NnH := exp(SpecularIndex*ln(NH));  //to be optimized

      distfactor := LightSourceIntensity / (dist*LightSourceDistanceFactor + LightSourceDistanceTerm);

      if (LdotN <= 0) then //Point is not illuminated by light source.
        //Use negative diffuse for contrast
        diffuseterm := distfactor * NegativeDiffusionFactor * LdotN
      else
        diffuseterm := distfactor * DiffusionFactor * LdotN;
      Ic := round((AmbientFactor + diffuseterm)*PhongLightPrecision);

      //specular (reflection)
      specularterm := distfactor * SpecularFactor * NnH;
      sIw := specularterm*PhongLightPrecision;
      if sIw > PhongLightPrecision then Iw := PhongLightPrecision else
        Iw := round(sIw);

      //intensity bounds (0..PhongLightPrecision)
      If Ic < 0 then Ic := 0;
      If Ic > PhongLightPrecision then
      begin
        If DiffuseSaturation then
        begin
          Iw := Iw+(Ic-PhongLightPrecision);
          if Iw > PhongLightPrecision then Iw := PhongLightPrecision;
        end;
        Ic := PhongLightPrecision;
      end;
      Ic := Ic*(PhongLightPrecision-Iw) shr PhongLightPrecisionSh;

      DrawPixelInlineWithAlphaCheck(pdest, ComputePixel(x,y,Ic,Iw,mc.alpha));
      {$ifndef PARAM_SIMPLECOLOR}
        {$ifndef PARAM_SCANNER}
          inc(pcolormap);
        {$endif}
      {$endif}
      inc(pdest); //go to next pixel
    end;
  end;
end;

{$undef PARAM_PHONGSSE}
{$undef PARAM_SIMPLECOLOR}
{$undef PARAM_SCANNER}

