Added some B3D-style functions.

Added some B3D-style functions.

Strict

Rem
	bbdoc: Blitz3D style wrapper for MaxB3D
End Rem
Module MaxB3D.Retro
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.GLDriver
Import MaxB3D.D3D9Driver
Import MaxB3D.Primitives

Import MaxB3D.B3DCollision

Import MaxB3D.A3DSLoader
Import MaxB3D.B3DLoader
Import MaxB3D.MD2Loader

Private
Global _renderinfo:TRenderInfo = New TRenderInfo

Public

Function Graphics3D(width, height, depth = 32, mode = 0)
	Select mode
	Case 0
?Debug
		depth = 0
?Not Debug
		depth = 32
?
	Case 1
		depth = 32
	Case 2
		depth = 0
	End select
	Graphics width,height,depth
End Function
Rem
# Dither
# WBuffer
# AntiAlias
End Rem
Function Wireframe(enable)
	_currentworld.SetWireFrame enable
End Function
'# HWMultiTex
Function AmbientLight(red, green, blue)
	_currentworld.SetAmbientLight red, green, blue
End Function
Rem
# ClearCollisions
# Collisions
End Rem
Function UpdateWorld(speed#)
	_currentworld.Update(speed, speed)
End Function
Function RenderWorld(tween#)
	_renderinfo = _currentworld.Render(tween)
End Function
Rem
# CaptureWorld
# ClearWorld
# LoaderMatrix
# TrisRendered
End Rem
Function TrisRendered()
	return _renderinfo.triangles
End Function

Rem
# CreateTexture
# LoadTexture
# LoadAnimTexture
# FreeTexture
# TextureBlend
# TextureCoords
# ScaleTexture
# PositionTexture
# RotateTexture
# TextureWidth
# TextureHeight
# TextureBuffer
# TextureName
# GetBrushTexture
# ClearTextureFilters
# TextureFilter
# SetCubeFace
# SetCubeMode

# CreateBrush
# LoadBrush
# FreeBrush
# BrushColor
# BrushAlpha
# BrushShininess
# BrushTexture
# BrushBlend
# BrushFX
# GetEntityBrush
# GetSurfaceBrush

# CreateMesh
# LoadMesh
# LoadAnimMesh
# CreateCube
# CreateSphere
# CreateCylinder
# CreateCone
# CopyMesh
# AddMesh
# FlipMesh
# PaintMesh
# LightMesh
# FitMesh
# ScaleMesh
# RotateMesh
# PositionMesh
# UpdateNormals
# MeshesIntersect
# MeshWidth
# MeshHeight
# MeshDepth
# CountSurfaces
# GetSurface

# CreateSurface
# PaintSurface
# ClearSurface
# FindSurface
# AddVertex
# AddTriangle
# VertexCoords
# VertexNormal
# VertexColor
# VertexTexCoords
# CountVertices
# CountTriangles
# VertexX
# VertexY
# VertexZ
# VertexNX
# VertexNY
# VertexNZ
# VertexRed
# VertexGreen
# VertexBlue
# VertexAlpha
# VertexU
# VertexV
# VertexW
# TriangleVertex

# CreateCamera
# CameraProjMode
# CameraFogMode
# CameraFogRange
# CameraFogColor
# CameraViewport
# CameraClsMode
# CameraClsColor
# CameraRange
# CameraZoom
# CameraPick
# PickedX
# PickedY
# PickedZ
# PickedNX
# PickedNY
# PickedNZ
# PickedTime
# PickedEntity
# PickedSurface
# PickedTriangle
# CameraProject
# ProjectedX
# ProjectedY
# ProjectedZ
# EntityInView

# CreateLight
# LightRange
# LightColor
# LightConeAngles

# CreatePivot

# CreateSprite
# LoadSprite
# RotateSprite
# ScaleSprite
# HandleSprite
# SpriteViewMode

# LoadMD2
# AnimateMD2
# MD2AnimTime
# MD2AnimLength
# MD2Animating

# LoadBSP
# BSPAmbientLight
# BSPLighting

# CreatePlane

# CreateMirror

# CreateTerrain
# LoadTerrain
# TerrainSize
# TerrainDetail
# TerrainShading
# TerrainHeight
# ModifyTerrain
# TerrainX
# TerrainY
# TerrainZ

# CreateListener
# Load3DSound
# EmitSound

# ScaleEntity
# PositionEntity
# MoveEntity
# TranslateEntity
# RotateEntity
# TurnEntity
# PointEntity
# AlignToVector

# LoadAnimSeq
# SetAnimKey
# AddAnimSeq
# ExtractAnimSeq
# Animate
# SetAnimTime
# AnimSeq
# AnimLength
# AnimTime
# Animating
End Rem

Function FreeEntity(entity:TEntity)
	Return entity.Free()
End Function
Function CopyEntity:TEntity(entity:TEntity, parent:TEntity = Null)
	Return entity.Copy(parent)
End Function
Function EntityColor(entity:TEntity, red, green, blue)
	Return entity.SetColor(red, green, blue)
End Function
Function EntityAlpha(entity:TEntity, alpha#)
	Return entity.SetAlpha(alpha)
End Function
Function EntityShininess(entity:TEntity, shine#)
	Return entity.SetShine(shine)
End Function
Function EntityTexture(entity:TEntity, texture:TTexture, frame = 0, index = 0)
	Return entity.SetTexture(texture, frame, index)
End Function
Function EntityBlend(entity:TEntity, blend)
	Return entity.SetBlend(blend)
End Function
Function EntityFX(entity:TEntity, fx)
	Return entity.SetFX(fx)
End Function
'# EntityAutoFade
Function PaintEntity(entity:TEntity, brush:TBrush)
	Return entity.SetBrush(brush)
End Function
Function EntityOrder(entity:TEntity, order)
	Return entity.SetOrder(order)
End Function
Function HideEntity(entity:TEntity)
	Return entity.SetVisible(False)
End Function
Function ShowEntity(entity:TEntity)
	Return entity.SetVisible(True)
End Function
Function NameEntity(entity:TEntity, name$)
	Return entity.SetName(name)
End Function
Function EntityParent(entity:TEntity, parent:TEntity, glob = True)
	Return entity.SetParent(parent, glob)
End Function
Function GetParent:TEntity(entity:TEntity)
	Return entity.GetParent()
End Function

Function EntityX#(entity:TEntity, glob = False)
	Local x#,y#,z#
	entity.GetPosition x,y,z,glob
	Return x
End Function
Function EntityY#(entity:TEntity, glob = False)
	Local x#,y#,z#
	entity.GetPosition x,y,z,glob
	Return y
End Function
Function EntityZ#(entity:TEntity, glob = False)
	Local x#,y#,z#
	entity.GetPosition x,y,z,glob
	Return z
End Function
Rem
# EntityRoll
# EntityYaw
# EntityPitch
# EntityClass
# EntityName
# CountChildren
# GetChild
# FindChild
# EntityPick
# LinePick
# EntityVisible
# EntityDistance
# DeltaYaw
# DeltaPitch
# TFormPoint
# TFormVector
# TFormNormal
# TFormedX
# TFormedY
# TFormedZ
# GetMatElement

# ResetEntity
# EntityRadius
# EntityBox
# EntityType
# EntityPickMode
# EntityCollided
# CountCollisions
# CollisionX
# CollisionY
# CollisionZ
# CollisionNX
# CollisionNY
# CollisionNZ
# CollisionTime
# CollisionEntity
# CollisionSurface
# CollisionTriangle
End Rem
Function GetEntityType(entity:TEntity)
	Return entity.GetType()
End Function

Rem
# VectorYaw
# VectorPitch

# CountGfxModes3D
# GfxMode3D
# GfxMode3DExists
# GfxDriver3D
# GfxDriverCaps3D
# Windowed3D
# HWTexUnits
End Rem

Strict

Rem
	bbdoc: Blitz3D style wrapper for MaxB3D
End Rem
Module MaxB3D.Retro
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.GLDriver
Import MaxB3D.D3D9Driver
Import MaxB3D.Primitives

Import MaxB3D.B3DCollision

Import MaxB3D.A3DSLoader
Import MaxB3D.B3DLoader
Import MaxB3D.MD2Loader

Rem
# Graphics3D
# Dither
# WBuffer
# AntiAlias
# Wireframe
# HWMultiTex
# AmbientLight
# ClearCollisions
# Collisions
# UpdateWorld
# CaptureWorld
# RenderWorld
# ClearWorld
# LoaderMatrix
# TrisRendered

# CreateTexture
# LoadTexture
# LoadAnimTexture
# FreeTexture
# TextureBlend
# TextureCoords
# ScaleTexture
# PositionTexture
# RotateTexture
# TextureWidth
# TextureHeight
# TextureBuffer
# TextureName
# GetBrushTexture
# ClearTextureFilters
# TextureFilter
# SetCubeFace
# SetCubeMode

# CreateBrush
# LoadBrush
# FreeBrush
# BrushColor
# BrushAlpha
# BrushShininess
# BrushTexture
# BrushBlend
# BrushFX
# GetEntityBrush
# GetSurfaceBrush

# CreateMesh
# LoadMesh
# LoadAnimMesh
# CreateCube
# CreateSphere
# CreateCylinder
# CreateCone
# CopyMesh
# AddMesh
# FlipMesh
# PaintMesh
# LightMesh
# FitMesh
# ScaleMesh
# RotateMesh
# PositionMesh
# UpdateNormals
# MeshesIntersect
# MeshWidth
# MeshHeight
# MeshDepth
# CountSurfaces
# GetSurface

# CreateSurface
# PaintSurface
# ClearSurface
# FindSurface
# AddVertex
# AddTriangle
# VertexCoords
# VertexNormal
# VertexColor
# VertexTexCoords
# CountVertices
# CountTriangles
# VertexX
# VertexY
# VertexZ
# VertexNX
# VertexNY
# VertexNZ
# VertexRed
# VertexGreen
# VertexBlue
# VertexAlpha
# VertexU
# VertexV
# VertexW
# TriangleVertex

# CreateCamera
# CameraProjMode
# CameraFogMode
# CameraFogRange
# CameraFogColor
# CameraViewport
# CameraClsMode
# CameraClsColor
# CameraRange
# CameraZoom
# CameraPick
# PickedX
# PickedY
# PickedZ
# PickedNX
# PickedNY
# PickedNZ
# PickedTime
# PickedEntity
# PickedSurface
# PickedTriangle
# CameraProject
# ProjectedX
# ProjectedY
# ProjectedZ
# EntityInView

# CreateLight
# LightRange
# LightColor
# LightConeAngles

# CreatePivot

# CreateSprite
# LoadSprite
# RotateSprite
# ScaleSprite
# HandleSprite
# SpriteViewMode

# LoadMD2
# AnimateMD2
# MD2AnimTime
# MD2AnimLength
# MD2Animating

# LoadBSP
# BSPAmbientLight
# BSPLighting

# CreatePlane

# CreateMirror

# CreateTerrain
# LoadTerrain
# TerrainSize
# TerrainDetail
# TerrainShading
# TerrainHeight
# ModifyTerrain
# TerrainX
# TerrainY
# TerrainZ

# CreateListener
# Load3DSound
# EmitSound

# ScaleEntity
# PositionEntity
# MoveEntity
# TranslateEntity
# RotateEntity
# TurnEntity
# PointEntity
# AlignToVector

# LoadAnimSeq
# SetAnimKey
# AddAnimSeq
# ExtractAnimSeq
# Animate
# SetAnimTime
# AnimSeq
# AnimLength
# AnimTime
# Animating

# FreeEntity
# CopyEntity
# EntityColor
# EntityAlpha
# EntityShininess
# EntityTexture
# EntityBlend
# EntityFX
# EntityAutoFade
# PaintEntity
# EntityOrder
# ShowEntity
# HideEntity
# NameEntity
# EntityParent
# GetParent

# EntityX
# EntityY
# EntityZ
# EntityRoll
# EntityYaw
# EntityPitch
# EntityClass
# EntityName
# CountChildren
# GetChild
# FindChild
# EntityPick
# LinePick
# EntityVisible
# EntityDistance
# DeltaYaw
# DeltaPitch
# TFormPoint
# TFormVector
# TFormNormal
# TFormedX
# TFormedY
# TFormedZ
# GetMatElement

# ResetEntity
# EntityRadius
# EntityBox
# EntityType
# EntityPickMode
# EntityCollided
# CountCollisions
# CollisionX
# CollisionY
# CollisionZ
# CollisionNX
# CollisionNY
# CollisionNZ
# CollisionTime
# CollisionEntity
# CollisionSurface
# CollisionTriangle
# GetEntityType

# VectorYaw
# VectorPitch

# CountGfxModes3D
# GfxMode3D
# GfxMode3DExists
# GfxDriver3D
# GfxDriverCaps3D
# Windowed3D
# HWTexUnits
End Rem


corrected comparison logic

corrected comparison logic

' Blue Moon
' FOLD delegate to generate assembly instructions

' this file is a Private Include

'SuperStrict

Type CompileFold
	Field opc:BlueOpcode = New BlueOpcode, bi:BlueInstr = New BlueInstr, f:BlueAssembly, _gq:TList = CreateList()

	Function Make:CompileFold(f:BlueAssembly)
		Local c:CompileFold = New Self ; c.f = f ; Return c
	End Function

	Method l_varref:Node(n:Leaf)
		Local v:VarDef = f.fd.vars[StrToNum(n.val)], op:Int
		If v.isClosure Then op = opc.SETLC Else op = opc.MOV
		Return BytecodeNode.MakeL(n.key, bi.Make(op, v.stkpos, f.Pop()), n)
	End Method
	Method l_closref:Node(n:Leaf)
		Local ref:Int = StrToNum(n.val)
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.SETUPV, ref, f.Pop()))
	End Method
	Method l_getfield:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, [n.arg[0]])
		Local k:Int = f.GetConst("1", Leaf(n.arg[1]).val), tab:Int = f.Pop()
		r.inst.AddLast bi.Make(opc.SETTAB, tab, f.Pop(), k)
		Return r
	End Method
	Method l_index:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg)
		Local ind:Int = f.Pop(), tab:Int = f.Pop(), val:Int = f.Pop()
		Local pv:BlueInstr = BlueInstr(r.inst.Last())
		If pv.op = opc.LOADSI And Abs(pv.c) < f.VSI_LIMIT And pv.c = Abs pv.c
			pv.op = opc.SETTABSI ; pv.ra = tab ; pv.rb = val
		Else
			r.inst.AddLast bi.Make(opc.SETTABI, tab, val, ind)
		EndIf
		Return r
	End Method
	Method l_discard:Node(n:Node)
		f.Pop() ; Return BytecodeNode.Nil()
	End Method

	Method LocalVar:Node(n:Rose)
		Local vars:Node[] = Rose(BytecodeNode(n.arg[1]).old).arg
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg)
		For Local vi:Int = vars.Length - 1 To 0 Step -1
			Local v:VarDef = f.fd.vars[StrToNum(Leaf(BytecodeNode(vars[vi]).old).val)]
			If v.isClosure Then r.inst.AddFirst bi.Make(opc.NEWUPV, v.stkpos, 0)
		Next
		Return r
	End Method

	Method ReturnS:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg)
		r.inst.AddLast(bi.MakeK(opc.RET, f.Peek(), n.arg.Length))
		f.Pop(n.arg.Length) ; Return r
	End Method
	Method return_va:Node(n:Node)
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.RETVA, 0, 0))
	End Method

	Method break:Node(n:Node)
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.JMP, -1, -1, -1))
	End Method
	Method RepeatS:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), _lt:TList = f.fd._lt, _gt:TList = f.fd._gt
		Local loopStart:LabelDef = New LabelDef, lsID:Int = _lt.Count(), loop:GotoDef = New GotoDef, lID:Int = _gt.Count()
		loop.tgt = loopStart ; _lt.AddLast loopStart ; _gt.AddLast loop
		Local lastop:BlueInstr = BlueInstr(r.inst.Last()), reg:Int = f.Pop()
		If lastop.op = opc.LNOT
			lastop.op = opc.JIF ; lastop.ra = lastop.rb ; lastop.rb = 0 ; lastop.c = lID	'replace NOT with inverted jump
		Else
			r.inst.AddLast bi.MakeK(opc.JNOT, reg, lID)
		EndIf
		r.inst.AddFirst bi.Make(opc.MOV, 0, 0, lsID)
		r.inst.AddLast bi.Make(opc.MOV, -1, -1, -1)
		Return r
	End Method
	Method IfS:Node(n:Rose) { WhileS }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, [n.arg[0]]), lastop:BlueInstr = BlueInstr(r.inst.Last())
		Local before:LabelDef, befID:Int, loopRound:GotoDef, loopID:Int, _lt:TList = f.fd._lt, _gt:TList = f.fd._gt
		If n.key = "WhileS"
			before = New LabelDef ; befID = _lt.Count() ; _lt.AddLast before
			loopRound = New GotoDef ;loopRound.tgt = before ; loopID = _gt.Count() ; _gt.AddLast loopRound
			r.inst.AddFirst bi.Make(opc.MOV, 0, 0, befID)
		EndIf
		Local postThen:LabelDef = New LabelDef, skipThen:GotoDef = New GotoDef, ptID:Int = _lt.Count(), stID:Int = _gt.Count()
		_lt.AddLast postThen ; skipThen.tgt = postThen ; _gt.AddLast skipThen
		If lastop.op = opc.LNOT
			lastop.op = opc.JIF ; lastop.ra = lastop.rb ; lastop.rb = 0 ; lastop.c = stID	'replace NOT with inverted jump
		Else
			r.inst.AddLast bi.MakeK(opc.JNOT, f.Peek(), stID)
		EndIf
		f.Pop()
		For Local op:BlueInstr = EachIn BytecodeNode(n.arg[1]).inst
			r.inst.AddLast op
		Next
		Local postElse:LabelDef, skipElse:GotoDef
		If n.arg.Length > 2	'there's an else
			postElse = New LabelDef ; skipElse = New GotoDef ; skipElse.tgt = postElse
			Local peID:Int = _lt.Count(), seID:Int = _gt.Count()
			_lt.AddLast postElse ; _gt.AddLast skipElse
			r.inst.AddLast bi.MakeK(opc.JMP, 0, seID)
			r.inst.AddLast bi.Make(opc.MOV, 0, 0, ptID)
			For Local op:BlueInstr = EachIn BytecodeNode(n.arg[2]).inst
				r.inst.AddLast op
			Next
			r.inst.AddLast bi.Make(opc.MOV, 0, 0, peID)
		Else
			If n.key = "WhileS" Then r.inst.AddLast bi.MakeK(opc.JMP, 0, loopID) ; r.inst.AddLast bi.Make(opc.MOV, -1, -1, -1)
			r.inst.AddLast bi.Make(opc.MOV, 0, 0, ptID)
		EndIf
		Return r
	End Method
	Method kand:Node(n:Rose) { kor }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, [n.arg[0]]), lastop:BlueInstr = BlueInstr(r.inst.Last())
		Local _lt:TList = f.fd._lt, _gt:TList = f.fd._gt
		Local postR:LabelDef = New LabelDef, skipR:GotoDef = New GotoDef, prID:Int = _lt.Count(), srID:Int = _gt.Count()
		_lt.AddLast postR ; skipR.tgt = postR ; _gt.AddLast skipR
		Local reg:Int = f.Pop()
		If lastop.op = opc.LNOT
			Local op:Int = opc.JIF ; If n.key = "kor" Then op = opc.JNOT
			lastop.op = op ; lastop.ra = lastop.rb ; lastop.rb = 0 ; lastop.c = srID	'replace NOT with inverted jump
		Else
			Local op:Int = opc.JNOT ; If n.key = "kor" Then op = opc.JIF
			r.inst.AddLast bi.MakeK(op, reg - 1, srID)
		EndIf
		For Local op:BlueInstr = EachIn BytecodeNode(n.arg[1]).inst
			r.inst.AddLast op
		Next
		r.inst.AddLast bi.Make(opc.MOV, reg - 1, reg)
		r.inst.AddLast bi.Make(opc.MOV, 0, 0, prID)
		Return r
	End Method

	Method fcall:Node(n:Rose) { pfcall tfcall va_fcall }	'"p" prefix = "procedure" i.e. no return value required
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), argc:Int = Int(Leaf(n.arg[3]).val), retc:Int = Int(Leaf(n.arg[2]).val)
		Local func:Int = f.Pop(argc + 1), args:Int = func + 1, op:Int = opc.CALL
		If n.key = "tfcall" Then op = opc.TCALL
		r.inst.AddLast bi.Make(op, func, args, argc)
		If n.key = "fcall"
			r.inst.AddLast bi.MakeK(opc.POSTCALL, f.Peek() + 1, retc) ; f.Push(retc)
		ElseIf n.key = "va_fcall"
			r.inst.AddLast bi.Make(opc.CALLINIT, -1, 0)
		EndIf
		Return r
	End Method
	Method mcall:Node(n:Rose) { pmcall tmcall va_mcall }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), argc:Int = Int(Leaf(n.arg[3]).val), retc:Int = Int(Leaf(n.arg[2]).val)
		Local func:Int = f.Push(), argv:Int = f.Pop(argc + 2), op:Int = opc.CALL
		Local ks:String = Leaf(Rose(BytecodeNode(n.arg[1]).old).arg[0]).val		'yeesh
		r.inst.AddLast bi.Make(opc.GETTAB, func, argv, f.GetConst("1", ks))
		If n.key = "tmcall" Then op = opc.TCALL
		r.inst.AddLast bi.Make(op, func, argv, argc + 1)
		If n.key = "mcall"
			r.inst.AddLast bi.MakeK(opc.POSTCALL, argv, retc) ; f.Push(retc)
		ElseIf n.key = "va_fcall"
			r.inst.AddLast bi.Make(opc.CALLINIT, -1, 0)
		EndIf
		Return r
	End Method
	Method dotdotdot:Node(n:Leaf)
		Local retc:Int = Int(n.val), r:BytecodeNode
		r = BytecodeNode.MakeL(n.key, bi.MakeK(opc.VARARG, f.Peek() + 1, retc))
		f.Push(retc)
		Return r
	End Method

	Method BinaryOp:Node(n:Rose) { plus minus mul div kmod pow dotdot lt gt leq geq eq neq }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), op:Int
		Select n.key
			Case "plus"   ; op = opc.ADD
			Case "minus"  ; op = opc.SUB
			Case "mul"    ; op = opc.MUL
			Case "div"    ; op = opc.DIV
			Case "kmod"   ; op = opc.NMOD
			Case "pow"    ; op = opc.POW
			Case "dotdot" ; op = opc.CAT

			Case "lt"     ; op = opc.LT
			Case "gt"     ; op = opc.LT
			Case "leq"    ; op = opc.LEQ
			Case "geq"    ; op = opc.LEQ
			Case "eq"     ; op = opc.EQ
			Case "neq"    ; op = opc.EQ
		End Select
		Local lop:Int = f.Pop(), rop:Int = f.Pop()
		If n.key = "geq" Or n.key = "gt"
			r.inst.AddLast bi.Make(op, f.Push(), rop, lop)	'technically this violates IEEE-754, but it's how Lua defines it
		Else
			r.inst.AddLast bi.Make(op, f.Push(), lop, rop)
			If n.key = "neq" Then r.inst.AddLast bi.Make(opc.LNOT, f.Peek(), f.Peek())
		EndIf
		Return r
	End Method
	Method UnaryOp:Node(n:Rose) { negate klen knot }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), op:Int
		Select n.key
			Case "negate" ; op = opc.UNM
			Case "klen"   ; op = opc.ALEN
			Case "knot"   ; op = opc.LNOT
			Case "posate" ; op = opc.UNP
		End Select
		Local arg:Int = f.Pop()
		r.inst.AddLast bi.Make(op, f.Push(), arg)
		Return r
	End Method

	Method dconst:Node(n:Leaf) { sconst }
		Local ki:Int = Int(n.val), op:Int = opc.LOADK
		If n.key = "dconst" And Abs(ki) < BlueAssembly.SI_LIMIT And Double(n.val) = Double(ki)
			op = opc.LOADSI
		Else
			ki = f.GetConst(String(n.key = "sconst"), n.val)
		EndIf
		Return BytecodeNode.MakeL(n.key, bi.MakeK(op, f.Push(), ki))
	End Method
	Method bconst:Node(n:Leaf) { nil }
		Local ins:BlueInstr
		Select n.val
			Case "true"  ; ins = bi.MakeK(opc.LOADBOOL, f.Push(), 1)
			Case "false" ; ins = bi.MakeK(opc.LOADBOOL, f.Push(), 0)
			Case "nil"   ; ins = bi.MakeK(opc.LOADNIL, f.Push(), 0)
		End Select
		Return BytecodeNode.MakeL(n.key, ins)
	End Method

	Method funref:Node(n:Leaf) { varref closref }
		Local i:BlueInstr, ref:Int = StrToNum(n.val)
		Select n.key
			Case "funref" ; i = bi.MakeK(opc.CLOSURE, f.Push(), ref)
			Case "varref"
				Local v:VarDef = f.fd.vars[ref], op:Int = opc.MOV ; If v.isClosure Then op = opc.GETLC
				i = bi.Make(op, f.Push(), v.stkpos)
			Case "closref" ; i = bi.Make(opc.GETUPV, f.Push(), ref)' + f.fd.vmax)
		End Select
		Return BytecodeNode.MakeL(n.key, i)
	End Method
	Method getfield:Node(n:Rose) { index }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), i:BlueInstr
		If n.key = "getfield"
			Local k:Int = f.GetConst("1", Leaf(n.arg[1]).val), tab:Int = f.Pop()
			r.inst.AddLast bi.Make(opc.GETTAB, f.Push(), tab, k)
		Else
			Local idx:Int = f.Pop(), tab:Int = f.Pop()
			Local pv:BlueInstr = BlueInstr(r.inst.Last())
			If pv.op = opc.LOADSI And Abs(pv.c) < f.VSI_LIMIT And pv.c = Abs pv.c
				pv.op = opc.GETTABSI ; pv.ra = f.Push() ; pv.rb = tab
			Else
				r.inst.AddLast bi.Make(opc.GETTABI, f.Push(), tab, idx)
			EndIf
		EndIf
		Return r
	End Method

	Method TableCons:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), fld:BytecodeNode = Null
		Local rslot:Int = f.Push(), tslot:Int = -1
		For fld = EachIn n.arg	'work out where it's safe to put the table
			For Local in:BlueInstr = EachIn fld.inst
				If in.ra >= tslot Then tslot = in.ra + 1
			Next
		Next
		For fld = EachIn n.arg	'then patch the operations to point there
			For Local in:BlueInstr = EachIn fld.inst
				If in.ra = -1 And (in.op = opc.SETTAB Or in.op = opc.SETTABI ..
					Or in.op = opc.VAINIT Or in.op = opc.CALLINIT) Then in.ra = tslot
			Next
		Next
		If n.arg	'pass the ongoing index count to the variable initializer
			fld = BytecodeNode(n.arg[n.arg.Length - 2])
			If fld.key = "va_fcall" Or fld.key = "va_init"
				Local in:BlueInstr = BlueInstr(fld.inst.Last())
				in.c = Int(Leaf(n.arg[n.arg.Length - 1]).val)
			EndIf
		EndIf
		If tslot = -1 Then tslot = rslot Else tslot = f.Push(tslot - f.Peek())
		r.inst.AddFirst bi.Make(opc.NEWTAB, tslot, 0)
		If tslot <> rslot Then r.inst.AddLast bi.Make(opc.MOV, rslot, tslot)	'put the resulting table in the expected slot
		f.Pop(f.Peek() - rslot)	'...and correct the stack
		Return r
	End Method
	Method FieldIndex:Node(n:Rose) { FieldKey }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg)
		Local val:Int = f.Pop(), idx:Int, op:Int
		If n.key = "FieldIndex"
			idx = f.Pop() ; op = opc.SETTABI
		Else
			idx = f.GetConst("1", Leaf(n.arg[0]).val) ; op = opc.SETTAB
		EndIf
		r.inst.AddLast bi.Make(op, -1, val, idx)
		Return r
	End Method
	Method va_init:Node(n:Node)
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.VAINIT, -1, 0))
	End Method

	Method Label:Node(n:Leaf)	'insert dummy instructions to remove in the next pass
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.MOV, 0, 0, StrToNum(n.val)))
	End Method
	Method GotoS:Node(n:Leaf)	'resolve these in the next pass after removing dummy targets
		Return BytecodeNode.MakeL(n.key, bi.MakeK(opc.JMP, 0, StrToNum(n.val)))
	End Method

	Method PASSTHROUGH:Node(n:Node) { FuncBody Block ExpList FArgs MArgs NameList VarList Assgt }
		If Rose(n)
			Return BytecodeNode.Join(n.key, Rose(n).arg, n)	'combine instruction sequences where they exist
		Else
			Return BytecodeNode.Nil(n.key, n)
		EndIf
	End Method
	Method IGNORE:Node(n:Node) { fname param rcount acount icount }
		Return n	'some things don't represent instructions at all and should be passed as data nodes
	End Method
	Method any:Node(n:Node)
		If Rose(n)
			Print "unhandled (passthrough): " + n.key
			Return BytecodeNode.Join(n.key, Rose(n).arg, n)
		Else
			Print "unhandled: " + n.key
			Return BytecodeNode.Nil(n.key, n)
		EndIf
	End Method
