Try and fix

Try and fix

import java, Font, Image, Quad from sauce3

Constants = require "sauce3/wrappers"
Color     = java.require "com.badlogic.gdx.graphics.Color"
Gdx       = java.require "com.badlogic.gdx.Gdx"
GL20      = java.require "com.badlogic.gdx.graphics.GL20"
Matrix4   = java.require "com.badlogic.gdx.math.Matrix4"
Sauce3VM   = java.require "sauce3.Sauce3VM"

OrthographicCamera = java.require "com.badlogic.gdx.graphics.OrthographicCamera"
ShapeRender        = java.require "com.badlogic.gdx.graphics.glutils.ShapeRenderer"
SpriteBatch        = java.require "com.badlogic.gdx.graphics.g2d.SpriteBatch"
shader = SpriteBatch\createDefaultShader!
batch = java.new SpriteBatch, 1000, shader
shapes = java.new ShapeRender
font = Font!
shapes\setAutoShapeType true
matrix = java.new Matrix4
color = java.new Color, 1, 1, 1, 1
background = java.new Color, 0, 0, 0, 1
blending = "alpha"
batch\setColor color
shapes\setColor color
font.font\setColor color
camera = java.new OrthographicCamera
camera\setToOrtho true
batch\setProjectionMatrix camera.combined
shapes\setProjectionMatrix camera.combined
matrixDirty = false

check = (textureBased) ->
  if textureBased
    if matrixDirty
      batch\setTransformMatrix matrix
      matrixDirty = false

    if shapes\isDrawing! then YaeVM.util\endShapes shapes
    if not batch\isDrawing! then batch\begin!
  else
    if matrixDirty
      shapes\setTransformMatrix matrix
      matrixDirty = false

    if batch\isDrawing! then YaeVM.util\endBatch batch
    if not shapes\isDrawing! then shapes\begin!

---
-- Draws a filled or unfilled arc at position (x, y). The arc is drawn from
-- angle1 to angle2 in radians.
-- @string mode How to draw the arc.
-- @number x The position of the center along x-axis.
-- @number y The position of the center along y-axis.
-- @number radius Radius of the arc.
-- @number angle1 The angle at which the arc begins.
-- @number angle2 The angle at which the arc terminates.
-- @usage
-- -- Drawing half a circle
-- yae.draw = ->
--   yae.graphics.arc( "fill", 400, 300, 100, 0, math.pi )
--
-- -- Drawing Pacman
-- pacwidth = math.pi / 6 -- size of his mouth
-- yae.draw = ->
--   yae.graphics.setColor( 255, 255, 0 ) -- pacman needs to be yellow
--   yae.graphics.arc( "fill", 400, 300, 100, pacwidth, (math.pi * 2) - pacwidth )
arc = (mode, x, y, radius, angle1, angle2) ->
  check false
  shapes\set Constants.shapetypes[mode]
  shapes\arc x, y, radius, math.deg(angle1), math.deg(angle2)

circle = (mode, x, y, radius) ->
  check false
  shapes\set Constants.shapetypes[mode]
  shapes\circle x, y, radius

clear = ->
  Gdx.gl\glClearColor background.r, background.g, background.b, background.a
  Gdx.gl\glClear GL20.GL_COLOR_BUFFER_BIT

draw = (image, x=0, y=0, r=0, sx=1, sy=1, ox=0, oy=0) ->
  check true

  w = image\get_width!
  h = image\get_height!
  srcX = 0
  srcY = 0
  srcW = w
  srcH = h
  x -= ox
  y -= oy

  batch\draw image.texture, x, y, ox, oy, w, h, sx, sy, r, srcX, srcY, srcW, srcH, false, true

drawq = (image, quad, x=0, y=0, r=0, sx=1, sy=1, ox=0, oy=0) ->
  check true

  w = quad.width
  h = quad.height
  srcX = quad.x
  srcY = quad.y
  srcW = quad.sw
  srcH = quad.sh
  x -= ox
  y -= oy

  batch\draw image.texture, x, y, ox, oy, w, h, sx, sy, r, srcX, srcY, srcW, srcH, false, true

ellipse = (mode, x, y, width, height) ->
  check false
  shapes\set Constants.shapetypes[mode]
  shapes\ellipse x, y, width, height

getBackgroundColor = ->
  return background.r * 255, background.g * 255, background.b * 255, background.a * 255

getBlendMode = ->
  return blending

getColor = ->
  return color.r * 255, color.g * 255, color.b * 255, color.a * 255

getFont = ->
  return font

line = (x1, y1, x2, y2) ->
  check false
  shapes\line x1, y1, x2, y2

newFont = (filename, size, filetype) ->
  return Font filename, size, filetype

newImage = (filename, format, filetype) ->
  return Image filename, format, filetype

newQuad = (x, y, width, height, sw, sh) ->
  return Quad x, y, width, height, sw, sh

origin = ->
  matrix\idt!
  matrixDirty = true

point = (x, y) ->
  check false
  shapes\point x, y, 0

polygon = (mode, ...) ->
  check false
  shapes\set Constants.shapetypes[mode]
  args = table.pack ...

  if type args[1] == "table"
    shapes\polygon args[1]
  else
    shapes\polygon args

present = ->
  if shapes\isDrawing! then YaeVM.util\endShapes shapes
  if batch\isDrawing! then YaeVM.util\endBatch batch

print = (text, x=0, y=0, r=0, sx=1, sy=1, ox=0, oy=0) ->
  check true
  tmp = nil

  if r != 0
    tmp = batch\getTransformMatrix!
    translate x, y
    rotate r
    translate -x, -y
    batch\setTransformMatrix matrix
    matrixDirty = false

  font.font\getData!\setScale sx, -sy
  font.font\draw batch, text, x - ox * sx, y - oy * sy

  if r != 0
    translate x, y
    rotate -r
    translate -x, -y
    batch\setTransformMatrix tmp
    matrixDirty = false

printf = (text, width=0, align="left", x=0, y=0, r=0, sx=1, sy=1, ox=0, oy=0) ->
  check true
  tmp = nil

  if r != 0
    tmp = batch\getTransformMatrix!
    translate x, y
    rotate r
    translate -x, -y
    batch\setTransformMatrix matrix
    matrixDirty = false

  font.font\getData!\setScale sx, -sy
  font.font\draw batch, text, x - ox * sx, y - oy * sy, width, Constants.aligns[align], true

  if r != 0
    translate x, y
    rotate -r
    translate -x, -y
    batch\setTransformMatrix tmp
    matrixDirty = false

rectangle = (mode, x, y, width, height) ->
  check false
  shapes\set Constants.shapetypes[mode]

  shapes\rect x, y, width, height

reset = ->
  shader = SpriteBatch\createDefaultShader!
  batch\setShader shader

  background\set 0.4, 0.3, 0.4, 1
  color\set 1, 1, 1, 1

  batch\setColor color
  shapes\setColor color
  font.font\setColor color
  matrix\idt!
  matrixDirty = true

rotate = (radians) ->
  matrix\rotate 0, 0, 1, math.deg(radians)
  matrixDirty = true

scale = (sx, sy) ->
  matrix\scale sx, sy, 1
  matrixDirty = true

setBackgroundColor = (r, g, b, a=255) ->
  background\set r / 255, g / 255, b / 255, a / 255

setBlendMode = (mode) ->
  blending = mode
  blendmode = Constants.blendmodes[blending]
  batch\setBlendFunction blendmode[1], blendmode[2]

setColor =  (r, g, b, a=255) ->
  color\set r / 255, g / 255, b / 255, a / 255
  batch\setColor color
  shapes\setColor color
  font.font\setColor color

setFont = (newFont) ->
  font = newFont

setNewFont = (filename, size, filetype) ->
  font = newFont filename, size, filetype

translate = (tx, ty) ->
  matrix\translate tx, ty, 0
  matrixDirty = true

{
  :arc
  :circle
  :clear
  :draw
  :drawq
  :ellipse
  :getBackgroundColor
  :getBlendMode
  :getColor
  :getFont
  :line
  :newFont
  :newImage
  :newQuad
  :origin
  :point
  :polygon
  :present
  :print
  :printf
  :rectangle
  :reset
  :rotate
  :scale
  :setBackgroundColor
  :setBlendMode
  :setColor
  :setFont
  :setNewFont
  :translate
}

import java, Font, Image, Quad from sauce3

Constants = require "sauce3/wrappers"
Color     = java.require "com.badlogic.gdx.graphics.Color"
Gdx       = java.require "com.badlogic.gdx.Gdx"
GL20      = java.require "com.badlogic.gdx.graphics.GL20"
Matrix4   = java.require "com.badlogic.gdx.math.Matrix4"
Sauce3VM   = java.require "sauce3.Sauce3VM"

OrthographicCamera = java.require "com.badlogic.gdx.graphics.OrthographicCamera"
ShapeRender        = java.require "com.badlogic.gdx.graphics.glutils.ShapeRenderer"
SpriteBatch        = java.require "com.badlogic.gdx.graphics.g2d.SpriteBatch"
shader = SpriteBatch\createDefaultShader!
batch = java.new SpriteBatch, 1000, shader
shapes = java.new ShapeRender
font = Font!
shapes\setAutoShapeType true
matrix = java.new Matrix4
color = java.new Color, 1, 1, 1, 1
background = java.new Color, 0, 0, 0, 1
blending = "alpha"
batch\setColor color
shapes\setColor color
font.font\setColor color
camera = java.new OrthographicCamera
camera\setToOrtho true
batch\setProjectionMatrix camera.combined
shapes\setProjectionMatrix camera.combined
matrixDirty = false

