Fixed sprite rendering.

Fixed sprite rendering.

Strict

Rem
	bbdoc: Direct3D 9 renderer for MaxB3D
End Rem
Module MaxB3D.D3D9Driver
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import Prime.D3D9Max2DEx
Import Prime.DirectXEx

?Win32

Import "d3d9.cpp"

Private

Extern "C"
	Function maxb3dD3D9VertexElements:Byte Ptr()'="_maxb3dD3D9VertexElements@0"
End Extern

Function GetD3D9MaxB3DVertexDecl:IDirect3DVertexDeclaration9(d3ddev:IDirect3DDevice9)
	Global decl:IDirect3DVertexDeclaration9,dev:IDirect3DDevice9
	If decl=Null Or dev<>d3ddev
		Assert d3ddev.CreateVertexDeclaration(maxb3dD3D9VertexElements(),decl)=D3D_OK,"Failed to create vertex declaration."
		dev=d3ddev
	EndIf
	Return decl
End Function

Function Pow2Size( n )
	Local t=1
	While t<n
		t:*2
	Wend
	Return t
End Function

Public

Type TD3D9MaxB3DDriver Extends TMaxB3DDriver
	Field _d3ddev:IDirect3DDevice9
	Field _viewporton

	Method SetGraphics( g:TGraphics )
		Super.SetGraphics g
		_d3ddev=Null
		If g<>Null
			_d3ddev=TD3D9Graphics(TMax2DGraphics(g)._graphics).GetDirect3DDevice()
			EndMax2D
		EndIf
	End Method

	Method MakeBuffer:TBuffer(src:Object,width,height,flags)
		Return TMax2DExDriver(_parent).MakeBuffer(src,width,height,flags)
	End Method

	Method GetCaps:TCaps()
		Local caps:TCaps=New TCaps
		Return caps
	End Method

	Method Abbr$()
		Return "d3d9"
	End Method

	Method SetMax2D(enable)
		If enable
			Global identity:TMatrix=TMatrix.Identity()
			Local width=GraphicsWidth(),height=GraphicsHeight()
			Local matrix#[]=[..
			2.0/width,0.0,0.0,0.0,..
			 0.0,-2.0/height,0.0,0.0,..
			 0.0,0.0,1.0,0.0,..
			 -1-(1.0/width),1+(1.0/height),1.0,1.0]

			SetBlend -1
			SetBlend SOLIDBLEND

			_d3ddev.SetTransform D3DTS_PROJECTION,matrix
			_d3ddev.SetTransform D3DTS_WORLD,identity.ToPtr()
			_d3ddev.SetTransform D3DTS_VIEW,identity.ToPtr()
			_d3ddev.SetTransform D3DTS_TEXTURE0,identity.ToPtr()

			_d3ddev.SetVertexDeclaration Null
			_d3ddev.SetIndices Null
			_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1

			_d3ddev.SetRenderState D3DRS_LIGHTING,False
			_d3ddev.SetRenderState D3DRS_ZENABLE,False
			_d3ddev.SetRenderState D3DRS_SCISSORTESTENABLE,_viewporton

			_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE,True

			For Local i=1 To 7
				_d3ddev.SetTexture i,Null
			Next

			_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1

			_d3ddev.SetTextureStageState 0,D3DTSS_COLOROP,D3DTOP_MODULATE
			_d3ddev.SetTextureStageState 0,D3DTSS_ALPHAOP,D3DTOP_MODULATE

			_d3ddev.SetTextureStageState 0,D3DTSS_ADDRESS,D3DTADDRESS_CLAMP

			_d3ddev.SetTextureStageState 0,D3DTSS_MAGFILTER,D3DTFG_POINT
			_d3ddev.SetTextureStageState 0,D3DTSS_MINFILTER,D3DTFN_POINT
			_d3ddev.SetTextureStageState 0,D3DTSS_MIPFILTER,D3DTFP_POINT

			_d3ddev.SetRenderState D3DRS_FOGENABLE,False

			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_NONE
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_SOLID
		Else
			_d3ddev.SetRenderState D3DRS_ZENABLE,True
			_d3ddev.SetRenderState D3DRS_ZWRITEENABLE,True

			_d3ddev.GetRenderState D3DRS_SCISSORTESTENABLE,_viewporton
			_d3ddev.SetRenderState D3DRS_SCISSORTESTENABLE,True

			_d3ddev.SetRenderState D3DRS_LIGHTING,True
			_d3ddev.SetRenderState D3DRS_NORMALIZENORMALS,True

			_d3ddev.SetRenderState D3DRS_COLORVERTEX, True

			'_d3ddev.SetRenderState D3DRS_ALPHAREF, 1
			'_d3ddev.SetRenderState D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL
		EndIf
	End Method

	Method SetCamera(camera:TCamera,config:TWorldConfig)
		_d3ddev.SetRenderState D3DRS_DITHERENABLE, config.Dither

		Local clearflags
		If camera._clsmode&CLSMODE_COLOR clearflags:|D3DCLEAR_TARGET
		If camera._clsmode&CLSMODE_DEPTH clearflags:|D3DCLEAR_ZBUFFER

		Local viewport[]=[camera._viewx,camera._viewy,camera._viewwidth-camera._viewx,camera._viewheight-camera._viewy]
		_d3ddev.SetScissorRect viewport
		_d3ddev.Clear(1,viewport,clearflags,D3DCOLOR_XRGB(camera._brush._r*255,camera._brush._g*255,camera._brush._b*255),1.0,0)

		Select camera._fogmode
		Case FOGMODE_LINEAR
			_d3ddev.SetRenderState D3DRS_FOGENABLE,True
			_d3ddev.SetRenderState D3DRS_FOGSTART,Int(Varptr camera._fognear)
			_d3ddev.SetRenderState D3DRS_FOGEND,Int(Varptr camera._fogfar)
		Default
			_d3ddev.SetRenderState D3DRS_FOGENABLE,False
		End Select

		Local oldnear# = camera._near ' Ugh, hack. Need to investigate cause.
		camera._near :- 0.396250010
		camera.UpdateMatrices()
		camera._near = oldnear

		_d3ddev.SetTransform D3DTS_PROJECTION,camera._projection.ToPtr()
		_d3ddev.SetTransform D3DTS_VIEW,camera._modelview.ToPtr()
	End Method

	Method SetLight(light:TLight,index)
		If light=Null
			_d3ddev.LightEnable index,False
			Return
		EndIf

		Local brush:TBrush=light._brush

		Global d3dlight:D3DLIGHT9=New D3DLIGHT9
		d3dlight.Type_=D3DLIGHT_DIRECTIONAL
		d3dlight.Diffuse_r=brush._r;d3dlight.Diffuse_g=brush._g;d3dlight.Diffuse_b=brush._b;d3dlight.Diffuse_a=brush._a
		'd3dlight.Ambient_r=WorldConfig.AmbientRed/255.0;d3dlight.Ambient_g=WorldConfig.AmbientGreen/255.0;d3dlight.Ambient_b=WorldConfig.AmbientBlue/255.0;d3dlight.Ambient_a=1.0

		d3dlight.Direction_x=0.0;d3dlight.Direction_y=0.0;d3dlight.Direction_z=1.0

		Local matrix:TMatrix=light._matrix
		matrix.TransformVec3 d3dlight.Direction_x,d3dlight.Direction_y,d3dlight.Direction_z
		matrix.GetPosition d3dlight.Position_x,d3dlight.Position_y,d3dlight.Position_z

		d3dlight.Range=light._range

		_d3ddev.SetLight index,d3dlight
		_d3ddev.LightEnable index,True
	End Method

	Method SetBrush(brush:TBrush,hasalpha,config:TWorldConfig)
		_d3ddev.SetRenderState D3DRS_ALPHABLENDENABLE,hasalpha
		_d3ddev.SetRenderState D3DRS_ZWRITEENABLE,Not hasalpha

		Select brush._blend
		Case BLEND_NONE, BLEND_ALPHA
			_d3ddev.SetRenderState D3DRS_SRCBLEND,D3DBLEND_SRCALPHA
			_d3ddev.SetRenderState D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA
		Case BLEND_MULTIPLY
			_d3ddev.SetRenderState D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR
			_d3ddev.SetRenderState D3DRS_DESTBLEND,D3DBLEND_ZERO
		Case BLEND_ADD
			_d3ddev.SetRenderState D3DRS_SRCBLEND,D3DBLEND_SRCALPHA
			_d3ddev.SetRenderState D3DRS_DESTBLEND,D3DBLEND_ONE
		End Select

		If brush._fx&FX_FULLBRIGHT
			_d3ddev.SetRenderState D3DRS_AMBIENT,D3DCOLOR_RGB(255,255,255)
		Else
			_d3ddev.SetRenderState D3DRS_AMBIENT,D3DCOLOR_RGB(config.AmbientRed,config.AmbientGreen,config.AmbientBlue)
		EndIf

		If brush._fx&FX_VERTEXCOLOR
			_d3ddev.SetRenderState D3DRS_DIFFUSEMATERIALSOURCE,D3DMCS_COLOR1
			_d3ddev.SetRenderState D3DRS_AMBIENTMATERIALSOURCE,D3DMCS_COLOR1
		Else
			_d3ddev.SetRenderState D3DRS_DIFFUSEMATERIALSOURCE,D3DMCS_MATERIAL
			_d3ddev.SetRenderState D3DRS_AMBIENTMATERIALSOURCE,D3DMCS_MATERIAL
		EndIf

		If brush._fx&FX_FLATSHADED
			_d3ddev.SetRenderState D3DRS_SHADEMODE, D3DSHADE_FLAT
		Else
			_d3ddev.SetRenderState D3DRS_SHADEMODE, D3DSHADE_GOURAUD
		EndIf

		If brush._fx&FX_NOCULLING
			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_NONE
		Else
			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_CCW
		EndIf

		If brush._fx&FX_WIREFRAME Or config.Wireframe
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_WIREFRAME
		Else
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_SOLID
		EndIf

		Local material:D3DMATERIAL9 = New D3DMATERIAL9
		material.Diffuse_r=brush._r;material.Diffuse_g=brush._g;material.Diffuse_b=brush._b;material.Diffuse_a=brush._a
		material.Ambient_r=brush._r;material.Ambient_g=brush._g;material.Ambient_b=brush._b;material.Ambient_a=brush._a

		_d3ddev.SetMaterial material

		Local alpha_test
		For Local i=0 To 7
			Local texture:TTexture=brush._texture[i]
			If texture=Null Or texture._blend=BLEND_NONE
				_d3ddev.SetTexture i,Null
				Continue
			EndIf

			_d3ddev.SetTexture i,UpdateTextureRes(texture._frame[brush._textureframe[i]],texture._flags)._tex

			Local matrix:TMatrix=TMatrix.Identity()
			'matrix._m[0,0]=-texture._sx
			'matrix._m[1,1]=texture._sy
			matrix=TMatrix.YawPitchRoll(0,0,-texture._r).Multiply(matrix)
			matrix=TMatrix.Scale(-texture._sx,texture._sy,1).Multiply(matrix)
			matrix._m[2,0]=-texture._px
			matrix._m[2,1]=-texture._py

			_d3ddev.SetTransform D3DTS_TEXTURE0+i,matrix.ToPtr()

			_d3ddev.SetTextureStageState i,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2
			_d3ddev.SetTextureStageState i,D3DTSS_ADDRESS,0

			_d3ddev.SetSamplerState i,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR
			_d3ddev.SetSamplerState i,D3DSAMP_MINFILTER,D3DTEXF_LINEAR

			'_d3ddev.SetRenderState D3DRS_WRAP0+i, D3DWRAP_U|D3DWRAP_V
			alpha_test :| (texture._flags&TEXTURE_ALPHA Or texture._flags&TEXTURE_MASKED)

			If texture._flags&TEXTURE_MIPMAP
				_d3ddev.SetSamplerState i,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR
				_d3ddev.SetTextureStageState i,D3DTSS_MAGFILTER,D3DTEXF_LINEAR
				_d3ddev.SetTextureStageState i,D3DTSS_MINFILTER,D3DTEXF_LINEAR
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_MIPFILTER,D3DTEXF_NONE
				_d3ddev.SetTextureStageState i,D3DTSS_MAGFILTER,D3DTEXF_POINT
				_d3ddev.SetTextureStageState i,D3DTSS_MINFILTER,D3DTEXF_POINT
			EndIf

			If texture._flags&TEXTURE_CLAMPU
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_CLAMP
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_WRAP
			EndIf

			If texture._flags&TEXTURE_CLAMPV
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_CLAMP
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSV,D3DTADDRESS_WRAP
			EndIf

			If texture._flags&TEXTURE_SPHEREMAP
				_d3ddev.SetTextureStageState i,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_SPHEREMAP
			Else
				_d3ddev.SetTextureStageState i,D3DTSS_TEXCOORDINDEX,texture._coords
			EndIf

			Select texture._blend
			Case BLEND_ALPHA
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE
			Case BLEND_MULTIPLY
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE
			Case BLEND_ADD
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_ADD
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_ADD
			Case BLEND_DOT3
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_DOTPRODUCT3
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_DOTPRODUCT3
			Case BLEND_MULTIPLY2
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE2X
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE2X
			Default
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_SELECTARG2
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_SELECTARG2
			End Select
		Next
		_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE, alpha_test
	End Method

	Method RenderSurface(resource:TSurfaceRes,brush:TBrush)
		Local res:TD3D9SurfaceRes=TD3D9SurfaceRes(resource)

		_d3ddev.SetFVF 0
		_d3ddev.SetVertexDeclaration GetD3D9MaxB3DVertexDecl(_d3ddev)
		_d3ddev.SetStreamSource 0,res._pos,0,12
		_d3ddev.SetStreamSource 1,res._nml,0,12
		_d3ddev.SetStreamSource 2,res._clr,0,16
		_d3ddev.SetStreamSource 3,res._tex[0],0,8
		_d3ddev.SetStreamSource 4,res._tex[1],0,8
		_d3ddev.SetStreamSource 5,res._tex[2],0,8
		_d3ddev.SetStreamSource 6,res._tex[3],0,8
		_d3ddev.SetStreamSource 7,res._tex[4],0,8
		_d3ddev.SetStreamSource 8,res._tex[5],0,8
		_d3ddev.SetStreamSource 9,res._tex[6],0,8
		_d3ddev.SetStreamSource 10,res._tex[7],0,8
		_d3ddev.SetIndices res._tri
		_d3ddev.DrawIndexedPrimitive D3DPT_TRIANGLELIST,0,0,res._vertexcnt,0,res._trianglecnt

		Return res._trianglecnt
	End Method

	Method RenderSprite(sprite:TSprite)
		Global _data#[]=[ -1.0, 1.0,0.0, 0.0,0.0,1.0, 0.0,0.0, ..
											-1.0,-1.0,0.0, 0.0,0.0,1.0, 0.0,1.0, ..
		                   1.0, 1.0,0.0, 0.0,0.0,1.0, 1.0,0.0, ..
		                   1.0,-1.0,0.0, 0.0,0.0,1.0, 1.0,1.0     ]

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
		_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLESTRIP,2,_data,8*4
	End Method

	Method BeginEntityRender(entity:TEntity)
		_d3ddev.SetTransform D3DTS_WORLD,entity.GetMatrix(True).ToPtr()
	End Method

	Method EndEntityRender(entity:TEntity)
	End Method

	Method RenderFlat(flat:TFlat)
		Local x#,y#,z#
		flat.GetScale x,y,z,True

		Global _data#[]=[ -1.0,0.0, 1.0, 0.0,1.0,0.0, 0.0,0.0, ..
		                   1.0,0.0, 1.0, 0.0,1.0,0.0,   x,0.0, ..
		                  -1.0,0.0,-1.0, 0.0,1.0,0.0, 0.0,  z, ..
		                   1.0,0.0,-1.0, 0.0,1.0,0.0,   x,  z     ]

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
		_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLESTRIP,2,_data,8*4
	End Method

	Method RenderTerrain(terrain:TTerrain)
	End Method

	Method RenderBSPTree(tree:TBSPTree)
		Local node:TBSPNode=tree.Node
		If node=Null Return
		Local triangles
		triangles:+RenderBSPTree(node.In)

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1

		For Local poly:TBSPPolygon=EachIn node.On
			Local ptA:TVector=poly.Point[0],v0
			Local trianglecnt=poly.Count()-2
			Local data#[trianglecnt*24],dataptr:Float Ptr=data
			For Local i=0 To trianglecnt-1
				Local ptB:TVector=poly.Point[i+1],ptC:TVector=poly.Point[i+2]
				data[i*24+00]=ptA.x;data[i*24+01]=ptA.y;data[i*24+02]=ptA.z;data[i*24+03]=poly.Plane.x;data[i*24+04]=poly.Plane.y;	data[i*24+05]=poly.Plane.z;data[i*24+06]=0.0;data[i*24+07]=0.0
				data[i*24+08]=ptB.x;data[i*24+09]=ptB.y;data[i*24+10]=ptB.z;data[i*24+11]=poly.Plane.x;data[i*24+12]=poly.Plane.y;	data[i*24+13]=poly.Plane.z;data[i*24+14]=0.0;data[i*24+15]=0.0
				data[i*24+16]=ptC.x;data[i*24+17]=ptC.y;data[i*24+18]=ptC.z;data[i*24+19]=poly.Plane.x;data[i*24+20]=poly.Plane.y;data[i*24+21]=poly.Plane.z;data[i*24+22]=0.0;data[i*24+23]=0.0
			Next
			_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLELIST,trianglecnt,data,8*4
			triangles:+trianglecnt
		Next

		triangles:+RenderBSPTree(node.Out)
		Return triangles
	End Method

	Method UpdateTextureRes:TD3D9TextureRes(frame:TTextureFrame,flags)
		Local res:TD3D9TextureRes=TD3D9TextureRes(frame._res)
		If res And frame._updateres=False Return res

		If res=Null res=New TD3D9TextureRes
		frame._res=res

		Local pixmap:TPixmap=frame._pixmap
		Local tex_width=Pow2Size(pixmap.width),tex_height=Pow2Size(pixmap.height)
		pixmap=ConvertPixmap(ResizePixmap(pixmap,tex_width,tex_height),PF_BGRA8888)
		If res._tex=Null Assert _d3ddev.CreateTexture(tex_width,tex_height,(flags & TEXTURE_MIPMAP)=0,D3DUSAGE_AUTOGENMIPMAP,D3DFMT_A8R8G8B8,D3DPOOL_MANAGED,res._tex,Null)=D3D_OK

		Local rect:D3DLOCKED_RECT=New D3DLOCKED_RECT
		res._tex.LockRect 0,rect,Null,0
		MemCopy rect.pBits,pixmap.pixels,pixmap.width*pixmap.height*4
		res._tex.UnlockRect 0

		frame._updateres=False

		Return res
	End Method

	Method UpdateSurfaceRes:TD3D9SurfaceRes(surface:TSurface)
		Local res:TD3D9SurfaceRes=TD3D9SurfaceRes(surface._res)
		If res=Null res=New TD3D9SurfaceRes;surface._reset=-1

		If surface._reset=0 Return res
		If surface._reset=-1 surface._reset=1|2|4|8|16|32|64|128|256

		If surface._reset&1 And surface._vertexpos UploadVertexData res._pos,surface._vertexpos
		If surface._reset&2 And surface._vertexnml UploadVertexData res._nml,surface._vertexnml
		If surface._reset&4 And surface._vertexclr UploadVertexData res._clr,surface._vertexclr
		If surface._reset&8 And surface._triangle
			If res._tri=Null _d3ddev.CreateIndexBuffer(surface._triangle.length*4,0,D3DFMT_INDEX32,D3DPOOL_MANAGED,res._tri,Null)
			Local dataptr:Byte Ptr
			Assert res._tri.Lock(0,0,dataptr,0)=D3D_OK,"Failed to lock index buffer."
			MemCopy dataptr,surface._triangle,surface._triangle.length*4
			res._tri.Unlock()
		EndIf

		For Local i=0 To surface._vertextex.length-1
			If surface._reset&Int(2^(4+i)) UploadVertexData res._tex[i],surface._vertextex[i]
		Next

		res._trianglecnt=surface._trianglecnt
		res._vertexcnt=surface._vertexcnt

		surface._reset=0
		surface._res=res

		Return res
	End Method

	Method MergeSurfaceRes:TSurfaceRes(base:TSurface,animation:TSurface,data)
		If animation=Null Return UpdateSurfaceRes(base)
		Local base_res:TD3D9SurfaceRes=UpdateSurfaceRes(base)
		Local anim_res:TD3D9SurfaceRes=UpdateSurfaceRes(animation)
		Local res:TD3D9SurfaceRes=base_res.Copy()
		res._pos=anim_res._pos
		Return res
	End Method

	Method UploadVertexData(buffer:IDirect3DVertexBuffer9 Var,data#[])
		If buffer=Null _d3ddev.CreateVertexBuffer(data.length*4,0,0,D3DPOOL_MANAGED,buffer,Null)
		Local dataptr:Byte Ptr
		Assert buffer.Lock(0,0,dataptr,0)=D3D_OK,"Failed to lock vertex buffer."
		MemCopy dataptr,data,data.length*4
		buffer.Unlock()
	End Method

End Type

Type TD3D9TextureRes Extends TTextureRes
	Field _tex:IDirect3DTexture9
End Type

Type TD3D9SurfaceRes Extends TSurfaceRes
	Field _pos:IDirect3DVertexBuffer9
	Field _nml:IDirect3DVertexBuffer9
	Field _clr:IDirect3DVertexBuffer9
	Field _tri:IDirect3DIndexBuffer9
	Field _tex:IDirect3DVertexBuffer9[8]

	Method Copy:TD3D9SurfaceRes()
		Local res:TD3D9SurfaceRes=New TD3D9SurfaceRes
		res._vertexcnt=_vertexcnt;res._trianglecnt=_trianglecnt
		res._pos=_pos;res._nml=_nml;res._clr=_clr;res._tri=_tri;
		For Local i=0 To 7
			res._tex[i]=_tex[i]
		Next
		Return res
	End Method
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function D3D9MaxB3DDriver:TD3D9MaxB3DDriver()
	If D3D9Max2DExDriver()
		Global driver:TD3D9MaxB3DDriver=New TD3D9MaxB3DDriver
		driver._parent=D3D9Max2DExDriver()
		Return driver
	End If
End Function

Rem
	bbdoc: Utility function that sets the MaxB3D D3D9 driver and calls Graphics.
End Rem
Function D3D9Graphics3D:TGraphics(width,height,depth=0,hertz=0,flags=0)
	SetGraphicsDriver D3D9MaxB3DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER
	Return Graphics(width,height,depth,hertz,flags)
End Function

Local driver:TD3D9MaxB3DDriver=D3D9MaxB3DDriver()
If driver SetGraphicsDriver driver,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER

?

Strict

Rem
	bbdoc: Direct3D 9 renderer for MaxB3D
End Rem
Module MaxB3D.D3D9Driver
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import Prime.D3D9Max2DEx
Import Prime.DirectXEx

?Win32

Import "d3d9.cpp"

Private

Extern "C"
	Function maxb3dD3D9VertexElements:Byte Ptr()'="_maxb3dD3D9VertexElements@0"
End Extern

Function GetD3D9MaxB3DVertexDecl:IDirect3DVertexDeclaration9(d3ddev:IDirect3DDevice9)
	Global decl:IDirect3DVertexDeclaration9,dev:IDirect3DDevice9
	If decl=Null Or dev<>d3ddev
		Assert d3ddev.CreateVertexDeclaration(maxb3dD3D9VertexElements(),decl)=D3D_OK,"Failed to create vertex declaration."
		dev=d3ddev
	EndIf
	Return decl
End Function

Function Pow2Size( n )
	Local t=1
	While t<n
		t:*2
	Wend
	Return t
End Function

Public

Type TD3D9MaxB3DDriver Extends TMaxB3DDriver
	Field _d3ddev:IDirect3DDevice9
	Field _viewporton

	Method SetGraphics( g:TGraphics )
		Super.SetGraphics g
		_d3ddev=Null
		If g<>Null
			_d3ddev=TD3D9Graphics(TMax2DGraphics(g)._graphics).GetDirect3DDevice()
			EndMax2D
		EndIf
	End Method

	Method MakeBuffer:TBuffer(src:Object,width,height,flags)
		Return TMax2DExDriver(_parent).MakeBuffer(src,width,height,flags)
	End Method

	Method GetCaps:TCaps()
		Local caps:TCaps=New TCaps
		Return caps
	End Method

	Method Abbr$()
		Return "d3d9"
	End Method

	Method SetMax2D(enable)
		If enable
			Global identity:TMatrix=TMatrix.Identity()
			Local width=GraphicsWidth(),height=GraphicsHeight()
			Local matrix#[]=[..
			2.0/width,0.0,0.0,0.0,..
			 0.0,-2.0/height,0.0,0.0,..
			 0.0,0.0,1.0,0.0,..
			 -1-(1.0/width),1+(1.0/height),1.0,1.0]

			SetBlend -1
			SetBlend SOLIDBLEND

			_d3ddev.SetTransform D3DTS_PROJECTION,matrix
			_d3ddev.SetTransform D3DTS_WORLD,identity.ToPtr()
			_d3ddev.SetTransform D3DTS_VIEW,identity.ToPtr()
			_d3ddev.SetTransform D3DTS_TEXTURE0,identity.ToPtr()

			_d3ddev.SetVertexDeclaration Null
			_d3ddev.SetIndices Null
			_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1

			_d3ddev.SetRenderState D3DRS_LIGHTING,False
			_d3ddev.SetRenderState D3DRS_ZENABLE,False
			_d3ddev.SetRenderState D3DRS_SCISSORTESTENABLE,_viewporton

			_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE,True

			For Local i=1 To 7
				_d3ddev.SetTexture i,Null
			Next

			_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1

			_d3ddev.SetTextureStageState 0,D3DTSS_COLOROP,D3DTOP_MODULATE
			_d3ddev.SetTextureStageState 0,D3DTSS_ALPHAOP,D3DTOP_MODULATE

			_d3ddev.SetTextureStageState 0,D3DTSS_ADDRESS,D3DTADDRESS_CLAMP

			_d3ddev.SetTextureStageState 0,D3DTSS_MAGFILTER,D3DTFG_POINT
			_d3ddev.SetTextureStageState 0,D3DTSS_MINFILTER,D3DTFN_POINT
			_d3ddev.SetTextureStageState 0,D3DTSS_MIPFILTER,D3DTFP_POINT

			_d3ddev.SetRenderState D3DRS_FOGENABLE,False

			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_NONE
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_SOLID
		Else
			_d3ddev.SetRenderState D3DRS_ZENABLE,True
			_d3ddev.SetRenderState D3DRS_ZWRITEENABLE,True

			_d3ddev.GetRenderState D3DRS_SCISSORTESTENABLE,_viewporton
			_d3ddev.SetRenderState D3DRS_SCISSORTESTENABLE,True

			_d3ddev.SetRenderState D3DRS_LIGHTING,True
			_d3ddev.SetRenderState D3DRS_NORMALIZENORMALS,True

			_d3ddev.SetRenderState D3DRS_COLORVERTEX, True

			'_d3ddev.SetRenderState D3DRS_ALPHAREF, 1
			'_d3ddev.SetRenderState D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL
		EndIf
	End Method

	Method SetCamera(camera:TCamera,config:TWorldConfig)
		_d3ddev.SetRenderState D3DRS_DITHERENABLE, config.Dither

		Local clearflags
		If camera._clsmode&CLSMODE_COLOR clearflags:|D3DCLEAR_TARGET
		If camera._clsmode&CLSMODE_DEPTH clearflags:|D3DCLEAR_ZBUFFER

		Local viewport[]=[camera._viewx,camera._viewy,camera._viewwidth-camera._viewx,camera._viewheight-camera._viewy]
		_d3ddev.SetScissorRect viewport
		_d3ddev.Clear(1,viewport,clearflags,D3DCOLOR_XRGB(camera._brush._r*255,camera._brush._g*255,camera._brush._b*255),1.0,0)

		Select camera._fogmode
		Case FOGMODE_LINEAR
			_d3ddev.SetRenderState D3DRS_FOGENABLE,True
			_d3ddev.SetRenderState D3DRS_FOGSTART,Int(Varptr camera._fognear)
			_d3ddev.SetRenderState D3DRS_FOGEND,Int(Varptr camera._fogfar)
		Default
			_d3ddev.SetRenderState D3DRS_FOGENABLE,False
		End Select

		Local oldnear# = camera._near ' Ugh, hack. Need to investigate cause.
		camera._near :- 0.396250010
		camera.UpdateMatrices()
		camera._near = oldnear

		_d3ddev.SetTransform D3DTS_PROJECTION,camera._projection.ToPtr()
		_d3ddev.SetTransform D3DTS_VIEW,camera._modelview.ToPtr()
	End Method

	Method SetLight(light:TLight,index)
		If light=Null
			_d3ddev.LightEnable index,False
			Return
		EndIf

		Local brush:TBrush=light._brush

		Global d3dlight:D3DLIGHT9=New D3DLIGHT9
		d3dlight.Type_=D3DLIGHT_DIRECTIONAL
		d3dlight.Diffuse_r=brush._r;d3dlight.Diffuse_g=brush._g;d3dlight.Diffuse_b=brush._b;d3dlight.Diffuse_a=brush._a
		'd3dlight.Ambient_r=WorldConfig.AmbientRed/255.0;d3dlight.Ambient_g=WorldConfig.AmbientGreen/255.0;d3dlight.Ambient_b=WorldConfig.AmbientBlue/255.0;d3dlight.Ambient_a=1.0

		d3dlight.Direction_x=0.0;d3dlight.Direction_y=0.0;d3dlight.Direction_z=1.0

		Local matrix:TMatrix=light._matrix
		matrix.TransformVec3 d3dlight.Direction_x,d3dlight.Direction_y,d3dlight.Direction_z
		matrix.GetPosition d3dlight.Position_x,d3dlight.Position_y,d3dlight.Position_z

		d3dlight.Range=light._range

		_d3ddev.SetLight index,d3dlight
		_d3ddev.LightEnable index,True
	End Method

	Method SetBrush(brush:TBrush,hasalpha,config:TWorldConfig)
		_d3ddev.SetRenderState D3DRS_ALPHABLENDENABLE,hasalpha
		_d3ddev.SetRenderState D3DRS_ZWRITEENABLE,Not hasalpha

		Select brush._blend
		Case BLEND_NONE, BLEND_ALPHA
			_d3ddev.SetRenderState D3DRS_SRCBLEND,D3DBLEND_SRCALPHA
			_d3ddev.SetRenderState D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA
		Case BLEND_MULTIPLY
			_d3ddev.SetRenderState D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR
			_d3ddev.SetRenderState D3DRS_DESTBLEND,D3DBLEND_ZERO
		Case BLEND_ADD
			_d3ddev.SetRenderState D3DRS_SRCBLEND,D3DBLEND_SRCALPHA
			_d3ddev.SetRenderState D3DRS_DESTBLEND,D3DBLEND_ONE
		End Select

		If brush._fx&FX_FULLBRIGHT
			_d3ddev.SetRenderState D3DRS_AMBIENT,D3DCOLOR_RGB(255,255,255)
		Else
			_d3ddev.SetRenderState D3DRS_AMBIENT,D3DCOLOR_RGB(config.AmbientRed,config.AmbientGreen,config.AmbientBlue)
		EndIf

		If brush._fx&FX_VERTEXCOLOR
			_d3ddev.SetRenderState D3DRS_DIFFUSEMATERIALSOURCE,D3DMCS_COLOR1
			_d3ddev.SetRenderState D3DRS_AMBIENTMATERIALSOURCE,D3DMCS_COLOR1
		Else
			_d3ddev.SetRenderState D3DRS_DIFFUSEMATERIALSOURCE,D3DMCS_MATERIAL
			_d3ddev.SetRenderState D3DRS_AMBIENTMATERIALSOURCE,D3DMCS_MATERIAL
		EndIf

		If brush._fx&FX_FLATSHADED
			_d3ddev.SetRenderState D3DRS_SHADEMODE, D3DSHADE_FLAT
		Else
			_d3ddev.SetRenderState D3DRS_SHADEMODE, D3DSHADE_GOURAUD
		EndIf

		If brush._fx&FX_NOCULLING
			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_NONE
		Else
			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_CCW
		EndIf

		If brush._fx&FX_WIREFRAME Or config.Wireframe
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_WIREFRAME
		Else
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_SOLID
		EndIf

		Local material:D3DMATERIAL9 = New D3DMATERIAL9
		material.Diffuse_r=brush._r;material.Diffuse_g=brush._g;material.Diffuse_b=brush._b;material.Diffuse_a=brush._a
		material.Ambient_r=brush._r;material.Ambient_g=brush._g;material.Ambient_b=brush._b;material.Ambient_a=brush._a

		_d3ddev.SetMaterial material

		Local alpha_test
		For Local i=0 To 7
			Local texture:TTexture=brush._texture[i]
			If texture=Null Or texture._blend=BLEND_NONE
				_d3ddev.SetTexture i,Null
				Continue
			EndIf

			_d3ddev.SetTexture i,UpdateTextureRes(texture._frame[brush._textureframe[i]],texture._flags)._tex

			Local matrix:TMatrix=TMatrix.Identity()
			'matrix._m[0,0]=-texture._sx
			'matrix._m[1,1]=texture._sy
			matrix=TMatrix.YawPitchRoll(0,0,-texture._r).Multiply(matrix)
			matrix=TMatrix.Scale(-texture._sx,texture._sy,1).Multiply(matrix)
			matrix._m[2,0]=-texture._px
			matrix._m[2,1]=-texture._py

			_d3ddev.SetTransform D3DTS_TEXTURE0+i,matrix.ToPtr()

			_d3ddev.SetTextureStageState i,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2
			_d3ddev.SetTextureStageState i,D3DTSS_ADDRESS,0

			_d3ddev.SetSamplerState i,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR
			_d3ddev.SetSamplerState i,D3DSAMP_MINFILTER,D3DTEXF_LINEAR

			'_d3ddev.SetRenderState D3DRS_WRAP0+i, D3DWRAP_U|D3DWRAP_V
			alpha_test :| (texture._flags&TEXTURE_ALPHA Or texture._flags&TEXTURE_MASKED)

			If texture._flags&TEXTURE_MIPMAP
				_d3ddev.SetSamplerState i,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR
				_d3ddev.SetTextureStageState i,D3DTSS_MAGFILTER,D3DTEXF_LINEAR
				_d3ddev.SetTextureStageState i,D3DTSS_MINFILTER,D3DTEXF_LINEAR
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_MIPFILTER,D3DTEXF_NONE
				_d3ddev.SetTextureStageState i,D3DTSS_MAGFILTER,D3DTEXF_POINT
				_d3ddev.SetTextureStageState i,D3DTSS_MINFILTER,D3DTEXF_POINT
			EndIf

			If texture._flags&TEXTURE_CLAMPU
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_CLAMP
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_WRAP
			EndIf

			If texture._flags&TEXTURE_CLAMPV
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_CLAMP
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSV,D3DTADDRESS_WRAP
			EndIf

			If texture._flags&TEXTURE_SPHEREMAP
				_d3ddev.SetTextureStageState i,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_SPHEREMAP
			Else
				_d3ddev.SetTextureStageState i,D3DTSS_TEXCOORDINDEX,texture._coords
			EndIf

			Select texture._blend
			Case BLEND_ALPHA
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE
			Case BLEND_MULTIPLY
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE
			Case BLEND_ADD
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_ADD
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_ADD
			Case BLEND_DOT3
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_DOTPRODUCT3
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_DOTPRODUCT3
			Case BLEND_MULTIPLY2
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE2X
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE2X
			Default
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_SELECTARG2
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_SELECTARG2
			End Select
		Next
		_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE, alpha_test
	End Method

	Method RenderSurface(resource:TSurfaceRes,brush:TBrush)
		Local res:TD3D9SurfaceRes=TD3D9SurfaceRes(resource)

		_d3ddev.SetFVF 0
		_d3ddev.SetVertexDeclaration GetD3D9MaxB3DVertexDecl(_d3ddev)
		_d3ddev.SetStreamSource 0,res._pos,0,12
		_d3ddev.SetStreamSource 1,res._nml,0,12
		_d3ddev.SetStreamSource 2,res._clr,0,16
		_d3ddev.SetStreamSource 3,res._tex[0],0,8
		_d3ddev.SetStreamSource 4,res._tex[1],0,8
		_d3ddev.SetStreamSource 5,res._tex[2],0,8
		_d3ddev.SetStreamSource 6,res._tex[3],0,8
		_d3ddev.SetStreamSource 7,res._tex[4],0,8
		_d3ddev.SetStreamSource 8,res._tex[5],0,8
		_d3ddev.SetStreamSource 9,res._tex[6],0,8
		_d3ddev.SetStreamSource 10,res._tex[7],0,8
		_d3ddev.SetIndices res._tri
		_d3ddev.DrawIndexedPrimitive D3DPT_TRIANGLELIST,0,0,res._vertexcnt,0,res._trianglecnt

		Return res._trianglecnt
	End Method

	Method RenderSprite(sprite:TSprite)
		Global _data#[]=[ -1.0, 1.0,0.0, 0.0,0.0,1.0, 0.0,0.0, ..
		                   1.0, 1.0,0.0, 0.0,0.0,1.0, 1.0,0.0, ..
		                  -1.0,-1.0,0.0, 0.0,0.0,1.0, 0.0,1.0, ..
		                   1.0,-1.0,0.0, 0.0,0.0,1.0, 1.0,1.0     ]

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
		_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLESTRIP,2,_data,8*4
	End Method

	Method BeginEntityRender(entity:TEntity)
		_d3ddev.SetTransform D3DTS_WORLD,entity.GetMatrix(True).ToPtr()
	End Method

	Method EndEntityRender(entity:TEntity)
	End Method

	Method RenderFlat(flat:TFlat)
		Local x#,y#,z#
		flat.GetScale x,y,z,True

		Global _data#[]=[ -1.0,0.0, 1.0, 0.0,1.0,0.0, 0.0,0.0, ..
		                   1.0,0.0, 1.0, 0.0,1.0,0.0,   x,0.0, ..
		                  -1.0,0.0,-1.0, 0.0,1.0,0.0, 0.0,  z, ..
		                   1.0,0.0,-1.0, 0.0,1.0,0.0,   x,  z     ]

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
		_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLESTRIP,2,_data,8*4
	End Method

	Method RenderTerrain(terrain:TTerrain)
	End Method

	Method RenderBSPTree(tree:TBSPTree)
		Local node:TBSPNode=tree.Node
		If node=Null Return
		Local triangles
		triangles:+RenderBSPTree(node.In)

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1

		For Local poly:TBSPPolygon=EachIn node.On
			Local ptA:TVector=poly.Point[0],v0
			Local trianglecnt=poly.Count()-2
			Local data#[trianglecnt*24],dataptr:Float Ptr=data
			For Local i=0 To trianglecnt-1
				Local ptB:TVector=poly.Point[i+1],ptC:TVector=poly.Point[i+2]
				data[i*24+00]=ptA.x;data[i*24+01]=ptA.y;data[i*24+02]=ptA.z;data[i*24+03]=poly.Plane.x;data[i*24+04]=poly.Plane.y;	data[i*24+05]=poly.Plane.z;data[i*24+06]=0.0;data[i*24+07]=0.0
				data[i*24+08]=ptB.x;data[i*24+09]=ptB.y;data[i*24+10]=ptB.z;data[i*24+11]=poly.Plane.x;data[i*24+12]=poly.Plane.y;	data[i*24+13]=poly.Plane.z;data[i*24+14]=0.0;data[i*24+15]=0.0
				data[i*24+16]=ptC.x;data[i*24+17]=ptC.y;data[i*24+18]=ptC.z;data[i*24+19]=poly.Plane.x;data[i*24+20]=poly.Plane.y;data[i*24+21]=poly.Plane.z;data[i*24+22]=0.0;data[i*24+23]=0.0
			Next
			_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLELIST,trianglecnt,data,8*4
			triangles:+trianglecnt
		Next

		triangles:+RenderBSPTree(node.Out)
		Return triangles
	End Method

	Method UpdateTextureRes:TD3D9TextureRes(frame:TTextureFrame,flags)
		Local res:TD3D9TextureRes=TD3D9TextureRes(frame._res)
		If res And frame._updateres=False Return res

		If res=Null res=New TD3D9TextureRes
		frame._res=res

		Local pixmap:TPixmap=frame._pixmap
		Local tex_width=Pow2Size(pixmap.width),tex_height=Pow2Size(pixmap.height)
		pixmap=ConvertPixmap(ResizePixmap(pixmap,tex_width,tex_height),PF_BGRA8888)
		If res._tex=Null Assert _d3ddev.CreateTexture(tex_width,tex_height,(flags & TEXTURE_MIPMAP)=0,D3DUSAGE_AUTOGENMIPMAP,D3DFMT_A8R8G8B8,D3DPOOL_MANAGED,res._tex,Null)=D3D_OK

		Local rect:D3DLOCKED_RECT=New D3DLOCKED_RECT
		res._tex.LockRect 0,rect,Null,0
		MemCopy rect.pBits,pixmap.pixels,pixmap.width*pixmap.height*4
		res._tex.UnlockRect 0

		frame._updateres=False

		Return res
	End Method

	Method UpdateSurfaceRes:TD3D9SurfaceRes(surface:TSurface)
		Local res:TD3D9SurfaceRes=TD3D9SurfaceRes(surface._res)
		If res=Null res=New TD3D9SurfaceRes;surface._reset=-1

		If surface._reset=0 Return res
		If surface._reset=-1 surface._reset=1|2|4|8|16|32|64|128|256

		If surface._reset&1 And surface._vertexpos UploadVertexData res._pos,surface._vertexpos
		If surface._reset&2 And surface._vertexnml UploadVertexData res._nml,surface._vertexnml
		If surface._reset&4 And surface._vertexclr UploadVertexData res._clr,surface._vertexclr
		If surface._reset&8 And surface._triangle
			If res._tri=Null _d3ddev.CreateIndexBuffer(surface._triangle.length*4,0,D3DFMT_INDEX32,D3DPOOL_MANAGED,res._tri,Null)
			Local dataptr:Byte Ptr
			Assert res._tri.Lock(0,0,dataptr,0)=D3D_OK,"Failed to lock index buffer."
			MemCopy dataptr,surface._triangle,surface._triangle.length*4
			res._tri.Unlock()
		EndIf

		For Local i=0 To surface._vertextex.length-1
			If surface._reset&Int(2^(4+i)) UploadVertexData res._tex[i],surface._vertextex[i]
		Next

		res._trianglecnt=surface._trianglecnt
		res._vertexcnt=surface._vertexcnt

		surface._reset=0
		surface._res=res

		Return res
	End Method

	Method MergeSurfaceRes:TSurfaceRes(base:TSurface,animation:TSurface,data)
		If animation=Null Return UpdateSurfaceRes(base)
		Local base_res:TD3D9SurfaceRes=UpdateSurfaceRes(base)
		Local anim_res:TD3D9SurfaceRes=UpdateSurfaceRes(animation)
		Local res:TD3D9SurfaceRes=base_res.Copy()
		res._pos=anim_res._pos
		Return res
	End Method

	Method UploadVertexData(buffer:IDirect3DVertexBuffer9 Var,data#[])
		If buffer=Null _d3ddev.CreateVertexBuffer(data.length*4,0,0,D3DPOOL_MANAGED,buffer,Null)
		Local dataptr:Byte Ptr
		Assert buffer.Lock(0,0,dataptr,0)=D3D_OK,"Failed to lock vertex buffer."
		MemCopy dataptr,data,data.length*4
		buffer.Unlock()
	End Method

End Type

Type TD3D9TextureRes Extends TTextureRes
	Field _tex:IDirect3DTexture9
End Type

Type TD3D9SurfaceRes Extends TSurfaceRes
	Field _pos:IDirect3DVertexBuffer9
	Field _nml:IDirect3DVertexBuffer9
	Field _clr:IDirect3DVertexBuffer9
	Field _tri:IDirect3DIndexBuffer9
	Field _tex:IDirect3DVertexBuffer9[8]

	Method Copy:TD3D9SurfaceRes()
		Local res:TD3D9SurfaceRes=New TD3D9SurfaceRes
		res._vertexcnt=_vertexcnt;res._trianglecnt=_trianglecnt
		res._pos=_pos;res._nml=_nml;res._clr=_clr;res._tri=_tri;
		For Local i=0 To 7
			res._tex[i]=_tex[i]
		Next
		Return res
	End Method
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function D3D9MaxB3DDriver:TD3D9MaxB3DDriver()
	If D3D9Max2DExDriver()
		Global driver:TD3D9MaxB3DDriver=New TD3D9MaxB3DDriver
		driver._parent=D3D9Max2DExDriver()
		Return driver
	End If
End Function

Rem
	bbdoc: Utility function that sets the MaxB3D D3D9 driver and calls Graphics.
End Rem
Function D3D9Graphics3D:TGraphics(width,height,depth=0,hertz=0,flags=0)
	SetGraphicsDriver D3D9MaxB3DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER
	Return Graphics(width,height,depth,hertz,flags)
End Function

Local driver:TD3D9MaxB3DDriver=D3D9MaxB3DDriver()
If driver SetGraphicsDriver driver,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER

?


Oops. Quick fix.

Oops. Quick fix.

Strict

Rem
	bbdoc: Direct3D 9 renderer for MaxB3D
End Rem
Module MaxB3D.D3D9Driver
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import BRL.D3D9Max2D

?Win32

Import "d3d9.bmx"

Private
Function Pow2Size( n )
	Local t=1
	While t<n
		t:*2
	Wend
	Return t
End Function

Public

Type TD3D9MaxB3DDriver Extends TMaxB3DDriver
	Field _d3ddev:IDirect3DDevice9
	Field _viewporton

	Method SetGraphics( g:TGraphics )
		Super.SetGraphics g
		_d3ddev=Null
		If g<>Null
			_d3ddev=TD3D9Graphics(TMax2DGraphics(g)._graphics).GetDirect3DDevice()
			EndMax2D
		EndIf
	End Method

	Method Flip(sync)
		Super.Flip(sync)
		If _d3ddev<>Null EndMax2D ' TODO: Figure out when this needs to be called. Most likely NOT always!
	End Method

	Method GetCaps:TCaps()
		Local caps:TCaps=New TCaps
		Return caps
	End Method

	Method BeginMax2D()
		Global identity:TMatrix=TMatrix.Identity()
		Local width=GraphicsWidth(),height=GraphicsHeight()
		Local matrix#[]=[..
		2.0/width,0.0,0.0,0.0,..
		 0.0,-2.0/height,0.0,0.0,..
		 0.0,0.0,1.0,0.0,..
		 -1-(1.0/width),1+(1.0/height),1.0,1.0]

		_d3ddev.SetTransform D3DTS_PROJECTION,matrix
		_d3ddev.SetTransform D3DTS_WORLD,identity.ToPtr()
		_d3ddev.SetTransform D3DTS_VIEW,identity.ToPtr()
		_d3ddev.SetTransform D3DTS_TEXTURE0,identity.ToPtr()

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetIndices Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1

		_d3ddev.SetRenderState D3DRS_LIGHTING,False
		_d3ddev.SetRenderState D3DRS_ZENABLE,False
		_d3ddev.SetRenderState D3DRS_SCISSORTESTENABLE,_viewporton

		_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE,True

		_d3ddev.SetRenderState D3DRS_WRAP0, 0

		_d3ddev.SetRenderState D3DRS_FOGENABLE,False

		For Local i=2 To 7
			_d3ddev.SetTexture i,Null
		Next

		_d3ddev.SetTextureStageState 0,D3DTSS_COLOROP,D3DTOP_MODULATE
		_d3ddev.SetTextureStageState 0,D3DTSS_ALPHAOP,D3DTOP_MODULATE

		_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_NONE
	End Method

	Method EndMax2D()
		_d3ddev.SetRenderState D3DRS_ZENABLE,True
		_d3ddev.SetRenderState D3DRS_ZWRITEENABLE,True

		_d3ddev.GetRenderState D3DRS_SCISSORTESTENABLE,_viewporton
		_d3ddev.SetRenderState D3DRS_SCISSORTESTENABLE,True

		_d3ddev.SetRenderState D3DRS_LIGHTING,True
		_d3ddev.SetRenderState D3DRS_NORMALIZENORMALS,True
		_d3ddev.SetRenderState D3DRS_AMBIENT,D3DCOLOR_RGB(WorldConfig.AmbientRed,WorldConfig.AmbientGreen,WorldConfig.AmbientBlue)
		_d3ddev.SetRenderState D3DRS_COLORVERTEX, True
		_d3ddev.SetRenderState D3DRS_DIFFUSEMATERIALSOURCE,D3DMCS_MATERIAL

		_d3ddev.SetRenderState D3DRS_SHADEMODE,D3DSHADE_GOURAUD

		'_d3ddev.SetRenderState D3DRS_ALPHAREF, 1
		'_d3ddev.SetRenderState D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL

		_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_CW
	End Method

	Method SetCamera(camera:TCamera)
		Local clearflags
		If camera._clsmode&CLSMODE_COLOR clearflags:|D3DCLEAR_TARGET
		If camera._clsmode&CLSMODE_DEPTH clearflags:|D3DCLEAR_ZBUFFER

		Local viewport[]=[camera._viewx,camera._viewy,camera._viewwidth-camera._viewx,camera._viewheight-camera._viewy]
		_d3ddev.SetScissorRect viewport
		_d3ddev.Clear(1,viewport,clearflags,D3DCOLOR_XRGB(camera._brush._r*255,camera._brush._g*255,camera._brush._b*255),1.0,0)

		Select camera._fogmode
		Case FOGMODE_LINEAR
			_d3ddev.SetRenderState D3DRS_FOGENABLE,True
			_d3ddev.SetRenderState D3DRS_FOGSTART,Int(Varptr camera._fognear)
			_d3ddev.SetRenderState D3DRS_FOGEND,Int(Varptr camera._fogfar)
		Default
			_d3ddev.SetRenderState D3DRS_FOGENABLE,False
		End Select

		Local ratio#=(Float(camera._viewwidth)/camera._viewheight)
		Local proj_matrix:TMatrix=TMatrix.PerspectiveFovLH(ATan((1.0/(camera._zoom*ratio)))*2.0,ratio#,camera._near,camera._far)
		proj_matrix=TMatrix.Scale(-1,1,1).Multiply(proj_matrix)
		_d3ddev.SetTransform D3DTS_PROJECTION,proj_matrix.ToPtr()

		Local matrix:TMatrix=camera._matrix.Inverse()
		_d3ddev.SetTransform D3DTS_VIEW,matrix.ToPtr()

		camera._lastmodelview=matrix
		camera._lastprojection=proj_matrix

		camera._lastviewport[0]=camera._viewx
		camera._lastviewport[1]=camera._viewy
		camera._lastviewport[2]=camera._viewwidth
		camera._lastviewport[3]=camera._viewheight

		camera._lastfrustum=TFrustum.Extract(camera._lastmodelview,camera._lastprojection)
	End Method

	Method SetLight(light:TLight,index)
		If light=Null
			_d3ddev.LightEnable index,False
			Return
		EndIf

		Local brush:TBrush=light._brush

		Global d3dlight:D3DLIGHT9=New D3DLIGHT9
		d3dlight.Type_=D3DLIGHT_DIRECTIONAL
		d3dlight.Diffuse_r=brush._r;d3dlight.Diffuse_g=brush._g;d3dlight.Diffuse_b=brush._b;d3dlight.Diffuse_a=brush._a
		'd3dlight.Ambient_r=WorldConfig.AmbientRed/255.0;d3dlight.Ambient_g=WorldConfig.AmbientGreen/255.0;d3dlight.Ambient_b=WorldConfig.AmbientBlue/255.0;d3dlight.Ambient_a=1.0

		d3dlight.Direction_x=0.0;d3dlight.Direction_y=0.0;d3dlight.Direction_z=1.0

		Local matrix:TMatrix=light._matrix,w#=1
		matrix.TransformVector d3dlight.Direction_x,d3dlight.Direction_y,d3dlight.Direction_z,w
		matrix.GetPosition d3dlight.Position_x,d3dlight.Position_y,d3dlight.Position_z

		d3dlight.Range=light._range

		_d3ddev.SetLight index,d3dlight
		_d3ddev.LightEnable index,True
	End Method

	Method SetBrush(brush:TBrush,hasalpha)
		_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE,False

		Local alpha_blending = (brush._fx&FX_FORCEALPHA Or hasalpha)>0
		_d3ddev.SetRenderState D3DRS_ALPHABLENDENABLE,alpha_blending
		_d3ddev.SetRenderState D3DRS_ZWRITEENABLE,Not alpha_blending

		If brush._fx&FX_FULLBRIGHT
			_d3ddev.SetRenderState D3DRS_AMBIENT,$ffffffff
		Else
			_d3ddev.SetRenderState D3DRS_AMBIENT,D3DCOLOR_RGB(WorldConfig.AmbientRed,WorldConfig.AmbientGreen,WorldConfig.AmbientBlue)
		EndIf

		If brush._fx&FX_NOCULLING
			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_NONE
		Else
			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_CW
		EndIf

		If brush._fx&FX_WIREFRAME Or WorldConfig.Wireframe
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_WIREFRAME
		Else
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_SOLID
		EndIf

		Local material:D3DMATERIAL9 = New D3DMATERIAL9
		material.Diffuse_r=brush._r;material.Diffuse_g=brush._g;material.Diffuse_b=brush._b;material.Diffuse_a=brush._a
		material.Ambient_r=brush._r;material.Ambient_g=brush._g;material.Ambient_b=brush._b;material.Ambient_a=brush._a

		_d3ddev.SetMaterial material

		For Local i=0 To 7
			Local texture:TTexture=brush._texture[i]
			If texture=Null Or texture._blend=BLEND_NONE
				_d3ddev.SetTexture i,Null
				Continue
			EndIf

			_d3ddev.SetTexture i,UpdateTextureRes(texture)._tex

			Local matrix:TMatrix=TMatrix.Identity()
			'matrix._m[0,0]=-texture._sx
			'matrix._m[1,1]=texture._sy
			matrix=TMatrix.YawPitchRoll(0,0,-texture._r).Multiply(matrix)
			matrix=TMatrix.Scale(-texture._sx,texture._sy,1).Multiply(matrix)
			matrix._m[2,0]=-texture._px
			matrix._m[2,1]=-texture._py

			_d3ddev.SetTransform D3DTS_TEXTURE0+i,matrix.ToPtr()

			_d3ddev.SetTextureStageState i,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2

			_d3ddev.SetSamplerState i,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR
			_d3ddev.SetSamplerState i,D3DSAMP_MINFILTER,D3DTEXF_LINEAR

			_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE, texture._flags&TEXTURE_ALPHA

			If texture._flags&TEXTURE_MIPMAP
				_d3ddev.SetSamplerState i,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_MIPFILTER,D3DTEXF_NONE
			EndIf

			If texture._flags&TEXTURE_CLAMPU
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_CLAMP
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_WRAP
			EndIf

			If texture._flags&TEXTURE_CLAMPV
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_CLAMP
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSV,D3DTADDRESS_WRAP
			EndIf

			If texture._flags&TEXTURE_SPHMAP
				_d3ddev.SetTextureStageState i,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_SPHEREMAP
			Else
				_d3ddev.SetTextureStageState i,D3DTSS_TEXCOORDINDEX,texture._coords
			EndIf

			'_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_SELECTARG1
			'_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_SELECTARG2

			Select texture._blend
			Case BLEND_ALPHA
				'glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL)
			Case BLEND_MULTIPLY
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE
			Case BLEND_ADD
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_ADD
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_ADD
			Case BLEND_DOT3
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_DOTPRODUCT3
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_DOTPRODUCT3
			Case BLEND_MULTIPLY2
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE2X
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE2X
			Default
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE
			End Select
		Next
	End Method

	Method RenderSurface(resource:TSurfaceRes,brush:TBrush)
		Local res:TD3D9SurfaceRes=TD3D9SurfaceRes(resource)

		_d3ddev.SetFVF 0
		_d3ddev.SetVertexDeclaration GetD3D9MaxB3DVertexDecl(_d3ddev)
		_d3ddev.SetStreamSource 0,res._pos,0,12
		_d3ddev.SetStreamSource 1,res._nml,0,12
		_d3ddev.SetStreamSource 2,res._clr,0,16
		_d3ddev.SetStreamSource 3,res._tex[0],0,8
		_d3ddev.SetIndices res._tri
		_d3ddev.DrawIndexedPrimitive D3DPT_TRIANGLELIST,0,0,res._vertexcnt,0,res._trianglecnt

		Return res._trianglecnt
	End Method

	Method RenderSprite(sprite:TSprite)
		Global _data#[]=[ -1.0, 1.0,0.0, 0.0,0.0,1.0, 0.0,0.0, ..
		                   1.0, 1.0,0.0, 0.0,0.0,1.0, 1.0,0.0, ..
		                  -1.0,-1.0,0.0, 0.0,0.0,1.0, 0.0,1.0, ..
		                   1.0,-1.0,0.0, 0.0,0.0,1.0, 1.0,1.0     ]

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
		_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLESTRIP,2,_data,8*4
	End Method

	Method BeginEntityRender(entity:TEntity)
		_d3ddev.SetTransform D3DTS_WORLD,entity.GetMatrix(True).ToPtr()
	End Method

	Method EndEntityRender(entity:TEntity)
	End Method

	Method RenderPlane(plane:TPlane)
		Local x#,y#,z#
		plane.GetScale x,y,z,True

		Global _data#[]=[ -1.0,0.0, 1.0, 0.0,1.0,0.0, 0.0,0.0, ..
		                   1.0,0.0, 1.0, 0.0,1.0,0.0,   x,0.0, ..
		                  -1.0,0.0,-1.0, 0.0,1.0,0.0, 0.0,  z, ..
		                   1.0,0.0,-1.0, 0.0,1.0,0.0,   x,  z     ]

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
		_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLESTRIP,2,_data,8*4
	End Method

	Method RenderTerrain(terrain:TTerrain)
	End Method

	Method UpdateTextureRes:TD3D9TextureRes(texture:TTexture)
		Local res:TD3D9TextureRes=TD3D9TextureRes(texture._res)
		If res And texture._updateres=False Return res

		If res=Null res=New TD3D9TextureRes
		texture._res=res

		Local pixmap:TPixmap=texture._pixmap
		Local tex_width=Pow2Size(pixmap.width),tex_height=Pow2Size(pixmap.height)
		pixmap=ConvertPixmap(ResizePixmap(pixmap,tex_width,tex_height),PF_BGRA8888)
		If res._tex=Null Assert _d3ddev.CreateTexture(tex_width,tex_height,(texture._flags & TEXTURE_MIPMAP)=0,D3DUSAGE_AUTOGENMIPMAP,D3DFMT_A8R8G8B8,D3DPOOL_MANAGED,res._tex,Null)=D3D_OK

		Local rect:D3DLOCKED_RECT=New D3DLOCKED_RECT
		res._tex.LockRect 0,rect,Null,0
		MemCopy rect.pBits,pixmap.pixels,pixmap.width*pixmap.height*4
		res._tex.UnlockRect 0

		texture._updateres=False

		Return res
	End Method

	Method UpdateSurfaceRes:TD3D9SurfaceRes(surface:TSurface)
		Local res:TD3D9SurfaceRes=TD3D9SurfaceRes(surface._res)
		If res=Null res=New TD3D9SurfaceRes;surface._reset=-1

		If surface._reset=0 Return res
		If surface._reset=-1 surface._reset=1|2|4|8|16|32|64|128|256

		If surface._reset&1 And surface._vertexpos UploadVertexData res._pos,surface._vertexpos
		If surface._reset&2 And surface._vertexnml UploadVertexData res._nml,surface._vertexnml
		If surface._reset&4 And surface._vertexclr UploadVertexData res._clr,surface._vertexclr
		If surface._reset&8 And surface._triangle
			If res._tri=Null _d3ddev.CreateIndexBuffer(surface._triangle.length*4,0,D3DFMT_INDEX32,D3DPOOL_MANAGED,res._tri,Null)
			Local dataptr:Byte Ptr
			Assert res._tri.Lock(0,0,dataptr,0)=D3D_OK,"Failed to lock index buffer."
			MemCopy dataptr,surface._triangle,surface._triangle.length*4
			res._tri.Unlock()
		EndIf

		For Local i=0 To surface._vertextex.length-1
			If surface._reset&Int(2^(4+i)) UploadVertexData res._tex[i],surface._vertextex[i]
		Next

		res._trianglecnt=surface._trianglecnt
		res._vertexcnt=surface._vertexcnt

		surface._reset=0
		surface._res=res

		Return res
	End Method

	Method MergeSurfaceRes:TSurfaceRes(base:TSurface,animation:TSurface,data)
		If animation=Null Return UpdateSurfaceRes(base)
		Local base_res:TD3D9SurfaceRes=UpdateSurfaceRes(base)
		Local anim_res:TD3D9SurfaceRes=UpdateSurfaceRes(animation)
		Local res:TD3D9SurfaceRes=base_res.Copy()
		res._pos=anim_res._pos
		Return res
	End Method

	Method UploadVertexData(buffer:IDirect3DVertexBuffer9 Var,data#[])
		If buffer=Null _d3ddev.CreateVertexBuffer(data.length*4,0,0,D3DPOOL_MANAGED,buffer,Null)
		Local dataptr:Byte Ptr
		Assert buffer.Lock(0,0,dataptr,0)=D3D_OK,"Failed to lock vertex buffer."
		MemCopy dataptr,data,data.length*4
		buffer.Unlock()
	End Method

End Type

Type TD3D9TextureRes Extends TTextureRes
	Field _tex:IDirect3DTexture9
End Type

Type TD3D9SurfaceRes Extends TSurfaceRes
	Field _pos:IDirect3DVertexBuffer9
	Field _nml:IDirect3DVertexBuffer9
	Field _clr:IDirect3DVertexBuffer9
	Field _tri:IDirect3DIndexBuffer9
	Field _tex:IDirect3DVertexBuffer9[8]

	Method Copy:TD3D9SurfaceRes()
		Local res:TD3D9SurfaceRes=New TD3D9SurfaceRes
		res._vertexcnt=_vertexcnt;res._trianglecnt=_trianglecnt
		res._pos=_pos;res._nml=_nml;res._clr=_clr;res._tri=_tri;
		For Local i=0 To 7
			res._tex[i]=_tex[i]
		Next
		Return res
	End Method
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function D3D9MaxB3DDriver:TD3D9MaxB3DDriver()
	If D3D9Max2DDriver()
		Global driver:TD3D9MaxB3DDriver=New TD3D9MaxB3DDriver
		driver._parent=D3D9Max2DDriver()
		Return driver
	End If
End Function

Rem
	bbdoc: Utility function that sets the MaxB3D D3D9 driver and calls Graphics.
End Rem
Function D3D9Graphics3D:TGraphics(width,height,depth=0,hertz=0,flags=0)
	SetGraphicsDriver D3D9MaxB3DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER
	Return Graphics(width,height,depth,hertz,flags)
End Function

Local driver:TD3D9MaxB3DDriver=D3D9MaxB3DDriver()
If driver SetGraphicsDriver driver,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER

?

Strict

Rem
	bbdoc: Direct3D 9 renderer for MaxB3D
End Rem
Module MaxB3D.D3D9Driver
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import BRL.D3D9Max2D

?Win32

Import "d3d9.bmx"

Private
Function Pow2Size( n )
	Local t=1
	While t<n
		t:*2
	Wend
	Return t
End Function

Public

Type TD3D9MaxB3DDriver Extends TMaxB3DDriver
	Field _d3ddev:IDirect3DDevice9
	Field _viewporton

	Method SetGraphics( g:TGraphics )
		Super.SetGraphics g
		_d3ddev=Null
		If g<>Null
			_d3ddev=TD3D9Graphics(TMax2DGraphics(g)._graphics).GetDirect3DDevice()
			EndMax2D
		EndIf
	End Method

	Method Flip(sync)
		Super.Flip(sync)
		If _d3ddev<>Null EndMax2D ' TODO: Figure out when this needs to be called. Most likely NOT always!
	End Method

	Method GetCaps:TCaps()
		Local caps:TCaps=New TCaps
		Return caps
	End Method

	Method BeginMax2D()
		Global identity:TMatrix=TMatrix.Identity()
		Local width=GraphicsWidth(),height=GraphicsHeight()
		Local matrix#[]=[..
		2.0/width,0.0,0.0,0.0,..
		 0.0,-2.0/height,0.0,0.0,..
		 0.0,0.0,1.0,0.0,..
		 -1-(1.0/width),1+(1.0/height),1.0,1.0]

		_d3ddev.SetTransform D3DTS_PROJECTION,matrix
		_d3ddev.SetTransform D3DTS_WORLD,identity.ToPtr()
		_d3ddev.SetTransform D3DTS_VIEW,identity.ToPtr()
		_d3ddev.SetTransform D3DTS_TEXTURE0,identity.ToPtr()

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetIndices Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1

		_d3ddev.SetRenderState D3DRS_LIGHTING,False
		_d3ddev.SetRenderState D3DRS_ZENABLE,False
		_d3ddev.SetRenderState D3DRS_SCISSORTESTENABLE,_viewporton

		_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE,True

		_d3ddev.SetRenderState D3DRS_WRAP0, 0

		_d3ddev.SetRenderState D3DRS_FOGENABLE,False

		For Local i=2 To 7
			_d3ddev.SetTexture i,Null
		Next

		_d3ddev.SetTextureStageState 0,D3DTSS_COLOROP,D3DTOP_MODULATE
		_d3ddev.SetTextureStageState 0,D3DTSS_ALPHAOP,D3DTOP_MODULATE

		_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_NONE
	End Method

	Method EndMax2D()
		_d3ddev.SetRenderState D3DRS_ZENABLE,True
		_d3ddev.SetRenderState D3DRS_ZWRITEENABLE,True

		_d3ddev.GetRenderState D3DRS_SCISSORTESTENABLE,_viewporton
		_d3ddev.SetRenderState D3DRS_SCISSORTESTENABLE,True

		_d3ddev.SetRenderState D3DRS_LIGHTING,True
		_d3ddev.SetRenderState D3DRS_NORMALIZENORMALS,True
		_d3ddev.SetRenderState D3DRS_AMBIENT,D3DCOLOR_RGB(WorldConfig.AmbientRed,WorldConfig.AmbientGreen,WorldConfig.AmbientBlue)
		_d3ddev.SetRenderState D3DRS_COLORVERTEX, True
		_d3ddev.SetRenderState D3DRS_DIFFUSEMATERIALSOURCE,D3DMCS_MATERIAL

		_d3ddev.SetRenderState D3DRS_SHADEMODE,D3DSHADE_GOURAUD

		'_d3ddev.SetRenderState D3DRS_ALPHAREF, 1
		'_d3ddev.SetRenderState D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL

		_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_CW
	End Method

	Method SetCamera(camera:TCamera)
		Local clearflags
		If camera._clsmode&CLSMODE_COLOR clearflags:|D3DCLEAR_TARGET
		If camera._clsmode&CLSMODE_DEPTH clearflags:|D3DCLEAR_ZBUFFER

		Local viewport[]=[camera._viewx,camera._viewy,camera._viewwidth-camera._viewx,camera._viewheight-camera._viewy]
		_d3ddev.SetScissorRect viewport
		_d3ddev.Clear(1,viewport,clearflags,D3DCOLOR_XRGB(camera._brush._r*255,camera._brush._g*255,camera._brush._b*255),1.0,0)

		Select camera._fogmode
		Case FOGMODE_LINEAR
			_d3ddev.SetRenderState D3DRS_FOGENABLE,True
			_d3ddev.SetRenderState D3DRS_FOGSTART,Int(Varptr camera._fognear)
			_d3ddev.SetRenderState D3DRS_FOGEND,Int(Varptr camera._fogfar)
		Default
			_d3ddev.SetRenderState D3DRS_FOGENABLE,False
		End Select

		Local ratio#=(Float(camera._viewwidth)/camera._viewheight)
		Local proj_matrix:TMatrix=TMatrix.PerspectiveFovRH(ATan((1.0/(camera._zoom*ratio)))*2.0,ratio#,camera._near,camera._far)
		proj_matrix=TMatrix.Scale(-1,1,1).Multiply(proj_matrix)
		_d3ddev.SetTransform D3DTS_PROJECTION,proj_matrix.ToPtr()

		Local matrix:TMatrix=camera._matrix.Inverse()
		_d3ddev.SetTransform D3DTS_VIEW,matrix.ToPtr()

		camera._lastmodelview=matrix
		camera._lastprojection=proj_matrix

		camera._lastviewport[0]=camera._viewx
		camera._lastviewport[1]=camera._viewy
		camera._lastviewport[2]=camera._viewwidth
		camera._lastviewport[3]=camera._viewheight

		camera._lastfrustum=TFrustum.Extract(camera._lastmodelview,camera._lastprojection)
	End Method

	Method SetLight(light:TLight,index)
		If light=Null
			_d3ddev.LightEnable index,False
			Return
		EndIf

		Local brush:TBrush=light._brush

		Global d3dlight:D3DLIGHT9=New D3DLIGHT9
		d3dlight.Type_=D3DLIGHT_DIRECTIONAL
		d3dlight.Diffuse_r=brush._r;d3dlight.Diffuse_g=brush._g;d3dlight.Diffuse_b=brush._b;d3dlight.Diffuse_a=brush._a
		'd3dlight.Ambient_r=WorldConfig.AmbientRed/255.0;d3dlight.Ambient_g=WorldConfig.AmbientGreen/255.0;d3dlight.Ambient_b=WorldConfig.AmbientBlue/255.0;d3dlight.Ambient_a=1.0

		d3dlight.Direction_x=0.0;d3dlight.Direction_y=0.0;d3dlight.Direction_z=1.0

		Local matrix:TMatrix=light._matrix,w#=1
		matrix.TransformVector d3dlight.Direction_x,d3dlight.Direction_y,d3dlight.Direction_z,w
		matrix.GetPosition d3dlight.Position_x,d3dlight.Position_y,d3dlight.Position_z

		d3dlight.Range=light._range

		_d3ddev.SetLight index,d3dlight
		_d3ddev.LightEnable index,True
	End Method

	Method SetBrush(brush:TBrush,hasalpha)
		_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE,False

		Local alpha_blending = (brush._fx&FX_FORCEALPHA Or hasalpha)>0
		_d3ddev.SetRenderState D3DRS_ALPHABLENDENABLE,alpha_blending
		_d3ddev.SetRenderState D3DRS_ZWRITEENABLE,Not alpha_blending

		If brush._fx&FX_FULLBRIGHT
			_d3ddev.SetRenderState D3DRS_AMBIENT,$ffffffff
		Else
			_d3ddev.SetRenderState D3DRS_AMBIENT,D3DCOLOR_RGB(WorldConfig.AmbientRed,WorldConfig.AmbientGreen,WorldConfig.AmbientBlue)
		EndIf

		If brush._fx&FX_NOCULLING
			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_NONE
		Else
			_d3ddev.SetRenderState D3DRS_CULLMODE,D3DCULL_CW
		EndIf

		If brush._fx&FX_WIREFRAME Or WorldConfig.Wireframe
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_WIREFRAME
		Else
			_d3ddev.SetRenderState D3DRS_FILLMODE,D3DFILL_SOLID
		EndIf

		Local material:D3DMATERIAL9 = New D3DMATERIAL9
		material.Diffuse_r=brush._r;material.Diffuse_g=brush._g;material.Diffuse_b=brush._b;material.Diffuse_a=brush._a
		material.Ambient_r=brush._r;material.Ambient_g=brush._g;material.Ambient_b=brush._b;material.Ambient_a=brush._a

		_d3ddev.SetMaterial material

		For Local i=0 To 7
			Local texture:TTexture=brush._texture[i]
			If texture=Null Or texture._blend=BLEND_NONE
				_d3ddev.SetTexture i,Null
				Continue
			EndIf

			_d3ddev.SetTexture i,UpdateTextureRes(texture)._tex

			Local matrix:TMatrix=TMatrix.Identity()
			'matrix._m[0,0]=-texture._sx
			'matrix._m[1,1]=texture._sy
			matrix=TMatrix.YawPitchRoll(0,0,-texture._r).Multiply(matrix)
			matrix=TMatrix.Scale(-texture._sx,texture._sy,1).Multiply(matrix)
			matrix._m[2,0]=-texture._px
			matrix._m[2,1]=-texture._py

			_d3ddev.SetTransform D3DTS_TEXTURE0+i,matrix.ToPtr()

			_d3ddev.SetTextureStageState i,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2

			_d3ddev.SetSamplerState i,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR
			_d3ddev.SetSamplerState i,D3DSAMP_MINFILTER,D3DTEXF_LINEAR

			_d3ddev.SetRenderState D3DRS_ALPHATESTENABLE, texture._flags&TEXTURE_ALPHA

			If texture._flags&TEXTURE_MIPMAP
				_d3ddev.SetSamplerState i,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_MIPFILTER,D3DTEXF_NONE
			EndIf

			If texture._flags&TEXTURE_CLAMPU
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_CLAMP
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_WRAP
			EndIf

			If texture._flags&TEXTURE_CLAMPV
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSU,D3DTADDRESS_CLAMP
			Else
				_d3ddev.SetSamplerState i,D3DSAMP_ADDRESSV,D3DTADDRESS_WRAP
			EndIf

			If texture._flags&TEXTURE_SPHMAP
				_d3ddev.SetTextureStageState i,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_SPHEREMAP
			Else
				_d3ddev.SetTextureStageState i,D3DTSS_TEXCOORDINDEX,texture._coords
			EndIf

			'_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_SELECTARG1
			'_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_SELECTARG2

			Select texture._blend
			Case BLEND_ALPHA
				'glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL)
			Case BLEND_MULTIPLY
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE
			Case BLEND_ADD
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_ADD
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_ADD
			Case BLEND_DOT3
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_DOTPRODUCT3
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_DOTPRODUCT3
			Case BLEND_MULTIPLY2
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE2X
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE2X
			Default
				_d3ddev.SetTextureStageState i,D3DTSS_COLOROP,D3DTOP_MODULATE
				_d3ddev.SetTextureStageState i,D3DTSS_ALPHAOP,D3DTOP_MODULATE
			End Select
		Next
	End Method

	Method RenderSurface(resource:TSurfaceRes,brush:TBrush)
		Local res:TD3D9SurfaceRes=TD3D9SurfaceRes(resource)

		_d3ddev.SetFVF 0
		_d3ddev.SetVertexDeclaration GetD3D9MaxB3DVertexDecl(_d3ddev)
		_d3ddev.SetStreamSource 0,res._pos,0,12
		_d3ddev.SetStreamSource 1,res._nml,0,12
		_d3ddev.SetStreamSource 2,res._clr,0,16
		_d3ddev.SetStreamSource 3,res._tex[0],0,8
		_d3ddev.SetIndices res._tri
		_d3ddev.DrawIndexedPrimitive D3DPT_TRIANGLELIST,0,0,res._vertexcnt,0,res._trianglecnt

		Return res._trianglecnt
	End Method

	Method RenderSprite(sprite:TSprite)
		Global _data#[]=[ -1.0, 1.0,0.0, 0.0,0.0,1.0, 0.0,0.0, ..
		                   1.0, 1.0,0.0, 0.0,0.0,1.0, 1.0,0.0, ..
		                  -1.0,-1.0,0.0, 0.0,0.0,1.0, 0.0,1.0, ..
		                   1.0,-1.0,0.0, 0.0,0.0,1.0, 1.0,1.0     ]

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
		_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLESTRIP,2,_data,8*4
	End Method

	Method BeginEntityRender(entity:TEntity)
		_d3ddev.SetTransform D3DTS_WORLD,entity.GetMatrix(True).ToPtr()
	End Method

	Method EndEntityRender(entity:TEntity)
	End Method

	Method RenderPlane(plane:TPlane)
		Local x#,y#,z#
		plane.GetScale x,y,z,True

		Global _data#[]=[ -1.0,0.0, 1.0, 0.0,1.0,0.0, 0.0,0.0, ..
		                   1.0,0.0, 1.0, 0.0,1.0,0.0,   x,0.0, ..
		                  -1.0,0.0,-1.0, 0.0,1.0,0.0, 0.0,  z, ..
		                   1.0,0.0,-1.0, 0.0,1.0,0.0,   x,  z     ]

		_d3ddev.SetVertexDeclaration Null
		_d3ddev.SetFVF D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
		_d3ddev.DrawPrimitiveUP D3DPT_TRIANGLESTRIP,2,_data,8*4
	End Method

	Method RenderTerrain(terrain:TTerrain)
	End Method

	Method UpdateTextureRes:TD3D9TextureRes(texture:TTexture)
		Local res:TD3D9TextureRes=TD3D9TextureRes(texture._res)
		If res And texture._updateres=False Return res

		If res=Null res=New TD3D9TextureRes
		texture._res=res

		Local pixmap:TPixmap=texture._pixmap
		Local tex_width=Pow2Size(pixmap.width),tex_height=Pow2Size(pixmap.height)
		pixmap=ConvertPixmap(ResizePixmap(pixmap,tex_width,tex_height),PF_BGRA8888)
		If res._tex=Null Assert _d3ddev.CreateTexture(tex_width,tex_height,(texture._flags & TEXTURE_MIPMAP)=0,D3DUSAGE_AUTOGENMIPMAP,D3DFMT_A8R8G8B8,D3DPOOL_MANAGED,res._tex,Null)=D3D_OK

		Local rect:D3DLOCKED_RECT=New D3DLOCKED_RECT
		res._tex.LockRect 0,rect,Null,0
		MemCopy rect.pBits,pixmap.pixels,pixmap.width*pixmap.height*4
		res._tex.UnlockRect 0

		texture._updateres=False

		Return res
	End Method

	Method UpdateSurfaceRes:TD3D9SurfaceRes(surface:TSurface)
		Local res:TD3D9SurfaceRes=TD3D9SurfaceRes(surface._res)
		If res=Null res=New TD3D9SurfaceRes;surface._reset=-1

		If surface._reset=0 Return res
		If surface._reset=-1 surface._reset=1|2|4|8|16|32|64|128|256

		If surface._reset&1 And surface._vertexpos UploadVertexData res._pos,surface._vertexpos
		If surface._reset&2 And surface._vertexnml UploadVertexData res._nml,surface._vertexnml
		If surface._reset&4 And surface._vertexclr UploadVertexData res._clr,surface._vertexclr
		If surface._reset&8 And surface._triangle
			If res._tri=Null _d3ddev.CreateIndexBuffer(surface._triangle.length*4,0,D3DFMT_INDEX32,D3DPOOL_MANAGED,res._tri,Null)
			Local dataptr:Byte Ptr
			Assert res._tri.Lock(0,0,dataptr,0)=D3D_OK,"Failed to lock index buffer."
			MemCopy dataptr,surface._triangle,surface._triangle.length*4
			res._tri.Unlock()
		EndIf

		For Local i=0 To surface._vertextex.length-1
			If surface._reset&Int(2^(4+i)) UploadVertexData res._tex[i],surface._vertextex[i]
		Next

		res._trianglecnt=surface._trianglecnt
		res._vertexcnt=surface._vertexcnt

		surface._reset=0
		surface._res=res

		Return res
	End Method

	Method MergeSurfaceRes:TSurfaceRes(base:TSurface,animation:TSurface,data)
		If animation=Null Return UpdateSurfaceRes(base)
		Local base_res:TD3D9SurfaceRes=UpdateSurfaceRes(base)
		Local anim_res:TD3D9SurfaceRes=UpdateSurfaceRes(animation)
		Local res:TD3D9SurfaceRes=base_res.Copy()
		res._pos=anim_res._pos
		Return res
	End Method

	Method UploadVertexData(buffer:IDirect3DVertexBuffer9 Var,data#[])
		If buffer=Null _d3ddev.CreateVertexBuffer(data.length*4,0,0,D3DPOOL_MANAGED,buffer,Null)
		Local dataptr:Byte Ptr
		Assert buffer.Lock(0,0,dataptr,0)=D3D_OK,"Failed to lock vertex buffer."
		MemCopy dataptr,data,data.length*4
		buffer.Unlock()
	End Method

End Type

Type TD3D9TextureRes Extends TTextureRes
	Field _tex:IDirect3DTexture9
End Type

Type TD3D9SurfaceRes Extends TSurfaceRes
	Field _pos:IDirect3DVertexBuffer9
	Field _nml:IDirect3DVertexBuffer9
	Field _clr:IDirect3DVertexBuffer9
	Field _tri:IDirect3DIndexBuffer9
	Field _tex:IDirect3DVertexBuffer9[8]

	Method Copy:TD3D9SurfaceRes()
		Local res:TD3D9SurfaceRes=New TD3D9SurfaceRes
		res._vertexcnt=_vertexcnt;res._trianglecnt=_trianglecnt
		res._pos=_pos;res._nml=_nml;res._clr=_clr;res._tri=_tri;
		For Local i=0 To 7
			res._tex[i]=_tex[i]
		Next
		Return res
	End Method
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function D3D9MaxB3DDriver:TD3D9MaxB3DDriver()
	If D3D9Max2DDriver()
		Global driver:TD3D9MaxB3DDriver=New TD3D9MaxB3DDriver
		driver._parent=D3D9Max2DDriver()
		Return driver
	End If
End Function

Rem
	bbdoc: Utility function that sets the MaxB3D D3D9 driver and calls Graphics.
End Rem
Function D3D9Graphics3D:TGraphics(width,height,depth=0,hertz=0,flags=0)
	SetGraphicsDriver D3D9MaxB3DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER
	Return Graphics(width,height,depth,hertz,flags)
End Function

Local driver:TD3D9MaxB3DDriver=D3D9MaxB3DDriver()
If driver SetGraphicsDriver driver,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER

?


can't optimise that out safely yet

can't optimise that out safely yet

' Blue Moon
' FOLD delegates to normalize and check syntax, fold constant expressions

' this file is a Private Include

'SuperStrict

'Import "blueerror.bmx"
'Import "TFold.bmx"
'Import "blueparser.bmx"

Type NormalizeFold
	Field tbl:TMap
	Function Make:NormalizeFold(tbl:NodeTable)
		Local f:NormalizeFold = New NormalizeFold ; f.tbl = tbl.leaves ; Return f
	End Function

	Method LeftAssociative:Node(n:Rose) { SumExpr MulExpr AndExpr RelExpr Expr }	'restructure left-associative operations from a list to a tree
		Local l:Node = n.Get("L"), r:Rose = Rose(n.Get("R"))
		For Local c:Int = 0 Until r.arg.Length
			Local rarg:Rose = Rose(r.arg[c])
			l = Rose.Make(rarg.arg[0].key, [l, rarg.arg[1]], ["L", "R"])
		Next
		Return l
	End Method
	Method RightAssociative:Node(n:Rose) { CatExpr PowExpr }	'properly structure right-associative operations
		Local l:Node = n.Get("L"), r:Rose = Rose(n.Get("R"))
		Return Rose.Make(r.arg[0].key, [l, r.arg[1]], ["L", "R"])
	End Method
	Method UnaryExpr:Node(n:Rose)	'remove duplicate nots and negations, completely remove unary posate
		Local ops:Rose = Rose(n.Get("op")), ret:Node = n.Get("arg")
		Local op:String[ops.arg.Length], prevK:String = "", count:Int
		For Local c:Int = 0 Until ops.arg.Length
			Local key:String = ops.arg[c].key ; If key = "minus" Then key = "negate"
			If prevK = key And (key = "negate" Or key = "knot")
				count :- 1 ; If count Then prevK = op[count - 1] Else prevK = ""
			ElseIf key <> "plus"
				op[count] = key ; count :+ 1 ; prevK = key
			EndIf
		Next
		For Local c:Int = count - 1 To 0 Step -1
			ret = Rose.Make(op[c], [ret], Null)
		Next
		Return ret
	End Method
	Method TableCons:Node(n:Rose)	'remove redundant levels from table constructor expressions
		n = Rose(n.Get("fields"))
		If n
			Local rst:Int = 0 ; If n.arg.Length > 1 Then rst = Rose(n.arg[1]).arg.Length
			Local arg:Node[] = New Node[1 + rst]
			For Local c:Int = 1 Until arg.Length
				arg[c] = Rose(Rose(n.arg[1]).arg[c - 1]).arg[1]
			Next
			arg[0] = n.arg[0]
			Local icount:Int = 1
			For Local c:Int = 0 Until arg.Length
				Local ac:Rose = Rose(arg[c])

				If arg[c].key = "FieldKey"	'these are actually useful unconverted (optimization)
					arg[c] = Rose.Make("FieldKey", [Node(Leaf.Make("fname", Leaf(ac.arg[0]).val)), ac.arg[1]], ac.id)

				ElseIf c = arg.Length - 1 And arg[c].key = "dotdotdot"	'vararg initialization
					arg[c] = Leaf.Make("va_init", "...")

				ElseIf c = arg.Length - 1 And (arg[c].key = "fcall" Or arg[c].key = "mcall")	'var-ret initialization
					arg[c] = Rose.Make("va_" + ac.key, ac.arg, Null)

				ElseIf arg[c].key <> "FieldIndex"	'convert basic expressions to indexed expressions
					Local idx:Node = Leaf.Make("dconst", icount) ; icount :+ 1
					arg[c] = Rose.Make("FieldIndex", [idx, arg[c]], ["key", "val"])
				EndIf
			Next
			arg :+ [Leaf.Make("icount", icount)]
			Return Rose.Make("TableCons", arg)
		Else
			Return Rose.Make("TableCons", Null)
		EndIf
	End Method

	Method LocalVar:Node(n:Rose) { Assgt }	'balance sides of assignment or initialization
		Local vals:Rose = Rose(n.Get("vals")), vars:Rose = Rose(n.arg[0])
		If vals = Null Then vals = Rose.Make("ExpList", [Leaf.Make("nil", "nil")])

		If vals.arg.Length < vars.arg.Length
			Local lastkey:String = vals.arg[vals.arg.Length - 1].key, extend:Int = Max(1, vars.arg.Length - (vals.arg.Length - 1))

			If lastkey <> "fcall" And lastkey <> "mcall" And lastkey <> "dotdotdot"	'polyvalue assignment doesn't extend
				Local nv:Node[] = vals.arg[..vars.arg.Length], nil:Node = Leaf.Make("nil", "nil")
				For Local v:Int = vals.arg.Length Until nv.Length
					nv[v] = nil
				Next
				vals = Rose.Make("ExpList", nv)
			ElseIf lastkey = "dotdotdot"	'mark required vararg values
				Local valarg:Node[] = vals.arg[..]
				valarg[valarg.Length - 1] = Leaf.Make("dotdotdot", extend)
				vals = Rose.Make(vals.key, valarg)
			Else	'adjust return count
				Local call:Rose = Rose(vals.arg[vals.arg.Length - 1]), carg:Node[] = call.arg[..]
				carg[2] = Leaf.Make("rcount", extend)
				call = Rose.Make(call.key, carg)
				Local valarg:Node[] = vals.arg[..] ; valarg[valarg.Length - 1] = call
				vals = Rose.Make(vals.key, valarg)
			EndIf

		ElseIf vars.arg.Length < vals.arg.Length
			Local l:Int = vars.arg.Length ; vars = Rose.Make(vars.key, vars.arg[..vals.arg.Length])
			For Local v:Int = l Until vals.arg.Length		'too many vals: discard into a fake var
				vars.arg[v] = Leaf.Make("discard", "discard")
			Next
		EndIf

		Return Rose.Make(n.key, [vars, vals], ["vars", "vals"])
	End Method

	Method Block:Node(n:Rose)	'remove empty statements
		Local old:Rose = Rose(n.arg[0])
		If old
			Local st:Node[] = New Node[old.arg.Length], count:Int = 0
			For Local s:Int = 0 Until old.arg.Length
				Local k:String = old.arg[s].key ; If k <> "semicolon" And k <> "#Nil"
					st[count] = old.arg[s] ; count :+ 1
				EndIf
			Next
			If count
				Return Rose.Make("Block", st[..count])
			Else
				Return Rose.Make("Block", Null)
			EndIf
		ElseIf NilNode(n.arg[0])
			Return Rose.Make("Block", Null)
		EndIf
		Return n
	End Method
	Method DoBlk:Node(n:Rose)	'extract block; reduce indirection
		Return n.arg[0]
	End Method
	Method IfS:Node(n:Rose)	'rewrite if/elseif chains into nested regular if/then/else
		Local elif:Rose = Rose(n.Get("elif")), _else:Node = n.Get("else")
		If elif
			For Local e:Int = elif.arg.Length - 1 To 0 Step -1
				If _else
					_else = Rose.Make("IfS", Rose(elif.arg[e]).arg + [_else], ["test", "then", "else"])
				Else
					_else = Rose.Make("IfS", Rose(elif.arg[e]).arg, ["test", "then"])
				EndIf
			Next
			Return Rose.Make("IfS", n.arg[..2] + [_else], ["test", "then", "else"])
	'	Else
	'		Return Rose.Make("IfS", n.arg + [Rose.Make("Block", Null)], ["test", "then", "else"])
		EndIf
		Return n
	End Method
	Method ForN:Node(n:Rose)	'convert for loops to equivalent scoped-whiles
		Global rewrite:String = ..
		"~n	local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)" +..	'building this with Make calls would be insane, and increase error surface
		"~n	if not (var and limit and step) then error() end" +..
		"~n	var = var - step" +..
		"~n	while true do" +..
		"~n		var = var + step" +..
		"~n		if (step >= 0 and var > limit) or (step < 0 and var < limit) then" +..
		"~n			break" +..
	 	"~n		end" +..
	 	"~n		local _var = var" +..
		"~n		--block" +..
	 	"~n	end~n"

		Global rwast:TParseNode ; If rwast = Null Then rwast = BlueParseString(rewrite)
		Local rwtbl:NodeTable = Node.FromParse(rwast), rw:Node = rwtbl.n
		rw = Node.Fold(rw, NormalizeFold.Make(rwtbl))

		Local v:Node = n.arg[0], f:Node = n.arg[1], t:Node = n.arg[2], s:Node = n.Get("step"), b:Node = n.Get("block")
		If s = Null Then s = Leaf.Make("dconst", "1")	'ensure step is present
		rw = Node.Fold(rw, FindReplaceFold.Make( ..
			["var", "limit", "step", "e1", "e2", "e3", "_var"], ..	'insert actual names and values into template
			[Node(Leaf.Make("name", "::var")), Node(Leaf.Make("name", "::limit")), Node(Leaf.Make("name", "::step")), f, t, s, v]))

		Rose(Rose(Rose(rw).arg[3]).arg[1]).arg :+ [b]
		Return rw
	End Method
	Method ForIn:Node(n:Rose)
		Global rewrite:String = ..
		"~n	local f, s, var = explist" +..	'doing it this way reduces error surface by reusing more of the same code machinery
		"~n	while true do" +..
		"~n		local var_1, var_2, var_n = f(s, var)" +..
		"~n		if var_1 == nil then break end" +..
		"~n		var = var_1" +..
		"~n		--block" +..
		"~n	end~n"	'the outer `do` is removed here because it's added implicitly by the parser anyway

		Global rwast:TParseNode ; If rwast = Null Then rwast = BlueParseString(rewrite)
		Local rwtbl:NodeTable = Node.FromParse(rwast), rw:Node = rwtbl.n
		rw = Node.Fold(rw, NormalizeFold.Make(rwtbl))

		Local va:Node = n.arg[0], v1:Node = Rose(va).arg[0], b:Node = n.arg[2]
		rw = Node.Fold(rw, FindReplaceFold.Make( ..
			["f", "s", "var", "var_1"], ..	'insert actual names and values into template
			[Node(Leaf.Make("name", "::f")), Node(Leaf.Make("name", "::s")), Node(Leaf.Make("name", "::var")), v1]))

		Rose(Rose(rw).arg[0]).arg[1] = n.arg[1]	'replace the expression list at declaration of ::f/::s/::var
		Local wstmts:Node[] = Rose(Rose(Rose(rw).arg[1]).arg[1]).arg
		wstmts :+ [b]	'add the loop body to the list of statements in the new while loop
		Rose(wstmts[0]).arg[0] = n.arg[0]	'loop variables

		Return rw
	End Method

	Method FunDecl:Node(n:Rose)	'replace function declarations with simple assignment
		Local name:Node = n.Get("name"), body:Rose = Rose(n.Get("body"))
		If name.key = "MethodName"
			Local params:Rose = Rose(body.arg[0])
			params = Rose.Make(params.key, [Leaf.Make("name", "self")] + params.arg)
			body = Rose.Make(body.key, [Node(params), body.arg[1]], body.id)
			name = Rose.Make("getfield", Rose(name).arg)
		EndIf
		Return Rose.Make("Assgt", [Rose.Make("VarList", [name]), Rose.Make("ExpList", [body])], ["vars", "vals"])
	End Method
	Method FuncName:Node(n:Rose)	'convert function name expressions to var+fields (but leave trailing method name to FunDecl)
		Local r:Node = n.Get("n"), path:Rose = Rose(n.Get("path")), meth:Rose = Rose(n.Get("method"))
		If path
			For Local pe:Rose = EachIn path.arg
				Local acc:Node = Leaf.Make("fname", Leaf(pe.arg[1]).val)
				r = Rose.Make("getfield", [r, acc])
			Next
		EndIf
		If meth
			Local mname:Node = Leaf.Make("fname", Leaf(meth.arg[1]).val)
			r = Rose.Make("MethodName", [r, mname])	'package for FunDecl to rebrand as a get
		EndIf
		Return r
	End Method
	Method LocalFun:Node(n:Rose)	'replace local function declarations with local assignment
		Local nam:Node = n.Get("name"), dec:Node = Rose.Make("LocalVar", [..
			Rose.Make("NameList", [nam]), Rose.Make("ExpList", [Leaf.Make("nil", "nil")])], ["names", "vals"])	'nil-init'd
		Local asg:Node = Rose.Make("Assgt", [Rose.Make("VarList", [nam]), Rose.Make("ExpList", [n.Get("body")])], ["vars", "vals"])
		Return Rose.Make("#EXPAND", [dec, asg])
	End Method
	Method FuncBody:Node(n:Rose)	'reduce indirection on parameter list and straighten out varargs
		Local param:Node = Rose(n.arg[0]).arg[0], plist:Rose = Rose(param)
		If NilNode(param)
			param = Rose.Make("NameList", Null)
		ElseIf param.key = "dotdotdot"
			param = Rose.Make("NameList", [param])
		ElseIf plist And plist.arg.Length = 2 And plist.arg[1].key = "dotdotdot"
			param = Rose.Make("NameList", Rose(plist.arg[0]).arg + [plist.arg[1]])
		EndIf
		Return Rose.Make(n.key, [param, n.arg[1]], n.id)
	End Method

	Method GotoS:Node(n:Rose) { Label }
		Local l:Leaf = Leaf(n.arg[0]), t:Object = tbl.ValueForKey(l) ; tbl.Remove(l)	'modifying an error-able leaf means updating its error entry
		l = Leaf.Make(n.key, l.val) ; tbl.Insert(l, t)
		Return l
	End Method
	Method ReturnS:Node(n:Rose)
		Local vals:Node[] = Null
		If Rose(n.arg[0]) Then vals = Rose(n.arg[0]).arg
		Return Rose.Make(n.key, vals)
	End Method

	Method PostfixExp:Node(n:Rose)	'rewrite element access/function call chains into nested single ops
		Local f:Node = n.arg[0], pl:Node[] = Rose(n.arg[1]).arg
		For Local e:Int = 0 Until pl.Length
			Select pl[e].key
				Case "sconst", "TableCons"
					f = Rose.Make("fcall", [f, Node(Rose.Make("FArgs", [pl[e]])), Node(Leaf.Make("rcount", "1"))])
				Case "FArgs"
					f = Rose.Make("fcall", [f, pl[e], Node(Leaf.Make("rcount", "1"))])	'add number of returned values
				Case "MArgs"
					Leaf(Rose(pl[e]).arg[0]).key = "fname"
					f = Rose.Make("mcall", [f, pl[e], Node(Leaf.Make("rcount", "1"))])
				Case "ElemGet"
					f = Rose.Make("index", [f, Rose(pl[e]).arg[0]])
				Case "FieldGet"
					Local acc:Node = Rose(pl[e]).arg[0] ;  acc = Leaf.Make("fname", Leaf(acc).val)
					f = Rose.Make("getfield", [f, acc])
			End Select
		Next
		Return f
	End Method
	Method FArgs:Node(n:Rose)	'clear empty arg lists and reduce indirection
		Local al:Rose = Rose(n.arg[0])
		If al Then Return Rose.Make("FArgs", al.arg) Else Return Rose.Make("FArgs", Null)
	End Method

	Method dotdotdot:Node(n:Leaf)
		Return Leaf.Make("dotdotdot", "1")
	End Method
End Type

Type FindReplaceFold
	Field names:String[], vals:Node[]

	Function Make:FindReplaceFold(names:String[], vals:Node[])
		Local f:FindReplaceFold = New Self
		f.names = names ; f.vals = vals ; Return f
	End Function

	Method name:Node(n:Leaf)	'replace occurrences of names in the name array with the matching vals value
		For Local i:Int = 0 Until names.Length
			If names[i] = n.val Then Return vals[i]
		Next
		Return n
	End Method
End Type

Type ConstantFold	'fold constant math and logic operations
	Method hconst:Node(n:Leaf) { hfconst }	'convert hex constants to uniform representation (unnecessary? BlitzMax natively supports 0x1a23p-4 format)
		Return Leaf.Make("dconst", String(Double(n.val)))
	End Method

	Method BinMathOp:Node(n:Rose) { plus minus mul div kmod pow }
		Const DK:String = "dconst"
		If n.arg[0].key = DK And n.arg[1].key = DK
			Local l:Double = Double(Leaf(n.arg[0]).val), r:Double = Double(Leaf(n.arg[1]).val)
			Select n.key
				Case "plus"  ; l :+ r
				Case "minus" ; l :- r
				Case "mul"   ; l :* r
				Case "div"   ; l :/ r
				Case "kmod"  ; l = l Mod r
				Case "pow"   ; l = l ^ r
			End Select
			Return Leaf.Make(DK, l)
		Else
			Return n
		EndIf
	End Method
	Method UnaryOp:Node(n:Rose) { negate klen knot }
		Local a:Leaf = Leaf(n.arg[0]) ; If a = Null Then Return n
		Select n.key
			Case "negate"
				If a.key = "dconst" Or a.key = "sconst"		'BlitzMax natively supports Lua number formats, it's just the C one
					Return Leaf.Make("dconst", String(-Double(a.val)))
				EndIf
			Case "not"
				Select a.key
					Case "nil", "bconst"
						If a.val = "true" Then Return Leaf.Make("bconst", "false") Else Return Leaf.Make("bconst", "true")
					Case "dconst", "sconst"
						Return Leaf.Make("bconst", "false")
				End Select
		End Select
		Return n
	End Method
	Method AndExpr:Node(n:Rose) { Expr }	'optimize short-circuit operators with constant first operand
		Local l:Leaf = Leaf(n.arg[0])
		If l
			If l.key = "sconst" Or l.key = "dconst" Or (l.key = "bconst" And l.val = "true")
				If n.key = "kor" Then Return l Else Return n.arg[1]
			ElseIf l.key = "nil" Or (l.key = "bconst" And l.val = "false")
				If n.key = "kor" Then Return n.arg[1] Else Return l
			EndIf
		EndIf
		Return n
	End Method
	Method eq:Node(n:Rose) { neq }
		Local l:Leaf = Leaf(n.arg[0]), r:Leaf = Leaf(n.arg[1])
		If l <> Null And r <> Null And l.key = r.key
			Global rv:String[] = ["false", "true"]
			Select l.key
				Case "bconst", "sconst" ; Return Leaf.Make("bconst", rv[(n.key = "eq") * (l.val = r.val)])
				Case "dconst" ; Return Leaf.Make("bconst", rv[(n.key = "eq") * (Double(l.val) = Double(r.val))])	'it's fine when they're constants
			End Select
		EndIf
		Return n
	End Method
	Method lt:Node(n:Rose) { gt leq geq }
		Local l:Leaf = Leaf(n.arg[0]), r:Leaf = Leaf(n.arg[1])
		If l <> Null And r <> Null And l.key = "dconst" And r.key = "dconst"	'string comparisons are affected by runtime locale
			Global rv:String[] = ["false", "true"] ; Local ret:Int
			Select n.key
				Case "gt" ; ret = Double(l.val) > Double(r.val)
				Case "lt" ; ret = Double(l.val) < Double(r.val)
				Case "geq" ; ret = Double(l.val) >= Double(r.val)
				Case "neq" ; ret = Double(l.val) <= Double(r.val)
			End Select
			Return Leaf.Make("bconst", rv[ret])
		EndIf
		Return n
	End Method

	Method IfS:Node(n:Rose)	'unpack constant if structures
		Local test:Leaf = Leaf(n.arg[0])
		If test
			Select test.key
				Case "bconst", "dconst", "sconst", "nil"
					If (test.key = "bconst" And test.val = "False") Or test.key = "nil"
						If n.Get("else") Then Return n.Get("else") Else Return Null
					Else
						Return n.Get("then")
					EndIf
			End Select
		EndIf
		Return n
	End Method
	Method WhileS:Node(n:Rose)	'remove while(false)
		Local test:Leaf = Leaf(n.arg[0])
		If test And ((test.key = "bconst" And test.val = "false") Or (test.key = "nil")) Then Return Null
		Return n
	End Method
	Method RepeatS:Node(n:Rose)	'replace repeat...until(false) with a while
		Local t:Leaf = Leaf(n.Get("test"))
		If t
			If t.key = "nil" Or (t.key = "bconst" And t.val = "false")
				Return Rose.Make("WhileS", [Node(Leaf.Make("bconst", "true")), n.Get("block")], ["test", "block"])
		'	ElseIf t.key = "dconst" Or t.key = "sconst" Or (t.key = "bconst" And t.val = "true")
		'		Return n.Get("block")
			EndIf
		EndIf
		Return n
	End Method
End Type

Type SyntaxErrFold	'report any remaining syntax errors
	Field tbl:TMap, breaks:TList
	Function Make:SyntaxErrFold(tbl:NodeTable)
		Local f:SyntaxErrFold = New SyntaxErrFold
		f.tbl = tbl.leaves ; f.breaks = CreateList()
		Return f
	End Function

	Method Block:Node(n:Rose)	'scan for non-statements
		For Local s:Int = 0 Until n.arg.Length
			Select n.arg[s].key
				Case "fcall", "mcall"
					n.arg[s].key = "p" + n.arg[s].key
				Case "Assgt", "break", "GotoS", "Label", "Block", "WhileS", "RepeatS", "IfS", "LocalVar", "ReturnS"', "ForN", "ForIn"
				Default
					Throw BlueCompileError.Make(TToken(tbl.ValueForKey(n.arg[s])), "invalid statement in statement block")
			End Select
		Next
		Return n
	End Method

	Method break:Node(n:Leaf)	'add breaks to a stack...
		breaks.AddFirst(n) ; Return n
	End Method
	Method AllLoops:Node(n:Rose) { WhileS RepeatS } 'ForN ForIn }	'...then remove them
		breaks.Clear() ; Return n
	End Method
	Method FuncBody:Node(n:Rose)	'...and error on any that weren't removed
		Self.BreakCheck() ; Return n
	End Method

	Method BreakCheck()	'need to call this externally for toplevel too
		If breaks.Count() Then Throw BlueCompileError.Make(TToken(tbl.ValueForKey(breaks.First())), "'break' statement without containing loop")
	End Method
End Type

' Blue Moon
' FOLD delegates to normalize and check syntax, fold constant expressions

' this file is a Private Include

'SuperStrict

'Import "blueerror.bmx"
'Import "TFold.bmx"
'Import "blueparser.bmx"

Type NormalizeFold
	Field tbl:TMap
	Function Make:NormalizeFold(tbl:NodeTable)
		Local f:NormalizeFold = New NormalizeFold ; f.tbl = tbl.leaves ; Return f
	End Function

	Method LeftAssociative:Node(n:Rose) { SumExpr MulExpr AndExpr RelExpr Expr }	'restructure left-associative operations from a list to a tree
		Local l:Node = n.Get("L"), r:Rose = Rose(n.Get("R"))
		For Local c:Int = 0 Until r.arg.Length
			Local rarg:Rose = Rose(r.arg[c])
			l = Rose.Make(rarg.arg[0].key, [l, rarg.arg[1]], ["L", "R"])
		Next
		Return l
	End Method
	Method RightAssociative:Node(n:Rose) { CatExpr PowExpr }	'properly structure right-associative operations
		Local l:Node = n.Get("L"), r:Rose = Rose(n.Get("R"))
		Return Rose.Make(r.arg[0].key, [l, r.arg[1]], ["L", "R"])
	End Method
	Method UnaryExpr:Node(n:Rose)	'remove duplicate nots and negations, completely remove unary posate
		Local ops:Rose = Rose(n.Get("op")), ret:Node = n.Get("arg")
		Local op:String[ops.arg.Length], prevK:String = "", count:Int
		For Local c:Int = 0 Until ops.arg.Length
			Local key:String = ops.arg[c].key ; If key = "minus" Then key = "negate"
			If prevK = key And (key = "negate" Or key = "knot")
				count :- 1 ; If count Then prevK = op[count - 1] Else prevK = ""
			ElseIf key <> "plus"
				op[count] = key ; count :+ 1 ; prevK = key
			EndIf
		Next
		For Local c:Int = count - 1 To 0 Step -1
			ret = Rose.Make(op[c], [ret], Null)
		Next
		Return ret
	End Method
	Method TableCons:Node(n:Rose)	'remove redundant levels from table constructor expressions
		n = Rose(n.Get("fields"))
		If n
			Local rst:Int = 0 ; If n.arg.Length > 1 Then rst = Rose(n.arg[1]).arg.Length
			Local arg:Node[] = New Node[1 + rst]
			For Local c:Int = 1 Until arg.Length
				arg[c] = Rose(Rose(n.arg[1]).arg[c - 1]).arg[1]
			Next
			arg[0] = n.arg[0]
			Local icount:Int = 1
			For Local c:Int = 0 Until arg.Length
				Local ac:Rose = Rose(arg[c])

				If arg[c].key = "FieldKey"	'these are actually useful unconverted (optimization)
					arg[c] = Rose.Make("FieldKey", [Node(Leaf.Make("fname", Leaf(ac.arg[0]).val)), ac.arg[1]], ac.id)

				ElseIf c = arg.Length - 1 And arg[c].key = "dotdotdot"	'vararg initialization
					arg[c] = Leaf.Make("va_init", "...")

				ElseIf c = arg.Length - 1 And (arg[c].key = "fcall" Or arg[c].key = "mcall")	'var-ret initialization
					arg[c] = Rose.Make("va_" + ac.key, ac.arg, Null)

				ElseIf arg[c].key <> "FieldIndex"	'convert basic expressions to indexed expressions
					Local idx:Node = Leaf.Make("dconst", icount) ; icount :+ 1
					arg[c] = Rose.Make("FieldIndex", [idx, arg[c]], ["key", "val"])
				EndIf
			Next
			arg :+ [Leaf.Make("icount", icount)]
			Return Rose.Make("TableCons", arg)
		Else
			Return Rose.Make("TableCons", Null)
		EndIf
	End Method

	Method LocalVar:Node(n:Rose) { Assgt }	'balance sides of assignment or initialization
		Local vals:Rose = Rose(n.Get("vals")), vars:Rose = Rose(n.arg[0])
		If vals = Null Then vals = Rose.Make("ExpList", [Leaf.Make("nil", "nil")])

		If vals.arg.Length < vars.arg.Length
			Local lastkey:String = vals.arg[vals.arg.Length - 1].key, extend:Int = Max(1, vars.arg.Length - (vals.arg.Length - 1))

			If lastkey <> "fcall" And lastkey <> "mcall" And lastkey <> "dotdotdot"	'polyvalue assignment doesn't extend
				Local nv:Node[] = vals.arg[..vars.arg.Length], nil:Node = Leaf.Make("nil", "nil")
				For Local v:Int = vals.arg.Length Until nv.Length
					nv[v] = nil
				Next
				vals = Rose.Make("ExpList", nv)
			ElseIf lastkey = "dotdotdot"	'mark required vararg values
				Local valarg:Node[] = vals.arg[..]
				valarg[valarg.Length - 1] = Leaf.Make("dotdotdot", extend)
				vals = Rose.Make(vals.key, valarg)
			Else	'adjust return count
				Local call:Rose = Rose(vals.arg[vals.arg.Length - 1]), carg:Node[] = call.arg[..]
				carg[2] = Leaf.Make("rcount", extend)
				call = Rose.Make(call.key, carg)
				Local valarg:Node[] = vals.arg[..] ; valarg[valarg.Length - 1] = call
				vals = Rose.Make(vals.key, valarg)
			EndIf

		ElseIf vars.arg.Length < vals.arg.Length
			Local l:Int = vars.arg.Length ; vars = Rose.Make(vars.key, vars.arg[..vals.arg.Length])
			For Local v:Int = l Until vals.arg.Length		'too many vals: discard into a fake var
				vars.arg[v] = Leaf.Make("discard", "discard")
			Next
		EndIf

		Return Rose.Make(n.key, [vars, vals], ["vars", "vals"])
	End Method

	Method Block:Node(n:Rose)	'remove empty statements
		Local old:Rose = Rose(n.arg[0])
		If old
			Local st:Node[] = New Node[old.arg.Length], count:Int = 0
			For Local s:Int = 0 Until old.arg.Length
				Local k:String = old.arg[s].key ; If k <> "semicolon" And k <> "#Nil"
					st[count] = old.arg[s] ; count :+ 1
				EndIf
			Next
			If count
				Return Rose.Make("Block", st[..count])
			Else
				Return Rose.Make("Block", Null)
			EndIf
		ElseIf NilNode(n.arg[0])
			Return Rose.Make("Block", Null)
		EndIf
		Return n
	End Method
	Method DoBlk:Node(n:Rose)	'extract block; reduce indirection
		Return n.arg[0]
	End Method
	Method IfS:Node(n:Rose)	'rewrite if/elseif chains into nested regular if/then/else
		Local elif:Rose = Rose(n.Get("elif")), _else:Node = n.Get("else")
		If elif
			For Local e:Int = elif.arg.Length - 1 To 0 Step -1
				If _else
					_else = Rose.Make("IfS", Rose(elif.arg[e]).arg + [_else], ["test", "then", "else"])
				Else
					_else = Rose.Make("IfS", Rose(elif.arg[e]).arg, ["test", "then"])
				EndIf
			Next
			Return Rose.Make("IfS", n.arg[..2] + [_else], ["test", "then", "else"])
	'	Else
	'		Return Rose.Make("IfS", n.arg + [Rose.Make("Block", Null)], ["test", "then", "else"])
		EndIf
		Return n
	End Method
	Method ForN:Node(n:Rose)	'convert for loops to equivalent scoped-whiles
		Global rewrite:String = ..
		"~n	local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)" +..	'building this with Make calls would be insane, and increase error surface
		"~n	if not (var and limit and step) then error() end" +..
		"~n	var = var - step" +..
		"~n	while true do" +..
		"~n		var = var + step" +..
		"~n		if (step >= 0 and var > limit) or (step < 0 and var < limit) then" +..
		"~n			break" +..
	 	"~n		end" +..
	 	"~n		local _var = var" +..
		"~n		--block" +..
	 	"~n	end~n"

		Global rwast:TParseNode ; If rwast = Null Then rwast = BlueParseString(rewrite)
		Local rwtbl:NodeTable = Node.FromParse(rwast), rw:Node = rwtbl.n
		rw = Node.Fold(rw, NormalizeFold.Make(rwtbl))

		Local v:Node = n.arg[0], f:Node = n.arg[1], t:Node = n.arg[2], s:Node = n.Get("step"), b:Node = n.Get("block")
		If s = Null Then s = Leaf.Make("dconst", "1")	'ensure step is present
		rw = Node.Fold(rw, FindReplaceFold.Make( ..
			["var", "limit", "step", "e1", "e2", "e3", "_var"], ..	'insert actual names and values into template
			[Node(Leaf.Make("name", "::var")), Node(Leaf.Make("name", "::limit")), Node(Leaf.Make("name", "::step")), f, t, s, v]))

		Rose(Rose(Rose(rw).arg[3]).arg[1]).arg :+ [b]
		Return rw
	End Method
	Method ForIn:Node(n:Rose)
		Global rewrite:String = ..
		"~n	local f, s, var = explist" +..	'doing it this way reduces error surface by reusing more of the same code machinery
		"~n	while true do" +..
		"~n		local var_1, var_2, var_n = f(s, var)" +..
		"~n		if var_1 == nil then break end" +..
		"~n		var = var_1" +..
		"~n		--block" +..
		"~n	end~n"	'the outer `do` is removed here because it's added implicitly by the parser anyway

		Global rwast:TParseNode ; If rwast = Null Then rwast = BlueParseString(rewrite)
		Local rwtbl:NodeTable = Node.FromParse(rwast), rw:Node = rwtbl.n
		rw = Node.Fold(rw, NormalizeFold.Make(rwtbl))

		Local va:Node = n.arg[0], v1:Node = Rose(va).arg[0], b:Node = n.arg[2]
		rw = Node.Fold(rw, FindReplaceFold.Make( ..
			["f", "s", "var", "var_1"], ..	'insert actual names and values into template
			[Node(Leaf.Make("name", "::f")), Node(Leaf.Make("name", "::s")), Node(Leaf.Make("name", "::var")), v1]))

		Rose(Rose(rw).arg[0]).arg[1] = n.arg[1]	'replace the expression list at declaration of ::f/::s/::var
		Local wstmts:Node[] = Rose(Rose(Rose(rw).arg[1]).arg[1]).arg
		wstmts :+ [b]	'add the loop body to the list of statements in the new while loop
		Rose(wstmts[0]).arg[0] = n.arg[0]	'loop variables

		Return rw
	End Method

	Method FunDecl:Node(n:Rose)	'replace function declarations with simple assignment
		Local name:Node = n.Get("name"), body:Rose = Rose(n.Get("body"))
		If name.key = "MethodName"
			Local params:Rose = Rose(body.arg[0])
			params = Rose.Make(params.key, [Leaf.Make("name", "self")] + params.arg)
			body = Rose.Make(body.key, [Node(params), body.arg[1]], body.id)
			name = Rose.Make("getfield", Rose(name).arg)
		EndIf
		Return Rose.Make("Assgt", [Rose.Make("VarList", [name]), Rose.Make("ExpList", [body])], ["vars", "vals"])
	End Method
	Method FuncName:Node(n:Rose)	'convert function name expressions to var+fields (but leave trailing method name to FunDecl)
		Local r:Node = n.Get("n"), path:Rose = Rose(n.Get("path")), meth:Rose = Rose(n.Get("method"))
		If path
			For Local pe:Rose = EachIn path.arg
				Local acc:Node = Leaf.Make("fname", Leaf(pe.arg[1]).val)
				r = Rose.Make("getfield", [r, acc])
			Next
		EndIf
		If meth
			Local mname:Node = Leaf.Make("fname", Leaf(meth.arg[1]).val)
			r = Rose.Make("MethodName", [r, mname])	'package for FunDecl to rebrand as a get
		EndIf
		Return r
	End Method
	Method LocalFun:Node(n:Rose)	'replace local function declarations with local assignment
		Local nam:Node = n.Get("name"), dec:Node = Rose.Make("LocalVar", [..
			Rose.Make("NameList", [nam]), Rose.Make("ExpList", [Leaf.Make("nil", "nil")])], ["names", "vals"])	'nil-init'd
		Local asg:Node = Rose.Make("Assgt", [Rose.Make("VarList", [nam]), Rose.Make("ExpList", [n.Get("body")])], ["vars", "vals"])
		Return Rose.Make("#EXPAND", [dec, asg])
	End Method
	Method FuncBody:Node(n:Rose)	'reduce indirection on parameter list and straighten out varargs
		Local param:Node = Rose(n.arg[0]).arg[0], plist:Rose = Rose(param)
		If NilNode(param)
			param = Rose.Make("NameList", Null)
		ElseIf param.key = "dotdotdot"
			param = Rose.Make("NameList", [param])
		ElseIf plist And plist.arg.Length = 2 And plist.arg[1].key = "dotdotdot"
			param = Rose.Make("NameList", Rose(plist.arg[0]).arg + [plist.arg[1]])
		EndIf
		Return Rose.Make(n.key, [param, n.arg[1]], n.id)
	End Method

	Method GotoS:Node(n:Rose) { Label }
		Local l:Leaf = Leaf(n.arg[0]), t:Object = tbl.ValueForKey(l) ; tbl.Remove(l)	'modifying an error-able leaf means updating its error entry
		l = Leaf.Make(n.key, l.val) ; tbl.Insert(l, t)
		Return l
	End Method
	Method ReturnS:Node(n:Rose)
		Local vals:Node[] = Null
		If Rose(n.arg[0]) Then vals = Rose(n.arg[0]).arg
		Return Rose.Make(n.key, vals)
	End Method

	Method PostfixExp:Node(n:Rose)	'rewrite element access/function call chains into nested single ops
		Local f:Node = n.arg[0], pl:Node[] = Rose(n.arg[1]).arg
		For Local e:Int = 0 Until pl.Length
			Select pl[e].key
				Case "sconst", "TableCons"
					f = Rose.Make("fcall", [f, Node(Rose.Make("FArgs", [pl[e]])), Node(Leaf.Make("rcount", "1"))])
				Case "FArgs"
					f = Rose.Make("fcall", [f, pl[e], Node(Leaf.Make("rcount", "1"))])	'add number of returned values
				Case "MArgs"
					Leaf(Rose(pl[e]).arg[0]).key = "fname"
					f = Rose.Make("mcall", [f, pl[e], Node(Leaf.Make("rcount", "1"))])
				Case "ElemGet"
					f = Rose.Make("index", [f, Rose(pl[e]).arg[0]])
				Case "FieldGet"
					Local acc:Node = Rose(pl[e]).arg[0] ;  acc = Leaf.Make("fname", Leaf(acc).val)
					f = Rose.Make("getfield", [f, acc])
			End Select
		Next
		Return f
	End Method
	Method FArgs:Node(n:Rose)	'clear empty arg lists and reduce indirection
		Local al:Rose = Rose(n.arg[0])
		If al Then Return Rose.Make("FArgs", al.arg) Else Return Rose.Make("FArgs", Null)
	End Method

	Method dotdotdot:Node(n:Leaf)
		Return Leaf.Make("dotdotdot", "1")
	End Method
End Type

Type FindReplaceFold
	Field names:String[], vals:Node[]

	Function Make:FindReplaceFold(names:String[], vals:Node[])
		Local f:FindReplaceFold = New Self
		f.names = names ; f.vals = vals ; Return f
	End Function

	Method name:Node(n:Leaf)	'replace occurrences of names in the name array with the matching vals value
		For Local i:Int = 0 Until names.Length
			If names[i] = n.val Then Return vals[i]
		Next
		Return n
	End Method
End Type

Type ConstantFold	'fold constant math and logic operations
	Method hconst:Node(n:Leaf) { hfconst }	'convert hex constants to uniform representation (unnecessary? BlitzMax natively supports 0x1a23p-4 format)
		Return Leaf.Make("dconst", String(Double(n.val)))
	End Method

	Method BinMathOp:Node(n:Rose) { plus minus mul div kmod pow }
		Const DK:String = "dconst"
		If n.arg[0].key = DK And n.arg[1].key = DK
			Local l:Double = Double(Leaf(n.arg[0]).val), r:Double = Double(Leaf(n.arg[1]).val)
			Select n.key
				Case "plus"  ; l :+ r
				Case "minus" ; l :- r
				Case "mul"   ; l :* r
				Case "div"   ; l :/ r
				Case "kmod"  ; l = l Mod r
				Case "pow"   ; l = l ^ r
			End Select
			Return Leaf.Make(DK, l)
		Else
			Return n
		EndIf
	End Method
	Method UnaryOp:Node(n:Rose) { negate klen knot }
		Local a:Leaf = Leaf(n.arg[0]) ; If a = Null Then Return n
		Select n.key
			Case "negate"
				If a.key = "dconst" Or a.key = "sconst"		'BlitzMax natively supports Lua number formats, it's just the C one
					Return Leaf.Make("dconst", String(-Double(a.val)))
				EndIf
			Case "not"
				Select a.key
					Case "nil", "bconst"
						If a.val = "true" Then Return Leaf.Make("bconst", "false") Else Return Leaf.Make("bconst", "true")
					Case "dconst", "sconst"
						Return Leaf.Make("bconst", "false")
				End Select
		End Select
		Return n
	End Method
	Method AndExpr:Node(n:Rose) { Expr }	'optimize short-circuit operators with constant first operand
		Local l:Leaf = Leaf(n.arg[0])
		If l
			If l.key = "sconst" Or l.key = "dconst" Or (l.key = "bconst" And l.val = "true")
				If n.key = "kor" Then Return l Else Return n.arg[1]
			ElseIf l.key = "nil" Or (l.key = "bconst" And l.val = "false")
				If n.key = "kor" Then Return n.arg[1] Else Return l
			EndIf
		EndIf
		Return n
	End Method
	Method eq:Node(n:Rose) { neq }
		Local l:Leaf = Leaf(n.arg[0]), r:Leaf = Leaf(n.arg[1])
		If l <> Null And r <> Null And l.key = r.key
			Global rv:String[] = ["false", "true"]
			Select l.key
				Case "bconst", "sconst" ; Return Leaf.Make("bconst", rv[(n.key = "eq") * (l.val = r.val)])
				Case "dconst" ; Return Leaf.Make("bconst", rv[(n.key = "eq") * (Double(l.val) = Double(r.val))])	'it's fine when they're constants
			End Select
		EndIf
		Return n
	End Method
	Method lt:Node(n:Rose) { gt leq geq }
		Local l:Leaf = Leaf(n.arg[0]), r:Leaf = Leaf(n.arg[1])
		If l <> Null And r <> Null And l.key = "dconst" And r.key = "dconst"	'string comparisons are affected by runtime locale
			Global rv:String[] = ["false", "true"] ; Local ret:Int
			Select n.key
				Case "gt" ; ret = Double(l.val) > Double(r.val)
				Case "lt" ; ret = Double(l.val) < Double(r.val)
				Case "geq" ; ret = Double(l.val) >= Double(r.val)
				Case "neq" ; ret = Double(l.val) <= Double(r.val)
			End Select
			Return Leaf.Make("bconst", rv[ret])
		EndIf
		Return n
	End Method

	Method IfS:Node(n:Rose)	'unpack constant if structures
		Local test:Leaf = Leaf(n.arg[0])
		If test
			Select test.key
				Case "bconst", "dconst", "sconst", "nil"
					If (test.key = "bconst" And test.val = "False") Or test.key = "nil"
						If n.Get("else") Then Return n.Get("else") Else Return Null
					Else
						Return n.Get("then")
					EndIf
			End Select
		EndIf
		Return n
	End Method
	Method WhileS:Node(n:Rose)	'remove while(false)
		Local test:Leaf = Leaf(n.arg[0])
		If test And ((test.key = "bconst" And test.val = "false") Or (test.key = "nil")) Then Return Null
		Return n
	End Method
	Method RepeatS:Node(n:Rose)	'replace repeat...until(true) with a while and replace ...until(false) with a block
		Local t:Leaf = Leaf(n.Get("test"))
		If t
			If t.key = "nil" Or (t.key = "bconst" And t.val = "false")
				Return Rose.Make("WhileS", [Node(Leaf.Make("bconst", "true")), n.Get("block")], ["test", "block"])
			ElseIf t.key = "dconst" Or t.key = "sconst" Or (t.key = "bconst" And t.val = "true")
				Return n.Get("block")
			EndIf
		EndIf
		Return n
	End Method
End Type

Type SyntaxErrFold	'report any remaining syntax errors
	Field tbl:TMap, breaks:TList
	Function Make:SyntaxErrFold(tbl:NodeTable)
		Local f:SyntaxErrFold = New SyntaxErrFold
		f.tbl = tbl.leaves ; f.breaks = CreateList()
		Return f
	End Function

	Method Block:Node(n:Rose)	'scan for non-statements
		For Local s:Int = 0 Until n.arg.Length
			Select n.arg[s].key
				Case "fcall", "mcall"
					n.arg[s].key = "p" + n.arg[s].key
				Case "Assgt", "break", "GotoS", "Label", "Block", "WhileS", "RepeatS", "IfS", "LocalVar", "ReturnS"', "ForN", "ForIn"
				Default
					Throw BlueCompileError.Make(TToken(tbl.ValueForKey(n.arg[s])), "invalid statement in statement block")
			End Select
		Next
		Return n
	End Method

	Method break:Node(n:Leaf)	'add breaks to a stack...
		breaks.AddFirst(n) ; Return n
	End Method
	Method AllLoops:Node(n:Rose) { WhileS RepeatS } 'ForN ForIn }	'...then remove them
		breaks.Clear() ; Return n
	End Method
	Method FuncBody:Node(n:Rose)	'...and error on any that weren't removed
		Self.BreakCheck() ; Return n
	End Method

	Method BreakCheck()	'need to call this externally for toplevel too
		If breaks.Count() Then Throw BlueCompileError.Make(TToken(tbl.ValueForKey(breaks.First())), "'break' statement without containing loop")
	End Method
End Type