unit Projection;
{************************************************************************}
{*     Projection.p                                                                                                                                          *}
{*     by Michael Castle (Pascal) and Janice Keller (assembly code)                                                          *}
{*     University of Michigan Mental Health Research Institute (MHRI)                                                     *}
{*     e-mail address: mike.castle@umich.edu                                                                                       *}
{* ** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *   * * * * * * * * * }

interface

	uses
		Types, Memory, QuickDraw, Packages, Menus, Events, Fonts, Scrap,
		ToolUtils, Resources, Errors, Palettes, Dialogs, TextUtils,
		Globals, Utilities, File2, File1, Graphics, Camera, Filters, Stacks;


	procedure DoProjection;
	function ShowProjectDialogBox: boolean;
	procedure Project;


implementation

	const
		bigpowerof2 = 8192;                               {used for integer trigonometric arithmetic}

	type
		MHRIRealArray = array[1..MaxPics] of extended;
		RealPoint = record
				x: extended;
				y: extended;
			end;     {record}
		IntPtr = ^integer;
		LongPtr = ^LongInt;

	var
		SliceInterval: extended;                                 {distance, in pixels, between original slices}
		BoundRect: rect;                                      {boundary rectangle for a rectangular selection}
		cancelled: boolean;
                                                                                                                                                                                                                                		ProjSize: LongInt;



{******************************************************************************}
{*     This procedure frees memory allocated for buffers used in projection calculations.                                       *}
{******************************************************************************}
	procedure DisposeProjectionPtrs (projptr, opaptr, brightcueptr: ptr; zbufptr, countbufptr, cuezbufptr: IntPtr; sumbufptr: LongPtr);
	begin
		if zbufptr <> nil then
			DisposePtr(Ptr(zbufptr));
		if opaptr <> nil then
			DisposePtr(opaptr);
		if sumbufptr <> nil then
			DisposePtr(Ptr(sumbufptr));
		if countbufptr <> nil then
			DisposePtr(Ptr(countbufptr));
		if cuezbufptr <> nil then
			DisposePtr(Ptr(cuezbufptr));
		if brightcueptr <> nil then
			DisposePtr(brightcueptr);
		if projptr <> nil then
			DisposePtr(projptr);
	end;