check = (textureBased) ->
  if textureBased
    if matrixDirty
      batch\setTransformMatrix matrix
      matrixDirty = false

    if shapes\isDrawing! then YaeVM.util\endShapes shapes
    if not batch\isDrawing! then batch\begin!
  else
    if matrixDirty
      shapes\setTransformMatrix matrix
      matrixDirty = false

    if batch\isDrawing! then YaeVM.util\endBatch batch
    if not shapes\isDrawing! then shapes\begin!

---
-- Draws a filled or unfilled arc at position (x, y). The arc is drawn from
-- angle1 to angle2 in radians.
-- @string mode How to draw the arc.
-- @number x The position of the center along x-axis.
-- @number y The position of the center along y-axis.
-- @number radius Radius of the arc.
-- @number angle1 The angle at which the arc begins.
-- @number angle2 The angle at which the arc terminates.
-- @usage
-- -- Drawing half a circle
-- yae.draw = ->
--   yae.graphics.arc( "fill", 400, 300, 100, 0, math.pi )
--
-- -- Drawing Pacman
-- pacwidth = math.pi / 6 -- size of his mouth
-- yae.draw = ->
--   yae.graphics.setColor( 255, 255, 0 ) -- pacman needs to be yellow
--   yae.graphics.arc( "fill", 400, 300, 100, pacwidth, (math.pi * 2) - pacwidth )
arc = (mode, x, y, radius, angle1, angle2) ->
  check false
  shapes\set Constants.shapetypes[mode]
  shapes\arc x, y, radius, math.deg(angle1), math.deg(angle2)

circle = (mode, x, y, radius) ->
  check false
  shapes\set Constants.shapetypes[mode]
  shapes\circle x, y, radius

clear = ->
  Gdx.gl\glClearColor background.r, background.g, background.b, background.a
  Gdx.gl\glClear GL20.GL_COLOR_BUFFER_BIT

draw = (image, x=0, y=0, r=0, sx=1, sy=1, ox=0, oy=0) ->
  check true

  w = image\getWidth!
  h = image\getHeight!
  srcX = 0
  srcY = 0
  srcW = w
  srcH = h
  x -= ox
  y -= oy

  batch\draw image.texture, x, y, ox, oy, w, h, sx, sy, r, srcX, srcY, srcW, srcH, false, true

drawq = (image, quad, x=0, y=0, r=0, sx=1, sy=1, ox=0, oy=0) ->
  check true

  w = quad.width
  h = quad.height
  srcX = quad.x
  srcY = quad.y
  srcW = quad.sw
  srcH = quad.sh
  x -= ox
  y -= oy

  batch\draw image.texture, x, y, ox, oy, w, h, sx, sy, r, srcX, srcY, srcW, srcH, false, true

ellipse = (mode, x, y, width, height) ->
  check false
  shapes\set Constants.shapetypes[mode]
  shapes\ellipse x, y, width, height

getBackgroundColor = ->
  return background.r * 255, background.g * 255, background.b * 255, background.a * 255

getBlendMode = ->
  return blending

getColor = ->
  return color.r * 255, color.g * 255, color.b * 255, color.a * 255

getFont = ->
  return font

line = (x1, y1, x2, y2) ->
  check false
  shapes\line x1, y1, x2, y2

newFont = (filename, size, filetype) ->
  return Font filename, size, filetype

newImage = (filename, format, filetype) ->
  return Image filename, format, filetype

newQuad = (x, y, width, height, sw, sh) ->
  return Quad x, y, width, height, sw, sh

origin = ->
  matrix\idt!
  matrixDirty = true

point = (x, y) ->
  check false
  shapes\point x, y, 0

polygon = (mode, ...) ->
  check false
  shapes\set Constants.shapetypes[mode]
  args = table.pack ...

  if type args[1] == "table"
    shapes\polygon args[1]
  else
    shapes\polygon args

present = ->
  if shapes\isDrawing! then YaeVM.util\endShapes shapes
  if batch\isDrawing! then YaeVM.util\endBatch batch

print = (text, x=0, y=0, r=0, sx=1, sy=1, ox=0, oy=0) ->
  check true
  tmp = nil

  if r != 0
    tmp = batch\getTransformMatrix!
    translate x, y
    rotate r
    translate -x, -y
    batch\setTransformMatrix matrix
    matrixDirty = false

  font.font\getData!\setScale sx, -sy
  font.font\draw batch, text, x - ox * sx, y - oy * sy

  if r != 0
    translate x, y
    rotate -r
    translate -x, -y
    batch\setTransformMatrix tmp
    matrixDirty = false

printf = (text, width=0, align="left", x=0, y=0, r=0, sx=1, sy=1, ox=0, oy=0) ->
  check true
  tmp = nil

  if r != 0
    tmp = batch\getTransformMatrix!
    translate x, y
    rotate r
    translate -x, -y
    batch\setTransformMatrix matrix
    matrixDirty = false

  font.font\getData!\setScale sx, -sy
  font.font\draw batch, text, x - ox * sx, y - oy * sy, width, Constants.aligns[align], true

  if r != 0
    translate x, y
    rotate -r
    translate -x, -y
    batch\setTransformMatrix tmp
    matrixDirty = false

rectangle = (mode, x, y, width, height) ->
  check false
  shapes\set Constants.shapetypes[mode]

  shapes\rect x, y, width, height

reset = ->
  shader = SpriteBatch\createDefaultShader!
  batch\setShader shader

  background\set 0.4, 0.3, 0.4, 1
  color\set 1, 1, 1, 1

  batch\setColor color
  shapes\setColor color
  font.font\setColor color
  matrix\idt!
  matrixDirty = true

rotate = (radians) ->
  matrix\rotate 0, 0, 1, math.deg(radians)
  matrixDirty = true

scale = (sx, sy) ->
  matrix\scale sx, sy, 1
  matrixDirty = true

setBackgroundColor = (r, g, b, a=255) ->
  background\set r / 255, g / 255, b / 255, a / 255

setBlendMode = (mode) ->
  blending = mode
  blendmode = Constants.blendmodes[blending]
  batch\setBlendFunction blendmode[1], blendmode[2]

setColor =  (r, g, b, a=255) ->
  color\set r / 255, g / 255, b / 255, a / 255
  batch\setColor color
  shapes\setColor color
  font.font\setColor color

setFont = (newFont) ->
  font = newFont

setNewFont = (filename, size, filetype) ->
  font = newFont filename, size, filetype

translate = (tx, ty) ->
  matrix\translate tx, ty, 0
  matrixDirty = true

{
  :arc
  :circle
  :clear
  :draw
  :drawq
  :ellipse
  :getBackgroundColor
  :getBlendMode
  :getColor
  :getFont
  :line
  :newFont
  :newImage
  :newQuad
  :origin
  :point
  :polygon
  :present
  :print
  :printf
  :rectangle
  :reset
  :rotate
  :scale
  :setBackgroundColor
  :setBlendMode
  :setColor
  :setFont
  :setNewFont
  :translate
}


Added flag for skipping existing directories.

rex = require "rex_pcre"
cli = require "cliargs"
lfs = require "lfs"

import insert from table

contains = (t, item) ->
	for k, v in pairs t
		return true if v == item
	false

local dir_list, dir_listing
dir_listing = (path, file) ->
	f = path .. '/' .. file
	attr = lfs.attributes(f).mode
	{file, attr, if attr == 'directory' then dir_list(f)}

dir_list = (path) ->
	[dir_listing(path, file) for file in lfs.dir(path) when not contains({'.', '..'}, file)]

map_listing = (structure, func) ->
	for k, v in pairs structure
		func(v, map_listing)

print_listing = (value, func) ->
	{file, mode, data} = value
	print file, mode
	func(data, print_listing) if data

file_ext = (str) ->
	"." .. str\match(".([^%.]+)$")

dbg = (b) ->
	"[DBG: #{(b and 'Y') or 'N'}]"

