You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			308 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			GDScript
		
	
		
		
			
		
	
	
			308 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			GDScript
		
	
| 
											2 years ago
										 | 
 | ||
|  | const VoxFile = preload("./VoxFile.gd"); | ||
|  | const VoxData = preload("./VoxFormat/VoxData.gd"); | ||
|  | const VoxNode = preload("./VoxFormat/VoxNode.gd"); | ||
|  | const VoxMaterial = preload("./VoxFormat/VoxMaterial.gd"); | ||
|  | const VoxLayer = preload("./VoxFormat/VoxLayer.gd"); | ||
|  | const CulledMeshGenerator = preload("./CulledMeshGenerator.gd"); | ||
|  | const GreedyMeshGenerator = preload("./GreedyMeshGenerator.gd"); | ||
|  | 
 | ||
|  | const debug_file = false; | ||
|  | const debug_models = false; | ||
|  | 
 | ||
|  | var fileKeyframeIds = []; | ||
|  | 
 | ||
|  | func import(source_path, destination_path, options, _platforms, _gen_files): | ||
|  | 	var scale = 0.1 | ||
|  | 	if options.Scale: | ||
|  | 		scale = float(options.Scale) | ||
|  | 	var greedy = true | ||
|  | 	if options.has("GreedyMeshGenerator"): | ||
|  | 		greedy = bool(options.GreedyMeshGenerator) | ||
|  | 	var snaptoground = false | ||
|  | 	if options.has("SnapToGround"): | ||
|  | 		snaptoground = bool(options.SnapToGround) | ||
|  | 	var mergeKeyframes = false | ||
|  | 	if options.has("FirstKeyframeOnly"): | ||
|  | 		mergeKeyframes = not bool(options.FirstKeyframeOnly) | ||
|  | 
 | ||
|  | 
 | ||
|  | 	var file = FileAccess.open(source_path, FileAccess.READ) | ||
|  | 
 | ||
|  | 	if file == null: | ||
|  | 		return FileAccess.get_open_error() | ||
|  | 
 | ||
|  | 	var identifier = PackedByteArray([ file.get_8(), file.get_8(), file.get_8(), file.get_8() ]).get_string_from_ascii() | ||
|  | 	var version = file.get_32() | ||
|  | 	print('Importing: ', source_path, ' (scale: ', scale, ', file version: ', version, ', greedy mesh: ', greedy, ', snap to ground: ', snaptoground, ')'); | ||
|  | 
 | ||
|  | 	var vox = VoxData.new(); | ||
|  | 	if identifier == 'VOX ': | ||
|  | 		var voxFile = VoxFile.new(file); | ||
|  | 		while voxFile.has_data_to_read(): | ||
|  | 			read_chunk(vox, voxFile); | ||
|  | 	file = null | ||
|  | 
 | ||
|  | 	fileKeyframeIds.sort() | ||
|  | 
 | ||
|  | 	var voxel_data = unify_voxels(vox, mergeKeyframes); | ||
|  | 	var meshes = [] | ||
|  | 	for keyframeVoxels in voxel_data: | ||
|  | 		if greedy: | ||
|  | 			meshes.append(GreedyMeshGenerator.new().generate(vox, voxel_data[keyframeVoxels], scale, snaptoground)) | ||
|  | 		else: | ||
|  | 			meshes.append(CulledMeshGenerator.new().generate(vox, voxel_data[keyframeVoxels], scale, snaptoground)) | ||
|  | 	return meshes | ||
|  | 
 | ||
|  | func string_to_vector3(input: String) -> Vector3: | ||
|  | 	var data = input.split_floats(' '); | ||
|  | 	return Vector3(data[0], data[1], data[2]); | ||
|  | 
 | ||
|  | func byte_to_basis(data: int): | ||
|  | 	var x_ind = ((data >> 0) & 0x03); | ||
|  | 	var y_ind = ((data >> 2) & 0x03); | ||
|  | 	var indexes = [0, 1, 2]; | ||
|  | 	indexes.erase(x_ind); | ||
|  | 	indexes.erase(y_ind); | ||
|  | 	var z_ind = indexes[0]; | ||
|  | 	var x_sign = 1 if ((data >> 4) & 0x01) == 0 else -1; | ||
|  | 	var y_sign = 1 if ((data >> 5) & 0x01) == 0 else -1; | ||
|  | 	var z_sign = 1 if ((data >> 6) & 0x01) == 0 else -1; | ||
|  | 	var result = Basis(); | ||
|  | 	result.x[0] = x_sign if x_ind == 0 else 0; | ||
|  | 	result.x[1] = x_sign if x_ind == 1 else 0; | ||
|  | 	result.x[2] = x_sign if x_ind == 2 else 0; | ||
|  | 
 | ||