{******************************************************************************}
{*     This procedure projects each pixel of a volume (stack of slices) onto a plane as the volume rotates about    *}
{*  the x-axis.  Integer arithmetic, precomputation of values, and iterative addition rather than multiplication  *}
{*  inside a loop are used extensively to make the code run efficiently.  Projection parameters stored in global   *}
{*  variables determine how the projection will be performed.  This procedure returns various buffers which   *}
{*  are actually used by DoProject to find the final projected image for the volume of slices at the current angle.*}
{******************************************************************************}
	procedure DoOneProjectionX (nSlices, ycenter, zcenter: integer; projwidth, projheight: LongInt; costheta, sintheta: longint; projptr, opaptr, brightcueptr: ptr; zbufptr, cuezbufptr, countbufptr: IntPtr; sumbufptr: LongPtr; str: string);
		var
			i, j, k,                                                 {loop indices}
			thispixel: integer;                              {the current pixel to be projected}
			offset, offsetinit: longint;                   {precomputed offsets into an image buffer}
			projaddr,                                            {buffer address for final projected image}
			opaaddr,                                              {buffer address for opacity surface projection component}
			brightcueaddr,                                   {buffer address for brightest-point projection with interior depth cues}
			zbufaddr,                                            {z-buffer address for surface projections (nearest-point/opacity)}
			cuezbufaddr,                                       {z-buffer address for brightest-point projection with interior depth cues}
			countbufaddr,                                     {buffer addr for counting pixels in mean-value projection}
			sumbufaddr: longint;                         {buffer addr for summing pixels in mean-value projection}
			z,                                                        {z-coordinate of points in current slice before rotation}
			ynew, znew,                                       {y- and z-coordinates of current point after rotation}
			zmax, zmin,                                       {z-coordinates of first and last slices before rotation}
			zmaxminuszmintimes100: longint;  {precomputed values to save time in loops}
			c100minusDepthCueInt, c100minusDepthCueSurf: integer;
			DepthCueIntlessthan100, DepthCueSurflessthan100: boolean;
			OpacityOrNearestPt, OpacityAndNotNearestPt: boolean;
			MeanVal, BrightestPt: boolean;
			ysintheta, ycostheta,                          {values used in rotational calculations before projection}
			zsintheta, zcostheta, ysinthetainit, ycosthetainit: longint;
			p,                                                        {pointer to final projected image buffer}
			op,                                                      {pointer to opacity surface projection buffer}
			bp: ptr;                                              {pointer to brightest-point projection buffer with interior depth cues}
			zp,                                                      {pointer to surface projection (nearest-point/opacity) z-buffer}
			qp,                                                      {pointer to z-buffer for brightest-point projection with interior depth cues}
			cp: IntPtr;                                          {pointer to buffer for counting pixels for mean-value projection}
			sp: LongPtr;                                       {pointer to mean-value summing buffer}
			width: integer;
			theLine: LineType;
	begin
		with BoundRect do begin                    {precompute values to avoid computations in loop}
				width := right - left;
				zmax := zcenter + projheight div 2; {find z-coordinates of first and last slices}
				zmin := zcenter - projheight div 2;
				zmaxminuszmintimes100 := 100 * (zmax - zmin);
				c100minusDepthCueInt := 100 - DepthCueInt;
				c100minusDepthCueSurf := 100 - DepthCueSurf;
				DepthCueIntlessthan100 := DepthCueInt < 100;
				DepthCueSurflessthan100 := DepthCueSurf < 100;
				OpacityOrNearestPt := (ProjectionMethod = NearestPoint) or (Opacity > 0);
				OpacityAndNotNearestPt := (Opacity > 0) and (ProjectionMethod <> NearestPoint);
				MeanVal := ProjectionMethod = MeanValue;
				BrightestPt := ProjectionMethod = BrightestPoint;
				projaddr := ord4(projptr);
				opaaddr := ord4(opaptr);
				brightcueaddr := ord4(brightcueptr);
				zbufaddr := ord4(zbufptr);
				cuezbufaddr := ord4(cuezbufptr);
				countbufaddr := ord4(countbufptr);
				sumbufaddr := ord4(sumbufptr);
				ycosthetainit := (top - ycenter - 1) * costheta;
				ysinthetainit := (top - ycenter - 1) * sintheta;
				offsetinit := ((projheight - bottom + top) div 2) * projwidth + (projwidth - right + left) div 2 - 1;
				for k := 1 to nSlices do begin
						SelectSlice(k);
						z := round((k - 1) * SliceInterval) - zcenter;
						zcostheta := z * costheta;
						zsintheta := z * sintheta;
						ycostheta := ycosthetainit;
						ysintheta := ysinthetainit;
						for j := top to bottom - 1 do begin               {read each row in the current slice}
								ycostheta := ycostheta + costheta;               {rotate about x-axis and find new y,z}
								ysintheta := ysintheta + sintheta;               {x-coordinates will not change}
								ynew := (ycostheta - zsintheta) div bigpowerof2 + ycenter - top;
								znew := (ysintheta + zcostheta) div bigpowerof2 + zcenter;
								offset := offsetinit + ynew * projwidth;
								GetLine(left, j, width, theLine);
								for i := 0 to width - 1 do begin             {read each pixel in current row and project it}
										thispixel := theLine[i];
										offset := offset + 1;
										if (offset >= ProjSize) or (offset < 0) then
											offset := 0;
										if (thispixel <= TransparencyUpper) and (thispixel >= TransparencyLower) then begin
												if OpacityOrNearestPt then begin
														zp := IntPtr(zbufaddr + offset + offset);
														if (znew < zp^) then begin
																zp^ := znew;
																if OpacityAndNotNearestPt then begin
																		op := ptr(opaaddr + offset);
																		if (DepthCueSurflessthan100) then
																			op^ := 255 - (DepthCueSurf * (255 - thispixel) div 100 + (c100minusDepthCueSurf) * (255 - thispixel) * (zmax - znew) div zmaxminuszmintimes100)
																		else
																			op^ := thispixel;
																	end
																else begin
																		p := ptr(projaddr + offset);
																		if DepthCueSurflessthan100 then
																			p^ := 255 - (DepthCueSurf * (255 - thispixel) div 100 + (c100minusDepthCueSurf) * (255 - thispixel) * (zmax - znew) div zmaxminuszmintimes100)
																		else
																			p^ := thispixel;
																	end;
															end;
													end;     {NearestPoint case}
												if MeanVal then begin
														sp := LongPtr(sumbufaddr + offset + offset + offset + offset);
														sp^ := sp^ + thispixel;
														cp := IntPtr(countbufaddr + offset + offset);
														cp^ := cp^ + 1;
													end     {MeanValue case}
												else if BrightestPt then begin
														if (DepthCueIntlessthan100) then begin
																p := ptr(projaddr + offset);
																bp := ptr(brightcueaddr + offset);
																qp := IntPtr(cuezbufaddr + offset + offset);
																if (thispixel < BAND(bp^, 255)) or ((thispixel = BAND(bp^, 255)) and (znew < qp^)) then begin
																		bp^ := thispixel;                     {use z-buffer to ensure that if depth-cueing is on,  }
																		qp^ := znew;                            {the closer of two equally-bright points is displayed}
																		p^ := 255 - (DepthCueInt * (255 - thispixel) div 100 + (c100minusDepthCueInt) * (255 - thispixel) * (zmax - znew) div zmaxminuszmintimes100);
																	end;     {then}
															end
														else begin
																p := ptr(projaddr + offset);
																if (thispixel < BAND(p^, 255)) then
																	p^ := thispixel;
															end;     {else}
													end;     {BrightestPoint case}
											end;
									end;     {for j}
							end;     {for i}
						UpdateMeter(10 + (k * 90) div nSlices, str);
						if CommandPeriod then begin
								cancelled := true;
								beep;
								leave;
							end;
					end;     {for k}
			end;     {with}
	end;     {procedure DoOneProjectionX}



