Files
gtav-src/tools_ng/lib/pipeline/content/content_map.rb
T
2025-09-29 00:52:08 +02:00

668 lines
21 KiB
Ruby
Executable File

#
# File:: content_map.rb
# Description:: Content system map content tree node class implementation for a given map.
#
# Author:: Luke Openshaw <luke.openshaw@rockstarnorth.com>
# Author:: David Muir <david.muir@rockstarnorth.com>
# Date:: 22 July 2008
#
#-----------------------------------------------------------------------------
# Uses
#-----------------------------------------------------------------------------
require 'pipeline/content/content_core'
require 'pipeline/log/log'
require 'rexml/document'
include REXML
module Pipeline
module Content
#
# == Description
# Define a map content module for common functionality. Really added
# so we can get logging information about why this process takes so
# damn long.
#
module ContentMap
# Return our static ContentMaps log object.
def ContentMap::log()
@@log = Pipeline::Log.new( 'content_maps' )
@@log
end
private
@@log = nil
end
#
#
#
class MapComponent < Group
attr_reader :p
attr_reader :mapname
attr_reader :stream_src
attr_reader :stream_dst
XML_CONTENT_TYPE = 'mapcomponent'
def initialize( name, srcfile, project, mapname, stream_src, stream_dst )
super(name)
@p = project
@mapname = mapname
@stream_src = stream_src
@stream_dst = stream_dst
if ::File.exist?(srcfile)
::File.open( srcfile ) { |sourcefile|
sourcedoc = Document.new( sourcefile )
populate_lists( sourcedoc ) }
end
flush_stream()
end
def populate_lists( sourcedoc )
ContentMap::log().info( 'No method implemented for list population for this map component content')
end
#
#
#
def build()
ContentMap::log().info( 'No build method implemented for this map component.' )
end
#
#
#
def flush_stream()
# Flush the stream. This happens in maxscript at the moment, consider moving
end
end
#
#
#
class MapAnim < MapComponent
attr_reader :anim_list
attr_reader :relative_path
XML_CONTENT_TYPE = 'mapanim'
#
#
#
def initialize( srcfile, project, mapname, generic )
@anim_list = Array.new()
@relative_path = "anim"
stream_path = OS::Path.combine(project.netstream, "maps")
super( "mapanim", srcfile, project, mapname, stream_path, stream_path )
end
#
#
#
def populate_lists( sourcedoc )
sourcedoc.root.each_element do | elem |
@anim_list << elem.name
end
end
#
#
#
def build()
ContentMap::log().info( "Building animation content: #{@anim_list.size}." )
build_anim_content() if @anim_list.size > 0
end
#
#
#
def build_anim_content()
pack_content = Zip::new(@mapname, OS::Path.combine(@stream_dst, @mapname), "icd.zip", @p.ind_target, false )
project = Pipeline::Config.instance.project
src_dir = OS::Path.combine(project.assets, "maps/anims", @mapname)
@anim_list.each do | anim_file |
# Only add the inputs where both the anim and the clip exist
if ::File.exist?( OS::Path.combine( src_dir, anim_file + ".anim" ) ) and ::File.exist?( OS::Path.combine( src_dir, anim_file + ".clip" ) )
pack_content.add_input( File.new( anim_file, src_dir, "anim" ) )
pack_content.add_input( File.new( anim_file, src_dir, "clip" ) )
end
end
add_child( pack_content )
end
end
#
#
#
class MapBounds < MapComponent
attr_reader :bound_dict_list
attr_reader :hidetail_bound_dict_list
attr_reader :ext
attr_reader :relative_path
attr_reader :static
XML_CONTENT_TYPE = 'mapbound'
#
#
#
def initialize( srcfile, project, mapname, relative_path, ext, generic, static )
@ext = ext
@relative_path = relative_path
@bound_dict_list = Hash.new()
@hidetail_bound_dict_list = Hash.new()
@static = static
@mapzip_content_node = project.content.find_first( mapname, 'mapzip' )
stream_path = OS::Path.combine(project.netstream, "maps")
super( "mapbounds", srcfile, project, mapname, stream_path, stream_path )
end
#
#
#
def populate_lists( sourcedoc )
if true == @mapzip_content_node.viaboundsprocessor then
bounds = Array.new()
sourcedoc.root.each_element do | elem |#for each item in the source xml
bounds << elem.name
end
@bound_dict_list[mapname] = bounds#add the bounds files to the zip
else#old school bounds resourcing
if false == @static then
sourcedoc.root.each_element do | elem |
if elem.elements.size > 0 then
bounds = Array.new()
elem.each_element do | bound_elem |
bounds << bound_elem.name
end
@bound_dict_list[elem.name] = bounds
end
end
else
sourcedoc.root.each_element do | elem |
if elem.elements.size > 0 then
case elem.name
when "standard"
elem.each_element do | bound_elem |
bounds = Array.new()
bound_elem.each_element do | subbound_elem |
bounds << bound_elem.name
end
@bound_dict_list[bound_elem.name] = bounds
end
when "hidetail"
elem.each_element do | bound_elem |
bounds = Array.new()
bound_elem.each_element do | subbound_elem |
bounds << bound_elem.name
end
@hidetail_bound_dict_list[bound_elem.name] = bounds
end
end
end
end
end
end
end
#
#
#
def build()
ContentMap::log().info( "Building bound content: #{@bound_dict_list.size}." )
build_bound_content() if @bound_dict_list.size > 0
end
#
#
#
def build_bound_content()
@bound_dict_list.each do | bound_dict |
bound_dict_name = bound_dict[0]
bound_list = bound_dict[1]
if true == @mapzip_content_node.viaboundsprocessor then
pack_content = Zip::new(bound_dict_name + "_collision", OS::Path.combine(@stream_dst, mapname), "zip", @p.ind_target, false )
else
pack_content = Zip::new(bound_dict_name, OS::Path.combine(@stream_dst, mapname), "#{@ext}.zip", @p.ind_target, false )
end
src_dir = ""
if ( false == @static ) then
if true == @mapzip_content_node.viaboundsprocessor then
src_dir = OS::Path.combine(@stream_src, @mapname, "/bound_dynamic/")
else
src_dir = OS::Path.combine(@stream_src, @mapname, OS::Path.combine("/bound_dynamic/", bound_dict_name))
end
else
src_dir = OS::Path.combine(@stream_src, @mapname, @relative_path)
end
bound_list.each do | bound |
pack_content.add_input( File.new( bound, src_dir, "bnd" ) )
end
add_child( pack_content )
end
@hidetail_bound_dict_list.each do | bound_dict |
bound_dict_name = "hi@" + bound_dict[0]
bound_list = bound_dict[1]
pack_content = Zip::new(bound_dict_name, OS::Path.combine(@stream_dst, mapname), "#{@ext}.zip", @p.ind_target, false )
src_dir = OS::Path.combine(@stream_src, @mapname, @relative_path)
bound_list.each do | bound |
pack_content.add_input( File.new( bound, src_dir, "bnd" ) )
end
add_child( pack_content )
end
end
end
#
# MapOcclusion content node is used to gather and process all occlusion
# meshes and process them in the pipeline.
#
class MapOcclusion < MapComponent
XML_CONTENT_TYPE = 'mapocclusion'
attr_reader :filename
attr_reader :occlusion_xml_list # Output XML list for IMAP serialising.
attr_reader :occlusion_mesh_list # Input .mesh list from exporter.
def initialize( srcfile, project, mapname, cacheFolder)
@occlusion_xml_list = []
@occlusion_mesh_list = []
stream_src = OS::Path::combine( cacheFolder, 'raw', 'maps', mapname, 'occlusion' )
stream_dst = OS::Path::combine( cacheFolder, 'maps', 'dev', mapname )
puts "stream_src: #{stream_src}"
puts "stream_dst: #{stream_dst}"
super( 'mapocclusion', srcfile, project, mapname, stream_src, stream_dst )
@filename = OS::Path::combine( @stream_dst, "#{@mapname}.occl.zip" )
end
# Parse XML occlusion to populate internal list.
def populate_lists( sourcedoc )
puts "populate_lists hit"
sourcedoc.elements.each( '/occlusion/occludeMeshes/*' ) do |elem|
@occlusion_mesh_list << OS::Path::combine( @stream_src, "#{elem.name}.mesh" )
puts "stream_src: #{stream_src}"
puts ".mesh: #{elem.name}"
end
# Even although the occludeBoxes are mentioned we don't do
# any processing here; thats all handled in the IMAP serialiser.
end
# Build occlusion ZIP data for this map.
def build( )
ContentMap::log().info( "Building occlusion content: #{@occlusion_mesh_list.size}." )
build_occlusion_content( ) if ( @occlusion_mesh_list.size() > 0 )
end
#--------------------------------------------------------------------
# Private
#--------------------------------------------------------------------
private
def build_occlusion_content( )
@occlusion_xml_list = []
@occlusion_mesh_list.each do |occlusion_file|
output_file = OS::Path::combine( @stream_src, "#{OS::Path::get_basename( occlusion_file )}.xml" )
if ( occludermesh_convert( occlusion_file, output_file ) ) then
@occlusion_xml_list << output_file
end
end
pack_content = Zip::new( @mapname, @stream_dst, "occl.zip", @p.ind_target, false )
if ( @occlusion_xml_list.size > 0 ) then
@occlusion_xml_list.each do |occlusion_file|
basename = OS::Path::get_basename( occlusion_file )
pack_content.add_input( File::new( basename, @stream_src, 'xml' ) )
end
else
ContentMap::log().error( "Occlusion list is empty!" )
end
add_child( pack_content )
end
# Convert .mesh to .xml
def occludermesh_convert( infile, outfile )
puts "infile: #{infile}"
puts "outfile: #{outfile}"
c = Pipeline::Config::instance()
branch = @p.branches[@p.default_branch]
projtools = RageConvertTool::parse( @p, branch.name )
projtool = projtools['win32'] if ( projtools.has_key?( 'win32' ) )
projtool = projtools['win64'] if ( projtools.has_key?( 'win64' ) )
options = "#{OS::Path::combine( c.toolslib, 'util', 'ragebuilder', 'convert_occluders.rbs' )}"
options += " -nopopups -build #{branch.build}"
options += " -shader #{branch.shaders} -shaderdb #{OS::Path::combine(branch.shaders, 'db')}"
options += " #{projtool.options}"
options += " -input #{infile}"
options += " -output #{outfile}"
ContentMap::log().info( "Occluder mesh processing: '#{projtool.path} #{options}'." )
system( "#{projtool.path} #{options}" )
end
end
#
# == Description
# Map texture dictionary content node.
#
class MapTxd < MapComponent
attr_reader :txd_list
attr_reader :force_add_textures_list
XML_CONTENT_TYPE = 'maptxd'
#
#
#
def initialize(srcfile, project, mapname, generic )
@txd_list = Hash.new()
@force_add_textures_list = Hash.new()
stream_path = OS::Path.combine(project.netstream, "maps")
super( "maptxd", srcfile, project, mapname, OS::Path.combine(project.assets, "maps/textures"), stream_path )
end
#
#
#
def populate_lists( sourcedoc)
sourcedoc.root.each_element do | elem |
if nil!=elem.attributes["forceAddTexture"]
@force_add_textures_list[elem.name] = true
else
@force_add_textures_list[elem.name] = false
end
if elem.elements.size > 0
textures = Array.new()
elem.each_element do | tex_elem |
path = tex_elem.attributes["path"]
if path != nil
tex_name = OS::Path.combine( path, tex_elem.name )
else
tex_name = OS::Path.combine( @stream_src, tex_elem.name )
end
textures << tex_name
end
@txd_list[elem.name] = textures
end
end
end
#
# Build the map component.
#
def build()
ContentMap::log().info( "Building texture dictionary content: #{@txd_list.size}." )
build_texture_dict_content() if @txd_list.size > 0
end
#----------------------------------------------------------------
# Private
#----------------------------------------------------------------
private
#
# Build the texture dictionary.
#
def build_texture_dict_content( )
@txd_list.each do |txd|
txd_name = txd[0]
tex_list = txd[1]
path = OS::Path::combine( @stream_dst, @mapname )
zip_content = Zip::new( txd_name, path, 'itd.zip', @p.ind_target, false )
tex_list.each do |tex_file|
next unless OS::Path.get_extension(tex_file) == "dds"
#scoop up the related tcl file
tex = OS::Path.get_basename( tex_file )
tcl_file = OS::Path.combine( @p.netstream, "maps/tcls", tex + ".tcl" )
zip_content.add_input( Content::File::from_filename( tcl_file ) ) if ::File.exist?( tcl_file )
if @force_add_textures_list[txd_name]
# JWR - Eventually we will just pump out TCLs here as the TCS file will contain texture linkage information
# GD - Well eventually this will hopefully go awa when we sorted ragebuilder...
zip_content.add_input( File::from_filename( tex_file ) )
end
end
add_child( zip_content )
end
end
end
#
# == Description
# Map geometry (drawables, fragments) content node.
#
class MapGeo < MapComponent
attr_reader :geo_big_list # LODs and other large geom
attr_reader :geo_list # LPXO: Think this will be deprecated by the time im finished
attr_reader :frag_list # Fragments
attr_reader :geo_tex_list # Geom with textures
attr_reader :geo_tex_packed_list # Geom with packed textures
attr_reader :frag_tex_packed_list # Frag with packed textures
XML_CONTENT_TYPE = 'mapgeo'
#
#
#
def initialize( srcfile, project, mapname, generic, stream_subdir )
@geo_big_list = Hash.new()
@geo_list = Array.new()
@frag_list = Array.new()
@geo_tex_list = Array.new()
@geo_tex_packed_list = Hash.new()
@frag_tex_packed_list = Hash.new()
stream_path = OS::Path.combine(project.netstream, stream_subdir)
super( "mapgeo", srcfile, project, mapname, stream_path, stream_path )
end
#
#
#
def build()
ContentMap::log().info( "Building fragment content: #{@frag_list.size}." )
build_fragment_content() if @frag_list.size > 0
ContentMap::log().info( "Building geometry content: #{@geo_tex_list.size}." )
build_geo_with_tex_content() if @geo_tex_list.size > 0
ContentMap::log().info( "Building big geometry content: #{@geo_big_list.size}." )
build_geo_big_content() if @geo_big_list.size > 0
end
#
#
#
def populate_lists( sourcedoc )
sourcedoc.root.each_element do | elem |
case elem.name
when 'geo'
@geo_list << elem.name
when 'geotex'
elem.each_element do | geo_elem |
@geo_tex_list << geo_elem.name
end
when 'geotextex'
elem.each_element do | geo_elem |
if geo_elem.elements.size > 0
packed_texs = Array.new()
geo_elem.each_element do | sub_elem |
if ( sub_elem.attributes['path'].nil? ) then
packed_texs << sub_elem.name
else
# Has path attribute; fix for tint palettes.
# See Bug #262824.
packed_texs << OS::Path::combine( sub_elem.attributes['path'], sub_elem.name )
end
end
@geo_tex_packed_list[geo_elem.name] = packed_texs
end
end
when 'geobig'
elem.each_element do | group_elem |
if group_elem.elements.size > 0
big_geos = Array.new()
group_elem.each_element do | big_geo |
big_geos << big_geo.name
end
@geo_big_list[group_elem.name] = big_geos
end
end
when 'frags'
elem.each_element do | frag |
@frag_list << frag.name
end
when 'fragtex'
elem.each_element do | frag_elem |
if frag_elem.elements.size > 0
packed_texs = Array.new()
frag_elem.each_element do | sub_elem |
if ( sub_elem.attributes['path'].nil? ) then
packed_texs << sub_elem.name
else
packed_texs << OS::Path::combine( sub_elem.attributes['path'], sub_elem.name )
end
end
@frag_tex_packed_list[frag_elem.name] = packed_texs
end
end
end
end
end
#----------------------------------------------------------------
# Private
#----------------------------------------------------------------
private
def pack_textures(packed_texs, zip_content)
packed_texs.each do | tex |
tex_file = ''
tcl_file = ''
if ( tex.include?( ':' ) ) then
# Have absolute path; probably from tint palette bug fix.
tex_file = "#{tex}.dds"
tcl_file = OS::Path::combine( @p.netstream, "maps/tcls", OS::Path::get_basename( tex ) + ".tcl" )
else
tex_file = OS::Path.combine( @p.assets, "maps/textures", tex + ".dds" )
tcl_file = OS::Path.combine( @p.netstream, "maps/tcls", tex + ".tcl" )
end
zip_content.add_input( Content::File::from_filename( tex_file ) ) if ::File.exist?( tex_file )
zip_content.add_input( Content::File::from_filename( tcl_file ) ) if ::File.exist?( tcl_file )
end
end
# Build fragment files.
def build_fragment_content()
fragments = Group.new( "fragments")
@frag_list.each do | frag_name |
zip_content = Zip::new( frag_name, OS::Path.combine(@stream_dst, @mapname), "ift.zip", @p.ind_target, false )
frag_dir = OS::Path.combine(@stream_src, @mapname, frag_name)
filelist = OS::FindEx.find_files(frag_dir + "/*.*", false)
filelist.each do | file |
zip_content.add_input( File::from_filename( file ) ) \
if ::File.exist?( file )
end
# Handle packed textures.
packed_texs = @frag_tex_packed_list[frag_name]
if ( packed_texs != nil ) then
pack_textures(packed_texs, zip_content)
end
fragments.add_child( zip_content ) unless ( zip_content.nil? )
end
add_child( fragments )
end
# Build drawables with packed textures.
def build_geo_with_tex_content()
geo_with_texs = Group.new( "geo_with_texs")
@geo_tex_list.each_with_index do |geo_name, index|
ContentMap::log().info( " Building geometry: #{geo_name} [#{index}/#{@geo_tex_list.size}]" )
time_start = Time.now()
zip_content = Zip::new( geo_name, OS::Path.combine(@stream_dst, @mapname), "idr.zip", @p.ind_target, false )
geo_dir = OS::Path.combine(@stream_src, @mapname, geo_name)
filelist = OS::FindEx.find_files(geo_dir + "/*.*", false)
# Handle the actual drawable data.
filelist.each do |file|
zip_content.add_input( Content::File::from_filename( file ) ) \
if ::File.exist?( file )
end
# Handle packed textures.
packed_texs = @geo_tex_packed_list[geo_name]
if ( packed_texs != nil ) then
pack_textures(packed_texs, zip_content)
end
geo_with_texs.add_child( zip_content ) unless ( zip_content.nil? )
ContentMap::log().info( " Built geometry: #{geo_name} [#{index}/#{@geo_tex_list.size}] [#{(Time.now() - time_start).to_s( 3 )}s]" )
end
add_child( geo_with_texs )
end
#
def build_geo_big_content()
geo_big = Group.new("geo_big")
@geo_big_list.each do | geo_group_name |
pack_content = Zip::new(geo_group_name[0], OS::Path.combine(@stream_dst, @mapname), 'idd.zip', @p.ind_target, false )
geo_list = geo_group_name[1]
geo_list.each do | geo |
geo_dir = OS::Path.combine( @stream_src, @mapname, geo )
filelist = OS::FindEx.find_files(geo_dir + "/*.*", false)
geo_group = Group.new( geo, geo )
filelist.each do | file |
geo_group.add_child( File.new( OS::Path.get_basename( file ), OS::Path.get_directory( file ), OS::Path.get_extension( file ) ) ) if ::File.exist?( file )
end
pack_content.add_input( geo_group ) if geo_group.children.size > 0
end
geo_big.add_child(pack_content) if pack_content != nil
end
add_child( geo_big )
end
end
end # module Content
end # module Pipeline