perform_action = (old, new, action) ->
	print dbg(debug) .. switch action
		when "copy"
			"[cp]: Not yet implemented."
		when "move"
			"[mv]: Not yet implemented."
		when "symlink", "hardlink"
			lfs.link(old, new, action == "hardlink" or false) if not debug
			"[link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata
		if rex.gsub(file, regex, '')
			if mode == 'directory'
				f = rex.gsub(file, regex, '')\match("^%s*(.-)%s*$")
				f = parent.out .. '/' .. f
				lfs.mkdir(f)
				process_files(data, regex, {in: parent.in .. '/' .. file, out: f})
			else
				ext = file_ext(file)
				newer_f = rex.gsub(file, regex, '')
				new_f = rex.match(newer_f, [[(.*)(v\d)]]) or newer_f
				new_f = new_f\gsub('[ ]', '')
				new_f = rex.gsub(new_f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
				old_f, new_f = parent.in .. '/' .. file, parent.out .. '/' .. new_f .. ext
				perform_action(old_f, new_f, args["a"])

		else
			print "ERR: ", file, mode

cli\set_name("regexulon.lua")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories", "")
cli\add_option("-a, --action=ACTION", "action to take: cp, mv, symlink, hardlink", "hardlink")
cli\add_flag("-e, --existing", "skip creating previously existing target directories.")
cli\add_flag("-d, --debug", "script will simulate execution.")
cli\add_flag("-V, --version", "prints the program version")
cli\add_flag("-v, --verbose", "verbose output")

export args = cli\parse_args()
return if not args

ignored_files = switch type(args["IGNORED"])
	when "string" then {}
	else args["IGNORED"]

inputdir = args["DIR"]
targetdir = args["TARGET"]
export debug = args["d"]

files = dir_list(args["DIR"])
regex = [==[\(.*?\)|\[.*?\]]==]

--map_listing(files, print_listing)
processed = process_files(files, regex, {in: inputdir, out: targetdir})

rex = require "rex_pcre"
cli = require "cliargs"
lfs = require "lfs"

import insert from table

contains = (t, item) ->
	for k, v in pairs t
		return true if v == item
	false

local dir_list, dir_listing
dir_listing = (path, file) ->
	f = path .. '/' .. file
	attr = lfs.attributes(f).mode
	{file, attr, if attr == 'directory' then dir_list(f)}

dir_list = (path) ->
	[dir_listing(path, file) for file in lfs.dir(path) when not contains({'.', '..'}, file)]

map_listing = (structure, func) ->
	for k, v in pairs structure
		func(v, map_listing)

print_listing = (value, func) ->
	{file, mode, data} = value
	print file, mode
	func(data, print_listing) if data

file_ext = (str) ->
	"." .. str\match(".([^%.]+)$")

dbg = (b) ->
	"[DBG: #{(b and 'Y') or 'N'}]"

perform_action = (old, new, action) ->
	print dbg(debug) .. switch action
		when "copy"
			"[cp]: Not yet implemented."
		when "move"
			"[mv]: Not yet implemented."
		when "symlink", "hardlink"
			lfs.link(old, new, action == "hardlink" or false) if not debug
			"[link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata
		if rex.gsub(file, regex, '')
			if mode == 'directory'
				f = rex.gsub(file, regex, '')\match("^%s*(.-)%s*$")
				f = parent.out .. '/' .. f
				lfs.mkdir(f)
				process_files(data, regex, {in: parent.in .. '/' .. file, out: f})
			else
				ext = file_ext(file)
				newer_f = rex.gsub(file, regex, '')
				new_f = rex.match(newer_f, [[(.*)(v\d)]]) or newer_f
				new_f = new_f\gsub('[ ]', '')
				new_f = rex.gsub(new_f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
				old_f, new_f = parent.in .. '/' .. file, parent.out .. '/' .. new_f .. ext
				perform_action(old_f, new_f, args["a"])

		else
			print "ERR: ", file, mode

cli\set_name("regexulon.lua")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories", "")
cli\add_option("-a, --action=ACTION", "action to take: cp, mv, symlink, hardlink", "hardlink")
cli\add_flag("-d, --debug", "script will simulate execution.")
cli\add_flag("-V, --version", "prints the program version")
cli\add_flag("-v, --verbose", "verbose output")
export args = cli\parse_args()
return if not args

ignored_files = switch type(args["IGNORED"])
	when "string" then {}
	else args["IGNORED"]

inputdir = args["DIR"]
targetdir = args["TARGET"]
export debug = args["d"]

files = dir_list(args["DIR"])
regex = [==[\(.*?\)|\[.*?\]]==]

--map_listing(files, print_listing)
processed = process_files(files, regex, {in: inputdir, out: targetdir})


test select

test select

-- Copyright (C) 2017-2018 DBot

-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at

--     http://www.apache.org/licenses/LICENSE-2.0

-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

ENABLE_FULLBRIGHT = CreateConVar('ppm2_editor_fullbright', '1', {FCVAR_ARCHIVE}, 'Disable lighting in editor')

local EDIT_TREE

inRadius = (val, min, max) -> val >= min and val <= max
inBox = (pointX, pointY, x, y, w, h) -> inRadius(pointX, x - w, x + w) and inRadius(pointY, y - h, y + h)

MODEL_BOX_PANEL = {
	SEQUENCE_STAND: 22
	PONY_VEC_Z: 64 * .7

	SEQUENCES: {
		'Standing':    22
		'Moving':      316
		'Walking':     232
		'Sit':         202
		'Swim':        370
		'Run':         328
		'Crouch walk': 286
		'Crouch':      76
		'Jump':        160
	}

	Init: =>
		@animRate = 1
		@seq = @SEQUENCE_STAND
		@hold = false
		@holdOnPoint = false
		@holdRightClick = false
		@canHold = true
		@lastTick = RealTimeL()

		@holdLast = 0
		@mouseX, @mouseY = 0, 0

		@angle = Angle(0, 0, 0)
		@distToPony = 90
		@trackBone = -1
		@trackBoneName = 'LrigSpine2'
		@trackAttach = -1
		@trackAttachName = ''
		@shouldAutoTrack = true
		@autoTrackPos = Vector(0, 0, 0)
		@fixedDistanceToPony = 100

		@vectorPos = Vector(@fixedDistanceToPony, 0, 0)
		@targetPos = Vector(0, 0, @PONY_VEC_Z * .7)

		@SetCursor('none')
		@SetMouseInputEnabled(true)

		@buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		@buildingModel\SetNoDraw(true)
		@buildingModel\SetModelScale(0.9)

		with @seqButton = vgui.Create('DComboBox', @)
			\SetSize(120, 20)
			\SetValue('Standing')
			\AddChoice(choice, num) for choice, num in pairs @SEQUENCES
			.OnSelect = (pnl = box, index = 1, value = '', data = value) -> @SetSequence(data)

		with @emotesPanel = PPM2.CreateEmotesPanel(@, @model, false)
			\SetPos(10, 40)
			\SetMouseInputEnabled(true)
			\SetVisible(true)

	UpdateAttachsIDs: =>
		@trackBone = @model\LookupBone(@trackBoneName) or -1 if @trackBoneName ~= ''
		@trackAttach = @model\LookupAttachment(@trackAttachName) or -1 if @trackAttachName ~= ''
	GetTrackedPosition: =>
		return @autoTrackPos if @shouldAutoTrack
		return @targetPos

	PerformLayout: (w = 0, h = 0) =>
		@seqButton\SetPos(10, 10)
		@emotesPanel\SetPos(10, 40)

	OnMousePressed: (code = MOUSE_LEFT) =>
		if code == MOUSE_LEFT and @canHold
			if not @selectPoint
				@hold = true
				@SetCursor('sizeall')
				@holdLast = RealTimeL() + .1
				@mouseX, @mouseY = gui.MousePos()
			else
				@holdOnPoint = true
				@SetCursor('crosshair')
		elseif code == MOUSE_RIGHT
			@holdRightClick = true

	OnMouseReleased: (code = MOUSE_LEFT) =>
		if code == MOUSE_LEFT and @canHold
			@holdOnPoint = false

			if not @selectPoint
				@hold = false
				@SetCursor('none')
			else
				@PushMenu(@selectPoint)
		elseif code == MOUSE_RIGHT
			@holdRightClick = false

	SetController: (val) => @controller = val

	OnMouseWheeled: (wheelDelta = 0) =>
		if @canHold
			@distToPony = math.clamp(@distToPony - wheelDelta * 10, 20, 150)

	GetModel: => @model
	GetSequence: => @seq
	GetAnimRate: => @animRate
	SetAnimRate: (val = 1) => @animRate = val
	SetSequence: (val = @SEQUENCE_STAND) =>
		@seq = val
		@model\SetSequence(@seq) if IsValid(@model)

	ResetSequence: => @SetSequence(@SEQUENCE_STAND)
	ResetSeq: => @SetSequence(@SEQUENCE_STAND)

	PushMenu: (menu) =>
		table.insert(@stack, menu)
		return @

	PopMenu: =>
		assert(#@stack > 1, 'invalid stack size to pop from')
		table.remove(@stack)
		return @

	CurrentMenu: => @stack[#@stack]
	InRoot: => #@stack == 1

	ResetModel: (ponydata, model = 'models/ppm/player_default_base_new.mdl') =>
		@model\Remove() if IsValid(@model)

		with @model = ClientsideModel(model)
			\SetNoDraw(true)
			.__PPM2_PonyData = ponydata
			\SetSequence(@seq)
			\FrameAdvance(0)
			\SetPos(Vector())
			\InvalidateBoneCache()

		@UpdateAttachsIDs()
		return @model

	Think: =>
		rtime = RealTimeL()
		delta = rtime - @lastTick
		@lastTick = rtime

		if IsValid(@model)
			@model\FrameAdvance(delta * @animRate)
			@model\SetPlaybackRate(1)
			@model\SetPoseParameter('move_x', 1)

			if @shouldAutoTrack
				if @trackAttach ~= -1
					{:Ang, :Pos} = @model\GetAttachment(@trackAttach)
					@autoTrackPos = Pos or Vector()
				elseif @trackBone ~= -1
					@autoTrackPos = @model\GetBonePosition(@trackBone) or Vector()
				else
					@shouldAutoTrack = false

		@hold = @IsHovered() if @hold

		if @hold
			x, y = gui.MousePos()
			deltaX, deltaY = x - @mouseX, y - @mouseY
			@mouseX, @mouseY = x, y
			{:pitch, :yaw, :roll} = @angle
			yaw -= deltaX * .5
			pitch = math.clamp(pitch - deltaY * .5, -45, 45)
			@angle = Angle(pitch, yaw % 360, roll)

		@vectorPos = Vector(@fixedDistanceToPony, 0, 0)
		@vectorPos\Rotate(@angle)
		@drawAngle = Angle(-@angle.p, @angle.y - 180)

	FLOOR_VECTOR: Vector(0, 0, -30)
	FLOOR_ANGLE: Vector(0, 0, 1)

	DRAW_WALLS: {
		{Vector(-4000, 0, 900), Vector(1, 0, 0), 8000, 2000}
		{Vector(4000, 0, 900), Vector(-1, 0, 0), 8000, 2000}
		{Vector(0, -4000, 900), Vector(0, 1, 0), 8000, 2000}
		{Vector(0, 4000, 900), Vector(0, -1, 0), 8000, 2000}
		{Vector(0, 0, 900), Vector(0, 0, -1), 8000, 8000}
	}

	WALL_COLOR: Color() - 255
	FLOOR_COLOR: Color() - 255
	EMPTY_VECTOR: Vector()

	Paint: (w = 0, h = 0) =>
		surface.SetDrawColor(0, 0, 0)
		surface.DrawRect(0, 0, w, h)
		return if not IsValid(@model)
		x, y = @LocalToScreen(0, 0)
		drawpos = @vectorPos + @GetTrackedPosition()
		cam.Start3D(drawpos, @drawAngle, @distToPony, x, y, w, h)

		if @holdRightClick
			@model\SetEyeTarget(drawpos)
			turnpitch, turnyaw = DLib.combat.turnAngle(@EMPTY_VECTOR, drawpos, Angle())

			if not inRadius(turnyaw, -20, 20)
				if turnyaw < 0
					@model\SetPoseParameter('head_yaw', turnyaw + 20)
				else
					@model\SetPoseParameter('head_yaw', turnyaw - 20)
			else
				@model\SetPoseParameter('head_yaw', 0)

			turnpitch = turnpitch + 20
			if not inRadius(turnpitch, -10, 0)
				if turnpitch < 0
					@model\SetPoseParameter('head_pitch', turnpitch + 10)
				else
					@model\SetPoseParameter('head_pitch', turnpitch)
			else
				@model\SetPoseParameter('head_pitch', 0)
		else
			@model\SetEyeTarget(@EMPTY_VECTOR)
			@model\SetPoseParameter('head_yaw', 0)
			@model\SetPoseParameter('head_pitch', 0)

		render.DrawQuadEasy(@FLOOR_VECTOR, @FLOOR_ANGLE, 7000, 7000, @FLOOR_COLOR)

		for {pos, ang, w, h} in *@DRAW_WALLS
			render.DrawQuadEasy(pos, ang, w, h, @WALL_COLOR)

		if ENABLE_FULLBRIGHT\GetBool()
			render.SuppressEngineLighting(true)
			render.ResetModelLighting(1, 1, 1)
			render.SetColorModulation(1, 1, 1)

		@buildingModel\DrawModel()
		ctrl = @controller\GetRenderController()

		if bg = @controller\GetBodygroupController()
			bg\ApplyBodygroups()

		with @model\PPMBonesModifier()
			\ResetBones()
			hook.Call('PPM2.SetupBones', nil, @model, @controller)
			\Think(true)

		with ctrl
			\DrawModels()
			\HideModels(true)
			\PreDraw(@model)

		@model\DrawModel()
		ctrl\PostDraw(@model)

		render.SuppressEngineLighting(false) if ENABLE_FULLBRIGHT\GetBool()

		menu = @CurrentMenu()
		@drawPoints = false

		if type(menu.points) == 'table'
			@drawPoints = true
			@pointsData = for point in *menu.points
				vecpos = point.getpos(@model)
				position = vecpos\ToScreen()
				{position, point, vecpos\Distance(drawpos)}

		cam.End3D()

		if @drawPoints
			lx, ly = x, y
			mx, my = gui.MousePos()
			mx, my = mx - lx, my - ly
			radius = ScreenScale(10)
			local drawnSelected
			min = 9999

			for pointdata in *@pointsData
				{:x, :y} = pointdata[1]
				x, y = x - lx, y - ly
				pointdata[1].x, pointdata[1].y = x, y

				if inBox(mx, my, x, y, radius, radius)
					if min > pointdata[3]
						drawnSelected = pointdata
						min = pointdata[3]

			@selectPoint = drawnSelected and drawnSelected[2] or false

			for pointdata in *@pointsData
				{:x, :y} = pointdata[1]

				if pointdata == drawnSelected
					surface.SetDrawColor(255, 255, 255)
				else
					surface.SetDrawColor(100, 100, 100)

				surface.DrawLine(x - radius, y - radius, x + radius, y + radius)
				surface.DrawLine(x + radius, y - radius, x - radius, y + radius)

			if not @hold and not @holdOnPoint
				if drawnSelected
					@SetCursor('hand')
				else
					@SetCursor('none')
		else
			@selectPoint = false

	OnRemove: =>
		@model\Remove() if IsValid(@model)
		@buildingModel\Remove() if IsValid(@buildingModel)
}

vgui.Register('PPM2Model2Panel', MODEL_BOX_PANEL, 'EditablePanel')

EDIT_TREE = {
	type: 'level'
	name: 'Pony overview'

	points: {
		{
			type: 'bone'
			target: 'LrigScull'
			link: 'head_submenu'
		}

		{
			type: 'bone'
			target: 'LrigSpine1'
			link: 'spine_length'
		}

		{
			type: 'bone'
			target: 'Tail01'
			link: 'tail'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_FL_Metacarpus'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_FR_Metacarpus'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_BR_LargeCannon'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_BL_LargeCannon'
			link: 'legs_submenu'
		}
	}

	children: {
		head_submenu: {
			type: 'level'
			name: 'Head anatomy'

			points: {
				{
					type: 'attach'
					target: 'eyes'
					link: 'eyes'
				}
			}

			children: {
				eyes: {
					type: 'menu'
					populate: =>
				}
			}
		}

		legs_submenu: {
			type: 'level'
			name: 'Hooves anatomy'

			points: {

			}

			children: {

			}
		}
	}
}

patchSubtree = (node) ->
	if type(node.children) == 'table'
		for childID, child in pairs node.children
			child.id = childID
			patchSubtree(child)

	if type(node.points) == 'table'
		for point in *node.points
			point.addvector = point.addvector or Vector()
			if type(node.children) == 'table'
				point.linkTable = node.children[point.link]
			switch point.type
				when 'point'
					point.getpos = => Vector(point.target)
				when 'bone'
					point.getpos = =>
						if not point.targetID or point.targetID == -1
							point.targetID = @LookupBone(point.target) or -1

						if point.targetID == -1
							return Vector(point.addvector)
						else
							return @GetBonePosition(point.targetID) + point.addvector
				when 'attach'
					point.getpos = =>
						if not point.targetID or point.targetID == -1
							point.targetID = @LookupAttachment(point.target) or -1

						if point.targetID == -1
							return Vector(point.addvector)
						else
							{:Pos, :Ang} = @GetAttachment(point.targetID)
							return Pos and (Pos + point.addvector) or Vector(point.addvector)

EDIT_TREE.id = 'root'
patchSubtree(EDIT_TREE)

ppm2_editor3 = ->
	if IsValid(PPM2.EDITOR3)
		PPM2.EDITOR3\SetVisible(true)
		PPM2.EDITOR3\MakePopup()
		return PPM2.EDITOR3
	PPM2.EDITOR3 = vgui.Create('DLib_Window')
	self = PPM2.EDITOR3
	@SetSize(ScrWL(), ScrHL())
	@SetPos(0, 0)
	@MakePopup()
	@SetTitle('PPM/2 Editor/3')
	@SetDraggable(false)
	@RemoveResize()

	with @modelPanel = vgui.Create('PPM2Model2Panel', @)
		\Dock(FILL)
		\DockMargin(3, 3, 3, 3)

	copy = PPM2.GetMainData()\Copy()
	ply = LocalPlayer()
	ent = @modelPanel\ResetModel()

	controller = copy\CreateCustomController(ent)
	controller\SetFlexLerpMultiplier(1.3)
	copy\SetController(controller)

	@modelPanel\SetController(controller)
	controller\SetupEntity(ent)
	controller\SetDisableTask(true)

	@modelPanel.stack = {EDIT_TREE}

concommand.Add 'ppm2_editor3', ppm2_editor3

-- Copyright (C) 2017-2018 DBot

-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at

--     http://www.apache.org/licenses/LICENSE-2.0

-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

ENABLE_FULLBRIGHT = CreateConVar('ppm2_editor_fullbright', '1', {FCVAR_ARCHIVE}, 'Disable lighting in editor')

local EDIT_TREE

inRadius = (val, min, max) -> val >= min and val <= max
inBox = (pointX, pointY, x, y, w, h) -> inRadius(pointX, x - w, x + w) and inRadius(pointY, y - h, y + h)

MODEL_BOX_PANEL = {
	SEQUENCE_STAND: 22
	PONY_VEC_Z: 64 * .7

	SEQUENCES: {
		'Standing':    22
		'Moving':      316
		'Walking':     232
		'Sit':         202
		'Swim':        370
		'Run':         328
		'Crouch walk': 286
		'Crouch':      76
		'Jump':        160
	}

	Init: =>
		@animRate = 1
		@seq = @SEQUENCE_STAND
		@hold = false
		@holdRightClick = false
		@canHold = true
		@lastTick = RealTimeL()

		@holdLast = 0
		@mouseX, @mouseY = 0, 0

		@angle = Angle(0, 0, 0)
		@distToPony = 90
		@trackBone = -1
		@trackBoneName = 'LrigSpine2'
		@trackAttach = -1
		@trackAttachName = ''
		@shouldAutoTrack = true
		@autoTrackPos = Vector(0, 0, 0)
		@fixedDistanceToPony = 100

		@vectorPos = Vector(@fixedDistanceToPony, 0, 0)
		@targetPos = Vector(0, 0, @PONY_VEC_Z * .7)

		@SetCursor('none')
		@SetMouseInputEnabled(true)

		@buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		@buildingModel\SetNoDraw(true)
		@buildingModel\SetModelScale(0.9)

		with @seqButton = vgui.Create('DComboBox', @)
			\SetSize(120, 20)
			\SetValue('Standing')
			\AddChoice(choice, num) for choice, num in pairs @SEQUENCES
			.OnSelect = (pnl = box, index = 1, value = '', data = value) -> @SetSequence(data)

		with @emotesPanel = PPM2.CreateEmotesPanel(@, @model, false)
			\SetPos(10, 40)
			\SetMouseInputEnabled(true)
			\SetVisible(true)

	UpdateAttachsIDs: =>
		@trackBone = @model\LookupBone(@trackBoneName) or -1 if @trackBoneName ~= ''
		@trackAttach = @model\LookupAttachment(@trackAttachName) or -1 if @trackAttachName ~= ''
	GetTrackedPosition: =>
		return @autoTrackPos if @shouldAutoTrack
		return @targetPos

	PerformLayout: (w = 0, h = 0) =>
		@seqButton\SetPos(10, 10)
		@emotesPanel\SetPos(10, 40)

	OnMousePressed: (code = MOUSE_LEFT) =>
		if code == MOUSE_LEFT and @canHold
			@hold = true
			@SetCursor('sizeall')
			@holdLast = RealTimeL() + .1
			@mouseX, @mouseY = gui.MousePos()
		elseif code == MOUSE_RIGHT
			@holdRightClick = true

	OnMouseReleased: (code = MOUSE_LEFT) =>
		if code == MOUSE_LEFT and @canHold
			@hold = false
			@SetCursor('none')
		elseif code == MOUSE_RIGHT
			@holdRightClick = false

	SetController: (val) => @controller = val

	OnMouseWheeled: (wheelDelta = 0) =>
		if @canHold
			@distToPony = math.clamp(@distToPony - wheelDelta * 10, 20, 150)

	GetModel: => @model
	GetSequence: => @seq
	GetAnimRate: => @animRate
	SetAnimRate: (val = 1) => @animRate = val
	SetSequence: (val = @SEQUENCE_STAND) =>
		@seq = val
		@model\SetSequence(@seq) if IsValid(@model)

	ResetSequence: => @SetSequence(@SEQUENCE_STAND)
	ResetSeq: => @SetSequence(@SEQUENCE_STAND)

	PushMenu: (menu) =>
		table.insert(@stack, menu)
		return @

	PopMenu: =>
		assert(#@stack > 1, 'invalid stack size to pop from')
		table.remove(@stack)
		return @

	CurrentMenu: => @stack[#@stack]
	InRoot: => #@stack == 1

	ResetModel: (ponydata, model = 'models/ppm/player_default_base_new.mdl') =>
		@model\Remove() if IsValid(@model)

		with @model = ClientsideModel(model)
			\SetNoDraw(true)
			.__PPM2_PonyData = ponydata
			\SetSequence(@seq)
			\FrameAdvance(0)
			\SetPos(Vector())
			\InvalidateBoneCache()

		@UpdateAttachsIDs()
		return @model

	Think: =>
		rtime = RealTimeL()
		delta = rtime - @lastTick
		@lastTick = rtime

		if IsValid(@model)
			@model\FrameAdvance(delta * @animRate)
			@model\SetPlaybackRate(1)
			@model\SetPoseParameter('move_x', 1)

			if @shouldAutoTrack
				if @trackAttach ~= -1
					{:Ang, :Pos} = @model\GetAttachment(@trackAttach)
					@autoTrackPos = Pos or Vector()
				elseif @trackBone ~= -1
					@autoTrackPos = @model\GetBonePosition(@trackBone) or Vector()
				else
					@shouldAutoTrack = false

		@hold = @IsHovered() if @hold

		if @hold
			x, y = gui.MousePos()
			deltaX, deltaY = x - @mouseX, y - @mouseY
			@mouseX, @mouseY = x, y
			{:pitch, :yaw, :roll} = @angle
			yaw -= deltaX * .5
			pitch = math.clamp(pitch - deltaY * .5, -45, 45)
			@angle = Angle(pitch, yaw % 360, roll)

		@vectorPos = Vector(@fixedDistanceToPony, 0, 0)
		@vectorPos\Rotate(@angle)
		@drawAngle = Angle(-@angle.p, @angle.y - 180)

	FLOOR_VECTOR: Vector(0, 0, -30)
	FLOOR_ANGLE: Vector(0, 0, 1)

	DRAW_WALLS: {
		{Vector(-4000, 0, 900), Vector(1, 0, 0), 8000, 2000}
		{Vector(4000, 0, 900), Vector(-1, 0, 0), 8000, 2000}
		{Vector(0, -4000, 900), Vector(0, 1, 0), 8000, 2000}
		{Vector(0, 4000, 900), Vector(0, -1, 0), 8000, 2000}
		{Vector(0, 0, 900), Vector(0, 0, -1), 8000, 8000}
	}

	WALL_COLOR: Color() - 255
	FLOOR_COLOR: Color() - 255
	EMPTY_VECTOR: Vector()

	Paint: (w = 0, h = 0) =>
		surface.SetDrawColor(0, 0, 0)
		surface.DrawRect(0, 0, w, h)
		return if not IsValid(@model)
		x, y = @LocalToScreen(0, 0)
		drawpos = @vectorPos + @GetTrackedPosition()
		cam.Start3D(drawpos, @drawAngle, @distToPony, x, y, w, h)

		if @holdRightClick
			@model\SetEyeTarget(drawpos)
			turnpitch, turnyaw = DLib.combat.turnAngle(@EMPTY_VECTOR, drawpos, Angle())

			if not inRadius(turnyaw, -20, 20)
				if turnyaw < 0
					@model\SetPoseParameter('head_yaw', turnyaw + 20)
				else
					@model\SetPoseParameter('head_yaw', turnyaw - 20)
			else
				@model\SetPoseParameter('head_yaw', 0)

			turnpitch = turnpitch + 20
			if not inRadius(turnpitch, -10, 0)
				if turnpitch < 0
					@model\SetPoseParameter('head_pitch', turnpitch + 10)
				else
					@model\SetPoseParameter('head_pitch', turnpitch)
			else
				@model\SetPoseParameter('head_pitch', 0)
		else
			@model\SetEyeTarget(@EMPTY_VECTOR)
			@model\SetPoseParameter('head_yaw', 0)
			@model\SetPoseParameter('head_pitch', 0)

		render.DrawQuadEasy(@FLOOR_VECTOR, @FLOOR_ANGLE, 7000, 7000, @FLOOR_COLOR)

		for {pos, ang, w, h} in *@DRAW_WALLS
			render.DrawQuadEasy(pos, ang, w, h, @WALL_COLOR)

		if ENABLE_FULLBRIGHT\GetBool()
			render.SuppressEngineLighting(true)
			render.ResetModelLighting(1, 1, 1)
			render.SetColorModulation(1, 1, 1)

		@buildingModel\DrawModel()
		ctrl = @controller\GetRenderController()

		if bg = @controller\GetBodygroupController()
			bg\ApplyBodygroups()

		with @model\PPMBonesModifier()
			\ResetBones()
			hook.Call('PPM2.SetupBones', nil, @model, @controller)
			\Think(true)

		with ctrl
			\DrawModels()
			\HideModels(true)
			\PreDraw(@model)

		@model\DrawModel()
		ctrl\PostDraw(@model)

		render.SuppressEngineLighting(false) if ENABLE_FULLBRIGHT\GetBool()

		menu = @CurrentMenu()
		@drawPoints = false

		if type(menu.points) == 'table'
			@drawPoints = true
			@pointsData = for point in *menu.points
				vecpos = point.getpos(@model)
				position = vecpos\ToScreen()
				{position, point, vecpos\Distance(drawpos)}

		cam.End3D()

		if @drawPoints
			lx, ly = x, y
			mx, my = gui.MousePos()
			mx, my = mx - lx, my - ly
			radius = ScreenScale(10)
			local drawnSelected
			min = 9999

			for pointdata in *@pointsData
				{:x, :y} = pointdata[1]
				x, y = x - lx, y - ly
				pointdata[1].x, pointdata[1].y = x, y

				if inBox(mx, my, x, y, radius, radius)
					if min > pointdata[3]
						drawnSelected = pointdata
						min = pointdata[3]

			@selectPoint = drawnSelected and drawnSelected[2] or false

			for pointdata in *@pointsData
				{:x, :y} = pointdata[1]

				if pointdata == drawnSelected
					surface.SetDrawColor(255, 255, 255)
				else
					surface.SetDrawColor(100, 100, 100)

				surface.DrawLine(x - radius, y - radius, x + radius, y + radius)
				surface.DrawLine(x + radius, y - radius, x - radius, y + radius)

			if not @hold
				if drawnSelected
					@SetCursor('hand')
				else
					@SetCursor('none')

	OnRemove: =>
		@model\Remove() if IsValid(@model)
		@buildingModel\Remove() if IsValid(@buildingModel)
}

vgui.Register('PPM2Model2Panel', MODEL_BOX_PANEL, 'EditablePanel')

EDIT_TREE = {
	type: 'level'
	name: 'Pony overview'

	points: {
		{
			type: 'bone'
			target: 'LrigScull'
			link: 'head_submenu'
		}

		{
			type: 'bone'
			target: 'LrigSpine1'
			link: 'spine_length'
		}

		{
			type: 'bone'
			target: 'Tail01'
			link: 'tail'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_FL_Metacarpus'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_FR_Metacarpus'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_BR_LargeCannon'
			link: 'legs_submenu'
		}

		{
			type: 'bone'
			target: 'Lrig_LEG_BL_LargeCannon'
			link: 'legs_submenu'
		}
	}

	children: {
		head_submenu: {
			type: 'level'
			name: 'Head anatomy'

			points: {
				{
					type: 'attach'
					target: 'eyes'
					link: 'eyes'
				}
			}

			children: {
				eyes: {
					type: 'menu'
					populate: =>
				}
			}
		}

		legs_submenu: {
			type: 'level'
			name: 'Hooves anatomy'

			points: {

			}

			children: {

			}
		}
	}
}

patchSubtree = (node) ->
	if type(node.children) == 'table'
		for childID, child in pairs node.children
			child.id = childID
			patchSubtree(child)

	if type(node.points) == 'table'
		for point in *node.points
			point.addvector = point.addvector or Vector()
			if type(node.children) == 'table'
				point.linkTable = node.children[point.link]
			switch point.type
				when 'point'
					point.getpos = => Vector(point.target)
				when 'bone'
					point.getpos = =>
						if not point.targetID or point.targetID == -1
							point.targetID = @LookupBone(point.target) or -1

						if point.targetID == -1
							return Vector(point.addvector)
						else
							return @GetBonePosition(point.targetID) + point.addvector
				when 'attach'
					point.getpos = =>
						if not point.targetID or point.targetID == -1
							point.targetID = @LookupAttachment(point.target) or -1

						if point.targetID == -1
							return Vector(point.addvector)
						else
							{:Pos, :Ang} = @GetAttachment(point.targetID)
							return Pos and (Pos + point.addvector) or Vector(point.addvector)

EDIT_TREE.id = 'root'
patchSubtree(EDIT_TREE)

ppm2_editor3 = ->
	if IsValid(PPM2.EDITOR3)
		PPM2.EDITOR3\SetVisible(true)
		PPM2.EDITOR3\MakePopup()
		return PPM2.EDITOR3
	PPM2.EDITOR3 = vgui.Create('DLib_Window')
	self = PPM2.EDITOR3
	@SetSize(ScrWL(), ScrHL())
	@SetPos(0, 0)
	@MakePopup()
	@SetTitle('PPM/2 Editor/3')
	@SetDraggable(false)
	@RemoveResize()

	with @modelPanel = vgui.Create('PPM2Model2Panel', @)
		\Dock(FILL)
		\DockMargin(3, 3, 3, 3)

	copy = PPM2.GetMainData()\Copy()
	ply = LocalPlayer()
	ent = @modelPanel\ResetModel()

	controller = copy\CreateCustomController(ent)
	controller\SetFlexLerpMultiplier(1.3)
	copy\SetController(controller)

	@modelPanel\SetController(controller)
	controller\SetupEntity(ent)
	controller\SetDisableTask(true)

	@modelPanel.stack = {EDIT_TREE}

concommand.Add 'ppm2_editor3', ppm2_editor3


removed "dot"-shape from the pool of random shapes

-- shapes description
-- each shape is an array of 4 coordinates (x,y), which are offset from the shape origin

dotShape = {
    {0, 0}
}

iShape = {
    {0, 0},
    {0, 1},
    {0, 2},
    {0, 3}
}

oShape = {
    {0, 0},
    {0, 1},
    {1, 1},
    {1, 0}
}

tShape = {
    {0, 0},
    {1, 0},
    {1, 1},
    {2, 0}
}

jShape = {
    {0, 0},
    {0, 1},
    {1, 0},
    {2, 0}
}

lShape = {
    {0, 0},
    {1, 0},
    {2, 0},
    {2, 1}
}

sShape = {
    {0, 0},
    {1, 0},
    {1, 1},
    {2, 1}
}

zShape = {
    {0, 0},
    {1, 0},
    {1, -1},
    {2, -1}
}

randomShape = ->
    shapes = {
        iShape, oShape, tShape, jShape, lShape, sShape, zShape
    }
    return shapes[math.random(#shapes)]

turnShapeLeft = (shape) ->
    newShape = {}
    for {x, y} in *shape
        newX, newY = -y, x
        table.insert(newShape, {newX, newY})
    return newShape

{
    dot: dotShape
    i: iShape
    o: oShape
    t: tShape
    j: jShape
    l: lShape
    s: sShape
    z: zShape
    random: randomShape
    turnLeft: turnShapeLeft
}

-- shapes description
-- each shape is an array of 4 coordinates (x,y), which are offset from the shape origin

dotShape = {
    {0, 0}
}

iShape = {
    {0, 0},
    {0, 1},
    {0, 2},
    {0, 3}
}

oShape = {
    {0, 0},
    {0, 1},
    {1, 1},
    {1, 0}
}

tShape = {
    {0, 0},
    {1, 0},
    {1, 1},
    {2, 0}
}

jShape = {
    {0, 0},
    {0, 1},
    {1, 0},
    {2, 0}
}

lShape = {
    {0, 0},
    {1, 0},
    {2, 0},
    {2, 1}
}

sShape = {
    {0, 0},
    {1, 0},
    {1, 1},
    {2, 1}
}

zShape = {
    {0, 0},
    {1, 0},
    {1, -1},
    {2, -1}
}

randomShape = ->
    shapes = {
        dotShape, iShape, oShape, tShape, jShape, lShape, sShape, zShape
    }
    return shapes[math.random(#shapes)]

turnShapeLeft = (shape) ->
    newShape = {}
    for {x, y} in *shape
        newX, newY = -y, x
        table.insert(newShape, {newX, newY})
    return newShape

{
    dot: dotShape
    i: iShape
    o: oShape
    t: tShape
    j: jShape
    l: lShape
    s: sShape
    z: zShape
    random: randomShape
    turnLeft: turnShapeLeft
}


Fixed parameter name mismatch when extracting gvec4 uniforms.

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

Log = require "glurg.common.Log"
Promise = require "glurg.common.Promise"
Filter = require "glurg.filters.Filter"
Call = require "glurg.trace.Call"
Trace = require "glurg.trace.Trace"

class Shader
	new: (name, shader_type) =>
		@name = name
		@type = shader_type
		@source = ""
		@is_deleted = false

	_extract_source: (call) =>
		Promise.keep("@name == shader",
			@name == call\get_argument_by_name("shader")\query!)

		s = {}
		argument = call\get_argument_by_name("string")\query!
		for i = 1, #argument
			table.insert(s, argument[i]\query!)
		@source = table.concat(s, "\n")

class Program
	new: (name) =>
		@name = name
		@attributes = {}
		@shaders = {}
		@uniforms = {}
		@is_deleted = false

	_set_attribute: (name, index) =>
		Promise.keep("name", Promise.IsString(name))
		Promise.keep("index", Promise.IsNumber(index))

		@attributes[name] = index
		@attributes[index] = name

	get_attribute: (key) =>
		return switch type(key)
			when 'number', 'string' then @attributes[key]
			else error "invalid key type, expected string or number"

	has_attribute: (key) =>
		return switch type(key)
			when 'number', 'string' then @attributes[key] != nil
			else error "invalid key type, expected string or number"

	_add_uniform: (name, location) =>
		if location == -1
			return false

		uniform = { call: false, :name, :location }
		if @uniforms[name] == nil
			Promise.keep("@uniforms[location] == nil",
				@uniforms[location] == nil)

			@uniforms[name] = uniform
			@uniforms[location] = uniform

		return true

	_set_uniform: (key, call, trace) =>
		Promise.keep_any("key", Promise.IsNumber(key), Promise.IsString(key))
		Promise.keep("call", Promise.IsClass(call, Call))
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("@uniforms[key] != nil", @uniforms[key] != nil)

		if @uniforms[key].call then
			trace\delete_call(@uniforms[key].call.index)
		@uniforms[key].call = call

	get_uniforms: =>
		return [item for key, item in pairs(@uniforms) when type(key) == "number"]

	get_uniform: (key) =>
		Promise.keep_any("key", Promise.IsNumber(key), Promise.IsString(key))

		if @uniforms[key] and @uniforms[key].call then
			call = @uniforms[key].call
			switch call.signature.name
				when "glUniform1f", "glUniform1i", "glUniform1ui"
					v0 = call\get_argument_by_name("v0")\query!
					return v0, 'scalar'
				when "glUniform2f", "glUniform2i", "glUniform2ui"
					v0 = call\get_argument_by_name("v0")\query!
					v1 = call\get_argument_by_name("v1")\query!
					return { x: v0, y: v1 }, 'vec2'
				when "glUniform3f", "glUniform3i", "glUniform3ui"
					v0 = call\get_argument_by_name("v0")\query!
					v1 = call\get_argument_by_name("v1")\query!
					v2 = call\get_argument_by_name("v2")\query!
					return { x: v0, y: v1, z: v2 }, 'vec3'
				when "glUniform4f", "glUniform4i", "glUniform4ui"
					v0 = call\get_argument_by_name("v0")\query!
					v1 = call\get_argument_by_name("v1")\query!
					v2 = call\get_argument_by_name("v2")\query!
					v3 = call\get_argument_by_name("v3")\query!
					return { x: v0, y: v1, z: v2, w: v3 }, 'vec4'
				when "glUniform1fv", "glUniform1iv", "glUniform1uiv"
					count = call\get_argument_by_name("count")\query!
					value = call\get_argument_by_name("value")\query!
					if count == 1
						return value[1], 'scalar'
					else
						return value, 'scalar_array'
				when "glUniform2fv", "glUniform2iv", "glUniform2uiv"
					count = call\get_argument_by_name("count")\query!
					value = call\get_argument_by_name("value")\query!
					result = {}
					for i = 1, count
						result[i] = {
							x: value[i * 2 + 0]\query!,
							y: value[i * 2 + 1]\query!
						}
					if count == 1
						return result[1], 'vec2'
					else
						return result, 'vec2_array'
				when "glUniform3fv", "glUniform3iv", "glUniform3uiv"
					count = call\get_argument_by_name("count")\query!
					value = call\get_argument_by_name("value")\query!
					result = {}
					for i = 1, count
						result[i] = {
							x: value[(i - 1) * 3 + 1]\query!,
							y: value[(i - 1) * 3 + 2]\query!,
							z: value[(i - 1) * 3 + 3]\query!
						}
					if count == 1
						return result[1], 'vec3'
					else
						return result, 'vec3_array'
				when "glUniform4fv", "glUniform4iv", "glUniform4uiv"
					count = call\get_argument_by_name("count")\query!
					value = call\get_argument_by_name("value")\query!
					result = {}
					for i = 1, count
						result[i] = {
							x: value[(i - 1) * 4 + 1]\query!,
							y: value[(i - 1) * 4 + 2]\query!,
							z: value[(i - 1) * 4 + 3]\query!,
							w: value[(i - 1) * 4 + 4]\query!
						}
					if count == 1
						return result[1], 'vec4'
					else
						return result, 'vec4_array'
				when "glUniformMatrix2fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix3fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix4fv"
					count = call\get_argument_by_name("count")\query!
					transpose = call\get_argument_by_name("transpose")\query!
					value = call\get_argument_by_name("value")\query!
					result = {}
					for i = 1, count
						result[i] = {}
						if transpose.value_name == "GL_TRUE"
							for j = 1, 4 do
								result[i][j] = {}
							for j = 1, 4 do
								result[i][1][j] = value[(i - 1) * 16 + (j - 1) * 4 + 1]\query!
								result[i][2][j] = value[(i - 1) * 16 + (j - 1) * 4 + 2]\query!
								result[i][3][j] = value[(i - 1) * 16 + (j - 1) * 4 + 3]\query!
								result[i][4][j] = value[(i - 1) * 16 + (j - 1) * 4 + 4]\query!
						else
							for j = 1, 4 do
								result[i][j] = {
									value[(i - 1) * 16 + (j - 1) * 4 + 1]\query!,
									value[(i - 1) * 16 + (j - 1) * 4 + 2]\query!,
									value[(i - 1) * 16 + (j - 1) * 4 + 3]\query!,
									value[(i - 1) * 16 + (j - 1) * 4 + 4]\query!
								}

					if count == 1
						return result[1], 'mat4'
					else
						return result, 'mat4_array'
				when "glUniformMatrix2x3fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix2x4fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix3x2fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix3x4fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix4x2fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix4x3fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				else
					Log\error("unknown glUniform call")
					return nil
		else
			return nil

	_attach_shader: (shader) =>
		Promise.keep("shader", Promise.IsClass(shader, Shader))

		if @shaders[shader.type] == nil
			@shaders[shader.type] = {}

		table.insert(@shaders[shader.type], shader)

	_detach_shader: (shader) =>
		Promise.keep("shader", Promise.IsClass(shader, Shader))

		if @shaders[shader.type]
			for i = 1, #@shaders[shader.type]
				if @shaders[shader.type][i] == shader then
					table.remove(@shaders[shader.type], i)
					break

	get_shader_source: (shader_type) =>
		source = [s.source for i, s in ipairs(@shaders[shader_type] or {})]
		return table.concat(source, "\n")

	dispose: (trace) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		uniforms = @\get_uniforms!
		for i = 1, #uniforms
			if uniforms[i].call
				trace\delete_call(uniforms[i].call.index)
				uniforms[i].call = false

class ProgramFilter extends Filter
	new: =>
		@_shaders = {}
		@_programs = {}
		@current_program = 0

	_get_shader: (name) =>
		Promise.keep("@_shaders[name] != nil", @_shaders[name] != nil)

		return @_shaders[name]

	_add_shader: (shader) =>
		Promise.keep("@_shaders[shader.name] == nil",
			@_shaders[shader.name] == nil)

		@_shaders[shader.name] = shader

	_remove_shader: (shader) =>
		@_shaders[shader] = nil

	_get_program: (name) =>
		Promise.keep("@_programs[name] != nil", @_programs[name] != nil)

		return @_programs[name]

	_add_program: (program) =>
		Promise.keep("@_programs[program.name] == nil",
			@_programs[program.name] == nil)

		@_programs[program.name] = program

	_remove_program: (program) =>
		@_programs[program] = nil

	get_current_program: =>
		return @\_get_program(@current_program)

	glCreateShader: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		shader_type = call\get_argument_by_name("type")\query!.value_name
		name = call.result\query!

		@\_add_shader(Shader(name, shader_type))

		return false

	glShaderSource: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		name = call\get_argument_by_name("shader")\query!
		@\_get_shader(name)\_extract_source(call)

		return false

	glCreateProgram: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		name = call.result\query!
		@\_add_program(Program(name))

		return false

	glUseProgram: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		@current_program = call\get_argument_by_name("program")\query!

		return false

	glAttachShader: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		program = call\get_argument_by_name("program")\query!
		shader = call\get_argument_by_name("shader")\query!

		@\_get_program(program)\_attach_shader(@\_get_shader(shader))

		return false

	glDetachShader: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		program = call\get_argument_by_name("program")\query!
		shader = call\get_argument_by_name("shader")\query!

		@\_get_program(program)\_detach_shader(@\_get_shader(shader))

		return false

	glDeleteShader: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		shader = call\get_argument_by_name("shader")\query!
		@\_get_shader(shader).is_deleted = true
		@\_remove_shader(shader)

		return false

	glDeleteProgram: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		name = call\get_argument_by_name("program")\query!
		program = @\_get_program(name)
		program.is_deleted = true
		program\dispose(trace)
		@\_remove_program(name)

		return false

	glBindAttribLocation: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		program = call\get_argument_by_name("program")\query!
		name = call\get_argument_by_name("name")\query!
		index = call\get_argument_by_name("index")\query!

		@\_get_program(program)\_set_attribute(name, index)

		return false

	glGetUniformLocation: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		program = call\get_argument_by_name("program")\query!
		name = call\get_argument_by_name("name")\query!
		location = call.result\query!
		@\_get_program(program)\_add_uniform(name, location)

	_set_uniform: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		location = call\get_argument_by_name("location")\query!
		@\get_current_program!\_set_uniform(location, call, trace)

		return true

	glUniform1f: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2f: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3f: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4f: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1i: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2i: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3i: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4i: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1ui: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2ui: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3ui: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4ui: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1iv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2iv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3iv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4iv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1uiv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2uiv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3uiv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4uiv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix2fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix3fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix4fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix2x3fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix2x4fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix3x2fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix3x4fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix4x2fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix4x3fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	dispose: (trace) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))

		for _, program in pairs(@_programs)
			program\dispose(trace)

return ProgramFilter

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright 2016 [bk]door.maus

Log = require "glurg.common.Log"
Promise = require "glurg.common.Promise"
Filter = require "glurg.filters.Filter"
Call = require "glurg.trace.Call"
Trace = require "glurg.trace.Trace"

class Shader
	new: (name, shader_type) =>
		@name = name
		@type = shader_type
		@source = ""
		@is_deleted = false

	_extract_source: (call) =>
		Promise.keep("@name == shader",
			@name == call\get_argument_by_name("shader")\query!)

		s = {}
		argument = call\get_argument_by_name("string")\query!
		for i = 1, #argument
			table.insert(s, argument[i]\query!)
		@source = table.concat(s, "\n")

class Program
	new: (name) =>
		@name = name
		@attributes = {}
		@shaders = {}
		@uniforms = {}
		@is_deleted = false

	_set_attribute: (name, index) =>
		Promise.keep("name", Promise.IsString(name))
		Promise.keep("index", Promise.IsNumber(index))

		@attributes[name] = index
		@attributes[index] = name

	get_attribute: (key) =>
		return switch type(key)
			when 'number', 'string' then @attributes[key]
			else error "invalid key type, expected string or number"

	has_attribute: (key) =>
		return switch type(key)
			when 'number', 'string' then @attributes[key] != nil
			else error "invalid key type, expected string or number"

	_add_uniform: (name, location) =>
		if location == -1
			return false

		uniform = { call: false, :name, :location }
		if @uniforms[name] == nil
			Promise.keep("@uniforms[location] == nil",
				@uniforms[location] == nil)

			@uniforms[name] = uniform
			@uniforms[location] = uniform

		return true

	_set_uniform: (key, call, trace) =>
		Promise.keep_any("key", Promise.IsNumber(key), Promise.IsString(key))
		Promise.keep("call", Promise.IsClass(call, Call))
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("@uniforms[key] != nil", @uniforms[key] != nil)

		if @uniforms[key].call then
			trace\delete_call(@uniforms[key].call.index)
		@uniforms[key].call = call

	get_uniforms: =>
		return [item for key, item in pairs(@uniforms) when type(key) == "number"]

	get_uniform: (key) =>
		Promise.keep_any("key", Promise.IsNumber(key), Promise.IsString(key))

		if @uniforms[key] and @uniforms[key].call then
			call = @uniforms[key].call
			switch call.signature.name
				when "glUniform1f", "glUniform1i", "glUniform1ui"
					v0 = call\get_argument_by_name("v0")\query!
					return v0, 'scalar'
				when "glUniform2f", "glUniform2i", "glUniform2ui"
					v0 = call\get_argument_by_name("v0")\query!
					v1 = call\get_argument_by_name("v1")\query!
					return { x: v0, y: v1 }, 'vec2'
				when "glUniform3f", "glUniform3i", "glUniform3ui"
					v0 = call\get_argument_by_name("v0")\query!
					v1 = call\get_argument_by_name("v1")\query!
					v2 = call\get_argument_by_name("v2")\query!
					return { x: v0, y: v1, z: v2 }, 'vec3'
				when "glUniform4f", "glUniform4i", "glUniform4ui"
					v0 = call\get_argument_by_name("v0")\query!
					v1 = call\get_argument_by_name("v1")\query!
					v2 = call\get_argument_by_name("v2")\query!
					v4 = call\get_argument_by_name("v4")\query!
					return { x: v0, y: v1, z: v2, w: v4 }, 'vec4'
				when "glUniform1fv", "glUniform1iv", "glUniform1uiv"
					count = call\get_argument_by_name("count")\query!
					value = call\get_argument_by_name("value")\query!
					if count == 1
						return value[1], 'scalar'
					else
						return value, 'scalar_array'
				when "glUniform2fv", "glUniform2iv", "glUniform2uiv"
					count = call\get_argument_by_name("count")\query!
					value = call\get_argument_by_name("value")\query!
					result = {}
					for i = 1, count
						result[i] = {
							x: value[i * 2 + 0]\query!,
							y: value[i * 2 + 1]\query!
						}
					if count == 1
						return result[1], 'vec2'
					else
						return result, 'vec2_array'
				when "glUniform3fv", "glUniform3iv", "glUniform3uiv"
					count = call\get_argument_by_name("count")\query!
					value = call\get_argument_by_name("value")\query!
					result = {}
					for i = 1, count
						result[i] = {
							x: value[(i - 1) * 3 + 1]\query!,
							y: value[(i - 1) * 3 + 2]\query!,
							z: value[(i - 1) * 3 + 3]\query!
						}
					if count == 1
						return result[1], 'vec3'
					else
						return result, 'vec3_array'
				when "glUniform4fv", "glUniform4iv", "glUniform4uiv"
					count = call\get_argument_by_name("count")\query!
					value = call\get_argument_by_name("value")\query!
					result = {}
					for i = 1, count
						result[i] = {
							x: value[(i - 1) * 4 + 1]\query!,
							y: value[(i - 1) * 4 + 2]\query!,
							z: value[(i - 1) * 4 + 3]\query!,
							w: value[(i - 1) * 4 + 4]\query!
						}
					if count == 1
						return result[1], 'vec4'
					else
						return result, 'vec4_array'
				when "glUniformMatrix2fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix3fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix4fv"
					count = call\get_argument_by_name("count")\query!
					transpose = call\get_argument_by_name("transpose")\query!
					value = call\get_argument_by_name("value")\query!
					result = {}
					for i = 1, count
						result[i] = {}
						if transpose.value_name == "GL_TRUE"
							for j = 1, 4 do
								result[i][j] = {}
							for j = 1, 4 do
								result[i][1][j] = value[(i - 1) * 16 + (j - 1) * 4 + 1]\query!
								result[i][2][j] = value[(i - 1) * 16 + (j - 1) * 4 + 2]\query!
								result[i][3][j] = value[(i - 1) * 16 + (j - 1) * 4 + 3]\query!
								result[i][4][j] = value[(i - 1) * 16 + (j - 1) * 4 + 4]\query!
						else
							for j = 1, 4 do
								result[i][j] = {
									value[(i - 1) * 16 + (j - 1) * 4 + 1]\query!,
									value[(i - 1) * 16 + (j - 1) * 4 + 2]\query!,
									value[(i - 1) * 16 + (j - 1) * 4 + 3]\query!,
									value[(i - 1) * 16 + (j - 1) * 4 + 4]\query!
								}

					if count == 1
						return result[1], 'mat4'
					else
						return result, 'mat4_array'
				when "glUniformMatrix2x3fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix2x4fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix3x2fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix3x4fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix4x2fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				when "glUniformMatrix4x3fv"
					Log\warn('high', "cannot get uniform; type not yet implemented")
					return nil
				else
					Log\error("unknown glUniform call")
					return nil
		else
			return nil

	_attach_shader: (shader) =>
		Promise.keep("shader", Promise.IsClass(shader, Shader))

		if @shaders[shader.type] == nil
			@shaders[shader.type] = {}

		table.insert(@shaders[shader.type], shader)

	_detach_shader: (shader) =>
		Promise.keep("shader", Promise.IsClass(shader, Shader))

		if @shaders[shader.type]
			for i = 1, #@shaders[shader.type]
				if @shaders[shader.type][i] == shader then
					table.remove(@shaders[shader.type], i)
					break

	get_shader_source: (shader_type) =>
		source = [s.source for i, s in ipairs(@shaders[shader_type] or {})]
		return table.concat(source, "\n")

	dispose: (trace) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		uniforms = @\get_uniforms!
		for i = 1, #uniforms
			if uniforms[i].call
				trace\delete_call(uniforms[i].call.index)
				uniforms[i].call = false

class ProgramFilter extends Filter
	new: =>
		@_shaders = {}
		@_programs = {}
		@current_program = 0

	_get_shader: (name) =>
		Promise.keep("@_shaders[name] != nil", @_shaders[name] != nil)

		return @_shaders[name]

	_add_shader: (shader) =>
		Promise.keep("@_shaders[shader.name] == nil",
			@_shaders[shader.name] == nil)

		@_shaders[shader.name] = shader

	_remove_shader: (shader) =>
		@_shaders[shader] = nil

	_get_program: (name) =>
		Promise.keep("@_programs[name] != nil", @_programs[name] != nil)

		return @_programs[name]

	_add_program: (program) =>
		Promise.keep("@_programs[program.name] == nil",
			@_programs[program.name] == nil)

		@_programs[program.name] = program

	_remove_program: (program) =>
		@_programs[program] = nil

	get_current_program: =>
		return @\_get_program(@current_program)

	glCreateShader: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		shader_type = call\get_argument_by_name("type")\query!.value_name
		name = call.result\query!

		@\_add_shader(Shader(name, shader_type))

		return false

	glShaderSource: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		name = call\get_argument_by_name("shader")\query!
		@\_get_shader(name)\_extract_source(call)

		return false

	glCreateProgram: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		name = call.result\query!
		@\_add_program(Program(name))

		return false

	glUseProgram: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		@current_program = call\get_argument_by_name("program")\query!

		return false

	glAttachShader: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		program = call\get_argument_by_name("program")\query!
		shader = call\get_argument_by_name("shader")\query!

		@\_get_program(program)\_attach_shader(@\_get_shader(shader))

		return false

	glDetachShader: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		program = call\get_argument_by_name("program")\query!
		shader = call\get_argument_by_name("shader")\query!

		@\_get_program(program)\_detach_shader(@\_get_shader(shader))

		return false

	glDeleteShader: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		shader = call\get_argument_by_name("shader")\query!
		@\_get_shader(shader).is_deleted = true
		@\_remove_shader(shader)

		return false

	glDeleteProgram: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		name = call\get_argument_by_name("program")\query!
		program = @\_get_program(name)
		program.is_deleted = true
		program\dispose(trace)
		@\_remove_program(name)

		return false

	glBindAttribLocation: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		program = call\get_argument_by_name("program")\query!
		name = call\get_argument_by_name("name")\query!
		index = call\get_argument_by_name("index")\query!

		@\_get_program(program)\_set_attribute(name, index)

		return false

	glGetUniformLocation: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		program = call\get_argument_by_name("program")\query!
		name = call\get_argument_by_name("name")\query!
		location = call.result\query!
		@\_get_program(program)\_add_uniform(name, location)

	_set_uniform: (trace, call) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))
		Promise.keep("call", Promise.IsClass(call, Call))

		location = call\get_argument_by_name("location")\query!
		@\get_current_program!\_set_uniform(location, call, trace)

		return true

	glUniform1f: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2f: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3f: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4f: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1i: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2i: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3i: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4i: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1ui: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2ui: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3ui: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4ui: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1iv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2iv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3iv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4iv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform1uiv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform2uiv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform3uiv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniform4uiv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix2fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix3fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix4fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix2x3fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix2x4fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix3x2fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix3x4fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix4x2fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	glUniformMatrix4x3fv: (trace, call) =>
		return @\_set_uniform(trace, call)

	dispose: (trace) =>
		Promise.keep("trace", Promise.IsClass(trace, Trace))

		for _, program in pairs(@_programs)
			program\dispose(trace)

return ProgramFilter