{******************************************************************************}
{*     This procedure projects each pixel of a volume (stack of slices) onto a plane as the volume rotates about    *}
{*  the y-axis.  Integer arithmetic, precomputation of values, and iterative addition rather than multiplication  *}
{*  inside a loop are used extensively to make the code run efficiently.  Projection parameters stored in global   *}
{*  variables determine how the projection will be performed.  This procedure returns various buffers which   *}
{*  are actually used by DoProject to find the final projected image for the volume of slices at the current angle.*}
{******************************************************************************}
	procedure DoOneProjectionY (nSlices, xcenter, zcenter: integer; projwidth, projheight: LongInt; costheta, sintheta: longint; projptr, opaptr, brightcueptr: ptr; zbufptr, cuezbufptr, countbufptr: IntPtr; sumbufptr: LongPtr; str: string);
		var
			i, j, k, thispixel: integer;
			offset, offsetinit: longint;
			projaddr, opaaddr, brightcueaddr, zbufaddr, cuezbufaddr, countbufaddr, sumbufaddr: longint;
			z, xnew, znew, zmax, zmin, zmaxminuszmintimes100: longint;
			c100minusDepthCueInt, c100minusDepthCueSurf: integer;
			DepthCueIntlessthan100, DepthCueSurflessthan100: boolean;
			OpacityOrNearestPt, OpacityAndNotNearestPt: boolean;
			MeanVal, BrightestPt: boolean;
			xsintheta, xcostheta, zsintheta, zcostheta, xsinthetainit, xcosthetainit: longint;
			p, op, bp: ptr;
			zp, qp, cp: IntPtr;
			sp: LongPtr;
			width: integer;
			aLine: LineType;
	begin
		with BoundRect do begin
				width := right - left;
				zmax := zcenter + projwidth div 2;
				zmin := zcenter - projwidth div 2;
				zmaxminuszmintimes100 := 100 * (zmax - zmin);
				c100minusDepthCueInt := 100 - DepthCueInt;
				c100minusDepthCueSurf := 100 - DepthCueSurf;
				DepthCueIntlessthan100 := DepthCueInt < 100;
				DepthCueSurflessthan100 := DepthCueSurf < 100;
				OpacityOrNearestPt := (ProjectionMethod = NearestPoint) or (Opacity > 0);
				OpacityAndNotNearestPt := (Opacity > 0) and (ProjectionMethod <> NearestPoint);
				MeanVal := ProjectionMethod = MeanValue;
				BrightestPt := ProjectionMethod = BrightestPoint;
				projaddr := ord4(projptr);
				opaaddr := ord4(opaptr);
				brightcueaddr := ord4(brightcueptr);
				zbufaddr := ord4(zbufptr);
				cuezbufaddr := ord4(cuezbufptr);
				countbufaddr := ord4(countbufptr);
				sumbufaddr := ord4(sumbufptr);
				xcosthetainit := (left - xcenter - 1) * costheta;
				xsinthetainit := (left - xcenter - 1) * sintheta;
				for k := 1 to nSlices do begin
						SelectSlice(k);
						z := round((k - 1) * SliceInterval) - zcenter;
						zcostheta := z * costheta;
						zsintheta := z * sintheta;
						offsetinit := ((projheight - bottom + top) div 2) * projwidth + (projwidth - right + left) div 2 - projwidth;
						for j := top to bottom - 1 do begin
								xcostheta := xcosthetainit;
								xsintheta := xsinthetainit;
								offsetinit := offsetinit + projwidth;
								GetLine(left, j, width, aLine);
								for i := 0 to width - 1 do begin
										thispixel := aLine[i];
										xcostheta := xcostheta + costheta;
										xsintheta := xsintheta + sintheta;
										if (thispixel <= TransparencyUpper) and (thispixel >= TransparencyLower) then begin
												xnew := (xcostheta + zsintheta) div bigpowerof2 + xcenter - left;
												znew := (zcostheta - xsintheta) div bigpowerof2 + zcenter;
												offset := offsetinit + xnew;
												if (offset >= ProjSize) or (offset < 0) then
													offset := 0;
												if OpacityOrNearestPt then begin
														zp := IntPtr(zbufaddr + offset + offset);
														if (znew < zp^) then begin
																zp^ := znew;
																if OpacityAndNotNearestPt then begin
																		op := ptr(opaaddr + offset);
																		if (DepthCueSurflessthan100) then
																			op^ := 255 - (DepthCueSurf * (255 - thispixel) div 100 + (c100minusDepthCueSurf) * (255 - thispixel) * (zmax - znew) div zmaxminuszmintimes100)
																		else
																			op^ := thispixel;
																	end
																else begin
																		p := ptr(projaddr + offset);
																		if DepthCueSurflessthan100 then
																			p^ := 255 - (DepthCueSurf * (255 - thispixel) div 100 + (c100minusDepthCueSurf) * (255 - thispixel) * (zmax - znew) div zmaxminuszmintimes100)
																		else
																			p^ := thispixel;
																	end;
															end;
													end;     {NearestPoint case}
												if MeanVal then begin
														sp := LongPtr(sumbufaddr + offset + offset + offset + offset);
														sp^ := sp^ + thispixel;
														cp := IntPtr(countbufaddr + offset + offset);
														cp^ := cp^ + 1;
													end     {MeanValue case}
												else if BrightestPt then begin
														if (DepthCueIntlessthan100) then begin
																p := ptr(projaddr + offset);
																bp := ptr(brightcueaddr + offset);
																qp := IntPtr(cuezbufaddr + offset + offset);
																if (thispixel < BAND(bp^, 255)) or ((thispixel = BAND(bp^, 255)) and (znew < qp^)) then begin
																		bp^ := thispixel;
																		qp^ := znew;
																		p^ := 255 - (DepthCueInt * (255 - thispixel) div 100 + (c100minusDepthCueInt) * (255 - thispixel) * (zmax - znew) div zmaxminuszmintimes100);
																	end;     {then}
															end
														else begin
																p := ptr(projaddr + offset);
																if (thispixel < BAND(p^, 255)) then
																	p^ := thispixel;
															end;     {else}
													end;     {BrightestPoint case}
											end;
									end;     {for j}
							end;     {for i}
						UpdateMeter(10 + (k * 90) div nSlices, str);
						if CommandPeriod then begin
								cancelled := true;
								beep;
								leave;
							end;
					end;     {for k}
			end;     {with}
	end;     {procedure DoOneProjectionY}



