io_scene_3ds: Added fog and gradient import and export #104811

Merged
Sebastian Sille merged 64 commits from :main into main 2023-08-01 16:21:54 +02:00
2 changed files with 162 additions and 37 deletions

View File

@ -38,8 +38,10 @@ VGRADIENT = 0x1300 # The background gradient colors
USE_VGRADIENT = 0x1301 # The background gradient flag
O_CONSTS = 0x1500 # The origin of the 3D cursor
AMBIENTLIGHT = 0x2100 # The color of the ambient light
LAYER_FOG = 0x2302 # The fog atmosphere settings
USE_LAYER_FOG = 0x2303 # The fog atmosphere flag
FOG = 0x2200 # The fog atmosphere settings
USE_FOG = 0x2201 # The fog atmosphere flag
LAYER_FOG = 0x2302 # The fog layer atmosphere settings
USE_LAYER_FOG = 0x2303 # The fog layer atmosphere flag
MATERIAL = 45055 # 0xAFFF // This stored the texture info
OBJECT = 16384 # 0x4000 // This stores the faces, vertices, etc...
@ -1468,7 +1470,7 @@ def make_ambient_node(world):
emission = next((lk.from_socket.node for lk in ambilinks if lk.to_node.type in ambioutput), False)
ambinode = next((lk.from_socket.node for lk in ambilinks if lk.to_node.type == 'EMISSION'), emission)
kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points]
ambipath = ('nodes[\"RGB\"].outputs[0].default_value' if ambinode.type == 'RGB' else
ambipath = ('nodes[\"RGB\"].outputs[0].default_value' if ambinode and ambinode.type == 'RGB' else
'nodes[\"Emission\"].inputs[0].default_value')
nkeys = len(kframes)
if not 0 in kframes:
@ -1615,16 +1617,18 @@ def save(operator, context, filepath="", scale_factor=1.0, use_scene_unit=False,
# Add BACKGROUND and BITMAP
if world.use_nodes:
bgtype = 'BACKGROUND'
ntree = world.node_tree.links
background_color_chunk = _3ds_chunk(RGB)
background_chunk = _3ds_chunk(SOLIDBACKGND)
background_flag = _3ds_chunk(USE_SOLIDBGND)
bgtype = 'BACKGROUND'
bgmixer = 'BACKGROUND', 'MIX', 'MIX_RGB'
bgshade = 'ADD_SHADER', 'MIX_SHADER', 'OUTPUT_WORLD'
bg_tex = 'TEX_IMAGE', 'TEX_ENVIRONMENT'
bg_color = next((lk.from_node.inputs[0].default_value[:3] for lk in ntree if lk.from_node.type == bgtype and lk.to_node.type in bgshade), world.color)
bg_mixer = next((lk.from_node.type for lk in ntree if lk.from_node.type in {'MIX', 'MIX_RGB'} and lk.to_node.type == bgtype), bgtype)
bg_mixer = next((lk.from_node.type for lk in ntree if lk.from_node.type in bgmixer and lk.to_node.type == bgtype), bgtype)
bg_image = next((lk.from_node.image.name for lk in ntree if lk.from_node.type in bg_tex and lk.to_node.type == bg_mixer), False)
gradient = next((lk.from_node.color_ramp.elements for lk in ntree if lk.from_node.type == 'VALTORGB' and lk.to_node.type in bgmixer), False)
background_color_chunk.add_variable("color", _3ds_float_color(bg_color))
background_chunk.add_subchunk(background_color_chunk)
if bg_image:
@ -1633,28 +1637,59 @@ def save(operator, context, filepath="", scale_factor=1.0, use_scene_unit=False,
background_image.add_variable("image", _3ds_string(sane_name(bg_image)))
object_info.add_subchunk(background_image)
object_info.add_subchunk(background_chunk)
# Add VGRADIENT chunk
if gradient and len(gradient) >= 3:
gradient_chunk = _3ds_chunk(VGRADIENT)
background_flag = _3ds_chunk(USE_VGRADIENT)
gradient_chunk.add_variable("midpoint", _3ds_float(gradient[1].position))
gradient_topcolor_chunk = _3ds_chunk(RGB)
gradient_topcolor_chunk.add_variable("color", _3ds_float_color(gradient[2].color[:3]))
gradient_chunk.add_subchunk(gradient_topcolor_chunk)
gradient_midcolor_chunk = _3ds_chunk(RGB)
gradient_midcolor_chunk.add_variable("color", _3ds_float_color(gradient[1].color[:3]))
gradient_chunk.add_subchunk(gradient_midcolor_chunk)
gradient_lowcolor_chunk = _3ds_chunk(RGB)
gradient_lowcolor_chunk.add_variable("color", _3ds_float_color(gradient[0].color[:3]))
gradient_chunk.add_subchunk(gradient_lowcolor_chunk)
object_info.add_subchunk(gradient_chunk)
object_info.add_subchunk(background_flag)
# Add LAYER_FOG settings
fogshader = next((lk.from_socket.node for lk in ntree if lk.from_socket.identifier and lk.to_socket.identifier == 'Volume'), False)
if fogshader:
fogflag = 0
if world.mist_settings.falloff == 'QUADRATIC':
fogflag |= 0x1
if world.mist_settings.falloff == 'INVERSE_QUADRATIC':
fogflag |= 0x2
fog_chunk = _3ds_chunk(LAYER_FOG)
# Add FOG
fognode = next((lk.from_socket.node for lk in ntree if lk.from_socket.node.type == 'VOLUME_ABSORPTION' and lk.to_socket.node.type in bgshade), False)
if fognode:
fog_chunk = _3ds_chunk(FOG)
fog_color_chunk = _3ds_chunk(RGB)
use_fog_flag = _3ds_chunk(USE_LAYER_FOG)
fog_color_chunk.add_variable("color", _3ds_float_color(fogshader.inputs['Color'].default_value[:3]))
fog_chunk.add_variable("lowZ", _3ds_float(world.mist_settings.start))
fog_chunk.add_variable("highZ", _3ds_float(world.mist_settings.depth))
fog_chunk.add_variable("density", _3ds_float(fogshader.inputs['Density'].default_value))
fog_chunk.add_variable("flags", _3ds_uint(fogflag))
use_fog_flag = _3ds_chunk(USE_FOG)
fog_density = fognode.inputs['Density'].default_value * 100
fog_color_chunk.add_variable("color", _3ds_float_color(fognode.inputs[0].default_value[:3]))
fog_chunk.add_variable("nearplane", _3ds_float(world.mist_settings.start))
fog_chunk.add_variable("nearfog", _3ds_float(fog_density * 0.5))
fog_chunk.add_variable("farplane", _3ds_float(world.mist_settings.depth))
fog_chunk.add_variable("farfog", _3ds_float(fog_density + fog_density * 0.5))
fog_chunk.add_subchunk(fog_color_chunk)
object_info.add_subchunk(fog_chunk)
if layer.use_pass_mist:
object_info.add_subchunk(use_fog_flag)
# Add LAYER FOG
foglayer = next((lk.from_socket.node for lk in ntree if lk.from_socket.node.type == 'VOLUME_SCATTER' and lk.to_socket.node.type in bgshade), False)
if foglayer:
layerfog_flag = 0
if world.mist_settings.falloff == 'QUADRATIC':
layerfog_flag |= 0x1
if world.mist_settings.falloff == 'INVERSE_QUADRATIC':
layerfog_flag |= 0x2
layerfog_chunk = _3ds_chunk(LAYER_FOG)
layerfog_color_chunk = _3ds_chunk(RGB)
use_fog_flag = _3ds_chunk(USE_LAYER_FOG)
layerfog_color_chunk.add_variable("color", _3ds_float_color(foglayer.inputs[0].default_value[:3]))
layerfog_chunk.add_variable("lowZ", _3ds_float(world.mist_settings.start))
layerfog_chunk.add_variable("highZ", _3ds_float(world.mist_settings.height))
layerfog_chunk.add_variable("density", _3ds_float(foglayer.inputs[1].default_value))
layerfog_chunk.add_variable("flags", _3ds_uint(layerfog_flag))
layerfog_chunk.add_subchunk(layerfog_color_chunk)
object_info.add_subchunk(layerfog_chunk)
if fognode or foglayer and layer.use_pass_mist:
object_info.add_subchunk(use_fog_flag)
if use_keyframes and world.animation_data:
kfdata.add_subchunk(make_ambient_node(world))

View File

@ -45,8 +45,11 @@ VGRADIENT = 0x1300 # The background gradient colors
USE_VGRADIENT = 0x1301 # The background gradient flag
O_CONSTS = 0x1500 # The origin of the 3D cursor
AMBIENTLIGHT = 0x2100 # The color of the ambient light
LAYER_FOG = 0x2302 # The fog atmosphere settings
USE_LAYER_FOG = 0x2303 # The fog atmosphere flag
FOG = 0x2200 # The fog atmosphere settings
USE_FOG = 0x2201 # The fog atmosphere flag
FOG_BGND = 0x2210 # The fog atmosphere background flag
LAYER_FOG = 0x2302 # The fog layer atmosphere settings
USE_LAYER_FOG = 0x2303 # The fog layer atmosphere flag
MATERIAL = 0xAFFF # This stored the texture info
OBJECT = 0x4000 # This stores the faces, vertices, etc...
@ -746,16 +749,95 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
bitmapnode = nodes.new(type='ShaderNodeTexEnvironment')
bitmap_mix.label = "Solid Color"
bitmapnode.label = "Bitmap: " + bitmap_name
bitmap_mix.location = (-250, 360)
bitmapnode.location = (-600, 300)
bitmap_mix.inputs[2].default_value = nodes['Background'].inputs[0].default_value
bitmapnode.image = load_image(bitmap_name, dirname, place_holder=False, recursive=IMAGE_SEARCH, check_existing=True)
bitmap_mix.inputs[0].default_value = 0.0 if bitmapnode.image is not None else 1.0
bitmap_mix.inputs[0].default_value = 0.5 if bitmapnode.image is not None else 1.0
bitmapnode.location = (-600, 360) if bitmapnode.image is not None else (-600, 300)
bitmap_mix.location = (-250, 300)
links.new(bitmap_mix.outputs['Color'], nodes['Background'].inputs[0])
links.new(bitmapnode.outputs['Color'], bitmap_mix.inputs[1])
new_chunk.bytes_read += read_str_len
# If fog chunk
# If gradient chunk:
elif CreateWorld and new_chunk.ID == VGRADIENT:
if contextWorld is None:
path, filename = os.path.split(file.name)
realname, ext = os.path.splitext(filename)
contextWorld = bpy.data.worlds.new("Gradient: " + realname)
context.scene.world = contextWorld
contextWorld.use_nodes = True
links = contextWorld.node_tree.links
nodes = contextWorld.node_tree.nodes
gradientnode = nodes.new(type='ShaderNodeValToRGB')
gradientnode.location = (-600, 100)
gradientnode.label = "Gradient"
backgroundmix = next((wn for wn in worldnodes if wn.type in {'MIX', 'MIX_RGB'}), False)
if backgroundmix:
links.new(gradientnode.outputs['Color'], backgroundmix.inputs[2])
else:
links.new(gradientnode.outputs['Color'], nodes['Background'].inputs[0])
gradientnode.color_ramp.elements.new(read_float(new_chunk))
read_chunk(file, temp_chunk)
if temp_chunk.ID == COLOR_F:
gradientnode.color_ramp.elements[2].color[:3] = read_float_array(temp_chunk)
elif temp_chunk.ID == LIN_COLOR_F:
gradientnode.color_ramp.elements[2].color[:3] = read_float_array(temp_chunk)
else:
skip_to_end(file, temp_chunk)
new_chunk.bytes_read += temp_chunk.bytes_read
read_chunk(file, temp_chunk)
if temp_chunk.ID == COLOR_F:
gradientnode.color_ramp.elements[1].color[:3] = read_float_array(temp_chunk)
elif temp_chunk.ID == LIN_COLOR_F:
gradientnode.color_ramp.elements[1].color[:3] = read_float_array(temp_chunk)
else:
skip_to_end(file, temp_chunk)
new_chunk.bytes_read += temp_chunk.bytes_read
read_chunk(file, temp_chunk)
if temp_chunk.ID == COLOR_F:
gradientnode.color_ramp.elements[0].color[:3] = read_float_array(temp_chunk)
elif temp_chunk.ID == LIN_COLOR_F:
gradientnode.color_ramp.elements[0].color[:3] = read_float_array(temp_chunk)
else:
skip_to_end(file, temp_chunk)
new_chunk.bytes_read += temp_chunk.bytes_read
# If fog chunk:
elif CreateWorld and new_chunk.ID == FOG:
if contextWorld is None:
path, filename = os.path.split(file.name)
realname, ext = os.path.splitext(filename)
newWorld = bpy.data.worlds.new("LayerFog: " + realname)
context.scene.world = contextWorld
contextWorld.use_nodes = True
links = contextWorld.node_tree.links
nodes = contextWorld.node_tree.nodes
fognode = nodes.new(type='ShaderNodeVolumeAbsorption')
fognode.label = "Fog"
fognode.location = (300, 60)
volumemix = next((wn for wn in worldnodes if wn.label == 'Volume' and wn.type in {'ADD_SHADER', 'MIX_SHADER'}), False)
if volumemix:
links.new(fognode.outputs['Volume'], volumemix.inputs[1])
else:
links.new(fognode.outputs[0], nodes['World Output'].inputs[1])
contextWorld.mist_settings.use_mist = True
contextWorld.mist_settings.start = read_float(new_chunk)
nearfog = read_float(new_chunk) * 0.01
contextWorld.mist_settings.depth = read_float(new_chunk)
farfog = read_float(new_chunk) * 0.01
fognode.inputs[1].default_value = (nearfog + farfog) * 0.5
read_chunk(file, temp_chunk)
if temp_chunk.ID == COLOR_F:
fognode.inputs[0].default_value[:3] = read_float_array(temp_chunk)
elif temp_chunk.ID == LIN_COLOR_F:
fognode.inputs[0].default_value[:3] = read_float_array(temp_chunk)
else:
skip_to_end(file, temp_chunk)
new_chunk.bytes_read += temp_chunk.bytes_read
elif CreateWorld and new_chunk.ID == FOG_BGND:
pass
# If layer fog chunk:
elif CreateWorld and new_chunk.ID == LAYER_FOG:
"""Fog options flags are bit 20 (0x100000) for background fogging,
bit 0 (0x1) for bottom falloff, and bit 1 (0x2) for top falloff."""
@ -767,16 +849,23 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
contextWorld.use_nodes = True
links = contextWorld.node_tree.links
nodes = contextWorld.node_tree.nodes
mxvolume = nodes.new(type='ShaderNodeMixShader')
layerfog = nodes.new(type='ShaderNodeVolumeScatter')
layerfog.label = "Layer Fog"
layerfog.location = (300, 100)
links.new(layerfog.outputs['Volume'], nodes['World Output'].inputs['Volume'])
mxvolume.label = "Volume"
layerfog.location = (10, -60)
mxvolume.location = (300, 50)
links.new(layerfog.outputs['Volume'], mxvolume.inputs[2])
links.new(mxvolume.outputs[0], nodes['World Output'].inputs[1])
fognode = next((wn for wn in worldnodes if wn.type == 'VOLUME_ABSORPTION'), False)
if fognode:
links.new(fognode.outputs['Volume'], mxvolume.inputs[1])
fognode.location = (10, 60)
context.view_layer.use_pass_mist = False
contextWorld.mist_settings.use_mist = True
contextWorld.mist_settings.start = read_float(new_chunk)
contextWorld.mist_settings.depth = read_float(new_chunk)
contextWorld.mist_settings.height = contextWorld.mist_settings.depth * 0.5
layerfog.inputs['Density'].default_value = read_float(new_chunk)
contextWorld.mist_settings.height = read_float(new_chunk)
layerfog.inputs[1].default_value = read_float(new_chunk)
layerfog_flag = read_long(new_chunk)
if layerfog_flag == 0:
contextWorld.mist_settings.falloff = 'LINEAR'
@ -1152,11 +1241,12 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
ambinode = nodes.new(type='ShaderNodeEmission')
ambilite = nodes.new(type='ShaderNodeRGB')
ambilite.label = "Ambient Color"
mixshade.label = "Surface"
ambinode.inputs[0].default_value[:3] = child.color
ambinode.location = (10, 150)
worldout.location = (600, 200)
mixshade.location = (300, 300)
ambilite.location = (-250, 150)
ambinode.location = (10, 180)
worldout.location = (600, 180)
mixshade.location = (300, 280)
ambilite.location = (-250, 100)
links.new(mixshade.outputs[0], worldout.inputs['Surface'])
links.new(nodes['Background'].outputs[0], mixshade.inputs[1])
links.new(ambinode.outputs[0], mixshade.inputs[2])