End Type

Type BytecodeNode Extends Node
	Field inst:TList, old:Node
	Function MakeL:BytecodeNode(key:String, ins:BlueInstr, old:Node = Null)
		Local n:BytecodeNode = New Self ; n.key = key ; n.inst = CreateList() ; n.old = old
		n.inst.AddLast(ins) ; Return n
	End Function
	Function MakeR:BytecodeNode(key:String, inst:BlueInstr[], old:Node = Null)
		Local n:BytecodeNode = New Self ; n.key = key ; n.inst = TList.FromArray(inst) ; n.old = old ; Return n
	End Function
	Function Join:BytecodeNode(key:String, els:Node[], old:Node = Null)
		Local n:BytecodeNode = New Self ; n.key = key ; n.inst = CreateList() ; n.old = old
		For Local e:BytecodeNode = EachIn els	'use of Eachin is important, as it autocasts
			For Local o:Object = EachIn e.inst
				n.inst.AddLast o
			Next
		Next
		Return n
	End Function
	Function Nil:BytecodeNode(key:String = "@", old:Node = Null)
		Return MakeR(key, Null, old)
	End Function
End Type

' Blue Moon
' FOLD delegate to generate assembly instructions

' this file is a Private Include

'SuperStrict

Type CompileFold
	Field opc:BlueOpcode = New BlueOpcode, bi:BlueInstr = New BlueInstr, f:BlueAssembly, _gq:TList = CreateList()

	Function Make:CompileFold(f:BlueAssembly)
		Local c:CompileFold = New Self ; c.f = f ; Return c
	End Function

	Method l_varref:Node(n:Leaf)
		Local v:VarDef = f.fd.vars[StrToNum(n.val)], op:Int
		If v.isClosure Then op = opc.SETLC Else op = opc.MOV
		Return BytecodeNode.MakeL(n.key, bi.Make(op, v.stkpos, f.Pop()), n)
	End Method
	Method l_closref:Node(n:Leaf)
		Local ref:Int = StrToNum(n.val)
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.SETUPV, ref, f.Pop()))
	End Method
	Method l_getfield:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, [n.arg[0]])
		Local k:Int = f.GetConst("1", Leaf(n.arg[1]).val), tab:Int = f.Pop()
		r.inst.AddLast bi.Make(opc.SETTAB, tab, f.Pop(), k)
		Return r
	End Method
	Method l_index:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg)
		Local ind:Int = f.Pop(), tab:Int = f.Pop(), val:Int = f.Pop()
		Local pv:BlueInstr = BlueInstr(r.inst.Last())
		If pv.op = opc.LOADSI And Abs(pv.c) < f.VSI_LIMIT And pv.c = Abs pv.c
			pv.op = opc.SETTABSI ; pv.ra = tab ; pv.rb = val
		Else
			r.inst.AddLast bi.Make(opc.SETTABI, tab, val, ind)
		EndIf
		Return r
	End Method
	Method l_discard:Node(n:Node)
		f.Pop() ; Return BytecodeNode.Nil()
	End Method

	Method LocalVar:Node(n:Rose)
		Local vars:Node[] = Rose(BytecodeNode(n.arg[1]).old).arg
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg)
		For Local vi:Int = vars.Length - 1 To 0 Step -1
			Local v:VarDef = f.fd.vars[StrToNum(Leaf(BytecodeNode(vars[vi]).old).val)]
			If v.isClosure Then r.inst.AddFirst bi.Make(opc.NEWUPV, v.stkpos, 0)
		Next
		Return r
	End Method

	Method ReturnS:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg)
		r.inst.AddLast(bi.MakeK(opc.RET, f.Peek(), n.arg.Length))
		f.Pop(n.arg.Length) ; Return r
	End Method
	Method return_va:Node(n:Node)
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.RETVA, 0, 0))
	End Method

	Method break:Node(n:Node)
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.JMP, -1, -1, -1))
	End Method
	Method RepeatS:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), _lt:TList = f.fd._lt, _gt:TList = f.fd._gt
		Local loopStart:LabelDef = New LabelDef, lsID:Int = _lt.Count(), loop:GotoDef = New GotoDef, lID:Int = _gt.Count()
		loop.tgt = loopStart ; _lt.AddLast loopStart ; _gt.AddLast loop
		Local lastop:BlueInstr = BlueInstr(r.inst.Last()), reg:Int = f.Pop()
		If lastop.op = opc.LNOT
			lastop.op = opc.JIF ; lastop.ra = lastop.rb ; lastop.rb = 0 ; lastop.c = lID	'replace NOT with inverted jump
		Else
			r.inst.AddLast bi.MakeK(opc.JNOT, reg, lID)
		EndIf
		r.inst.AddFirst bi.Make(opc.MOV, 0, 0, lsID)
		r.inst.AddLast bi.Make(opc.MOV, -1, -1, -1)
		Return r
	End Method
	Method IfS:Node(n:Rose) { WhileS }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, [n.arg[0]]), lastop:BlueInstr = BlueInstr(r.inst.Last())
		Local before:LabelDef, befID:Int, loopRound:GotoDef, loopID:Int, _lt:TList = f.fd._lt, _gt:TList = f.fd._gt
		If n.key = "WhileS"
			before = New LabelDef ; befID = _lt.Count() ; _lt.AddLast before
			loopRound = New GotoDef ;loopRound.tgt = before ; loopID = _gt.Count() ; _gt.AddLast loopRound
			r.inst.AddFirst bi.Make(opc.MOV, 0, 0, befID)
		EndIf
		Local postThen:LabelDef = New LabelDef, skipThen:GotoDef = New GotoDef, ptID:Int = _lt.Count(), stID:Int = _gt.Count()
		_lt.AddLast postThen ; skipThen.tgt = postThen ; _gt.AddLast skipThen
		If lastop.op = opc.LNOT
			lastop.op = opc.JIF ; lastop.ra = lastop.rb ; lastop.rb = 0 ; lastop.c = stID	'replace NOT with inverted jump
		Else
			r.inst.AddLast bi.MakeK(opc.JNOT, f.Peek(), stID)
		EndIf
		f.Pop()
		For Local op:BlueInstr = EachIn BytecodeNode(n.arg[1]).inst
			r.inst.AddLast op
		Next
		Local postElse:LabelDef, skipElse:GotoDef
		If n.arg.Length > 2	'there's an else
			postElse = New LabelDef ; skipElse = New GotoDef ; skipElse.tgt = postElse
			Local peID:Int = _lt.Count(), seID:Int = _gt.Count()
			_lt.AddLast postElse ; _gt.AddLast skipElse
			r.inst.AddLast bi.MakeK(opc.JMP, 0, seID)
			r.inst.AddLast bi.Make(opc.MOV, 0, 0, ptID)
			For Local op:BlueInstr = EachIn BytecodeNode(n.arg[2]).inst
				r.inst.AddLast op
			Next
			r.inst.AddLast bi.Make(opc.MOV, 0, 0, peID)
		Else
			If n.key = "WhileS" Then r.inst.AddLast bi.MakeK(opc.JMP, 0, loopID) ; r.inst.AddLast bi.Make(opc.MOV, -1, -1, -1)
			r.inst.AddLast bi.Make(opc.MOV, 0, 0, ptID)
		EndIf
		Return r
	End Method
	Method kand:Node(n:Rose) { kor }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, [n.arg[0]]), lastop:BlueInstr = BlueInstr(r.inst.Last())
		Local _lt:TList = f.fd._lt, _gt:TList = f.fd._gt
		Local postR:LabelDef = New LabelDef, skipR:GotoDef = New GotoDef, prID:Int = _lt.Count(), srID:Int = _gt.Count()
		_lt.AddLast postR ; skipR.tgt = postR ; _gt.AddLast skipR
		Local reg:Int = f.Pop()
		If lastop.op = opc.LNOT
			Local op:Int = opc.JIF ; If n.key = "kor" Then op = opc.JNOT
			lastop.op = op ; lastop.ra = lastop.rb ; lastop.rb = 0 ; lastop.c = srID	'replace NOT with inverted jump
		Else
			Local op:Int = opc.JNOT ; If n.key = "kor" Then op = opc.JIF
			r.inst.AddLast bi.MakeK(op, reg - 1, srID)
		EndIf
		For Local op:BlueInstr = EachIn BytecodeNode(n.arg[1]).inst
			r.inst.AddLast op
		Next
		r.inst.AddLast bi.Make(opc.MOV, reg - 1, reg)
		r.inst.AddLast bi.Make(opc.MOV, 0, 0, prID)
		Return r
	End Method

	Method fcall:Node(n:Rose) { pfcall tfcall va_fcall }	'"p" prefix = "procedure" i.e. no return value required
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), argc:Int = Int(Leaf(n.arg[3]).val), retc:Int = Int(Leaf(n.arg[2]).val)
		Local func:Int = f.Pop(argc + 1), args:Int = func + 1, op:Int = opc.CALL
		If n.key = "tfcall" Then op = opc.TCALL
		r.inst.AddLast bi.Make(op, func, args, argc)
		If n.key = "fcall"
			r.inst.AddLast bi.MakeK(opc.POSTCALL, f.Peek() + 1, retc) ; f.Push(retc)
		ElseIf n.key = "va_fcall"
			r.inst.AddLast bi.Make(opc.CALLINIT, -1, 0)
		EndIf
		Return r
	End Method
	Method mcall:Node(n:Rose) { pmcall tmcall va_mcall }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), argc:Int = Int(Leaf(n.arg[3]).val), retc:Int = Int(Leaf(n.arg[2]).val)
		Local func:Int = f.Push(), argv:Int = f.Pop(argc + 2), op:Int = opc.CALL
		Local ks:String = Leaf(Rose(BytecodeNode(n.arg[1]).old).arg[0]).val		'yeesh
		r.inst.AddLast bi.Make(opc.GETTAB, func, argv, f.GetConst("1", ks))
		If n.key = "tmcall" Then op = opc.TCALL
		r.inst.AddLast bi.Make(op, func, argv, argc + 1)
		If n.key = "mcall"
			r.inst.AddLast bi.MakeK(opc.POSTCALL, argv, retc) ; f.Push(retc)
		ElseIf n.key = "va_fcall"
			r.inst.AddLast bi.Make(opc.CALLINIT, -1, 0)
		EndIf
		Return r
	End Method
	Method dotdotdot:Node(n:Leaf)
		Local retc:Int = Int(n.val), r:BytecodeNode
		r = BytecodeNode.MakeL(n.key, bi.MakeK(opc.VARARG, f.Peek() + 1, retc))
		f.Push(retc)
		Return r
	End Method

	Method BinaryOp:Node(n:Rose) { plus minus mul div kmod pow dotdot lt gt leq geq eq neq }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), op:Int
		Select n.key
			Case "plus"   ; op = opc.ADD
			Case "minus"  ; op = opc.SUB
			Case "mul"    ; op = opc.MUL
			Case "div"    ; op = opc.DIV
			Case "kmod"   ; op = opc.NMOD
			Case "pow"    ; op = opc.POW
			Case "dotdot" ; op = opc.CAT

			Case "lt"     ; op = opc.LT
			Case "gt"     ; op = opc.LEQ
			Case "leq"    ; op = opc.LEQ
			Case "geq"    ; op = opc.LT
			Case "eq"     ; op = opc.EQ
			Case "neq"    ; op = opc.EQ
		End Select
		Local lop:Int = f.Pop(), rop:Int = f.Pop()
		If n.key = "geq" Or n.key = "gt"
			r.inst.AddLast bi.Make(op, f.Push(), rop, lop)	'technically this violates IEEE-754, but it's how Lua defines it
		Else
			r.inst.AddLast bi.Make(op, f.Push(), lop, rop)
			If n.key = "neq" Then r.inst.AddLast bi.Make(opc.LNOT, f.Peek(), f.Peek())
		EndIf
		Return r
	End Method
	Method UnaryOp:Node(n:Rose) { negate klen knot }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), op:Int
		Select n.key
			Case "negate" ; op = opc.UNM
			Case "klen"   ; op = opc.ALEN
			Case "knot"   ; op = opc.LNOT
			Case "posate" ; op = opc.UNP
		End Select
		Local arg:Int = f.Pop()
		r.inst.AddLast bi.Make(op, f.Push(), arg)
		Return r
	End Method

	Method dconst:Node(n:Leaf) { sconst }
		Local ki:Int = Int(n.val), op:Int = opc.LOADK
		If n.key = "dconst" And Abs(ki) < BlueAssembly.SI_LIMIT And Double(n.val) = Double(ki)
			op = opc.LOADSI
		Else
			ki = f.GetConst(String(n.key = "sconst"), n.val)
		EndIf
		Return BytecodeNode.MakeL(n.key, bi.MakeK(op, f.Push(), ki))
	End Method
	Method bconst:Node(n:Leaf) { nil }
		Local ins:BlueInstr
		Select n.val
			Case "true"  ; ins = bi.MakeK(opc.LOADBOOL, f.Push(), 1)
			Case "false" ; ins = bi.MakeK(opc.LOADBOOL, f.Push(), 0)
			Case "nil"   ; ins = bi.MakeK(opc.LOADNIL, f.Push(), 0)
		End Select
		Return BytecodeNode.MakeL(n.key, ins)
	End Method

	Method funref:Node(n:Leaf) { varref closref }
		Local i:BlueInstr, ref:Int = StrToNum(n.val)
		Select n.key
			Case "funref" ; i = bi.MakeK(opc.CLOSURE, f.Push(), ref)
			Case "varref"
				Local v:VarDef = f.fd.vars[ref], op:Int = opc.MOV ; If v.isClosure Then op = opc.GETLC
				i = bi.Make(op, f.Push(), v.stkpos)
			Case "closref" ; i = bi.Make(opc.GETUPV, f.Push(), ref)' + f.fd.vmax)
		End Select
		Return BytecodeNode.MakeL(n.key, i)
	End Method
	Method getfield:Node(n:Rose) { index }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), i:BlueInstr
		If n.key = "getfield"
			Local k:Int = f.GetConst("1", Leaf(n.arg[1]).val), tab:Int = f.Pop()
			r.inst.AddLast bi.Make(opc.GETTAB, f.Push(), tab, k)
		Else
			Local idx:Int = f.Pop(), tab:Int = f.Pop()
			Local pv:BlueInstr = BlueInstr(r.inst.Last())
			If pv.op = opc.LOADSI And Abs(pv.c) < f.VSI_LIMIT And pv.c = Abs pv.c
				pv.op = opc.GETTABSI ; pv.ra = f.Push() ; pv.rb = tab
			Else
				r.inst.AddLast bi.Make(opc.GETTABI, f.Push(), tab, idx)
			EndIf
		EndIf
		Return r
	End Method

	Method TableCons:Node(n:Rose)
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg), fld:BytecodeNode = Null
		Local rslot:Int = f.Push(), tslot:Int = -1
		For fld = EachIn n.arg	'work out where it's safe to put the table
			For Local in:BlueInstr = EachIn fld.inst
				If in.ra >= tslot Then tslot = in.ra + 1
			Next
		Next
		For fld = EachIn n.arg	'then patch the operations to point there
			For Local in:BlueInstr = EachIn fld.inst
				If in.ra = -1 And (in.op = opc.SETTAB Or in.op = opc.SETTABI ..
					Or in.op = opc.VAINIT Or in.op = opc.CALLINIT) Then in.ra = tslot
			Next
		Next
		If n.arg	'pass the ongoing index count to the variable initializer
			fld = BytecodeNode(n.arg[n.arg.Length - 2])
			If fld.key = "va_fcall" Or fld.key = "va_init"
				Local in:BlueInstr = BlueInstr(fld.inst.Last())
				in.c = Int(Leaf(n.arg[n.arg.Length - 1]).val)
			EndIf
		EndIf
		If tslot = -1 Then tslot = rslot Else tslot = f.Push(tslot - f.Peek())
		r.inst.AddFirst bi.Make(opc.NEWTAB, tslot, 0)
		If tslot <> rslot Then r.inst.AddLast bi.Make(opc.MOV, rslot, tslot)	'put the resulting table in the expected slot
		f.Pop(f.Peek() - rslot)	'...and correct the stack
		Return r
	End Method
	Method FieldIndex:Node(n:Rose) { FieldKey }
		Local r:BytecodeNode = BytecodeNode.Join(n.key, n.arg)
		Local val:Int = f.Pop(), idx:Int, op:Int
		If n.key = "FieldIndex"
			idx = f.Pop() ; op = opc.SETTABI
		Else
			idx = f.GetConst("1", Leaf(n.arg[0]).val) ; op = opc.SETTAB
		EndIf
		r.inst.AddLast bi.Make(op, -1, val, idx)
		Return r
	End Method
	Method va_init:Node(n:Node)
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.VAINIT, -1, 0))
	End Method

	Method Label:Node(n:Leaf)	'insert dummy instructions to remove in the next pass
		Return BytecodeNode.MakeL(n.key, bi.Make(opc.MOV, 0, 0, StrToNum(n.val)))
	End Method
	Method GotoS:Node(n:Leaf)	'resolve these in the next pass after removing dummy targets
		Return BytecodeNode.MakeL(n.key, bi.MakeK(opc.JMP, 0, StrToNum(n.val)))
	End Method

	Method PASSTHROUGH:Node(n:Node) { FuncBody Block ExpList FArgs MArgs NameList VarList Assgt }
		If Rose(n)
			Return BytecodeNode.Join(n.key, Rose(n).arg, n)	'combine instruction sequences where they exist
		Else
			Return BytecodeNode.Nil(n.key, n)
		EndIf
	End Method
	Method IGNORE:Node(n:Node) { fname param rcount acount icount }
		Return n	'some things don't represent instructions at all and should be passed as data nodes
	End Method
	Method any:Node(n:Node)
		If Rose(n)
			Print "unhandled (passthrough): " + n.key
			Return BytecodeNode.Join(n.key, Rose(n).arg, n)
		Else
			Print "unhandled: " + n.key
			Return BytecodeNode.Nil(n.key, n)
		EndIf
	End Method