{******************************************************************************}
{*     This procedure projects each pixel of a volume (stack of slices) onto a plane as the volume rotates about    *}
{*  the z-axis.  Integer arithmetic, precomputation of values, and iterative addition rather than multiplication  *}
{*  inside a loop are used extensively to make the code run efficiently.  Projection parameters stored in global   *}
{*  variables determine how the projection will be performed.  This procedure returns various buffers which   *}
{*  are actually used by DoProject to find the final projected image for the volume of slices at the current angle.*}
{******************************************************************************}
	procedure DoOneProjectionZ (nSlices, xcenter, ycenter, zcenter: integer; projwidth, projheight: LongInt; costheta, sintheta: longint; projptr, opaptr, brightcueptr: ptr; zbufptr, cuezbufptr, countbufptr: IntPtr; sumbufptr: LongPtr; str: string);
		var
			i, j, k, thispixel: integer;
			offset, offsetinit: longint;
			projaddr, opaaddr, brightcueaddr, zbufaddr, cuezbufaddr, countbufaddr, sumbufaddr: longint;
			z, xnew, ynew, zmax, zmin, zmaxminuszmintimes100: longint;
			c100minusDepthCueInt, c100minusDepthCueSurf: integer;
			DepthCueIntlessthan100, DepthCueSurflessthan100: boolean;
			OpacityOrNearestPt, OpacityAndNotNearestPt: boolean;
			MeanVal, BrightestPt: boolean;
			xsintheta, xcostheta, ysintheta, ycostheta: longint;
			xsinthetainit, xcosthetainit, ysinthetainit, ycosthetainit: longint;
			p, op, bp: ptr;
			zp, qp, cp: IntPtr;
			sp: LongPtr;
			width: integer;
			theLine: LineType;
	begin
		with BoundRect do begin
				width := right - left;
				zmax := zcenter + projwidth div 2;
				zmin := zcenter - projwidth div 2;
				zmaxminuszmintimes100 := 100 * (zmax - zmin);
				c100minusDepthCueInt := 100 - DepthCueInt;
				c100minusDepthCueSurf := 100 - DepthCueSurf;
				DepthCueIntlessthan100 := DepthCueInt < 100;
				DepthCueSurflessthan100 := DepthCueSurf < 100;
				OpacityOrNearestPt := (ProjectionMethod = NearestPoint) or (Opacity > 0);
				OpacityAndNotNearestPt := (Opacity > 0) and (ProjectionMethod <> NearestPoint);
				MeanVal := ProjectionMethod = MeanValue;
				BrightestPt := ProjectionMethod = BrightestPoint;
				projaddr := ord4(projptr);
				opaaddr := ord4(opaptr);
				brightcueaddr := ord4(brightcueptr);
				zbufaddr := ord4(zbufptr);
				cuezbufaddr := ord4(cuezbufptr);
				countbufaddr := ord4(countbufptr);
				sumbufaddr := ord4(sumbufptr);
				xcosthetainit := (left - xcenter - 1) * costheta;
				xsinthetainit := (left - xcenter - 1) * sintheta;
				ycosthetainit := (top - ycenter - 1) * costheta;
				ysinthetainit := (top - ycenter - 1) * sintheta;
				offsetinit := ((projheight - bottom + top) div 2) * projwidth + (projwidth - right + left) div 2 + left - 1;
				for k := 1 to nSlices do begin
						SelectSlice(k);
						z := round((k - 1) * SliceInterval) - zcenter;
						ycostheta := ycosthetainit;
						ysintheta := ysinthetainit;
						for j := top to bottom - 1 do begin
								ycostheta := ycostheta + costheta;
								ysintheta := ysintheta + sintheta;
								xcostheta := xcosthetainit;
								xsintheta := xsinthetainit;
								GetLine(left, j, width, theLine);
								for i := 0 to width - 1 do begin
										thisPixel := theLine[i];
										xcostheta := xcostheta + costheta;
										xsintheta := xsintheta + sintheta;
										if (thispixel <= TransparencyUpper) and (thispixel >= TransparencyLower) then begin
												xnew := (xcostheta - ysintheta) div bigpowerof2 + xcenter - left;
												ynew := (xsintheta + ycostheta) div bigpowerof2 + ycenter - top;
												offset := offsetinit + ynew * projwidth + xnew;
												if (offset >= ProjSize) or (offset < 0) then
													offset := 0;
												if OpacityOrNearestPt then begin
														zp := IntPtr(zbufaddr + offset + offset);
														if (z < zp^) then begin
																zp^ := z;
																if OpacityAndNotNearestPt then begin
																		op := ptr(opaaddr + offset);
																		if (DepthCueSurflessthan100) then
																			op^ := 255 - (DepthCueSurf * (255 - thispixel) div 100 + (c100minusDepthCueSurf) * (255 - thispixel) * (zmax - z) div zmaxminuszmintimes100)
																		else
																			op^ := thispixel;
																	end
																else begin
																		p := ptr(projaddr + offset);
																		if DepthCueSurflessthan100 then
																			p^ := 255 - (DepthCueSurf * (255 - thispixel) div 100 + (c100minusDepthCueSurf) * (255 - thispixel) * (zmax - z) div zmaxminuszmintimes100)
																		else
																			p^ := thispixel;
																	end;
															end;
													end;     {NearestPoint case}
												if MeanVal then begin
														sp := LongPtr(sumbufaddr + offset + offset + offset + offset);
														sp^ := sp^ + thispixel;
														cp := IntPtr(countbufaddr + offset + offset);
														cp^ := cp^ + 1;
													end     {MeanValue case}
												else if BrightestPt then begin
														if (DepthCueIntlessthan100) then begin
																p := ptr(projaddr + offset);
																bp := ptr(brightcueaddr + offset);
																qp := IntPtr(cuezbufaddr + offset + offset);
																if (thispixel < BAND(bp^, 255)) or ((thispixel = BAND(bp^, 255)) and (z < qp^)) then begin
																		bp^ := thispixel;
																		qp^ := z;
																		p^ := 255 - (DepthCueInt * (255 - thispixel) div 100 + (c100minusDepthCueInt) * (255 - thispixel) * (zmax - z) div zmaxminuszmintimes100);
																	end;     {then}
															end
														else begin
																p := ptr(projaddr + offset);
																if (thispixel < BAND(p^, 255)) then
																	p^ := thispixel;
															end;     {else}
													end;     {BrightestPoint case}
											end;
									end;     {for j}
							end;     {for i}
						UpdateMeter(10 + (k * 90) div nSlices, str);
						if CommandPeriod then begin
								cancelled := true;
								beep;
								leave;
							end;
					end;     {for k}
			end;     {with}
	end;     {procedure DoOneProjectionZ}