|  | 	result.y[0] = y_sign if y_ind == 0 else 0; | ||
|  | 	result.y[1] = y_sign if y_ind == 1 else 0; | ||
|  | 	result.y[2] = y_sign if y_ind == 2 else 0; | ||
|  | 
 | ||
|  | 	result.z[0] = z_sign if z_ind == 0 else 0; | ||
|  | 	result.z[1] = z_sign if z_ind == 1 else 0; | ||
|  | 	result.z[2] = z_sign if z_ind == 2 else 0; | ||
|  | 	return result; | ||
|  | 
 | ||
|  | func read_chunk(vox: VoxData, file: VoxFile): | ||
|  | 	var chunk_id = file.get_string(4); | ||
|  | 	var chunk_size = file.get_32(); | ||
|  | 	var childChunks = file.get_32() | ||
|  | 
 | ||
|  | 	file.set_chunk_size(chunk_size); | ||
|  | 	match chunk_id: | ||
|  | 		'SIZE': | ||
|  | 			vox.current_index += 1; | ||
|  | 			var model = vox.get_model(); | ||
|  | 			var x = file.get_32(); | ||
|  | 			var y = file.get_32(); | ||
|  | 			var z = file.get_32(); | ||
|  | 			model.size = Vector3(x, y, z); | ||
|  | 			if debug_file: print('SIZE ', model.size); | ||
|  | 		'XYZI': | ||
|  | 			var model = vox.get_model(); | ||
|  | 			if debug_file: print('XYZI'); | ||
|  | 			for _i in range(file.get_32()): | ||
|  | 				var x = file.get_8() | ||
|  | 				var y = file.get_8() | ||
|  | 				var z = file.get_8() | ||
|  | 				var c = file.get_8() | ||
|  | 				var voxel = Vector3(x, y, z) | ||
|  | 				model.voxels[voxel] = c - 1 | ||
|  | 				if debug_file && debug_models: print('\t', voxel, ' ', c-1); | ||
|  | 		'RGBA': | ||
|  | 			vox.colors = [] | ||
|  | 			for _i in range(256): | ||
|  | 				var r = float(file.get_8() / 255.0) | ||
|  | 				var g = float(file.get_8() / 255.0) | ||
|  | 				var b = float(file.get_8() / 255.0) | ||
|  | 				var a = float(file.get_8() / 255.0) | ||
|  | 				vox.colors.append(Color(r, g, b, a)) | ||
|  | 		'nTRN': | ||
|  | 			var node_id = file.get_32(); | ||
|  | 			var attributes = file.get_vox_dict(); | ||
|  | 			var node = VoxNode.new(node_id, attributes); | ||
|  | 			vox.nodes[node_id] = node; | ||
|  | 
 | ||
|  | 			var child = file.get_32(); | ||
|  | 			node.child_nodes.append(child); | ||
|  | 
 | ||
|  | 			file.get_32(); | ||
|  | 			node.layerId = file.get_32(); | ||
|  | 			var num_of_frames = file.get_32(); | ||
|  | 
 | ||
|  | 			if debug_file: | ||
|  | 				print('nTRN[', node_id, '] -> ', child); | ||
|  | 				if (!attributes.is_empty()): print('\t', attributes); | ||
|  | 			if num_of_frames > 0: | ||
|  | 				node.transforms = {}; | ||
|  | 			for _frame in range(num_of_frames): | ||
|  | 				var keyframe = 0; | ||
|  | 				var newTransform = { "position": Vector3(), "rotation": Basis() }; | ||
|  | 				var frame_attributes = file.get_vox_dict(); | ||
|  | 				if (frame_attributes.has('_f')): | ||
|  | 					keyframe = int(frame_attributes['_f']); | ||
|  | 				if (frame_attributes.has('_t')): | ||
|  | 					var trans = frame_attributes['_t']; | ||
|  | 					newTransform.position = string_to_vector3(trans); | ||
|  | 					if debug_file: print('\tT: ', newTransform.position); | ||
|  | 				if (frame_attributes.has('_r')): | ||
|  | 					var rot = frame_attributes['_r']; | ||
|  | 					newTransform.rotation = byte_to_basis(int(rot)).inverse(); | ||
|  | 					if debug_file: print('\tR: ', newTransform.rotation); | ||
|  | 				node.transforms[keyframe] = newTransform; | ||
|  | 				if not fileKeyframeIds.has(keyframe): | ||
|  | 					fileKeyframeIds.append(keyframe); | ||
|  | 		'nGRP': | ||
|  | 			var node_id = file.get_32(); | ||
|  | 			var attributes = file.get_vox_dict(); | ||
|  | 			var node = VoxNode.new(node_id, attributes); | ||
|  | 			vox.nodes[node_id] = node; | ||
|  | 
 | ||
