filled out var/table ops

filled out var/table ops

' Blue Moon
' JIT engine/opcodes

' this is the core of the runtime: the machine code generator and opcodes
' it's a halfway blend between JIT and interpreter: a basic JIT generates machine code
' for "unrolled" interpreter loops, theoretically (ha!) providing a speed boost
' it can also "inline" selected operations, coming closer to being a true JIT

' for now this is an Include rather than Import lib due to recursive dependency
' we'll fix that later

Rem
SuperStrict

Import "blueerror.bmx"
Import "bluevm.bmx"
Import etc etc...
End Rem

'Private	'may need to rename and open these (in the shared interface?)
Extern
	Type Stack
		Field retIP:Byte Ptr, prevBase:Stack, varp:Long Ptr, func:Bytecode, _:Long Ptr, argv:Long Ptr, retv:Long Ptr, argc:Short, retc:Short
	End Type
	Type Bytecode
		Field mcode:Byte Ptr, idMod:Int, kcount:Int, pcount:Int, upvars:Int, frameSz:Int, icount:Int, vm:Byte Ptr
	End Type
End Extern
'Public

Type BlueJIT Final
	Const PROLOGUESZ:Int = 25, ISIZE:Int = 5

	Global opTbl:Int(s:Stack, b:Bytecode, r:Byte Ptr)[], opc:BlueOpcode
	Global Prologue:Int[] = [ ..
		$90, $90, ..
		$8b, $44, $24, $04, ..                     'mov 4(%esp), %eax
		$89, $44, $24, $f4, ..                     'mov %eax, -12(%esp)
		$c7, $44, $24, $f8, $FF, $FF, $FF, $FF, .. 'movl ########, -8(%esp)
		$89, $64, $24, $fc, ..                     'mov %esp, -4(%esp)
		$83, $ec, $0c..                            'sub $12, %esp
	]

	Function Compile:Byte Ptr(vm:BlueVM, ins:Byte Ptr, icount:Int, bytecode:Bytecode)
		?Not x86
		RuntimeError "The Blue Moon JIT does not support your platform (x86-32 only at this time)"
		?
		Assert SizeOf(0:Long) = SizeOf(0:Double) And SizeOf(0:Int) = 4 And SizeOf(0:Long) = 8 And SizeOf(Byte Ptr(0)) = 4, ..
			"assumptions about platform datatype sizes are invalid"

		If opTbl = Null Then InitOpTbl()

		' compute executable code size
		Local codesize:Int = PROLOGUESZ, opPos:Int[icount]	'since sizes are irregular, looking up offsets is easiest
		For Local i:Int = 0 Until icount
			opPos[i] = codesize	'finalized below
			Select ins[i * 8]
				Case opc.SETTAB, opc.GETTAB
					codesize :+ ISIZE * 2
				Default
					codesize :+ ISIZE
			End Select
		Next

		' allocate executable space
		Local code:Byte Ptr = vm.mem.AllocCodeBlock(codesize)

		'finalize op offsets se we can look them up
		For Local i:Int = 0 Until icount
			opPos[i] :+ Int(code)	'direct to executable space
		Next

		' emplace prologue (used for calling in from native only)
		For Local p:Int = 0 Until PROLOGUESZ
			code[p] = Prologue[p]
		Next
		Byte Ptr Ptr(code + 14)[0] = Byte Ptr(bytecode)	'replace the ########

		' epilogue (shared location per-vm, probably already set)
		vm.mem.returnToNative[0] = $c30cc483	'add $12, %esp ; ret  - i.e. restore the stack to normal

		Local ktable:Long Ptr = Long Ptr(ins + 8 * bytecode.icount + 8 * bytecode.upvars)	'constant table

		' generate machine code!
		For Local i:Int = 0 Until icount	'emplace opcode calls and supporting bytecode data
			Local codep:Byte Ptr = Byte Ptr(opPos[i]), bytecodep:Byte Ptr = Byte Ptr(Int(codep) + vm.mem.PAGESZ)
			Local bi:Int = i * 8, op:Int = ins[bi], ip:Int Ptr = Int Ptr(ins + bi), func:Byte Ptr = opTbl[op]

			codep[0] = $e8	'call
			Byte Ptr Ptr(codep + 1)[0] = func - Int(codep + ISIZE)

			bytecodep[0] = ins[bi + 1] ; bytecodep[1] = ins[bi + 2]	'used by most, may get overwritten

			Select op
				Case opc.LOADSI, opc.CLOSURE, opc.RET, opc.POSTCALL
					Int Ptr(bytecodep + 1)[0] = ip[1]

				Case opc.LOADK
					Long Ptr Ptr(bytecodep + 1)[0] = ktable + ip[1]

				Case opc.SETTABSI, opc.GETTABSI
					Short Ptr(bytecodep)[1] = ip[1]

				Case opc.SETTAB, opc.GETTAB
					codep[ISIZE] = $90 ; Int Ptr(codep + ISIZE + 1)[0] = $90909090	'nops; the space is needed in the bytecode
					Long Ptr Ptr(bytecodep + 2)[0] = ktable + ip[1]
					Long Ptr Ptr(bytecodep + 2)[1] = Null	'inline cache space

				Case opc.GETUPV
					If Int Ptr(ins + icount * 8)[2 * bytecodep[1]] = -1
						Byte Ptr Ptr(codep + 1)[0] = Byte Ptr(BlueJIT.GETENV) - Int(codep + ISIZE)
						Byte Ptr Ptr(bytecodep + 1)[0] = Varptr(vm._ENV.val)	'note: doesn't allow _ENV to be changed from Max
					EndIf

				Case opc.CALL
					Short Ptr(bytecodep)[1] = ip[1]

				Case opc.JIF, opc.JNOT
					Int Ptr(bytecodep + 1)[0] = opPos[i + ip[1]]

				Case opc.JMP
					codep[0] = $e9	'use a true jump
					Int Ptr(codep + 1)[0] = opPos[i + ip[1]] - (opPos[i] + ISIZE)

				Default	'binary operations A = B op C
					bytecodep[2] = ip[1]
			End Select
		Next

		Return code
	End Function

	Const STACKFRAME_INC:Int = BlueVMMemory.STACKFRAMESZ + 4, BYTECODE_INC:Int = BlueVMMemory.BYTECODESZ + 4
	Const IP_OFFSET:Int = BlueVMMemory.PAGESZ - ISIZE

	'note: extern vars must take into account the vtbl offset, so that converting from a typed pointer can be a simple cast (faster)

	Function MOV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "MOV      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = varp[rp[1]]
	End Function
	Function GETLC(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETLC    //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = Long Ptr Ptr(varp + rp[1])[0][0]
	End Function
	Function SETLC(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETLC    //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.mem.Write(Long Ptr Ptr(varp + rp[0])[0], varp[rp[1]])	'unlikely but technically possible to need this
	End Function
	Function LOADK(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADK    //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, varp:Long Ptr = stk.varp + rp[0], kp:Long Ptr = Long Ptr Ptr(rp + 1)[0]
		varp[0] = kp[0]
	End Function
	Function LOADSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADSI   //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local val:Int = Int Ptr(rp + 1)[0]
		Double Ptr(stk.varp)[rp[0]] = Double(val)
	End Function
	Function LOADBOOL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADBOOL //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, vp:Int Ptr = Int Ptr(stk.varp + rp[0])
		vp[1] = BlueTypeTag.NANBOX | BlueTypeTag.BOOL
		vp[0] = Int Ptr(rp + 1)[0]
	End Function
	Function LOADNIL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADNIL  //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Int Ptr(stk.varp + rp[0])[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL
	End Function

	Function GETTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, kp:Long Ptr = Long Ptr Ptr(rp + 2)[0]
		Local tabp:Byte Ptr = Byte Ptr Ptr(varp + rp[1])[0]
		If Int Ptr(varp + rp[1])[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(tabp, kp[0], Varptr(keyslot))
			If (slot = Null) Or (keyslot[0] <> kp[0])
				If keyslot = Long Ptr(1) Then varp[rp[0]] = BlueVMMemory.NIL ; Return	'nil key
			Else
				varp[rp[0]] = slot[0] ; Return
			EndIf
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], varp[rp[1]], kp[0]	'not in table; invoke metamethod
	End Function
	Function GETTABSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTABSI //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabp:Byte Ptr = Byte Ptr Ptr(varp + rp[1])[0], key:Long ; Double Ptr(Varptr(key))[0] = Short Ptr(rp)[1]
		If Int Ptr(varp + rp[0])[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(tabp, key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then varp[rp[0]] = slot[0] ; Return
			If keyslot = Long Ptr(1) Then varp[rp[0]] = BlueVMMemory.NIL ; Return
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], varp[rp[1]], key
	End Function
	Function SETTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[0], kp:Long Ptr = Long Ptr Ptr(rp + 2)[0]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], kp[0], Varptr(keyslot))
			If Not (slot = Null Or keyslot[0] <> kp[0]) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, varp[rp[0]], varp[rp[1]], kp[0]	'not in table; invoke metamethod or rawset
	End Function
	Function SETTABSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTABSI //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, tabvp:Byte Ptr = varp + rp[0]
		Local key:Long ; Double Ptr(Varptr(key))[0] = Short Ptr(rp)[1]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, Long Ptr(tabvp)[0], varp[rp[1]], key
	End Function
	Function GETTABI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTABI  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[1], key:Long = varp[rp[2]]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then varp[rp[0]] = slot[0] ; Return
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], Long Ptr(tabvp)[0], key
	End Function
	Function SETTABI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTABI  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[0], key:Long = varp[rp[2]]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, Long Ptr(tabvp)[0], varp[rp[1]], key
	End Function
	Function GETUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETUPV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local upvp:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC), valp:Long Ptr = Long Ptr(upvp[rp[1]])
		varp[rp[0]] = valp[0]
	End Function
	Function GETENV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)	'"unlisted" instruction: GETUPV for _ENV is translated into this at load-time
	'	Print "GETENV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = Long Ptr Ptr(rp + 1)[0][0]	'type check should not be necessary (will be handled by following op)
	End Function
	Function SETUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETUPV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local upvp:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC), valp:Long Ptr = Long Ptr(upvp[rp[0]])
		vm.mem.Write(valp, varp[rp[1]])
	End Function
	Function NEWTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "NEWTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local tab:Byte Ptr = vm.mem.AllocTable(Null)
		Local d:Int Ptr = Int Ptr(varp + rp[0])
		d[0] = Int(tab) ; d[1] = BlueTypeTag.TBLBOX
	End Function
	Function CLOSURE(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "CLOSURE  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local d:Int Ptr = Int Ptr(varp + rp[0])
		Local cbytecode:Bytecode = vm.funIndex[Int Ptr(rp + 1)[0]], upvp:Int Ptr = Int Ptr(Byte Ptr(cbytecode) + BYTECODE_INC + 8 * cbytecode.icount)
		Local closure:Byte Ptr = vm.mem.AllocClosure(cbytecode, cbytecode.upvars)	'upvars is always even (alignment)

		'get upvalues off the stack
		For Local u:Int = 0 Until cbytecode.upvars
			If upvp[2 * u]
				Byte Ptr Ptr(closure)[2 + u] = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC)[upvp[2 * u + 1]]
			Else
				Byte Ptr Ptr(closure)[2 + u] = Byte Ptr Ptr(varp + upvp[2 * u + 1])[0]
			EndIf
		Next

		d[0] = Int(closure) ; d[1] = BlueTypeTag.FUNBOX
	End Function
	Function NEWUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "NEWUPV   //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local upv:Byte Ptr = vm.mem.AllocObject(8, BlueTypeTag.UPV), d:Int Ptr = Int Ptr(stk.varp + rp[0])
		Long Ptr(upv)[0] = Long Ptr(d)[0]	'promote a value if it existed (useful for parameters)
		d[0] = Int(upv) ; d[1] = BlueTypeTag.NANBOX | BlueTypeTag.UPV
	End Function

	Function ADD(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "ADD      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local d:Double Ptr = Double Ptr(varp + rp[0])
		Local r:Int Ptr = Int Ptr(varp + rp[1]), l:Int Ptr = Int Ptr(varp + rp[2])
		If l[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX Or r[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX
			DebugStop
		Else
			Print "  " + Double Ptr(l)[0] + " " + Double Ptr(r)[0]
			d[0] = Double Ptr(l)[0] + Double Ptr(r)[0]
		EndIf
	End Function
	Function SUB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function MUL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function DIV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function NMOD(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function POW(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CAT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function IDIV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BAND(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BOR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BXOR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BSHL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BSHR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function UNM(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function LNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function ALEN(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function UNP(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function EQ(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "EQ       //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = (Byte Ptr Ptr(retptr) - 4)[0] + IP_OFFSET
		Local r:Double = Double Ptr(varp)[rp[1]]
		Local l:Double = Double Ptr(varp)[rp[2]]
		Int Ptr(varp + rp[0])[0] = l = r
	End Function
	Function LT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function LEQ(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function

	Function JMP(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		RuntimeError "wait, why are we here?"
	End Function
	Function JIF(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "JIF      //"
		Local rp:Byte Ptr = (Byte Ptr Ptr(retptr) - 4)[0] + IP_OFFSET
		If Int Ptr(stk.varp + rp[0])[0]
			Local target:Int = Int Ptr(rp + 1)[0]
			Int Ptr(retptr)[-4] = target
		EndIf
	End Function
	Function JNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "CALL     //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local fp:Int Ptr = Int Ptr(varp + rp[0])
		If Not PrepareCall(fp, stk, rp, varp, retptr)	'PrepareCall sets everything up so there's nothing else to do to make the call happen
			'__call metamethod
		EndIf
	End Function
	Function TCALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function RET:Byte Ptr(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "RET      //"
		Local varp:Long Ptr = stk.varp
		Local rp0:Byte Ptr = Byte Ptr Ptr(retptr)[-4], rp:Byte Ptr = rp0 + IP_OFFSET

		Local oldStk:Stack = stk.prevBase
		Local retv:Long Ptr = varp + rp[0]
		If oldStk
			oldStk.retv = retv
			oldStk.retc = Int Ptr(rp + 1)[0]
			Byte Ptr Ptr(retptr)[-4] = stk.retIP
			Byte Ptr Ptr(retptr)[-3] = Byte Ptr(oldStk)
			Byte Ptr Ptr(retptr)[-2] = Byte Ptr(oldStk.func) + BYTECODE_INC
		Else	'return to native code
			Local codePage:Byte Ptr = Byte Ptr(Int(rp0) & ((Int(2^12)-1) Shl 20))
			Byte Ptr Ptr(retptr)[-4] = codePage + 4
			Return retv
		EndIf
	End Function
	Function RETVA(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function POSTCALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "POSTCALL //"
		Local varp:Long Ptr = stk.varp
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET

		For Local r:Int = 0 Until Min(Int Ptr(rp + 1)[0], stk.retc)
			(varp + rp[0])[r] = stk.retv[r]
		'	Print "  return " + r + ": " + Double Ptr(stk.retv)[r]
		Next
		'nil the rest
	End Function
	Function VARARG(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function VAINIT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CALLINIT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function

	Function PrepareCall:Int(fp:Int Ptr, stk:Stack, rp:Byte Ptr, varp:Long Ptr, retptr:Byte Ptr)
		If fp[1] = BlueTypeTag.NANBOX | BlueTypeTag.FUN
			Local newStk0:Stack ; Byte Ptr Ptr(Varptr(newStk0))[0] = Byte Ptr(stk) + stk.func.frameSz
			Local newStk:Stack = newStk0	'micro-opt: newStk0 can't be a register because of the unwieldy conversion

			newStk.retIP = Byte Ptr Ptr(retptr)[-4]
			newStk.prevBase = stk
			Local closure:Byte Ptr = Byte Ptr(fp[0])
			Local newBC:Bytecode = Bytecode Ptr(closure)[0]
			Local voff:Int = STACKFRAME_INC + 4 * newBC.upvars
			newStk.varp = Long Ptr(Byte Ptr(newStk) + voff)
			newStk.func = newBC

			Local argc_actual:Int = Short Ptr(rp)[1], argc_required:Int = newBC.pcount
		'	Local argc_min:Int ; If argc_actual < argc_required Then argc_min = argc_actual Else argc_min = argc_required
			Local argc_min:Int = argc_actual - argc_required ; argc_min = (argc_min & (argc_min Shr 31)) + argc_required 'branchless 32-bit Min

			Local argv:Long Ptr = varp + rp[1], destv:Long Ptr = newStk.varp
			For Local a:Int = 0 Until argc_min
				destv[a] = argv[a]
			'	Print "  arg " + a + ": " + argv[a]
			Next
			For Local a:Int = argc_min Until argc_required	'nil any unfilled parameters
				Int Ptr(destv + a)[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL
			Next
			newStk.argv = argv + argc_min	'argv should be to the varargs (if any)
			newStk.argc = argc_actual - argc_min	'argc is the number of varargs

			Local destup:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(newStk) + STACKFRAME_INC)
			Local upv:Byte Ptr Ptr = Byte Ptr Ptr(closure + 8)
			For Local up:Int = 0 Until newBC.upvars	'emplace upvars
				destup[up] = upv[up]
			'	Print "  upv " + up + ": " + destv[up]
			'	Print "  val: " + Double Ptr Ptr(Byte Ptr Ptr(closure) + 2 + up)[0][0]
			Next

			'note that the following OVERWRITE THE PARAMETERS (in release mode), so no touching stk from here on
			Byte Ptr Ptr(retptr)[-4] = newBC.mcode
			Byte Ptr Ptr(retptr)[-3] = Byte Ptr(newStk)
			Byte Ptr Ptr(retptr)[-2] = Byte Ptr(newBC) + BYTECODE_INC

		ElseIf fp[1] = BlueTypeTag.NANBOX | BlueTypeTag.NATFUN	'native call
			Local fun:Int(vm:BlueVM, ac:Int, av:Long Ptr, rv:Long Ptr) = Byte Ptr(fp[0])
			Local argc:Int = Short Ptr(rp)[1], argv:Long Ptr = varp + rp[1], retv:Long Ptr = Long Ptr(Byte Ptr(stk) + stk.func.frameSz)
			Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(stk.func.vm)
			stk.retc = fun(vm, argc, argv, retv)
			stk.retv = retv
		Else
			Return False	'not a function; take appropriate action
		EndIf
		Return True	'all good and will auto-call when the caller returns
	End Function

	Function IndexMetamethod(bc:Bytecode, retptr:Byte Ptr, d:Long Ptr, tbl:Long, key:Long)	'prep but not execute a call
		'need to work out a way to return values from this without POSTCALL (maybe can co-opt RET to handle singles by itself)
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.Error("metamethod access isn't implemented yet")
	End Function
	Function NewindexMetamethod(bc:Bytecode, retptr:Byte Ptr, d:Long, val:Long, key:Long)	'as above
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
	'	vm.Error("metamethod access isn't implemented yet")
		'just rawset it for now
		'remember to handle nil key (what is correct?)
		Local tabp:Byte Ptr = Byte Ptr(Int(d))
		BlueTable.RawSet(vm.mem, tabp, key, val)
	End Function

	Function BinopMetamethod(op:Int, bc:Bytecode, retptr:Byte Ptr, d:Long Ptr, r:Long Ptr, l:Long Ptr)	'as above
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.Error("metamethod access isn't implemented yet")
	End Function

	Function InitOpTbl()
		opc = New BlueOpcode
		opTbl = [MOV, GETLC, SETLC, LOADK, LOADSI, LOADBOOL, LOADNIL, ..
			GETTAB, GETTABSI, SETTAB, SETTABSI, GETTABI, SETTABI, GETUPV, SETUPV, ..
			NEWTAB, CLOSURE, NEWUPV, ..
			ADD, SUB, MUL, DIV, NMOD, POW, CAT, ..
			IDIV, BAND, BOR, BXOR, BSHL, BSHR, ..
			UNM, LNOT, ALEN, BNOT, UNP, ..
			EQ, LT, LEQ, ..
			JMP, JIF, JNOT, CALL, TCALL, RETVA, RETVA, ..	'not a mistake: RET doesn't fit but we need the spacer
			POSTCALL, VARARG, VAINIT, CALLINIT]

		opTbl[opc.RET] = Byte Ptr(RET)
	End Function

	Function Identity:Byte Ptr(b:Byte Ptr)
		Return b
	End Function
	Function PointerToExtType:Byte Ptr(p:Byte Ptr)	'horrible pointer abuse (increment down so we can ignore the nonexistent vtbl)
		Return p - SizeOf(Byte Ptr(0))
	End Function
	Global BPtoS:Stack(p:Byte Ptr) = Byte Ptr(BlueJIT.PointerToExtType)
End Type

' Blue Moon
' JIT engine/opcodes

' this is the core of the runtime: the machine code generator and opcodes
' it's a halfway blend between JIT and interpreter: a basic JIT generates machine code
' for "unrolled" interpreter loops, theoretically (ha!) providing a speed boost
' it can also "inline" selected operations, coming closer to being a true JIT

' for now this is an Include rather than Import lib due to recursive dependency
' we'll fix that later

Rem
SuperStrict

Import "blueerror.bmx"
Import "bluevm.bmx"
Import etc etc...
End Rem

'Private	'may need to rename and open these (in the shared interface?)
Extern
	Type Stack
		Field retIP:Byte Ptr, prevBase:Stack, varp:Long Ptr, func:Bytecode, _:Long Ptr, argv:Long Ptr, retv:Long Ptr, argc:Short, retc:Short
	End Type
	Type Bytecode
		Field mcode:Byte Ptr, idMod:Int, kcount:Int, pcount:Int, upvars:Int, frameSz:Int, icount:Int, vm:Byte Ptr
	End Type
End Extern
'Public

Type BlueJIT Final
	Const PROLOGUESZ:Int = 25, ISIZE:Int = 5

	Global opTbl:Int(s:Stack, b:Bytecode, r:Byte Ptr)[], opc:BlueOpcode
	Global Prologue:Int[] = [ ..
		$90, $90, ..
		$8b, $44, $24, $04, ..                     'mov 4(%esp), %eax
		$89, $44, $24, $f4, ..                     'mov %eax, -12(%esp)
		$c7, $44, $24, $f8, $FF, $FF, $FF, $FF, .. 'movl ########, -8(%esp)
		$89, $64, $24, $fc, ..                     'mov %esp, -4(%esp)
		$83, $ec, $0c..                            'sub $12, %esp
	]

	Function Compile:Byte Ptr(vm:BlueVM, ins:Byte Ptr, icount:Int, bytecode:Bytecode)
		?Not x86
		RuntimeError "The Blue Moon JIT does not support your platform (x86-32 only at this time)"
		?
		Assert SizeOf(0:Long) = SizeOf(0:Double) And SizeOf(0:Int) = 4 And SizeOf(0:Long) = 8 And SizeOf(Byte Ptr(0)) = 4, ..
			"assumptions about platform datatype sizes are invalid"

		If opTbl = Null Then InitOpTbl()

		' compute executable code size
		Local codesize:Int = PROLOGUESZ, opPos:Int[icount]	'since sizes are irregular, looking up offsets is easiest
		For Local i:Int = 0 Until icount
			opPos[i] = codesize	'finalized below
			Select ins[i * 8]
				Case opc.SETTAB, opc.GETTAB
					codesize :+ ISIZE * 2
				Default
					codesize :+ ISIZE
			End Select
		Next

		' allocate executable space
		Local code:Byte Ptr = vm.mem.AllocCodeBlock(codesize)

		'finalize op offsets se we can look them up
		For Local i:Int = 0 Until icount
			opPos[i] :+ Int(code)	'direct to executable space
		Next

		' emplace prologue (used for calling in from native only)
		For Local p:Int = 0 Until PROLOGUESZ
			code[p] = Prologue[p]
		Next
		Byte Ptr Ptr(code + 14)[0] = Byte Ptr(bytecode)	'replace the ########

		' epilogue (shared location per-vm, probably already set)
		vm.mem.returnToNative[0] = $c30cc483	'add $12, %esp ; ret  - i.e. restore the stack to normal

		Local ktable:Long Ptr = Long Ptr(ins + 8 * bytecode.icount + 8 * bytecode.upvars)	'constant table

		' generate machine code!
		For Local i:Int = 0 Until icount	'emplace opcode calls and supporting bytecode data
			Local codep:Byte Ptr = Byte Ptr(opPos[i]), bytecodep:Byte Ptr = Byte Ptr(Int(codep) + vm.mem.PAGESZ)
			Local bi:Int = i * 8, op:Int = ins[bi], ip:Int Ptr = Int Ptr(ins + bi), func:Byte Ptr = opTbl[op]

			codep[0] = $e8	'call
			Byte Ptr Ptr(codep + 1)[0] = func - Int(codep + ISIZE)

			bytecodep[0] = ins[bi + 1] ; bytecodep[1] = ins[bi + 2]	'used by most, may get overwritten

			Select op
				Case opc.LOADSI, opc.CLOSURE, opc.RET, opc.POSTCALL
					Int Ptr(bytecodep + 1)[0] = ip[1]

				Case opc.LOADK
					Long Ptr Ptr(bytecodep + 1)[0] = ktable + ip[1]

				Case opc.SETTABSI, opc.GETTABSI
					Short Ptr(bytecodep)[1] = ip[1]

				Case opc.SETTAB, opc.GETTAB
					codep[ISIZE] = $90 ; Int Ptr(codep + ISIZE + 1)[0] = $90909090	'nops; the space is needed in the bytecode
					Long Ptr Ptr(bytecodep + 2)[0] = ktable + ip[1]
					Long Ptr Ptr(bytecodep + 2)[1] = Null	'inline cache space

				Case opc.GETUPV
					If Int Ptr(ins + icount * 8)[2 * bytecodep[1]] = -1
						Byte Ptr Ptr(codep + 1)[0] = Byte Ptr(BlueJIT.GETENV) - Int(codep + ISIZE)
						Byte Ptr Ptr(bytecodep + 1)[0] = Varptr(vm._ENV.val)	'note: doesn't allow _ENV to be changed from Max
					EndIf

				Case opc.CALL
					Short Ptr(bytecodep)[1] = ip[1]

				Case opc.JIF, opc.JNOT
					Int Ptr(bytecodep + 1)[0] = opPos[i + ip[1]]

				Case opc.JMP
					codep[0] = $e9	'use a true jump
					Int Ptr(codep + 1)[0] = opPos[i + ip[1]] - (opPos[i] + ISIZE)

				Default	'binary operations A = B op C
					bytecodep[2] = ip[1]
			End Select
		Next

		Return code
	End Function

	Const STACKFRAME_INC:Int = BlueVMMemory.STACKFRAMESZ + 4, BYTECODE_INC:Int = BlueVMMemory.BYTECODESZ + 4
	Const IP_OFFSET:Int = BlueVMMemory.PAGESZ - ISIZE

	'note: extern vars must take into account the vtbl offset, so that converting from a typed pointer can be a simple cast (faster)

	Function MOV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "MOV      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = varp[rp[1]]
	End Function
	Function GETLC(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function SETLC(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETLC    //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.mem.Write(Long Ptr Ptr(varp + rp[0])[0], varp[rp[1]])	'unlikely but technically possible to need this
	End Function
	Function LOADK(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADK    //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, varp:Long Ptr = stk.varp + rp[0], kp:Long Ptr = Long Ptr Ptr(rp + 1)[0]
		varp[0] = kp[0]
	End Function
	Function LOADSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADSI   //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local val:Int = Int Ptr(rp + 1)[0]
		Double Ptr(stk.varp)[rp[0]] = Double(val)
	End Function
	Function LOADBOOL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADBOOL //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, vp:Int Ptr = Int Ptr(stk.varp + rp[0])
		vp[1] = BlueTypeTag.NANBOX | BlueTypeTag.BOOL
		vp[0] = Int Ptr(rp + 1)[0]
	End Function
	Function LOADNIL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADNIL  //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Int Ptr(stk.varp + rp[0])[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL
	End Function

	Function GETTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "GETTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, kp:Long Ptr = Long Ptr Ptr(rp + 2)[0]
		Local tabp:Byte Ptr = Byte Ptr Ptr(varp + rp[1])[0]
		If Int Ptr(varp + rp[1])[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(tabp, kp[0], Varptr(keyslot))
			If (slot = Null) Or (keyslot[0] <> kp[0])	'not in table; invoke metamethod
				If keyslot = Long Ptr(1) Then varp[rp[0]] = BlueVMMemory.NIL ; Return	'nil key
			Else
				varp[rp[0]] = slot[0] ; Return
			EndIf
		EndIf
		Metamethod opc.GETTAB, bc, retptr, varp + rp[0], varp + rp[1], varp + rp[2]
	End Function
	Function GETTABSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function SETTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "SETTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, kp:Long Ptr = Long Ptr Ptr(rp + 2)[0]
		Local tabp:Byte Ptr = Byte Ptr Ptr(varp + rp[0])[0]
		Print "  tag: " + Bin(Int Ptr(kp)[1])
		Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(tabp, kp[0], Varptr(keyslot))
		Print "  slot: " + Hex(Int(slot))
		If (slot = Null) Or (keyslot[0] <> kp[0])	'not in table; invoke metamethod or rawset
			'if keyslot == 1 then it's invalid
			Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
			BlueTable.RawSet(vm.mem, tabp, kp[0], varp[rp[1]])
		Else
			slot[0] = varp[rp[1]]
		EndIf
	End Function
	Function SETTABSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "SETTABSI //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local tabp:Byte Ptr = varp + rp[0]
		Print "  " + Short Ptr(rp)[1]
		Local key:Long ; Double Ptr(Varptr(key))[0] = Short Ptr(rp)[1]
		BlueTable.RawSet(vm.mem, Byte Ptr Ptr(tabp)[0], key, varp[rp[1]])
	End Function
	Function GETTABI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "GETTABI  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabp:Byte Ptr = varp + rp[1]
		Print "  tag: " + Bin(Int Ptr(varp + rp[2])[1])
		Local val:Long = BlueTable.RawGet(Byte Ptr Ptr(tabp)[0], varp[rp[2]])
		varp[rp[0]] = val
	End Function
	Function SETTABI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "SETTABI  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local tabp:Byte Ptr = varp + rp[0]
		Print "  " + Double Ptr(varp)[rp[2]] + " " + Double Ptr(varp)[rp[1]]
		Print "  tag: " + Bin(Int Ptr(varp + rp[2])[1])
		BlueTable.RawSet(vm.mem, Byte Ptr Ptr(tabp)[0], varp[rp[2]], varp[rp[1]])
	End Function
	Function GETUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "GETUPV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Print "  " + rp[0] + " " + rp[1] + " " + rp[2]
		DebugStop
		Local upvp:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC), valp:Long Ptr = Long Ptr(upvp[rp[1]])
		varp[rp[0]] = valp[0]
	End Function
	Function GETENV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)	'"unlisted" instruction: GETUPV for _ENV is translated into this at load-time
		Print "GETENV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Print "  " + rp[0] + " " + Hex(Int Ptr(rp + 1)[0])
		'need a type check
		varp[rp[0]] = Long Ptr Ptr(rp + 1)[0][0]
	End Function
	Function SETUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function NEWTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "NEWTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local tab:Byte Ptr = vm.mem.AllocTable(Null)
		Local d:Int Ptr = Int Ptr(varp + rp[0])
		d[0] = Int(tab) ; d[1] = BlueTypeTag.NANBOX | BlueTypeTag.TBL
	End Function
	Function CLOSURE(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "CLOSURE  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local d:Int Ptr = Int Ptr(varp + rp[0])
		Local cbytecode:Bytecode = vm.funIndex[Int Ptr(rp + 1)[0]], upvp:Int Ptr = Int Ptr(Byte Ptr(cbytecode) + BYTECODE_INC + 8 * cbytecode.icount)
		Local closure:Byte Ptr = vm.mem.AllocClosure(cbytecode, cbytecode.upvars)	'upvars is always even (alignment)

		'get upvalues off the stack
		For Local u:Int = 0 Until cbytecode.upvars
			If upvp[2 * u]
				Byte Ptr Ptr(closure)[2 + u] = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC)[upvp[2 * u + 1]]
			Else
				Byte Ptr Ptr(closure)[2 + u] = Byte Ptr Ptr(varp + upvp[2 * u + 1])[0]
			EndIf
		Next

		d[0] = Int(closure) ; d[1] = BlueTypeTag.NANBOX | BlueTypeTag.FUN
	End Function
	Function NEWUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "NEWUPV   //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local upv:Byte Ptr = vm.mem.AllocObject(8, BlueTypeTag.UPV), d:Int Ptr = Int Ptr(stk.varp + rp[0])
		Long Ptr(upv)[0] = Long Ptr(d)[0]	'promote a value if it existed (useful for parameters)
		d[0] = Int(upv) ; d[1] = BlueTypeTag.NANBOX | BlueTypeTag.UPV
	End Function

	Function ADD(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "ADD      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local d:Double Ptr = Double Ptr(varp + rp[0])
		Local r:Int Ptr = Int Ptr(varp + rp[1]), l:Int Ptr = Int Ptr(varp + rp[2])
		If l[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX Or r[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX
			DebugStop
		Else
			Print "  " + Double Ptr(l)[0] + " " + Double Ptr(r)[0]
			d[0] = Double Ptr(l)[0] + Double Ptr(r)[0]
		EndIf
	End Function
	Function SUB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function MUL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function DIV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function NMOD(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function POW(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CAT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function IDIV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BAND(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BOR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BXOR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BSHL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BSHR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function UNM(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function LNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function ALEN(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function UNP(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function EQ(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "EQ       //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = (Byte Ptr Ptr(retptr) - 4)[0] + IP_OFFSET
		Local r:Double = Double Ptr(varp)[rp[1]]
		Local l:Double = Double Ptr(varp)[rp[2]]
		Int Ptr(varp + rp[0])[0] = l = r
	End Function
	Function LT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function LEQ(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function

	Function JMP(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		RuntimeError "wait, why are we here?"
	End Function
	Function JIF(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "JIF      //"
		Local rp:Byte Ptr = (Byte Ptr Ptr(retptr) - 4)[0] + IP_OFFSET
		If Int Ptr(stk.varp + rp[0])[0]
			Local target:Int = Int Ptr(rp + 1)[0]
			Int Ptr(retptr)[-4] = target
		EndIf
	End Function
	Function JNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "CALL     //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local fp:Int Ptr = Int Ptr(varp + rp[0])
		If Not PrepareCall(fp, stk, rp, varp, retptr)	'PrepareCall sets everything up so there's nothing else to do to make the call happen
			'__call metamethod
		EndIf
	End Function
	Function TCALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function RET:Byte Ptr(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "RET      //"
		Local varp:Long Ptr = stk.varp
		Local rp0:Byte Ptr = Byte Ptr Ptr(retptr)[-4], rp:Byte Ptr = rp0 + IP_OFFSET

		Local oldStk:Stack = stk.prevBase
		Local retv:Long Ptr = varp + rp[0]
		If oldStk
			oldStk.retv = retv
			oldStk.retc = Int Ptr(rp + 1)[0]
			Byte Ptr Ptr(retptr)[-4] = stk.retIP
			Byte Ptr Ptr(retptr)[-3] = Byte Ptr(oldStk)
			Byte Ptr Ptr(retptr)[-2] = Byte Ptr(oldStk.func) + BYTECODE_INC
		Else	'return to native code
			Local codePage:Byte Ptr = Byte Ptr(Int(rp0) & ((Int(2^12)-1) Shl 20))
			Byte Ptr Ptr(retptr)[-4] = codePage + 4
			Return retv
		EndIf
	End Function
	Function RETVA(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function POSTCALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "POSTCALL //"
		Local varp:Long Ptr = stk.varp
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET

		For Local r:Int = 0 Until Min(Int Ptr(rp + 1)[0], stk.retc)
			(varp + rp[0])[r] = stk.retv[r]
		'	Print "  return " + r + ": " + Double Ptr(stk.retv)[r]
		Next
		'nil the rest
	End Function
	Function VARARG(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function VAINIT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CALLINIT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function

	Function PrepareCall:Int(fp:Int Ptr, stk:Stack, rp:Byte Ptr, varp:Long Ptr, retptr:Byte Ptr)
		If fp[1] = BlueTypeTag.NANBOX | BlueTypeTag.FUN
			Local newStk0:Stack ; Byte Ptr Ptr(Varptr(newStk0))[0] = Byte Ptr(stk) + stk.func.frameSz
			Local newStk:Stack = newStk0	'micro-opt: newStk0 can't be a register because of the unwieldy conversion

			newStk.retIP = Byte Ptr Ptr(retptr)[-4]
			newStk.prevBase = stk
			Local closure:Byte Ptr = Byte Ptr(fp[0])
			Local newBC:Bytecode = Bytecode Ptr(closure)[0]
			Local voff:Int = STACKFRAME_INC + 4 * newBC.upvars
			newStk.varp = Long Ptr(Byte Ptr(newStk) + voff)
			newStk.func = newBC

			Local argc_actual:Int = Short Ptr(rp)[1], argc_required:Int = newBC.pcount
		'	Local argc_min:Int ; If argc_actual < argc_required Then argc_min = argc_actual Else argc_min = argc_required
			Local argc_min:Int = argc_actual - argc_required ; argc_min = (argc_min & (argc_min Shr 31)) + argc_required 'branchless 32-bit Min

			Local argv:Long Ptr = varp + rp[1], destv:Long Ptr = newStk.varp
			For Local a:Int = 0 Until argc_min
				destv[a] = argv[a]
			'	Print "  arg " + a + ": " + argv[a]
			Next
			For Local a:Int = argc_min Until argc_required	'nil any unfilled parameters
				Int Ptr(destv + a)[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL
			Next
			newStk.argv = argv + argc_min	'argv should be to the varargs (if any)
			newStk.argc = argc_actual - argc_min	'argc is the number of varargs

			Local destup:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(newStk) + STACKFRAME_INC)
			Local upv:Byte Ptr Ptr = Byte Ptr Ptr(closure + 8)
			For Local up:Int = 0 Until newBC.upvars	'emplace upvars
				destup[up] = upv[up]
			'	Print "  upv " + up + ": " + destv[up]
			'	Print "  val: " + Double Ptr Ptr(Byte Ptr Ptr(closure) + 2 + up)[0][0]
			Next

			'note that the following OVERWRITE THE PARAMETERS (in release mode), so no touching stk from here on
			Byte Ptr Ptr(retptr)[-4] = newBC.mcode
			Byte Ptr Ptr(retptr)[-3] = Byte Ptr(newStk)
			Byte Ptr Ptr(retptr)[-2] = Byte Ptr(newBC) + BYTECODE_INC

		ElseIf fp[1] = BlueTypeTag.NANBOX | BlueTypeTag.NATFUN	'native call
			Local fun:Int(vm:BlueVM, ac:Int, av:Long Ptr, rv:Long Ptr) = Byte Ptr(fp[0])
			Local argc:Int = Short Ptr(rp)[1], argv:Long Ptr = varp + rp[1], retv:Long Ptr = Long Ptr(Byte Ptr(stk) + stk.func.frameSz)
			Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(stk.func.vm)
			stk.retc = fun(vm, argc, argv, retv)
			stk.retv = retv
		Else
			Return False	'not a function; take appropriate action
		EndIf
		Return True	'all good and will auto-call when the caller returns
	End Function

	Function Metamethod(op:Int, bc:Bytecode, retptr:Byte Ptr, d:Long Ptr, r:Long Ptr, l:Long Ptr)	'prep but not execute a call
		'need to work out a way to return values from this without POSTCALL (maybe can co-opt RET to handle singles by itself)
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.Error("metamethod access isn't implemented yet")
	End Function

	Function InitOpTbl()
		opc = New BlueOpcode
		opTbl = [MOV, GETLC, SETLC, LOADK, LOADSI, LOADBOOL, LOADNIL, ..
			GETTAB, GETTABSI, SETTAB, SETTABSI, GETTABI, SETTABI, GETUPV, SETUPV, ..
			NEWTAB, CLOSURE, NEWUPV, ..
			ADD, SUB, MUL, DIV, NMOD, POW, CAT, ..
			IDIV, BAND, BOR, BXOR, BSHL, BSHR, ..
			UNM, LNOT, ALEN, BNOT, UNP, ..
			EQ, LT, LEQ, ..
			JMP, JIF, JNOT, CALL, TCALL, RETVA, RETVA, ..	'not a mistake: RET doesn't fit but we need the spacer
			POSTCALL, VARARG, VAINIT, CALLINIT]

		opTbl[opc.RET] = Byte Ptr(RET)
	End Function

	Function Identity:Byte Ptr(b:Byte Ptr)
		Return b
	End Function
	Function PointerToExtType:Byte Ptr(p:Byte Ptr)	'horrible pointer abuse (increment down so we can ignore the nonexistent vtbl)
		Return p - SizeOf(Byte Ptr(0))
	End Function
	Global BPtoS:Stack(p:Byte Ptr) = Byte Ptr(BlueJIT.PointerToExtType)
End Type


Added DependencyEnumerator and CheckCompliance to mxModuleDependencies.

Rem
Copyright (c) 2010 Tim Howard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
End Rem

Rem
	bbdoc: Maximus module dependencies.
End Rem
Type mxModuleDependencies Extends dObjectMap

	Rem
		bbdoc: Add a new dependency with the given key.
		returns: True if the dependency was added, or False if it was not (given string is Null).
	End Rem
	Method AddNewDependency:Int(key:String)
		If key <> Null
			Local dep:mxModuleDependency = New mxModuleDependency.Create(key)
			_Insert(key, dep)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Add the given dependency.
		returns: True if the dependency was added, or False if it was not (given string is Null).
	End Rem
	Method AddDependency:Int(dep:mxModuleDependency)
		If dep <> Null
			_Insert(dep.Get(), dep)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given dependency exists.
		returns: True if the given dependency was found, or False if it was not.
	End Rem
	Method HasDependency:Int(key:String)
		Return _Contains(key)
	End Method

	Rem
		bbdoc: Add the dependencies from the given mxModuleDependencies.
		returns: Nothing.
	End Rem
	Method MergeDependencies(dependencies:mxModuleDependencies)
		For Local dep:mxModuleDependency = EachIn dependencies.DependencyEnumerator()
			AddDependency(dep) ' No need to check if they're already in here, they'll just be overwritten with the same thing
		Next
	End Method

	Rem
		bbdoc: Check the dependency compliance of the current module set.
		returns: Null if all dependencies are matched by existing modules, or a list of module ids that are not yet installed.
	End Rem
	Method CheckCompliance:TListEx()
		Local nocomp:TListEx = New TListEx
		For Local dep:mxModuleDependency = EachIn DependencyEnumerator()
			If mxModUtils.HasModule(dep.Get()) = False
				nocomp.AddLast(dep.Get())
			End If
		Next
		If nocomp.Count() > 0 Then Return nocomp
		Return Null
	End Method

	Rem
		bbdoc: Load the dependencies from the given dJArray.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleDependencies(root:dJArray)
		If root <> Null
			For Local strvar:dStringVariable = EachIn root.GetValues()
				AddDependency(New mxModuleDependency.FromVariable(strvar))
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get a string containing the list of dependencies, separated by @separator.
		returns: A string containing the list of dependencies.
	End Rem
	Method DependencyList:String(separator:String = ", ")
		Local a:String, dep:mxModuleDependency
		For dep = EachIn DependencyEnumerator()
			a:+ dep.Get() + separator
		Next
		If a.Length > 0 Then a = a[..a.Length - separator.Length]
		Return a
	End Method

	Rem
		bbdoc: Get the dependency enumerator.
		returns: The dependency enumerator.
	End Rem
	Method DependencyEnumerator:TMapEnumerator()
		Return ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module dependency.
End Rem
Type mxModuleDependency

	Field m_key:String

	Rem
		bbdoc: Create a new mxModuleDependency.
		returns: Itself.
	End Rem
	Method Create:mxModuleDependency(key:String)
		Set(key)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the dependency.
		returns: Nothing.
	End Rem
	Method Set(key:String)
		Assert key, "(mxModuleDependency.Set) key cannot be Null"
		m_key = key
	End Method

	Rem
		bbdoc: Get the dependency.
		returns: The dependency.
	End Rem
	Method Get:String()
		Return m_key
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set the dependency from the given variable.
		returns: Itself, or Null if the given variable is Null.
	End Rem
	Method FromVariable:mxModuleDependency(strvar:dStringVariable)
		If strvar <> Null
			Set(strvar.Get())
			Return Self
		End If
		Return Null
	End Method

End Type

Rem
Copyright (c) 2010 Tim Howard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
End Rem

Rem
	bbdoc: Maximus module dependencies.
End Rem
Type mxModuleDependencies Extends dObjectMap

	Rem
		bbdoc: Add a new dependency with the given key.
		returns: True if the dependency was added, or False if it was not (given string is Null).
	End Rem
	Method AddNewDependency:Int(key:String)
		If key <> Null
			Local dep:mxModuleDependency = New mxModuleDependency.Create(key)
			_Insert(key, dep)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Add the given dependency.
		returns: True if the dependency was added, or False if it was not (given string is Null).
	End Rem
	Method AddDependency:Int(dep:mxModuleDependency)
		If dep <> Null
			_Insert(dep.Get(), dep)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given dependency exists.
		returns: True if the given dependency was found, or False if it was not.
	End Rem
	Method HasDependency:Int(key:String)
		Return _Contains(key)
	End Method

	Rem
		bbdoc: Add the dependencies from the given mxModuleDependencies.
		returns: Nothing.
	End Rem
	Method MergeDependencies(dependencies:mxModuleDependencies)
		For Local dep:mxModuleDependency = EachIn dependencies.ValueEnumerator()
			AddDependency(dep) ' No need to check if they're already in here, they'll just be overwritten with the same thing
		Next
	End Method

	Rem
		bbdoc: Load the dependencies from the given dJArray.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleDependencies(root:dJArray)
		If root <> Null
			For Local strvar:dStringVariable = EachIn root.GetValues()
				AddDependency(New mxModuleDependency.FromVariable(strvar))
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get a string containing the list of dependencies, separated by @separator.
		returns: A string containing the list of dependencies.
	End Rem
	Method DependencyList:String(separator:String = ", ")
		Local a:String, dep:mxModuleDependency
		For dep = EachIn ValueEnumerator()
			a:+ dep.Get() + separator
		Next
		If a.Length > 0 Then a = a[..a.Length - separator.Length]
		Return a
	End Method

End Type

Rem
	bbdoc: Maximus module dependency.
End Rem
Type mxModuleDependency

	Field m_key:String

	Rem
		bbdoc: Create a new mxModuleDependency.
		returns: Itself.
	End Rem
	Method Create:mxModuleDependency(key:String)
		Set(key)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the dependency.
		returns: Nothing.
	End Rem
	Method Set(key:String)
		Assert key, "(mxModuleDependency.Set) key cannot be Null"
		m_key = key
	End Method

	Rem
		bbdoc: Get the dependency.
		returns: The dependency.
	End Rem
	Method Get:String()
		Return m_key
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set the dependency from the given variable.
		returns: Itself, or Null if the given variable is Null.
	End Rem
	Method FromVariable:mxModuleDependency(strvar:dStringVariable)
		If strvar <> Null
			Set(strvar.Get())
			Return Self
		End If
		Return Null
	End Method

End Type


Made Newton driver compatible with 3.11.

Strict

Import MaxB3D.Core
Import Prime.Newton

Import "util.c"

Extern
	Function _byte_ptr_to_object:Object(p:Byte Ptr)
End Extern

Type TNewtonCollisionDriver Extends TCollisionDriver
	Field _world:Byte Ptr

	Method Init()
		_world = NewtonCreate()
		' NewtonSetPlatformArchitecture _world, 0

		Local minSize#[] = [-500.0, -500.0, -500.0]
		Local maxSize#[] = [ 500.0,  500.0,  500.0]
		'NewtonSetWorldSize _world, minSize, maxSize

		NewtonSetSolverModel _world, 1
	End Method

	Method Destroy()
		NewtonDestroyAllBodies _world
		NewtonDestroy _world
	End Method

	Method Update(config:TWorldConfig,speed#)
		For Local body:TBody=EachIn config.List[WORLDLIST_BODY]
			If Not body._update And body._data<>Null Continue
			If Not TNewtonData(body._data) body._data=New TNewtonData
			Local data:TNewtonData=TNewtonData(body._data)

			Local collision:Byte Ptr
			Select body._shape
			Case BODY_SPHERE,BODY_NONE
				collision=NewtonCreateCapsule(_world,body._radiusx,body._radiusy,0,Null)
			Case BODY_BOX
				'Local offset:TMatrix=TMatrix.Translation((body._boxwidth/2.0)-body._boxx,(body._boxheight/2.0)-body._boxy,(body._boxdepth/2.0)-body._boxz)
				collision=NewtonCreateBox(_world,body._boxwidth,body._boxheight,body._boxdepth,0,Null)
			End Select

			If data._ptr NewtonDestroyBody(data._ptr)

			data._ptr=NewtonCreateDynamicBody(_world,collision,body._matrix.ToPtr())
			NewtonBodySetUserData data._ptr, Byte Ptr(body) - 8

			If body._mass>0
				Local inertia#[3],origin#[3]
				NewtonConvexCollisionCalculateInertialMatrix collision,inertia,origin
				NewtonBodySetMassMatrix data._ptr,body._mass,body._mass*inertia[0],body._mass*inertia[1],body._mass*inertia[2]
				NewtonBodySetCentreOfMass data._ptr,origin
			EndIf

			NewtonBodySetDestructorCallback data._ptr, DestroyCallback
			NewtonBodySetForceAndTorqueCallback data._ptr, ForceAndTorqueCallback
			NewtonBodySetTransformCallback data._ptr, TransformCallback

			'NewtonReleaseCollision _world,collision
			body._update=False
		Next
  	NewtonUpdate _world,speed
	End Method

	Function TransformCallback(body:Byte Ptr,matrix_data:Float Ptr,thread_index)
		Local entity:TBody = TBody(_byte_ptr_to_object(NewtonBodyGetUserData(body)))
		entity.SetMatrix TMatrix.FromPtr(matrix_data)
		entity._update=False
	End Function

	Function ForceAndTorqueCallback(body:Byte Ptr,timestep#,thread_index)
		Local ixx#,iyy#,izz#,mass#
		NewtonBodyGetMassMatrix body,Varptr mass,Varptr ixx,Varptr iyy,Varptr izz
		NewtonBodySetForce body,[0.0,mass*-9.8,0.0]
	End Function

	Function DestroyCallback(body:Byte Ptr)

	End Function
End Type

Type TNewtonData
	Field _ptr:Byte Ptr
End Type

Strict

Import MaxB3D.Core
Import Prime.Newton

Import "util.c"

Extern
	Function _byte_ptr_to_object:Object(p:Byte Ptr)
End Extern

Type TNewtonCollisionDriver Extends TCollisionDriver
	Field _world:Byte Ptr

	Method Init()
		_world = NewtonCreate()
		NewtonSetPlatformArchitecture _world, 0

		Local minSize#[] = [-500.0, -500.0, -500.0]
		Local maxSize#[] = [ 500.0,  500.0,  500.0]
		NewtonSetWorldSize _world, minSize, maxSize

		NewtonSetSolverModel _world, 1
	End Method

	Method Destroy()
		NewtonDestroyAllBodies _world
		NewtonDestroy _world
	End Method

	Method Update(config:TWorldConfig,speed#)
		For Local body:TBody=EachIn config.List[WORLDLIST_BODY]
			If Not body._update And body._data<>Null Continue
			If Not TNewtonData(body._data) body._data=New TNewtonData
			Local data:TNewtonData=TNewtonData(body._data)

			Local collision:Byte Ptr
			Select body._shape
			Case BODY_SPHERE,BODY_NONE
				collision=NewtonCreateSphere(_world,body._radiusx,body._radiusy,body._radiusx,0,Null)
			Case BODY_BOX
				'Local offset:TMatrix=TMatrix.Translation((body._boxwidth/2.0)-body._boxx,(body._boxheight/2.0)-body._boxy,(body._boxdepth/2.0)-body._boxz)
				collision=NewtonCreateBox(_world,body._boxwidth,body._boxheight,body._boxdepth,0,Null)
			End Select

			If data._ptr NewtonDestroyBody(_world,data._ptr)

			data._ptr=NewtonCreateBody(_world,collision,body._matrix.ToPtr())
			NewtonBodySetUserData data._ptr, Byte Ptr(body) - 8

			If body._mass>0
				Local inertia#[3],origin#[3]
				NewtonConvexCollisionCalculateInertialMatrix collision,inertia,origin
				NewtonBodySetMassMatrix data._ptr,body._mass,body._mass*inertia[0],body._mass*inertia[1],body._mass*inertia[2]
				NewtonBodySetCentreOfMass data._ptr,origin
			EndIf

			NewtonBodySetDestructorCallback data._ptr, DestroyCallback
			NewtonBodySetForceAndTorqueCallback data._ptr, ForceAndTorqueCallback
			NewtonBodySetTransformCallback data._ptr, TransformCallback

			NewtonReleaseCollision _world,collision
			body._update=False
		Next
  	NewtonUpdate _world,speed
	End Method

	Function TransformCallback(body:Byte Ptr,matrix_data:Float Ptr,thread_index)
		Local entity:TBody = TBody(_byte_ptr_to_object(NewtonBodyGetUserData(body)))
		entity.SetMatrix TMatrix.FromPtr(matrix_data)
		entity._update=False
	End Function

	Function ForceAndTorqueCallback(body:Byte Ptr,timestep#,thread_index)
		Local ixx#,iyy#,izz#,mass#
		NewtonBodyGetMassMatrix body,Varptr mass,Varptr ixx,Varptr iyy,Varptr izz
		NewtonBodySetForce body,[0.0,mass*-9.8,0.0]
	End Function

	Function DestroyCallback(body:Byte Ptr)

	End Function
End Type

Type TNewtonData
	Field _ptr:Byte Ptr
End Type


Added some bbdoc

Added some bbdoc

refs gh-17

Rem
	bbdoc: This observer for mxLogger will write received messages to a TGadget or use Notify
End Rem
Type mxLoggerObserverGUI

	Rem
		bbdoc: Object to write message to
	End Rem
	Field m_output:Object

	Rem
		bbdoc: Reset content of m_object
	End Rem
	Method Reset()
		If TGadget(m_output)
			SetGadgetText(TGadget(m_output), "")
		End If
	End Method

	Rem
		bbdoc: Handle messages
	End Rem
	Method SendMessage:Object(message:Object, context:Object)
		Local msg:String = String(message)
		If String(context) = "error"
			Notify(msg, True)
		Else If TGadget(m_output)
			Local gadget:TGadget = TGadget(m_output)
			SetGadgetText(gadget, GadgetText(gadget) + String(message))
			'Refresh GUI
			Driver.Poll
		Else If m_output = Null
			Notify(msg)
		End If
	End Method

End Type

Type mxLoggerObserverGUI

	Field m_output:Object

	Method Reset()
		If TGadget(m_output)
			SetGadgetText(TGadget(m_output), "")
		End If
	End Method

	Method SendMessage:Object(message:Object, context:Object)
		Local msg:String = String(message)
		If String(context) = "error"
			Notify(msg, True)
		Else If TGadget(m_output)
			Local gadget:TGadget = TGadget(m_output)
			SetGadgetText(gadget, GadgetText(gadget) + String(message))
			'Refresh GUI
			Driver.Poll
		Else If m_output = Null
			Notify(msg)
		End If
	End Method

End Type


Added test for TZMQ_Message.Create

Added test for TZMQ_Message.Create

SuperStrict
Framework bah.maxunit
Import htbaapub.zmq

New TTestSuite.run()

Type TZMQ_Test Extends TTest

	Method Setup() {before}
	End Method

	Method Teardown() {after}
	End Method

	Method Version() {test}
		Local parts:String[] = TZMQ.Version().Split(".")
		assertTrue(parts.Length = 3, "Version string contains 3 parts")
	End Method

	'Make sure recreating an existing context
	Method ContextReCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Try
			ct = ct.Create()
		Catch ex:String
			assertNotNull(ex, "Recreating context failed properly")
		Catch ex:TZMQ_Exception
			fail(ex.ToString())
		End Try
		ct.Term()
	End Method

	'Make sure a socket can be created
	Method SocketCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local socket:TZMQ_Socket = ct.socket(ZMQ_PUB)
		assertNotNull(socket, "Created socket")
		assertTrue(socket.socket <> Null, "Created pointer to socket")
		socket.Close()
		ct.Term()
	End Method

	'Make sure created messages contain expected data
	Method MessageCreationFromStringIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.CreateFromString(text)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
		Next
	End Method

	'Make sure created messages contain expected data
	Method MessageCreationFromPointerIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.Create(text.ToCString(), text.Length)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
			assertTrue(msg.content = Null, "Message data freed")
		Next
	End Method

	'Make sure exchanged messages aren't corrupt
	Method MessageExchangingIntegrity() {test}
		Local addr:String = "tcp://127.0.0.1:6000"
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local publisher:TZMQ_Socket = ct.socket(ZMQ_PUB)
		Local subscriber:TZMQ_Socket = ct.socket(ZMQ_SUB)

		publisher.Bind(addr)
		subscriber.Connect(addr)
		subscriber.SetSockOpt(ZMQ_SUBSCRIBE , Null)

		Delay 1000
		For Local i:Int = 0 To 10
			Delay 10
			Local text:String = "Message " + i
			publisher.Send(text)
			Local msg:TZMQ_Message = subscriber.Recv()
			If msg = Null Then Continue
			assertEquals(text, msg.ToString(), "Message " + i + " match")
			assertEqualsI(text.Length, msg.Size(), "Message " + i + " size match")
			msg.Close()
		Next

		subscriber.Close()
		publisher.Close()
		ct.Term()
	End Method
End Type

SuperStrict
Framework bah.maxunit
Import htbaapub.zmq

New TTestSuite.run()

Type TZMQ_Test Extends TTest

	Method Setup() {before}
	End Method

	Method Teardown() {after}
	End Method

	Method Version() {test}
		Local parts:String[] = TZMQ.Version().Split(".")
		assertTrue(parts.Length = 3, "Version string contains 3 parts")
	End Method

	'Make sure recreating an existing context
	Method ContextReCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Try
			ct = ct.Create()
		Catch ex:String
			assertNotNull(ex, "Recreating context failed properly")
		Catch ex:TZMQ_Exception
			fail(ex.ToString())
		End Try
		ct.Term()
	End Method

	'Make sure a socket can be created
	Method SocketCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local socket:TZMQ_Socket = ct.socket(ZMQ_PUB)
		assertNotNull(socket, "Created socket")
		assertTrue(socket.socket <> Null, "Created pointer to socket")
		socket.Close()
		ct.Term()
	End Method

	'Make sure created messages contain expected data
	Method MessageCreationIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.CreateFromString(text)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
		Next
	End Method

	'Make sure exchanged messages aren't corrupt
	Method MessageExchangingIntegrity() {test}
		Local addr:String = "tcp://127.0.0.1:6000"
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local publisher:TZMQ_Socket = ct.socket(ZMQ_PUB)
		Local subscriber:TZMQ_Socket = ct.socket(ZMQ_SUB)

		publisher.Bind(addr)
		subscriber.Connect(addr)
		subscriber.SetSockOpt(ZMQ_SUBSCRIBE , Null)

		Delay 1000
		For Local i:Int = 0 To 10
			Delay 10
			Local text:String = "Message " + i
			publisher.Send(text)
			Local msg:TZMQ_Message = subscriber.Recv()
			If msg = Null Then Continue
			assertEquals(text, msg.ToString(), "Message " + i + " match")
			assertEqualsI(text.Length, msg.Size(), "Message " + i + " size match")
			msg.Close()
		Next

		subscriber.Close()
		publisher.Close()
		ct.Term()
	End Method
End Type


Compare to Const

Compare to Const

Rem
	bbdoc: This observer for mxLogger will write received messages to a TGadget or use Notify
End Rem
Type mxLoggerObserverGUI

	Rem
		bbdoc: Object to write message to
	End Rem
	Field m_output:Object

	Rem
		bbdoc: Reset content of m_object
	End Rem
	Method Reset()
		If TGadget(m_output)
			SetGadgetText(TGadget(m_output), "")
		End If
	End Method

	Rem
		bbdoc: Handle messages
	End Rem
	Method SendMessage:Object(message:Object, context:Object)
		Local msg:String = String(message)
		If String(context) = mxLogger.c_error
			Notify(msg, True)
		Else If TGadget(m_output)
			Local gadget:TGadget = TGadget(m_output)
			SetGadgetText(gadget, GadgetText(gadget) + String(message))
			'Refresh GUI
			Driver.Poll
		Else If m_output = Null
			Notify(msg)
		End If
	End Method

End Type

Rem
	bbdoc: This observer for mxLogger will write received messages to a TGadget or use Notify
End Rem
Type mxLoggerObserverGUI

	Rem
		bbdoc: Object to write message to
	End Rem
	Field m_output:Object

	Rem
		bbdoc: Reset content of m_object
	End Rem
	Method Reset()
		If TGadget(m_output)
			SetGadgetText(TGadget(m_output), "")
		End If
	End Method

	Rem
		bbdoc: Handle messages
	End Rem
	Method SendMessage:Object(message:Object, context:Object)
		Local msg:String = String(message)
		If String(context) = "error"
			Notify(msg, True)
		Else If TGadget(m_output)
			Local gadget:TGadget = TGadget(m_output)
			SetGadgetText(gadget, GadgetText(gadget) + String(message))
			'Refresh GUI
			Driver.Poll
		Else If m_output = Null
			Notify(msg)
		End If
	End Method

End Type


Newton now skips compilation on MacOS.

Newton now skips compilation on MacOS.

Strict

Rem
	bbdoc: Netwon Game Dynamics for MaxB3D
End Rem
Module MaxB3D.Newton
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

?Not MacOS

' Separated to bypass a "feature" that would otherwise result in a linking error...ugh.
Import "driver.bmx"

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function NewtonCollisionDriver:TNewtonCollisionDriver()
	Global _driver:TNewtonCollisionDriver=New TNewtonCollisionDriver
	Return _driver
End Function

?

Strict

Rem
	bbdoc: Netwon Game Dynamics for MaxB3D
End Rem
Module MaxB3D.Newton
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

' Seperated to bypass a "feature" that would otherwise result in a linking error...ugh.
Import "driver.bmx"

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function NewtonCollisionDriver:TNewtonCollisionDriver()
	Global _driver:TNewtonCollisionDriver=New TNewtonCollisionDriver
	Return _driver
End Function


Played with collision tree building.

Played with collision tree building.

Strict

Import "worldconfig.bmx"
Import "entity.bmx"
Import "surface.bmx"
Import "bone.bmx"

Import MaxB3D.Logging

Private
Function ModuleLog(message$)
	TMaxB3DLogger.Write "core/mesh",message
End Function

Public

Type TMesh Extends TAnimEntity
	Field _surfaces:TSurface[]

	Field _bone:TBone[]

	Field _tree:Byte Ptr
	Field _resettree

	Field _width,_height,_depth,_updatebounds
	Field _minx#,_miny#,_minz#,_maxx#,_maxy#,_maxz#

	Method HasAlpha()
		If Super.HasAlpha() Return True
		For Local surface:TSurface=EachIn _surfaces
			If surface.HasAlpha() Return True
		Next
	End Method

	Method Lists[]()
		Return Super.Lists()+[WORLDLIST_MESH, WORLDLIST_RENDER]
	End Method

	Method CopyData:TEntity(entity:TEntity)
		Local mesh:TMesh=TMesh(entity)
		For Local surface:TSurface=EachIn mesh._surfaces
			AppendSurface surface
		Next
		Return Super.CopyData(entity)
	End Method

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

	Method Clone:TMesh(parent:TEntity=Null)
		Local mesh:TMesh=Copy(parent)
		For Local i=0 To _surfaces.length-1
			mesh._surfaces[i] = _surfaces[i].Copy()
		Next
		Return mesh
	End Method

	Method Add(mesh:TMesh)
		For Local surface:TSurface=EachIn mesh._surfaces
			Local new_surface:TSurface=surface.Copy()
			new_surface.SetBrush new_surface._brush.Merge(mesh._brush)
			AppendSurface new_surface
		Next
	End Method

	Method AddSurface:TSurface(brush:TBrush=Null,vertices=0,triangles=0)
		Local surface:TSurface=New TSurface
		surface.SetBrush brush
		surface.Resize vertices,triangles
		Return AppendSurface(surface)
	End Method

	Method AppendSurface:TSurface(surface:TSurface)
		_surfaces=_surfaces[.._surfaces.length+1]
		_surfaces[_surfaces.length-1]=surface
		surface._boundsupdatedmsg.Add BoundsUpdated,Self
		Return surface
	End Method

	Method SwapSurface(surface:TSurface,new_surface:TSurface)
		For Local i=0 To _surfaces.length-1
			If _surfaces[i]=surface _surfaces[i]=new_surface
		Next
	End Method

	Method GetSurface:TSurface(index)
		Return _surfaces[index]
	End Method

	Method CountSurfaces()
		Return _surfaces.length
	End Method

	Method UpdateBounds(force=False)
		For Local surface:TSurface=EachIn _surfaces
			surface.UpdateBounds force
		Next
		If Not _updatebounds Return

		_minx=INFINITY;_miny=INFINITY;_minz=INFINITY
		_maxx=-INFINITY;_maxy=-INFINITY;_maxz=-INFINITY

		For Local surface:TSurface=EachIn _surfaces
			_minx=Min(_minx,surface._minx);_maxx=Max(_maxx,surface._maxx)
			_miny=Min(_miny,surface._miny);_maxy=Max(_maxy,surface._maxy)
			_minz=Min(_minz,surface._minz);_maxz=Max(_maxz,surface._maxz)
		Next

		_width=_maxx-_minx;_height=_maxy-_miny;_depth=_maxz-_minz
		If _cullradius>=0
			Local radius#=Max(_width,Max(_height,_depth))/2.0
			Local radius_sqr#=radius*radius
			_cullradius=Sqr(radius_sqr+radius_sqr+radius_sqr)
		EndIf
		_updatebounds=False
	End Method

	Function BoundsUpdated(entity:Object)
		TMesh(entity)._updatebounds=True
	End Function

	Method GetSize(width# Var,height# Var,depth# Var)
		UpdateBounds
		width=_width;height=_height;depth=_depth
	End Method

	Method Fit(x#,y#,z#,width#,height#,depth#,uniform=False)
		UpdateBounds

		Local wr#,hr#,dr#
		wr=_width/width;hr=_height/height;dr=_depth/depth
		If uniform
			If wr>=hr And wr>=dr
				y=y+((height-(_height/wr))/2.0)
				z=z+((depth-(_depth/wr))/2.0)

				height=_height/wr
				depth=_depth/wr
			ElseIf hr>dr
				x=x+((width-(_width/hr))/2.0)
				z=z+((depth-(_depth/hr))/2.0)

				width=_width/hr
				depth=_depth/hr
			Else
				x=x+((width-(_width/dr))/2.0)
				y=y+((height-(_height/dr))/2.0)

				width=_width/dr
				height=_height/dr
			EndIf
		EndIf
		wr=_width/width;hr=_height/height;dr=_depth/depth

		For Local surface:TSurface=EachIn _surfaces
			For Local v=0 To surface._vertexcnt-1
				Local vx#,vy#,vz#,nx#,ny#,nz#
				surface.GetCoords v,vx,vy,vz
				surface.GetNormal v,nx,ny,nz

				Local ux#,uy#,uz#

				If _width<0.0001 And _width>-0.0001 Then ux=0.0 Else ux=(vx-_minx)/_width
				If _height<0.0001 And _height>-0.0001 Then uy=0.0 Else uy=(vy-_miny)/_height
				If _depth<0.0001 And _depth>-0.0001 Then uz=0.0 Else uz=(vz-_minz)/_depth

				surface.SetCoords v,x+(ux*width),y+(uy*height),z+(uz*depth)
				surface.SetNormal v,nx*wr,ny*hr,nz*dr
			Next
		Next
		UpdateBounds True
	End Method

	Method Center()
		Local w#,h#,d#
		GetSize w,h,d
		Fit -w/2.0,-h/2.0,-d/2.0,w,h,d
	End Method

	Method Flip()
		For Local surface:TSurface=EachIn _surfaces
			surface.Flip()
		Next
	End Method

	Method Unweld()
		For Local surface:TSurface=EachIn _surfaces
			surface.Unweld()
		Next
	End Method

	Method GetCounts(vertices Var, triangles Var)
		vertices = 0
		triangles = 0
		For Local surface:TSurface=EachIn _surfaces
			triangles:+surface._trianglecnt
			vertices:+surface._vertexcnt
		Next
	End Method

	Method UpdateNormals(smoothing=True)
		For Local surface:TSurface=EachIn _surfaces
			surface.UpdateNormals(smoothing)
		Next
	End Method

	Method Scale(x#,y#,z#)
		Return Morph(TMatrix.Scale(x,y,z))
	End Method

	Method Rotate(pitch#,yaw#,roll#)
		Return Morph(TMatrix.YawPitchRoll(yaw,pitch,roll))
	End Method

	Method Position(x#,y#,z#)
		Return Morph(TMatrix.Translation(x#,y#,z#))
	End Method

	Method Morph(matrix:TMatrix)
		For Local surface:TSurface=EachIn _surfaces
			surface.Transform matrix
		Next
	End Method

	Method ForEachSurfaceDo(func(surface:TSurface,data:Object),data:Object=Null)
		For Local surface:TSurface=EachIn _surfaces
			func surface,data
		Next
	End Method

	Method TreeCheck:Byte Ptr()
		If _resettree=True
			If _tree<>Null C_DeleteColTree(_tree);_tree=Null
			_resettree=False
		EndIf

		If _tree=Null
			Local vertextotal,info:Byte Ptr=C_NewMeshInfo(),count

			For Local surface:TSurface=EachIn _surfaces
				count:+1
				Local triangles[]=surface._triangle[..]
				Local vertices#[]=surface._vertexpos[..]

				If surface._trianglecnt<>0 And surface._vertexcnt<>0
					For Local i=0 To surface._trianglecnt-1
						triangles[i*3+0]:+vertextotal
						triangles[i*3+1]:+vertextotal
						triangles[i*3+2]:+vertextotal

						Local old=triangles[i*3+0]
						triangles[i*3+0]=triangles[i*3+2]
						triangles[i*3+2]=old
					Next

					Rem

					For Local i=0 To surface._vertexcnt-1
						vertices[i*3+2]=-vertices[i*3+2]
					Next

					End Rem

					C_AddSurface(info,surface._trianglecnt,surface._vertexcnt,triangles,vertices,count)
					vertextotal:+surface._vertexcnt
				EndIf
			Next

			_tree=C_CreateColTree(info)
			C_DeleteMeshInfo(info)
		EndIf
		Return _tree
	End Method

	Method GetCullParams(x# Var,y# Var,z# Var,radius# Var)
		UpdateBounds

		x=_minx;y=_miny;z=_minz
		x=x+(_maxx-_minx)/2.0
		y=y+(_maxy-_miny)/2.0
		z=z+(_maxz-_minz)/2.0

		_matrix.TransformVec3 x,y,z

		Local sx#,sy#,sz#
		GetScale sx,sy,sz,True

		radius=GetCullRadius()

		Local rx#=radius*sx,ry#=radius*sy,rz#=radius*sz
		radius=Max(Max(rx,ry),rz)
	End Method

	Method ObjectEnumerator:TObjectArrayEnumerator()
		Return New TObjectArrayEnumerator.Create(_surfaces)
	End Method
End Type

Strict

Import "worldconfig.bmx"
Import "entity.bmx"
Import "surface.bmx"
Import "bone.bmx"

Import MaxB3D.Logging

Private
Function ModuleLog(message$)
	TMaxB3DLogger.Write "core/mesh",message
End Function

Public

Type TMesh Extends TAnimEntity
	Field _surfaces:TSurface[]

	Field _bone:TBone[]

	Field _tree:Byte Ptr
	Field _resettree

	Field _width,_height,_depth,_updatebounds
	Field _minx#,_miny#,_minz#,_maxx#,_maxy#,_maxz#

	Method HasAlpha()
		If Super.HasAlpha() Return True
		For Local surface:TSurface=EachIn _surfaces
			If surface.HasAlpha() Return True
		Next
	End Method

	Method Lists[]()
		Return Super.Lists()+[WORLDLIST_MESH, WORLDLIST_RENDER]
	End Method

	Method CopyData:TEntity(entity:TEntity)
		Local mesh:TMesh=TMesh(entity)
		For Local surface:TSurface=EachIn mesh._surfaces
			AppendSurface surface
		Next
		Return Super.CopyData(entity)
	End Method

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

	Method Clone:TMesh(parent:TEntity=Null)
		Local mesh:TMesh=Copy(parent)
		For Local i=0 To _surfaces.length-1
			mesh._surfaces[i] = _surfaces[i].Copy()
		Next
		Return mesh
	End Method

	Method Add(mesh:TMesh)
		For Local surface:TSurface=EachIn mesh._surfaces
			Local new_surface:TSurface=surface.Copy()
			new_surface.SetBrush new_surface._brush.Merge(mesh._brush)
			AppendSurface new_surface
		Next
	End Method

	Method AddSurface:TSurface(brush:TBrush=Null,vertices=0,triangles=0)
		Local surface:TSurface=New TSurface
		surface.SetBrush brush
		surface.Resize vertices,triangles
		Return AppendSurface(surface)
	End Method

	Method AppendSurface:TSurface(surface:TSurface)
		_surfaces=_surfaces[.._surfaces.length+1]
		_surfaces[_surfaces.length-1]=surface
		surface._boundsupdatedmsg.Add BoundsUpdated,Self
		Return surface
	End Method

	Method SwapSurface(surface:TSurface,new_surface:TSurface)
		For Local i=0 To _surfaces.length-1
			If _surfaces[i]=surface _surfaces[i]=new_surface
		Next
	End Method

	Method GetSurface:TSurface(index)
		Return _surfaces[index]
	End Method

	Method CountSurfaces()
		Return _surfaces.length
	End Method

	Method UpdateBounds(force=False)
		For Local surface:TSurface=EachIn _surfaces
			surface.UpdateBounds force
		Next
		If Not _updatebounds Return

		_minx=INFINITY;_miny=INFINITY;_minz=INFINITY
		_maxx=-INFINITY;_maxy=-INFINITY;_maxz=-INFINITY

		For Local surface:TSurface=EachIn _surfaces
			_minx=Min(_minx,surface._minx);_maxx=Max(_maxx,surface._maxx)
			_miny=Min(_miny,surface._miny);_maxy=Max(_maxy,surface._maxy)
			_minz=Min(_minz,surface._minz);_maxz=Max(_maxz,surface._maxz)
		Next

		_width=_maxx-_minx;_height=_maxy-_miny;_depth=_maxz-_minz
		If _cullradius>=0
			Local radius#=Max(_width,Max(_height,_depth))/2.0
			Local radius_sqr#=radius*radius
			_cullradius=Sqr(radius_sqr+radius_sqr+radius_sqr)
		EndIf
		_updatebounds=False
	End Method

	Function BoundsUpdated(entity:Object)
		TMesh(entity)._updatebounds=True
	End Function

	Method GetSize(width# Var,height# Var,depth# Var)
		UpdateBounds
		width=_width;height=_height;depth=_depth
	End Method

	Method Fit(x#,y#,z#,width#,height#,depth#,uniform=False)
		UpdateBounds

		Local wr#,hr#,dr#
		wr=_width/width;hr=_height/height;dr=_depth/depth
		If uniform
			If wr>=hr And wr>=dr
				y=y+((height-(_height/wr))/2.0)
				z=z+((depth-(_depth/wr))/2.0)

				height=_height/wr
				depth=_depth/wr
			ElseIf hr>dr
				x=x+((width-(_width/hr))/2.0)
				z=z+((depth-(_depth/hr))/2.0)

				width=_width/hr
				depth=_depth/hr
			Else
				x=x+((width-(_width/dr))/2.0)
				y=y+((height-(_height/dr))/2.0)

				width=_width/dr
				height=_height/dr
			EndIf
		EndIf
		wr=_width/width;hr=_height/height;dr=_depth/depth

		For Local surface:TSurface=EachIn _surfaces
			For Local v=0 To surface._vertexcnt-1
				Local vx#,vy#,vz#,nx#,ny#,nz#
				surface.GetCoords v,vx,vy,vz
				surface.GetNormal v,nx,ny,nz

				Local ux#,uy#,uz#

				If _width<0.0001 And _width>-0.0001 Then ux=0.0 Else ux=(vx-_minx)/_width
				If _height<0.0001 And _height>-0.0001 Then uy=0.0 Else uy=(vy-_miny)/_height
				If _depth<0.0001 And _depth>-0.0001 Then uz=0.0 Else uz=(vz-_minz)/_depth

				surface.SetCoords v,x+(ux*width),y+(uy*height),z+(uz*depth)
				surface.SetNormal v,nx*wr,ny*hr,nz*dr
			Next
		Next
		UpdateBounds True
	End Method

	Method Center()
		Local w#,h#,d#
		GetSize w,h,d
		Fit -w/2.0,-h/2.0,-d/2.0,w,h,d
	End Method

	Method Flip()
		For Local surface:TSurface=EachIn _surfaces
			surface.Flip()
		Next
	End Method

	Method Unweld()
		For Local surface:TSurface=EachIn _surfaces
			surface.Unweld()
		Next
	End Method

	Method GetCounts(vertices Var, triangles Var)
		vertices = 0
		triangles = 0
		For Local surface:TSurface=EachIn _surfaces
			triangles:+surface._trianglecnt
			vertices:+surface._vertexcnt
		Next
	End Method

	Method UpdateNormals(smoothing=True)
		For Local surface:TSurface=EachIn _surfaces
			surface.UpdateNormals(smoothing)
		Next
	End Method

	Method Scale(x#,y#,z#)
		Return Morph(TMatrix.Scale(x,y,z))
	End Method

	Method Rotate(pitch#,yaw#,roll#)
		Return Morph(TMatrix.YawPitchRoll(yaw,pitch,roll))
	End Method

	Method Position(x#,y#,z#)
		Return Morph(TMatrix.Translation(x#,y#,z#))
	End Method

	Method Morph(matrix:TMatrix)
		For Local surface:TSurface=EachIn _surfaces
			surface.Transform matrix
		Next
	End Method

	Method ForEachSurfaceDo(func(surface:TSurface,data:Object),data:Object=Null)
		For Local surface:TSurface=EachIn _surfaces
			func surface,data
		Next
	End Method

	Method TreeCheck:Byte Ptr()
		If _resettree=True
			If _tree<>Null C_DeleteColTree(_tree);_tree=Null
			_resettree=False
		EndIf

		If _tree=Null
			Local vertextotal,info:Byte Ptr=C_NewMeshInfo(),count

			For Local surface:TSurface=EachIn _surfaces
				count:+1
				Local triangles[]=surface._triangle[..]
				Local vertices:Float[]=surface._vertexpos[..]

				If surface._trianglecnt<>0 And surface._vertexcnt<>0
					For Local i=0 To surface._trianglecnt-1
						triangles[i*3+0]:+vertextotal
						triangles[i*3+1]:+vertextotal
						triangles[i*3+2]:+vertextotal
					Next

					For Local i=0 To surface._trianglecnt-1
						Local old=triangles[i*3+0]
						triangles[i*3+0]=triangles[i*3+2]
						triangles[i*3+2]=old
					Next

					For Local i=0 To surface._vertexcnt-1
						vertices[i*3+2]=-vertices[i*3+2]
					Next

					C_AddSurface(info,surface._trianglecnt,surface._vertexcnt,triangles,vertices,count)
					vertextotal:+surface._vertexcnt
				EndIf
			Next

			_tree=C_CreateColTree(info)
			C_DeleteMeshInfo(info)
		EndIf
		Return _tree
	End Method

	Method GetCullParams(x# Var,y# Var,z# Var,radius# Var)
		UpdateBounds

		x=_minx;y=_miny;z=_minz
		x=x+(_maxx-_minx)/2.0
		y=y+(_maxy-_miny)/2.0
		z=z+(_maxz-_minz)/2.0

		_matrix.TransformVec3 x,y,z

		Local sx#,sy#,sz#
		GetScale sx,sy,sz,True

		radius=GetCullRadius()

		Local rx#=radius*sx,ry#=radius*sy,rz#=radius*sz
		radius=Max(Max(rx,ry),rz)
	End Method

	Method ObjectEnumerator:TObjectArrayEnumerator()
		Return New TObjectArrayEnumerator.Create(_surfaces)
	End Method
End Type


Documentation correction

Documentation correction

Rem
	bbdoc: Exception for TXMLRPC_Server
End Rem
Type TXMLRPC_Server_Exception Extends TXMLRPC_Exception
End Type

Rem
	bbdoc: XML-RPC Server
	about: This type will allow you to create an XML-RPC server.
End Rem
Type TXMLRPC_Server

	Field server:Byte Ptr

	Rem
		bbdoc: Creates a new XML-RPC server
		about: After calling the constructor you might want to use RegisterMethod() to add methods to the server
	End Rem
	Method New()
		Self.server = XMLRPC_ServerCreate()
	End Method

	Rem
		bbdoc: Call this method when your done with the server
	End Rem
	Method Shutdown()
		If Self.server
			XMLRPC_ServerDestroy(Self.server)
		End If
	End Method

	Rem
		bbdoc: Handles an request
		about: Will process the xmlMessage and returns a TXMLRPC_Response_Data object containing the returned values by the callback function
	End Rem
	Method HandleInput:TXMLRPC_Response_Data(xmlMessage:String)
		Local xmlRequest:Byte Ptr = xmlMessage.ToCString()
		'Create XMLRPC_REQUEST object from given xmlMessage
		Local request:Byte Ptr = XMLRPC_REQUEST_FromXML(xmlRequest, Null, Null)
		MemFree(xmlRequest)

		'Create response
		Local response:Byte Ptr = XMLRPC_RequestNew()
		XMLRPC_RequestSetRequestType(response, xmlrpc_request_response)
		'Set response data by calling the callback function
		XMLRPC_RequestSetData(response, XMLRPC_ServerCallMethod(Self.server, request, Null))
		'Copy output options from request
		XMLRPC_RequestSetOutputOptions(response, XMLRPC_RequestGetOutputOptions(request))

		'Convert response to XML message
		Local xmlResponse:Byte Ptr = XMLRPC_REQUEST_ToXML(response, Null)
		'Create workable response data out of it
		Local responseData:TXMLRPC_Response_Data = New TXMLRPC_Response_Data.Create(convertUTF8toISO8859(xmlResponse), XMLRPC_RequestGetOutputOptions(response))
		XMLRPC_Free(xmlResponse)

		XMLRPC_RequestFree(request, 1)
		XMLRPC_RequestFree(response, 1)

		Return responseData
	End Method

	Rem
		bbdoc: Register a function to the XML-RPC server
		about: functionName will be the identifier. callBack is a pointer to a function
	End Rem
	Method RegisterMethod(functionName:String, callBack:Byte Ptr)
		Local cStr:Byte Ptr = functionName.ToCString()
		XMLRPC_ServerRegisterMethod(Self.server, cStr, callBack)
		MemFree(cStr)
	End Method
End Type

Rem
	bbdoc: Exception for TXMLRPC_Server
End Rem
Type TXMLRPC_Server_Exception Extends TXMLRPC_Exception
End Type

Rem
	bbdoc: XML-RPC Server
	about: This type will allow you to create an XML-RPC server.
End Rem
Type TXMLRPC_Server

	Field server:Byte Ptr

	Rem
		bbdoc: Creates a new XML-RPC server
		about: After calling the constructor you might want to use RegisterMethod() to add methods to the server
	End Rem
	Method New()
		Self.server = XMLRPC_ServerCreate()
	End Method

	Rem
		bbdoc: Call this method when your done with the server
	End Rem
	Method Shutdown()
		If Self.server
			XMLRPC_ServerDestroy(Self.server)
		End If
	End Method

	Rem
		bbdoc: Handles an incoming message
		about: Will try to execute the method set in the methodName field, and returns a TXMLRPC_Response_Data object containing the returned values
	End Rem
	Method HandleInput:TXMLRPC_Response_Data(xmlMessage:String)
		Local xmlRequest:Byte Ptr = xmlMessage.ToCString()
		'Create XMLRPC_REQUEST object from given xmlMessage
		Local request:Byte Ptr = XMLRPC_REQUEST_FromXML(xmlRequest, Null, Null)
		MemFree(xmlRequest)

		'Create response
		Local response:Byte Ptr = XMLRPC_RequestNew()
		XMLRPC_RequestSetRequestType(response, xmlrpc_request_response)
		'Set response data by calling the callback function
		XMLRPC_RequestSetData(response, XMLRPC_ServerCallMethod(Self.server, request, Null))
		'Copy output options from request
		XMLRPC_RequestSetOutputOptions(response, XMLRPC_RequestGetOutputOptions(request))

		'Convert response to XML message
		Local xmlResponse:Byte Ptr = XMLRPC_REQUEST_ToXML(response, Null)
		'Create workable response data out of it
		Local responseData:TXMLRPC_Response_Data = New TXMLRPC_Response_Data.Create(convertUTF8toISO8859(xmlResponse), XMLRPC_RequestGetOutputOptions(response))
		XMLRPC_Free(xmlResponse)

		XMLRPC_RequestFree(request, 1)
		XMLRPC_RequestFree(response, 1)

		Return responseData
	End Method

	Rem
		bbdoc: Register a function to the XML-RPC server
		about: functionName will be the identifier. callBack is a pointer to a function
	End Rem
	Method RegisterMethod(functionName:String, callBack:Byte Ptr)
		Local cStr:Byte Ptr = functionName.ToCString()
		XMLRPC_ServerRegisterMethod(Self.server, cStr, callBack)
		MemFree(cStr)
	End Method
End Type


changed current to curr.

changed current to curr.

Strict

Import MaxB3D.Drivers
Import MaxB3D.Loaders

Graphics 800,600

Local light:TLight = CreateLight()

Local camera:TCamera = CreateCamera()
SetEntityPosition camera,0,0,-3

Global primitives:TMesh[3]

primitives[0] = CreateBunny()
SetEntityName primitives[0], "Stanford Bunny"
SetEntityScale primitives[0], 2,2,2

primitives[1] = CreateMonkeyHead()
SetEntityName primitives[1], "Suzanne (Blender Monkey)"

primitives[2] = CreateTeapot()
SetEntityName primitives[2], "Utah Teapot"

Local curr

While Not KeyDown(KEY_ESCAPE) And Not AppTerminate()
	For Local i = 0 To 9
		If KeyHit(KEY_0 + i)
			curr = i
			Exit
		EndIf
	Next

	If KeyHit(KEY_UP) Or KeyHit(KEY_LEFT) curr :- 1
	If KeyHit(KEY_DOWN) Or KeyHit(KEY_RIGHT) curr :+ 1

	If curr > primitives.length -1 curr = 0
	If curr < 0 curr = primitives.length - 1

	For Local i = 0 To primitives.length-1
		SetEntityVisible primitives[i], curr = i
	Next

	TurnEntity primitives[curr],0,1,0

	SetWireFrame KeyDown(KEY_W)

	RenderWorld
	DoMax2D
	DrawText "Press 1, 2, or 3 to select a particular meshes.",0,0
	DrawText "Press Up/Left or Down/Right to cycle through meshes.",0,15
	DrawText "Current mesh: "+GetEntityName(primitives[curr]),0,30

	Flip
Wend

Strict

Import MaxB3D.Drivers
Import MaxB3D.Loaders

Graphics 800,600

Local light:TLight = CreateLight()

Local camera:TCamera = CreateCamera()
SetEntityPosition camera,0,0,-3

Global primitives:TMesh[3]

primitives[0] = CreateBunny()
SetEntityName primitives[0], "Stanford Bunny"
SetEntityScale primitives[0], 2,2,2

primitives[1] = CreateMonkeyHead()
SetEntityName primitives[1], "Suzanne (Blender Monkey)"

primitives[2] = CreateTeapot()
SetEntityName primitives[2], "Utah Teapot"

Local current

While Not KeyDown(KEY_ESCAPE) And Not AppTerminate()
	For Local i = 0 to 9
		If KeyHit(KEY_0 + i)
			current = i
			Exit
		EndIf
	Next

	If KeyHit(KEY_UP) Or KeyHit(KEY_LEFT) current :- 1
	If KeyHit(KEY_DOWN) Or KeyHit(KEY_RIGHT) current :+ 1

	If current > primitives.length -1 current = 0
	If current < 0 current = primitives.length - 1

	For Local i = 0 To primitives.length-1
		SetEntityVisible primitives[i], current = i
	Next

	TurnEntity primitives[current],0,1,0

	SetWireFrame KeyDown(KEY_W)

	RenderWorld
	DoMax2D
	DrawText "Press 1, 2, or 3 to select a particular meshes.",0,0
	DrawText "Press Up/Left or Down/Right to cycle through meshes.",0,15
	DrawText "Current mesh: "+GetEntityName(primitives[current]),0,30

	Flip
Wend


small fix

small fix

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.Load(url)
		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

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.Load(url)
		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