{******************************************************************************}
{*     This code initializes buffers by filling them with uniform values. The *}
{*     The buffer can be filled with one, two or four byte values.            *}
{******************************************************************************}
	procedure InitializeBuffer (p: ptr; size: longint; value, step: integer);
	type
		IntPtrType=^integer;
		LongPtrType=^LongInt;
	var
		i, Lstep: longint;
		IntPtr:IntPtrTYpe;
		LongPtr:LongPtrType;
	begin
		Lstep:=step;
		case step of
			1: for i := 1 to size do begin
					p^ := value;
					p := Ptr(ord4(p) + Lstep);
				end;
			2: begin
					IntPtr:=IntPtrType(p);
					for i := 1 to size do begin
						IntPtr^ := value;
						IntPtr := IntPtrType(ord4(IntPtr) + Lstep);
					end;
				end;
			4: begin
					LongPtr:=LongPtrType(p);
					for i := 1 to size do begin
						LongPtr^ := value;
						LongPtr := LongPtrType(ord4(LongPtr) + Lstep);
					end;
				end;
		end;
	end;



{******************************************************************************}
{*     This procedure creates a sequence of projections of a rotating volume (stack of slices) onto a plane using   *}
{*  nearest-point (surface), brightest-point, or mean-value projection or a weighted combination of nearest- *}
{*  point projection with either of the other two methods (partial opacity).  The user may choose to rotate the   *}
{*  volume about any of the three orthogonal axes (x,y, or z), make portions of the volume transparent, or add  *}
{*  a greater degree of visual realism by employing depth cues.                                                                               *}
{******************************************************************************}
procedure DoProjection;
	var
		tport: Grafptr;
		nSlices: integer;                                      {number of slices in volume}
		projwidth, projheight: LongInt;              {dimensions of projection image}
		xcenter, ycenter, zcenter: integer;         {coordinates of center of volume of rotation}
		theta: integer;                                          {current angle of rotation in degrees}
		thetarad: extended;                                          {current angle of rotation in radians}
		sintheta, costheta: longint;                      {sine and cosine of current angle}
		xsinthetainit, xcosthetainit: longint;      {precomputed products to avoid mult in loops}
		offset, MemoryRequired: longint;
		p, op, bp, projptr, opaptr, brightcueptr: ptr;
		zp, zbufptr, qp, cuezbufptr, countbufptr, cp: IntPtr;
		sp, sumbufptr: LongPtr;
		curval, prevval, nextval, aboveval, belowval: integer;
		ignore: integer;                                        {irrelevant return value from a function}
		ticksinit, tickstogo, TicksForOneProjection: longint;
		str, TimeStr, seconds: str255;
		SaveProjectionsTemp, AutoSelectAll, AllocatingBuffers: boolean;
		n, nProjections, angle: integer;
		SourceInfo, DestInfo: InfoPtr;
		width, height: LongInt;

	procedure Abort;
	begin
		DisposeProjectionPtrs(projptr, opaptr, brightcueptr, zbufptr, countbufptr, cuezbufptr, sumbufptr);
		if AllocatingBuffers and (MaxBlock > 20000) then
			PutError('Insufficient Memory.');
		AbortMacro;
		{exit(DoProjection);} {ppc-bug}
	end;