End Type

Type BytecodeNode Extends Node
	Field inst:TList, old:Node
	Function MakeL:BytecodeNode(key:String, ins:BlueInstr, old:Node = Null)
		Local n:BytecodeNode = New Self ; n.key = key ; n.inst = CreateList() ; n.old = old
		n.inst.AddLast(ins) ; Return n
	End Function
	Function MakeR:BytecodeNode(key:String, inst:BlueInstr[], old:Node = Null)
		Local n:BytecodeNode = New Self ; n.key = key ; n.inst = TList.FromArray(inst) ; n.old = old ; Return n
	End Function
	Function Join:BytecodeNode(key:String, els:Node[], old:Node = Null)
		Local n:BytecodeNode = New Self ; n.key = key ; n.inst = CreateList() ; n.old = old
		For Local e:BytecodeNode = EachIn els	'use of Eachin is important, as it autocasts
			For Local o:Object = EachIn e.inst
				n.inst.AddLast o
			Next
		Next
		Return n
	End Function
	Function Nil:BytecodeNode(key:String = "@", old:Node = Null)
		Return MakeR(key, Null, old)
	End Function
End Type


Moved TCamera.Project/Unproject math to Prime.Math3D.

Strict

Import "entity.bmx"
Import "worldconfig.bmx"