|  | 			var num_children = file.get_32(); | ||
|  | 			for _c in num_children: | ||
|  | 				node.child_nodes.append(file.get_32()); | ||
|  | 			if debug_file: | ||
|  | 				print('nGRP[', node_id, '] -> ', node.child_nodes); | ||
|  | 				if (!attributes.is_empty()): print('\t', attributes); | ||
|  | 		'nSHP': | ||
|  | 			var node_id = file.get_32(); | ||
|  | 			var attributes = file.get_vox_dict(); | ||
|  | 			var node = VoxNode.new(node_id, attributes); | ||
|  | 			vox.nodes[node_id] = node; | ||
|  | 
 | ||
|  | 			var num_models = file.get_32(); | ||
|  | 			for _i in range(num_models): | ||
|  | 				var keyframe = 0; | ||
|  | 				var modelId = file.get_32(); | ||
|  | 				var model_attributes = file.get_vox_dict(); | ||
|  | 				if (model_attributes.has('_f')): | ||
|  | 					keyframe = int(model_attributes['_f']); | ||
|  | 				node.models[keyframe] = modelId; | ||
|  | 				if not fileKeyframeIds.has(keyframe): | ||
|  | 					fileKeyframeIds.append(keyframe); | ||
|  | 			if debug_file: | ||
|  | 				print('nSHP[', node_id,'] -> ', node.models); | ||
|  | 				if (!attributes.is_empty()): print('\t', attributes); | ||
|  | 		'MATL': | ||
|  | 			var material_id = file.get_32() - 1; | ||
|  | 			var properties = file.get_vox_dict(); | ||
|  | 			vox.materials[material_id] = VoxMaterial.new(properties); | ||
|  | 			if debug_file: | ||
|  | 				print("MATL ", material_id); | ||
|  | 				print("\t", properties); | ||
|  | 		'LAYR': | ||
|  | 			var layer_id = file.get_32(); | ||
|  | 			var attributes = file.get_vox_dict(); | ||
|  | 			var isVisible = true; | ||
|  | 			if '_hidden' in attributes and attributes['_hidden'] == '1': | ||
|  | 				isVisible = false; | ||
|  | 			var layer = VoxLayer.new(layer_id, isVisible); | ||
|  | 			vox.layers[layer_id] = layer; | ||
|  | 		_: | ||
|  | 			if debug_file: print(chunk_id); | ||
|  | 	file.read_remaining(); | ||
|  | 
 | ||
|  | func unify_voxels(vox: VoxData, mergeKeyframes: bool): | ||
|  | 	var node = vox.nodes[0]; | ||
|  | 	var layeredVoxelData = get_layeredVoxels(node, vox, -1, mergeKeyframes) | ||
|  | 	return layeredVoxelData.getDataMergedFromLayers(); | ||
|  | 
 | ||
|  | class LayeredVoxelData: | ||
|  | 	var data_keyframed_layered = {}; | ||
|  | 
 | ||
|  | 	func combine(keyframeId, layerId, model): | ||
|  | 		# Make sure there's space | ||
|  | 		if not keyframeId in data_keyframed_layered: | ||
|  | 			data_keyframed_layered[keyframeId] = {} | ||
|  | 		if not layerId in data_keyframed_layered[keyframeId]: | ||
|  | 			data_keyframed_layered[keyframeId][layerId] = {} | ||
|  | 		# Add the model voxels to the data | ||
|  | 		var offset = (model.size / 2.0).floor(); | ||
|  | 		for voxel in model.voxels: | ||
|  | 			data_keyframed_layered[keyframeId][layerId][voxel - offset] = model.voxels[voxel]; | ||
|  | 
 | ||
|  | 	func combine_data(other): | ||
|  | 		for keyframeId in other.data_keyframed_layered: | ||
|  | 			if not keyframeId in data_keyframed_layered: | ||
|  | 				data_keyframed_layered[keyframeId] = {} | ||
|  | 			for layerId in other.data_keyframed_layered[keyframeId]: | ||
|  | 				if not layerId in data_keyframed_layered[keyframeId]: | ||
|  | 					data_keyframed_layered[keyframeId][layerId] = {} | ||
|  | 				for voxel in other.data_keyframed_layered[keyframeId][layerId]: | ||
|  | 					data_keyframed_layered[keyframeId][layerId][voxel] = ( | ||
|  | 						other.data_keyframed_layered[keyframeId][layerId][voxel]); | ||
|  | 
 | ||