begin
	ShowWatch;
	AutoSelectAll := not Info^.RoiShowing;
	if AutoSelectAll then
		SelectAll(false);
	if NotInBounds then
		exit(DoProjection);
	cancelled := false;
	SourceInfo := Info;
	GetPort(tPort);
	with Info^ do begin
			SetPort(GrafPtr(osPort));
			BoundRect := Roirect;
		end;
	if (AngleInc = 0) and (TotalAngle <> 0) then
		AngleInc := 5;
	angle := 0;
	nProjections := 0;
	if AngleInc = 0 then
		nProjections := 1
	else
		while angle <= TotalAngle do begin
				nProjections := nProjections + 1;
				angle := angle + AngleInc;
			end;
	if angle > 360 then
		nProjections := nProjections - 1;
	if nProjections <= 0 then
		nProjections := 1;
	nSlices := Info^.StackInfo^.nSlices;         {get number of slices in volume}
	SliceInterval := info^.StackInfo^.SliceSpacing;
	with BoundRect do begin
			width := right - left;
			height := bottom - top;
			xcenter := (left + right) div 2;          {find center of volume of rotation}
			ycenter := (top + bottom) div 2;
			zcenter := round(nSlices * SliceInterval / 2.0);
			if MinProjSize and (AxisOfRotation <> ZAxis) then begin
					case AxisOfRotation of                    {find dimensions of projection image}
						XAxis:  begin
								projheight := round(sqrt(sqr(nSlices * SliceInterval) + height * height));
								projwidth := right - left;
							end;     {XAxis}
						YAxis:  begin
								projwidth := round(sqrt(sqr(nSlices * SliceInterval) + ord4(right - left) * (right - left)));
								projheight := bottom - top;
							end;     {YAxis}
					end;     {case}
				end     {then}
			else begin
					projwidth := round(sqrt(sqr(nSlices * SliceInterval) + width * width));
					projheight := round(sqrt(sqr(nSlices * SliceInterval) + height * height));
				end;     {else make all windows the same size regardless of rotation axis}
		end;     {with BoundRect}
	if odd(projwidth) then
		projwidth := projwidth + 1;
	projptr := nil;
	zbufptr := nil;
	opaptr := nil;
	brightcueptr := nil;
	cuezbufptr := nil;
	sumbufptr := nil;
	countbufptr := nil;
	AllocatingBuffers := true;
	projsize := projwidth * projheight;
	projptr := NewPtr(projsize);
	if projptr = nil then
		begin Abort; exit(DoProjection) end;

	if (ProjectionMethod = NearestPoint) or (Opacity > 0) then begin    {get other required buffers}
			zbufptr := IntPtr(NewPtr(projsize * 2));
			if zbufptr = nil then
				begin Abort; exit(DoProjection) end;
		end;
	if (Opacity > 0) and (ProjectionMethod <> NearestPoint) then begin
			opaptr := NewPtr(projsize);
			if opaptr = nil then
				begin Abort; exit(DoProjection) end;
		end;
	if (ProjectionMethod = BrightestPoint) and (DepthCueInt < 100) then begin
			brightcueptr := NewPtr(projsize);
			cuezbufptr := IntPtr(NewPtr(projsize * 2));
			if (brightcueptr = nil) or (cuezbufptr = nil) then
				begin Abort; exit(DoProjection) end;
		end;
	if (ProjectionMethod = MeanValue) then begin
			sumbufptr := LongPtr(NewPtr(projsize * 4));
			countbufptr := IntPtr(NewPtr(projsize * 2));
			if (sumbufptr = nil) or (countbufptr = nil) then
				begin Abort; exit(DoProjection) end;
		end;
	AllocatingBuffers := false;
	SaveProjectionsTemp := FALSE;              {check whether we have enough memory to open}
	MemoryRequired := nProjections * projsize + SizeOf(PicInfo) + MinFree;
	if (MemoryRequired > FreeMem) and not (SaveProjections) then begin
			str := 'Insufficient memory to create entire stack of projections.  Projection images will be saved to disk.';
			if (PutMessageWithCancel(str) = cancel) then
				begin Abort; exit(DoProjection) end;
			SaveProjections := TRUE;
			SaveProjectionsTemp := TRUE;
		end;
	if (SaveProjections) then begin           {prepare to save projections as created if desired}
			SaveAsWhat := AsTiff;
			SaveAllState := SaveAllStage1;
		end;
	TimeStr := '?';
	theta := InitAngle;                                     {begin rotation with user-specified angle}
	ticksinit := TickCount;
	for n := 1 to nProjections do begin
			if (SaveProjections) or (n = 1) then begin    {open new window or stack slice}
					if SaveProjections then
						case AxisOfRotation of
							XAxis: 
								str := StringOf('Projection X ', theta:3);
							YAxis: 
								str := StringOf('Projection Y ', theta:3);
							ZAxis: 
								str := StringOf('Projection Z ', theta:3);
						end
					else
						str := 'Projections';
					if not NewPicWindow(str, projwidth, projheight) then
						begin Abort; exit(DoProjection) end;
				end
			else if not (AddSlice(false)) then
				begin Abort; exit(DoProjection) end;
			str := concat('Projecting: ', long2str(n), '/', long2str(nProjections), ' (', long2str(Theta), '¡', ', ', TimeStr, ')');
			ShowMeter;
			UpdateMeter(0, str);
			thetarad := theta * pi / 180.0;
			costheta := round(bigpowerof2 * cos(thetarad));
			sintheta := round(bigpowerof2 * sin(thetarad));
			p := projptr;                                          {initialize required projection buffers}
			InitializeBuffer(p, projsize, 255, 1);
			if (ProjectionMethod = NearestPoint) or (Opacity > 0) then begin
					zp := zbufptr;
					InitializeBuffer(Ptr(zp), projsize, 32767, 2);
				end;     {then}
			if (Opacity > 0) and (ProjectionMethod <> NearestPoint) then begin
					op := opaptr;
					InitializeBuffer(op, projsize, 255, 1);
				end;     {then}
			if (ProjectionMethod = MeanValue) then begin
					sp := sumbufptr;
					cp := countbufptr;
					InitializeBuffer(Ptr(sp), projsize, 0, 4);
					InitializeBuffer(Ptr(cp), projsize, 0, 2);
				end;
			if (ProjectionMethod = BrightestPoint) and (DepthCueInt < 100) then begin
					bp := brightcueptr;
					InitializeBuffer(bp, projsize, 255, 1);
					qp := cuezbufptr;
					InitializeBuffer(Ptr(qp), projsize, 255, 2);
				end;     {then}
			UpdateMeter(10, str);
			DestInfo := Info;
			Info := SourceInfo;
			case AxisOfRotation of
				XAxis: 
					DoOneProjectionX(nSlices, ycenter, zcenter, projwidth, projheight, costheta, sintheta, projptr, opaptr, brightcueptr, zbufptr, cuezbufptr, countbufptr, sumbufptr, str);
				YAxis: 
					DoOneProjectionY(nSlices, xcenter, zcenter, projwidth, projheight, costheta, sintheta, projptr, opaptr, brightcueptr, zbufptr, cuezbufptr, countbufptr, sumbufptr, str);
				ZAxis: 
					DoOneProjectionZ(nSlices, xcenter, ycenter, zcenter, projwidth, projheight, costheta, sintheta, projptr, opaptr, brightcueptr, zbufptr, cuezbufptr, countbufptr, sumbufptr, str);
			end;
			Info := DestInfo;
			if n = 1 then
				TicksForOneProjection := TickCount - TicksInit;
			TicksToGo := (nProjections - n) * TicksForOneProjection;
			NumToString((TicksToGo div 60) mod 60, seconds);
			if length(seconds) = 1 then
				seconds := concat('0', seconds);
			timestr := concat(long2str(TicksToGo div 3600), ':', seconds);
			if (ProjectionMethod = MeanValue) then begin
					p := projptr;                                    {calculate the mean-value image from returned info}
					sp := sumbufptr;
					cp := countbufptr;
					for offset := 0 to projsize - 1 do begin
							if (cp^ > 0) then
								p^ := sp^ div cp^;
							p := ptr(ord4(p) + 1);
							sp := LongPtr(ord4(sp) + 4);
							cp := IntPtr(ord4(cp) + 2);
						end;     {for}
				end;     {then}
			if (Opacity > 0) and (ProjectionMethod <> NearestPoint) then begin
					p := projptr;                                    {calculate surface proj component (opacity on)}
					op := opaptr;                                     {and combine with another proj component}
					for offset := 0 to projsize - 1 do begin
							p^ := (Opacity * BAND(op^, 255) + (100 - Opacity) * BAND(p^, 255)) div 100;
							p := ptr(ord4(p) + 1);
							op := ptr(ord4(op) + 1);
						end;     {for}
				end;     {then}
			if (AxisOfRotation = ZAxis) then begin  {interpolate for z-axis rotation}
					p := ptr(ord4(projptr) + projwidth);
					for offset := projwidth to projsize - 1 - projwidth do begin
							curval := BAND(p^, 255);
							prevval := BAND(ptr(ord4(p) - 1)^, 255);
							nextval := BAND(ptr(ord4(p) + 1)^, 255);
							aboveval := BAND(ptr(ord4(p) - projwidth)^, 255);
							belowval := BAND(ptr(ord4(p) + projwidth)^, 255);
							if (curval = 255) and (prevval <> 255) and (nextval <> 255) and (aboveval <> 255) and (belowval <> 255) then
								p^ := (prevval + nextval + aboveval + belowval) div 4;
							p := ptr(ord4(p) + 1);
						end;
				end;
			if (SaveProjections) or (n = 1) then
				BlockMove(projptr, Info^.PicBaseAddr, projsize)         {whole ROI write to projection image}
			else
				BlockMove(projptr, Info^.StackInfo^.PicBaseH[n]^, projsize);
			UpdateMeter(-1, '');        {dispose of meter}
			if cancelled then begin
					if n > 1 then
						DeleteSlice;
					leave;
				end;
			if (SaveProjections) then begin
					SaveAs('', 0);                                    {just save and put away current image after creating it}
					ignore := CloseAWindow(info^.wptr);
				end
			else if n = 1 then begin  {create new stack for first projection if not saving projections}
					if not MakeStackFromWindow then
						begin Abort; exit(DoProjection) end
				end;
			theta := (theta + AngleInc) mod 360;
			UpdatePicWindow;
		end;     {for}
	SaveAllState := NoSaveAll;
	if SaveProjectionsTemp then                 {turn this back off if we turned it on due to lack of memory}
		SaveProjections := FALSE;
	DisposeProjectionPtrs(projptr, opaptr, brightcueptr, zbufptr, countbufptr, cuezbufptr, sumbufptr);
	SetPort(tPort);
	DestInfo := info;
	info := SourceInfo;
	SelectSlice(info^.StackInfo^.CurrentSlice);
	if AutoSelectAll then
		KillRoi;
	info := DestInfo;
