File size: 6,908 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
EngineBinAssetsPrints = {}
EngineBinAssetsPrint  = CreatePrint { "", output = function(s) table.insert(EngineBinAssetsPrints, s) end }
EngineBinAssetsPrintf = CreatePrint { "", output = function(s) table.insert(EngineBinAssetsPrints, s) end, format = string.format }
EngineBinAssetsError  = CreatePrint { "EB_ERROR", output = function(s) table.insert(EngineBinAssetsPrints, s) end }
EngineBinAssetsErrorf = CreatePrint { "EB_ERROR", output = function(s) table.insert(EngineBinAssetsPrints, s) end, format = string.format }

local function MarkTestEntityTextures(texture_list)
	local tex_map = {}
	local test_assets_path = ConvertToOSPath("svnAssets/Bin/Common/Materials/")
	for texture_id, path in pairs(texture_list) do
		local texture_name = path:sub(path:find("[^\\]*$"))
		tex_map[texture_name] = {
			id = texture_id,
			path = path,
			skip = string.starts_with(path, test_assets_path),
		}
	end
	return tex_map;
end

local function AddTextureHashes(textures_data, cached_hashes)
	local cached_hashes = cached_hashes or {}
	for texture_id, texture_data in pairs(textures_data) do
		local tex_hash = cached_hashes[texture_id]
		if not tex_hash then 
			local err, hash = AsyncFileToString(texture_data.path, nil, nil, "hash")
			if err then
				EngineBinAssetsError("AsyncFileToString(" .. texture_data.path .. ") failed: " .. err)
				return err
			else
				tex_hash = tostring(hash)
			end
		end
		texture_data.hash = tex_hash
	end
end

local function GetTexturesCachedHashes()
	local err, hash_list = FileToLuaValue("svnAssets/BuildCache/TextureHashes.lua")
	if err then
		EngineBinAssetsError("Could not load hash map with material textures!")
	end

	local cached_hashes = {}
	for filename, v in pairs(hash_list or empty_table) do
		cached_hashes[filename:sub(filename:find("[^/]*$"))] = tostring(v.hash)
	end

	return cached_hashes
end

function MarkUniqueTextures(textures_data)
	local sorted_texture_ids = table.keys(textures_data)
	table.sort(sorted_texture_ids, function (a, b) return #a == #b and a < b or #a < #b end)
	local unique_hashes = {}
	for _, texture_id in ipairs(sorted_texture_ids) do
		local tex_data = textures_data[texture_id]
		if not tex_data.skip then
			local tex_alias = unique_hashes[tex_data.hash]
			
			if tex_alias then
				tex_data.alias = tex_alias
			elseif not tex_data.hash then
				EngineBinAssetsError("Missing texture hash for(" .. texture_id .. ")")
			else
				--tex_data.alias = texture_id -- not needed it its the same
				unique_hashes[tex_data.hash] = texture_id
			end
		end
	end
	local num_tex = table.count(textures_data)
	local num_unique = table.count(unique_hashes)
	EngineBinAssetsPrintf("Entity textures: %d (%d Unique, %d Duplicates)", num_tex, num_unique, num_tex - num_unique)	
	return unique_hashes
end

local function RemapMaterialTextures(mat_name, textures_data, used_tex, ent_textures)
	local num_sub_mats = GetNumSubMaterials(mat_name)
	for subi=1, num_sub_mats do
		local props = GetMaterialProperties(mat_name, subi-1)
		local new_props = false
		for prop, value in sorted_pairs(props) do
			if type(value) == "string" then
				local tex_name = value:sub(value:find("[^/]*$"))
				if tex_name then
					local tex_data = textures_data[tex_name]
					if tex_data then
						if tex_data.alias then
							new_props = new_props or {}
							new_props[prop] = textures_data[tex_data.alias].id

							ent_textures[tex_data.alias] = true

							local alias_data = textures_data[tex_data.alias]
							alias_data.used_in = alias_data.used_in or {}
							table.insert(alias_data.used_in, mat_name)
						else
							used_tex[tex_name] = tex_data.path
							ent_textures[tex_name] = true

							tex_data.used_in = tex_data.used_in or {}
							table.insert(tex_data.used_in, mat_name)
						end
					end
				elseif value:find("<unknown>", 1, "plain") then
					EngineBinAssetsPrintf("once", "Missing texture Textures/%s in material %s", value:match("Textures/(.*)%.<unknown>"), mat_name)
				end
			end
		end
		if new_props then
			SetMaterialProperties(mat_name, subi-1, new_props)
		end
	end
end

local function BuildTextureIdx(used_tex)
	local tex_idx = {}
	local textures = table.keys(used_tex)
	table.sort(textures, function (a, b) return #a == #b and a < b or #a < #b end)
	
	for _, tex_name in ipairs(textures) do
		tex_idx[#tex_idx+1] = string.format("Textures/%s=%s", tex_name, used_tex[tex_name])
	end
	
	local filename = "entities.txt"
	local path = "svnAssets/BuildCache/win32/TextureIdx/"
	local err = AsyncCreatePath(path)
	assert(not err, "Error creating textures idx path: " .. tostring(err))
	local err = StringToFileIfDifferent(path .. filename, table.concat(tex_idx, "\r\n"))
	assert(not err, "Error writing textures idx: " .. tostring(err))
end

local function ShortenTexturePaths(textures_data)
	for _, texture_data in pairs(textures_data) do
		texture_data.path = string.match(texture_data.path, "\\([^\\]+Assets.+)$")
	end
end

function CollapseMaterialTextures(texture_list, entities, cached_hashes, shorten_paths)
	local textures_data = MarkTestEntityTextures(texture_list)
	AddTextureHashes(textures_data, cached_hashes)
	if shorten_paths then
		ShortenTexturePaths(textures_data)
	end
	MarkUniqueTextures(textures_data)

	local materials_seen, entity_textures, used_tex = {}, {}, {}
	for entity, _ in sorted_pairs(entities) do
		if entity:sub(1,1) ~= "#" then
			local ent_textures = {}
			local states = GetStates(entity)
			for si=1, #states do
				local state = GetStateIdx(states[si])
				local num_lods = GetStateLODCount(entity, state)
				for li=1, num_lods do
					local material = GetStateMaterial(entity, state, li - 1)
					if not materials_seen[material] then
						RemapMaterialTextures(material, textures_data, used_tex, ent_textures)
						materials_seen[material] = entity
						entity_textures[entity] = ent_textures
					else
						local other_entity = materials_seen[material]
						entity_textures[entity] = entity_textures[other_entity]
					end
				end
			end
		end
	end	
	
	return materials_seen, used_tex, textures_data
end

function CollapseAllTextures()
	local all_entities = GetAllEntities()
	local texture_list = CollectMtlReferencedTextures()
	local cached_hashes = GetTexturesCachedHashes()

	local materials_seen, used_tex, textures_data = CollapseMaterialTextures(texture_list, all_entities, cached_hashes, true)

	BuildTextureIdx(used_tex)

	return materials_seen, used_tex, textures_data
end

function CollapseEntitiesTextures(entities)
	local texture_list = {}
	for entity, _ in sorted_pairs(entities) do
		local states = GetStates(entity)
		for si=1, #states do
			local state = GetStateIdx(states[si])
			local state_textures = GetStateMaterialTextures(entity, state)
			table.append(texture_list, state_textures)
		end
	end

	local materials_seen, used_tex, textures_data = CollapseMaterialTextures(texture_list, entities)

	return materials_seen, used_tex, textures_data
end