function __vec3Sub(_x = 0, _y = _x, _z = _x) : __vec3(_x, _y, _z) constructor {
	old       = false;
	connected = [];
	
	static smooth = function() {
		if(array_empty(connected)) return;
		
		var _n   = array_length(connected);
		var _k   = _n / 2;
		var beta = 3 / (5 * _k);
		var _sx  = x * (1 - _k * beta);
		var _sy  = y * (1 - _k * beta);
		var _sz  = z * (1 - _k * beta);
		var _cx  = 0;
		var _cy  = 0;
		var _cz  = 0;
		
		for( var i = 0; i < _n; i++ ) {
			_cx += connected[i].x;
			_cy += connected[i].y;
			_cz += connected[i].z;
		}
		
		x = _cx * 0.5 * beta + _sx;
		y = _cy * 0.5 * beta + _sy;
		z = _cz * 0.5 * beta + _sz;
	}
	
	static connectTo = function(point) { array_push(connected, point); }
}

function __3dICOSphere(radius = 0.5, level = 2, smt = false) : __3dObject() constructor {
	VF = global.VF_POS_NORM_TEX_COL;
	render_type = pr_trianglelist;
	
	self.radius = radius;
	self.level  = level;
	self.smooth = smt;
	
	_vhash = ds_map_create();
	
	static getVertex = function(vertex) {
		var h = $"[{vertex.x},{vertex.y},{vertex.z}]";
		if(ds_map_exists(_vhash, h))
			return _vhash[? h];
		_vhash[? h] = vertex;
		return vertex;
	}
	
	static initModel = function() { // swap H, V because fuck me
		var _vertices = ds_list_create();
		var _normals  = ds_list_create();
		
		var phi = (1 + sqrt(5)) * 0.5; // golden ratio
		var a = 1.0;
		var b = 1.0 / phi;
	
		icoverts = [
			new __vec3Sub( 1,  1,  1)._normalize()._multiply(radius),
			new __vec3Sub( 0,  b, -a)._normalize()._multiply(radius),
			new __vec3Sub( b,  a,  0)._normalize()._multiply(radius),
			new __vec3Sub(-b,  a,  0)._normalize()._multiply(radius),
			new __vec3Sub( 0,  b,  a)._normalize()._multiply(radius),
			new __vec3Sub( 0, -b,  a)._normalize()._multiply(radius),
			new __vec3Sub(-a,  0,  b)._normalize()._multiply(radius),
			new __vec3Sub( 0, -b, -a)._normalize()._multiply(radius),
			new __vec3Sub( a,  0, -b)._normalize()._multiply(radius),
			new __vec3Sub( a,  0,  b)._normalize()._multiply(radius),
			new __vec3Sub(-a,  0, -b)._normalize()._multiply(radius),
			new __vec3Sub( b, -a,  0)._normalize()._multiply(radius),
			new __vec3Sub(-b, -a,  0)._normalize()._multiply(radius),
		]
		
		array_foreach(icoverts, function(vert) { vert.old = true; });
		
		// Generate icosphere vertices
		ds_list_add(_vertices, icoverts[ 3], icoverts[ 1], icoverts[ 2]);
		ds_list_add(_vertices, icoverts[ 2], icoverts[ 4], icoverts[ 3]);
		ds_list_add(_vertices, icoverts[ 6], icoverts[ 4], icoverts[ 5]);
		ds_list_add(_vertices, icoverts[ 5], icoverts[ 4], icoverts[ 9]);
		ds_list_add(_vertices, icoverts[ 8], icoverts[ 1], icoverts[ 7]);
		ds_list_add(_vertices, icoverts[ 7], icoverts[ 1], icoverts[10]);
		ds_list_add(_vertices, icoverts[12], icoverts[ 5], icoverts[11]);
		ds_list_add(_vertices, icoverts[11], icoverts[ 7], icoverts[12]);
		ds_list_add(_vertices, icoverts[10], icoverts[ 3], icoverts[ 6]);
		ds_list_add(_vertices, icoverts[ 6], icoverts[12], icoverts[10]);
		ds_list_add(_vertices, icoverts[ 9], icoverts[ 2], icoverts[ 8]);
		ds_list_add(_vertices, icoverts[ 8], icoverts[11], icoverts[ 9]);
		ds_list_add(_vertices, icoverts[ 3], icoverts[ 4], icoverts[ 6]);
		ds_list_add(_vertices, icoverts[ 9], icoverts[ 4], icoverts[ 2]);
		ds_list_add(_vertices, icoverts[10], icoverts[ 1], icoverts[ 3]);
		ds_list_add(_vertices, icoverts[ 2], icoverts[ 1], icoverts[ 8]);
		ds_list_add(_vertices, icoverts[12], icoverts[ 7], icoverts[10]);
		ds_list_add(_vertices, icoverts[ 8], icoverts[ 7], icoverts[11]);
		ds_list_add(_vertices, icoverts[ 6], icoverts[ 5], icoverts[12]);
		ds_list_add(_vertices, icoverts[11], icoverts[ 5], icoverts[ 9]);
		
		var lv = min(level, 5);
		repeat(lv) { #region subdivide
		    ds_map_clear(_vhash);
			var newVertices = ds_list_create();
		    
		    for (var i = 0, n = ds_list_size(_vertices) / 3; i < n; i++) {
		        var v1 = _vertices[| i * 3 + 0];
		        var v2 = _vertices[| i * 3 + 1];
		        var v3 = _vertices[| i * 3 + 2];
				
		        var mid12Pos = getVertex(new __vec3Sub(v1.add(v2).divide(2)));
		        var mid23Pos = getVertex(new __vec3Sub(v2.add(v3).divide(2)));
		        var mid31Pos = getVertex(new __vec3Sub(v3.add(v1).divide(2)));
				
				v1.connectTo(mid12Pos); v1.connectTo(mid31Pos);
				v2.connectTo(mid12Pos); v2.connectTo(mid23Pos);
				v3.connectTo(mid23Pos); v3.connectTo(mid31Pos);
				
		        ds_list_add(newVertices, v1, mid12Pos, mid31Pos);
		        ds_list_add(newVertices, mid12Pos, v2, mid23Pos);
		        ds_list_add(newVertices, mid31Pos, mid23Pos, v3);
				ds_list_add(newVertices, mid12Pos, mid23Pos, mid31Pos);
		    }
			
			for (var i = 0, n = ds_list_size(newVertices); i < n; i++) {
				var _v = newVertices[| i];
				if(_v.old) _v.smooth();
				
				_v.old = true;
				_v.connected = [];
			}
			
			ds_list_destroy(_vertices);
		    _vertices = newVertices;
		} #endregion

		for( var i = 0, n = ds_list_size(_vertices) / 3; i < n; i++ ) { #region normal, uv generation
			var _v0 = _vertices[| i * 3 + 0];
			var _v1 = _vertices[| i * 3 + 1];
			var _v2 = _vertices[| i * 3 + 2];
			if(smooth) {
				ds_list_add(_normals, _v0.normalize(), _v1.normalize(), _v2.normalize());
			} else {
				var _n  = _v2.subtract(_v0).cross(_v1.subtract(_v0));
				ds_list_add(_normals, _n, _n, _n);
			}
		} #endregion
		
		vertex   = [ array_create(ds_list_size(_vertices)) ];
		
		for( var i = 0, n = ds_list_size(_vertices); i < n; i++ ) {
			var _v = _vertices[| i];
			var _n = _normals[| i];
			
			var _ha = point_direction(0, 0, _v.x, _v.y);
			var _va = (point_direction(0, 0, _v.x, _v.z) + 90) % 360;
			if(_va > 180) _va = 360 - _va;
			
			vertex[0][i] = new __vertex(_v.x, _v.y, _v.z).setNormal(_n.x, _n.y, _n.z).setUV(_ha / 360, _va / 180);
		}
		
		ds_list_destroy(_vertices);
		ds_list_destroy(_normals);
		
		VB = build();
	} initModel();
	
	static onParameterUpdate = initModel;
}