end;     {procedure DoProjection}



{******************************************************************************}
{*     This procedure allows the user to set parameters for projection in a large dialog box.                                  *}
{******************************************************************************}
function ShowProjectDialogBox: boolean;
	const
		ProjectOptionsID = 128;
		SliceIntervalID = 3;
		InitAngleID = 4;
		TotalAngleID = 5;
		AngleIncID = 6;
		TransparencyLowerID = 7;
		TransparencyUpperID = 8;
		OpacityID = 9;
		DepthCueSurfID = 10;
		DepthCueIntID = 11;
		RotationAboutXID = 12;
		RotationAboutYID = 13;
		RotationAboutZID = 14;
		SaveProjectionsID = 15;
		MinProjSizeID = 16;
		NearestID = 28;
		BrightestID = 29;
		MeanID = 30;
	var
		mylog: Dialogptr;                                           {pointer to dialog box}
		i, item, alldone: integer;
		SaveInitAngle, SaveTotalAngle, SaveAngleInc: integer;
		SaveOpacity: integer;
		SaveAxisOfRotation: AxisType;
		SaveSaveProjections, SaveCloseSlices, SaveMinProjSize: Boolean;
		SaveLower, SaveUpper: integer;
		PercentSurf, PercentInt: integer;
		interval: extended;