|  | 	func transform(transforms): | ||
|  | 		var new_data = {}; | ||
|  | 		for keyframeId in data_keyframed_layered: | ||
|  | 			new_data[keyframeId] = {} | ||
|  | 			var transform = get_input_for_keyframe(keyframeId, transforms); | ||
|  | 			for layerId in data_keyframed_layered[keyframeId]: | ||
|  | 				new_data[keyframeId][layerId] = {} | ||
|  | 				for voxel in data_keyframed_layered[keyframeId][layerId]: | ||
|  | 					var half_step = Vector3(0.5, 0.5, 0.5); | ||
|  | 					var new_voxel = ( | ||
|  | 						(transform.rotation * voxel+half_step-half_step).floor() + | ||
|  | 						transform.position); | ||
|  | 					new_data[keyframeId][layerId][new_voxel] = ( | ||
|  | 						data_keyframed_layered[keyframeId][layerId][voxel]); | ||
|  | 		data_keyframed_layered = new_data; | ||
|  | 
 | ||
|  | 	func getDataMergedFromLayers(): | ||
|  | 		# The result of this function | ||
|  | 		var result = {}; | ||
|  | 		for keyframeId in data_keyframed_layered: | ||
|  | 			result[keyframeId] = {} | ||
|  | 			# Merge all layer data in layerId order (highest layer overrides all) | ||
|  | 			var layerIds = data_keyframed_layered[keyframeId].keys(); | ||
|  | 			layerIds.sort(); | ||
|  | 			for layerId in layerIds: | ||
|  | 				for voxel in data_keyframed_layered[keyframeId][layerId]: | ||
|  | 					result[keyframeId][voxel] = data_keyframed_layered[keyframeId][layerId][voxel]; | ||
|  | 		# Return the merged data | ||
|  | 		return result; | ||
|  | 
 | ||
|  | 	static func get_input_for_keyframe(focusKeyframeId, inputCollection): | ||
|  | 		var inputKeyframeIds = inputCollection.keys(); | ||
|  | 		inputKeyframeIds.sort(); | ||
|  | 		inputKeyframeIds.reverse(); | ||
|  | 		var result = inputKeyframeIds.back(); | ||
|  | 		for inputKeyframeId in inputKeyframeIds: | ||
|  | 			if inputKeyframeId <= focusKeyframeId: | ||
|  | 				result = inputKeyframeId; | ||
|  | 				break; | ||
|  | 		return inputCollection[result]; | ||
|  | 
 | ||
|  | 
 | ||
|  | func get_layeredVoxels(node: VoxNode, vox: VoxData, layerId: int, mergeKeyframes: bool): | ||
|  | 	var result = LayeredVoxelData.new(); | ||
|  | 
 | ||
|  | 	# Handle layers (keeping separated and ignoring hidden) | ||
|  | 	if node.layerId in vox.layers: | ||
|  | 		if vox.layers[node.layerId].isVisible: | ||
|  | 			layerId = node.layerId; | ||
|  | 		else: | ||
|  | 			return result; | ||
|  | 
 | ||
|  | 	# Add all models in this node | ||
|  | 	if mergeKeyframes: | ||
|  | 		for model_index in node.models: | ||
|  | 			var modelId = node.models[model_index]; | ||
|  | 			var model = vox.models[modelId]; | ||
|  | 			result.combine(0, layerId, model); | ||
|  | 	elif node.models.size() > 0: | ||
|  | 		for fileKeyframeId in fileKeyframeIds: | ||
|  | 			var frameModelId = LayeredVoxelData.get_input_for_keyframe(fileKeyframeId, node.models); | ||
|  | 			var model = vox.models[frameModelId]; | ||
|  | 			result.combine(fileKeyframeId, layerId, model); | ||
|  | 
 | ||
|  | 	# Process child nodes | ||
|  | 	for child_index in node.child_nodes: | ||
|  | 		var child = vox.nodes[child_index]; | ||
|  | 		var child_data = get_layeredVoxels(child, vox, layerId, mergeKeyframes); | ||
|  | 		result.combine_data(child_data); | ||
|  | 
 | ||
|  | 	# Run transforms | ||
|  | 	result.transform(node.transforms); | ||
|  | 
 | ||
|  | 	return result; |