Const CAMMODE_NONE	= 0
Const CAMMODE_PERSP	= 1
Const CAMMODE_ORTHO	= 2

Const CLSMODE_COLOR	= 1
Const CLSMODE_DEPTH	= 2

Const FOGMODE_NONE		= 0
Const FOGMODE_LINEAR	= 1

Type TCamera Extends TEntity
	Field _projmode
	Field _fogmode,_fogr#,_fogg#,_fogb#,_fognear#,_fogfar#
	Field _viewx,_viewy,_viewwidth,_viewheight
	Field _clsmode,_near#,_far#
	Field _zoom#

	Field _modelview:TMatrix=New TMatrix
	Field _projection:TMatrix=New TMatrix
	Field _viewport[4]
	Field _frustum:TFrustum

	Method Init:TCamera(config:TWorldConfig,parent:TEntity)
		Super.Init(config, parent)
		SetMode CAMMODE_PERSP
		SetFogMode FOGMODE_NONE
		SetFogRange 1,1000
		SetViewport 0,0,_config.Width,_config.Height
		SetClsMode CLSMODE_COLOR|CLSMODE_DEPTH
		SetColor 0,0,0
		SetRange 1,1000
		SetZoom 1.0
		Return Self
	End Method

	Method Lists[]()
		Return Super.Lists() + [WORLDLIST_CAMERA]
	End Method

	Method CopyData:TEntity(entity:TEntity)
		Local camera:TCamera = TCamera(entity)
		Local red,green,blue,fognear#,fogfar#,x,y,width,height,near#,far#
		camera.GetFogColor red,green,blue
		camera.GetFogRange fognear,fogfar
		camera.GetViewport x,y,width,height
		camera.GetRange near,far

		SetMode camera.GetMode()
		SetFogMode camera.GetFogMode()
		SetFogColor red,green,blue
		SetFogRange fognear,fogfar
		SetViewport x,y,width,height
		SetClsMode camera.GetClsMode()
		SetRange near,far
		SetZoom camera.GetZoom()
		Return Super.CopyData(entity)
	End Method

	Method Copy:TCamera(parent:TEntity=Null)
		Return TCamera(Super.Copy_(parent))
	End Method

	Method GetMode()
		Return _projmode
	End Method
	Method SetMode(mode)
		_projmode=mode
	End Method

	Method GetFogMode()
		Return _fogmode
	End Method
	Method SetFogMode(mode)
		_fogmode=mode
	End Method

	Method GetFogColor(red Var,green Var,blue Var)
		red=_fogr*255.0;green=_fogg*255.0;blue=_fogb*255.0
	End Method
	Method SetFogColor(red,green,blue)
		_fogr=red/255.0;_fogg=green/255.0;_fogb=blue/255.0
	End Method

	Method GetFogRange(near# Var,far# Var)
		near=_fognear;far=_fogfar
	End Method
	Method SetFogRange(near#,far#)
		_fognear=Max(near,0);_fogfar=Max(far,0.001)
	End Method

	Method GetViewport(x Var,y Var,width Var,height Var)
		x=_viewx;y=_viewy;width=_viewwidth;height=_viewheight
	End Method
	Method SetViewport(x,y,width,height)
		_viewx=x;_viewy=y;_viewwidth=width;_viewheight=height
	End Method

	Method GetClsMode()
		Return _clsmode
	End Method
	Method SetClsMode(mode)
		_clsmode=mode
	End Method

	Method GetRange(near# Var,far# Var)
		near=_near;far=_far
	End Method
	Method SetRange(near#,far#)
		_near=near;_far=far
	End Method

	Method GetZoom#()
		Return _zoom
	End Method
	Method SetZoom(zoom#)
		_zoom=zoom
	End Method

	Method GetFOV#()
		Return ATan(_zoom / 1.0)
	End Method
	Method SetFOV(angle#)
		_zoom = 1.0 / Tan(angle / 2.0)
	End Method

	Method GetEye:TRay()
		Local x#,y#,z#,dx#,dy#,dz#=1.0,o:TVector,d:TVector
		GetPosition x,y,z,True
		_matrix.TransformVec3 dx,dy,dz
		Return New TRay.Create(New TVector.Create3(x,y,z),New TVector.Create3(dx-x,dy-y,dz-x))
	End Method

	Method UpdateMatrices()
		_viewport[0]=_viewx
		_viewport[1]=_viewy
		_viewport[2]=_viewwidth
		_viewport[3]=_viewheight

		_modelview = _matrix.Inverse()

		Local ratio#=(Float(_viewwidth)/_viewheight)
		_projection = TMatrix.Scale(1,1,-1).Multiply(TMatrix.PerspectiveFovRH(ATan((1.0/(_zoom*ratio)))*2.0,ratio,_near,_far))

		_frustum=TFrustum.Extract(_modelview, _projection)
	End Method

	Method Project(target:Object,x# Var,y# Var, offset#[] = Null)
		Local z#
		TEntity.GetTargetPosition target,x,y,z

		If offset
			x :+ offset[0]
			y :+ offset[1]
			z :+ offset[2]
		EndIf

		Return TMatrix.Project(_modelview,_projection,_viewport,x,y,z)
	End Method

	Method Unproject(wx#,wy#,wz#,x# Var,y# Var,z# Var)
		Return TMatrix.Unproject(_modelview,_projection,_viewport,wx,wy,wz,x,y,z)
	End Method

	Method InView#(target:Object)
		Local x#,y#,z#,radius#
		Local entity:TEntity=TEntity(target),point#[]=Float[](target)
		If entity
			entity.GetCullParams x,y,z,radius
		ElseIf point
			x=point[0];y=point[1];z=point[2];radius=point[3]
		Else
			Return 0
		EndIf
		Return _frustum.IntersectsPoint(x,y,z,radius)
	End Method
End Type

Strict

Import "entity.bmx"
Import "worldconfig.bmx"

Const CAMMODE_NONE	= 0
Const CAMMODE_PERSP	= 1
Const CAMMODE_ORTHO	= 2

Const CLSMODE_COLOR	= 1
Const CLSMODE_DEPTH	= 2

Const FOGMODE_NONE		= 0
Const FOGMODE_LINEAR	= 1

Type TCamera Extends TEntity
	Field _projmode
	Field _fogmode,_fogr#,_fogg#,_fogb#,_fognear#,_fogfar#
	Field _viewx,_viewy,_viewwidth,_viewheight
	Field _clsmode,_near#,_far#
	Field _zoom#

	Field _modelview:TMatrix=New TMatrix
	Field _projection:TMatrix=New TMatrix
	Field _viewport[4]
	Field _frustum:TFrustum

	Method Init:TCamera(config:TWorldConfig,parent:TEntity)
		Super.Init(config, parent)
		SetMode CAMMODE_PERSP
		SetFogMode FOGMODE_NONE
		SetFogRange 1,1000
		SetViewport 0,0,_config.Width,_config.Height
		SetClsMode CLSMODE_COLOR|CLSMODE_DEPTH
		SetColor 0,0,0
		SetRange 1,1000
		SetZoom 1.0
		Return Self
	End Method

	Method Lists[]()
		Return Super.Lists() + [WORLDLIST_CAMERA]
	End Method

	Method CopyData:TEntity(entity:TEntity)
		Local camera:TCamera = TCamera(entity)
		Local red,green,blue,fognear#,fogfar#,x,y,width,height,near#,far#
		camera.GetFogColor red,green,blue
		camera.GetFogRange fognear,fogfar
		camera.GetViewport x,y,width,height
		camera.GetRange near,far

		SetMode camera.GetMode()
		SetFogMode camera.GetFogMode()
		SetFogColor red,green,blue
		SetFogRange fognear,fogfar
		SetViewport x,y,width,height
		SetClsMode camera.GetClsMode()
		SetRange near,far
		SetZoom camera.GetZoom()
		Return Super.CopyData(entity)
	End Method

	Method Copy:TCamera(parent:TEntity=Null)
		Return TCamera(Super.Copy_(parent))
	End Method

	Method GetMode()
		Return _projmode
	End Method
	Method SetMode(mode)
		_projmode=mode
	End Method

	Method GetFogMode()
		Return _fogmode
	End Method
	Method SetFogMode(mode)
		_fogmode=mode
	End Method

	Method GetFogColor(red Var,green Var,blue Var)
		red=_fogr*255.0;green=_fogg*255.0;blue=_fogb*255.0
	End Method
	Method SetFogColor(red,green,blue)
		_fogr=red/255.0;_fogg=green/255.0;_fogb=blue/255.0
	End Method

	Method GetFogRange(near# Var,far# Var)
		near=_fognear;far=_fogfar
	End Method
	Method SetFogRange(near#,far#)
		_fognear=Max(near,0);_fogfar=Max(far,0.001)
	End Method

	Method GetViewport(x Var,y Var,width Var,height Var)
		x=_viewx;y=_viewy;width=_viewwidth;height=_viewheight
	End Method
	Method SetViewport(x,y,width,height)
		_viewx=x;_viewy=y;_viewwidth=width;_viewheight=height
	End Method

	Method GetClsMode()
		Return _clsmode
	End Method
	Method SetClsMode(mode)
		_clsmode=mode
	End Method

	Method GetRange(near# Var,far# Var)
		near=_near;far=_far
	End Method
	Method SetRange(near#,far#)
		_near=near;_far=far
	End Method

	Method GetZoom#()
		Return _zoom
	End Method
	Method SetZoom(zoom#)
		_zoom=zoom
	End Method

	Method GetFOV#()
		Return ATan(_zoom / 1.0)
	End Method
	Method SetFOV(angle#)
		_zoom = 1.0 / Tan(angle / 2.0)
	End Method

	Method GetEye:TRay()
		Local x#,y#,z#,dx#,dy#,dz#=1.0,o:TVector,d:TVector
		GetPosition x,y,z,True
		_matrix.TransformVec3 dx,dy,dz
		Return New TRay.Create(New TVector.Create3(x,y,z),New TVector.Create3(dx-x,dy-y,dz-x))
	End Method

	Method UpdateMatrices()
		_viewport[0]=_viewx
		_viewport[1]=_viewy
		_viewport[2]=_viewwidth
		_viewport[3]=_viewheight

		_modelview = _matrix.Inverse()

		Local ratio#=(Float(_viewwidth)/_viewheight)
		_projection = TMatrix.Scale(1,1,-1).Multiply(TMatrix.PerspectiveFovRH(ATan((1.0/(_zoom*ratio)))*2.0,ratio,_near,_far))

		_frustum=TFrustum.Extract(_modelview, _projection)
	End Method

	Method Project(target:Object,x# Var,y# Var, offset#[] = Null)
		Local z#
		TEntity.GetTargetPosition target,x,y,z

		If offset
			x :+ offset[0]
			y :+ offset[1]
			z :+ offset[2]
		EndIf

		Local w#=1.0
		_modelview.TransformVec4 x,y,z,w
		_projection.TransformVec4 x,y,z,w
		If w=0 Return False
		x:/w;y:/w;z:/w

		x=x*0.5+0.5;y=-y*0.5+0.5;z=z*0.5+0.5;

		x=x*_viewport[2]+_viewport[0]
		y=y*_viewport[3]+_viewport[1]
		Return True
	End Method

	Method Unproject(wx#,wy#,wz#,x# Var,y# Var,z# Var)
		x=((wx-_viewport[0])*2/_viewport[2]) - 1
		y=((wy-_viewport[1])*2/_viewport[3]) - 1
		z=wz*2-1
		Local w#=1.0
		_projection.Multiply(_modelview).Inverse().TransformVec4 x,y,z,w
		If w=0 Return False
		x:/w;y:/-w;z:/-w
		Return True
	End Method

	Method InView#(target:Object)
		Local x#,y#,z#,radius#
		Local entity:TEntity=TEntity(target),point#[]=Float[](target)
		If entity
			entity.GetCullParams x,y,z,radius
		ElseIf point
			x=point[0];y=point[1];z=point[2];radius=point[3]
		Else
			Return 0
		EndIf
		Return _frustum.IntersectsPoint(x,y,z,radius)
	End Method
End Type


Patched possible bug

Patched possible bug

Strict

Rem
	bbdoc: Blitz3D model loader for MaxB3D
End Rem
Module MaxB3D.B3DLoader
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import MaxB3D.BoneAnimator
Import Prime.libB3D

Type TMeshLoaderB3D Extends TMeshLoader
	Field _mesh:TMesh

	Method Run(mesh:TMesh,stream:TStream,url:Object)
		Local model:TBB3DChunk = TBB3DChunk(url)
		If model=Null model = TBB3DChunk.Load(stream)
		If model=Null Return False

		Local olddir$=CurrentDir()
		If String(url) ChangeDir(ExtractDir(String(url)))

		Local texture:TTexture[model.texs.length]
		Local brush:TBrush[model.brus.length]

		For Local i=0 To texture.length-1
			Local chunk:TTEXSChunk=model.texs[i]
			texture[i]=_currentworld.AddTexture(chunk.file,chunk.flags)
			If texture[i]
				texture[i].SetBlend chunk.blend
				texture[i].SetPosition chunk.x_pos,chunk.y_pos
				texture[i].SetScale chunk.x_scale,chunk.y_scale
				texture[i].SetRotation chunk.rotation*(180.0/Pi)
				If chunk.flags&65536 texture[i].SetCoords 1
			EndIf
		Next

		For Local i=0 To brush.length-1
			Local chunk:TBRUSChunk=model.brus[i]
			brush[i]=_currentworld.AddBrush()
			brush[i].SetName chunk.name
			brush[i].SetColor chunk.red*255,chunk.green*255,chunk.blue*255;brush[i].SetAlpha chunk.alpha
			brush[i].SetShine chunk.shininess
			brush[i].SetBlend chunk.blend;brush[i].SetFX chunk.fx
			For Local j=0 To chunk.n_texs-1
				If chunk.texture_id[j]>-1 brush[i].SetTexture texture[chunk.texture_id[j]],j
			Next
		Next
		ChangeDir olddir

		' model.dump StandardIOStream

		If model.node
			ParseNode model.node,mesh,brush,mesh
			_mesh = Null
			Return True
		EndIf
		Return False
	End Method

	Method ParseNode(node:TNODEChunk,parent:TEntity,brush:TBrush[],entity:TEntity=Null)
		Local meshchunk:TMESHChunk=TMESHChunk(node.kind),bonechunk:TBONEChunk=TBONEChunk(node.kind)

		Select node.kind
		Case Null
			entity=_currentworld.AddPivot(parent)
		Case meshchunk
			If entity=Null entity=_currentworld.AddMesh("//empty",parent)
			Local mesh:TMesh=TMesh(entity)
			mesh._animator=New TBoneAnimator
			_mesh=mesh

			If meshchunk.brush_id>-1 entity.SetBrush brush[meshchunk.brush_id]

			Local vrts:TVRTSChunk=meshchunk.vrts
			Local vertsurface:TSurface=New TSurface
			vertsurface.Resize(vrts.xyz.length/3,0)

			For Local i=0 To vertsurface.CountVertices()-1
				vertsurface.SetCoords i,vrts.xyz[i*3+0],vrts.xyz[i*3+1],vrts.xyz[i*3+2]
				If vrts.nxyz vertsurface.SetNormal i,vrts.nxyz[i*3+0],vrts.nxyz[i*3+1],vrts.nxyz[i*3+2]
				If vrts.rgba vertsurface.SetColor i,vrts.rgba[i*4+0]*255,vrts.rgba[i*4+1]*255,vrts.rgba[i*4+2]*255,vrts.rgba[i*4+3]
				If vrts.SetSize()>1
					For Local j=0 To vrts.SetCount()-1
						vertsurface.SetTexCoords i,1-vrts.tex_coords[i][j,0],vrts.tex_coords[i][j,1],j
					Next
				EndIf
			Next

			For Local i=0 To meshchunk.tris.length-1
				Local tri:TTRISChunk=meshchunk.tris[i]
				Local surface:TSurface=vertsurface.Copy()
				surface.Resize(-1,tri.vertex_id.length/3)
				If tri.brush_id>-1 surface.SetBrush brush[tri.brush_id]
				For Local t=0 To surface.CountTriangles()-1
					surface.SetTriangle t,tri.vertex_id[t*3+0],tri.vertex_id[t*3+1],tri.vertex_id[t*3+2]
				Next
				If vrts.nxyz=Null surface.UpdateNormals
				mesh.AppendSurface surface
			Next
		Case bonechunk
			entity=_currentworld.AddBone(parent)
			Local bone:TBone=TBone(entity)

			For Local surface:TSurface=EachIn _mesh._surfaces
				bone.AddSurface surface
				For Local i=0 To bonechunk.vertex_id.length-1
					bone.AddVertex surface,bonechunk.vertex_id[i],bonechunk.weight[i]
				Next
			Next

			Local ident_matrix:TMatrix=TMatrix.Identity()
			For Local chunk:TKEYSChunk=EachIn node.keys
				Local keys:TAnimKey[chunk.frame.length]
				Local pos#[]=chunk.position,rot#[]=chunk.rotation,scl#[]=chunk.scale
				For Local i=0 To chunk.frame.length-1
					Local bonekey:TBoneKey=New TBoneKey

					If pos.length>0 bonekey._px=pos[i*3+0];bonekey._py=pos[i*3+1];bonekey._pz=pos[i*3+2]
					If rot.length>0 bonekey._rw=rot[i*4+0];bonekey._rx=rot[i*4+1];bonekey._ry=rot[i*4+2];bonekey._rz=rot[i*4+3]
					If scl.length>0 bonekey._sx=scl[i*3+0];bonekey._sy=scl[i*3+1];bonekey._sz=scl[i*3+2]

					Local key:TAnimKey=New TAnimKey
					key._frame=chunk.frame[i]
					key._object=bonekey
					keys[i]=key
				Next
				TBoneAnimator(_mesh._animator).AddBone bone,keys
			Next
		Default
			entity=_currentworld.AddPivot(parent)
		End Select

		entity.SetName node.name
		entity.SetPosition node.position[0],node.position[1],node.position[2]
		Local pitch#,yaw#,roll#
		TQuaternion.Euler node.rotation[0],node.rotation[1],node.rotation[2],node.rotation[3],pitch,yaw,roll
		entity.SetRotation pitch,yaw,roll
		entity.SetScale node.scale[0],node.scale[1],node.scale[2]

		For Local child:TNODEChunk=EachIn node.node
			ParseNode child,entity,brush
		Next
		If TMesh(entity) If TBoneAnimator(TMesh(entity)._animator)._root=Null TMesh(entity)._animator=Null
		If TBone(entity) TBone(entity)._start_matrix=entity.GetMatrix()
	End Method

	Method Info$()
		Return "Blitz3D|b3d"
	End Method
	Method ModuleName$()
		Return "b3dloader"
	End Method
End Type
New TMeshLoaderB3D

Strict

Rem
	bbdoc: Blitz3D model loader for MaxB3D
End Rem
Module MaxB3D.B3DLoader
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import MaxB3D.BoneAnimator
Import Prime.libB3D

Type TMeshLoaderB3D Extends TMeshLoader
	Field _mesh:TMesh

	Method Run(mesh:TMesh,stream:TStream,url:Object)
		Local model:TBB3DChunk = TBB3DChunk(url)
		If model=Null model = TBB3DChunk.Load(stream)
		If model=Null Return False

		Local olddir$=CurrentDir()
		If String(url) ChangeDir(ExtractDir(String(url)))

		Local texture:TTexture[model.texs.length]
		Local brush:TBrush[model.brus.length]

		For Local i=0 To texture.length-1
			Local chunk:TTEXSChunk=model.texs[i]
			texture[i]=_currentworld.AddTexture(chunk.file,chunk.flags)
			If texture[i]
				texture[i].SetBlend chunk.blend
				texture[i].SetPosition chunk.x_pos,chunk.y_pos
				texture[i].SetScale chunk.x_scale,chunk.y_scale
				texture[i].SetRotation chunk.rotation*(180.0/Pi)
				If chunk.flags&65536 texture[i].SetCoords 1
			EndIf
		Next

		For Local i=0 To brush.length-1
			Local chunk:TBRUSChunk=model.brus[i]
			brush[i]=_currentworld.AddBrush()
			brush[i].SetName chunk.name
			brush[i].SetColor chunk.red*255,chunk.green*255,chunk.blue*255;brush[i].SetAlpha chunk.alpha
			brush[i].SetShine chunk.shininess
			brush[i].SetBlend chunk.blend;brush[i].SetFX chunk.fx
			For Local j=0 To chunk.n_texs-1
				If chunk.texture_id[j]>-1 brush[i].SetTexture texture[chunk.texture_id[j]],j
			Next
		Next
		ChangeDir olddir

		' model.dump StandardIOStream

		If model.node
			ParseNode model.node,mesh,brush,mesh
			Return True
		EndIf
		Return False
	End Method

	Method ParseNode(node:TNODEChunk,parent:TEntity,brush:TBrush[],entity:TEntity=Null)
		Local meshchunk:TMESHChunk=TMESHChunk(node.kind),bonechunk:TBONEChunk=TBONEChunk(node.kind)

		Select node.kind
		Case Null
			entity=_currentworld.AddPivot(parent)
		Case meshchunk
			If entity=Null entity=_currentworld.AddMesh("//empty",parent)
			Local mesh:TMesh=TMesh(entity)
			mesh._animator=New TBoneAnimator
			_mesh=mesh

			If meshchunk.brush_id>-1 entity.SetBrush brush[meshchunk.brush_id]

			Local vrts:TVRTSChunk=meshchunk.vrts
			Local vertsurface:TSurface=New TSurface
			vertsurface.Resize(vrts.xyz.length/3,0)

			For Local i=0 To vertsurface.CountVertices()-1
				vertsurface.SetCoords i,vrts.xyz[i*3+0],vrts.xyz[i*3+1],vrts.xyz[i*3+2]
				If vrts.nxyz vertsurface.SetNormal i,vrts.nxyz[i*3+0],vrts.nxyz[i*3+1],vrts.nxyz[i*3+2]
				If vrts.rgba vertsurface.SetColor i,vrts.rgba[i*4+0]*255,vrts.rgba[i*4+1]*255,vrts.rgba[i*4+2]*255,vrts.rgba[i*4+3]
				If vrts.SetSize()>1
					For Local j=0 To vrts.SetCount()-1
						vertsurface.SetTexCoords i,1-vrts.tex_coords[i][j,0],vrts.tex_coords[i][j,1],j
					Next
				EndIf
			Next

			For Local i=0 To meshchunk.tris.length-1
				Local tri:TTRISChunk=meshchunk.tris[i]
				Local surface:TSurface=vertsurface.Copy()
				surface.Resize(-1,tri.vertex_id.length/3)
				If tri.brush_id>-1 surface.SetBrush brush[tri.brush_id]
				For Local t=0 To surface.CountTriangles()-1
					surface.SetTriangle t,tri.vertex_id[t*3+0],tri.vertex_id[t*3+1],tri.vertex_id[t*3+2]
				Next
				If vrts.nxyz=Null surface.UpdateNormals
				mesh.AppendSurface surface
			Next
		Case bonechunk
			entity=_currentworld.AddBone(parent)
			Local bone:TBone=TBone(entity)

			For Local surface:TSurface=EachIn _mesh._surfaces
				bone.AddSurface surface
				For Local i=0 To bonechunk.vertex_id.length-1
					bone.AddVertex surface,bonechunk.vertex_id[i],bonechunk.weight[i]
				Next
			Next

			Local ident_matrix:TMatrix=TMatrix.Identity()
			For Local chunk:TKEYSChunk=EachIn node.keys
				Local keys:TAnimKey[chunk.frame.length]
				Local pos#[]=chunk.position,rot#[]=chunk.rotation,scl#[]=chunk.scale
				For Local i=0 To chunk.frame.length-1
					Local bonekey:TBoneKey=New TBoneKey

					If pos.length>0 bonekey._px=pos[i*3+0];bonekey._py=pos[i*3+1];bonekey._pz=pos[i*3+2]
					If rot.length>0 bonekey._rw=rot[i*4+0];bonekey._rx=rot[i*4+1];bonekey._ry=rot[i*4+2];bonekey._rz=rot[i*4+3]
					If scl.length>0 bonekey._sx=scl[i*3+0];bonekey._sy=scl[i*3+1];bonekey._sz=scl[i*3+2]

					Local key:TAnimKey=New TAnimKey
					key._frame=chunk.frame[i]
					key._object=bonekey
					keys[i]=key
				Next
				TBoneAnimator(_mesh._animator).AddBone bone,keys
			Next
		Default
			entity=_currentworld.AddPivot(parent)
		End Select

		entity.SetName node.name
		entity.SetPosition node.position[0],node.position[1],node.position[2]
		Local pitch#,yaw#,roll#
		TQuaternion.Euler node.rotation[0],node.rotation[1],node.rotation[2],node.rotation[3],pitch,yaw,roll
		entity.SetRotation pitch,yaw,roll
		entity.SetScale node.scale[0],node.scale[1],node.scale[2]

		For Local child:TNODEChunk=EachIn node.node
			ParseNode child,entity,brush
		Next
		If TMesh(entity) If TBoneAnimator(TMesh(entity)._animator)._root=Null TMesh(entity)._animator=Null
		If TBone(entity) TBone(entity)._start_matrix=entity.GetMatrix()
	End Method

	Method Info$()
		Return "Blitz3D|b3d"
	End Method
	Method ModuleName$()
		Return "b3dloader"
	End Method
End Type
New TMeshLoaderB3D


Removed some test code

Removed some test code

refs gh-6

SuperStrict

Framework brl.blitz
Import brl.standardio
Import brl.maxutil
Import brl.ramstream

Import bah.volumes
Import gman.zipengine
Import htbaapub.rest

Import duct.variables
Import duct.objectmap
Import duct.json
Import duct.locale
Import duct.clapp
Import duct.time

Incbin "locales/en.loc"

Include "src/logger.bmx"
Include "src/errors.bmx"
Include "src/config.bmx"
Include "src/dependencies.bmx"
Include "src/module.bmx"
Include "src/sources.bmx"
Include "src/utils.bmx"
Include "src/impl/help.bmx"
Include "src/impl/version.bmx"
Include "src/impl/modpath.bmx"
Include "src/impl/install.bmx"
Include "src/impl/update.bmx"
Include "src/impl/list.bmx"

Global logger:mxLogger = New mxLogger
Global mainapp:mxApp
New mxApp.Create() ' Skip the first element because it is the program's location
mainapp.SetArgs(AppArgs[1..])
mainapp.Run()

Rem
	bbdoc: Maximus app.
	about: This handles the basic flow of the program (initiation, parsing, command calling, exiting..)
End Rem
Type mxApp Extends dCLApp

	Const c_version:String = "0.9.0"
	Const c_configfile:String = "maximus.config"

	Field m_apppath:String
	Field m_maxpath:String, m_binpath:String, m_modpath:String

	Field m_confighandler:mxConfigHandler
	Field m_defaultlocale:dLocale, m_locale:dLocale
	Field m_sourcesfile:String = "sources", m_sourcesurl:String = "http://maximus.htbaa.com/module/sources/json"
	Field m_useragent:String
	Field m_autoupdatesources:Int = True

	Field m_sourceshandler:mxSourcesHandler

	Field m_updateimpl:mxUpdateImpl
	Field m_sourcesupdated:Int

	Rem
		bbdoc: Create a new mxApp.
		returns: Itself.
	End Rem
	Method Create:mxApp()
		mainapp = Self
		Super.Create()
		Return Self
	End Method

	Rem
		bbdoc: Called when the application is created.
		returns: Nothing.
	End Rem
	Method OnInit()
		m_apppath = GetUserAppDir() + "/"
		?Linux
		m_apppath:+ ".maximus/"
		?Not Linux
		m_apppath:+ "maximus/"
		?
		If FileType(m_apppath) = FILETYPE_NONE
			If Not CreateDir(m_apppath, False)
				ThrowError(_s("error.createperms", [m_apppath]))
			End If
		End If
		ChangeDir(m_apppath)
		m_confighandler = New mxConfigHandler.Create(m_apppath + c_configfile)
		m_confighandler.LoadDefaultLocale()
		m_confighandler.Load()
		If Not m_maxpath
			Try
				SetMaxPath(BlitzMaxPath())
			Catch e:Object
				ThrowError(_s("error.notfound.maxenv"))
			End Try
		End If
		If Not m_modpath Then SetModPath(m_maxpath + "/mod", False)
		m_arghandler = New dArgumentHandler.Create()
		m_arghandler.AddArgImpl(New mxHelpImpl)
		m_arghandler.AddArgImpl(New mxVersionImpl)
		m_arghandler.AddArgImpl(New mxModPathImpl)
		m_arghandler.AddArgImpl(New mxInstallImpl)
		m_updateimpl = New mxUpdateImpl
		m_arghandler.AddArgImpl(m_updateimpl)
		m_arghandler.AddArgImpl(New mxListImpl)
		UpdateSources()
		m_sourceshandler = New mxSourcesHandler.FromFile(m_apppath + m_sourcesfile)
		If Not m_sourceshandler
			' Don't throw an error here, the user may be updating the sources (an error will occur otherwise)
			logger.LogWarning(_s("error.load.sources.file", [m_apppath + m_sourcesfile]))
		End If
		Local os:String
		?Win32
			os = "Windows"
		?Linux
			os = "Linux"
		?MacOS
			os = "MacOS"
		?
		m_useragent = "Maximus/" + c_version + " (" + os + "; " + m_locale.GetName() + ")"
	End Method

	Rem
		bbdoc: Called when the application is ended (this includes #ThrowError calls).
		returns: Nothing.
	End Rem
	Method OnExit()
	End Method

	Rem
		bbdoc: Run the application.
		returns: Nothing.
	End Rem
	Method Run()
		Local argimpl:dArgumentImplementation, isopt:Int
		For Local arg:dIdentifier = EachIn m_arguments
			argimpl = m_arghandler.GetArgImplWithAlias(arg.GetName())
			isopt = (arg.GetName()[0] = 45)
			If argimpl
				If isopt
					argimpl.SetCallConvention(dCallConvention.OPTION)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
				Else
					argimpl.SetCallConvention(dCallConvention.COMMAND)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
					Exit
				End If
			Else
				If isopt
					ThrowCommonError(mxOptErrors.UNKNOWN, arg.GetName())
				Else
					ThrowCommonError(mxCmdErrors.UNKNOWN, arg.GetName())
				End If
			End If
		Next

		OnExit()
	End Method

	Rem
		bbdoc: Update the sources list.
		returns: Nothing.
		about: Updates when: the sources file is greater than 23 hours old; the sources file does not exist.
	End Rem
	Method UpdateSources()
		If m_autoupdatesources And Not m_sourcesupdated
			Local ctime:dTime, ftime:dTime
			ctime = New dTime.CreateFromCurrent()
			ftime = New dTime.CreateFromFile(m_apppath + m_sourcesfile)
			'DebugLog("Auto-update file time: c" + ctime.Get() + " | " + ftime.Get() + ", hours: " + (ctime.Get() - ftime.Get()) / 3600)
			If Not ftime Or (ctime.Get() - ftime.Get()) / 3600 > 23
				logger.LogMessage(_s("arg.update.autoupdate"))
				m_updateimpl.SetCallConvention(dCallConvention.COMMAND)
				m_updateimpl.SetArgs(New dIdentifier.Create())
				m_updateimpl.CheckArgs()
				m_updateimpl.Execute()
			End If
		End If
	End Method

	Rem
		bbdoc: Set the application's arguments.
		returns: Nothing.
		about: NOTE: This will resolve to '--help' if the given args are Null.
	End Rem
	Method SetArgs(args:String[])
		If Not args
			args = ["--help"]
		End If
		ParseArguments(args, False, 1)
	End Method

	Rem
		bbdoc: Set the module path.
		returns: Nothing.
		about: This will throw an error if the module path could not be found.
	End Rem
	Method SetModPath(modpath:String, logchange:Int = True)
		m_modpath = FixPathEnding(modpath, True)
		If FileType(m_modpath) = FILETYPE_DIR
			If logchange Then logger.LogMessage(_s("message.setmodpath", [m_modpath]))
		Else
			ThrowError(_s("error.notfound.modpath", [m_modpath]))
		End If
	End Method

	Rem
		bbdoc: Set the path to the BlitzMax installation.
		returns: Nothing.
		about: This will throw an error if the path could not be found.
	End Rem
	Method SetMaxPath(maxpath:String)
		m_maxpath = FixPathEnding(maxpath, True)
		m_binpath = m_maxpath + "/bin"
		Local bmk:String
		?Win32
			bmk = "/bmk.exe"
		?Not Win32
			bmk = "/bmk"
		?
		If FileType(m_maxpath) = FILETYPE_NONE
			ThrowError(_s("error.notfound.maxpath", [m_maxpath]))
		Else If FileType(m_binpath + bmk) = FILETYPE_NONE
			ThrowError(_s("error.notfound.bmk", [m_binpath + bmk]))
		End If
	End Method

	Rem
		bbdoc: Set the sources url.
		returns: Nothing.
	End Rem
	Method SetSourcesUrl(url:String)
		m_sourcesurl = url
	End Method

	Rem
		bbdoc: Set the sources file.
		returns: Nothing.
		about: NOTE: This will throw an error if the path is Null (and, later, a warning will be logged if the path does not exist).
	End Rem
	Method SetSourcesFile(file:String)
		If Not file
			ThrowError(_s("error.sources.setfile"))
		End If
		m_sourcesfile = file
	End Method

End Type

Rem
	bbdoc: Throw an error (terminating the application).
	returns: Nothing.
End Rem
Function ThrowError(error:String)
	logger.LogError(error)
	mainapp.OnExit()
	End
End Function

Rem
	bbdoc: Get the localized text for the given identifier.
	returns: The translated text.
End Rem
Function _s:String(iden:String, extra:String[] = Null)
	Global replacer:dTextReplacer = New dTextReplacer
	Local ltext:dLocalizedText
	If mainapp.m_locale Then ltext = mainapp.m_locale.TextFromStructureL(iden)
	If ltext Then ltext = mainapp.m_defaultlocale.TextFromStructureL(iden)
	If ltext
		replacer.SetString(ltext.GetValue())
		replacer.AutoReplacements("{", "}")
		If extra
			Local i:Int
			For Local rep:dTextReplacement = EachIn replacer.GetList()
				If i > extra.Length Then Exit
				rep.SetReplacement(extra[i])
				i:+ 1
			Next
		End If
		Return replacer.DoReplacements()
	Else
		DebugLog("Failed to find localized text from structure: ~q" + iden + "~q")
	End If
	Return Null
End Function

SuperStrict

Framework brl.blitz
Import brl.standardio
Import brl.maxutil
Import brl.ramstream

Import bah.volumes
Import gman.zipengine
Import htbaapub.rest

Import duct.variables
Import duct.objectmap
Import duct.json
Import duct.locale
Import duct.clapp
Import duct.time

Incbin "locales/en.loc"

Include "src/logger.bmx"
Include "src/errors.bmx"
Include "src/config.bmx"
Include "src/dependencies.bmx"
Include "src/module.bmx"
Include "src/sources.bmx"
Include "src/utils.bmx"
Include "src/impl/help.bmx"
Include "src/impl/version.bmx"
Include "src/impl/modpath.bmx"
Include "src/impl/install.bmx"
Include "src/impl/update.bmx"
Include "src/impl/list.bmx"

Global logger:mxLogger = New mxLogger
Global mainapp:mxApp
New mxApp.Create() ' Skip the first element because it is the program's location
mainapp.SetArgs(AppArgs[1..])
mainapp.Run()

Rem
	bbdoc: Maximus app.
	about: This handles the basic flow of the program (initiation, parsing, command calling, exiting..)
End Rem
Type mxApp Extends dCLApp

	Const c_version:String = "0.9.0"
	Const c_configfile:String = "maximus.config"

	Field m_apppath:String
	Field m_maxpath:String, m_binpath:String, m_modpath:String

	Field m_confighandler:mxConfigHandler
	Field m_defaultlocale:dLocale, m_locale:dLocale
	Field m_sourcesfile:String = "sources", m_sourcesurl:String = "http://maximus.htbaa.com/module/sources/json"
	Field m_useragent:String
	Field m_autoupdatesources:Int = True

	Field m_sourceshandler:mxSourcesHandler

	Field m_updateimpl:mxUpdateImpl
	Field m_sourcesupdated:Int

	Rem
		bbdoc: Create a new mxApp.
		returns: Itself.
	End Rem
	Method Create:mxApp()
		mainapp = Self
		Super.Create()
		Return Self
	End Method

	Rem
		bbdoc: Called when the application is created.
		returns: Nothing.
	End Rem
	Method OnInit()
		m_apppath = GetUserAppDir() + "/"
		?Linux
		m_apppath:+ ".maximus/"
		?Not Linux
		m_apppath:+ "maximus/"
		?
		If FileType(m_apppath) = FILETYPE_NONE
			If Not CreateDir(m_apppath, False)
				ThrowError(_s("error.createperms", [m_apppath]))
			End If
		End If
		ChangeDir(m_apppath)
		m_confighandler = New mxConfigHandler.Create(m_apppath + c_configfile)
		m_confighandler.LoadDefaultLocale()
		m_confighandler.Load()
		If Not m_maxpath
			Try
				SetMaxPath(BlitzMaxPath())
			Catch e:Object
				ThrowError(_s("error.notfound.maxenv"))
			End Try
		End If
		If Not m_modpath Then SetModPath(m_maxpath + "/mod", False)
		m_arghandler = New dArgumentHandler.Create()
		m_arghandler.AddArgImpl(New mxHelpImpl)
		m_arghandler.AddArgImpl(New mxVersionImpl)
		m_arghandler.AddArgImpl(New mxModPathImpl)
		m_arghandler.AddArgImpl(New mxInstallImpl)
		m_updateimpl = New mxUpdateImpl
		m_arghandler.AddArgImpl(m_updateimpl)
		m_arghandler.AddArgImpl(New mxListImpl)
		UpdateSources()
		m_sourceshandler = New mxSourcesHandler.FromFile(m_apppath + m_sourcesfile)
		If Not m_sourceshandler
			' Don't throw an error here, the user may be updating the sources (an error will occur otherwise)
			logger.LogWarning(_s("error.load.sources.file", [m_apppath + m_sourcesfile]))
		End If
		Local os:String
		?Win32
			os = "Windows"
		?Linux
			os = "Linux"
		?MacOS
			os = "MacOS"
		?
		m_useragent = "Maximus/" + c_version + " (" + os + "; " + m_locale.GetName() + ")"
	End Method

	Rem
		bbdoc: Called when the application is ended (this includes #ThrowError calls).
		returns: Nothing.
	End Rem
	Method OnExit()
	End Method

	Rem
		bbdoc: Run the application.
		returns: Nothing.
	End Rem
	Method Run()
		Local argimpl:dArgumentImplementation, isopt:Int
		For Local arg:dIdentifier = EachIn m_arguments
			argimpl = m_arghandler.GetArgImplWithAlias(arg.GetName())
			isopt = (arg.GetName()[0] = 45)
			If argimpl
				If isopt
					argimpl.SetCallConvention(dCallConvention.OPTION)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
				Else
					argimpl.SetCallConvention(dCallConvention.COMMAND)
					argimpl.SetArgs(arg)
					argimpl.CheckArgs()
					argimpl.Execute()
					Exit
				End If
			Else
				If isopt
					ThrowCommonError(mxOptErrors.UNKNOWN, arg.GetName())
				Else
					ThrowCommonError(mxCmdErrors.UNKNOWN, arg.GetName())
				End If
			End If
		Next
		Rem
		' Test version snatching
		Local time:Int = MilliSecs(), ver:String
		For Local modid:String = EachIn mxModUtils.GetModules().KeyEnumerator()
			ver = mxModUtils.GetInstalledVersionFromVerID(modid)
			If ver And Not (ver = "dev")
				DebugLog("versioned-id: ~q" + modid + "/" + ver + "~q")
			Else If Not ver
				DebugLog("Failed to get version from " + modid)
			End If
		Next
		DebugLog("timed: " + mxModUtils.GetModules().Count() + " modules in " + (MilliSecs() - time) + "ms")
		End Rem
		OnExit()
	End Method

	Rem
		bbdoc: Update the sources list.
		returns: Nothing.
		about: Updates when: the sources file is greater than 23 hours old; the sources file does not exist.
	End Rem
	Method UpdateSources()
		If m_autoupdatesources And Not m_sourcesupdated
			Local ctime:dTime, ftime:dTime
			ctime = New dTime.CreateFromCurrent()
			ftime = New dTime.CreateFromFile(m_apppath + m_sourcesfile)
			'DebugLog("Auto-update file time: c" + ctime.Get() + " | " + ftime.Get() + ", hours: " + (ctime.Get() - ftime.Get()) / 3600)
			If Not ftime Or (ctime.Get() - ftime.Get()) / 3600 > 23
				logger.LogMessage(_s("arg.update.autoupdate"))
				m_updateimpl.SetCallConvention(dCallConvention.COMMAND)
				m_updateimpl.SetArgs(New dIdentifier.Create())
				m_updateimpl.CheckArgs()
				m_updateimpl.Execute()
			End If
		End If
	End Method

	Rem
		bbdoc: Set the application's arguments.
		returns: Nothing.
		about: NOTE: This will resolve to '--help' if the given args are Null.
	End Rem
	Method SetArgs(args:String[])
		If Not args
			args = ["--help"]
		End If
		ParseArguments(args, False, 1)
	End Method

	Rem
		bbdoc: Set the module path.
		returns: Nothing.
		about: This will throw an error if the module path could not be found.
	End Rem
	Method SetModPath(modpath:String, logchange:Int = True)
		m_modpath = FixPathEnding(modpath, True)
		If FileType(m_modpath) = FILETYPE_DIR
			If logchange Then logger.LogMessage(_s("message.setmodpath", [m_modpath]))
		Else
			ThrowError(_s("error.notfound.modpath", [m_modpath]))
		End If
	End Method

	Rem
		bbdoc: Set the path to the BlitzMax installation.
		returns: Nothing.
		about: This will throw an error if the path could not be found.
	End Rem
	Method SetMaxPath(maxpath:String)
		m_maxpath = FixPathEnding(maxpath, True)
		m_binpath = m_maxpath + "/bin"
		Local bmk:String
		?Win32
			bmk = "/bmk.exe"
		?Not Win32
			bmk = "/bmk"
		?
		If FileType(m_maxpath) = FILETYPE_NONE
			ThrowError(_s("error.notfound.maxpath", [m_maxpath]))
		Else If FileType(m_binpath + bmk) = FILETYPE_NONE
			ThrowError(_s("error.notfound.bmk", [m_binpath + bmk]))
		End If
	End Method

	Rem
		bbdoc: Set the sources url.
		returns: Nothing.
	End Rem
	Method SetSourcesUrl(url:String)
		m_sourcesurl = url
	End Method

	Rem
		bbdoc: Set the sources file.
		returns: Nothing.
		about: NOTE: This will throw an error if the path is Null (and, later, a warning will be logged if the path does not exist).
	End Rem
	Method SetSourcesFile(file:String)
		If Not file
			ThrowError(_s("error.sources.setfile"))
		End If
		m_sourcesfile = file
	End Method

End Type

Rem
	bbdoc: Throw an error (terminating the application).
	returns: Nothing.
End Rem
Function ThrowError(error:String)
	logger.LogError(error)
	mainapp.OnExit()
	End
End Function

Rem
	bbdoc: Get the localized text for the given identifier.
	returns: The translated text.
End Rem
Function _s:String(iden:String, extra:String[] = Null)
	Global replacer:dTextReplacer = New dTextReplacer
	Local ltext:dLocalizedText
	If mainapp.m_locale Then ltext = mainapp.m_locale.TextFromStructureL(iden)
	If ltext Then ltext = mainapp.m_defaultlocale.TextFromStructureL(iden)
	If ltext
		replacer.SetString(ltext.GetValue())
		replacer.AutoReplacements("{", "}")
		If extra
			Local i:Int
			For Local rep:dTextReplacement = EachIn replacer.GetList()
				If i > extra.Length Then Exit
				rep.SetReplacement(extra[i])
				i:+ 1
			Next
		End If
		Return replacer.DoReplacements()
	Else
		DebugLog("Failed to find localized text from structure: ~q" + iden + "~q")
	End If
	Return Null
End Function