begin
	InitCursor;
	Interval := info^.StackInfo^.SliceSpacing;
	if Interval <= 0.0 then
		Interval := 1.0;
	SaveInitAngle := InitAngle;
	SaveTotalAngle := TotalAngle;
	SaveAngleInc := AngleInc;
	SaveOpacity := Opacity;
	SaveAxisOfRotation := AxisOfRotation;
	SaveSaveProjections := SaveProjections;
	SaveMinProjSize := MinProjSize;
	SaveLower := TransparencyLower;
	SaveUpper := TransparencyUpper;
	PercentSurf := 100 - DepthCueSurf;
	PercentInt := 100 - DepthCueInt;
	if DensitySlicing then
		with info^ do begin
				TransparencyLower := SliceStart;
				TransparencyUpper := SliceEnd;
			end;
	mylog := GetNewDialog(ProjectOptionsID, nil, pointer(-1));
	SetDReal(MyLog, SliceIntervalID, Interval, 1);
	SelectdialogItemText(MyLog, SliceIntervalID, 0, 32767);
	SetDNum(MyLog, InitAngleID, InitAngle);
	SetDNum(MyLog, TotalAngleID, TotalAngle);
	SetDNum(MyLog, AngleIncID, AngleInc);
	SetDNum(MyLog, TransparencyLowerID, TransparencyLower);
	SetDNum(MyLog, TransparencyUpperID, TransparencyUpper);
	SetDNum(MyLog, OpacityID, Opacity);
	SetDNum(MyLog, DepthCueSurfID, PercentSurf);
	SetDNum(MyLog, DepthCueIntID, PercentInt);
	OutlineButton(MyLog, ok, 16);
	SetDlogItem(MyLog, RotationAboutXID, ord(AxisOfRotation = XAxis));
	SetDlogItem(MyLog, RotationAboutYID, ord(AxisOfRotation = YAxis));
	SetDlogItem(MyLog, RotationAboutZID, ord(AxisOfRotation = ZAxis));
	SetDlogItem(MyLog, NearestID, ord(ProjectionMethod = NearestPoint));
	SetDlogItem(MyLog, BrightestID, ord(ProjectionMethod = BrightestPoint));
	SetDlogItem(MyLog, MeanID, ord(ProjectionMethod = MeanValue));
	SetDlogItem(MyLog, SaveProjectionsID, ord(SaveProjections));
	SetDlogItem(MyLog, MinProjSizeID, ord(MinProjSize));
	alldone := 0;
	repeat  {if we don't do it this way, ModalDialog throws us into code checking after each keystroke}
		repeat   {meaning you can't type in a 2 digit number}
			ModalDialog(nil, item);
			if item = SaveProjectionsID then begin
					SaveProjections := not SaveProjections;
					SetDlogItem(MyLog, SaveProjectionsID, ord(SaveProjections));
				end;
			if item = MinProjSizeID then begin
					MinProjSize := not MinProjSize;
					SetDlogItem(MyLog, MinProjSizeID, ord(MinProjSize));
				end;
			if (item = RotationAboutXID) or (item = RotationAboutYID) or (item = RotationAboutZID) then begin
					case item of
						RotationAboutXID: 
							AxisOfRotation := XAxis;
						RotationAboutYID: 
							AxisOfRotation := YAxis;
						RotationAboutZID: 
							AxisOfRotation := ZAxis;
					end;     {case}
					SetDlogItem(MyLog, RotationAboutXID, ord(AxisOfRotation = XAxis));
					SetDlogItem(MyLog, RotationAboutYID, ord(AxisOfRotation = YAxis));
					SetDlogItem(MyLog, RotationAboutZID, ord(AxisOfRotation = ZAxis));
				end;
			if (item >= nearestID) and (item <= MeanID) then begin
					case item of
						NearestID: 
							ProjectionMethod := NearestPoint;
						BrightestID: 
							ProjectionMethod := BrightestPoint;
						MeanID: 
							ProjectionMethod := MeanValue;
					end;
					SetDlogItem(MyLog, NearestID, ord(projectionMethod = NearestPoint));
					SetDlogItem(MyLog, BrightestID, ord(projectionMethod = BrightestPoint));
					SetDlogItem(MyLog, MeanID, ord(projectionMethod = MeanValue));
				end;
		until (item = ok) or (item = cancel);
		alldone := 1;
		if (item = ok) then begin  {check all the values that could have been 
